diff --git a/.github/ISSUE_TEMPLATE/ask-question.md b/.github/ISSUE_TEMPLATE/ask-question.md new file mode 100644 index 0000000000..c8313fba9f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ask-question.md @@ -0,0 +1,13 @@ +--- +name: Ask question +about: Ask a question about this project. + +--- + +You should not create an issue but use Gitter instead: https://gitter.im/ImageSharp/General + +You should not create an issue but use Gitter instead: https://gitter.im/ImageSharp/General + +You should not create an issue but use Gitter instead: https://gitter.im/ImageSharp/General + +You should not create an issue but use Gitter instead: https://gitter.im/ImageSharp/General \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/bug-report.md similarity index 93% rename from .github/ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE/bug-report.md index a172605e64..58a31246a9 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -1,3 +1,9 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + ### Prerequisites - [ ] I have written a descriptive issue title diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 0000000000..be1e593be4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,13 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +You should first discuss the feature on Gitter: https://gitter.im/ImageSharp/General + +You should first discuss the feature on Gitter: https://gitter.im/ImageSharp/General + +You should first discuss the feature on Gitter: https://gitter.im/ImageSharp/General + +You should first discuss the feature on Gitter: https://gitter.im/ImageSharp/General \ No newline at end of file diff --git a/ImageSharp.sln b/ImageSharp.sln index 1c41d8db51..40282d8bcd 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -7,13 +7,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionIt .editorconfig = .editorconfig .travis.yml = .travis.yml appveyor.yml = appveyor.yml + .github\ISSUE_TEMPLATE\ask-question.md = .github\ISSUE_TEMPLATE\ask-question.md + .github\ISSUE_TEMPLATE\bug-report.md = .github\ISSUE_TEMPLATE\bug-report.md codecov.yml = codecov.yml CodeCoverage.runsettings = CodeCoverage.runsettings .github\CONTRIBUTING.md = .github\CONTRIBUTING.md + .github\ISSUE_TEMPLATE\feature-request.md = .github\ISSUE_TEMPLATE\feature-request.md features.md = features.md ImageSharp.ruleset = ImageSharp.ruleset ImageSharp.sln.DotSettings = ImageSharp.sln.DotSettings - .github\ISSUE_TEMPLATE.md = .github\ISSUE_TEMPLATE.md NuGet.config = NuGet.config .github\PULL_REQUEST_TEMPLATE.md = .github\PULL_REQUEST_TEMPLATE.md README.md = README.md diff --git a/README.md b/README.md index a420c07c8e..a72074f8f3 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Designed to democratize image processing, ImageSharp brings you an incredibly po Compared to `System.Drawing` we have been able to develop something much more flexible, easier to code against, and much, much less prone to memory leaks. Gone are system-wide process-locks; ImageSharp images are thread-safe and fully supported in web environments. -Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and embedded/IoT scenarios. +Built against .NET Standard 1.3 ImageSharp can be used in device, cloud, and embedded/IoT scenarios. ### Documentation For all SixLabors projects, including ImageSharp: @@ -70,6 +70,9 @@ Our API is designed to be simple to consume. Here's an example of the code requi On platforms supporting netstandard 1.3+ ```csharp +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; + // Image.Load(string path) is a shortcut for our default type. // Other pixel formats use Image.Load(string path)) using (Image image = Image.Load("foo.jpg")) @@ -80,25 +83,13 @@ using (Image image = Image.Load("foo.jpg")) image.Save("bar.jpg"); // Automatic encoder selected based on extension. } ``` -On netstandard 1.1 - 1.2 - -```csharp -// Image.Load(Stream stream) is a shortcut for our default type. -// Other pixel formats use Image.Load(Stream stream)) -using (FileStream stream = File.OpenRead("foo.jpg")) -using (FileStream output = File.OpenWrite("bar.jpg")) -using (Image image = Image.Load(stream)) -{ - image.Mutate(x => x - .Resize(image.Width / 2, image.Height / 2) - .Grayscale()); - image.Save(output); -} -``` Setting individual pixel values can be performed as follows: ```csharp +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + // Individual pixels using (Image image = new Image(400, 400)) { @@ -124,7 +115,7 @@ If you prefer, you can compile ImageSharp yourself (please do and help!) Alternatively, you can work from command line and/or with a lightweight editor on **both Linux/Unix and Windows**: - [Visual Studio Code](https://code.visualstudio.com/) with [C# Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp) -- [.Net Core](https://www.microsoft.com/net/core#linuxubuntu) +- [.NET Core](https://www.microsoft.com/net/core#linuxubuntu) To clone ImageSharp locally click the "Clone in Windows" button above or run the following git commands. diff --git a/appveyor.yml b/appveyor.yml index fccac0c44d..821fd427c8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ version: 1.0.0.{build} -image: Previous Visual Studio 2017 +image: Visual Studio 2017 # prevent the double build when a branch has an active PR skip_branch_with_pr: true diff --git a/build.ps1 b/build.ps1 index 35b8344dcc..215b551170 100644 --- a/build.ps1 +++ b/build.ps1 @@ -94,7 +94,7 @@ if("$env:APPVEYOR_API_URL" -ne ""){ } Write-Host "Building version '${version}'" -dotnet restore /p:packageversion=$version +dotnet restore /p:packageversion=$version /p:DisableImplicitNuGetFallbackFolder=true Write-Host "Building projects" dotnet build -c Release /p:packageversion=$version diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj index 42ef080e53..1cb3f444f0 100644 --- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj +++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj @@ -5,8 +5,8 @@ $(packageversion) 0.0.1 SixLabors and contributors - netstandard1.1;netstandard2.0 - 7.2 + netstandard1.3;netstandard2.0 + 7.3 true true SixLabors.ImageSharp.Drawing diff --git a/src/ImageSharp.Drawing/Processing/DrawImageExtensions.cs b/src/ImageSharp.Drawing/Processing/DrawImageExtensions.cs index 8ccbe22acb..a8ee4d90bc 100644 --- a/src/ImageSharp.Drawing/Processing/DrawImageExtensions.cs +++ b/src/ImageSharp.Drawing/Processing/DrawImageExtensions.cs @@ -1,121 +1,137 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Drawing; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Adds extensions that allow the drawing of images to the type. - /// - public static class DrawImageExtensions - { - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The pixel format. - /// 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) - where TPixel : struct, IPixel - => source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, GraphicsOptions.Default.ColorBlendingMode, GraphicsOptions.Default.AlphaCompositionMode, opacity)); - - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The pixel format. - /// 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) - where TPixel : struct, IPixel - => source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, colorBlending, GraphicsOptions.Default.AlphaCompositionMode, opacity)); +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The pixel format. - /// The image this method extends. - /// The image to blend with the currently processing image. +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Drawing; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Adds extensions that allow the drawing of images to the type. + /// + public static class DrawImageExtensions + { + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The pixel format of the destination image. + /// The pixel format of the source image. + /// 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) + where TPixelDst : struct, IPixel + where TPixelSrc : struct, IPixel + => source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, GraphicsOptions.Default.ColorBlendingMode, GraphicsOptions.Default.AlphaCompositionMode, opacity)); + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The pixel format of the destination image. + /// The pixel format of the source image. + /// 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) + where TPixelDst : struct, IPixel + where TPixelSrc : struct, IPixel + => source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, colorBlending, GraphicsOptions.Default.AlphaCompositionMode, opacity)); + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The pixel format of the destination image. + /// The pixel format of the source image. + /// 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) - where TPixel : struct, IPixel - => source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, colorBlending, alphaComposition, opacity)); + /// 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) + where TPixelDst : struct, IPixel + where TPixelSrc : struct, IPixel + => source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, colorBlending, alphaComposition, opacity)); - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The pixel format. + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The pixel format of the destination image. + /// The pixel format of the source image. /// The image this method extends. - /// The image to blend with the currently processing image. + /// 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) - where TPixel : struct, IPixel - => source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage)); + /// The . + public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, GraphicsOptions options) + where TPixelDst : struct, IPixel + where TPixelSrc : struct, IPixel + => 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. + /// + /// Draws the given image together with the current one by blending their pixels. /// - /// The pixel format. - /// The image this method extends. + /// The pixel format of the destination image. + /// The pixel format of the source image. + /// The image this method extends. /// The image to blend with the currently processing image. - /// The location to draw the blended 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) - where TPixel : struct, IPixel - => source.ApplyProcessor(new DrawImageProcessor(image, location, GraphicsOptions.Default.ColorBlendingMode, GraphicsOptions.Default.AlphaCompositionMode, opacity)); + /// The . + public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, Point location, float opacity) + where TPixelDst : struct, IPixel + where TPixelSrc : struct, IPixel + => source.ApplyProcessor(new DrawImageProcessor(image, location, GraphicsOptions.Default.ColorBlendingMode, GraphicsOptions.Default.AlphaCompositionMode, opacity)); - /// - /// Draws the given image together with the current one by blending their pixels. + /// + /// Draws the given image together with the current one by blending their pixels. /// - /// The pixel format. - /// The image this method extends. + /// The pixel format of the destination image. + /// The pixel format of the source image. + /// 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 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) - where TPixel : struct, IPixel - => source.ApplyProcessor(new DrawImageProcessor(image, location, colorBlending, GraphicsOptions.Default.AlphaCompositionMode, opacity)); + /// The . + public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, Point location, PixelColorBlendingMode colorBlending, float opacity) + where TPixelDst : struct, IPixel + where TPixelSrc : struct, IPixel + => source.ApplyProcessor(new DrawImageProcessor(image, location, colorBlending, GraphicsOptions.Default.AlphaCompositionMode, opacity)); - /// - /// Draws the given image together with the current one by blending their pixels. + /// + /// Draws the given image together with the current one by blending their pixels. /// - /// The pixel format. - /// The image this method extends. + /// The pixel format of the destination image. + /// The pixel format of the source image. + /// The image this method extends. /// The image to blend with the currently processing image. - /// The location to draw the blended image. + /// The location to draw the blended image. /// The color blending to apply. - /// The alpha composition 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, Point location, PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, float opacity) - where TPixel : struct, IPixel - => source.ApplyProcessor(new DrawImageProcessor(image, location, colorBlending, alphaComposition, opacity)); + /// The . + public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, Point location, PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, float opacity) + where TPixelDst : struct, IPixel + where TPixelSrc : struct, IPixel + => source.ApplyProcessor(new DrawImageProcessor(image, location, colorBlending, alphaComposition, opacity)); - /// - /// Draws the given image together with the current one by blending their pixels. + /// + /// Draws the given image together with the current one by blending their pixels. /// - /// The pixel format. + /// The pixel format of the destination image. + /// The pixel format of the source image. /// 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) - where TPixel : struct, IPixel - => source.ApplyProcessor(new DrawImageProcessor(image, location, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage)); - } + /// The options containing the blend mode and opacity. + /// The . + public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, Point location, GraphicsOptions options) + where TPixelDst : struct, IPixel + where TPixelSrc : struct, IPixel + => source.ApplyProcessor(new DrawImageProcessor(image, location, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage)); + } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs index 324d25e097..dc73420f30 100644 --- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs +++ b/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs @@ -1,98 +1,101 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Drawing -{ - /// - /// Combines two images together by blending the pixels. - /// - /// The pixel format. - internal class DrawImageProcessor : ImageProcessor - where TPixel : struct, IPixel +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; + +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 TPixelDst : struct, IPixel + where TPixelSrc : struct, IPixel { - /// - /// Initializes a new instance of the class. - /// - /// The image to blend with the currently processing image. + /// + /// Initializes a new instance of the class. + /// + /// The image to blend with the currently processing image. /// 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(Image image, Point location, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, float opacity) - { - Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); - - 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 opacity of the image to blend - /// - public float Opacity { get; } - - /// - /// Gets the pixel blender - /// - public PixelBlender Blender { get; } - - /// - /// Gets the location to draw the blended image - /// - public Point Location { get; } - - /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - Image targetImage = this.Image; - PixelBlender blender = this.Blender; - int locationY = this.Location.Y; - - // 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.Width); - 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 width = maxX - minX; - - MemoryAllocator memoryAllocator = this.Image.GetConfiguration().MemoryAllocator; - - using (IMemoryOwner amount = memoryAllocator.Allocate(width)) - { - amount.GetSpan().Fill(this.Opacity); - - ParallelFor.WithConfiguration( - minY, - maxY, - configuration, - y => - { - Span background = source.GetPixelRowSpan(y).Slice(minX, width); - Span foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width); - blender.Blend(memoryAllocator, background, background, foreground, amount.GetSpan()); - }); - } - } - } + /// The opacity of the image to blend. Must be between 0 and 1. + public DrawImageProcessor(Image image, Point location, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, float opacity) + { + Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); + + 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 opacity of the image to blend + /// + public float Opacity { get; } + + /// + /// Gets the pixel blender + /// + public PixelBlender Blender { get; } + + /// + /// Gets the location to draw the blended image + /// + public Point Location { get; } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + Image targetImage = this.Image; + PixelBlender blender = this.Blender; + int locationY = this.Location.Y; + + // 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.Width); + 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 width = maxX - minX; + + MemoryAllocator memoryAllocator = this.Image.GetConfiguration().MemoryAllocator; + + var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + + ParallelHelper.IterateRows( + workingRect, + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span background = source.GetPixelRowSpan(y).Slice(minX, width); + Span foreground = + targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width); + blender.Blend(memoryAllocator, background, background, foreground, this.Opacity); + } + }); + } + } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs index 3285e75a7b..ed6c869511 100644 --- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs +++ b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs @@ -1,107 +1,116 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors.Drawing -{ - /// - /// Using the brush as a source of pixels colors blends the brush color with source. - /// - /// The pixel format. - internal class FillProcessor : ImageProcessor - where TPixel : struct, IPixel - { - /// - /// The brush. - /// - private readonly IBrush brush; - private readonly GraphicsOptions options; - - /// - /// Initializes a new instance of the class. - /// - /// The brush to source pixel colors from. - /// The options - public FillProcessor(IBrush brush, GraphicsOptions options) - { - this.brush = brush; - this.options = options; - } - - /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - - // Align start/end positions. - int minX = Math.Max(0, startX); - int maxX = Math.Min(source.Width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(source.Height, endY); - - int width = maxX - minX; - - // If there's no reason for blending, then avoid it. - if (this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush)) - { - ParallelFor.WithConfiguration( - minY, - maxY, - configuration, - y => - { - source.GetPixelRowSpan(y).Slice(minX, width).Fill(solidBrush.Color); - }); - } - else - { - // Reset offset if necessary. - if (minX > 0) - { - startX = 0; - } - - if (minY > 0) - { - startY = 0; - } - - using (IMemoryOwner amount = source.MemoryAllocator.Allocate(width)) - using (BrushApplicator applicator = this.brush.CreateApplicator( - source, - sourceRectangle, - this.options)) - { - amount.GetSpan().Fill(1f); - - ParallelFor.WithConfiguration( - minY, - maxY, - configuration, - y => - { - int offsetY = y - startY; - int offsetX = minX - startX; - - applicator.Apply(amount.GetSpan(), offsetX, offsetY); - }); - } - } - } - - private bool IsSolidBrushWithoutBlending(out SolidBrush solidBrush) - { +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors.Drawing +{ + /// + /// Using the brush as a source of pixels colors blends the brush color with source. + /// + /// The pixel format. + internal class FillProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// The brush. + /// + private readonly IBrush brush; + private readonly GraphicsOptions options; + + /// + /// Initializes a new instance of the class. + /// + /// The brush to source pixel colors from. + /// The options + public FillProcessor(IBrush brush, GraphicsOptions options) + { + this.brush = brush; + this.options = options; + } + + /// + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + int width = maxX - minX; + + var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + + // If there's no reason for blending, then avoid it. + if (this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush)) + { + ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings().MultiplyMinimumPixelsPerTask(4); + + ParallelHelper.IterateRows( + workingRect, + parallelSettings, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + source.GetPixelRowSpan(y).Slice(minX, width).Fill(solidBrush.Color); + } + }); + } + else + { + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + using (IMemoryOwner amount = source.MemoryAllocator.Allocate(width)) + using (BrushApplicator applicator = this.brush.CreateApplicator( + source, + sourceRectangle, + this.options)) + { + amount.GetSpan().Fill(1f); + + ParallelHelper.IterateRows( + workingRect, + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + int offsetY = y - startY; + int offsetX = minX - startX; + + applicator.Apply(amount.GetSpan(), offsetX, offsetY); + } + }); + } + } + } + + private bool IsSolidBrushWithoutBlending(out SolidBrush solidBrush) + { solidBrush = this.brush as SolidBrush; if (solidBrush == null) @@ -109,7 +118,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing return false; } - return this.options.IsOpaqueColorWithoutBlending(solidBrush.Color); - } - } + return this.options.IsOpaqueColorWithoutBlending(solidBrush.Color); + } + } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/CieLab.cs b/src/ImageSharp/ColorSpaces/CieLab.cs index 82975d9330..ea6df86e27 100644 --- a/src/ImageSharp/ColorSpaces/CieLab.cs +++ b/src/ImageSharp/ColorSpaces/CieLab.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; @@ -12,7 +11,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// Represents a CIE L*a*b* 1976 color. /// /// - internal readonly struct CieLab : IColorVector, IEquatable, IAlmostEquatable + public readonly struct CieLab : IEquatable { /// /// D50 standard illuminant. @@ -21,9 +20,27 @@ namespace SixLabors.ImageSharp.ColorSpaces public static readonly CieXyz DefaultWhitePoint = Illuminants.D50; /// - /// The backing vector for SIMD support. + /// Gets the lightness dimension. + /// A value usually ranging between 0 (black), 100 (diffuse white) or higher (specular white). + /// + public readonly float L; + + /// + /// Gets the a color component. + /// A value usually ranging from -100 to 100. Negative is green, positive magenta. + /// + public readonly float A; + + /// + /// Gets the b color component. + /// A value usually ranging from -100 to 100. Negative is blue, positive is yellow + /// + public readonly float B; + + /// + /// Gets the reference white point of this color /// - private readonly Vector3 backingVector; + public readonly CieXyz WhitePoint; /// /// Initializes a new instance of the struct. @@ -32,9 +49,9 @@ namespace SixLabors.ImageSharp.ColorSpaces /// The a (green - magenta) component. /// The b (blue - yellow) component. /// Uses as white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public CieLab(float l, float a, float b) - : this(new Vector3(l, a, b), DefaultWhitePoint) + : this(l, a, b, DefaultWhitePoint) { } @@ -45,7 +62,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// The a (green - magenta) component. /// The b (blue - yellow) component. /// The reference white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public CieLab(float l, float a, float b, CieXyz whitePoint) : this(new Vector3(l, a, b), whitePoint) { @@ -56,7 +73,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// The vector representing the l, a, b components. /// Uses as white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public CieLab(Vector3 vector) : this(vector, DefaultWhitePoint) { @@ -67,126 +84,62 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// The vector representing the l, a, b components. /// The reference white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public CieLab(Vector3 vector, CieXyz whitePoint) : this() { - this.backingVector = vector; + // 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 reference white point of this color - /// - public CieXyz WhitePoint { get; } - - /// - /// Gets the lightness dimension. - /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). - /// - public float L - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.X; - } - - /// - /// Gets the a color component. - /// A value ranging from -100 to 100. Negative is green, positive magenta. - /// - public float A - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Y; - } - - /// - /// Gets the b color component. - /// A value ranging from -100 to 100. Negative is blue, positive is yellow - /// - public float B - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Z; - } - - /// - public Vector3 Vector => this.backingVector; - /// /// Compares two objects for equality. /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// + /// 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 ==(CieLab left, CieLab right) - { - return left.Equals(right); - } + [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. - /// + /// 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 !=(CieLab left, CieLab right) - { - return !left.Equals(right); - } + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(CieLab left, CieLab right) => !left.Equals(right); /// public override int GetHashCode() { - return HashHelpers.Combine(this.WhitePoint.GetHashCode(), this.backingVector.GetHashCode()); + int hash = this.L.GetHashCode(); + hash = HashHelpers.Combine(hash, this.A.GetHashCode()); + hash = HashHelpers.Combine(hash, this.B.GetHashCode()); + return HashHelpers.Combine(hash, this.WhitePoint.GetHashCode()); } /// - public override string ToString() - { - return this.Equals(default) - ? "CieLab [Empty]" - : $"CieLab [ L={this.L:#0.##}, A={this.A:#0.##}, B={this.B:#0.##}]"; - } + public override string ToString() => FormattableString.Invariant($"CieLab({this.L:#0.##}, {this.A:#0.##}, {this.B:#0.##})"); /// - public override bool Equals(object obj) - { - return obj is CieLab other && this.Equals(other); - } + public override bool Equals(object obj) => obj is CieLab other && this.Equals(other); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public bool Equals(CieLab other) { - return this.backingVector.Equals(other.backingVector) + return this.L.Equals(other.L) + && this.A.Equals(other.A) + && this.B.Equals(other.B) && this.WhitePoint.Equals(other.WhitePoint); } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool AlmostEquals(CieLab other, float precision) - { - var result = Vector3.Abs(this.backingVector - other.backingVector); - - return this.WhitePoint.Equals(other.WhitePoint) - && result.X <= precision - && result.Y <= precision - && result.Z <= precision; - } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/CieLch.cs b/src/ImageSharp/ColorSpaces/CieLch.cs index 67a9956bdc..f1a7425e9e 100644 --- a/src/ImageSharp/ColorSpaces/CieLch.cs +++ b/src/ImageSharp/ColorSpaces/CieLch.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; @@ -12,8 +11,11 @@ namespace SixLabors.ImageSharp.ColorSpaces /// Represents the CIE L*C*h°, cylindrical form of the CIE L*a*b* 1976 color. /// /// - internal readonly struct CieLch : IColorVector, IEquatable, IAlmostEquatable + public readonly struct CieLch : IEquatable { + private static readonly Vector3 Min = new Vector3(0, -200, 0); + private static readonly Vector3 Max = new Vector3(100, 200, 360); + /// /// D50 standard illuminant. /// Used when reference white is not specified explicitly. @@ -21,9 +23,27 @@ namespace SixLabors.ImageSharp.ColorSpaces public static readonly CieXyz DefaultWhitePoint = Illuminants.D50; /// - /// The backing vector for SIMD support. + /// Gets the lightness dimension. + /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). + /// + public readonly float L; + + /// + /// Gets the a chroma component. + /// A value ranging from 0 to 200. + /// + public readonly float C; + + /// + /// Gets the h° hue component in degrees. + /// A value ranging from 0 to 360. /// - private readonly Vector3 backingVector; + public readonly float H; + + /// + /// Gets the reference white point of this color + /// + public readonly CieXyz WhitePoint; /// /// Initializes a new instance of the struct. @@ -32,9 +52,9 @@ namespace SixLabors.ImageSharp.ColorSpaces /// The chroma, relative saturation. /// The hue in degrees. /// Uses as white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public CieLch(float l, float c, float h) - : this(new Vector3(l, c, h), DefaultWhitePoint) + : this(l, c, h, DefaultWhitePoint) { } @@ -45,7 +65,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// The chroma, relative saturation. /// The hue in degrees. /// The reference white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public CieLch(float l, float c, float h, CieXyz whitePoint) : this(new Vector3(l, c, h), whitePoint) { @@ -56,7 +76,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// The vector representing the l, c, h components. /// Uses as white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public CieLch(Vector3 vector) : this(vector, DefaultWhitePoint) { @@ -67,129 +87,64 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// The vector representing the l, c, h components. /// The reference white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public CieLch(Vector3 vector, CieXyz whitePoint) - : this() { - this.backingVector = vector; + vector = Vector3.Clamp(vector, Min, Max); + this.L = vector.X; + this.C = vector.Y; + this.H = vector.Z; this.WhitePoint = whitePoint; } - /// - /// Gets the reference white point of this color - /// - public CieXyz WhitePoint { get; } - - /// - /// Gets the lightness dimension. - /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). - /// - public float L - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.X; - } - - /// - /// Gets the a chroma component. - /// A value ranging from 0 to 100. - /// - public float C - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Y; - } - - /// - /// Gets the h° hue component in degrees. - /// A value ranging from 0 to 360. - /// - public float H - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Z; - } - - /// - public Vector3 Vector => this.backingVector; - /// /// Compares two objects for equality. /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// + /// 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 ==(CieLch left, CieLch right) - { - return left.Equals(right); - } + [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. - /// + /// 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 !=(CieLch left, CieLch right) - { - return !left.Equals(right); - } + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(CieLch left, CieLch right) => !left.Equals(right); /// public override int GetHashCode() { - return HashHelpers.Combine(this.WhitePoint.GetHashCode(), this.backingVector.GetHashCode()); + int hash = this.L.GetHashCode(); + hash = HashHelpers.Combine(hash, this.C.GetHashCode()); + hash = HashHelpers.Combine(hash, this.H.GetHashCode()); + return HashHelpers.Combine(hash, this.WhitePoint.GetHashCode()); } /// - public override string ToString() - { - return this.Equals(default) - ? "CieLch [Empty]" - : $"CieLch [ L={this.L:#0.##}, C={this.C:#0.##}, H={this.H:#0.##}]"; - } + public override string ToString() => FormattableString.Invariant($"CieLch({this.L:#0.##}, {this.C:#0.##}, {this.H:#0.##})"); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool Equals(object obj) - { - return obj is CieLch other && this.Equals(other); - } + [MethodImpl(InliningOptions.ShortMethod)] + public override bool Equals(object obj) => obj is CieLch other && this.Equals(other); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public bool Equals(CieLch other) { - return this.backingVector.Equals(other.backingVector) + return this.L.Equals(other.L) + && this.C.Equals(other.C) + && this.H.Equals(other.H) && this.WhitePoint.Equals(other.WhitePoint); } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool AlmostEquals(CieLch other, float precision) - { - var result = Vector3.Abs(this.backingVector - other.backingVector); - - return this.WhitePoint.Equals(other.WhitePoint) - && result.X <= precision - && result.Y <= precision - && result.Z <= precision; - } - /// /// Computes the saturation of the color (chroma normalized by lightness) /// @@ -197,7 +152,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// A value ranging from 0 to 100. /// /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public float Saturation() { float result = 100 * (this.C / this.L); diff --git a/src/ImageSharp/ColorSpaces/CieLchuv.cs b/src/ImageSharp/ColorSpaces/CieLchuv.cs index 0b4c7a9036..256b5dc0fd 100644 --- a/src/ImageSharp/ColorSpaces/CieLchuv.cs +++ b/src/ImageSharp/ColorSpaces/CieLchuv.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; @@ -10,10 +9,13 @@ namespace SixLabors.ImageSharp.ColorSpaces { /// /// Represents the CIE L*C*h°, cylindrical form of the CIE L*u*v* 1976 color. - /// + /// /// - internal readonly struct CieLchuv : IColorVector, IEquatable, IAlmostEquatable + public readonly struct CieLchuv : IEquatable { + private static readonly Vector3 Min = new Vector3(0, -200, 0); + private static readonly Vector3 Max = new Vector3(100, 200, 360); + /// /// D50 standard illuminant. /// Used when reference white is not specified explicitly. @@ -21,9 +23,27 @@ namespace SixLabors.ImageSharp.ColorSpaces public static readonly CieXyz DefaultWhitePoint = Illuminants.D65; /// - /// The backing vector for SIMD support. + /// Gets the lightness dimension. + /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). + /// + public readonly float L; + + /// + /// Gets the a chroma component. + /// A value ranging from 0 to 200. + /// + public readonly float C; + + /// + /// Gets the h° hue component in degrees. + /// A value ranging from 0 to 360. /// - private readonly Vector3 backingVector; + public readonly float H; + + /// + /// Gets the reference white point of this color + /// + public readonly CieXyz WhitePoint; /// /// Initializes a new instance of the struct. @@ -32,9 +52,9 @@ namespace SixLabors.ImageSharp.ColorSpaces /// The chroma, relative saturation. /// The hue in degrees. /// Uses as white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public CieLchuv(float l, float c, float h) - : this(new Vector3(l, c, h), DefaultWhitePoint) + : this(l, c, h, DefaultWhitePoint) { } @@ -45,9 +65,9 @@ namespace SixLabors.ImageSharp.ColorSpaces /// The chroma, relative saturation. /// The hue in degrees. /// The reference white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public CieLchuv(float l, float c, float h, CieXyz whitePoint) - : this(new Vector3(l, c, h), whitePoint) + : this(new Vector3(l, c, h), whitePoint) { } @@ -56,7 +76,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// The vector representing the l, c, h components. /// Uses as white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public CieLchuv(Vector3 vector) : this(vector, DefaultWhitePoint) { @@ -67,127 +87,62 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// The vector representing the l, c, h components. /// The reference white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public CieLchuv(Vector3 vector, CieXyz whitePoint) : this() { - this.backingVector = vector; + vector = Vector3.Clamp(vector, Min, Max); + this.L = vector.X; + this.C = vector.Y; + this.H = vector.Z; this.WhitePoint = whitePoint; } - /// - /// Gets the reference white point of this color - /// - public CieXyz WhitePoint { get; } - - /// - /// Gets the lightness dimension. - /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). - /// - public float L - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.X; - } - - /// - /// Gets the a chroma component. - /// A value ranging from 0 to 100. - /// - public float C - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Y; - } - - /// - /// Gets the h° hue component in degrees. - /// A value ranging from 0 to 360. - /// - public float H - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Z; - } - - /// - public Vector3 Vector => this.backingVector; - /// /// Compares two objects for equality. /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// + /// 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) - { - return left.Equals(right); - } + 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. - /// + /// 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) - { - return !left.Equals(right); - } + public static bool operator !=(CieLchuv left, CieLchuv right) => !left.Equals(right); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { - return HashHelpers.Combine(this.WhitePoint.GetHashCode(), this.backingVector.GetHashCode()); + int hash = this.L.GetHashCode(); + hash = HashHelpers.Combine(hash, this.C.GetHashCode()); + hash = HashHelpers.Combine(hash, this.H.GetHashCode()); + return HashHelpers.Combine(hash, this.WhitePoint.GetHashCode()); } /// - public override string ToString() - { - return this.Equals(default) - ? "CieLchuv [Empty]" - : $"CieLchuv [ L={this.L:#0.##}, C={this.C:#0.##}, H={this.H:#0.##}"; - } + public override string ToString() => FormattableString.Invariant($"CieLchuv({this.L:#0.##}, {this.C:#0.##}, {this.H:#0.##})"); /// - public override bool Equals(object obj) - { - return obj is CieLchuv other && this.Equals(other); - } + public override bool Equals(object obj) => obj is CieLchuv other && this.Equals(other); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public bool Equals(CieLchuv other) { - return this.backingVector.Equals(other.backingVector) + return this.L.Equals(other.L) + && this.C.Equals(other.C) + && this.H.Equals(other.H) && this.WhitePoint.Equals(other.WhitePoint); } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool AlmostEquals(CieLchuv other, float precision) - { - var result = Vector3.Abs(this.backingVector - other.backingVector); - - return this.WhitePoint.Equals(other.WhitePoint) - && result.X <= precision - && result.Y <= precision - && result.Z <= precision; - } - /// /// Computes the saturation of the color (chroma normalized by lightness) /// @@ -195,7 +150,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// A value ranging from 0 to 100. /// /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public float Saturation() { float result = 100 * (this.C / this.L); diff --git a/src/ImageSharp/ColorSpaces/CieLuv.cs b/src/ImageSharp/ColorSpaces/CieLuv.cs index dbc3b6dee5..8fe073d6bf 100644 --- a/src/ImageSharp/ColorSpaces/CieLuv.cs +++ b/src/ImageSharp/ColorSpaces/CieLuv.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; @@ -14,7 +13,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// attempted perceptual uniformity /// /// - internal readonly struct CieLuv : IColorVector, IEquatable, IAlmostEquatable + public readonly struct CieLuv : IEquatable { /// /// D65 standard illuminant. @@ -23,9 +22,27 @@ namespace SixLabors.ImageSharp.ColorSpaces public static readonly CieXyz DefaultWhitePoint = Illuminants.D65; /// - /// The backing vector for SIMD support. + /// Gets the lightness dimension + /// A value usually ranging between 0 and 100. + /// + public readonly float L; + + /// + /// Gets the blue-yellow chromaticity coordinate of the given whitepoint. + /// A value usually ranging between -100 and 100. + /// + public readonly float U; + + /// + /// Gets the red-green chromaticity coordinate of the given whitepoint. + /// A value usually ranging between -100 and 100. + /// + public readonly float V; + + /// + /// Gets the reference white point of this color /// - private readonly Vector3 backingVector; + public readonly CieXyz WhitePoint; /// /// Initializes a new instance of the struct. @@ -34,9 +51,9 @@ namespace SixLabors.ImageSharp.ColorSpaces /// The blue-yellow chromaticity coordinate of the given whitepoint. /// The red-green chromaticity coordinate of the given whitepoint. /// Uses as white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public CieLuv(float l, float u, float v) - : this(new Vector3(l, u, v), DefaultWhitePoint) + : this(l, u, v, DefaultWhitePoint) { } @@ -47,7 +64,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// The blue-yellow chromaticity coordinate of the given whitepoint. /// The red-green chromaticity coordinate of the given whitepoint. /// The reference white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public CieLuv(float l, float u, float v, CieXyz whitePoint) : this(new Vector3(l, u, v), whitePoint) { @@ -58,7 +75,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// The vector representing the l, u, v components. /// Uses as white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public CieLuv(Vector3 vector) : this(vector, DefaultWhitePoint) { @@ -69,126 +86,61 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// The vector representing the l, u, v components. /// The reference white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public CieLuv(Vector3 vector, CieXyz whitePoint) - : this() { - this.backingVector = vector; + // 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 reference white point of this color - /// - public CieXyz WhitePoint { get; } - - /// - /// Gets the lightness dimension - /// A value usually ranging between 0 and 100. - /// - public float L - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.X; - } - - /// - /// Gets the blue-yellow chromaticity coordinate of the given whitepoint. - /// A value usually ranging between -100 and 100. - /// - public float U - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Y; - } - - /// - /// Gets the red-green chromaticity coordinate of the given whitepoint. - /// A value usually ranging between -100 and 100. - /// - public float V - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Z; - } - - /// - public Vector3 Vector => this.backingVector; - /// /// Compares two objects for equality. /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// + /// 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 ==(CieLuv left, CieLuv right) - { - return left.Equals(right); - } + [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. - /// + /// 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 !=(CieLuv left, CieLuv right) - { - return !left.Equals(right); - } + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(CieLuv left, CieLuv right) => !left.Equals(right); /// public override int GetHashCode() { - return HashHelpers.Combine(this.WhitePoint.GetHashCode(), this.backingVector.GetHashCode()); + int hash = this.L.GetHashCode(); + hash = HashHelpers.Combine(hash, this.U.GetHashCode()); + hash = HashHelpers.Combine(hash, this.V.GetHashCode()); + return HashHelpers.Combine(hash, this.WhitePoint.GetHashCode()); } /// - public override string ToString() - { - return this.Equals(default) - ? "CieLuv [ Empty ]" - : $"CieLuv [ L={this.L:#0.##}, U={this.U:#0.##}, V={this.V:#0.##} ]"; - } + public override string ToString() => FormattableString.Invariant($"CieLuv({this.L:#0.##}, {this.U:#0.##}, {this.V:#0.##})"); /// - public override bool Equals(object obj) - { - return obj is CieLuv other && this.Equals(other); - } + public override bool Equals(object obj) => obj is CieLuv other && this.Equals(other); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public bool Equals(CieLuv other) { - return this.backingVector.Equals(other.backingVector) - && this.WhitePoint.Equals(other.WhitePoint); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool AlmostEquals(CieLuv other, float precision) - { - var result = Vector3.Abs(this.backingVector - other.backingVector); - - return this.WhitePoint.Equals(other.WhitePoint) - && result.X <= precision - && result.Y <= precision - && result.Z <= precision; + return this.L.Equals(other.L) + && this.U.Equals(other.U) + && this.V.Equals(other.V) + && this.WhitePoint.Equals(other.WhitePoint); } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs b/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs index 4f4f951472..f625bb7616 100644 --- a/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs +++ b/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs @@ -2,8 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.ComponentModel; -using System.Numerics; using System.Runtime.CompilerServices; // ReSharper disable CompareOfFloatsByEqualityOperator @@ -12,45 +10,15 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// Represents the coordinates of CIEXY chromaticity space /// - internal readonly struct CieXyChromaticityCoordinates : IEquatable, IAlmostEquatable + public readonly struct CieXyChromaticityCoordinates : IEquatable { - /// - /// The backing vector for SIMD support. - /// - private readonly Vector2 backingVector; - - /// - /// Initializes a new instance of the struct. - /// - /// Chromaticity coordinate x (usually from 0 to 1) - /// Chromaticity coordinate y (usually from 0 to 1) - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CieXyChromaticityCoordinates(float x, float y) - : this(new Vector2(x, y)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector containing the XY Chromaticity coordinates - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CieXyChromaticityCoordinates(Vector2 vector) - { - this.backingVector = vector; - } - /// /// Gets the chromaticity X-coordinate. /// /// /// Ranges usually from 0 to 1. /// - public float X - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.X; - } + public readonly float X; /// /// Gets the chromaticity Y-coordinate @@ -58,85 +26,54 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// Ranges usually from 0 to 1. /// - public float Y + public readonly 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) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Y; + this.X = x; + this.Y = y; } /// /// Compares two objects for equality. /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// + /// 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 ==(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right) - { - return left.Equals(right); - } + [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. - /// + /// 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 !=(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right) - { - return !left.Equals(right); - } + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(CieXyChromaticityCoordinates left, CieXyChromaticityCoordinates right) => !left.Equals(right); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() - { - return this.backingVector.GetHashCode(); - } + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() => HashHelpers.Combine(this.X.GetHashCode(), this.Y.GetHashCode()); /// - public override string ToString() - { - return this.Equals(default) - ? "CieXyChromaticityCoordinates [Empty]" - : $"CieXyChromaticityCoordinates [ X={this.X:#0.##}, Y={this.Y:#0.##}]"; - } + public override string ToString() => FormattableString.Invariant($"CieXyChromaticityCoordinates({this.X:#0.##}, {this.Y:#0.##})"); /// - public override bool Equals(object obj) - { - return obj is CieXyChromaticityCoordinates other && this.Equals(other); - } + public override bool Equals(object obj) => obj is CieXyChromaticityCoordinates other && this.Equals(other); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(CieXyChromaticityCoordinates other) - { - // The memberwise comparison here is a workaround for https://github.com/dotnet/coreclr/issues/16443 - return this.X == other.X && this.Y == other.Y; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool AlmostEquals(CieXyChromaticityCoordinates other, float precision) - { - var result = Vector2.Abs(this.backingVector - other.backingVector); - - return result.X <= precision - && result.Y <= precision; - } + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(CieXyChromaticityCoordinates other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/CieXyy.cs b/src/ImageSharp/ColorSpaces/CieXyy.cs index ac1a4532c5..7137360e94 100644 --- a/src/ImageSharp/ColorSpaces/CieXyy.cs +++ b/src/ImageSharp/ColorSpaces/CieXyy.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; @@ -12,12 +11,25 @@ namespace SixLabors.ImageSharp.ColorSpaces /// Represents an CIE xyY 1931 color /// /// - internal readonly struct CieXyy : IColorVector, IEquatable, IAlmostEquatable + public readonly struct CieXyy : IEquatable { /// - /// The backing vector for SIMD support. + /// Gets the X chrominance component. + /// A value usually ranging between 0 and 1. + /// + public readonly float X; + + /// + /// Gets the Y chrominance component. + /// A value usually ranging between 0 and 1. + /// + public readonly float Y; + + /// + /// Gets the Y luminance component. + /// A value usually ranging between 0 and 1. /// - private readonly Vector3 backingVector; + public readonly float Yl; /// /// Initializes a new instance of the struct. @@ -25,130 +37,72 @@ namespace SixLabors.ImageSharp.ColorSpaces /// The x chroma component. /// The y chroma component. /// The y luminance component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public CieXyy(float x, float y, float yl) - : this(new Vector3(x, y, 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(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public CieXyy(Vector3 vector) : this() { - // Not clamping as documentation about this space seems to indicate "usual" ranges - this.backingVector = vector; + // 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 float X - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.X; - } - - /// - /// Gets the Y chrominance component. - /// A value usually ranging between 0 and 1. - /// - public float Y - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Y; - } - - /// - /// Gets the Y luminance component. - /// A value usually ranging between 0 and 1. - /// - public float Yl - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Z; - } - - /// - public Vector3 Vector => this.backingVector; - /// /// Compares two objects for equality. /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// + /// 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 ==(CieXyy left, CieXyy right) - { - return left.Equals(right); - } + [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. - /// + /// 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 !=(CieXyy left, CieXyy right) - { - return !left.Equals(right); - } + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(CieXyy left, CieXyy right) => !left.Equals(right); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { - return this.backingVector.GetHashCode(); + int hash = this.X.GetHashCode(); + hash = HashHelpers.Combine(hash, this.Y.GetHashCode()); + return HashHelpers.Combine(hash, this.Yl.GetHashCode()); } /// - public override string ToString() - { - return this.Equals(default) - ? "CieXyy [ Empty ]" - : $"CieXyy [ X={this.X:#0.##}, Y={this.Y:#0.##}, Yl={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) - { - return obj is CieXyy other && this.Equals(other); - } + public override bool Equals(object obj) => obj is CieXyy other && this.Equals(other); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public bool Equals(CieXyy other) { - return this.backingVector.Equals(other.backingVector); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool AlmostEquals(CieXyy other, float precision) - { - var result = Vector3.Abs(this.backingVector - other.backingVector); - - return result.X <= precision - && result.Y <= precision - && result.Z <= precision; + return this.X.Equals(other.X) + && this.Y.Equals(other.Y) + && this.Yl.Equals(other.Yl); } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/CieXyz.cs b/src/ImageSharp/ColorSpaces/CieXyz.cs index fa4261b46d..c0ed356601 100644 --- a/src/ImageSharp/ColorSpaces/CieXyz.cs +++ b/src/ImageSharp/ColorSpaces/CieXyz.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; @@ -12,12 +11,25 @@ namespace SixLabors.ImageSharp.ColorSpaces /// Represents an CIE XYZ 1931 color /// /// - internal readonly struct CieXyz : IColorVector, IEquatable, IAlmostEquatable + public readonly struct CieXyz : IEquatable { /// - /// The backing vector for SIMD support. + /// 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; + + /// + /// Gets the Y luminance component. + /// A value usually ranging between 0 and 1. /// - private readonly Vector3 backingVector; + public readonly float Y; + + /// + /// 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; /// /// Initializes a new instance of the struct. @@ -25,7 +37,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// 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(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public CieXyz(float x, float y, float z) : this(new Vector3(x, y, z)) { @@ -38,116 +50,62 @@ namespace SixLabors.ImageSharp.ColorSpaces public CieXyz(Vector3 vector) : this() { - // Not clamping as documentation about this space seems to indicate "usual" ranges - this.backingVector = vector; - } - - /// - /// 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 float X - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.X; - } - - /// - /// Gets the Y luminance component. - /// A value usually ranging between 0 and 1. - /// - public float Y - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Y; + // 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 Z component. Quasi-equal to blue stimulation, or the S cone response - /// A value usually ranging between 0 and 1. - /// - public float Z - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Z; - } - - /// - public Vector3 Vector => this.backingVector; - /// /// Compares two objects for equality. /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// + /// 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 ==(CieXyz left, CieXyz right) - { - return left.Equals(right); - } + [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. - /// + /// 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 !=(CieXyz left, CieXyz right) - { - return !left.Equals(right); - } + [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 Vector3(this.X, this.Y, this.Z); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { - return this.backingVector.GetHashCode(); + int hash = this.X.GetHashCode(); + hash = HashHelpers.Combine(hash, this.Y.GetHashCode()); + return HashHelpers.Combine(hash, this.Z.GetHashCode()); } /// - public override string ToString() - { - return this.Equals(default) - ? "CieXyz [ Empty ]" - : $"CieXyz [ X={this.X:#0.##}, Y={this.Y:#0.##}, Z={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) - { - return obj is CieXyz other && this.Equals(other); - } + public override bool Equals(object obj) => obj is CieXyz other && this.Equals(other); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public bool Equals(CieXyz other) { - return this.backingVector.Equals(other.backingVector); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool AlmostEquals(CieXyz other, float precision) - { - var result = Vector3.Abs(this.backingVector - other.backingVector); - - return result.X <= precision - && result.Y <= precision - && result.Z <= precision; + return this.X.Equals(other.X) + && this.Y.Equals(other.Y) + && this.Z.Equals(other.Z); } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Cmyk.cs b/src/ImageSharp/ColorSpaces/Cmyk.cs index 2702d4ba33..634667c0c6 100644 --- a/src/ImageSharp/ColorSpaces/Cmyk.cs +++ b/src/ImageSharp/ColorSpaces/Cmyk.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; @@ -11,151 +10,108 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// Represents an CMYK (cyan, magenta, yellow, keyline) color. /// - internal readonly struct Cmyk : IEquatable, IAlmostEquatable + public readonly struct Cmyk : IEquatable { - /// - /// The backing vector for SIMD support. - /// - private readonly Vector4 backingVector; + private static readonly Vector4 Min = Vector4.Zero; + private static readonly Vector4 Max = Vector4.One; /// - /// Initializes a new instance of the struct. + /// Gets the cyan color component. + /// A value ranging between 0 and 1. /// - /// The cyan component. - /// The magenta component. - /// The yellow component. - /// The keyline black component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Cmyk(float c, float m, float y, float k) - : this(new Vector4(c, m, y, k)) - { - } + public readonly float C; /// - /// Initializes a new instance of the struct. + /// Gets the magenta color component. + /// A value ranging between 0 and 1. /// - /// The vector representing the c, m, y, k components. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Cmyk(Vector4 vector) - : this() - { - this.backingVector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); - } + public readonly float M; /// - /// Gets the cyan color component. + /// Gets the yellow color component. /// A value ranging between 0 and 1. /// - public float C - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.X; - } + public readonly float Y; /// - /// Gets the magenta color component. + /// Gets the keyline black color component. /// A value ranging between 0 and 1. /// - public float M - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Y; - } + public readonly float K; /// - /// Gets the yellow color component. - /// A value ranging between 0 and 1. + /// Initializes a new instance of the struct. /// - public float Y + /// 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)) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Z; } /// - /// Gets the keyline black color component. - /// A value ranging between 0 and 1. + /// Initializes a new instance of the struct. /// - public float K + /// The vector representing the c, m, y, k components. + [MethodImpl(InliningOptions.ShortMethod)] + public Cmyk(Vector4 vector) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.W; + vector = Vector4.Clamp(vector, Min, Max); + this.C = vector.X; + this.M = vector.Y; + this.Y = vector.Z; + this.K = vector.W; } /// /// Compares two objects for equality. /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// + /// 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 ==(Cmyk left, Cmyk right) - { - return left.Equals(right); - } + [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. - /// + /// 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 !=(Cmyk left, Cmyk right) - { - return !left.Equals(right); - } + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Cmyk left, Cmyk right) => !left.Equals(right); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public override int GetHashCode() { - return this.backingVector.GetHashCode(); + int hash = this.C.GetHashCode(); + hash = HashHelpers.Combine(hash, this.M.GetHashCode()); + hash = HashHelpers.Combine(hash, this.Y.GetHashCode()); + return HashHelpers.Combine(hash, this.K.GetHashCode()); } /// - public override string ToString() - { - return this.Equals(default) - ? "Cmyk [Empty]" - : $"Cmyk [ C={this.C:#0.##}, M={this.M:#0.##}, Y={this.Y:#0.##}, K={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) - { - return obj is Cmyk other && this.Equals(other); - } + public override bool Equals(object obj) => obj is Cmyk other && this.Equals(other); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public bool Equals(Cmyk other) { - return this.backingVector.Equals(other.backingVector); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool AlmostEquals(Cmyk other, float precision) - { - var result = Vector4.Abs(this.backingVector - other.backingVector); - - return result.X <= precision - && result.Y <= precision - && result.Z <= precision - && result.W <= precision; + return this.C.Equals(other.C) + && this.M.Equals(other.M) + && this.Y.Equals(other.Y) + && this.K.Equals(other.K); } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Companding/GammaCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/GammaCompanding.cs new file mode 100644 index 0000000000..13cca1582d --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Companding/GammaCompanding.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Companding +{ + /// + /// Implements gamma companding + /// + /// + /// + /// + /// + 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); + + /// + /// 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); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs new file mode 100644 index 0000000000..9e2cf8ad86 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Companding/LCompanding.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorSpaces.Conversion; + +namespace SixLabors.ImageSharp.ColorSpaces.Companding +{ + /// + /// Implements L* companding + /// + /// + /// 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.08 ? 100 * channel / CieConstants.Kappa : ImageMaths.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 : MathF.Pow(1.16F * channel, 0.3333333F) - 0.16F; + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Companding/Rec2020Companding.cs b/src/ImageSharp/ColorSpaces/Companding/Rec2020Companding.cs new file mode 100644 index 0000000000..a3a9121727 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Companding/Rec2020Companding.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Companding +{ + /// + /// Implements Rec. 2020 companding function (for 12-bits). + /// + /// + /// + /// For 10-bits, companding is identical to + /// + public static class Rec2020Companding + { + /// + /// 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.08145F ? channel / 4.5F : MathF.Pow((channel + 0.0993F) / 1.0993F, 2.222222F); + + /// + /// 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.0181F ? 4500F * channel : (1.0993F * channel) - 0.0993F; + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Companding/Rec709Companding.cs b/src/ImageSharp/ColorSpaces/Companding/Rec709Companding.cs new file mode 100644 index 0000000000..e2e802d08a --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Companding/Rec709Companding.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Companding +{ + /// + /// Implements the Rec. 709 companding function. + /// + /// + /// http://en.wikipedia.org/wiki/Rec._709 + /// + public static class Rec709Companding + { + /// + /// 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, 2.222222F); + + /// + /// 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 ? 4500F * channel : (1.099F * channel) - 0.099F; + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs new file mode 100644 index 0000000000..5ae4629137 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs @@ -0,0 +1,89 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Companding +{ + /// + /// Implements sRGB companding + /// + /// + /// For more info see: + /// + /// + /// + public static class SRgbCompanding + { + /// + /// Expands the companded vectors to their linear equivalents with respect to the energy. + /// + /// The span of vectors. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Expand(Span vectors) + { + ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); + + for (int i = 0; i < vectors.Length; i++) + { + ref Vector4 v = ref Unsafe.Add(ref baseRef, i); + v.X = Expand(v.X); + v.Y = Expand(v.Y); + v.Z = Expand(v.Z); + } + } + + /// + /// Compresses the uncompanded vectors to their nonlinear equivalents with respect to the energy. + /// + /// The span of vectors. + [MethodImpl(InliningOptions.ShortMethod)] + public static void Compress(Span vectors) + { + ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); + + for (int i = 0; i < vectors.Length; i++) + { + ref Vector4 v = ref Unsafe.Add(ref baseRef, i); + v.X = Compress(v.X); + v.Y = Compress(v.Y); + v.Z = Compress(v.Z); + } + } + + /// + /// Expands a companded vector to its linear equivalent with respect to the energy. + /// + /// The vector. + /// The representing the linear channel values. + [MethodImpl(InliningOptions.ShortMethod)] + public static Vector4 Expand(Vector4 vector) => new Vector4(Expand(vector.X), Expand(vector.Y), Expand(vector.Z), vector.W); + + /// + /// Compresses an uncompanded vector (linear) to its nonlinear equivalent. + /// + /// The vector. + /// The representing the nonlinear channel values. + [MethodImpl(InliningOptions.ShortMethod)] + public static Vector4 Compress(Vector4 vector) => new Vector4(Compress(vector.X), Compress(vector.Y), Compress(vector.Z), vector.W); + + /// + /// 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.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(InliningOptions.ShortMethod)] + public static float Compress(float channel) => channel <= 0.0031308F ? 12.92F * channel : (1.055F * MathF.Pow(channel, 0.416666666666667F)) - 0.055F; + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs index e2c1308fa8..892c0d5e38 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Adapt.cs @@ -1,151 +1,130 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Performs chromatic adaptation on the various color spaces. /// - internal partial class ColorSpaceConverter + public partial class ColorSpaceConverter { /// /// Performs chromatic adaptation of given color. - /// Target white point is . + /// Target white point is . /// /// The color to adapt - /// The white point to adapt for + /// The source white point. /// The adapted color - public CieXyz Adapt(in CieXyz color, in CieXyz sourceWhitePoint) + 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.IsChromaticAdaptationPerformed) + if (!this.performChromaticAdaptation || sourceWhitePoint.Equals(targetWhitePoint)) { - throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point."); + return color; } - return this.ChromaticAdaptation.Transform(color, sourceWhitePoint, this.WhitePoint); + return this.chromaticAdaptation.Transform(color, sourceWhitePoint, targetWhitePoint); } /// - /// Adapts color from the source white point to white point set in . + /// 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.IsChromaticAdaptationPerformed) - { - throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point."); - } - - if (color.WhitePoint.Equals(this.TargetLabWhitePoint)) + if (!this.performChromaticAdaptation || color.WhitePoint.Equals(this.targetLabWhitePoint)) { return color; } - CieXyz xyzColor = this.ToCieXyz(color); + var xyzColor = this.ToCieXyz(color); return this.ToCieLab(xyzColor); } /// - /// Adapts color from the source white point to white point set in . + /// 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.IsChromaticAdaptationPerformed) - { - throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point."); - } - - if (color.WhitePoint.Equals(this.TargetLabWhitePoint)) + if (!this.performChromaticAdaptation || color.WhitePoint.Equals(this.targetLabWhitePoint)) { return color; } - CieLab labColor = this.ToCieLab(color); + var labColor = this.ToCieLab(color); return this.ToCieLch(labColor); } /// - /// Adapts color from the source white point to white point set in . + /// 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.IsChromaticAdaptationPerformed) - { - throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point."); - } - - if (color.WhitePoint.Equals(this.TargetLabWhitePoint)) + if (!this.performChromaticAdaptation || color.WhitePoint.Equals(this.targetLabWhitePoint)) { return color; } - CieLuv luvColor = this.ToCieLuv(color); + var luvColor = this.ToCieLuv(color); return this.ToCieLchuv(luvColor); } /// - /// Adapts color from the source white point to white point set in . + /// 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.IsChromaticAdaptationPerformed) - { - throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point."); - } - - if (color.WhitePoint.Equals(this.TargetLuvWhitePoint)) + if (!this.performChromaticAdaptation || color.WhitePoint.Equals(this.targetLuvWhitePoint)) { return color; } - CieXyz xyzColor = this.ToCieXyz(color); + var xyzColor = this.ToCieXyz(color); return this.ToCieLuv(xyzColor); } /// - /// Adapts color from the source white point to white point set in . + /// 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.IsChromaticAdaptationPerformed) - { - throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point."); - } - - if (color.WhitePoint.Equals(this.TargetHunterLabWhitePoint)) + if (!this.performChromaticAdaptation || color.WhitePoint.Equals(this.targetHunterLabWhitePoint)) { return color; } - CieXyz xyzColor = this.ToCieXyz(color); + var xyzColor = this.ToCieXyz(color); return this.ToHunterLab(xyzColor); } /// - /// Adapts a color from the source working space to working space set in . + /// 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.IsChromaticAdaptationPerformed) - { - throw new InvalidOperationException("Cannot perform chromatic adaptation, provide a chromatic adaptation method and white point."); - } - - if (color.WorkingSpace.Equals(this.TargetRgbWorkingSpace)) + if (!this.performChromaticAdaptation || color.WorkingSpace.Equals(this.targetRgbWorkingSpace)) { return color; } @@ -155,21 +134,25 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion CieXyz unadapted = converterToXYZ.Convert(color); // Adaptation - CieXyz adapted = this.ChromaticAdaptation.Transform(unadapted, color.WorkingSpace.WhitePoint, this.TargetRgbWorkingSpace.WhitePoint); + CieXyz adapted = this.chromaticAdaptation.Transform(unadapted, color.WorkingSpace.WhitePoint, this.targetRgbWorkingSpace.WhitePoint); // Conversion back to RGB - CieXyzToLinearRgbConverter converterToRGB = this.GetCieXyxToLinearRgbConverter(this.TargetRgbWorkingSpace); - return converterToRGB.Convert(adapted); + return this.cieXyzToLinearRgbConverter.Convert(adapted); } /// - /// Adapts an color from the source working space to working space set in . + /// 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) { - LinearRgb linearInput = this.ToLinearRgb(color); + if (!this.performChromaticAdaptation) + { + return color; + } + + var linearInput = this.ToLinearRgb(color); LinearRgb linearOutput = this.Adapt(linearInput); return this.ToRgb(linearOutput); } diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs index 1cead3001f..3ce14cdea4 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLab.cs @@ -1,15 +1,17 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLabColorSapce; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLchColorSapce; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Allows conversion to . /// - internal partial class ColorSpaceConverter + public partial class ColorSpaceConverter { /// /// The converter for converting between CieLch to CieLab. @@ -26,15 +28,31 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion // Conversion (perserving white point) CieLab unadapted = CieLchToCieLabConverter.Convert(color); - if (!this.IsChromaticAdaptationPerformed) - { - return unadapted; - } - // Adaptation return this.Adapt(unadapted); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -42,10 +60,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLab ToCieLab(in CieLchuv color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -53,10 +93,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLab ToCieLab(in CieLuv color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -64,10 +126,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLab ToCieLab(in CieXyy color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -76,13 +160,31 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion public CieLab ToCieLab(in CieXyz color) { // Adaptation - CieXyz adapted = !this.WhitePoint.Equals(this.TargetLabWhitePoint) && this.IsChromaticAdaptationPerformed - ? this.ChromaticAdaptation.Transform(color, this.WhitePoint, this.TargetLabWhitePoint) - : color; + CieXyz adapted = this.Adapt(color, this.whitePoint, this.targetLabWhitePoint); // Conversion - var converter = new CieXyzToCieLabConverter(this.TargetLabWhitePoint); - return converter.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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } } /// @@ -92,10 +194,31 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLab ToCieLab(in Cmyk color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -103,10 +226,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLab ToCieLab(in Hsl color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -114,10 +259,31 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLab ToCieLab(in Hsv color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -125,10 +291,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLab ToCieLab(in HunterLab color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -136,10 +324,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLab ToCieLab(in Lms color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -147,10 +357,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLab ToCieLab(in LinearRgb color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -158,10 +390,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLab ToCieLab(in Rgb color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -169,8 +423,30 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLab ToCieLab(in YCbCr color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs index cbefc5ac59..3c9e6658cd 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLch.cs @@ -1,14 +1,17 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLchColorSapce; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Allows conversion to . /// - internal partial class ColorSpaceConverter + public partial class ColorSpaceConverter { /// /// The converter for converting between CieLab to CieLch. @@ -23,12 +26,33 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion public CieLch ToCieLch(in CieLab color) { // Adaptation - CieLab adapted = this.IsChromaticAdaptationPerformed ? this.Adapt(color) : color; + CieLab adapted = this.Adapt(color); // Conversion return CieLabToCieLchConverter.Convert(adapted); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -36,10 +60,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLch ToCieLch(in CieLchuv color) { - CieXyz xyzColor = this.ToCieXyz(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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -47,10 +93,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLch ToCieLch(in CieLuv color) { - CieXyz xyzColor = this.ToCieXyz(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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -58,10 +126,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLch ToCieLch(in CieXyy color) { - CieXyz xyzColor = this.ToCieXyz(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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -69,10 +159,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLch ToCieLch(in CieXyz color) { - CieLab labColor = this.ToCieLab(color); + var labColor = this.ToCieLab(color); + return this.ToCieLch(labColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -80,10 +192,31 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLch ToCieLch(in Cmyk color) { - CieXyz xyzColor = this.ToCieXyz(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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -91,10 +224,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLch ToCieLch(in Hsl color) { - CieXyz xyzColor = this.ToCieXyz(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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -102,10 +257,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLch ToCieLch(in Hsv color) { - CieXyz xyzColor = this.ToCieXyz(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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -113,10 +290,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLch ToCieLch(in HunterLab color) { - CieXyz xyzColor = this.ToCieXyz(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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -124,10 +323,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLch ToCieLch(in LinearRgb color) { - CieXyz xyzColor = this.ToCieXyz(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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -135,10 +356,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLch ToCieLch(in Lms color) { - CieXyz xyzColor = this.ToCieXyz(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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -146,10 +389,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLch ToCieLch(in Rgb color) { - CieXyz xyzColor = this.ToCieXyz(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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -157,8 +422,30 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLch ToCieLch(in YCbCr color) { - CieXyz xyzColor = this.ToCieXyz(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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs index a44541bdb5..01de794885 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs @@ -1,14 +1,17 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLchuvColorSapce; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Allows conversion to . /// - internal partial class ColorSpaceConverter + public partial class ColorSpaceConverter { /// /// The converter for converting between CieLab to CieLchuv. @@ -22,10 +25,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLchuv ToCieLchuv(in CieLab color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -33,10 +58,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLchuv ToCieLchuv(in CieLch color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -45,12 +92,33 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion public CieLchuv ToCieLchuv(in CieLuv color) { // Adaptation - CieLuv adapted = this.IsChromaticAdaptationPerformed ? this.Adapt(color) : color; + CieLuv adapted = this.Adapt(color); // Conversion return CieLuvToCieLchuvConverter.Convert(adapted); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -58,10 +126,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLchuv ToCieLchuv(in CieXyy color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -69,8 +159,30 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLchuv ToCieLchuv(in CieXyz color) { - CieLab labColor = this.ToCieLab(color); - return this.ToCieLchuv(labColor); + var luvColor = this.ToCieLuv(color); + + return this.ToCieLchuv(luvColor); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } } /// @@ -80,10 +192,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLchuv ToCieLchuv(in Cmyk color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -91,10 +225,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLchuv ToCieLchuv(in Hsl color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -102,10 +258,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLchuv ToCieLchuv(in Hsv color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -113,10 +291,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLchuv ToCieLchuv(in HunterLab color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -124,10 +324,32 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLchuv ToCieLchuv(in LinearRgb color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -135,21 +357,65 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLchuv ToCieLchuv(in Lms color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// /// The color to convert. /// The - public CieLchuv ToCieLchuv(Rgb color) + public CieLchuv ToCieLchuv(in Rgb color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -157,8 +423,29 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// The public CieLchuv ToCieLchuv(in YCbCr color) { - CieXyz xyzColor = this.ToCieXyz(color); + var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs index 36e6501a5f..0b469e065f 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLuv.cs @@ -1,16 +1,17 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorSpaces; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLchuvColorSapce; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLuvColorSapce; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Allows conversion to . /// - internal partial class ColorSpaceConverter + public partial class ColorSpaceConverter { private static readonly CieLchuvToCieLuvConverter CieLchuvToCieLuvConverter = new CieLchuvToCieLuvConverter(); @@ -25,6 +26,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieLuv(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -36,6 +58,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieLuv(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -46,15 +89,31 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion // Conversion (perserving white point) CieLuv unadapted = CieLchuvToCieLuvConverter.Convert(color); - if (!this.IsChromaticAdaptationPerformed) - { - return unadapted; - } - // Adaptation return this.Adapt(unadapted); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -66,6 +125,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieLuv(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 /// @@ -74,13 +154,31 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion public CieLuv ToCieLuv(in CieXyz color) { // Adaptation - CieXyz adapted = !this.WhitePoint.Equals(this.TargetLabWhitePoint) && this.IsChromaticAdaptationPerformed - ? this.ChromaticAdaptation.Transform(color, this.WhitePoint, this.TargetLabWhitePoint) - : color; + CieXyz adapted = this.Adapt(color, this.whitePoint, this.targetLuvWhitePoint); // Conversion - var converter = new CieXyzToCieLuvConverter(this.TargetLuvWhitePoint); - return converter.Convert(adapted); + return this.cieXyzToCieLuvConverter.Convert(adapted); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } } /// @@ -94,6 +192,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieLuv(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 /// @@ -105,6 +224,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieLuv(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -116,6 +256,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieLuv(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 /// @@ -127,6 +288,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieLuv(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -138,6 +320,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieLuv(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -149,6 +352,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieLuv(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -160,6 +384,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieLuv(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -170,5 +415,26 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion var 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs index 25a9f75a4b..b77f48325f 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyy.cs @@ -1,14 +1,17 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieXyyColorSapce; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Allows conversion to . /// - internal partial class ColorSpaceConverter + public partial class ColorSpaceConverter { private static readonly CieXyzAndCieXyyConverter CieXyzAndCieXyyConverter = new CieXyzAndCieXyyConverter(); @@ -24,6 +27,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieXyy(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -36,6 +60,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieXyy(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -48,6 +93,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieXyy(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -60,14 +126,53 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieXyy(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// /// The color to convert. /// The - public CieXyy ToCieXyy(in CieXyz color) + public 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) { - return CieXyzAndCieXyyConverter.Convert(color); + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToCieXyy(sp); + } } /// @@ -82,6 +187,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieXyy(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -94,6 +220,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieXyy(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -106,6 +253,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieXyy(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -118,6 +286,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieXyy(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -130,6 +319,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieXyy(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -142,6 +352,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieXyy(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -154,6 +385,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieXyy(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -165,5 +417,26 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieXyy(xyzColor); } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs index f4f28401f1..8963ad495a 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieXyz.cs @@ -1,17 +1,17 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLabColorSapce; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLuvColorSapce; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.HunterLabColorSapce; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Allows conversion to . /// - internal partial class ColorSpaceConverter + public partial class ColorSpaceConverter { private static readonly CieLabToCieXyzConverter CieLabToCieXyzConverter = new CieLabToCieXyzConverter(); @@ -32,11 +32,28 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion CieXyz unadapted = CieLabToCieXyzConverter.Convert(color); // Adaptation - CieXyz adapted = color.WhitePoint.Equals(this.WhitePoint) || !this.IsChromaticAdaptationPerformed - ? unadapted - : this.Adapt(unadapted, color.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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + ref CieLab sourceRef = ref MemoryMarshal.GetReference(source); + ref CieXyz destRef = ref MemoryMarshal.GetReference(destination); - return adapted; + 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); + } } /// @@ -53,6 +70,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieXyz(labColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -67,6 +105,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieXyz(luvColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -78,11 +137,28 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion CieXyz unadapted = CieLuvToCieXyzConverter.Convert(color); // Adaptation - CieXyz adapted = color.WhitePoint.Equals(this.WhitePoint) || !this.IsChromaticAdaptationPerformed - ? unadapted - : this.Adapt(unadapted, color.WhitePoint); + return this.Adapt(unadapted, color.WhitePoint); + } - return adapted; + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } } /// @@ -96,6 +172,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return CieXyzAndCieXyyConverter.Convert(color); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToCieXyz(sp); + } + } + /// /// Converts a into a /// @@ -109,6 +206,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieXyz(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -122,6 +240,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieXyz(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -135,6 +274,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieXyz(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -146,11 +306,28 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion CieXyz unadapted = HunterLabToCieXyzConverter.Convert(color); // Adaptation - CieXyz adapted = color.WhitePoint.Equals(this.WhitePoint) || !this.IsChromaticAdaptationPerformed - ? unadapted - : this.Adapt(unadapted, color.WhitePoint); + return this.Adapt(unadapted, color.WhitePoint); + } - return adapted; + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } } /// @@ -165,9 +342,28 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion CieXyz unadapted = converter.Convert(color); // Adaptation - return color.WorkingSpace.WhitePoint.Equals(this.WhitePoint) || !this.IsChromaticAdaptationPerformed - ? unadapted - : this.Adapt(unadapted, color.WorkingSpace.WhitePoint); + 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } } /// @@ -178,7 +374,28 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion public CieXyz ToCieXyz(in Lms color) { // Conversion - return this.cachedCieXyzAndLmsConverter.Convert(color); + return this.cieXyzAndLmsConverter.Convert(color); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } } /// @@ -193,6 +410,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieXyz(linear); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -206,14 +444,35 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCieXyz(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Gets the correct converter for the given rgb working space. /// /// The source working space /// The - private LinearRgbToCieXyzConverter GetLinearRgbToCieXyzConverter(RgbWorkingSpace workingSpace) + private LinearRgbToCieXyzConverter GetLinearRgbToCieXyzConverter(RgbWorkingSpaceBase workingSpace) { - if (this.linearRgbToCieXyzConverter != null && this.linearRgbToCieXyzConverter.SourceWorkingSpace.Equals(workingSpace)) + if (this.linearRgbToCieXyzConverter?.SourceWorkingSpace.Equals(workingSpace) == true) { return this.linearRgbToCieXyzConverter; } diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs index 1e403828a2..6f8fe61469 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Cmyk.cs @@ -1,14 +1,17 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CmykColorSapce; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Allows conversion to . /// - internal partial class ColorSpaceConverter + public partial class ColorSpaceConverter { private static readonly CmykAndRgbConverter CmykAndRgbConverter = new CmykAndRgbConverter(); @@ -24,6 +27,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCmyk(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -36,6 +60,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCmyk(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -48,6 +93,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCmyk(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -60,6 +126,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCmyk(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -72,6 +159,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCmyk(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -84,6 +192,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return CmykAndRgbConverter.Convert(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -96,6 +225,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return CmykAndRgbConverter.Convert(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToCmyk(sp); + } + } + /// /// Converts a into a /// @@ -108,6 +258,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return CmykAndRgbConverter.Convert(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToCmyk(sp); + } + } + /// /// Converts a into a /// @@ -120,6 +291,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCmyk(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -132,6 +324,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return CmykAndRgbConverter.Convert(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToCmyk(sp); + } + } + /// /// Converts a into a /// @@ -144,14 +357,53 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToCmyk(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// /// The color to convert. /// The - public Cmyk ToCmyk(in Rgb color) + public 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) { - return CmykAndRgbConverter.Convert(color); + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToCmyk(sp); + } } /// @@ -165,5 +417,26 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return CmykAndRgbConverter.Convert(rgb); } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs index 78f8c36fb6..106e8956f1 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsl.cs @@ -1,14 +1,17 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.HslColorSapce; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Allows conversion to . /// - internal partial class ColorSpaceConverter + public partial class ColorSpaceConverter { private static readonly HslAndRgbConverter HslAndRgbConverter = new HslAndRgbConverter(); @@ -24,6 +27,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHsl(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -36,6 +60,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHsl(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -48,6 +93,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHsl(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -60,6 +126,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHsl(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -72,6 +159,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHsl(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -84,6 +192,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return HslAndRgbConverter.Convert(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -96,6 +225,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return HslAndRgbConverter.Convert(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToHsl(sp); + } + } + /// /// Converts a into a /// @@ -108,6 +258,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return HslAndRgbConverter.Convert(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToHsl(sp); + } + } + /// /// Converts a into a /// @@ -120,6 +291,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHsl(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -132,6 +324,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return HslAndRgbConverter.Convert(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToHsl(sp); + } + } + /// /// Converts a into a /// @@ -144,14 +357,53 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHsl(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// /// The color to convert. /// The - public Hsl ToHsl(in Rgb color) + public 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) { - return HslAndRgbConverter.Convert(color); + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToHsl(sp); + } } /// @@ -165,5 +417,26 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return HslAndRgbConverter.Convert(rgb); } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs index 3edd72c59b..8b4e29215c 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Hsv.cs @@ -1,14 +1,17 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.HsvColorSapce; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Allows conversion to . /// - internal partial class ColorSpaceConverter + public partial class ColorSpaceConverter { private static readonly HsvAndRgbConverter HsvAndRgbConverter = new HsvAndRgbConverter(); @@ -24,6 +27,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHsv(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -36,6 +60,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHsv(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -48,6 +93,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHsv(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -60,6 +126,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHsv(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -72,6 +159,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHsv(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -84,6 +192,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return HsvAndRgbConverter.Convert(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -96,6 +225,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return HsvAndRgbConverter.Convert(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToHsv(sp); + } + } + /// /// Converts a into a /// @@ -108,6 +258,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return HsvAndRgbConverter.Convert(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToHsv(sp); + } + } + /// /// Converts a into a /// @@ -120,6 +291,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHsv(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -132,6 +324,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return HsvAndRgbConverter.Convert(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToHsv(sp); + } + } + /// /// Converts a into a /// @@ -144,14 +357,53 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHsv(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// /// The color to convert. /// The - public Hsv ToHsv(in Rgb color) + public 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) { - return HsvAndRgbConverter.Convert(color); + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToHsv(sp); + } } /// @@ -165,5 +417,26 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return HsvAndRgbConverter.Convert(rgb); } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs index f3a64164b1..b3286a9cc4 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs @@ -1,14 +1,16 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.HunterLabColorSapce; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Allows conversion to . /// - internal partial class ColorSpaceConverter + public partial class ColorSpaceConverter { /// /// Converts a into a @@ -21,6 +23,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHunterLab(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -32,6 +55,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHunterLab(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -43,6 +87,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHunterLab(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -54,6 +119,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHunterLab(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -65,6 +151,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHunterLab(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -73,12 +180,31 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion public HunterLab ToHunterLab(in CieXyz color) { // Adaptation - CieXyz adapted = !this.WhitePoint.Equals(this.TargetHunterLabWhitePoint) && this.IsChromaticAdaptationPerformed - ? this.ChromaticAdaptation.Transform(color, this.WhitePoint, this.TargetHunterLabWhitePoint) - : color; + CieXyz adapted = this.Adapt(color, this.whitePoint, this.targetHunterLabWhitePoint); // Conversion - return new CieXyzToHunterLabConverter(this.TargetHunterLabWhitePoint).Convert(adapted); + return this.cieXyzToHunterLabConverter.Convert(adapted); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } } /// @@ -92,6 +218,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHunterLab(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -103,6 +250,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHunterLab(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -114,6 +282,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHunterLab(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -125,6 +314,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHunterLab(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -136,6 +346,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHunterLab(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -147,6 +378,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToHunterLab(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -157,5 +409,26 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs index 5fdde5c758..98943c034a 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs @@ -1,19 +1,20 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Allows conversion to . /// - internal partial class ColorSpaceConverter + public partial class ColorSpaceConverter { private static readonly RgbToLinearRgbConverter RgbToLinearRgbConverter = new RgbToLinearRgbConverter(); - private CieXyzToLinearRgbConverter cieXyzToLinearRgbConverter; - /// /// Converts a into a /// @@ -25,6 +26,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLinearRgb(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -36,6 +58,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLinearRgb(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -47,6 +90,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLinearRgb(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -58,6 +122,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLinearRgb(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -69,6 +154,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLinearRgb(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -77,13 +183,31 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion public LinearRgb ToLinearRgb(in CieXyz color) { // Adaptation - CieXyz adapted = this.TargetRgbWorkingSpace.WhitePoint.Equals(this.WhitePoint) || !this.IsChromaticAdaptationPerformed - ? color - : this.ChromaticAdaptation.Transform(color, this.WhitePoint, this.TargetRgbWorkingSpace.WhitePoint); + CieXyz adapted = this.Adapt(color, this.whitePoint, this.targetRgbWorkingSpace.WhitePoint); // Conversion - CieXyzToLinearRgbConverter xyzConverter = this.GetCieXyxToLinearRgbConverter(this.TargetRgbWorkingSpace); - return xyzConverter.Convert(adapted); + return this.cieXyzToLinearRgbConverter.Convert(adapted); + } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } } /// @@ -97,6 +221,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLinearRgb(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToLinearRgb(sp); + } + } + /// /// Converts a into a /// @@ -108,6 +253,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLinearRgb(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToLinearRgb(sp); + } + } + /// /// Converts a into a /// @@ -119,6 +285,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLinearRgb(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToLinearRgb(sp); + } + } + /// /// Converts a into a /// @@ -130,6 +317,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLinearRgb(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -141,6 +349,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLinearRgb(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -152,6 +381,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return RgbToLinearRgbConverter.Convert(color); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToLinearRgb(sp); + } + } + /// /// Converts a into a /// @@ -164,18 +414,24 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Gets the correct converter for the given rgb working space. + /// Performs the bulk conversion from into /// - /// The target working space - /// The - private CieXyzToLinearRgbConverter GetCieXyxToLinearRgbConverter(RgbWorkingSpace workingSpace) + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) { - if (this.cieXyzToLinearRgbConverter != null && this.cieXyzToLinearRgbConverter.TargetWorkingSpace.Equals(workingSpace)) + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + ref YCbCr sourceRef = ref MemoryMarshal.GetReference(source); + ref LinearRgb destRef = ref MemoryMarshal.GetReference(destination); + + for (int i = 0; i < count; i++) { - return this.cieXyzToLinearRgbConverter; + ref YCbCr sp = ref Unsafe.Add(ref sourceRef, i); + ref LinearRgb dp = ref Unsafe.Add(ref destRef, i); + dp = this.ToLinearRgb(sp); } - - return this.cieXyzToLinearRgbConverter = new CieXyzToLinearRgbConverter(workingSpace); } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Lms.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Lms.cs index 3293e2f4a4..ffd0f88d11 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Lms.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Lms.cs @@ -1,14 +1,16 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorSpaces; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Allows conversion to . /// - internal partial class ColorSpaceConverter + public partial class ColorSpaceConverter { /// /// Converts a into a @@ -21,6 +23,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLms(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -32,6 +55,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLms(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -43,6 +87,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLms(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -54,6 +119,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLms(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -65,14 +151,53 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLms(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// /// The color to convert. /// The - public Lms ToLms(in CieXyz color) + public Lms ToLms(in CieXyz color) => this.cieXyzAndLmsConverter.Convert(color); + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) { - return this.cachedCieXyzAndLmsConverter.Convert(color); + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } } /// @@ -86,6 +211,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLms(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -97,6 +243,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLms(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -108,6 +275,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLms(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -119,6 +307,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLms(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -130,6 +339,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLms(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -141,6 +371,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLms(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -151,5 +402,26 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion 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 + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs index 5bfb6ee052..cd40c966b1 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs @@ -1,14 +1,17 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Allows conversion to . /// - internal partial class ColorSpaceConverter + public partial class ColorSpaceConverter { private static readonly LinearRgbToRgbConverter LinearRgbToRgbConverter = new LinearRgbToRgbConverter(); @@ -23,6 +26,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToRgb(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -34,6 +58,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToRgb(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -45,6 +90,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToRgb(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -56,6 +122,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToRgb(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -67,6 +154,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToRgb(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -81,6 +189,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToRgb(linear); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -92,6 +221,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return CmykAndRgbConverter.Convert(color); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToRgb(sp); + } + } + /// /// Converts a into a /// @@ -103,6 +253,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return HsvAndRgbConverter.Convert(color); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToRgb(sp); + } + } + /// /// Converts a into a /// @@ -114,6 +285,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return HslAndRgbConverter.Convert(color); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToRgb(sp); + } + } + /// /// Converts a into a /// @@ -125,6 +317,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToRgb(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -136,6 +349,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return LinearRgbToRgbConverter.Convert(color); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToRgb(sp); + } + } + /// /// Converts a into a /// @@ -147,6 +381,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToRgb(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -160,5 +415,26 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion // Adaptation return this.Adapt(rgb); } + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs index b7fe34f41a..38e6d5fae0 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.YCbCr.cs @@ -1,14 +1,17 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.YCbCrColorSapce; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// /// Allows conversion to . /// - internal partial class ColorSpaceConverter + public partial class ColorSpaceConverter { private static readonly YCbCrAndRgbConverter YCbCrAndRgbConverter = new YCbCrAndRgbConverter(); @@ -24,6 +27,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToYCbCr(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -37,15 +61,24 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Converts a into a + /// Performs the bulk conversion from into /// - /// The color to convert. - /// The - public YCbCr ToYCbCr(in CieLchuv color) + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) { - var xyzColor = this.ToCieXyz(color); + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); - return this.ToYCbCr(xyzColor); + 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); + } } /// @@ -60,6 +93,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToYCbCr(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -72,6 +126,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToYCbCr(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -84,6 +159,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return YCbCrAndRgbConverter.Convert(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -96,6 +192,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return YCbCrAndRgbConverter.Convert(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToYCbCr(sp); + } + } + /// /// Converts a into a /// @@ -108,6 +225,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return YCbCrAndRgbConverter.Convert(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToYCbCr(sp); + } + } + /// /// Converts a into a /// @@ -120,6 +258,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return YCbCrAndRgbConverter.Convert(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToYCbCr(sp); + } + } + /// /// Converts a into a /// @@ -132,6 +291,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToYCbCr(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// @@ -144,6 +324,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return YCbCrAndRgbConverter.Convert(rgb); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToYCbCr(sp); + } + } + /// /// Converts a into a /// @@ -156,14 +357,53 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToYCbCr(xyzColor); } + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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); + } + } + /// /// Converts a into a /// /// The color to convert. /// The - public YCbCr ToYCbCr(in Rgb color) + public YCbCr ToYCbCr(in Rgb color) => YCbCrAndRgbConverter.Convert(color); + + /// + /// Performs the bulk conversion from into + /// + /// The span to the source colors + /// The span to the destination colors + /// The number of colors to convert. + public void Convert(ReadOnlySpan source, Span destination, int count) { - return YCbCrAndRgbConverter.Convert(color); + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + 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 = this.ToYCbCr(sp); + } } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs index 7142ab0e8c..fe6a57f7ac 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.cs @@ -2,100 +2,60 @@ // Licensed under the Apache License, Version 2.0. using System.Numerics; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.LmsColorSapce; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// - /// Converts between color spaces ensuring that the color is adapted using chromatic adaptation. + /// Provides methods to allow the conversion of color values between different color spaces. /// - internal partial class ColorSpaceConverter + public partial class ColorSpaceConverter { - /// - /// The default whitepoint used for converting to CieLab - /// - public static readonly CieXyz DefaultWhitePoint = Illuminants.D65; - - private Matrix4x4 transformationMatrix; - - private CieXyzAndLmsConverter cachedCieXyzAndLmsConverter; + // 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 RgbWorkingSpaceBase 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) { - // Note the order here this is important. - this.WhitePoint = DefaultWhitePoint; - this.LmsAdaptationMatrix = CieXyzAndLmsConverter.DefaultTransformationMatrix; - this.ChromaticAdaptation = new VonKriesChromaticAdaptation(this.cachedCieXyzAndLmsConverter); - this.TargetLuvWhitePoint = CieLuv.DefaultWhitePoint; - this.TargetLabWhitePoint = CieLab.DefaultWhitePoint; - this.TargetHunterLabWhitePoint = HunterLab.DefaultWhitePoint; - this.TargetRgbWorkingSpace = Rgb.DefaultWorkingSpace; } /// - /// Gets or sets the white point used for chromatic adaptation in conversions from/to XYZ color space. - /// When null, no adaptation will be performed. - /// - public CieXyz WhitePoint { get; set; } - - /// - /// 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; } - - /// - /// 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; } - - /// - /// 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; } - - /// - /// 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; } - - /// - /// Gets or sets the chromatic adaptation method used. When null, no adaptation will be performed. - /// - public IChromaticAdaptation ChromaticAdaptation { get; set; } - - /// - /// Gets or sets transformation matrix used in conversion to , - /// also used in the default Von Kries Chromatic Adaptation method. + /// Initializes a new instance of the class. /// - public Matrix4x4 LmsAdaptationMatrix + /// The configuration options. + public ColorSpaceConverter(ColorSpaceConverterOptions options) { - get => this.transformationMatrix; - - set - { - this.transformationMatrix = value; - if (this.cachedCieXyzAndLmsConverter == null) - { - this.cachedCieXyzAndLmsConverter = new CieXyzAndLmsConverter(value); - } - else - { - this.cachedCieXyzAndLmsConverter.TransformationMatrix = value; - } - } + 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); } - - /// - /// Gets a value indicating whether chromatic adaptation has been performed. - /// - private bool IsChromaticAdaptationPerformed => this.ChromaticAdaptation != null; } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverterOptions.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverterOptions.cs new file mode 100644 index 0000000000..fcd031e263 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverterOptions.cs @@ -0,0 +1,55 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion +{ + /// + /// Configuration options for the class. + /// + 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 RgbWorkingSpaceBase 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; + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/IChromaticAdaptation.cs b/src/ImageSharp/ColorSpaces/Conversion/IChromaticAdaptation.cs index dfba4b9269..1b14c6413e 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/IChromaticAdaptation.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/IChromaticAdaptation.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; + namespace SixLabors.ImageSharp.ColorSpaces.Conversion { /// @@ -8,16 +10,27 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// 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). /// - internal interface IChromaticAdaptation + 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 color. /// The source white point. - /// The target white point. + /// The destination white point. /// The - CieXyz Transform(in CieXyz sourceColor, in CieXyz sourceWhitePoint, in CieXyz targetWhitePoint); + 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. + /// The number of colors to convert. + void Transform(Span source, Span destination, CieXyz sourceWhitePoint, in CieXyz destinationWhitePoint, int count); } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/IColorConversion.cs b/src/ImageSharp/ColorSpaces/Conversion/IColorConversion.cs deleted file mode 100644 index 009b91c40a..0000000000 --- a/src/ImageSharp/ColorSpaces/Conversion/IColorConversion.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.ColorSpaces.Conversion -{ - /// - /// Converts color between two color spaces. - /// - /// The input color type. - /// The result color type. - internal interface IColorConversion - where T : struct - where TResult : struct - { - /// - /// Performs the conversion from the input to an instance of the output type. - /// - /// The input color instance. - /// The converted result - TResult Convert(in T input); - } -} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Cmyk/CmykAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Cmyk/CmykAndRgbConverter.cs deleted file mode 100644 index 8691783703..0000000000 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Cmyk/CmykAndRgbConverter.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CmykColorSapce -{ - /// - /// Color converter between CMYK and Rgb - /// - internal class CmykAndRgbConverter : IColorConversion, IColorConversion - { - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgb Convert(in Cmyk input) - { - float r = (1F - input.C) * (1F - input.K); - float g = (1F - input.M) * (1F - input.K); - float b = (1F - input.Y) * (1F - input.K); - - return new Rgb(r, g, b); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Cmyk Convert(in Rgb input) - { - // To CMYK - float c = 1F - input.R; - float m = 1F - input.G; - float y = 1F - input.B; - - // To CMYK - float k = MathF.Min(c, MathF.Min(m, y)); - - if (MathF.Abs(k - 1F) < Constants.Epsilon) - { - return new Cmyk(0, 0, 0, 1F); - } - - c = (c - k) / (1F - k); - m = (m - k) / (1F - k); - y = (y - k) / (1F - k); - - return new Cmyk(c, m, y, k); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CIeLchToCieLabConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CIeLchToCieLabConverter.cs similarity index 68% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CIeLchToCieLabConverter.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CIeLchToCieLabConverter.cs index 061d04493d..dd352db809 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CIeLchToCieLabConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CIeLchToCieLabConverter.cs @@ -4,15 +4,19 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLchColorSapce +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// /// Converts from to . /// - internal class CieLchToCieLabConverter : IColorConversion + internal sealed class CieLchToCieLabConverter { - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// 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 CieLch input) { // Conversion algorithm described here: diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CieLabToCieLchConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieLchConverter.cs similarity index 71% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CieLabToCieLchConverter.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieLchConverter.cs index 105fb2aa11..81196604e5 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLch/CieLabToCieLchConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieLchConverter.cs @@ -4,15 +4,19 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLchColorSapce +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// /// Converts from to . /// - internal class CieLabToCieLchConverter : IColorConversion + internal sealed class CieLabToCieLchConverter { - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] public CieLch Convert(in CieLab input) { // Conversion algorithm described here: @@ -23,7 +27,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLchColor float hDegrees = MathFExtensions.RadianToDegree(hRadians); // Wrap the angle round at 360. - hDegrees = hDegrees % 360; + hDegrees %= 360; // Make sure it's not negative. while (hDegrees < 0) diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieLabToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieXyzConverter.cs similarity index 55% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieLabToCieXyzConverter.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieXyzConverter.cs index ca8f23c564..dfbbc8f0c7 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieLabToCieXyzConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLabToCieXyzConverter.cs @@ -1,18 +1,22 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; +using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLabColorSapce +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// /// Converts from to . /// - internal class CieLabToCieXyzConverter : IColorConversion + internal sealed class CieLabToCieXyzConverter { - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// 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 CieLab input) { // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html @@ -21,25 +25,20 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLabColor float fx = (a / 500F) + fy; float fz = fy - (b / 200F); - float fx3 = MathF.Pow(fx, 3F); - float fz3 = MathF.Pow(fz, 3F); + float fx3 = ImageMaths.Pow3(fx); + float fz3 = ImageMaths.Pow3(fz); float xr = fx3 > CieConstants.Epsilon ? fx3 : ((116F * fx) - 16F) / CieConstants.Kappa; - float yr = l > CieConstants.Kappa * CieConstants.Epsilon ? MathF.Pow((l + 16F) / 116F, 3F) : l / CieConstants.Kappa; + float yr = l > CieConstants.Kappa * CieConstants.Epsilon ? ImageMaths.Pow3((l + 16F) / 116F) : l / CieConstants.Kappa; float zr = fz3 > CieConstants.Epsilon ? fz3 : ((116F * fz) - 16F) / CieConstants.Kappa; - float wx = input.WhitePoint.X, wy = input.WhitePoint.Y, wz = input.WhitePoint.Z; + var wxyz = new Vector3(input.WhitePoint.X, input.WhitePoint.Y, input.WhitePoint.Z); // Avoids XYZ coordinates out range (restricted by 0 and XYZ reference white) - xr = xr.Clamp(0, 1F); - yr = yr.Clamp(0, 1F); - zr = zr.Clamp(0, 1F); + var xyzr = Vector3.Clamp(new Vector3(xr, yr, zr), Vector3.Zero, Vector3.One); - float x = xr * wx; - float y = yr * wy; - float z = zr * wz; - - return new CieXyz(x, y, z); + Vector3 xyz = xyzr * wxyz; + return new CieXyz(xyz); } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLchuvToCieLuvConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLchuvToCieLuvConverter.cs similarity index 68% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLchuvToCieLuvConverter.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLchuvToCieLuvConverter.cs index 7f8e0fc1e9..4f5a20bec7 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLchuvToCieLuvConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLchuvToCieLuvConverter.cs @@ -4,15 +4,19 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLchuvColorSapce +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// /// Converts from to . /// - internal class CieLchuvToCieLuvConverter : IColorConversion + internal sealed class CieLchuvToCieLuvConverter { - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] public CieLuv Convert(in CieLchuv input) { // Conversion algorithm described here: diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLuvToCieLchuvConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieLchuvConverter.cs similarity index 71% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLuvToCieLchuvConverter.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieLchuvConverter.cs index 7a23e2da1a..297c18c5c3 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLchuv/CieLuvToCieLchuvConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieLchuvConverter.cs @@ -4,15 +4,19 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLchuvColorSapce +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// /// Converts from to . /// - internal class CieLuvToCieLchuvConverter : IColorConversion + internal sealed class CieLuvToCieLchuvConverter { - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] public CieLchuv Convert(in CieLuv input) { // Conversion algorithm described here: @@ -23,7 +27,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLchuvCol float hDegrees = MathFExtensions.RadianToDegree(hRadians); // Wrap the angle round at 360. - hDegrees = hDegrees % 360; + hDegrees %= 360; // Make sure it's not negative. while (hDegrees < 0) diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieLuvToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieXyzConverter.cs similarity index 73% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieLuvToCieXyzConverter.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieXyzConverter.cs index cd2ec488d2..33f3ec3d3e 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieLuvToCieXyzConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieLuvToCieXyzConverter.cs @@ -1,18 +1,20 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLuvColorSapce +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// /// Converts from to . /// - internal class CieLuvToCieXyzConverter : IColorConversion + internal sealed class CieLuvToCieXyzConverter { - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result public CieXyz Convert(in CieLuv input) { // Conversion algorithm described here: http://www.brucelindbloom.com/index.html?Eqn_Luv_to_XYZ.html @@ -22,12 +24,12 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLuvColor float v0 = ComputeV0(input.WhitePoint); float y = l > CieConstants.Kappa * CieConstants.Epsilon - ? MathF.Pow((l + 16) / 116, 3) + ? ImageMaths.Pow3((l + 16) / 116) : l / CieConstants.Kappa; float a = ((52 * l / (u + (13 * l * u0))) - 1) / 3; float b = -5 * y; - float c = -0.3333333F; + const float c = -0.3333333F; float d = y * ((39 * l / (v + (13 * l * v0))) - 5); float x = (d - b) / (a - c); @@ -56,21 +58,17 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLuvColor /// /// The whitepoint /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] private static float ComputeU0(in CieXyz input) - { - return (4 * input.X) / (input.X + (15 * input.Y) + (3 * input.Z)); - } + => (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(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] private static float ComputeV0(in CieXyz input) - { - return (9 * input.Y) / (input.X + (15 * input.Y) + (3 * input.Z)); - } + => (9 * input.Y) / (input.X + (15 * input.Y) + (3 * input.Z)); } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyy/CieXyzAndCieXyyConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs similarity index 61% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyy/CieXyzAndCieXyyConverter.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs index d15f7360e4..f33d1ddcc9 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieXyy/CieXyzAndCieXyyConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndCieXyyConverter.cs @@ -4,16 +4,20 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieXyyColorSapce +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// /// Color converter between CIE XYZ and CIE xyY /// for formulas. /// - internal class CieXyzAndCieXyyConverter : IColorConversion, IColorConversion + internal sealed class CieXyzAndCieXyyConverter { - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] public CieXyy Convert(in CieXyz input) { float x = input.X / (input.X + input.Y + input.Z); @@ -27,8 +31,12 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieXyyColor return new CieXyy(x, y, input.Y); } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// 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 CieXyy input) { if (MathF.Abs(input.Y) < Constants.Epsilon) diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzAndHunterLabConverterBase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndHunterLabConverterBase.cs similarity index 90% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzAndHunterLabConverterBase.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndHunterLabConverterBase.cs index ebf75e0d50..1cd511e819 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzAndHunterLabConverterBase.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndHunterLabConverterBase.cs @@ -3,7 +3,7 @@ using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.HunterLabColorSapce +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// /// The base class for converting between and color spaces. @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.HunterLabCo /// /// The whitepoint /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public static float ComputeKa(CieXyz whitePoint) { if (whitePoint.Equals(Illuminants.C)) @@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.HunterLabCo /// /// The whitepoint /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public static float ComputeKb(CieXyz whitePoint) { if (whitePoint == Illuminants.C) diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/CieXyzAndLmsConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndLmsConverter.cs similarity index 56% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/CieXyzAndLmsConverter.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndLmsConverter.cs index c29496c37e..f860652b18 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/CieXyzAndLmsConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzAndLmsConverter.cs @@ -4,12 +4,12 @@ using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.LmsColorSapce +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// - /// Color converter between CIE XYZ and LMS + /// Color converter between and /// - internal class CieXyzAndLmsConverter : IColorConversion, IColorConversion + internal sealed class CieXyzAndLmsConverter { /// /// Default transformation matrix used, when no other is set. (Bradford) @@ -23,7 +23,6 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.LmsColorSap /// /// Initializes a new instance of the class. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public CieXyzAndLmsConverter() : this(DefaultTransformationMatrix) { @@ -36,41 +35,35 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.LmsColorSap /// Definition of the cone response domain (see ), /// if not set will be used. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public CieXyzAndLmsConverter(Matrix4x4 transformationMatrix) { - this.TransformationMatrix = transformationMatrix; + this.transformationMatrix = transformationMatrix; + Matrix4x4.Invert(this.transformationMatrix, out this.inverseTransformationMatrix); } /// - /// Gets or sets the transformation matrix used for the conversion (definition of the cone response domain). - /// + /// Performs the conversion from the input to an instance of type. /// - public Matrix4x4 TransformationMatrix - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.transformationMatrix; - - set - { - this.transformationMatrix = value; - Matrix4x4.Invert(this.transformationMatrix, out this.inverseTransformationMatrix); - } - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] public Lms Convert(in CieXyz input) { - Vector3 vector = Vector3.Transform(input.Vector, this.transformationMatrix); + var vector = Vector3.Transform(input.ToVector3(), this.transformationMatrix); + return new Lms(vector); } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// 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.Vector, this.inverseTransformationMatrix); + var vector = Vector3.Transform(input.ToVector3(), this.inverseTransformationMatrix); + return new CieXyz(vector); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieXyzToCieLabConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLabConverter.cs similarity index 79% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieXyzToCieLabConverter.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLabConverter.cs index 0fe52e6af1..c155087ff5 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLab/CieXyzToCieLabConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLabConverter.cs @@ -4,17 +4,16 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLabColorSapce +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// /// Converts from to . /// - internal class CieXyzToCieLabConverter : IColorConversion + internal sealed class CieXyzToCieLabConverter { /// /// Initializes a new instance of the class. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public CieXyzToCieLabConverter() : this(CieLab.DefaultWhitePoint) { @@ -24,19 +23,19 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLabColor /// Initializes a new instance of the class. /// /// The target reference lab white point - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CieXyzToCieLabConverter(CieXyz labWhitePoint) - { - this.LabWhitePoint = labWhitePoint; - } + public CieXyzToCieLabConverter(CieXyz labWhitePoint) => this.LabWhitePoint = labWhitePoint; /// /// Gets the target reference whitepoint. When not set, is used. /// public CieXyz LabWhitePoint { get; } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// 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 diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieXyzToCieLuvConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLuvConverter.cs similarity index 79% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieXyzToCieLuvConverter.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLuvConverter.cs index c34a2455a7..7f2bb0cf6a 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/CieLuv/CieXyzToCieLuvConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToCieLuvConverter.cs @@ -3,19 +3,17 @@ using System; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.ColorSpaces; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLuvColorSapce +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// /// Converts from to . /// - internal class CieXyzToCieLuvConverter : IColorConversion + internal sealed class CieXyzToCieLuvConverter { /// /// Initializes a new instance of the class. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public CieXyzToCieLuvConverter() : this(CieLuv.DefaultWhitePoint) { @@ -25,19 +23,18 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLuvColor /// Initializes a new instance of the class. /// /// The target reference luv white point - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CieXyzToCieLuvConverter(CieXyz luvWhitePoint) - { - this.LuvWhitePoint = luvWhitePoint; - } + public CieXyzToCieLuvConverter(CieXyz luvWhitePoint) => this.LuvWhitePoint = luvWhitePoint; /// /// Gets the target reference whitepoint. When not set, is used. /// public CieXyz LuvWhitePoint { get; } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// 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 @@ -77,18 +74,15 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.CieLuvColor /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float ComputeUp(in CieXyz input) - { - return (4 * input.X) / (input.X + (15 * input.Y) + (3 * input.Z)); - } + => (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) - { - return (9 * input.Y) / (input.X + (15 * input.Y) + (3 * input.Z)); - } + => (9 * input.Y) / (input.X + (15 * input.Y) + (3 * input.Z)); } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzToHunterLabConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToHunterLabConverter.cs similarity index 75% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzToHunterLabConverter.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToHunterLabConverter.cs index af681e981f..c27c61608d 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/CieXyzToHunterLabConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToHunterLabConverter.cs @@ -4,17 +4,16 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.HunterLabColorSapce +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// - /// Color converter between CieXyz and HunterLab + /// Color converter between and /// - internal class CieXyzToHunterLabConverter : CieXyzAndHunterLabConverterBase, IColorConversion + internal sealed class CieXyzToHunterLabConverter : CieXyzAndHunterLabConverterBase { /// /// Initializes a new instance of the class. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public CieXyzToHunterLabConverter() : this(HunterLab.DefaultWhitePoint) { @@ -24,19 +23,19 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.HunterLabCo /// Initializes a new instance of the class. /// /// The hunter Lab white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CieXyzToHunterLabConverter(CieXyz labWhitePoint) - { - this.HunterLabWhitePoint = labWhitePoint; - } + public CieXyzToHunterLabConverter(CieXyz labWhitePoint) => this.HunterLabWhitePoint = labWhitePoint; /// /// Gets the target reference white. When not set, is used. /// public CieXyz HunterLabWhitePoint { get; } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// 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 diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/CieXyzToLinearRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToLinearRgbConverter.cs similarity index 64% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/CieXyzToLinearRgbConverter.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToLinearRgbConverter.cs index 217698c23a..3812cdbdd8 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/CieXyzToLinearRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CieXyzToLinearRgbConverter.cs @@ -2,13 +2,14 @@ // Licensed under the Apache License, Version 2.0. using System.Numerics; +using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// - /// Color converter between CieXyz and LinearRgb + /// Color converter between and /// - internal sealed class CieXyzToLinearRgbConverter : LinearRgbAndCieXyzConverterBase, IColorConversion + internal sealed class CieXyzToLinearRgbConverter : LinearRgbAndCieXyzConverterBase { private readonly Matrix4x4 conversionMatrix; @@ -24,7 +25,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSap /// Initializes a new instance of the class. /// /// The target working space. - public CieXyzToLinearRgbConverter(RgbWorkingSpace workingSpace) + public CieXyzToLinearRgbConverter(RgbWorkingSpaceBase workingSpace) { this.TargetWorkingSpace = workingSpace; this.conversionMatrix = GetRgbToCieXyzMatrix(workingSpace); @@ -33,13 +34,18 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSap /// /// Gets the target working space /// - public RgbWorkingSpace TargetWorkingSpace { get; } + public RgbWorkingSpaceBase 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) { Matrix4x4.Invert(this.conversionMatrix, out Matrix4x4 inverted); - Vector3 vector = Vector3.Transform(input.Vector, inverted); + var vector = Vector3.Transform(input.ToVector3(), inverted); 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 new file mode 100644 index 0000000000..29fd32905b --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/CmykAndRgbConverter.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Color converter between and + /// + internal sealed class CmykAndRgbConverter + { + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public 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); + } + + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public Cmyk Convert(in Rgb input) + { + // To CMY + Vector3 cmy = Vector3.One - input.ToVector3(); + + // To CMYK + var k = new Vector3(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); + } + + cmy = (cmy - k) / (Vector3.One - k); + + return new Cmyk(cmy.X, cmy.Y, cmy.Z, k.X); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsl/HslAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs similarity index 82% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsl/HslAndRgbConverter.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs index 1bec834a80..761313b7e0 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsl/HslAndRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs @@ -4,16 +4,20 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.HslColorSapce +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// /// Color converter between HSL and Rgb /// See for formulas. /// - internal class HslAndRgbConverter : IColorConversion, IColorConversion + internal sealed class HslAndRgbConverter { - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] public Rgb Convert(in Hsl input) { float rangedH = input.H / 360F; @@ -43,8 +47,12 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.HslColorSap return new Rgb(r, g, b); } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] public Hsl Convert(in Rgb input) { float r = input.R; @@ -103,7 +111,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.HslColorSap /// /// The . /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] private static float GetColorComponent(float first, float second, float third) { third = MoveIntoRange(third); @@ -134,16 +142,16 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.HslColorSap /// /// The . /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] private static float MoveIntoRange(float value) { if (value < 0F) { - value += 1F; + value++; } else if (value > 1F) { - value -= 1F; + value--; } return value; diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsv/HsvAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HsvAndRgbConverter.cs similarity index 80% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsv/HsvAndRgbConverter.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HsvAndRgbConverter.cs index f2c4cc188f..20ada7e7dd 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Hsv/HsvAndRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HsvAndRgbConverter.cs @@ -4,16 +4,20 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.HsvColorSapce +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// /// Color converter between HSV and Rgb /// See for formulas. /// - internal class HsvAndRgbConverter : IColorConversion, IColorConversion + internal sealed class HsvAndRgbConverter { - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] public Rgb Convert(in Hsv input) { float s = input.S; @@ -75,8 +79,12 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.HsvColorSap return new Rgb(r, g, b); } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] public Hsv Convert(in Rgb input) { float r = input.R; diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/HunterLabToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HunterLabToCieXyzConverter.cs similarity index 61% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/HunterLabToCieXyzConverter.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HunterLabToCieXyzConverter.cs index eba9fe1c83..783d29a557 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/HunterLab/HunterLabToCieXyzConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HunterLabToCieXyzConverter.cs @@ -4,15 +4,19 @@ using System; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.HunterLabColorSapce +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// - /// Color converter between HunterLab and CieXyz + /// Color converter between and /// - internal class HunterLabToCieXyzConverter : CieXyzAndHunterLabConverterBase, IColorConversion + internal sealed class HunterLabToCieXyzConverter : CieXyzAndHunterLabConverterBase { - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// 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 HunterLab input) { // Conversion algorithm described here: http://en.wikipedia.org/wiki/Lab_color_space#Hunter_Lab @@ -22,7 +26,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.HunterLabCo float ka = ComputeKa(input.WhitePoint); float kb = ComputeKb(input.WhitePoint); - float y = MathF.Pow(l / 100F, 2) * yn; + float y = ImageMaths.Pow2(l / 100F) * yn; float x = (((a / ka) * MathF.Sqrt(y / yn)) + (y / yn)) * xn; float z = (((b / kb) * MathF.Sqrt(y / yn)) - (y / yn)) * (-zn); diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbAndCieXyzConverterBase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbAndCieXyzConverterBase.cs similarity index 68% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbAndCieXyzConverterBase.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbAndCieXyzConverterBase.cs index bc11c51b54..a93773262c 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbAndCieXyzConverterBase.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbAndCieXyzConverterBase.cs @@ -3,10 +3,10 @@ using System.Numerics; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// - /// Provides base methods for converting between Rgb and CieXyz color spaces. + /// Provides base methods for converting between and color spaces. /// internal abstract class LinearRgbAndCieXyzConverterBase { @@ -15,10 +15,9 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSap /// /// The Rgb working space. /// The based on the chromaticity and working space. - public static Matrix4x4 GetRgbToCieXyzMatrix(RgbWorkingSpace workingSpace) + public static Matrix4x4 GetRgbToCieXyzMatrix(RgbWorkingSpaceBase workingSpace) { DebugGuard.NotNull(workingSpace, nameof(workingSpace)); - RgbPrimariesChromaticityCoordinates chromaticity = workingSpace.ChromaticityCoordinates; float xr = chromaticity.R.X; @@ -42,23 +41,35 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSap var xyzMatrix = new Matrix4x4 { - M11 = mXr, M21 = mXg, M31 = mXb, - M12 = Yr, M22 = Yg, M32 = Yb, - M13 = mZr, M23 = mZg, M33 = mZb, + M11 = mXr, + M21 = mXg, + M31 = mXb, + M12 = Yr, + M22 = Yg, + M32 = Yb, + M13 = mZr, + M23 = mZg, + M33 = mZb, M44 = 1F }; Matrix4x4.Invert(xyzMatrix, out Matrix4x4 inverseXyzMatrix); - Vector3 vector = Vector3.Transform(workingSpace.WhitePoint.Vector, inverseXyzMatrix); + var 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 * Yr, M22 = vector.Y * Yg, M32 = vector.Z * Yb, - M13 = vector.X * mZr, M23 = vector.Y * mZg, M33 = vector.Z * mZb, + M11 = vector.X * mXr, + M21 = vector.Y * mXg, + M31 = vector.Z * mXb, + M12 = vector.X * Yr, + M22 = vector.Y * Yg, + M32 = vector.Z * Yb, + M13 = vector.X * mZr, + M23 = vector.Y * mZg, + M33 = vector.Z * mZb, M44 = 1F }; } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToCieXyzConverter.cs similarity index 65% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToCieXyzConverter.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToCieXyzConverter.cs index e597b66af0..1030ac9819 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToCieXyzConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToCieXyzConverter.cs @@ -2,13 +2,14 @@ // Licensed under the Apache License, Version 2.0. using System.Numerics; +using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// - /// Color converter between LinearRgb and CieXyz + /// Color converter between and /// - internal sealed class LinearRgbToCieXyzConverter : LinearRgbAndCieXyzConverterBase, IColorConversion + internal sealed class LinearRgbToCieXyzConverter : LinearRgbAndCieXyzConverterBase { private readonly Matrix4x4 conversionMatrix; @@ -24,7 +25,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSap /// Initializes a new instance of the class. /// /// The target working space. - public LinearRgbToCieXyzConverter(RgbWorkingSpace workingSpace) + public LinearRgbToCieXyzConverter(RgbWorkingSpaceBase workingSpace) { this.SourceWorkingSpace = workingSpace; this.conversionMatrix = GetRgbToCieXyzMatrix(workingSpace); @@ -33,14 +34,19 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSap /// /// Gets the source working space /// - public RgbWorkingSpace SourceWorkingSpace { get; } + public RgbWorkingSpaceBase 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.Vector, this.conversionMatrix); + var 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 new file mode 100644 index 0000000000..1cc055bee2 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/LinearRgbToRgbConverter.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Color converter between and + /// + internal sealed class LinearRgbToRgbConverter + { + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] + public Rgb Convert(in LinearRgb input) + { + var vector = input.ToVector3(); + vector.X = input.WorkingSpace.Compress(vector.X); + vector.Y = input.WorkingSpace.Compress(vector.Y); + vector.Z = input.WorkingSpace.Compress(vector.Z); + + return new Rgb(vector, input.WorkingSpace); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/RgbToLinearRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/RgbToLinearRgbConverter.cs new file mode 100644 index 0000000000..03912a421e --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/RgbToLinearRgbConverter.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Color converter between Rgb and LinearRgb + /// + internal class RgbToLinearRgbConverter + { + /// + /// 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 Rgb input) + { + var vector = input.ToVector3(); + vector.X = input.WorkingSpace.Expand(vector.X); + vector.Y = input.WorkingSpace.Expand(vector.Y); + vector.Z = input.WorkingSpace.Expand(vector.Z); + + return new LinearRgb(vector, input.WorkingSpace); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/YCbCr/YCbCrAndRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/YCbCrAndRgbConverter.cs similarity index 62% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/YCbCr/YCbCrAndRgbConverter.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/YCbCrAndRgbConverter.cs index e8d32572a4..4ac3ad3cf9 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/YCbCr/YCbCrAndRgbConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/YCbCrAndRgbConverter.cs @@ -5,18 +5,22 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.YCbCrColorSapce +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// - /// Color converter between YCbCr and Rgb + /// Color converter between and /// See for formulas. /// - internal class YCbCrAndRgbConverter : IColorConversion, IColorConversion + internal sealed class YCbCrAndRgbConverter { private static readonly Vector3 MaxBytes = new Vector3(255F); - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] public Rgb Convert(in YCbCr input) { float y = input.Y; @@ -30,11 +34,15 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.YCbCrColorS return new Rgb(new Vector3(r, g, b) / MaxBytes); } - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + /// + /// Performs the conversion from the input to an instance of type. + /// + /// The input color instance. + /// The converted result + [MethodImpl(InliningOptions.ShortMethod)] public YCbCr Convert(in Rgb input) { - Vector3 rgb = input.Vector * MaxBytes; + Vector3 rgb = input.ToVector3() * MaxBytes; float r = rgb.X; float g = rgb.Y; float b = rgb.Z; diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/LmsAdaptationMatrix.cs similarity index 55% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/LmsAdaptationMatrix.cs index d535d73342..37e4b1a1a6 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Lms/LmsAdaptationMatrix.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/LmsAdaptationMatrix.cs @@ -3,11 +3,10 @@ using System.Numerics; -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.LmsColorSapce +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// - /// AdaptionMatrix3X3 used for transformation from XYZ to LMS, defining the cone response domain. + /// Matrices used for transformation from to , defining the cone response domain. /// Used in /// /// @@ -17,7 +16,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.LmsColorSap /// 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 /// - internal static class LmsAdaptationMatrix + public static class LmsAdaptationMatrix { /// /// Von Kries chromatic adaptation transform matrix (Hunt-Pointer-Estevez adjusted for D65) @@ -25,9 +24,15 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.LmsColorSap 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, + 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. }); @@ -37,9 +42,15 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.LmsColorSap 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, + 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 }); @@ -54,9 +65,15 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.LmsColorSap 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, + 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 }); @@ -65,35 +82,53 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.LmsColorSap /// 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 - }); + { + 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 - }); + { + 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 - }); + { + 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 + }); } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RGBPrimariesChromaticityCoordinates.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/RGBPrimariesChromaticityCoordinates.cs similarity index 78% rename from src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RGBPrimariesChromaticityCoordinates.cs rename to src/ImageSharp/ColorSpaces/Conversion/Implementation/RGBPrimariesChromaticityCoordinates.cs index 4359d666e6..68b4d95fc6 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RGBPrimariesChromaticityCoordinates.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/RGBPrimariesChromaticityCoordinates.cs @@ -3,13 +3,13 @@ using System; -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation { /// /// Represents the chromaticity coordinates of RGB primaries. - /// One of the specifiers of . + /// One of the specifiers of . /// - internal readonly struct RgbPrimariesChromaticityCoordinates : IEquatable + public readonly struct RgbPrimariesChromaticityCoordinates : IEquatable { /// /// Initializes a new instance of the struct. @@ -40,13 +40,13 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSap public CieXyChromaticityCoordinates B { get; } /// - /// Compares two objects for equality. + /// Compares two objects for equality. /// /// - /// The on the left side of the operand. + /// The on the left side of the operand. /// /// - /// The on the right side of the operand. + /// The on the right side of the operand. /// /// /// True if the current left is equal to the parameter; otherwise, false. @@ -57,13 +57,13 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSap } /// - /// Compares two objects for inequality + /// Compares two objects for inequality /// /// - /// The on the left side of the operand. + /// The on the left side of the operand. /// /// - /// The on the right side of the operand. + /// The on the right side of the operand. /// /// /// True if the current left is unequal to the parameter; otherwise, false. @@ -92,8 +92,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSap { int hashCode = this.R.GetHashCode(); hashCode = (hashCode * 397) ^ this.G.GetHashCode(); - hashCode = (hashCode * 397) ^ this.B.GetHashCode(); - return hashCode; + return (hashCode * 397) ^ this.B.GetHashCode(); } } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/GammaCompanding.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/GammaCompanding.cs deleted file mode 100644 index a7b0ecc984..0000000000 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/GammaCompanding.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce -{ - /// - /// Implements gamma companding - /// - /// - /// - /// - /// - internal class GammaCompanding : ICompanding - { - /// - /// Initializes a new instance of the class. - /// - /// The gamma value. - public GammaCompanding(float gamma) - { - this.Gamma = gamma; - } - - /// - /// Gets the gamma value - /// - public float Gamma { get; } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float Expand(float channel) - { - return MathF.Pow(channel, this.Gamma); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float Compress(float channel) - { - return MathF.Pow(channel, 1 / this.Gamma); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LCompanding.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LCompanding.cs deleted file mode 100644 index 309ae21833..0000000000 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LCompanding.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce -{ - /// - /// Implements L* companding - /// - /// - /// For more info see: - /// - /// - /// - internal class LCompanding : ICompanding - { - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float Expand(float channel) - { - return channel <= 0.08 ? 100 * channel / CieConstants.Kappa : MathF.Pow((channel + 0.16F) / 1.16F, 3); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float Compress(float channel) - { - return channel <= CieConstants.Epsilon - ? channel * CieConstants.Kappa / 100F - : MathF.Pow(1.16F * channel, 0.3333333F) - 0.16F; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToRgbConverter.cs deleted file mode 100644 index 34873c1f5f..0000000000 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToRgbConverter.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; - -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce -{ - /// - /// Color converter between LinearRgb and Rgb - /// - internal class LinearRgbToRgbConverter : IColorConversion - { - /// - public Rgb Convert(in LinearRgb input) - { - Vector3 vector = input.Vector; - vector.X = input.WorkingSpace.Companding.Compress(vector.X); - vector.Y = input.WorkingSpace.Companding.Compress(vector.Y); - vector.Z = input.WorkingSpace.Companding.Compress(vector.Z); - - return new Rgb(vector, input.WorkingSpace); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec2020Companding.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec2020Companding.cs deleted file mode 100644 index 0b2b28b2d2..0000000000 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec2020Companding.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce -{ - /// - /// Implements Rec. 2020 companding function (for 12-bits). - /// - /// - /// - /// For 10-bits, companding is identical to - /// - internal class Rec2020Companding : ICompanding - { - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float Expand(float channel) - { - return channel < 0.08145F ? channel / 4.5F : MathF.Pow((channel + 0.0993F) / 1.0993F, 2.222222F); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float Compress(float channel) - { - return channel < 0.0181F ? 4500F * channel : (1.0993F * channel) - 0.0993F; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec709Companding.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec709Companding.cs deleted file mode 100644 index 439cb29018..0000000000 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/Rec709Companding.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce -{ - /// - /// Implements the Rec. 709 companding function - /// - /// - /// http://en.wikipedia.org/wiki/Rec._709 - /// - internal class Rec709Companding : ICompanding - { - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float Expand(float channel) - { - return channel < 0.081F ? channel / 4.5F : MathF.Pow((channel + 0.099F) / 1.099F, 2.222222F); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float Compress(float channel) - { - return channel < 0.018F ? 4500F * channel : (1.099F * channel) - 0.099F; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbToLinearRgbConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbToLinearRgbConverter.cs deleted file mode 100644 index 4cc3d607f6..0000000000 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbToLinearRgbConverter.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; - -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce -{ - /// - /// Color converter between Rgb and LinearRgb - /// - internal class RgbToLinearRgbConverter : IColorConversion - { - /// - public LinearRgb Convert(in Rgb input) - { - Vector3 vector = input.Vector; - vector.X = input.WorkingSpace.Companding.Expand(vector.X); - vector.Y = input.WorkingSpace.Companding.Expand(vector.Y); - vector.Z = input.WorkingSpace.Companding.Expand(vector.Z); - - return new LinearRgb(vector, input.WorkingSpace); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbWorkingSpace.cs deleted file mode 100644 index f4a79c744e..0000000000 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbWorkingSpace.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce -{ - /// - /// Trivial implementation of - /// - internal class RgbWorkingSpace - { - /// - /// Initializes a new instance of the class. - /// - /// The reference white point. - /// The function pair for converting to and back. - /// The chromaticity of the rgb primaries. - public RgbWorkingSpace(CieXyz referenceWhite, ICompanding companding, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) - { - this.WhitePoint = referenceWhite; - this.Companding = companding; - this.ChromaticityCoordinates = chromaticityCoordinates; - } - - /// - /// Gets the reference white point - /// - public CieXyz WhitePoint { get; } - - /// - /// Gets the function pair for converting to and back. - /// - public ICompanding Companding { get; } - - /// - /// Gets the chromaticity of the rgb primaries. - /// - public RgbPrimariesChromaticityCoordinates ChromaticityCoordinates { 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 ==(RgbWorkingSpace left, RgbWorkingSpace right) - { - return Equals(left, 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 !=(RgbWorkingSpace left, RgbWorkingSpace right) - { - return !Equals(left, right); - } - - public override bool Equals(object obj) - { - return obj is RgbWorkingSpace other && this.Equals(other); - } - - public bool Equals(RgbWorkingSpace other) - { - // TODO: Object.Equals for ICompanding will be slow. - return this.WhitePoint.Equals(other.WhitePoint) - && this.ChromaticityCoordinates.Equals(other.ChromaticityCoordinates) - && Equals(this.Companding, other.Companding); - } - - /// - public override int GetHashCode() - { - unchecked - { - int hashCode = this.WhitePoint.GetHashCode(); - hashCode = (hashCode * 397) ^ this.ChromaticityCoordinates.GetHashCode(); - hashCode = (hashCode * 397) ^ (this.Companding?.GetHashCode() ?? 0); - return hashCode; - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/SRgbCompanding.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/SRgbCompanding.cs deleted file mode 100644 index bde1b9123f..0000000000 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/SRgbCompanding.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce -{ - /// - /// Implements sRGB companding - /// - /// - /// For more info see: - /// - /// - /// - internal class SRgbCompanding : ICompanding - { - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float Expand(float channel) - { - return channel <= 0.04045F ? channel / 12.92F : MathF.Pow((channel + 0.055F) / 1.055F, 2.4F); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float Compress(float channel) - { - return channel <= 0.0031308F ? 12.92F * channel : (1.055F * MathF.Pow(channel, 0.416666666666667F)) - 0.055F; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs new file mode 100644 index 0000000000..73aa60b6cb --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/GammaWorkingSpace.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorSpaces.Companding; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// The gamma working space. + /// + public class GammaWorkingSpace : RgbWorkingSpaceBase + { + /// + /// 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; + + /// + /// 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 Expand(float channel) => GammaCompanding.Expand(channel, this.Gamma); + + /// + public override bool Equals(object obj) + { + if (obj is null) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj is GammaWorkingSpace other) + { + return this.Gamma.Equals(other.Gamma) + && this.WhitePoint.Equals(other.WhitePoint) + && this.ChromaticityCoordinates.Equals(other.ChromaticityCoordinates); + } + + return false; + } + + /// + public override int GetHashCode() + { + int hash = base.GetHashCode(); + return HashHelpers.Combine(hash, this.Gamma.GetHashCode()); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs new file mode 100644 index 0000000000..16617ea242 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/LWorkingSpace.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorSpaces.Companding; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// L* working space. + /// + public sealed class LWorkingSpace : RgbWorkingSpaceBase + { + /// + /// 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 Expand(float channel) => LCompanding.Expand(channel); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs new file mode 100644 index 0000000000..9ba1ff8811 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec2020WorkingSpace.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorSpaces.Companding; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Rec. 2020 (ITU-R Recommendation BT.2020F) working space. + /// + public sealed class Rec2020WorkingSpace : RgbWorkingSpaceBase + { + /// + /// 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 Expand(float channel) => Rec2020Companding.Expand(channel); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs new file mode 100644 index 0000000000..88623e958d --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/Rec709WorkingSpace.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorSpaces.Companding; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Rec. 709 (ITU-R Recommendation BT.709) working space. + /// + public sealed class Rec709WorkingSpace : RgbWorkingSpaceBase + { + /// + /// 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 Expand(float channel) => Rec709Companding.Expand(channel); + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpaceBase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpaceBase.cs new file mode 100644 index 0000000000..5a89321c8f --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/RgbWorkingSpaceBase.cs @@ -0,0 +1,83 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// Base class for all implementations of . + /// + public abstract class RgbWorkingSpaceBase + { + /// + /// Initializes a new instance of the class. + /// + /// The reference white point. + /// The chromaticity of the rgb primaries. + protected RgbWorkingSpaceBase(CieXyz referenceWhite, RgbPrimariesChromaticityCoordinates chromaticityCoordinates) + { + this.WhitePoint = referenceWhite; + this.ChromaticityCoordinates = chromaticityCoordinates; + } + + /// + /// Gets the reference white point + /// + public CieXyz WhitePoint { 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); + + /// + /// 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) + { + if (obj is null) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj is RgbWorkingSpaceBase other) + { + return this.WhitePoint.Equals(other.WhitePoint) + && this.ChromaticityCoordinates.Equals(other.ChromaticityCoordinates); + } + + return false; + } + + /// + public override int GetHashCode() + { + int hash = this.WhitePoint.GetHashCode(); + return HashHelpers.Combine(hash, this.ChromaticityCoordinates.GetHashCode()); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs new file mode 100644 index 0000000000..b44db06817 --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/WorkingSpaces/SRgbWorkingSpace.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorSpaces.Companding; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation +{ + /// + /// The sRgb working space. + /// + public sealed class SRgbWorkingSpace : RgbWorkingSpaceBase + { + /// + /// 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 Expand(float channel) => SRgbCompanding.Expand(channel); + } +} diff --git a/src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs b/src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs index 0ab194af2f..9b200b8736 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/VonKriesChromaticAdaptation.cs @@ -1,8 +1,11 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.LmsColorSapce; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces.Conversion { @@ -13,7 +16,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// Transformation described here: /// http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html /// - internal class VonKriesChromaticAdaptation : IChromaticAdaptation + public class VonKriesChromaticAdaptation : IChromaticAdaptation { private readonly CieXyzAndLmsConverter converter; @@ -41,27 +44,54 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion /// Initializes a new instance of the class. /// /// The color converter - public VonKriesChromaticAdaptation(CieXyzAndLmsConverter converter) - { - this.converter = converter; - } + internal VonKriesChromaticAdaptation(CieXyzAndLmsConverter converter) => this.converter = converter; /// - public CieXyz Transform(in CieXyz sourceColor, in CieXyz sourceWhitePoint, in CieXyz targetWhitePoint) + public CieXyz Transform(in CieXyz source, in CieXyz sourceWhitePoint, in CieXyz destinationWhitePoint) { - if (sourceWhitePoint.Equals(targetWhitePoint)) + if (sourceWhitePoint.Equals(destinationWhitePoint)) { - return sourceColor; + return source; } - Lms sourceColorLms = this.converter.Convert(sourceColor); + Lms sourceColorLms = this.converter.Convert(source); Lms sourceWhitePointLms = this.converter.Convert(sourceWhitePoint); - Lms targetWhitePointLms = this.converter.Convert(targetWhitePoint); + Lms targetWhitePointLms = this.converter.Convert(destinationWhitePoint); - var vector = new Vector3(targetWhitePointLms.L / sourceWhitePointLms.L, targetWhitePointLms.M / sourceWhitePointLms.M, targetWhitePointLms.S / sourceWhitePointLms.S); - var targetColorLms = new Lms(Vector3.Multiply(vector, sourceColorLms.Vector)); + Vector3 vector = targetWhitePointLms.ToVector3() / sourceWhitePointLms.ToVector3(); + var targetColorLms = new Lms(Vector3.Multiply(vector, sourceColorLms.ToVector3())); return this.converter.Convert(targetColorLms); } + + /// + public void Transform(Span source, Span destination, CieXyz sourceWhitePoint, in CieXyz destinationWhitePoint, int count) + { + Guard.SpansMustBeSizedAtLeast(source, nameof(source), destination, nameof(destination), count); + + if (sourceWhitePoint.Equals(destinationWhitePoint)) + { + source.CopyTo(destination.Slice(0, 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); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Hsl.cs b/src/ImageSharp/ColorSpaces/Hsl.cs index 8ed4067539..f6e531df35 100644 --- a/src/ImageSharp/ColorSpaces/Hsl.cs +++ b/src/ImageSharp/ColorSpaces/Hsl.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; @@ -11,17 +10,28 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// Represents a Hsl (hue, saturation, lightness) color. /// - internal readonly struct Hsl : IColorVector, IEquatable, IAlmostEquatable + public readonly struct Hsl : IEquatable { + private static readonly Vector3 Min = Vector3.Zero; + private static readonly Vector3 Max = new Vector3(360, 1, 1); + /// - /// Max range used for clamping. + /// Gets the hue component. + /// A value ranging between 0 and 360. /// - private static readonly Vector3 VectorMax = new Vector3(360, 1, 1); + public readonly float H; /// - /// The backing vector for SIMD support. + /// Gets the saturation component. + /// A value ranging between 0 and 1. /// - private readonly Vector3 backingVector; + public readonly float S; + + /// + /// Gets the lightness component. + /// A value ranging between 0 and 1. + /// + public readonly float L; /// /// Initializes a new instance of the struct. @@ -29,7 +39,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// The h hue component. /// The s saturation component. /// The l value (lightness) component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public Hsl(float h, float s, float l) : this(new Vector3(h, s, l)) { @@ -39,118 +49,61 @@ namespace SixLabors.ImageSharp.ColorSpaces /// Initializes a new instance of the struct. /// /// The vector representing the h, s, l components. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public Hsl(Vector3 vector) { - this.backingVector = Vector3.Clamp(vector, Vector3.Zero, VectorMax); - } - - /// - /// Gets the hue component. - /// A value ranging between 0 and 360. - /// - public float H - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.X; + vector = Vector3.Clamp(vector, Min, Max); + this.H = vector.X; + this.S = vector.Y; + this.L = vector.Z; } - /// - /// Gets the saturation component. - /// A value ranging between 0 and 1. - /// - public float S - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Y; - } - - /// - /// Gets the lightness component. - /// A value ranging between 0 and 1. - /// - public float L - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Z; - } - - /// - public Vector3 Vector => this.backingVector; - /// /// Compares two objects for equality. /// /// /// The on the left side of the operand. /// - /// - /// The on the right 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 ==(Hsl left, Hsl right) - { - return left.Equals(right); - } + [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. - /// + /// 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 !=(Hsl left, Hsl right) - { - return !left.Equals(right); - } + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Hsl left, Hsl right) => !left.Equals(right); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public override int GetHashCode() { - return this.backingVector.GetHashCode(); + int hash = this.H.GetHashCode(); + hash = HashHelpers.Combine(hash, this.S.GetHashCode()); + return HashHelpers.Combine(hash, this.L.GetHashCode()); } /// - public override string ToString() - { - return this.Equals(default) - ? "Hsl [ Empty ]" - : $"Hsl [ H={this.H:#0.##}, S={this.S:#0.##}, L={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) - { - return obj is Hsl other && this.Equals(other); - } + public override bool Equals(object obj) => obj is Hsl other && this.Equals(other); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public bool Equals(Hsl other) { - return this.backingVector.Equals(other.backingVector); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool AlmostEquals(Hsl other, float precision) - { - var result = Vector3.Abs(this.backingVector - other.backingVector); - - return result.X <= precision - && result.Y <= precision - && result.Z <= precision; + return this.H.Equals(other.H) + && this.S.Equals(other.S) + && this.L.Equals(other.L); } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Hsv.cs b/src/ImageSharp/ColorSpaces/Hsv.cs index 78a49097ed..631f03d09f 100644 --- a/src/ImageSharp/ColorSpaces/Hsv.cs +++ b/src/ImageSharp/ColorSpaces/Hsv.cs @@ -2,28 +2,36 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; - namespace SixLabors.ImageSharp.ColorSpaces { /// /// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness). /// - internal readonly struct Hsv : IColorVector, IEquatable, IAlmostEquatable + public readonly struct Hsv : IEquatable { + private static readonly Vector3 Min = Vector3.Zero; + private static readonly Vector3 Max = new Vector3(360, 1, 1); + /// - /// Max range used for clamping. + /// Gets the hue component. + /// A value ranging between 0 and 360. /// - private static readonly Vector3 VectorMax = new Vector3(360, 1, 1); + public readonly float H; /// - /// The backing vector for SIMD support. + /// Gets the saturation component. + /// A value ranging between 0 and 1. /// - private readonly Vector3 backingVector; + public readonly float S; + + /// + /// Gets the value (brightness) component. + /// A value ranging between 0 and 1. + /// + public readonly float V; /// /// Initializes a new instance of the struct. @@ -31,7 +39,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// The h hue component. /// The s saturation component. /// The v value (brightness) component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public Hsv(float h, float s, float v) : this(new Vector3(h, s, v)) { @@ -41,168 +49,59 @@ namespace SixLabors.ImageSharp.ColorSpaces /// Initializes a new instance of the struct. /// /// The vector representing the h, s, v components. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public Hsv(Vector3 vector) { - this.backingVector = Vector3.Clamp(vector, Vector3.Zero, VectorMax); - } - - /// - /// Gets the hue component. - /// A value ranging between 0 and 360. - /// - public float H - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.X; - } - - /// - /// Gets the saturation component. - /// A value ranging between 0 and 1. - /// - public float S - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Y; - } - - /// - /// Gets the value (brightness) component. - /// A value ranging between 0 and 1. - /// - public float V - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Z; - } - - /// - public Vector3 Vector => this.backingVector; - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// - /// An instance of . - /// - public static implicit operator Hsv(Rgba32 color) - { - float r = color.R / 255F; - float g = color.G / 255F; - float b = color.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 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); + vector = Vector3.Clamp(vector, Min, Max); + this.H = vector.X; + this.S = vector.Y; + this.V = vector.Z; } /// /// Compares two objects for equality. /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// + /// 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 ==(Hsv left, Hsv right) - { - return left.Equals(right); - } + [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. - /// + /// 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 !=(Hsv left, Hsv right) - { - return !left.Equals(right); - } + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Hsv left, Hsv right) => !left.Equals(right); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public override int GetHashCode() { - return this.backingVector.GetHashCode(); + int hash = this.H.GetHashCode(); + hash = HashHelpers.Combine(hash, this.S.GetHashCode()); + return HashHelpers.Combine(hash, this.V.GetHashCode()); } /// - public override string ToString() - { - return this.Equals(default) - ? "Hsv [ Empty ]" - : $"Hsv [ H={this.H:#0.##}, S={this.S:#0.##}, V={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) - { - return obj is Hsv other && this.Equals(other); - } + public override bool Equals(object obj) => obj is Hsv other && this.Equals(other); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public bool Equals(Hsv other) { - return this.backingVector.Equals(other.backingVector); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool AlmostEquals(Hsv other, float precision) - { - var result = Vector3.Abs(this.backingVector - other.backingVector); - - return result.X <= precision - && result.Y <= precision - && result.Z <= precision; + return this.H.Equals(other.H) + && this.S.Equals(other.S) + && this.V.Equals(other.V); } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/HunterLab.cs b/src/ImageSharp/ColorSpaces/HunterLab.cs index 44f31bc295..f4fa29d314 100644 --- a/src/ImageSharp/ColorSpaces/HunterLab.cs +++ b/src/ImageSharp/ColorSpaces/HunterLab.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; @@ -12,7 +11,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// Represents an Hunter LAB color. /// /// - internal readonly struct HunterLab : IColorVector, IEquatable, IAlmostEquatable + public readonly struct HunterLab : IEquatable { /// /// D50 standard illuminant. @@ -21,9 +20,27 @@ namespace SixLabors.ImageSharp.ColorSpaces public static readonly CieXyz DefaultWhitePoint = Illuminants.C; /// - /// The backing vector for SIMD support. + /// Gets the lightness dimension. + /// A value usually ranging between 0 (black), 100 (diffuse white) or higher (specular white). + /// + public readonly float L; + + /// + /// Gets the a color component. + /// A value usually ranging from -100 to 100. Negative is green, positive magenta. + /// + public readonly float A; + + /// + /// Gets the b color component. + /// A value usually ranging from -100 to 100. Negative is blue, positive is yellow + /// + public readonly float B; + + /// + /// Gets the reference white point of this color /// - private readonly Vector3 backingVector; + public readonly CieXyz WhitePoint; /// /// Initializes a new instance of the struct. @@ -32,7 +49,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// The a (green - magenta) component. /// The b (blue - yellow) component. /// Uses as white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public HunterLab(float l, float a, float b) : this(new Vector3(l, a, b), DefaultWhitePoint) { @@ -45,7 +62,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// The a (green - magenta) component. /// The b (blue - yellow) component. /// The reference white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public HunterLab(float l, float a, float b, CieXyz whitePoint) : this(new Vector3(l, a, b), whitePoint) { @@ -56,7 +73,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// The vector representing the l, a, b components. /// Uses as white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public HunterLab(Vector3 vector) : this(vector, DefaultWhitePoint) { @@ -67,126 +84,61 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// The vector representing the l a b components. /// The reference white point. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public HunterLab(Vector3 vector, CieXyz whitePoint) - : this() { - this.backingVector = vector; + // 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 reference white point of this color - /// - public CieXyz WhitePoint { get; } - - /// - /// Gets the lightness dimension. - /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). - /// - public float L - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.X; - } - - /// - /// Gets the a color component. - /// A value ranging from -100 to 100. Negative is green, positive magenta. - /// - public float A - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Y; - } - - /// - /// Gets the b color component. - /// A value ranging from -100 to 100. Negative is blue, positive is yellow - /// - public float B - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Z; - } - - /// - public Vector3 Vector => this.backingVector; - /// /// Compares two objects for equality. /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// + /// 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) - { - return left.Equals(right); - } + 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. - /// + /// 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 !=(HunterLab left, HunterLab right) - { - return !left.Equals(right); - } + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(HunterLab left, HunterLab right) => !left.Equals(right); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public override int GetHashCode() { - return HashHelpers.Combine(this.WhitePoint.GetHashCode(), this.backingVector.GetHashCode()); + int hash = this.L.GetHashCode(); + hash = HashHelpers.Combine(hash, this.A.GetHashCode()); + hash = HashHelpers.Combine(hash, this.B.GetHashCode()); + return HashHelpers.Combine(hash, this.WhitePoint.GetHashCode()); } /// - public override string ToString() - { - return this.Equals(default) - ? "HunterLab [Empty]" - : $"HunterLab [ L={this.L:#0.##}, A={this.A:#0.##}, B={this.B:#0.##}]"; - } + public override string ToString() => FormattableString.Invariant($"HunterLab({this.L:#0.##}, {this.A:#0.##}, {this.B:#0.##})"); /// - public override bool Equals(object obj) - { - return obj is HunterLab other && this.Equals(other); - } + public override bool Equals(object obj) => obj is HunterLab other && this.Equals(other); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public bool Equals(HunterLab other) { - return this.backingVector.Equals(other.backingVector) + return this.L.Equals(other.L) + && this.A.Equals(other.A) + && this.B.Equals(other.B) && this.WhitePoint.Equals(other.WhitePoint); } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool AlmostEquals(HunterLab other, float precision) - { - var result = Vector3.Abs(this.backingVector - other.backingVector); - - return this.WhitePoint.Equals(other.WhitePoint) - && result.X <= precision - && result.Y <= precision - && result.Z <= precision; - } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/IAlmostEquatable.cs b/src/ImageSharp/ColorSpaces/IAlmostEquatable.cs deleted file mode 100644 index 08c2dafbc6..0000000000 --- a/src/ImageSharp/ColorSpaces/IAlmostEquatable.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -namespace SixLabors.ImageSharp.ColorSpaces -{ - /// - /// Defines a generalized method that a value type or class implements to create - /// a type-specific method for determining approximate equality of instances. - /// - /// The type of objects to compare. - /// The object specifying the type to specify precision with. - internal interface IAlmostEquatable - where TPrecision : struct, IComparable - { - /// - /// Indicates whether the current object is equal to another object of the same type - /// when compared to the specified precision level. - /// - /// An object to compare with this object. - /// The object specifying the level of precision. - /// - /// true if the current object is equal to the other parameter; otherwise, false. - /// - bool AlmostEquals(TPixel other, TPrecision precision); - } -} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/IColorVector.cs b/src/ImageSharp/ColorSpaces/IColorVector.cs deleted file mode 100644 index 85c040b868..0000000000 --- a/src/ImageSharp/ColorSpaces/IColorVector.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; - -namespace SixLabors.ImageSharp.ColorSpaces -{ - /// - /// Color represented as a vector in its color space - /// - internal interface IColorVector - { - /// - /// Gets the vector representation of the color - /// - Vector3 Vector { get; } - } -} \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/ICompanding.cs b/src/ImageSharp/ColorSpaces/ICompanding.cs deleted file mode 100644 index 053c8d17b2..0000000000 --- a/src/ImageSharp/ColorSpaces/ICompanding.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce; - -namespace SixLabors.ImageSharp.ColorSpaces -{ - /// - /// Pair of companding functions for . - /// Used for conversion to and backwards. - /// See also: - /// - internal interface ICompanding - { - /// - /// Expands a companded channel to its linear equivalent with respect to the energy. - /// - /// - /// For more info see: - /// - /// - /// The channel value - /// The linear channel value - 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 nonlinear channel value - float Compress(float channel); - } -} diff --git a/src/ImageSharp/ColorSpaces/Illuminants.cs b/src/ImageSharp/ColorSpaces/Illuminants.cs index 85c4063bf4..ed385e02cd 100644 --- a/src/ImageSharp/ColorSpaces/Illuminants.cs +++ b/src/ImageSharp/ColorSpaces/Illuminants.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// Descriptions taken from: /// http://en.wikipedia.org/wiki/Standard_illuminant /// - internal static class Illuminants + public static class Illuminants { /// /// Incandescent / Tungsten diff --git a/src/ImageSharp/ColorSpaces/LinearRgb.cs b/src/ImageSharp/ColorSpaces/LinearRgb.cs index aaf05e0358..ec6d18be2b 100644 --- a/src/ImageSharp/ColorSpaces/LinearRgb.cs +++ b/src/ImageSharp/ColorSpaces/LinearRgb.cs @@ -4,24 +4,45 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; namespace SixLabors.ImageSharp.ColorSpaces { /// - /// Represents an linear Rgb color with specified working space + /// Represents an linear Rgb color with specified working space /// - internal readonly struct LinearRgb : IColorVector, IEquatable, IAlmostEquatable + 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; + public static readonly RgbWorkingSpaceBase DefaultWorkingSpace = RgbWorkingSpaces.SRgb; + + /// + /// Gets the red component. + /// A value usually ranging between 0 and 1. + /// + public readonly float R; + + /// + /// Gets the green component. + /// A value usually ranging between 0 and 1. + /// + public readonly float G; + + /// + /// Gets the blue component. + /// A value usually ranging between 0 and 1. + /// + public readonly float B; /// - /// The backing vector for SIMD support. + /// Gets the LinearRgb color space /// - private readonly Vector3 backingVector; + public readonly RgbWorkingSpaceBase WorkingSpace; /// /// Initializes a new instance of the struct. @@ -29,9 +50,9 @@ namespace SixLabors.ImageSharp.ColorSpaces /// 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(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public LinearRgb(float r, float g, float b) - : this(new Vector3(r, g, b)) + : this(r, g, b, DefaultWorkingSpace) { } @@ -42,8 +63,8 @@ namespace SixLabors.ImageSharp.ColorSpaces /// The green component ranging between 0 and 1. /// The blue component ranging between 0 and 1. /// The rgb working space. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public LinearRgb(float r, float g, float b, RgbWorkingSpace workingSpace) + [MethodImpl(InliningOptions.ShortMethod)] + public LinearRgb(float r, float g, float b, RgbWorkingSpaceBase workingSpace) : this(new Vector3(r, g, b), workingSpace) { } @@ -52,7 +73,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// Initializes a new instance of the struct. /// /// The vector representing the r, g, b components. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public LinearRgb(Vector3 vector) : this(vector, DefaultWorkingSpace) { @@ -63,126 +84,68 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// The vector representing the r, g, b components. /// The LinearRgb working space. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public LinearRgb(Vector3 vector, RgbWorkingSpace workingSpace) - : this() + [MethodImpl(InliningOptions.ShortMethod)] + public LinearRgb(Vector3 vector, RgbWorkingSpaceBase workingSpace) { // Clamp to 0-1 range. - this.backingVector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One); + 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 float R - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.X; - } - - /// - /// Gets the green component. - /// A value usually ranging between 0 and 1. - /// - public float G - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Y; - } - - /// - /// Gets the blue component. - /// A value usually ranging between 0 and 1. - /// - public float B - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Z; - } - - /// - /// Gets the LinearRgb color space - /// - public RgbWorkingSpace WorkingSpace { get; } - - /// - public Vector3 Vector => this.backingVector; - /// /// Compares two objects for equality. /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// + /// 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 ==(LinearRgb left, LinearRgb right) - { - return left.Equals(right); - } + [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. - /// + /// 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 !=(LinearRgb left, LinearRgb right) - { - return !left.Equals(right); - } + [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 Vector3(this.R, this.G, this.B); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public override int GetHashCode() { - return this.backingVector.GetHashCode(); + int hash = this.R.GetHashCode(); + hash = HashHelpers.Combine(hash, this.G.GetHashCode()); + return HashHelpers.Combine(hash, this.B.GetHashCode()); } /// - public override string ToString() - { - return this.Equals(default) - ? "LinearRgb [ Empty ]" - : $"LinearRgb [ R={this.R:#0.##}, G={this.G:#0.##}, B={this.B:#0.##} ]"; - } + public override string ToString() => FormattableString.Invariant($"LinearRgb({this.R:#0.##}, {this.G:#0.##}, {this.B:#0.##})"); /// - public override bool Equals(object obj) - { - return obj is LinearRgb other && this.Equals(other); - } + public override bool Equals(object obj) => obj is LinearRgb other && this.Equals(other); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public bool Equals(LinearRgb other) { - return this.backingVector.Equals(other.backingVector); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool AlmostEquals(LinearRgb other, float precision) - { - var result = Vector3.Abs(this.backingVector - other.backingVector); - - return result.X <= precision - && result.Y <= precision - && result.Z <= precision; + return this.R.Equals(other.R) + && this.G.Equals(other.G) + && this.B.Equals(other.B); } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Lms.cs b/src/ImageSharp/ColorSpaces/Lms.cs index 9b0331e0bc..0a8b7aa7b9 100644 --- a/src/ImageSharp/ColorSpaces/Lms.cs +++ b/src/ImageSharp/ColorSpaces/Lms.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; @@ -13,12 +12,25 @@ namespace SixLabors.ImageSharp.ColorSpaces /// named after their responsivity (sensitivity) at long, medium and short wavelengths. /// /// - internal readonly struct Lms : IColorVector, IEquatable, IAlmostEquatable + public readonly struct Lms : IEquatable { /// - /// The backing vector for SIMD support. + /// Gets the L long component. + /// A value usually ranging between -1 and 1. + /// + public readonly float L; + + /// + /// Gets the M medium component. + /// A value usually ranging between -1 and 1. /// - private readonly Vector3 backingVector; + public readonly float M; + + /// + /// Gets the S short component. + /// A value usually ranging between -1 and 1. + /// + public readonly float S; /// /// Initializes a new instance of the struct. @@ -26,7 +38,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// L represents the responsivity at long wavelengths. /// M represents the responsivity at medium wavelengths. /// S represents the responsivity at short wavelengths. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public Lms(float l, float m, float s) : this(new Vector3(l, m, s)) { @@ -36,119 +48,65 @@ namespace SixLabors.ImageSharp.ColorSpaces /// Initializes a new instance of the struct. /// /// The vector representing the l, m, s components. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public Lms(Vector3 vector) - : this() - { - // Not clamping as documentation about this space seems to indicate "usual" ranges - this.backingVector = vector; - } - - /// - /// Gets the L long component. - /// A value usually ranging between -1 and 1. - /// - public float L - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.X; - } - - /// - /// Gets the M medium component. - /// A value usually ranging between -1 and 1. - /// - public float M { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Y; + // 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 S short component. - /// A value usually ranging between -1 and 1. - /// - public float S - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Z; - } - - /// - public Vector3 Vector => this.backingVector; - /// /// Compares two objects for equality. /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// + /// 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 ==(Lms left, Lms right) - { - return left.Equals(right); - } + [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. - /// + /// 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 !=(Lms left, Lms right) - { - return !left.Equals(right); - } + [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 Vector3(this.L, this.M, this.S); /// public override int GetHashCode() { - return this.backingVector.GetHashCode(); + int hash = this.L.GetHashCode(); + hash = HashHelpers.Combine(hash, this.M.GetHashCode()); + return HashHelpers.Combine(hash, this.S.GetHashCode()); } /// - public override string ToString() - { - return this.Equals(default) - ? "Lms [ Empty ]" - : $"Lms [ L={this.L:#0.##}, M={this.M:#0.##}, S={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) - { - return obj is Lms other && this.Equals(other); - } + public override bool Equals(object obj) => obj is Lms other && this.Equals(other); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public bool Equals(Lms other) { - return this.backingVector.Equals(other.backingVector); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool AlmostEquals(Lms other, float precision) - { - var result = Vector3.Abs(this.backingVector - other.backingVector); - - return result.X <= precision - && result.Y <= precision - && result.Z <= precision; + return this.L.Equals(other.L) + && this.M.Equals(other.M) + && this.S.Equals(other.S); } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Rgb.cs b/src/ImageSharp/ColorSpaces/Rgb.cs index ccfa1760fd..97fafbaf37 100644 --- a/src/ImageSharp/ColorSpaces/Rgb.cs +++ b/src/ImageSharp/ColorSpaces/Rgb.cs @@ -4,25 +4,46 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.ColorSpaces { /// - /// Represents an RGB color with specified working space + /// Represents an RGB color with specified working space. /// - internal readonly struct Rgb : IColorVector, IEquatable, IAlmostEquatable + public readonly struct Rgb : IEquatable { + private static readonly Vector3 Min = Vector3.Zero; + private static readonly Vector3 Max = Vector3.One; + /// /// The default rgb working space /// - public static readonly RgbWorkingSpace DefaultWorkingSpace = RgbWorkingSpaces.SRgb; + public static readonly RgbWorkingSpaceBase DefaultWorkingSpace = RgbWorkingSpaces.SRgb; /// - /// The backing vector for SIMD support. + /// Gets the red component. + /// A value usually ranging between 0 and 1. + /// + public readonly float R; + + /// + /// Gets the green component. + /// A value usually ranging between 0 and 1. + /// + public readonly float G; + + /// + /// Gets the blue component. + /// A value usually ranging between 0 and 1. + /// + public readonly float B; + + /// + /// Gets the Rgb color space /// - private readonly Vector3 backingVector; + public readonly RgbWorkingSpaceBase WorkingSpace; /// /// Initializes a new instance of the struct. @@ -30,9 +51,9 @@ namespace SixLabors.ImageSharp.ColorSpaces /// 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(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public Rgb(float r, float g, float b) - : this(new Vector3(r, g, b)) + : this(r, g, b, DefaultWorkingSpace) { } @@ -43,8 +64,8 @@ namespace SixLabors.ImageSharp.ColorSpaces /// The green component ranging between 0 and 1. /// The blue component ranging between 0 and 1. /// The rgb working space. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgb(float r, float g, float b, RgbWorkingSpace workingSpace) + [MethodImpl(InliningOptions.ShortMethod)] + public Rgb(float r, float g, float b, RgbWorkingSpaceBase workingSpace) : this(new Vector3(r, g, b), workingSpace) { } @@ -53,7 +74,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// Initializes a new instance of the struct. /// /// The vector representing the r, g, b components. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public Rgb(Vector3 vector) : this(vector, DefaultWorkingSpace) { @@ -64,68 +85,33 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// The vector representing the r, g, b components. /// The rgb working space. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgb(Vector3 vector, RgbWorkingSpace workingSpace) - : this() + [MethodImpl(InliningOptions.ShortMethod)] + public Rgb(Vector3 vector, RgbWorkingSpaceBase workingSpace) { - // Clamp to 0-1 range. - this.backingVector = Vector3.Clamp(vector, Vector3.Zero, Vector3.One); + 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 float R - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.X; - } - - /// - /// Gets the green component. - /// A value usually ranging between 0 and 1. - /// - public float G - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Y; - } - - /// - /// Gets the blue component. - /// A value usually ranging between 0 and 1. - /// - public float B - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Z; - } - - /// - /// Gets the Rgb color space + /// Allows the implicit conversion of an instance of to a + /// . /// - public RgbWorkingSpace WorkingSpace { get; } - - /// - public Vector3 Vector => this.backingVector; + /// The instance of to convert. + /// An instance of . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Rgb(Rgb24 color) => new Rgb(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(MethodImplOptions.AggressiveInlining)] - public static implicit operator Rgb(Rgba32 color) - { - return new Rgb(color.R / 255F, color.G / 255F, color.B / 255F); - } + /// The instance of to convert. + /// An instance of . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Rgb(Rgba32 color) => new Rgb(color.R / 255F, color.G / 255F, color.B / 255F); /// /// Compares two objects for equality. @@ -139,66 +125,48 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// True if the current left is equal to the parameter; otherwise, false. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rgb left, Rgb right) - { - return left.Equals(right); - } + [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. - /// + /// 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 !=(Rgb left, Rgb right) - { - return !left.Equals(right); - } + [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 Vector3(this.R, this.G, this.B); /// public override int GetHashCode() { - return this.backingVector.GetHashCode(); + int hash = this.R.GetHashCode(); + hash = HashHelpers.Combine(hash, this.G.GetHashCode()); + return HashHelpers.Combine(hash, this.B.GetHashCode()); } /// - public override string ToString() - { - return this.Equals(default) - ? "Rgb [ Empty ]" - : $"Rgb [ R={this.R:#0.##}, G={this.G:#0.##}, B={this.B:#0.##} ]"; - } + public override string ToString() => FormattableString.Invariant($"Rgb({this.R:#0.##}, {this.G:#0.##}, {this.B:#0.##})"); /// - public override bool Equals(object obj) - { - return obj is Rgb other && this.Equals(other); - } + public override bool Equals(object obj) => obj is Rgb other && this.Equals(other); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public bool Equals(Rgb other) { - return this.backingVector.Equals(other.backingVector); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool AlmostEquals(Rgb other, float precision) - { - var result = Vector3.Abs(this.backingVector - other.backingVector); - - return result.X <= precision - && result.Y <= precision - && result.Z <= precision; + return this.R.Equals(other.R) + && this.G.Equals(other.G) + && this.B.Equals(other.B); } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs b/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs index 978a35725f..ee3822c152 100644 --- a/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs +++ b/src/ImageSharp/ColorSpaces/RgbWorkingSpaces.cs @@ -1,7 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce; +using SixLabors.ImageSharp.ColorSpaces.Companding; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.ColorSpaces @@ -10,7 +11,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// Chromaticity coordinates taken from: /// /// - internal static class RgbWorkingSpaces + public static class RgbWorkingSpaces { /// /// sRgb working space. @@ -19,97 +20,97 @@ namespace SixLabors.ImageSharp.ColorSpaces /// Uses proper companding function, according to: /// /// - public static readonly RgbWorkingSpace SRgb = new RgbWorkingSpace(Illuminants.D65, new SRgbCompanding(), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.3000F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); + public static readonly RgbWorkingSpaceBase 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 RgbWorkingSpace(Illuminants.D65, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.3000F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); + public static readonly RgbWorkingSpaceBase 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 RgbWorkingSpace(Illuminants.D65, new Rec709Companding(), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.64F, 0.33F), new CieXyChromaticityCoordinates(0.30F, 0.60F), new CieXyChromaticityCoordinates(0.15F, 0.06F))); + public static readonly RgbWorkingSpaceBase 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 RgbWorkingSpace(Illuminants.D65, new Rec2020Companding(), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.708F, 0.292F), new CieXyChromaticityCoordinates(0.170F, 0.797F), new CieXyChromaticityCoordinates(0.131F, 0.046F))); + public static readonly RgbWorkingSpaceBase 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 RgbWorkingSpace(Illuminants.D50, new LCompanding(), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6700F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1400F, 0.0800F))); + public static readonly RgbWorkingSpaceBase 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 RgbWorkingSpace(Illuminants.D65, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); + public static readonly RgbWorkingSpaceBase 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 RgbWorkingSpace(Illuminants.D65, new GammaCompanding(1.8F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6250F, 0.3400F), new CieXyChromaticityCoordinates(0.2800F, 0.5950F), new CieXyChromaticityCoordinates(0.1550F, 0.0700F))); + public static readonly RgbWorkingSpaceBase 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 RgbWorkingSpace(Illuminants.D50, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7347F, 0.2653F), new CieXyChromaticityCoordinates(0.2150F, 0.7750F), new CieXyChromaticityCoordinates(0.1300F, 0.0350F))); + public static readonly RgbWorkingSpaceBase 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 RgbWorkingSpace(Illuminants.D50, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6888F, 0.3112F), new CieXyChromaticityCoordinates(0.1986F, 0.7551F), new CieXyChromaticityCoordinates(0.1265F, 0.0352F))); + public static readonly RgbWorkingSpaceBase 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 RgbWorkingSpace(Illuminants.D65, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2800F, 0.6500F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); + public static readonly RgbWorkingSpaceBase 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 RgbWorkingSpace(Illuminants.E, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7350F, 0.2650F), new CieXyChromaticityCoordinates(0.2740F, 0.7170F), new CieXyChromaticityCoordinates(0.1670F, 0.0090F))); + public static readonly RgbWorkingSpaceBase 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 RgbWorkingSpace(Illuminants.D50, new GammaCompanding(1.8F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6300F, 0.3400F), new CieXyChromaticityCoordinates(0.2950F, 0.6050F), new CieXyChromaticityCoordinates(0.1500F, 0.0750F))); + public static readonly RgbWorkingSpaceBase 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 RgbWorkingSpace(Illuminants.D50, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6960F, 0.3000F), new CieXyChromaticityCoordinates(0.2150F, 0.7650F), new CieXyChromaticityCoordinates(0.1300F, 0.0350F))); + public static readonly RgbWorkingSpaceBase 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 RgbWorkingSpace(Illuminants.D50, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6950F, 0.3050F), new CieXyChromaticityCoordinates(0.2600F, 0.7000F), new CieXyChromaticityCoordinates(0.1100F, 0.0050F))); + public static readonly RgbWorkingSpaceBase 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 RgbWorkingSpace(Illuminants.C, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6700F, 0.3300F), new CieXyChromaticityCoordinates(0.2100F, 0.7100F), new CieXyChromaticityCoordinates(0.1400F, 0.0800F))); + public static readonly RgbWorkingSpaceBase 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 RgbWorkingSpace(Illuminants.D65, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6400F, 0.3300F), new CieXyChromaticityCoordinates(0.2900F, 0.6000F), new CieXyChromaticityCoordinates(0.1500F, 0.0600F))); + public static readonly RgbWorkingSpaceBase 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 RgbWorkingSpace(Illuminants.D50, new GammaCompanding(1.8F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7347F, 0.2653F), new CieXyChromaticityCoordinates(0.1596F, 0.8404F), new CieXyChromaticityCoordinates(0.0366F, 0.0001F))); + public static readonly RgbWorkingSpaceBase 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 RgbWorkingSpace(Illuminants.D65, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.6300F, 0.3400F), new CieXyChromaticityCoordinates(0.3100F, 0.5950F), new CieXyChromaticityCoordinates(0.1550F, 0.0700F))); + public static readonly RgbWorkingSpaceBase 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 RgbWorkingSpace(Illuminants.D50, new GammaCompanding(2.2F), new RgbPrimariesChromaticityCoordinates(new CieXyChromaticityCoordinates(0.7350F, 0.2650F), new CieXyChromaticityCoordinates(0.1150F, 0.8260F), new CieXyChromaticityCoordinates(0.1570F, 0.0180F))); + public static readonly RgbWorkingSpaceBase 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))); } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/YCbCr.cs b/src/ImageSharp/ColorSpaces/YCbCr.cs index 00533c6991..6aa191c2de 100644 --- a/src/ImageSharp/ColorSpaces/YCbCr.cs +++ b/src/ImageSharp/ColorSpaces/YCbCr.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; @@ -13,17 +12,28 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// /// - internal readonly struct YCbCr : IColorVector, IEquatable, IAlmostEquatable + public readonly struct YCbCr : IEquatable { + private static readonly Vector3 Min = Vector3.Zero; + private static readonly Vector3 Max = new Vector3(255); + + /// + /// Gets the Y luminance component. + /// A value ranging between 0 and 255. + /// + public readonly float Y; + /// - /// Vector which is used in clamping to the max value. + /// Gets the Cb chroma component. + /// A value ranging between 0 and 255. /// - private static readonly Vector3 VectorMax = new Vector3(255F); + public readonly float Cb; /// - /// The backing vector for SIMD support. + /// Gets the Cr chroma component. + /// A value ranging between 0 and 255. /// - private readonly Vector3 backingVector; + public readonly float Cr; /// /// Initializes a new instance of the struct. @@ -31,7 +41,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// The y luminance component. /// The cb chroma component. /// The cr chroma component. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public YCbCr(float y, float cb, float cr) : this(new Vector3(y, cb, cr)) { @@ -41,117 +51,58 @@ namespace SixLabors.ImageSharp.ColorSpaces /// Initializes a new instance of the struct. /// /// The vector representing the y, cb, cr components. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public YCbCr(Vector3 vector) { - this.backingVector = Vector3.Clamp(vector, Vector3.Zero, VectorMax); - } - - /// - /// Gets the Y luminance component. - /// A value ranging between 0 and 255. - /// - public float Y - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.X; - } - - /// - /// Gets the Cb chroma component. - /// A value ranging between 0 and 255. - /// - public float Cb - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Y; - } - - /// - /// Gets the Cr chroma component. - /// A value ranging between 0 and 255. - /// - public float Cr - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.backingVector.Z; + vector = Vector3.Clamp(vector, Min, Max); + this.Y = vector.X; + this.Cb = vector.Y; + this.Cr = vector.Z; } - /// - public Vector3 Vector => this.backingVector; - /// /// Compares two objects for equality. /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// + /// 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) - { - return left.Equals(right); - } + 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. - /// + /// 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 !=(YCbCr left, YCbCr right) - { - return !left.Equals(right); - } + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(YCbCr left, YCbCr right) => !left.Equals(right); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public override int GetHashCode() { - return this.backingVector.GetHashCode(); + int hash = this.Y.GetHashCode(); + hash = HashHelpers.Combine(hash, this.Cb.GetHashCode()); + return HashHelpers.Combine(hash, this.Cr.GetHashCode()); } /// - public override string ToString() - { - return this.Equals(default) - ? "YCbCr [ Empty ]" - : $"YCbCr [ Y={this.Y}, Cb={this.Cb}, Cr={this.Cr} ]"; - } + public override string ToString() => FormattableString.Invariant($"YCbCr({this.Y}, {this.Cb}, {this.Cr})"); /// - public override bool Equals(object obj) - { - return obj is YCbCr other && this.Equals(other); - } + public override bool Equals(object obj) => obj is YCbCr other && this.Equals(other); /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public bool Equals(YCbCr other) { - return this.backingVector.Equals(other.backingVector); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool AlmostEquals(YCbCr other, float precision) - { - var result = Vector3.Abs(this.backingVector - other.backingVector); - - return result.X <= precision - && result.Y <= precision - && result.Z <= precision; + return this.Y.Equals(other.Y) + && this.Cb.Equals(other.Cb) + && this.Cr.Equals(other.Cr); } } } \ No newline at end of file diff --git a/src/ImageSharp/Common/Extensions/ComparableExtensions.cs b/src/ImageSharp/Common/Extensions/ComparableExtensions.cs index d6dade7703..3c8570a2a4 100644 --- a/src/ImageSharp/Common/Extensions/ComparableExtensions.cs +++ b/src/ImageSharp/Common/Extensions/ComparableExtensions.cs @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp /// /// The representing the clamped value. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public static byte Clamp(this byte value, byte min, byte max) { // Order is important here as someone might set min to higher than max. @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp /// /// The representing the clamped value. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public static uint Clamp(this uint value, uint min, uint max) { if (value >= max) @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp /// /// The representing the clamped value. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public static int Clamp(this int value, int min, int max) { if (value >= max) @@ -96,7 +96,7 @@ namespace SixLabors.ImageSharp /// /// The representing the clamped value. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public static float Clamp(this float value, float min, float max) { if (value >= max) @@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp /// /// The representing the clamped value. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public static double Clamp(this double value, double min, double max) { if (value >= max) @@ -136,38 +136,5 @@ namespace SixLabors.ImageSharp return value; } - - /// - /// Converts an to a first restricting the value between the - /// minimum and maximum allowable ranges. - /// - /// The this method extends. - /// The - public static byte ToByte(this int value) - { - return (byte)value.Clamp(0, 255); - } - - /// - /// Converts an to a first restricting the value between the - /// minimum and maximum allowable ranges. - /// - /// The this method extends. - /// The - public static byte ToByte(this float value) - { - return (byte)value.Clamp(0, 255); - } - - /// - /// Converts an to a first restricting the value between the - /// minimum and maximum allowable ranges. - /// - /// The this method extends. - /// The - public static byte ToByte(this double value) - { - return (byte)value.Clamp(0, 255); - } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Extensions/EncoderExtensions.cs b/src/ImageSharp/Common/Extensions/EncoderExtensions.cs new file mode 100644 index 0000000000..82899863c9 --- /dev/null +++ b/src/ImageSharp/Common/Extensions/EncoderExtensions.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +#if !NETCOREAPP2_1 +using System; +using System.Text; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + internal static unsafe class EncoderExtensions + { + /// + /// Gets a string from the provided buffer data. + /// + /// The encoding. + /// The buffer. + /// The string. + public static string GetString(this Encoding encoding, ReadOnlySpan buffer) + { + fixed (byte* bytes = buffer) + { + return encoding.GetString(bytes, buffer.Length); + } + } + } +} +#endif \ No newline at end of file diff --git a/src/ImageSharp/Common/Extensions/Vector4Extensions.cs b/src/ImageSharp/Common/Extensions/Vector4Extensions.cs deleted file mode 100644 index b88c229c5d..0000000000 --- a/src/ImageSharp/Common/Extensions/Vector4Extensions.cs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp -{ - /// - /// Extension methods for the struct. - /// - internal static class Vector4Extensions - { - /// - /// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact. - /// - /// The to premultiply - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Premultiply(this Vector4 source) - { - float w = source.W; - Vector4 premultiplied = source * w; - premultiplied.W = w; - return premultiplied; - } - - /// - /// Reverses the result of premultiplying a vector via . - /// - /// The to premultiply - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 UnPremultiply(this Vector4 source) - { - float w = source.W; - Vector4 unpremultiplied = source / w; - unpremultiplied.W = w; - return unpremultiplied; - } - - /// - /// Compresses a linear color signal to its sRGB equivalent. - /// - /// - /// - /// The whose signal to compress. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Compress(this Vector4 linear) - { - // TODO: Is there a faster way to do this? - return new Vector4(Compress(linear.X), Compress(linear.Y), Compress(linear.Z), linear.W); - } - - /// - /// Expands an sRGB color signal to its linear equivalent. - /// - /// - /// - /// The whose signal to expand. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 Expand(this Vector4 gamma) - { - // TODO: Is there a faster way to do this? - return new Vector4(Expand(gamma.X), Expand(gamma.Y), Expand(gamma.Z), gamma.W); - } - - /// - /// Gets the compressed sRGB value from an linear signal. - /// - /// - /// - /// The signal value to compress. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float Compress(float signal) - { - if (signal <= 0.0031308F) - { - return signal * 12.92F; - } - - return (1.055F * MathF.Pow(signal, 0.41666666F)) - 0.055F; - } - - /// - /// Gets the expanded linear value from an sRGB signal. - /// - /// - /// - /// The signal value to expand. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float Expand(float signal) - { - if (signal <= 0.04045F) - { - return signal / 12.92F; - } - - return MathF.Pow((signal + 0.055F) / 1.055F, 2.4F); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs new file mode 100644 index 0000000000..2e700c9d67 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs @@ -0,0 +1,124 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for . + /// + internal static class DenseMatrixUtils + { + /// + /// Computes the sum of vectors in weighted by the kernel weight values. + /// + /// The pixel format. + /// The dense matrix. + /// The source frame. + /// The target row. + /// The current row. + /// The current column. + /// The maximum working area row. + /// The maximum working area column. + /// The column offset to apply to source sampling. + public static void Convolve( + in DenseMatrix matrix, + Buffer2D sourcePixels, + Span targetRow, + int row, + int column, + int maxRow, + int maxColumn, + int offsetColumn) + where TPixel : struct, IPixel + { + Vector4 vector = default; + int matrixHeight = matrix.Rows; + int matrixWidth = matrix.Columns; + int radiusY = matrixHeight >> 1; + int radiusX = matrixWidth >> 1; + int sourceOffsetColumnBase = column + offsetColumn; + + for (int y = 0; y < matrixHeight; y++) + { + int offsetY = (row + y - radiusY).Clamp(0, maxRow); + Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); + + for (int x = 0; x < matrixWidth; x++) + { + int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(offsetColumn, maxColumn); + var currentColor = sourceRowSpan[offsetX].ToVector4(); + Vector4Utils.Premultiply(ref currentColor); + + vector += matrix[y, x] * currentColor; + } + } + + ref Vector4 target = ref targetRow[column]; + vector.W = target.W; + Vector4Utils.UnPremultiply(ref vector); + target = vector; + } + + /// + /// Computes the sum of vectors in weighted by the two kernel weight values. + /// + /// The pixel format. + /// The vertical dense matrix. + /// The horizontal dense matrix. + /// The source frame. + /// The target row. + /// The current row. + /// The current column. + /// The maximum working area row. + /// The maximum working area column. + /// The column offset to apply to source sampling. + public static void Convolve2D( + in DenseMatrix matrixY, + in DenseMatrix matrixX, + Buffer2D sourcePixels, + Span targetRow, + int row, + int column, + int maxRow, + int maxColumn, + int offsetColumn) + where TPixel : struct, IPixel + { + Vector4 vectorY = default; + Vector4 vectorX = default; + int matrixHeight = matrixY.Rows; + int matrixWidth = matrixY.Columns; + int radiusY = matrixHeight >> 1; + int radiusX = matrixWidth >> 1; + int sourceOffsetColumnBase = column + offsetColumn; + + for (int y = 0; y < matrixHeight; y++) + { + int offsetY = (row + y - radiusY).Clamp(0, maxRow); + Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); + + for (int x = 0; x < matrixWidth; x++) + { + int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(offsetColumn, maxColumn); + var currentColor = sourceRowSpan[offsetX].ToVector4(); + Vector4Utils.Premultiply(ref currentColor); + + vectorX += matrixX[y, x] * currentColor; + vectorY += matrixY[y, x] * currentColor; + } + } + + var vector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); + ref Vector4 target = ref targetRow[column]; + vector.W = target.W; + Vector4Utils.UnPremultiply(ref vector); + target = vector; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/Guard.cs b/src/ImageSharp/Common/Helpers/Guard.cs index b4a29f9fb5..34ba544726 100644 --- a/src/ImageSharp/Common/Helpers/Guard.cs +++ b/src/ImageSharp/Common/Helpers/Guard.cs @@ -242,5 +242,49 @@ namespace SixLabors.ImageSharp throw new ArgumentException($"Span-s must be at least of length {minLength}!", parameterName); } } + + /// + /// Verifies that the given 'source' and 'dest' spans are at least of 'minLength' size. + /// Throwing an if the condition is not met. + /// + /// The source element type + /// The destination element type + /// The source span + /// The source parameter name + /// The destination span + /// The destination parameter name + /// The minimum length + public static void SpansMustBeSizedAtLeast( + Span source, + string sourceParamName, + Span dest, + string destParamName, + int minLength) + { + MustBeSizedAtLeast(source, minLength, sourceParamName); + MustBeSizedAtLeast(dest, minLength, destParamName); + } + + /// + /// Verifies that the given 'source' and 'dest' spans are at least of 'minLength' size. + /// Throwing an if the condition is not met. + /// + /// The source element type + /// The destination element type + /// The source span + /// The source parameter name + /// The destination span + /// The destination parameter name + /// The minimum length + public static void SpansMustBeSizedAtLeast( + ReadOnlySpan source, + string sourceParamName, + Span dest, + string destParamName, + int minLength) + { + MustBeSizedAtLeast(source, minLength, sourceParamName); + MustBeSizedAtLeast(dest, minLength, destParamName); + } } } diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs index 3c48488ecc..35769d96a7 100644 --- a/src/ImageSharp/Common/Helpers/ImageMaths.cs +++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; + using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; @@ -13,6 +14,31 @@ namespace SixLabors.ImageSharp /// internal static class ImageMaths { + /// + /// Determine the Greatest CommonDivisor (GCD) of two numbers. + /// + public static int GreatestCommonDivisor(int a, int b) + { + while (b != 0) + { + int temp = b; + b = a % b; + a = temp; + } + + return a; + } + + /// + /// Determine the Least Common Multiple (LCM) of two numbers. + /// TODO: This method might be useful for building a more compact + /// + public static int LeastCommonMultiple(int a, int b) + { + // https://en.wikipedia.org/wiki/Least_common_multiple#Reduction_by_the_greatest_common_divisor + return (a / GreatestCommonDivisor(a, b)) * b; + } + /// /// Returns the absolute value of a 32-bit signed integer. Uses bit shifting to speed up the operation. /// @@ -27,6 +53,22 @@ namespace SixLabors.ImageSharp 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; + /// /// Returns how many bits are required to store the specified number of colors. /// Performs a Log2() on the value. @@ -36,10 +78,15 @@ namespace SixLabors.ImageSharp /// The /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetBitsNeededForColorDepth(int colors) - { - return Math.Max(1, (int)Math.Ceiling(Math.Log(colors, 2))); - } + 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; /// /// Implementation of 1D Gaussian G(x) function @@ -54,7 +101,7 @@ namespace SixLabors.ImageSharp float denominator = MathF.Sqrt(2 * MathF.PI) * sigma; float exponentNumerator = -x * x; - float exponentDenominator = (float)(2 * Math.Pow(sigma, 2)); + float exponentDenominator = 2 * Pow2(sigma); float left = Numerator / denominator; float right = MathF.Exp(exponentNumerator / exponentDenominator); @@ -96,14 +143,12 @@ namespace SixLabors.ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float GetBcValue(float x, float b, float c) { - float temp; - if (x < 0F) { x = -x; } - temp = 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)); @@ -132,10 +177,7 @@ namespace SixLabors.ImageSharp /// The bounding . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) - { - return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y); - } + public 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 diff --git a/src/ImageSharp/Common/Helpers/InliningOptions.cs b/src/ImageSharp/Common/Helpers/InliningOptions.cs index e1d51da8d4..ad85c4fc81 100644 --- a/src/ImageSharp/Common/Helpers/InliningOptions.cs +++ b/src/ImageSharp/Common/Helpers/InliningOptions.cs @@ -8,12 +8,12 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp { /// - /// Global inlining options. Helps temporarily disable inling for better profiler output. + /// Global inlining options. Helps temporarily disable inlining for better profiler output. /// internal static class InliningOptions { #if PROFILING - public const MethodImplOptions ShortMethod = 0; + public const MethodImplOptions ShortMethod = MethodImplOptions.NoInlining; #else public const MethodImplOptions ShortMethod = MethodImplOptions.AggressiveInlining; #endif diff --git a/src/ImageSharp/Common/Helpers/ParallelFor.cs b/src/ImageSharp/Common/Helpers/ParallelFor.cs deleted file mode 100644 index 4c14bb6e3d..0000000000 --- a/src/ImageSharp/Common/Helpers/ParallelFor.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Buffers; -using System.Threading.Tasks; -using SixLabors.Memory; - -namespace SixLabors.ImageSharp -{ - /// - /// Utility methods for Parallel.For() execution. Use this instead of raw calls! - /// - internal static class ParallelFor - { - /// - /// Helper method to execute Parallel.For using the settings in - /// - public static void WithConfiguration(int fromInclusive, int toExclusive, Configuration configuration, Action body) - { - Parallel.For(fromInclusive, toExclusive, configuration.GetParallelOptions(), body); - } - - /// - /// Helper method to execute Parallel.For with temporary worker buffer shared between executing tasks. - /// The buffer is not guaranteed to be clean! - /// - /// The value type of the buffer - /// The start index, inclusive. - /// The end index, exclusive. - /// The used for getting the and - /// The length of the requested parallel buffer - /// The delegate that is invoked once per iteration. - public static void WithTemporaryBuffer( - int fromInclusive, - int toExclusive, - Configuration configuration, - int bufferLength, - Action> body) - where T : struct - { - MemoryAllocator memoryAllocator = configuration.MemoryAllocator; - ParallelOptions parallelOptions = configuration.GetParallelOptions(); - - IMemoryOwner InitBuffer() - { - return memoryAllocator.Allocate(bufferLength); - } - - void CleanUpBuffer(IMemoryOwner buffer) - { - buffer.Dispose(); - } - - IMemoryOwner BodyFunc(int i, ParallelLoopState state, IMemoryOwner buffer) - { - body(i, buffer); - return buffer; - } - - Parallel.For(fromInclusive, toExclusive, parallelOptions, InitBuffer, BodyFunc, CleanUpBuffer); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/TestHelpers.cs b/src/ImageSharp/Common/Helpers/TestHelpers.cs index 14e5835b49..45ef7706dd 100644 --- a/src/ImageSharp/Common/Helpers/TestHelpers.cs +++ b/src/ImageSharp/Common/Helpers/TestHelpers.cs @@ -13,9 +13,7 @@ namespace SixLabors.ImageSharp.Common.Helpers /// Only intended to be used in tests! /// internal const string ImageSharpBuiltAgainst = -#if NETSTANDARD1_1 - "netstandard1.1"; -#elif NETCOREAPP2_1 +#if NETCOREAPP2_1 "netcoreapp2.1"; #else "netstandard2.0"; diff --git a/src/ImageSharp/Common/Helpers/Vector4Utils.cs b/src/ImageSharp/Common/Helpers/Vector4Utils.cs new file mode 100644 index 0000000000..75bb00b6a5 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/Vector4Utils.cs @@ -0,0 +1,74 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp +{ + /// + /// Utility methods for the struct. + /// + internal static class Vector4Utils + { + /// + /// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact. + /// + /// The to premultiply + [MethodImpl(InliningOptions.ShortMethod)] + 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(InliningOptions.ShortMethod)] + public static void UnPremultiply(ref Vector4 source) + { + float w = source.W; + source /= w; + source.W = w; + } + + /// + /// Bulk variant of + /// + /// The span of vectors + [MethodImpl(InliningOptions.ShortMethod)] + public static void Premultiply(Span vectors) + { + // TODO: This method can be AVX2 optimized using Vector + ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); + + for (int i = 0; i < vectors.Length; i++) + { + ref Vector4 v = ref Unsafe.Add(ref baseRef, i); + Premultiply(ref v); + } + } + + /// + /// Bulk variant of + /// + /// The span of vectors + [MethodImpl(InliningOptions.ShortMethod)] + public static void UnPremultiply(Span vectors) + { + // TODO: This method can be AVX2 optimized using Vector + ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors); + + for (int i = 0; i < vectors.Length; i++) + { + ref Vector4 v = ref Unsafe.Add(ref baseRef, i); + UnPremultiply(ref v); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs b/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs new file mode 100644 index 0000000000..0b45719c38 --- /dev/null +++ b/src/ImageSharp/Common/ParallelUtils/ParallelExecutionSettings.cs @@ -0,0 +1,71 @@ +// Copyright(c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Threading.Tasks; + +using SixLabors.Memory; + +namespace SixLabors.ImageSharp.ParallelUtils +{ + /// + /// Defines execution settings for methods in . + /// + internal readonly struct ParallelExecutionSettings + { + /// + /// Default value for . + /// + public const int DefaultMinimumPixelsProcessedPerTask = 4096; + + /// + /// Initializes a new instance of the struct. + /// + public ParallelExecutionSettings( + int maxDegreeOfParallelism, + int minimumPixelsProcessedPerTask, + MemoryAllocator memoryAllocator) + { + this.MaxDegreeOfParallelism = maxDegreeOfParallelism; + this.MinimumPixelsProcessedPerTask = minimumPixelsProcessedPerTask; + this.MemoryAllocator = memoryAllocator; + } + + /// + /// Initializes a new instance of the struct. + /// + public ParallelExecutionSettings(int maxDegreeOfParallelism, MemoryAllocator memoryAllocator) + : this(maxDegreeOfParallelism, DefaultMinimumPixelsProcessedPerTask, memoryAllocator) + { + } + + /// + /// Gets the MemoryAllocator + /// + public MemoryAllocator MemoryAllocator { 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; } + + /// + /// Creates a new instance of + /// having multiplied by + /// + public ParallelExecutionSettings MultiplyMinimumPixelsPerTask(int multiplier) + { + return new ParallelExecutionSettings( + this.MaxDegreeOfParallelism, + this.MinimumPixelsProcessedPerTask * multiplier, + this.MemoryAllocator); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs b/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs new file mode 100644 index 0000000000..1d1734a863 --- /dev/null +++ b/src/ImageSharp/Common/ParallelUtils/ParallelHelper.cs @@ -0,0 +1,146 @@ +// Copyright(c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +using SixLabors.ImageSharp.Memory; +using SixLabors.Memory; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.ParallelUtils +{ + /// + /// Utility methods for batched processing of pixel row intervals. + /// Parallel execution is optimized for image processing. + /// Use this instead of direct calls! + /// + internal static class ParallelHelper + { + /// + /// Get the default for a + /// + public static ParallelExecutionSettings GetParallelSettings(this Configuration configuration) + { + return new ParallelExecutionSettings(configuration.MaxDegreeOfParallelism, configuration.MemoryAllocator); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s. + /// + public static void IterateRows(Rectangle rectangle, Configuration configuration, Action body) + { + ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings(); + + IterateRows(rectangle, parallelSettings, body); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s. + /// + public static void IterateRows( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + Action body) + { + DebugGuard.MustBeGreaterThan(rectangle.Width, 0, nameof(rectangle)); + + int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask); + + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + var rows = new RowInterval(rectangle.Top, rectangle.Bottom); + body(rows); + return; + } + + int verticalStep = DivideCeil(rectangle.Height, numOfSteps); + + var parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = numOfSteps }; + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + i => + { + int yMin = rectangle.Top + (i * verticalStep); + int yMax = Math.Min(yMin + verticalStep, rectangle.Bottom); + + var rows = new RowInterval(yMin, yMax); + body(rows); + }); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s + /// instantiating a temporary buffer for each invocation. + /// + public static void IterateRowsWithTempBuffer( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + Action> body) + where T : struct + { + int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask); + + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + + MemoryAllocator memoryAllocator = parallelSettings.MemoryAllocator; + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + var rows = new RowInterval(rectangle.Top, rectangle.Bottom); + using (IMemoryOwner buffer = memoryAllocator.Allocate(rectangle.Width)) + { + body(rows, buffer.Memory); + } + + return; + } + + int verticalStep = DivideCeil(rectangle.Height, numOfSteps); + + var parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = numOfSteps }; + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + i => + { + int yMin = rectangle.Top + (i * verticalStep); + int yMax = Math.Min(yMin + verticalStep, rectangle.Bottom); + + var rows = new RowInterval(yMin, yMax); + + using (IMemoryOwner buffer = memoryAllocator.Allocate(rectangle.Width)) + { + body(rows, buffer.Memory); + } + }); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s + /// instantiating a temporary buffer for each invocation. + /// + public static void IterateRowsWithTempBuffer( + Rectangle rectangle, + Configuration configuration, + Action> body) + where T : struct + { + IterateRowsWithTempBuffer(rectangle, configuration.GetParallelSettings(), body); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 1b009bfedd..c0064d1877 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -3,15 +3,12 @@ using System; using System.Collections.Generic; -using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; -#if !NETSTANDARD1_1 using SixLabors.ImageSharp.IO; -#endif using SixLabors.ImageSharp.Processing; using SixLabors.Memory; @@ -59,6 +56,7 @@ namespace SixLabors.ImageSharp /// /// Gets or sets the maximum number of concurrent tasks enabled in ImageSharp algorithms /// configured with this instance. + /// Initialized with by default. /// public int MaxDegreeOfParallelism { @@ -99,12 +97,10 @@ namespace SixLabors.ImageSharp /// internal int MaxHeaderSize => this.ImageFormatsManager.MaxHeaderSize; -#if !NETSTANDARD1_1 /// /// Gets or sets the filesystem helper for accessing the local file system. /// internal IFileSystem FileSystem { get; set; } = new LocalFileSystem(); -#endif /// /// Gets or sets the image operations provider factory. @@ -125,7 +121,7 @@ namespace SixLabors.ImageSharp /// Creates a shallow copy of the /// /// A new configuration instance - public Configuration ShallowCopy() + public Configuration Clone() { return new Configuration { @@ -134,10 +130,7 @@ namespace SixLabors.ImageSharp MemoryAllocator = this.MemoryAllocator, ImageOperationsProvider = this.ImageOperationsProvider, ReadOrigin = this.ReadOrigin, - -#if !NETSTANDARD1_1 FileSystem = this.FileSystem -#endif }; } diff --git a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs index 0029a6b68d..618999c87d 100644 --- a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs +++ b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs @@ -6,16 +6,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Enumerates the available bits per pixel for bitmap. /// - public enum BmpBitsPerPixel + public enum BmpBitsPerPixel : short { /// /// 24 bits per pixel. Each pixel consists of 3 bytes. /// - Pixel24 = 3, + Pixel24 = 24, /// /// 32 bits per pixel. Each pixel consists of 4 bytes. /// - Pixel32 = 4 + Pixel32 = 32 } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs b/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs index 956acc1578..57117cc075 100644 --- a/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs +++ b/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs @@ -9,11 +9,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp public sealed class BmpConfigurationModule : IConfigurationModule { /// - public void Configure(Configuration config) + public void Configure(Configuration configuration) { - config.ImageFormatsManager.SetEncoder(ImageFormats.Bmp, new BmpEncoder()); - config.ImageFormatsManager.SetDecoder(ImageFormats.Bmp, new BmpDecoder()); - config.ImageFormatsManager.AddImageFormatDetector(new BmpImageFormatDetector()); + configuration.ImageFormatsManager.SetEncoder(BmpFormat.Instance, new BmpEncoder()); + configuration.ImageFormatsManager.SetDecoder(BmpFormat.Instance, new BmpDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new BmpImageFormatDetector()); } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index d67beb0368..71852acddd 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -175,10 +175,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// Whether the bitmap is inverted. /// The representing the inverted value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Invert(int y, int height, bool inverted) - { - return (!inverted) ? height - y - 1 : y; - } + private static int Invert(int y, int height, bool inverted) => (!inverted) ? height - y - 1 : y; /// /// Calculates the amount of bytes to pad a row. @@ -206,10 +203,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The masked and shifted value /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte GetBytesFrom5BitValue(int value) - { - return (byte)((value << 3) | (value >> 2)); - } + private static byte GetBytesFrom5BitValue(int value) => (byte)((value << 3) | (value >> 2)); /// /// Looks up color values and builds the image from de-compressed RLE8 data. @@ -524,8 +518,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp } // Resolution is stored in PPM. - var meta = new ImageMetaData(); - meta.ResolutionUnits = PixelResolutionUnit.PixelsPerMeter; + var meta = new ImageMetaData + { + ResolutionUnits = PixelResolutionUnit.PixelsPerMeter + }; if (this.infoHeader.XPelsPerMeter > 0 && this.infoHeader.YPelsPerMeter > 0) { meta.HorizontalResolution = this.infoHeader.XPelsPerMeter; @@ -540,6 +536,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.metaData = meta; + short bitsPerPixel = this.infoHeader.BitsPerPixel; + var bmpMetaData = this.metaData.GetFormatMetaData(BmpFormat.Instance); + + // We can only encode at these bit rates so far. + if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel24) + || bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel32)) + { + bmpMetaData.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel; + } + // skip the remaining header because we can't read those parts this.stream.Skip(skipAmount); } @@ -585,11 +591,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp if (this.infoHeader.ClrUsed == 0) { - if (this.infoHeader.BitsPerPixel == 1 || - this.infoHeader.BitsPerPixel == 4 || - this.infoHeader.BitsPerPixel == 8) + if (this.infoHeader.BitsPerPixel == 1 + || this.infoHeader.BitsPerPixel == 4 + || this.infoHeader.BitsPerPixel == 8) { - colorMapSize = (int)Math.Pow(2, this.infoHeader.BitsPerPixel) * 4; + colorMapSize = ImageMaths.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel) * 4; } } else diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index 23b01ae9e8..b1a66accec 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Gets or sets the number of bits per pixel. /// - public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; + public BmpBitsPerPixel? BitsPerPixel { get; set; } /// public void Encode(Image image, Stream stream) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index b49b8a8959..44e42528cf 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -21,10 +21,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// private int padding; - private readonly BmpBitsPerPixel bitsPerPixel; - private readonly MemoryAllocator memoryAllocator; + private BmpBitsPerPixel? bitsPerPixel; + /// /// Initializes a new instance of the class. /// @@ -48,37 +48,39 @@ namespace SixLabors.ImageSharp.Formats.Bmp Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - // Cast to int will get the bytes per pixel - short bpp = (short)(8 * (int)this.bitsPerPixel); + ImageMetaData metaData = image.MetaData; + BmpMetaData bmpMetaData = metaData.GetFormatMetaData(BmpFormat.Instance); + this.bitsPerPixel = this.bitsPerPixel ?? bmpMetaData.BitsPerPixel; + + short bpp = (short)this.bitsPerPixel; int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); - this.padding = bytesPerLine - (image.Width * (int)this.bitsPerPixel); + this.padding = bytesPerLine - (int)(image.Width * (bpp / 8F)); // Set Resolution. - ImageMetaData meta = image.MetaData; int hResolution = 0; int vResolution = 0; - if (meta.ResolutionUnits != PixelResolutionUnit.AspectRatio) + if (metaData.ResolutionUnits != PixelResolutionUnit.AspectRatio) { - if (meta.HorizontalResolution > 0 && meta.VerticalResolution > 0) + if (metaData.HorizontalResolution > 0 && metaData.VerticalResolution > 0) { - switch (meta.ResolutionUnits) + switch (metaData.ResolutionUnits) { case PixelResolutionUnit.PixelsPerInch: - hResolution = (int)Math.Round(UnitConverter.InchToMeter(meta.HorizontalResolution)); - vResolution = (int)Math.Round(UnitConverter.InchToMeter(meta.VerticalResolution)); + hResolution = (int)Math.Round(UnitConverter.InchToMeter(metaData.HorizontalResolution)); + vResolution = (int)Math.Round(UnitConverter.InchToMeter(metaData.VerticalResolution)); break; case PixelResolutionUnit.PixelsPerCentimeter: - hResolution = (int)Math.Round(UnitConverter.CmToMeter(meta.HorizontalResolution)); - vResolution = (int)Math.Round(UnitConverter.CmToMeter(meta.VerticalResolution)); + hResolution = (int)Math.Round(UnitConverter.CmToMeter(metaData.HorizontalResolution)); + vResolution = (int)Math.Round(UnitConverter.CmToMeter(metaData.VerticalResolution)); break; case PixelResolutionUnit.PixelsPerMeter: - hResolution = (int)Math.Round(meta.HorizontalResolution); - vResolution = (int)Math.Round(meta.VerticalResolution); + hResolution = (int)Math.Round(metaData.HorizontalResolution); + vResolution = (int)Math.Round(metaData.VerticalResolution); break; } @@ -145,10 +147,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp } } - private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) - { - return this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding); - } + private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding); /// /// Writes the 32bit color palette to the stream. diff --git a/src/ImageSharp/Formats/Bmp/BmpFormat.cs b/src/ImageSharp/Formats/Bmp/BmpFormat.cs index 64c6574c1e..a5eaab8ebf 100644 --- a/src/ImageSharp/Formats/Bmp/BmpFormat.cs +++ b/src/ImageSharp/Formats/Bmp/BmpFormat.cs @@ -8,8 +8,17 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Registers the image encoders, decoders and mime type detectors for the bmp format. /// - internal sealed class BmpFormat : IImageFormat + public sealed class BmpFormat : IImageFormat { + private BmpFormat() + { + } + + /// + /// Gets the current instance. + /// + public static BmpFormat Instance { get; } = new BmpFormat(); + /// public string Name => "BMP"; @@ -21,5 +30,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// public IEnumerable FileExtensions => BmpConstants.FileExtensions; + + /// + public BmpMetaData CreateDefaultFormatMetaData() => new BmpMetaData(); } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs index bb884019b7..6a740d47d1 100644 --- a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// public IImageFormat DetectFormat(ReadOnlySpan header) { - return this.IsSupportedFileFormat(header) ? ImageFormats.Bmp : null; + return this.IsSupportedFileFormat(header) ? BmpFormat.Instance : null; } private bool IsSupportedFileFormat(ReadOnlySpan header) diff --git a/src/ImageSharp/Formats/Bmp/BmpMetaData.cs b/src/ImageSharp/Formats/Bmp/BmpMetaData.cs new file mode 100644 index 0000000000..8b33e30fa6 --- /dev/null +++ b/src/ImageSharp/Formats/Bmp/BmpMetaData.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Bmp +{ + /// + /// Provides Bmp specific metadata information for the image. + /// + public class BmpMetaData : IDeepCloneable + { + /// + /// 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; + + /// + /// Gets or sets the number of bits per pixel. + /// + public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; + + /// + public IDeepCloneable DeepClone() => new BmpMetaData(this); + + // TODO: Colors used once we support encoding palette bmps. + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs index 56952f0356..f62504d08f 100644 --- a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs +++ b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs @@ -12,6 +12,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Gets the number of bits per pixel. /// - BmpBitsPerPixel BitsPerPixel { get; } + BmpBitsPerPixel? BitsPerPixel { get; } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/ImageExtensions.cs b/src/ImageSharp/Formats/Bmp/ImageExtensions.cs index 57e4615bad..aa1c353db2 100644 --- a/src/ImageSharp/Formats/Bmp/ImageExtensions.cs +++ b/src/ImageSharp/Formats/Bmp/ImageExtensions.cs @@ -34,6 +34,6 @@ namespace SixLabors.ImageSharp /// Thrown if the stream is null. public static void SaveAsBmp(this Image source, Stream stream, BmpEncoder encoder) where TPixel : struct, IPixel - => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ImageFormats.Bmp)); + => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance)); } } diff --git a/src/ImageSharp/Formats/Gif/GifColorTableMode.cs b/src/ImageSharp/Formats/Gif/GifColorTableMode.cs index aa41928633..95b3335626 100644 --- a/src/ImageSharp/Formats/Gif/GifColorTableMode.cs +++ b/src/ImageSharp/Formats/Gif/GifColorTableMode.cs @@ -4,7 +4,7 @@ namespace SixLabors.ImageSharp.Formats.Gif { /// - /// Provides enumeration for the available Gif color table modes. + /// Provides enumeration for the available color table modes. /// public enum GifColorTableMode { diff --git a/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs b/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs index 0bb62779eb..861d3e0368 100644 --- a/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs +++ b/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs @@ -9,12 +9,11 @@ namespace SixLabors.ImageSharp.Formats.Gif public sealed class GifConfigurationModule : IConfigurationModule { /// - public void Configure(Configuration config) + public void Configure(Configuration configuration) { - config.ImageFormatsManager.SetEncoder(ImageFormats.Gif, new GifEncoder()); - config.ImageFormatsManager.SetDecoder(ImageFormats.Gif, new GifDecoder()); - - config.ImageFormatsManager.AddImageFormatDetector(new GifImageFormatDetector()); + configuration.ImageFormatsManager.SetEncoder(GifFormat.Instance, new GifEncoder()); + configuration.ImageFormatsManager.SetDecoder(GifFormat.Instance, new GifDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new GifImageFormatDetector()); } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifConstants.cs b/src/ImageSharp/Formats/Gif/GifConstants.cs index 0dbd39b992..288c3dfa19 100644 --- a/src/ImageSharp/Formats/Gif/GifConstants.cs +++ b/src/ImageSharp/Formats/Gif/GifConstants.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The ASCII encoded bytes used to identify the GIF file. /// - internal static readonly byte[] MagicNumber = Encoding.UTF8.GetBytes(FileType + FileVersion); + internal static readonly byte[] MagicNumber = Encoding.ASCII.GetBytes(FileType + FileVersion); /// /// The extension block introducer !. @@ -41,20 +41,25 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public const byte ApplicationExtensionLabel = 0xFF; + /// + /// The application block size. + /// + public const byte ApplicationBlockSize = 11; + /// /// The application identification. /// - public const string ApplicationIdentification = "NETSCAPE2.0"; + public const string NetscapeApplicationIdentification = "NETSCAPE2.0"; /// /// The ASCII encoded application identification bytes. /// - internal static readonly byte[] ApplicationIdentificationBytes = Encoding.UTF8.GetBytes(ApplicationIdentification); + internal static readonly byte[] NetscapeApplicationIdentificationBytes = Encoding.ASCII.GetBytes(NetscapeApplicationIdentification); /// - /// The application block size. + /// The Netscape looping application sub block size. /// - public const byte ApplicationBlockSize = 11; + public const byte NetscapeLoopingSubBlockSize = 3; /// /// The comment label. @@ -99,7 +104,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Gets the default encoding to use when reading comments. /// - public static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ASCII"); + public static readonly Encoding DefaultEncoding = Encoding.ASCII; /// /// The list of mimetypes that equate to a gif. diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index ac451a3550..42c76d6400 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -3,6 +3,7 @@ using System.IO; using System.Text; +using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Gif diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 2a4d981ebb..207f126f9e 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -56,10 +56,20 @@ namespace SixLabors.ImageSharp.Formats.Gif private GifGraphicControlExtension graphicsControlExtension; /// - /// The metadata + /// The image desciptor. + /// + private GifImageDescriptor imageDescriptor; + + /// + /// The abstract metadata. /// private ImageMetaData metaData; + /// + /// The gif specific metadata. + /// + private GifMetaData gifMetaData; + /// /// Initializes a new instance of the class. /// @@ -120,8 +130,7 @@ namespace SixLabors.ImageSharp.Formats.Gif } else if (nextFlag == GifConstants.ExtensionIntroducer) { - int label = stream.ReadByte(); - switch (label) + switch (stream.ReadByte()) { case GifConstants.GraphicControlLabel: this.ReadGraphicalControlExtension(); @@ -130,11 +139,7 @@ namespace SixLabors.ImageSharp.Formats.Gif this.ReadComments(); break; case GifConstants.ApplicationExtensionLabel: - - // The application extension length should be 11 but we've got test images that incorrectly - // set this to 252. - int appLength = stream.ReadByte(); - this.Skip(appLength); // No need to read. + this.ReadApplicationExtension(); break; case GifConstants.PlainTextLabel: int plainLength = stream.ReadByte(); @@ -178,13 +183,11 @@ namespace SixLabors.ImageSharp.Formats.Gif { if (nextFlag == GifConstants.ImageLabel) { - // Skip image block - this.Skip(0); + this.ReadImageDescriptor(); } else if (nextFlag == GifConstants.ExtensionIntroducer) { - int label = stream.ReadByte(); - switch (label) + switch (stream.ReadByte()) { case GifConstants.GraphicControlLabel: @@ -195,11 +198,7 @@ namespace SixLabors.ImageSharp.Formats.Gif this.ReadComments(); break; case GifConstants.ApplicationExtensionLabel: - - // The application extension length should be 11 but we've got test images that incorrectly - // set this to 252. - int appLength = stream.ReadByte(); - this.Skip(appLength); // No need to read. + this.ReadApplicationExtension(); break; case GifConstants.PlainTextLabel: int plainLength = stream.ReadByte(); @@ -224,7 +223,11 @@ namespace SixLabors.ImageSharp.Formats.Gif this.globalColorTable?.Dispose(); } - return new ImageInfo(new PixelTypeInfo(this.logicalScreenDescriptor.BitsPerPixel), this.logicalScreenDescriptor.Width, this.logicalScreenDescriptor.Height, this.metaData); + return new ImageInfo( + new PixelTypeInfo(this.logicalScreenDescriptor.BitsPerPixel), + this.logicalScreenDescriptor.Width, + this.logicalScreenDescriptor.Height, + this.metaData); } /// @@ -238,14 +241,13 @@ namespace SixLabors.ImageSharp.Formats.Gif } /// - /// Reads the image descriptor + /// Reads the image descriptor. /// - /// - private GifImageDescriptor ReadImageDescriptor() + private void ReadImageDescriptor() { this.stream.Read(this.buffer, 0, 9); - return GifImageDescriptor.Parse(this.buffer); + this.imageDescriptor = GifImageDescriptor.Parse(this.buffer); } /// @@ -258,6 +260,41 @@ namespace SixLabors.ImageSharp.Formats.Gif this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer); } + /// + /// Reads the application extension block parsing any animation 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 or ANIMEXTS extension. We want the loop count from this. + if (appLength == GifConstants.ApplicationBlockSize) + { + this.stream.Skip(appLength); + 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; + } + + // Could be XMP or something else not supported yet. + // Back up and skip. + this.stream.Position -= appLength + 1; + this.Skip(appLength); + return; + } + + this.Skip(appLength); // Not supported by any known decoder. + } + /// /// Skips the designated number of bytes in the stream. /// @@ -312,25 +349,25 @@ namespace SixLabors.ImageSharp.Formats.Gif private void ReadFrame(ref Image image, ref ImageFrame previousFrame) where TPixel : struct, IPixel { - GifImageDescriptor imageDescriptor = this.ReadImageDescriptor(); + this.ReadImageDescriptor(); IManagedByteBuffer localColorTable = null; IManagedByteBuffer 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 (imageDescriptor.LocalColorTableFlag) + if (this.imageDescriptor.LocalColorTableFlag) { - int length = imageDescriptor.LocalColorTableSize * 3; + int length = this.imageDescriptor.LocalColorTableSize * 3; localColorTable = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(length, AllocationOptions.Clean); this.stream.Read(localColorTable.Array, 0, length); } - indices = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(imageDescriptor.Width * imageDescriptor.Height, AllocationOptions.Clean); + indices = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(this.imageDescriptor.Width * this.imageDescriptor.Height, AllocationOptions.Clean); - this.ReadFrameIndices(imageDescriptor, indices.GetSpan()); + this.ReadFrameIndices(this.imageDescriptor, indices.GetSpan()); ReadOnlySpan colorTable = MemoryMarshal.Cast((localColorTable ?? this.globalColorTable).GetSpan()); - this.ReadFrameColors(ref image, ref previousFrame, indices.GetSpan(), colorTable, imageDescriptor); + this.ReadFrameColors(ref image, ref previousFrame, indices.GetSpan(), colorTable, this.imageDescriptor); // Skip any remaining blocks this.Skip(0); @@ -388,7 +425,7 @@ namespace SixLabors.ImageSharp.Formats.Gif } else { - if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious) + if (this.graphicsControlExtension.DisposalMethod == GifDisposalMethod.RestoreToPrevious) { prevFrame = previousFrame; } @@ -471,7 +508,7 @@ namespace SixLabors.ImageSharp.Formats.Gif previousFrame = currentFrame ?? image.Frames.RootFrame; - if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground) + if (this.graphicsControlExtension.DisposalMethod == GifDisposalMethod.RestoreToBackground) { this.restoreArea = new Rectangle(descriptor.Left, descriptor.Top, descriptor.Width, descriptor.Height); } @@ -503,12 +540,25 @@ namespace SixLabors.ImageSharp.Formats.Gif [MethodImpl(MethodImplOptions.AggressiveInlining)] private void SetFrameMetaData(ImageFrameMetaData meta) { + GifFrameMetaData gifMeta = meta.GetFormatMetaData(GifFormat.Instance); if (this.graphicsControlExtension.DelayTime > 0) { - meta.FrameDelay = this.graphicsControlExtension.DelayTime; + gifMeta.FrameDelay = this.graphicsControlExtension.DelayTime; + } + + // Frames can either use the global table or their own local table. + if (this.logicalScreenDescriptor.GlobalColorTableFlag + && this.logicalScreenDescriptor.GlobalColorTableSize > 0) + { + gifMeta.ColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize; + } + else if (this.imageDescriptor.LocalColorTableFlag + && this.imageDescriptor.LocalColorTableSize > 0) + { + gifMeta.ColorTableLength = this.imageDescriptor.LocalColorTableSize; } - meta.DisposalMethod = this.graphicsControlExtension.DisposalMethod; + gifMeta.DisposalMethod = this.graphicsControlExtension.DisposalMethod; } /// @@ -552,10 +602,15 @@ namespace SixLabors.ImageSharp.Formats.Gif } this.metaData = meta; + this.gifMetaData = meta.GetFormatMetaData(GifFormat.Instance); + this.gifMetaData.ColorTableMode = this.logicalScreenDescriptor.GlobalColorTableFlag + ? GifColorTableMode.Global + : GifColorTableMode.Local; if (this.logicalScreenDescriptor.GlobalColorTableFlag) { int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3; + this.gifMetaData.GlobalColorTableLength = globalColorTableLength; this.globalColorTable = this.MemoryAllocator.AllocateManagedByteBuffer(globalColorTableLength, AllocationOptions.Clean); diff --git a/src/ImageSharp/Formats/Gif/DisposalMethod.cs b/src/ImageSharp/Formats/Gif/GifDisposalMethod.cs similarity index 97% rename from src/ImageSharp/Formats/Gif/DisposalMethod.cs rename to src/ImageSharp/Formats/Gif/GifDisposalMethod.cs index 5d3e1b4d89..982340db66 100644 --- a/src/ImageSharp/Formats/Gif/DisposalMethod.cs +++ b/src/ImageSharp/Formats/Gif/GifDisposalMethod.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// in an animation sequence. /// section 23 /// - public enum DisposalMethod + public enum GifDisposalMethod { /// /// No disposal specified. diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index e8e28ccdd8..4210b08765 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -14,11 +14,6 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public sealed class GifEncoder : IImageEncoder, IGifEncoderOptions { - /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded. - /// - public bool IgnoreMetadata { get; set; } = false; - /// /// Gets or sets the encoding that should be used when writing comments. /// @@ -33,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Gets or sets the color table mode: Global or local. /// - public GifColorTableMode ColorTableMode { get; set; } + public GifColorTableMode? ColorTableMode { get; set; } /// public void Encode(Image image, Stream stream) diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 5532900355..ae0366e6e6 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers.Binary; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -44,17 +43,17 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The color table mode: Global or local. /// - private readonly GifColorTableMode colorTableMode; + private GifColorTableMode? colorTableMode; /// - /// A flag indicating whether to ingore the metadata when writing the image. + /// The number of bits requires to store the color palette. /// - private readonly bool ignoreMetadata; + private int bitDepth; /// - /// The number of bits requires to store the color palette. + /// Gif specific meta data. /// - private int bitDepth; + private GifMetaData gifMetaData; /// /// Initializes a new instance of the class. @@ -67,7 +66,6 @@ namespace SixLabors.ImageSharp.Formats.Gif this.textEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding; this.quantizer = options.Quantizer; this.colorTableMode = options.ColorTableMode; - this.ignoreMetadata = options.IgnoreMetadata; } /// @@ -82,6 +80,11 @@ namespace SixLabors.ImageSharp.Formats.Gif Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); + ImageMetaData metaData = image.MetaData; + this.gifMetaData = metaData.GetFormatMetaData(GifFormat.Instance); + this.colorTableMode = this.colorTableMode ?? this.gifMetaData.ColorTableMode; + bool useGlobalTable = this.colorTableMode.Equals(GifColorTableMode.Global); + // Quantize the image returning a palette. QuantizedFrame quantized = this.quantizer.CreateFrameQuantizer().QuantizeFrame(image.Frames.RootFrame); @@ -94,8 +97,7 @@ namespace SixLabors.ImageSharp.Formats.Gif // Write the LSD. int index = this.GetTransparentIndex(quantized); - bool useGlobalTable = this.colorTableMode.Equals(GifColorTableMode.Global); - this.WriteLogicalScreenDescriptor(image, index, useGlobalTable, stream); + this.WriteLogicalScreenDescriptor(metaData, image.Width, image.Height, index, useGlobalTable, stream); if (useGlobalTable) { @@ -103,12 +105,12 @@ namespace SixLabors.ImageSharp.Formats.Gif } // Write the comments. - this.WriteComments(image.MetaData, stream); + this.WriteComments(metaData, stream); // Write application extension to allow additional frames. if (image.Frames.Count > 1) { - this.WriteApplicationExtension(stream, image.MetaData.RepeatCount); + this.WriteApplicationExtension(stream, this.gifMetaData.RepeatCount); } if (useGlobalTable) @@ -136,8 +138,9 @@ namespace SixLabors.ImageSharp.Formats.Gif for (int i = 0; i < image.Frames.Count; i++) { ImageFrame frame = image.Frames[i]; - - this.WriteGraphicalControlExtension(frame.MetaData, transparencyIndex, stream); + ImageFrameMetaData metaData = frame.MetaData; + GifFrameMetaData frameMetaData = metaData.GetFormatMetaData(GifFormat.Instance); + this.WriteGraphicalControlExtension(frameMetaData, transparencyIndex, stream); this.WriteImageDescriptor(frame, false, stream); if (i == 0) @@ -146,7 +149,8 @@ namespace SixLabors.ImageSharp.Formats.Gif } else { - using (QuantizedFrame paletteQuantized = palleteQuantizer.CreateFrameQuantizer(() => quantized.Palette).QuantizeFrame(frame)) + using (QuantizedFrame paletteQuantized + = palleteQuantizer.CreateFrameQuantizer(() => quantized.Palette).QuantizeFrame(frame)) { this.WriteImageData(paletteQuantized, stream); } @@ -157,20 +161,37 @@ namespace SixLabors.ImageSharp.Formats.Gif private void EncodeLocal(Image image, QuantizedFrame quantized, Stream stream) where TPixel : struct, IPixel { + ImageFrame previousFrame = null; + GifFrameMetaData previousMeta = null; foreach (ImageFrame frame in image.Frames) { + ImageFrameMetaData metaData = frame.MetaData; + GifFrameMetaData frameMetaData = metaData.GetFormatMetaData(GifFormat.Instance); if (quantized is null) { - quantized = this.quantizer.CreateFrameQuantizer().QuantizeFrame(frame); + // 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) + { + quantized = this.quantizer.CreateFrameQuantizer(frameMetaData.ColorTableLength).QuantizeFrame(frame); + } + else + { + quantized = this.quantizer.CreateFrameQuantizer().QuantizeFrame(frame); + } } - this.WriteGraphicalControlExtension(frame.MetaData, this.GetTransparentIndex(quantized), stream); + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); + this.WriteGraphicalControlExtension(frameMetaData, this.GetTransparentIndex(quantized), stream); this.WriteImageDescriptor(frame, true, stream); this.WriteColorTable(quantized, stream); this.WriteImageData(quantized, stream); quantized?.Dispose(); quantized = null; // So next frame can regenerate it + previousFrame = frame; + previousMeta = frameMetaData; } } @@ -210,21 +231,24 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The stream to write to. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteHeader(Stream stream) - { - stream.Write(GifConstants.MagicNumber, 0, GifConstants.MagicNumber.Length); - } + private void WriteHeader(Stream stream) => stream.Write(GifConstants.MagicNumber, 0, GifConstants.MagicNumber.Length); /// /// Writes the logical screen descriptor to the stream. /// - /// The pixel format. - /// The image to encode. + /// 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(Image image, int transparencyIndex, bool useGlobalTable, Stream stream) - where TPixel : struct, IPixel + 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); @@ -237,13 +261,12 @@ namespace SixLabors.ImageSharp.Formats.Gif // 1..255 - Value used in the computation. // // Aspect Ratio = (Pixel Aspect Ratio + 15) / 64 - ImageMetaData meta = image.MetaData; byte ratio = 0; - if (meta.ResolutionUnits == PixelResolutionUnit.AspectRatio) + if (metaData.ResolutionUnits == PixelResolutionUnit.AspectRatio) { - double hr = meta.HorizontalResolution; - double vr = meta.VerticalResolution; + double hr = metaData.HorizontalResolution; + double vr = metaData.VerticalResolution; if (hr != vr) { if (hr > vr) @@ -258,8 +281,8 @@ namespace SixLabors.ImageSharp.Formats.Gif } var descriptor = new GifLogicalScreenDescriptor( - width: (ushort)image.Width, - height: (ushort)image.Height, + width: (ushort)width, + height: (ushort)height, packed: packedValue, backgroundColorIndex: unchecked((byte)transparencyIndex), ratio); @@ -279,25 +302,8 @@ namespace SixLabors.ImageSharp.Formats.Gif // Application Extension Header if (repeatCount != 1) { - this.buffer[0] = GifConstants.ExtensionIntroducer; - this.buffer[1] = GifConstants.ApplicationExtensionLabel; - this.buffer[2] = GifConstants.ApplicationBlockSize; - - // Write NETSCAPE2.0 - GifConstants.ApplicationIdentificationBytes.AsSpan().CopyTo(this.buffer.AsSpan(3, 11)); - - // Application Data ---- - this.buffer[14] = 3; // Application block length - this.buffer[15] = 1; // Data sub-block index (always 1) - - // 0 means loop indefinitely. Count is set as play n + 1 times. - repeatCount = (ushort)Math.Max(0, repeatCount - 1); - - BinaryPrimitives.WriteUInt16LittleEndian(this.buffer.AsSpan(16, 2), repeatCount); // Repeat count for images. - - this.buffer[18] = GifConstants.Terminator; // Terminator - - stream.Write(this.buffer, 0, 19); + var loopingExtension = new GifNetscapeLoopingApplicationExtension(repeatCount); + this.WriteExtension(loopingExtension, stream); } } @@ -308,11 +314,6 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The stream to write to. private void WriteComments(ImageMetaData metadata, Stream stream) { - if (this.ignoreMetadata) - { - return; - } - if (!metadata.TryGetProperty(GifConstants.Comments, out ImageProperty property) || string.IsNullOrEmpty(property.Value)) { return; @@ -337,7 +338,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// 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(ImageFrameMetaData metaData, int transparencyIndex, Stream stream) + private void WriteGraphicalControlExtension(GifFrameMetaData metaData, int transparencyIndex, Stream stream) { byte packedValue = GifGraphicControlExtension.GetPackedValue( disposalMethod: metaData.DisposalMethod, @@ -382,7 +383,7 @@ namespace SixLabors.ImageSharp.Formats.Gif localColorTableFlag: hasColorTable, interfaceFlag: false, sortFlag: false, - localColorTableSize: (byte)this.bitDepth); + localColorTableSize: this.bitDepth - 1); var descriptor = new GifImageDescriptor( left: 0, @@ -407,7 +408,8 @@ namespace SixLabors.ImageSharp.Formats.Gif { int pixelCount = image.Palette.Length; - int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3; // The maximium number of colors for the bit depth + // The maximium number of colors for the bit depth + int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * 3; Rgb24 rgb = default; using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength)) diff --git a/src/ImageSharp/Formats/Gif/GifFormat.cs b/src/ImageSharp/Formats/Gif/GifFormat.cs index 6353690f47..07d4a54547 100644 --- a/src/ImageSharp/Formats/Gif/GifFormat.cs +++ b/src/ImageSharp/Formats/Gif/GifFormat.cs @@ -8,8 +8,17 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Registers the image encoders, decoders and mime type detectors for the gif format. /// - internal sealed class GifFormat : IImageFormat + public sealed class GifFormat : IImageFormat { + private GifFormat() + { + } + + /// + /// Gets the current instance. + /// + public static GifFormat Instance { get; } = new GifFormat(); + /// public string Name => "GIF"; @@ -21,5 +30,11 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public IEnumerable FileExtensions => GifConstants.FileExtensions; + + /// + public GifMetaData CreateDefaultFormatMetaData() => new GifMetaData(); + + /// + public GifFrameMetaData CreateDefaultFormatFrameMetaData() => new GifFrameMetaData(); } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifFrameMetaData.cs b/src/ImageSharp/Formats/Gif/GifFrameMetaData.cs new file mode 100644 index 0000000000..0042c6a108 --- /dev/null +++ b/src/ImageSharp/Formats/Gif/GifFrameMetaData.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Gif +{ + /// + /// Provides Gif specific metadata information for the image frame. + /// + public class GifFrameMetaData : IDeepCloneable + { + /// + /// 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; + } + + /// + /// 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 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); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs b/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs index bfbd334b01..b8f9a03f1a 100644 --- a/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public IImageFormat DetectFormat(ReadOnlySpan header) { - return this.IsSupportedFileFormat(header) ? ImageFormats.Gif : null; + return this.IsSupportedFileFormat(header) ? GifFormat.Instance : null; } private bool IsSupportedFileFormat(ReadOnlySpan header) diff --git a/src/ImageSharp/Formats/Gif/GifMetaData.cs b/src/ImageSharp/Formats/Gif/GifMetaData.cs new file mode 100644 index 0000000000..bb7fb50518 --- /dev/null +++ b/src/ImageSharp/Formats/Gif/GifMetaData.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Gif +{ + /// + /// Provides Gif specific metadata information for the image. + /// + public class GifMetaData : IDeepCloneable + { + /// + /// 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) + { + this.RepeatCount = other.RepeatCount; + this.ColorTableMode = other.ColorTableMode; + this.GlobalColorTableLength = other.GlobalColorTableLength; + } + + /// + /// Gets or sets the number of times any animation is repeated. + /// + /// 0 means to repeat indefinitely, count is set as play n + 1 times + /// + /// + public ushort RepeatCount { get; set; } + + /// + /// 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; } + + /// + public IDeepCloneable DeepClone() => new GifMetaData(this); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs index e99f09add3..42c202a3d9 100644 --- a/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs +++ b/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Text; +using SixLabors.ImageSharp.MetaData; namespace SixLabors.ImageSharp.Formats.Gif { diff --git a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs index bad6e0031b..4b3c28a92c 100644 --- a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs +++ b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs @@ -11,11 +11,6 @@ namespace SixLabors.ImageSharp.Formats.Gif /// internal interface IGifEncoderOptions { - /// - /// Gets a value indicating whether the metadata should be ignored when the image is being encoded. - /// - bool IgnoreMetadata { get; } - /// /// Gets the text encoding used to write comments. /// @@ -29,6 +24,6 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Gets the color table mode: Global or local. /// - GifColorTableMode ColorTableMode { get; } + GifColorTableMode? ColorTableMode { get; } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/ImageExtensions.cs b/src/ImageSharp/Formats/Gif/ImageExtensions.cs index 1c41285a97..8ddd4247e1 100644 --- a/src/ImageSharp/Formats/Gif/ImageExtensions.cs +++ b/src/ImageSharp/Formats/Gif/ImageExtensions.cs @@ -34,6 +34,6 @@ namespace SixLabors.ImageSharp /// Thrown if the stream is null. public static void SaveAsGif(this Image source, Stream stream, GifEncoder encoder) where TPixel : struct, IPixel - => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ImageFormats.Gif)); + => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance)); } } diff --git a/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs index 7ec5f20309..cb548d687d 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// Gets the disposal method which indicates the way in which the /// graphic is to be treated after being displayed. /// - public DisposalMethod DisposalMethod => (DisposalMethod)((this.Packed & 0x1C) >> 2); + public GifDisposalMethod DisposalMethod => (GifDisposalMethod)((this.Packed & 0x1C) >> 2); /// /// Gets a value indicating whether transparency flag is to be set. @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Formats.Gif return MemoryMarshal.Cast(buffer)[0]; } - public static byte GetPackedValue(DisposalMethod disposalMethod, bool userInputFlag = false, bool transparencyFlag = false) + public static byte GetPackedValue(GifDisposalMethod disposalMethod, bool userInputFlag = false, bool transparencyFlag = false) { /* Reserved | 3 Bits diff --git a/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs b/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs index c5360729e8..c3504dfe7b 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Formats.Gif return MemoryMarshal.Cast(buffer)[0]; } - public static byte GetPackedValue(bool localColorTableFlag, bool interfaceFlag, bool sortFlag, byte localColorTableSize) + public static byte GetPackedValue(bool localColorTableFlag, bool interfaceFlag, bool sortFlag, int localColorTableSize) { /* Local Color Table Flag | 1 Bit @@ -108,7 +108,7 @@ namespace SixLabors.ImageSharp.Formats.Gif value |= 1 << 5; } - value |= (byte)(localColorTableSize - 1); + value |= (byte)localColorTableSize; return value; } diff --git a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs new file mode 100644 index 0000000000..49a52edf6a --- /dev/null +++ b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; + +namespace SixLabors.ImageSharp.Formats.Gif +{ + internal readonly struct GifNetscapeLoopingApplicationExtension : IGifExtension + { + public GifNetscapeLoopingApplicationExtension(ushort repeatCount) => this.RepeatCount = repeatCount; + + public byte Label => GifConstants.ApplicationExtensionLabel; + + /// + /// 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.Slice(0, 2)); + return new GifNetscapeLoopingApplicationExtension(repeatCount); + } + + public int WriteTo(Span buffer) + { + buffer[0] = GifConstants.ApplicationBlockSize; + + // Write NETSCAPE2.0 + GifConstants.NetscapeApplicationIdentificationBytes.AsSpan().CopyTo(buffer.Slice(1, 11)); + + // Application Data ---- + buffer[12] = 3; // Application block length (always 3) + buffer[13] = 1; // Data sub-block indentity (always 1) + + // 0 means loop indefinitely. Count is set as play n + 1 times. + BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(14, 2), this.RepeatCount); + + return 16; // Length - Introducer + Label + Terminator. + } + } +} diff --git a/src/ImageSharp/Formats/IImageFormat.cs b/src/ImageSharp/Formats/IImageFormat.cs index 15bdc73a84..94191c1493 100644 --- a/src/ImageSharp/Formats/IImageFormat.cs +++ b/src/ImageSharp/Formats/IImageFormat.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; namespace SixLabors.ImageSharp.Formats { /// - /// Describes an image format. + /// Defines the contract for an image format. /// public interface IImageFormat { @@ -30,4 +30,34 @@ namespace SixLabors.ImageSharp.Formats /// IEnumerable FileExtensions { get; } } + + /// + /// Defines the contract for an image format containing metadata. + /// + /// The type of format metadata. + public interface IImageFormat : IImageFormat + where TFormatMetaData : class + { + /// + /// Creates a default instance of the format metadata. + /// + /// 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 + { + /// + /// Creates a default instance of the format frame metadata. + /// + /// The . + TFormatFrameMetaData CreateDefaultFormatFrameMetaData(); + } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs index bebc13f6de..b7dd125a88 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs @@ -5,7 +5,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; -using SixLabors.Memory; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Formats.Jpeg.Components @@ -15,7 +14,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// /// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical. /// - public void CopyTo(BufferArea area, int horizontalScale, int verticalScale) + public void CopyTo(in BufferArea area, int horizontalScale, int verticalScale) { if (horizontalScale == 1 && verticalScale == 1) { @@ -57,7 +56,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } // [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyTo(BufferArea area) + public void CopyTo(in BufferArea area) { ref byte selfBase = ref Unsafe.As(ref this); ref byte destBase = ref Unsafe.As(ref area.GetReferenceToOrigin()); @@ -81,7 +80,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float)); } - private void CopyTo2x2(BufferArea area) + private void CopyTo2x2(in BufferArea area) { ref float destBase = ref area.GetReferenceToOrigin(); int destStride = area.Stride; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs index bac77f905e..7a14d072e6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - public override void ConvertToRgba(ComponentValues values, Span result) + public override void ConvertToRgba(in ComponentValues values, Span result) { // TODO: We can optimize a lot here with Vector and SRCS.Unsafe()! ReadOnlySpan cVals = values.Component0; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs index b07e57e170..5d7a31a12b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - public override void ConvertToRgba(ComponentValues values, Span result) + public override void ConvertToRgba(in ComponentValues values, Span result) { // TODO: We can optimize a lot here with Vector and SRCS.Unsafe()! ReadOnlySpan yVals = values.Component0; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs index 6b7e77e148..7cd97c4140 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - public override void ConvertToRgba(ComponentValues values, Span result) + public override void ConvertToRgba(in ComponentValues values, Span result) { // TODO: We can optimize a lot here with Vector and SRCS.Unsafe()! ReadOnlySpan rVals = values.Component0; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs index 35700ea312..cb71889bc5 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs @@ -15,12 +15,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - public override void ConvertToRgba(ComponentValues values, Span result) + public override void ConvertToRgba(in ComponentValues values, Span result) { ConvertCore(values, result); } - internal static void ConvertCore(ComponentValues values, Span result) + internal static void ConvertCore(in ComponentValues values, Span result) { // TODO: We can optimize a lot here with Vector and SRCS.Unsafe()! ReadOnlySpan yVals = values.Component0; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs index fd2f17da9e..4b2626c582 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - public override void ConvertToRgba(ComponentValues values, Span result) + public override void ConvertToRgba(in ComponentValues values, Span result) { int remainder = result.Length % 8; int simdCount = result.Length - remainder; @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// /// SIMD convert using buffers of sizes divisable by 8. /// - internal static void ConvertCore(ComponentValues values, Span result) + internal static void ConvertCore(in ComponentValues values, Span result) { DebugGuard.IsTrue(result.Length % 8 == 0, nameof(result), "result.Length should be divisable by 8!"); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs index 25342f4d67..ab4947e65c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters public static bool IsAvailable => Vector.IsHardwareAccelerated && SimdUtils.IsAvx2CompatibleArchitecture; - public override void ConvertToRgba(ComponentValues values, Span result) + public override void ConvertToRgba(in ComponentValues values, Span result) { int remainder = result.Length % 8; int simdCount = result.Length - remainder; @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// /// SIMD convert using buffers of sizes divisable by 8. /// - internal static void ConvertCore(ComponentValues values, Span result) + internal static void ConvertCore(in ComponentValues values, Span result) { // This implementation is actually AVX specific. // An AVX register is capable of storing 8 float-s. diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs index 83feefa94a..6f940f62f9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - public override void ConvertToRgba(ComponentValues values, Span result) + public override void ConvertToRgba(in ComponentValues values, Span result) { // TODO: We can optimize a lot here with Vector and SRCS.Unsafe()! ReadOnlySpan yVals = values.Component0; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs index 8aeb01d7f0..60abb7fb2c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs @@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// /// The input as a stack-only struct /// The destination buffer of values - public abstract void ConvertToRgba(ComponentValues values, Span result); + public abstract void ConvertToRgba(in ComponentValues values, Span result); /// /// Returns the for the YCbCr colorspace that matches the current CPU architecture. diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTables.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTables.cs index 26bcde8e51..06b46746a6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTables.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/FastACTables.cs @@ -20,54 +20,47 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// Initializes a new instance of the class. /// /// The memory allocator used to allocate memory for image processing operations. - public FastACTables(MemoryAllocator memoryAllocator) - { - this.tables = memoryAllocator.Allocate2D(512, 4, AllocationOptions.Clean); - } + public FastACTables(MemoryAllocator memoryAllocator) => this.tables = memoryAllocator.Allocate2D(512, 4, AllocationOptions.Clean); /// /// Gets the representing the table at the index in the collection. /// /// The table index. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan GetTableSpan(int index) - { - return this.tables.GetRowSpan(index); - } + [MethodImpl(InliningOptions.ShortMethod)] + public ReadOnlySpan GetTableSpan(int index) => this.tables.GetRowSpan(index); /// - /// Gets a reference to the first element of the AC table indexed by /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref short GetAcTableReference(JpegComponent component) - { - return ref this.tables.GetRowSpan(component.ACHuffmanTableId)[0]; - } + /// Gets a reference to the first element of the AC table indexed by + /// + /// The frame component. + [MethodImpl(InliningOptions.ShortMethod)] + public ref short GetAcTableReference(JpegComponent component) => ref this.tables.GetRowSpan(component.ACHuffmanTableId)[0]; /// /// Builds a lookup table for fast AC entropy scan decoding. /// /// The table index. /// The collection of AC Huffman tables. - public void BuildACTableLut(int index, HuffmanTables acHuffmanTables) + public unsafe void BuildACTableLut(int index, HuffmanTables acHuffmanTables) { const int FastBits = ScanDecoder.FastBits; Span fastAC = this.tables.GetRowSpan(index); - ref HuffmanTable huffman = ref acHuffmanTables[index]; + ref HuffmanTable huffmanTable = ref acHuffmanTables[index]; int i; for (i = 0; i < (1 << FastBits); i++) { - byte fast = huffman.Lookahead[i]; + byte fast = huffmanTable.Lookahead[i]; fastAC[i] = 0; if (fast < byte.MaxValue) { - int rs = huffman.Values[fast]; + int rs = huffmanTable.Values[fast]; int run = (rs >> 4) & 15; int magbits = rs & 15; - int len = huffman.Sizes[fast]; + int len = huffmanTable.Sizes[fast]; - if (magbits > 0 && len + magbits <= FastBits) + if (magbits != 0 && len + magbits <= FastBits) { // Magnitude code followed by receive_extend code int k = ((i << len) & ((1 << FastBits) - 1)) >> (FastBits - magbits); @@ -80,7 +73,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // if the result is small enough, we can fit it in fastAC table if (k >= -128 && k <= 127) { - fastAC[i] = (short)((k * 256) + (run * 16) + (len + magbits)); + fastAC[i] = (short)((k << 8) + (run << 4) + (len + magbits)); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedByteBuffer256.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedByteBuffer256.cs deleted file mode 100644 index 1d26178e0c..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedByteBuffer256.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder -{ - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct FixedByteBuffer256 - { - public fixed byte Data[256]; - - public byte this[int idx] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - ref byte self = ref Unsafe.As(ref this); - return Unsafe.Add(ref self, idx); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedByteBuffer512.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedByteBuffer512.cs deleted file mode 100644 index 556e74fd58..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedByteBuffer512.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder -{ - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct FixedByteBuffer512 - { - public fixed byte Data[1 << ScanDecoder.FastBits]; - - public byte this[int idx] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - ref byte self = ref Unsafe.As(ref this); - return Unsafe.Add(ref self, idx); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedInt16Buffer257.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedInt16Buffer257.cs deleted file mode 100644 index a3b67a700b..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedInt16Buffer257.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder -{ - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct FixedInt16Buffer257 - { - public fixed short Data[257]; - - public short this[int idx] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - ref short self = ref Unsafe.As(ref this); - return Unsafe.Add(ref self, idx); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedInt32Buffer18.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedInt32Buffer18.cs deleted file mode 100644 index bba89f072f..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedInt32Buffer18.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder -{ - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct FixedInt32Buffer18 - { - public fixed int Data[18]; - - public int this[int idx] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - ref int self = ref Unsafe.As(ref this); - return Unsafe.Add(ref self, idx); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedUInt32Buffer18.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedUInt32Buffer18.cs deleted file mode 100644 index 1d3ca99338..0000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/FixedUInt32Buffer18.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder -{ - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct FixedUInt32Buffer18 - { - public fixed uint Data[18]; - - public uint this[int idx] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - ref uint self = ref Unsafe.As(ref this); - return Unsafe.Add(ref self, idx); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index 0138164ed2..24d570bf1c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -20,114 +20,105 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// /// Gets the max code array /// - public FixedUInt32Buffer18 MaxCode; + public fixed uint MaxCode[18]; /// /// Gets the value offset array /// - public FixedInt32Buffer18 ValOffset; + public fixed int ValOffset[18]; /// /// Gets the huffman value array /// - public FixedByteBuffer256 Values; + public fixed byte Values[256]; /// /// Gets the lookahead array /// - public FixedByteBuffer512 Lookahead; + public fixed byte Lookahead[512]; /// /// Gets the sizes array /// - public FixedInt16Buffer257 Sizes; + public fixed short Sizes[257]; /// /// Initializes a new instance of the struct. /// /// The to use for buffer allocations. - /// The code lengths + /// The code lengths /// The huffman values - public HuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan count, ReadOnlySpan values) + public HuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan codeLengths, ReadOnlySpan values) { const int Length = 257; using (IMemoryOwner huffcode = memoryAllocator.Allocate(Length)) { ref short huffcodeRef = ref MemoryMarshal.GetReference(huffcode.GetSpan()); + ref byte codeLengthsRef = ref MemoryMarshal.GetReference(codeLengths); // Figure C.1: make table of Huffman code length for each symbol - fixed (short* sizesRef = this.Sizes.Data) + ref short sizesRef = ref this.Sizes[0]; + short x = 0; + + for (short i = 1; i < 17; i++) { - short x = 0; - for (short i = 1; i < 17; i++) + byte length = Unsafe.Add(ref codeLengthsRef, i); + for (short j = 0; j < length; j++) { - byte l = count[i]; - for (short j = 0; j < l; j++) - { - sizesRef[x] = i; - x++; - } + Unsafe.Add(ref sizesRef, x++) = i; } + } - sizesRef[x] = 0; + Unsafe.Add(ref sizesRef, x) = 0; - // Figure C.2: generate the codes themselves - int k = 0; - fixed (int* valOffsetRef = this.ValOffset.Data) - fixed (uint* maxcodeRef = this.MaxCode.Data) + // Figure C.2: generate the codes themselves + int si = 0; + ref int valOffsetRef = ref this.ValOffset[0]; + ref uint maxcodeRef = ref this.MaxCode[0]; + + uint code = 0; + int k; + for (k = 1; k < 17; k++) + { + // Compute delta to add to code to compute symbol id. + Unsafe.Add(ref valOffsetRef, k) = (int)(si - code); + if (Unsafe.Add(ref sizesRef, si) == k) { - uint code = 0; - int j; - for (j = 1; j < 17; j++) + while (Unsafe.Add(ref sizesRef, si) == k) { - // Compute delta to add to code to compute symbol id. - valOffsetRef[j] = (int)(k - code); - if (sizesRef[k] == j) - { - while (sizesRef[k] == j) - { - Unsafe.Add(ref huffcodeRef, k++) = (short)code++; - } - } - - // Figure F.15: generate decoding tables for bit-sequential decoding. - // Compute largest code + 1 for this size. preshifted as need later. - maxcodeRef[j] = code << (16 - j); - code <<= 1; + Unsafe.Add(ref huffcodeRef, si++) = (short)code++; } - - maxcodeRef[j] = 0xFFFFFFFF; } - // Generate non-spec lookup tables to speed up decoding. - fixed (byte* lookaheadRef = this.Lookahead.Data) - { - const int FastBits = ScanDecoder.FastBits; - var fast = new Span(lookaheadRef, 1 << FastBits); - fast.Fill(0xFF); // Flag for non-accelerated + // Figure F.15: generate decoding tables for bit-sequential decoding. + // Compute largest code + 1 for this size. preshifted as we needit later. + Unsafe.Add(ref maxcodeRef, k) = code << (16 - k); + code <<= 1; + } + + Unsafe.Add(ref maxcodeRef, k) = 0xFFFFFFFF; - for (int i = 0; i < k; i++) + // Generate non-spec lookup tables to speed up decoding. + const int FastBits = ScanDecoder.FastBits; + ref byte fastRef = ref this.Lookahead[0]; + Unsafe.InitBlockUnaligned(ref fastRef, 0xFF, 1 << FastBits); // Flag for non-accelerated + + for (int i = 0; i < si; i++) + { + int size = Unsafe.Add(ref sizesRef, i); + if (size <= FastBits) + { + int c = Unsafe.Add(ref huffcodeRef, i) << (FastBits - size); + int m = 1 << (FastBits - size); + for (int l = 0; l < m; l++) { - int s = sizesRef[i]; - if (s <= ScanDecoder.FastBits) - { - int c = Unsafe.Add(ref huffcodeRef, i) << (FastBits - s); - int m = 1 << (FastBits - s); - for (int j = 0; j < m; j++) - { - fast[c + j] = (byte)i; - } - } + Unsafe.Add(ref fastRef, c + l) = (byte)i; } } } } - fixed (byte* huffValRef = this.Values.Data) - { - var huffValSpan = new Span(huffValRef, 256); - values.CopyTo(huffValSpan); - } + Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), 256); } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs index 900dd3bc89..0108e30815 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs @@ -2,9 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Runtime.InteropServices; - using SixLabors.ImageSharp.Memory; -using SixLabors.Memory; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder @@ -43,6 +41,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// /// Initializes a new instance of the struct. /// + /// The raw jpeg data. + /// The raw component. public JpegBlockPostProcessor(IRawJpegData decoder, IJpegComponent component) { int qtIndex = component.QuantizationTableIndex; @@ -61,9 +61,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// - Level shift by +128, clip to [0, 255] /// - Copy the resultin color values into 'destArea' scaling up the block by amount defined in /// + /// The source block. + /// The destination buffer area. public void ProcessBlockColorsInto( ref Block8x8 sourceBlock, - BufferArea destArea) + in BufferArea destArea) { ref Block8x8F b = ref this.SourceBlock; b.LoadFrom(ref sourceBlock); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs index da089fa44a..36a3dc2d26 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs @@ -45,6 +45,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// 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 /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs index a6d5faaea1..3e7108b151 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs @@ -14,22 +14,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// /// Describes the EXIF specific markers /// - public static readonly byte[] JFifMarker = Encoding.UTF8.GetBytes("JFIF\0"); + public static readonly byte[] JFifMarker = Encoding.ASCII.GetBytes("JFIF\0"); /// /// Describes the EXIF specific markers /// - public static readonly byte[] IccMarker = Encoding.UTF8.GetBytes("ICC_PROFILE\0"); + public static readonly byte[] IccMarker = Encoding.ASCII.GetBytes("ICC_PROFILE\0"); /// /// Describes the ICC specific markers /// - public static readonly byte[] ExifMarker = Encoding.UTF8.GetBytes("Exif\0\0"); + public static readonly byte[] ExifMarker = Encoding.ASCII.GetBytes("Exif\0\0"); /// /// Describes Adobe specific markers /// - public static readonly byte[] AdobeMarker = Encoding.UTF8.GetBytes("Adobe"); + public static readonly byte[] AdobeMarker = Encoding.ASCII.GetBytes("Adobe"); /// /// Returns a value indicating whether the passed bytes are a match to the profile identifier diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs new file mode 100644 index 0000000000..4e11568c8d --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs @@ -0,0 +1,140 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder +{ + /// + /// Provides methods to evaluate the quality of an image. + /// Ported from + /// + internal static class QualityEvaluator + { + private static readonly int[] Hash = new int[101] + { + 1020, 1015, 932, 848, 780, 735, 702, 679, 660, 645, + 632, 623, 613, 607, 600, 594, 589, 585, 581, 571, + 555, 542, 529, 514, 494, 474, 457, 439, 424, 410, + 397, 386, 373, 364, 351, 341, 334, 324, 317, 309, + 299, 294, 287, 279, 274, 267, 262, 257, 251, 247, + 243, 237, 232, 227, 222, 217, 213, 207, 202, 198, + 192, 188, 183, 177, 173, 168, 163, 157, 153, 148, + 143, 139, 132, 128, 125, 119, 115, 108, 104, 99, + 94, 90, 84, 79, 74, 70, 64, 59, 55, 49, + 45, 40, 34, 30, 25, 20, 15, 11, 6, 4, + 0 + }; + + private static readonly int[] Sums = new int[101] + { + 32640, 32635, 32266, 31495, 30665, 29804, 29146, 28599, 28104, + 27670, 27225, 26725, 26210, 25716, 25240, 24789, 24373, 23946, + 23572, 22846, 21801, 20842, 19949, 19121, 18386, 17651, 16998, + 16349, 15800, 15247, 14783, 14321, 13859, 13535, 13081, 12702, + 12423, 12056, 11779, 11513, 11135, 10955, 10676, 10392, 10208, + 9928, 9747, 9564, 9369, 9193, 9017, 8822, 8639, 8458, + 8270, 8084, 7896, 7710, 7527, 7347, 7156, 6977, 6788, + 6607, 6422, 6236, 6054, 5867, 5684, 5495, 5305, 5128, + 4945, 4751, 4638, 4442, 4248, 4065, 3888, 3698, 3509, + 3326, 3139, 2957, 2775, 2586, 2405, 2216, 2037, 1846, + 1666, 1483, 1297, 1109, 927, 735, 554, 375, 201, + 128, 0 + }; + + private static readonly int[] Hash1 = new int[101] + { + 510, 505, 422, 380, 355, 338, 326, 318, 311, 305, + 300, 297, 293, 291, 288, 286, 284, 283, 281, 280, + 279, 278, 277, 273, 262, 251, 243, 233, 225, 218, + 211, 205, 198, 193, 186, 181, 177, 172, 168, 164, + 158, 156, 152, 148, 145, 142, 139, 136, 133, 131, + 129, 126, 123, 120, 118, 115, 113, 110, 107, 105, + 102, 100, 97, 94, 92, 89, 87, 83, 81, 79, + 76, 74, 70, 68, 66, 63, 61, 57, 55, 52, + 50, 48, 44, 42, 39, 37, 34, 31, 29, 26, + 24, 21, 18, 16, 13, 11, 8, 6, 3, 2, + 0 + }; + + private static readonly int[] Sums1 = new int[101] + { + 16320, 16315, 15946, 15277, 14655, 14073, 13623, 13230, 12859, + 12560, 12240, 11861, 11456, 11081, 10714, 10360, 10027, 9679, + 9368, 9056, 8680, 8331, 7995, 7668, 7376, 7084, 6823, + 6562, 6345, 6125, 5939, 5756, 5571, 5421, 5240, 5086, + 4976, 4829, 4719, 4616, 4463, 4393, 4280, 4166, 4092, + 3980, 3909, 3835, 3755, 3688, 3621, 3541, 3467, 3396, + 3323, 3247, 3170, 3096, 3021, 2952, 2874, 2804, 2727, + 2657, 2583, 2509, 2437, 2362, 2290, 2211, 2136, 2068, + 1996, 1915, 1858, 1773, 1692, 1620, 1552, 1477, 1398, + 1326, 1251, 1179, 1109, 1031, 961, 884, 814, 736, + 667, 592, 518, 441, 369, 292, 221, 151, 86, + 64, 0 + }; + + /// + /// Returns an estimated quality of the image based on the quantization tables. + /// + /// The quantization tables. + /// The . + public static int EstimateQuality(Block8x8F[] quantizationTables) + { + int quality = 75; + float sum = 0; + + for (int i = 0; i < quantizationTables.Length; i++) + { + ref Block8x8F qTable = ref quantizationTables[i]; + for (int j = 0; j < Block8x8F.Size; j++) + { + sum += qTable[j]; + } + } + + ref Block8x8F qTable0 = ref quantizationTables[0]; + ref Block8x8F qTable1 = ref quantizationTables[1]; + + if (!qTable0.Equals(default)) + { + if (!qTable1.Equals(default)) + { + quality = (int)(qTable0[2] + + qTable0[53] + + qTable1[0] + + qTable1[Block8x8F.Size - 1]); + + for (int i = 0; i < 100; i++) + { + if (quality < Hash[i] && sum < Sums[i]) + { + continue; + } + + if (((quality <= Hash[i]) && (sum <= Sums[i])) || (i >= 50)) + { + return i + 1; + } + } + } + else + { + quality = (int)(qTable0[2] + qTable0[53]); + + for (int i = 0; i < 100; i++) + { + if (quality < Hash1[i] && sum < Sums1[i]) + { + continue; + } + + if (((quality <= Hash1[i]) && (sum <= Sums1[i])) || (i >= 50)) + { + return i + 1; + } + } + } + } + + return quality; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs index 8c525335bc..351e453484 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ScanDecoder.cs @@ -34,9 +34,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // The restart interval. private readonly int restartInterval; - // The current component index. - private readonly int componentIndex; - // The number of interleaved components. private readonly int componentsLength; @@ -87,7 +84,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// The DC Huffman tables. /// The AC Huffman tables. /// The fast AC decoding tables. - /// The component index within the array. /// The length of the components. Different to the array length. /// The reset interval. /// The spectral selection start. @@ -100,7 +96,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder HuffmanTables dcHuffmanTables, HuffmanTables acHuffmanTables, FastACTables fastACTables, - int componentIndex, int componentsLength, int restartInterval, int spectralStart, @@ -117,7 +112,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.components = frame.Components; this.marker = JpegConstants.Markers.XFF; this.markerPosition = 0; - this.componentIndex = componentIndex; this.componentsLength = componentsLength; this.restartInterval = restartInterval; this.spectralStart = spectralStart; @@ -176,7 +170,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // Scan an interleaved mcu... process components in order for (int k = 0; k < this.componentsLength; k++) { - JpegComponent component = this.components[k]; + int order = this.frame.ComponentOrder[k]; + JpegComponent component = this.components[order]; ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; @@ -223,14 +218,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } /// - /// Non-interleaved data, we just need to process one block at a ti - /// in trivial scanline order - /// number of blocks to do just depends on how many actual "pixels" - /// component has, independent of interleaved MCU blocking and such + /// Non-interleaved data, we just need to process one block at a time in trivial scanline order + /// number of blocks to do just depends on how many actual "pixels" each component has, + /// independent of interleaved MCU blocking and such. /// private void ParseBaselineDataNonInterleaved() { - JpegComponent component = this.components[this.componentIndex]; + JpegComponent component = this.components[this.frame.ComponentOrder[0]]; int w = component.WidthInBlocks; int h = component.HeightInBlocks; @@ -295,7 +289,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // Scan an interleaved mcu... process components in order for (int k = 0; k < this.componentsLength; k++) { - JpegComponent component = this.components[k]; + int order = this.frame.ComponentOrder[k]; + JpegComponent component = this.components[order]; ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; int h = component.HorizontalSamplingFactor; int v = component.VerticalSamplingFactor; @@ -344,7 +339,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// private void ParseProgressiveDataNonInterleaved() { - JpegComponent component = this.components[this.componentIndex]; + JpegComponent component = this.components[this.frame.ComponentOrder[0]]; int w = component.WidthInBlocks; int h = component.HeightInBlocks; @@ -729,8 +724,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } uint k = LRot(this.codeBuffer, n); - this.codeBuffer = k & ~Bmask[n]; - k &= Bmask[n]; + uint mask = Bmask[n]; + this.codeBuffer = k & ~mask; + k &= mask; this.codeBits -= n; return (int)k; } @@ -804,7 +800,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } [MethodImpl(InliningOptions.ShortMethod)] - private int DecodeHuffman(ref HuffmanTable table) + private unsafe int DecodeHuffman(ref HuffmanTable table) { this.CheckBits(); @@ -829,7 +825,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } [MethodImpl(InliningOptions.ColdPath)] - private int DecodeHuffmanSlow(ref HuffmanTable table) + private unsafe int DecodeHuffmanSlow(ref HuffmanTable table) { // Naive test is to shift the code_buffer down so k bits are // valid, then test against MaxCode. To speed this up, we've @@ -839,7 +835,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // that way we don't need to shift inside the loop. uint temp = this.codeBuffer >> 16; int k; - for (k = FastBits + 1; ; k++) + for (k = FastBits + 1; ; ++k) { if (temp < table.MaxCode[k]) { diff --git a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs index 4076b7da82..53108de934 100644 --- a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs +++ b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs @@ -8,17 +8,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// internal interface IJpegEncoderOptions { - /// - /// Gets a value indicating whether the metadata should be ignored when the image is being decoded. - /// - bool IgnoreMetadata { get; } - /// /// Gets the quality, that will be used to encode the image. Quality /// index must be between 0 and 100 (compression from max to min). /// /// The quality of the jpg image from 0 to 100. - int Quality { get; } + int? Quality { get; } /// /// Gets the subsample ration, that will be used to encode the image. diff --git a/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs b/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs index 1d3be063dd..cb7fc19446 100644 --- a/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs +++ b/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs @@ -34,6 +34,6 @@ namespace SixLabors.ImageSharp /// Thrown if the stream is null. public static void SaveAsJpeg(this Image source, Stream stream, JpegEncoder encoder) where TPixel : struct, IPixel - => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ImageFormats.Jpeg)); + => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance)); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs b/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs index c3bf801ac8..9840a2ae88 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs @@ -9,12 +9,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public sealed class JpegConfigurationModule : IConfigurationModule { /// - public void Configure(Configuration config) + public void Configure(Configuration configuration) { - config.ImageFormatsManager.SetEncoder(ImageFormats.Jpeg, new JpegEncoder()); - config.ImageFormatsManager.SetDecoder(ImageFormats.Jpeg, new JpegDecoder()); - - config.ImageFormatsManager.AddImageFormatDetector(new JpegImageFormatDetector()); + configuration.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder()); + configuration.ImageFormatsManager.SetDecoder(JpegFormat.Instance, new JpegDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new JpegImageFormatDetector()); } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 7561afa1ef..22d9cbdee4 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -234,6 +234,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.InitExifProfile(); this.InitIccProfile(); this.InitDerivedMetaDataProperties(); + return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.MetaData); } @@ -258,17 +259,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.InputStream.Read(this.markerBuffer, 0, 2); byte marker = this.markerBuffer[1]; fileMarker = new JpegFileMarker(marker, (int)this.InputStream.Position - 2); + this.QuantizationTables = new Block8x8F[4]; // Only assign what we need if (!metadataOnly) { - this.QuantizationTables = new Block8x8F[4]; this.dcHuffmanTables = new HuffmanTables(); this.acHuffmanTables = new HuffmanTables(); this.fastACTables = new FastACTables(this.configuration.MemoryAllocator); } - while (fileMarker.Marker != JpegConstants.Markers.EOI) + // Break only when we discover a valid EOI marker. + // https://github.com/SixLabors/ImageSharp/issues/695 + while (fileMarker.Marker != JpegConstants.Markers.EOI + || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid)) { if (!fileMarker.Invalid) { @@ -310,15 +314,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg break; case JpegConstants.Markers.DQT: - if (metadataOnly) - { - this.InputStream.Skip(remaining); - } - else - { - this.ProcessDefineQuantizationTablesMarker(remaining); - } - + this.ProcessDefineQuantizationTablesMarker(remaining); break; case JpegConstants.Markers.DRI: @@ -463,13 +459,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } else if (this.isExif) { - double horizontalValue = this.MetaData.ExifProfile.TryGetValue(ExifTag.XResolution, out ExifValue horizontalTag) - ? ((Rational)horizontalTag.Value).ToDouble() - : 0; - - double verticalValue = this.MetaData.ExifProfile.TryGetValue(ExifTag.YResolution, out ExifValue verticalTag) - ? ((Rational)verticalTag.Value).ToDouble() - : 0; + double horizontalValue = this.GetExifResolutionValue(ExifTag.XResolution); + double verticalValue = this.GetExifResolutionValue(ExifTag.YResolution); if (horizontalValue > 0 && verticalValue > 0) { @@ -480,6 +471,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } } + private double GetExifResolutionValue(ExifTag tag) + { + if (!this.MetaData.ExifProfile.TryGetValue(tag, out ExifValue exifValue)) + { + return 0; + } + + switch (exifValue.DataType) + { + case ExifDataType.Rational: + return ((Rational)exifValue.Value).ToDouble(); + case ExifDataType.Long: + return (uint)exifValue.Value; + case ExifDataType.DoubleFloat: + return (double)exifValue.Value; + default: + return 0; + } + } + /// /// Extends the profile with additional data. /// @@ -499,7 +510,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The remaining bytes in the segment block. private void ProcessApplicationHeaderMarker(int remaining) { - if (remaining < 5) + // We can only decode JFif identifiers. + if (remaining < JFifMarker.Length) { // Skip the application header length this.InputStream.Skip(remaining); @@ -689,6 +701,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { throw new ImageFormatException("DQT has wrong length"); } + + this.MetaData.GetFormatMetaData(JpegFormat.Instance).Quality = QualityEvaluator.EstimateQuality(this.QuantizationTables); } /// @@ -733,11 +747,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (!metadataOnly) { // No need to pool this. They max out at 4 - this.Frame.ComponentIds = new byte[this.Frame.ComponentCount]; - this.Frame.Components = new JpegComponent[this.Frame.ComponentCount]; + this.Frame.ComponentIds = new byte[this.ComponentCount]; + this.Frame.ComponentOrder = new byte[this.ComponentCount]; + this.Frame.Components = new JpegComponent[this.ComponentCount]; this.ColorSpace = this.DeduceJpegColorSpace(); - for (int i = 0; i < this.Frame.ComponentCount; i++) + for (int i = 0; i < this.ComponentCount; i++) { byte hv = this.temp[index + 1]; int h = hv >> 4; @@ -809,10 +824,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg codeLengths.GetSpan(), huffmanValues.GetSpan()); - if (huffmanTableSpec >> 4 != 0) + if (tableType != 0) { // Build a table that decodes both magnitude and value of small ACs in one go. - this.fastACTables.BuildACTableLut(huffmanTableSpec & 15, this.acHuffmanTables); + this.fastACTables.BuildACTableLut(tableIndex, this.acHuffmanTables); } } } @@ -853,6 +868,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (selector == id) { componentIndex = j; + break; } } @@ -865,6 +881,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int tableSpec = this.InputStream.ReadByte(); component.DCHuffmanTableId = tableSpec >> 4; component.ACHuffmanTableId = tableSpec & 15; + this.Frame.ComponentOrder[i] = (byte)componentIndex; } this.InputStream.Read(this.temp, 0, 3); @@ -879,7 +896,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.dcHuffmanTables, this.acHuffmanTables, this.fastACTables, - componentIndex, selectorsCount, this.resetInterval, spectralStart, @@ -899,9 +915,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The values [MethodImpl(MethodImplOptions.AggressiveInlining)] private void BuildHuffmanTable(HuffmanTables tables, int index, ReadOnlySpan codeLengths, ReadOnlySpan values) - { - tables[index] = new HuffmanTable(this.configuration.MemoryAllocator, codeLengths, values); - } + => tables[index] = new HuffmanTable(this.configuration.MemoryAllocator, codeLengths, values); /// /// Reads a from the stream advancing it by two bytes diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 0f389dee0f..d649d30418 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -11,17 +11,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions { - /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. - /// - public bool IgnoreMetadata { get; set; } - /// /// 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; } = 75; + public int? Quality { get; set; } /// /// Gets or sets the subsample ration, that will be used to encode the image. diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index f7b6fe9967..a4677ba2b7 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -123,19 +123,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg private readonly byte[] huffmanBuffer = new byte[179]; /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// Gets or sets the subsampling method to use. /// - private readonly bool ignoreMetadata; + private JpegSubsample? subsample; /// /// The quality, that will be used to encode the image. /// - private readonly int quality; - - /// - /// Gets or sets the subsampling method to use. - /// - private readonly JpegSubsample? subsample; + private readonly int? quality; /// /// The accumulated bits to write to the stream. @@ -168,11 +163,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The options public JpegEncoderCore(IJpegEncoderOptions options) { - // System.Drawing produces identical output for jpegs with a quality parameter of 0 and 1. - this.quality = options.Quality.Clamp(1, 100); - this.subsample = options.Subsample ?? (this.quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); - - this.ignoreMetadata = options.IgnoreMetadata; + this.quality = options.Quality; + this.subsample = options.Subsample; } /// @@ -194,16 +186,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } this.outputStream = stream; + ImageMetaData metaData = image.MetaData; + + // System.Drawing produces identical output for jpegs with a quality parameter of 0 and 1. + int qlty = (this.quality ?? metaData.GetFormatMetaData(JpegFormat.Instance).Quality).Clamp(1, 100); + this.subsample = this.subsample ?? (qlty >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); // Convert from a quality rating to a scaling factor. int scale; - if (this.quality < 50) + if (qlty < 50) { - scale = 5000 / this.quality; + scale = 5000 / qlty; } else { - scale = 200 - (this.quality * 2); + scale = 200 - (qlty * 2); } // Initialize the quantization tables. @@ -214,10 +211,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int componentCount = 3; // Write the Start Of Image marker. - this.WriteApplicationHeader(image.MetaData); + this.WriteApplicationHeader(metaData); // Write Exif and ICC profiles - this.WriteProfiles(image); + this.WriteProfiles(metaData); // Write the quantization tables. this.WriteDefineQuantizationTables(); @@ -624,6 +621,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// private void WriteExifProfile(ExifProfile exifProfile) { + if (exifProfile is null) + { + return; + } + const int MaxBytesApp1 = 65533; const int MaxBytesWithExifId = 65527; @@ -762,19 +764,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Writes the metadata profiles to the image. /// - /// The image. - /// The pixel format. - private void WriteProfiles(Image image) - where TPixel : struct, IPixel + /// The image meta data. + private void WriteProfiles(ImageMetaData metaData) { - if (this.ignoreMetadata) + if (metaData is null) { return; } - image.MetaData.SyncProfiles(); - this.WriteExifProfile(image.MetaData.ExifProfile); - this.WriteIccProfile(image.MetaData.IccProfile); + metaData.SyncProfiles(); + this.WriteExifProfile(metaData.ExifProfile); + this.WriteIccProfile(metaData.IccProfile); } /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs index 9a18f14d30..6b23ceac7a 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs @@ -8,8 +8,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Registers the image encoders, decoders and mime type detectors for the jpeg format. /// - internal sealed class JpegFormat : IImageFormat + public sealed class JpegFormat : IImageFormat { + private JpegFormat() + { + } + + /// + /// Gets the current instance. + /// + public static JpegFormat Instance { get; } = new JpegFormat(); + /// public string Name => "JPEG"; @@ -21,5 +30,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public IEnumerable FileExtensions => JpegConstants.FileExtensions; + + /// + public JpegMetaData CreateDefaultFormatMetaData() => new JpegMetaData(); } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs b/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs index e25957efcf..7594f44770 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public IImageFormat DetectFormat(ReadOnlySpan header) { - return this.IsSupportedFileFormat(header) ? ImageFormats.Jpeg : null; + return this.IsSupportedFileFormat(header) ? JpegFormat.Instance : null; } private bool IsSupportedFileFormat(ReadOnlySpan header) diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetaData.cs b/src/ImageSharp/Formats/Jpeg/JpegMetaData.cs new file mode 100644 index 0000000000..fcad29e5d0 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/JpegMetaData.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Jpeg +{ + /// + /// Provides Jpeg specific metadata information for the image. + /// + public class JpegMetaData : IDeepCloneable + { + /// + /// 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.Quality = other.Quality; + + /// + /// Gets or sets the encoded quality. + /// + public int Quality { get; set; } = 75; + + /// + public IDeepCloneable DeepClone() => new JpegMetaData(this); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/Adam7.cs b/src/ImageSharp/Formats/Png/Adam7.cs new file mode 100644 index 0000000000..4e6485b55f --- /dev/null +++ b/src/ImageSharp/Formats/Png/Adam7.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Png +{ + /// + /// Constants and helper methods for the Adam7 interlacing algorithm. + /// + 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 }; + + /// + /// 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 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 }; + + /// + /// 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) + { + 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}"); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/Chunks/PhysicalChunkData.cs b/src/ImageSharp/Formats/Png/Chunks/PhysicalChunkData.cs new file mode 100644 index 0000000000..07fc688d50 --- /dev/null +++ b/src/ImageSharp/Formats/Png/Chunks/PhysicalChunkData.cs @@ -0,0 +1,107 @@ +using System; +using System.Buffers.Binary; +using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.MetaData; + +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; } + + /// + /// 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) + { + uint hResolution = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(0, 4)); + uint vResolution = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(4, 4)); + byte unit = data[8]; + + return new PhysicalChunkData(hResolution, vResolution, unit); + } + + /// + /// Constructs the PngPhysicalChunkData from the provided metadata. + /// If the resolution units are not in meters, they are automatically convereted. + /// + /// 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); + } + + /// + /// Writes the data to the given buffer. + /// + /// The buffer. + public void WriteTo(Span buffer) + { + BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(0, 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 ffcf9b0f30..bc5a54e8b9 100644 --- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs @@ -30,7 +30,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters // Average(x) + floor((Raw(x-bpp)+Prior(x))/2) int x = 1; - for (; x <= bytesPerPixel /* Note the <= because x starts at 1 */; ++x) { + 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)); @@ -68,7 +69,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters resultBaseRef = 3; int x = 0; - for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) { + 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; @@ -77,7 +79,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters sum += ImageMaths.FastAbs(unchecked((sbyte)res)); } - for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) { + 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); @@ -97,9 +100,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters /// The above byte /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Average(byte left, byte above) - { - return (left + above) >> 1; - } + private static int Average(byte left, byte above) => (left + above) >> 1; } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs index 0d3df079c9..4ffc39bdbd 100644 --- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs @@ -72,7 +72,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters resultBaseRef = 4; int x = 0; - for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) { + 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; @@ -81,7 +82,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters sum += ImageMaths.FastAbs(unchecked((sbyte)res)); } - for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) { + 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); diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs index cfb7781be4..6af5f0b648 100644 --- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs @@ -59,7 +59,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters resultBaseRef = 1; int x = 0; - for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) { + 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); @@ -67,7 +68,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters sum += ImageMaths.FastAbs(unchecked((sbyte)res)); } - for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) { + 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; diff --git a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs index f3231fa22a..7e5a9fa6b8 100644 --- a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs +++ b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs @@ -14,17 +14,17 @@ namespace SixLabors.ImageSharp.Formats.Png /// 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; } + PngColorType? ColorType { get; } /// /// Gets the filter method. /// - PngFilterMethod FilterMethod { get; } + PngFilterMethod? FilterMethod { get; } /// /// Gets the compression level 1-9. @@ -33,15 +33,13 @@ namespace SixLabors.ImageSharp.Formats.Png int CompressionLevel { get; } /// - /// Gets the gamma value, that will be written - /// the the stream, when the property - /// is set to true. The default value is 2.2F. + /// Gets the gamma value, that will be written the the image. /// /// The gamma value of the image. - float Gamma { get; } + float? Gamma { get; } /// - /// Gets quantizer for reducing the color count. + /// Gets the quantizer for reducing the color count. /// IQuantizer Quantizer { get; } @@ -49,11 +47,5 @@ namespace SixLabors.ImageSharp.Formats.Png /// Gets the transparency threshold. /// byte Threshold { get; } - - /// - /// Gets a value indicating whether this instance should write - /// gamma information to the stream. The default value is false. - /// - bool WriteGamma { get; } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/ImageExtensions.cs b/src/ImageSharp/Formats/Png/ImageExtensions.cs index a65845e02d..c73ec6f57e 100644 --- a/src/ImageSharp/Formats/Png/ImageExtensions.cs +++ b/src/ImageSharp/Formats/Png/ImageExtensions.cs @@ -35,6 +35,6 @@ namespace SixLabors.ImageSharp /// Thrown if the stream is null. public static void SaveAsPng(this Image source, Stream stream, PngEncoder encoder) where TPixel : struct, IPixel - => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ImageFormats.Png)); + => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance)); } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngBitDepth.cs b/src/ImageSharp/Formats/Png/PngBitDepth.cs index 0c22a4c913..0321b532ab 100644 --- a/src/ImageSharp/Formats/Png/PngBitDepth.cs +++ b/src/ImageSharp/Formats/Png/PngBitDepth.cs @@ -7,8 +7,23 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Provides enumeration for the available PNG bit depths. /// - public enum PngBitDepth + public enum PngBitDepth : byte { + /// + /// 1 bit per sample or per palette index (not per pixel). + /// + Bit1 = 1, + + /// + /// 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, + /// /// 8 bits per sample or per palette index (not per pixel). /// diff --git a/src/ImageSharp/Formats/Png/PngChunkType.cs b/src/ImageSharp/Formats/Png/PngChunkType.cs index e0844ca6b8..7654c17014 100644 --- a/src/ImageSharp/Formats/Png/PngChunkType.cs +++ b/src/ImageSharp/Formats/Png/PngChunkType.cs @@ -8,58 +8,58 @@ namespace SixLabors.ImageSharp.Formats.Png /// internal enum PngChunkType : uint { - /// - /// 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, // IHDR - - /// - /// The PLTE chunk contains from 1 to 256 palette entries, each a three byte - /// series in the RGB format. - /// - Palette = 0x504C5445U, // PLTE - /// /// 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, // IDAT + 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, // IEND + End = 0x49454E44U, /// - /// This 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). + /// 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. /// - PaletteAlpha = 0x74524E53U, // tRNS + Header = 0x49484452U, /// - /// 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. + /// 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. /// - Text = 0x74455874U, // tEXt + Exif = 0x65584966U, /// /// This chunk specifies the relationship between the image samples and the desired /// display output intensity. /// - Gamma = 0x67414D41U, // gAMA + Gamma = 0x67414D41U, /// /// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. /// - Physical = 0x70485973U, // pHYs + Physical = 0x70485973U, /// - /// The data chunk which contains the Exif profile. + /// 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, + + /// + /// This 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). /// - Exif = 0x65584966U // eXIf + PaletteAlpha = 0x74524E53U } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngConfigurationModule.cs b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs index 64dad23bca..3c9fddbad4 100644 --- a/src/ImageSharp/Formats/Png/PngConfigurationModule.cs +++ b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs @@ -11,8 +11,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// public void Configure(Configuration configuration) { - configuration.ImageFormatsManager.SetEncoder(ImageFormats.Png, new PngEncoder()); - configuration.ImageFormatsManager.SetDecoder(ImageFormats.Png, new PngDecoder()); + 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 ff25e26b7a..62a7b74aba 100644 --- a/src/ImageSharp/Formats/Png/PngConstants.cs +++ b/src/ImageSharp/Formats/Png/PngConstants.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The default encoding for text metadata. /// - public static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ASCII"); + public static readonly Encoding DefaultEncoding = Encoding.ASCII; /// /// The list of mimetypes that equate to a png. @@ -41,5 +41,17 @@ namespace SixLabors.ImageSharp.Formats.Png /// 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 } + }; } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index aa96b926ca..d66ac6c0d2 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -3,14 +3,12 @@ using System; using System.Buffers.Binary; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Formats.Png.Zlib; using SixLabors.ImageSharp.Memory; @@ -27,51 +25,9 @@ namespace SixLabors.ImageSharp.Formats.Png internal sealed class PngDecoderCore { /// - /// The dictionary of available color types. + /// Reusable buffer. /// - private 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 amount to increment when processing each column per scanline for each interlaced pass - /// - private static readonly int[] Adam7ColumnIncrement = { 8, 8, 4, 4, 2, 2, 1 }; - - /// - /// The index to start at when processing each column per scanline for each interlaced pass - /// - private static readonly int[] Adam7FirstColumn = { 0, 4, 0, 2, 0, 1, 0 }; - - /// - /// The index to start at when processing each row per scanline for each interlaced pass - /// - private static readonly int[] Adam7FirstRow = { 0, 0, 4, 0, 2, 0, 1 }; - - /// - /// The amount to increment when processing each row per scanline for each interlaced pass - /// - private static readonly int[] Adam7RowIncrement = { 8, 8, 8, 4, 4, 2, 2 }; - - /// - /// Reusable buffer for reading chunk types. - /// - private readonly byte[] chunkTypeBuffer = new byte[4]; - - /// - /// Reusable buffer for reading chunk lengths. - /// - private readonly byte[] chunkLengthBuffer = new byte[4]; - - /// - /// Reusable buffer for reading crc values. - /// - private readonly byte[] crcBuffer = new byte[4]; + private readonly byte[] buffer = new byte[4]; /// /// Reusable crc for validating chunks. @@ -93,6 +49,11 @@ namespace SixLabors.ImageSharp.Formats.Png /// private readonly bool ignoreMetadata; + /// + /// Used the manage memory allocations. + /// + private readonly MemoryAllocator memoryAllocator; + /// /// The stream to decode from. /// @@ -146,7 +107,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The index of the current scanline being processed /// - private int currentRow = Adam7FirstRow[0]; + private int currentRow = Adam7.FirstRow[0]; /// /// The current pass for an interlaced PNG @@ -188,6 +149,11 @@ namespace SixLabors.ImageSharp.Formats.Png /// private bool hasTrans; + /// + /// The next chunk of data to return + /// + private PngChunk? nextChunk; + /// /// Initializes a new instance of the class. /// @@ -196,12 +162,11 @@ namespace SixLabors.ImageSharp.Formats.Png public PngDecoderCore(Configuration configuration, IPngDecoderOptions options) { this.configuration = configuration ?? Configuration.Default; + this.memoryAllocator = this.configuration.MemoryAllocator; this.textEncoding = options.TextEncoding ?? PngConstants.DefaultEncoding; this.ignoreMetadata = options.IgnoreMetadata; } - private MemoryAllocator MemoryAllocator => this.configuration.MemoryAllocator; - /// /// Decodes the stream to the image. /// @@ -217,70 +182,73 @@ namespace SixLabors.ImageSharp.Formats.Png public Image Decode(Stream stream) where TPixel : struct, IPixel { - var metadata = new ImageMetaData(); + var metaData = new ImageMetaData(); + PngMetaData pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance); this.currentStream = stream; this.currentStream.Skip(8); Image image = null; try { - using (var deframeStream = new ZlibInflateStream(this.currentStream)) + while (!this.isEndChunkReached && this.TryReadChunk(out PngChunk chunk)) { - while (!this.isEndChunkReached && this.TryReadChunk(out PngChunk chunk)) + try { - try + switch (chunk.Type) { - switch (chunk.Type) - { - case PngChunkType.Header: - this.ReadHeaderChunk(chunk.Data.Array); - this.ValidateHeader(); - break; - case PngChunkType.Physical: - this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); - break; - case PngChunkType.Data: - if (image is null) - { - this.InitializeImage(metadata, out image); - } + case PngChunkType.Header: + this.ReadHeaderChunk(pngMetaData, chunk.Data.Array); + break; + case PngChunkType.Physical: + this.ReadPhysicalChunk(metaData, chunk.Data.GetSpan()); + break; + case PngChunkType.Gamma: + this.ReadGammaChunk(pngMetaData, chunk.Data.GetSpan()); + break; + case PngChunkType.Data: + if (image is null) + { + this.InitializeImage(metaData, out image); + } + using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk)) + { deframeStream.AllocateNewBytes(chunk.Length); this.ReadScanlines(deframeStream.CompressedStream, image.Frames.RootFrame); - this.currentStream.Read(this.crcBuffer, 0, 4); - break; - case PngChunkType.Palette: - byte[] pal = new byte[chunk.Length]; - Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length); - this.palette = pal; - break; - case PngChunkType.PaletteAlpha: - byte[] alpha = new byte[chunk.Length]; - Buffer.BlockCopy(chunk.Data.Array, 0, alpha, 0, chunk.Length); - this.paletteAlpha = alpha; - this.AssignTransparentMarkers(alpha); - break; - case PngChunkType.Text: - this.ReadTextChunk(metadata, chunk.Data.Array, chunk.Length); - break; - case PngChunkType.Exif: - if (!this.ignoreMetadata) - { - byte[] exifData = new byte[chunk.Length]; - Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length); - metadata.ExifProfile = new ExifProfile(exifData); - } - - break; - case PngChunkType.End: - this.isEndChunkReached = true; - break; - } - } - finally - { - chunk.Data?.Dispose(); // Data is rented in ReadChunkData() + } + + break; + case PngChunkType.Palette: + byte[] pal = new byte[chunk.Length]; + Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length); + this.palette = pal; + break; + case PngChunkType.PaletteAlpha: + byte[] alpha = new byte[chunk.Length]; + Buffer.BlockCopy(chunk.Data.Array, 0, alpha, 0, chunk.Length); + this.paletteAlpha = alpha; + this.AssignTransparentMarkers(alpha); + break; + case PngChunkType.Text: + this.ReadTextChunk(metaData, chunk.Data.Array.AsSpan(0, chunk.Length)); + break; + case PngChunkType.Exif: + if (!this.ignoreMetadata) + { + byte[] exifData = new byte[chunk.Length]; + Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length); + metaData.ExifProfile = new ExifProfile(exifData); + } + + break; + case PngChunkType.End: + this.isEndChunkReached = true; + break; } } + finally + { + chunk.Data?.Dispose(); // Data is rented in ReadChunkData() + } } if (image is null) @@ -303,7 +271,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// The containing image data. public IImageInfo Identify(Stream stream) { - var metadata = new ImageMetaData(); + var metaData = new ImageMetaData(); + PngMetaData pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance); this.currentStream = stream; this.currentStream.Skip(8); try @@ -315,17 +284,19 @@ namespace SixLabors.ImageSharp.Formats.Png switch (chunk.Type) { case PngChunkType.Header: - this.ReadHeaderChunk(chunk.Data.Array); - this.ValidateHeader(); + this.ReadHeaderChunk(pngMetaData, chunk.Data.Array); break; case PngChunkType.Physical: - this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); + this.ReadPhysicalChunk(metaData, chunk.Data.GetSpan()); + break; + case PngChunkType.Gamma: + this.ReadGammaChunk(pngMetaData, chunk.Data.GetSpan()); break; case PngChunkType.Data: this.SkipChunkDataAndCrc(chunk); break; case PngChunkType.Text: - this.ReadTextChunk(metadata, chunk.Data.Array, chunk.Length); + this.ReadTextChunk(metaData, chunk.Data.Array.AsSpan(0, chunk.Length)); break; case PngChunkType.End: this.isEndChunkReached = true; @@ -349,7 +320,7 @@ namespace SixLabors.ImageSharp.Formats.Png throw new ImageFormatException("PNG Image does not contain a header chunk"); } - return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata); + return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metaData); } /// @@ -360,9 +331,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte ReadByteLittleEndian(ReadOnlySpan buffer, int offset) - { - return (byte)(((buffer[offset] & 0xFF) << 16) | (buffer[offset + 1] & 0xFF)); - } + => (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 @@ -381,19 +350,19 @@ namespace SixLabors.ImageSharp.Formats.Png return false; } - buffer = this.MemoryAllocator.AllocateManagedByteBuffer(bytesPerScanline * 8 / bits, AllocationOptions.Clean); - byte[] result = buffer.Array; + buffer = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline * 8 / bits, AllocationOptions.Clean); + ref byte sourceRef = ref MemoryMarshal.GetReference(source); + ref byte resultRef = ref buffer.Array[0]; int mask = 0xFF >> (8 - bits); int resultOffset = 0; - for (int i = 0; i < bytesPerScanline - 1; i++) + for (int i = 0; i < bytesPerScanline; i++) { - byte b = source[i]; + byte b = Unsafe.Add(ref sourceRef, i); for (int shift = 0; shift < 8; shift += bits) { int colorIndex = (b >> (8 - bits - shift)) & mask; - result[resultOffset] = (byte)colorIndex; - + Unsafe.Add(ref resultRef, resultOffset) = (byte)colorIndex; resultOffset++; } } @@ -408,26 +377,26 @@ namespace SixLabors.ImageSharp.Formats.Png /// The data containing physical data. private void ReadPhysicalChunk(ImageMetaData metadata, ReadOnlySpan data) { - // The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. It contains: - // Pixels per unit, X axis: 4 bytes (unsigned integer) - // Pixels per unit, Y axis: 4 bytes (unsigned integer) - // Unit specifier: 1 byte - // - // The following values are legal for 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. - int hResolution = BinaryPrimitives.ReadInt32BigEndian(data.Slice(0, 4)); - int vResolution = BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4)); - byte unit = data[8]; - - metadata.ResolutionUnits = unit == byte.MinValue + var physicalChunk = PhysicalChunkData.Parse(data); + + metadata.ResolutionUnits = physicalChunk.UnitSpecifier == byte.MinValue ? PixelResolutionUnit.AspectRatio : PixelResolutionUnit.PixelsPerMeter; - metadata.HorizontalResolution = hResolution; - metadata.VerticalResolution = vResolution; + 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 void ReadGammaChunk(PngMetaData pngMetadata, ReadOnlySpan data) + { + // The value is encoded as a 4-byte unsigned integer, representing gamma times 100000. + // For example, a gamma of 1/2.2 would be stored as 45455. + pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) / 100_000F; } /// @@ -448,7 +417,7 @@ namespace SixLabors.ImageSharp.Formats.Png this.bytesPerSample = this.header.BitDepth / 8; } - this.previousScanline = this.MemoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean); + this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean); this.scanline = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean); } @@ -567,22 +536,18 @@ namespace SixLabors.ImageSharp.Formats.Png 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; @@ -590,7 +555,7 @@ namespace SixLabors.ImageSharp.Formats.Png throw new ImageFormatException("Unknown filter type."); } - this.ProcessDefilteredScanline(this.scanline.Array, image); + this.ProcessDefilteredScanline(scanlineSpan, image); this.SwapBuffers(); this.currentRow++; @@ -609,7 +574,7 @@ namespace SixLabors.ImageSharp.Formats.Png { while (true) { - int numColumns = this.ComputeColumnsAdam7(this.pass); + int numColumns = Adam7.ComputeColumns(this.header.Width, this.pass); if (numColumns == 0) { @@ -641,22 +606,18 @@ namespace SixLabors.ImageSharp.Formats.Png break; case FilterType.Sub: - SubFilter.Decode(scanSpan, this.bytesPerPixel); break; case FilterType.Up: - UpFilter.Decode(scanSpan, prevSpan); break; case FilterType.Average: - AverageFilter.Decode(scanSpan, prevSpan, this.bytesPerPixel); break; case FilterType.Paeth: - PaethFilter.Decode(scanSpan, prevSpan, this.bytesPerPixel); break; @@ -665,11 +626,11 @@ namespace SixLabors.ImageSharp.Formats.Png } Span rowSpan = image.GetPixelRowSpan(this.currentRow); - this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, Adam7FirstColumn[this.pass], Adam7ColumnIncrement[this.pass]); + this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, Adam7.FirstColumn[this.pass], Adam7.ColumnIncrement[this.pass]); this.SwapBuffers(); - this.currentRow += Adam7RowIncrement[this.pass]; + this.currentRow += Adam7.RowIncrement[this.pass]; } this.pass++; @@ -677,7 +638,7 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.pass < 7) { - this.currentRow = Adam7FirstRow[this.pass]; + this.currentRow = Adam7.FirstRow[this.pass]; } else { @@ -696,211 +657,69 @@ namespace SixLabors.ImageSharp.Formats.Png private void ProcessDefilteredScanline(ReadOnlySpan defilteredScanline, ImageFrame pixels) where TPixel : struct, IPixel { - TPixel pixel = default; Span rowSpan = pixels.GetPixelRowSpan(this.currentRow); // Trim the first marker byte from the buffer ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); // Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent. - ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline, this.header.BitDepth, out IManagedByteBuffer buffer) + ReadOnlySpan scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline - 1, this.header.BitDepth, out IManagedByteBuffer buffer) ? buffer.GetSpan() : trimmed; switch (this.pngColorType) { case PngColorType.Grayscale: - - int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); - - if (!this.hasTrans) - { - if (this.header.BitDepth == 16) - { - Rgb48 rgb48 = default; - for (int x = 0, o = 0; x < this.header.Width; x++, o += 2) - { - ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - rgb48.R = luminance; - rgb48.G = luminance; - rgb48.B = luminance; - pixel.PackFromRgb48(rgb48); - rowSpan[x] = pixel; - } - } - else - { - // TODO: We should really be using Rgb24 here but IPixel does not have a PackFromRgb24 method. - var rgba32 = new Rgba32(0, 0, 0, byte.MaxValue); - for (int x = 0; x < this.header.Width; x++) - { - byte luminance = (byte)(scanlineSpan[x] * factor); - rgba32.R = luminance; - rgba32.G = luminance; - rgba32.B = luminance; - pixel.PackFromRgba32(rgba32); - rowSpan[x] = pixel; - } - } - } - else - { - if (this.header.BitDepth == 16) - { - Rgba64 rgba64 = default; - for (int x = 0, o = 0; x < this.header.Width; x++, o += 2) - { - ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - rgba64.R = luminance; - rgba64.G = luminance; - rgba64.B = luminance; - rgba64.A = luminance.Equals(this.luminance16Trans) ? ushort.MinValue : ushort.MaxValue; - - pixel.PackFromRgba64(rgba64); - rowSpan[x] = pixel; - } - } - else - { - Rgba32 rgba32 = default; - for (int x = 0; x < this.header.Width; x++) - { - byte luminance = (byte)(scanlineSpan[x] * factor); - rgba32.R = luminance; - rgba32.G = luminance; - rgba32.B = luminance; - rgba32.A = luminance.Equals(this.luminanceTrans) ? byte.MinValue : byte.MaxValue; - - pixel.PackFromRgba32(rgba32); - rowSpan[x] = pixel; - } - } - } + PngScanlineProcessor.ProcessGrayscaleScanline( + this.header, + scanlineSpan, + rowSpan, + this.hasTrans, + this.luminance16Trans, + this.luminanceTrans); break; case PngColorType.GrayscaleWithAlpha: - - if (this.header.BitDepth == 16) - { - Rgba64 rgba64 = default; - for (int x = 0, o = 0; x < this.header.Width; x++, o += 4) - { - ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - ushort alpha = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); - rgba64.R = luminance; - rgba64.G = luminance; - rgba64.B = luminance; - rgba64.A = alpha; - - pixel.PackFromRgba64(rgba64); - rowSpan[x] = pixel; - } - } - else - { - Rgba32 rgba32 = default; - for (int x = 0; x < this.header.Width; x++) - { - int offset = x * this.bytesPerPixel; - byte luminance = scanlineSpan[offset]; - byte alpha = scanlineSpan[offset + this.bytesPerSample]; - - rgba32.R = luminance; - rgba32.G = luminance; - rgba32.B = luminance; - rgba32.A = alpha; - - pixel.PackFromRgba32(rgba32); - rowSpan[x] = pixel; - } - } + PngScanlineProcessor.ProcessGrayscaleWithAlphaScanline( + this.header, + scanlineSpan, + rowSpan, + this.bytesPerPixel, + this.bytesPerSample); break; case PngColorType.Palette: - - this.ProcessScanlineFromPalette(scanlineSpan, rowSpan); + PngScanlineProcessor.ProcessPaletteScanline( + this.header, + scanlineSpan, + rowSpan, + this.palette, + this.paletteAlpha); break; case PngColorType.Rgb: - - if (!this.hasTrans) - { - if (this.header.BitDepth == 16) - { - Rgb48 rgb48 = default; - for (int x = 0, o = 0; x < this.header.Width; x++, o += 6) - { - rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); - rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2)); - pixel.PackFromRgb48(rgb48); - rowSpan[x] = pixel; - } - } - else - { - PixelOperations.Instance.PackFromRgb24Bytes(scanlineSpan, rowSpan, this.header.Width); - } - } - else - { - if (this.header.BitDepth == 16) - { - Rgb48 rgb48 = default; - Rgba64 rgba64 = default; - for (int x = 0, o = 0; x < this.header.Width; x++, o += 6) - { - rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); - rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2)); - - rgba64.Rgb = rgb48; - rgba64.A = rgb48.Equals(this.rgb48Trans) ? ushort.MinValue : ushort.MaxValue; - - pixel.PackFromRgba64(rgba64); - rowSpan[x] = pixel; - } - } - else - { - ReadOnlySpan rgb24Span = MemoryMarshal.Cast(scanlineSpan); - for (int x = 0; x < this.header.Width; x++) - { - ref readonly Rgb24 rgb24 = ref rgb24Span[x]; - Rgba32 rgba32 = default; - rgba32.Rgb = rgb24; - rgba32.A = rgb24.Equals(this.rgb24Trans) ? byte.MinValue : byte.MaxValue; - - pixel.PackFromRgba32(rgba32); - rowSpan[x] = pixel; - } - } - } + PngScanlineProcessor.ProcessRgbScanline( + this.header, + scanlineSpan, + rowSpan, + this.bytesPerPixel, + this.bytesPerSample, + this.hasTrans, + this.rgb48Trans, + this.rgb24Trans); break; case PngColorType.RgbWithAlpha: - - if (this.header.BitDepth == 16) - { - Rgba64 rgba64 = default; - for (int x = 0, o = 0; x < this.header.Width; x++, o += 8) - { - rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); - rgba64.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2)); - rgba64.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 6, 2)); - pixel.PackFromRgba64(rgba64); - rowSpan[x] = pixel; - } - } - else - { - PixelOperations.Instance.PackFromRgba32Bytes(scanlineSpan, rowSpan, this.header.Width); - } + PngScanlineProcessor.ProcessRgbaScanline( + this.header, + scanlineSpan, + rowSpan, + this.bytesPerPixel, + this.bytesPerSample); break; } @@ -919,8 +738,6 @@ namespace SixLabors.ImageSharp.Formats.Png private void ProcessInterlacedDefilteredScanline(ReadOnlySpan defilteredScanline, Span rowSpan, int pixelOffset = 0, int increment = 1) where TPixel : struct, IPixel { - TPixel pixel = default; - // Trim the first marker byte from the buffer ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); @@ -932,245 +749,66 @@ namespace SixLabors.ImageSharp.Formats.Png switch (this.pngColorType) { case PngColorType.Grayscale: - - int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); - - if (!this.hasTrans) - { - if (this.header.BitDepth == 16) - { - Rgb48 rgb48 = default; - for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 2) - { - ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - rgb48.R = luminance; - rgb48.G = luminance; - rgb48.B = luminance; - - pixel.PackFromRgb48(rgb48); - rowSpan[x] = pixel; - } - } - else - { - // TODO: We should really be using Rgb24 here but IPixel does not have a PackFromRgb24 method. - var rgba32 = new Rgba32(0, 0, 0, byte.MaxValue); - for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++) - { - byte luminance = (byte)(scanlineSpan[o] * factor); - rgba32.R = luminance; - rgba32.G = luminance; - rgba32.B = luminance; - - pixel.PackFromRgba32(rgba32); - rowSpan[x] = pixel; - } - } - } - else - { - if (this.header.BitDepth == 16) - { - Rgba64 rgba64 = default; - for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 2) - { - ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - rgba64.R = luminance; - rgba64.G = luminance; - rgba64.B = luminance; - rgba64.A = luminance.Equals(this.luminance16Trans) ? ushort.MinValue : ushort.MaxValue; - - pixel.PackFromRgba64(rgba64); - rowSpan[x] = pixel; - } - } - else - { - Rgba32 rgba32 = default; - for (int x = pixelOffset; x < this.header.Width; x += increment) - { - byte luminance = (byte)(scanlineSpan[x] * factor); - rgba32.R = luminance; - rgba32.G = luminance; - rgba32.B = luminance; - rgba32.A = luminance.Equals(this.luminanceTrans) ? byte.MinValue : byte.MaxValue; - - pixel.PackFromRgba32(rgba32); - rowSpan[x] = pixel; - } - } - } + PngScanlineProcessor.ProcessInterlacedGrayscaleScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + this.hasTrans, + this.luminance16Trans, + this.luminanceTrans); break; case PngColorType.GrayscaleWithAlpha: - - if (this.header.BitDepth == 16) - { - Rgba64 rgba64 = default; - for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 4) - { - ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - ushort alpha = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); - rgba64.R = luminance; - rgba64.G = luminance; - rgba64.B = luminance; - rgba64.A = alpha; - - pixel.PackFromRgba64(rgba64); - rowSpan[x] = pixel; - } - } - else - { - Rgba32 rgba32 = default; - for (int x = pixelOffset; x < this.header.Width; x += increment) - { - int offset = x * this.bytesPerPixel; - byte luminance = scanlineSpan[offset]; - byte alpha = scanlineSpan[offset + this.bytesPerSample]; - rgba32.R = luminance; - rgba32.G = luminance; - rgba32.B = luminance; - rgba32.A = alpha; - - pixel.PackFromRgba32(rgba32); - rowSpan[x] = pixel; - } - } + PngScanlineProcessor.ProcessInterlacedGrayscaleWithAlphaScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + this.bytesPerPixel, + this.bytesPerSample); break; case PngColorType.Palette: - - Span palettePixels = MemoryMarshal.Cast(this.palette); - - if (this.paletteAlpha?.Length > 0) - { - // 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; - for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++) - { - int index = scanlineSpan[o]; - rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : byte.MaxValue; - rgba.Rgb = palettePixels[index]; - - pixel.PackFromRgba32(rgba); - rowSpan[x] = pixel; - } - } - else - { - var rgba = new Rgba32(0, 0, 0, byte.MaxValue); - for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++) - { - int index = scanlineSpan[o]; - rgba.Rgb = palettePixels[index]; - - pixel.PackFromRgba32(rgba); - rowSpan[x] = pixel; - } - } + PngScanlineProcessor.ProcessInterlacedPaletteScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + this.palette, + this.paletteAlpha); break; case PngColorType.Rgb: - - if (this.header.BitDepth == 16) - { - if (this.hasTrans) - { - Rgb48 rgb48 = default; - Rgba64 rgba64 = default; - for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 6) - { - rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); - rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2)); - - rgba64.Rgb = rgb48; - rgba64.A = rgb48.Equals(this.rgb48Trans) ? ushort.MinValue : ushort.MaxValue; - - pixel.PackFromRgba64(rgba64); - rowSpan[x] = pixel; - } - } - else - { - Rgb48 rgb48 = default; - for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 6) - { - rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); - rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2)); - pixel.PackFromRgb48(rgb48); - rowSpan[x] = pixel; - } - } - } - else - { - if (this.hasTrans) - { - Rgba32 rgba = default; - for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel) - { - rgba.R = scanlineSpan[o]; - rgba.G = scanlineSpan[o + this.bytesPerSample]; - rgba.B = scanlineSpan[o + (2 * this.bytesPerSample)]; - rgba.A = this.rgb24Trans.Equals(rgba.Rgb) ? byte.MinValue : byte.MaxValue; - - pixel.PackFromRgba32(rgba); - rowSpan[x] = pixel; - } - } - else - { - var rgba = new Rgba32(0, 0, 0, byte.MaxValue); - for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel) - { - rgba.R = scanlineSpan[o]; - rgba.G = scanlineSpan[o + this.bytesPerSample]; - rgba.B = scanlineSpan[o + (2 * this.bytesPerSample)]; - - pixel.PackFromRgba32(rgba); - rowSpan[x] = pixel; - } - } - } + PngScanlineProcessor.ProcessInterlacedRgbScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + this.bytesPerPixel, + this.bytesPerSample, + this.hasTrans, + this.rgb48Trans, + this.rgb24Trans); break; case PngColorType.RgbWithAlpha: - - if (this.header.BitDepth == 16) - { - Rgba64 rgba64 = default; - for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 8) - { - rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); - rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); - rgba64.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 4, 2)); - rgba64.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 6, 2)); - pixel.PackFromRgba64(rgba64); - rowSpan[x] = pixel; - } - } - else - { - Rgba32 rgba = default; - for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel) - { - rgba.R = scanlineSpan[o]; - rgba.G = scanlineSpan[o + this.bytesPerSample]; - rgba.B = scanlineSpan[o + (2 * this.bytesPerSample)]; - rgba.A = scanlineSpan[o + (3 * this.bytesPerSample)]; - - pixel.PackFromRgba32(rgba); - rowSpan[x] = pixel; - } - } + PngScanlineProcessor.ProcessInterlacedRgbaScanline( + this.header, + scanlineSpan, + rowSpan, + pixelOffset, + increment, + this.bytesPerPixel, + this.bytesPerSample); break; } @@ -1193,6 +831,7 @@ namespace SixLabors.ImageSharp.Formats.Png ushort rc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(0, 2)); ushort gc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(2, 2)); ushort bc = BinaryPrimitives.ReadUInt16LittleEndian(alpha.Slice(4, 2)); + this.rgb48Trans = new Rgb48(rc, gc, bc); this.hasTrans = true; return; @@ -1224,124 +863,66 @@ namespace SixLabors.ImageSharp.Formats.Png } /// - /// Processes a scanline that uses a palette + /// Reads a header chunk from the data. /// - /// The type of pixel we are expanding to - /// The defiltered scanline - /// The current output image row - private void ProcessScanlineFromPalette(ReadOnlySpan scanline, Span row) - where TPixel : struct, IPixel + /// The png metadata. + /// The containing data. + private void ReadHeaderChunk(PngMetaData pngMetaData, ReadOnlySpan data) { - ReadOnlySpan palettePixels = MemoryMarshal.Cast(this.palette); - var color = default(TPixel); + this.header = PngHeader.Parse(data); - if (this.paletteAlpha?.Length > 0) - { - Rgba32 rgba = default; + this.header.Validate(); - // 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. - for (int x = 0; x < this.header.Width; x++) - { - int index = scanline[x]; - rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : byte.MaxValue; - rgba.Rgb = palettePixels[index]; + pngMetaData.BitDepth = (PngBitDepth)this.header.BitDepth; + pngMetaData.ColorType = this.header.ColorType; - color.PackFromRgba32(rgba); - row[x] = color; - } - } - else - { - // TODO: We should have PackFromRgb24. - var rgba = new Rgba32(0, 0, 0, byte.MaxValue); - for (int x = 0; x < this.header.Width; x++) - { - int index = scanline[x]; - - rgba.Rgb = palettePixels[index]; - - color.PackFromRgba32(rgba); - row[x] = color; - } - } - } - - /// - /// Reads a header chunk from the data. - /// - /// The containing data. - private void ReadHeaderChunk(ReadOnlySpan data) - { - this.header = new PngHeader( - width: BinaryPrimitives.ReadInt32BigEndian(data.Slice(0, 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]); + this.pngColorType = this.header.ColorType; } /// - /// Validates the png header. + /// Reads a text chunk containing image properties from the data. /// - /// - /// Thrown if the image does pass validation. - /// - private void ValidateHeader() + /// The metadata to decode to. + /// The containing the data. + private void ReadTextChunk(ImageMetaData metadata, ReadOnlySpan data) { - if (!ColorTypes.ContainsKey(this.header.ColorType)) - { - throw new NotSupportedException("Color type is not supported or not valid."); - } - - if (!ColorTypes[this.header.ColorType].Contains(this.header.BitDepth)) + if (this.ignoreMetadata) { - throw new NotSupportedException("Bit depth is not supported or not valid."); + return; } - if (this.header.FilterMethod != 0) - { - throw new NotSupportedException("The png specification only defines 0 as filter method."); - } + int zeroIndex = data.IndexOf((byte)0); - if (this.header.InterlaceMethod != PngInterlaceMode.None && this.header.InterlaceMethod != PngInterlaceMode.Adam7) - { - throw new NotSupportedException("The png specification only defines 'None' and 'Adam7' as interlaced methods."); - } + string name = this.textEncoding.GetString(data.Slice(0, zeroIndex)); + string value = this.textEncoding.GetString(data.Slice(zeroIndex + 1)); - this.pngColorType = this.header.ColorType; + metadata.Properties.Add(new ImageProperty(name, value)); } /// - /// Reads a text chunk containing image properties from the data. + /// Reads the next data chunk. /// - /// The metadata to decode to. - /// The containing data. - /// The maximum length to read. - private void ReadTextChunk(ImageMetaData metadata, byte[] data, int length) + /// Count of bytes in the next data chunk, or 0 if there are no more data chunks left. + private int ReadNextDataChunk() { - if (this.ignoreMetadata) + if (this.nextChunk != null) { - return; + return 0; } - int zeroIndex = 0; + this.currentStream.Read(this.buffer, 0, 4); - for (int i = 0; i < length; i++) + if (this.TryReadChunk(out PngChunk chunk)) { - if (data[i] == 0) + if (chunk.Type == PngChunkType.Data) { - zeroIndex = i; - break; + return chunk.Length; } - } - string name = this.textEncoding.GetString(data, 0, zeroIndex); - string value = this.textEncoding.GetString(data, zeroIndex + 1, length - zeroIndex - 1); + this.nextChunk = chunk; + } - metadata.Properties.Add(new ImageProperty(name, value)); + return 0; } /// @@ -1353,9 +934,16 @@ namespace SixLabors.ImageSharp.Formats.Png /// private bool TryReadChunk(out PngChunk chunk) { - int length = this.ReadChunkLength(); + if (this.nextChunk != null) + { + chunk = this.nextChunk.Value; + + this.nextChunk = null; - if (length == -1) + return true; + } + + if (!this.TryReadChunkLength(out int length)) { chunk = default; @@ -1369,9 +957,7 @@ namespace SixLabors.ImageSharp.Formats.Png // That lets us read one byte at a time until we reach a known chunk. this.currentStream.Position -= 3; - length = this.ReadChunkLength(); - - if (length == -1) + if (!this.TryReadChunkLength(out length)) { chunk = default; @@ -1409,13 +995,17 @@ namespace SixLabors.ImageSharp.Formats.Png /// The . private void ValidateChunk(in PngChunk chunk) { + Span chunkType = stackalloc byte[4]; + + BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type); + this.crc.Reset(); - this.crc.Update(this.chunkTypeBuffer); + this.crc.Update(chunkType); this.crc.Update(chunk.Data.GetSpan()); if (this.crc.Value != chunk.Crc) { - string chunkTypeName = Encoding.UTF8.GetString(this.chunkTypeBuffer, 0, 4); + string chunkTypeName = Encoding.ASCII.GetString(chunkType); throw new ImageFormatException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!"); } @@ -1429,14 +1019,9 @@ namespace SixLabors.ImageSharp.Formats.Png /// private uint ReadChunkCrc() { - int numBytes = this.currentStream.Read(this.crcBuffer, 0, 4); - - if (numBytes >= 1 && numBytes <= 3) - { - throw new ImageFormatException("Image stream is not valid!"); - } - - return BinaryPrimitives.ReadUInt32BigEndian(this.crcBuffer); + return this.currentStream.Read(this.buffer, 0, 4) == 4 + ? BinaryPrimitives.ReadUInt32BigEndian(this.buffer) + : throw new ImageFormatException("Image stream is not valid!"); } /// @@ -1471,54 +1056,29 @@ namespace SixLabors.ImageSharp.Formats.Png /// private PngChunkType ReadChunkType() { - int numBytes = this.currentStream.Read(this.chunkTypeBuffer, 0, 4); - - if (numBytes >= 1 && numBytes <= 3) - { - throw new ImageFormatException("Image stream is not valid!"); - } - - return (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.chunkTypeBuffer.AsSpan()); + return this.currentStream.Read(this.buffer, 0, 4) == 4 + ? (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer) + : throw new ImageFormatException("Invalid PNG data."); } /// - /// Calculates the length of the given chunk. + /// Attempts to read the length of the next chunk. /// - /// - /// Thrown if the input stream is not valid. - /// - private int ReadChunkLength() + /// + /// Whether the the length was read. + /// + private bool TryReadChunkLength(out int result) { - int numBytes = this.currentStream.Read(this.chunkLengthBuffer, 0, 4); - - if (numBytes < 4) + if (this.currentStream.Read(this.buffer, 0, 4) == 4) { - return -1; + result = BinaryPrimitives.ReadInt32BigEndian(this.buffer); + + return true; } - return BinaryPrimitives.ReadInt32BigEndian(this.chunkLengthBuffer); - } + result = default; - /// - /// Returns the correct number of columns for each interlaced pass. - /// - /// Th current pass index - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int ComputeColumnsAdam7(int passIndex) - { - int width = this.header.Width; - 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}"); - } + return false; } private void SwapBuffers() diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index 109e6ad770..96e97a305f 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -17,17 +17,17 @@ namespace SixLabors.ImageSharp.Formats.Png /// 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; } = PngBitDepth.Bit8; + public PngBitDepth? BitDepth { get; set; } /// /// Gets or sets the color type. /// - public PngColorType ColorType { get; set; } = PngColorType.RgbWithAlpha; + public PngColorType? ColorType { get; set; } /// /// Gets or sets the filter method. /// - public PngFilterMethod FilterMethod { get; set; } = PngFilterMethod.Paeth; + public PngFilterMethod? FilterMethod { get; set; } /// /// Gets or sets the compression level 1-9. @@ -36,30 +36,21 @@ namespace SixLabors.ImageSharp.Formats.Png public int CompressionLevel { get; set; } = 6; /// - /// Gets or sets the gamma value, that will be written - /// the the stream, when the property - /// is set to true. The default value is 2.2F. + /// Gets or sets the gamma value, that will be written the the image. /// - /// The gamma value of the image. - public float Gamma { get; set; } = 2.2F; + public float? Gamma { get; set; } /// /// Gets or sets quantizer for reducing the color count. /// Defaults to the /// - public IQuantizer Quantizer { get; set; } = new WuQuantizer(); + public IQuantizer Quantizer { get; set; } /// /// Gets or sets the transparency threshold. /// public byte Threshold { get; set; } = 255; - /// - /// Gets or sets a value indicating whether this instance should write - /// gamma information to the stream. The default value is false. - /// - public bool WriteGamma { get; set; } - /// /// Encodes the image to the specified stream from the . /// diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index ffe29aecae..a46d83707e 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -3,10 +3,13 @@ using System; using System.Buffers.Binary; +using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Formats.Png.Zlib; using SixLabors.ImageSharp.Memory; @@ -22,6 +25,21 @@ namespace SixLabors.ImageSharp.Formats.Png /// internal sealed class PngEncoderCore : IDisposable { + /// + /// The dictionary of available color types. + /// + private 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 } + }; + + /// + /// Used the manage memory allocations. + /// private readonly MemoryAllocator memoryAllocator; /// @@ -45,49 +63,49 @@ namespace SixLabors.ImageSharp.Formats.Png private readonly Crc32 crc = new Crc32(); /// - /// The png bit depth + /// The png filter method. /// - private readonly PngBitDepth pngBitDepth; + private readonly PngFilterMethod pngFilterMethod; /// - /// Gets or sets a value indicating whether to use 16 bit encoding for supported color types. + /// Gets or sets the CompressionLevel value /// - private readonly bool use16Bit; + private readonly int compressionLevel; /// - /// The png color type. + /// Gets or sets the alpha threshold value /// - private readonly PngColorType pngColorType; + private readonly byte threshold; /// - /// The png filter method. + /// The quantizer for reducing the color count. /// - private readonly PngFilterMethod pngFilterMethod; + private IQuantizer quantizer; /// - /// The quantizer for reducing the color count. + /// Gets or sets a value indicating whether to write the gamma chunk /// - private readonly IQuantizer quantizer; + private bool writeGamma; /// - /// Gets or sets the CompressionLevel value + /// The png bit depth /// - private readonly int compressionLevel; + private PngBitDepth? pngBitDepth; /// - /// Gets or sets the Gamma value + /// Gets or sets a value indicating whether to use 16 bit encoding for supported color types. /// - private readonly float gamma; + private bool use16Bit; /// - /// Gets or sets the Threshold value + /// The png color type. /// - private readonly byte threshold; + private PngColorType? pngColorType; /// - /// Gets or sets a value indicating whether to Write Gamma + /// Gets or sets the Gamma value /// - private readonly bool writeGamma; + private float? gamma; /// /// The image width. @@ -158,14 +176,16 @@ namespace SixLabors.ImageSharp.Formats.Png { this.memoryAllocator = memoryAllocator; this.pngBitDepth = options.BitDepth; - this.use16Bit = this.pngBitDepth.Equals(PngBitDepth.Bit16); this.pngColorType = options.ColorType; - this.pngFilterMethod = options.FilterMethod; + + // Specification recommends default filter method None for paletted images and Paeth for others. + this.pngFilterMethod = options.FilterMethod ?? (options.ColorType.Equals(PngColorType.Palette) + ? PngFilterMethod.None + : PngFilterMethod.Paeth); this.compressionLevel = options.CompressionLevel; this.gamma = options.Gamma; this.quantizer = options.Quantizer; this.threshold = options.Threshold; - this.writeGamma = options.WriteGamma; } /// @@ -183,23 +203,51 @@ namespace SixLabors.ImageSharp.Formats.Png this.width = image.Width; this.height = image.Height; + // Always take the encoder options over the metadata values. + ImageMetaData metaData = image.MetaData; + PngMetaData pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance); + this.gamma = this.gamma ?? pngMetaData.Gamma; + this.writeGamma = this.gamma > 0; + this.pngColorType = this.pngColorType ?? pngMetaData.ColorType; + this.pngBitDepth = this.pngBitDepth ?? pngMetaData.BitDepth; + this.use16Bit = this.pngBitDepth.Equals(PngBitDepth.Bit16); + + // Ensure we are not allowing impossible combinations. + if (!ColorTypes.ContainsKey(this.pngColorType.Value)) + { + throw new NotSupportedException("Color type is not supported or not valid."); + } + stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length); QuantizedFrame quantized = null; - ReadOnlySpan quantizedPixelsSpan = default; if (this.pngColorType == PngColorType.Palette) { + byte bits = (byte)this.pngBitDepth; + if (!ColorTypes[this.pngColorType.Value].Contains(bits)) + { + throw new NotSupportedException("Bit depth is not supported or not valid."); + } + + // Use the metadata to determine what quantization depth to use if no quantizer has been set. + if (this.quantizer == null) + { + this.quantizer = new WuQuantizer(ImageMaths.GetColorCountForBitDepth(bits)); + } + // Create quantized frame returning the palette and set the bit depth. quantized = this.quantizer.CreateFrameQuantizer().QuantizeFrame(image.Frames.RootFrame); - quantizedPixelsSpan = quantized.GetPixelSpan(); - byte bits = (byte)ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); + byte quantizedBits = (byte)ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); + bits = Math.Max(bits, 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) + else if (bits >= 5 && bits <= 7) { bits = 8; } @@ -208,7 +256,11 @@ namespace SixLabors.ImageSharp.Formats.Png } else { - this.bitDepth = (byte)(this.use16Bit ? 16 : 8); + this.bitDepth = (byte)this.pngBitDepth; + if (!ColorTypes[this.pngColorType.Value].Contains(this.bitDepth)) + { + throw new NotSupportedException("Bit depth is not supported or not valid."); + } } this.bytesPerPixel = this.CalculateBytesPerPixel(); @@ -217,7 +269,7 @@ namespace SixLabors.ImageSharp.Formats.Png width: image.Width, height: image.Height, bitDepth: this.bitDepth, - colorType: this.pngColorType, + colorType: this.pngColorType.Value, compressionMethod: 0, // None filterMethod: 0, interlaceMethod: 0); // TODO: Can't write interlaced yet. @@ -227,13 +279,13 @@ namespace SixLabors.ImageSharp.Formats.Png // Collect the indexed pixel data if (quantized != null) { - this.WritePaletteChunk(stream, header, quantized); + this.WritePaletteChunk(stream, quantized); } - this.WritePhysicalChunk(stream, image); + this.WritePhysicalChunk(stream, metaData); this.WriteGammaChunk(stream); - this.WriteExifChunk(stream, image); - this.WriteDataChunks(image.Frames.RootFrame, quantizedPixelsSpan, stream); + this.WriteExifChunk(stream, metaData); + this.WriteDataChunks(image.Frames.RootFrame, quantized, stream); this.WriteEndChunk(stream); stream.Flush(); @@ -264,32 +316,55 @@ namespace SixLabors.ImageSharp.Formats.Png const float RX = .2126F; const float GX = .7152F; const float BX = .0722F; + + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); Span rawScanlineSpan = this.rawScanline.GetSpan(); + ref byte rawScanlineSpanRef = ref MemoryMarshal.GetReference(rawScanlineSpan); if (this.pngColorType.Equals(PngColorType.Grayscale)) { - // TODO: Realistically we should support 1, 2, 4, 8, and 16 bit grayscale images. - // we currently do the other types via palette. Maybe RC as I don't understand how the data is packed yet - // for 1, 2, and 4 bit grayscale images. + // TODO: Research and add support for grayscale plus tRNS if (this.use16Bit) { // 16 bit grayscale Rgb48 rgb = default; for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 2) { - rowSpan[x].ToRgb48(ref rgb); + Unsafe.Add(ref rowSpanRef, x).ToRgb48(ref rgb); ushort luminance = (ushort)((RX * rgb.R) + (GX * rgb.G) + (BX * rgb.B)); BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), luminance); } } else { - // 8 bit grayscale - Rgb24 rgb = default; - for (int x = 0; x < rowSpan.Length; x++) + if (this.bitDepth == 8) { - rowSpan[x].ToRgb24(ref rgb); - rawScanlineSpan[x] = (byte)((RX * rgb.R) + (GX * rgb.G) + (BX * rgb.B)); + // 8 bit grayscale + Rgb24 rgb = default; + for (int x = 0; x < rowSpan.Length; x++) + { + Unsafe.Add(ref rowSpanRef, x).ToRgb24(ref rgb); + Unsafe.Add(ref rawScanlineSpanRef, x) = (byte)((RX * rgb.R) + (GX * rgb.G) + (BX * rgb.B)); + } + } + else + { + // 1, 2, and 4 bit grayscale + using (IManagedByteBuffer temp = this.memoryAllocator.AllocateManagedByteBuffer(rowSpan.Length, AllocationOptions.Clean)) + { + int scaleFactor = 255 / (ImageMaths.GetColorCountForBitDepth(this.bitDepth) - 1); + Span tempSpan = temp.GetSpan(); + ref byte tempSpanRef = ref MemoryMarshal.GetReference(tempSpan); + + Rgb24 rgb = default; + for (int x = 0; x < rowSpan.Length; x++) + { + Unsafe.Add(ref rowSpanRef, x).ToRgb24(ref rgb); + float luminance = ((RX * rgb.R) + (GX * rgb.G) + (BX * rgb.B)) / scaleFactor; + Unsafe.Add(ref tempSpanRef, x) = (byte)luminance; + this.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth); + } + } } } } @@ -301,7 +376,7 @@ namespace SixLabors.ImageSharp.Formats.Png Rgba64 rgba = default; for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 4) { - rowSpan[x].ToRgba64(ref rgba); + Unsafe.Add(ref rowSpanRef, x).ToRgba64(ref rgba); ushort luminance = (ushort)((RX * rgba.R) + (GX * rgba.G) + (BX * rgba.B)); BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), luminance); BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgba.A); @@ -313,9 +388,9 @@ namespace SixLabors.ImageSharp.Formats.Png Rgba32 rgba = default; for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 2) { - rowSpan[x].ToRgba32(ref rgba); - rawScanlineSpan[o] = (byte)((RX * rgba.R) + (GX * rgba.G) + (BX * rgba.B)); - rawScanlineSpan[o + 1] = rgba.A; + Unsafe.Add(ref rowSpanRef, x).ToRgba32(ref rgba); + Unsafe.Add(ref rawScanlineSpanRef, o) = (byte)((RX * rgba.R) + (GX * rgba.G) + (BX * rgba.B)); + Unsafe.Add(ref rawScanlineSpanRef, o + 1) = rgba.A; } } } @@ -351,9 +426,10 @@ namespace SixLabors.ImageSharp.Formats.Png { // 16 bit Rgba Rgba64 rgba = default; + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 8) { - rowSpan[x].ToRgba64(ref rgba); + Unsafe.Add(ref rowSpanRef, x).ToRgba64(ref rgba); 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); @@ -367,9 +443,10 @@ namespace SixLabors.ImageSharp.Formats.Png { // 16 bit Rgb Rgb48 rgb = default; + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 6) { - rowSpan[x].ToRgb48(ref rgb); + Unsafe.Add(ref rowSpanRef, x).ToRgb48(ref rgb); 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); @@ -386,18 +463,25 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The pixel format. /// The row span. - /// The span of quantized pixels. Can be null. + /// The quantized pixels. Can be null. /// The row. /// The - private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, ReadOnlySpan quantizedPixelsSpan, int row) + private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, QuantizedFrame quantized, int row) where TPixel : struct, IPixel { switch (this.pngColorType) { case PngColorType.Palette: - int stride = this.rawScanline.Length(); - quantizedPixelsSpan.Slice(row * stride, stride).CopyTo(this.rawScanline.GetSpan()); + if (this.bitDepth < 8) + { + this.ScaleDownFrom8BitArray(quantized.GetRowSpan(row), this.rawScanline.GetSpan(), this.bitDepth); + } + else + { + int stride = this.rawScanline.Length(); + quantized.GetPixelSpan().Slice(row * stride, stride).CopyTo(this.rawScanline.GetSpan()); + } break; case PngColorType.Grayscale: @@ -522,16 +606,9 @@ namespace SixLabors.ImageSharp.Formats.Png /// The . private void WriteHeaderChunk(Stream stream, in PngHeader header) { - BinaryPrimitives.WriteInt32BigEndian(this.chunkDataBuffer.AsSpan(0, 4), header.Width); - BinaryPrimitives.WriteInt32BigEndian(this.chunkDataBuffer.AsSpan(4, 4), header.Height); + header.WriteTo(this.chunkDataBuffer); - this.chunkDataBuffer[8] = header.BitDepth; - this.chunkDataBuffer[9] = (byte)header.ColorType; - this.chunkDataBuffer[10] = header.CompressionMethod; - this.chunkDataBuffer[11] = header.FilterMethod; - this.chunkDataBuffer[12] = (byte)header.InterlaceMethod; - - this.WriteChunk(stream, PngChunkType.Header, this.chunkDataBuffer, 0, 13); + this.WriteChunk(stream, PngChunkType.Header, this.chunkDataBuffer, 0, PngHeader.Size); } /// @@ -539,39 +616,36 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The pixel format. /// The containing image data. - /// The . /// The quantized frame. - private void WritePaletteChunk(Stream stream, in PngHeader header, QuantizedFrame quantized) + private void WritePaletteChunk(Stream stream, QuantizedFrame quantized) where TPixel : struct, IPixel { // Grab the palette and write it to the stream. TPixel[] palette = quantized.Palette; - byte pixelCount = palette.Length.ToByte(); - - // Get max colors for bit depth. - int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3; + int paletteLength = Math.Min(palette.Length, 256); + int colorTableLength = paletteLength * 3; Rgba32 rgba = default; bool anyAlpha = false; using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength)) - using (IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(pixelCount)) + using (IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength)) { - Span colorTableSpan = colorTable.GetSpan(); - Span alphaTableSpan = alphaTable.GetSpan(); + ref byte colorTableRef = ref MemoryMarshal.GetReference(colorTable.GetSpan()); + ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan()); Span quantizedSpan = quantized.GetPixelSpan(); - for (byte i = 0; i < pixelCount; i++) + for (int i = 0; i < paletteLength; i++) { - if (quantizedSpan.IndexOf(i) > -1) + if (quantizedSpan.IndexOf((byte)i) > -1) { int offset = i * 3; palette[i].ToRgba32(ref rgba); byte alpha = rgba.A; - colorTableSpan[offset] = rgba.R; - colorTableSpan[offset + 1] = rgba.G; - colorTableSpan[offset + 2] = rgba.B; + Unsafe.Add(ref colorTableRef, offset) = rgba.R; + Unsafe.Add(ref colorTableRef, offset + 1) = rgba.G; + Unsafe.Add(ref colorTableRef, offset + 2) = rgba.B; if (alpha > this.threshold) { @@ -579,7 +653,7 @@ namespace SixLabors.ImageSharp.Formats.Png } anyAlpha = anyAlpha || alpha < byte.MaxValue; - alphaTableSpan[i] = alpha; + Unsafe.Add(ref alphaTableRef, i) = alpha; } } @@ -588,7 +662,7 @@ namespace SixLabors.ImageSharp.Formats.Png // Write the transparency data if (anyAlpha) { - this.WriteChunk(stream, PngChunkType.PaletteAlpha, alphaTable.Array, 0, pixelCount); + this.WriteChunk(stream, PngChunkType.PaletteAlpha, alphaTable.Array, 0, paletteLength); } } } @@ -596,73 +670,26 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Writes the physical dimension information to the stream. /// - /// The pixel format. /// The containing image data. - /// The image. - private void WritePhysicalChunk(Stream stream, Image image) - where TPixel : struct, IPixel + /// The image meta data. + private void WritePhysicalChunk(Stream stream, ImageMetaData meta) { - // The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. It contains: - // Pixels per unit, X axis: 4 bytes (unsigned integer) - // Pixels per unit, Y axis: 4 bytes (unsigned integer) - // Unit specifier: 1 byte - // - // The following values are legal for 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. - ImageMetaData meta = image.MetaData; - Span hResolution = this.chunkDataBuffer.AsSpan(0, 4); - Span vResolution = this.chunkDataBuffer.AsSpan(4, 4); - - switch (meta.ResolutionUnits) - { - case PixelResolutionUnit.AspectRatio: - - this.chunkDataBuffer[8] = 0; - BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(meta.HorizontalResolution)); - BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.VerticalResolution)); - break; - - case PixelResolutionUnit.PixelsPerInch: - - this.chunkDataBuffer[8] = 1; // Per meter - BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(UnitConverter.InchToMeter(meta.HorizontalResolution))); - BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(UnitConverter.InchToMeter(meta.VerticalResolution))); - break; - - case PixelResolutionUnit.PixelsPerCentimeter: - - this.chunkDataBuffer[8] = 1; // Per meter - BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(UnitConverter.CmToMeter(meta.HorizontalResolution))); - BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(UnitConverter.CmToMeter(meta.VerticalResolution))); - break; - - default: - - this.chunkDataBuffer[8] = 1; // Per meter - BinaryPrimitives.WriteInt32BigEndian(hResolution, (int)Math.Round(meta.HorizontalResolution)); - BinaryPrimitives.WriteInt32BigEndian(vResolution, (int)Math.Round(meta.VerticalResolution)); - break; - } + PhysicalChunkData.FromMetadata(meta).WriteTo(this.chunkDataBuffer); - this.WriteChunk(stream, PngChunkType.Physical, this.chunkDataBuffer, 0, 9); + 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 meta data. /// - /// The pixel format. /// The containing image data. - /// The image. - private void WriteExifChunk(Stream stream, Image image) - where TPixel : struct, IPixel + /// The image meta data. + private void WriteExifChunk(Stream stream, ImageMetaData meta) { - if (image.MetaData.ExifProfile?.Values.Count > 0) + if (meta.ExifProfile?.Values.Count > 0) { - image.MetaData.SyncProfiles(); - this.WriteChunk(stream, PngChunkType.Exif, image.MetaData.ExifProfile.ToByteArray()); + meta.SyncProfiles(); + this.WriteChunk(stream, PngChunkType.Exif, meta.ExifProfile.ToByteArray()); } } @@ -688,12 +715,12 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The pixel format. /// The image. - /// The span of quantized pixel data. Can be null. + /// The quantized pixel data. Can be null. /// The stream. - private void WriteDataChunks(ImageFrame pixels, ReadOnlySpan quantizedPixelsSpan, Stream stream) + private void WriteDataChunks(ImageFrame pixels, QuantizedFrame quantized, Stream stream) where TPixel : struct, IPixel { - this.bytesPerScanline = this.width * this.bytesPerPixel; + this.bytesPerScanline = this.CalculateScanlineLength(this.width); int resultLength = this.bytesPerScanline + 1; this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean); @@ -706,26 +733,22 @@ namespace SixLabors.ImageSharp.Formats.Png break; case PngFilterMethod.Sub: - this.sub = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); break; case PngFilterMethod.Up: - this.up = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); break; case PngFilterMethod.Average: - this.average = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); break; case PngFilterMethod.Paeth: - this.paeth = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); break; - case PngFilterMethod.Adaptive: + case PngFilterMethod.Adaptive: this.sub = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); this.up = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); this.average = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean); @@ -742,7 +765,7 @@ namespace SixLabors.ImageSharp.Formats.Png { for (int y = 0; y < this.height; y++) { - IManagedByteBuffer r = this.EncodePixelRow((ReadOnlySpan)pixels.GetPixelRowSpan(y), quantizedPixelsSpan, y); + IManagedByteBuffer r = this.EncodePixelRow((ReadOnlySpan)pixels.GetPixelRowSpan(y), quantized, y); deflateStream.Write(r.Array, 0, resultLength); IManagedByteBuffer temp = this.rawScanline; @@ -781,10 +804,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// Writes the chunk end to the stream. /// /// The containing image data. - private void WriteEndChunk(Stream stream) - { - this.WriteChunk(stream, PngChunkType.End, null); - } + private void WriteEndChunk(Stream stream) => this.WriteChunk(stream, PngChunkType.End, null); /// /// Writes a chunk to the stream. @@ -792,10 +812,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The to write to. /// The type of chunk to write. /// The containing data. - private void WriteChunk(Stream stream, PngChunkType type, byte[] data) - { - this.WriteChunk(stream, type, data, 0, data?.Length ?? 0); - } + private void WriteChunk(Stream stream, PngChunkType type, byte[] data) => this.WriteChunk(stream, type, data, 0, data?.Length ?? 0); /// /// Writes a chunk of a specified length to the stream at the given offset. @@ -827,5 +844,67 @@ namespace SixLabors.ImageSharp.Formats.Png stream.Write(this.buffer, 0, 4); // write the crc } + + /// + /// Packs the given 8 bit array into and array of depths. + /// + /// The source span in 8 bits. + /// The resultant span in . + /// The bit depth. + private void ScaleDownFrom8BitArray(ReadOnlySpan source, Span result, int bits) + { + ref byte sourceRef = ref MemoryMarshal.GetReference(source); + ref byte resultRef = ref MemoryMarshal.GetReference(result); + + byte mask = (byte)(0xFF >> (8 - bits)); + byte shift0 = (byte)(8 - bits); + int shift = 8 - bits; + int v = 0; + int resultOffset = 0; + + for (int i = 0; i < source.Length; i++) + { + int value = Unsafe.Add(ref sourceRef, i) & mask; + v |= value << shift; + + 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; + } + } + + /// + /// 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; + + int amount = scanlineLength % mod; + if (amount != 0) + { + scanlineLength += mod - amount; + } + + return scanlineLength / mod; + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngFormat.cs b/src/ImageSharp/Formats/Png/PngFormat.cs index 142f660712..210e2a837d 100644 --- a/src/ImageSharp/Formats/Png/PngFormat.cs +++ b/src/ImageSharp/Formats/Png/PngFormat.cs @@ -8,8 +8,17 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Registers the image encoders, decoders and mime type detectors for the png format. /// - internal sealed class PngFormat : IImageFormat + public sealed class PngFormat : IImageFormat { + private PngFormat() + { + } + + /// + /// Gets the current instance. + /// + public static PngFormat Instance { get; } = new PngFormat(); + /// public string Name => "PNG"; @@ -21,5 +30,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// public IEnumerable FileExtensions => PngConstants.FileExtensions; + + /// + public PngMetaData CreateDefaultFormatMetaData() => new PngMetaData(); } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngHeader.cs b/src/ImageSharp/Formats/Png/PngHeader.cs index df85642bed..ea43ba96a5 100644 --- a/src/ImageSharp/Formats/Png/PngHeader.cs +++ b/src/ImageSharp/Formats/Png/PngHeader.cs @@ -1,6 +1,9 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Buffers.Binary; + namespace SixLabors.ImageSharp.Formats.Png { /// @@ -8,6 +11,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// internal readonly struct PngHeader { + public const int Size = 13; + public PngHeader( int width, int height, @@ -74,5 +79,68 @@ namespace SixLabors.ImageSharp.Formats.Png /// 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 (!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 != PngInterlaceMode.None && this.InterlaceMethod != 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.Slice(0, 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) + { + return new PngHeader( + width: BinaryPrimitives.ReadInt32BigEndian(data.Slice(0, 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 c1c039a1be..5deed86e30 100644 --- a/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// public IImageFormat DetectFormat(ReadOnlySpan header) { - return this.IsSupportedFileFormat(header) ? ImageFormats.Png : null; + return this.IsSupportedFileFormat(header) ? PngFormat.Instance : null; } private bool IsSupportedFileFormat(ReadOnlySpan header) diff --git a/src/ImageSharp/Formats/Png/PngMetaData.cs b/src/ImageSharp/Formats/Png/PngMetaData.cs new file mode 100644 index 0000000000..9c76765146 --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngMetaData.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Png +{ + /// + /// Provides Png specific metadata information for the image. + /// + public class PngMetaData : IDeepCloneable + { + /// + /// 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; + } + + /// + /// 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; } = PngBitDepth.Bit8; + + /// + /// Gets or sets the color type. + /// + public PngColorType ColorType { get; set; } = PngColorType.RgbWithAlpha; + + /// + /// Gets or sets the gamma value for the image. + /// + public float Gamma { get; set; } + + /// + public IDeepCloneable DeepClone() => new PngMetaData(this); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs new file mode 100644 index 0000000000..6c81ba76c2 --- /dev/null +++ b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs @@ -0,0 +1,600 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers.Binary; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Png +{ + /// + /// Provides methods to allow the decoding of raw scanlines to image rows of different pixel formats. + /// + internal static class PngScanlineProcessor + { + public static void ProcessGrayscaleScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + bool hasTrans, + ushort luminance16Trans, + byte luminanceTrans) + where TPixel : struct, IPixel + { + TPixel pixel = default; + ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + int scaleFactor = 255 / (ImageMaths.GetColorCountForBitDepth(header.BitDepth) - 1); + + if (!hasTrans) + { + if (header.BitDepth == 16) + { + Rgb48 rgb48 = default; + for (int x = 0, o = 0; x < header.Width; x++, o += 2) + { + ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + rgb48.R = luminance; + rgb48.G = luminance; + rgb48.B = luminance; + + pixel.PackFromRgb48(rgb48); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + // TODO: We should really be using Rgb24 here but IPixel does not have a PackFromRgb24 method. + var rgba32 = new Rgba32(0, 0, 0, byte.MaxValue); + for (int x = 0; x < header.Width; x++) + { + byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, x) * scaleFactor); + rgba32.R = luminance; + rgba32.G = luminance; + rgba32.B = luminance; + + pixel.PackFromRgba32(rgba32); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + + return; + } + + if (header.BitDepth == 16) + { + Rgba64 rgba64 = default; + for (int x = 0, o = 0; x < header.Width; x++, o += 2) + { + ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + rgba64.R = luminance; + rgba64.G = luminance; + rgba64.B = luminance; + rgba64.A = luminance.Equals(luminance16Trans) ? ushort.MinValue : ushort.MaxValue; + + pixel.PackFromRgba64(rgba64); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + Rgba32 rgba32 = default; + for (int x = 0; x < header.Width; x++) + { + byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, x) * scaleFactor); + rgba32.R = luminance; + rgba32.G = luminance; + rgba32.B = luminance; + rgba32.A = luminance.Equals(luminanceTrans) ? byte.MinValue : byte.MaxValue; + + pixel.PackFromRgba32(rgba32); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + } + + public static void ProcessInterlacedGrayscaleScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int pixelOffset, + int increment, + bool hasTrans, + ushort luminance16Trans, + byte luminanceTrans) + where TPixel : struct, IPixel + { + TPixel pixel = default; + ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + int scaleFactor = 255 / (ImageMaths.GetColorCountForBitDepth(header.BitDepth) - 1); + + if (!hasTrans) + { + if (header.BitDepth == 16) + { + Rgb48 rgb48 = default; + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += 2) + { + ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + rgb48.R = luminance; + rgb48.G = luminance; + rgb48.B = luminance; + + pixel.PackFromRgb48(rgb48); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + // TODO: We should really be using Rgb24 here but IPixel does not have a PackFromRgb24 method. + var rgba32 = new Rgba32(0, 0, 0, byte.MaxValue); + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++) + { + byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor); + rgba32.R = luminance; + rgba32.G = luminance; + rgba32.B = luminance; + + pixel.PackFromRgba32(rgba32); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + + return; + } + + if (header.BitDepth == 16) + { + Rgba64 rgba64 = default; + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += 2) + { + ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + rgba64.R = luminance; + rgba64.G = luminance; + rgba64.B = luminance; + rgba64.A = luminance.Equals(luminance16Trans) ? ushort.MinValue : ushort.MaxValue; + + pixel.PackFromRgba64(rgba64); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + Rgba32 rgba32 = default; + for (int x = pixelOffset; x < header.Width; x += increment) + { + byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, x) * scaleFactor); + rgba32.R = luminance; + rgba32.G = luminance; + rgba32.B = luminance; + rgba32.A = luminance.Equals(luminanceTrans) ? byte.MinValue : byte.MaxValue; + + pixel.PackFromRgba32(rgba32); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + } + + public static void ProcessGrayscaleWithAlphaScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int bytesPerPixel, + int bytesPerSample) + where TPixel : struct, 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 = 0, o = 0; x < header.Width; x++, o += 4) + { + ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + ushort alpha = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); + rgba64.R = luminance; + rgba64.G = luminance; + rgba64.B = luminance; + rgba64.A = alpha; + + pixel.PackFromRgba64(rgba64); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + Rgba32 rgba32 = default; + int bps = bytesPerSample; + for (int x = 0; x < header.Width; x++) + { + int offset = x * bytesPerPixel; + byte luminance = Unsafe.Add(ref scanlineSpanRef, offset); + byte alpha = Unsafe.Add(ref scanlineSpanRef, offset + bps); + + rgba32.R = luminance; + rgba32.G = luminance; + rgba32.B = luminance; + rgba32.A = alpha; + + pixel.PackFromRgba32(rgba32); + 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 : struct, 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 += 4) + { + ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); + ushort alpha = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); + rgba64.R = luminance; + rgba64.G = luminance; + rgba64.B = luminance; + rgba64.A = alpha; + + pixel.PackFromRgba64(rgba64); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + Rgba32 rgba32 = default; + for (int x = pixelOffset; x < header.Width; x += increment) + { + int offset = x * bytesPerPixel; + byte luminance = Unsafe.Add(ref scanlineSpanRef, offset); + byte alpha = Unsafe.Add(ref scanlineSpanRef, offset + bytesPerSample); + rgba32.R = luminance; + rgba32.G = luminance; + rgba32.B = luminance; + rgba32.A = alpha; + + pixel.PackFromRgba32(rgba32); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + } + + public static void ProcessPaletteScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + ReadOnlySpan palette, + byte[] paletteAlpha) + where TPixel : struct, 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) + { + // 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; + + pixel.PackFromRgba32(rgba); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + // TODO: We should have PackFromRgb24. + var rgba = new Rgba32(0, 0, 0, byte.MaxValue); + for (int x = 0; x < header.Width; x++) + { + int index = Unsafe.Add(ref scanlineSpanRef, x); + rgba.Rgb = Unsafe.Add(ref palettePixelsRef, index); + + pixel.PackFromRgba32(rgba); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + } + + public static void ProcessInterlacedPaletteScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int pixelOffset, + int increment, + ReadOnlySpan palette, + byte[] paletteAlpha) + where TPixel : struct, 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) + { + // 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); + + pixel.PackFromRgba32(rgba); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + var rgba = new Rgba32(0, 0, 0, byte.MaxValue); + for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++) + { + int index = Unsafe.Add(ref scanlineSpanRef, o); + rgba.Rgb = Unsafe.Add(ref palettePixelsRef, index); + + pixel.PackFromRgba32(rgba); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + } + + public static void ProcessRgbScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int bytesPerPixel, + int bytesPerSample, + bool hasTrans, + Rgb48 rgb48Trans, + Rgb24 rgb24Trans) + where TPixel : struct, IPixel + { + TPixel pixel = default; + ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + + if (!hasTrans) + { + 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.PackFromRgb48(rgb48); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + PixelOperations.Instance.PackFromRgb24Bytes(scanlineSpan, rowSpan, header.Width); + } + + return; + } + + 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.PackFromRgba64(rgba64); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + 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 rgba32 = default; + rgba32.Rgb = rgb24; + rgba32.A = rgb24.Equals(rgb24Trans) ? byte.MinValue : byte.MaxValue; + + pixel.PackFromRgba32(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 : struct, IPixel + { + TPixel pixel = default; + ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); + ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); + + if (header.BitDepth == 16) + { + 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.PackFromRgba64(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.PackFromRgb48(rgb48); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + + return; + } + + if (hasTrans) + { + 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 = rgb24Trans.Equals(rgba.Rgb) ? byte.MinValue : byte.MaxValue; + + pixel.PackFromRgba32(rgba); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + var rgba = new Rgba32(0, 0, 0, byte.MaxValue); + 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)); + + pixel.PackFromRgba32(rgba); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + } + + public static void ProcessRgbaScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int bytesPerPixel, + int bytesPerSample) + where TPixel : struct, 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)); + + pixel.PackFromRgba64(rgba64); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + PixelOperations.Instance.PackFromRgba32Bytes(scanlineSpan, rowSpan, header.Width); + } + } + + public static void ProcessInterlacedRgbaScanline( + in PngHeader header, + ReadOnlySpan scanlineSpan, + Span rowSpan, + int pixelOffset, + int increment, + int bytesPerPixel, + int bytesPerSample) + where TPixel : struct, 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.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.PackFromRgba64(rgba64); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + else + { + 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)); + + pixel.PackFromRgba32(rgba); + Unsafe.Add(ref rowSpanRef, x) = pixel; + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs index 55432d60b1..583175b566 100644 --- a/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs +++ b/src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs @@ -43,22 +43,24 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib private bool isDisposed; /// - /// Whether the crc value has been read. + /// The current data remaining to be read /// - private bool crcRead; + private int currentDataRemaining; /// - /// The current data remaining to be read + /// Delegate to get more data once we've exhausted the current data remaining /// - private int currentDataRemaining; + private readonly Func getData; /// /// Initializes a new instance of the class. /// /// The inner raw stream - public ZlibInflateStream(Stream innerStream) + /// A delegate to get more data from the inner stream + public ZlibInflateStream(Stream innerStream, Func getData) { this.innerStream = innerStream; + this.getData = getData; } /// @@ -112,12 +114,36 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib { if (this.currentDataRemaining == 0) { - return 0; + // last buffer was read in its entirety, let's make sure we don't actually have more + this.currentDataRemaining = this.getData(); + + if (this.currentDataRemaining == 0) + { + return 0; + } } int bytesToRead = Math.Min(count, this.currentDataRemaining); this.currentDataRemaining -= bytesToRead; - return this.innerStream.Read(buffer, offset, bytesToRead); + int bytesRead = this.innerStream.Read(buffer, offset, bytesToRead); + + // keep reading data until we've reached the end of the stream or filled the buffer + while (this.currentDataRemaining == 0 && bytesRead < count) + { + this.currentDataRemaining = this.getData(); + + if (this.currentDataRemaining == 0) + { + return bytesRead; + } + + offset += bytesRead; + bytesToRead = Math.Min(count - bytesRead, this.currentDataRemaining); + this.currentDataRemaining -= bytesToRead; + bytesRead += this.innerStream.Read(buffer, offset, bytesToRead); + } + + return bytesRead; } /// @@ -153,14 +179,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib { this.compressedStream.Dispose(); this.compressedStream = null; - - if (!this.crcRead) - { - // Consume the trailing 4 bytes - this.innerStream.Read(ChecksumBuffer, 0, 4); - this.currentDataRemaining -= 4; - this.crcRead = true; - } } } diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor.cs index 48a3a10980..bc82ba6bf4 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor.cs @@ -3,7 +3,7 @@ using System; using System.Runtime.CompilerServices; - +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The width of the image block. /// The height of the image block. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Decode(byte[] data, PixelAccessor pixels, int left, int top, int width, int height) + public static void Decode(byte[] data, Buffer2D pixels, int left, int top, int width, int height) where TPixel : struct, IPixel { TPixel color = default(TPixel); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor.cs index 5a9a0fc976..5deb38fbd9 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; - +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The width of the image block. /// The height of the image block. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Decode(byte[] data, PixelAccessor pixels, int left, int top, int width, int height) + public static void Decode(byte[] data, Buffer2D pixels, int left, int top, int width, int height) where TPixel : struct, IPixel { TPixel color = default(TPixel); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor.cs index d712e89bdb..a31868980b 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; - +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The width of the image block. /// The height of the image block. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Decode(byte[] data, PixelAccessor pixels, int left, int top, int width, int height) + public static void Decode(byte[] data, Buffer2D pixels, int left, int top, int width, int height) where TPixel : struct, IPixel { TPixel color = default(TPixel); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor.cs index 5a8d633feb..39e3f8104a 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor.cs @@ -4,7 +4,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; - +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The width of the image block. /// The height of the image block. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Decode(byte[] data, uint[] bitsPerSample, PixelAccessor pixels, int left, int top, int width, int height) + public static void Decode(byte[] data, uint[] bitsPerSample, Buffer2D pixels, int left, int top, int width, int height) where TPixel : struct, IPixel { TPixel color = default(TPixel); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs index cc90aa405a..11913c89a1 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs @@ -4,7 +4,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; - +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The width of the image block. /// The height of the image block. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Decode(byte[] data, uint[] bitsPerSample, uint[] colorMap, PixelAccessor pixels, int left, int top, int width, int height) + public static void Decode(byte[] data, uint[] bitsPerSample, uint[] colorMap, Buffer2D pixels, int left, int top, int width, int height) where TPixel : struct, IPixel { int colorCount = (int)Math.Pow(2, bitsPerSample[0]); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor.cs index 820c73d2a1..b39ae92ca8 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; - +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The width of the image block. /// The height of the image block. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Decode(byte[] data, PixelAccessor pixels, int left, int top, int width, int height) + public static void Decode(byte[] data, Buffer2D pixels, int left, int top, int width, int height) where TPixel : struct, IPixel { TPixel color = default(TPixel); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor.cs index 74c05fcd50..5ea36dffa5 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor.cs @@ -4,7 +4,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; - +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The width of the image block. /// The height of the image block. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Decode(byte[][] data, uint[] bitsPerSample, PixelAccessor pixels, int left, int top, int width, int height) + public static void Decode(byte[][] data, uint[] bitsPerSample, Buffer2D pixels, int left, int top, int width, int height) where TPixel : struct, IPixel { TPixel color = default(TPixel); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor.cs index 51915de465..75675dd9ac 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor.cs @@ -4,7 +4,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; - +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The width of the image block. /// The height of the image block. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Decode(byte[] data, uint[] bitsPerSample, PixelAccessor pixels, int left, int top, int width, int height) + public static void Decode(byte[] data, uint[] bitsPerSample, Buffer2D pixels, int left, int top, int width, int height) where TPixel : struct, IPixel { TPixel color = default(TPixel); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor.cs index 227aef5a4f..84cc26228c 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor.cs @@ -3,7 +3,7 @@ using System; using System.Runtime.CompilerServices; - +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The width of the image block. /// The height of the image block. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Decode(byte[] data, PixelAccessor pixels, int left, int top, int width, int height) + public static void Decode(byte[] data, Buffer2D pixels, int left, int top, int width, int height) where TPixel : struct, IPixel { TPixel color = default(TPixel); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor.cs index 053f5165b3..8e9eaaa479 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; - +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The width of the image block. /// The height of the image block. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Decode(byte[] data, PixelAccessor pixels, int left, int top, int width, int height) + public static void Decode(byte[] data, Buffer2D pixels, int left, int top, int width, int height) where TPixel : struct, IPixel { TPixel color = default(TPixel); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor.cs index 74a1b2de97..410c920e1e 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; - +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The width of the image block. /// The height of the image block. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Decode(byte[] data, PixelAccessor pixels, int left, int top, int width, int height) + public static void Decode(byte[] data, Buffer2D pixels, int left, int top, int width, int height) where TPixel : struct, IPixel { TPixel color = default(TPixel); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor.cs index f85d858e7a..1da647d99d 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor.cs @@ -4,7 +4,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; - +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The width of the image block. /// The height of the image block. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Decode(byte[] data, uint[] bitsPerSample, PixelAccessor pixels, int left, int top, int width, int height) + public static void Decode(byte[] data, uint[] bitsPerSample, Buffer2D pixels, int left, int top, int width, int height) where TPixel : struct, IPixel { TPixel color = default(TPixel); diff --git a/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs b/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs index 868cbdef6c..0da193239d 100644 --- a/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs +++ b/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs @@ -9,11 +9,11 @@ namespace SixLabors.ImageSharp.Formats public sealed class TiffConfigurationModule : IConfigurationModule { /// - public void Configure(Configuration host) + public void Configure(Configuration configuration) { - host.ImageFormatsManager.SetEncoder(ImageFormats.Tiff, new TiffEncoder()); - host.ImageFormatsManager.SetDecoder(ImageFormats.Tiff, new TiffDecoder()); - host.ImageFormatsManager.AddImageFormatDetector(new TiffImageFormatDetector()); + configuration.ImageFormatsManager.SetEncoder(TiffFormat.Instance, new TiffEncoder()); + configuration.ImageFormatsManager.SetDecoder(TiffFormat.Instance, new TiffDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new TiffImageFormatDetector()); } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index d9928c52e2..608c2e5583 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -6,9 +6,11 @@ using System.Buffers; using System.IO; using System.Text; using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; namespace SixLabors.ImageSharp.Formats { @@ -585,7 +587,7 @@ namespace SixLabors.ImageSharp.Formats /// The y-coordinate of the top of the image block. /// The width of the image block. /// The height of the image block. - public void ProcessImageBlockChunky(byte[] data, PixelAccessor pixels, int left, int top, int width, int height) + public void ProcessImageBlockChunky(byte[] data, Buffer2D pixels, int left, int top, int width, int height) where TPixel : struct, IPixel { switch (this.ColorType) @@ -638,7 +640,7 @@ namespace SixLabors.ImageSharp.Formats /// The y-coordinate of the top of the image block. /// The width of the image block. /// The height of the image block. - public void ProcessImageBlockPlanar(byte[][] data, PixelAccessor pixels, int left, int top, int width, int height) + public void ProcessImageBlockPlanar(byte[][] data, Buffer2D pixels, int left, int top, int width, int height) where TPixel : struct, IPixel { switch (this.ColorType) @@ -1235,46 +1237,45 @@ namespace SixLabors.ImageSharp.Formats int stripsPerPixel = this.PlanarConfiguration == TiffPlanarConfiguration.Chunky ? 1 : this.BitsPerSample.Length; int stripsPerPlane = stripOffsets.Length / stripsPerPixel; - using (PixelAccessor pixels = image.Lock()) + Buffer2D pixels = image.GetRootFramePixelBuffer(); + + byte[][] stripBytes = new byte[stripsPerPixel][]; + + for (int stripIndex = 0; stripIndex < stripBytes.Length; stripIndex++) { - byte[][] stripBytes = new byte[stripsPerPixel][]; + int uncompressedStripSize = this.CalculateImageBufferSize(image.Width, rowsPerStrip, stripIndex); + stripBytes[stripIndex] = ArrayPool.Shared.Rent(uncompressedStripSize); + } - for (int stripIndex = 0; stripIndex < stripBytes.Length; stripIndex++) + try + { + for (int i = 0; i < stripsPerPlane; i++) { - int uncompressedStripSize = this.CalculateImageBufferSize(image.Width, rowsPerStrip, stripIndex); - stripBytes[stripIndex] = ArrayPool.Shared.Rent(uncompressedStripSize); - } + int stripHeight = i < stripsPerPlane - 1 || image.Height % rowsPerStrip == 0 ? rowsPerStrip : image.Height % rowsPerStrip; - try - { - for (int i = 0; i < stripsPerPlane; i++) + for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++) { - int stripHeight = i < stripsPerPlane - 1 || image.Height % rowsPerStrip == 0 ? rowsPerStrip : image.Height % rowsPerStrip; - - for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++) - { - int stripIndex = (i * stripsPerPixel) + planeIndex; - this.DecompressImageBlock(stripOffsets[stripIndex], stripByteCounts[stripIndex], stripBytes[planeIndex]); - } + int stripIndex = (i * stripsPerPixel) + planeIndex; + this.DecompressImageBlock(stripOffsets[stripIndex], stripByteCounts[stripIndex], stripBytes[planeIndex]); + } - if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky) - { - this.ProcessImageBlockChunky(stripBytes[0], pixels, 0, rowsPerStrip * i, image.Width, stripHeight); - } - else - { - this.ProcessImageBlockPlanar(stripBytes, pixels, 0, rowsPerStrip * i, image.Width, stripHeight); - } + if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky) + { + this.ProcessImageBlockChunky(stripBytes[0], pixels, 0, rowsPerStrip * i, image.Width, stripHeight); } - } - finally - { - for (int stripIndex = 0; stripIndex < stripBytes.Length; stripIndex++) + else { - ArrayPool.Shared.Return(stripBytes[stripIndex]); + this.ProcessImageBlockPlanar(stripBytes, pixels, 0, rowsPerStrip * i, image.Width, stripHeight); } } } + finally + { + for (int stripIndex = 0; stripIndex < stripBytes.Length; stripIndex++) + { + ArrayPool.Shared.Return(stripBytes[stripIndex]); + } + } } } } diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index 6006984466..9ab72f316a 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; namespace SixLabors.ImageSharp.Formats { diff --git a/src/ImageSharp/Formats/Tiff/TiffFormat.cs b/src/ImageSharp/Formats/Tiff/TiffFormat.cs index 6c0279ab5e..3f2807a06f 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFormat.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFormat.cs @@ -11,6 +11,15 @@ namespace SixLabors.ImageSharp.Formats /// public class TiffFormat : IImageFormat { + private TiffFormat() + { + } + + /// + /// Gets the current instance. + /// + public static TiffFormat Instance { get; } = new TiffFormat(); + /// public string Name => "TIFF"; diff --git a/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntryCreator.cs b/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntryCreator.cs index bd9a529390..35517d1901 100644 --- a/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntryCreator.cs +++ b/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntryCreator.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Text; using SixLabors.ImageSharp.MetaData.Profiles.Exif; +using SixLabors.ImageSharp.Primitives; namespace SixLabors.ImageSharp.Formats.Tiff { diff --git a/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs b/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs index 447f523e93..12ffd5ed5a 100644 --- a/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats { if (this.IsSupportedFileFormat(header)) { - return ImageFormats.Tiff; + return TiffFormat.Instance; } return null; diff --git a/src/ImageSharp/IDeepCloneable.cs b/src/ImageSharp/IDeepCloneable.cs new file mode 100644 index 0000000000..f80247a5d0 --- /dev/null +++ b/src/ImageSharp/IDeepCloneable.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp +{ + /// + /// A generic interface for a deeply cloneable type. + /// + /// 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(); + } + + /// + /// An interface for objects that can be cloned. This creates a deep copy of the object. + /// + public interface IDeepCloneable + { + /// + /// Creates a new object that is a deep copy of the current instance. + /// + /// The . + IDeepCloneable DeepClone(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/IO/IFileSystem.cs b/src/ImageSharp/IO/IFileSystem.cs index 088d4abb8b..593c760fcf 100644 --- a/src/ImageSharp/IO/IFileSystem.cs +++ b/src/ImageSharp/IO/IFileSystem.cs @@ -5,7 +5,6 @@ using System.IO; namespace SixLabors.ImageSharp.IO { - #if !NETSTANDARD1_1 /// /// A simple interface representing the filesystem. /// @@ -25,5 +24,4 @@ namespace SixLabors.ImageSharp.IO /// A stream representing the file to open. Stream Create(string path); } -#endif } diff --git a/src/ImageSharp/IO/LocalFileSystem.cs b/src/ImageSharp/IO/LocalFileSystem.cs index 204f5f4e1e..dc5901ff92 100644 --- a/src/ImageSharp/IO/LocalFileSystem.cs +++ b/src/ImageSharp/IO/LocalFileSystem.cs @@ -1,30 +1,19 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Collections.Generic; using System.IO; -using System.Text; namespace SixLabors.ImageSharp.IO { - #if !NETSTANDARD1_1 /// /// A wrapper around the local File apis. /// internal class LocalFileSystem : IFileSystem { /// - public Stream OpenRead(string path) - { - return File.OpenRead(path); - } + public Stream OpenRead(string path) => File.OpenRead(path); /// - public Stream Create(string path) - { - return File.Create(path); - } + public Stream Create(string path) => File.Create(path); } -#endif -} +} \ No newline at end of file diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index 894551e08f..8b9f3fdb5b 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp return null; } - using (IManagedByteBuffer buffer = config.MemoryAllocator.AllocateManagedByteBuffer(maxHeaderSize)) + using (IManagedByteBuffer buffer = config.MemoryAllocator.AllocateManagedByteBuffer(maxHeaderSize, AllocationOptions.Clean)) { long startPosition = stream.Position; stream.Read(buffer.Array, 0, maxHeaderSize); diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs index 12abf720bd..07adc03ff6 100644 --- a/src/ImageSharp/Image.FromBytes.cs +++ b/src/ImageSharp/Image.FromBytes.cs @@ -174,8 +174,6 @@ namespace SixLabors.ImageSharp } } -#if !NETSTANDARD1_1 - /// /// By reading the header on the provided byte array this calculates the images format. /// @@ -303,6 +301,5 @@ namespace SixLabors.ImageSharp } } } -#endif } } \ No newline at end of file diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index ad8f3426f2..b13cef4824 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -#if !NETSTANDARD1_1 using System; using System.IO; using SixLabors.ImageSharp.Formats; @@ -212,5 +211,4 @@ namespace SixLabors.ImageSharp } } } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/src/ImageSharp/ImageExtensions.cs b/src/ImageSharp/ImageExtensions.cs index bf312cb6f5..e579bec1a6 100644 --- a/src/ImageSharp/ImageExtensions.cs +++ b/src/ImageSharp/ImageExtensions.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Runtime.InteropServices; using System.Text; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; @@ -17,7 +16,6 @@ namespace SixLabors.ImageSharp /// public static partial class ImageExtensions { -#if !NETSTANDARD1_1 /// /// Writes the image to the given stream using the currently loaded image format. /// @@ -78,7 +76,6 @@ namespace SixLabors.ImageSharp source.Save(fs, encoder); } } -#endif /// /// Writes the image to the given stream using the currently loaded image format. diff --git a/src/ImageSharp/ImageFormats.cs b/src/ImageSharp/ImageFormats.cs deleted file mode 100644 index 986c57c0bd..0000000000 --- a/src/ImageSharp/ImageFormats.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Png; - -namespace SixLabors.ImageSharp -{ - /// - /// The static collection of all the default image formats - /// - public static class ImageFormats - { - /// - /// The format details for the jpegs. - /// - public static readonly IImageFormat Jpeg = new JpegFormat(); - - /// - /// The format details for the pngs. - /// - public static readonly IImageFormat Png = new PngFormat(); - - /// - /// The format details for the gifs. - /// - public static readonly IImageFormat Gif = new GifFormat(); - - /// - /// The format details for the bitmaps. - /// - public static readonly IImageFormat Bmp = new BmpFormat(); - - /// - /// The format details for the tiffs. - /// - public static readonly IImageFormat Tiff = new TiffFormat(); - } -} \ No newline at end of file diff --git a/src/ImageSharp/ImageFrameCollection.cs b/src/ImageSharp/ImageFrameCollection.cs index 59571ce92e..bbe05ce435 100644 --- a/src/ImageSharp/ImageFrameCollection.cs +++ b/src/ImageSharp/ImageFrameCollection.cs @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp public ImageFrame InsertFrame(int index, ImageFrame source) { this.ValidateFrame(source); - ImageFrame clonedFrame = source.Clone(); + ImageFrame clonedFrame = source.Clone(this.parent.GetConfiguration()); this.frames.Insert(index, clonedFrame); return clonedFrame; } @@ -107,7 +107,7 @@ namespace SixLabors.ImageSharp public ImageFrame AddFrame(ImageFrame source) { this.ValidateFrame(source); - ImageFrame clonedFrame = source.Clone(); + ImageFrame clonedFrame = source.Clone(this.parent.GetConfiguration()); this.frames.Add(clonedFrame); return clonedFrame; } @@ -155,10 +155,7 @@ namespace SixLabors.ImageSharp /// /// true if the contains the specified frame; otherwise, false. /// - public bool Contains(ImageFrame frame) - { - return this.frames.Contains(frame); - } + public bool Contains(ImageFrame frame) => this.frames.Contains(frame); /// /// Moves an from to . @@ -195,7 +192,7 @@ namespace SixLabors.ImageSharp this.frames.Remove(frame); - return new Image(this.parent.GetConfiguration(), this.parent.MetaData.Clone(), new[] { frame }); + return new Image(this.parent.GetConfiguration(), this.parent.MetaData.DeepClone(), new[] { frame }); } /// @@ -208,7 +205,7 @@ namespace SixLabors.ImageSharp { ImageFrame frame = this[index]; ImageFrame clonedFrame = frame.Clone(); - return new Image(this.parent.GetConfiguration(), this.parent.MetaData.Clone(), new[] { clonedFrame }); + return new Image(this.parent.GetConfiguration(), this.parent.MetaData.DeepClone(), new[] { clonedFrame }); } /// @@ -217,10 +214,7 @@ namespace SixLabors.ImageSharp /// /// The new . /// - public ImageFrame CreateFrame() - { - return this.CreateFrame(default); - } + public ImageFrame CreateFrame() => this.CreateFrame(default); /// /// Creates a new and appends it to the end of the collection. diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index a3971fe9ce..be1792ced1 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.MetaData; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; using SixLabors.Primitives; @@ -84,18 +85,21 @@ namespace SixLabors.ImageSharp Guard.NotNull(configuration, nameof(configuration)); Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - Guard.NotNull(metaData, nameof(metaData)); this.configuration = configuration; this.MemoryAllocator = configuration.MemoryAllocator; this.PixelBuffer = this.MemoryAllocator.Allocate2D(width, height); - this.MetaData = metaData; + this.MetaData = metaData ?? new ImageFrameMetaData(); this.Clear(configuration.GetParallelOptions(), 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, MemorySource memorySource) : this(configuration, width, height, memorySource, new ImageFrameMetaData()) { @@ -104,12 +108,12 @@ namespace SixLabors.ImageSharp /// /// Initializes a new instance of the class wrapping an existing buffer. /// - internal ImageFrame( - Configuration configuration, - int width, - int height, - MemorySource memorySource, - ImageFrameMetaData metaData) + /// 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 meta data. + internal ImageFrame(Configuration configuration, int width, int height, MemorySource memorySource, ImageFrameMetaData metaData) { Guard.NotNull(configuration, nameof(configuration)); Guard.MustBeGreaterThan(width, 0, nameof(width)); @@ -136,7 +140,7 @@ namespace SixLabors.ImageSharp this.MemoryAllocator = configuration.MemoryAllocator; this.PixelBuffer = this.MemoryAllocator.Allocate2D(source.PixelBuffer.Width, source.PixelBuffer.Height); source.PixelBuffer.GetSpan().CopyTo(this.PixelBuffer.GetSpan()); - this.MetaData = source.MetaData.Clone(); + this.MetaData = source.MetaData.DeepClone(); } /// @@ -201,10 +205,7 @@ namespace SixLabors.ImageSharp /// 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) - { - return ref this.PixelBuffer[x, y]; - } + internal ref TPixel GetPixelReference(int x, int y) => ref this.PixelBuffer[x, y]; /// /// Copies the pixels to a of the same size. @@ -249,10 +250,20 @@ namespace SixLabors.ImageSharp } /// - public override string ToString() - { - return $"ImageFrame<{typeof(TPixel).Name}>: {this.Width}x{this.Height}"; - } + public override string ToString() => $"ImageFrame<{typeof(TPixel).Name}>: {this.Width}x{this.Height}"; + + /// + /// Clones the current instance. + /// + /// The + internal ImageFrame Clone() => this.Clone(this.configuration); + + /// + /// 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. @@ -260,29 +271,42 @@ namespace SixLabors.ImageSharp /// The pixel format. /// The internal ImageFrame CloneAs() + where TPixel2 : struct, IPixel => this.CloneAs(this.configuration); + + /// + /// 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 : struct, IPixel { if (typeof(TPixel2) == typeof(TPixel)) { - return this.Clone() as ImageFrame; + return this.Clone(configuration) as ImageFrame; } - var target = new ImageFrame(this.configuration, this.Width, this.Height, this.MetaData.Clone()); - - ParallelFor.WithTemporaryBuffer( - 0, - this.Height, - this.configuration, - this.Width, - (int y, IMemoryOwner tempRowBuffer) => - { - Span sourceRow = this.GetPixelRowSpan(y); - Span targetRow = target.GetPixelRowSpan(y); - Span tempRowSpan = tempRowBuffer.GetSpan(); - - PixelOperations.Instance.ToScaledVector4(sourceRow, tempRowSpan, sourceRow.Length); - PixelOperations.Instance.PackFromScaledVector4(tempRowSpan, targetRow, targetRow.Length); - }); + var target = new ImageFrame(configuration, this.Width, this.Height, this.MetaData.DeepClone()); + + ParallelHelper.IterateRowsWithTempBuffer( + this.Bounds(), + configuration, + (rows, tempRowBuffer) => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = this.GetPixelRowSpan(y); + Span targetRow = target.GetPixelRowSpan(y); + Span tempRowSpan = tempRowBuffer.Span; + + PixelOperations.Instance.ToScaledVector4(sourceRow, tempRowSpan, sourceRow.Length); + PixelOperations.Instance.PackFromScaledVector4( + tempRowSpan, + targetRow, + targetRow.Length); + } + }); return target; } @@ -294,30 +318,19 @@ namespace SixLabors.ImageSharp /// The value to initialize the bitmap with. internal void Clear(ParallelOptions parallelOptions, TPixel value) { - Parallel.For( - 0, - this.Height, - parallelOptions, - y => - { - Span targetRow = this.GetPixelRowSpan(y); - targetRow.Fill(value); - }); - } + Span span = this.GetPixelSpan(); - /// - /// Clones the current instance. - /// - /// The - internal ImageFrame Clone() - { - return new ImageFrame(this.configuration, this); + if (value.Equals(default)) + { + span.Clear(); + } + else + { + span.Fill(value); + } } /// - void IDisposable.Dispose() - { - this.Dispose(); - } + void IDisposable.Dispose() => this.Dispose(); } } \ No newline at end of file diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index a7ca0a014c..83b2b12604 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -5,7 +5,7 @@ $(packageversion) 0.0.1 Six Labors and contributors - netstandard1.1;netstandard1.3;netstandard2.0;netcoreapp2.1 + netstandard1.3;netstandard2.0;netcoreapp2.1 true true SixLabors.ImageSharp @@ -47,7 +47,7 @@ - + diff --git a/src/ImageSharp/ImageSharp.csproj.DotSettings b/src/ImageSharp/ImageSharp.csproj.DotSettings new file mode 100644 index 0000000000..8b2e1bcf07 --- /dev/null +++ b/src/ImageSharp/ImageSharp.csproj.DotSettings @@ -0,0 +1,3 @@ + + True + True \ No newline at end of file diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 8bc5a40bdc..9d4c1ef0b3 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -11,7 +11,6 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; namespace SixLabors.ImageSharp { @@ -23,15 +22,12 @@ namespace SixLabors.ImageSharp where TPixel : struct, IPixel { private readonly Configuration configuration; - 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 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) @@ -43,9 +39,7 @@ namespace SixLabors.ImageSharp /// 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 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. @@ -69,9 +63,7 @@ namespace SixLabors.ImageSharp /// 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 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. @@ -80,37 +72,41 @@ namespace SixLabors.ImageSharp this.configuration = configuration ?? Configuration.Default; this.PixelType = new PixelTypeInfo(Unsafe.SizeOf() * 8); this.MetaData = metadata ?? new ImageMetaData(); - this.frames = new ImageFrameCollection(this, width, height, default(TPixel)); + this.Frames = new ImageFrameCollection(this, width, height, default(TPixel)); } /// /// 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, MemorySource memorySource, int width, int height, ImageMetaData metadata) { this.configuration = configuration; this.PixelType = new PixelTypeInfo(Unsafe.SizeOf() * 8); this.MetaData = metadata; - this.frames = new ImageFrameCollection(this, width, height, memorySource); + this.Frames = new ImageFrameCollection(this, width, height, memorySource); } /// /// 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 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) { + internal Image(Configuration configuration, int width, int height, TPixel backgroundColor, ImageMetaData metadata) + { this.configuration = configuration ?? Configuration.Default; this.PixelType = new PixelTypeInfo(Unsafe.SizeOf() * 8); this.MetaData = metadata ?? new ImageMetaData(); - this.frames = new ImageFrameCollection(this, width, height, backgroundColor); + this.Frames = new ImageFrameCollection(this, width, height, backgroundColor); } /// @@ -126,7 +122,7 @@ namespace SixLabors.ImageSharp this.PixelType = new PixelTypeInfo(Unsafe.SizeOf() * 8); this.MetaData = metadata ?? new ImageMetaData(); - this.frames = new ImageFrameCollection(this, frames); + this.Frames = new ImageFrameCollection(this, frames); } /// @@ -138,10 +134,10 @@ namespace SixLabors.ImageSharp public PixelTypeInfo PixelType { get; } /// - public int Width => this.frames.RootFrame.Width; + public int Width => this.Frames.RootFrame.Width; /// - public int Height => this.frames.RootFrame.Height; + public int Height => this.Frames.RootFrame.Height; /// public ImageMetaData MetaData { get; } @@ -149,12 +145,12 @@ namespace SixLabors.ImageSharp /// /// Gets the frames. /// - public ImageFrameCollection Frames => this.frames; + public ImageFrameCollection Frames { get; } /// /// Gets the root frame. /// - private IPixelSource PixelSource => this.frames?.RootFrame ?? throw new ObjectDisposedException(nameof(Image)); + private IPixelSource PixelSource => this.Frames?.RootFrame ?? throw new ObjectDisposedException(nameof(Image)); /// /// Gets or sets the pixel at the specified position. @@ -187,16 +183,17 @@ namespace SixLabors.ImageSharp /// Clones the current image /// /// Returns a new image with all the same metadata as the original. - public Image Clone() - { - IEnumerable> clonedFrames = this.frames.Select(x => x.Clone()); - return new Image(this.configuration, this.MetaData.Clone(), clonedFrames); - } + public Image Clone() => this.Clone(this.configuration); - /// - public override string ToString() + /// + /// Clones the current image with the given configueation. + /// + /// 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) { - return $"Image<{typeof(TPixel).Name}>: {this.Width}x{this.Height}"; + IEnumerable> clonedFrames = this.Frames.Select(x => x.Clone(configuration)); + return new Image(configuration, this.MetaData.DeepClone(), clonedFrames); } /// @@ -205,22 +202,27 @@ namespace SixLabors.ImageSharp /// The pixel format. /// The public Image CloneAs() - where TPixel2 : struct, IPixel - { - IEnumerable> clonedFrames = this.frames.Select(x => x.CloneAs()); - var target = new Image(this.configuration, this.MetaData.Clone(), clonedFrames); - - return target; - } + where TPixel2 : struct, IPixel => this.CloneAs(this.configuration); /// - /// Releases managed resources. + /// Returns a copy of the image in the given pixel format. /// - public void Dispose() + /// The pixel format. + /// The configuration providing initialization code which allows extending the library. + /// The + public Image CloneAs(Configuration configuration) + where TPixel2 : struct, IPixel { - this.frames.Dispose(); + IEnumerable> clonedFrames = this.Frames.Select(x => x.CloneAs(configuration)); + return new Image(configuration, this.MetaData.DeepClone(), clonedFrames); } + /// + public void Dispose() => this.Frames.Dispose(); + + /// + public override string ToString() => $"Image<{typeof(TPixel).Name}>: {this.Width}x{this.Height}"; + /// /// 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. /// @@ -229,9 +231,9 @@ namespace SixLabors.ImageSharp { Guard.NotNull(pixelSource, nameof(pixelSource)); - for (int i = 0; i < this.frames.Count; i++) + for (int i = 0; i < this.Frames.Count; i++) { - this.frames[i].SwapOrCopyPixelsBufferFrom(pixelSource.frames[i]); + this.Frames[i].SwapOrCopyPixelsBufferFrom(pixelSource.Frames[i]); } } } diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index 107457ae73..17ab6e2522 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -96,15 +96,14 @@ namespace SixLabors.ImageSharp.Memory /// The /// The rectangle subarea /// The - public static BufferArea GetArea(this Buffer2D buffer, Rectangle rectangle) + public static BufferArea GetArea(this Buffer2D buffer, in Rectangle rectangle) where T : struct => new BufferArea(buffer, rectangle); public static BufferArea GetArea(this Buffer2D buffer, int x, int y, int width, int height) - where T : struct - { - var rectangle = new Rectangle(x, y, width, height); - return new BufferArea(buffer, rectangle); - } + where T : struct => new BufferArea(buffer, new Rectangle(x, y, width, height)); + + public static BufferArea GetAreaBetweenRows(this Buffer2D buffer, int minY, int maxY) + where T : struct => new BufferArea(buffer, new Rectangle(0, minY, buffer.Width, maxY - minY)); /// /// Return a to the whole area of 'buffer' @@ -114,5 +113,14 @@ namespace SixLabors.ImageSharp.Memory /// The public static BufferArea GetArea(this Buffer2D buffer) where T : struct => new BufferArea(buffer); + + /// + /// Gets a span for all the pixels in defined by + /// + public static Span GetMultiRowSpan(this Buffer2D buffer, in RowInterval rows) + where T : struct + { + return buffer.Span.Slice(rows.Min * buffer.Width, rows.Height * buffer.Width); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Memory/RowInterval.cs b/src/ImageSharp/Memory/RowInterval.cs new file mode 100644 index 0000000000..0750e0368c --- /dev/null +++ b/src/ImageSharp/Memory/RowInterval.cs @@ -0,0 +1,45 @@ +// Copyright(c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Represents an interval of rows in a and/or + /// + internal readonly struct RowInterval + { + /// + /// Initializes a new instance of the struct. + /// + public RowInterval(int min, int max) + { + DebugGuard.MustBeLessThan(min, max, nameof(min)); + + this.Min = min; + this.Max = max; + } + + /// + /// Gets the INCLUSIVE minimum + /// + public int Min { get; } + + /// + /// Gets the EXCLUSIVE maximum + /// + public int Max { get; } + + /// + /// Gets the difference ( - ) + /// + public int Height => this.Max - this.Min; + + /// + public override string ToString() + { + return $"RowInterval [{this.Min}->{this.Max}["; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/FrameDecodingMode.cs b/src/ImageSharp/MetaData/FrameDecodingMode.cs similarity index 91% rename from src/ImageSharp/Formats/Gif/FrameDecodingMode.cs rename to src/ImageSharp/MetaData/FrameDecodingMode.cs index 05791c92e5..2863fbf8f9 100644 --- a/src/ImageSharp/Formats/Gif/FrameDecodingMode.cs +++ b/src/ImageSharp/MetaData/FrameDecodingMode.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -namespace SixLabors.ImageSharp.Formats.Gif +namespace SixLabors.ImageSharp.MetaData { /// /// Enumerated frame process modes to apply to multi-frame images. diff --git a/src/ImageSharp/MetaData/ImageFrameMetaData.cs b/src/ImageSharp/MetaData/ImageFrameMetaData.cs index 47a2fb775f..f1f884be68 100644 --- a/src/ImageSharp/MetaData/ImageFrameMetaData.cs +++ b/src/ImageSharp/MetaData/ImageFrameMetaData.cs @@ -1,15 +1,18 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.Gif; +using System.Collections.Generic; +using SixLabors.ImageSharp.Formats; namespace SixLabors.ImageSharp.MetaData { /// /// Encapsulates the metadata of an image frame. /// - public sealed class ImageFrameMetaData + public sealed class ImageFrameMetaData : IDeepCloneable { + private readonly Dictionary formatMetaData = new Dictionary(); + /// /// Initializes a new instance of the class. /// @@ -28,32 +31,36 @@ namespace SixLabors.ImageSharp.MetaData { DebugGuard.NotNull(other, nameof(other)); - this.FrameDelay = other.FrameDelay; - this.DisposalMethod = other.DisposalMethod; + foreach (KeyValuePair meta in other.formatMetaData) + { + this.formatMetaData.Add(meta.Key, meta.Value.DeepClone()); + } } - /// - /// 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; } + /// + public ImageFrameMetaData DeepClone() => new ImageFrameMetaData(this); /// - /// 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. + /// Gets the metadata value associated with the specified key. /// - public DisposalMethod DisposalMethod { get; set; } - - /// - /// Clones this ImageFrameMetaData. - /// - /// The cloned instance. - public ImageFrameMetaData Clone() + /// 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 { - return new ImageFrameMetaData(this); + if (this.formatMetaData.TryGetValue(key, out IDeepCloneable meta)) + { + return (TFormatFrameMetaData)meta; + } + + TFormatFrameMetaData newMeta = key.CreateDefaultFormatFrameMetaData(); + this.formatMetaData[key] = newMeta; + return newMeta; } } } \ No newline at end of file diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index 40880bd085..73549d98aa 100644 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ b/src/ImageSharp/MetaData/ImageMetaData.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.MetaData.Profiles.Icc; @@ -10,7 +11,7 @@ namespace SixLabors.ImageSharp.MetaData /// /// Encapsulates the metadata of an image. /// - public sealed class ImageMetaData + public sealed class ImageMetaData : IDeepCloneable { /// /// The default horizontal resolution value (dots per inch) in x direction. @@ -24,6 +25,13 @@ namespace SixLabors.ImageSharp.MetaData /// 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 Dictionary(); private double horizontalResolution; private double verticalResolution; @@ -34,6 +42,7 @@ namespace SixLabors.ImageSharp.MetaData { this.horizontalResolution = DefaultHorizontalResolution; this.verticalResolution = DefaultVerticalResolution; + this.ResolutionUnits = DefaultPixelResolutionUnits; } /// @@ -48,20 +57,19 @@ namespace SixLabors.ImageSharp.MetaData this.HorizontalResolution = other.HorizontalResolution; this.VerticalResolution = other.VerticalResolution; this.ResolutionUnits = other.ResolutionUnits; - this.RepeatCount = other.RepeatCount; + + foreach (KeyValuePair meta in other.formatMetaData) + { + this.formatMetaData.Add(meta.Key, meta.Value.DeepClone()); + } foreach (ImageProperty property in other.Properties) { this.Properties.Add(property); } - this.ExifProfile = other.ExifProfile != null - ? new ExifProfile(other.ExifProfile) - : null; - - this.IccProfile = other.IccProfile != null - ? new IccProfile(other.IccProfile) - : null; + this.ExifProfile = other.ExifProfile?.DeepClone(); + this.IccProfile = other.IccProfile?.DeepClone(); } /// @@ -107,7 +115,7 @@ namespace SixLabors.ImageSharp.MetaData /// 02 : Pixels per centimeter /// 03 : Pixels per meter /// - public PixelResolutionUnit ResolutionUnits { get; set; } = PixelResolutionUnit.PixelsPerInch; + public PixelResolutionUnit ResolutionUnits { get; set; } /// /// Gets or sets the Exif profile. @@ -125,10 +133,28 @@ namespace SixLabors.ImageSharp.MetaData public IList Properties { get; } = new List(); /// - /// Gets or sets the number of times any animation is repeated. - /// 0 means to repeat indefinitely. + /// Gets the metadata value associated with the specified key. /// - public ushort RepeatCount { get; set; } + /// 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 ImageMetaData(this); /// /// Looks up a property with the provided name. @@ -153,21 +179,9 @@ namespace SixLabors.ImageSharp.MetaData return false; } - /// - /// Clones this into a new instance - /// - /// The cloned metadata instance - public ImageMetaData Clone() - { - return new ImageMetaData(this); - } - /// /// Synchronizes the profiles with the current meta data. /// - internal void SyncProfiles() - { - this.ExifProfile?.Sync(this); - } + internal void SyncProfiles() => this.ExifProfile?.Sync(this); } } diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs index 1dd8857217..b48b146f11 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs @@ -12,23 +12,18 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// /// Represents an EXIF profile providing access to the collection of values. /// - public sealed class ExifProfile + public sealed class ExifProfile : IDeepCloneable { /// /// The byte array to read the EXIF profile from. /// - private byte[] data; + private readonly byte[] data; /// /// The collection of EXIF values /// private List values; - /// - /// The list of invalid EXIF tags - /// - private IReadOnlyList invalidTags; - /// /// The thumbnail offset position in the byte stream /// @@ -55,7 +50,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif { this.Parts = ExifParts.All; this.data = data; - this.invalidTags = new List(); + this.InvalidTags = new List(); } /// @@ -63,22 +58,19 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// by making a copy from another EXIF profile. /// /// The other EXIF profile, where the clone should be made from. - /// is null. - public ExifProfile(ExifProfile other) + private ExifProfile(ExifProfile other) { - Guard.NotNull(other, nameof(other)); - this.Parts = other.Parts; this.thumbnailLength = other.thumbnailLength; this.thumbnailOffset = other.thumbnailOffset; - this.invalidTags = new List(other.invalidTags); + this.InvalidTags = new List(other.InvalidTags); if (other.values != null) { this.values = new List(other.Values.Count); foreach (ExifValue value in other.Values) { - this.values.Add(new ExifValue(value)); + this.values.Add(value.DeepClone()); } } @@ -97,7 +89,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// /// Gets the tags that where found but contained an invalid value. /// - public IReadOnlyList InvalidTags => this.invalidTags; + public IReadOnlyList InvalidTags { get; private set; } /// /// Gets the values of this EXIF profile. @@ -249,6 +241,9 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif return writer.GetData(); } + /// + public ExifProfile DeepClone() => new ExifProfile(this); + /// /// Synchronizes the profiles with the specified meta data. /// @@ -294,7 +289,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif this.values = reader.ReadValues(); - this.invalidTags = new List(reader.InvalidTags); + this.InvalidTags = new List(reader.InvalidTags); 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 72db6305dd..5f95499088 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs @@ -88,19 +88,19 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif } uint ifdOffset = this.ReadUInt32(); - this.AddValues(values, (int)ifdOffset); + this.AddValues(values, ifdOffset); uint thumbnailOffset = this.ReadUInt32(); - this.GetThumbnail((int)thumbnailOffset); + this.GetThumbnail(thumbnailOffset); if (this.exifOffset != 0) { - this.AddValues(values, (int)this.exifOffset); + this.AddValues(values, this.exifOffset); } if (this.gpsOffset != 0) { - this.AddValues(values, (int)this.gpsOffset); + this.AddValues(values, this.gpsOffset); } return values; @@ -127,25 +127,14 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif private unsafe string ConvertToString(ReadOnlySpan buffer) { - Span nullChar = stackalloc byte[1] { 0 }; - - int nullCharIndex = buffer.IndexOf(nullChar); + int nullCharIndex = buffer.IndexOf((byte)0); if (nullCharIndex > -1) { buffer = buffer.Slice(0, nullCharIndex); } -#if NETSTANDARD1_1 - return Encoding.UTF8.GetString(buffer.ToArray(), 0, buffer.Length); -#elif NETCOREAPP2_1 return Encoding.UTF8.GetString(buffer); -#else - fixed (byte* pointer = &MemoryMarshal.GetReference(buffer)) - { - return Encoding.UTF8.GetString(pointer, buffer.Length); - } -#endif } /// @@ -153,9 +142,14 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// /// The values. /// The index. - private void AddValues(List values, int index) + private void AddValues(List values, uint index) { - this.position = index; + if (index > (uint)this.exifData.Length) + { + return; + } + + this.position = (int)index; int count = this.ReadUInt16(); for (int i = 0; i < count; i++) @@ -431,7 +425,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif return null; } - private void GetThumbnail(int offset) + private void GetThumbnail(uint offset) { var values = new List(); this.AddValues(values, offset); @@ -515,10 +509,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif return new Rational(numerator, denominator, false); } - private sbyte ConvertToSignedByte(ReadOnlySpan buffer) - { - return unchecked((sbyte)buffer[0]); - } + private sbyte ConvertToSignedByte(ReadOnlySpan buffer) => unchecked((sbyte)buffer[0]); private int ConvertToInt32(ReadOnlySpan buffer) // SignedLong in Exif Specification { diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs index e6da9b7d1e..ccacfd0bf9 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs @@ -11,15 +11,30 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// /// Represent the value of the EXIF profile. /// - public sealed class ExifValue : IEquatable + public sealed class ExifValue : IEquatable, IDeepCloneable { + /// + /// Initializes a new instance of the class. + /// + /// The tag. + /// The data type. + /// The value. + /// Whether the value is an array. + internal ExifValue(ExifTag tag, ExifDataType dataType, object value, bool isArray) + { + this.Tag = tag; + this.DataType = dataType; + this.IsArray = isArray && dataType != ExifDataType.Ascii; + this.Value = value; + } + /// /// Initializes a new instance of the class /// by making a copy from another exif value. /// /// The other exif value, where the clone should be made from. - /// is null. - public ExifValue(ExifValue other) + /// is null. + private ExifValue(ExifValue other) { Guard.NotNull(other, nameof(other)); @@ -29,30 +44,17 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif if (!other.IsArray) { + // All types are value types except for string which is immutable so safe to simply assign. this.Value = other.Value; } else { + // All array types are value types so Clone() is sufficient here. var array = (Array)other.Value; this.Value = array.Clone(); } } - /// - /// Initializes a new instance of the class. - /// - /// The tag. - /// The data type. - /// The value. - /// Whether the value is an array. - internal ExifValue(ExifTag tag, ExifDataType dataType, object value, bool isArray) - { - this.Tag = tag; - this.DataType = dataType; - this.IsArray = isArray && dataType != ExifDataType.Ascii; - this.Value = value; - } - /// /// Gets the data type of the exif value. /// @@ -145,10 +147,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// /// True if the parameter is equal to the parameter; otherwise, false. /// - public static bool operator ==(ExifValue left, ExifValue right) - { - return ReferenceEquals(left, right) || left.Equals(right); - } + public static bool operator ==(ExifValue left, ExifValue right) => ReferenceEquals(left, right) || left.Equals(right); /// /// Compares two objects for equality. @@ -162,16 +161,10 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// /// True if the parameter is not equal to the parameter; otherwise, false. /// - public static bool operator !=(ExifValue left, ExifValue right) - { - return !(left == right); - } + public static bool operator !=(ExifValue left, ExifValue right) => !(left == right); /// - public override bool Equals(object obj) - { - return obj is ExifValue other && this.Equals(other); - } + public override bool Equals(object obj) => obj is ExifValue other && this.Equals(other); /// public bool Equals(ExifValue other) @@ -187,9 +180,9 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif } return - this.Tag == other.Tag && - this.DataType == other.DataType && - object.Equals(this.Value, other.Value); + this.Tag == other.Tag + && this.DataType == other.DataType + && object.Equals(this.Value, other.Value); } /// @@ -205,10 +198,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif } /// - public override int GetHashCode() - { - return this.GetHashCode(this); - } + public override int GetHashCode() => this.GetHashCode(this); /// public override string ToString() @@ -238,6 +228,9 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif return sb.ToString(); } + /// + public ExifValue DeepClone() => new ExifValue(this); + /// /// Creates a new /// @@ -584,7 +577,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif private static ExifValue CreateNumber(ExifTag tag, object value, bool isArray) { Type type = value?.GetType(); - if (type != null && type.IsArray) + if (type?.IsArray == true) { type = type.GetElementType(); } diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs index 5be0060f61..bb85a5ca3e 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.Primitives.cs @@ -102,7 +102,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc } Guard.MustBeGreaterThan(length, 0, nameof(length)); - string value = AsciiEncoding.GetString(this.data, this.AddIndex(length), length); + string value = Encoding.ASCII.GetString(this.data, this.AddIndex(length), length); // remove data after (potential) null terminator int pos = value.IndexOf('\0'); diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs index cc0f8f34dc..91a28fd743 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataReader/IccDataReader.cs @@ -11,8 +11,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// internal sealed partial class IccDataReader { - private static readonly Encoding AsciiEncoding = Encoding.GetEncoding("ASCII"); - /// /// The data that is read /// diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs index 5d7d729b2c..79b9132192 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// 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(DenseMatrix value, bool isSingle) + public int WriteMatrix(in DenseMatrix value, bool isSingle) { int count = 0; for (int y = 0; y < value.Rows; y++) diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs index a58f62519c..404285b500 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Primitives.cs @@ -178,7 +178,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc return 0; } - byte[] data = AsciiEncoding.GetBytes(value); + byte[] data = Encoding.ASCII.GetBytes(value); this.dataStream.Write(data, 0, data.Length); return data.Length; } @@ -215,7 +215,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc value = value.Substring(0, Math.Min(length - lengthAdjust, value.Length)); - byte[] textData = AsciiEncoding.GetBytes(value); + 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++) diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs index cfcc66c8e4..21b7b6421b 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using System.Text; namespace SixLabors.ImageSharp.MetaData.Profiles.Icc { @@ -12,9 +11,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// internal sealed partial class IccDataWriter : IDisposable { - private static readonly bool IsLittleEndian = BitConverter.IsLittleEndian; - private static readonly Encoding AsciiEncoding = Encoding.GetEncoding("ASCII"); - /// /// The underlying stream where the data is written to /// @@ -181,7 +177,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// The number of bytes written private unsafe int WriteBytes(byte* data, int length) { - if (IsLittleEndian) + if (BitConverter.IsLittleEndian) { for (int i = length - 1; i >= 0; i--) { diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs index dac56c608e..72665bc69c 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccProfile.cs @@ -3,17 +3,14 @@ using System; using System.Collections.Generic; - -#if !NETSTANDARD1_1 using System.Security.Cryptography; -#endif namespace SixLabors.ImageSharp.MetaData.Profiles.Icc { /// /// Represents an ICC profile /// - public sealed class IccProfile + public sealed class IccProfile : IDeepCloneable { /// /// The byte array to read the ICC profile from @@ -42,23 +39,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// 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 - /// by making a copy from another ICC profile. - /// - /// The other ICC profile, where the clone should be made from. - /// is null.> - public IccProfile(IccProfile other) - { - Guard.NotNull(other, nameof(other)); - - this.data = other.ToByteArray(); - } + public IccProfile(byte[] data) => this.data = data; /// /// Initializes a new instance of the class. @@ -74,6 +55,19 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc this.entries = new List(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 /// @@ -100,7 +94,8 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc } } -#if !NETSTANDARD1_1 + /// + public IccProfile DeepClone() => new IccProfile(this); /// /// Calculates the MD5 hash value of an ICC profile @@ -147,8 +142,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc } } -#endif - /// /// Checks for signs of a corrupt profile. /// @@ -227,4 +220,4 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc this.entries = new List(reader.ReadTagData(this.data)); } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs b/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs index c42e32d55a..b476e31955 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/IccWriter.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.Collections.Generic; using System.Linq; @@ -51,12 +50,8 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc writer.WriteXyzNumber(header.PcsIlluminant); writer.WriteAsciiString(header.CreatorSignature, 4, false); -#if !NETSTANDARD1_1 IccProfileId id = IccProfile.CalculateHash(writer.GetData()); writer.WriteProfileId(id); -#else - writer.WriteProfileId(IccProfileId.Zero); -#endif } private void WriteTagTable(IccDataWriter writer, IccTagTableEntry[] table) diff --git a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs index 9b24bffe85..4510882904 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/TagDataEntries/IccDataTagDataEntry.cs @@ -12,8 +12,6 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// internal sealed class IccDataTagDataEntry : IccTagDataEntry, IEquatable { - private static readonly Encoding AsciiEncoding = Encoding.GetEncoding("ASCII"); - /// /// Initializes a new instance of the class. /// @@ -60,7 +58,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// Gets the decoded as 7bit ASCII. /// If is false, returns null /// - public string AsciiString => this.IsAscii ? AsciiEncoding.GetString(this.Data, 0, this.Data.Length) : null; + public string AsciiString => this.IsAscii ? Encoding.ASCII.GetString(this.Data, 0, this.Data.Length) : null; /// public override bool Equals(IccTagDataEntry other) diff --git a/src/ImageSharp/PixelFormats/Bgr24.cs b/src/ImageSharp/PixelFormats/Bgr24.cs index fc283b5684..1f401f1a13 100644 --- a/src/ImageSharp/PixelFormats/Bgr24.cs +++ b/src/ImageSharp/PixelFormats/Bgr24.cs @@ -197,5 +197,11 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); + + /// + public override string ToString() + { + return $"({this.B},{this.G},{this.R})"; + } } } \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/Bgra32.cs b/src/ImageSharp/PixelFormats/Bgra32.cs index 233df2f29e..ff52600081 100644 --- a/src/ImageSharp/PixelFormats/Bgra32.cs +++ b/src/ImageSharp/PixelFormats/Bgra32.cs @@ -280,5 +280,11 @@ namespace SixLabors.ImageSharp.PixelFormats this.B = (byte)vector.Z; this.A = (byte)vector.W; } + + /// + public override string ToString() + { + return $"({this.B},{this.G},{this.R},{this.A})"; + } } } \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.cs b/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.cs index f644fbefb5..e8908fe05e 100644 --- a/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.cs +++ b/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.cs @@ -24,13 +24,14 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(source); ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); - var rgba = new Rgba64(0, 0, 0, 65535); + // For conversion methods writing only to RGB channels, we need to keep the alpha channel opaque! + var temp = NamedColors.Black; for (int i = 0; i < count; i++) { ref TPixel dp = ref Unsafe.Add(ref destRef, i); - rgba = Unsafe.Add(ref sourceRef, i); - dp.PackFromRgba64(rgba); + temp = Unsafe.Add(ref sourceRef, i); + dp.PackFromRgba64(temp); } } @@ -95,13 +96,14 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(source); ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); - var rgb = default(Rgb48); + // For conversion methods writing only to RGB channels, we need to keep the alpha channel opaque! + var temp = NamedColors.Black; for (int i = 0; i < count; i++) { ref TPixel dp = ref Unsafe.Add(ref destRef, i); - rgb = Unsafe.Add(ref sourceRef, i); - dp.PackFromRgb48(rgb); + temp = Unsafe.Add(ref sourceRef, i); + dp.PackFromRgb48(temp); } } @@ -166,13 +168,14 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(source); ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); - var rgba = new Rgba32(0, 0, 0, 255); + // For conversion methods writing only to RGB channels, we need to keep the alpha channel opaque! + var temp = NamedColors.Black; for (int i = 0; i < count; i++) { ref TPixel dp = ref Unsafe.Add(ref destRef, i); - rgba = Unsafe.Add(ref sourceRef, i); - dp.PackFromRgba32(rgba); + temp = Unsafe.Add(ref sourceRef, i); + dp.PackFromRgba32(temp); } } @@ -237,13 +240,14 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(source); ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); - var bgra = new Bgra32(0, 0, 0, 255); + // For conversion methods writing only to RGB channels, we need to keep the alpha channel opaque! + var temp = NamedColors.Black; for (int i = 0; i < count; i++) { ref TPixel dp = ref Unsafe.Add(ref destRef, i); - bgra = Unsafe.Add(ref sourceRef, i); - dp.PackFromBgra32(bgra); + temp = Unsafe.Add(ref sourceRef, i); + dp.PackFromBgra32(temp); } } @@ -308,13 +312,14 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(source); ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); - var rgba = new Rgba32(0, 0, 0, 255); + // For conversion methods writing only to RGB channels, we need to keep the alpha channel opaque! + var temp = NamedColors.Black; for (int i = 0; i < count; i++) { ref TPixel dp = ref Unsafe.Add(ref destRef, i); - rgba.Rgb = Unsafe.Add(ref sourceRef, i); - dp.PackFromRgba32(rgba); + temp.Rgb = Unsafe.Add(ref sourceRef, i); + dp.PackFromRgba32(temp); } } @@ -379,13 +384,14 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(source); ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); - var rgba = new Rgba32(0, 0, 0, 255); + // For conversion methods writing only to RGB channels, we need to keep the alpha channel opaque! + var temp = NamedColors.Black; for (int i = 0; i < count; i++) { ref TPixel dp = ref Unsafe.Add(ref destRef, i); - rgba.Bgr = Unsafe.Add(ref sourceRef, i); - dp.PackFromRgba32(rgba); + temp.Bgr = Unsafe.Add(ref sourceRef, i); + dp.PackFromRgba32(temp); } } @@ -450,13 +456,14 @@ namespace SixLabors.ImageSharp.PixelFormats ref Argb32 sourceRef = ref MemoryMarshal.GetReference(source); ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); - var argb = new Argb32(0, 0, 0, 255); + // For conversion methods writing only to RGB channels, we need to keep the alpha channel opaque! + var temp = NamedColors.Black; for (int i = 0; i < count; i++) { ref TPixel dp = ref Unsafe.Add(ref destRef, i); - argb = Unsafe.Add(ref sourceRef, i); - dp.PackFromArgb32(argb); + temp = Unsafe.Add(ref sourceRef, i); + dp.PackFromArgb32(temp); } } @@ -509,4 +516,5 @@ namespace SixLabors.ImageSharp.PixelFormats } } + } \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.tt b/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.tt index 1a6ac60f58..5c762c7df1 100644 --- a/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.tt +++ b/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.tt @@ -10,132 +10,8 @@ <#@ import namespace="System.Runtime.InteropServices" #> <#@ output extension=".cs" #> <# - void GenerateToDestFormatMethods(string pixelType) - { - #> - - /// - /// Converts 'count' pixels in 'sourcePixels` span to a span of -s. - /// Bulk version of . - /// - /// The span of source pixels - /// The destination span of data. - /// The number of pixels to convert. - internal virtual void To<#=pixelType#>(ReadOnlySpan sourcePixels, Span<<#=pixelType#>> dest, int count) - { - GuardSpans(sourcePixels, nameof(sourcePixels), dest, nameof(dest), count); - - ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); - ref <#=pixelType#> destBaseRef = ref MemoryMarshal.GetReference(dest); - - for (int i = 0; i < count; i++) - { - ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); - ref <#=pixelType#> dp = ref Unsafe.Add(ref destBaseRef, i); - sp.To<#=pixelType#>(ref dp); - } - } - - /// - /// A helper for that expects a byte span as destination. - /// The layout of the data in 'destBytes' must be compatible with layout. - /// - /// The to the source colors. - /// The to the destination bytes. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void To<#=pixelType#>Bytes(ReadOnlySpan sourceColors, Span destBytes, int count) - { - this.To<#=pixelType#>(sourceColors, MemoryMarshal.Cast>(destBytes), count); - } - <# - } - - void GeneratePackFromMethodUsingPackFromRgba64(string pixelType, string rgbaOperationCode) - { - #> - - /// - /// Converts 'count' elements in 'source` span of data to a span of -s. - /// - /// The source of data. - /// The to the destination pixels. - /// The number of pixels to convert. - internal virtual void PackFrom<#=pixelType#>(ReadOnlySpan<<#=pixelType#>> source, Span destPixels, int count) - { - GuardSpans(source, nameof(source), destPixels, nameof(destPixels), count); - - ref <#=pixelType#> sourceRef = ref MemoryMarshal.GetReference(source); - ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); - - var rgba = new Rgba64(0, 0, 0, 65535); - - for (int i = 0; i < count; i++) - { - ref TPixel dp = ref Unsafe.Add(ref destRef, i); - <#=rgbaOperationCode#> - dp.PackFromRgba64(rgba); - } - } - - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void PackFrom<#=pixelType#>Bytes(ReadOnlySpan sourceBytes, Span destPixels, int count) - { - this.PackFrom<#=pixelType#>(MemoryMarshal.Cast>(sourceBytes), destPixels, count); - } - <# - } - - void GeneratePackFromMethodUsingPackFromRgb48(string pixelType, string rgbaOperationCode) - { - #> - - /// - /// Converts 'count' elements in 'source` span of data to a span of -s. - /// - /// The source of data. - /// The to the destination pixels. - /// The number of pixels to convert. - internal virtual void PackFrom<#=pixelType#>(ReadOnlySpan<<#=pixelType#>> source, Span destPixels, int count) - { - GuardSpans(source, nameof(source), destPixels, nameof(destPixels), count); - - ref <#=pixelType#> sourceRef = ref MemoryMarshal.GetReference(source); - ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); - - var rgb = default(Rgb48); - - for (int i = 0; i < count; i++) - { - ref TPixel dp = ref Unsafe.Add(ref destRef, i); - <#=rgbaOperationCode#> - dp.PackFromRgb48(rgb); - } - } - - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void PackFrom<#=pixelType#>Bytes(ReadOnlySpan sourceBytes, Span destPixels, int count) - { - this.PackFrom<#=pixelType#>(MemoryMarshal.Cast>(sourceBytes), destPixels, count); - } - <# - } - void GeneratePackFromMethodUsingPackFromRgba32(string pixelType, string rgbaOperationCode) + void GeneratePackFromMethods(string pixelType, string tempPixelType, string assignToTempCode) { #> @@ -152,13 +28,14 @@ ref <#=pixelType#> sourceRef = ref MemoryMarshal.GetReference(source); ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); - var rgba = new Rgba32(0, 0, 0, 255); + // For conversion methods writing only to RGB channels, we need to keep the alpha channel opaque! + var temp = NamedColors<<#=tempPixelType#>>.Black; for (int i = 0; i < count; i++) { ref TPixel dp = ref Unsafe.Add(ref destRef, i); - <#=rgbaOperationCode#> - dp.PackFromRgba32(rgba); + <#=assignToTempCode#> + dp.PackFrom<#=tempPixelType#>(temp); } } @@ -177,86 +54,43 @@ <# } - void GeneratePackFromMethodUsingPackFromArgb32(string pixelType, string argbOperationCode) - { - #> + void GenerateToDestFormatMethods(string pixelType) + { + #> /// - /// Converts 'count' elements in 'source` span of data to a span of -s. + /// Converts 'count' pixels in 'sourcePixels` span to a span of -s. + /// Bulk version of . /// - /// The source of data. - /// The to the destination pixels. + /// The span of source pixels + /// The destination span of data. /// The number of pixels to convert. - internal virtual void PackFrom<#=pixelType#>(ReadOnlySpan<<#=pixelType#>> source, Span destPixels, int count) + internal virtual void To<#=pixelType#>(ReadOnlySpan sourcePixels, Span<<#=pixelType#>> dest, int count) { - GuardSpans(source, nameof(source), destPixels, nameof(destPixels), count); - - ref <#=pixelType#> sourceRef = ref MemoryMarshal.GetReference(source); - ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); + GuardSpans(sourcePixels, nameof(sourcePixels), dest, nameof(dest), count); - var argb = new Argb32(0, 0, 0, 255); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref <#=pixelType#> destBaseRef = ref MemoryMarshal.GetReference(dest); for (int i = 0; i < count; i++) { - ref TPixel dp = ref Unsafe.Add(ref destRef, i); - <#=argbOperationCode#> - dp.PackFromArgb32(argb); + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref <#=pixelType#> dp = ref Unsafe.Add(ref destBaseRef, i); + sp.To<#=pixelType#>(ref dp); } } - - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. - /// - /// The to the source bytes. - /// The to the destination pixels. - /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void PackFrom<#=pixelType#>Bytes(ReadOnlySpan sourceBytes, Span destPixels, int count) - { - this.PackFrom<#=pixelType#>(MemoryMarshal.Cast>(sourceBytes), destPixels, count); - } - <# - } - - void GeneratePackFromMethodUsingPackFromBgra32(string pixelType, string bgraOperationCode) - { - #> /// - /// Converts 'count' elements in 'source` span of data to a span of -s. - /// - /// The source of data. - /// The to the destination pixels. - /// The number of pixels to convert. - internal virtual void PackFrom<#=pixelType#>(ReadOnlySpan<<#=pixelType#>> source, Span destPixels, int count) - { - GuardSpans(source, nameof(source), destPixels, nameof(destPixels), count); - - ref <#=pixelType#> sourceRef = ref MemoryMarshal.GetReference(source); - ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); - - var bgra = new Bgra32(0, 0, 0, 255); - - for (int i = 0; i < count; i++) - { - ref TPixel dp = ref Unsafe.Add(ref destRef, i); - <#=bgraOperationCode#> - dp.PackFromBgra32(bgra); - } - } - - /// - /// A helper for that expects a byte span. - /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. /// - /// The to the source bytes. - /// The to the destination pixels. + /// The to the source colors. + /// The to the destination bytes. /// The number of pixels to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void PackFrom<#=pixelType#>Bytes(ReadOnlySpan sourceBytes, Span destPixels, int count) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void To<#=pixelType#>Bytes(ReadOnlySpan sourceColors, Span destBytes, int count) { - this.PackFrom<#=pixelType#>(MemoryMarshal.Cast>(sourceBytes), destPixels, count); + this.To<#=pixelType#>(sourceColors, MemoryMarshal.Cast>(destBytes), count); } <# } @@ -275,29 +109,28 @@ namespace SixLabors.ImageSharp.PixelFormats public partial class PixelOperations { <# - - GeneratePackFromMethodUsingPackFromRgba64("Rgba64", "rgba = Unsafe.Add(ref sourceRef, i);"); + GeneratePackFromMethods("Rgba64", "Rgba64", "temp = Unsafe.Add(ref sourceRef, i);"); GenerateToDestFormatMethods("Rgba64"); - GeneratePackFromMethodUsingPackFromRgb48("Rgb48", "rgb = Unsafe.Add(ref sourceRef, i);"); + GeneratePackFromMethods("Rgb48", "Rgb48", "temp = Unsafe.Add(ref sourceRef, i);"); GenerateToDestFormatMethods("Rgb48"); - GeneratePackFromMethodUsingPackFromRgba32("Rgba32", "rgba = Unsafe.Add(ref sourceRef, i);"); + GeneratePackFromMethods("Rgba32", "Rgba32", "temp = Unsafe.Add(ref sourceRef, i);"); GenerateToDestFormatMethods("Rgba32"); - GeneratePackFromMethodUsingPackFromBgra32("Bgra32", "bgra = Unsafe.Add(ref sourceRef, i);"); + GeneratePackFromMethods("Bgra32", "Bgra32", "temp = Unsafe.Add(ref sourceRef, i);"); GenerateToDestFormatMethods("Bgra32"); - GeneratePackFromMethodUsingPackFromRgba32("Rgb24", "rgba.Rgb = Unsafe.Add(ref sourceRef, i);"); + GeneratePackFromMethods("Rgb24", "Rgba32", "temp.Rgb = Unsafe.Add(ref sourceRef, i);"); GenerateToDestFormatMethods("Rgb24"); - GeneratePackFromMethodUsingPackFromRgba32("Bgr24", "rgba.Bgr = Unsafe.Add(ref sourceRef, i);"); + GeneratePackFromMethods("Bgr24", "Rgba32", "temp.Bgr = Unsafe.Add(ref sourceRef, i);"); GenerateToDestFormatMethods("Bgr24"); - GeneratePackFromMethodUsingPackFromArgb32("Argb32", "argb = Unsafe.Add(ref sourceRef, i);"); + GeneratePackFromMethods("Argb32", "Argb32", "temp = Unsafe.Add(ref sourceRef, i);"); GenerateToDestFormatMethods("Argb32"); - #> } + } \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs index 9b1e29db81..2ca58c461a 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs @@ -1,4 +1,11 @@ -// Copyright (c) Six Labors and contributors. + + + + + + + +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. // @@ -26,6 +33,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders where TPixel : struct, IPixel { + internal class NormalSrc : PixelBlender { /// @@ -36,35 +44,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.NormalSrc(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.NormalSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.NormalSrc(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class MultiplySrc : PixelBlender { /// @@ -75,35 +80,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.MultiplySrc(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.MultiplySrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.MultiplySrc(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class AddSrc : PixelBlender { /// @@ -114,35 +116,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.AddSrc(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.AddSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.AddSrc(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class SubtractSrc : PixelBlender { /// @@ -153,35 +152,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.SubtractSrc(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.SubtractSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.SubtractSrc(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class ScreenSrc : PixelBlender { /// @@ -192,35 +188,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.ScreenSrc(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.ScreenSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.ScreenSrc(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class DarkenSrc : PixelBlender { /// @@ -231,35 +224,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.DarkenSrc(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.DarkenSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.DarkenSrc(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class LightenSrc : PixelBlender { /// @@ -270,35 +260,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.LightenSrc(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.LightenSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.LightenSrc(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class OverlaySrc : PixelBlender { /// @@ -309,35 +296,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.OverlaySrc(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.OverlaySrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.OverlaySrc(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class HardLightSrc : PixelBlender { /// @@ -348,35 +332,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.HardLightSrc(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.HardLightSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.HardLightSrc(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class NormalSrcAtop : PixelBlender { /// @@ -387,35 +368,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.NormalSrcAtop(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.NormalSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.NormalSrcAtop(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class MultiplySrcAtop : PixelBlender { /// @@ -426,35 +404,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.MultiplySrcAtop(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.MultiplySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.MultiplySrcAtop(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class AddSrcAtop : PixelBlender { /// @@ -465,35 +440,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.AddSrcAtop(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.AddSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.AddSrcAtop(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class SubtractSrcAtop : PixelBlender { /// @@ -504,35 +476,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.SubtractSrcAtop(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.SubtractSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.SubtractSrcAtop(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class ScreenSrcAtop : PixelBlender { /// @@ -543,35 +512,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.ScreenSrcAtop(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.ScreenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.ScreenSrcAtop(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class DarkenSrcAtop : PixelBlender { /// @@ -582,35 +548,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.DarkenSrcAtop(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.DarkenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.DarkenSrcAtop(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class LightenSrcAtop : PixelBlender { /// @@ -621,35 +584,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.LightenSrcAtop(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.LightenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.LightenSrcAtop(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class OverlaySrcAtop : PixelBlender { /// @@ -660,35 +620,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.OverlaySrcAtop(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.OverlaySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.OverlaySrcAtop(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class HardLightSrcAtop : PixelBlender { /// @@ -699,35 +656,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.HardLightSrcAtop(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.HardLightSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.HardLightSrcAtop(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class NormalSrcOver : PixelBlender { /// @@ -738,35 +692,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.NormalSrcOver(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.NormalSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.NormalSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class MultiplySrcOver : PixelBlender { /// @@ -777,35 +728,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.MultiplySrcOver(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.MultiplySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.MultiplySrcOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class AddSrcOver : PixelBlender { /// @@ -816,35 +764,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.AddSrcOver(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.AddSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.AddSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class SubtractSrcOver : PixelBlender { /// @@ -855,35 +800,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.SubtractSrcOver(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.SubtractSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.SubtractSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class ScreenSrcOver : PixelBlender { /// @@ -894,35 +836,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.ScreenSrcOver(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.ScreenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.ScreenSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class DarkenSrcOver : PixelBlender { /// @@ -933,35 +872,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.DarkenSrcOver(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.DarkenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.DarkenSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class LightenSrcOver : PixelBlender { /// @@ -972,35 +908,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.LightenSrcOver(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.LightenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.LightenSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class OverlaySrcOver : PixelBlender { /// @@ -1011,35 +944,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.OverlaySrcOver(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.OverlaySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.OverlaySrcOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class HardLightSrcOver : PixelBlender { /// @@ -1050,35 +980,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.HardLightSrcOver(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.HardLightSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.HardLightSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class NormalSrcIn : PixelBlender { /// @@ -1089,35 +1016,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.NormalSrcIn(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.NormalSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.NormalSrcIn(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class MultiplySrcIn : PixelBlender { /// @@ -1128,35 +1052,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.MultiplySrcIn(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.MultiplySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.MultiplySrcIn(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class AddSrcIn : PixelBlender { /// @@ -1167,35 +1088,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.AddSrcIn(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.AddSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.AddSrcIn(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class SubtractSrcIn : PixelBlender { /// @@ -1206,35 +1124,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.SubtractSrcIn(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.SubtractSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.SubtractSrcIn(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class ScreenSrcIn : PixelBlender { /// @@ -1245,35 +1160,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.ScreenSrcIn(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.ScreenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.ScreenSrcIn(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class DarkenSrcIn : PixelBlender { /// @@ -1284,35 +1196,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.DarkenSrcIn(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.DarkenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.DarkenSrcIn(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class LightenSrcIn : PixelBlender { /// @@ -1323,35 +1232,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.LightenSrcIn(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.LightenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.LightenSrcIn(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class OverlaySrcIn : PixelBlender { /// @@ -1362,35 +1268,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.OverlaySrcIn(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.OverlaySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.OverlaySrcIn(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class HardLightSrcIn : PixelBlender { /// @@ -1401,35 +1304,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.HardLightSrcIn(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.HardLightSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.HardLightSrcIn(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class NormalSrcOut : PixelBlender { /// @@ -1440,35 +1340,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.NormalSrcOut(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.NormalSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.NormalSrcOut(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class MultiplySrcOut : PixelBlender { /// @@ -1479,35 +1376,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.MultiplySrcOut(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.MultiplySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.MultiplySrcOut(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class AddSrcOut : PixelBlender { /// @@ -1518,35 +1412,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.AddSrcOut(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.AddSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.AddSrcOut(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class SubtractSrcOut : PixelBlender { /// @@ -1557,35 +1448,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.SubtractSrcOut(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.SubtractSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.SubtractSrcOut(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class ScreenSrcOut : PixelBlender { /// @@ -1596,35 +1484,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.ScreenSrcOut(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.ScreenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.ScreenSrcOut(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class DarkenSrcOut : PixelBlender { /// @@ -1635,35 +1520,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.DarkenSrcOut(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.DarkenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.DarkenSrcOut(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class LightenSrcOut : PixelBlender { /// @@ -1674,35 +1556,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.LightenSrcOut(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.LightenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.LightenSrcOut(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class OverlaySrcOut : PixelBlender { /// @@ -1713,35 +1592,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.OverlaySrcOut(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.OverlaySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.OverlaySrcOut(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class HardLightSrcOut : PixelBlender { /// @@ -1752,35 +1628,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.HardLightSrcOut(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.HardLightSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.HardLightSrcOut(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class NormalDest : PixelBlender { /// @@ -1791,35 +1664,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.NormalDest(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.NormalDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.NormalDest(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class MultiplyDest : PixelBlender { /// @@ -1830,35 +1700,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.MultiplyDest(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.MultiplyDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.MultiplyDest(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class AddDest : PixelBlender { /// @@ -1869,35 +1736,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.AddDest(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.AddDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.AddDest(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class SubtractDest : PixelBlender { /// @@ -1908,35 +1772,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.SubtractDest(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.SubtractDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.SubtractDest(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class ScreenDest : PixelBlender { /// @@ -1947,35 +1808,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.ScreenDest(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.ScreenDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.ScreenDest(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class DarkenDest : PixelBlender { /// @@ -1986,35 +1844,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.DarkenDest(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.DarkenDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.DarkenDest(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class LightenDest : PixelBlender { /// @@ -2025,35 +1880,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.LightenDest(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.LightenDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.LightenDest(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class OverlayDest : PixelBlender { /// @@ -2064,35 +1916,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.OverlayDest(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.OverlayDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.OverlayDest(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class HardLightDest : PixelBlender { /// @@ -2103,35 +1952,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.HardLightDest(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.HardLightDest(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.HardLightDest(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class NormalDestAtop : PixelBlender { /// @@ -2142,35 +1988,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.NormalDestAtop(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.NormalDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.NormalDestAtop(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class MultiplyDestAtop : PixelBlender { /// @@ -2181,35 +2024,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.MultiplyDestAtop(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.MultiplyDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.MultiplyDestAtop(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class AddDestAtop : PixelBlender { /// @@ -2220,35 +2060,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.AddDestAtop(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.AddDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.AddDestAtop(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class SubtractDestAtop : PixelBlender { /// @@ -2259,35 +2096,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.SubtractDestAtop(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.SubtractDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.SubtractDestAtop(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class ScreenDestAtop : PixelBlender { /// @@ -2298,35 +2132,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.ScreenDestAtop(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.ScreenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.ScreenDestAtop(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class DarkenDestAtop : PixelBlender { /// @@ -2337,35 +2168,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.DarkenDestAtop(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.DarkenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.DarkenDestAtop(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class LightenDestAtop : PixelBlender { /// @@ -2376,35 +2204,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.LightenDestAtop(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.LightenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.LightenDestAtop(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class OverlayDestAtop : PixelBlender { /// @@ -2415,35 +2240,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.OverlayDestAtop(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.OverlayDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.OverlayDestAtop(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class HardLightDestAtop : PixelBlender { /// @@ -2454,35 +2276,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.HardLightDestAtop(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.HardLightDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.HardLightDestAtop(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class NormalDestOver : PixelBlender { /// @@ -2493,35 +2312,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.NormalDestOver(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.NormalDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.NormalDestOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class MultiplyDestOver : PixelBlender { /// @@ -2532,35 +2348,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.MultiplyDestOver(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.MultiplyDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.MultiplyDestOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class AddDestOver : PixelBlender { /// @@ -2571,35 +2384,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.AddDestOver(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.AddDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.AddDestOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class SubtractDestOver : PixelBlender { /// @@ -2610,35 +2420,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.SubtractDestOver(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.SubtractDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.SubtractDestOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class ScreenDestOver : PixelBlender { /// @@ -2649,35 +2456,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.ScreenDestOver(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.ScreenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.ScreenDestOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class DarkenDestOver : PixelBlender { /// @@ -2688,35 +2492,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.DarkenDestOver(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.DarkenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.DarkenDestOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class LightenDestOver : PixelBlender { /// @@ -2727,35 +2528,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.LightenDestOver(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.LightenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.LightenDestOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class OverlayDestOver : PixelBlender { /// @@ -2766,35 +2564,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.OverlayDestOver(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.OverlayDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.OverlayDestOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class HardLightDestOver : PixelBlender { /// @@ -2805,35 +2600,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.HardLightDestOver(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.HardLightDestOver(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.HardLightDestOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class NormalDestIn : PixelBlender { /// @@ -2844,35 +2636,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.NormalDestIn(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.NormalDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.NormalDestIn(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class MultiplyDestIn : PixelBlender { /// @@ -2883,35 +2672,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.MultiplyDestIn(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.MultiplyDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.MultiplyDestIn(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class AddDestIn : PixelBlender { /// @@ -2922,35 +2708,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.AddDestIn(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.AddDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.AddDestIn(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class SubtractDestIn : PixelBlender { /// @@ -2961,35 +2744,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.SubtractDestIn(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.SubtractDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.SubtractDestIn(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class ScreenDestIn : PixelBlender { /// @@ -3000,35 +2780,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.ScreenDestIn(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.ScreenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.ScreenDestIn(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class DarkenDestIn : PixelBlender { /// @@ -3039,35 +2816,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.DarkenDestIn(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.DarkenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.DarkenDestIn(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class LightenDestIn : PixelBlender { /// @@ -3078,35 +2852,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.LightenDestIn(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.LightenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.LightenDestIn(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class OverlayDestIn : PixelBlender { /// @@ -3117,35 +2888,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.OverlayDestIn(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.OverlayDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.OverlayDestIn(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class HardLightDestIn : PixelBlender { /// @@ -3156,35 +2924,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.HardLightDestIn(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.HardLightDestIn(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.HardLightDestIn(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class NormalDestOut : PixelBlender { /// @@ -3195,35 +2960,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.NormalDestOut(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.NormalDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.NormalDestOut(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class MultiplyDestOut : PixelBlender { /// @@ -3234,35 +2996,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.MultiplyDestOut(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.MultiplyDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.MultiplyDestOut(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class AddDestOut : PixelBlender { /// @@ -3273,35 +3032,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.AddDestOut(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.AddDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.AddDestOut(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class SubtractDestOut : PixelBlender { /// @@ -3312,35 +3068,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.SubtractDestOut(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.SubtractDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.SubtractDestOut(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class ScreenDestOut : PixelBlender { /// @@ -3351,35 +3104,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.ScreenDestOut(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.ScreenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.ScreenDestOut(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class DarkenDestOut : PixelBlender { /// @@ -3390,35 +3140,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.DarkenDestOut(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.DarkenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.DarkenDestOut(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class LightenDestOut : PixelBlender { /// @@ -3429,35 +3176,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.LightenDestOut(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.LightenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.LightenDestOut(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class OverlayDestOut : PixelBlender { /// @@ -3468,35 +3212,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.OverlayDestOut(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.OverlayDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.OverlayDestOut(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class HardLightDestOut : PixelBlender { /// @@ -3507,35 +3248,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.HardLightDestOut(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.HardLightDestOut(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.HardLightDestOut(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class NormalClear : PixelBlender { /// @@ -3546,35 +3284,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.NormalClear(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.NormalClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.NormalClear(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class MultiplyClear : PixelBlender { /// @@ -3585,35 +3320,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.MultiplyClear(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.MultiplyClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.MultiplyClear(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class AddClear : PixelBlender { /// @@ -3624,35 +3356,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.AddClear(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.AddClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.AddClear(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class SubtractClear : PixelBlender { /// @@ -3663,35 +3392,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.SubtractClear(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.SubtractClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.SubtractClear(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class ScreenClear : PixelBlender { /// @@ -3702,35 +3428,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.ScreenClear(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.ScreenClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.ScreenClear(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class DarkenClear : PixelBlender { /// @@ -3741,35 +3464,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.DarkenClear(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.DarkenClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.DarkenClear(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class LightenClear : PixelBlender { /// @@ -3780,35 +3500,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.LightenClear(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.LightenClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.LightenClear(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class OverlayClear : PixelBlender { /// @@ -3819,35 +3536,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.OverlayClear(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.OverlayClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.OverlayClear(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class HardLightClear : PixelBlender { /// @@ -3858,35 +3572,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.HardLightClear(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.HardLightClear(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.HardLightClear(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class NormalXor : PixelBlender { /// @@ -3897,35 +3608,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.NormalXor(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.NormalXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.NormalXor(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class MultiplyXor : PixelBlender { /// @@ -3936,35 +3644,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.MultiplyXor(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.MultiplyXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.MultiplyXor(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class AddXor : PixelBlender { /// @@ -3975,35 +3680,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.AddXor(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.AddXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.AddXor(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class SubtractXor : PixelBlender { /// @@ -4014,35 +3716,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.SubtractXor(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.SubtractXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.SubtractXor(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class ScreenXor : PixelBlender { /// @@ -4053,35 +3752,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.ScreenXor(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.ScreenXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.ScreenXor(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class DarkenXor : PixelBlender { /// @@ -4092,35 +3788,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.DarkenXor(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.DarkenXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.DarkenXor(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class LightenXor : PixelBlender { /// @@ -4131,35 +3824,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.LightenXor(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.LightenXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.LightenXor(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class OverlayXor : PixelBlender { /// @@ -4170,35 +3860,32 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.OverlayXor(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.OverlayXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.OverlayXor(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + internal class HardLightXor : PixelBlender { /// @@ -4209,34 +3896,31 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.HardLightXor(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.HardLightXor(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.HardLightXor(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } + } } \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt index 34fe4d4cda..ca7031df28 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt @@ -15,6 +15,8 @@ using System; using System.Numerics; using System.Buffers; + +using SixLabors.ImageSharp.Memory; using SixLabors.Memory; namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders @@ -78,32 +80,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// public override TPixel Blend(TPixel background, TPixel source, float amount) { - return PorterDuffFunctions.<#=blender_composer#>(background, source, amount); + TPixel dest = default; + dest.PackFromScaledVector4(PorterDuffFunctions.<#=blender_composer#>(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1))); + return dest; } /// - public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount) + protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) { - 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)); - - using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3)) + amount = amount.Clamp(0, 1); + for (int i = 0; i < destination.Length; i++) { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - - PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); - PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); - - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.<#=blender_composer#>(backgroundSpan[i], sourceSpan[i], amount[i]); - } + destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); + } + } - PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); - } + /// + 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], amount[i].Clamp(0, 1)); + } } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs index 4b0ffdd485..0a6ef60eca 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs @@ -3,6 +3,7 @@ // + using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -16,107 +17,96 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalSrc(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return source; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalSrcAtop(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Atop(backdrop, source, Normal(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalSrcOver(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Over(backdrop, source, Normal(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalSrcIn(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return In(backdrop, source, Normal(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalSrcOut(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Out(backdrop, source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalDest(Vector4 backdrop, Vector4 source, float opacity) { return backdrop; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalDestAtop(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Atop(source, backdrop, Normal(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalDestOver(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Over(source, backdrop, Normal(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalDestIn(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return In(source, backdrop, Normal(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalDestOut(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Out(source, backdrop); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalXor(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Xor(backdrop, source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 NormalClear(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Clear(backdrop, source); } @@ -126,8 +116,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel NormalSrc(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(NormalSrc(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(NormalSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -137,8 +128,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel NormalSrcAtop(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(NormalSrcAtop(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(NormalSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -148,8 +140,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel NormalSrcOver(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(NormalSrcOver(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(NormalSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -159,8 +152,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel NormalSrcIn(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(NormalSrcIn(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(NormalSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -170,8 +164,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel NormalSrcOut(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(NormalSrcOut(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(NormalSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -181,8 +176,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel NormalDest(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(NormalDest(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(NormalDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -192,8 +188,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel NormalDestAtop(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(NormalDestAtop(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(NormalDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -203,8 +200,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel NormalDestOver(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(NormalDestOver(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(NormalDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -214,8 +212,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel NormalDestIn(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(NormalDestIn(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(NormalDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -225,8 +224,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel NormalDestOut(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(NormalDestOut(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(NormalDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -236,8 +236,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel NormalClear(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(NormalClear(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(NormalClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -247,113 +248,103 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel NormalXor(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(NormalXor(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(NormalXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplySrc(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return source; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplySrcAtop(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Atop(backdrop, source, Multiply(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplySrcOver(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Over(backdrop, source, Multiply(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplySrcIn(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return In(backdrop, source, Multiply(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplySrcOut(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Out(backdrop, source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplyDest(Vector4 backdrop, Vector4 source, float opacity) { return backdrop; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplyDestAtop(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Atop(source, backdrop, Multiply(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplyDestOver(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Over(source, backdrop, Multiply(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplyDestIn(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return In(source, backdrop, Multiply(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplyDestOut(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Out(source, backdrop); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplyXor(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Xor(backdrop, source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 MultiplyClear(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Clear(backdrop, source); } @@ -363,8 +354,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel MultiplySrc(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(MultiplySrc(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(MultiplySrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -374,8 +366,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel MultiplySrcAtop(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(MultiplySrcAtop(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(MultiplySrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -385,8 +378,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel MultiplySrcOver(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(MultiplySrcOver(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(MultiplySrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -396,8 +390,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel MultiplySrcIn(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(MultiplySrcIn(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(MultiplySrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -407,8 +402,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel MultiplySrcOut(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(MultiplySrcOut(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(MultiplySrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -418,8 +414,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel MultiplyDest(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(MultiplyDest(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(MultiplyDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -429,8 +426,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel MultiplyDestAtop(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(MultiplyDestAtop(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(MultiplyDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -440,8 +438,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel MultiplyDestOver(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(MultiplyDestOver(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(MultiplyDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -451,8 +450,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel MultiplyDestIn(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(MultiplyDestIn(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(MultiplyDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -462,8 +462,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel MultiplyDestOut(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(MultiplyDestOut(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(MultiplyDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -473,8 +474,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel MultiplyClear(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(MultiplyClear(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(MultiplyClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -484,113 +486,103 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel MultiplyXor(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(MultiplyXor(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(MultiplyXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddSrc(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return source; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddSrcAtop(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Atop(backdrop, source, Add(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddSrcOver(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Over(backdrop, source, Add(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddSrcIn(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return In(backdrop, source, Add(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddSrcOut(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Out(backdrop, source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddDest(Vector4 backdrop, Vector4 source, float opacity) { return backdrop; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddDestAtop(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Atop(source, backdrop, Add(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddDestOver(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Over(source, backdrop, Add(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddDestIn(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return In(source, backdrop, Add(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddDestOut(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Out(source, backdrop); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddXor(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Xor(backdrop, source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 AddClear(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Clear(backdrop, source); } @@ -600,8 +592,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel AddSrc(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(AddSrc(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(AddSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -611,8 +604,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel AddSrcAtop(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(AddSrcAtop(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(AddSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -622,8 +616,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel AddSrcOver(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(AddSrcOver(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(AddSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -633,8 +628,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel AddSrcIn(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(AddSrcIn(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(AddSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -644,8 +640,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel AddSrcOut(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(AddSrcOut(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(AddSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -655,8 +652,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel AddDest(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(AddDest(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(AddDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -666,8 +664,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel AddDestAtop(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(AddDestAtop(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(AddDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -677,8 +676,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel AddDestOver(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(AddDestOver(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(AddDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -688,8 +688,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel AddDestIn(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(AddDestIn(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(AddDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -699,8 +700,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel AddDestOut(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(AddDestOut(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(AddDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -710,8 +712,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel AddClear(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(AddClear(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(AddClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -721,113 +724,103 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel AddXor(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(AddXor(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(AddXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractSrc(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return source; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractSrcAtop(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Atop(backdrop, source, Subtract(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractSrcOver(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Over(backdrop, source, Subtract(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractSrcIn(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return In(backdrop, source, Subtract(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractSrcOut(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Out(backdrop, source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractDest(Vector4 backdrop, Vector4 source, float opacity) { return backdrop; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractDestAtop(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Atop(source, backdrop, Subtract(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractDestOver(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Over(source, backdrop, Subtract(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractDestIn(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return In(source, backdrop, Subtract(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractDestOut(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Out(source, backdrop); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractXor(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Xor(backdrop, source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 SubtractClear(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Clear(backdrop, source); } @@ -837,8 +830,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel SubtractSrc(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(SubtractSrc(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(SubtractSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -848,8 +842,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel SubtractSrcAtop(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(SubtractSrcAtop(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(SubtractSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -859,8 +854,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel SubtractSrcOver(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(SubtractSrcOver(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(SubtractSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -870,8 +866,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel SubtractSrcIn(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(SubtractSrcIn(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(SubtractSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -881,8 +878,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel SubtractSrcOut(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(SubtractSrcOut(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(SubtractSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -892,8 +890,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel SubtractDest(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(SubtractDest(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(SubtractDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -903,8 +902,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel SubtractDestAtop(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(SubtractDestAtop(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(SubtractDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -914,8 +914,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel SubtractDestOver(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(SubtractDestOver(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(SubtractDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -925,8 +926,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel SubtractDestIn(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(SubtractDestIn(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(SubtractDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -936,8 +938,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel SubtractDestOut(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(SubtractDestOut(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(SubtractDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -947,8 +950,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel SubtractClear(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(SubtractClear(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(SubtractClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -958,113 +962,103 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel SubtractXor(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(SubtractXor(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(SubtractXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenSrc(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return source; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Atop(backdrop, source, Screen(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenSrcOver(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Over(backdrop, source, Screen(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenSrcIn(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return In(backdrop, source, Screen(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenSrcOut(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Out(backdrop, source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenDest(Vector4 backdrop, Vector4 source, float opacity) { return backdrop; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenDestAtop(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Atop(source, backdrop, Screen(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenDestOver(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Over(source, backdrop, Screen(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenDestIn(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return In(source, backdrop, Screen(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenDestOut(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Out(source, backdrop); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenXor(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Xor(backdrop, source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 ScreenClear(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Clear(backdrop, source); } @@ -1074,8 +1068,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel ScreenSrc(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(ScreenSrc(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(ScreenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1085,8 +1080,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel ScreenSrcAtop(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(ScreenSrcAtop(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(ScreenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1096,8 +1092,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel ScreenSrcOver(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(ScreenSrcOver(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(ScreenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1107,8 +1104,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel ScreenSrcIn(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(ScreenSrcIn(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(ScreenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1118,8 +1116,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel ScreenSrcOut(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(ScreenSrcOut(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(ScreenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1129,8 +1128,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel ScreenDest(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(ScreenDest(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(ScreenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1140,8 +1140,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel ScreenDestAtop(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(ScreenDestAtop(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(ScreenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1151,8 +1152,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel ScreenDestOver(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(ScreenDestOver(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(ScreenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1162,8 +1164,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel ScreenDestIn(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(ScreenDestIn(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(ScreenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1173,8 +1176,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel ScreenDestOut(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(ScreenDestOut(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(ScreenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1184,8 +1188,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel ScreenClear(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(ScreenClear(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(ScreenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1195,113 +1200,103 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel ScreenXor(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(ScreenXor(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(ScreenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenSrc(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return source; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Atop(backdrop, source, Darken(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenSrcOver(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Over(backdrop, source, Darken(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenSrcIn(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return In(backdrop, source, Darken(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenSrcOut(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Out(backdrop, source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenDest(Vector4 backdrop, Vector4 source, float opacity) { return backdrop; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenDestAtop(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Atop(source, backdrop, Darken(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenDestOver(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Over(source, backdrop, Darken(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenDestIn(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return In(source, backdrop, Darken(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenDestOut(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Out(source, backdrop); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenXor(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Xor(backdrop, source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 DarkenClear(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Clear(backdrop, source); } @@ -1311,8 +1306,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel DarkenSrc(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(DarkenSrc(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(DarkenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1322,8 +1318,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel DarkenSrcAtop(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(DarkenSrcAtop(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(DarkenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1333,8 +1330,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel DarkenSrcOver(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(DarkenSrcOver(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(DarkenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1344,8 +1342,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel DarkenSrcIn(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(DarkenSrcIn(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(DarkenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1355,8 +1354,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel DarkenSrcOut(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(DarkenSrcOut(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(DarkenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1366,8 +1366,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel DarkenDest(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(DarkenDest(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(DarkenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1377,8 +1378,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel DarkenDestAtop(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(DarkenDestAtop(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(DarkenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1388,8 +1390,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel DarkenDestOver(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(DarkenDestOver(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(DarkenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1399,8 +1402,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel DarkenDestIn(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(DarkenDestIn(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(DarkenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1410,8 +1414,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel DarkenDestOut(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(DarkenDestOut(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(DarkenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1421,8 +1426,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel DarkenClear(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(DarkenClear(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(DarkenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1432,113 +1438,103 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel DarkenXor(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(DarkenXor(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(DarkenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenSrc(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return source; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Atop(backdrop, source, Lighten(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenSrcOver(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Over(backdrop, source, Lighten(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenSrcIn(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return In(backdrop, source, Lighten(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenSrcOut(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Out(backdrop, source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenDest(Vector4 backdrop, Vector4 source, float opacity) { return backdrop; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenDestAtop(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Atop(source, backdrop, Lighten(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenDestOver(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Over(source, backdrop, Lighten(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenDestIn(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return In(source, backdrop, Lighten(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenDestOut(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Out(source, backdrop); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenXor(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Xor(backdrop, source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 LightenClear(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Clear(backdrop, source); } @@ -1548,8 +1544,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel LightenSrc(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(LightenSrc(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(LightenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1559,8 +1556,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel LightenSrcAtop(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(LightenSrcAtop(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(LightenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1570,8 +1568,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel LightenSrcOver(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(LightenSrcOver(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(LightenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1581,8 +1580,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel LightenSrcIn(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(LightenSrcIn(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(LightenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1592,8 +1592,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel LightenSrcOut(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(LightenSrcOut(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(LightenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1603,8 +1604,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel LightenDest(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(LightenDest(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(LightenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1614,8 +1616,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel LightenDestAtop(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(LightenDestAtop(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(LightenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1625,8 +1628,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel LightenDestOver(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(LightenDestOver(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(LightenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1636,8 +1640,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel LightenDestIn(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(LightenDestIn(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(LightenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1647,8 +1652,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel LightenDestOut(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(LightenDestOut(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(LightenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1658,8 +1664,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel LightenClear(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(LightenClear(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(LightenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1669,113 +1676,103 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel LightenXor(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(LightenXor(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(LightenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlaySrc(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return source; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlaySrcAtop(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Atop(backdrop, source, Overlay(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlaySrcOver(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Over(backdrop, source, Overlay(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlaySrcIn(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return In(backdrop, source, Overlay(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlaySrcOut(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Out(backdrop, source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlayDest(Vector4 backdrop, Vector4 source, float opacity) { return backdrop; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlayDestAtop(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Atop(source, backdrop, Overlay(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlayDestOver(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Over(source, backdrop, Overlay(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlayDestIn(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return In(source, backdrop, Overlay(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlayDestOut(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Out(source, backdrop); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlayXor(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Xor(backdrop, source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 OverlayClear(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Clear(backdrop, source); } @@ -1785,8 +1782,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel OverlaySrc(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(OverlaySrc(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(OverlaySrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1796,8 +1794,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel OverlaySrcAtop(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(OverlaySrcAtop(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(OverlaySrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1807,8 +1806,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel OverlaySrcOver(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(OverlaySrcOver(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(OverlaySrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1818,8 +1818,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel OverlaySrcIn(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(OverlaySrcIn(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(OverlaySrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1829,8 +1830,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel OverlaySrcOut(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(OverlaySrcOut(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(OverlaySrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1840,8 +1842,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel OverlayDest(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(OverlayDest(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(OverlayDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1851,8 +1854,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel OverlayDestAtop(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(OverlayDestAtop(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(OverlayDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1862,8 +1866,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel OverlayDestOver(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(OverlayDestOver(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(OverlayDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1873,8 +1878,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel OverlayDestIn(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(OverlayDestIn(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(OverlayDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1884,8 +1890,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel OverlayDestOut(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(OverlayDestOut(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(OverlayDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1895,8 +1902,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel OverlayClear(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(OverlayClear(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(OverlayClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -1906,113 +1914,103 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel OverlayXor(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(OverlayXor(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(OverlayXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightSrc(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return source; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightSrcAtop(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Atop(backdrop, source, HardLight(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightSrcOver(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Over(backdrop, source, HardLight(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightSrcIn(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return In(backdrop, source, HardLight(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightSrcOut(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Out(backdrop, source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightDest(Vector4 backdrop, Vector4 source, float opacity) { return backdrop; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightDestAtop(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Atop(source, backdrop, HardLight(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightDestOver(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Over(source, backdrop, HardLight(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightDestIn(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return In(source, backdrop, HardLight(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightDestOut(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Out(source, backdrop); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightXor(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Xor(backdrop, source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 HardLightClear(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Clear(backdrop, source); } @@ -2022,8 +2020,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel HardLightSrc(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(HardLightSrc(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(HardLightSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -2033,8 +2032,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel HardLightSrcAtop(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(HardLightSrcAtop(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(HardLightSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -2044,8 +2044,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel HardLightSrcOver(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(HardLightSrcOver(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(HardLightSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -2055,8 +2056,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel HardLightSrcIn(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(HardLightSrcIn(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(HardLightSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -2066,8 +2068,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel HardLightSrcOut(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(HardLightSrcOut(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(HardLightSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -2077,8 +2080,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel HardLightDest(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(HardLightDest(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(HardLightDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -2088,8 +2092,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel HardLightDestAtop(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(HardLightDestAtop(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(HardLightDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -2099,8 +2104,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel HardLightDestOver(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(HardLightDestOver(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(HardLightDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -2110,8 +2116,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel HardLightDestIn(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(HardLightDestIn(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(HardLightDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -2121,8 +2128,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel HardLightDestOut(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(HardLightDestOut(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(HardLightDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -2132,8 +2140,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel HardLightClear(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(HardLightClear(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(HardLightClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -2143,8 +2152,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel HardLightXor(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(HardLightXor(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(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 5e46a89a85..73c835e606 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt @@ -13,6 +13,11 @@ // +<# +// Note use of MethodImplOptions.NoInlining. We have tests that are failing on certain architectures when +// AggresiveInlining is used. Confirmed on Intel i7-6600U in 64bit. +#> + using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -24,107 +29,96 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders <# void GeneratePixelBlenders(string blender) { #> - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>Src(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return source; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>SrcAtop(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Atop(backdrop, source, <#=blender#>(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>SrcOver(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Over(backdrop, source, <#=blender#>(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>SrcIn(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return In(backdrop, source, <#=blender#>(backdrop, source)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>SrcOut(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Out(backdrop, source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>Dest(Vector4 backdrop, Vector4 source, float opacity) { return backdrop; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>DestAtop(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Atop(source, backdrop, <#=blender#>(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>DestOver(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Over(source, backdrop, <#=blender#>(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>DestIn(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return In(source, backdrop, <#=blender#>(source, backdrop)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>DestOut(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Out(source, backdrop); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>Xor(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Xor(backdrop, source); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.NoInlining)] public static Vector4 <#=blender#>Clear(Vector4 backdrop, Vector4 source, float opacity) { - opacity = opacity.Clamp(0, 1); - source.W *= opacity; + source.W *= opacity; return Clear(backdrop, source); } @@ -137,8 +131,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders public static TPixel <#=blender#><#=composer#>(TPixel backdrop, TPixel source, float opacity) where TPixel : struct, IPixel { + opacity = opacity.Clamp(0, 1); TPixel dest = default; - dest.PackFromVector4(<#=blender#><#=composer#>(backdrop.ToVector4(),source.ToVector4(),opacity)); + dest.PackFromScaledVector4(<#=blender#><#=composer#>(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); return dest; } @@ -175,7 +170,7 @@ string[] blenders = new []{ foreach(var blender in blenders) { - GeneratePixelBlenders(blender); + GeneratePixelBlenders(blender); foreach(var composer in composers) { diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs index e10c8fe918..9d0e9d04d3 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs @@ -8,7 +8,7 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders { /// - /// Collection of Porter Duff alpha blending functions applying an the 'Over' composition model. + /// Collection of Porter Duff Color Blending and Alpha Composition Functions. /// /// /// These functions are designed to be a general solution for all color cases, @@ -148,31 +148,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - ((2 * (1 - source)) * (1 - backdrop)); } - /// - /// General composition function for all modes, with a general solution for alpha channel - /// - /// Original Backdrop color - /// Original source color - /// Desired transformed color, without taking Alpha channel in account - /// The final color [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector4 SrcOverReference(Vector4 backdrop, Vector4 source, Vector4 xform) - { - // calculate weights - float xw = backdrop.W * source.W; - float bw = backdrop.W - xw; - float sw = source.W - xw; - - // calculate final alpha - float a = xw + bw + sw; - - // calculate final value - xform = ((xform * xw) + (backdrop * bw) + (source * sw)) / MathF.Max(a, Constants.Epsilon); - xform.W = a; - - return xform; - } - public static Vector4 Over(Vector4 dst, Vector4 src, Vector4 blend) { // calculate weights @@ -193,6 +169,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return color; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Atop(Vector4 dst, Vector4 src, Vector4 blend) { // calculate weights @@ -212,6 +189,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return color; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 In(Vector4 dst, Vector4 src, Vector4 blend) { float alpha = dst.W * src.W; @@ -223,6 +201,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return color; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Out(Vector4 dst, Vector4 src) { float alpha = (1 - dst.W) * src.W; @@ -234,6 +213,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return color; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 Xor(Vector4 dst, Vector4 src) { float srcW = 1 - dst.W; @@ -249,6 +229,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders 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 b8b97ea0a4..a531716117 100644 --- a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs @@ -2,6 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; +using System.Numerics; +using SixLabors.ImageSharp.Memory; using SixLabors.Memory; namespace SixLabors.ImageSharp.PixelFormats @@ -26,16 +29,111 @@ namespace SixLabors.ImageSharp.PixelFormats public abstract TPixel Blend(TPixel background, TPixel source, float amount); /// - /// Blend 2 pixels together. + /// 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, "from" is returned, at amount = 1, "to" 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, "from" is returned, at amount = 1, "to" is returned. + /// + protected abstract void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount); + + /// + /// Blends 2 rows together + /// + /// memory manager 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, "from" is returned, at amount = 1, "to" is returned. + /// + public void Blend(MemoryAllocator memoryManager, Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + this.Blend(memoryManager, destination, background, source, amount); + } + + /// + /// Blends 2 rows together /// - /// The - /// The destination span. - /// The background span. - /// The source span. + /// the pixel format of the source span + /// memory manager 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, "from" is returned, at amount = 1, "to" is returned. + /// + public void Blend(MemoryAllocator memoryManager, Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + where TPixelSrc : struct, 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)); + + using (IMemoryOwner buffer = memoryManager.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); + + PixelOperations.Instance.ToScaledVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToScaledVector4(source, sourceSpan, destination.Length); + + this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount); + + PixelOperations.Instance.PackFromScaledVector4(destinationSpan, destination, destination.Length); + } + } + + /// + /// Blends 2 rows together + /// + /// the pixel format of the source span + /// memory manager 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, "from" is returned, at amount = 1, "to" is returned. /// - public abstract void Blend(MemoryAllocator memoryAllocator, Span destination, Span background, Span source, Span amount); + public void Blend(MemoryAllocator memoryManager, Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + where TPixelSrc : struct, IPixel + { + Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); + Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + + using (IMemoryOwner buffer = memoryManager.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); + + PixelOperations.Instance.ToScaledVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToScaledVector4(source, sourceSpan, destination.Length); + + this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount); + + PixelOperations.Instance.PackFromScaledVector4(destinationSpan, destination, destination.Length); + } + } } } diff --git a/src/ImageSharp/PixelFormats/README.md b/src/ImageSharp/PixelFormats/README.md index c332bc92c1..cbebaf23ad 100644 --- a/src/ImageSharp/PixelFormats/README.md +++ b/src/ImageSharp/PixelFormats/README.md @@ -2,9 +2,5 @@ https://github.com/MonoGame/MonoGame -Rgba32 is our default format. As such it positioned within the ImageSharp root namespace to ensure visibility of the format. - -All other pixel formats should be positioned within ImageSharp.PixelFormats to reduce intellisense burden. - 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 Rgba32 pixel format the R component is the least significant byte, and the A component is the most significant. diff --git a/src/ImageSharp/PixelFormats/Rgb24.cs b/src/ImageSharp/PixelFormats/Rgb24.cs index d7e1c47ec0..24c311d0d3 100644 --- a/src/ImageSharp/PixelFormats/Rgb24.cs +++ b/src/ImageSharp/PixelFormats/Rgb24.cs @@ -50,6 +50,21 @@ namespace SixLabors.ImageSharp.PixelFormats this.B = b; } + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// An instance of . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Rgb24(ColorSpaces.Rgb color) + { + var vector = new Vector4(color.ToVector3(), 1); + Rgb24 rgb = default; + rgb.PackFromScaledVector4(vector); + return rgb; + } + /// public PixelOperations CreatePixelOperations() => new PixelOperations(); diff --git a/src/ImageSharp/PixelFormats/Rgba32.cs b/src/ImageSharp/PixelFormats/Rgba32.cs index cf66538c52..7349639fdc 100644 --- a/src/ImageSharp/PixelFormats/Rgba32.cs +++ b/src/ImageSharp/PixelFormats/Rgba32.cs @@ -111,10 +111,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// The alpha component. [MethodImpl(MethodImplOptions.AggressiveInlining)] public Rgba32(float r, float g, float b, float a = 1) - : this() - { - this.Pack(r, g, b, a); - } + : this() => this.Pack(r, g, b, a); /// /// Initializes a new instance of the struct. @@ -124,10 +121,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Rgba32(Vector3 vector) - : this() - { - this.Pack(ref vector); - } + : this() => this.Pack(ref vector); /// /// Initializes a new instance of the struct. @@ -137,10 +131,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Rgba32(Vector4 vector) - : this() - { - this = PackNew(ref vector); - } + : this() => this = PackNew(ref vector); /// /// Initializes a new instance of the struct. @@ -150,10 +141,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Rgba32(uint packed) - : this() - { - this.Rgba = packed; - } + : this() => this.Rgba = packed; /// /// Gets or sets the packed representation of the Rgba32 struct. @@ -206,23 +194,31 @@ namespace SixLabors.ImageSharp.PixelFormats set => this.Rgba = value; } + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// An instance of . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Rgba32(ColorSpaces.Rgb color) + { + var vector = new Vector4(color.ToVector3(), 1); + Rgba32 rgba = default; + rgba.PackFromScaledVector4(vector); + return rgba; + } + /// /// Compares two objects for equality. /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// + /// 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(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Rgba32 left, Rgba32 right) - { - return left.Rgba == right.Rgba; - } + public static bool operator ==(Rgba32 left, Rgba32 right) => left.Rgba == right.Rgba; /// /// Compares two objects for equality. @@ -233,10 +229,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// True if the parameter is not equal to the parameter; otherwise, false. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Rgba32 left, Rgba32 right) - { - return left.Rgba != right.Rgba; - } + public static bool operator !=(Rgba32 left, Rgba32 right) => left.Rgba != right.Rgba; /// /// Creates a new instance of the struct. @@ -248,20 +241,14 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . /// - public static Rgba32 FromHex(string hex) - { - return ColorBuilder.FromHex(hex); - } + public static Rgba32 FromHex(string hex) => ColorBuilder.FromHex(hex); /// public PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PackFromRgba32(Rgba32 source) - { - this = source; - } + public void PackFromRgba32(Rgba32 source) => this = source; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -295,17 +282,11 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgb24(ref Rgb24 dest) - { - dest = Unsafe.As(ref this); - } + public void ToRgb24(ref Rgb24 dest) => dest = Unsafe.As(ref this); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ToRgba32(ref Rgba32 dest) - { - dest = this; - } + public void ToRgba32(ref Rgba32 dest) => dest = this; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -338,31 +319,19 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PackFromScaledVector4(Vector4 vector) - { - this.PackFromVector4(vector); - } + public void PackFromScaledVector4(Vector4 vector) => this.PackFromVector4(vector); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToScaledVector4() - { - return this.ToVector4(); - } + public Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PackFromVector4(Vector4 vector) - { - this.Pack(ref vector); - } + public void PackFromVector4(Vector4 vector) => this.Pack(ref vector); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() - { - return new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; - } + public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; /// /// Gets the value of this struct as . @@ -417,23 +386,14 @@ namespace SixLabors.ImageSharp.PixelFormats public void ToRgba64(ref Rgba64 dest) => dest.PackFromScaledVector4(this.ToScaledVector4()); /// - public override bool Equals(object obj) - { - return obj is Rgba32 rgba32 && this.Equals(rgba32); - } + public override bool Equals(object obj) => obj is Rgba32 rgba32 && this.Equals(rgba32); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Rgba32 other) - { - return this.Rgba == other.Rgba; - } + public bool Equals(Rgba32 other) => this.Rgba == other.Rgba; /// - public override string ToString() - { - return $"({this.R},{this.G},{this.B},{this.A})"; - } + public override string ToString() => $"({this.R},{this.G},{this.B},{this.A})"; /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -444,10 +404,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// A of values in [0, 255] [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Vector4 ToByteScaledVector4() - { - return new Vector4(this.R, this.G, this.B, this.A); - } + internal Vector4 ToByteScaledVector4() => new Vector4(this.R, this.G, this.B, this.A); /// /// Packs a into a color returning a new instance as a result. diff --git a/src/ImageSharp/Primitives/DenseMatrix{T}.cs b/src/ImageSharp/Primitives/DenseMatrix{T}.cs index ef1abc8971..7cfa98ec1b 100644 --- a/src/ImageSharp/Primitives/DenseMatrix{T}.cs +++ b/src/ImageSharp/Primitives/DenseMatrix{T}.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.Runtime.CompilerServices; +using SixLabors.Primitives; namespace SixLabors.ImageSharp.Primitives { @@ -31,6 +32,11 @@ namespace SixLabors.ImageSharp.Primitives /// public readonly int Rows; + /// + /// Gets the size of the dense matrix. + /// + public readonly Size Size; + /// /// Gets the number of items in the array. /// @@ -57,6 +63,7 @@ namespace SixLabors.ImageSharp.Primitives this.Rows = rows; this.Columns = columns; + this.Size = new Size(columns, rows); this.Count = columns * rows; this.Data = new T[this.Columns * this.Rows]; } @@ -76,6 +83,7 @@ namespace SixLabors.ImageSharp.Primitives 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]; @@ -182,13 +190,13 @@ namespace SixLabors.ImageSharp.Primitives } /// - public bool Equals(DenseMatrix other) => - this.Columns == other.Columns && - this.Rows == other.Rows && - this.Span.SequenceEqual(other.Span); + public override bool Equals(object obj) => obj is DenseMatrix other && this.Equals(other); /// - public override bool Equals(object obj) => obj is DenseMatrix other && this.Equals(other); + public bool Equals(DenseMatrix other) => + this.Columns == other.Columns + && this.Rows == other.Rows + && this.Span.SequenceEqual(other.Span); /// public override int GetHashCode() => this.Data.GetHashCode(); diff --git a/src/ImageSharp/Processing/CropExtensions.cs b/src/ImageSharp/Processing/CropExtensions.cs index 34c754a08e..1c0d80afc9 100644 --- a/src/ImageSharp/Processing/CropExtensions.cs +++ b/src/ImageSharp/Processing/CropExtensions.cs @@ -35,6 +35,6 @@ namespace SixLabors.ImageSharp.Processing /// The public static IImageProcessingContext Crop(this IImageProcessingContext source, Rectangle cropRectangle) where TPixel : struct, IPixel - => source.ApplyProcessor(new CropProcessor(cropRectangle)); + => source.ApplyProcessor(new CropProcessor(cropRectangle, source.GetCurrentSize())); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs index c4f4266d98..60754b3bf2 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs @@ -2,10 +2,10 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Threading.Tasks; + using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Binarization @@ -56,7 +56,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization public TPixel LowerColor { get; set; } /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + protected override void OnFrameApply( + ImageFrame source, + Rectangle sourceRectangle, + Configuration configuration) { float threshold = this.Threshold * 255F; TPixel upper = this.UpperColor; @@ -70,25 +73,29 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); - ParallelFor.WithConfiguration( - startY, - endY, + var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); + + ParallelHelper.IterateRows( + workingRect, configuration, - y => + rows => { - Span row = source.GetPixelRowSpan(y); - Rgba32 rgba = default; - - for (int x = startX; x < endX; x++) + for (int y = rows.Min; y < rows.Max; y++) { - ref TPixel color = ref row[x]; - color.ToRgba32(ref rgba); + Span row = source.GetPixelRowSpan(y); + Rgba32 rgba = default; + + for (int x = startX; x < endX; x++) + { + ref TPixel color = ref row[x]; + color.ToRgba32(ref rgba); - // Convert to grayscale using ITU-R Recommendation BT.709 if required - float luminance = isAlphaOnly - ? rgba.A - : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B); - color = luminance >= threshold ? upper : lower; + // Convert to grayscale using ITU-R Recommendation BT.709 if required + float luminance = isAlphaOnly + ? rgba.A + : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B); + color = luminance >= threshold ? upper : lower; + } } }); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs index b5a2725437..0669a12470 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -3,12 +3,10 @@ using System; using System.Numerics; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; -using SixLabors.Memory; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution @@ -27,6 +25,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// The vertical gradient operator. public Convolution2DProcessor(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; } @@ -42,19 +41,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution public DenseMatrix KernelY { get; } /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + protected override void OnFrameApply( + ImageFrame source, + Rectangle sourceRectangle, + Configuration configuration) { - int kernelYHeight = this.KernelY.Rows; - int kernelYWidth = this.KernelY.Columns; - int kernelXHeight = this.KernelX.Rows; - int kernelXWidth = this.KernelX.Columns; - int radiusY = kernelYHeight >> 1; - int radiusX = kernelXWidth >> 1; - - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; + DenseMatrix matrixY = this.KernelY; + DenseMatrix matrixX = this.KernelX; + + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; int maxY = endY - 1; int maxX = endX - 1; @@ -62,67 +61,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { source.CopyTo(targetPixels); - ParallelFor.WithConfiguration( - startY, - endY, - configuration, - y => - { - Span sourceRow = source.GetPixelRowSpan(y); - Span targetRow = targetPixels.GetRowSpan(y); + var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); + int width = workingRectangle.Width; - for (int x = startX; x < endX; x++) + ParallelHelper.IterateRowsWithTempBuffer( + workingRectangle, + configuration, + (rows, vectorBuffer) => { - float rX = 0; - float gX = 0; - float bX = 0; - float rY = 0; - float gY = 0; - float bY = 0; + Span vectorSpan = vectorBuffer.Span; + int length = vectorSpan.Length; - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelYHeight; fy++) + for (int y = rows.Min; y < rows.Max; y++) { - int fyr = fy - radiusY; - int offsetY = y + fyr; + Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); + PixelOperations.Instance.ToVector4(targetRowSpan, vectorSpan, length); - offsetY = offsetY.Clamp(0, maxY); - Span sourceOffsetRow = source.GetPixelRowSpan(offsetY); - - for (int fx = 0; fx < kernelXWidth; fx++) + for (int x = 0; x < width; x++) { - int fxr = fx - radiusX; - int offsetX = x + fxr; - - offsetX = offsetX.Clamp(0, maxX); - Vector4 currentColor = sourceOffsetRow[offsetX].ToVector4().Premultiply(); - - if (fy < kernelXHeight) - { - Vector4 kx = this.KernelX[fy, fx] * currentColor; - rX += kx.X; - gX += kx.Y; - bX += kx.Z; - } - - if (fx < kernelYWidth) - { - Vector4 ky = this.KernelY[fy, fx] * currentColor; - rY += ky.X; - gY += ky.Y; - bY += ky.Z; - } + DenseMatrixUtils.Convolve2D(in matrixY, in matrixX, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX); } - } - float red = MathF.Sqrt((rX * rX) + (rY * rY)); - float green = MathF.Sqrt((gX * gX) + (gY * gY)); - float blue = MathF.Sqrt((bX * bX) + (bY * bY)); - - ref TPixel pixel = ref targetRow[x]; - pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W).UnPremultiply()); - } - }); + PixelOperations.Instance.PackFromVector4(vectorSpan, targetRowSpan, length); + } + }); Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs index 07b2ed064e..1f47649e6f 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -3,13 +3,11 @@ using System; using System.Numerics; -using System.Threading.Tasks; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; -using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.Memory; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution @@ -47,8 +45,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { using (Buffer2D firstPassPixels = configuration.MemoryAllocator.Allocate2D(source.Size())) { - this.ApplyConvolution(firstPassPixels, source.PixelBuffer, source.Bounds(), this.KernelX, configuration); - this.ApplyConvolution(source.PixelBuffer, firstPassPixels, sourceRectangle, this.KernelY, configuration); + source.CopyTo(firstPassPixels); + + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + this.ApplyConvolution(firstPassPixels, source.PixelBuffer, interest, this.KernelX, configuration); + this.ApplyConvolution(source.PixelBuffer, firstPassPixels, interest, this.KernelY, configuration); } } @@ -67,14 +68,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Buffer2D targetPixels, Buffer2D sourcePixels, Rectangle sourceRectangle, - DenseMatrix kernel, + in DenseMatrix kernel, Configuration configuration) { - int kernelHeight = kernel.Rows; - int kernelWidth = kernel.Columns; - int radiusY = kernelHeight >> 1; - int radiusX = kernelWidth >> 1; - + DenseMatrix matrix = kernel; int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; @@ -82,43 +79,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution int maxY = endY - 1; int maxX = endX - 1; - ParallelFor.WithConfiguration( - startY, - endY, - configuration, - y => - { - Span targetRow = targetPixels.GetRowSpan(y); + var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); + int width = workingRectangle.Width; - for (int x = startX; x < endX; x++) + ParallelHelper.IterateRowsWithTempBuffer( + workingRectangle, + configuration, + (rows, vectorBuffer) => { - Vector4 destination = default; + Span vectorSpan = vectorBuffer.Span; + int length = vectorSpan.Length; - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelHeight; fy++) + for (int y = rows.Min; y < rows.Max; y++) { - int fyr = fy - radiusY; - int offsetY = y + fyr; - - offsetY = offsetY.Clamp(0, maxY); - Span row = sourcePixels.GetRowSpan(offsetY); + Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); + PixelOperations.Instance.ToVector4(targetRowSpan, vectorSpan, length); - for (int fx = 0; fx < kernelWidth; fx++) + for (int x = 0; x < width; x++) { - int fxr = fx - radiusX; - int offsetX = x + fxr; - - offsetX = offsetX.Clamp(0, maxX); - - Vector4 currentColor = row[offsetX].ToVector4().Premultiply(); - destination += kernel[fy, fx] * currentColor; + DenseMatrixUtils.Convolve(in matrix, sourcePixels, vectorSpan, y, x, maxY, maxX, startX); } - } - ref TPixel pixel = ref targetRow[x]; - pixel.PackFromVector4(destination.UnPremultiply()); - } - }); + PixelOperations.Instance.PackFromVector4(vectorSpan, targetRowSpan, length); + } + }); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs index 31e638a0ad..d2f3f8fc58 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -3,13 +3,10 @@ using System; using System.Numerics; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; -using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.Memory; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution @@ -25,10 +22,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// Initializes a new instance of the class. /// /// The 2d gradient operator. - public ConvolutionProcessor(DenseMatrix kernelXY) - { - this.KernelXY = kernelXY; - } + public ConvolutionProcessor(DenseMatrix kernelXY) => this.KernelXY = kernelXY; /// /// Gets the 2d gradient operator. @@ -38,13 +32,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { - int kernelLength = this.KernelXY.Rows; - int radius = kernelLength >> 1; - - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; + DenseMatrix matrix = this.KernelXY; + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; int maxY = endY - 1; int maxX = endX - 1; @@ -52,50 +45,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { source.CopyTo(targetPixels); - ParallelFor.WithConfiguration( - startY, - endY, - configuration, - y => - { - Span sourceRow = source.GetPixelRowSpan(y); - Span targetRow = targetPixels.GetRowSpan(y); - - for (int x = startX; x < endX; x++) - { - float red = 0; - float green = 0; - float blue = 0; - - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelLength; fy++) - { - int fyr = fy - radius; - int offsetY = y + fyr; - - offsetY = offsetY.Clamp(0, maxY); - Span sourceOffsetRow = source.GetPixelRowSpan(offsetY); - - for (int fx = 0; fx < kernelLength; fx++) - { - int fxr = fx - radius; - int offsetX = x + fxr; - - offsetX = offsetX.Clamp(0, maxX); - - Vector4 currentColor = sourceOffsetRow[offsetX].ToVector4().Premultiply(); - currentColor *= this.KernelXY[fy, fx]; - - red += currentColor.X; - green += currentColor.Y; - blue += currentColor.Z; - } - } - - ref TPixel pixel = ref targetRow[x]; - pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W).UnPremultiply()); - } - }); + var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); + int width = workingRectangle.Width; + + ParallelHelper.IterateRowsWithTempBuffer( + workingRectangle, + configuration, + (rows, vectorBuffer) => + { + Span vectorSpan = vectorBuffer.Span; + int length = vectorSpan.Length; + + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); + PixelOperations.Instance.ToVector4(targetRowSpan, vectorSpan, length); + + for (int x = 0; x < width; x++) + { + DenseMatrixUtils.Convolve(in matrix, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX); + } + + PixelOperations.Instance.PackFromVector4(vectorSpan, targetRowSpan, length); + } + }); Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs index dd43d3e159..8927716492 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor.cs @@ -23,6 +23,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// Whether to convert the image to grayscale before performing edge detection. protected EdgeDetector2DProcessor(DenseMatrix kernelX, DenseMatrix kernelY, bool grayscale) { + Guard.IsTrue(kernelX.Size.Equals(kernelY.Size), $"{nameof(kernelX)} {nameof(kernelY)}", "Kernel sizes must be the same."); this.KernelX = kernelX; this.KernelY = kernelY; this.Grayscale = grayscale; @@ -42,10 +43,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution public bool Grayscale { get; set; } /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - new Convolution2DProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration); - } + protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) => new Convolution2DProcessor(this.KernelX, this.KernelY).Apply(source, sourceRectangle, configuration); /// protected override void BeforeFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs index 316de422f5..ebf9c8dec2 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor.cs @@ -8,6 +8,7 @@ using System.Runtime.InteropServices; using System.Threading.Tasks; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; using SixLabors.ImageSharp.Processing.Processors.Filters; @@ -124,6 +125,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution shiftY = 0; } + var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + // Additional runs. // ReSharper disable once ForCanBeConvertedToForeach for (int i = 1; i < kernels.Length; i++) @@ -135,30 +138,35 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Buffer2D passPixels = pass.PixelBuffer; Buffer2D targetPixels = source.PixelBuffer; - ParallelFor.WithConfiguration( - minY, - maxY, + ParallelHelper.IterateRows( + workingRect, configuration, - y => + rows => { - int offsetY = y - shiftY; + for (int y = rows.Min; y < rows.Max; y++) + { + int offsetY = y - shiftY; - ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(passPixels.GetRowSpan(offsetY)); - ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(targetPixels.GetRowSpan(offsetY)); + ref TPixel passPixelsBase = + ref MemoryMarshal.GetReference(passPixels.GetRowSpan(offsetY)); + ref TPixel targetPixelsBase = + ref MemoryMarshal.GetReference(targetPixels.GetRowSpan(offsetY)); - for (int x = minX; x < maxX; x++) - { - int offsetX = x - shiftX; + for (int x = minX; x < maxX; x++) + { + int offsetX = x - shiftX; - // Grab the max components of the two pixels - ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, offsetX); - ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, offsetX); + // Grab the max components of the two pixels + ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, offsetX); + ref TPixel currentTargetPixel = + ref Unsafe.Add(ref targetPixelsBase, offsetX); - var pixelValue = Vector4.Max( - currentPassPixel.ToVector4(), - currentTargetPixel.ToVector4()); + var pixelValue = Vector4.Max( + currentPassPixel.ToVector4(), + currentTargetPixel.ToVector4()); - currentTargetPixel.PackFromVector4(pixelValue); + currentTargetPixel.PackFromVector4(pixelValue); + } } }); } diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs index 59898e9fc1..6ad4dcba97 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs @@ -3,11 +3,11 @@ using System; using System.Numerics; -using System.Threading.Tasks; + using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Memory; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Effects @@ -49,7 +49,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects public int BrushSize { get; } /// - protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + protected override void OnFrameApply( + ImageFrame source, + Rectangle sourceRectangle, + Configuration configuration) { if (this.BrushSize <= 0 || this.BrushSize > source.Height || this.BrushSize > source.Width) { @@ -70,69 +73,74 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects { source.CopyTo(targetPixels); - ParallelFor.WithConfiguration( - startY, - maxY, + var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); + ParallelHelper.IterateRows( + workingRect, configuration, - y => - { - Span sourceRow = source.GetPixelRowSpan(y); - Span targetRow = targetPixels.GetRowSpan(y); - - for (int x = startX; x < endX; x++) + rows => { - int maxIntensity = 0; - int maxIndex = 0; + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = source.GetPixelRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(y); - int[] intensityBin = new int[levels]; - float[] redBin = new float[levels]; - float[] blueBin = new float[levels]; - float[] greenBin = new float[levels]; + for (int x = startX; x < endX; x++) + { + int maxIntensity = 0; + int maxIndex = 0; - for (int fy = 0; fy <= radius; fy++) - { - int fyr = fy - radius; - int offsetY = y + fyr; + int[] intensityBin = new int[levels]; + float[] redBin = new float[levels]; + float[] blueBin = new float[levels]; + float[] greenBin = new float[levels]; - offsetY = offsetY.Clamp(0, maxY); + for (int fy = 0; fy <= radius; fy++) + { + int fyr = fy - radius; + int offsetY = y + fyr; - Span sourceOffsetRow = source.GetPixelRowSpan(offsetY); + offsetY = offsetY.Clamp(0, maxY); - for (int fx = 0; fx <= radius; fx++) - { - int fxr = fx - radius; - int offsetX = x + fxr; - offsetX = offsetX.Clamp(0, maxX); + Span sourceOffsetRow = source.GetPixelRowSpan(offsetY); - var vector = sourceOffsetRow[offsetX].ToVector4(); + for (int fx = 0; fx <= radius; fx++) + { + int fxr = fx - radius; + int offsetX = x + fxr; + offsetX = offsetX.Clamp(0, maxX); - float sourceRed = vector.X; - float sourceBlue = vector.Z; - float sourceGreen = vector.Y; + var vector = sourceOffsetRow[offsetX].ToVector4(); - int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (levels - 1)); + float sourceRed = vector.X; + float sourceBlue = vector.Z; + float sourceGreen = vector.Y; - intensityBin[currentIntensity]++; - blueBin[currentIntensity] += sourceBlue; - greenBin[currentIntensity] += sourceGreen; - redBin[currentIntensity] += sourceRed; + int currentIntensity = (int)MathF.Round( + (sourceBlue + sourceGreen + sourceRed) / 3F * (levels - 1)); - if (intensityBin[currentIntensity] > maxIntensity) - { - maxIntensity = intensityBin[currentIntensity]; - maxIndex = currentIntensity; - } - } + intensityBin[currentIntensity]++; + blueBin[currentIntensity] += sourceBlue; + greenBin[currentIntensity] += sourceGreen; + redBin[currentIntensity] += sourceRed; - float red = MathF.Abs(redBin[maxIndex] / maxIntensity); - float green = MathF.Abs(greenBin[maxIndex] / maxIntensity); - float blue = MathF.Abs(blueBin[maxIndex] / maxIntensity); + if (intensityBin[currentIntensity] > maxIntensity) + { + maxIntensity = intensityBin[currentIntensity]; + maxIndex = currentIntensity; + } + } - ref TPixel pixel = ref targetRow[x]; - pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); + float red = MathF.Abs(redBin[maxIndex] / maxIntensity); + float green = MathF.Abs(greenBin[maxIndex] / maxIntensity); + float blue = MathF.Abs(blueBin[maxIndex] / maxIntensity); + + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4( + new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); + } + } } - } - }); + }); Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs index 6244d8bf76..e20b42eb7c 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs @@ -5,6 +5,7 @@ using System; using System.Numerics; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; @@ -35,25 +36,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); - int startY = interest.Y; - int endY = interest.Bottom; - int startX = interest.X; - int endX = interest.Right; + Matrix4x4 matrix = this.Matrix; - ParallelFor.WithConfiguration( - startY, - endY, + ParallelHelper.IterateRows( + interest, configuration, - y => + rows => { - Span row = source.GetPixelRowSpan(y); - - for (int x = startX; x < endX; x++) + for (int y = rows.Min; y < rows.Max; y++) { - ref TPixel pixel = ref row[x]; - var vector = Vector4.Transform(pixel.ToVector4(), matrix); - pixel.PackFromVector4(vector); + Span row = source.GetPixelRowSpan(y); + + for (int x = interest.X; x < interest.Right; x++) + { + ref TPixel pixel = ref row[x]; + var vector = Vector4.Transform(pixel.ToVector4(), matrix); + pixel.PackFromVector4(vector); + } } }); } diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs index ecbeebeb06..4adddd1536 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; using SixLabors.Primitives; @@ -67,6 +68,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays int width = maxX - minX; + var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + using (IMemoryOwner colors = source.MemoryAllocator.Allocate(width)) using (IMemoryOwner amount = source.MemoryAllocator.Allocate(width)) { @@ -74,25 +77,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays Span colorSpan = colors.GetSpan(); Span amountSpan = amount.GetSpan(); - // TODO: Use Span.Fill? - for (int i = 0; i < width; i++) - { - colorSpan[i] = this.Color; - amountSpan[i] = this.GraphicsOptions.BlendPercentage; - } + colorSpan.Fill(this.Color); + amountSpan.Fill(this.GraphicsOptions.BlendPercentage); PixelBlender blender = PixelOperations.Instance.GetPixelBlender(this.GraphicsOptions); - ParallelFor.WithConfiguration( - minY, - maxY, + + ParallelHelper.IterateRows( + workingRect, configuration, - y => - { - Span destination = source.GetPixelRowSpan(y - startY).Slice(minX - startX, width); + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span destination = + source.GetPixelRowSpan(y - startY).Slice(minX - startX, width); - // This switched color & destination in the 2nd and 3rd places because we are applying the target color under the current one - blender.Blend(source.MemoryAllocator, destination, colors.GetSpan(), destination, amount.GetSpan()); - }); + // This switched color & destination in the 2nd and 3rd places because we are applying the target color under the current one + blender.Blend( + source.MemoryAllocator, + destination, + colors.GetSpan(), + destination, + amount.GetSpan()); + } + }); } } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs index eb91fec043..93d6edff19 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs @@ -7,6 +7,7 @@ using System.Numerics; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; using SixLabors.Memory; @@ -84,6 +85,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { + // TODO: can we simplify the rectangle calculation? int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; @@ -113,36 +115,43 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays } int width = maxX - minX; + int offsetX = minX - startX; + + var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + using (IMemoryOwner rowColors = source.MemoryAllocator.Allocate(width)) { - // Be careful! Do not capture rowColorsSpan in the lambda below! - Span rowColorsSpan = rowColors.GetSpan(); - - for (int i = 0; i < width; i++) - { - rowColorsSpan[i] = glowColor; - } + rowColors.GetSpan().Fill(glowColor); - ParallelFor.WithTemporaryBuffer( - minY, - maxY, + ParallelHelper.IterateRowsWithTempBuffer( + workingRect, configuration, - width, - (y, amounts) => - { - Span amountsSpan = amounts.GetSpan(); - int offsetY = y - startY; - int offsetX = minX - startX; - for (int i = 0; i < width; i++) + (rows, amounts) => { - float distance = Vector2.Distance(center, new Vector2(i + offsetX, offsetY)); - amountsSpan[i] = (this.GraphicsOptions.BlendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1); - } - - Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width); - - this.blender.Blend(source.MemoryAllocator, destination, destination, rowColors.GetSpan(), amountsSpan); - }); + Span amountsSpan = amounts.Span; + + for (int y = rows.Min; y < rows.Max; y++) + { + int offsetY = y - startY; + + for (int i = 0; i < width; i++) + { + float distance = Vector2.Distance(center, new Vector2(i + offsetX, offsetY)); + amountsSpan[i] = + (this.GraphicsOptions.BlendPercentage * (1 - (.95F * (distance / maxDistance)))) + .Clamp(0, 1); + } + + Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width); + + this.blender.Blend( + source.MemoryAllocator, + destination, + destination, + rowColors.GetSpan(), + amountsSpan); + } + }); } } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs index 63780df476..52dade4eff 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs @@ -7,6 +7,7 @@ using System.Numerics; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; using SixLabors.Memory; @@ -115,43 +116,43 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays } int width = maxX - minX; + int offsetX = minX - startX; + + var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + using (IMemoryOwner rowColors = source.MemoryAllocator.Allocate(width)) { - // Be careful! Do not capture rowColorsSpan in the lambda below! - Span rowColorsSpan = rowColors.GetSpan(); - - for (int i = 0; i < width; i++) - { - rowColorsSpan[i] = vignetteColor; - } + rowColors.GetSpan().Fill(vignetteColor); - ParallelFor.WithTemporaryBuffer( - minY, - maxY, + ParallelHelper.IterateRowsWithTempBuffer( + workingRect, configuration, - width, - (y, amounts) => + (rows, amounts) => { - Span amountsSpan = amounts.GetSpan(); - int offsetY = y - startY; - int offsetX = minX - startX; - for (int i = 0; i < width; i++) + Span amountsSpan = amounts.Span; + + for (int y = rows.Min; y < rows.Max; y++) { - float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); - amountsSpan[i] = - (this.GraphicsOptions.BlendPercentage * (.9F * (distance / maxDistance))).Clamp( - 0, - 1); + int offsetY = y - startY; + + for (int i = 0; i < width; i++) + { + float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); + amountsSpan[i] = + (this.GraphicsOptions.BlendPercentage * (.9F * (distance / maxDistance))).Clamp( + 0, + 1); + } + + Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width); + + this.blender.Blend( + source.MemoryAllocator, + destination, + destination, + rowColors.GetSpan(), + amountsSpan); } - - Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width); - - this.blender.Blend( - source.MemoryAllocator, - destination, - destination, - rowColors.GetSpan(), - amountsSpan); }); } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs index 0f6846d1bf..3da09cde09 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs @@ -23,5 +23,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The IFrameQuantizer CreateFrameQuantizer() where TPixel : struct, IPixel; + + /// + /// Creates the generic frame quantizer + /// + /// The pixel format. + /// The maximum number of colors to hold in the color palette. + /// The + IFrameQuantizer CreateFrameQuantizer(int maxColors) + where TPixel : struct, IPixel; } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs index 3eac70eea5..39546d63f7 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Maximum allowed color depth /// - private readonly byte colors; + private readonly int colors; /// /// Stores the tree @@ -43,9 +43,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// the second pass quantizes a color based on the nodes in the tree /// public OctreeFrameQuantizer(OctreeQuantizer quantizer) + : this(quantizer, quantizer.MaxColors) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The octree quantizer. + /// The maximum number of colors to hold in the color palette. + /// + /// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree, + /// the second pass quantizes a color based on the nodes in the tree + /// + public OctreeFrameQuantizer(OctreeQuantizer quantizer, int maxColors) : base(quantizer, false) { - this.colors = (byte)quantizer.MaxColors; + this.colors = maxColors; this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.colors).Clamp(1, 8)); } @@ -261,13 +275,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization [MethodImpl(MethodImplOptions.AggressiveInlining)] public TPixel[] Palletize(int colorCount) { - while (this.Leaves > colorCount) + while (this.Leaves > colorCount - 1) { this.Reduce(); } // Now palletize the nodes - var palette = new TPixel[colorCount + 1]; + var palette = new TPixel[colorCount]; int paletteIndex = 0; this.root.ConstructPalette(palette, ref paletteIndex); @@ -285,10 +299,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetPaletteIndex(ref TPixel pixel, ref Rgba32 rgba) - { - return this.root.GetPaletteIndex(ref pixel, 0, ref rgba); - } + public int GetPaletteIndex(ref TPixel pixel, ref Rgba32 rgba) => this.root.GetPaletteIndex(ref pixel, 0, ref rgba); /// /// Keep track of the previous node that was quantized @@ -297,10 +308,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The node last quantized /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void TrackPrevious(OctreeNode node) - { - this.previousNode = node; - } + protected void TrackPrevious(OctreeNode node) => this.previousNode = node; /// /// Reduce the depth of the tree diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs index 385f6246f8..22bb5223f0 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs @@ -15,6 +15,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class OctreeQuantizer : IQuantizer { + /// + /// The default maximum number of colors to use when quantizing the image. + /// + public const int DefaultMaxColors = 256; + /// /// Initializes a new instance of the class. /// @@ -26,7 +31,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Initializes a new instance of the class. /// - /// The maximum number of colors to hold in the color palette + /// The maximum number of colors to hold in the color palette. public OctreeQuantizer(int maxColors) : this(GetDiffuser(true), maxColors) { @@ -37,7 +42,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Whether to apply dithering to the output image public OctreeQuantizer(bool dither) - : this(GetDiffuser(dither), 255) + : this(GetDiffuser(dither), DefaultMaxColors) { } @@ -46,7 +51,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The error diffusion algorithm, if any, to apply to the output image public OctreeQuantizer(IErrorDiffuser diffuser) - : this(diffuser, 255) + : this(diffuser, DefaultMaxColors) { } @@ -57,10 +62,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The maximum number of colors to hold in the color palette public OctreeQuantizer(IErrorDiffuser diffuser, int maxColors) { - Guard.MustBeBetweenOrEqualTo(maxColors, 1, 255, nameof(maxColors)); - this.Diffuser = diffuser; - this.MaxColors = maxColors; + this.MaxColors = maxColors.Clamp(1, DefaultMaxColors); } /// @@ -76,6 +79,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization where TPixel : struct, IPixel => new OctreeFrameQuantizer(this); + /// + public IFrameQuantizer CreateFrameQuantizer(int maxColors) + where TPixel : struct, IPixel + { + maxColors = maxColors.Clamp(1, DefaultMaxColors); + return new OctreeFrameQuantizer(this, maxColors); + } + private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null; } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs index 8df81b426f..cdf3514e2d 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs @@ -36,6 +36,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public PaletteFrameQuantizer(PaletteQuantizer quantizer, TPixel[] colors) : base(quantizer, true) { + // TODO: Why is this value constrained? Gif has limitations but theoretically + // we might want to reduce the palette of an image to greater than that limitation. Guard.MustBeBetweenOrEqualTo(colors.Length, 1, 256, nameof(colors)); this.palette = colors; this.paletteVector = new Vector4[this.palette.Length]; diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs index 8ae9177185..27ef05dfe9 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs @@ -37,10 +37,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Initializes a new instance of the class. /// /// The error diffusion algorithm, if any, to apply to the output image - public PaletteQuantizer(IErrorDiffuser diffuser) - { - this.Diffuser = diffuser; - } + public PaletteQuantizer(IErrorDiffuser diffuser) => this.Diffuser = diffuser; /// public IErrorDiffuser Diffuser { get; } @@ -50,6 +47,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization where TPixel : struct, IPixel => this.CreateFrameQuantizer(() => NamedColors.WebSafePalette); + /// + public IFrameQuantizer CreateFrameQuantizer(int maxColors) + where TPixel : struct, IPixel + { + TPixel[] websafe = NamedColors.WebSafePalette; + int max = Math.Min(maxColors, websafe.Length); + + if (max != websafe.Length) + { + return this.CreateFrameQuantizer(() => NamedColors.WebSafePalette.AsSpan(0, max).ToArray()); + } + + return this.CreateFrameQuantizer(() => websafe); + } + /// /// Gets the palette to use to quantize the image. /// diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs index 2e3bb2c419..38862ef446 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs @@ -3,7 +3,7 @@ using System; using System.Buffers; - +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; @@ -57,8 +57,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Gets the pixels of this . /// /// The + [MethodImpl(InliningOptions.ShortMethod)] public Span GetPixelSpan() => this.pixels.GetSpan(); + /// + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from the the first pixel on that row. + /// + /// The row. + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public Span GetRowSpan(int rowIndex) => this.GetPixelSpan().Slice(rowIndex * this.Width, this.Width); + /// public void Dispose() { diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs index 021dc62fbf..13bc057da8 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -3,7 +3,6 @@ using System; using System.Buffers; -using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -38,7 +37,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization where TPixel : struct, IPixel { // TODO: The WuFrameQuantizer code is rising several questions: - // - Do we really need to ALWAYS allocate the whole table of size TableLength? (~ 2471625 * sizeof(long) * 5 bytes ) + // - Do we really need to ALWAYS allocate the whole table of size TableLength? (~ 2471625 * sizeof(long) * 5 bytes ) JS. I'm afraid so. // - Isn't an AOS ("array of structures") layout more efficient & more readable than SOA ("structure of arrays") for this particular use case? // (T, R, G, B, A, M2) could be grouped together! // - It's a frequently used class, we need tests! (So we can optimize safely.) There are tests in the original!!! We should just adopt them! @@ -47,12 +46,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The index bits. /// - private const int IndexBits = 6; + private const int IndexBits = 5; /// - /// The index alpha bits. + /// The index alpha bits. Keep separate for now to allow easy adjustment. /// - private const int IndexAlphaBits = 3; + private const int IndexAlphaBits = 5; /// /// The index count. @@ -65,7 +64,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1; /// - /// The table length. + /// The table length. Now 1185921. /// private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; @@ -128,11 +127,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// the second pass quantizes a color based on the position in the histogram. /// public WuFrameQuantizer(WuQuantizer quantizer) - : base(quantizer, false) + : this(quantizer, quantizer.MaxColors) { - this.colors = quantizer.MaxColors; } + /// + /// Initializes a new instance of the class. + /// + /// The wu quantizer. + /// The maximum number of colors to hold in the color palette. + /// + /// The Wu quantizer is a two pass algorithm. The initial pass sets up the 3-D color histogram, + /// the second pass quantizes a color based on the position in the histogram. + /// + public WuFrameQuantizer(WuQuantizer quantizer, int maxColors) + : base(quantizer, false) => this.colors = maxColors; + /// public override QuantizedFrame QuantizeFrame(ImageFrame image) { @@ -169,18 +179,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization if (this.palette is null) { this.palette = new TPixel[this.colors]; + Span vwtSpan = this.vwt.GetSpan(); + Span vmrSpan = this.vmr.GetSpan(); + Span vmgSpan = this.vmg.GetSpan(); + Span vmbSpan = this.vmb.GetSpan(); + Span vmaSpan = this.vma.GetSpan(); + for (int k = 0; k < this.colors; k++) { this.Mark(ref this.colorCube[k], (byte)k); - float weight = Volume(ref this.colorCube[k], this.vwt.GetSpan()); + float weight = Volume(ref this.colorCube[k], vwtSpan); if (MathF.Abs(weight) > Constants.Epsilon) { - float r = Volume(ref this.colorCube[k], this.vmr.GetSpan()); - float g = Volume(ref this.colorCube[k], this.vmg.GetSpan()); - float b = Volume(ref this.colorCube[k], this.vmb.GetSpan()); - float a = Volume(ref this.colorCube[k], this.vma.GetSpan()); + float r = Volume(ref this.colorCube[k], vmrSpan); + float g = Volume(ref this.colorCube[k], vmgSpan); + float b = Volume(ref this.colorCube[k], vmbSpan); + float a = Volume(ref this.colorCube[k], vmaSpan); ref TPixel color = ref this.palette[k]; color.PackFromScaledVector4(new Vector4(r, g, b, a) / weight / 255F); @@ -191,57 +207,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization return this.palette; } - /// - /// Quantizes the pixel - /// - /// The rgba used to quantize the pixel input - private void QuantizePixel(ref Rgba32 rgba) - { - // Add the color to a 3-D color histogram. - int r = rgba.R >> (8 - IndexBits); - int g = rgba.G >> (8 - IndexBits); - int b = rgba.B >> (8 - IndexBits); - int a = rgba.A >> (8 - IndexAlphaBits); - - int index = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); - - Span vwtSpan = this.vwt.GetSpan(); - Span vmrSpan = this.vmr.GetSpan(); - Span vmgSpan = this.vmg.GetSpan(); - Span vmbSpan = this.vmb.GetSpan(); - Span vmaSpan = this.vma.GetSpan(); - Span m2Span = this.m2.GetSpan(); - - vwtSpan[index]++; - vmrSpan[index] += rgba.R; - vmgSpan[index] += rgba.G; - vmbSpan[index] += rgba.B; - vmaSpan[index] += rgba.A; - - var vector = new Vector4(rgba.R, rgba.G, rgba.B, rgba.A); - m2Span[index] += Vector4.Dot(vector, vector); - } - /// protected override void FirstPass(ImageFrame source, int width, int height) { - // Build up the 3-D color histogram - // Loop through each row - for (int y = 0; y < height; y++) - { - Span row = source.GetPixelRowSpan(y); - ref TPixel scanBaseRef = ref MemoryMarshal.GetReference(row); - - // And loop through each column - Rgba32 rgba = default; - for (int x = 0; x < width; x++) - { - ref TPixel pixel = ref Unsafe.Add(ref scanBaseRef, x); - pixel.ToRgba32(ref rgba); - this.QuantizePixel(ref rgba); - } - } - + this.Build3DHistogram(source, width, height); this.Get3DMoments(source.MemoryAllocator); this.BuildCube(); } @@ -456,6 +425,54 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } } + /// + /// Builds a 3-D color histogram of counts, r/g/b, c^2. + /// + /// The source data. + /// The width in pixels of the image. + /// The height in pixels of the image. + private void Build3DHistogram(ImageFrame source, int width, int height) + { + Span vwtSpan = this.vwt.GetSpan(); + Span vmrSpan = this.vmr.GetSpan(); + Span vmgSpan = this.vmg.GetSpan(); + Span vmbSpan = this.vmb.GetSpan(); + Span vmaSpan = this.vma.GetSpan(); + Span m2Span = this.m2.GetSpan(); + + // Build up the 3-D color histogram + // Loop through each row + for (int y = 0; y < height; y++) + { + Span row = source.GetPixelRowSpan(y); + ref TPixel scanBaseRef = ref MemoryMarshal.GetReference(row); + + // And loop through each column + Rgba32 rgba = default; + for (int x = 0; x < width; x++) + { + ref TPixel pixel = ref Unsafe.Add(ref scanBaseRef, x); + pixel.ToRgba32(ref rgba); + + int r = rgba.R >> (8 - IndexBits); + int g = rgba.G >> (8 - IndexBits); + int b = rgba.B >> (8 - IndexBits); + int a = rgba.A >> (8 - IndexAlphaBits); + + int index = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); + + vwtSpan[index]++; + vmrSpan[index] += rgba.R; + vmgSpan[index] += rgba.G; + vmbSpan[index] += rgba.B; + vmaSpan[index] += rgba.A; + + var vector = new Vector4(rgba.R, rgba.G, rgba.B, rgba.A); + m2Span[index] += Vector4.Dot(vector, vector); + } + } + } + /// /// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box. /// @@ -621,22 +638,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The . private float Maximize(ref Box cube, int direction, int first, int last, out int cut, float wholeR, float wholeG, float wholeB, float wholeA, float wholeW) { - long baseR = Bottom(ref cube, direction, this.vmr.GetSpan()); - long baseG = Bottom(ref cube, direction, this.vmg.GetSpan()); - long baseB = Bottom(ref cube, direction, this.vmb.GetSpan()); - long baseA = Bottom(ref cube, direction, this.vma.GetSpan()); - long baseW = Bottom(ref cube, direction, this.vwt.GetSpan()); + Span vwtSpan = this.vwt.GetSpan(); + Span vmrSpan = this.vmr.GetSpan(); + Span vmgSpan = this.vmg.GetSpan(); + Span vmbSpan = this.vmb.GetSpan(); + Span vmaSpan = this.vma.GetSpan(); + + long baseR = Bottom(ref cube, direction, vmrSpan); + long baseG = Bottom(ref cube, direction, vmgSpan); + long baseB = Bottom(ref cube, direction, vmbSpan); + long baseA = Bottom(ref cube, direction, vmaSpan); + long baseW = Bottom(ref cube, direction, vwtSpan); float max = 0F; cut = -1; for (int i = first; i < last; i++) { - float halfR = baseR + Top(ref cube, direction, i, this.vmr.GetSpan()); - float halfG = baseG + Top(ref cube, direction, i, this.vmg.GetSpan()); - float halfB = baseB + Top(ref cube, direction, i, this.vmb.GetSpan()); - float halfA = baseA + Top(ref cube, direction, i, this.vma.GetSpan()); - float halfW = baseW + Top(ref cube, direction, i, this.vwt.GetSpan()); + float halfR = baseR + Top(ref cube, direction, i, vmrSpan); + float halfG = baseG + Top(ref cube, direction, i, vmgSpan); + float halfB = baseB + Top(ref cube, direction, i, vmbSpan); + float halfA = baseA + Top(ref cube, direction, i, vmaSpan); + float halfW = baseW + Top(ref cube, direction, i, vwtSpan); if (MathF.Abs(halfW) < Constants.Epsilon) { diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs index 3aa1f4c5e6..5123e737d3 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs @@ -14,6 +14,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class WuQuantizer : IQuantizer { + /// + /// The default maximum number of colors to use when quantizing the image. + /// + public const int DefaultMaxColors = 256; + /// /// Initializes a new instance of the class. /// @@ -36,7 +41,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Whether to apply dithering to the output image public WuQuantizer(bool dither) - : this(GetDiffuser(dither), 255) + : this(GetDiffuser(dither), DefaultMaxColors) { } @@ -45,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The error diffusion algorithm, if any, to apply to the output image public WuQuantizer(IErrorDiffuser diffuser) - : this(diffuser, 255) + : this(diffuser, DefaultMaxColors) { } @@ -56,10 +61,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The maximum number of colors to hold in the color palette public WuQuantizer(IErrorDiffuser diffuser, int maxColors) { - Guard.MustBeBetweenOrEqualTo(maxColors, 1, 255, nameof(maxColors)); - this.Diffuser = diffuser; - this.MaxColors = maxColors; + this.MaxColors = maxColors.Clamp(1, DefaultMaxColors); } /// @@ -75,6 +78,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization where TPixel : struct, IPixel => new WuFrameQuantizer(this); + /// + public IFrameQuantizer CreateFrameQuantizer(int maxColors) + where TPixel : struct, IPixel + { + maxColors = maxColors.Clamp(1, DefaultMaxColors); + return new WuFrameQuantizer(this, maxColors); + } + private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null; } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs index 3993ab1a8d..790eb80482 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs @@ -7,9 +7,9 @@ using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; using SixLabors.Primitives; @@ -51,10 +51,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { // We will always be creating the clone even for mutate because we may need to resize the canvas IEnumerable> frames = - source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.TargetDimensions, x.MetaData.Clone())); + source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.TargetDimensions, x.MetaData.DeepClone())); // Use the overload to prevent an extra frame being added - return new Image(source.GetConfiguration(), source.MetaData.Clone(), frames); + return new Image(source.GetConfiguration(), source.MetaData.DeepClone(), frames); } /// @@ -78,23 +78,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.Sampler is NearestNeighborResampler) { - ParallelFor.WithConfiguration( - 0, - height, + ParallelHelper.IterateRows( + targetBounds, configuration, - y => - { - Span destRow = destination.GetPixelRowSpan(y); - - for (int x = 0; x < width; x++) + rows => { - var point = Point.Transform(new Point(x, y), matrix); - if (sourceBounds.Contains(point.X, point.Y)) + for (int y = rows.Min; y < rows.Max; y++) { - destRow[x] = source[point.X, point.Y]; + Span destRow = destination.GetPixelRowSpan(y); + + for (int x = 0; x < width; x++) + { + var point = Point.Transform(new Point(x, y), matrix); + if (sourceBounds.Contains(point.X, point.Y)) + { + destRow[x] = source[point.X, point.Y]; + } + } } - } - }); + }); return; } @@ -116,86 +118,108 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms using (Buffer2D yBuffer = memoryAllocator.Allocate2D(yLength, height)) using (Buffer2D xBuffer = memoryAllocator.Allocate2D(xLength, height)) { - ParallelFor.WithConfiguration( - 0, - height, + ParallelHelper.IterateRows( + targetBounds, configuration, - y => + rows => { - ref TPixel destRowRef = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y)); - ref float ySpanRef = ref MemoryMarshal.GetReference(yBuffer.GetRowSpan(y)); - ref float xSpanRef = ref MemoryMarshal.GetReference(xBuffer.GetRowSpan(y)); - - for (int x = 0; x < width; x++) + for (int y = rows.Min; y < rows.Max; y++) { - // Use the single precision position to calculate correct bounding pixels - // otherwise we get rogue pixels outside of the bounds. - var point = Vector2.Transform(new Vector2(x, y), matrix); - - // Clamp sampling pixel radial extents to the source image edges - Vector2 maxXY = point + radius; - Vector2 minXY = point - radius; - - // max, maxY, minX, minY - var extents = new Vector4( - MathF.Floor(maxXY.X + .5F), - MathF.Floor(maxXY.Y + .5F), - MathF.Ceiling(minXY.X - .5F), - MathF.Ceiling(minXY.Y - .5F)); - - int right = (int)extents.X; - int bottom = (int)extents.Y; - int left = (int)extents.Z; - int top = (int)extents.W; - - extents = Vector4.Clamp(extents, Vector4.Zero, maxSource); - - int maxX = (int)extents.X; - int maxY = (int)extents.Y; - int minX = (int)extents.Z; - int minY = (int)extents.W; - - if (minX == maxX || minY == maxY) - { - continue; - } + ref TPixel destRowRef = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y)); + ref float ySpanRef = ref MemoryMarshal.GetReference(yBuffer.GetRowSpan(y)); + ref float xSpanRef = ref MemoryMarshal.GetReference(xBuffer.GetRowSpan(y)); - // It appears these have to be calculated on-the-fly. - // Precalulating transformed weights would require prior knowledge of every transformed pixel location - // since they can be at sub-pixel positions on both axis. - // I've optimized where I can but am always open to suggestions. - if (yScale > 1 && xScale > 1) - { - CalculateWeightsDown(top, bottom, minY, maxY, point.Y, sampler, yScale, ref ySpanRef, yLength); - CalculateWeightsDown(left, right, minX, maxX, point.X, sampler, xScale, ref xSpanRef, xLength); - } - else + for (int x = 0; x < width; x++) { - CalculateWeightsScaleUp(minY, maxY, point.Y, sampler, ref ySpanRef); - CalculateWeightsScaleUp(minX, maxX, point.X, sampler, ref xSpanRef); - } + // Use the single precision position to calculate correct bounding pixels + // otherwise we get rogue pixels outside of the bounds. + var point = Vector2.Transform(new Vector2(x, y), matrix); + + // Clamp sampling pixel radial extents to the source image edges + Vector2 maxXY = point + radius; + Vector2 minXY = point - radius; + + // max, maxY, minX, minY + var extents = new Vector4( + MathF.Floor(maxXY.X + .5F), + MathF.Floor(maxXY.Y + .5F), + MathF.Ceiling(minXY.X - .5F), + MathF.Ceiling(minXY.Y - .5F)); + + int right = (int)extents.X; + int bottom = (int)extents.Y; + int left = (int)extents.Z; + int top = (int)extents.W; + + extents = Vector4.Clamp(extents, Vector4.Zero, maxSource); + + int maxX = (int)extents.X; + int maxY = (int)extents.Y; + int minX = (int)extents.Z; + int minY = (int)extents.W; + + if (minX == maxX || minY == maxY) + { + continue; + } - // Now multiply the results against the offsets - Vector4 sum = Vector4.Zero; - for (int yy = 0, j = minY; j <= maxY; j++, yy++) - { - float yWeight = Unsafe.Add(ref ySpanRef, yy); + // It appears these have to be calculated on-the-fly. + // Precalculating transformed weights would require prior knowledge of every transformed pixel location + // since they can be at sub-pixel positions on both axis. + // I've optimized where I can but am always open to suggestions. + if (yScale > 1 && xScale > 1) + { + CalculateWeightsDown( + top, + bottom, + minY, + maxY, + point.Y, + sampler, + yScale, + ref ySpanRef, + yLength); + + CalculateWeightsDown( + left, + right, + minX, + maxX, + point.X, + sampler, + xScale, + ref xSpanRef, + xLength); + } + else + { + CalculateWeightsScaleUp(minY, maxY, point.Y, sampler, ref ySpanRef); + CalculateWeightsScaleUp(minX, maxX, point.X, sampler, ref xSpanRef); + } - for (int xx = 0, i = minX; i <= maxX; i++, xx++) + // Now multiply the results against the offsets + Vector4 sum = Vector4.Zero; + for (int yy = 0, j = minY; j <= maxY; j++, yy++) { - float xWeight = Unsafe.Add(ref xSpanRef, xx); - var vector = source[i, j].ToVector4(); + float yWeight = Unsafe.Add(ref ySpanRef, yy); + + for (int xx = 0, i = minX; i <= maxX; i++, xx++) + { + float xWeight = Unsafe.Add(ref xSpanRef, xx); - // Values are first premultiplied to prevent darkening of edge pixels - Vector4 multiplied = vector.Premultiply(); - sum += multiplied * xWeight * yWeight; + // Values are first premultiplied to prevent darkening of edge pixels + var current = source[i, j].ToVector4(); + Vector4Utils.Premultiply(ref current); + sum += current * xWeight * yWeight; + } } - } - ref TPixel dest = ref Unsafe.Add(ref destRowRef, x); + ref TPixel dest = ref Unsafe.Add(ref destRowRef, x); - // Reverse the premultiplication - dest.PackFromVector4(sum.UnPremultiply()); + // Reverse the premultiplication + Vector4Utils.UnPremultiply(ref sum); + dest.PackFromVector4(sum); + } } }); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs index 0c52123755..3b1d7e94dd 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs @@ -4,8 +4,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; @@ -22,8 +22,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Initializes a new instance of the class. /// /// The target cropped rectangle. - public CropProcessor(Rectangle cropRectangle) + /// 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."); this.CropRectangle = cropRectangle; } @@ -36,10 +39,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms protected override Image CreateDestination(Image source, Rectangle sourceRectangle) { // We will always be creating the clone even for mutate because we may need to resize the canvas - IEnumerable> frames = source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.CropRectangle.Width, this.CropRectangle.Height, x.MetaData.Clone())); + IEnumerable> frames = source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.CropRectangle.Width, this.CropRectangle.Height, x.MetaData.DeepClone())); // Use the overload to prevent an extra frame being added - return new Image(source.GetConfiguration(), source.MetaData.Clone(), frames); + return new Image(source.GetConfiguration(), source.MetaData.DeepClone(), frames); } /// @@ -53,21 +56,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return; } - int minY = Math.Max(this.CropRectangle.Y, sourceRectangle.Y); - int maxY = Math.Min(this.CropRectangle.Bottom, sourceRectangle.Bottom); - int minX = Math.Max(this.CropRectangle.X, sourceRectangle.X); - int maxX = Math.Min(this.CropRectangle.Right, sourceRectangle.Right); + Rectangle rect = this.CropRectangle; - ParallelFor.WithConfiguration( - minY, - maxY, - configuration, - y => - { - Span sourceRow = source.GetPixelRowSpan(y).Slice(minX); - Span targetRow = destination.GetPixelRowSpan(y - minY); - sourceRow.Slice(0, maxX - minX).CopyTo(targetRow); - }); + // Copying is cheap, we should process more pixels per task: + ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings().MultiplyMinimumPixelsPerTask(4); + + ParallelHelper.IterateRows( + rect, + parallelSettings, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = source.GetPixelRowSpan(y).Slice(rect.Left); + Span targetRow = destination.GetPixelRowSpan(y - rect.Top); + sourceRow.Slice(0, rect.Width).CopyTo(targetRow); + } + }); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs index 8eeae5d1fc..6de717afd9 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0); } - new CropProcessor(rectangle).Apply(source, sourceRectangle); + new CropProcessor(rectangle, source.Size()).Apply(source, sourceRectangle); } /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs index cea6df391f..c6f5e9d7b8 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs @@ -2,9 +2,11 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; using SixLabors.Primitives; @@ -55,27 +57,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private void FlipX(ImageFrame source, Configuration configuration) { int height = source.Height; - int halfHeight = (int)Math.Ceiling(source.Height * .5F); - using (Buffer2D targetPixels = configuration.MemoryAllocator.Allocate2D(source.Size())) + using (IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(source.Width)) { - ParallelFor.WithConfiguration( - 0, - halfHeight, - configuration, - y => - { - int newY = height - y - 1; - Span sourceRow = source.GetPixelRowSpan(y); - Span altSourceRow = source.GetPixelRowSpan(newY); - Span targetRow = targetPixels.GetRowSpan(y); - Span altTargetRow = targetPixels.GetRowSpan(newY); - - sourceRow.CopyTo(altTargetRow); - altSourceRow.CopyTo(targetRow); - }); + Span temp = tempBuffer.Memory.Span; - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + for (int yTop = 0; yTop < height / 2; yTop++) + { + int yBottom = height - yTop - 1; + Span topRow = source.GetPixelRowSpan(yBottom); + Span bottomRow = source.GetPixelRowSpan(yTop); + topRow.CopyTo(temp); + bottomRow.CopyTo(topRow); + temp.CopyTo(bottomRow); + } } } @@ -86,31 +81,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void FlipY(ImageFrame source, Configuration configuration) { - int width = source.Width; - int height = source.Height; - int halfWidth = (int)Math.Ceiling(width * .5F); - - using (Buffer2D targetPixels = configuration.MemoryAllocator.Allocate2D(source.Size())) - { - ParallelFor.WithConfiguration( - 0, - height, - configuration, - y => + ParallelHelper.IterateRows( + source.Bounds(), + configuration, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) { - Span sourceRow = source.GetPixelRowSpan(y); - Span targetRow = targetPixels.GetRowSpan(y); - - for (int x = 0; x < halfWidth; x++) - { - int newX = width - x - 1; - targetRow[x] = sourceRow[newX]; - targetRow[newX] = sourceRow[x]; - } - }); - - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); - } + source.GetPixelRowSpan(y).Reverse(); + } + }); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/KernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/KernelMap.cs new file mode 100644 index 0000000000..277be53fff --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/KernelMap.cs @@ -0,0 +1,130 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +using SixLabors.ImageSharp.Memory; +using SixLabors.Memory; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Holds the values in an optimized contigous memory region. + /// + internal class KernelMap : IDisposable + { + private readonly Buffer2D data; + + /// + /// Initializes a new instance of the class. + /// + /// The to use for allocations. + /// The size of the destination window + /// The radius of the kernel + public KernelMap(MemoryAllocator memoryAllocator, int destinationSize, float kernelRadius) + { + int width = (int)Math.Ceiling(kernelRadius * 2); + this.data = memoryAllocator.Allocate2D(width, destinationSize, AllocationOptions.Clean); + this.Kernels = new ResizeKernel[destinationSize]; + } + + /// + /// Gets the calculated values. + /// + public ResizeKernel[] Kernels { get; } + + /// + /// Disposes instance releasing it's backing buffer. + /// + public void Dispose() + { + this.data.Dispose(); + } + + /// + /// Computes the weights to apply at each pixel when resizing. + /// + /// The + /// The destination size + /// The source size + /// The to use for buffer allocations + /// The + public static KernelMap Calculate( + IResampler sampler, + int destinationSize, + int sourceSize, + MemoryAllocator memoryAllocator) + { + float ratio = (float)sourceSize / destinationSize; + float scale = ratio; + + if (scale < 1F) + { + scale = 1F; + } + + float radius = MathF.Ceiling(scale * sampler.Radius); + var result = new KernelMap(memoryAllocator, destinationSize, radius); + + for (int i = 0; i < destinationSize; i++) + { + float center = ((i + .5F) * ratio) - .5F; + + // Keep inside bounds. + int left = (int)MathF.Ceiling(center - radius); + if (left < 0) + { + left = 0; + } + + int right = (int)MathF.Floor(center + radius); + if (right > sourceSize - 1) + { + right = sourceSize - 1; + } + + float sum = 0; + + ResizeKernel ws = result.CreateKernel(i, left, right); + result.Kernels[i] = ws; + + ref float weightsBaseRef = ref ws.GetStartReference(); + + for (int j = left; j <= right; j++) + { + float weight = sampler.GetValue((j - center) / scale); + sum += weight; + + // weights[j - left] = weight: + Unsafe.Add(ref weightsBaseRef, j - left) = weight; + } + + // Normalize, best to do it here rather than in the pixel loop later on. + if (sum > 0) + { + for (int w = 0; w < ws.Length; w++) + { + // weights[w] = weights[w] / sum: + ref float wRef = ref Unsafe.Add(ref weightsBaseRef, w); + wRef /= sum; + } + } + } + + return result; + } + + /// + /// Slices a weights value at the given positions. + /// + /// The index in destination buffer + /// The local left index value + /// The local right index value + /// The weights + private ResizeKernel CreateKernel(int destIdx, int leftIdx, int rightIdx) + { + return new ResizeKernel(destIdx, leftIdx, this.data, rightIdx - leftIdx + 1); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs index 042ce2ff6d..bad8eab3af 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs @@ -7,9 +7,9 @@ using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; using SixLabors.Primitives; @@ -51,10 +51,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { // We will always be creating the clone even for mutate because we may need to resize the canvas IEnumerable> frames = - source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.TargetDimensions.Width, this.TargetDimensions.Height, x.MetaData.Clone())); + source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.TargetDimensions.Width, this.TargetDimensions.Height, x.MetaData.DeepClone())); // Use the overload to prevent an extra frame being added - return new Image(source.GetConfiguration(), source.MetaData.Clone(), frames); + return new Image(source.GetConfiguration(), source.MetaData.DeepClone(), frames); } /// @@ -75,28 +75,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.Sampler is NearestNeighborResampler) { - ParallelFor.WithConfiguration( - 0, - height, + ParallelHelper.IterateRows( + targetBounds, configuration, - y => - { - Span destRow = destination.GetPixelRowSpan(y); - - for (int x = 0; x < width; x++) + rows => { - var v3 = Vector3.Transform(new Vector3(x, y, 1), matrix); + for (int y = rows.Min; y < rows.Max; y++) + { + Span destRow = destination.GetPixelRowSpan(y); - float z = MathF.Max(v3.Z, Epsilon); - int px = (int)MathF.Round(v3.X / z); - int py = (int)MathF.Round(v3.Y / z); + for (int x = 0; x < width; x++) + { + var v3 = Vector3.Transform(new Vector3(x, y, 1), matrix); - if (sourceBounds.Contains(px, py)) - { - destRow[x] = source[px, py]; + float z = MathF.Max(v3.Z, Epsilon); + int px = (int)MathF.Round(v3.X / z); + int py = (int)MathF.Round(v3.Y / z); + + if (sourceBounds.Contains(px, py)) + { + destRow[x] = source[px, py]; + } + } } - } - }); + }); return; } @@ -121,92 +123,114 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms using (Buffer2D yBuffer = memoryAllocator.Allocate2D(yLength, height)) using (Buffer2D xBuffer = memoryAllocator.Allocate2D(xLength, height)) { - ParallelFor.WithConfiguration( - 0, - height, + ParallelHelper.IterateRows( + targetBounds, configuration, - y => - { - ref TPixel destRowRef = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y)); - ref float ySpanRef = ref MemoryMarshal.GetReference(yBuffer.GetRowSpan(y)); - ref float xSpanRef = ref MemoryMarshal.GetReference(xBuffer.GetRowSpan(y)); - - for (int x = 0; x < width; x++) + rows => { - // Use the single precision position to calculate correct bounding pixels - // otherwise we get rogue pixels outside of the bounds. - var v3 = Vector3.Transform(new Vector3(x, y, 1), matrix); - float z = MathF.Max(v3.Z, Epsilon); - - // Using Vector4 with dummy 0-s, because Vector2 SIMD implementation is not reliable: - Vector4 point = new Vector4(v3.X, v3.Y, 0, 0) / z; - - // Clamp sampling pixel radial extents to the source image edges - Vector4 maxXY = point + radius; - Vector4 minXY = point - radius; - - // max, maxY, minX, minY - var extents = new Vector4( - MathF.Floor(maxXY.X + .5F), - MathF.Floor(maxXY.Y + .5F), - MathF.Ceiling(minXY.X - .5F), - MathF.Ceiling(minXY.Y - .5F)); - - int right = (int)extents.X; - int bottom = (int)extents.Y; - int left = (int)extents.Z; - int top = (int)extents.W; - - extents = Vector4.Clamp(extents, Vector4.Zero, maxSource); - - int maxX = (int)extents.X; - int maxY = (int)extents.Y; - int minX = (int)extents.Z; - int minY = (int)extents.W; - - if (minX == maxX || minY == maxY) - { - continue; - } - - // It appears these have to be calculated on-the-fly. - // Precalulating transformed weights would require prior knowledge of every transformed pixel location - // since they can be at sub-pixel positions on both axis. - // I've optimized where I can but am always open to suggestions. - if (yScale > 1 && xScale > 1) + for (int y = rows.Min; y < rows.Max; y++) { - CalculateWeightsDown(top, bottom, minY, maxY, point.Y, sampler, yScale, ref ySpanRef, yLength); - CalculateWeightsDown(left, right, minX, maxX, point.X, sampler, xScale, ref xSpanRef, xLength); - } - else - { - CalculateWeightsScaleUp(minY, maxY, point.Y, sampler, ref ySpanRef); - CalculateWeightsScaleUp(minX, maxX, point.X, sampler, ref xSpanRef); - } - - // Now multiply the results against the offsets - Vector4 sum = Vector4.Zero; - for (int yy = 0, j = minY; j <= maxY; j++, yy++) - { - float yWeight = Unsafe.Add(ref ySpanRef, yy); + ref TPixel destRowRef = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y)); + ref float ySpanRef = ref MemoryMarshal.GetReference(yBuffer.GetRowSpan(y)); + ref float xSpanRef = ref MemoryMarshal.GetReference(xBuffer.GetRowSpan(y)); - for (int xx = 0, i = minX; i <= maxX; i++, xx++) + for (int x = 0; x < width; x++) { - float xWeight = Unsafe.Add(ref xSpanRef, xx); - var vector = source[i, j].ToVector4(); - - // Values are first premultiplied to prevent darkening of edge pixels - Vector4 multiplied = vector.Premultiply(); - sum += multiplied * xWeight * yWeight; + // Use the single precision position to calculate correct bounding pixels + // otherwise we get rogue pixels outside of the bounds. + var v3 = Vector3.Transform(new Vector3(x, y, 1), matrix); + float z = MathF.Max(v3.Z, Epsilon); + + // Using Vector4 with dummy 0-s, because Vector2 SIMD implementation is not reliable: + Vector4 point = new Vector4(v3.X, v3.Y, 0, 0) / z; + + // Clamp sampling pixel radial extents to the source image edges + Vector4 maxXY = point + radius; + Vector4 minXY = point - radius; + + // max, maxY, minX, minY + var extents = new Vector4( + MathF.Floor(maxXY.X + .5F), + MathF.Floor(maxXY.Y + .5F), + MathF.Ceiling(minXY.X - .5F), + MathF.Ceiling(minXY.Y - .5F)); + + int right = (int)extents.X; + int bottom = (int)extents.Y; + int left = (int)extents.Z; + int top = (int)extents.W; + + extents = Vector4.Clamp(extents, Vector4.Zero, maxSource); + + int maxX = (int)extents.X; + int maxY = (int)extents.Y; + int minX = (int)extents.Z; + int minY = (int)extents.W; + + if (minX == maxX || minY == maxY) + { + continue; + } + + // It appears these have to be calculated on-the-fly. + // Precalulating transformed weights would require prior knowledge of every transformed pixel location + // since they can be at sub-pixel positions on both axis. + // I've optimized where I can but am always open to suggestions. + if (yScale > 1 && xScale > 1) + { + CalculateWeightsDown( + top, + bottom, + minY, + maxY, + point.Y, + sampler, + yScale, + ref ySpanRef, + yLength); + + CalculateWeightsDown( + left, + right, + minX, + maxX, + point.X, + sampler, + xScale, + ref xSpanRef, + xLength); + } + else + { + CalculateWeightsScaleUp(minY, maxY, point.Y, sampler, ref ySpanRef); + CalculateWeightsScaleUp(minX, maxX, point.X, sampler, ref xSpanRef); + } + + // Now multiply the results against the offsets + Vector4 sum = Vector4.Zero; + for (int yy = 0, j = minY; j <= maxY; j++, yy++) + { + float yWeight = Unsafe.Add(ref ySpanRef, yy); + + for (int xx = 0, i = minX; i <= maxX; i++, xx++) + { + float xWeight = Unsafe.Add(ref xSpanRef, xx); + + // Values are first premultiplied to prevent darkening of edge pixels + var current = source[i, j].ToVector4(); + Vector4Utils.Premultiply(ref current); + sum += current * xWeight * yWeight; + } + } + + ref TPixel dest = ref Unsafe.Add(ref destRowRef, x); + + // Reverse the premultiplication + Vector4Utils.UnPremultiply(ref sum); + dest.PackFromVector4(sum); } } - - ref TPixel dest = ref Unsafe.Add(ref destRowRef, x); - - // Reverse the premultiplication - dest.PackFromVector4(sum.UnPremultiply()); - } - }); + }); } } @@ -218,9 +242,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The . /// - protected virtual Matrix4x4 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle) - { - return this.TransformMatrix; - } + protected virtual Matrix4x4 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle) => this.TransformMatrix; } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeKernel.cs new file mode 100644 index 0000000000..cc3c204534 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeKernel.cs @@ -0,0 +1,95 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SixLabors.ImageSharp.Memory; +using SixLabors.Memory; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Points to a collection of of weights allocated in . + /// + internal struct ResizeKernel + { + /// + /// The local left index position + /// + public int Left; + + /// + /// The length of the weights window + /// + public int Length; + + /// + /// The buffer containing the weights values. + /// + private readonly Memory buffer; + + /// + /// Initializes a new instance of the struct. + /// + /// The destination index in the buffer + /// The local left index + /// The span + /// The length of the window + [MethodImpl(InliningOptions.ShortMethod)] + internal ResizeKernel(int index, int left, Buffer2D buffer, int length) + { + int flatStartIndex = index * buffer.Width; + this.Left = left; + this.buffer = buffer.MemorySource.Memory.Slice(flatStartIndex, length); + this.Length = length; + } + + /// + /// Gets a reference to the first item of the window. + /// + /// The reference to the first item of the window + [MethodImpl(InliningOptions.ShortMethod)] + public ref float GetStartReference() + { + Span span = this.buffer.Span; + return ref span[0]; + } + + /// + /// Gets the span representing the portion of the that this window covers + /// + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public Span GetSpan() => this.buffer.Span; + + /// + /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance. + /// + /// The input span of vectors + /// The source row position. + /// The weighted sum + [MethodImpl(InliningOptions.ShortMethod)] + public Vector4 Convolve(Span rowSpan, int sourceX) + { + ref float horizontalValues = ref this.GetStartReference(); + int left = this.Left; + ref Vector4 vecPtr = ref Unsafe.Add(ref MemoryMarshal.GetReference(rowSpan), left + sourceX); + + // Destination color components + Vector4 result = Vector4.Zero; + + for (int i = 0; i < this.Length; i++) + { + float weight = Unsafe.Add(ref horizontalValues, i); + Vector4 v = Unsafe.Add(ref vecPtr, i); + result += v * weight; + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index fd3c34d6c1..812c0578b2 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -2,15 +2,16 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading.Tasks; + using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.ColorSpaces.Companding; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; using SixLabors.Primitives; @@ -26,8 +27,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms where TPixel : struct, IPixel { // The following fields are not immutable but are optionally created on demand. - private WeightsBuffer horizontalWeights; - private WeightsBuffer verticalWeights; + private KernelMap horizontalKernelMap; + private KernelMap verticalKernelMap; /// /// Initializes a new instance of the class. @@ -39,25 +40,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Guard.NotNull(options, nameof(options)); Guard.NotNull(options.Sampler, nameof(options.Sampler)); - int tempWidth = options.Size.Width; - int tempHeight = options.Size.Height; + int targetWidth = options.Size.Width; + int targetHeight = options.Size.Height; // Ensure size is populated across both dimensions. // These dimensions are used to calculate the final dimensions determined by the mode algorithm. - if (tempWidth == 0 && tempHeight > 0) + // 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 (targetWidth == 0 && targetHeight > 0) { - tempWidth = (int)MathF.Round(sourceSize.Width * tempHeight / (float)sourceSize.Height); + targetWidth = (int)MathF.Max(min, MathF.Round(sourceSize.Width * targetHeight / (float)sourceSize.Height)); } - if (tempHeight == 0 && tempWidth > 0) + if (targetHeight == 0 && targetWidth > 0) { - tempHeight = (int)MathF.Round(sourceSize.Height * tempWidth / (float)sourceSize.Width); + targetHeight = (int)MathF.Max(min, MathF.Round(sourceSize.Height * targetWidth / (float)sourceSize.Width)); } - Guard.MustBeGreaterThan(tempWidth, 0, nameof(tempWidth)); - Guard.MustBeGreaterThan(tempHeight, 0, nameof(tempHeight)); + Guard.MustBeGreaterThan(targetWidth, 0, nameof(targetWidth)); + Guard.MustBeGreaterThan(targetHeight, 0, nameof(targetHeight)); - (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options, tempWidth, tempHeight); + (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options, targetWidth, targetHeight); this.Sampler = options.Sampler; this.Width = size.Width; @@ -94,15 +98,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Guard.NotNull(sampler, nameof(sampler)); // Ensure size is populated across both dimensions. + // 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.Round(sourceSize.Width * height / (float)sourceSize.Height); + width = (int)MathF.Max(min, MathF.Round(sourceSize.Width * height / (float)sourceSize.Height)); resizeRectangle.Width = width; } if (height == 0 && width > 0) { - height = (int)MathF.Round(sourceSize.Height * width / (float)sourceSize.Width); + height = (int)MathF.Max(min, MathF.Round(sourceSize.Height * width / (float)sourceSize.Width)); resizeRectangle.Height = height; } @@ -141,84 +148,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// public bool Compand { get; } - /// - /// Computes the weights to apply at each pixel when resizing. - /// - /// The to use for buffer allocations - /// The destination size - /// The source size - /// The - // TODO: Made internal to simplify experimenting with weights data. Make it private when finished figuring out how to optimize all the stuff! - internal WeightsBuffer PrecomputeWeights(MemoryAllocator memoryAllocator, int destinationSize, int sourceSize) - { - float ratio = (float)sourceSize / destinationSize; - float scale = ratio; - - if (scale < 1F) - { - scale = 1F; - } - - IResampler sampler = this.Sampler; - float radius = MathF.Ceiling(scale * sampler.Radius); - var result = new WeightsBuffer(memoryAllocator, sourceSize, destinationSize); - - for (int i = 0; i < destinationSize; i++) - { - float center = ((i + .5F) * ratio) - .5F; - - // Keep inside bounds. - int left = (int)MathF.Ceiling(center - radius); - if (left < 0) - { - left = 0; - } - - int right = (int)MathF.Floor(center + radius); - if (right > sourceSize - 1) - { - right = sourceSize - 1; - } - - float sum = 0; - - WeightsWindow ws = result.GetWeightsWindow(i, left, right); - result.Weights[i] = ws; - - ref float weightsBaseRef = ref ws.GetStartReference(); - - for (int j = left; j <= right; j++) - { - float weight = sampler.GetValue((j - center) / scale); - sum += weight; - - // weights[j - left] = weight: - Unsafe.Add(ref weightsBaseRef, j - left) = weight; - } - - // Normalize, best to do it here rather than in the pixel loop later on. - if (sum > 0) - { - for (int w = 0; w < ws.Length; w++) - { - // weights[w] = weights[w] / sum: - ref float wRef = ref Unsafe.Add(ref weightsBaseRef, w); - wRef /= sum; - } - } - } - - return result; - } - /// protected override Image CreateDestination(Image source, Rectangle sourceRectangle) { // We will always be creating the clone even for mutate because we may need to resize the canvas - IEnumerable> frames = source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.Width, this.Height, x.MetaData.Clone())); + IEnumerable> frames = source.Frames.Select(x => new ImageFrame(source.GetConfiguration(), this.Width, this.Height, x.MetaData.DeepClone())); // Use the overload to prevent an extra frame being added - return new Image(source.GetConfiguration(), source.MetaData.Clone(), frames); + return new Image(source.GetConfiguration(), source.MetaData.DeepClone(), frames); } /// @@ -228,15 +165,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { // Since all image frame dimensions have to be the same we can calculate this for all frames. MemoryAllocator memoryAllocator = source.GetMemoryAllocator(); - this.horizontalWeights = this.PrecomputeWeights( - memoryAllocator, + this.horizontalKernelMap = KernelMap.Calculate( + this.Sampler, this.ResizeRectangle.Width, - sourceRectangle.Width); + sourceRectangle.Width, + memoryAllocator); - this.verticalWeights = this.PrecomputeWeights( - memoryAllocator, + this.verticalKernelMap = KernelMap.Calculate( + this.Sampler, this.ResizeRectangle.Height, - sourceRectangle.Height); + sourceRectangle.Height, + memoryAllocator); } } @@ -267,105 +206,112 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.Sampler is NearestNeighborResampler) { + var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + // Scaling factors float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; - ParallelFor.WithConfiguration( - minY, - maxY, + ParallelHelper.IterateRows( + workingRect, configuration, - y => - { - // Y coordinates of source points - Span sourceRow = source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY)); - Span targetRow = destination.GetPixelRowSpan(y); - - for (int x = minX; x < maxX; x++) + rows => { - // X coordinates of source points - targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; - } - }); + for (int y = rows.Min; y < rows.Max; y++) + { + // Y coordinates of source points + Span sourceRow = + source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY)); + Span targetRow = destination.GetPixelRowSpan(y); + + for (int x = minX; x < maxX; x++) + { + // X coordinates of source points + targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; + } + } + }); return; } + int sourceHeight = source.Height; + // Interpolate the image using the calculated weights. // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm // First process the columns. Since we are not using multiple threads startY and endY // are the upper and lower bounds of the source rectangle. - // TODO: Using a transposed variant of 'firstPassPixels' could eliminate the need for the WeightsWindow.ComputeWeightedColumnSum() method, and improve speed! - using (Buffer2D firstPassPixels = source.MemoryAllocator.Allocate2D(width, source.Height)) + using (Buffer2D firstPassPixelsTransposed = source.MemoryAllocator.Allocate2D(sourceHeight, width)) { - firstPassPixels.MemorySource.Clear(); + firstPassPixelsTransposed.MemorySource.Clear(); - ParallelFor.WithTemporaryBuffer( - 0, - sourceRectangle.Bottom, + var processColsRect = new Rectangle(0, 0, source.Width, sourceRectangle.Bottom); + + ParallelHelper.IterateRowsWithTempBuffer( + processColsRect, configuration, - source.Width, - (int y, IMemoryOwner tempRowBuffer) => + (rows, tempRowBuffer) => { - ref Vector4 firstPassRow = ref MemoryMarshal.GetReference(firstPassPixels.GetRowSpan(y)); - Span sourceRow = source.GetPixelRowSpan(y); - Span tempRowSpan = tempRowBuffer.GetSpan(); + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = source.GetPixelRowSpan(y); + Span tempRowSpan = tempRowBuffer.Span; - PixelOperations.Instance.ToVector4(sourceRow, tempRowSpan, sourceRow.Length); + PixelOperations.Instance.ToVector4(sourceRow, tempRowSpan, sourceRow.Length); + Vector4Utils.Premultiply(tempRowSpan); - if (this.Compand) - { - for (int x = minX; x < maxX; x++) + ref Vector4 firstPassBaseRef = ref firstPassPixelsTransposed.Span[y]; + + if (this.Compand) { - WeightsWindow window = this.horizontalWeights.Weights[x - startX]; - Unsafe.Add(ref firstPassRow, x) = window.ComputeExpandedWeightedRowSum(tempRowSpan, sourceX); + SRgbCompanding.Expand(tempRowSpan); } - } - else - { + for (int x = minX; x < maxX; x++) { - WeightsWindow window = this.horizontalWeights.Weights[x - startX]; - Unsafe.Add(ref firstPassRow, x) = window.ComputeWeightedRowSum(tempRowSpan, sourceX); + ResizeKernel window = this.horizontalKernelMap.Kernels[x - startX]; + Unsafe.Add(ref firstPassBaseRef, x * sourceHeight) = + window.Convolve(tempRowSpan, sourceX); } } }); + var processRowsRect = Rectangle.FromLTRB(0, minY, width, maxY); + // Now process the rows. - ParallelFor.WithConfiguration( - minY, - maxY, + ParallelHelper.IterateRowsWithTempBuffer( + processRowsRect, configuration, - y => - { - // Ensure offsets are normalized for cropping and padding. - WeightsWindow window = this.verticalWeights.Weights[y - startY]; - ref TPixel targetRow = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y)); - - if (this.Compand) + (rows, tempRowBuffer) => { - for (int x = 0; x < width; x++) - { - // Destination color components - Vector4 destinationVector = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY); - destinationVector = destinationVector.Compress(); + Span tempRowSpan = tempRowBuffer.Span; - ref TPixel pixel = ref Unsafe.Add(ref targetRow, x); - pixel.PackFromVector4(destinationVector); - } - } - else - { - for (int x = 0; x < width; x++) + for (int y = rows.Min; y < rows.Max; y++) { - // Destination color components - Vector4 destinationVector = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY); + // Ensure offsets are normalized for cropping and padding. + ResizeKernel window = this.verticalKernelMap.Kernels[y - startY]; + + ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempRowSpan); + + for (int x = 0; x < width; x++) + { + Span firstPassColumn = firstPassPixelsTransposed.GetRowSpan(x); + + // Destination color components + Unsafe.Add(ref tempRowBase, x) = window.Convolve(firstPassColumn, sourceY); + } - ref TPixel pixel = ref Unsafe.Add(ref targetRow, x); - pixel.PackFromVector4(destinationVector); + Vector4Utils.UnPremultiply(tempRowSpan); + + if (this.Compand) + { + SRgbCompanding.Compress(tempRowSpan); + } + + Span targetRowSpan = destination.GetPixelRowSpan(y); + PixelOperations.Instance.PackFromVector4(tempRowSpan, targetRowSpan, tempRowSpan.Length); } - } - }); + }); } } @@ -374,10 +320,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms base.AfterImageApply(source, destination, sourceRectangle); // TODO: An exception in the processing chain can leave these buffers undisposed. We should consider making image processors IDisposable! - this.horizontalWeights?.Dispose(); - this.horizontalWeights = null; - this.verticalWeights?.Dispose(); - this.verticalWeights = null; + this.horizontalKernelMap?.Dispose(); + this.horizontalKernelMap = null; + this.verticalKernelMap?.Dispose(); + this.verticalKernelMap = null; } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs index 93c847d598..2ad626755c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs @@ -5,6 +5,7 @@ using System; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.MetaData.Profiles.Exif; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.Primitives; @@ -147,25 +148,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int height = source.Height; Rectangle destinationBounds = destination.Bounds(); - ParallelFor.WithConfiguration( - 0, - height, + ParallelHelper.IterateRows( + source.Bounds(), configuration, - y => - { - Span sourceRow = source.GetPixelRowSpan(y); - for (int x = 0; x < width; x++) + rows => { - int newX = height - y - 1; - newX = height - newX - 1; - int newY = width - x - 1; - - if (destinationBounds.Contains(newX, newY)) + for (int y = rows.Min; y < rows.Max; y++) { - destination[newX, newY] = sourceRow[x]; + Span sourceRow = source.GetPixelRowSpan(y); + for (int x = 0; x < width; x++) + { + int newX = height - y - 1; + newX = height - newX - 1; + int newY = width - x - 1; + + if (destinationBounds.Contains(newX, newY)) + { + destination[newX, newY] = sourceRow[x]; + } + } } - } - }); + }); } /// @@ -179,20 +182,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int width = source.Width; int height = source.Height; - ParallelFor.WithConfiguration( - 0, - height, + ParallelHelper.IterateRows( + source.Bounds(), configuration, - y => - { - Span sourceRow = source.GetPixelRowSpan(y); - Span targetRow = destination.GetPixelRowSpan(height - y - 1); - - for (int x = 0; x < width; x++) + rows => { - targetRow[width - x - 1] = sourceRow[x]; - } - }); + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = source.GetPixelRowSpan(y); + Span targetRow = destination.GetPixelRowSpan(height - y - 1); + + for (int x = 0; x < width; x++) + { + targetRow[width - x - 1] = sourceRow[x]; + } + } + }); } /// @@ -207,22 +212,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int height = source.Height; Rectangle destinationBounds = destination.Bounds(); - ParallelFor.WithConfiguration( - 0, - height, + ParallelHelper.IterateRows( + source.Bounds(), configuration, - y => - { - Span sourceRow = source.GetPixelRowSpan(y); - int newX = height - y - 1; - for (int x = 0; x < width; x++) + rows => { - if (destinationBounds.Contains(newX, x)) + for (int y = rows.Min; y < rows.Max; y++) { - destination[newX, x] = sourceRow[x]; + Span sourceRow = source.GetPixelRowSpan(y); + int newX = height - y - 1; + for (int x = 0; x < width; x++) + { + // TODO: Optimize this: + if (destinationBounds.Contains(newX, x)) + { + destination[newX, x] = sourceRow[x]; + } + } } - } - }); + }); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs b/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs deleted file mode 100644 index 68133a5489..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.Memory; -using SixLabors.Memory; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// Holds the values in an optimized contigous memory region. - /// - internal class WeightsBuffer : IDisposable - { - private readonly Buffer2D dataBuffer; - - /// - /// Initializes a new instance of the class. - /// - /// The to use for allocations. - /// The size of the source window - /// The size of the destination window - public WeightsBuffer(MemoryAllocator memoryAllocator, int sourceSize, int destinationSize) - { - this.dataBuffer = memoryAllocator.Allocate2D(sourceSize, destinationSize, AllocationOptions.Clean); - this.Weights = new WeightsWindow[destinationSize]; - } - - /// - /// Gets the calculated values. - /// - public WeightsWindow[] Weights { get; } - - /// - /// Disposes instance releasing it's backing buffer. - /// - public void Dispose() - { - this.dataBuffer.Dispose(); - } - - /// - /// Slices a weights value at the given positions. - /// - /// The index in destination buffer - /// The local left index value - /// The local right index value - /// The weights - public WeightsWindow GetWeightsWindow(int destIdx, int leftIdx, int rightIdx) - { - return new WeightsWindow(destIdx, leftIdx, this.dataBuffer, rightIdx - leftIdx + 1); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs b/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs deleted file mode 100644 index 01cf97e591..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -using SixLabors.ImageSharp.Memory; -using SixLabors.Memory; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// Points to a collection of of weights allocated in . - /// - internal struct WeightsWindow - { - /// - /// The local left index position - /// - public int Left; - - /// - /// The length of the weights window - /// - public int Length; - - /// - /// The index in the destination buffer - /// - private readonly int flatStartIndex; - - /// - /// The buffer containing the weights values. - /// - private readonly MemorySource buffer; - - /// - /// Initializes a new instance of the struct. - /// - /// The destination index in the buffer - /// The local left index - /// The span - /// The length of the window - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal WeightsWindow(int index, int left, Buffer2D buffer, int length) - { - this.flatStartIndex = (index * buffer.Width) + left; - this.Left = left; - this.buffer = buffer.MemorySource; - this.Length = length; - } - - /// - /// Gets a reference to the first item of the window. - /// - /// The reference to the first item of the window - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref float GetStartReference() - { - Span span = this.buffer.GetSpan(); - return ref span[this.flatStartIndex]; - } - - /// - /// Gets the span representing the portion of the that this window covers - /// - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetWindowSpan() => this.buffer.GetSpan().Slice(this.flatStartIndex, this.Length); - - /// - /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance. - /// - /// The input span of vectors - /// The source row position. - /// The weighted sum - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ComputeWeightedRowSum(Span rowSpan, int sourceX) - { - ref float horizontalValues = ref this.GetStartReference(); - int left = this.Left; - ref Vector4 vecPtr = ref Unsafe.Add(ref MemoryMarshal.GetReference(rowSpan), left + sourceX); - - // Destination color components - Vector4 result = Vector4.Zero; - - for (int i = 0; i < this.Length; i++) - { - float weight = Unsafe.Add(ref horizontalValues, i); - Vector4 v = Unsafe.Add(ref vecPtr, i); - result += v.Premultiply() * weight; - } - - return result; - } - - /// - /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance. - /// Applies to all input vectors. - /// - /// The input span of vectors - /// The source row position. - /// The weighted sum - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ComputeExpandedWeightedRowSum(Span rowSpan, int sourceX) - { - ref float horizontalValues = ref this.GetStartReference(); - int left = this.Left; - ref Vector4 vecPtr = ref Unsafe.Add(ref MemoryMarshal.GetReference(rowSpan), left + sourceX); - - // Destination color components - Vector4 result = Vector4.Zero; - - for (int i = 0; i < this.Length; i++) - { - float weight = Unsafe.Add(ref horizontalValues, i); - Vector4 v = Unsafe.Add(ref vecPtr, i); - result += v.Premultiply().Expand() * weight; - } - - return result.UnPremultiply(); - } - - /// - /// Computes the sum of vectors in 'firstPassPixels' at a row pointed by 'x', - /// weighted by weight values, pointed by this instance. - /// - /// The buffer of input vectors in row first order - /// The row position - /// The source column position. - /// The weighted sum - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ComputeWeightedColumnSum(Buffer2D firstPassPixels, int x, int sourceY) - { - ref float verticalValues = ref this.GetStartReference(); - int left = this.Left; - - // Destination color components - Vector4 result = Vector4.Zero; - - for (int i = 0; i < this.Length; i++) - { - float yw = Unsafe.Add(ref verticalValues, i); - int index = left + i + sourceY; - result += firstPassPixels[x, index] * yw; - } - - return result.UnPremultiply(); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/ResizeExtensions.cs b/src/ImageSharp/Processing/ResizeExtensions.cs index 8a370db693..7b6c14d7de 100644 --- a/src/ImageSharp/Processing/ResizeExtensions.cs +++ b/src/ImageSharp/Processing/ResizeExtensions.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Processing /// The image to resize. /// The resize options. /// The - /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image + /// 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) where TPixel : struct, IPixel => source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize())); @@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Processing /// The image to resize. /// The target image size. /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + /// 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) where TPixel : struct, IPixel => Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, false); @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing /// The target image size. /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + /// 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) where TPixel : struct, IPixel => Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, compand); @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Processing /// The target image width. /// The target image height. /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + /// 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) where TPixel : struct, IPixel => Resize(source, width, height, KnownResamplers.Bicubic, false); @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing /// The target image height. /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + /// 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) where TPixel : struct, IPixel => Resize(source, width, height, KnownResamplers.Bicubic, compand); @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Processing /// The target image height. /// The to perform the resampling. /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + /// 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) where TPixel : struct, IPixel => Resize(source, width, height, sampler, false); @@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp.Processing /// The to perform the resampling. /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + /// 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) where TPixel : struct, IPixel => Resize(source, size.Width, size.Height, sampler, new Rectangle(0, 0, size.Width, size.Height), compand); @@ -114,7 +114,7 @@ namespace SixLabors.ImageSharp.Processing /// The to perform the resampling. /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + /// 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) where TPixel : struct, IPixel => Resize(source, width, height, sampler, new Rectangle(0, 0, width, height), compand); @@ -136,7 +136,7 @@ namespace SixLabors.ImageSharp.Processing /// /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + /// 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, @@ -161,7 +161,7 @@ namespace SixLabors.ImageSharp.Processing /// /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + /// 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, diff --git a/tests/ImageSharp.Benchmarks/Codecs/CopyPixels.cs b/tests/ImageSharp.Benchmarks/Codecs/CopyPixels.cs deleted file mode 100644 index d55c231a73..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/CopyPixels.cs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Threading.Tasks; - -using BenchmarkDotNet.Attributes; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs -{ - public class CopyPixels : BenchmarkBase - { - [Benchmark(Baseline = true, Description = "PixelAccessor Copy by indexer")] - public Rgba32 CopyByPixelAccesor() - { - using (var source = new Image(1024, 768)) - using (var target = new Image(1024, 768)) - { - Buffer2D sourcePixels = source.GetRootFramePixelBuffer(); - Buffer2D targetPixels = target.GetRootFramePixelBuffer(); - ParallelFor.WithConfiguration( - 0, - source.Height, - Configuration.Default, - y => - { - for (int x = 0; x < source.Width; x++) - { - targetPixels[x, y] = sourcePixels[x, y]; - } - }); - - return targetPixels[0, 0]; - } - } - - [Benchmark(Description = "PixelAccessor Copy by Span")] - public Rgba32 CopyByPixelAccesorSpan() - { - using (var source = new Image(1024, 768)) - using (var target = new Image(1024, 768)) - { - Buffer2D sourcePixels = source.GetRootFramePixelBuffer(); - Buffer2D targetPixels = target.GetRootFramePixelBuffer(); - ParallelFor.WithConfiguration( - 0, - source.Height, - Configuration.Default, - y => - { - Span sourceRow = sourcePixels.GetRowSpan(y); - Span targetRow = targetPixels.GetRowSpan(y); - - for (int x = 0; x < source.Width; x++) - { - targetRow[x] = sourceRow[x]; - } - }); - - return targetPixels[0, 0]; - } - } - - [Benchmark(Description = "Copy by indexer")] - public Rgba32 Copy() - { - using (var source = new Image(1024, 768)) - using (var target = new Image(1024, 768)) - { - ParallelFor.WithConfiguration( - 0, - source.Height, - Configuration.Default, - y => - { - for (int x = 0; x < source.Width; x++) - { - target[x, y] = source[x, y]; - } - }); - - return target[0, 0]; - } - } - - [Benchmark(Description = "Copy by Span")] - public Rgba32 CopySpan() - { - using (var source = new Image(1024, 768)) - using (var target = new Image(1024, 768)) - { - ParallelFor.WithConfiguration( - 0, - source.Height, - Configuration.Default, - y => - { - Span sourceRow = source.Frames.RootFrame.GetPixelRowSpan(y); - Span targetRow = target.Frames.RootFrame.GetPixelRowSpan(y); - - for (int x = 0; x < source.Width; x++) - { - targetRow[x] = sourceRow[x]; - } - }); - - return target[0, 0]; - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeTiff.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs similarity index 96% rename from tests/ImageSharp.Benchmarks/Image/DecodeTiff.cs rename to tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs index 1ddd3e91cf..dd6fc34b3e 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodeTiff.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs @@ -11,7 +11,7 @@ using SixLabors.ImageSharp.PixelFormats; using CoreImage = SixLabors.ImageSharp.Image; using CoreSize = SixLabors.Primitives.Size; -namespace SixLabors.ImageSharp.Benchmarks.Image +namespace SixLabors.ImageSharp.Benchmarks.Codecs { public class DecodeTiff : BenchmarkBase { diff --git a/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs index eba6b5d9be..92008f6e20 100644 --- a/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs +++ b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs @@ -14,7 +14,7 @@ private static readonly RGBColor RGBColor = new RGBColor(0.206162, 0.260277, 0.746717, RGBWorkingSpaces.WideGamutRGB); - private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter { TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; + private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(new ColorSpaceConverterOptions { TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }); private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter { TargetRGBWorkingSpace = RGBWorkingSpaces.sRGB }; diff --git a/tests/ImageSharp.Benchmarks/General/Pow.cs b/tests/ImageSharp.Benchmarks/General/Pow.cs new file mode 100644 index 0000000000..325bd9d20e --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Pow.cs @@ -0,0 +1,40 @@ +using System; +using BenchmarkDotNet.Attributes; + +namespace SixLabors.ImageSharp.Benchmarks.General +{ + public class Pow + { + [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(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 = "Pow x3")] + public float PowMult3() + { + float x = this.X; + return x * x * x; + } + } +} diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs new file mode 100644 index 0000000000..23f13c89b7 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs @@ -0,0 +1,59 @@ +using System.Numerics; +using System.Runtime.CompilerServices; +using BenchmarkDotNet.Attributes; + +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +{ + 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) + { + 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 }; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 35a3a67e9e..a705c9bacb 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -1,11 +1,11 @@  - netcoreapp2.0;net461 + netcoreapp2.1;net461 Exe True SixLabors.ImageSharp.Benchmarks ImageSharp.Benchmarks - 7.2 + 7.3 win7-x64 @@ -15,13 +15,12 @@ - + - diff --git a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs index fe1d4221d1..ff2e57b974 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Processing.Processors.Overlays; @@ -112,26 +113,29 @@ namespace SixLabors.ImageSharp.Benchmarks Buffer2D sourcePixels = source.PixelBuffer; rowColors.GetSpan().Fill(glowColor); - ParallelFor.WithConfiguration( - minY, - maxY, + var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + ParallelHelper.IterateRows( + workingRect, configuration, - y => + rows => { - int offsetY = y - startY; - - for (int x = minX; x < maxX; x++) + for (int y = rows.Min; y < rows.Max; y++) { - int offsetX = x - startX; - float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); - Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); - TPixel packed = default(TPixel); - packed.PackFromVector4( - PremultipliedLerp( - sourceColor, - glowColor.ToVector4(), - 1 - (.95F * (distance / maxDistance)))); - sourcePixels[offsetX, offsetY] = packed; + int offsetY = y - startY; + + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); + Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); + TPixel packed = default(TPixel); + packed.PackFromVector4( + PremultipliedLerp( + sourceColor, + glowColor.ToVector4(), + 1 - (.95F * (distance / maxDistance)))); + sourcePixels[offsetX, offsetY] = packed; + } } }); } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs b/tests/ImageSharp.Benchmarks/Samplers/Resize.cs index 86dc13e91e..f53061d4e1 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Resize.cs @@ -1,7 +1,5 @@ -// -// Copyright (c) James Jackson-South and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// using System; using System.Drawing; @@ -9,90 +7,91 @@ using System.Drawing.Drawing2D; using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using CoreSize = SixLabors.Primitives.Size; - namespace SixLabors.ImageSharp.Benchmarks { - using System.Threading.Tasks; - - using SixLabors.ImageSharp.Formats.Jpeg; - [Config(typeof(Config.ShortClr))] - public class Resize : BenchmarkBase + public abstract class ResizeBenchmarkBase { - private readonly Configuration configuration = new Configuration(new JpegConfigurationModule()); + protected readonly Configuration Configuration = new Configuration(new JpegConfigurationModule()); + + private Image sourceImage; + + private Bitmap sourceBitmap; + + public const int SourceSize = 3032; - [Params(false, true)] - public bool EnableParallelExecution { get; set; } + public const int DestSize = 400; [GlobalSetup] public void Setup() { - this.configuration.MaxDegreeOfParallelism = - this.EnableParallelExecution ? Environment.ProcessorCount : 1; + this.sourceImage = new Image(this.Configuration, SourceSize, SourceSize); + this.sourceBitmap = new Bitmap(SourceSize, SourceSize); } - [Benchmark(Baseline = true, Description = "System.Drawing Resize")] - public Size ResizeSystemDrawing() + [GlobalCleanup] + public void Cleanup() { - using (Bitmap source = new Bitmap(2000, 2000)) + this.sourceImage.Dispose(); + this.sourceBitmap.Dispose(); + } + + [Benchmark(Baseline = true)] + public int SystemDrawing() + { + using (var destination = new Bitmap(DestSize, DestSize)) { - using (Bitmap destination = new Bitmap(400, 400)) + using (var graphics = Graphics.FromImage(destination)) { - using (Graphics graphics = Graphics.FromImage(destination)) - { - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - graphics.CompositingQuality = CompositingQuality.HighQuality; - graphics.DrawImage(source, 0, 0, 400, 400); - } - - return destination.Size; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.DrawImage(this.sourceBitmap, 0, 0, DestSize, DestSize); } + + return destination.Width; } } - [Benchmark(Description = "ImageSharp Resize")] - public CoreSize ResizeCore() + [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 1")] + public int ImageSharp_P1() => this.RunImageSharpResize(1); + + [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 4")] + public int ImageSharp_P4() => this.RunImageSharpResize(4); + + [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 8")] + public int ImageSharp_P8() => this.RunImageSharpResize(8); + + protected int RunImageSharpResize(int maxDegreeOfParallelism) { - using (var image = new Image(this.configuration, 2000, 2000)) + this.Configuration.MaxDegreeOfParallelism = maxDegreeOfParallelism; + + using (Image clone = this.sourceImage.Clone(this.ExecuteResizeOperation)) { - image.Mutate(x => x.Resize(400, 400)); - return new CoreSize(image.Width, image.Height); + return clone.Width; } } - //[Benchmark(Description = "ImageSharp Vector Resize")] - //public CoreSize ResizeCoreVector() - //{ - // using (Image image = new Image(2000, 2000)) - // { - // image.Resize(400, 400); - // return new CoreSize(image.Width, image.Height); - // } - //} - - //[Benchmark(Description = "ImageSharp Compand Resize")] - //public CoreSize ResizeCoreCompand() - //{ - // using (Image image = new Image(2000, 2000)) - // { - // image.Resize(400, 400, true); - // return new CoreSize(image.Width, image.Height); - // } - //} - - //[Benchmark(Description = "ImageSharp Vector Compand Resize")] - //public CoreSize ResizeCoreVectorCompand() - //{ - // using (Image image = new Image(2000, 2000)) - // { - // image.Resize(400, 400, true); - // return new CoreSize(image.Width, image.Height); - // } - //} + protected abstract void ExecuteResizeOperation(IImageProcessingContext ctx); + } + + public class Resize_Bicubic : ResizeBenchmarkBase + { + protected override void ExecuteResizeOperation(IImageProcessingContext ctx) + { + ctx.Resize(DestSize, DestSize, KnownResamplers.Bicubic); + } + } + + public class Resize_BicubicCompand : ResizeBenchmarkBase + { + protected override void ExecuteResizeOperation(IImageProcessingContext ctx) + { + ctx.Resize(DestSize, DestSize, KnownResamplers.Bicubic, true); + } } } diff --git a/tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs b/tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs index 34b2f718ee..7adbefb346 100644 --- a/tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs +++ b/tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs @@ -14,7 +14,9 @@ namespace SixLabors.ImageSharp.Tests private readonly FakeImageOperationsProvider.FakeImageOperations internalOperations; protected readonly Rectangle rect; protected readonly GraphicsOptions options; - private Image source; + private readonly Image source; + + public Rectangle SourceBounds() => this.source.Bounds(); public BaseImageOperationsExtensionTest() { @@ -29,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests { Assert.InRange(index, 0, this.internalOperations.Applied.Count - 1); - var operation = this.internalOperations.Applied[index]; + FakeImageOperationsProvider.FakeImageOperations.AppliedOperation operation = this.internalOperations.Applied[index]; return Assert.IsType(operation.Processor); } @@ -38,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests { Assert.InRange(index, 0, this.internalOperations.Applied.Count - 1); - var operation = this.internalOperations.Applied[index]; + FakeImageOperationsProvider.FakeImageOperations.AppliedOperation operation = this.internalOperations.Applied[index]; Assert.Equal(rect, operation.Rectangle); return Assert.IsType(operation.Processor); diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLabTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieLabTests.cs new file mode 100644 index 0000000000..dbc07b916e --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieLabTests.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class CieLabTests + { + [Fact] + public void CieLabConstructorAssignsFields() + { + 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); + } + + [Fact] + public void CieLabEquality() + { + var x = default(CieLab); + var y = new CieLab(Vector3.One); + + Assert.True(default(CieLab) == default(CieLab)); + Assert.True(default(CieLab) != new CieLab(1, 0, 1)); + Assert.False(default(CieLab) == new CieLab(1, 0, 1)); + Assert.Equal(default(CieLab), default(CieLab)); + Assert.Equal(new CieLab(1, 0, 1), new CieLab(1, 0, 1)); + Assert.Equal(new CieLab(Vector3.One), new CieLab(Vector3.One)); + Assert.False(x.Equals(y)); + Assert.False(default(CieLab) == new CieLab(1, 0, 1)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLchTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieLchTests.cs new file mode 100644 index 0000000000..90c2c22446 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieLchTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class CieLchTests + { + [Fact] + public void CieLchConstructorAssignsFields() + { + 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); + } + + [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())); + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLchuvTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieLchuvTests.cs new file mode 100644 index 0000000000..a6a5fa32ad --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieLchuvTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class CieLchuvTests + { + [Fact] + public void CieLchuvConstructorAssignsFields() + { + 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); + } + + [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())); + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLuvTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieLuvTests.cs new file mode 100644 index 0000000000..dbf64cb1d0 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieLuvTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class CieLuvTests + { + [Fact] + public void CieLuvConstructorAssignsFields() + { + 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); + } + + [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())); + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs new file mode 100644 index 0000000000..42ace9dbed --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class CieXyChromaticityCoordinatesTests + { + [Fact] + public void CieXyChromaticityCoordinatesConstructorAssignsFields() + { + const float x = .75F; + const float y = .64F; + var coordinates = new CieXyChromaticityCoordinates(x, 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); + + Assert.True(default(CieXyChromaticityCoordinates) == default(CieXyChromaticityCoordinates)); + Assert.True(default(CieXyChromaticityCoordinates) != new CieXyChromaticityCoordinates(1, 0)); + Assert.False(default(CieXyChromaticityCoordinates) == new CieXyChromaticityCoordinates(1, 0)); + Assert.Equal(default(CieXyChromaticityCoordinates), default(CieXyChromaticityCoordinates)); + Assert.Equal(new CieXyChromaticityCoordinates(1, 0), new CieXyChromaticityCoordinates(1, 0)); + Assert.Equal(new CieXyChromaticityCoordinates(1, 1), new CieXyChromaticityCoordinates(1, 1)); + Assert.False(x.Equals(y)); + Assert.False(default(CieXyChromaticityCoordinates) == new CieXyChromaticityCoordinates(1, 0)); + Assert.False(x.Equals((object)y)); + Assert.False(x.GetHashCode().Equals(y.GetHashCode())); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyyTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyyTests.cs new file mode 100644 index 0000000000..88196034bf --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyyTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class CieXyyTests + { + [Fact] + public void CieXyyConstructorAssignsFields() + { + 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); + } + + [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())); + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyzAndHunterLabConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyzAndHunterLabConversionTest.cs deleted file mode 100644 index bea392c167..0000000000 --- a/tests/ImageSharp.Tests/Colorspaces/CieXyzAndHunterLabConversionTest.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Collections.Generic; -using SixLabors.ImageSharp.ColorSpaces; -using SixLabors.ImageSharp.ColorSpaces.Conversion; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Colorspaces -{ - /// - /// Tests - conversions. - /// - /// - /// Test data generated using: - /// - /// - public class CieXyzAndHunterLabConversionTest - { - private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); - - /// - /// 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 converter = new ColorSpaceConverter { WhitePoint = Illuminants.C }; - - // Act - CieXyz output = converter.ToCieXyz(input); - - // Assert - Assert.Equal(x, output.X, FloatRoundingComparer); - Assert.Equal(y, output.Y, FloatRoundingComparer); - Assert.Equal(z, output.Z, FloatRoundingComparer); - } - - /// - /// 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 converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65 }; - - // Act - CieXyz output = converter.ToCieXyz(input); - - // Assert - Assert.Equal(x, output.X, FloatRoundingComparer); - Assert.Equal(y, output.Y, FloatRoundingComparer); - Assert.Equal(z, output.Z, FloatRoundingComparer); - } - - /// - /// 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 converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65 }; - - // Act - HunterLab output = converter.ToHunterLab(input); - - // Assert - Assert.Equal(l, output.L, FloatRoundingComparer); - Assert.Equal(a, output.A, FloatRoundingComparer); - Assert.Equal(b, output.B, FloatRoundingComparer); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyzTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyzTests.cs new file mode 100644 index 0000000000..3c77f132e3 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyzTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class CieXyzTests + { + [Fact] + public void CieXyzConstructorAssignsFields() + { + 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); + } + + [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())); + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/CmykTests.cs b/tests/ImageSharp.Tests/Colorspaces/CmykTests.cs new file mode 100644 index 0000000000..dbf3fe6d8a --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/CmykTests.cs @@ -0,0 +1,46 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class CmykTests + { + [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); + + 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); + + 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())); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/ColorConverterAdaptTest.cs b/tests/ImageSharp.Tests/Colorspaces/ColorConverterAdaptTest.cs deleted file mode 100644 index b393c51b73..0000000000 --- a/tests/ImageSharp.Tests/Colorspaces/ColorConverterAdaptTest.cs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Collections.Generic; -using SixLabors.ImageSharp.ColorSpaces; -using SixLabors.ImageSharp.ColorSpaces.Conversion; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.LmsColorSapce; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Colorspaces -{ - /// - /// Tests methods. - /// Test data generated using: - /// - /// - /// - public class ColorConverterAdaptTest - { - private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(3); - - private static readonly ApproximateFloatComparer ApproximateComparer = new ApproximateFloatComparer(0.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) - { - // Arrange - var input = new Rgb(r1, g1, b1, RgbWorkingSpaces.WideGamutRgb); - var expectedOutput = new Rgb(r2, g2, b2, RgbWorkingSpaces.SRgb); - var converter = new ColorSpaceConverter { TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; - - // Action - Rgb output = converter.Adapt(input); - - // Assert - Assert.Equal(expectedOutput.WorkingSpace, output.WorkingSpace, ApproximateComparer); - Assert.Equal(expectedOutput.R, output.R, FloatRoundingComparer); - Assert.Equal(expectedOutput.G, output.G, FloatRoundingComparer); - Assert.Equal(expectedOutput.B, output.B, FloatRoundingComparer); - } - - [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 expectedOutput = new Rgb(r2, g2, b2, RgbWorkingSpaces.WideGamutRgb); - var converter = new ColorSpaceConverter { TargetRgbWorkingSpace = RgbWorkingSpaces.WideGamutRgb }; - - // Action - Rgb output = converter.Adapt(input); - - // Assert - Assert.Equal(expectedOutput.WorkingSpace, output.WorkingSpace, ApproximateComparer); - Assert.Equal(expectedOutput.R, output.R, FloatRoundingComparer); - Assert.Equal(expectedOutput.G, output.G, FloatRoundingComparer); - Assert.Equal(expectedOutput.B, output.B, FloatRoundingComparer); - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(22, 33, 1, 22.269869, 32.841164, 1.633926)] - public void Adapt_Lab_D50_To_D65(float l1, float a1, float b1, float l2, float a2, float b2) - { - // Arrange - var input = new CieLab(l1, a1, b1, Illuminants.D65); - var expectedOutput = new CieLab(l2, a2, b2); - var converter = new ColorSpaceConverter { TargetLabWhitePoint = Illuminants.D50 }; - - // Action - CieLab output = converter.Adapt(input); - - // Assert - Assert.Equal(expectedOutput.L, output.L, FloatRoundingComparer); - Assert.Equal(expectedOutput.A, output.A, FloatRoundingComparer); - Assert.Equal(expectedOutput.B, output.B, FloatRoundingComparer); - } - - [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 - CieXyz input = new CieXyz(x1, y1, z1); - CieXyz expectedOutput = new CieXyz(x2, y2, z2); - ColorSpaceConverter converter = new ColorSpaceConverter - { - WhitePoint = Illuminants.D50 - }; - - // Action - CieXyz output = converter.Adapt(input, Illuminants.D65); - - // Assert - Assert.Equal(expectedOutput.X, output.X, FloatRoundingComparer); - Assert.Equal(expectedOutput.Y, output.Y, FloatRoundingComparer); - Assert.Equal(expectedOutput.Z, output.Z, FloatRoundingComparer); - } - - [Theory] - [InlineData(0, 0, 0, 0, 0, 0)] - [InlineData(0.5, 0.5, 0.5, 0.507233, 0.500000, 0.378943)] - public void Adapt_CieXyz_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 expectedOutput = new CieXyz(x2, y2, z2); - var converter = new ColorSpaceConverter - { - ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XyzScaling), - WhitePoint = Illuminants.D50 - }; - - // Action - CieXyz output = converter.Adapt(input, Illuminants.D65); - - // Assert - Assert.Equal(expectedOutput.X, output.X, FloatRoundingComparer); - Assert.Equal(expectedOutput.Y, output.Y, FloatRoundingComparer); - Assert.Equal(expectedOutput.Z, output.Z, FloatRoundingComparer); - } - - [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 expectedOutput = new CieXyz(x2, y2, z2); - var converter = new ColorSpaceConverter - { - ChromaticAdaptation = new VonKriesChromaticAdaptation(LmsAdaptationMatrix.XyzScaling), - WhitePoint = Illuminants.D50 - }; - - // Action - CieXyz output = converter.Adapt(input, Illuminants.D65); - - // Assert - Assert.Equal(expectedOutput.X, output.X, FloatRoundingComparer); - Assert.Equal(expectedOutput.Y, output.Y, FloatRoundingComparer); - Assert.Equal(expectedOutput.Z, output.Z, FloatRoundingComparer); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/ColorSpaceEqualityTests.cs b/tests/ImageSharp.Tests/Colorspaces/ColorSpaceEqualityTests.cs deleted file mode 100644 index 759d0f12f6..0000000000 --- a/tests/ImageSharp.Tests/Colorspaces/ColorSpaceEqualityTests.cs +++ /dev/null @@ -1,312 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using SixLabors.ImageSharp.ColorSpaces; -using Xunit; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Colorspaces -{ - /// - /// Test implementations of IEquatable and IAlmostEquatable in our colorspaces - /// - public class ColorSpaceEqualityTests - { - internal static readonly Dictionary EmptyDataLookup = - new Dictionary - { - {nameof( CieLab), default(CieLab) }, - {nameof( CieLch), default(CieLch) }, - {nameof( CieLchuv), default(CieLchuv) }, - {nameof( CieLuv), default(CieLuv) }, - {nameof( CieXyz), default(CieXyz) }, - {nameof( CieXyy), default(CieXyy) }, - {nameof( Hsl), default(Hsl) }, - {nameof( HunterLab), default(HunterLab) }, - {nameof( Lms), default(Lms) }, - {nameof( LinearRgb), default(LinearRgb) }, - {nameof( Rgb), default(Rgb) }, - {nameof( YCbCr), default(YCbCr) } - }; - - public static readonly IEnumerable EmptyData = EmptyDataLookup.Select(x => new [] { x.Key }); - - public static readonly TheoryData EqualityData = - new TheoryData - { - { new CieLab(Vector3.One), new CieLab(Vector3.One), typeof(CieLab) }, - { new CieLch(Vector3.One), new CieLch(Vector3.One), typeof(CieLch) }, - { new CieLchuv(Vector3.One), new CieLchuv(Vector3.One), typeof(CieLchuv) }, - { new CieLuv(Vector3.One), new CieLuv(Vector3.One), typeof(CieLuv) }, - { new CieXyz(Vector3.One), new CieXyz(Vector3.One), typeof(CieXyz) }, - { new CieXyy(Vector3.One), new CieXyy(Vector3.One), typeof(CieXyy) }, - { new HunterLab(Vector3.One), new HunterLab(Vector3.One), typeof(HunterLab) }, - { new Lms(Vector3.One), new Lms(Vector3.One), typeof(Lms) }, - { new LinearRgb(Vector3.One), new LinearRgb(Vector3.One), typeof(LinearRgb) }, - { new Rgb(Vector3.One), new Rgb(Vector3.One), typeof(Rgb) }, - { new Hsl(Vector3.One), new Hsl(Vector3.One), typeof(Hsl) }, - { new Hsv(Vector3.One), new Hsv(Vector3.One), typeof(Hsv) }, - { new YCbCr(Vector3.One), new YCbCr(Vector3.One), typeof(YCbCr) }, - }; - - public static readonly TheoryData NotEqualityDataNulls = - new TheoryData - { - // Valid object against null - { new CieLab(Vector3.One), null, typeof(CieLab) }, - { new CieLch(Vector3.One), null, typeof(CieLch) }, - { new CieLchuv(Vector3.One), null, typeof(CieLchuv) }, - { new CieLuv(Vector3.One), null, typeof(CieLuv) }, - { new CieXyz(Vector3.One), null, typeof(CieXyz) }, - { new CieXyy(Vector3.One), null, typeof(CieXyy) }, - { new HunterLab(Vector3.One), null, typeof(HunterLab) }, - { new Lms(Vector3.One), null, typeof(Lms) }, - { new LinearRgb(Vector3.One), null, typeof(LinearRgb) }, - { new Rgb(Vector3.One), null, typeof(Rgb) }, - { new Hsl(Vector3.One), null, typeof(Hsl) }, - { new Hsv(Vector3.One), null, typeof(Hsv) }, - { new YCbCr(Vector3.One), null, typeof(YCbCr) }, - }; - - public static readonly TheoryData NotEqualityDataDifferentObjects = - new TheoryData - { - // Valid objects of different types but not equal - { new CieLab(Vector3.One), new CieLch(Vector3.Zero), null }, - { new CieLuv(Vector3.One), new CieLchuv(Vector3.Zero), null }, - { new CieXyz(Vector3.One), new HunterLab(Vector3.Zero), null }, - { new Rgb(Vector3.One), new LinearRgb(Vector3.Zero), null }, - { new Rgb(Vector3.One), new Lms(Vector3.Zero), null }, - { new Cmyk(Vector4.One), new Hsl(Vector3.Zero), null }, - { new YCbCr(Vector3.One), new CieXyy(Vector3.Zero), null }, - { new Hsv(Vector3.One), new Hsl(Vector3.Zero), null }, - }; - - public static readonly TheoryData NotEqualityData = - new TheoryData - { - // Valid objects of the same type but not equal - { new CieLab(Vector3.One), new CieLab(Vector3.Zero), typeof(CieLab) }, - { new CieLch(Vector3.One), new CieLch(Vector3.Zero), typeof(CieLch) }, - { new CieLchuv(Vector3.One), new CieLchuv(Vector3.Zero), typeof(CieLchuv) }, - { new CieLuv(Vector3.One), new CieLuv(Vector3.Zero), typeof(CieLuv) }, - { new CieXyz(Vector3.One), new CieXyz(Vector3.Zero), typeof(CieXyz) }, - { new CieXyy(Vector3.One), new CieXyy(Vector3.Zero), typeof(CieXyy) }, - { new HunterLab(Vector3.One), new HunterLab(Vector3.Zero), typeof(HunterLab) }, - { new Lms(Vector3.One), new Lms(Vector3.Zero), typeof(Lms) }, - { new LinearRgb(Vector3.One), new LinearRgb(Vector3.Zero), typeof(LinearRgb) }, - { new Rgb(Vector3.One), new Rgb(Vector3.Zero), typeof(Rgb) }, - { new Cmyk(Vector4.One), new Cmyk(Vector4.Zero), typeof(Cmyk) }, - { new Hsl(Vector3.One), new Hsl(Vector3.Zero), typeof(Hsl) }, - { new Hsv(Vector3.One), new Hsv(Vector3.Zero), typeof(Hsv) }, - { new YCbCr(Vector3.One), new YCbCr(Vector3.Zero), typeof(YCbCr) }, - }; - - public static readonly TheoryData AlmostEqualsData = - new TheoryData - { - { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, 0F), typeof(CieLab), 0F }, - { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, 0F), typeof(CieLab), .001F }, - { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, 0F), typeof(CieLab), .0001F }, - { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, 0F), typeof(CieLab), .0005F }, - { new CieLab(0F, 0F, 0F), new CieLab(0F, .001F, 0F), typeof(CieLab), .001F }, - { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, .0001F), typeof(CieLab), .0001F }, - { new CieLab(0F, 0F, 0F), new CieLab(.0005F, 0F, 0F), typeof(CieLab), .0005F }, - { new CieLch(0F, 0F, 0F), new CieLch(0F, .001F, 0F), typeof(CieLch), .001F }, - { new CieLchuv(0F, 0F, 0F), new CieLchuv(0F, .001F, 0F), typeof(CieLchuv), .001F }, - { new CieLuv(0F, 0F, 0F), new CieLuv(0F, .001F, 0F), typeof(CieLuv), .001F }, - { new CieXyz(380F, 380F, 380F), new CieXyz(380F, 380F, 380F), typeof(CieXyz), 0F }, - { new CieXyz(380F, 380F, 380F), new CieXyz(380.001F, 380F, 380F), typeof(CieXyz), .01F }, - { new CieXyz(380F, 380F, 380F), new CieXyz(380F, 380.001F, 380F), typeof(CieXyz), .01F }, - { new CieXyz(380F, 380F, 380F), new CieXyz(380F, 380F, 380.001F), typeof(CieXyz), .01F }, - { new Cmyk(1, 1, 1, 1), new Cmyk(1, 1, 1, .99F), typeof(Cmyk), .01F }, - { new YCbCr(255F, 128F, 128F), new YCbCr(255F, 128F, 128.001F), typeof(YCbCr), .01F }, - { new Hsv(0F, 0F, 0F), new Hsv(0F, 0F, 0F), typeof(Hsv), 0F }, - { new Hsl(0F, 0F, 0F), new Hsl(0F, 0F, 0F), typeof(Hsl), 0F }, - }; - - public static readonly TheoryData AlmostNotEqualsData = - new TheoryData - { - { new CieLab(0F, 0F, 0F), new CieLab(0.1F, 0F, 0F), typeof(CieLab), .001F }, - { new CieLab(0F, 0F, 0F), new CieLab(0F, 0.1F, 0F), typeof(CieLab), .001F }, - { new CieLab(0F, 0F, 0F), new CieLab(0F, 0F, 0.1F), typeof(CieLab), .001F }, - { new CieXyz(380F, 380F, 380F), new CieXyz(380.1F, 380F, 380F), typeof(CieXyz), .001F }, - { new CieXyz(380F, 380F, 380F), new CieXyz(380F, 380.1F, 380F), typeof(CieXyz), .001F }, - { new CieXyz(380F, 380F, 380F), new CieXyz(380F, 380F, 380.1F), typeof(CieXyz), .001F }, - }; - - [Theory] - [MemberData(nameof(EmptyData))] - public void Vector_Equals_WhenTrue(string color) - { - IColorVector colorVector = EmptyDataLookup[color]; - // Act - bool equal = colorVector.Vector.Equals(Vector3.Zero); - - // Assert - Assert.True(equal); - } - - [Theory] - [MemberData(nameof(EqualityData))] - public void Equals_WhenTrue(object first, object second, Type type) - { - // Act - bool equal = first.Equals(second); - - // Assert - Assert.True(equal); - } - - [Theory] - [MemberData(nameof(NotEqualityDataNulls))] - [MemberData(nameof(NotEqualityDataDifferentObjects))] - [MemberData(nameof(NotEqualityData))] - public void Equals_WhenFalse(object first, object second, Type type) - { - // Act - bool equal = first.Equals(second); - - // Assert - Assert.False(equal); - } - - [Theory] - [MemberData(nameof(EqualityData))] - public void GetHashCode_WhenEqual(object first, object second, Type type) - { - // Act - bool equal = first.GetHashCode() == second.GetHashCode(); - - // Assert - Assert.True(equal); - } - - [Theory] - [MemberData(nameof(NotEqualityDataDifferentObjects))] - public void GetHashCode_WhenNotEqual(object first, object second, Type type) - { - // Act - bool equal = first.GetHashCode() == second.GetHashCode(); - - // Assert - Assert.False(equal); - } - - [Theory] - [MemberData(nameof(EqualityData))] - public void GenericEquals_WhenTrue(object first, object second, Type type) - { - // Arrange - // Cast to the known object types, this is so that we can hit the - // equality operator on the concrete type, otherwise it goes to the - // default "object" one :) - dynamic firstObject = Convert.ChangeType(first, type); - dynamic secondObject = Convert.ChangeType(second, type); - - // Act - dynamic equal = firstObject.Equals(secondObject); - - // Assert - Assert.True(equal); - } - - [Theory] - [MemberData(nameof(NotEqualityData))] - public void GenericEquals_WhenFalse(object first, object second, Type type) - { - // Arrange - // Cast to the known object types, this is so that we can hit the - // equality operator on the concrete type, otherwise it goes to the - // default "object" one :) - dynamic firstObject = Convert.ChangeType(first, type); - dynamic secondObject = Convert.ChangeType(second, type); - - // Act - dynamic equal = firstObject.Equals(secondObject); - - // Assert - Assert.False(equal); - } - - // TODO:Disabled due to RuntypeBinder errors while structs are internal - //[Theory] - //[MemberData(nameof(EqualityData))] - //public void EqualityOperator(object first, object second, Type type) - //{ - // // Arrange - // // Cast to the known object types, this is so that we can hit the - // // equality operator on the concrete type, otherwise it goes to the - // // default "object" one :) - // dynamic firstObject = Convert.ChangeType(first, type); - // dynamic secondObject = Convert.ChangeType(second, type); - - // // Act - // dynamic equal = firstObject == secondObject; - - // // Assert - // Assert.True(equal); - //} - - [Theory] - [MemberData(nameof(NotEqualityData))] - public void Operator_WhenTrue(object first, object second, Type type) - { - // Arrange - // Cast to the known object types, this is so that we can hit the - // equality operator on the concrete type, otherwise it goes to the - // default "object" one :) - dynamic firstObject = Convert.ChangeType(first, type); - dynamic secondObject = Convert.ChangeType(second, type); - - // Act - dynamic notEqual = firstObject != secondObject; - - // Assert - Assert.True(notEqual); - } - - // TODO:Disabled due to RuntypeBinder errors while structs are internal - //[Theory] - //[MemberData(nameof(AlmostEqualsData))] - //public void AlmostEquals(object first, object second, Type type, float precision) - //{ - // // Arrange - // // Cast to the known object types, this is so that we can hit the - // // equality operator on the concrete type, otherwise it goes to the - // // default "object" one :) - // dynamic firstObject = Convert.ChangeType(first, type); - // dynamic secondObject = Convert.ChangeType(second, type); - - // // Act - // dynamic almostEqual = firstObject.AlmostEquals(secondObject, precision); - - // // Assert - // Assert.True(almostEqual); - //} - - // TODO:Disabled due to RuntypeBinder errors while structs are internal - //[Theory] - //[MemberData(nameof(AlmostNotEqualsData))] - //public void AlmostNotEquals(object first, object second, Type type, float precision) - //{ - // // Arrange - // // Cast to the known object types, this is so that we can hit the - // // equality operator on the concrete type, otherwise it goes to the - // // default "object" one :) - // dynamic firstObject = Convert.ChangeType(first, type); - // dynamic secondObject = Convert.ChangeType(second, type); - - // // Act - // dynamic almostEqual = firstObject.AlmostEquals(secondObject, precision); - - // // Assert - // Assert.False(almostEqual); - //} - } -} diff --git a/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs b/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs new file mode 100644 index 0000000000..91cacfe3f0 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Companding/CompandingTests.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces.Companding; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Companding +{ + /// + /// Tests various companding algorithms. Numbers are hand calculated from formulas online. + /// TODO: Oddly the formula for converting to/from Rec 2020 and 709 from Wikipedia seems to cause the value to + /// fail a round trip. They're large spaces so this is a surprise. More reading required!! + /// + public class CompandingTests + { + private static readonly ApproximateFloatComparer FloatComparer = new ApproximateFloatComparer(.00001F); + + [Fact] + public void Rec2020Companding_IsCorrect() + { + const float input = .667F; + float e = Rec2020Companding.Expand(input); + float c = Rec2020Companding.Compress(e); + CompandingIsCorrectImpl(e, c, .4484759F, .3937096F); + } + + [Fact] + public void Rec709Companding_IsCorrect() + { + const float input = .667F; + float e = Rec709Companding.Expand(input); + float c = Rec709Companding.Compress(e); + CompandingIsCorrectImpl(e, c, .4483577F, .3937451F); + } + + [Fact] + public void SRgbCompanding_IsCorrect() + { + const float input = .667F; + float e = SRgbCompanding.Expand(input); + float c = SRgbCompanding.Compress(e); + CompandingIsCorrectImpl(e, c, .40242353F, .667F); + } + + [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); + Vector4[] expected = source.Select(v => SRgbCompanding.Expand(v)).ToArray(); + + SRgbCompanding.Expand(source); + + 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); + Vector4[] expected = source.Select(v => SRgbCompanding.Compress(v)).ToArray(); + + SRgbCompanding.Compress(source); + + 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, .667F); + } + + [Fact] + public void LCompanding_IsCorrect() + { + const float input = .667F; + float e = LCompanding.Expand(input); + float c = LCompanding.Compress(e); + CompandingIsCorrectImpl(e, c, .36236193F, .58908917F); + } + + private static void CompandingIsCorrectImpl(float e, float c, float expanded, float compressed) + { + Assert.Equal(expanded, e, FloatComparer); + Assert.Equal(compressed, c, FloatComparer); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs new file mode 100644 index 0000000000..57da2ff170 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs @@ -0,0 +1,240 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; + +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; + + /// + /// 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); + } + + /// + public int GetHashCode(Rgb 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 int GetHashCode(LinearRgb 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 int GetHashCode(CieLab 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 int GetHashCode(CieLch 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 int GetHashCode(CieLchuv 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 int GetHashCode(CieLuv 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 int GetHashCode(CieXyz 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 int GetHashCode(CieXyy 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 int GetHashCode(Cmyk 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 int GetHashCode(HunterLab 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 int GetHashCode(Hsl 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 int GetHashCode(Hsv 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 int GetHashCode(Lms 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 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 int GetHashCode(RgbPrimariesChromaticityCoordinates obj) => obj.GetHashCode(); + + /// + public bool Equals(GammaWorkingSpace x, GammaWorkingSpace y) + { + 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; + } + + /// + public int GetHashCode(GammaWorkingSpace obj) => obj.GetHashCode(); + + /// + public bool Equals(RgbWorkingSpaceBase x, RgbWorkingSpaceBase y) + { + return this.Equals(x.WhitePoint, y.WhitePoint) + && this.Equals(x.ChromaticityCoordinates, y.ChromaticityCoordinates); + } + + /// + public int GetHashCode(RgbWorkingSpaceBase obj) => obj.GetHashCode(); + + private bool Equals(float x, float y) + { + float d = x - y; + return d >= -this.Epsilon && d <= this.Epsilon; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLabAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchConversionTests.cs similarity index 60% rename from tests/ImageSharp.Tests/Colorspaces/CieLabAndCieLchConversionTests.cs rename to tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchConversionTests.cs index 299b9e9e5b..eb9a50d185 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieLabAndCieLchConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchConversionTests.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Collections.Generic; +using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion { /// /// Tests - conversions. @@ -17,8 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces /// public class CieLabAndCieLchConversionTests { - private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); - + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); /// @@ -37,14 +36,24 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces { // Arrange var input = new CieLch(l, c, h); + var expected = new CieLab(l2, a, b); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; // Act - var output = Converter.ToCieLab(input); + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(l2, output.L, FloatRoundingComparer); - Assert.Equal(a, output.A, FloatRoundingComparer); - Assert.Equal(b, output.B, FloatRoundingComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } } /// @@ -59,18 +68,28 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces [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_LCHab(float l, float a, float b, float l2, float c, float h) + 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 actualSpan = new CieLch[5]; // Act - var output = Converter.ToCieLch(input); + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(l2, output.L, FloatRoundingComparer); - Assert.Equal(c, output.C, FloatRoundingComparer); - Assert.Equal(h, output.H, FloatRoundingComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchuvConversionTests.cs new file mode 100644 index 0000000000..7fb5770ddb --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLchuvConversionTests.cs @@ -0,0 +1,83 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieLchuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLchuv[5]; + + // Act + var actual = Converter.ToCieLchuv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLuvConversionTests.cs new file mode 100644 index 0000000000..14a1c6fd37 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieLuvConversionTests.cs @@ -0,0 +1,83 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieXyyConversionTests.cs new file mode 100644 index 0000000000..9a42a9d47d --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCieXyyConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCmykConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCmykConversionTests.cs new file mode 100644 index 0000000000..944fab574e --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndCmykConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new Cmyk[5]; + + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHslConversionTests.cs new file mode 100644 index 0000000000..836be1bf27 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHslConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new Hsl[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsl[5]; + + // Act + var actual = Converter.ToHsl(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHsvConversionTests.cs new file mode 100644 index 0000000000..fb1982bfc8 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHsvConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new Hsv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsv[5]; + + // Act + var actual = Converter.ToHsv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHunterLabConversionTests.cs new file mode 100644 index 0000000000..7e3c4251bf --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndHunterLabConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new HunterLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new HunterLab[5]; + + // Act + var actual = Converter.ToHunterLab(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLinearRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLinearRgbConversionTests.cs new file mode 100644 index 0000000000..a43f0095d7 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLinearRgbConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new LinearRgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new LinearRgb[5]; + + // Act + var actual = Converter.ToLinearRgb(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLmsConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLmsConversionTests.cs new file mode 100644 index 0000000000..62d08263a6 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndLmsConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new Lms[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new Lms[5]; + + // Act + var actual = Converter.ToLms(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndRgbConversionTests.cs new file mode 100644 index 0000000000..1b30412752 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndRgbConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new Rgb[5]; + + // Act + var actual = Converter.ToRgb(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndYCbCrConversionTests.cs new file mode 100644 index 0000000000..53d33af2b1 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLabAndYCbCrConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new YCbCr[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLab[5]; + + // Act + var actual = Converter.ToCieLab(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new YCbCr[5]; + + // Act + var actual = Converter.ToYCbCr(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieLuvConversionTests.cs new file mode 100644 index 0000000000..e465757ef7 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieLuvConversionTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs new file mode 100644 index 0000000000..18b8a47397 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHslConversionTests.cs new file mode 100644 index 0000000000..d00a164c08 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHslConversionTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsl[5]; + + // Act + var actual = Converter.ToHsl(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new Hsl[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHsvConversionTests.cs new file mode 100644 index 0000000000..d3ff04a759 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHsvConversionTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsv[5]; + + // Act + var actual = Converter.ToHsv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new Hsv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHunterLabConversionTests.cs new file mode 100644 index 0000000000..852e56110b --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndHunterLabConversionTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new HunterLab[5]; + + // Act + var actual = Converter.ToHunterLab(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new HunterLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLinearRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLinearRgbConversionTests.cs new file mode 100644 index 0000000000..80b72cb2c2 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLinearRgbConversionTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new LinearRgb[5]; + + // Act + var actual = Converter.ToLinearRgb(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new LinearRgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLmsConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLmsConversionTests.cs new file mode 100644 index 0000000000..314734ff2e --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndLmsConversionTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new Lms[5]; + + // Act + var actual = Converter.ToLms(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new Lms[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndRgbConversionTests.cs new file mode 100644 index 0000000000..389528dcd3 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndRgbConversionTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new Rgb[5]; + + // Act + var actual = Converter.ToRgb(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndYCbCrConversionTests.cs new file mode 100644 index 0000000000..a2bd7eadc7 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndYCbCrConversionTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new YCbCr[5]; + + // Act + var actual = Converter.ToYCbCr(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new YCbCr[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLchConversionTests.cs new file mode 100644 index 0000000000..e7f511bab1 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLchConversionTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLchuv[5]; + + // Act + var actual = Converter.ToCieLchuv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieLchuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLuvAndCieLchuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs similarity index 57% rename from tests/ImageSharp.Tests/Colorspaces/CieLuvAndCieLchuvConversionTests.cs rename to tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs index cbcddcfe50..3bc4fd519b 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieLuvAndCieLchuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Collections.Generic; +using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion { /// /// Tests - conversions. @@ -15,10 +15,9 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces /// Test data generated using: /// /// - public class CieLuvAndCieLchuvuvConversionTests + public class CieLchuvAndCieLuvConversionTests { - private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); - + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); /// @@ -33,18 +32,28 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces [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_Lchuv_to_Luv(float l, float c, float h, float l2, float u, float v) + 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); + + Span inputSpan = new CieLchuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; // Act - CieLuv output = Converter.ToCieLuv(input); + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(l2, output.L, FloatRoundingComparer); - Assert.Equal(u, output.U, FloatRoundingComparer); - Assert.Equal(v, output.V, FloatRoundingComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } } /// @@ -60,18 +69,29 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces [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_Luv_to_LCHuv(float l, float u, float v, float l2, float c, float h) + 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 actualSpan = new CieLchuv[5]; // Act - CieLchuv output = Converter.ToCieLchuv(input); + var actual = Converter.ToCieLchuv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(l2, output.L, FloatRoundingComparer); - Assert.Equal(c, output.C, FloatRoundingComparer); - Assert.Equal(h, output.H, FloatRoundingComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCmykConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCmykConversionTests.cs new file mode 100644 index 0000000000..f3940e4d14 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCmykConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLchuv[5]; + + // Act + var actual = Converter.ToCieLchuv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieLchuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new Cmyk[5]; + + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs new file mode 100644 index 0000000000..61bfe79634 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHslConversionTests.cs new file mode 100644 index 0000000000..7bc430aa37 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHslConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsl[5]; + + // Act + var actual = Converter.ToHsl(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new Hsl[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHsvConversionTests.cs new file mode 100644 index 0000000000..23cc5082c4 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHsvConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsv[5]; + + // Act + var actual = Converter.ToHsv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new Hsv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHunterLabConversionTests.cs new file mode 100644 index 0000000000..04699bde46 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHunterLabConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new HunterLab[5]; + + // Act + var actual = Converter.ToHunterLab(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new HunterLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLinearRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLinearRgbConversionTests.cs new file mode 100644 index 0000000000..98914a6b92 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLinearRgbConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new LinearRgb[5]; + + // Act + var actual = Converter.ToLinearRgb(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new LinearRgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLmsConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLmsConversionTests.cs new file mode 100644 index 0000000000..306d60b531 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLmsConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new Lms[5]; + + // Act + var actual = Converter.ToLms(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new Lms[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndRgbConversionTests.cs new file mode 100644 index 0000000000..21cf08dede --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndRgbConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new Rgb[5]; + + // Act + var actual = Converter.ToRgb(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndYCbCrConversionTests.cs new file mode 100644 index 0000000000..8c07c38d60 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndYCbCrConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new YCbCr[5]; + + // Act + var actual = Converter.ToYCbCr(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new YCbCr[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHslConversionTests.cs new file mode 100644 index 0000000000..fb415f43ba --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHslConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsl[5]; + + // Act + var actual = Converter.ToHsl(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new Hsl[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHsvConversionTests.cs new file mode 100644 index 0000000000..3c8aee807a --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHsvConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsv[5]; + + // Act + var actual = Converter.ToHsv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new Hsv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHunterLabConversionTests.cs new file mode 100644 index 0000000000..1fcbb75cb2 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHunterLabConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new HunterLab[5]; + + // Act + var actual = Converter.ToHunterLab(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new HunterLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLinearRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLinearRgbConversionTests.cs new file mode 100644 index 0000000000..8c45378ed4 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLinearRgbConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new LinearRgb[5]; + + // Act + var actual = Converter.ToLinearRgb(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new LinearRgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLmsConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLmsConversionTests.cs new file mode 100644 index 0000000000..67ec26f6d4 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLmsConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new Lms[5]; + + // Act + var actual = Converter.ToLms(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new Lms[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndRgbConversionTests.cs new file mode 100644 index 0000000000..e309e2d555 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndRgbConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new Rgb[5]; + + // Act + var actual = Converter.ToRgb(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndYCbCrConversionTests.cs new file mode 100644 index 0000000000..3e33f05192 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndYCbCrConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new YCbCr[5]; + + // Act + var actual = Converter.ToYCbCr(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new YCbCr[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieLabConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLabConversionTest.cs similarity index 56% rename from tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieLabConversionTest.cs rename to tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLabConversionTest.cs index 186f976188..746e37c0e6 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieLabConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLabConversionTest.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Collections.Generic; +using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion { /// /// Tests - conversions. @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces /// public class CieXyzAndCieLabConversionTest { - private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); /// /// Tests conversion from to (). @@ -35,15 +35,26 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces { // Arrange var input = new CieLab(l, a, b, Illuminants.D65); - var converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65, TargetLabWhitePoint = 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 actualSpan = new CieXyz[5]; // Act - CieXyz output = converter.ToCieXyz(input); + var actual = converter.ToCieXyz(input); + converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(x, output.X, FloatRoundingComparer); - Assert.Equal(y, output.Y, FloatRoundingComparer); - Assert.Equal(z, output.Z, FloatRoundingComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } } /// @@ -60,15 +71,26 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces { // Arrange var input = new CieXyz(x, y, z); - var converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; + 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 actualSpan = new CieLab[5]; // Act - var output = converter.ToCieLab(input); + var actual = converter.ToCieLab(input); + converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(l, output.L, FloatRoundingComparer); - Assert.Equal(a, output.A, FloatRoundingComparer); - Assert.Equal(b, output.B, FloatRoundingComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs new file mode 100644 index 0000000000..89d78ece1f --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = Converter.ToCieXyz(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchuvConversionTests.cs new file mode 100644 index 0000000000..fbd602d9a0 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchuvConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLchuv[5]; + + // Act + var actual = Converter.ToCieLchuv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieLchuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = Converter.ToCieXyz(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieLuvConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLuvConversionTest.cs similarity index 56% rename from tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieLuvConversionTest.cs rename to tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLuvConversionTest.cs index 46f4f15b8a..c0856a2bc1 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieLuvConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLuvConversionTest.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Collections.Generic; +using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion { /// /// Tests - conversions. @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces /// public class CieXyzAndCieLuvConversionTest { - private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); /// /// Tests conversion from to (). @@ -34,15 +34,26 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces { // Arrange var input = new CieLuv(l, u, v, Illuminants.D65); - var converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65, TargetLabWhitePoint = 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 actualSpan = new CieXyz[5]; // Act - CieXyz output = converter.ToCieXyz(input); + var actual = converter.ToCieXyz(input); + converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(x, output.X, FloatRoundingComparer); - Assert.Equal(y, output.Y, FloatRoundingComparer); - Assert.Equal(z, output.Z, FloatRoundingComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } } /// @@ -59,15 +70,26 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces { // Arrange var input = new CieXyz(x, y, z); - var converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65, TargetLabWhitePoint = Illuminants.D65 }; + 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 actualSpan = new CieLuv[5]; // Act - CieLuv output = converter.ToCieLuv(input); + var actual = converter.ToCieLuv(input); + converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(l, output.L, FloatRoundingComparer); - Assert.Equal(u, output.U, FloatRoundingComparer); - Assert.Equal(v, output.V, FloatRoundingComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieXyyConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieXyyConversionTest.cs similarity index 57% rename from tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieXyyConversionTest.cs rename to tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieXyyConversionTest.cs index d461acd56c..3f5ea4cfd8 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieXyzAndCieXyyConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieXyyConversionTest.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Collections.Generic; +using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion { /// /// Tests - conversions. @@ -17,8 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces /// public class CieXyzAndCieXyyConversionTest { - private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); - + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); [Theory] @@ -29,14 +28,24 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces 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 actualSpan = new CieXyz[5]; // Act - CieXyz output = Converter.ToCieXyz(input); + var actual = Converter.ToCieXyz(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(xyzX, output.X, FloatRoundingComparer); - Assert.Equal(xyzY, output.Y, FloatRoundingComparer); - Assert.Equal(xyzZ, output.Z, FloatRoundingComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } } [Theory] @@ -47,14 +56,24 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces 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 actualSpan = new CieXyy[5]; // Act - CieXyy output = Converter.ToCieXyy(input); + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(x, output.X, FloatRoundingComparer); - Assert.Equal(y, output.Y, FloatRoundingComparer); - Assert.Equal(yl, output.Yl, FloatRoundingComparer); + Assert.Equal(expected, actual, 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 new file mode 100644 index 0000000000..8443722641 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHslConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsl[5]; + + // Act + var actual = Converter.ToHsl(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new Hsl[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = Converter.ToCieXyz(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHsvConversionTests.cs new file mode 100644 index 0000000000..327d660c6c --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHsvConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsv[5]; + + // Act + var actual = Converter.ToHsv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new Hsv[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = Converter.ToCieXyz(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHunterLabConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHunterLabConversionTest.cs new file mode 100644 index 0000000000..d162940151 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHunterLabConversionTest.cs @@ -0,0 +1,118 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// Tests - conversions. + /// + /// + /// Test data generated using: + /// + /// + public class CieXyzAndHunterLabConversionTest + { + 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); + + Span inputSpan = new HunterLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = converter.ToCieXyz(input); + converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new HunterLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = converter.ToCieXyz(input); + converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new HunterLab[5]; + + // Act + var actual = converter.ToHunterLab(input); + converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyzAndLmsConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndLmsConversionTest.cs similarity index 61% rename from tests/ImageSharp.Tests/Colorspaces/CieXyzAndLmsConversionTest.cs rename to tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndLmsConversionTest.cs index 45ca9049ab..484d302e9a 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieXyzAndLmsConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndLmsConversionTest.cs @@ -1,22 +1,22 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Collections.Generic; +using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion { /// - /// Tests - conversions. + /// Tests - conversions. /// /// /// Test data generated using original colorful library. /// public class CieXyzAndLmsConversionTest { - private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(5); + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); /// /// Tests conversion from () to . @@ -33,14 +33,24 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces // 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 actualSpan = new CieXyz[5]; // Act - CieXyz output = converter.ToCieXyz(input); + var actual = converter.ToCieXyz(input); + converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(x, output.X, FloatRoundingComparer); - Assert.Equal(y, output.Y, FloatRoundingComparer); - Assert.Equal(z, output.Z, FloatRoundingComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } } /// @@ -58,14 +68,24 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces // 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 actualSpan = new Lms[5]; // Act - Lms output = converter.ToLms(input); + var actual = converter.ToLms(input); + converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(l, output.L, FloatRoundingComparer); - Assert.Equal(m, output.M, FloatRoundingComparer); - Assert.Equal(s, output.S, FloatRoundingComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs new file mode 100644 index 0000000000..eacdc7ffba --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new YCbCr[5]; + + // Act + var actual = Converter.ToYCbCr(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new YCbCr[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = Converter.ToCieXyz(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLchConversionTests.cs new file mode 100644 index 0000000000..4a0c88c841 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLchConversionTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLch[5]; + + // Act + var actual = Converter.ToCieLch(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieLch[5]; + inputSpan.Fill(input); + + Span actualSpan = new Cmyk[5]; + + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLuvConversionTests.cs new file mode 100644 index 0000000000..2131ba630b --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieLuvConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieLuv[5]; + + // Act + var actual = Converter.ToCieLuv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieLuv[5]; + inputSpan.Fill(input); + + Span actualSpan = new Cmyk[5]; + + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyyConversionTests.cs new file mode 100644 index 0000000000..ac93aaf25b --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyyConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyy[5]; + + // Act + var actual = Converter.ToCieXyy(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieXyy[5]; + inputSpan.Fill(input); + + Span actualSpan = new Cmyk[5]; + + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyzConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyzConversionTests.cs new file mode 100644 index 0000000000..cbb8f7dc4e --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndCieXyzConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + // Act + var actual = Converter.ToCieXyz(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new Cmyk[5]; + + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHslConversionTests.cs new file mode 100644 index 0000000000..1c9ad170d3 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHslConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsl[5]; + + // Act + var actual = Converter.ToHsl(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new Hsl[5]; + inputSpan.Fill(input); + + Span actualSpan = new Cmyk[5]; + + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHsvConversionTests.cs new file mode 100644 index 0000000000..6fd1ba88ec --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHsvConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsv[5]; + + // Act + var actual = Converter.ToHsv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new Hsv[5]; + inputSpan.Fill(input); + + Span actualSpan = new Cmyk[5]; + + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHunterLabConversionTests.cs new file mode 100644 index 0000000000..e92ac2e528 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndHunterLabConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); + + Span actualSpan = new HunterLab[5]; + + // Act + var actual = Converter.ToHunterLab(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new HunterLab[5]; + inputSpan.Fill(input); + + Span actualSpan = new Cmyk[5]; + + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndYCbCrConversionTests.cs new file mode 100644 index 0000000000..575122661a --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CmykAndYCbCrConversionTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +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 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); + + Span inputSpan = new Cmyk[5]; + inputSpan.Fill(input); + + Span actualSpan = new YCbCr[5]; + + // Act + var actual = Converter.ToYCbCr(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, 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); + + Span inputSpan = new YCbCr[5]; + inputSpan.Fill(input); + + Span actualSpan = new Cmyk[5]; + + // Act + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); + + // Assert + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/ColorConverterAdaptTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/ColorConverterAdaptTest.cs new file mode 100644 index 0000000000..326777f3c6 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/ColorConverterAdaptTest.cs @@ -0,0 +1,181 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + /// + /// 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) + { + // 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) + { + // 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 + { + 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 + { + 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); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndCieXyzConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCieXyzConversionTest.cs similarity index 54% rename from tests/ImageSharp.Tests/Colorspaces/RgbAndCieXyzConversionTest.cs rename to tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCieXyzConversionTest.cs index 929c35ee90..a3b0cbd953 100644 --- a/tests/ImageSharp.Tests/Colorspaces/RgbAndCieXyzConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCieXyzConversionTest.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Collections.Generic; +using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion { /// /// Tests - conversions. @@ -17,9 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces /// public class RgbAndCieXyzConversionTest { - private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(5); - - private static readonly ApproximateFloatComparer ApproximateComparer = new ApproximateFloatComparer(0.0001F); + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); /// /// Tests conversion from () @@ -36,18 +34,27 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces { // Arrange var input = new CieXyz(x, y, z); - var converter = new ColorSpaceConverter { WhitePoint = Illuminants.D50, TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; + 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 actualSpan = new Rgb[5]; // Act - Rgb output = converter.ToRgb(input); + var actual = converter.ToRgb(input); + converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - IEqualityComparer comparer = new ApproximateFloatComparer(0.001f); + Assert.Equal(Rgb.DefaultWorkingSpace, actual.WorkingSpace, ColorSpaceComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); - Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace, ApproximateComparer); - Assert.Equal(r, output.R, comparer); - Assert.Equal(g, output.G, comparer); - Assert.Equal(b, output.B, comparer); + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } } /// @@ -65,17 +72,28 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces public void Convert_XYZ_D65_to_SRGB(float x, float y, float z, float r, float g, float b) { // Arrange - CieXyz input = new CieXyz(x, y, z); - ColorSpaceConverter converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65, TargetRgbWorkingSpace = RgbWorkingSpaces.SRgb }; + 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 actualSpan = new Rgb[5]; // Act - Rgb output = converter.ToRgb(input); + var actual = converter.ToRgb(input); + converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace, ApproximateComparer); - Assert.Equal(r, output.R, FloatRoundingComparer); - Assert.Equal(g, output.G, FloatRoundingComparer); - Assert.Equal(b, output.B, FloatRoundingComparer); + 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); + } } /// @@ -93,16 +111,26 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces { // Arrange var input = new Rgb(r, g, b); - var converter = new ColorSpaceConverter { WhitePoint = Illuminants.D50 }; + 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 actualSpan = new CieXyz[5]; // Act - CieXyz output = converter.ToCieXyz(input); + var actual = converter.ToCieXyz(input); + converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - IEqualityComparer comparer = new ApproximateFloatComparer(0.001f); - Assert.Equal(x, output.X, comparer); - Assert.Equal(y, output.Y, comparer); - Assert.Equal(z, output.Z, comparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } } /// @@ -120,15 +148,26 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces { // Arrange var input = new Rgb(r, g, b); - var converter = new ColorSpaceConverter { WhitePoint = Illuminants.D65 }; + 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 actualSpan = new CieXyz[5]; // Act - CieXyz output = converter.ToCieXyz(input); + var actual = converter.ToCieXyz(input); + converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(x, output.X, FloatRoundingComparer); - Assert.Equal(y, output.Y, FloatRoundingComparer); - Assert.Equal(z, output.Z, FloatRoundingComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndCmykConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCmykConversionTest.cs similarity index 55% rename from tests/ImageSharp.Tests/Colorspaces/RgbAndCmykConversionTest.cs rename to tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCmykConversionTest.cs index 9a6ff7b491..2b03ee9883 100644 --- a/tests/ImageSharp.Tests/Colorspaces/RgbAndCmykConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndCmykConversionTest.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Collections.Generic; +using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion { /// /// Tests - conversions. @@ -18,11 +18,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces /// public class RgbAndCmykConversionTest { - private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - private static readonly ApproximateFloatComparer ApproximateComparer = new ApproximateFloatComparer(0.0001F); + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); /// /// Tests conversion from to . @@ -35,15 +32,25 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces { // 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 actualSpan = new Rgb[5]; // Act - Rgb output = Converter.ToRgb(input); + var actual = Converter.ToRgb(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace, ApproximateComparer); - Assert.Equal(r, output.R, FloatRoundingComparer); - Assert.Equal(g, output.G, FloatRoundingComparer); - Assert.Equal(b, output.B, FloatRoundingComparer); + 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); + } } /// @@ -57,15 +64,24 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces { // 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 actualSpan = new Cmyk[5]; // Act - Cmyk output = Converter.ToCmyk(input); + var actual = Converter.ToCmyk(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(c, output.C, FloatRoundingComparer); - Assert.Equal(m, output.M, FloatRoundingComparer); - Assert.Equal(y, output.Y, FloatRoundingComparer); - Assert.Equal(k, output.K, FloatRoundingComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndHslConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs similarity index 57% rename from tests/ImageSharp.Tests/Colorspaces/RgbAndHslConversionTest.cs rename to tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs index 4f15379329..22f5c6d514 100644 --- a/tests/ImageSharp.Tests/Colorspaces/RgbAndHslConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Collections.Generic; +using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion { /// /// Tests - conversions. @@ -18,11 +18,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces /// public class RgbAndHslConversionTest { - private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - private static readonly ApproximateFloatComparer ApproximateComparer = new ApproximateFloatComparer(0.0001F); + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); /// /// Tests conversion from to . @@ -38,15 +35,25 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces { // Arrange var input = new Hsl(h, s, l); + var expected = new Rgb(r, g, b); + + Span inputSpan = new Hsl[5]; + inputSpan.Fill(input); + + Span actualSpan = new Rgb[5]; // Act - Rgb output = Converter.ToRgb(input); + var actual = Converter.ToRgb(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace, ApproximateComparer); - Assert.Equal(r, output.R, FloatRoundingComparer); - Assert.Equal(g, output.G, FloatRoundingComparer); - Assert.Equal(b, output.B, FloatRoundingComparer); + 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); + } } /// @@ -62,14 +69,25 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces { // Arrange var input = new Rgb(r, g, b); + var expected = new Hsl(h, s, l); + + + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsl[5]; // Act - Hsl output = Converter.ToHsl(input); + var actual = Converter.ToHsl(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(h, output.H, FloatRoundingComparer); - Assert.Equal(s, output.S, FloatRoundingComparer); - Assert.Equal(l, output.L, FloatRoundingComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndHsvConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHsvConversionTest.cs similarity index 56% rename from tests/ImageSharp.Tests/Colorspaces/RgbAndHsvConversionTest.cs rename to tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHsvConversionTest.cs index 7f46ff1fc9..e84ce97237 100644 --- a/tests/ImageSharp.Tests/Colorspaces/RgbAndHsvConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHsvConversionTest.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Collections.Generic; +using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion { /// /// Tests - conversions. @@ -17,11 +17,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces /// public class RgbAndHsvConversionTest { - private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(4); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - private static readonly ApproximateFloatComparer ApproximateComparer = new ApproximateFloatComparer(0.0001F); + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); /// /// Tests conversion from to . @@ -37,15 +34,25 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces { // Arrange var input = new Hsv(h, s, v); + var expected = new Rgb(r, g, b); + + Span inputSpan = new Hsv[5]; + inputSpan.Fill(input); + + Span actualSpan = new Rgb[5]; // Act - Rgb output = Converter.ToRgb(input); + var actual = Converter.ToRgb(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace, ApproximateComparer); - Assert.Equal(r, output.R, FloatRoundingComparer); - Assert.Equal(g, output.G, FloatRoundingComparer); - Assert.Equal(b, output.B, FloatRoundingComparer); + 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); + } } /// @@ -61,14 +68,24 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces { // Arrange var input = new Rgb(r, g, b); + var expected = new Hsv(h, s, v); + + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new Hsv[5]; // Act - Hsv output = Converter.ToHsv(input); + var actual = Converter.ToHsv(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(h, output.H, FloatRoundingComparer); - Assert.Equal(s, output.S, FloatRoundingComparer); - Assert.Equal(v, output.V, FloatRoundingComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } } } } diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndYCbCrConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndYCbCrConversionTest.cs similarity index 54% rename from tests/ImageSharp.Tests/Colorspaces/RgbAndYCbCrConversionTest.cs rename to tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndYCbCrConversionTest.cs index 46c12e3a55..f5c7dbae66 100644 --- a/tests/ImageSharp.Tests/Colorspaces/RgbAndYCbCrConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndYCbCrConversionTest.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.Collections.Generic; +using System; using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; using Xunit; -namespace SixLabors.ImageSharp.Tests.Colorspaces +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion { /// /// Tests - conversions. @@ -16,11 +16,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces /// public class RgbAndYCbCrConversionTest { - private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(3); - private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); - - private static readonly ApproximateFloatComparer ApproximateComparer = new ApproximateFloatComparer(0.0001F); + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.001F); /// /// Tests conversion from to . @@ -33,15 +30,25 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces { // Arrange var input = new YCbCr(y, cb, cr); + var expected = new Rgb(r, g, b); + + Span inputSpan = new YCbCr[5]; + inputSpan.Fill(input); + + Span actualSpan = new Rgb[5]; // Act - Rgb output = Converter.ToRgb(input); + var actual = Converter.ToRgb(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace, ApproximateComparer); - Assert.Equal(r, output.R, FloatRoundingComparer); - Assert.Equal(g, output.G, FloatRoundingComparer); - Assert.Equal(b, output.B, FloatRoundingComparer); + 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); + } } /// @@ -56,14 +63,24 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces { // Arrange var input = new Rgb(r, g, b); + var expected = new YCbCr(y, cb, cr); + + Span inputSpan = new Rgb[5]; + inputSpan.Fill(input); + + Span actualSpan = new YCbCr[5]; // Act - YCbCr output = Converter.ToYCbCr(input); + var actual = Converter.ToYCbCr(input); + Converter.Convert(inputSpan, actualSpan, actualSpan.Length); // Assert - Assert.Equal(y, output.Y, FloatRoundingComparer); - Assert.Equal(cb, output.Cb, FloatRoundingComparer); - Assert.Equal(cr, output.Cr, FloatRoundingComparer); + Assert.Equal(expected, actual, ColorSpaceComparer); + + for (int i = 0; i < actualSpan.Length; i++) + { + Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs new file mode 100644 index 0000000000..cfd48b694d --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.ColorSpaces.Conversion; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion +{ + public class VonKriesChromaticAdaptationTests + { + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); + public static readonly TheoryData WhitePoints = new TheoryData + { + {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); + + Span inputSpan = new CieXyz[5]; + inputSpan.Fill(input); + + Span actualSpan = new CieXyz[5]; + + adaptation.Transform(inputSpan, actualSpan, sourceWhitePoint, destinationWhitePoint, inputSpan.Length); + + 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 new file mode 100644 index 0000000000..60cfa9761a --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/HslTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class HslTests + { + [Fact] + public void HslConstructorAssignsFields() + { + 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); + } + + [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())); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/HsvTests.cs b/tests/ImageSharp.Tests/Colorspaces/HsvTests.cs new file mode 100644 index 0000000000..d1d1d15c8a --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/HsvTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class HsvTests + { + [Fact] + public void HsvConstructorAssignsFields() + { + 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); + } + + [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())); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs b/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs new file mode 100644 index 0000000000..95261e1d98 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class HunterLabTests + { + [Fact] + public void HunterLabConstructorAssignsFields() + { + 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); + } + + [Fact] + public void HunterLabEquality() + { + var x = default(HunterLab); + var y = new HunterLab(Vector3.One); + + Assert.True(default(HunterLab) == default(HunterLab)); + Assert.True(default(HunterLab) != new HunterLab(1, 0, 1)); + Assert.False(default(HunterLab) == new HunterLab(1, 0, 1)); + Assert.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())); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/LinearRgbTests.cs b/tests/ImageSharp.Tests/Colorspaces/LinearRgbTests.cs new file mode 100644 index 0000000000..ef42e68bcc --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/LinearRgbTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class LinearRgbTests + { + [Fact] + public void LinearRgbConstructorAssignsFields() + { + 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); + } + + [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())); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs b/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs new file mode 100644 index 0000000000..1b0939dc5c --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class LmsTests + { + [Fact] + public void LmsConstructorAssignsFields() + { + 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); + } + + [Fact] + public void LmsEquality() + { + var x = default(Lms); + var y = new Lms(Vector3.One); + + Assert.True(default(Lms) == default(Lms)); + Assert.True(default(Lms) != new Lms(1, 0, 1)); + Assert.False(default(Lms) == new Lms(1, 0, 1)); + Assert.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())); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbTests.cs b/tests/ImageSharp.Tests/Colorspaces/RgbTests.cs new file mode 100644 index 0000000000..7987fbe9f2 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/RgbTests.cs @@ -0,0 +1,83 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class RgbTests + { + [Fact] + public void RgbConstructorAssignsFields() + { + 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); + } + + [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())); + } + + [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; + + 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); + } + + [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; + + 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); + } + } +} diff --git a/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs b/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs new file mode 100644 index 0000000000..5249b709b1 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + public class StringRepresentationTests + { + private static readonly Vector3 one = new Vector3(1); + private static readonly Vector3 zero = new Vector3(0); + private static readonly Vector3 random = new Vector3(42.4F, 94.5F, 83.4F); + + 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()); + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colorspaces/YCbCrTests.cs b/tests/ImageSharp.Tests/Colorspaces/YCbCrTests.cs new file mode 100644 index 0000000000..f3e6f88f49 --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/YCbCrTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.ColorSpaces; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Colorspaces +{ + /// + /// Tests the struct. + /// + public class YCbCrTests + { + [Fact] + public void YCbCrConstructorAssignsFields() + { + 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); + } + + [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())); + } + } +} diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 4bec25f7a2..963d674466 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using Moq; +using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.IO; using Xunit; // ReSharper disable InconsistentNaming @@ -22,7 +23,7 @@ namespace SixLabors.ImageSharp.Tests { // 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().ShallowCopy(); + this.DefaultConfiguration = Configuration.CreateDefaultInstance().Clone(); this.ConfigurationEmpty = new Configuration(); } @@ -37,19 +38,13 @@ namespace SixLabors.ImageSharp.Tests /// Test that the default configuration is not null. /// [Fact] - public void TestDefaultConfigurationIsNotNull() - { - Assert.True(Configuration.Default != null); - } + public void TestDefaultConfigurationIsNotNull() => Assert.True(Configuration.Default != null); /// /// Test that the default configuration read origin options is set to begin. /// [Fact] - public void TestDefaultConfigurationReadOriginIsCurrent() - { - Assert.True(Configuration.Default.ReadOrigin == ReadOrigin.Current); - } + public void TestDefaultConfigurationReadOriginIsCurrent() => Assert.True(Configuration.Default.ReadOrigin == ReadOrigin.Current); /// /// Test that the default configuration parallel options max degrees of parallelism matches the @@ -101,7 +96,7 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(count, config.ImageFormats.Count()); - config.ImageFormatsManager.AddImageFormat(ImageFormats.Bmp); + config.ImageFormatsManager.AddImageFormat(BmpFormat.Instance); Assert.Equal(count, config.ImageFormats.Count()); } diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs index 3522ade7c4..d65796d37f 100644 --- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs @@ -319,13 +319,14 @@ namespace SixLabors.ImageSharp.Tests.Drawing var coloringVariant = new StringBuilder(); ColorStop[] colorStops = new ColorStop[stopPositions.Length]; + Rgba32 rgba = default; for (int i = 0; i < stopPositions.Length; i++) { TPixel color = colors[stopColorCodes[i % colors.Length]]; float position = stopPositions[i]; - + color.ToRgba32(ref rgba); colorStops[i] = new ColorStop(position, color); - coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", color, position); + coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", rgba.ToHex(), position); } FormattableString variant = $"({startX},{startY})_TO_({endX},{endY})__[{coloringVariant}]"; diff --git a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs index 93715c586e..b310c7afc6 100644 --- a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs @@ -54,172 +54,225 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void ImageShouldBeFloodFilledWithPercent10() { - this.Test("Percent10", Rgba32.Blue, Brushes.Percent10(Rgba32.HotPink, Rgba32.LimeGreen), + this.Test( + "Percent10", + Rgba32.Blue, + Brushes.Percent10(Rgba32.HotPink, Rgba32.LimeGreen), new[,] - { - { Rgba32.HotPink , Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen}, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen}, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink , Rgba32.LimeGreen}, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen} - }); + { + { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen } + }); } [Fact] public void ImageShouldBeFloodFilledWithPercent10Transparent() { - Test("Percent10_Transparent", Rgba32.Blue, Brushes.Percent10(Rgba32.HotPink), - new Rgba32[,] { - { Rgba32.HotPink , Rgba32.Blue, Rgba32.Blue, Rgba32.Blue}, - { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue}, - { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink , Rgba32.Blue}, - { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue} - }); + this.Test( + "Percent10_Transparent", + Rgba32.Blue, + Brushes.Percent10(Rgba32.HotPink), + new Rgba32[,] + { + { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue } + }); } [Fact] public void ImageShouldBeFloodFilledWithPercent20() { - Test("Percent20", Rgba32.Blue, Brushes.Percent20(Rgba32.HotPink, Rgba32.LimeGreen), - new Rgba32[,] { - { Rgba32.HotPink , Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen}, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink , Rgba32.LimeGreen}, - { Rgba32.HotPink , Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen}, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink , Rgba32.LimeGreen} - }); + this.Test( + "Percent20", + Rgba32.Blue, + Brushes.Percent20(Rgba32.HotPink, Rgba32.LimeGreen), + new Rgba32[,] + { + { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen }, + { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen } + }); } [Fact] public void ImageShouldBeFloodFilledWithPercent20_transparent() { - Test("Percent20_Transparent", Rgba32.Blue, Brushes.Percent20(Rgba32.HotPink), - new Rgba32[,] { - { Rgba32.HotPink , Rgba32.Blue, Rgba32.Blue, Rgba32.Blue}, - { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink , Rgba32.Blue}, - { Rgba32.HotPink , Rgba32.Blue, Rgba32.Blue, Rgba32.Blue}, - { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink , Rgba32.Blue} - }); + this.Test( + "Percent20_Transparent", + Rgba32.Blue, + Brushes.Percent20(Rgba32.HotPink), + new Rgba32[,] + { + { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue }, + { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue } + }); } [Fact] public void ImageShouldBeFloodFilledWithHorizontal() { - Test("Horizontal", Rgba32.Blue, Brushes.Horizontal(Rgba32.HotPink, Rgba32.LimeGreen), - new Rgba32[,] { - { Rgba32.LimeGreen , Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen}, - { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink , Rgba32.HotPink}, - { Rgba32.LimeGreen , Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen}, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen , Rgba32.LimeGreen} - }); + this.Test( + "Horizontal", + Rgba32.Blue, + Brushes.Horizontal(Rgba32.HotPink, Rgba32.LimeGreen), + new Rgba32[,] + { + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen } + }); } [Fact] public void ImageShouldBeFloodFilledWithHorizontal_transparent() { - Test("Horizontal_Transparent", Rgba32.Blue, Brushes.Horizontal(Rgba32.HotPink), - new Rgba32[,] { - { Rgba32.Blue , Rgba32.Blue, Rgba32.Blue, Rgba32.Blue}, - { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink , Rgba32.HotPink}, - { Rgba32.Blue , Rgba32.Blue, Rgba32.Blue, Rgba32.Blue}, - { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue , Rgba32.Blue} - }); + this.Test( + "Horizontal_Transparent", + Rgba32.Blue, + Brushes.Horizontal(Rgba32.HotPink), + new Rgba32[,] + { + { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue } + }); } - - [Fact] public void ImageShouldBeFloodFilledWithMin() { - Test("Min", Rgba32.Blue, Brushes.Min(Rgba32.HotPink, Rgba32.LimeGreen), - new Rgba32[,] { - { Rgba32.LimeGreen , Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen}, - { Rgba32.LimeGreen , Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen}, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen , Rgba32.LimeGreen}, - { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink , Rgba32.HotPink} - }); + this.Test( + "Min", + Rgba32.Blue, + Brushes.Min(Rgba32.HotPink, Rgba32.LimeGreen), + new Rgba32[,] + { + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink } + }); } [Fact] public void ImageShouldBeFloodFilledWithMin_transparent() { - Test("Min_Transparent", Rgba32.Blue, Brushes.Min(Rgba32.HotPink), - new Rgba32[,] { - { Rgba32.Blue , Rgba32.Blue, Rgba32.Blue, Rgba32.Blue}, - { Rgba32.Blue , Rgba32.Blue, Rgba32.Blue, Rgba32.Blue}, - { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue , Rgba32.Blue}, - { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink , Rgba32.HotPink}, - }); + this.Test( + "Min_Transparent", + Rgba32.Blue, + Brushes.Min(Rgba32.HotPink), + new Rgba32[,] + { + { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink }, + }); } [Fact] public void ImageShouldBeFloodFilledWithVertical() { - Test("Vertical", Rgba32.Blue, Brushes.Vertical(Rgba32.HotPink, Rgba32.LimeGreen), - new Rgba32[,] { - { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen}, - { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen}, - { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen}, - { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen} - }); + this.Test( + "Vertical", + Rgba32.Blue, + Brushes.Vertical(Rgba32.HotPink, Rgba32.LimeGreen), + new Rgba32[,] + { + { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen } + }); } [Fact] public void ImageShouldBeFloodFilledWithVertical_transparent() { - Test("Vertical_Transparent", Rgba32.Blue, Brushes.Vertical(Rgba32.HotPink), - new Rgba32[,] { - { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue}, - { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue}, - { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue}, - { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue} - }); + this.Test( + "Vertical_Transparent", + Rgba32.Blue, + Brushes.Vertical(Rgba32.HotPink), + new Rgba32[,] + { + { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue } + }); } [Fact] public void ImageShouldBeFloodFilledWithForwardDiagonal() { - Test("ForwardDiagonal", Rgba32.Blue, Brushes.ForwardDiagonal(Rgba32.HotPink, Rgba32.LimeGreen), - new Rgba32[,] { - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink}, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen}, - { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen}, - { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen} - }); + this.Test( + "ForwardDiagonal", + Rgba32.Blue, + Brushes.ForwardDiagonal(Rgba32.HotPink, Rgba32.LimeGreen), + new Rgba32[,] + { + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen } + }); } [Fact] public void ImageShouldBeFloodFilledWithForwardDiagonal_transparent() { - Test("ForwardDiagonal_Transparent", Rgba32.Blue, Brushes.ForwardDiagonal(Rgba32.HotPink), - new Rgba32[,] { - { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink}, - { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue}, - { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue}, - { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue} - }); + this.Test( + "ForwardDiagonal_Transparent", + Rgba32.Blue, + Brushes.ForwardDiagonal(Rgba32.HotPink), + new Rgba32[,] + { + { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue } + }); } [Fact] public void ImageShouldBeFloodFilledWithBackwardDiagonal() { - Test("BackwardDiagonal", Rgba32.Blue, Brushes.BackwardDiagonal(Rgba32.HotPink, Rgba32.LimeGreen), - new Rgba32[,] { - { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen}, - { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen}, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen}, - { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink} - }); + this.Test( + "BackwardDiagonal", + Rgba32.Blue, + Brushes.BackwardDiagonal(Rgba32.HotPink, Rgba32.LimeGreen), + new Rgba32[,] + { + { Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen }, + { Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink } + }); } [Fact] public void ImageShouldBeFloodFilledWithBackwardDiagonal_transparent() { - Test("BackwardDiagonal_Transparent", Rgba32.Blue, Brushes.BackwardDiagonal(Rgba32.HotPink), - new Rgba32[,] { - { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue}, - { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue}, - { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue}, - { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink} - }); + this.Test( + "BackwardDiagonal_Transparent", + Rgba32.Blue, + Brushes.BackwardDiagonal(Rgba32.HotPink), + new Rgba32[,] + { + { Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue }, + { Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink } + }); } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs index e86d41f574..32f723e72a 100644 --- a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs @@ -1,20 +1,21 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; + using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Primitives; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; using SixLabors.Shapes; + using Xunit; + // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Drawing { - using System; - - using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - using SixLabors.Primitives; - [GroupOutput("Drawing")] public class FillSolidBrushTests { @@ -55,7 +56,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Theory] [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, "Blue")] [WithSolidFilledImages(16, 16, "Yellow", PixelTypes.Rgba32, "Khaki")] - public void WhenColorIsOpaque_OverridePreviousColor(TestImageProvider provider, string newColorName) + public void WhenColorIsOpaque_OverridePreviousColor( + TestImageProvider provider, + string newColorName) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) @@ -63,7 +66,11 @@ namespace SixLabors.ImageSharp.Tests.Drawing TPixel color = TestUtils.GetPixelOfNamedColor(newColorName); image.Mutate(c => c.Fill(color)); - image.DebugSave(provider, newColorName, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); + image.DebugSave( + provider, + newColorName, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); image.ComparePixelBufferTo(color); } } @@ -84,7 +91,12 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Theory] [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 5, 7, 3, 8)] [WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 8, 5, 6, 4)] - public void FillRegion_WorksOnWrappedMemoryImage(TestImageProvider provider, int x0, int y0, int w, int h) + public void FillRegion_WorksOnWrappedMemoryImage( + TestImageProvider provider, + int x0, + int y0, + int w, + int h) where TPixel : struct, IPixel { FormattableString testDetails = $"(x{x0},y{y0},w{w},h{h})"; @@ -105,27 +117,22 @@ namespace SixLabors.ImageSharp.Tests.Drawing { false, "Blue", 1.0f, PixelColorBlendingMode.Normal, 0.5f }, { false, "Green", 0.5f, PixelColorBlendingMode.Normal, 0.3f }, { false, "HotPink", 0.8f, PixelColorBlendingMode.Normal, 0.8f }, - { false, "Blue", 0.5f, PixelColorBlendingMode.Multiply, 1.0f }, { false, "Blue", 1.0f, PixelColorBlendingMode.Multiply, 0.5f }, { false, "Green", 0.5f, PixelColorBlendingMode.Multiply, 0.3f }, { false, "HotPink", 0.8f, PixelColorBlendingMode.Multiply, 0.8f }, - { false, "Blue", 0.5f, PixelColorBlendingMode.Add, 1.0f }, { false, "Blue", 1.0f, PixelColorBlendingMode.Add, 0.5f }, { false, "Green", 0.5f, PixelColorBlendingMode.Add, 0.3f }, { false, "HotPink", 0.8f, PixelColorBlendingMode.Add, 0.8f }, - { true, "Blue", 0.5f, PixelColorBlendingMode.Normal, 1.0f }, { true, "Blue", 1.0f, PixelColorBlendingMode.Normal, 0.5f }, { true, "Green", 0.5f, PixelColorBlendingMode.Normal, 0.3f }, { true, "HotPink", 0.8f, PixelColorBlendingMode.Normal, 0.8f }, - { true, "Blue", 0.5f, PixelColorBlendingMode.Multiply, 1.0f }, { true, "Blue", 1.0f, PixelColorBlendingMode.Multiply, 0.5f }, { true, "Green", 0.5f, PixelColorBlendingMode.Multiply, 0.3f }, { true, "HotPink", 0.8f, PixelColorBlendingMode.Multiply, 0.8f }, - { true, "Blue", 0.5f, PixelColorBlendingMode.Add, 1.0f }, { true, "Blue", 1.0f, PixelColorBlendingMode.Add, 0.5f }, { true, "Green", 0.5f, PixelColorBlendingMode.Add, 0.3f }, @@ -155,8 +162,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing var options = new GraphicsOptions(false) { - ColorBlendingMode = blenderMode, - BlendPercentage = blendPercentage + ColorBlendingMode = blenderMode, BlendPercentage = blendPercentage }; if (triggerFillRegion) @@ -185,11 +191,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); - PixelBlender blender = PixelOperations.Instance.GetPixelBlender(blenderMode, PixelAlphaCompositionMode.SrcOver); + PixelBlender blender = PixelOperations.Instance.GetPixelBlender( + blenderMode, + PixelAlphaCompositionMode.SrcOver); TPixel expectedPixel = blender.Blend(bgColor, fillColor, blendPercentage); image.ComparePixelBufferTo(expectedPixel); } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index d887d23ade..b9f855cf12 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -28,10 +28,14 @@ namespace SixLabors.ImageSharp.Tests { TestImages.Bmp.RLE, 2835, 2835, PixelResolutionUnit.PixelsPerMeter } }; - public BmpEncoderTests(ITestOutputHelper output) + public static readonly TheoryData BmpBitsPerPixelFiles = + new TheoryData { - this.Output = output; - } + { TestImages.Bmp.Car, BmpBitsPerPixel.Pixel24 }, + { TestImages.Bmp.Bit32Rgb, BmpBitsPerPixel.Pixel32 } + }; + + public BmpEncoderTests(ITestOutputHelper output) => this.Output = output; private ITestOutputHelper Output { get; } @@ -61,13 +65,34 @@ namespace SixLabors.ImageSharp.Tests } [Theory] - [WithTestPatternImages(nameof(BitsPerPixel), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] - public void Encode_IsNotBoundToSinglePixelType(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel + [MemberData(nameof(BmpBitsPerPixelFiles))] + public void Encode_PreserveBitsPerPixel(string imagePath, BmpBitsPerPixel bmpBitsPerPixel) { - TestBmpEncoderCore(provider, bitsPerPixel); + var options = new BmpEncoder(); + + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateImage()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + BmpMetaData meta = output.MetaData.GetFormatMetaData(BmpFormat.Instance); + + 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 : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); + [Theory] [WithTestPatternImages(nameof(BitsPerPixel), 48, 24, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel), 47, 8, PixelTypes.Rgba32)] @@ -75,10 +100,7 @@ namespace SixLabors.ImageSharp.Tests [WithSolidFilledImages(nameof(BitsPerPixel), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel), 7, 5, PixelTypes.Rgba32)] public void Encode_WorksWithDifferentSizes(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel - { - TestBmpEncoderCore(provider, bitsPerPixel); - } + where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); private static void TestBmpEncoderCore(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) where TPixel : struct, IPixel diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetaDataTests.cs new file mode 100644 index 0000000000..991768a11a --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetaDataTests.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Bmp; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Bmp +{ + public class BmpMetaDataTests + { + [Fact] + public void CloneIsDeep() + { + var meta = new BmpMetaData() { BitsPerPixel = BmpBitsPerPixel.Pixel24 }; + var clone = (BmpMetaData)meta.DeepClone(); + + clone.BitsPerPixel = BmpBitsPerPixel.Pixel32; + + Assert.False(meta.BitsPerPixel.Equals(clone.BitsPerPixel)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 23b806767c..1d21c65fda 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -14,10 +14,9 @@ namespace SixLabors.ImageSharp.Tests { using System; using System.Reflection; - - using SixLabors.Memory; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; + using SixLabors.Memory; public class GeneralFormatTests : FileTestBase { @@ -44,7 +43,7 @@ namespace SixLabors.ImageSharp.Tests using (Image image = file.CreateImage()) { string filename = path + "/" + file.FileNameWithoutExtension + ".txt"; - File.WriteAllText(filename, image.ToBase64String(ImageFormats.Png)); + File.WriteAllText(filename, image.ToBase64String(PngFormat.Instance)); } } } @@ -58,7 +57,8 @@ namespace SixLabors.ImageSharp.Tests { using (Image image = file.CreateImage()) { - image.Save($"{path}/{file.FileName}"); + var encoder = new PngEncoder { Quantizer = new WuQuantizer(KnownDiffusers.JarvisJudiceNinke, 256), ColorType = PngColorType.Palette }; + image.Save($"{path}/{file.FileName}.png", encoder); } } } @@ -135,15 +135,15 @@ namespace SixLabors.ImageSharp.Tests foreach (TestFile file in Files) { byte[] serialized; - using (Image image = Image.Load(file.Bytes, out IImageFormat mimeType)) - using (MemoryStream memoryStream = new MemoryStream()) + 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 (Image image2 = Image.Load(serialized)) + using (var image2 = Image.Load(serialized)) { image2.Save($"{path}/{file.FileName}"); } @@ -169,14 +169,14 @@ namespace SixLabors.ImageSharp.Tests [InlineData(10, 100, "jpg")] public void CanIdentifyImageLoadedFromBytes(int width, int height, string format) { - using (Image image = Image.LoadPixelData(new Rgba32[width * height], width, height)) + using (var image = Image.LoadPixelData(new Rgba32[width * height], width, height)) { using (var memoryStream = new MemoryStream()) { image.Save(memoryStream, GetEncoder(format)); memoryStream.Position = 0; - var imageInfo = Image.Identify(memoryStream); + IImageInfo imageInfo = Image.Identify(memoryStream); Assert.Equal(imageInfo.Width, width); Assert.Equal(imageInfo.Height, height); diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index e9104ef8d9..c5c971962c 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -55,10 +55,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif [MemberData(nameof(RatioFiles))] public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) { - var options = new GifEncoder() - { - IgnoreMetadata = false - }; + var options = new GifEncoder(); var testFile = TestFile.Create(imagePath); using (Image input = testFile.CreateImage()) @@ -82,10 +79,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif [Fact] public void Encode_IgnoreMetadataIsFalse_CommentsAreWritten() { - var options = new GifEncoder() - { - IgnoreMetadata = false - }; + var options = new GifEncoder(); var testFile = TestFile.Create(TestImages.Gif.Rings); @@ -109,15 +103,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif [Fact] public void Encode_IgnoreMetadataIsTrue_CommentsAreNotWritten() { - var options = new GifEncoder() - { - IgnoreMetadata = true - }; + var options = new GifEncoder(); var testFile = TestFile.Create(TestImages.Gif.Rings); using (Image input = testFile.CreateImage()) { + input.MetaData.Properties.Clear(); using (var memStream = new MemoryStream()) { input.SaveAsGif(memStream, options); @@ -179,5 +171,49 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif Assert.True(fileInfoGlobal.Length < fileInfoLocal.Length); } } + + [Fact] + public void NonMutatingEncodePreservesPaletteCount() + { + 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.GetFormatMetaData(GifFormat.Instance); + GifFrameMetaData frameMetaData = image.Frames.RootFrame.MetaData.GetFormatMetaData(GifFormat.Instance); + GifColorTableMode colorMode = metaData.ColorTableMode; + var encoder = new GifEncoder() + { + ColorTableMode = colorMode, + Quantizer = new OctreeQuantizer(frameMetaData.ColorTableLength) + }; + + image.Save(outStream, encoder); + outStream.Position = 0; + + outStream.Position = 0; + var clone = Image.Load(outStream); + + GifMetaData cloneMetaData = clone.MetaData.GetFormatMetaData(GifFormat.Instance); + Assert.Equal(metaData.ColorTableMode, cloneMetaData.ColorTableMode); + + // Gifiddle and Cyotek GifInfo say this image has 64 colors. + Assert.Equal(64, frameMetaData.ColorTableLength); + + for (int i = 0; i < image.Frames.Count; i++) + { + GifFrameMetaData ifm = image.Frames[i].MetaData.GetFormatMetaData(GifFormat.Instance); + GifFrameMetaData cifm = clone.Frames[i].MetaData.GetFormatMetaData(GifFormat.Instance); + + 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 new file mode 100644 index 0000000000..a39fc47b40 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Gif/GifFrameMetaDataTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Gif; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Gif +{ + public class GifFrameMetaDataTests + { + [Fact] + public void CloneIsDeep() + { + var meta = new GifFrameMetaData() + { + FrameDelay = 1, + DisposalMethod = GifDisposalMethod.RestoreToBackground, + ColorTableLength = 2 + }; + + var clone = (GifFrameMetaData)meta.DeepClone(); + + 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)); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs new file mode 100644 index 0000000000..29db32b4ab --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Gif/GifMetaDataTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Gif; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Gif +{ + public class GifMetaDataTests + { + [Fact] + public void CloneIsDeep() + { + var meta = new GifMetaData() + { + RepeatCount = 1, + ColorTableMode = GifColorTableMode.Global, + GlobalColorTableLength = 2 + }; + + 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)); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs index 2790b1a576..dc0da5e2db 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs @@ -12,10 +12,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif [Fact] public void TestPackedValue() { - Assert.Equal(0, GifGraphicControlExtension.GetPackedValue(DisposalMethod.Unspecified, false, false)); - Assert.Equal(11, GifGraphicControlExtension.GetPackedValue(DisposalMethod.RestoreToBackground, true, true)); - Assert.Equal(4, GifGraphicControlExtension.GetPackedValue(DisposalMethod.NotDispose, false, false)); - Assert.Equal(14, GifGraphicControlExtension.GetPackedValue(DisposalMethod.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)); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs index 4ef4c12d97..6a90c0c277 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs @@ -12,13 +12,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif [Fact] public void TestPackedValue() { - Assert.Equal(128, GifImageDescriptor.GetPackedValue(true, false, false, 1)); // localColorTable - Assert.Equal(64, GifImageDescriptor.GetPackedValue(false, true, false, 1)); // interfaceFlag - Assert.Equal(32, GifImageDescriptor.GetPackedValue(false, false, true, 1)); // sortFlag - Assert.Equal(224, GifImageDescriptor.GetPackedValue(true, true, true, 1)); // all - Assert.Equal(7, GifImageDescriptor.GetPackedValue(false, false, false, 8)); - Assert.Equal(227, GifImageDescriptor.GetPackedValue(true, true, true, 4)); - Assert.Equal(231, 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)); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs index f10a4ce842..c2100c302f 100644 --- a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs +++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs @@ -2,17 +2,15 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Generic; using System.IO; using System.Linq; +using Moq; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Gif; -using Moq; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -27,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests { this.DefaultFormatsManager = Configuration.CreateDefaultInstance().ImageFormatsManager; this.FormatsManagerEmpty = new ImageFormatManager(); - } + } [Fact] public void IfAutoloadWellKnownFormatsIsTrueAllFormatsAreLoaded() @@ -61,7 +59,7 @@ namespace SixLabors.ImageSharp.Tests }); Assert.Throws(() => { - this.DefaultFormatsManager.SetEncoder(ImageFormats.Bmp, null); + this.DefaultFormatsManager.SetEncoder(BmpFormat.Instance, null); }); Assert.Throws(() => { @@ -78,7 +76,7 @@ namespace SixLabors.ImageSharp.Tests }); Assert.Throws(() => { - this.DefaultFormatsManager.SetDecoder(ImageFormats.Bmp, null); + this.DefaultFormatsManager.SetDecoder(BmpFormat.Instance, null); }); Assert.Throws(() => { @@ -114,7 +112,7 @@ namespace SixLabors.ImageSharp.Tests IImageDecoder found2 = this.FormatsManagerEmpty.FindDecoder(TestFormat.GlobalTestFormat); Assert.Equal(decoder2, found2); Assert.NotEqual(found, found2); - } + } [Fact] public void AddFormatCallsConfig() @@ -125,5 +123,24 @@ namespace SixLabors.ImageSharp.Tests provider.Verify(x => x.Configure(config)); } + + [Fact] + public void DetectFormatAllocatesCleanBuffer() + { + 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); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index a80312766c..8e30eb9e5d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -8,6 +8,7 @@ using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; +using SixLabors.ImageSharp.Tests.Colorspaces.Conversion; using SixLabors.ImageSharp.Memory; using Xunit; @@ -17,7 +18,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public class JpegColorConverterTests { - private const float Precision = 0.1f / 255; + private const float Precision = 0.1F / 255; + + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(Precision); public static readonly TheoryData CommonConversionData = new TheoryData @@ -48,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg seed); } - private static void ValidateYCbCr(JpegColorConverter.ComponentValues values, Vector4[] result, int i) + private static void ValidateYCbCr(in JpegColorConverter.ComponentValues values, Vector4[] result, int i) { float y = values.Component0[i]; float cb = values.Component1[i]; @@ -59,7 +62,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); var expected = ColorSpaceConverter.ToRgb(ycbcr); - Assert.True(actual.AlmostEquals(expected, Precision), $"{actual} != {expected}"); + Assert.Equal(expected, actual, ColorSpaceComparer); Assert.Equal(1, rgba.W); } @@ -182,7 +185,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); var expected = new Rgb(v.X, v.Y, v.Z); - Assert.True(actual.AlmostEquals(expected, Precision)); + Assert.Equal(expected, actual); Assert.Equal(1, rgba.W); } } @@ -204,7 +207,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); var expected = new Rgb(y / 255F, y / 255F, y / 255F); - Assert.True(actual.AlmostEquals(expected, Precision)); + Assert.Equal(expected, actual, ColorSpaceComparer); Assert.Equal(1, rgba.W); } } @@ -228,7 +231,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); var expected = new Rgb(r / 255F, g / 255F, b / 255F); - Assert.True(actual.AlmostEquals(expected, Precision)); + Assert.Equal(expected, actual, ColorSpaceComparer); Assert.Equal(1, rgba.W); } } @@ -266,7 +269,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); var expected = new Rgb(v.X, v.Y, v.Z); - Assert.True(actual.AlmostEquals(expected, Precision)); + Assert.Equal(expected, actual, ColorSpaceComparer); Assert.Equal(1, rgba.W); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs index 3c98d5be72..6bc559978c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs @@ -21,7 +21,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Baseline.Bad.BadEOF, TestImages.Jpeg.Issues.MultiHuffmanBaseline394, TestImages.Jpeg.Baseline.MultiScanBaselineCMYK, - TestImages.Jpeg.Baseline.Bad.BadRST + 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 }; public static string[] ProgressiveTestJpegs = @@ -39,6 +44,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Issues.BadRstProgressive518, TestImages.Jpeg.Issues.MissingFF00ProgressiveBedroom159, TestImages.Jpeg.Issues.DhtHasWrongLength624, + TestImages.Jpeg.Issues.OrderedInterleavedProgressive723A, + TestImages.Jpeg.Issues.OrderedInterleavedProgressive723B, + TestImages.Jpeg.Issues.OrderedInterleavedProgressive723C }; /// @@ -50,7 +58,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Issues.MissingFF00ProgressiveBedroom159 }; - private static readonly Dictionary CustomToleranceValues = + private static readonly Dictionary CustomToleranceValues = new Dictionary { // Baseline: diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs index 2e67c06c18..4810985f11 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs @@ -49,6 +49,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } }; + public static readonly TheoryData QualityFiles = + new TheoryData + { + { TestImages.Jpeg.Baseline.Calliphora, 80}, + { TestImages.Jpeg.Progressive.Fb, 75 } + }; + [Theory] [MemberData(nameof(MetaDataTestData))] public void MetaDataIsParsedCorrectly( @@ -101,6 +108,36 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } + [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(Configuration.Default, stream); + JpegMetaData meta = image.MetaData.GetFormatMetaData(JpegFormat.Instance); + 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)) + { + var decoder = new JpegDecoder(); + using (Image image = decoder.Decode(Configuration.Default, stream)) + { + JpegMetaData meta = image.MetaData.GetFormatMetaData(JpegFormat.Instance); + Assert.Equal(quality, meta.Quality); + } + } + } + private static void TestImageInfo(string imagePath, IImageDecoder decoder, bool useIdentify, Action test) { var testFile = TestFile.Create(imagePath); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 4ae955c323..5977e59cf3 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -48,6 +48,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Issues.BadZigZagProgressive385, TestImages.Jpeg.Issues.NoEoiProgressive517, TestImages.Jpeg.Issues.BadRstProgressive518, + TestImages.Jpeg.Issues.InvalidEOI695, + TestImages.Jpeg.Issues.ExifResizeOutOfRange696 }; return !TestEnvironment.Is64BitProcess && largeImagesToSkipOn32Bit.Contains(provider.SourceFileOrDescription); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index a31ae37b75..598d99274a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -14,6 +14,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public class JpegEncoderTests { + public static readonly TheoryData QualityFiles = + new TheoryData + { + { TestImages.Jpeg.Baseline.Calliphora, 80}, + { TestImages.Jpeg.Progressive.Fb, 75 } + }; + public static readonly TheoryData BitsPerPixel_Quality = new TheoryData { @@ -34,6 +41,29 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } }; + [Theory] + [MemberData(nameof(QualityFiles))] + public void Encode_PreserveQuality(string imagePath, int quality) + { + var options = new JpegEncoder(); + + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateImage()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + JpegMetaData meta = output.MetaData.GetFormatMetaData(JpegFormat.Instance); + Assert.Equal(quality, meta.Quality); + } + } + } + } + [Theory] [WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 73, 71, PixelTypes.Rgba32)] @@ -43,18 +73,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)] public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegSubsample subsample, int quality) - where TPixel : struct, IPixel - { - TestJpegEncoderCore(provider, subsample, quality); - } + where TPixel : struct, IPixel => TestJpegEncoderCore(provider, subsample, quality); [Theory] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)] public void EncodeBaseline_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegSubsample subsample, int quality) - where TPixel : struct, IPixel - { - TestJpegEncoderCore(provider, subsample, quality); - } + where TPixel : struct, IPixel => TestJpegEncoderCore(provider, subsample, quality); /// /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation @@ -103,38 +127,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void IgnoreMetadata_ControlsIfExifProfileIsWritten(bool ignoreMetaData) - { - var encoder = new JpegEncoder() - { - IgnoreMetadata = ignoreMetaData - }; - - using (Image input = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage()) - { - using (var memStream = new MemoryStream()) - { - input.Save(memStream, encoder); - - memStream.Position = 0; - using (var output = Image.Load(memStream)) - { - if (ignoreMetaData) - { - Assert.Null(output.MetaData.ExifProfile); - } - else - { - Assert.NotNull(output.MetaData.ExifProfile); - } - } - } - } - } - [Fact] public void Quality_0_And_1_Are_Identical() { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetaDataTests.cs new file mode 100644 index 0000000000..431de4be31 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetaDataTests.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Jpeg; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + public class JpegMetaDataTests + { + [Fact] + public void CloneIsDeep() + { + var meta = new JpegMetaData() { Quality = 50 }; + var clone = (JpegMetaData)meta.DeepClone(); + + clone.Quality = 99; + + Assert.False(meta.Quality.Equals(clone.Quality)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs index 35652dd6b5..e4cd06ab1b 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png private static PngChunkType GetType(string text) { - return (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(Encoding.UTF8.GetBytes(text)); + return (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(Encoding.ASCII.GetBytes(text)); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index dc29b19497..2a7d696164 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png { // Needs a minimum length of 9 for pHYs chunk. memStream.Write(new byte[] { 0, 0, 0, 9 }, 0, 4); - memStream.Write(Encoding.GetEncoding("ASCII").GetBytes(chunkName), 0, 4); // 4 bytes chunk header + 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 } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 62de45064a..5d328db361 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -9,7 +9,6 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -19,9 +18,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png { public class PngEncoderTests { - // This is bull. Failing online for no good reason. - // The images are an exact match. Maybe the submodule isn't updating? - private const float ToleranceThresholdForPaletteEncoder = 1.3F / 100; + public static readonly TheoryData PngBitDepthFiles = + new TheoryData + { + { TestImages.Png.Rgb48Bpp, PngBitDepth.Bit16 }, + { TestImages.Png.Bpp1, PngBitDepth.Bit1 } + }; /// /// All types except Palette @@ -130,19 +132,32 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } [Theory] - [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.Rgb)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.RgbWithAlpha)] - [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.RgbWithAlpha)] - public void WorksWithBitDepth16(TestImageProvider provider, PngColorType pngColorType) + [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 : struct, IPixel { TestPngEncoderCore( provider, pngColorType, PngFilterMethod.Adaptive, - PngBitDepth.Bit16, + pngBitDepth, appendPngColorType: true, - appendPixelType: true); + appendPixelType: true, + appendPngBitDepth: true); } [Theory] @@ -159,87 +174,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png appendPaletteSize: true); } - private static bool HasAlpha(PngColorType pngColorType) => - pngColorType == PngColorType.GrayscaleWithAlpha || pngColorType == PngColorType.RgbWithAlpha; - - private static void TestPngEncoderCore( - TestImageProvider provider, - PngColorType pngColorType, - PngFilterMethod pngFilterMethod, - PngBitDepth bitDepth, - int compressionLevel = 6, - int paletteSize = 255, - bool appendPngColorType = false, - bool appendPngFilterMethod = false, - bool appendPixelType = false, - bool appendCompressionLevel = false, - bool appendPaletteSize = false) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - if (!HasAlpha(pngColorType)) - { - image.Mutate(c => c.MakeOpaque()); - } - - var encoder = new PngEncoder - { - ColorType = pngColorType, - FilterMethod = pngFilterMethod, - CompressionLevel = compressionLevel, - BitDepth = bitDepth, - Quantizer = new WuQuantizer(paletteSize) - }; - - 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 debugInfo = $"{pngColorTypeInfo}{pngFilterMethodInfo}{compressionLevelInfo}{paletteSizeInfo}"; - //string referenceInfo = $"{pngColorTypeInfo}"; - - // Does DebugSave & load reference CompareToReferenceInput(): - string actualOutputFile = ((ITestImageProvider)provider).Utility.SaveTestOutputFile(image, "png", encoder, debugInfo, appendPixelType); - - if (TestEnvironment.IsMono) - { - // There are bugs in mono's System.Drawing implementation, reference decoders are not always reliable! - return; - } - - IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); - string referenceOutputFile = ((ITestImageProvider)provider).Utility.GetReferenceOutputFileName("png", debugInfo, appendPixelType, true); - - bool referenceOutputFileExists = File.Exists(referenceOutputFile); - - using (var actualImage = Image.Load(actualOutputFile, referenceDecoder)) - { - // TODO: Do we still need the reference output files? - Image referenceImage = referenceOutputFileExists - ? Image.Load(referenceOutputFile, referenceDecoder) - : image; - - float paletteToleranceHack = 80f / paletteSize; - paletteToleranceHack = paletteToleranceHack * paletteToleranceHack; - ImageComparer comparer = pngColorType == PngColorType.Palette - ? ImageComparer.Tolerant(ToleranceThresholdForPaletteEncoder * paletteToleranceHack) - : ImageComparer.Exact; - try - { - comparer.VerifySimilarity(referenceImage, actualImage); - } - finally - { - if (referenceOutputFileExists) - { - referenceImage.Dispose(); - } - } - } - } - } - [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32)] public void WritesFileMarker(TestImageProvider provider) @@ -290,5 +224,78 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } } } + + [Theory] + [MemberData(nameof(PngBitDepthFiles))] + public void Encode_PreserveBits(string imagePath, PngBitDepth pngBitDepth) + { + var options = new PngEncoder(); + + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateImage()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + PngMetaData meta = output.MetaData.GetFormatMetaData(PngFormat.Instance); + + Assert.Equal(pngBitDepth, meta.BitDepth); + } + } + } + } + + private static void TestPngEncoderCore( + TestImageProvider provider, + PngColorType pngColorType, + PngFilterMethod pngFilterMethod, + PngBitDepth bitDepth, + int compressionLevel = 6, + int paletteSize = 255, + bool appendPngColorType = false, + bool appendPngFilterMethod = false, + bool appendPixelType = false, + bool appendCompressionLevel = false, + bool appendPaletteSize = false, + bool appendPngBitDepth = false) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + var encoder = new PngEncoder + { + ColorType = pngColorType, + FilterMethod = pngFilterMethod, + CompressionLevel = compressionLevel, + BitDepth = bitDepth, + Quantizer = new WuQuantizer(paletteSize) + }; + + 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 debugInfo = $"{pngColorTypeInfo}{pngFilterMethodInfo}{compressionLevelInfo}{paletteSizeInfo}{pngBitDepthInfo}"; + + 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 + // occurrs within the encoder itself leaving the input image unaffected. + // This means we are benefiting from testing our decoder also. + using (var imageSharpImage = Image.Load(actualOutputFile, new PngDecoder())) + using (var referenceImage = Image.Load(actualOutputFile, referenceDecoder)) + { + ImageComparer.Exact.VerifySimilarity(referenceImage, imageSharpImage); + } + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs new file mode 100644 index 0000000000..a21bb9acbe --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Png/PngMetaDataTests.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Png; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Png +{ + public class PngMetaDataTests + { + [Fact] + public void CloneIsDeep() + { + var meta = new PngMetaData() + { + BitDepth = PngBitDepth.Bit16, + ColorType = PngColorType.GrayscaleWithAlpha, + Gamma = 2 + }; + var clone = (PngMetaData)meta.DeepClone(); + + clone.BitDepth = PngBitDepth.Bit2; + clone.ColorType = PngColorType.Palette; + clone.Gamma = 1; + + Assert.False(meta.BitDepth.Equals(clone.BitDepth)); + Assert.False(meta.ColorType.Equals(clone.ColorType)); + Assert.False(meta.Gamma.Equals(clone.Gamma)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs index 29e9f50aee..1c8341fad6 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs @@ -3,13 +3,13 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Drawing; namespace SixLabors.ImageSharp.Tests { using System; using Xunit; using ImageSharp; + using SixLabors.ImageSharp.Memory; public abstract class PhotometricInterpretationTestBase { @@ -43,27 +43,22 @@ namespace SixLabors.ImageSharp.Tests return output; } - internal static void AssertDecode(Rgba32[][] expectedResult, Action> decodeAction) + internal static void AssertDecode(Rgba32[][] expectedResult, Action> decodeAction) { int resultWidth = expectedResult[0].Length; int resultHeight = expectedResult.Length; Image image = new Image(resultWidth, resultHeight); image.Mutate(x => x.Fill(DefaultColor)); - using (PixelAccessor pixels = image.Lock()) - { - decodeAction(pixels); - } + Buffer2D pixels = image.GetRootFramePixelBuffer(); + decodeAction(pixels); - using (PixelAccessor pixels = image.Lock()) + 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/TiffDecoderIfdEntryTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderIfdEntryTests.cs index 0688129874..64a5b95161 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderIfdEntryTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderIfdEntryTests.cs @@ -12,6 +12,7 @@ namespace SixLabors.ImageSharp.Tests using ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.MetaData.Profiles.Exif; + using SixLabors.ImageSharp.Primitives; public class TiffDecoderIfdEntryTests { diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderMetadataTests.cs index 598d924b67..6766ba80f7 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderMetadataTests.cs @@ -102,7 +102,7 @@ namespace SixLabors.ImageSharp.Tests Image image = new Image(null, 20, 20); decoder.ReadMetadata(ifd, image); - var metadata = image.MetaData.Properties.FirstOrDefault(m => m.Name == metadataName)?.Value; + var metadata = image.MetaData.Properties.FirstOrDefault(m => m.Name == metadataName).Value; Assert.Equal(metadataValue, metadata); } @@ -129,7 +129,7 @@ namespace SixLabors.ImageSharp.Tests Image image = new Image(null, 20, 20); decoder.ReadMetadata(ifd, image); - var metadata = image.MetaData.Properties.FirstOrDefault(m => m.Name == metadataName)?.Value; + var metadata = image.MetaData.Properties.FirstOrDefault(m => m.Name == metadataName).Value; Assert.Null(metadata); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMetadataTests.cs index bb1e351047..c90d77f4a6 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMetadataTests.cs @@ -13,6 +13,7 @@ namespace SixLabors.ImageSharp.Tests using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData.Profiles.Exif; + using SixLabors.ImageSharp.Primitives; public class TiffEncoderMetadataTests { diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffFormatTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffFormatTests.cs index 43428a0e99..a6bcfb9ef5 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffFormatTests.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void FormatProperties_AreAsExpected() { - TiffFormat tiffFormat = new TiffFormat(); + TiffFormat tiffFormat = TiffFormat.Instance; Assert.Equal("TIFF", tiffFormat.Name); Assert.Equal("image/tiff", tiffFormat.DefaultMimeType); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryCreatorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryCreatorTests.cs index f9f3adfe8c..7c0f55ee70 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryCreatorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryCreatorTests.cs @@ -11,6 +11,7 @@ namespace SixLabors.ImageSharp.Tests using ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.MetaData.Profiles.Exif; + using SixLabors.ImageSharp.Primitives; public class TiffIfdEntryCreatorTests { diff --git a/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs b/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs new file mode 100644 index 0000000000..6c2979fe9e --- /dev/null +++ b/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Tests.Helpers +{ + using Xunit; + + public class ImageMathsTests + { + [Fact] + public void FasAbsResultMatchesMath() + { + const int X = -33; + int expected = Math.Abs(X); + + Assert.Equal(expected, ImageMaths.FastAbs(X)); + } + + [Fact] + public void Pow2ResultMatchesMath() + { + const float X = -33; + float expected = (float)Math.Pow(X, 2); + + Assert.Equal(expected, ImageMaths.Pow2(X)); + } + + [Fact] + public void Pow3ResultMatchesMath() + { + const float X = -33; + float expected = (float)Math.Pow(X, 3); + + Assert.Equal(expected, ImageMaths.Pow3(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 = ImageMaths.GreatestCommonDivisor(a, b); + + Assert.Equal(expected, actual); + } + + [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 = ImageMaths.LeastCommonMultiple(a, b); + + Assert.Equal(expected, actual); + } + + // TODO: We need to test all ImageMaths methods! + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs new file mode 100644 index 0000000000..ef6b133f75 --- /dev/null +++ b/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs @@ -0,0 +1,338 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Numerics; +using System.Threading; + +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.ParallelUtils; +using SixLabors.Memory; +using SixLabors.Primitives; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.Helpers +{ + public class ParallelHelperTests + { + private readonly ITestOutputHelper Output; + + public ParallelHelperTests(ITestOutputHelper output) + { + this.Output = output; + } + + /// + /// maxDegreeOfParallelism, minY, maxY, expectedStepLength, expectedLastStepLength + /// + public static TheoryData IterateRows_OverMinimumPixelsLimit_Data = + new TheoryData() + { + { 1, 0, 100, -1, 100 }, + { 2, 0, 9, 5, 4 }, + { 4, 0, 19, 5, 4 }, + { 2, 10, 19, 5, 4 }, + { 4, 0, 200, 50, 50 }, + { 4, 123, 323, 50, 50 }, + { 4, 0, 1201, 301, 298 }, + { 8, 10, 236, 29, 23 } + }; + + [Theory] + [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] + public void IterateRows_OverMinimumPixelsLimit_IntervalsAreCorrect( + int maxDegreeOfParallelism, + int minY, + int maxY, + int expectedStepLength, + int expectedLastStepLength) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + 1, + Configuration.Default.MemoryAllocator); + + var rectangle = new Rectangle(0, minY, 10, maxY - minY); + + int actualNumberOfSteps = 0; + + ParallelHelper.IterateRows( + rectangle, + parallelSettings, + rows => + { + Assert.True(rows.Min >= minY); + Assert.True(rows.Max <= maxY); + + int step = rows.Max - rows.Min; + int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; + + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + }); + + Assert.Equal(maxDegreeOfParallelism, actualNumberOfSteps); + } + + [Theory] + [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] + public void IterateRows_OverMinimumPixelsLimit_ShouldVisitAllRows( + int maxDegreeOfParallelism, + int minY, + int maxY, + int expectedStepLength, + int expectedLastStepLength) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + 1, + Configuration.Default.MemoryAllocator); + + var rectangle = new Rectangle(0, minY, 10, maxY - minY); + + + int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); + int[] actualData = new int[maxY]; + + ParallelHelper.IterateRows( + rectangle, + parallelSettings, + rows => + { + for (int y = rows.Min; y < rows.Max; y++) + { + actualData[y] = y; + } + }); + + Assert.Equal(expectedData, actualData); + } + + [Theory] + [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] + public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit( + int maxDegreeOfParallelism, + int minY, + int maxY, + int expectedStepLength, + int expectedLastStepLength) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + 1, + Configuration.Default.MemoryAllocator); + + var rectangle = new Rectangle(0, minY, 10, maxY - minY); + + var bufferHashes = new ConcurrentBag(); + + int actualNumberOfSteps = 0; + ParallelHelper.IterateRowsWithTempBuffer( + rectangle, + parallelSettings, + (RowInterval rows, Memory buffer) => + { + Assert.True(rows.Min >= minY); + Assert.True(rows.Max <= maxY); + + bufferHashes.Add(buffer.GetHashCode()); + + int step = rows.Max - rows.Min; + int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; + + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + }); + + Assert.Equal(maxDegreeOfParallelism, actualNumberOfSteps); + + int numberOfDifferentBuffers = bufferHashes.Distinct().Count(); + Assert.Equal(actualNumberOfSteps, numberOfDifferentBuffers); + } + + [Theory] + [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] + public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit_ShouldVisitAllRows( + int maxDegreeOfParallelism, + int minY, + int maxY, + int expectedStepLength, + int expectedLastStepLength) + { + var parallelSettings = new ParallelExecutionSettings( + maxDegreeOfParallelism, + 1, + Configuration.Default.MemoryAllocator); + + var rectangle = new Rectangle(0, minY, 10, maxY - minY); + + int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); + int[] actualData = new int[maxY]; + + ParallelHelper.IterateRowsWithTempBuffer( + rectangle, + parallelSettings, + (RowInterval rows, Memory buffer) => + { + for (int y = rows.Min; y < rows.Max; y++) + { + actualData[y] = y; + } + }); + + Assert.Equal(expectedData, actualData); + + } + + public static TheoryData IterateRows_WithEffectiveMinimumPixelsLimit_Data = + new TheoryData() + { + { 2, 200, 50, 2, 1, -1, 2 }, + { 2, 200, 200, 1, 1, -1, 1 }, + { 4, 200, 100, 4, 2, 2, 2 }, + { 4, 300, 100, 8, 3, 3, 2 }, + { 2, 5000, 1, 4500, 1, -1, 4500 }, + { 2, 5000, 1, 5000, 1, -1, 5000 }, + { 2, 5000, 1, 5001, 2, 2501, 2500 }, + }; + + [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 rectangle = new Rectangle(0, 0, width, height); + + int actualNumberOfSteps = 0; + + ParallelHelper.IterateRows( + rectangle, + parallelSettings, + rows => + { + Assert.True(rows.Min >= 0); + Assert.True(rows.Max <= height); + + int step = rows.Max - rows.Min; + int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; + + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + }); + + Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); + } + + [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 rectangle = new Rectangle(0, 0, width, height); + + int actualNumberOfSteps = 0; + ParallelHelper.IterateRowsWithTempBuffer( + rectangle, + parallelSettings, + (RowInterval rows, Memory buffer) => + { + Assert.True(rows.Min >= 0); + Assert.True(rows.Max <= height); + + int step = rows.Max - rows.Min; + int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; + + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + }); + + Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); + } + + public static readonly TheoryData IterateRectangularBuffer_Data = + new TheoryData() + { + { 8, 582, 453, 10, 10, 291, 226 }, // boundary data from DetectEdgesTest.DetectEdges_InBox + { 2, 582, 453, 10, 10, 291, 226 }, + { 16, 582, 453, 10, 10, 291, 226 }, + { 16, 582, 453, 10, 10, 1, 226 }, + { 16, 1, 453, 0, 10, 1, 226 }, + }; + + [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; + + 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 FillRow(int y, Buffer2D buffer) + { + for (int x = rect.Left; x < rect.Right; x++) + { + buffer[x, y] = new Point(x, y); + } + } + + // Fill Expected data: + for (int y = rectY; y < rect.Bottom; y++) + { + FillRow(y, expected); + } + + // Fill actual data using IterateRows: + var settings = new ParallelExecutionSettings(maxDegreeOfParallelism, memoryAllocator); + + ParallelHelper.IterateRows(rect, settings, + rows => + { + this.Output.WriteLine(rows.ToString()); + for (int y = rows.Min; y < rows.Max; y++) + { + FillRow(y, actual); + } + }); + + // Assert: + TestImageExtensions.CompareBuffers(expected.Span, actual.Span); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs b/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs new file mode 100644 index 0000000000..f092da7082 --- /dev/null +++ b/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs @@ -0,0 +1,38 @@ +using System; +using System.Runtime.CompilerServices; + +using SixLabors.ImageSharp.Memory; +using SixLabors.Memory; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Helpers +{ + public class RowIntervalTests + { + [Theory] + [InlineData(10, 20, 5, 10)] + [InlineData(1, 10, 0, 10)] + [InlineData(1, 10, 5, 8)] + [InlineData(1, 1, 0, 1)] + [InlineData(10, 20, 9, 10)] + [InlineData(10, 20, 0, 1)] + public void GetMultiRowSpan(int width, int height, int min, int max) + { + using (Buffer2D buffer = Configuration.Default.MemoryAllocator.Allocate2D(width, height)) + { + var rows = new RowInterval(min, max); + + Span span = buffer.GetMultiRowSpan(rows); + + ref int expected0 = ref buffer.Span[min * width]; + int expectedLength = (max - min) * width; + + ref int actual0 = ref span[0]; + + Assert.Equal(span.Length, expectedLength); + Assert.True(Unsafe.AreSame(ref expected0, ref actual0)); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs b/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs new file mode 100644 index 0000000000..9416be740a --- /dev/null +++ b/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +using System.Numerics; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Helpers +{ + public class Vector4UtilsTests + { + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(30)] + public void Premultiply_VectorSpan(int length) + { + var rnd = new Random(42); + Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); + Vector4[] expected = source.Select(v => { Vector4Utils.Premultiply(ref v); return v; }).ToArray(); + + Vector4Utils.Premultiply(source); + + Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f)); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(30)] + public void UnPremultiply_VectorSpan(int length) + { + var rnd = new Random(42); + Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); + Vector4[] expected = source.Select(v => { Vector4Utils.UnPremultiply(ref v); return v; }).ToArray(); + + Vector4Utils.UnPremultiply(source); + + Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f)); + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index 69572425c9..16d999ad25 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void WrapMemory_CreatedImageIsCorrect() { - Configuration cfg = Configuration.Default.ShallowCopy(); + Configuration cfg = Configuration.Default.Clone(); var metaData = new ImageMetaData(); var array = new Rgba32[25]; diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index ed142ed974..f3c04d5e14 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void Configuration_Width_Height() { - Configuration configuration = Configuration.Default.ShallowCopy(); + Configuration configuration = Configuration.Default.Clone(); using (var image = new Image(configuration, 11, 23)) { @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void Configuration_Width_Height_BackroundColor() { - Configuration configuration = Configuration.Default.ShallowCopy(); + Configuration configuration = Configuration.Default.Clone(); Rgba32 color = Rgba32.Aquamarine; using (var image = new Image(configuration, 11, 23, color)) diff --git a/tests/ImageSharp.Tests/ImageOperationTests.cs b/tests/ImageSharp.Tests/ImageOperationTests.cs index d73eea6870..869882f672 100644 --- a/tests/ImageSharp.Tests/ImageOperationTests.cs +++ b/tests/ImageSharp.Tests/ImageOperationTests.cs @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void CloneCallsImageOperationsProvider_Func_WithDuplicateImage() { - var returned = this.image.Clone(x => x.ApplyProcessor(this.processor)); + Image returned = this.image.Clone(x => x.ApplyProcessor(this.processor)); Assert.True(this.provider.HasCreated(returned)); Assert.Contains(this.processor, this.provider.AppliedOperations(returned).Select(x => x.Processor)); @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void CloneCallsImageOperationsProvider_ListOfProcessors_WithDuplicateImage() { - var returned = this.image.Clone(this.processor); + Image returned = this.image.Clone(this.processor); Assert.True(this.provider.HasCreated(returned)); Assert.Contains(this.processor, this.provider.AppliedOperations(returned).Select(x => x.Processor)); @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void CloneCallsImageOperationsProvider_Func_NotOnOrigional() { - var returned = this.image.Clone(x => x.ApplyProcessor(this.processor)); + Image returned = this.image.Clone(x => x.ApplyProcessor(this.processor)); Assert.False(this.provider.HasCreated(this.image)); Assert.DoesNotContain(this.processor, this.provider.AppliedOperations(this.image).Select(x => x.Processor)); } @@ -82,7 +82,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void CloneCallsImageOperationsProvider_ListOfProcessors_NotOnOrigional() { - var returned = this.image.Clone(this.processor); + Image returned = this.image.Clone(this.processor); Assert.False(this.provider.HasCreated(this.image)); Assert.DoesNotContain(this.processor, this.provider.AppliedOperations(this.image).Select(x => x.Processor)); } @@ -95,9 +95,6 @@ namespace SixLabors.ImageSharp.Tests Assert.Contains(this.processor, operations.Applied.Select(x => x.Processor)); } - public void Dispose() - { - this.image.Dispose(); - } + public void Dispose() => this.image.Dispose(); } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs index 507401398e..8c49039603 100644 --- a/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs +++ b/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.MetaData; using Xunit; @@ -16,14 +15,30 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void ConstructorImageFrameMetaData() { - ImageFrameMetaData metaData = new ImageFrameMetaData(); - metaData.FrameDelay = 42; - metaData.DisposalMethod = DisposalMethod.RestoreToBackground; + const int frameDelay = 42; + const int colorTableLength = 128; + const GifDisposalMethod disposalMethod = GifDisposalMethod.RestoreToBackground; - ImageFrameMetaData clone = new ImageFrameMetaData(metaData); + var metaData = new ImageFrameMetaData(); + GifFrameMetaData gifFrameMetaData = metaData.GetFormatMetaData(GifFormat.Instance); + gifFrameMetaData.FrameDelay = frameDelay; + gifFrameMetaData.ColorTableLength = colorTableLength; + gifFrameMetaData.DisposalMethod = disposalMethod; - Assert.Equal(42, clone.FrameDelay); - Assert.Equal(DisposalMethod.RestoreToBackground, clone.DisposalMethod); + var clone = new ImageFrameMetaData(metaData); + GifFrameMetaData cloneGifFrameMetaData = clone.GetFormatMetaData(GifFormat.Instance); + + Assert.Equal(frameDelay, cloneGifFrameMetaData.FrameDelay); + Assert.Equal(colorTableLength, cloneGifFrameMetaData.ColorTableLength); + Assert.Equal(disposalMethod, cloneGifFrameMetaData.DisposalMethod); + } + + [Fact] + public void CloneIsDeep() + { + var metaData = new ImageFrameMetaData(); + ImageFrameMetaData clone = metaData.DeepClone(); + Assert.False(metaData.GetFormatMetaData(GifFormat.Instance).Equals(clone.GetFormatMetaData(GifFormat.Instance))); } } } diff --git a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs index 8934ebc361..b9619cb3f8 100644 --- a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs +++ b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; +using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; @@ -28,15 +28,37 @@ namespace SixLabors.ImageSharp.Tests metaData.HorizontalResolution = 4; metaData.VerticalResolution = 2; metaData.Properties.Add(imageProperty); - metaData.RepeatCount = 1; - ImageMetaData clone = metaData.Clone(); + ImageMetaData clone = metaData.DeepClone(); Assert.Equal(exifProfile.ToByteArray(), clone.ExifProfile.ToByteArray()); Assert.Equal(4, clone.HorizontalResolution); Assert.Equal(2, clone.VerticalResolution); Assert.Equal(imageProperty, clone.Properties[0]); - Assert.Equal(1, clone.RepeatCount); + } + + [Fact] + public void CloneIsDeep() + { + var metaData = new ImageMetaData(); + + var exifProfile = new ExifProfile(); + var imageProperty = new ImageProperty("name", "value"); + + metaData.ExifProfile = exifProfile; + metaData.HorizontalResolution = 4; + metaData.VerticalResolution = 2; + metaData.Properties.Add(imageProperty); + + 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)); + Assert.False(metaData.Properties.Equals(clone.Properties)); + Assert.False(metaData.GetFormatMetaData(GifFormat.Instance).Equals(clone.GetFormatMetaData(GifFormat.Instance))); } [Fact] @@ -45,13 +67,13 @@ namespace SixLabors.ImageSharp.Tests var metaData = new ImageMetaData(); Assert.Equal(96, metaData.HorizontalResolution); - metaData.HorizontalResolution=0; + metaData.HorizontalResolution = 0; Assert.Equal(96, metaData.HorizontalResolution); - metaData.HorizontalResolution=-1; + metaData.HorizontalResolution = -1; Assert.Equal(96, metaData.HorizontalResolution); - metaData.HorizontalResolution=1; + metaData.HorizontalResolution = 1; Assert.Equal(1, metaData.HorizontalResolution); } diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index 3deb382ea6..c10ffb6c8e 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -67,16 +67,16 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void ConstructorCopy() { - Assert.Throws(() => { new ExifProfile((ExifProfile)null); }); + Assert.Throws(() => ((ExifProfile)null).DeepClone()); ExifProfile profile = GetExifProfile(); - var clone = new ExifProfile(profile); + ExifProfile clone = profile.DeepClone(); TestProfile(clone); profile.SetValue(ExifTag.ColorSpace, (ushort)2); - clone = new ExifProfile(profile); + clone = profile.DeepClone(); TestProfile(clone); } @@ -234,10 +234,12 @@ namespace SixLabors.ImageSharp.Tests exifProfile.SetValue(ExifTag.XResolution, new Rational(200)); exifProfile.SetValue(ExifTag.YResolution, new Rational(300)); - var metaData = new ImageMetaData(); - metaData.ExifProfile = exifProfile; - metaData.HorizontalResolution = 200; - metaData.VerticalResolution = 300; + var metaData = new ImageMetaData + { + ExifProfile = exifProfile, + HorizontalResolution = 200, + VerticalResolution = 300 + }; metaData.HorizontalResolution = 100; @@ -355,11 +357,11 @@ namespace SixLabors.ImageSharp.Tests // act Image reloadedImage = WriteAndRead(image, imageFormat); - + // assert ExifProfile actual = reloadedImage.MetaData.ExifProfile; Assert.NotNull(actual); - foreach(KeyValuePair expectedProfileValue in TestProfileValues) + foreach (KeyValuePair expectedProfileValue in TestProfileValues) { ExifValue actualProfileValue = actual.GetValue(expectedProfileValue.Key); Assert.NotNull(actualProfileValue); @@ -371,7 +373,7 @@ namespace SixLabors.ImageSharp.Tests public void ProfileToByteArray() { // arrange - byte[] exifBytesWithExifCode = ProfileResolver.ExifMarker.Concat(ExifConstants.LittleEndianByteOrderMarker).ToArray(); + byte[] exifBytesWithExifCode = ProfileResolver.ExifMarker.Concat(ExifConstants.LittleEndianByteOrderMarker).ToArray(); byte[] exifBytesWithoutExifCode = ExifConstants.LittleEndianByteOrderMarker; ExifProfile expectedProfile = CreateExifProfile(); var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); @@ -384,7 +386,7 @@ namespace SixLabors.ImageSharp.Tests Assert.NotNull(actualBytes); Assert.NotEmpty(actualBytes); Assert.Equal(exifBytesWithoutExifCode, actualBytes.Take(exifBytesWithoutExifCode.Length).ToArray()); - foreach(ExifTag expectedProfileTag in expectedProfileTags) + foreach (ExifTag expectedProfileTag in expectedProfileTags) { ExifValue actualProfileValue = actualProfile.GetValue(expectedProfileTag); ExifValue expectedProfileValue = expectedProfile.GetValue(expectedProfileTag); @@ -396,7 +398,7 @@ namespace SixLabors.ImageSharp.Tests { var profile = new ExifProfile(); - foreach(KeyValuePair exifProfileValue in TestProfileValues) + foreach (KeyValuePair exifProfileValue in TestProfileValues) { profile.SetValue(exifProfileValue.Key, exifProfileValue.Value); } @@ -416,7 +418,7 @@ namespace SixLabors.ImageSharp.Tests private static Image WriteAndRead(Image image, TestImageWriteFormat imageFormat) { - switch(imageFormat) + switch (imageFormat) { case TestImageWriteFormat.Jpeg: return WriteAndReadJpeg(image); diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccProfileTests.cs index 2e2c92182e..17b5dacc4c 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/ICC/IccProfileTests.cs @@ -9,9 +9,6 @@ namespace SixLabors.ImageSharp.Tests.Icc { public class IccProfileTests { - -#if !NETSTANDARD1_1 - [Theory] [MemberData(nameof(IccTestDataProfiles.ProfileIdTestData), MemberType = typeof(IccTestDataProfiles))] public void CalculateHash_WithByteArray_CalculatesProfileHash(byte[] data, IccProfileId expected) @@ -33,8 +30,6 @@ namespace SixLabors.ImageSharp.Tests.Icc Assert.Equal(data, copy); } -#endif - [Theory] [MemberData(nameof(IccTestDataProfiles.ProfileValidityTestData), MemberType = typeof(IccTestDataProfiles))] public void CheckIsValid_WithProfiles_ReturnsValidity(byte[] data, bool expected) diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs index 8f574ca169..3c562057a8 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { new TestPixel(), typeof(DefaultPixelBlenders.AddSrcOver), PixelColorBlendingMode.Add }, { new TestPixel(), typeof(DefaultPixelBlenders.SubtractSrcOver), PixelColorBlendingMode.Subtract }, { new TestPixel(), typeof(DefaultPixelBlenders.MultiplySrcOver), PixelColorBlendingMode.Multiply }, - }; + }; [Theory] [MemberData(nameof(BlenderMappings))] @@ -43,6 +43,62 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { PixelBlender blender = PixelOperations.Instance.GetPixelBlender(mode, PixelAlphaCompositionMode.SrcOver); Assert.IsType(type, blender); + } + + public static TheoryData ColorBlendingExpectedResults = new TheoryData() + { + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Normal, Rgba32.MidnightBlue }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Screen, new Rgba32(0xFFEEE7FF) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.HardLight, new Rgba32(0xFFC62D32) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Overlay, new Rgba32(0xFFDDCEFF) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Darken, new Rgba32(0xFF701919) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Lighten, new Rgba32(0xFFE1E4FF) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Add, new Rgba32(0xFFFFFDFF) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Subtract, new Rgba32(0xFF71CBE6) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Multiply, new Rgba32(0xFF631619) }, + + }; + + [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()); + } + + public static TheoryData AlphaCompositionExpectedResults = new TheoryData() + { + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Clear, new Rgba32(0) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Xor, new Rgba32(0) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Dest, Rgba32.MistyRose }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestAtop, Rgba32.MistyRose }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestIn, Rgba32.MistyRose }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestOut, new Rgba32(0) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestOver, Rgba32.MistyRose }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Src, Rgba32.MidnightBlue }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcAtop, Rgba32.MidnightBlue }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcIn, Rgba32.MidnightBlue }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOut, new Rgba32(0) }, + { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOver, Rgba32.MidnightBlue }, + }; + + [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); + + // var str = actualResult.Rgba.ToString("X8"); // used to extract expectedResults + + Assert.Equal(actualResult.ToVector4(), expectedResult.ToVector4()); } } } diff --git a/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs b/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs index 7d161d35f7..fa4862293f 100644 --- a/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs +++ b/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs @@ -3,6 +3,7 @@ using System; using SixLabors.ImageSharp.Primitives; +using SixLabors.Primitives; using Xunit; namespace SixLabors.ImageSharp.Tests.Primitives @@ -59,6 +60,7 @@ namespace SixLabors.ImageSharp.Tests.Primitives 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] diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs index a32239d96f..de72f6d09e 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs @@ -10,8 +10,6 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { - using SixLabors.ImageSharp.Advanced; - public class DetectEdgesTest : FileTestBase { private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0456F); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs index c154c8ff3c..c01c3b1bd3 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs @@ -1,24 +1,33 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; + using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.Primitives; + using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { + [GroupOutput("Transforms")] public class CropTest : FileTestBase { [Theory] - [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] - public void ImageShouldCrop(TestImageProvider provider) + [WithTestPatternImages(70, 30, PixelTypes.Rgba32, 0, 0, 70, 30)] + [WithTestPatternImages(30, 70, PixelTypes.Rgba32, 7, 13, 20, 50)] + public void Crop(TestImageProvider provider, int x, int y, int w, int h) where TPixel : struct, IPixel { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Crop(image.Width / 2, image.Height / 2)); - image.DebugSave(provider); - } + 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); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs index d7e7a724c4..3c932bfaa6 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs @@ -5,23 +5,24 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using Xunit; + // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - using SixLabors.ImageSharp.Processing; - + [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) diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs new file mode 100644 index 0000000000..1b4b3cf6a3 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/KernelMapTests.cs @@ -0,0 +1,61 @@ +using System; +using System.IO; +using System.Text; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.Primitives; + +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms +{ + public class KernelMapTests + { + private ITestOutputHelper Output { get; } + + public KernelMapTests(ITestOutputHelper output) + { + this.Output = output; + } + + [Theory(Skip = "TODO: Add asserionts")] + [InlineData(500, 200, nameof(KnownResamplers.Bicubic))] + [InlineData(50, 40, nameof(KnownResamplers.Bicubic))] + [InlineData(40, 30, nameof(KnownResamplers.Bicubic))] + [InlineData(500, 200, nameof(KnownResamplers.Lanczos8))] + [InlineData(100, 80, nameof(KnownResamplers.Lanczos8))] + [InlineData(100, 10, nameof(KnownResamplers.Lanczos8))] + [InlineData(10, 100, nameof(KnownResamplers.Lanczos8))] + public void PrintKernelMap(int srcSize, int destSize, string resamplerName) + { + var resampler = (IResampler)typeof(KnownResamplers).GetProperty(resamplerName).GetValue(null); + + var kernelMap = KernelMap.Calculate(resampler, destSize, srcSize, Configuration.Default.MemoryAllocator); + + var bld = new StringBuilder(); + + foreach (ResizeKernel window in kernelMap.Kernels) + { + Span span = window.GetSpan(); + for (int i = 0; i < window.Length; i++) + { + float value = span[i]; + bld.Append($"{value,7:F4}"); + bld.Append("| "); + } + + bld.AppendLine(); + } + + string outDir = TestEnvironment.CreateOutputDirectory("." + nameof(this.PrintKernelMap)); + string fileName = $@"{outDir}\{resamplerName}_{srcSize}_{destSize}.MD"; + + File.WriteAllText(fileName, bld.ToString()); + + this.Output.WriteLine(bld.ToString()); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs index d5f015404d..e24458d384 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs @@ -1,69 +1,47 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.IO; -using System.Text; - using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Transforms; -using SixLabors.Primitives; +using Xunit; using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { public class ResizeProfilingBenchmarks : MeasureFixture { + public const string SkipText = +#if false + null; +#else + "Benchmark, enable manually!"; +#endif + + private readonly Configuration configuration = Configuration.CreateDefaultInstance(); + public ResizeProfilingBenchmarks(ITestOutputHelper output) : base(output) { + this.configuration.MaxDegreeOfParallelism = 1; } public int ExecutionCount { get; set; } = 50; - - // [Theory] // Benchmark, enable manually! - // [InlineData(100, 100)] - // [InlineData(2000, 2000)] + + [Theory(Skip = SkipText)] + [InlineData(100, 100)] + [InlineData(2000, 2000)] public void ResizeBicubic(int width, int height) { this.Measure(this.ExecutionCount, () => { - using (var image = new Image(width, height)) + using (var image = new Image(this.configuration, width, height)) { - image.Mutate(x => x.Resize(width / 4, height / 4)); + image.Mutate(x => x.Resize(width / 5, height / 5)); } }); } - // [Fact] - public void PrintWeightsData() - { - var size = new Size(500, 500); - var proc = new ResizeProcessor(KnownResamplers.Bicubic, 200, 200, size); - - WeightsBuffer weights = proc.PrecomputeWeights(Configuration.Default.MemoryAllocator, proc.Width, size.Width); - - var bld = new StringBuilder(); - - foreach (WeightsWindow window in weights.Weights) - { - Span span = window.GetWindowSpan(); - for (int i = 0; i < window.Length; i++) - { - float value = span[i]; - bld.Append(value); - bld.Append("| "); - } - - bld.AppendLine(); - } - - File.WriteAllText("BicubicWeights.MD", bld.ToString()); - - // this.Output.WriteLine(bld.ToString()); - } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 746d8da16e..bec64e4d37 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -11,14 +11,15 @@ using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.Primitives; using Xunit; + namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { public class ResizeTests : FileTestBase { public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial }; - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.069F); - + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.07F); + public static readonly TheoryData AllReSamplers = new TheoryData { @@ -52,10 +53,30 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms FormattableString details = $"{name}-{ratio.ToString(System.Globalization.CultureInfo.InvariantCulture)}"; image.DebugSave(provider, details); - image.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.005f), provider, details); + image.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.02f), provider, details); } } + [Theory] + [WithFileCollection(nameof(CommonTestImages), DefaultPixelType, 1)] + [WithFileCollection(nameof(CommonTestImages), DefaultPixelType, 4)] + [WithFileCollection(nameof(CommonTestImages), DefaultPixelType, 8)] + [WithFileCollection(nameof(CommonTestImages), DefaultPixelType, -1)] + public void Resize_WorksWithAllParallelismLevels(TestImageProvider provider, int maxDegreeOfParallelism) + where TPixel : struct, IPixel + { + provider.Configuration.MaxDegreeOfParallelism = + maxDegreeOfParallelism > 0 ? maxDegreeOfParallelism : Environment.ProcessorCount; + + FormattableString details = $"MDP{maxDegreeOfParallelism}"; + + provider.RunValidatingProcessorTest( + x => x.Resize(x.GetCurrentSize() / 2), + details, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + [Theory] [WithTestPatternImages(100, 100, DefaultPixelType)] public void Resize_Compand(TestImageProvider provider) @@ -75,16 +96,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms public void Resize_IsNotBoundToSinglePixelType(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Resize(image.Width / 2, image.Height / 2, true)); - - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); - } + provider.RunValidatingProcessorTest(x => x.Resize(x.GetCurrentSize() / 2), comparer: ValidatorComparer); } - [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void Resize_ThrowsForWrappedMemoryImage(TestImageProvider provider) @@ -105,20 +119,21 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } } - [Theory] - [WithFile(TestImages.Png.Kaboom, DefaultPixelType)] - public void Resize_DoesNotBleedAlphaPixels(TestImageProvider provider) + [WithFile(TestImages.Png.Kaboom, DefaultPixelType, false)] + [WithFile(TestImages.Png.Kaboom, DefaultPixelType, true)] + public void Resize_DoesNotBleedAlphaPixels(TestImageProvider provider, bool compand) where TPixel : struct, IPixel { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Resize(image.Width / 2, image.Height / 2)); - image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); - } - } + string details = compand ? "Compand" : ""; + provider.RunValidatingProcessorTest( + x => x.Resize(x.GetCurrentSize() / 2, compand), + details, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + [Theory] [WithFile(TestImages.Gif.Giphy, DefaultPixelType)] public void Resize_IsAppliedToAllFrames(TestImageProvider provider) @@ -178,6 +193,32 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } } + [Theory] + [WithTestPatternImages(100, 10, DefaultPixelType)] + public void ResizeWidthCannotKeepAspectKeepsOnePixel(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.Resize(5, 0)); + Assert.Equal(5, image.Width); + Assert.Equal(1, image.Height); + } + } + + [Theory] + [WithTestPatternImages(10, 100, DefaultPixelType)] + public void ResizeHeightCannotKeepAspectKeepsOnePixel(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.Resize(0, 5)); + Assert.Equal(1, image.Width); + Assert.Equal(5, image.Height); + } + } + [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeWithCropWidthMode(TestImageProvider provider) @@ -324,7 +365,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [InlineData(2, 0)] public static void BicubicWindowOscillatesCorrectly(float x, float expected) { - var sampler = KnownResamplers.Bicubic; + IResampler sampler = KnownResamplers.Bicubic; float result = sampler.GetValue(x); Assert.Equal(result, expected); @@ -338,7 +379,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [InlineData(2, 0)] public static void TriangleWindowOscillatesCorrectly(float x, float expected) { - var sampler = KnownResamplers.Triangle; + IResampler sampler = KnownResamplers.Triangle; float result = sampler.GetValue(x); Assert.Equal(result, expected); @@ -352,7 +393,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [InlineData(2, 0)] public static void Lanczos3WindowOscillatesCorrectly(float x, float expected) { - var sampler = KnownResamplers.Lanczos3; + IResampler sampler = KnownResamplers.Lanczos3; float result = sampler.GetValue(x); Assert.Equal(result, expected); @@ -366,7 +407,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [InlineData(4, 0)] public static void Lanczos5WindowOscillatesCorrectly(float x, float expected) { - var sampler = KnownResamplers.Lanczos5; + IResampler sampler = KnownResamplers.Lanczos5; float result = sampler.GetValue(x); Assert.Equal(result, expected); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs index c0db205f9e..7801c71432 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs @@ -7,7 +7,8 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { - public class RotateTests : FileTestBase + [GroupOutput("Transforms")] + public class RotateTests { public static readonly TheoryData RotateAngles = new TheoryData @@ -25,29 +26,21 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms }; [Theory] - [WithTestPatternImages(nameof(RotateAngles), 100, 50, DefaultPixelType)] - [WithTestPatternImages(nameof(RotateAngles), 50, 100, DefaultPixelType)] + [WithTestPatternImages(nameof(RotateAngles), 100, 50, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(RotateAngles), 50, 100, PixelTypes.Rgba32)] public void Rotate_WithAngle(TestImageProvider provider, float value) where TPixel : struct, IPixel { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Rotate(value)); - image.DebugSave(provider, value); - } + provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false); } [Theory] - [WithTestPatternImages(nameof(RotateEnumValues), 100, 50, DefaultPixelType)] - [WithTestPatternImages(nameof(RotateEnumValues), 50, 100, DefaultPixelType)] + [WithTestPatternImages(nameof(RotateEnumValues), 100, 50, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(RotateEnumValues), 50, 100, PixelTypes.Rgba32)] public void Rotate_WithRotateTypeEnum(TestImageProvider provider, RotateMode value) where TPixel : struct, IPixel { - using (Image image = provider.GetImage()) - { - image.Mutate(x => x.Rotate(value)); - image.DebugSave(provider, value); - } + provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs index 154167f15f..6731debd36 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; @@ -33,5 +34,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms Assert.Equal(cropRectangle, processor.CropRectangle); } + + [Fact] + public void CropRectangleWithInvalidBoundsThrowsException() + { + var cropRectangle = Rectangle.Inflate(this.SourceBounds(), 5, 5); + Assert.Throws(() => this.operations.Crop(cropRectangle)); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs index 8cf9dd62f5..5190a71e71 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms private static readonly ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.5f, 3); private ITestOutputHelper Output { get; } - + public static readonly TheoryData ResamplerNames = new TheoryData { nameof(KnownResamplers.Bicubic), diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs index 35ffa2bbb6..b037a7a9af 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs @@ -14,20 +14,12 @@ namespace SixLabors.ImageSharp.Tests public static readonly byte[] Header_Random_Id_Array = { -#if !NETSTANDARD1_1 - 0x84, 0xA8, 0xD4, 0x60, 0xC7, 0x16, 0xB6, 0xF3, 0x9B, 0x0E, 0x4C, 0x3D, 0xAB, 0x95, 0xF8, 0x38, -#else - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -#endif + 0x84, 0xA8, 0xD4, 0x60, 0xC7, 0x16, 0xB6, 0xF3, 0x9B, 0x0E, 0x4C, 0x3D, 0xAB, 0x95, 0xF8, 0x38, }; public static readonly byte[] Profile_Random_Id_Array = { -#if !NETSTANDARD1_1 - 0x91, 0x7D, 0x6D, 0xE6, 0x84, 0xC9, 0x58, 0xD1, 0x3B, 0xB0, 0xF5, 0xBB, 0xAD, 0xD1, 0x13, 0x4F, -#else - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -#endif + 0x91, 0x7D, 0x6D, 0xE6, 0x84, 0xC9, 0x58, 0xD1, 0x3B, 0xB0, 0xF5, 0xBB, 0xAD, 0xD1, 0x13, 0x4F, }; public static readonly IccProfileHeader Header_Random_Write = CreateHeaderRandomValue( @@ -35,13 +27,7 @@ namespace SixLabors.ImageSharp.Tests new IccProfileId(1, 2, 3, 4), // should be overwritten "ijkl"); // should be overwritten to "acsp" - public static readonly IccProfileHeader Header_Random_Read = CreateHeaderRandomValue(132, -#if !NETSTANDARD1_1 - Header_Random_Id_Value, -#else - IccProfileId.Zero, -#endif - "acsp"); + public static readonly IccProfileHeader Header_Random_Read = CreateHeaderRandomValue(132, Header_Random_Id_Value, "acsp"); public static readonly byte[] Header_Random_Array = CreateHeaderRandomArray(132, 0, Header_Random_Id_Array); @@ -120,11 +106,7 @@ namespace SixLabors.ImageSharp.Tests ); public static readonly IccProfile Profile_Random_Val = new IccProfile(CreateHeaderRandomValue(168, -#if !NETSTANDARD1_1 Profile_Random_Id_Value, -#else - IccProfileId.Zero, -#endif "acsp"), new IccTagDataEntry[] { @@ -239,4 +221,4 @@ namespace SixLabors.ImageSharp.Tests new object[] { Header_Random_Array, true }, }; } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 1ee3f96757..fdf586c430 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -152,6 +152,13 @@ namespace SixLabors.ImageSharp.Tests 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 static readonly string[] All = Baseline.All.Concat(Progressive.All).ToArray(); @@ -175,6 +182,7 @@ namespace SixLabors.ImageSharp.Tests 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 static readonly string[] All = { @@ -201,6 +209,7 @@ namespace SixLabors.ImageSharp.Tests public const string Cheers = "Gif/cheers.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"; @@ -211,7 +220,7 @@ namespace SixLabors.ImageSharp.Tests public const string BadDescriptorWidth = "Gif/issues/issue403_baddescriptorwidth.gif"; } - public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Ratio4x1, Ratio1x4 }; + public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Leo, Ratio4x1, Ratio1x4 }; } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs index 102a629be9..854e57d8f5 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs @@ -1,96 +1,41 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.Collections.Generic; using System.Numerics; -using SixLabors.ImageSharp.ColorSpaces; -using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSapce; namespace SixLabors.ImageSharp.Tests { - internal struct ApproximateFloatComparer : + /// + /// Allows the approximate comparison of single precision floating point values. + /// + internal readonly struct ApproximateFloatComparer : IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer, - IEqualityComparer + IEqualityComparer { - private readonly float Eps; + private readonly float Epsilon; - public ApproximateFloatComparer(float eps = 1f) - { - this.Eps = eps; - } + /// + /// Initializes a new instance of the class. + /// + /// 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; - return d >= -this.Eps && d <= this.Eps; + return d >= -this.Epsilon && d <= this.Epsilon; } - public int GetHashCode(float obj) - { - throw new InvalidOperationException(); - } + /// + public int GetHashCode(float obj) => obj.GetHashCode(); - public bool Equals(Vector4 a, Vector4 b) - { - return this.Equals(a.X, b.X) && this.Equals(a.Y, b.Y) && this.Equals(a.Z, b.Z) && this.Equals(a.W, b.W); - } - - public int GetHashCode(Vector4 obj) - { - throw new InvalidOperationException(); - } + /// + 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(CieXyChromaticityCoordinates x, CieXyChromaticityCoordinates y) - { - return this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y); - } - - public int GetHashCode(CieXyChromaticityCoordinates obj) - { - throw new NotImplementedException(); - } - - public bool Equals(RgbPrimariesChromaticityCoordinates x, RgbPrimariesChromaticityCoordinates y) - { - return this.Equals(x.R, y.R) && this.Equals(x.G, y.G) && this.Equals(x.B, y.B); - } - - public int GetHashCode(RgbPrimariesChromaticityCoordinates obj) - { - throw new NotImplementedException(); - } - - 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(CieXyz obj) - { - throw new NotImplementedException(); - } - - public bool Equals(RgbWorkingSpace x, RgbWorkingSpace y) - { - if (x is RgbWorkingSpace g1 && y is RgbWorkingSpace g2) - { - return this.Equals(g1.WhitePoint, g2.WhitePoint) - && this.Equals(g1.ChromaticityCoordinates, g2.ChromaticityCoordinates); - } - - return this.Equals(x.WhitePoint, y.WhitePoint) - && this.Equals(x.ChromaticityCoordinates, y.ChromaticityCoordinates); - } - - public int GetHashCode(RgbWorkingSpace obj) - { - throw new NotImplementedException(); - } + /// + public int GetHashCode(Vector4 obj) => obj.GetHashCode(); } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/FloatRoundingComparer.cs b/tests/ImageSharp.Tests/TestUtilities/FloatRoundingComparer.cs deleted file mode 100644 index 27c675823f..0000000000 --- a/tests/ImageSharp.Tests/TestUtilities/FloatRoundingComparer.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Collections.Generic; -using System.Numerics; - -namespace SixLabors.ImageSharp.Tests -{ - /// - /// Allows the comparison of single-precision floating point values by precision. - /// - public struct FloatRoundingComparer : IEqualityComparer, IEqualityComparer - { - /// - /// Initializes a new instance of the struct. - /// - /// The number of decimal places (valid values: 0-7) - public FloatRoundingComparer(int precision) - { - Guard.MustBeBetweenOrEqualTo(precision, 0, 7, nameof(precision)); - this.Precision = precision; - } - - /// - /// Gets the number of decimal places (valid values: 0-7) - /// - public int Precision { get; } - - /// - public bool Equals(float x, float y) - { - float xp = (float)Math.Round(x, this.Precision, MidpointRounding.AwayFromZero); - float yp = (float)Math.Round(y, this.Precision, MidpointRounding.AwayFromZero); - - // ReSharper disable once CompareOfFloatsByEqualityOperator - return xp == yp; - } - - /// - public bool Equals(Vector4 x, Vector4 y) - { - return 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(float obj) - { - unchecked - { - int hashCode = obj.GetHashCode(); - hashCode = (hashCode * 397) ^ this.Precision.GetHashCode(); - return hashCode; - } - } - - /// - public int GetHashCode(Vector4 obj) - { - unchecked - { - int hashCode = obj.GetHashCode(); - hashCode = (hashCode * 397) ^ this.Precision.GetHashCode(); - return hashCode; - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 6475547a06..3ed696c472 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -55,9 +55,20 @@ namespace SixLabors.ImageSharp.Tests public bool Equals(Key other) { - if (other is null) return false; - if (ReferenceEquals(this, other)) return true; - if (!this.commonValues.Equals(other.commonValues)) return false; + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (!this.commonValues.Equals(other.commonValues)) + { + return false; + } if (this.decoderParameters.Count != other.decoderParameters.Count) { @@ -66,8 +77,7 @@ namespace SixLabors.ImageSharp.Tests foreach (KeyValuePair kv in this.decoderParameters) { - object otherVal; - if (!other.decoderParameters.TryGetValue(kv.Key, out otherVal)) + if (!other.decoderParameters.TryGetValue(kv.Key, out object otherVal)) { return false; } @@ -81,26 +91,29 @@ namespace SixLabors.ImageSharp.Tests public override bool Equals(object obj) { - if (obj is null) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; + if (obj is null) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != this.GetType()) + { + return false; + } + return this.Equals((Key)obj); } - public override int GetHashCode() - { - return this.commonValues.GetHashCode(); - } + public override int GetHashCode() => this.commonValues.GetHashCode(); - public static bool operator ==(Key left, Key right) - { - return Equals(left, right); - } + public static bool operator ==(Key left, Key right) => Equals(left, right); - public static bool operator !=(Key left, Key right) - { - return !Equals(left, right); - } + public static bool operator !=(Key left, Key right) => !Equals(left, right); } private static readonly ConcurrentDictionary> cache = new ConcurrentDictionary>(); @@ -111,10 +124,7 @@ namespace SixLabors.ImageSharp.Tests { } - public FileProvider(string filePath) - { - this.FilePath = filePath; - } + public FileProvider(string filePath) => this.FilePath = filePath; /// /// Gets the file path relative to the "~/tests/images" folder @@ -135,12 +145,12 @@ namespace SixLabors.ImageSharp.Tests if (!TestEnvironment.Is64BitProcess) { - return LoadImage(decoder); + return this.LoadImage(decoder); } var key = new Key(this.PixelType, this.FilePath, decoder); - Image cachedImage = cache.GetOrAdd(key, fn => { return LoadImage(decoder); }); + Image cachedImage = cache.GetOrAdd(key, _ => this.LoadImage(decoder)); return cachedImage.Clone(); } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index ab0cc42f93..5b5e4740a3 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Tests public virtual string SourceFileOrDescription => ""; - public Configuration Configuration { get; set; } = Configuration.Default.ShallowCopy(); + public Configuration Configuration { get; set; } = Configuration.CreateDefaultInstance(); /// /// Utility instance to provide informations about the test image & manage input/output diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index 8cfc2472f5..7e942691e9 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -29,13 +29,13 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { if (magickImage.Depth == 8) { - byte[] data = pixels.ToByteArray("RGBA"); - + byte[] data = pixels.ToByteArray(PixelMapping.RGBA); + PixelOperations.Instance.PackFromRgba32Bytes(data, resultPixels, resultPixels.Length); } else if (magickImage.Depth == 16) { - ushort[] data = pixels.ToShortArray("RGBA"); + ushort[] data = pixels.ToShortArray(PixelMapping.RGBA); Span bytes = MemoryMarshal.Cast(data.AsSpan()); PixelOperations.Instance.PackFromRgba64Bytes(bytes, resultPixels, resultPixels.Length); diff --git a/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs b/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs index 9eb051e7a7..0b1b89cc00 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestDataGenerator.cs @@ -1,4 +1,5 @@ using System; +using System.Numerics; namespace SixLabors.ImageSharp.Tests { @@ -10,7 +11,23 @@ namespace SixLabors.ImageSharp.Tests for (int i = 0; i < length; i++) { - values[i] = (float)rnd.NextDouble() * (maxVal - minVal) + minVal; + values[i] = GetRandomFloat(rnd, minVal, maxVal); + } + + return values; + } + + 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; @@ -28,5 +45,10 @@ namespace SixLabors.ImageSharp.Tests return values; } + + private static float GetRandomFloat(Random rnd, float minVal, float maxVal) + { + return (float)rnd.NextDouble() * (maxVal - minVal) + minVal; + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 90c999f7cd..334b6552ac 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -62,13 +62,13 @@ namespace SixLabors.ImageSharp.Tests IImageEncoder bmpEncoder = IsWindows ? (IImageEncoder)SystemDrawingReferenceEncoder.Bmp : new BmpEncoder(); cfg.ConfigureCodecs( - ImageFormats.Png, + PngFormat.Instance, MagickReferenceDecoder.Instance, pngEncoder, new PngImageFormatDetector()); cfg.ConfigureCodecs( - ImageFormats.Bmp, + BmpFormat.Instance, SystemDrawingReferenceDecoder.Instance, bmpEncoder, new BmpImageFormatDetector()); diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index a935873670..2384333bfb 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -441,14 +441,23 @@ namespace SixLabors.ImageSharp.Tests { Span actualPixels = image.GetPixelSpan(); - Assert.True(expectedPixels.Length == actualPixels.Length, "Buffer sizes are not equal!"); + CompareBuffers(expectedPixels, actualPixels); - for (int i = 0; i < expectedPixels.Length; i++) + return image; + } + + public static void CompareBuffers(Span expected, Span actual) + where T : struct, IEquatable + { + Assert.True(expected.Length == actual.Length, "Buffer sizes are not equal!"); + + for (int i = 0; i < expected.Length; i++) { - Assert.True(expectedPixels[i].Equals(actualPixels[i]), $"Pixels are different on position {i}!"); - } + T x = expected[i]; + T a = actual[i]; - return image; + Assert.True(x.Equals(a), $"Buffers differ at position {i}! Expected: {x} | Actual: {a}"); + } } /// diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs index db651886f2..b60439b488 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs @@ -14,10 +14,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests public class MagickReferenceCodecTests { - public MagickReferenceCodecTests(ITestOutputHelper output) - { - this.Output = output; - } + public MagickReferenceCodecTests(ITestOutputHelper output) => this.Output = output; private ITestOutputHelper Output { get; } @@ -61,6 +58,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [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.Gray16Bit)] public void MagickDecode_16BitDepthImage_IsApproximatelyEquivalentTo_SystemDrawingResult(TestImageProvider dummyProvider, string testImage) where TPixel : struct, IPixel { @@ -71,7 +69,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests // 1020 == 4 * 255 (Equivalent to manhattan distance of 1+1+1+1=4 in Rgba32 space) var comparer = ImageComparer.TolerantPercentage(1, 1020); - + using (var mImage = Image.Load(path, magickDecoder)) using (var sdImage = Image.Load(path, sdDecoder)) { diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs index 724c2e4144..033f0866a3 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests private ITestOutputHelper Output { get; } public const string SkipBenchmarks = -#if false +#if true "Benchmark, enable manually!"; #else null; diff --git a/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffIfdParser.cs b/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffIfdParser.cs index 4fc8bca847..55e95dc7f1 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffIfdParser.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tiff/TiffIfdParser.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Tests using ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.MetaData.Profiles.Exif; - + using SixLabors.ImageSharp.Primitives; using Xunit; /// diff --git a/tests/Images/Input/Bmp/rgb32.bmp b/tests/Images/Input/Bmp/rgb32.bmp new file mode 100644 index 0000000000..5d57eaaea8 Binary files /dev/null and b/tests/Images/Input/Bmp/rgb32.bmp differ diff --git a/tests/Images/Input/Gif/leo.gif b/tests/Images/Input/Gif/leo.gif new file mode 100644 index 0000000000..691842f42c Binary files /dev/null and b/tests/Images/Input/Gif/leo.gif differ diff --git a/tests/Images/Input/Jpg/issues/Issue694-Decode-Exif-OutOfRange.jpg b/tests/Images/Input/Jpg/issues/Issue694-Decode-Exif-OutOfRange.jpg new file mode 100644 index 0000000000..cfb8424c79 Binary files /dev/null and b/tests/Images/Input/Jpg/issues/Issue694-Decode-Exif-OutOfRange.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue695-Invalid-EOI.jpg b/tests/Images/Input/Jpg/issues/Issue695-Invalid-EOI.jpg new file mode 100644 index 0000000000..e106c1a19b Binary files /dev/null and b/tests/Images/Input/Jpg/issues/Issue695-Invalid-EOI.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue696-Resize-Exif-OutOfRange.jpg b/tests/Images/Input/Jpg/issues/Issue696-Resize-Exif-OutOfRange.jpg new file mode 100644 index 0000000000..ca18ce5b25 Binary files /dev/null and b/tests/Images/Input/Jpg/issues/Issue696-Resize-Exif-OutOfRange.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue721-InvalidAPP0.jpg b/tests/Images/Input/Jpg/issues/Issue721-InvalidAPP0.jpg new file mode 100644 index 0000000000..6fa3bf660e Binary files /dev/null and b/tests/Images/Input/Jpg/issues/Issue721-InvalidAPP0.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-A.jpg b/tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-A.jpg new file mode 100644 index 0000000000..0a11065ce9 Binary files /dev/null and b/tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-A.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-B.jpg b/tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-B.jpg new file mode 100644 index 0000000000..eb52570e1c Binary files /dev/null and b/tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-B.jpg differ diff --git a/tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-C.jpg b/tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-C.jpg new file mode 100644 index 0000000000..0224cb7f1f Binary files /dev/null and b/tests/Images/Input/Jpg/issues/Issue723-Ordered-Interleaved-Progressive-C.jpg differ