diff --git a/.travis.yml b/.travis.yml index 70501a484b..54e4dee2f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ matrix: - os: linux # Ubuntu 14.04 dist: trusty sudo: required - dotnet: 1.0.4 + dotnet: 2.1.4 mono: latest # - os: osx # OSX 10.11 # osx_image: xcode7.3.1 @@ -21,7 +21,7 @@ branches: script: - git submodule -q update --init - dotnet restore - - dotnet test tests/ImageSharp.Tests/ImageSharp.Tests.csproj -c Release -f "netcoreapp1.1" + - dotnet test tests/ImageSharp.Tests/ImageSharp.Tests.csproj -c Release -f "netcoreapp2.0" env: global: diff --git a/.vscode/launch.json b/.vscode/launch.json index c9c7453f64..c772e647ce 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,7 @@ "request": "launch", "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceRoot}/samples/AvatarWithRoundedCorner/bin/Debug/netcoreapp1.1/AvatarWithRoundedCorner.dll", + "program": "${workspaceRoot}/tests/ImageSharp.Benchmarks/bin/Debug/netcoreapp2.0/ImageSharp.Benchmarks.dll", "args": [], "cwd": "${workspaceRoot}/samples/AvatarWithRoundedCorner", // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 4a7b35ac2c..82aaa2f8d0 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -16,13 +16,13 @@ { "taskName": "build benchmark", "suppressTaskName": true, - "args": [ "build", "tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj", "-f", "netcoreapp1.1", "-c", "Release" ], + "args": [ "build", "tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj", "-f", "netcoreapp2.0", "-c", "Release" ], "showOutput": "always", "problemMatcher": "$msCompile" }, { "taskName": "test", - "args": ["tests/ImageSharp.Tests/ImageSharp.Tests.csproj", "-c", "release", "-f", "netcoreapp1.1"], + "args": ["tests/ImageSharp.Tests/ImageSharp.Tests.csproj", "-c", "release", "-f", "netcoreapp2.0"], "isTestCommand": true, "showOutput": "always", "problemMatcher": "$msCompile" diff --git a/ImageSharp.sln b/ImageSharp.sln index f2155f4245..51b63325b7 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -42,12 +42,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests", "tests\I EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Benchmarks", "tests\ImageSharp.Benchmarks\ImageSharp.Benchmarks.csproj", "{2BF743D8-2A06-412D-96D7-F448F00C5EA5}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{7CC6D57E-B916-43B8-B315-A0BB92F260A2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvatarWithRoundedCorner", "samples\AvatarWithRoundedCorner\AvatarWithRoundedCorner.csproj", "{844FC582-4E78-4371-847D-EFD4D1103578}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChangeDefaultEncoderOptions", "samples\ChangeDefaultEncoderOptions\ChangeDefaultEncoderOptions.csproj", "{07EE511D-4BAB-4323-BAFC-3AF2BF9366F0}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Sandbox46", "tests\ImageSharp.Sandbox46\ImageSharp.Sandbox46.csproj", "{561B880A-D9EE-44EF-90F5-817C54A9D9AB}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Formats.Tiff.Tests", "tests\ImageSharp.Formats.Tiff.Tests\ImageSharp.Formats.Tiff.Tests.csproj", "{F74D25AB-1E5C-4272-9FD3-6DBBD3E207AC}" diff --git a/samples/AvatarWithRoundedCorner/AvatarWithRoundedCorner.csproj b/samples/AvatarWithRoundedCorner/AvatarWithRoundedCorner.csproj deleted file mode 100644 index e000aacf10..0000000000 --- a/samples/AvatarWithRoundedCorner/AvatarWithRoundedCorner.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - Exe - netcoreapp1.1 - - - - - - - \ No newline at end of file diff --git a/samples/AvatarWithRoundedCorner/Program.cs b/samples/AvatarWithRoundedCorner/Program.cs deleted file mode 100644 index 087bbc29d5..0000000000 --- a/samples/AvatarWithRoundedCorner/Program.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.Primitives; -using SixLabors.Shapes; - -namespace AvatarWithRoundedCorner -{ - static class Program - { - static void Main(string[] args) - { - System.IO.Directory.CreateDirectory("output"); - using (var img = Image.Load("fb.jpg")) - { - // as generate returns a new IImage make sure we dispose of it - using (Image destRound = img.Clone(x => x.ConvertToAvatar(new Size(200, 200), 20))) - { - destRound.Save("output/fb.png"); - } - - using (Image destRound = img.Clone(x => x.ConvertToAvatar(new Size(200, 200), 100))) - { - destRound.Save("output/fb-round.png"); - } - - using (Image destRound = img.Clone(x => x.ConvertToAvatar(new Size(200, 200), 150))) - { - destRound.Save("output/fb-rounder.png"); - } - - using (Image destRound = img.CloneAndConvertToAvatarWithoutApply(new Size(200, 200), 150)) - { - destRound.Save("output/fb-rounder-without-apply.png"); - } - - // the original `img` object has not been altered at all. - } - } - - // 1. The short way: - // Implements a full image mutating pipeline operating on IImageProcessingContext - // We need the dimensions of the resized image to deduce 'IPathCollection' needed to build the corners, - // so we implement an "inline" image processor by utilizing 'ImageExtensions.Apply()' - private static IImageProcessingContext ConvertToAvatar(this IImageProcessingContext processingContext, Size size, float cornerRadius) - { - return processingContext.Resize(new ResizeOptions - { - Size = size, - Mode = ResizeMode.Crop - }).Apply(i => ApplyRoundedCorners(i, cornerRadius)); - } - - // 2. A more verbose way, avoiding 'Apply()': - // First we create a resized clone of the image, then we draw the corners on that instance with Mutate(). - private static Image CloneAndConvertToAvatarWithoutApply(this Image image, Size size, float cornerRadius) - { - Image result = image.Clone( - ctx => ctx.Resize( - new ResizeOptions - { - Size = size, - Mode = ResizeMode.Crop - })); - - ApplyRoundedCorners(result, cornerRadius); - return result; - } - - // This method can be seen as an inline implementation of an `IImageProcessor`: - // (The combination of `IImageOperations.Apply()` + this could be replaced with an `IImageProcessor`) - public static void ApplyRoundedCorners(Image img, float cornerRadius) - { - IPathCollection corners = BuildCorners(img.Width, img.Height, cornerRadius); - - // mutating in here as we already have a cloned original - img.Mutate(x => x.Fill(Rgba32.Transparent, corners, new GraphicsOptions(true) - { - BlenderMode = PixelBlenderMode.Src // enforces that any part of this shape that has color is punched out of the background - })); - } - - public static IPathCollection BuildCorners(int imageWidth, int imageHeight, float cornerRadius) - { - // first create a square - var rect = new RectangularePolygon(-0.5f, -0.5f, cornerRadius, cornerRadius); - - // then cut out of the square a circle so we are left with a corner - IPath cornerToptLeft = rect.Clip(new EllipsePolygon(cornerRadius - 0.5f, cornerRadius - 0.5f, cornerRadius)); - - // corner is now a corner shape positions top left - //lets make 3 more positioned correctly, we can do that by translating the orgional artound the center of the image - var center = new Vector2(imageWidth / 2F, imageHeight / 2F); - - float rightPos = imageWidth - cornerToptLeft.Bounds.Width + 1; - float bottomPos = imageHeight - cornerToptLeft.Bounds.Height + 1; - - // move it across the widthof the image - the width of the shape - IPath cornerTopRight = cornerToptLeft.RotateDegree(90).Translate(rightPos, 0); - IPath cornerBottomLeft = cornerToptLeft.RotateDegree(-90).Translate(0, bottomPos); - IPath cornerBottomRight = cornerToptLeft.RotateDegree(180).Translate(rightPos, bottomPos); - - return new PathCollection(cornerToptLeft, cornerBottomLeft, cornerTopRight, cornerBottomRight); - } - } -} \ No newline at end of file diff --git a/samples/AvatarWithRoundedCorner/fb.jpg b/samples/AvatarWithRoundedCorner/fb.jpg deleted file mode 100644 index 7241890e2e..0000000000 --- a/samples/AvatarWithRoundedCorner/fb.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:93bb4d6281dc1e845db57e836e0dca30b7a4062e81044efb27ad4d8b1a33130c -size 15787 diff --git a/samples/ChangeDefaultEncoderOptions/ChangeDefaultEncoderOptions.csproj b/samples/ChangeDefaultEncoderOptions/ChangeDefaultEncoderOptions.csproj deleted file mode 100644 index 5797be0f56..0000000000 --- a/samples/ChangeDefaultEncoderOptions/ChangeDefaultEncoderOptions.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - Exe - netcoreapp1.1 - - - - - - - \ No newline at end of file diff --git a/samples/ChangeDefaultEncoderOptions/Program.cs b/samples/ChangeDefaultEncoderOptions/Program.cs deleted file mode 100644 index a8fbd75993..0000000000 --- a/samples/ChangeDefaultEncoderOptions/Program.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats.Jpeg; - -namespace ChangeDefaultEncoderOptions -{ - class Program - { - static void Main(string[] args) - { - // lets switch out the default encoder for jpeg to one - // that saves at 90 quality and ignores the matadata - Configuration.Default.SetEncoder(ImageFormats.Jpeg, new JpegEncoder() - { - Quality = 90, - IgnoreMetadata = true - }); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs index 2d29e23fe5..320c94c96b 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs @@ -122,24 +122,27 @@ namespace SixLabors.ImageSharp.Drawing.Brushes internal override void Apply(Span scanline, int x, int y) { // Create a span for colors - using (var amountBuffer = new Buffer(scanline.Length)) - using (var overlay = new Buffer(scanline.Length)) + using (IBuffer amountBuffer = this.Target.MemoryManager.Allocate(scanline.Length)) + using (IBuffer overlay = this.Target.MemoryManager.Allocate(scanline.Length)) { + Span amountSpan = amountBuffer.Span; + Span overlaySpan = overlay.Span; + int sourceY = (y - this.offsetY) % this.yLength; int offsetX = x - this.offsetX; Span sourceRow = this.source.GetPixelRowSpan(sourceY); for (int i = 0; i < scanline.Length; i++) { - amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; + amountSpan[i] = scanline[i] * this.Options.BlendPercentage; int sourceX = (i + offsetX) % this.xLength; TPixel pixel = sourceRow[sourceX]; - overlay[i] = pixel; + overlaySpan[i] = pixel; } Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); - this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer); + this.Blender.Blend(this.source.MemoryManager, destinationRow, destinationRow, overlaySpan, amountSpan); } } } diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs index 844df0e0e9..cc22b26391 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs @@ -152,19 +152,24 @@ namespace SixLabors.ImageSharp.Drawing.Brushes internal override void Apply(Span scanline, int x, int y) { int patternY = y % this.pattern.Height; - using (var amountBuffer = new Buffer(scanline.Length)) - using (var overlay = new Buffer(scanline.Length)) + MemoryManager memoryManager = this.Target.MemoryManager; + + using (IBuffer amountBuffer = memoryManager.Allocate(scanline.Length)) + using (IBuffer overlay = memoryManager.Allocate(scanline.Length)) { + Span amountSpan = amountBuffer.Span; + Span overlaySpan = overlay.Span; + for (int i = 0; i < scanline.Length; i++) { - amountBuffer[i] = (scanline[i] * this.Options.BlendPercentage).Clamp(0, 1); + amountSpan[i] = (scanline[i] * this.Options.BlendPercentage).Clamp(0, 1); int patternX = (x + i) % this.pattern.Width; - overlay[i] = this.pattern[patternY, patternX]; + overlaySpan[i] = this.pattern[patternY, patternX]; } Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); - this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer); + this.Blender.Blend(memoryManager, destinationRow, destinationRow, overlaySpan, amountSpan); } } } diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index ca6f7630d9..d8ea435586 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -65,21 +65,26 @@ namespace SixLabors.ImageSharp.Drawing.Brushes.Processors /// scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs. internal virtual void Apply(Span scanline, int x, int y) { - using (var amountBuffer = new Buffer(scanline.Length)) - using (var overlay = new Buffer(scanline.Length)) + MemoryManager memoryManager = this.Target.MemoryManager; + + using (IBuffer amountBuffer = memoryManager.Allocate(scanline.Length)) + using (IBuffer overlay = memoryManager.Allocate(scanline.Length)) { + Span amountSpan = amountBuffer.Span; + Span overlaySpan = overlay.Span; + for (int i = 0; i < scanline.Length; i++) { if (this.Options.BlendPercentage < 1) { - amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; + amountSpan[i] = scanline[i] * this.Options.BlendPercentage; } - overlay[i] = this[x + i, y]; + overlaySpan[i] = this[x + i, y]; } Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); - this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer); + this.Blender.Blend(memoryManager, destinationRow, destinationRow, overlaySpan, amountSpan); } } } diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs index ba2fca4e4b..39afd965c8 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs @@ -144,22 +144,27 @@ namespace SixLabors.ImageSharp.Drawing.Brushes /// internal override void Apply(Span scanline, int x, int y) { - using (var amountBuffer = new Buffer(scanline.Length)) - using (var overlay = new Buffer(scanline.Length)) + MemoryManager memoryManager = this.Target.MemoryManager; + + using (IBuffer amountBuffer = memoryManager.Allocate(scanline.Length)) + using (IBuffer overlay = memoryManager.Allocate(scanline.Length)) { + Span amountSpan = amountBuffer.Span; + Span overlaySpan = overlay.Span; + for (int i = 0; i < scanline.Length; i++) { - amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; + amountSpan[i] = scanline[i] * this.Options.BlendPercentage; int offsetX = x + i; // no doubt this one can be optermised further but I can't imagine its // actually being used and can probably be removed/interalised for now - overlay[i] = this[offsetX, y]; + overlaySpan[i] = this[offsetX, y]; } Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); - this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer); + this.Blender.Blend(memoryManager, destinationRow, destinationRow, overlaySpan, amountSpan); } } } diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs index 658164339d..9630c707ef 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs @@ -61,17 +61,14 @@ namespace SixLabors.ImageSharp.Drawing.Brushes public SolidBrushApplicator(ImageFrame source, TPixel color, GraphicsOptions options) : base(source, options) { - this.Colors = new Buffer(source.Width); - for (int i = 0; i < this.Colors.Length; i++) - { - this.Colors[i] = color; - } + this.Colors = source.MemoryManager.Allocate(source.Width); + this.Colors.Span.Fill(color); } /// /// Gets the colors. /// - protected Buffer Colors { get; } + protected IBuffer Colors { get; } /// /// Gets the color for a single pixel. @@ -81,7 +78,7 @@ namespace SixLabors.ImageSharp.Drawing.Brushes /// /// The color /// - internal override TPixel this[int x, int y] => this.Colors[x]; + internal override TPixel this[int x, int y] => this.Colors.Span[x]; /// public override void Dispose() @@ -92,23 +89,20 @@ namespace SixLabors.ImageSharp.Drawing.Brushes /// internal override void Apply(Span scanline, int x, int y) { - try + Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); + + MemoryManager memoryManager = this.Target.MemoryManager; + + using (IBuffer amountBuffer = memoryManager.Allocate(scanline.Length)) { - Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); + Span amountSpan = amountBuffer.Span; - using (var amountBuffer = new Buffer(scanline.Length)) + for (int i = 0; i < scanline.Length; i++) { - for (int i = 0; i < scanline.Length; i++) - { - amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; - } - - this.Blender.Blend(destinationRow, destinationRow, this.Colors, amountBuffer); + amountSpan[i] = scanline[i] * this.Options.BlendPercentage; } - } - catch (Exception) - { - throw; + + this.Blender.Blend(memoryManager, destinationRow, destinationRow, this.Colors.Span, amountSpan); } } } diff --git a/src/ImageSharp.Drawing/DrawImage.cs b/src/ImageSharp.Drawing/DrawImage.cs index d55e224162..f1db72db60 100644 --- a/src/ImageSharp.Drawing/DrawImage.cs +++ b/src/ImageSharp.Drawing/DrawImage.cs @@ -18,24 +18,13 @@ namespace SixLabors.ImageSharp /// The image this method extends. /// The image to blend with the currently processing image. /// The pixel format. - /// The size to draw the blended image. /// The location to draw the blended image. /// The options. /// The . - public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, Size size, Point location, GraphicsOptions options) + public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, Point location, GraphicsOptions options) where TPixel : struct, IPixel { - if (size == default(Size)) - { - size = new Size(image.Width, image.Height); - } - - if (location == default(Point)) - { - location = Point.Empty; - } - - source.ApplyProcessor(new DrawImageProcessor(image, size, location, options)); + source.ApplyProcessor(new DrawImageProcessor(image, location, options)); return source; } @@ -45,14 +34,14 @@ namespace SixLabors.ImageSharp /// The pixel format. /// The image this method extends. /// The image to blend with the currently processing image. - /// The opacity of the image image to blend. Must be between 0 and 1. + /// The opacity of the image to blend. Must be between 0 and 1. /// The . - public static IImageProcessingContext Blend(this IImageProcessingContext source, Image image, float percent) + public static IImageProcessingContext Blend(this IImageProcessingContext source, Image image, float opacity) where TPixel : struct, IPixel { GraphicsOptions options = GraphicsOptions.Default; - options.BlendPercentage = percent; - return DrawImage(source, image, default(Size), default(Point), options); + options.BlendPercentage = opacity; + return DrawImage(source, image, Point.Empty, options); } /// @@ -62,15 +51,15 @@ namespace SixLabors.ImageSharp /// The image this method extends. /// The image to blend with the currently processing image. /// The blending mode. - /// The opacity of the image image to blend. Must be between 0 and 1. + /// The opacity of the image to blend. Must be between 0 and 1. /// The . - public static IImageProcessingContext Blend(this IImageProcessingContext source, Image image, PixelBlenderMode blender, float percent) + public static IImageProcessingContext Blend(this IImageProcessingContext source, Image image, PixelBlenderMode blender, float opacity) where TPixel : struct, IPixel { GraphicsOptions options = GraphicsOptions.Default; - options.BlendPercentage = percent; + options.BlendPercentage = opacity; options.BlenderMode = blender; - return DrawImage(source, image, default(Size), default(Point), options); + return DrawImage(source, image, Point.Empty, options); } /// @@ -79,12 +68,12 @@ namespace SixLabors.ImageSharp /// The pixel format. /// The image this method extends. /// The image to blend with the currently processing image. - /// The options, including the blending type and belnding amount. + /// The options, including the blending type and blending amount. /// The . public static IImageProcessingContext Blend(this IImageProcessingContext source, Image image, GraphicsOptions options) where TPixel : struct, IPixel { - return DrawImage(source, image, default(Size), default(Point), options); + return DrawImage(source, image, Point.Empty, options); } /// @@ -93,16 +82,15 @@ namespace SixLabors.ImageSharp /// The image this method extends. /// The image to blend with the currently processing image. /// The pixel format. - /// The opacity of the image image to blend. Must be between 0 and 1. - /// The size to draw the blended image. + /// The opacity of the image to blend. Must be between 0 and 1. /// The location to draw the blended image. /// The . - public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, float percent, Size size, Point location) + public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, float opacity, Point location) where TPixel : struct, IPixel { GraphicsOptions options = GraphicsOptions.Default; - options.BlendPercentage = percent; - return source.DrawImage(image, size, location, options); + options.BlendPercentage = opacity; + return source.DrawImage(image, location, options); } /// @@ -112,17 +100,16 @@ namespace SixLabors.ImageSharp /// The image to blend with the currently processing image. /// The pixel format. /// The type of bending to apply. - /// The opacity of the image image to blend. Must be between 0 and 1. - /// The size to draw the blended image. + /// The opacity of the image to blend. Must be between 0 and 1. /// The location to draw the blended image. /// The . - public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, PixelBlenderMode blender, float percent, Size size, Point location) + public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, PixelBlenderMode blender, float opacity, Point location) where TPixel : struct, IPixel { GraphicsOptions options = GraphicsOptions.Default; options.BlenderMode = blender; - options.BlendPercentage = percent; - return source.DrawImage(image, size, location, options); + options.BlendPercentage = opacity; + return source.DrawImage(image, location, options); } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Paths/ShapePath.cs b/src/ImageSharp.Drawing/Paths/ShapePath.cs index 61f1291c45..4c22787195 100644 --- a/src/ImageSharp.Drawing/Paths/ShapePath.cs +++ b/src/ImageSharp.Drawing/Paths/ShapePath.cs @@ -4,6 +4,8 @@ using System; using System.Buffers; using System.Numerics; + +using SixLabors.ImageSharp.Memory; using SixLabors.Shapes; namespace SixLabors.ImageSharp.Drawing diff --git a/src/ImageSharp.Drawing/Paths/ShapeRegion.cs b/src/ImageSharp.Drawing/Paths/ShapeRegion.cs index a96b03dd04..cc27f7fbb8 100644 --- a/src/ImageSharp.Drawing/Paths/ShapeRegion.cs +++ b/src/ImageSharp.Drawing/Paths/ShapeRegion.cs @@ -46,18 +46,17 @@ namespace SixLabors.ImageSharp.Drawing { var start = new PointF(this.Bounds.Left - 1, y); var end = new PointF(this.Bounds.Right + 1, y); - using (var innerBuffer = new Buffer(buffer.Length)) - { - PointF[] array = innerBuffer.Array; - int count = this.Shape.FindIntersections(start, end, array, 0); - for (int i = 0; i < count; i++) - { - buffer[i + offset] = array[i].X; - } + // TODO: This is a temporary workaround because of the lack of Span API-s on IPath. We should use MemoryManager.Allocate() here! + PointF[] innerBuffer = new PointF[buffer.Length]; + int count = this.Shape.FindIntersections(start, end, innerBuffer, 0); - return count; + for (int i = 0; i < count; i++) + { + buffer[i + offset] = innerBuffer[i].X; } + + return count; } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs index 47763c0aaf..632b4d449d 100644 --- a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs @@ -19,93 +19,77 @@ namespace SixLabors.ImageSharp.Drawing.Processors internal class DrawImageProcessor : ImageProcessor where TPixel : struct, IPixel { - private readonly PixelBlender blender; - /// /// Initializes a new instance of the class. /// /// The image to blend with the currently processing image. - /// The size to draw the blended image. /// The location to draw the blended image. - /// The opacity of the image to blend. Between 0 and 100. - public DrawImageProcessor(Image image, Size size, Point location, GraphicsOptions options) + /// The opacity of the image to blend. Between 0 and 1. + public DrawImageProcessor(Image image, Point location, GraphicsOptions options) { Guard.MustBeBetweenOrEqualTo(options.BlendPercentage, 0, 1, nameof(options.BlendPercentage)); + this.Image = image; - this.Size = size; - this.Alpha = options.BlendPercentage; - this.blender = PixelOperations.Instance.GetPixelBlender(options.BlenderMode); + this.Opacity = options.BlendPercentage; + this.Blender = PixelOperations.Instance.GetPixelBlender(options.BlenderMode); this.Location = location; } /// - /// Gets the image to blend. + /// Gets the image to blend /// public Image Image { get; } /// - /// Gets the alpha percentage value. + /// Gets the opacity of the image to blend /// - public float Alpha { get; } + public float Opacity { get; } /// - /// Gets the size to draw the blended image. + /// Gets the pixel blender /// - public Size Size { get; } + public PixelBlender Blender { get; } /// - /// Gets the location to draw the blended image. + /// Gets the location to draw the blended image /// public Point Location { get; } /// protected override void OnApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { - Image disposableImage = null; Image targetImage = this.Image; + PixelBlender blender = this.Blender; + int locationY = this.Location.Y; - try - { - if (targetImage.Size() != this.Size) - { - targetImage = disposableImage = this.Image.Clone(x => x.Resize(this.Size.Width, this.Size.Height)); - } + // Align start/end positions. + Rectangle bounds = targetImage.Bounds(); - // Align start/end positions. - Rectangle bounds = targetImage.Bounds(); - int minX = Math.Max(this.Location.X, sourceRectangle.X); - int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width); - maxX = Math.Min(this.Location.X + this.Size.Width, maxX); - int targetX = minX - this.Location.X; + int minX = Math.Max(this.Location.X, sourceRectangle.X); + int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.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 minY = Math.Max(this.Location.Y, sourceRectangle.Y); + int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); - maxY = Math.Min(this.Location.Y + this.Size.Height, maxY); + int width = maxX - minX; - int width = maxX - minX; - using (var amount = new Buffer(width)) - { - for (int i = 0; i < width; i++) - { - amount[i] = this.Alpha; - } + MemoryManager memoryManager = this.Image.GetConfiguration().MemoryManager; - Parallel.For( - minY, - maxY, - configuration.ParallelOptions, - y => - { - Span background = source.GetPixelRowSpan(y).Slice(minX, width); - Span foreground = targetImage.GetPixelRowSpan(y - this.Location.Y).Slice(targetX, width); - this.blender.Blend(background, background, foreground, amount); - }); - } - } - finally + using (IBuffer amount = memoryManager.Allocate(width)) { - disposableImage?.Dispose(); + amount.Span.Fill(this.Opacity); + + Parallel.For( + minY, + maxY, + configuration.ParallelOptions, + y => + { + Span background = source.GetPixelRowSpan(y).Slice(minX, width); + Span foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width); + blender.Blend(memoryManager, background, background, foreground, amount.Span); + }); } } } diff --git a/src/ImageSharp.Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processors/FillProcessor.cs index 679ca6a228..3bf18a37ba 100644 --- a/src/ImageSharp.Drawing/Processors/FillProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillProcessor.cs @@ -66,25 +66,25 @@ namespace SixLabors.ImageSharp.Drawing.Processors int width = maxX - minX; - using (var amount = new Buffer(width)) - using (BrushApplicator applicator = this.brush.CreateApplicator(source, sourceRectangle, this.options)) + using (IBuffer amount = source.MemoryManager.Allocate(width)) + using (BrushApplicator applicator = this.brush.CreateApplicator( + source, + sourceRectangle, + this.options)) { - for (int i = 0; i < width; i++) - { - amount[i] = this.options.BlendPercentage; - } + amount.Span.Fill(this.options.BlendPercentage); - Parallel.For( + Parallel.For( minY, maxY, configuration.ParallelOptions, y => - { - int offsetY = y - startY; - int offsetX = minX - startX; + { + int offsetY = y - startY; + int offsetX = minX - startX; - applicator.Apply(amount, offsetX, offsetY); - }); + applicator.Apply(amount.Span, offsetX, offsetY); + }); } } } diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index b6ef4be218..076785526c 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -78,8 +78,6 @@ namespace SixLabors.ImageSharp.Drawing.Processors return; // no effect inside image; } - ArrayPool arrayPool = ArrayPool.Shared; - int maxIntersections = region.MaxIntersections; float subpixelCount = 4; @@ -100,101 +98,94 @@ namespace SixLabors.ImageSharp.Drawing.Processors using (BrushApplicator applicator = this.Brush.CreateApplicator(source, rect, this.Options)) { - float[] buffer = arrayPool.Rent(maxIntersections); int scanlineWidth = maxX - minX; - using (var scanline = new Buffer(scanlineWidth)) + using (BasicArrayBuffer buffer = source.MemoryManager.AllocateFake(maxIntersections)) + using (BasicArrayBuffer scanline = source.MemoryManager.AllocateFake(scanlineWidth)) { - try + bool scanlineDirty = true; + for (int y = minY; y < maxY; y++) { - bool scanlineDirty = true; - for (int y = minY; y < maxY; y++) + if (scanlineDirty) { - if (scanlineDirty) + // clear the buffer + for (int x = 0; x < scanlineWidth; x++) { - // clear the buffer - for (int x = 0; x < scanlineWidth; x++) - { - scanline[x] = 0; - } - - scanlineDirty = false; + scanline[x] = 0; } - float subpixelFraction = 1f / subpixelCount; - float subpixelFractionPoint = subpixelFraction / subpixelCount; - for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction) - { - int pointsFound = region.Scan(subPixel + offset, buffer, 0); - if (pointsFound == 0) - { - // nothing on this line skip - continue; - } + scanlineDirty = false; + } - QuickSort(new Span(buffer, 0, pointsFound)); + float subpixelFraction = 1f / subpixelCount; + float subpixelFractionPoint = subpixelFraction / subpixelCount; + for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction) + { + int pointsFound = region.Scan(subPixel + offset, buffer.Array, 0); + if (pointsFound == 0) + { + // nothing on this line skip + continue; + } - for (int point = 0; point < pointsFound; point += 2) - { - // points will be paired up - float scanStart = buffer[point] - minX; - float scanEnd = buffer[point + 1] - minX; - int startX = (int)MathF.Floor(scanStart + offset); - int endX = (int)MathF.Floor(scanEnd + offset); + QuickSort(new Span(buffer.Array, 0, pointsFound)); - if (startX >= 0 && startX < scanline.Length) - { - for (float x = scanStart; x < startX + 1; x += subpixelFraction) - { - scanline[startX] += subpixelFractionPoint; - scanlineDirty = true; - } - } + for (int point = 0; point < pointsFound; point += 2) + { + // points will be paired up + float scanStart = buffer[point] - minX; + float scanEnd = buffer[point + 1] - minX; + int startX = (int)MathF.Floor(scanStart + offset); + int endX = (int)MathF.Floor(scanEnd + offset); - if (endX >= 0 && endX < scanline.Length) + if (startX >= 0 && startX < scanline.Length) + { + for (float x = scanStart; x < startX + 1; x += subpixelFraction) { - for (float x = endX; x < scanEnd; x += subpixelFraction) - { - scanline[endX] += subpixelFractionPoint; - scanlineDirty = true; - } + scanline[startX] += subpixelFractionPoint; + scanlineDirty = true; } + } - int nextX = startX + 1; - endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge - nextX = Math.Max(nextX, 0); - for (int x = nextX; x < endX; x++) + if (endX >= 0 && endX < scanline.Length) + { + for (float x = endX; x < scanEnd; x += subpixelFraction) { - scanline[x] += subpixelFraction; + scanline[endX] += subpixelFractionPoint; scanlineDirty = true; } } + + int nextX = startX + 1; + endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge + nextX = Math.Max(nextX, 0); + for (int x = nextX; x < endX; x++) + { + scanline[x] += subpixelFraction; + scanlineDirty = true; + } } + } - if (scanlineDirty) + if (scanlineDirty) + { + if (!this.Options.Antialias) { - if (!this.Options.Antialias) + for (int x = 0; x < scanlineWidth; x++) { - for (int x = 0; x < scanlineWidth; x++) + if (scanline[x] >= 0.5) + { + scanline[x] = 1; + } + else { - if (scanline[x] >= 0.5) - { - scanline[x] = 1; - } - else - { - scanline[x] = 0; - } + scanline[x] = 0; } } - - applicator.Apply(scanline, minX, y); } + + applicator.Apply(scanline.Span, minX, y); } } - finally - { - arrayPool.Return(buffer); - } } } } diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index 0acb846c50..24d2dd4cc4 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -88,6 +89,14 @@ namespace SixLabors.ImageSharp.Advanced where TPixel : struct, IPixel => source.Frames.RootFrame.GetPixelRowSpan(row); + /// + /// Gets the assigned to 'source'. + /// + /// The source image + /// Returns the configuration. + internal static MemoryManager GetMemoryManager(this IConfigurable source) + => GetConfiguration(source).MemoryManager; + /// /// Gets the span to the backing buffer. /// @@ -140,6 +149,6 @@ namespace SixLabors.ImageSharp.Advanced /// A reference to the element. private static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(IPixelSource source) where TPixel : struct, IPixel - => ref source.PixelBuffer.Span.DangerousGetPinnableReference(); + => ref MemoryMarshal.GetReference(source.PixelBuffer.Span); } } diff --git a/src/ImageSharp/ApplyProcessors.cs b/src/ImageSharp/ApplyProcessors.cs index 58a952c406..c4954ef0d1 100644 --- a/src/ImageSharp/ApplyProcessors.cs +++ b/src/ImageSharp/ApplyProcessors.cs @@ -4,7 +4,6 @@ using System; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; namespace SixLabors.ImageSharp { diff --git a/src/ImageSharp/ColorSpaces/CieLab.cs b/src/ImageSharp/ColorSpaces/CieLab.cs index 107be4cb2b..cb08d08bf9 100644 --- a/src/ImageSharp/ColorSpaces/CieLab.cs +++ b/src/ImageSharp/ColorSpaces/CieLab.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// Represents a CIE L*a*b* 1976 color. /// /// - internal struct CieLab : IColorVector, IEquatable, IAlmostEquatable + internal readonly struct CieLab : IColorVector, IEquatable, IAlmostEquatable { /// /// D50 standard illuminant. diff --git a/src/ImageSharp/ColorSpaces/CieLch.cs b/src/ImageSharp/ColorSpaces/CieLch.cs index 834ef56a89..94443fd863 100644 --- a/src/ImageSharp/ColorSpaces/CieLch.cs +++ b/src/ImageSharp/ColorSpaces/CieLch.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// Represents the CIE L*C*h°, cylindrical form of the CIE L*a*b* 1976 color. /// /// - internal struct CieLch : IColorVector, IEquatable, IAlmostEquatable + internal readonly struct CieLch : IColorVector, IEquatable, IAlmostEquatable { /// /// D50 standard illuminant. diff --git a/src/ImageSharp/ColorSpaces/CieLchuv.cs b/src/ImageSharp/ColorSpaces/CieLchuv.cs index f35914d641..705b770d35 100644 --- a/src/ImageSharp/ColorSpaces/CieLchuv.cs +++ b/src/ImageSharp/ColorSpaces/CieLchuv.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// Represents the CIE L*C*h°, cylindrical form of the CIE L*u*v* 1976 color. /// /// - internal struct CieLchuv : IColorVector, IEquatable, IAlmostEquatable + internal readonly struct CieLchuv : IColorVector, IEquatable, IAlmostEquatable { /// /// D50 standard illuminant. diff --git a/src/ImageSharp/ColorSpaces/CieLuv.cs b/src/ImageSharp/ColorSpaces/CieLuv.cs index 9b52517083..b0ae048ab7 100644 --- a/src/ImageSharp/ColorSpaces/CieLuv.cs +++ b/src/ImageSharp/ColorSpaces/CieLuv.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// attempted perceptual uniformity /// /// - internal struct CieLuv : IColorVector, IEquatable, IAlmostEquatable + internal readonly struct CieLuv : IColorVector, IEquatable, IAlmostEquatable { /// /// D65 standard illuminant. diff --git a/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs b/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs index d9767d45ea..d0a70dd191 100644 --- a/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs +++ b/src/ImageSharp/ColorSpaces/CieXyChromaticityCoordinates.cs @@ -6,12 +6,13 @@ using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; +// ReSharper disable CompareOfFloatsByEqualityOperator namespace SixLabors.ImageSharp.ColorSpaces { /// /// Represents the coordinates of CIEXY chromaticity space /// - internal struct CieXyChromaticityCoordinates : IEquatable, IAlmostEquatable + internal readonly struct CieXyChromaticityCoordinates : IEquatable, IAlmostEquatable { /// /// Represents a that has X, Y values set to zero. @@ -143,7 +144,8 @@ namespace SixLabors.ImageSharp.ColorSpaces [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(CieXyChromaticityCoordinates other) { - return this.backingVector.Equals(other.backingVector); + // The memberwise comparison here is a workaround for https://github.com/dotnet/coreclr/issues/16443 + return this.X == other.X && this.Y == other.Y; } /// diff --git a/src/ImageSharp/ColorSpaces/CieXyy.cs b/src/ImageSharp/ColorSpaces/CieXyy.cs index d5ef4b15d3..751830a0ba 100644 --- a/src/ImageSharp/ColorSpaces/CieXyy.cs +++ b/src/ImageSharp/ColorSpaces/CieXyy.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// Represents an CIE xyY 1931 color /// /// - internal struct CieXyy : IColorVector, IEquatable, IAlmostEquatable + internal readonly struct CieXyy : IColorVector, IEquatable, IAlmostEquatable { /// /// Represents a that has X, Y, and Y values set to zero. diff --git a/src/ImageSharp/ColorSpaces/CieXyz.cs b/src/ImageSharp/ColorSpaces/CieXyz.cs index 908408000a..0f1866009b 100644 --- a/src/ImageSharp/ColorSpaces/CieXyz.cs +++ b/src/ImageSharp/ColorSpaces/CieXyz.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// Represents an CIE XYZ 1931 color /// /// - internal struct CieXyz : IColorVector, IEquatable, IAlmostEquatable + internal readonly struct CieXyz : IColorVector, IEquatable, IAlmostEquatable { /// /// Represents a that has X, Y, and Z values set to zero. diff --git a/src/ImageSharp/ColorSpaces/Cmyk.cs b/src/ImageSharp/ColorSpaces/Cmyk.cs index 2a58a5762a..2eb148a8c3 100644 --- a/src/ImageSharp/ColorSpaces/Cmyk.cs +++ b/src/ImageSharp/ColorSpaces/Cmyk.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// Represents an CMYK (cyan, magenta, yellow, keyline) color. /// - internal struct Cmyk : IEquatable, IAlmostEquatable + internal readonly struct Cmyk : IEquatable, IAlmostEquatable { /// /// Represents a that has C, M, Y, and K values set to zero. diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs index de13b97eb8..6844e3a3ca 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.Rgb.cs @@ -184,8 +184,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion Rgb rgb = YCbCrAndRgbConverter.Convert(color); // Adaptation - // TODO: Check this! - return rgb.WorkingSpace.Equals(this.TargetRgbWorkingSpace) ? rgb : this.Adapt(rgb); + return this.Adapt(rgb); } } } \ No newline at end of file diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbAndCieXyzConverterBase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbAndCieXyzConverterBase.cs index 2ec79b353b..b40a02af76 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbAndCieXyzConverterBase.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbAndCieXyzConverterBase.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSap internal abstract class LinearRgbAndCieXyzConverterBase { /// - /// Geturns the correct matrix to convert between the Rgb and CieXyz color space. + /// Returns the correct matrix to convert between the Rgb and CieXyz color space. /// /// The Rgb working space. /// The based on the chromaticity and working space. @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSap Vector3 vector = Vector3.Transform(workingSpace.WhitePoint.Vector, inverseXyzMatrix); - // Use transposed Rows/Coloumns + // Use transposed Rows/Columns // TODO: Is there a built in method for this multiplication? return new Matrix4x4 { diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToCieXyzConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToCieXyzConverter.cs index 19d4130373..bf36e252a2 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToCieXyzConverter.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/LinearRgbToCieXyzConverter.cs @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSap public CieXyz Convert(LinearRgb input) { DebugGuard.NotNull(input, nameof(input)); - Guard.IsTrue(input.WorkingSpace.Equals(this.SourceWorkingSpace), nameof(input.WorkingSpace), "Input and source working spaces must be equal."); + 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); return new CieXyz(vector); diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbWorkingSpace.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbWorkingSpace.cs index 8a2c66a80c..5a5c39647f 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbWorkingSpace.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Rgb/RgbWorkingSpace.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.RgbColorSap /// /// Trivial implementation of /// - internal struct RgbWorkingSpace : IRgbWorkingSpace + internal readonly struct RgbWorkingSpace : IRgbWorkingSpace { /// /// Initializes a new instance of the struct. diff --git a/src/ImageSharp/ColorSpaces/Hsl.cs b/src/ImageSharp/ColorSpaces/Hsl.cs index cf880f1548..1944ac0c6b 100644 --- a/src/ImageSharp/ColorSpaces/Hsl.cs +++ b/src/ImageSharp/ColorSpaces/Hsl.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// Represents a Hsl (hue, saturation, lightness) color. /// - internal struct Hsl : IColorVector, IEquatable, IAlmostEquatable + internal readonly struct Hsl : IColorVector, IEquatable, IAlmostEquatable { /// /// Represents a that has H, S, and L values set to zero. diff --git a/src/ImageSharp/ColorSpaces/Hsv.cs b/src/ImageSharp/ColorSpaces/Hsv.cs index 9f47393792..45ffd7f121 100644 --- a/src/ImageSharp/ColorSpaces/Hsv.cs +++ b/src/ImageSharp/ColorSpaces/Hsv.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness). /// - internal struct Hsv : IColorVector, IEquatable, IAlmostEquatable + internal readonly struct Hsv : IColorVector, IEquatable, IAlmostEquatable { /// /// Represents a that has H, S, and V values set to zero. diff --git a/src/ImageSharp/ColorSpaces/HunterLab.cs b/src/ImageSharp/ColorSpaces/HunterLab.cs index b5ba7c86c7..de42518d76 100644 --- a/src/ImageSharp/ColorSpaces/HunterLab.cs +++ b/src/ImageSharp/ColorSpaces/HunterLab.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// Represents an Hunter LAB color. /// /// - internal struct HunterLab : IColorVector, IEquatable, IAlmostEquatable + internal readonly struct HunterLab : IColorVector, IEquatable, IAlmostEquatable { /// /// D50 standard illuminant. diff --git a/src/ImageSharp/ColorSpaces/LinearRgb.cs b/src/ImageSharp/ColorSpaces/LinearRgb.cs index 07889c3529..b8c446285a 100644 --- a/src/ImageSharp/ColorSpaces/LinearRgb.cs +++ b/src/ImageSharp/ColorSpaces/LinearRgb.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// Represents an linear Rgb color with specified working space /// - internal struct LinearRgb : IColorVector, IEquatable, IAlmostEquatable + internal readonly struct LinearRgb : IColorVector, IEquatable, IAlmostEquatable { /// /// Represents a that has R, G, and B values set to zero. diff --git a/src/ImageSharp/ColorSpaces/Lms.cs b/src/ImageSharp/ColorSpaces/Lms.cs index 82c291de3d..72ac16f213 100644 --- a/src/ImageSharp/ColorSpaces/Lms.cs +++ b/src/ImageSharp/ColorSpaces/Lms.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// named after their responsivity (sensitivity) at long, medium and short wavelengths. /// /// - internal struct Lms : IColorVector, IEquatable, IAlmostEquatable + internal readonly struct Lms : IColorVector, IEquatable, IAlmostEquatable { /// /// Represents a that has L, M, and S values set to zero. diff --git a/src/ImageSharp/ColorSpaces/Rgb.cs b/src/ImageSharp/ColorSpaces/Rgb.cs index 8ac8411b20..53fa6086df 100644 --- a/src/ImageSharp/ColorSpaces/Rgb.cs +++ b/src/ImageSharp/ColorSpaces/Rgb.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// Represents an RGB color with specified working space /// - internal struct Rgb : IColorVector, IEquatable, IAlmostEquatable + internal readonly struct Rgb : IColorVector, IEquatable, IAlmostEquatable { /// /// Represents a that has R, G, and B values set to zero. diff --git a/src/ImageSharp/ColorSpaces/YCbCr.cs b/src/ImageSharp/ColorSpaces/YCbCr.cs index 708a74308a..44a0b245d5 100644 --- a/src/ImageSharp/ColorSpaces/YCbCr.cs +++ b/src/ImageSharp/ColorSpaces/YCbCr.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.ColorSpaces /// /// /// - internal struct YCbCr : IColorVector, IEquatable, IAlmostEquatable + internal readonly struct YCbCr : IColorVector, IEquatable, IAlmostEquatable { /// /// Represents a that has Y, Cb, and Cr values set to zero. diff --git a/src/ImageSharp/Common/Extensions/SimdUtils.cs b/src/ImageSharp/Common/Extensions/SimdUtils.cs index 0188bc03cf..7b77fefcac 100644 --- a/src/ImageSharp/Common/Extensions/SimdUtils.cs +++ b/src/ImageSharp/Common/Extensions/SimdUtils.cs @@ -76,14 +76,14 @@ namespace SixLabors.ImageSharp return; } - ref Vector srcBase = ref Unsafe.As>(ref source.DangerousGetPinnableReference()); - ref Octet.OfByte destBase = ref Unsafe.As(ref dest.DangerousGetPinnableReference()); + ref Vector srcBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Octet.OfByte destBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); int n = source.Length / 8; Vector magick = new Vector(32768.0f); Vector scale = new Vector(255f) / new Vector(256f); - // need to copy to a temporal struct, because + // need to copy to a temporary struct, because // SimdUtils.Octet.OfUInt32 temp = Unsafe.As, SimdUtils.Octet.OfUInt32>(ref x) // does not work. TODO: This might be a CoreClr bug, need to ask/report var temp = default(Octet.OfUInt32); @@ -117,14 +117,14 @@ namespace SixLabors.ImageSharp return; } - ref Vector srcBase = ref Unsafe.As>(ref source.DangerousGetPinnableReference()); - ref Octet.OfByte destBase = ref Unsafe.As(ref dest.DangerousGetPinnableReference()); + ref Vector srcBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Octet.OfByte destBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); int n = source.Length / 8; Vector magick = new Vector(32768.0f); Vector scale = new Vector(255f) / new Vector(256f); - // need to copy to a temporal struct, because + // need to copy to a temporary struct, because // SimdUtils.Octet.OfUInt32 temp = Unsafe.As, SimdUtils.Octet.OfUInt32>(ref x) // does not work. TODO: This might be a CoreClr bug, need to ask/report var temp = default(Octet.OfUInt32); diff --git a/src/ImageSharp/Common/Extensions/StreamExtensions.cs b/src/ImageSharp/Common/Extensions/StreamExtensions.cs index b717abab1c..7a9a34ac1a 100644 --- a/src/ImageSharp/Common/Extensions/StreamExtensions.cs +++ b/src/ImageSharp/Common/Extensions/StreamExtensions.cs @@ -29,23 +29,16 @@ namespace SixLabors.ImageSharp } else { - byte[] foo = ArrayPool.Shared.Rent(count); - try + byte[] foo = new byte[count]; + while (count > 0) { - while (count > 0) + int bytesRead = stream.Read(foo, 0, count); + if (bytesRead == 0) { - int bytesRead = stream.Read(foo, 0, count); - if (bytesRead == 0) - { - break; - } - - count -= bytesRead; + break; } - } - finally - { - ArrayPool.Shared.Return(foo); + + count -= bytesRead; } } } diff --git a/src/ImageSharp/Common/Helpers/ParallelFor.cs b/src/ImageSharp/Common/Helpers/ParallelFor.cs new file mode 100644 index 0000000000..da91259051 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/ParallelFor.cs @@ -0,0 +1,60 @@ +using System; +using System.Threading.Tasks; +using SixLabors.ImageSharp.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.ParallelOptions, 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 + { + MemoryManager memoryManager = configuration.MemoryManager; + ParallelOptions parallelOptions = configuration.ParallelOptions; + + IBuffer InitBuffer() + { + return memoryManager.Allocate(bufferLength); + } + + void CleanUpBuffer(IBuffer buffer) + { + buffer.Dispose(); + } + + IBuffer BodyFunc(int i, ParallelLoopState state, IBuffer 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/Configuration.cs b/src/ImageSharp/Configuration.cs index 7401035331..d41e48678b 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -1,261 +1,145 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -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; -using SixLabors.ImageSharp.IO; - -namespace SixLabors.ImageSharp -{ - /// - /// Provides initialization code which allows extending the library. - /// - public sealed class Configuration - { - /// - /// A lazily initialized configuration default instance. - /// - private static readonly Lazy Lazy = new Lazy(CreateDefaultInstance); - - /// - /// The list of supported keyed to mime types. - /// - private readonly ConcurrentDictionary mimeTypeEncoders = new ConcurrentDictionary(); - - /// - /// The list of supported keyed to mime types. - /// - private readonly ConcurrentDictionary mimeTypeDecoders = new ConcurrentDictionary(); - - /// - /// The list of supported s. - /// - private readonly ConcurrentBag imageFormats = new ConcurrentBag(); - - /// - /// The list of supported s. - /// - private ConcurrentBag imageFormatDetectors = new ConcurrentBag(); - - /// - /// Initializes a new instance of the class. - /// - public Configuration() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// A collection of configuration modules to register - public Configuration(params IConfigurationModule[] configurationModules) - { - if (configurationModules != null) - { - foreach (IConfigurationModule p in configurationModules) - { - p.Configure(this); - } - } - } - - /// - /// Gets the default instance. - /// - public static Configuration Default { get; } = Lazy.Value; - - /// - /// Gets the global parallel options for processing tasks in parallel. - /// - public ParallelOptions ParallelOptions { get; } = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; - - /// - /// Gets the currently registered s. - /// - public IEnumerable ImageFormats => this.imageFormats; - - /// - /// Gets the maximum header size of all the formats. - /// - internal int MaxHeaderSize { get; private set; } - - /// - /// Gets the currently registered s. - /// - internal IEnumerable FormatDetectors => this.imageFormatDetectors; - - /// - /// Gets the currently registered s. - /// - internal IEnumerable> ImageDecoders => this.mimeTypeDecoders; - - /// - /// Gets the currently registered s. - /// - internal IEnumerable> ImageEncoders => this.mimeTypeEncoders; - -#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. - /// - internal IImageProcessingContextFactory ImageOperationsProvider { get; set; } = new DefaultImageOperationsProviderFactory(); - - /// - /// Registers a new format provider. - /// - /// The configuration provider to call configure on. - public void Configure(IConfigurationModule configuration) - { - Guard.NotNull(configuration, nameof(configuration)); - configuration.Configure(this); - } - - /// - /// Registers a new format provider. - /// - /// The format to register as a known format. - public void AddImageFormat(IImageFormat format) - { - Guard.NotNull(format, nameof(format)); - Guard.NotNull(format.MimeTypes, nameof(format.MimeTypes)); - Guard.NotNull(format.FileExtensions, nameof(format.FileExtensions)); - this.imageFormats.Add(format); - } - - /// - /// For the specified file extensions type find the e . - /// - /// The extension to discover - /// The if found otherwise null - public IImageFormat FindFormatByFileExtension(string extension) - { - return this.imageFormats.FirstOrDefault(x => x.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)); - } - - /// - /// For the specified mime type find the . - /// - /// The mime-type to discover - /// The if found; otherwise null - public IImageFormat FindFormatByMimeType(string mimeType) - { - return this.imageFormats.FirstOrDefault(x => x.MimeTypes.Contains(mimeType, StringComparer.OrdinalIgnoreCase)); - } - - /// - /// Sets a specific image encoder as the encoder for a specific image format. - /// - /// The image format to register the encoder for. - /// The encoder to use, - public void SetEncoder(IImageFormat imageFormat, IImageEncoder encoder) - { - Guard.NotNull(imageFormat, nameof(imageFormat)); - Guard.NotNull(encoder, nameof(encoder)); - this.AddImageFormat(imageFormat); - this.mimeTypeEncoders.AddOrUpdate(imageFormat, encoder, (s, e) => encoder); - } - - /// - /// Sets a specific image decoder as the decoder for a specific image format. - /// - /// The image format to register the encoder for. - /// The decoder to use, - public void SetDecoder(IImageFormat imageFormat, IImageDecoder decoder) - { - Guard.NotNull(imageFormat, nameof(imageFormat)); - Guard.NotNull(decoder, nameof(decoder)); - this.AddImageFormat(imageFormat); - this.mimeTypeDecoders.AddOrUpdate(imageFormat, decoder, (s, e) => decoder); - } - - /// - /// Removes all the registered image format detectors. - /// - public void ClearImageFormatDetectors() - { - this.imageFormatDetectors = new ConcurrentBag(); - } - - /// - /// Adds a new detector for detecting mime types. - /// - /// The detector to add - public void AddImageFormatDetector(IImageFormatDetector detector) - { - Guard.NotNull(detector, nameof(detector)); - this.imageFormatDetectors.Add(detector); - this.SetMaxHeaderSize(); - } - - /// - /// For the specified mime type find the decoder. - /// - /// The format to discover - /// The if found otherwise null - public IImageDecoder FindDecoder(IImageFormat format) - { - Guard.NotNull(format, nameof(format)); - if (this.mimeTypeDecoders.TryGetValue(format, out IImageDecoder decoder)) - { - return decoder; - } - - return null; - } - - /// - /// For the specified mime type find the encoder. - /// - /// The format to discover - /// The if found otherwise null - public IImageEncoder FindEncoder(IImageFormat format) - { - Guard.NotNull(format, nameof(format)); - if (this.mimeTypeEncoders.TryGetValue(format, out IImageEncoder encoder)) - { - return encoder; - } - - return null; - } - - /// - /// Creates the default instance with the following s preregistered: - /// - /// - /// - /// - /// - /// The default configuration of - internal static Configuration CreateDefaultInstance() - { - return new Configuration( - new PngConfigurationModule(), - new JpegConfigurationModule(), - new GifConfigurationModule(), - new BmpConfigurationModule()); - } - - /// - /// Sets the max header size. - /// - private void SetMaxHeaderSize() - { - this.MaxHeaderSize = this.imageFormatDetectors.Max(x => x.HeaderSize); - } - } -} +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +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; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp +{ + /// + /// Provides initialization code which allows extending the library. + /// + public sealed class Configuration + { + /// + /// A lazily initialized configuration default instance. + /// + private static readonly Lazy Lazy = new Lazy(CreateDefaultInstance); + + /// + /// Initializes a new instance of the class. + /// + public Configuration() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A collection of configuration modules to register + public Configuration(params IConfigurationModule[] configurationModules) + { + if (configurationModules != null) + { + foreach (IConfigurationModule p in configurationModules) + { + p.Configure(this); + } + } + } + + /// + /// Gets the default instance. + /// + public static Configuration Default { get; } = Lazy.Value; + + /// + /// Gets the global parallel options for processing tasks in parallel. + /// + public ParallelOptions ParallelOptions { get; private set; } = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; + + /// + /// Gets the currently registered s. + /// + public IEnumerable ImageFormats => this.ImageFormatsManager.ImageFormats; + + /// + /// Gets or sets the position in a stream to use for reading when using a seekable stream as an image data source. + /// + public ReadOrigin ReadOrigin { get; set; } = ReadOrigin.Current; + + /// + /// Gets or sets the that is currently in use. + /// + public ImageFormatManager ImageFormatsManager { get; set; } = new ImageFormatManager(); + + /// + /// Gets or sets the that is currently in use. + /// + public MemoryManager MemoryManager { get; set; } = ArrayPoolMemoryManager.CreateDefault(); + + /// + /// Gets the maximum header size of all the formats. + /// + 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. + /// + internal IImageProcessingContextFactory ImageOperationsProvider { get; set; } = new DefaultImageOperationsProviderFactory(); + + /// + /// Registers a new format provider. + /// + /// The configuration provider to call configure on. + public void Configure(IConfigurationModule configuration) + { + Guard.NotNull(configuration, nameof(configuration)); + configuration.Configure(this); + } + + /// + /// Creates a shallow copy of the + /// + /// A new configuration instance + public Configuration ShallowCopy() + { + return new Configuration + { + ParallelOptions = this.ParallelOptions, + ImageFormatsManager = this.ImageFormatsManager, + MemoryManager = this.MemoryManager, + ImageOperationsProvider = this.ImageOperationsProvider, + ReadOrigin = this.ReadOrigin, + +#if !NETSTANDARD1_1 + FileSystem = this.FileSystem +#endif + }; + } + + /// + /// Creates the default instance with the following s preregistered: + /// + /// + /// + /// + /// + /// The default configuration of + internal static Configuration CreateDefaultInstance() + { + return new Configuration( + new PngConfigurationModule(), + new JpegConfigurationModule(), + new GifConfigurationModule(), + new BmpConfigurationModule()); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/DefaultInternalImageProcessorContext.cs b/src/ImageSharp/DefaultInternalImageProcessorContext.cs index 6e6feed84e..7ccc65e27e 100644 --- a/src/ImageSharp/DefaultInternalImageProcessorContext.cs +++ b/src/ImageSharp/DefaultInternalImageProcessorContext.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Helpers; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.Primitives; @@ -34,6 +36,9 @@ namespace SixLabors.ImageSharp } } + /// + public MemoryManager MemoryManager => this.source.GetConfiguration().MemoryManager; + /// public Image Apply() { @@ -46,6 +51,9 @@ namespace SixLabors.ImageSharp return this.destination; } + /// + public Size GetCurrentSize() => this.GetCurrentBounds().Size; + /// public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle) { @@ -70,7 +78,12 @@ namespace SixLabors.ImageSharp /// public IImageProcessingContext ApplyProcessor(IImageProcessor processor) { - return this.ApplyProcessor(processor, this.source.Bounds()); + return this.ApplyProcessor(processor, this.GetCurrentBounds()); + } + + private Rectangle GetCurrentBounds() + { + return this.destination?.Bounds() ?? this.source.Bounds(); } } } \ No newline at end of file diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs index 46bafcc0cf..8f448198b0 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuserBase.cs @@ -70,22 +70,10 @@ namespace SixLabors.ImageSharp.Dithering.Base /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dither(ImageFrame pixels, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY) + public void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY) where TPixel : struct, IPixel { - this.Dither(pixels, source, transformed, x, y, minX, minY, maxX, maxY, true); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY, bool replacePixel) - where TPixel : struct, IPixel - { - if (replacePixel) - { - // Assign the transformed pixel to the array. - image[x, y] = transformed; - } + image[x, y] = transformed; // Calculate the error Vector4 error = source.ToVector4() - transformed.ToVector4(); diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs index c538d643c6..dabc4e6822 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Dithering { /// - /// Encapsulates properties and methods required to perfom diffused error dithering on an image. + /// Encapsulates properties and methods required to perform diffused error dithering on an image. /// public interface IErrorDiffuser { @@ -25,25 +25,5 @@ namespace SixLabors.ImageSharp.Dithering /// The pixel format. void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY) where TPixel : struct, IPixel; - - /// - /// Transforms the image applying the dither matrix. This method alters the input pixels array - /// - /// The image - /// The source pixel - /// The transformed pixel - /// The column index. - /// The row index. - /// The minimum column value. - /// The minimum row value. - /// The maximum column value. - /// The maximum row value. - /// - /// Whether to replace the pixel at the given coordinates with the transformed value. - /// Generally this would be true for standard two-color dithering but when used in conjunction with color quantization this should be false. - /// - /// The pixel format. - void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int minY, int maxX, int maxY, bool replacePixel) - where TPixel : struct, IPixel; } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/KnownDiffusers.cs b/src/ImageSharp/Dithering/ErrorDiffusion/KnownDiffusers.cs new file mode 100644 index 0000000000..c75530b8e7 --- /dev/null +++ b/src/ImageSharp/Dithering/ErrorDiffusion/KnownDiffusers.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// Contains reusable static instances of known error diffusion algorithms + /// + public static class KnownDiffusers + { + /// + /// Gets the error diffuser that implements the Atkinson algorithm. + /// + public static IErrorDiffuser Atkinson { get; } = new AtkinsonDiffuser(); + + /// + /// Gets the error diffuser that implements the Burks algorithm. + /// + public static IErrorDiffuser Burks { get; } = new BurksDiffuser(); + + /// + /// Gets the error diffuser that implements the Floyd-Steinberg algorithm. + /// + public static IErrorDiffuser FloydSteinberg { get; } = new FloydSteinbergDiffuser(); + + /// + /// Gets the error diffuser that implements the Jarvis-Judice-Ninke algorithm. + /// + public static IErrorDiffuser JarvisJudiceNinke { get; } = new JarvisJudiceNinkeDiffuser(); + + /// + /// Gets the error diffuser that implements the Sierra-2 algorithm. + /// + public static IErrorDiffuser Sierra2 { get; } = new Sierra2Diffuser(); + + /// + /// Gets the error diffuser that implements the Sierra-3 algorithm. + /// + public static IErrorDiffuser Sierra3 { get; } = new Sierra3Diffuser(); + + /// + /// Gets the error diffuser that implements the Sierra-Lite algorithm. + /// + public static IErrorDiffuser SierraLite { get; } = new SierraLiteDiffuser(); + + /// + /// Gets the error diffuser that implements the Stevenson-Arce algorithm. + /// + public static IErrorDiffuser StevensonArce { get; } = new StevensonArceDiffuser(); + + /// + /// Gets the error diffuser that implements the Stucki algorithm. + /// + public static IErrorDiffuser Stucki { get; } = new StuckiDiffuser(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/StevensonArceDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/StevensonArceDiffuser.cs new file mode 100644 index 0000000000..0f0338ac72 --- /dev/null +++ b/src/ImageSharp/Dithering/ErrorDiffusion/StevensonArceDiffuser.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Dithering.Base; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// Applies error diffusion based dithering using the Stevenson-Arce image dithering algorithm. + /// + public sealed class StevensonArceDiffuser : ErrorDiffuserBase + { + /// + /// The diffusion matrix + /// + private static readonly Fast2DArray StevensonArceMatrix = + new float[,] + { + { 0, 0, 0, 0, 0, 32, 0 }, + { 12, 0, 26, 0, 30, 0, 16 }, + { 0, 12, 0, 26, 0, 12, 0 }, + { 5, 0, 12, 0, 12, 0, 5 } + }; + + /// + /// Initializes a new instance of the class. + /// + public StevensonArceDiffuser() + : base(StevensonArceMatrix, 200) + { + } + } +} diff --git a/src/ImageSharp/Dithering/Ordered/BayerDither.cs b/src/ImageSharp/Dithering/Ordered/BayerDither.cs deleted file mode 100644 index 685dca5fe8..0000000000 --- a/src/ImageSharp/Dithering/Ordered/BayerDither.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Dithering.Base; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Dithering -{ - /// - /// Applies error diffusion based dithering using the 4x4 Bayer dithering matrix. - /// - /// - public sealed class BayerDither : OrderedDitherBase - { - /// - /// The threshold matrix. - /// This is calculated by multiplying each value in the original matrix by 16 and subtracting 1 - /// - private static readonly Fast2DArray ThresholdMatrix = - new byte[,] - { - { 15, 143, 47, 175 }, - { 207, 79, 239, 111 }, - { 63, 191, 31, 159 }, - { 255, 127, 223, 95 } - }; - - /// - /// Initializes a new instance of the class. - /// - public BayerDither() - : base(ThresholdMatrix) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/BayerDither2x2.cs b/src/ImageSharp/Dithering/Ordered/BayerDither2x2.cs new file mode 100644 index 0000000000..1d844c8a79 --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/BayerDither2x2.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// Applies order dithering using the 2x2 Bayer dithering matrix. + /// + public sealed class BayerDither2x2 : OrderedDither + { + /// + /// Initializes a new instance of the class. + /// + public BayerDither2x2() + : base(2) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/BayerDither4x4.cs b/src/ImageSharp/Dithering/Ordered/BayerDither4x4.cs new file mode 100644 index 0000000000..4e9f20beb9 --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/BayerDither4x4.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// Applies order dithering using the 4x4 Bayer dithering matrix. + /// + public sealed class BayerDither4x4 : OrderedDither + { + /// + /// Initializes a new instance of the class. + /// + public BayerDither4x4() + : base(4) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/BayerDither8x8.cs b/src/ImageSharp/Dithering/Ordered/BayerDither8x8.cs new file mode 100644 index 0000000000..3ff179a06a --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/BayerDither8x8.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// Applies order dithering using the 8x8 Bayer dithering matrix. + /// + public sealed class BayerDither8x8 : OrderedDither + { + /// + /// Initializes a new instance of the class. + /// + public BayerDither8x8() + : base(8) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs index 689c9a85b4..339f2861d9 100644 --- a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs +++ b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Dithering { /// - /// Encapsulates properties and methods required to perfom ordered dithering on an image. + /// Encapsulates properties and methods required to perform ordered dithering on an image. /// public interface IOrderedDither { @@ -17,12 +17,11 @@ namespace SixLabors.ImageSharp.Dithering /// The source pixel /// The color to apply to the pixels above the threshold. /// The color to apply to the pixels below the threshold. - /// The to pack/unpack to. - /// The component index to test the threshold against. Must range from 0 to 3. + /// The threshold to split the image. Must be between 0 and 1. /// The column index. /// The row index. /// The pixel format. - void Dither(ImageFrame image, TPixel source, TPixel upper, TPixel lower, ref Rgba32 rgba, int index, int x, int y) + void Dither(ImageFrame image, TPixel source, TPixel upper, TPixel lower, float threshold, int x, int y) where TPixel : struct, IPixel; } } \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/KnownDitherers.cs b/src/ImageSharp/Dithering/Ordered/KnownDitherers.cs new file mode 100644 index 0000000000..e58cbad8ab --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/KnownDitherers.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// Contains reusable static instances of known ordered dither matrices + /// + public class KnownDitherers + { + /// + /// Gets the order ditherer using the 2x2 Bayer dithering matrix + /// + public static IOrderedDither BayerDither2x2 { get; } = new BayerDither2x2(); + + /// + /// Gets the order ditherer using the 3x3 dithering matrix + /// + public static IOrderedDither OrderedDither3x3 { get; } = new OrderedDither3x3(); + + /// + /// Gets the order ditherer using the 4x4 Bayer dithering matrix + /// + public static IOrderedDither BayerDither4x4 { get; } = new BayerDither4x4(); + + /// + /// Gets the order ditherer using the 8x8 Bayer dithering matrix + /// + public static IOrderedDither BayerDither8x8 { get; } = new BayerDither8x8(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/OrderedDither.cs b/src/ImageSharp/Dithering/Ordered/OrderedDither.cs index 12968914d0..c07b185bbe 100644 --- a/src/ImageSharp/Dithering/Ordered/OrderedDither.cs +++ b/src/ImageSharp/Dithering/Ordered/OrderedDither.cs @@ -1,36 +1,50 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Dithering.Base; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Dithering { /// - /// Applies error diffusion based dithering using the 4x4 ordered dithering matrix. - /// + /// An ordered dithering matrix with equal sides of arbitrary length /// - public sealed class OrderedDither : OrderedDitherBase + public class OrderedDither : IOrderedDither { - /// - /// The threshold matrix. - /// This is calculated by multiplying each value in the original matrix by 16 - /// - private static readonly Fast2DArray ThresholdMatrix = - new byte[,] - { - { 0, 128, 32, 160 }, - { 192, 64, 224, 96 }, - { 48, 176, 16, 144 }, - { 240, 112, 208, 80 } - }; + private readonly Fast2DArray thresholdMatrix; + private readonly int modulusX; + private readonly int modulusY; /// /// Initializes a new instance of the class. /// - public OrderedDither() - : base(ThresholdMatrix) + /// The length of the matrix sides + public OrderedDither(uint length) + { + Fast2DArray ditherMatrix = OrderedDitherFactory.CreateDitherMatrix(length); + this.modulusX = ditherMatrix.Width; + this.modulusY = ditherMatrix.Height; + + // Adjust the matrix range for 0-255 + // It looks like it's actually possible to dither an image using it's own colors. We should investigate for V2 + // https://stackoverflow.com/questions/12422407/monochrome-dithering-in-javascript-bayer-atkinson-floyd-steinberg + int multiplier = 256 / ditherMatrix.Count; + for (int y = 0; y < ditherMatrix.Height; y++) + { + for (int x = 0; x < ditherMatrix.Width; x++) + { + ditherMatrix[y, x] = (uint)((ditherMatrix[y, x] + 1) * multiplier) - 1; + } + } + + this.thresholdMatrix = ditherMatrix; + } + + /// + public void Dither(ImageFrame image, TPixel source, TPixel upper, TPixel lower, float threshold, int x, int y) + where TPixel : struct, IPixel { + image[x, y] = this.thresholdMatrix[y % this.modulusY, x % this.modulusX] >= threshold ? lower : upper; } } } \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/OrderedDither3x3.cs b/src/ImageSharp/Dithering/Ordered/OrderedDither3x3.cs new file mode 100644 index 0000000000..0436b35e9c --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/OrderedDither3x3.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// Applies order dithering using the 3x3 dithering matrix. + /// + public sealed class OrderedDither3x3 : OrderedDither + { + /// + /// Initializes a new instance of the class. + /// + public OrderedDither3x3() + : base(3) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/OrderedDitherBase.cs b/src/ImageSharp/Dithering/Ordered/OrderedDitherBase.cs deleted file mode 100644 index 818a24d5dd..0000000000 --- a/src/ImageSharp/Dithering/Ordered/OrderedDitherBase.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Dithering.Base -{ - /// - /// The base class for performing ordered dithering using a 4x4 matrix. - /// - public abstract class OrderedDitherBase : IOrderedDither - { - /// - /// The dithering matrix - /// - private Fast2DArray matrix; - - /// - /// Initializes a new instance of the class. - /// - /// The thresholding matrix. - internal OrderedDitherBase(Fast2DArray matrix) - { - this.matrix = matrix; - } - - /// - public void Dither(ImageFrame image, TPixel source, TPixel upper, TPixel lower, ref Rgba32 rgba, int index, int x, int y) - where TPixel : struct, IPixel - { - source.ToRgba32(ref rgba); - switch (index) - { - case 0: - image[x, y] = this.matrix[y % 3, x % 3] >= rgba.R ? lower : upper; - return; - case 1: - image[x, y] = this.matrix[y % 3, x % 3] >= rgba.G ? lower : upper; - return; - case 2: - image[x, y] = this.matrix[y % 3, x % 3] >= rgba.B ? lower : upper; - return; - case 3: - image[x, y] = this.matrix[y % 3, x % 3] >= rgba.A ? lower : upper; - return; - } - - throw new ArgumentOutOfRangeException(nameof(index), "Index should be between 0 and 3 inclusive."); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/OrderedDitherFactory.cs b/src/ImageSharp/Dithering/Ordered/OrderedDitherFactory.cs new file mode 100644 index 0000000000..fc9ac25517 --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/OrderedDitherFactory.cs @@ -0,0 +1,94 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Dithering +{ + /// + /// A factory for creating ordered dither matrices. + /// + internal static class OrderedDitherFactory + { + /// + /// Creates an ordered dithering matrix with equal sides of arbitrary length. + /// + /// + /// The length of the matrix sides + /// The + public static Fast2DArray CreateDitherMatrix(uint length) + { + // Calculate the the logarithm of length to the base 2 + uint exponent = 0; + uint bayerLength = 0; + do + { + exponent++; + bayerLength = (uint)(1 << (int)exponent); + } + while (length > bayerLength); + + // Create our Bayer matrix that matches the given exponent and dimensions + var matrix = new Fast2DArray((int)length); + uint i = 0; + for (int y = 0; y < length; y++) + { + for (int x = 0; x < length; x++) + { + matrix[y, x] = Bayer(i / length, i % length, exponent); + i++; + } + } + + // If the user requested a matrix with a non-power-of-2 length e.g. 3x3 and we used 4x4 algorithm, + // we need to convert the numbers so that the resulting range is un-gapped. + // We generated: We saved: We compress the number range: + // 0 8 2 10 0 8 2 0 5 2 + // 12 4 14 6 12 4 14 7 4 8 + // 3 11 1 9 3 11 1 3 6 1 + // 15 7 13 5 + uint maxValue = bayerLength * bayerLength; + uint missing = 0; + for (uint v = 0; v < maxValue; ++v) + { + bool found = false; + for (int y = 0; y < length; ++y) + { + for (int x = 0; x < length; x++) + { + if (matrix[y, x] == v) + { + matrix[y, x] -= missing; + found = true; + break; + } + } + } + + if (!found) + { + ++missing; + } + } + + return matrix; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Bayer(uint x, uint y, uint order) + { + uint result = 0; + for (uint i = 0; i < order; ++i) + { + uint xOdd_XOR_yOdd = (x & 1) ^ (y & 1); + uint xOdd = x & 1; + result = ((result << 1 | xOdd_XOR_yOdd) << 1) | xOdd; + x >>= 1; + y >>= 1; + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/error_diffusion.txt b/src/ImageSharp/Dithering/error_diffusion.txt new file mode 100644 index 0000000000..ea412f6351 --- /dev/null +++ b/src/ImageSharp/Dithering/error_diffusion.txt @@ -0,0 +1,58 @@ +List of error diffusion schemes. + +Quantization error of *current* pixel is added to the pixels +on the right and below according to the formulas below. +This works nicely for most static pictures, but causes +an avalanche of jittering artifacts if used in animation. + +Floyd-Steinberg: + + * 7 + 3 5 1 / 16 + +Jarvis-Judice-Ninke: + + * 7 5 + 3 5 7 5 3 + 1 3 5 3 1 / 48 + +Stucki: + + * 8 4 + 2 4 8 4 2 + 1 2 4 2 1 / 42 + +Burkes: + + * 8 4 + 2 4 8 4 2 / 32 + + +Sierra3: + + * 5 3 + 2 4 5 4 2 + 2 3 2 / 32 + +Sierra2: + + * 4 3 + 1 2 3 2 1 / 16 + +Sierra-2-4A: + + * 2 + 1 1 / 4 + +Stevenson-Arce: + + * . 32 + 12 . 26 . 30 . 16 + . 12 . 26 . 12 . + 5 . 12 . 12 . 5 / 200 + +Atkinson: + + * 1 1 / 8 + 1 1 1 + 1 diff --git a/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs b/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs index b091467bf5..956acc1578 100644 --- a/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs +++ b/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs @@ -11,9 +11,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// public void Configure(Configuration config) { - config.SetEncoder(ImageFormats.Bmp, new BmpEncoder()); - config.SetDecoder(ImageFormats.Bmp, new BmpDecoder()); - config.AddImageFormatDetector(new BmpImageFormatDetector()); + config.ImageFormatsManager.SetEncoder(ImageFormats.Bmp, new BmpEncoder()); + config.ImageFormatsManager.SetDecoder(ImageFormats.Bmp, new BmpDecoder()); + config.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 e552ac1042..9f4dba5b4f 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -69,7 +69,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// private BmpInfoHeader infoHeader; - private Configuration configuration; + private readonly Configuration configuration; + + private readonly MemoryManager memoryManager; /// /// Initializes a new instance of the class. @@ -79,6 +81,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options) { this.configuration = configuration; + this.memoryManager = configuration.MemoryManager; } /// @@ -221,9 +224,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp var color = default(TPixel); var rgba = new Rgba32(0, 0, 0, 255); - using (var buffer = Buffer2D.CreateClean(width, height)) + using (var buffer = this.memoryManager.AllocateClean2D(width, height)) { - this.UncompressRle8(width, buffer); + this.UncompressRle8(width, buffer.Span); for (int y = 0; y < height; y++) { @@ -343,15 +346,17 @@ namespace SixLabors.ImageSharp.Formats.Bmp padding = 4 - padding; } - using (var row = Buffer.CreateClean(arrayWidth + padding)) + using (IManagedByteBuffer row = this.memoryManager.AllocateCleanManagedByteBuffer(arrayWidth + padding)) { var color = default(TPixel); var rgba = new Rgba32(0, 0, 0, 255); + Span rowSpan = row.Span; + for (int y = 0; y < height; y++) { int newY = Invert(y, height, inverted); - this.currentStream.Read(row.Array, 0, row.Length); + this.currentStream.Read(row.Array, 0, row.Length()); int offset = 0; Span pixelRow = pixels.GetRowSpan(newY); @@ -362,7 +367,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int shift = 0; shift < ppb && (x + shift) < width; shift++) { - int colorIndex = ((row[offset] >> (8 - bits - (shift * bits))) & mask) * 4; + int colorIndex = ((rowSpan[offset] >> (8 - bits - (shift * bits))) & mask) * 4; int newX = colOffset + shift; // Stored in b-> g-> r order. @@ -393,7 +398,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp var color = default(TPixel); var rgba = new Rgba32(0, 0, 0, 255); - using (var buffer = new Buffer(stride)) + using (var buffer = this.memoryManager.AllocateManagedByteBuffer(stride)) { for (int y = 0; y < height; y++) { @@ -430,14 +435,15 @@ namespace SixLabors.ImageSharp.Formats.Bmp where TPixel : struct, IPixel { int padding = CalculatePadding(width, 3); - using (var row = new PixelArea(width, ComponentOrder.Zyx, padding)) + + using (IManagedByteBuffer row = this.memoryManager.AllocatePaddedPixelRowBuffer(width, 3, padding)) { for (int y = 0; y < height; y++) { - row.Read(this.currentStream); - + this.currentStream.Read(row); int newY = Invert(y, height, inverted); - pixels.CopyFrom(row, newY); + Span pixelSpan = pixels.GetRowSpan(newY); + PixelOperations.Instance.PackFromBgr24Bytes(row.Span, pixelSpan, width); } } } @@ -454,14 +460,15 @@ namespace SixLabors.ImageSharp.Formats.Bmp where TPixel : struct, IPixel { int padding = CalculatePadding(width, 4); - using (var row = new PixelArea(width, ComponentOrder.Zyxw, padding)) + + using (IManagedByteBuffer row = this.memoryManager.AllocatePaddedPixelRowBuffer(width, 4, padding)) { for (int y = 0; y < height; y++) { - row.Read(this.currentStream); - + this.currentStream.Read(row); int newY = Invert(y, height, inverted); - pixels.CopyFrom(row, newY); + Span pixelSpan = pixels.GetRowSpan(newY); + PixelOperations.Instance.PackFromBgra32Bytes(row.Span, pixelSpan, width); } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index 366afceb5f..d80e43c63b 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Bmp @@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp public void Encode(Image image, Stream stream) where TPixel : struct, IPixel { - var encoder = new BmpEncoderCore(this); + var encoder = new BmpEncoderCore(this, image.GetMemoryManager()); encoder.Encode(image, stream); } } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index d34d170847..66c8b6c086 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -4,6 +4,7 @@ using System; using System.IO; using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Bmp @@ -21,14 +22,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Gets or sets the number of bits per pixel. /// - private BmpBitsPerPixel bitsPerPixel; + private readonly BmpBitsPerPixel bitsPerPixel; + + private readonly MemoryManager memoryManager; /// /// Initializes a new instance of the class. /// /// The encoder options - public BmpEncoderCore(IBmpEncoderOptions options) + /// The memory manager + public BmpEncoderCore(IBmpEncoderOptions options, MemoryManager memoryManager) { + this.memoryManager = memoryManager; this.bitsPerPixel = options.BitsPerPixel; } @@ -145,6 +150,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp } } + private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) + { + return this.memoryManager.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding); + } + /// /// Writes the 32bit color palette to the stream. /// @@ -154,12 +164,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp private void Write32Bit(EndianBinaryWriter writer, PixelAccessor pixels) where TPixel : struct, IPixel { - using (PixelArea row = new PixelArea(pixels.Width, ComponentOrder.Zyxw, this.padding)) + using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4)) { for (int y = pixels.Height - 1; y >= 0; y--) { - pixels.CopyTo(row, y); - writer.Write(row.Bytes, 0, row.Length); + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToBgra32Bytes(pixelSpan, row.Span, pixelSpan.Length); + writer.Write(row.Array, 0, row.Length()); } } } @@ -173,12 +184,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp private void Write24Bit(EndianBinaryWriter writer, PixelAccessor pixels) where TPixel : struct, IPixel { - using (PixelArea row = new PixelArea(pixels.Width, ComponentOrder.Zyx, this.padding)) + using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3)) { for (int y = pixels.Height - 1; y >= 0; y--) { - pixels.CopyTo(row, y); - writer.Write(row.Bytes, 0, row.Length); + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.ToBgr24Bytes(pixelSpan, row.Span, pixelSpan.Length); + writer.Write(row.Array, 0, row.Length()); } } } diff --git a/src/ImageSharp/Formats/Bmp/ImageExtensions.cs b/src/ImageSharp/Formats/Bmp/ImageExtensions.cs index 935ce8f4ad..35e168e278 100644 --- a/src/ImageSharp/Formats/Bmp/ImageExtensions.cs +++ b/src/ImageSharp/Formats/Bmp/ImageExtensions.cs @@ -36,6 +36,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().FindEncoder(ImageFormats.Bmp)); + => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ImageFormats.Bmp)); } } diff --git a/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs b/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs index 4c42a833c0..0bb62779eb 100644 --- a/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs +++ b/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs @@ -11,10 +11,10 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public void Configure(Configuration config) { - config.SetEncoder(ImageFormats.Gif, new GifEncoder()); - config.SetDecoder(ImageFormats.Gif, new GifDecoder()); + config.ImageFormatsManager.SetEncoder(ImageFormats.Gif, new GifEncoder()); + config.ImageFormatsManager.SetDecoder(ImageFormats.Gif, new GifDecoder()); - config.AddImageFormatDetector(new GifImageFormatDetector()); + config.ImageFormatsManager.AddImageFormatDetector(new GifImageFormatDetector()); } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 3c22518057..48c8ceb8c9 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The global color table. /// - private Buffer globalColorTable; + private IManagedByteBuffer globalColorTable; /// /// The global color table length @@ -92,6 +92,8 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public FrameDecodingMode DecodingMode { get; } + private MemoryManager MemoryManager => this.configuration.MemoryManager; + /// /// Decodes the stream to the image. /// @@ -333,18 +335,12 @@ namespace SixLabors.ImageSharp.Formats.Gif continue; } - byte[] commentsBuffer = ArrayPool.Shared.Rent(length); - - try + using (IManagedByteBuffer commentsBuffer = this.MemoryManager.AllocateManagedByteBuffer(length)) { - this.currentStream.Read(commentsBuffer, 0, length); - string comments = this.TextEncoding.GetString(commentsBuffer, 0, length); + this.currentStream.Read(commentsBuffer.Array, 0, length); + string comments = this.TextEncoding.GetString(commentsBuffer.Array, 0, length); this.metaData.Properties.Add(new ImageProperty(GifConstants.Comments, comments)); } - finally - { - ArrayPool.Shared.Return(commentsBuffer); - } } } @@ -359,22 +355,23 @@ namespace SixLabors.ImageSharp.Formats.Gif { GifImageDescriptor imageDescriptor = this.ReadImageDescriptor(); - Buffer localColorTable = null; - Buffer indices = null; + 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) { int length = imageDescriptor.LocalColorTableSize * 3; - localColorTable = Buffer.CreateClean(length); + localColorTable = this.configuration.MemoryManager.AllocateManagedByteBuffer(length, true); this.currentStream.Read(localColorTable.Array, 0, length); } - indices = Buffer.CreateClean(imageDescriptor.Width * imageDescriptor.Height); + indices = this.configuration.MemoryManager.AllocateManagedByteBuffer(imageDescriptor.Width * imageDescriptor.Height, true); - this.ReadFrameIndices(imageDescriptor, indices); - this.ReadFrameColors(ref image, ref previousFrame, indices, localColorTable ?? this.globalColorTable, imageDescriptor); + this.ReadFrameIndices(imageDescriptor, indices.Span); + IManagedByteBuffer colorTable = localColorTable ?? this.globalColorTable; + this.ReadFrameColors(ref image, ref previousFrame, indices.Span, colorTable.Span, imageDescriptor); // Skip any remaining blocks this.Skip(0); @@ -395,7 +392,7 @@ namespace SixLabors.ImageSharp.Formats.Gif private void ReadFrameIndices(GifImageDescriptor imageDescriptor, Span indices) { int dataSize = this.currentStream.ReadByte(); - using (var lzwDecoder = new LzwDecoder(this.currentStream)) + using (var lzwDecoder = new LzwDecoder(this.configuration.MemoryManager, this.currentStream)) { lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize, indices); } @@ -445,7 +442,7 @@ namespace SixLabors.ImageSharp.Formats.Gif imageFrame = currentFrame; - this.RestoreToBackground(imageFrame, image.Width, image.Height); + this.RestoreToBackground(imageFrame); } int i = 0; @@ -535,9 +532,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The pixel format. /// The frame. - /// Width of the image. - /// Height of the image. - private void RestoreToBackground(ImageFrame frame, int imageWidth, int imageHeight) + private void RestoreToBackground(ImageFrame frame) where TPixel : struct, IPixel { if (this.restoreArea == null) @@ -545,28 +540,8 @@ namespace SixLabors.ImageSharp.Formats.Gif return; } - // Optimization for when the size of the frame is the same as the image size. - if (this.restoreArea.Value.Width == imageWidth && - this.restoreArea.Value.Height == imageHeight) - { - using (PixelAccessor pixelAccessor = frame.Lock()) - { - pixelAccessor.Reset(); - } - } - else - { - using (var emptyRow = new PixelArea(this.restoreArea.Value.Width, ComponentOrder.Xyzw)) - { - using (PixelAccessor pixelAccessor = frame.Lock()) - { - for (int y = this.restoreArea.Value.Top; y < this.restoreArea.Value.Top + this.restoreArea.Value.Height; y++) - { - pixelAccessor.CopyFrom(emptyRow, y, this.restoreArea.Value.Left); - } - } - } - } + BufferArea pixelArea = frame.PixelBuffer.GetArea(this.restoreArea.Value); + pixelArea.Clear(); this.restoreArea = null; } @@ -606,7 +581,8 @@ namespace SixLabors.ImageSharp.Formats.Gif if (this.logicalScreenDescriptor.GlobalColorTableFlag) { this.globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3; - this.globalColorTable = Buffer.CreateClean(this.globalColorTableLength); + + this.globalColorTable = this.MemoryManager.AllocateManagedByteBuffer(this.globalColorTableLength, true); // Read the global color table from the stream stream.Read(this.globalColorTable.Array, 0, this.globalColorTableLength); diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index ccf46a17d6..b548098be3 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Generic; using System.IO; using System.Text; + +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Quantizers; @@ -44,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Gif public void Encode(Image image, Stream stream) where TPixel : struct, IPixel { - var encoder = new GifEncoderCore(this); + var encoder = new GifEncoderCore(image.GetConfiguration().MemoryManager, this); encoder.Encode(image, stream); } } diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 41c8e944d8..13ca5f2c61 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Text; using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Quantizers; @@ -18,6 +19,8 @@ namespace SixLabors.ImageSharp.Formats.Gif /// internal sealed class GifEncoderCore { + private readonly MemoryManager memoryManager; + /// /// The temp buffer used to reduce allocations. /// @@ -61,9 +64,11 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The options for the encoder. - public GifEncoderCore(IGifEncoderOptions options) + public GifEncoderCore(MemoryManager memoryManager, IGifEncoderOptions options) { + this.memoryManager = memoryManager; this.textEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding; this.quantizer = options.Quantizer; @@ -350,24 +355,21 @@ namespace SixLabors.ImageSharp.Formats.Gif // Get max colors for bit depth. int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3; - byte[] colorTable = ArrayPool.Shared.Rent(colorTableLength); var rgb = default(Rgb24); - try + using (IManagedByteBuffer colorTable = this.memoryManager.AllocateManagedByteBuffer(colorTableLength)) { + Span colorTableSpan = colorTable.Span; + for (int i = 0; i < pixelCount; i++) { int offset = i * 3; image.Palette[i].ToRgb24(ref rgb); - colorTable[offset] = rgb.R; - colorTable[offset + 1] = rgb.G; - colorTable[offset + 2] = rgb.B; + colorTableSpan[offset] = rgb.R; + colorTableSpan[offset + 1] = rgb.G; + colorTableSpan[offset + 2] = rgb.B; } - writer.Write(colorTable, 0, colorTableLength); - } - finally - { - ArrayPool.Shared.Return(colorTable); + writer.Write(colorTable.Array, 0, colorTableLength); } } @@ -380,7 +382,7 @@ namespace SixLabors.ImageSharp.Formats.Gif private void WriteImageData(QuantizedImage image, EndianBinaryWriter writer) where TPixel : struct, IPixel { - using (var encoder = new LzwEncoder(image.Pixels, (byte)this.bitDepth)) + using (var encoder = new LzwEncoder(this.memoryManager, image.Pixels, (byte)this.bitDepth)) { encoder.Encode(writer.BaseStream); } diff --git a/src/ImageSharp/Formats/Gif/ImageExtensions.cs b/src/ImageSharp/Formats/Gif/ImageExtensions.cs index 939eb456e1..78acadb4b1 100644 --- a/src/ImageSharp/Formats/Gif/ImageExtensions.cs +++ b/src/ImageSharp/Formats/Gif/ImageExtensions.cs @@ -36,6 +36,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().FindEncoder(ImageFormats.Gif)); + => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ImageFormats.Gif)); } } diff --git a/src/ImageSharp/Formats/Gif/LzwDecoder.cs b/src/ImageSharp/Formats/Gif/LzwDecoder.cs index 3284dad657..0c73efea46 100644 --- a/src/ImageSharp/Formats/Gif/LzwDecoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwDecoder.cs @@ -5,6 +5,8 @@ using System; using System.Buffers; using System.IO; +using SixLabors.ImageSharp.Memory; + namespace SixLabors.ImageSharp.Formats.Gif { /// @@ -30,17 +32,17 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The prefix buffer. /// - private readonly int[] prefix; + private readonly IBuffer prefix; /// /// The suffix buffer. /// - private readonly int[] suffix; + private readonly IBuffer suffix; /// /// The pixel stack buffer. /// - private readonly int[] pixelStack; + private readonly IBuffer pixelStack; /// /// A value indicating whether this instance of the given entity has been disposed. @@ -59,21 +61,18 @@ namespace SixLabors.ImageSharp.Formats.Gif /// Initializes a new instance of the class /// and sets the stream, where the compressed data should be read from. /// + /// The to use for buffer allocations. /// The stream to read from. /// is null. - public LzwDecoder(Stream stream) + public LzwDecoder(MemoryManager memoryManager, Stream stream) { Guard.NotNull(stream, nameof(stream)); this.stream = stream; - this.prefix = ArrayPool.Shared.Rent(MaxStackSize); - this.suffix = ArrayPool.Shared.Rent(MaxStackSize); - this.pixelStack = ArrayPool.Shared.Rent(MaxStackSize + 1); - - Array.Clear(this.prefix, 0, MaxStackSize); - Array.Clear(this.suffix, 0, MaxStackSize); - Array.Clear(this.pixelStack, 0, MaxStackSize + 1); + this.prefix = memoryManager.Allocate(MaxStackSize, true); + this.suffix = memoryManager.Allocate(MaxStackSize, true); + this.pixelStack = memoryManager.Allocate(MaxStackSize + 1, true); } /// @@ -116,10 +115,14 @@ namespace SixLabors.ImageSharp.Formats.Gif int data = 0; int first = 0; + Span prefixSpan = this.prefix.Span; + Span suffixSpan = this.suffix.Span; + Span pixelStackSpan = this.pixelStack.Span; + for (code = 0; code < clearCode; code++) { - this.prefix[code] = 0; - this.suffix[code] = (byte)code; + prefixSpan[code] = 0; + suffixSpan[code] = (byte)code; } byte[] buffer = new byte[255]; @@ -173,7 +176,7 @@ namespace SixLabors.ImageSharp.Formats.Gif if (oldCode == NullCode) { - this.pixelStack[top++] = this.suffix[code]; + pixelStackSpan[top++] = suffixSpan[code]; oldCode = code; first = code; continue; @@ -182,27 +185,27 @@ namespace SixLabors.ImageSharp.Formats.Gif int inCode = code; if (code == availableCode) { - this.pixelStack[top++] = (byte)first; + pixelStackSpan[top++] = (byte)first; code = oldCode; } while (code > clearCode) { - this.pixelStack[top++] = this.suffix[code]; - code = this.prefix[code]; + pixelStackSpan[top++] = suffixSpan[code]; + code = prefixSpan[code]; } - first = this.suffix[code]; + first = suffixSpan[code]; - this.pixelStack[top++] = this.suffix[code]; + pixelStackSpan[top++] = suffixSpan[code]; // Fix for Gifs that have "deferred clear code" as per here : // https://bugzilla.mozilla.org/show_bug.cgi?id=55918 if (availableCode < MaxStackSize) { - this.prefix[availableCode] = oldCode; - this.suffix[availableCode] = first; + prefixSpan[availableCode] = oldCode; + suffixSpan[availableCode] = first; availableCode++; if (availableCode == codeMask + 1 && availableCode < MaxStackSize) { @@ -218,7 +221,7 @@ namespace SixLabors.ImageSharp.Formats.Gif top--; // Clear missing pixels - pixels[xyz++] = (byte)this.pixelStack[top]; + pixels[xyz++] = (byte)pixelStackSpan[top]; } } @@ -262,9 +265,9 @@ namespace SixLabors.ImageSharp.Formats.Gif if (disposing) { - ArrayPool.Shared.Return(this.prefix); - ArrayPool.Shared.Return(this.suffix); - ArrayPool.Shared.Return(this.pixelStack); + this.prefix?.Dispose(); + this.suffix?.Dispose(); + this.pixelStack?.Dispose(); } this.isDisposed = true; diff --git a/src/ImageSharp/Formats/Gif/LzwEncoder.cs b/src/ImageSharp/Formats/Gif/LzwEncoder.cs index b7bfd0fd25..35c4148964 100644 --- a/src/ImageSharp/Formats/Gif/LzwEncoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwEncoder.cs @@ -5,6 +5,8 @@ using System; using System.Buffers; using System.IO; +using SixLabors.ImageSharp.Memory; + namespace SixLabors.ImageSharp.Formats.Gif { /// @@ -69,12 +71,12 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The hash table. /// - private readonly int[] hashTable; + private readonly IBuffer hashTable; /// /// The code table. /// - private readonly int[] codeTable; + private readonly IBuffer codeTable; /// /// Define the storage for the packet accumulator. @@ -189,17 +191,16 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The array of indexed pixels. /// The color depth in bits. - public LzwEncoder(byte[] indexedPixels, int colorDepth) + public LzwEncoder(MemoryManager memoryManager, byte[] indexedPixels, int colorDepth) { this.pixelArray = indexedPixels; this.initialCodeSize = Math.Max(2, colorDepth); - this.hashTable = ArrayPool.Shared.Rent(HashSize); - this.codeTable = ArrayPool.Shared.Rent(HashSize); - Array.Clear(this.hashTable, 0, HashSize); - Array.Clear(this.codeTable, 0, HashSize); + this.hashTable = memoryManager.Allocate(HashSize, true); + this.codeTable = memoryManager.Allocate(HashSize, true); } /// @@ -258,7 +259,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The output stream. private void ClearBlock(Stream stream) { - this.ResetCodeTable(this.hsize); + this.ResetCodeTable(); this.freeEntry = this.clearCode + 2; this.clearFlag = true; @@ -268,13 +269,15 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Reset the code table. /// - /// The hash size. - private void ResetCodeTable(int size) + private void ResetCodeTable() { - for (int i = 0; i < size; ++i) - { - this.hashTable[i] = -1; - } + this.hashTable.Span.Fill(-1); + + // Original code: + // for (int i = 0; i < size; ++i) + // { + // this.hashTable[i] = -1; + // } } /// @@ -316,23 +319,26 @@ namespace SixLabors.ImageSharp.Formats.Gif hsizeReg = this.hsize; - this.ResetCodeTable(hsizeReg); // clear hash table + this.ResetCodeTable(); // clear hash table this.Output(this.clearCode, stream); + Span hashTableSpan = this.hashTable.Span; + Span codeTableSpan = this.codeTable.Span; + while ((c = this.NextPixel()) != Eof) { fcode = (c << this.maxbits) + ent; int i = (c << hshift) ^ ent /* = 0 */; - if (this.hashTable[i] == fcode) + if (hashTableSpan[i] == fcode) { - ent = this.codeTable[i]; + ent = codeTableSpan[i]; continue; } // Non-empty slot - if (this.hashTable[i] >= 0) + if (hashTableSpan[i] >= 0) { int disp = hsizeReg - i; if (i == 0) @@ -347,15 +353,15 @@ namespace SixLabors.ImageSharp.Formats.Gif i += hsizeReg; } - if (this.hashTable[i] == fcode) + if (hashTableSpan[i] == fcode) { - ent = this.codeTable[i]; + ent = codeTableSpan[i]; break; } } - while (this.hashTable[i] >= 0); + while (hashTableSpan[i] >= 0); - if (this.hashTable[i] == fcode) + if (hashTableSpan[i] == fcode) { continue; } @@ -365,8 +371,8 @@ namespace SixLabors.ImageSharp.Formats.Gif ent = c; if (this.freeEntry < this.maxmaxcode) { - this.codeTable[i] = this.freeEntry++; // code -> hashtable - this.hashTable[i] = fcode; + codeTableSpan[i] = this.freeEntry++; // code -> hashtable + hashTableSpan[i] = fcode; } else { @@ -483,8 +489,8 @@ namespace SixLabors.ImageSharp.Formats.Gif if (disposing) { - ArrayPool.Shared.Return(this.hashTable); - ArrayPool.Shared.Return(this.codeTable); + this.hashTable?.Dispose(); + this.codeTable?.Dispose(); } this.isDisposed = true; diff --git a/src/ImageSharp/Formats/Gif/PackedField.cs b/src/ImageSharp/Formats/Gif/PackedField.cs index 962e2082bf..28a415e2b8 100644 --- a/src/ImageSharp/Formats/Gif/PackedField.cs +++ b/src/ImageSharp/Formats/Gif/PackedField.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// Represents a byte of data in a GIF data stream which contains a number /// of data items. /// - internal struct PackedField : IEquatable + internal readonly struct PackedField : IEquatable { /// /// The individual bits representing the packed byte. diff --git a/src/ImageSharp/Formats/ImageFormatManager.cs b/src/ImageSharp/Formats/ImageFormatManager.cs new file mode 100644 index 0000000000..67ba111474 --- /dev/null +++ b/src/ImageSharp/Formats/ImageFormatManager.cs @@ -0,0 +1,186 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace SixLabors.ImageSharp.Formats +{ + /// + /// Collection of Image Formats to be used in class. + /// + public class ImageFormatManager + { + /// + /// The list of supported keyed to mime types. + /// + private readonly ConcurrentDictionary mimeTypeEncoders = new ConcurrentDictionary(); + + /// + /// The list of supported keyed to mime types. + /// + private readonly ConcurrentDictionary mimeTypeDecoders = new ConcurrentDictionary(); + + /// + /// The list of supported s. + /// + private readonly ConcurrentBag imageFormats = new ConcurrentBag(); + + /// + /// The list of supported s. + /// + private ConcurrentBag imageFormatDetectors = new ConcurrentBag(); + + /// + /// Initializes a new instance of the class. + /// + public ImageFormatManager() + { + } + + /// + /// Gets the maximum header size of all the formats. + /// + internal int MaxHeaderSize { get; private set; } + + /// + /// Gets the currently registered s. + /// + public IEnumerable ImageFormats => this.imageFormats; + + /// + /// Gets the currently registered s. + /// + internal IEnumerable FormatDetectors => this.imageFormatDetectors; + + /// + /// Gets the currently registered s. + /// + internal IEnumerable> ImageDecoders => this.mimeTypeDecoders; + + /// + /// Gets the currently registered s. + /// + internal IEnumerable> ImageEncoders => this.mimeTypeEncoders; + + /// + /// Registers a new format provider. + /// + /// The format to register as a known format. + public void AddImageFormat(IImageFormat format) + { + Guard.NotNull(format, nameof(format)); + Guard.NotNull(format.MimeTypes, nameof(format.MimeTypes)); + Guard.NotNull(format.FileExtensions, nameof(format.FileExtensions)); + this.imageFormats.Add(format); + } + + /// + /// For the specified file extensions type find the e . + /// + /// The extension to discover + /// The if found otherwise null + public IImageFormat FindFormatByFileExtension(string extension) + { + return this.imageFormats.FirstOrDefault(x => x.FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)); + } + + /// + /// For the specified mime type find the . + /// + /// The mime-type to discover + /// The if found; otherwise null + public IImageFormat FindFormatByMimeType(string mimeType) + { + return this.imageFormats.FirstOrDefault(x => x.MimeTypes.Contains(mimeType, StringComparer.OrdinalIgnoreCase)); + } + + /// + /// Sets a specific image encoder as the encoder for a specific image format. + /// + /// The image format to register the encoder for. + /// The encoder to use, + public void SetEncoder(IImageFormat imageFormat, IImageEncoder encoder) + { + Guard.NotNull(imageFormat, nameof(imageFormat)); + Guard.NotNull(encoder, nameof(encoder)); + this.AddImageFormat(imageFormat); + this.mimeTypeEncoders.AddOrUpdate(imageFormat, encoder, (s, e) => encoder); + } + + /// + /// Sets a specific image decoder as the decoder for a specific image format. + /// + /// The image format to register the encoder for. + /// The decoder to use, + public void SetDecoder(IImageFormat imageFormat, IImageDecoder decoder) + { + Guard.NotNull(imageFormat, nameof(imageFormat)); + Guard.NotNull(decoder, nameof(decoder)); + this.AddImageFormat(imageFormat); + this.mimeTypeDecoders.AddOrUpdate(imageFormat, decoder, (s, e) => decoder); + } + + /// + /// Removes all the registered image format detectors. + /// + public void ClearImageFormatDetectors() + { + this.imageFormatDetectors = new ConcurrentBag(); + } + + /// + /// Adds a new detector for detecting mime types. + /// + /// The detector to add + public void AddImageFormatDetector(IImageFormatDetector detector) + { + Guard.NotNull(detector, nameof(detector)); + this.imageFormatDetectors.Add(detector); + this.SetMaxHeaderSize(); + } + + /// + /// For the specified mime type find the decoder. + /// + /// The format to discover + /// The if found otherwise null + public IImageDecoder FindDecoder(IImageFormat format) + { + Guard.NotNull(format, nameof(format)); + if (this.mimeTypeDecoders.TryGetValue(format, out IImageDecoder decoder)) + { + return decoder; + } + + return null; + } + + /// + /// For the specified mime type find the encoder. + /// + /// The format to discover + /// The if found otherwise null + public IImageEncoder FindEncoder(IImageFormat format) + { + Guard.NotNull(format, nameof(format)); + if (this.mimeTypeEncoders.TryGetValue(format, out IImageEncoder encoder)) + { + return encoder; + } + + return null; + } + + /// + /// Sets the max header size. + /// + private void SetMaxHeaderSize() + { + this.MaxHeaderSize = this.imageFormatDetectors.Max(x => x.HeaderSize); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs index 1066cfa808..11a456ef9b 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; namespace SixLabors.ImageSharp.Formats.Jpeg.Common @@ -34,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common public Block8x8(Span coefficients) { ref byte selfRef = ref Unsafe.As(ref this); - ref byte sourceRef = ref coefficients.NonPortableCast().DangerousGetPinnableReference(); + ref byte sourceRef = ref MemoryMarshal.GetReference(coefficients.NonPortableCast()); Unsafe.CopyBlock(ref selfRef, ref sourceRef, Size * sizeof(short)); } @@ -204,7 +205,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common public void CopyTo(Span destination) { ref byte selfRef = ref Unsafe.As(ref this); - ref byte destRef = ref destination.NonPortableCast().DangerousGetPinnableReference(); + ref byte destRef = ref MemoryMarshal.GetReference(destination.NonPortableCast()); Unsafe.CopyBlock(ref destRef, ref selfRef, Size * sizeof(short)); } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs index 39a6bee2e4..d8963a8b60 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs @@ -26,6 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common return; } + ref float destBase = ref area.GetReferenceToOrigin(); + // TODO: Optimize: implement all the cases with loopless special code! (T4?) for (int y = 0; y < 8; y++) { @@ -40,9 +42,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common for (int i = 0; i < verticalScale; i++) { + int baseIdx = ((yy + i) * area.Stride) + xx; + for (int j = 0; j < horizontalScale; j++) { - area[xx + j, yy + i] = value; + // area[xx + j, yy + i] = value; + Unsafe.Add(ref destBase, baseIdx + j) = value; } } } @@ -53,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common public void CopyTo(BufferArea area) { ref byte selfBase = ref Unsafe.As(ref this); - ref byte destBase = ref Unsafe.As(ref area.GetReferenceToOrigo()); + ref byte destBase = ref Unsafe.As(ref area.GetReferenceToOrigin()); int destStride = area.Stride * sizeof(float); CopyRowImpl(ref selfBase, ref destBase, destStride, 0); @@ -76,7 +81,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common private void CopyTo2x2(BufferArea area) { - ref float destBase = ref area.GetReferenceToOrigo(); + ref float destBase = ref area.GetReferenceToOrigin(); int destStride = area.Stride; this.WidenCopyImpl2x2(ref destBase, 0, destStride); diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs index 2dd42288cb..f45b5df4eb 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs @@ -163,7 +163,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common [MethodImpl(MethodImplOptions.AggressiveInlining)] public void LoadFrom(Span source) { - ref byte s = ref Unsafe.As(ref source.DangerousGetPinnableReference()); + ref byte s = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); ref byte d = ref Unsafe.As(ref this); Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float)); @@ -203,7 +203,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common [MethodImpl(MethodImplOptions.AggressiveInlining)] public void CopyTo(Span dest) { - ref byte d = ref Unsafe.As(ref dest.DangerousGetPinnableReference()); + ref byte d = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); ref byte s = ref Unsafe.As(ref this); Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float)); diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/AdobeMarker.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/AdobeMarker.cs index 0ec2297d76..d55e36bd48 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/AdobeMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/AdobeMarker.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder /// Provides information about the Adobe marker segment. /// /// See the included 5116.DCT.pdf file in the source for more information. - internal struct AdobeMarker : IEquatable + internal readonly struct AdobeMarker : IEquatable { /// /// Gets the length of an adobe marker segment. diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs index a7fc136afe..2f214f88a9 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs @@ -4,6 +4,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Common.Tuples; namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters @@ -37,14 +38,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters DebugGuard.IsTrue(result.Length % 8 == 0, nameof(result), "result.Length should be divisable by 8!"); ref Vector4Pair yBase = - ref Unsafe.As(ref values.Component0.DangerousGetPinnableReference()); + ref Unsafe.As(ref MemoryMarshal.GetReference(values.Component0)); ref Vector4Pair cbBase = - ref Unsafe.As(ref values.Component1.DangerousGetPinnableReference()); + ref Unsafe.As(ref MemoryMarshal.GetReference(values.Component1)); ref Vector4Pair crBase = - ref Unsafe.As(ref values.Component2.DangerousGetPinnableReference()); + ref Unsafe.As(ref MemoryMarshal.GetReference(values.Component2)); ref Vector4Octet resultBase = - ref Unsafe.As(ref result.DangerousGetPinnableReference()); + ref Unsafe.As(ref MemoryMarshal.GetReference(result)); var chromaOffset = new Vector4(-128f); diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs index 77e74c32b0..f8a4514221 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs @@ -4,6 +4,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Common.Tuples; // ReSharper disable ImpureMethodCallOnReadonlyValueField @@ -46,14 +47,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters } ref Vector yBase = - ref Unsafe.As>(ref values.Component0.DangerousGetPinnableReference()); + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); ref Vector cbBase = - ref Unsafe.As>(ref values.Component1.DangerousGetPinnableReference()); + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); ref Vector crBase = - ref Unsafe.As>(ref values.Component2.DangerousGetPinnableReference()); + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); ref Vector4Octet resultBase = - ref Unsafe.As(ref result.DangerousGetPinnableReference()); + ref Unsafe.As(ref MemoryMarshal.GetReference(result)); var chromaOffset = new Vector(-128f); diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.cs index e0abc3215c..187b65f72b 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/ColorConverters/JpegColorConverter.cs @@ -66,7 +66,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder.ColorConverters /// /// A stack-only struct to reference the input buffers using -s. /// - public struct ComponentValues +#pragma warning disable SA1206 // Declaration keywords should follow order + public readonly ref struct ComponentValues +#pragma warning restore SA1206 // Declaration keywords should follow order { /// /// The component count diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JFifMarker.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JFifMarker.cs index cba7be5539..c856fd04a6 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JFifMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JFifMarker.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder /// Provides information about the JFIF marker segment /// TODO: Thumbnail? /// - internal struct JFifMarker : IEquatable + internal readonly struct JFifMarker : IEquatable { /// /// Gets the length of an JFIF marker segment. diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs index 574967b6bf..5e8e8fa2cc 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs @@ -8,7 +8,7 @@ using SixLabors.Primitives; namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder { /// - /// Encapsulates the implementation of processing "raw" -s into Jpeg image channels. + /// Encapsulates the implementation of processing "raw" -s into Jpeg image channels. /// [StructLayout(LayoutKind.Sequential)] internal struct JpegBlockPostProcessor diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs index 87c1431e02..1be637b6df 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs @@ -22,11 +22,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder /// /// Initializes a new instance of the class. /// - public JpegComponentPostProcessor(JpegImagePostProcessor imagePostProcessor, IJpegComponent component) + public JpegComponentPostProcessor(MemoryManager memoryManager, JpegImagePostProcessor imagePostProcessor, IJpegComponent component) { this.Component = component; this.ImagePostProcessor = imagePostProcessor; - this.ColorBuffer = new Buffer2D(imagePostProcessor.PostProcessorBufferSize); + this.ColorBuffer = memoryManager.Allocate2D( + imagePostProcessor.PostProcessorBufferSize.Width, + imagePostProcessor.PostProcessorBufferSize.Height); this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height; this.blockAreaSize = this.Component.SubSamplingDivisors * 8; @@ -43,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder public IJpegComponent Component { get; } /// - /// Gets the temporal working buffer of color values. + /// Gets the temporary working buffer of color values. /// public Buffer2D ColorBuffer { get; } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs index 125ec52723..2adf3e02d0 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegImagePostProcessor.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder /// /// Temporal buffer to store a row of colors. /// - private readonly Buffer rgbaBuffer; + private readonly IBuffer rgbaBuffer; /// /// The corresponding to the current determined by . @@ -44,16 +44,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The representing the uncompressed spectral Jpeg data - public JpegImagePostProcessor(IRawJpegData rawJpeg) + public JpegImagePostProcessor(MemoryManager memoryManager, IRawJpegData rawJpeg) { this.RawJpeg = rawJpeg; IJpegComponent c0 = rawJpeg.Components.First(); this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / BlockRowsPerStep; this.PostProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, PixelRowsPerStep); - this.ComponentProcessors = rawJpeg.Components.Select(c => new JpegComponentPostProcessor(this, c)).ToArray(); - this.rgbaBuffer = new Buffer(rawJpeg.ImageSizeInPixels.Width); + this.ComponentProcessors = rawJpeg.Components.Select(c => new JpegComponentPostProcessor(memoryManager, this, c)).ToArray(); + this.rgbaBuffer = memoryManager.Allocate(rawJpeg.ImageSizeInPixels.Width); this.colorConverter = ColorConverters.JpegColorConverter.GetConverter(rawJpeg.ColorSpace); } @@ -73,7 +74,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder public int NumberOfPostProcessorSteps { get; } /// - /// Gets the size of the temporal buffers we need to allocate into . + /// Gets the size of the temporary buffers we need to allocate into . /// public Size PostProcessorBufferSize { get; } @@ -149,11 +150,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder int y = yy - this.PixelRowCounter; var values = new ColorConverters.JpegColorConverter.ComponentValues(buffers, y); - this.colorConverter.ConvertToRGBA(values, this.rgbaBuffer); + this.colorConverter.ConvertToRGBA(values, this.rgbaBuffer.Span); Span destRow = destination.GetPixelRowSpan(yy); - PixelOperations.Instance.PackFromVector4(this.rgbaBuffer, destRow, destination.Width); + PixelOperations.Instance.PackFromVector4(this.rgbaBuffer.Span, destRow, destination.Width); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.cs b/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.cs new file mode 100644 index 0000000000..1bb37a7d32 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; + +// +namespace SixLabors.ImageSharp.Formats.Jpeg.Common +{ + internal unsafe partial struct GenericBlock8x8 + { + #pragma warning disable 169 + + // It's not allowed use fix-sized buffers with generics, need to place all the fields manually: + private T _y0_x0, _y0_x1, _y0_x2, _y0_x3, _y0_x4, _y0_x5, _y0_x6, _y0_x7; + private T _y1_x0, _y1_x1, _y1_x2, _y1_x3, _y1_x4, _y1_x5, _y1_x6, _y1_x7; + private T _y2_x0, _y2_x1, _y2_x2, _y2_x3, _y2_x4, _y2_x5, _y2_x6, _y2_x7; + private T _y3_x0, _y3_x1, _y3_x2, _y3_x3, _y3_x4, _y3_x5, _y3_x6, _y3_x7; + private T _y4_x0, _y4_x1, _y4_x2, _y4_x3, _y4_x4, _y4_x5, _y4_x6, _y4_x7; + private T _y5_x0, _y5_x1, _y5_x2, _y5_x3, _y5_x4, _y5_x5, _y5_x6, _y5_x7; + private T _y6_x0, _y6_x1, _y6_x2, _y6_x3, _y6_x4, _y6_x5, _y6_x6, _y6_x7; + private T _y7_x0, _y7_x1, _y7_x2, _y7_x3, _y7_x4, _y7_x5, _y7_x6, _y7_x7; + + #pragma warning restore 169 + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.tt b/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.tt new file mode 100644 index 0000000000..d9b15b34fa --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.Generated.tt @@ -0,0 +1,43 @@ +<# +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. +#> +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ output extension=".cs" #> +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; + +// +namespace SixLabors.ImageSharp.Formats.Jpeg.Common +{ + internal unsafe partial struct GenericBlock8x8 + { + #pragma warning disable 169 + + // It's not allowed use fix-sized buffers with generics, need to place all the fields manually: + <# + PushIndent(" "); + Write(" "); + for (int y = 0; y < 8; y++) + { + Write("private T "); + for (int x = 0; x < 8; x++) + { + Write($"_y{y}_x{x}"); + if (x < 7) Write(", "); + } + WriteLine(";"); + } + PopIndent(); + #> + + #pragma warning restore 169 + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.cs b/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.cs new file mode 100644 index 0000000000..09a7eb73aa --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/GenericBlock8x8.cs @@ -0,0 +1,129 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Formats.Jpeg.Common +{ + /// + /// A generic 8x8 block implementation, useful for manipulating custom 8x8 pixel data. + /// + [StructLayout(LayoutKind.Sequential)] + internal unsafe partial struct GenericBlock8x8 + where T : struct + { + public const int Size = 64; + + public const int SizeInBytes = Size * 3; + + /// + /// FOR TESTING ONLY! + /// Gets or sets a value at the given index + /// + /// The index + /// The value + public T this[int idx] + { + get + { + ref T selfRef = ref Unsafe.As, T>(ref this); + return Unsafe.Add(ref selfRef, idx); + } + + set + { + ref T selfRef = ref Unsafe.As, T>(ref this); + Unsafe.Add(ref selfRef, idx) = value; + } + } + + /// + /// FOR TESTING ONLY! + /// Gets or sets a value in a row+coulumn of the 8x8 block + /// + /// The x position index in the row + /// The column index + /// The value + public T this[int x, int y] + { + get => this[(y * 8) + x]; + set => this[(y * 8) + x] = value; + } + + public void LoadAndStretchEdges(IPixelSource source, int sourceX, int sourceY) + where TPixel : struct, IPixel + { + var buffer = source.PixelBuffer as Buffer2D; + if (buffer == null) + { + throw new InvalidOperationException("LoadAndStretchEdges() is only valid for TPixel == T !"); + } + + this.LoadAndStretchEdges(buffer, sourceX, sourceY); + } + + /// + /// Load a 8x8 region of an image into the block. + /// The "outlying" area of the block will be stretched out with pixels on the right and bottom edge of the image. + /// + public void LoadAndStretchEdges(Buffer2D source, int sourceX, int sourceY) + { + int width = Math.Min(8, source.Width - sourceX); + int height = Math.Min(8, source.Height - sourceY); + + if (width <= 0 || height <= 0) + { + return; + } + + uint byteWidth = (uint)width * (uint)Unsafe.SizeOf(); + int remainderXCount = 8 - width; + + ref byte blockStart = ref Unsafe.As, byte>(ref this); + ref byte imageStart = ref Unsafe.As( + ref Unsafe.Add(ref MemoryMarshal.GetReference(source.GetRowSpan(sourceY)), sourceX)); + + int blockRowSizeInBytes = 8 * Unsafe.SizeOf(); + int imageRowSizeInBytes = source.Width * Unsafe.SizeOf(); + + for (int y = 0; y < height; y++) + { + ref byte s = ref Unsafe.Add(ref imageStart, y * imageRowSizeInBytes); + ref byte d = ref Unsafe.Add(ref blockStart, y * blockRowSizeInBytes); + + Unsafe.CopyBlock(ref d, ref s, byteWidth); + + ref T last = ref Unsafe.Add(ref Unsafe.As(ref d), width - 1); + + for (int x = 1; x <= remainderXCount; x++) + { + Unsafe.Add(ref last, x) = last; + } + } + + int remainderYCount = 8 - height; + + if (remainderYCount == 0) + { + return; + } + + ref byte lastRowStart = ref Unsafe.Add(ref blockStart, (height - 1) * blockRowSizeInBytes); + + for (int y = 1; y <= remainderYCount; y++) + { + ref byte remStart = ref Unsafe.Add(ref lastRowStart, blockRowSizeInBytes * y); + Unsafe.CopyBlock(ref remStart, ref lastRowStart, (uint)blockRowSizeInBytes); + } + } + + /// + /// Only for on-stack instances! + /// + public Span AsSpanUnsafe() => new Span(Unsafe.AsPointer(ref this), Size); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs index 38507d58c8..2a3817400c 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/Bytes.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; using System.IO; using System.Runtime.CompilerServices; @@ -12,6 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// Bytes is a byte buffer, similar to a stream, except that it /// has to be able to unread more than 1 byte, due to byte stuffing. /// Byte stuffing is specified in section F.1.2.3. + /// TODO: Optimize buffer management inside this class! /// internal struct Bytes : IDisposable { @@ -48,20 +48,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// public int UnreadableBytes; - private static readonly ArrayPool BytePool = ArrayPool.Create(BufferSize, 50); - - private static readonly ArrayPool IntPool = ArrayPool.Create(BufferSize, 50); - /// /// Creates a new instance of the , and initializes it's buffer. /// /// The bytes created public static Bytes Create() { + // DO NOT bother with buffers and array pooling here! + // It only makes things worse! return new Bytes { - Buffer = BytePool.Rent(BufferSize), - BufferAsInt = IntPool.Rent(BufferSize) + Buffer = new byte[BufferSize], + BufferAsInt = new int[BufferSize] }; } @@ -70,12 +68,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// public void Dispose() { - if (this.Buffer != null) - { - BytePool.Return(this.Buffer); - IntPool.Return(this.BufferAsInt); - } - this.Buffer = null; this.BufferAsInt = null; } diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/DecoderThrowHelper.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/DecoderThrowHelper.cs index d5a9340d72..904ce00dde 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/DecoderThrowHelper.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/DecoderThrowHelper.cs @@ -18,6 +18,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder [MethodImpl(MethodImplOptions.NoInlining)] public static void ThrowExceptionForErrorCode(this OrigDecoderErrorCode errorCode) { + // REMARK: If this method throws for an image that is expected to be decodable, + // consider using the ***Unsafe variant of the parsing method that asks for ThrowExceptionForErrorCode() + // then verify the error code + implement fallback logic manually! switch (errorCode) { case OrigDecoderErrorCode.NoError: diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs index a7a5fcd986..cb4b63cffd 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/InputProcessor.cs @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder public Stream InputStream { get; } /// - /// Gets the temporal buffer, same instance as + /// Gets the temporary buffer, same instance as /// public byte[] Temp { get; } @@ -105,6 +105,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder return this.Bytes.ReadByte(this.InputStream); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public OrigDecoderErrorCode ReadByteUnsafe(out byte result) + { + this.LastErrorCode = this.Bytes.ReadByteUnsafe(this.InputStream, out result); + return this.LastErrorCode; + } + /// /// Decodes a single bit /// TODO: This method (and also the usages) could be optimized by batching! @@ -373,5 +380,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder this.LastErrorCode = this.Bits.ReceiveExtendUnsafe(t, ref this, out x); return this.LastErrorCode; } + + /// + /// Reset the Huffman decoder. + /// + public void ResetHuffmanDecoder() + { + this.Bits = default(Bits); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs index c87752b371..e83dd75a54 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs @@ -54,8 +54,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// /// Initializes /// + /// The to use for buffer allocations. /// The instance - public void InitializeDerivedData(OrigJpegDecoderCore decoder) + public void InitializeDerivedData(MemoryManager memoryManager, OrigJpegDecoderCore decoder) { // For 4-component images (either CMYK or YCbCrK), we only support two // hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22]. @@ -78,7 +79,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors); } - this.SpectralBlocks = Buffer2D.CreateClean(this.SizeInBlocks); + this.SpectralBlocks = memoryManager.Allocate2D(this.SizeInBlocks.Width, this.SizeInBlocks.Height, true); } /// diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigHuffmanTree.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigHuffmanTree.cs index 4c97d57415..dbc7bb0f7f 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigHuffmanTree.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigHuffmanTree.cs @@ -3,13 +3,16 @@ using System; using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder { /// /// Represents a Huffman tree /// - internal struct OrigHuffmanTree : IDisposable + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct OrigHuffmanTree { /// /// The index of the AC table row @@ -67,35 +70,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// are 1 plus the code length, or 0 if the value is too large to fit in /// lutSize bits. /// - public int[] Lut; + public FixedInt32Buffer256 Lut; /// /// Gets the the decoded values, sorted by their encoding. /// - public int[] Values; + public FixedInt32Buffer256 Values; /// /// Gets the array of minimum codes. /// MinCodes[i] is the minimum code of length i, or -1 if there are no codes of that length. /// - public int[] MinCodes; + public FixedInt32Buffer16 MinCodes; /// /// Gets the array of maximum codes. /// MaxCodes[i] is the maximum code of length i, or -1 if there are no codes of that length. /// - public int[] MaxCodes; + public FixedInt32Buffer16 MaxCodes; /// /// Gets the array of indices. Indices[i] is the index into Values of MinCodes[i]. /// - public int[] Indices; - - private static readonly ArrayPool IntPool256 = ArrayPool.Create(MaxNCodes, 50); - - private static readonly ArrayPool BytePool256 = ArrayPool.Create(MaxNCodes, 50); - - private static readonly ArrayPool CodesPool16 = ArrayPool.Create(MaxCodeLength, 50); + public FixedInt32Buffer16 Indices; /// /// Creates and initializes an array of instances of size @@ -103,35 +100,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// An array of instances representing the Huffman tables public static OrigHuffmanTree[] CreateHuffmanTrees() { - OrigHuffmanTree[] result = new OrigHuffmanTree[NumberOfTrees]; - for (int i = 0; i < MaxTc + 1; i++) - { - for (int j = 0; j < MaxTh + 1; j++) - { - result[(i * ThRowSize) + j].Init(); - } - } - - return result; - } - - /// - /// Disposes the underlying buffers - /// - public void Dispose() - { - IntPool256.Return(this.Lut, true); - IntPool256.Return(this.Values, true); - CodesPool16.Return(this.MinCodes, true); - CodesPool16.Return(this.MaxCodes, true); - CodesPool16.Return(this.Indices, true); + return new OrigHuffmanTree[NumberOfTrees]; } /// /// Internal part of the DHT processor, whatever does it mean /// /// The decoder instance - /// The temporal buffer that holds the data that has been read from the Jpeg stream + /// The temporary buffer that holds the data that has been read from the Jpeg stream /// Remaining bits public void ProcessDefineHuffmanTablesMarkerLoop( ref InputProcessor inputProcessor, @@ -166,75 +142,76 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder throw new ImageFormatException("DHT has wrong length"); } - byte[] values = null; - try - { - values = BytePool256.Rent(MaxNCodes); - inputProcessor.ReadFull(values, 0, this.Length); + byte[] values = new byte[MaxNCodes]; + inputProcessor.ReadFull(values, 0, this.Length); + fixed (int* valuesPtr = this.Values.Data) + fixed (int* lutPtr = this.Lut.Data) + { for (int i = 0; i < values.Length; i++) { - this.Values[i] = values[i]; + valuesPtr[i] = values[i]; } - } - finally - { - BytePool256.Return(values, true); - } - - // Derive the look-up table. - for (int i = 0; i < this.Lut.Length; i++) - { - this.Lut[i] = 0; - } - int x = 0, code = 0; + // Derive the look-up table. + for (int i = 0; i < MaxNCodes; i++) + { + lutPtr[i] = 0; + } - for (int i = 0; i < LutSizeLog2; i++) - { - code <<= 1; + int x = 0, code = 0; - for (int j = 0; j < ncodes[i]; j++) + for (int i = 0; i < LutSizeLog2; i++) { - // The codeLength is 1+i, so shift code by 8-(1+i) to - // calculate the high bits for every 8-bit sequence - // whose codeLength's high bits matches code. - // The high 8 bits of lutValue are the encoded value. - // The low 8 bits are 1 plus the codeLength. - int base2 = code << (7 - i); - int lutValue = (this.Values[x] << 8) | (2 + i); - - for (int k = 0; k < 1 << (7 - i); k++) + code <<= 1; + + for (int j = 0; j < ncodes[i]; j++) { - this.Lut[base2 | k] = lutValue; + // The codeLength is 1+i, so shift code by 8-(1+i) to + // calculate the high bits for every 8-bit sequence + // whose codeLength's high bits matches code. + // The high 8 bits of lutValue are the encoded value. + // The low 8 bits are 1 plus the codeLength. + int base2 = code << (7 - i); + int lutValue = (valuesPtr[x] << 8) | (2 + i); + + for (int k = 0; k < 1 << (7 - i); k++) + { + lutPtr[base2 | k] = lutValue; + } + + code++; + x++; } - - code++; - x++; } } - // Derive minCodes, maxCodes, and indices. - int c = 0, index = 0; - for (int i = 0; i < ncodes.Length; i++) + fixed (int* minCodesPtr = this.MinCodes.Data) + fixed (int* maxCodesPtr = this.MaxCodes.Data) + fixed (int* indicesPtr = this.Indices.Data) { - int nc = ncodes[i]; - if (nc == 0) - { - this.MinCodes[i] = -1; - this.MaxCodes[i] = -1; - this.Indices[i] = -1; - } - else + // Derive minCodes, maxCodes, and indices. + int c = 0, index = 0; + for (int i = 0; i < ncodes.Length; i++) { - this.MinCodes[i] = c; - this.MaxCodes[i] = c + nc - 1; - this.Indices[i] = index; - c += nc; - index += nc; - } + int nc = ncodes[i]; + if (nc == 0) + { + minCodesPtr[i] = -1; + maxCodesPtr[i] = -1; + indicesPtr[i] = -1; + } + else + { + minCodesPtr[i] = c; + maxCodesPtr[i] = c + nc - 1; + indicesPtr[i] = index; + c += nc; + index += nc; + } - c <<= 1; + c <<= 1; + } } } @@ -244,21 +221,42 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// The code /// The code length /// The value + [MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetValue(int code, int codeLength) { return this.Values[this.Indices[codeLength] + code - this.MinCodes[codeLength]]; } - /// - /// Initializes the Huffman tree - /// - private void Init() + [StructLayout(LayoutKind.Sequential)] + internal struct FixedInt32Buffer256 { - this.Lut = IntPool256.Rent(MaxNCodes); - this.Values = IntPool256.Rent(MaxNCodes); - this.MinCodes = CodesPool16.Rent(MaxCodeLength); - this.MaxCodes = CodesPool16.Rent(MaxCodeLength); - this.Indices = CodesPool16.Rent(MaxCodeLength); + public fixed int Data[256]; + + public int this[int idx] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + ref int self = ref Unsafe.As(ref this); + return Unsafe.Add(ref self, idx); + } + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct FixedInt32Buffer16 + { + public fixed int Data[16]; + + 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/GolangPort/Components/Decoder/OrigJpegScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.cs index 67abba9f33..d10def3ce7 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.cs @@ -94,6 +94,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// private int eobRun; + /// + /// The block counter + /// + private int blockCounter; + + /// + /// The MCU counter + /// + private int mcuCounter; + + /// + /// The expected RST marker value + /// + private byte expectedRst; + /// /// Initializes a default-constructed instance for reading data from -s stream. /// @@ -139,100 +154,136 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder { decoder.InputProcessor.ResetErrorState(); - int blockCount = 0; - int mcu = 0; - byte expectedRst = OrigJpegConstants.Markers.RST0; + this.blockCounter = 0; + this.mcuCounter = 0; + this.expectedRst = OrigJpegConstants.Markers.RST0; for (int my = 0; my < decoder.MCUCountY; my++) { for (int mx = 0; mx < decoder.MCUCountX; mx++) { - for (int scanIndex = 0; scanIndex < this.componentScanCount; scanIndex++) - { - this.ComponentIndex = this.pointers.ComponentScan[scanIndex].ComponentIndex; - OrigComponent component = decoder.Components[this.ComponentIndex]; + this.DecodeBlocksAtMcuIndex(decoder, mx, my); - this.hi = component.HorizontalSamplingFactor; - int vi = component.VerticalSamplingFactor; - - for (int j = 0; j < this.hi * vi; j++) - { - if (this.componentScanCount != 1) - { - this.bx = (this.hi * mx) + (j % this.hi); - this.by = (vi * my) + (j / this.hi); - } - else - { - int q = decoder.MCUCountX * this.hi; - this.bx = blockCount % q; - this.by = blockCount / q; - blockCount++; - if (this.bx * 8 >= decoder.ImageWidth || this.by * 8 >= decoder.ImageHeight) - { - continue; - } - } + this.mcuCounter++; - // Find the block at (bx,by) in the component's buffer: - ref Block8x8 blockRefOnHeap = ref component.GetBlockReference(this.bx, this.by); + // Handling restart intervals + // Useful info: https://stackoverflow.com/a/8751802 + if (decoder.IsAtRestartInterval(this.mcuCounter)) + { + this.ProcessRSTMarker(decoder); + this.Reset(decoder); + } + } + } + } - // Copy block to stack - this.data.Block = blockRefOnHeap; + private void DecodeBlocksAtMcuIndex(OrigJpegDecoderCore decoder, int mx, int my) + { + for (int scanIndex = 0; scanIndex < this.componentScanCount; scanIndex++) + { + this.ComponentIndex = this.pointers.ComponentScan[scanIndex].ComponentIndex; + OrigComponent component = decoder.Components[this.ComponentIndex]; - if (!decoder.InputProcessor.ReachedEOF) - { - this.DecodeBlock(decoder, scanIndex); - } + this.hi = component.HorizontalSamplingFactor; + int vi = component.VerticalSamplingFactor; - // Store the result block: - blockRefOnHeap = this.data.Block; + for (int j = 0; j < this.hi * vi; j++) + { + if (this.componentScanCount != 1) + { + this.bx = (this.hi * mx) + (j % this.hi); + this.by = (vi * my) + (j / this.hi); + } + else + { + int q = decoder.MCUCountX * this.hi; + this.bx = this.blockCounter % q; + this.by = this.blockCounter / q; + this.blockCounter++; + if (this.bx * 8 >= decoder.ImageWidth || this.by * 8 >= decoder.ImageHeight) + { + continue; } + } + + // Find the block at (bx,by) in the component's buffer: + ref Block8x8 blockRefOnHeap = ref component.GetBlockReference(this.bx, this.by); - // for j + // Copy block to stack + this.data.Block = blockRefOnHeap; + + if (!decoder.InputProcessor.ReachedEOF) + { + this.DecodeBlock(decoder, scanIndex); } - // for i - mcu++; + // Store the result block: + blockRefOnHeap = this.data.Block; + } + } + } - if (decoder.RestartInterval > 0 && mcu % decoder.RestartInterval == 0 && mcu < decoder.TotalMCUCount) + private void ProcessRSTMarker(OrigJpegDecoderCore decoder) + { + // Attempt to look for RST[0-7] markers to resynchronize from corrupt input. + if (!decoder.InputProcessor.ReachedEOF) + { + decoder.InputProcessor.ReadFullUnsafe(decoder.Temp, 0, 2); + if (decoder.InputProcessor.CheckEOFEnsureNoError()) + { + if (decoder.Temp[0] != 0xFF || decoder.Temp[1] != this.expectedRst) { - // A more sophisticated decoder could use RST[0-7] markers to resynchronize from corrupt input, - // but this one assumes well-formed input, and hence the restart marker follows immediately. - if (!decoder.InputProcessor.ReachedEOF) + bool invalidRst = true; + + // Most jpeg's containing well-formed input will have a RST[0-7] marker following immediately + // but some, see Issue #481, contain padding bytes "0xFF" before the RST[0-7] marker. + // If we identify that case we attempt to read until we have bypassed the padded bytes. + // We then check again for our RST marker and throw if invalid. + // No other methods are attempted to resynchronize from corrupt input. + if (decoder.Temp[0] == 0xFF && decoder.Temp[1] == 0xFF) { - decoder.InputProcessor.ReadFullUnsafe(decoder.Temp, 0, 2); - if (decoder.InputProcessor.CheckEOFEnsureNoError()) + while (decoder.Temp[0] == 0xFF && decoder.InputProcessor.CheckEOFEnsureNoError()) { - if (decoder.Temp[0] != 0xff || decoder.Temp[1] != expectedRst) - { - throw new ImageFormatException("Bad RST marker"); - } - - expectedRst++; - if (expectedRst == OrigJpegConstants.Markers.RST7 + 1) + decoder.InputProcessor.ReadFullUnsafe(decoder.Temp, 0, 1); + if (!decoder.InputProcessor.CheckEOFEnsureNoError()) { - expectedRst = OrigJpegConstants.Markers.RST0; + break; } } - } - // Reset the Huffman decoder. - decoder.InputProcessor.Bits = default(Bits); + // Have we found a valid restart marker? + invalidRst = decoder.Temp[0] != this.expectedRst; + } - // Reset the DC components, as per section F.2.1.3.1. - this.ResetDc(); + if (invalidRst) + { + throw new ImageFormatException("Bad RST marker"); + } + } - // Reset the progressive decoder state, as per section G.1.2.2. - this.eobRun = 0; + this.expectedRst++; + if (this.expectedRst == OrigJpegConstants.Markers.RST7 + 1) + { + this.expectedRst = OrigJpegConstants.Markers.RST0; } } - - // for mx } } - private void ResetDc() + private void Reset(OrigJpegDecoderCore decoder) + { + decoder.InputProcessor.ResetHuffmanDecoder(); + + this.ResetDcValues(); + + // Reset the progressive decoder state, as per section G.1.2.2. + this.eobRun = 0; + } + + /// + /// Reset the DC components, as per section F.2.1.3.1. + /// + private void ResetDcValues() { Unsafe.InitBlock(this.pointers.Dc, default(byte), sizeof(int) * OrigJpegDecoderCore.MaxComponents); } diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/HuffmanLut.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/HuffmanLut.cs index 2fb01c5c8c..7756a7e3ba 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/HuffmanLut.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/HuffmanLut.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder /// codeword size in bits and the 24 least significant bits hold the codeword. /// The maximum codeword size is 16 bits. /// - internal struct HuffmanLut + internal readonly struct HuffmanLut { /// /// The compiled representations of theHuffmanSpec. diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/HuffmanSpec.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/HuffmanSpec.cs index 8e40cb3689..1c8228aaa2 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/HuffmanSpec.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/HuffmanSpec.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder /// /// The Huffman encoding specifications. /// - internal struct HuffmanSpec + internal readonly struct HuffmanSpec { #pragma warning disable SA1118 // ParameterMustNotSpanMultipleLines diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/RgbToYCbCrTables.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/RgbToYCbCrTables.cs index 02bd451b94..923fe244eb 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/RgbToYCbCrTables.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/RgbToYCbCrTables.cs @@ -3,11 +3,14 @@ using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder { /// /// Provides 8-bit lookup tables for converting from Rgb to YCbCr colorspace. /// Methods to build the tables are based on libjpeg implementation. + /// TODO: Replace this logic with SIMD conversion (similar to the one in the decoder)! /// internal unsafe struct RgbToYCbCrTables { @@ -91,27 +94,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder } /// + /// TODO: Replace this logic with SIMD conversion (similar to the one in the decoder)! /// Optimized method to allocates the correct y, cb, and cr values to the DCT blocks from the given r, g, b values. /// - /// The The luminance block. - /// The red chroma block. - /// The blue chroma block. - /// The reference to the tables instance. - /// The current index. - /// The red value. - /// The green value. - /// The blue value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Allocate(ref float* yBlockRaw, ref float* cbBlockRaw, ref float* crBlockRaw, ref RgbToYCbCrTables* tables, int index, int r, int g, int b) + public void ConvertPixelInto(int r, int g, int b, ref float yResult, ref float cbResult, ref float crResult) { - // float y = (0.299F * r) + (0.587F * g) + (0.114F * b); - yBlockRaw[index] = (tables->YRTable[r] + tables->YGTable[g] + tables->YBTable[b]) >> ScaleBits; + ref int start = ref Unsafe.As(ref this); + + ref int yR = ref start; + ref int yG = ref Unsafe.Add(ref start, 256 * 1); + ref int yB = ref Unsafe.Add(ref start, 256 * 2); + + ref int cbR = ref Unsafe.Add(ref start, 256 * 3); + ref int cbG = ref Unsafe.Add(ref start, 256 * 4); + ref int cbB = ref Unsafe.Add(ref start, 256 * 5); - // float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); - cbBlockRaw[index] = (tables->CbRTable[r] + tables->CbGTable[g] + tables->CbBTable[b]) >> ScaleBits; + ref int crG = ref Unsafe.Add(ref start, 256 * 6); + ref int crB = ref Unsafe.Add(ref start, 256 * 7); - // float b = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero); - crBlockRaw[index] = (tables->CbBTable[r] + tables->CrGTable[g] + tables->CrBTable[b]) >> ScaleBits; + yResult = (Unsafe.Add(ref yR, r) + Unsafe.Add(ref yG, g) + Unsafe.Add(ref yB, b)) >> ScaleBits; + cbResult = (Unsafe.Add(ref cbR, r) + Unsafe.Add(ref cbG, g) + Unsafe.Add(ref cbB, b)) >> ScaleBits; + crResult = (Unsafe.Add(ref cbB, r) + Unsafe.Add(ref crG, g) + Unsafe.Add(ref crB, b)) >> ScaleBits; } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/YCbCrForwardConverter{TPixel}.cs new file mode 100644 index 0000000000..3c95a85080 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Encoder/YCbCrForwardConverter{TPixel}.cs @@ -0,0 +1,82 @@ +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats.Jpeg.Common; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder +{ + /// + /// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks. + /// + /// The pixel type to work on + internal struct YCbCrForwardConverter + where TPixel : struct, IPixel + { + /// + /// The Y component + /// + public Block8x8F Y; + + /// + /// The Cb component + /// + public Block8x8F Cb; + + /// + /// The Cr component + /// + public Block8x8F Cr; + + /// + /// The color conversion tables + /// + private RgbToYCbCrTables colorTables; + + /// + /// Temporal 8x8 block to hold TPixel data + /// + private GenericBlock8x8 pixelBlock; + + /// + /// Temporal RGB block + /// + private GenericBlock8x8 rgbBlock; + + public static YCbCrForwardConverter Create() + { + var result = default(YCbCrForwardConverter); + result.colorTables = RgbToYCbCrTables.Create(); + return result; + } + + /// + /// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (, , ) + /// + public void Convert(IPixelSource pixels, int x, int y) + { + this.pixelBlock.LoadAndStretchEdges(pixels, x, y); + + Span rgbSpan = this.rgbBlock.AsSpanUnsafe(); + PixelOperations.Instance.ToRgb24(this.pixelBlock.AsSpanUnsafe(), rgbSpan, 64); + + ref float yBlockStart = ref Unsafe.As(ref this.Y); + ref float cbBlockStart = ref Unsafe.As(ref this.Cb); + ref float crBlockStart = ref Unsafe.As(ref this.Cr); + ref Rgb24 rgbStart = ref rgbSpan[0]; + + for (int i = 0; i < 64; i++) + { + ref Rgb24 c = ref Unsafe.Add(ref rgbStart, i); + + this.colorTables.ConvertPixelInto( + c.R, + c.G, + c.B, + ref Unsafe.Add(ref yBlockStart, i), + ref Unsafe.Add(ref cbBlockStart, i), + ref Unsafe.Add(ref crBlockStart, i)); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs index 2912a87199..ba40ef72b8 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs @@ -7,7 +7,7 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Jpeg.Common; using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components; using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Encoder; -using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.MetaData.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; @@ -231,10 +231,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort this.WriteDefineHuffmanTables(componentCount); // Write the image data. - using (PixelAccessor pixels = image.Lock()) - { - this.WriteStartOfScan(pixels); - } + this.WriteStartOfScan(image); // Write the End Of Image marker. this.buffer[0] = OrigJpegConstants.Markers.XFF; @@ -285,58 +282,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort } } - /// - /// Converts the 8x8 region of the image whose top-left corner is x,y to its YCbCr values. - /// - /// The pixel format. - /// The pixel accessor. - /// The reference to the tables instance. - /// The x-position within the image. - /// The y-position within the image. - /// The luminance block. - /// The red chroma block. - /// The blue chroma block. - /// Temporal provided by the caller - private static void ToYCbCr( - PixelAccessor pixels, - RgbToYCbCrTables* tables, - int x, - int y, - Block8x8F* yBlock, - Block8x8F* cbBlock, - Block8x8F* crBlock, - PixelArea rgbBytes) - where TPixel : struct, IPixel - { - float* yBlockRaw = (float*)yBlock; - float* cbBlockRaw = (float*)cbBlock; - float* crBlockRaw = (float*)crBlock; - - rgbBytes.Reset(); - pixels.CopyRGBBytesStretchedTo(rgbBytes, y, x); - - ref byte data0 = ref rgbBytes.Bytes[0]; - int dataIdx = 0; - - for (int j = 0; j < 8; j++) - { - int j8 = j * 8; - for (int i = 0; i < 8; i++) - { - // Convert returned bytes into the YCbCr color space. Assume RGBA - int r = Unsafe.Add(ref data0, dataIdx); - int g = Unsafe.Add(ref data0, dataIdx + 1); - int b = Unsafe.Add(ref data0, dataIdx + 2); - - int index = j8 + i; - - RgbToYCbCrTables.Allocate(ref yBlockRaw, ref cbBlockRaw, ref crBlockRaw, ref tables, index, r, g, b); - - dataIdx += 3; - } - } - } - /// /// Emits the least significant count of bits of bits to the bit-stream. /// The precondition is bits @@ -432,14 +377,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// /// The pixel format. /// The pixel accessor providing access to the image pixels. - private void Encode444(PixelAccessor pixels) + private void Encode444(Image pixels) where TPixel : struct, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) - Block8x8F b = default(Block8x8F); - Block8x8F cb = default(Block8x8F); - Block8x8F cr = default(Block8x8F); - + // (Partially done with YCbCrForwardConverter) Block8x8F temp1 = default(Block8x8F); Block8x8F temp2 = default(Block8x8F); @@ -451,42 +393,38 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort // ReSharper disable once InconsistentNaming int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - fixed (RgbToYCbCrTables* tables = &rgbToYCbCrTables) + YCbCrForwardConverter pixelConverter = YCbCrForwardConverter.Create(); + + for (int y = 0; y < pixels.Height; y += 8) { - using (PixelArea rgbBytes = new PixelArea(8, 8, ComponentOrder.Xyz)) + for (int x = 0; x < pixels.Width; x += 8) { - for (int y = 0; y < pixels.Height; y += 8) - { - for (int x = 0; x < pixels.Width; x += 8) - { - ToYCbCr(pixels, tables, x, y, &b, &cb, &cr, rgbBytes); - - prevDCY = this.WriteBlock( - QuantIndex.Luminance, - prevDCY, - &b, - &temp1, - &temp2, - &onStackLuminanceQuantTable, - unzig.Data); - prevDCCb = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCb, - &cb, - &temp1, - &temp2, - &onStackChrominanceQuantTable, - unzig.Data); - prevDCCr = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCr, - &cr, - &temp1, - &temp2, - &onStackChrominanceQuantTable, - unzig.Data); - } - } + pixelConverter.Convert(pixels.Frames.RootFrame, x, y); + + prevDCY = this.WriteBlock( + QuantIndex.Luminance, + prevDCY, + &pixelConverter.Y, + &temp1, + &temp2, + &onStackLuminanceQuantTable, + unzig.Data); + prevDCCb = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCb, + &pixelConverter.Cb, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); + prevDCCr = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCr, + &pixelConverter.Cr, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); } } } @@ -657,14 +595,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort // Loop through and collect the tables as one array. // This allows us to reduce the number of writes to the stream. int dqtCount = (QuantizationTableCount * Block8x8F.Size) + QuantizationTableCount; - byte[] dqt = ArrayPool.Shared.Rent(dqtCount); + byte[] dqt = new byte[dqtCount]; int offset = 0; WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref this.luminanceQuantTable); WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref this.chrominanceQuantTable); this.outputStream.Write(dqt, 0, dqtCount); - ArrayPool.Shared.Return(dqt); } /// @@ -858,8 +795,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// Writes the StartOfScan marker. /// /// The pixel format. - /// The pixel accessor providing access to the image pixels. - private void WriteStartOfScan(PixelAccessor pixels) + /// The pixel accessor providing access to the image pixels. + private void WriteStartOfScan(Image image) where TPixel : struct, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) @@ -869,10 +806,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort switch (this.subsample) { case JpegSubsample.Ratio444: - this.Encode444(pixels); + this.Encode444(image); break; case JpegSubsample.Ratio420: - this.Encode420(pixels); + this.Encode420(image); break; } @@ -886,7 +823,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// /// The pixel format. /// The pixel accessor providing access to the image pixels. - private void Encode420(PixelAccessor pixels) + private void Encode420(Image pixels) where TPixel : struct, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) @@ -905,54 +842,54 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort ZigZag unzig = ZigZag.CreateUnzigTable(); + var pixelConverter = YCbCrForwardConverter.Create(); + // ReSharper disable once InconsistentNaming int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - fixed (RgbToYCbCrTables* tables = &rgbToYCbCrTables) + + for (int y = 0; y < pixels.Height; y += 16) { - using (PixelArea rgbBytes = new PixelArea(8, 8, ComponentOrder.Xyz)) + for (int x = 0; x < pixels.Width; x += 16) { - for (int y = 0; y < pixels.Height; y += 16) + for (int i = 0; i < 4; i++) { - for (int x = 0; x < pixels.Width; x += 16) - { - for (int i = 0; i < 4; i++) - { - int xOff = (i & 1) * 8; - int yOff = (i & 2) * 4; - - ToYCbCr(pixels, tables, x + xOff, y + yOff, &b, cbPtr + i, crPtr + i, rgbBytes); - - prevDCY = this.WriteBlock( - QuantIndex.Luminance, - prevDCY, - &b, - &temp1, - &temp2, - &onStackLuminanceQuantTable, - unzig.Data); - } - - Block8x8F.Scale16X16To8X8(&b, cbPtr); - prevDCCb = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCb, - &b, - &temp1, - &temp2, - &onStackChrominanceQuantTable, - unzig.Data); - - Block8x8F.Scale16X16To8X8(&b, crPtr); - prevDCCr = this.WriteBlock( - QuantIndex.Chrominance, - prevDCCr, - &b, - &temp1, - &temp2, - &onStackChrominanceQuantTable, - unzig.Data); - } + int xOff = (i & 1) * 8; + int yOff = (i & 2) * 4; + + pixelConverter.Convert(pixels.Frames.RootFrame, x + xOff, y + yOff); + + cbPtr[i] = pixelConverter.Cb; + crPtr[i] = pixelConverter.Cr; + + prevDCY = this.WriteBlock( + QuantIndex.Luminance, + prevDCY, + &pixelConverter.Y, + &temp1, + &temp2, + &onStackLuminanceQuantTable, + unzig.Data); } + + Block8x8F.Scale16X16To8X8(&b, cbPtr); + prevDCCb = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCb, + &b, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); + + Block8x8F.Scale16X16To8X8(&b, crPtr); + prevDCCr = this.WriteBlock( + QuantIndex.Chrominance, + prevDCCr, + &b, + &temp1, + &temp2, + &onStackChrominanceQuantTable, + unzig.Data); } } } diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs index cdc2a91972..6cc275d49d 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs @@ -211,11 +211,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// public void Dispose() { - for (int i = 0; i < this.HuffmanTrees.Length; i++) - { - this.HuffmanTrees[i].Dispose(); - } - if (this.Components != null) { foreach (OrigComponent component in this.Components) @@ -240,6 +235,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort // Check for the Start Of Image marker. this.InputProcessor.ReadFull(this.Temp, 0, 2); + if (this.Temp[0] != OrigJpegConstants.Markers.XFF || this.Temp[1] != OrigJpegConstants.Markers.SOI) { throw new ImageFormatException("Missing SOI marker."); @@ -252,6 +248,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort while (processBytes) { this.InputProcessor.ReadFull(this.Temp, 0, 2); + + if (this.InputProcessor.ReachedEOF) + { + // We've reached the end of the stream. + processBytes = false; + } + while (this.Temp[0] != 0xff) { // Strictly speaking, this is a format error. However, libjpeg is @@ -286,7 +289,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort { // Section B.1.1.2 says, "Any marker may optionally be preceded by any // number of fill bytes, which are bytes assigned code X'FF'". - marker = this.InputProcessor.ReadByte(); + this.InputProcessor.ReadByteUnsafe(out marker); + + if (this.InputProcessor.ReachedEOF) + { + // We've reached the end of the stream. + processBytes = false; + break; + } } // End Of Image. @@ -308,7 +318,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort // Read the 16-bit length of the segment. The value includes the 2 bytes for the // length itself, so we subtract 2 to get the number of remaining bytes. - this.InputProcessor.ReadFull(this.Temp, 0, 2); + this.InputProcessor.ReadFullUnsafe(this.Temp, 0, 2); int remaining = (this.Temp[0] << 8) + this.Temp[1] - 2; if (remaining < 0) { @@ -356,12 +366,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort return; } - // when this is a progressive image this gets called a number of times - // need to know how many times this should be called in total. this.ProcessStartOfScanMarker(remaining); - if (this.InputProcessor.ReachedEOF || !this.IsProgressive) + if (this.InputProcessor.ReachedEOF) { - // if unexpeced EOF reached or this is not a progressive image we can stop processing bytes as we now have the image data. + // If unexpected EOF reached. We can stop processing bytes as we now have the image data. processBytes = false; } @@ -395,15 +403,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort { this.InputProcessor.Skip(remaining); } - else if (marker < OrigJpegConstants.Markers.SOF0) - { - // See Table B.1 "Marker code assignments". - throw new ImageFormatException("Unknown marker"); - } - else - { - throw new ImageFormatException("Unknown marker"); - } break; } @@ -412,6 +411,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort this.InitDerivedMetaDataProperties(); } + /// + /// Returns true if 'mcuCounter' is at restart interval + /// + public bool IsAtRestartInterval(int mcuCounter) + { + return this.RestartInterval > 0 && mcuCounter % this.RestartInterval == 0 + && mcuCounter < this.TotalMCUCount; + } + /// /// Assigns derived metadata properties to , eg. horizontal and vertical resolution if it has a JFIF header. /// @@ -680,7 +688,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort foreach (OrigComponent component in this.Components) { - component.InitializeDerivedData(this); + component.InitializeDerivedData(this.configuration.MemoryManager, this); } this.ColorSpace = this.DeduceJpegColorSpace(); @@ -788,7 +796,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort private Image PostProcessIntoImage() where TPixel : struct, IPixel { - using (var postProcessor = new JpegImagePostProcessor(this)) + using (var postProcessor = new JpegImagePostProcessor(this.configuration.MemoryManager, this)) { var image = new Image(this.configuration, this.ImageWidth, this.ImageHeight, this.MetaData); postProcessor.PostProcess(image.Frames.RootFrame); diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/OrigJpegUtils.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/OrigJpegUtils.cs deleted file mode 100644 index 01ed5063ba..0000000000 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Utils/OrigJpegUtils.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils -{ - /// - /// Jpeg specific utilities and extension methods - /// - internal static class OrigJpegUtils - { - /// - /// Copy a region of an image into dest. De "outlier" area will be stretched out with pixels on the right and bottom of the image. - /// - /// The pixel type - /// The input pixel acessor - /// The destination - /// Starting Y coord - /// Starting X coord - public static void CopyRGBBytesStretchedTo( - this PixelAccessor pixels, - PixelArea dest, - int sourceY, - int sourceX) - where TPixel : struct, IPixel - { - pixels.SafeCopyTo(dest, sourceY, sourceX); - int stretchFromX = pixels.Width - sourceX; - int stretchFromY = pixels.Height - sourceY; - StretchPixels(dest, stretchFromX, stretchFromY); - } - - // Nothing to stretch if (fromX, fromY) is outside the area, or is at (0,0) - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsInvalidStretchStartingPosition(PixelArea area, int fromX, int fromY) - where TPixel : struct, IPixel - { - return fromX <= 0 || fromY <= 0 || fromX >= area.Width || fromY >= area.Height; - } - - private static void StretchPixels(PixelArea area, int fromX, int fromY) - where TPixel : struct, IPixel - { - if (IsInvalidStretchStartingPosition(area, fromX, fromY)) - { - return; - } - - for (int y = 0; y < fromY; y++) - { - ref RGB24 ptrBase = ref GetRowStart(area, y); - - for (int x = fromX; x < area.Width; x++) - { - // Copy the left neighbour pixel to the current one - Unsafe.Add(ref ptrBase, x) = Unsafe.Add(ref ptrBase, x - 1); - } - } - - for (int y = fromY; y < area.Height; y++) - { - ref RGB24 currBase = ref GetRowStart(area, y); - ref RGB24 prevBase = ref GetRowStart(area, y - 1); - - for (int x = 0; x < area.Width; x++) - { - // Copy the top neighbour pixel to the current one - Unsafe.Add(ref currBase, x) = Unsafe.Add(ref prevBase, x); - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ref RGB24 GetRowStart(PixelArea area, int y) - where TPixel : struct, IPixel - { - return ref Unsafe.As(ref area.GetRowSpan(y).DangerousGetPinnableReference()); - } - - [StructLayout(LayoutKind.Sequential, Size = 3)] - private struct RGB24 - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs b/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs index 9cd7b3a8bd..d3f95e40c0 100644 --- a/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs +++ b/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs @@ -36,6 +36,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().FindEncoder(ImageFormats.Jpeg)); + => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ImageFormats.Jpeg)); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs b/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs index 1ab5093398..23cef59273 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs @@ -11,10 +11,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public void Configure(Configuration config) { - config.SetEncoder(ImageFormats.Jpeg, new JpegEncoder()); - config.SetDecoder(ImageFormats.Jpeg, new JpegDecoder()); + config.ImageFormatsManager.SetEncoder(ImageFormats.Jpeg, new JpegEncoder()); + config.ImageFormatsManager.SetDecoder(ImageFormats.Jpeg, new JpegDecoder()); - config.AddImageFormatDetector(new JpegImageFormatDetector()); + config.ImageFormatsManager.AddImageFormatDetector(new JpegImageFormatDetector()); } } } diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsComponent.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsComponent.cs index 3c35e311f1..0742293c78 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsComponent.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// /// Gets or sets the output /// - public Buffer Output; + public IBuffer Output; /// /// Gets or sets the scaling factors diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs index f60097dc9c..2442c39981 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsFrameComponent.cs @@ -15,10 +15,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// internal class PdfJsFrameComponent : IDisposable, IJpegComponent { + private readonly MemoryManager memoryManager; #pragma warning disable SA1401 // Fields should be private - public PdfJsFrameComponent(PdfJsFrame frame, byte id, int horizontalFactor, int verticalFactor, byte quantizationTableIndex, int index) + public PdfJsFrameComponent(MemoryManager memoryManager, PdfJsFrame frame, byte id, int horizontalFactor, int verticalFactor, byte quantizationTableIndex, int index) { + this.memoryManager = memoryManager; this.Frame = frame; this.Id = id; this.HorizontalSamplingFactor = horizontalFactor; @@ -58,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// /// Gets the block data /// - public Buffer BlockData { get; private set; } + public IBuffer BlockData { get; private set; } /// public int Index { get; } @@ -114,7 +116,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components int blocksBufferSize = 64 * this.BlocksPerColumnForMcu * (this.BlocksPerLineForMcu + 1); // Pooled. Disposed via frame disposal - this.BlockData = Buffer.CreateClean(blocksBufferSize); + this.BlockData = this.memoryManager.Allocate(blocksBufferSize, true); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs index 9dc8315677..3c43ba2444 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsHuffmanTable.cs @@ -12,32 +12,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// internal struct PdfJsHuffmanTable : IDisposable { - private Buffer lookahead; - private Buffer valOffset; - private Buffer maxcode; - private Buffer huffval; + private BasicArrayBuffer lookahead; + private BasicArrayBuffer valOffset; + private BasicArrayBuffer maxcode; + private IManagedByteBuffer huffval; /// /// Initializes a new instance of the struct. /// + /// The to use for buffer allocations. /// The code lengths /// The huffman values - public PdfJsHuffmanTable(byte[] lengths, byte[] values) + public PdfJsHuffmanTable(MemoryManager memoryManager, byte[] lengths, byte[] values) { - this.lookahead = Buffer.CreateClean(256); - this.valOffset = Buffer.CreateClean(18); - this.maxcode = Buffer.CreateClean(18); + // TODO: Replace FakeBuffer usages with standard or array orfixed-sized arrays + this.lookahead = memoryManager.AllocateFake(256); + this.valOffset = memoryManager.AllocateFake(18); + this.maxcode = memoryManager.AllocateFake(18); - using (var huffsize = Buffer.CreateClean(257)) - using (var huffcode = Buffer.CreateClean(257)) + using (IBuffer huffsize = memoryManager.Allocate(257)) + using (IBuffer huffcode = memoryManager.Allocate(257)) { - GenerateSizeTable(lengths, huffsize); - GenerateCodeTable(huffsize, huffcode); - GenerateDecoderTables(lengths, huffcode, this.valOffset, this.maxcode); - GenerateLookaheadTables(lengths, values, this.lookahead); + GenerateSizeTable(lengths, huffsize.Span); + GenerateCodeTable(huffsize.Span, huffcode.Span); + GenerateDecoderTables(lengths, huffcode.Span, this.valOffset.Span, this.maxcode.Span); + GenerateLookaheadTables(lengths, values, this.lookahead.Span); } - this.huffval = Buffer.CreateClean(values.Length); + this.huffval = memoryManager.AllocateManagedByteBuffer(values.Length, true); Buffer.BlockCopy(values, 0, this.huffval.Array, 0, values.Length); this.MaxCode = this.maxcode.Array; diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsIDCT.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsIDCT.cs index 49bdc2423e..00fa1985dd 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsIDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsIDCT.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components { diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsJpegPixelArea.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsJpegPixelArea.cs index 034986c2cb..f16fb9a2c2 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsJpegPixelArea.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsJpegPixelArea.cs @@ -14,22 +14,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// internal struct PdfJsJpegPixelArea : IDisposable { + private readonly MemoryManager memoryManager; + private readonly int imageWidth; private readonly int imageHeight; - private Buffer componentData; + private IBuffer componentData; private int rowStride; /// /// Initializes a new instance of the struct. /// + /// The to use for buffer allocations. /// The image width /// The image height /// The number of components - public PdfJsJpegPixelArea(int imageWidth, int imageHeight, int numberOfComponents) + public PdfJsJpegPixelArea(MemoryManager memoryManager, int imageWidth, int imageHeight, int numberOfComponents) { + this.memoryManager = memoryManager; this.imageWidth = imageWidth; this.imageHeight = imageHeight; this.Width = 0; @@ -69,19 +73,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components this.rowStride = width * numberOfComponents; var scale = new Vector2(this.imageWidth / (float)width, this.imageHeight / (float)height); - this.componentData = new Buffer(width * height * numberOfComponents); - Span componentDataSpan = this.componentData; + this.componentData = this.memoryManager.Allocate(width * height * numberOfComponents); + Span componentDataSpan = this.componentData.Span; const uint Mask3Lsb = 0xFFFFFFF8; // Used to clear the 3 LSBs - using (var xScaleBlockOffset = new Buffer(width)) + using (IBuffer xScaleBlockOffset = this.memoryManager.Allocate(width)) { - Span xScaleBlockOffsetSpan = xScaleBlockOffset; + Span xScaleBlockOffsetSpan = xScaleBlockOffset.Span; for (int i = 0; i < numberOfComponents; i++) { ref PdfJsComponent component = ref components.Components[i]; Vector2 componentScale = component.Scale * scale; int offset = i; - Span output = component.Output; + Span output = component.Output.Span; int blocksPerScanline = (component.BlocksPerLine + 1) << 3; // Precalculate the xScaleBlockOffset diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsQuantizationTables.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsQuantizationTables.cs index 1000ce82c5..afe0b30072 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsQuantizationTables.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsQuantizationTables.cs @@ -12,6 +12,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// internal sealed class PdfJsQuantizationTables : IDisposable { + public PdfJsQuantizationTables(MemoryManager memoryManager) + { + this.Tables = memoryManager.Allocate2D(64, 4); + } + /// /// Gets the ZigZag scan table /// @@ -49,8 +54,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components get; set; } - = new Buffer2D(64, 4); - /// public void Dispose() { diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs index e2e5d985e6..c6f6ac270f 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs @@ -154,7 +154,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components ushort marker = fileMarker.Marker; - // RSTn - We've alread read the bytes and altered the position so no need to skip + // RSTn - We've already read the bytes and altered the position so no need to skip if (marker >= PdfJsJpegConstants.Markers.RST0 && marker <= PdfJsJpegConstants.Markers.RST7) { continue; @@ -707,6 +707,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeBaseline(PdfJsFrameComponent component, int offset, ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, Stream stream) { + Span blockDataSpan = component.BlockData.Span; + int t = this.DecodeHuffman(ref dcHuffmanTable, stream); if (this.endOfStreamReached || this.unexpectedMarkerReached) { @@ -714,7 +716,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components } int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream); - component.BlockData[offset] = (short)(component.Pred += diff); + blockDataSpan[offset] = (short)(component.Pred += diff); int k = 1; while (k < 64) @@ -748,7 +750,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components byte z = PdfJsQuantizationTables.DctZigZag[k]; short re = (short)this.ReceiveAndExtend(s, stream); - component.BlockData[offset + z] = re; + blockDataSpan[offset + z] = re; k++; } } @@ -756,6 +758,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeDCFirst(PdfJsFrameComponent component, int offset, ref PdfJsHuffmanTable dcHuffmanTable, Stream stream) { + Span blockDataSpan = component.BlockData.Span; + int t = this.DecodeHuffman(ref dcHuffmanTable, stream); if (this.endOfStreamReached || this.unexpectedMarkerReached) { @@ -763,19 +767,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components } int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream) << this.successiveState; - component.BlockData[offset] = (short)(component.Pred += diff); + blockDataSpan[offset] = (short)(component.Pred += diff); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DecodeDCSuccessive(PdfJsFrameComponent component, int offset, Stream stream) { + Span blockDataSpan = component.BlockData.Span; + int bit = this.ReadBit(stream); if (this.endOfStreamReached || this.unexpectedMarkerReached) { return; } - component.BlockData[offset] |= (short)(bit << this.successiveState); + blockDataSpan[offset] |= (short)(bit << this.successiveState); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsYCbCrToRgbTables.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsYCbCrToRgbTables.cs index ddc577270b..203a7b1eb2 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsYCbCrToRgbTables.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsYCbCrToRgbTables.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components /// Provides 8-bit lookup tables for converting from YCbCr to Rgb colorspace. /// Methods to build the tables are based on libjpeg implementation. /// - internal struct PdfJsYCbCrToRgbTables + internal readonly struct PdfJsYCbCrToRgbTables { /// /// The red red-chrominance table diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs index 211c24d208..54e2833b11 100644 --- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs @@ -137,7 +137,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort return new PdfJsFileMarker(PdfJsJpegConstants.Markers.EOI, (int)stream.Length - 2); } - marker[1] = (byte)value; + marker[1] = (byte)suffix; } return new PdfJsFileMarker((ushort)((marker[0] << 8) | marker[1]), (int)(stream.Position - 2)); @@ -216,7 +216,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort ushort marker = this.ReadUint16(); fileMarker = new PdfJsFileMarker(marker, (int)this.InputStream.Position - 2); - this.quantizationTables = new PdfJsQuantizationTables(); + this.quantizationTables = new PdfJsQuantizationTables(this.configuration.MemoryManager); this.dcHuffmanTables = new PdfJsHuffmanTables(); this.acHuffmanTables = new PdfJsHuffmanTables(); @@ -335,7 +335,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort throw new ImageFormatException($"Unsupported color mode. Max components 4; found {this.NumberOfComponents}"); } - this.pixelArea = new PdfJsJpegPixelArea(image.Width, image.Height, this.NumberOfComponents); + this.pixelArea = new PdfJsJpegPixelArea(this.configuration.MemoryManager, image.Width, image.Height, this.NumberOfComponents); this.pixelArea.LinearizeBlockData(this.components, image.Width, image.Height); if (this.NumberOfComponents == 1) @@ -648,7 +648,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort maxV = v; } - var component = new PdfJsFrameComponent(this.Frame, this.temp[index], h, v, this.temp[index + 2], i); + var component = new PdfJsFrameComponent(this.configuration.MemoryManager, this.Frame, this.temp[index], h, v, this.temp[index + 2], i); this.Frame.Components[i] = component; this.Frame.ComponentIds[i] = component.Id; @@ -673,23 +673,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort throw new ImageFormatException($"DHT has wrong length: {remaining}"); } - using (var huffmanData = Buffer.CreateClean(256)) + using (IManagedByteBuffer huffmanData = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(256)) { + Span huffmanSpan = huffmanData.Span; for (int i = 2; i < remaining;) { byte huffmanTableSpec = (byte)this.InputStream.ReadByte(); this.InputStream.Read(huffmanData.Array, 0, 16); - using (var codeLengths = Buffer.CreateClean(17)) + using (IManagedByteBuffer codeLengths = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(17)) { + Span codeLengthsSpan = codeLengths.Span; int codeLengthSum = 0; for (int j = 1; j < 17; j++) { - codeLengthSum += codeLengths[j] = huffmanData[j - 1]; + codeLengthSum += codeLengthsSpan[j] = huffmanSpan[j - 1]; } - using (var huffmanValues = Buffer.CreateClean(256)) + using (IManagedByteBuffer huffmanValues = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(256)) { this.InputStream.Read(huffmanValues.Array, 0, codeLengthSum); @@ -784,18 +786,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort { int blocksPerLine = component.BlocksPerLine; int blocksPerColumn = component.BlocksPerColumn; - using (var computationBuffer = Buffer.CreateClean(64)) - using (var multiplicationBuffer = Buffer.CreateClean(64)) + using (IBuffer computationBuffer = this.configuration.MemoryManager.Allocate(64, true)) + using (IBuffer multiplicationBuffer = this.configuration.MemoryManager.Allocate(64, true)) { Span quantizationTable = this.quantizationTables.Tables.GetRowSpan(frameComponent.QuantizationTableIndex); - Span computationBufferSpan = computationBuffer; + Span computationBufferSpan = computationBuffer.Span; // For AA&N IDCT method, multiplier are equal to quantization // coefficients scaled by scalefactor[row]*scalefactor[col], where // scalefactor[0] = 1 // scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 // For integer operation, the multiplier table is to be scaled by 12. - Span multiplierSpan = multiplicationBuffer; + Span multiplierSpan = multiplicationBuffer.Span; // for (int i = 0; i < 64; i++) // { @@ -823,7 +825,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort /// The values private void BuildHuffmanTable(PdfJsHuffmanTables tables, int index, byte[] codeLengths, byte[] values) { - tables[index] = new PdfJsHuffmanTable(codeLengths, values); + tables[index] = new PdfJsHuffmanTable(this.configuration.MemoryManager, codeLengths, values); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs index 0d3a65dbd8..de62d47029 100644 --- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Png.Filters { @@ -24,8 +25,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - ref byte scanBaseRef = ref scanline.DangerousGetPinnableReference(); - ref byte prevBaseRef = ref previousScanline.DangerousGetPinnableReference(); + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); // Average(x) + floor((Raw(x-bpp)+Prior(x))/2) for (int x = 1; x < scanline.Length; x++) @@ -60,9 +61,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - ref byte scanBaseRef = ref scanline.DangerousGetPinnableReference(); - ref byte prevBaseRef = ref previousScanline.DangerousGetPinnableReference(); - ref byte resultBaseRef = ref result.DangerousGetPinnableReference(); + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); sum = 0; // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs index 08e4938804..7e05d736f9 100644 --- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Png.Filters { @@ -25,8 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - ref byte scanBaseRef = ref scanline.DangerousGetPinnableReference(); - ref byte prevBaseRef = ref previousScanline.DangerousGetPinnableReference(); + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); // Paeth(x) + PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp)) int offset = bytesPerPixel + 1; @@ -61,9 +62,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - ref byte scanBaseRef = ref scanline.DangerousGetPinnableReference(); - ref byte prevBaseRef = ref previousScanline.DangerousGetPinnableReference(); - ref byte resultBaseRef = ref result.DangerousGetPinnableReference(); + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); sum = 0; // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp)) diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs index 5ee8664400..c0db7da935 100644 --- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Png.Filters { @@ -21,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Decode(Span scanline, int bytesPerPixel) { - ref byte scanBaseRef = ref scanline.DangerousGetPinnableReference(); + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); // Sub(x) + Raw(x-bpp) for (int x = 1; x < scanline.Length; x++) @@ -52,8 +53,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters { DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - ref byte scanBaseRef = ref scanline.DangerousGetPinnableReference(); - ref byte resultBaseRef = ref result.DangerousGetPinnableReference(); + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); sum = 0; // Sub(x) = Raw(x) - Raw(x-bpp) diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs index 6e8f780e5c..81c063ea9e 100644 --- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Png.Filters { @@ -23,8 +24,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters { DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); - ref byte scanBaseRef = ref scanline.DangerousGetPinnableReference(); - ref byte prevBaseRef = ref previousScanline.DangerousGetPinnableReference(); + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); // Up(x) + Prior(x) for (int x = 1; x < scanline.Length; x++) @@ -48,9 +49,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline)); DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result)); - ref byte scanBaseRef = ref scanline.DangerousGetPinnableReference(); - ref byte prevBaseRef = ref previousScanline.DangerousGetPinnableReference(); - ref byte resultBaseRef = ref result.DangerousGetPinnableReference(); + ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); + ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); + ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); sum = 0; // Up(x) = Raw(x) - Prior(x) diff --git a/src/ImageSharp/Formats/Png/ImageExtensions.cs b/src/ImageSharp/Formats/Png/ImageExtensions.cs index 10970fc16a..f25d2bffe2 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().FindEncoder(ImageFormats.Png)); + => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ImageFormats.Png)); } } diff --git a/src/ImageSharp/Formats/Png/PngChunk.cs b/src/ImageSharp/Formats/Png/PngChunk.cs index f90def5b38..2483a3ad9d 100644 --- a/src/ImageSharp/Formats/Png/PngChunk.cs +++ b/src/ImageSharp/Formats/Png/PngChunk.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Memory; + namespace SixLabors.ImageSharp.Formats.Png { /// @@ -25,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// Gets or sets the data bytes appropriate to the chunk type, if any. /// This field can be of zero length. /// - public byte[] Data { get; set; } + public IManagedByteBuffer Data { get; set; } /// /// Gets or sets a CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk, diff --git a/src/ImageSharp/Formats/Png/PngConfigurationModule.cs b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs index 9346f7567e..0036280a83 100644 --- a/src/ImageSharp/Formats/Png/PngConfigurationModule.cs +++ b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs @@ -9,11 +9,11 @@ namespace SixLabors.ImageSharp.Formats.Png public sealed class PngConfigurationModule : IConfigurationModule { /// - public void Configure(Configuration host) + public void Configure(Configuration config) { - host.SetEncoder(ImageFormats.Png, new PngEncoder()); - host.SetDecoder(ImageFormats.Png, new PngDecoder()); - host.AddImageFormatDetector(new PngImageFormatDetector()); + config.ImageFormatsManager.SetEncoder(ImageFormats.Png, new PngEncoder()); + config.ImageFormatsManager.SetDecoder(ImageFormats.Png, new PngDecoder()); + config.ImageFormatsManager.AddImageFormatDetector(new PngImageFormatDetector()); } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index e39187e086..c1dccdcafc 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -137,12 +137,12 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Previous scanline processed /// - private Buffer previousScanline; + private IManagedByteBuffer previousScanline; /// /// The current scanline that is being processed /// - private Buffer scanline; + private IManagedByteBuffer scanline; /// /// The index of the current scanline being processed @@ -191,6 +191,8 @@ namespace SixLabors.ImageSharp.Formats.Png this.ignoreMetadata = options.IgnoreMetadata; } + private MemoryManager MemoryManager => this.configuration.MemoryManager; + /// /// Decodes the stream to the image. /// @@ -222,11 +224,11 @@ namespace SixLabors.ImageSharp.Formats.Png switch (currentChunk.Type) { case PngChunkTypes.Header: - this.ReadHeaderChunk(currentChunk.Data); + this.ReadHeaderChunk(currentChunk.Data.Array); this.ValidateHeader(); break; case PngChunkTypes.Physical: - this.ReadPhysicalChunk(metadata, currentChunk.Data); + this.ReadPhysicalChunk(metadata, currentChunk.Data.Array); break; case PngChunkTypes.Data: if (image == null) @@ -240,17 +242,17 @@ namespace SixLabors.ImageSharp.Formats.Png break; case PngChunkTypes.Palette: byte[] pal = new byte[currentChunk.Length]; - Buffer.BlockCopy(currentChunk.Data, 0, pal, 0, currentChunk.Length); + Buffer.BlockCopy(currentChunk.Data.Array, 0, pal, 0, currentChunk.Length); this.palette = pal; break; case PngChunkTypes.PaletteAlpha: byte[] alpha = new byte[currentChunk.Length]; - Buffer.BlockCopy(currentChunk.Data, 0, alpha, 0, currentChunk.Length); + Buffer.BlockCopy(currentChunk.Data.Array, 0, alpha, 0, currentChunk.Length); this.paletteAlpha = alpha; this.AssignTransparentMarkers(alpha); break; case PngChunkTypes.Text: - this.ReadTextChunk(metadata, currentChunk.Data, currentChunk.Length); + this.ReadTextChunk(metadata, currentChunk.Data.Array, currentChunk.Length); break; case PngChunkTypes.End: this.isEndChunkReached = true; @@ -262,7 +264,8 @@ namespace SixLabors.ImageSharp.Formats.Png // Data is rented in ReadChunkData() if (currentChunk.Data != null) { - ArrayPool.Shared.Return(currentChunk.Data); + currentChunk.Data.Dispose(); + currentChunk.Data = null; } } } @@ -296,17 +299,17 @@ namespace SixLabors.ImageSharp.Formats.Png switch (currentChunk.Type) { case PngChunkTypes.Header: - this.ReadHeaderChunk(currentChunk.Data); + this.ReadHeaderChunk(currentChunk.Data.Array); this.ValidateHeader(); break; case PngChunkTypes.Physical: - this.ReadPhysicalChunk(metadata, currentChunk.Data); + this.ReadPhysicalChunk(metadata, currentChunk.Data.Array); break; case PngChunkTypes.Data: this.SkipChunkDataAndCrc(currentChunk); break; case PngChunkTypes.Text: - this.ReadTextChunk(metadata, currentChunk.Data, currentChunk.Length); + this.ReadTextChunk(metadata, currentChunk.Data.Array, currentChunk.Length); break; case PngChunkTypes.End: this.isEndChunkReached = true; @@ -318,7 +321,7 @@ namespace SixLabors.ImageSharp.Formats.Png // Data is rented in ReadChunkData() if (currentChunk.Data != null) { - ArrayPool.Shared.Return(currentChunk.Data); + ArrayPool.Shared.Return(currentChunk.Data.Array); } } } @@ -434,8 +437,8 @@ namespace SixLabors.ImageSharp.Formats.Png this.bytesPerSample = this.header.BitDepth / 8; } - this.previousScanline = Buffer.CreateClean(this.bytesPerScanline); - this.scanline = Buffer.CreateClean(this.bytesPerScanline); + this.previousScanline = this.MemoryManager.AllocateCleanManagedByteBuffer(this.bytesPerScanline); + this.scanline = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(this.bytesPerScanline); } /// @@ -555,7 +558,8 @@ namespace SixLabors.ImageSharp.Formats.Png } this.currentRowBytesRead = 0; - var filterType = (FilterType)this.scanline[0]; + Span scanlineSpan = this.scanline.Span; + var filterType = (FilterType)scanlineSpan[0]; switch (filterType) { @@ -564,22 +568,22 @@ namespace SixLabors.ImageSharp.Formats.Png case FilterType.Sub: - SubFilter.Decode(this.scanline, this.bytesPerPixel); + SubFilter.Decode(scanlineSpan, this.bytesPerPixel); break; case FilterType.Up: - UpFilter.Decode(this.scanline, this.previousScanline); + UpFilter.Decode(scanlineSpan, this.previousScanline.Span); break; case FilterType.Average: - AverageFilter.Decode(this.scanline, this.previousScanline, this.bytesPerPixel); + AverageFilter.Decode(scanlineSpan, this.previousScanline.Span, this.bytesPerPixel); break; case FilterType.Paeth: - PaethFilter.Decode(this.scanline, this.previousScanline, this.bytesPerPixel); + PaethFilter.Decode(scanlineSpan, this.previousScanline.Span, this.bytesPerPixel); break; default: @@ -697,7 +701,7 @@ namespace SixLabors.ImageSharp.Formats.Png Span rowSpan = pixels.GetPixelRowSpan(this.currentRow); // Trim the first marker byte from the buffer - var scanlineBuffer = new Span(defilteredScanline, 1); + var scanlineBuffer = new Span(defilteredScanline, 1, defilteredScanline.Length - 1); switch (this.pngColorType) { @@ -750,11 +754,11 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.header.BitDepth == 16) { int length = this.header.Width * 3; - using (var compressed = new Buffer(length)) + using (IBuffer compressed = this.configuration.MemoryManager.Allocate(length)) { // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(scanlineBuffer, compressed, length); - PixelOperations.Instance.PackFromRgb24Bytes(compressed, rowSpan, this.header.Width); + this.From16BitTo8Bit(scanlineBuffer, compressed.Span, length); + PixelOperations.Instance.PackFromRgb24Bytes(compressed.Span, rowSpan, this.header.Width); } } else @@ -767,10 +771,10 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.header.BitDepth == 16) { int length = this.header.Width * 3; - using (var compressed = new Buffer(length)) + using (IBuffer compressed = this.configuration.MemoryManager.Allocate(length)) { // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(scanlineBuffer, compressed, length); + this.From16BitTo8Bit(scanlineBuffer, compressed.Span, length); Span rgb24Span = compressed.Span.NonPortableCast(); for (int x = 0; x < this.header.Width; x++) @@ -808,11 +812,11 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.header.BitDepth == 16) { int length = this.header.Width * 4; - using (var compressed = new Buffer(length)) + using (IBuffer compressed = this.configuration.MemoryManager.Allocate(length)) { // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(scanlineBuffer, compressed, length); - PixelOperations.Instance.PackFromRgba32Bytes(compressed, rowSpan, this.header.Width); + this.From16BitTo8Bit(scanlineBuffer, compressed.Span, length); + PixelOperations.Instance.PackFromRgba32Bytes(compressed.Span, rowSpan, this.header.Width); } } else @@ -928,7 +932,7 @@ namespace SixLabors.ImageSharp.Formats.Png var color = default(TPixel); // Trim the first marker byte from the buffer - var scanlineBuffer = new Span(defilteredScanline, 1); + var scanlineBuffer = new Span(defilteredScanline, 1, defilteredScanline.Length - 1); switch (this.pngColorType) { @@ -1011,18 +1015,20 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.header.BitDepth == 16) { int length = this.header.Width * 3; - using (var compressed = new Buffer(length)) + using (IBuffer compressed = this.configuration.MemoryManager.Allocate(length)) { + Span compressedSpan = compressed.Span; + // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(scanlineBuffer, compressed, length); + this.From16BitTo8Bit(scanlineBuffer, compressedSpan, length); if (this.hasTrans) { for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 3) { - rgba.R = compressed[o]; - rgba.G = compressed[o + 1]; - rgba.B = compressed[o + 2]; + rgba.R = compressedSpan[o]; + rgba.G = compressedSpan[o + 1]; + rgba.B = compressedSpan[o + 2]; rgba.A = (byte)(this.rgb24Trans.Equals(rgba.Rgb) ? 0 : 255); color.PackFromRgba32(rgba); @@ -1033,9 +1039,9 @@ namespace SixLabors.ImageSharp.Formats.Png { for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 3) { - rgba.R = compressed[o]; - rgba.G = compressed[o + 1]; - rgba.B = compressed[o + 2]; + rgba.R = compressedSpan[o]; + rgba.G = compressedSpan[o + 1]; + rgba.B = compressedSpan[o + 2]; color.PackFromRgba32(rgba); rowSpan[x] = color; @@ -1079,16 +1085,18 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.header.BitDepth == 16) { int length = this.header.Width * 4; - using (var compressed = new Buffer(length)) + using (IBuffer compressed = this.configuration.MemoryManager.Allocate(length)) { + Span compressedSpan = compressed.Span; + // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(scanlineBuffer, compressed, length); + this.From16BitTo8Bit(scanlineBuffer, compressedSpan, length); for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 4) { - rgba.R = compressed[o]; - rgba.G = compressed[o + 1]; - rgba.B = compressed[o + 2]; - rgba.A = compressed[o + 3]; + rgba.R = compressedSpan[o]; + rgba.G = compressedSpan[o + 1]; + rgba.B = compressedSpan[o + 2]; + rgba.A = compressedSpan[o + 3]; color.PackFromRgba32(rgba); rowSpan[x] = color; @@ -1254,7 +1262,7 @@ namespace SixLabors.ImageSharp.Formats.Png this.crc.Reset(); this.crc.Update(this.chunkTypeBuffer); - this.crc.Update(chunk.Data, 0, chunk.Length); + this.crc.Update(chunk.Data.Array, 0, chunk.Length); if (this.crc.Value != chunk.Crc && IsCriticalChunk(chunk)) { @@ -1278,8 +1286,8 @@ namespace SixLabors.ImageSharp.Formats.Png private void ReadChunkData(PngChunk chunk) { // We rent the buffer here to return it afterwards in Decode() - chunk.Data = ArrayPool.Shared.Rent(chunk.Length); - this.currentStream.Read(chunk.Data, 0, chunk.Length); + chunk.Data = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(chunk.Length); + this.currentStream.Read(chunk.Data.Array, 0, chunk.Length); } /// @@ -1350,7 +1358,7 @@ namespace SixLabors.ImageSharp.Formats.Png private void SwapBuffers() { - Buffer temp = this.previousScanline; + IManagedByteBuffer temp = this.previousScanline; this.previousScanline = this.scanline; this.scanline = temp; } diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index 4abafc9e80..0c40ccf2a0 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Quantizers; @@ -66,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Png public void Encode(Image image, Stream stream) where TPixel : struct, IPixel { - using (var encoder = new PngEncoderCore(this)) + using (var encoder = new PngEncoderCore(image.GetMemoryManager(), this)) { encoder.Encode(image, stream); } diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 385d40b6ba..1ab7a83ce0 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -19,6 +19,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// internal sealed class PngEncoderCore : IDisposable { + private readonly MemoryManager memoryManager; + /// /// The maximum block size, defaults at 64k for uncompressed blocks. /// @@ -72,37 +74,37 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The previous scanline. /// - private Buffer previousScanline; + private IManagedByteBuffer previousScanline; /// /// The raw scanline. /// - private Buffer rawScanline; + private IManagedByteBuffer rawScanline; /// /// The filtered scanline result. /// - private Buffer result; + private IManagedByteBuffer result; /// /// The buffer for the sub filter /// - private Buffer sub; + private IManagedByteBuffer sub; /// /// The buffer for the up filter /// - private Buffer up; + private IManagedByteBuffer up; /// /// The buffer for the average filter /// - private Buffer average; + private IManagedByteBuffer average; /// /// The buffer for the paeth filter /// - private Buffer paeth; + private IManagedByteBuffer paeth; /// /// The png color type. @@ -147,9 +149,11 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The options for influencing the encoder - public PngEncoderCore(IPngEncoderOptions options) + public PngEncoderCore(MemoryManager memoryManager, IPngEncoderOptions options) { + this.memoryManager = memoryManager; this.ignoreMetadata = options.IgnoreMetadata; this.paletteSize = options.PaletteSize > 0 ? options.PaletteSize.Clamp(1, int.MaxValue) : int.MaxValue; this.pngColorType = options.PngColorType; @@ -353,11 +357,11 @@ namespace SixLabors.ImageSharp.Formats.Png { if (this.bytesPerPixel == 4) { - PixelOperations.Instance.ToRgba32Bytes(rowSpan, this.rawScanline, this.width); + PixelOperations.Instance.ToRgba32Bytes(rowSpan, this.rawScanline.Span, this.width); } else { - PixelOperations.Instance.ToRgb24Bytes(rowSpan, this.rawScanline, this.width); + PixelOperations.Instance.ToRgb24Bytes(rowSpan, this.rawScanline.Span, this.width); } } @@ -369,13 +373,14 @@ namespace SixLabors.ImageSharp.Formats.Png /// The row span. /// The row. /// The - private Buffer EncodePixelRow(Span rowSpan, int row) + private IManagedByteBuffer EncodePixelRow(Span rowSpan, int row) where TPixel : struct, IPixel { switch (this.pngColorType) { case PngColorType.Palette: - Buffer.BlockCopy(this.palettePixelData, row * this.rawScanline.Length, this.rawScanline.Array, 0, this.rawScanline.Length); + // TODO: Use Span copy! + Buffer.BlockCopy(this.palettePixelData, row * this.rawScanline.Length(), this.rawScanline.Array, 0, this.rawScanline.Length()); break; case PngColorType.Grayscale: case PngColorType.GrayscaleWithAlpha: @@ -394,7 +399,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// to be most compressible, using lowest total variation as proxy for compressibility. /// /// The - private Buffer GetOptimalFilteredScanline() + private IManagedByteBuffer GetOptimalFilteredScanline() { Span scanSpan = this.rawScanline.Span; Span prevSpan = this.previousScanline.Span; @@ -402,18 +407,18 @@ namespace SixLabors.ImageSharp.Formats.Png // Palette images don't compress well with adaptive filtering. if (this.pngColorType == PngColorType.Palette || this.bitDepth < 8) { - NoneFilter.Encode(this.rawScanline, this.result); + NoneFilter.Encode(this.rawScanline.Span, this.result.Span); return this.result; } // This order, while different to the enumerated order is more likely to produce a smaller sum // early on which shaves a couple of milliseconds off the processing time. - UpFilter.Encode(scanSpan, prevSpan, this.up, out int currentSum); + UpFilter.Encode(scanSpan, prevSpan, this.up.Span, out int currentSum); int lowestSum = currentSum; - Buffer actualResult = this.up; + IManagedByteBuffer actualResult = this.up; - PaethFilter.Encode(scanSpan, prevSpan, this.paeth, this.bytesPerPixel, out currentSum); + PaethFilter.Encode(scanSpan, prevSpan, this.paeth.Span, this.bytesPerPixel, out currentSum); if (currentSum < lowestSum) { @@ -421,7 +426,7 @@ namespace SixLabors.ImageSharp.Formats.Png actualResult = this.paeth; } - SubFilter.Encode(scanSpan, this.sub, this.bytesPerPixel, out currentSum); + SubFilter.Encode(scanSpan, this.sub.Span, this.bytesPerPixel, out currentSum); if (currentSum < lowestSum) { @@ -429,7 +434,7 @@ namespace SixLabors.ImageSharp.Formats.Png actualResult = this.sub; } - AverageFilter.Encode(scanSpan, prevSpan, this.average, this.bytesPerPixel, out currentSum); + AverageFilter.Encode(scanSpan, prevSpan, this.average.Span, this.bytesPerPixel, out currentSum); if (currentSum < lowestSum) { @@ -516,12 +521,15 @@ namespace SixLabors.ImageSharp.Formats.Png // Get max colors for bit depth. int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3; - byte[] colorTable = ArrayPool.Shared.Rent(colorTableLength); - byte[] alphaTable = ArrayPool.Shared.Rent(pixelCount); var rgba = default(Rgba32); bool anyAlpha = false; - try + + using (IManagedByteBuffer colorTable = this.memoryManager.AllocateManagedByteBuffer(colorTableLength)) + using (IManagedByteBuffer alphaTable = this.memoryManager.AllocateManagedByteBuffer(pixelCount)) { + Span colorTableSpan = colorTable.Span; + Span alphaTableSpan = alphaTable.Span; + for (byte i = 0; i < pixelCount; i++) { if (quantized.Pixels.Contains(i)) @@ -531,9 +539,9 @@ namespace SixLabors.ImageSharp.Formats.Png byte alpha = rgba.A; - colorTable[offset] = rgba.R; - colorTable[offset + 1] = rgba.G; - colorTable[offset + 2] = rgba.B; + colorTableSpan[offset] = rgba.R; + colorTableSpan[offset + 1] = rgba.G; + colorTableSpan[offset + 2] = rgba.B; if (alpha > this.threshold) { @@ -541,23 +549,18 @@ namespace SixLabors.ImageSharp.Formats.Png } anyAlpha = anyAlpha || alpha < 255; - alphaTable[i] = alpha; + alphaTableSpan[i] = alpha; } } - this.WriteChunk(stream, PngChunkTypes.Palette, colorTable, 0, colorTableLength); + this.WriteChunk(stream, PngChunkTypes.Palette, colorTable.Array, 0, colorTableLength); // Write the transparency data if (anyAlpha) { - this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, alphaTable, 0, pixelCount); + this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, alphaTable.Array, 0, pixelCount); } } - finally - { - ArrayPool.Shared.Return(colorTable); - ArrayPool.Shared.Return(alphaTable); - } return quantized; } @@ -619,16 +622,16 @@ namespace SixLabors.ImageSharp.Formats.Png this.bytesPerScanline = this.width * this.bytesPerPixel; int resultLength = this.bytesPerScanline + 1; - this.previousScanline = Buffer.CreateClean(this.bytesPerScanline); - this.rawScanline = Buffer.CreateClean(this.bytesPerScanline); - this.result = Buffer.CreateClean(resultLength); + this.previousScanline = this.memoryManager.AllocateCleanManagedByteBuffer(this.bytesPerScanline); + this.rawScanline = this.memoryManager.AllocateCleanManagedByteBuffer(this.bytesPerScanline); + this.result = this.memoryManager.AllocateCleanManagedByteBuffer(resultLength); if (this.pngColorType != PngColorType.Palette) { - this.sub = Buffer.CreateClean(resultLength); - this.up = Buffer.CreateClean(resultLength); - this.average = Buffer.CreateClean(resultLength); - this.paeth = Buffer.CreateClean(resultLength); + this.sub = this.memoryManager.AllocateCleanManagedByteBuffer(resultLength); + this.up = this.memoryManager.AllocateCleanManagedByteBuffer(resultLength); + this.average = this.memoryManager.AllocateCleanManagedByteBuffer(resultLength); + this.paeth = this.memoryManager.AllocateCleanManagedByteBuffer(resultLength); } byte[] buffer; @@ -641,10 +644,10 @@ namespace SixLabors.ImageSharp.Formats.Png { for (int y = 0; y < this.height; y++) { - Buffer r = this.EncodePixelRow(pixels.GetPixelRowSpan(y), y); + IManagedByteBuffer r = this.EncodePixelRow(pixels.GetPixelRowSpan(y), y); deflateStream.Write(r.Array, 0, resultLength); - Buffer temp = this.rawScanline; + IManagedByteBuffer temp = this.rawScanline; this.rawScanline = this.previousScanline; this.previousScanline = temp; } diff --git a/src/ImageSharp/GraphicsOptions.cs b/src/ImageSharp/GraphicsOptions.cs index 114eaab2a6..a094abacbe 100644 --- a/src/ImageSharp/GraphicsOptions.cs +++ b/src/ImageSharp/GraphicsOptions.cs @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp // some API thought post V1. /// - /// Gets or sets a value indicating the blending percentage to apply to the drawing operation + /// Gets or sets a value indicating the blending mode to apply to the drawing operation /// public PixelBlenderMode BlenderMode { diff --git a/src/ImageSharp/IImageProcessingContext{TPixel}.cs b/src/ImageSharp/IImageProcessingContext{TPixel}.cs index 552e8d579d..0e8efde3b3 100644 --- a/src/ImageSharp/IImageProcessingContext{TPixel}.cs +++ b/src/ImageSharp/IImageProcessingContext{TPixel}.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.Primitives; @@ -14,6 +15,18 @@ namespace SixLabors.ImageSharp public interface IImageProcessingContext where TPixel : struct, IPixel { + /// + /// Gets a reference to the used to allocate buffers + /// for this context. + /// + MemoryManager MemoryManager { get; } + + /// + /// Gets the image dimensions at the current point in the processing pipeline. + /// + /// The + Size GetCurrentSize(); + /// /// Adds the processor to the current set of image operations to be applied. /// @@ -40,7 +53,7 @@ namespace SixLabors.ImageSharp /// /// Adds the processors to the current image /// - /// The current image or a new image depending on withere this is alloed to mutate the source image. + /// The current image or a new image depending on whether this is allowed to mutate the source image. Image Apply(); } } \ No newline at end of file diff --git a/src/ImageSharp/Image/IImage.cs b/src/ImageSharp/Image/IImage.cs index 7355dc1fec..b9e2cee616 100644 --- a/src/ImageSharp/Image/IImage.cs +++ b/src/ImageSharp/Image/IImage.cs @@ -1,12 +1,14 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; + namespace SixLabors.ImageSharp { /// /// Encapsulates the properties and methods that describe an image. /// - public interface IImage : IImageInfo + public interface IImage : IImageInfo, IDisposable { } } \ No newline at end of file diff --git a/src/ImageSharp/Image/Image.Decode.cs b/src/ImageSharp/Image/Image.Decode.cs index ef00ff15fd..6b44c893bc 100644 --- a/src/ImageSharp/Image/Image.Decode.cs +++ b/src/ImageSharp/Image/Image.Decode.cs @@ -29,12 +29,12 @@ namespace SixLabors.ImageSharp return null; } - using (var buffer = new Buffer(maxHeaderSize)) + using (IManagedByteBuffer buffer = config.MemoryManager.AllocateManagedByteBuffer(maxHeaderSize)) { long startPosition = stream.Position; stream.Read(buffer.Array, 0, maxHeaderSize); stream.Position = startPosition; - return config.FormatDetectors.Select(x => x.DetectFormat(buffer)).LastOrDefault(x => x != null); + return config.ImageFormatsManager.FormatDetectors.Select(x => x.DetectFormat(buffer.Span)).LastOrDefault(x => x != null); } } @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp format = InternalDetectFormat(stream, config); if (format != null) { - return config.FindDecoder(format); + return config.ImageFormatsManager.FindDecoder(format); } return null; diff --git a/src/ImageSharp/Image/Image.FromBytes.cs b/src/ImageSharp/Image/Image.FromBytes.cs index 9da9c5e432..44c53d7767 100644 --- a/src/ImageSharp/Image/Image.FromBytes.cs +++ b/src/ImageSharp/Image/Image.FromBytes.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp /// The format or null if none found. public static IImageFormat DetectFormat(byte[] data) { - return DetectFormat(null, data); + return DetectFormat(Configuration.Default, data); } /// @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp /// /// The byte array containing image data. /// A new . - public static Image Load(byte[] data) => Load(null, data); + public static Image Load(byte[] data) => Load(Configuration.Default, data); /// /// Create a new instance of the class from the given byte array. @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp /// The byte array containing image data. /// The mime type of the decoded image. /// A new . - public static Image Load(byte[] data, out IImageFormat format) => Load(null, data, out format); + public static Image Load(byte[] data, out IImageFormat format) => Load(Configuration.Default, data, out format); /// /// Create a new instance of the class from the given byte array. @@ -93,9 +93,7 @@ namespace SixLabors.ImageSharp /// A new . public static Image Load(byte[] data) where TPixel : struct, IPixel - { - return Load(null, data); - } + => Load(Configuration.Default, data); /// /// Create a new instance of the class from the given byte array. @@ -106,9 +104,7 @@ namespace SixLabors.ImageSharp /// A new . public static Image Load(byte[] data, out IImageFormat format) where TPixel : struct, IPixel - { - return Load(null, data, out format); - } + => Load(Configuration.Default, data, out format); /// /// Create a new instance of the class from the given byte array. diff --git a/src/ImageSharp/Image/Image.FromFile.cs b/src/ImageSharp/Image/Image.FromFile.cs index 0474a6d476..ad8f3426f2 100644 --- a/src/ImageSharp/Image/Image.FromFile.cs +++ b/src/ImageSharp/Image/Image.FromFile.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp /// The mime type or null if none found. public static IImageFormat DetectFormat(string filePath) { - return DetectFormat(null, filePath); + return DetectFormat(Configuration.Default, filePath); } /// @@ -118,7 +118,7 @@ namespace SixLabors.ImageSharp public static Image Load(string path) where TPixel : struct, IPixel { - return Load(null, path); + return Load(Configuration.Default, path); } /// @@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp public static Image Load(string path, out IImageFormat format) where TPixel : struct, IPixel { - return Load(null, path, out format); + return Load(Configuration.Default, path, out format); } /// @@ -150,7 +150,6 @@ namespace SixLabors.ImageSharp public static Image Load(Configuration config, string path) where TPixel : struct, IPixel { - config = config ?? Configuration.Default; using (Stream stream = config.FileSystem.OpenRead(path)) { return Load(config, stream); @@ -171,7 +170,6 @@ namespace SixLabors.ImageSharp public static Image Load(Configuration config, string path, out IImageFormat format) where TPixel : struct, IPixel { - config = config ?? Configuration.Default; using (Stream stream = config.FileSystem.OpenRead(path)) { return Load(config, stream, out format); @@ -191,7 +189,7 @@ namespace SixLabors.ImageSharp public static Image Load(string path, IImageDecoder decoder) where TPixel : struct, IPixel { - return Load(null, path, decoder); + return Load(Configuration.Default, path, decoder); } /// @@ -208,7 +206,6 @@ namespace SixLabors.ImageSharp public static Image Load(Configuration config, string path, IImageDecoder decoder) where TPixel : struct, IPixel { - config = config ?? Configuration.Default; using (Stream stream = config.FileSystem.OpenRead(path)) { return Load(config, stream, decoder); diff --git a/src/ImageSharp/Image/Image.FromStream.cs b/src/ImageSharp/Image/Image.FromStream.cs index 62668dd023..9061c334dc 100644 --- a/src/ImageSharp/Image/Image.FromStream.cs +++ b/src/ImageSharp/Image/Image.FromStream.cs @@ -19,62 +19,48 @@ namespace SixLabors.ImageSharp /// By reading the header on the provided stream this calculates the images mime type. /// /// The image stream to read the header from. + /// Thrown if the stream is not readable. /// The mime type or null if none found. - public static IImageFormat DetectFormat(Stream stream) - { - return DetectFormat(null, stream); - } + public static IImageFormat DetectFormat(Stream stream) => DetectFormat(Configuration.Default, stream); /// /// By reading the header on the provided stream this calculates the images mime type. /// /// The configuration. /// The image stream to read the header from. + /// Thrown if the stream is not readable. /// The mime type or null if none found. public static IImageFormat DetectFormat(Configuration config, Stream stream) - { - return WithSeekableStream(stream, s => InternalDetectFormat(s, config ?? Configuration.Default)); - } + => WithSeekableStream(config, stream, s => InternalDetectFormat(s, config)); /// /// By reading the header on the provided stream this reads the raw image information. /// /// The image stream to read the header from. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// Thrown if the stream is not readable. /// /// The or null if suitable info detector not found. /// - public static IImageInfo Identify(Stream stream) - { - return Identify(null, stream); - } + public static IImageInfo Identify(Stream stream) => Identify(Configuration.Default, stream); /// /// Reads the raw image information from the specified stream without fully decoding it. /// /// The configuration. /// The image stream to read the information from. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// Thrown if the stream is not readable. /// /// The or null if suitable info detector is not found. /// public static IImageInfo Identify(Configuration config, Stream stream) - { - return WithSeekableStream(stream, s => InternalIdentity(s, config ?? Configuration.Default)); - } + => WithSeekableStream(config, stream, s => InternalIdentity(s, config ?? Configuration.Default)); /// /// Create a new instance of the class from the given stream. /// /// The stream containing image information. /// the mime type of the decoded image. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// Thrown if the stream is not readable. /// A new .> public static Image Load(Stream stream, out IImageFormat format) => Load(stream, out format); @@ -82,9 +68,7 @@ namespace SixLabors.ImageSharp /// Create a new instance of the class from the given stream. /// /// The stream containing image information. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// Thrown if the stream is not readable. /// A new .> public static Image Load(Stream stream) => Load(stream); @@ -93,9 +77,7 @@ namespace SixLabors.ImageSharp /// /// The stream containing image information. /// The decoder. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// Thrown if the stream is not readable. /// A new .> public static Image Load(Stream stream, IImageDecoder decoder) => Load(stream, decoder); @@ -104,9 +86,7 @@ namespace SixLabors.ImageSharp /// /// The config for the decoder. /// The stream containing image information. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// Thrown if the stream is not readable. /// A new .> public static Image Load(Configuration config, Stream stream) => Load(config, stream); @@ -116,58 +96,45 @@ namespace SixLabors.ImageSharp /// The config for the decoder. /// The stream containing image information. /// the mime type of the decoded image. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// Thrown if the stream is not readable. /// A new .> - public static Image Load(Configuration config, Stream stream, out IImageFormat format) => Load(config, stream, out format); + public static Image Load(Configuration config, Stream stream, out IImageFormat format) + => Load(config, stream, out format); /// /// Create a new instance of the class from the given stream. /// /// The stream containing image information. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// Thrown if the stream is not readable. /// The pixel format. /// A new .> public static Image Load(Stream stream) where TPixel : struct, IPixel - { - return Load(null, stream); - } + => Load(null, stream); /// /// Create a new instance of the class from the given stream. /// /// The stream containing image information. /// the mime type of the decoded image. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// Thrown if the stream is not readable. /// The pixel format. /// A new .> public static Image Load(Stream stream, out IImageFormat format) where TPixel : struct, IPixel - { - return Load(null, stream, out format); - } + => Load(null, stream, out format); /// /// Create a new instance of the class from the given stream. /// /// The stream containing image information. /// The decoder. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// Thrown if the stream is not readable. /// The pixel format. /// A new .> public static Image Load(Stream stream, IImageDecoder decoder) where TPixel : struct, IPixel - { - return WithSeekableStream(stream, s => decoder.Decode(Configuration.Default, s)); - } + => WithSeekableStream(Configuration.Default, stream, s => decoder.Decode(Configuration.Default, s)); /// /// Create a new instance of the class from the given stream. @@ -175,32 +142,24 @@ namespace SixLabors.ImageSharp /// The Configuration. /// The stream containing image information. /// The decoder. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// Thrown if the stream is not readable. /// The pixel format. /// A new .> public static Image Load(Configuration config, Stream stream, IImageDecoder decoder) where TPixel : struct, IPixel - { - return WithSeekableStream(stream, s => decoder.Decode(config, s)); - } + => WithSeekableStream(config, stream, s => decoder.Decode(config, s)); /// /// Create a new instance of the class from the given stream. /// /// The configuration options. /// The stream containing image information. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// Thrown if the stream is not readable. /// The pixel format. /// A new .> public static Image Load(Configuration config, Stream stream) where TPixel : struct, IPixel - { - return Load(config, stream, out var _); - } + => Load(config, stream, out IImageFormat _); /// /// Create a new instance of the class from the given stream. @@ -208,16 +167,14 @@ namespace SixLabors.ImageSharp /// The configuration options. /// The stream containing image information. /// the mime type of the decoded image. - /// - /// Thrown if the stream is not readable nor seekable. - /// + /// Thrown if the stream is not readable. /// The pixel format. /// A new .> public static Image Load(Configuration config, Stream stream, out IImageFormat format) - where TPixel : struct, IPixel + where TPixel : struct, IPixel { config = config ?? Configuration.Default; - (Image img, IImageFormat format) data = WithSeekableStream(stream, s => Decode(s, config)); + (Image img, IImageFormat format) data = WithSeekableStream(config, stream, s => Decode(s, config)); format = data.format; @@ -229,7 +186,7 @@ namespace SixLabors.ImageSharp var stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Image cannot be loaded. Available decoders:"); - foreach (KeyValuePair val in config.ImageDecoders) + foreach (KeyValuePair val in config.ImageFormatsManager.ImageDecoders) { stringBuilder.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}"); } @@ -237,7 +194,7 @@ namespace SixLabors.ImageSharp throw new NotSupportedException(stringBuilder.ToString()); } - private static T WithSeekableStream(Stream stream, Func action) + private static T WithSeekableStream(Configuration config, Stream stream, Func action) { if (!stream.CanRead) { @@ -246,6 +203,11 @@ namespace SixLabors.ImageSharp if (stream.CanSeek) { + if (config.ReadOrigin == ReadOrigin.Begin) + { + stream.Position = 0; + } + return action(stream); } diff --git a/src/ImageSharp/Image/ImageExtensions.cs b/src/ImageSharp/Image/ImageExtensions.cs index c4de1c2988..7d23d95d9c 100644 --- a/src/ImageSharp/Image/ImageExtensions.cs +++ b/src/ImageSharp/Image/ImageExtensions.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp Guard.NotNullOrEmpty(filePath, nameof(filePath)); string ext = Path.GetExtension(filePath).Trim('.'); - IImageFormat format = source.GetConfiguration().FindFormatByFileExtension(ext); + IImageFormat format = source.GetConfiguration().ImageFormatsManager.FindFormatByFileExtension(ext); if (format == null) { var stringBuilder = new StringBuilder(); @@ -45,13 +45,13 @@ namespace SixLabors.ImageSharp throw new NotSupportedException(stringBuilder.ToString()); } - IImageEncoder encoder = source.GetConfiguration().FindEncoder(format); + IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); if (encoder == null) { var stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"Can't find encoder for file extention '{ext}' using image format '{format.Name}'. Registered encoders include:"); - foreach (KeyValuePair enc in source.GetConfiguration().ImageEncoders) + foreach (KeyValuePair enc in source.GetConfiguration().ImageFormatsManager.ImageEncoders) { stringBuilder.AppendLine($" - {enc.Key} : {enc.Value.GetType().Name}"); } @@ -93,14 +93,14 @@ namespace SixLabors.ImageSharp where TPixel : struct, IPixel { Guard.NotNull(format, nameof(format)); - IImageEncoder encoder = source.GetConfiguration().FindEncoder(format); + IImageEncoder encoder = source.GetConfiguration().ImageFormatsManager.FindEncoder(format); if (encoder == null) { var stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Can't find encoder for provided mime type. Available encoded:"); - foreach (KeyValuePair val in source.GetConfiguration().ImageEncoders) + foreach (KeyValuePair val in source.GetConfiguration().ImageFormatsManager.ImageEncoders) { stringBuilder.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}"); } diff --git a/src/ImageSharp/Image/ImageFrame.LoadPixelData.cs b/src/ImageSharp/Image/ImageFrame.LoadPixelData.cs index e2230c4367..b9341a1b24 100644 --- a/src/ImageSharp/Image/ImageFrame.LoadPixelData.cs +++ b/src/ImageSharp/Image/ImageFrame.LoadPixelData.cs @@ -20,30 +20,32 @@ namespace SixLabors.ImageSharp /// /// Create a new instance of the class from the given byte array in format. /// + /// The memory manager to use for allocations /// The byte array containing image data. /// The width of the final image. /// The height of the final image. /// The pixel format. /// A new . - public static ImageFrame LoadPixelData(Span data, int width, int height) + public static ImageFrame LoadPixelData(MemoryManager memoryManager, Span data, int width, int height) where TPixel : struct, IPixel - => LoadPixelData(data.NonPortableCast(), width, height); + => LoadPixelData(memoryManager, data.NonPortableCast(), width, height); /// /// Create a new instance of the class from the raw data. /// + /// The memory manager to use for allocations /// The Span containing the image Pixel data. /// The width of the final image. /// The height of the final image. /// The pixel format. /// A new . - public static ImageFrame LoadPixelData(Span data, int width, int height) + public static ImageFrame LoadPixelData(MemoryManager memoryManager, Span data, int width, int height) where TPixel : struct, IPixel { int count = width * height; Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); - var image = new ImageFrame(width, height); + var image = new ImageFrame(memoryManager, width, height); SpanHelper.Copy(data, image.GetPixelSpan(), count); return image; diff --git a/src/ImageSharp/Image/ImageFrameCollection.cs b/src/ImageSharp/Image/ImageFrameCollection.cs index 3e9bb03435..aefeacce15 100644 --- a/src/ImageSharp/Image/ImageFrameCollection.cs +++ b/src/ImageSharp/Image/ImageFrameCollection.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp this.parent = parent; // Frames are already cloned within the caller - this.frames.Add(new ImageFrame(width, height)); + this.frames.Add(new ImageFrame(parent.GetConfiguration().MemoryManager, width, height)); } internal ImageFrameCollection(Image parent, IEnumerable> frames) @@ -80,7 +80,11 @@ namespace SixLabors.ImageSharp /// public ImageFrame AddFrame(TPixel[] data) { - var frame = ImageFrame.LoadPixelData(new Span(data), this.RootFrame.Width, this.RootFrame.Height); + var frame = ImageFrame.LoadPixelData( + this.parent.GetMemoryManager(), + new Span(data), + this.RootFrame.Width, + this.RootFrame.Height); this.frames.Add(frame); return frame; } @@ -143,7 +147,7 @@ namespace SixLabors.ImageSharp /// public ImageFrame CreateFrame() { - var frame = new ImageFrame(this.RootFrame.Width, this.RootFrame.Height); + var frame = new ImageFrame(this.parent.GetConfiguration().MemoryManager, this.RootFrame.Width, this.RootFrame.Height); this.frames.Add(frame); return frame; } diff --git a/src/ImageSharp/Image/ImageFrame{TPixel}.cs b/src/ImageSharp/Image/ImageFrame{TPixel}.cs index ba475f9cf3..833a22f7c7 100644 --- a/src/ImageSharp/Image/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/Image/ImageFrame{TPixel}.cs @@ -7,6 +7,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Helpers; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.PixelFormats; @@ -21,72 +22,84 @@ namespace SixLabors.ImageSharp public sealed class ImageFrame : IPixelSource, IDisposable where TPixel : struct, IPixel { - /// - /// The image pixels. Not private as Buffer2D requires an array in its constructor. - /// - private Buffer2D pixelBuffer; - private bool isDisposed; /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The width of the image in pixels. /// The height of the image in pixels. - internal ImageFrame(int width, int height) - : this(width, height, new ImageFrameMetaData()) + internal ImageFrame(MemoryManager memoryManager, int width, int height) + : this(memoryManager, width, height, new ImageFrameMetaData()) { } /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The width of the image in pixels. /// The height of the image in pixels. /// The meta data. - internal ImageFrame(int width, int height, ImageFrameMetaData metaData) + internal ImageFrame(MemoryManager memoryManager, int width, int height, ImageFrameMetaData metaData) { + Guard.NotNull(memoryManager, nameof(memoryManager)); Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); Guard.NotNull(metaData, nameof(metaData)); - this.pixelBuffer = Buffer2D.CreateClean(width, height); + this.MemoryManager = memoryManager; + this.PixelBuffer = memoryManager.AllocateClean2D(width, height); this.MetaData = metaData; } /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The of the frame. /// The meta data. - internal ImageFrame(Size size, ImageFrameMetaData metaData) - : this(size.Width, size.Height, metaData) + internal ImageFrame(MemoryManager memoryManager, Size size, ImageFrameMetaData metaData) + : this(memoryManager, size.Width, size.Height, metaData) { } /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The source. - internal ImageFrame(ImageFrame source) + internal ImageFrame(MemoryManager memoryManager, ImageFrame source) { - this.pixelBuffer = new Buffer2D(source.pixelBuffer.Width, source.pixelBuffer.Height); - source.pixelBuffer.Span.CopyTo(this.pixelBuffer.Span); + this.MemoryManager = memoryManager; + this.PixelBuffer = memoryManager.Allocate2D(source.PixelBuffer.Width, source.PixelBuffer.Height); + source.PixelBuffer.Span.CopyTo(this.PixelBuffer.Span); this.MetaData = source.MetaData.Clone(); } + /// + /// Gets the to use for buffer allocations. + /// + public MemoryManager MemoryManager { get; } + + /// + /// Gets the image pixels. Not private as Buffer2D requires an array in its constructor. + /// + internal Buffer2D PixelBuffer { get; private set; } + /// - Buffer2D IPixelSource.PixelBuffer => this.pixelBuffer; + Buffer2D IPixelSource.PixelBuffer => this.PixelBuffer; /// /// Gets the width. /// - public int Width => this.pixelBuffer.Width; + public int Width => this.PixelBuffer.Width; /// /// Gets the height. /// - public int Height => this.pixelBuffer.Height; + public int Height => this.PixelBuffer.Height; /// /// Gets the meta data of the frame. @@ -104,13 +117,13 @@ namespace SixLabors.ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - return this.pixelBuffer[x, y]; + return this.PixelBuffer[x, y]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] set { - this.pixelBuffer[x, y] = value; + this.PixelBuffer[x, y] = value; } } @@ -123,7 +136,7 @@ namespace SixLabors.ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ref TPixel GetPixelReference(int x, int y) { - return ref this.pixelBuffer[x, y]; + return ref this.PixelBuffer[x, y]; } /// @@ -139,12 +152,26 @@ namespace SixLabors.ImageSharp } /// - /// Copies the pixels to another of the same size. + /// Copies the pixels to a of the same size. /// /// The target pixel buffer accessor. internal void CopyTo(PixelAccessor target) { - SpanHelper.Copy(this.GetPixelSpan(), target.PixelBuffer.Span); + this.CopyTo(target.PixelBuffer); + } + + /// + /// Copies the pixels to a of the same size. + /// + /// The target pixel buffer accessor. + internal void CopyTo(Buffer2D target) + { + if (this.Size() != target.Size()) + { + throw new ArgumentException("ImageFrame.CopyTo(): target must be of the same size!", nameof(target)); + } + + SpanHelper.Copy(this.GetPixelSpan(), target.Span); } /// @@ -156,8 +183,8 @@ namespace SixLabors.ImageSharp Guard.NotNull(pixelSource, nameof(pixelSource)); // Push my memory into the accessor (which in turn unpins the old buffer ready for the images use) - Buffer2D newPixels = pixelSource.SwapBufferOwnership(this.pixelBuffer); - this.pixelBuffer = newPixels; + Buffer2D newPixels = pixelSource.SwapBufferOwnership(this.PixelBuffer); + this.PixelBuffer = newPixels; } /// @@ -168,9 +195,9 @@ namespace SixLabors.ImageSharp { Guard.NotNull(pixelSource, nameof(pixelSource)); - Buffer2D temp = this.pixelBuffer; - this.pixelBuffer = pixelSource.pixelBuffer; - pixelSource.pixelBuffer = temp; + Buffer2D temp = this.PixelBuffer; + this.PixelBuffer = pixelSource.PixelBuffer; + pixelSource.PixelBuffer = temp; } /// @@ -183,8 +210,8 @@ namespace SixLabors.ImageSharp return; } - this.pixelBuffer?.Dispose(); - this.pixelBuffer = null; + this.PixelBuffer?.Dispose(); + this.PixelBuffer = null; // Note disposing is done. this.isDisposed = true; @@ -211,7 +238,7 @@ namespace SixLabors.ImageSharp Func scaleFunc = PackedPixelConverterHelper.ComputeScaleFunction(); - var target = new ImageFrame(this.Width, this.Height, this.MetaData.Clone()); + var target = new ImageFrame(this.MemoryManager, this.Width, this.Height, this.MetaData.Clone()); using (PixelAccessor pixels = this.Lock()) using (PixelAccessor targetPixels = target.Lock()) @@ -240,7 +267,7 @@ namespace SixLabors.ImageSharp /// The internal ImageFrame Clone() { - return new ImageFrame(this); + return new ImageFrame(this.MemoryManager, this); } /// diff --git a/src/ImageSharp/Image/Image{TPixel}.cs b/src/ImageSharp/Image/Image{TPixel}.cs index be38b41f24..f264d8a59d 100644 --- a/src/ImageSharp/Image/Image{TPixel}.cs +++ b/src/ImageSharp/Image/Image{TPixel}.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. /// /// The pixel format. - public sealed partial class Image : IImage, IDisposable, IConfigurable + public sealed partial class Image : IImage, IConfigurable where TPixel : struct, IPixel { private Configuration configuration; diff --git a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs index 8dcb1f7602..63e4c015c1 100644 --- a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs @@ -17,25 +17,6 @@ namespace SixLabors.ImageSharp internal sealed class PixelAccessor : IDisposable, IBuffer2D where TPixel : struct, IPixel { -#pragma warning disable SA1401 // Fields must be private - /// - /// The containing the pixel data. - /// - internal Buffer2D PixelBuffer; - private bool ownedBuffer; -#pragma warning restore SA1401 // Fields must be private - - /// - /// A value indicating whether this instance of the given entity has been disposed. - /// - /// if this instance has been disposed; otherwise, . - /// - /// If the entity is disposed, it must not be disposed a second time. The isDisposed field is set the first time the entity - /// is disposed. If the isDisposed field is true, then the Dispose() method will not dispose again. This help not to prolong the entity's - /// life in the Garbage Collector. - /// - private bool isDisposed; - /// /// Initializes a new instance of the class. /// @@ -46,47 +27,13 @@ namespace SixLabors.ImageSharp Guard.MustBeGreaterThan(image.PixelBuffer.Width, 0, "image width"); Guard.MustBeGreaterThan(image.PixelBuffer.Height, 0, "image height"); - this.SetPixelBufferUnsafe(image.PixelBuffer, false); - } - - /// - /// Initializes a new instance of the class. - /// - /// The width of the image represented by the pixel buffer. - /// The height of the image represented by the pixel buffer. - public PixelAccessor(int width, int height) - : this(width, height, Buffer2D.CreateClean(width, height), true) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The width of the image represented by the pixel buffer. - /// The height of the image represented by the pixel buffer. - /// The pixel buffer. - /// if set to true [owned buffer]. - private PixelAccessor(int width, int height, Buffer2D pixels, bool ownedBuffer) - { - Guard.NotNull(pixels, nameof(pixels)); - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - this.SetPixelBufferUnsafe(pixels, ownedBuffer); + this.SetPixelBufferUnsafe(image.PixelBuffer); } /// - /// Finalizes an instance of the class. + /// Gets the containing the pixel data. /// - ~PixelAccessor() - { - this.Dispose(); - } - - /// - /// Gets the pixel buffer array. - /// - public TPixel[] PixelArray => this.PixelBuffer.Array; + internal Buffer2D PixelBuffer { get; private set; } /// /// Gets the size of a single pixel in the number of bytes. @@ -105,7 +52,7 @@ namespace SixLabors.ImageSharp public int Height { get; private set; } /// - Span IBuffer2D.Span => this.PixelBuffer; + public Span Span => this.PixelBuffer.Span; private static PixelOperations Operations => PixelOperations.Instance; @@ -121,36 +68,21 @@ namespace SixLabors.ImageSharp get { this.CheckCoordinates(x, y); - return this.PixelArray[(y * this.Width) + x]; + return this.Span[(y * this.Width) + x]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.CheckCoordinates(x, y); - this.PixelArray[(y * this.Width) + x] = value; + Span span = this.Span; + span[(y * this.Width) + x] = value; } } /// public void Dispose() { - if (this.isDisposed || !this.ownedBuffer) - { - return; - } - - // Note disposing is done. - this.isDisposed = true; - - this.PixelBuffer.Dispose(); - - // This object will be cleaned up by the Dispose method. - // Therefore, you should call GC.SuppressFinalize to - // take this object off the finalization queue - // and prevent finalization code for this object - // from executing a second time. - GC.SuppressFinalize(this); } /// @@ -158,66 +90,7 @@ namespace SixLabors.ImageSharp /// public void Reset() { - this.PixelBuffer.Clear(); - } - - /// - /// Copy an area of pixels to the image. - /// - /// The area. - /// The target row index. - /// The target column index. - /// - /// Thrown when an unsupported component order value is passed. - /// - internal void CopyFrom(PixelArea area, int targetY, int targetX = 0) - { - this.CheckCoordinates(area, targetX, targetY); - - this.CopyFrom(area, targetX, targetY, area.Width, area.Height); - } - - /// - /// Copy pixels from the image to an area of pixels. - /// - /// The area. - /// The source row index. - /// The source column index. - /// - /// Thrown when an unsupported component order value is passed. - /// - internal void CopyTo(PixelArea area, int sourceY, int sourceX = 0) - { - this.CheckCoordinates(area, sourceX, sourceY); - - this.CopyTo(area, sourceX, sourceY, area.Width, area.Height); - } - - /// - /// Copy pixels from the image to an area of pixels. This method will make sure that the pixels - /// that are copied are within the bounds of the image. - /// - /// The area. - /// The source row index. - /// The source column index. - /// - /// Thrown when an unsupported component order value is passed. - /// - internal void SafeCopyTo(PixelArea area, int sourceY, int sourceX = 0) - { - int width = Math.Min(area.Width, this.Width - sourceX); - if (width < 1) - { - return; - } - - int height = Math.Min(area.Height, this.Height - sourceY); - if (height < 1) - { - return; - } - - this.CopyTo(area, sourceX, sourceY, width, height); + this.PixelBuffer.Buffer.Clear(); } /// @@ -229,7 +102,7 @@ namespace SixLabors.ImageSharp internal Buffer2D SwapBufferOwnership(Buffer2D pixels) { Buffer2D oldPixels = this.PixelBuffer; - this.SetPixelBufferUnsafe(pixels, this.ownedBuffer); + this.SetPixelBufferUnsafe(pixels); return oldPixels; } @@ -242,170 +115,13 @@ namespace SixLabors.ImageSharp SpanHelper.Copy(this.PixelBuffer.Span, target.PixelBuffer.Span); } - /// - /// Copies from an area in format. - /// - /// The area. - /// The target column index. - /// The target row index. - /// The width. - /// The height. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CopyFromZyx(PixelArea area, int targetX, int targetY, int width, int height) - { - for (int y = 0; y < height; y++) - { - Span source = area.GetRowSpan(y); - Span destination = this.GetRowSpan(targetX, targetY + y); - - Operations.PackFromBgr24Bytes(source, destination, width); - } - } - - /// - /// Copies from an area in format. - /// - /// The area. - /// The target column index. - /// The target row index. - /// The width. - /// The height. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CopyFromZyxw(PixelArea area, int targetX, int targetY, int width, int height) - { - for (int y = 0; y < height; y++) - { - Span source = area.GetRowSpan(y); - Span destination = this.GetRowSpan(targetX, targetY + y); - - Operations.PackFromBgra32Bytes(source, destination, width); - } - } - - /// - /// Copies from an area in format. - /// - /// The area. - /// The target column index. - /// The target row index. - /// The width. - /// The height. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CopyFromXyz(PixelArea area, int targetX, int targetY, int width, int height) - { - for (int y = 0; y < height; y++) - { - Span source = area.GetRowSpan(y); - Span destination = this.GetRowSpan(targetX, targetY + y); - - Operations.PackFromRgb24Bytes(source, destination, width); - } - } - - /// - /// Copies from an area in format. - /// - /// The area. - /// The target column index. - /// The target row index. - /// The width. - /// The height. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CopyFromXyzw(PixelArea area, int targetX, int targetY, int width, int height) - { - for (int y = 0; y < height; y++) - { - Span source = area.GetRowSpan(y); - Span destination = this.GetRowSpan(targetX, targetY + y); - Operations.PackFromRgba32Bytes(source, destination, width); - } - } - - /// - /// Copies to an area in format. - /// - /// The row. - /// The source column index. - /// The source row index. - /// The width. - /// The height. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CopyToZyx(PixelArea area, int sourceX, int sourceY, int width, int height) - { - for (int y = 0; y < height; y++) - { - Span source = this.GetRowSpan(sourceX, sourceY + y); - Span destination = area.GetRowSpan(y); - Operations.ToBgr24Bytes(source, destination, width); - } - } - - /// - /// Copies to an area in format. - /// - /// The row. - /// The source column index. - /// The source row index. - /// The width. - /// The height. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CopyToZyxw(PixelArea area, int sourceX, int sourceY, int width, int height) - { - for (int y = 0; y < height; y++) - { - Span source = this.GetRowSpan(sourceX, sourceY + y); - Span destination = area.GetRowSpan(y); - Operations.ToBgra32Bytes(source, destination, width); - } - } - - /// - /// Copies to an area in format. - /// - /// The row. - /// The source column index. - /// The source row index. - /// The width. - /// The height. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CopyToXyz(PixelArea area, int sourceX, int sourceY, int width, int height) - { - for (int y = 0; y < height; y++) - { - Span source = this.GetRowSpan(sourceX, sourceY + y); - Span destination = area.GetRowSpan(y); - Operations.ToRgb24Bytes(source, destination, width); - } - } - - /// - /// Copies to an area in format. - /// - /// The row. - /// The source column index. - /// The source row index. - /// The width. - /// The height. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CopyToXyzw(PixelArea area, int sourceX, int sourceY, int width, int height) - { - for (int y = 0; y < height; y++) - { - Span source = this.GetRowSpan(sourceX, sourceY + y); - Span destination = area.GetRowSpan(y); - Operations.ToRgba32Bytes(source, destination, width); - } - } - /// /// Sets the pixel buffer in an unsafe manor this should not be used unless you know what its doing!!! /// /// The pixel buffer - /// if set to true then this instance ownes the buffer and thus should dispose of it afterwards. - private void SetPixelBufferUnsafe(Buffer2D pixels, bool ownedBuffer) + private void SetPixelBufferUnsafe(Buffer2D pixels) { this.PixelBuffer = pixels; - this.ownedBuffer = ownedBuffer; this.Width = pixels.Width; this.Height = pixels.Height; @@ -413,95 +129,6 @@ namespace SixLabors.ImageSharp this.RowStride = this.Width * this.PixelSize; } - /// - /// Copy an area of pixels to the image. - /// - /// The area. - /// The target column index. - /// The target row index. - /// The width of the area to copy. - /// The height of the area to copy. - /// - /// Thrown when an unsupported component order value is passed. - /// - private void CopyFrom(PixelArea area, int targetX, int targetY, int width, int height) - { - switch (area.ComponentOrder) - { - case ComponentOrder.Zyx: - this.CopyFromZyx(area, targetX, targetY, width, height); - break; - case ComponentOrder.Zyxw: - this.CopyFromZyxw(area, targetX, targetY, width, height); - break; - case ComponentOrder.Xyz: - this.CopyFromXyz(area, targetX, targetY, width, height); - break; - case ComponentOrder.Xyzw: - this.CopyFromXyzw(area, targetX, targetY, width, height); - break; - default: - throw new NotSupportedException(); - } - } - - /// - /// Copy pixels from the image to an area of pixels. - /// - /// The area. - /// The source column index. - /// The source row index. - /// The width of the area to copy. - /// The height of the area to copy. - /// - /// Thrown when an unsupported component order value is passed. - /// - private void CopyTo(PixelArea area, int sourceX, int sourceY, int width, int height) - { - switch (area.ComponentOrder) - { - case ComponentOrder.Zyx: - this.CopyToZyx(area, sourceX, sourceY, width, height); - break; - case ComponentOrder.Zyxw: - this.CopyToZyxw(area, sourceX, sourceY, width, height); - break; - case ComponentOrder.Xyz: - this.CopyToXyz(area, sourceX, sourceY, width, height); - break; - case ComponentOrder.Xyzw: - this.CopyToXyzw(area, sourceX, sourceY, width, height); - break; - default: - throw new NotSupportedException(); - } - } - - /// - /// Checks that the given area and offset are within the bounds of the image. - /// - /// The area. - /// The x-coordinate of the pixel. Must be greater than zero and less than the width of the image. - /// The y-coordinate of the pixel. Must be greater than zero and less than the height of the image. - /// - /// Thrown if the dimensions are not within the bounds of the image. - /// - [Conditional("DEBUG")] - private void CheckCoordinates(PixelArea area, int x, int y) - { - int width = Math.Min(area.Width, this.Width - x); - if (width < 1) - { - throw new ArgumentOutOfRangeException(nameof(width), width, "Invalid area size specified."); - } - - int height = Math.Min(area.Height, this.Height - y); - if (height < 1) - { - throw new ArgumentOutOfRangeException(nameof(height), height, "Invalid area size specified."); - } - } - /// /// Checks the coordinates to ensure they are within bounds. /// diff --git a/src/ImageSharp/Image/PixelArea{TPixel}.cs b/src/ImageSharp/Image/PixelArea{TPixel}.cs deleted file mode 100644 index 1c7256455e..0000000000 --- a/src/ImageSharp/Image/PixelArea{TPixel}.cs +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Diagnostics; -using System.IO; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp -{ - /// - /// Represents an area of generic pixels. - /// - /// The pixel format. - internal sealed class PixelArea : IDisposable - where TPixel : struct, IPixel - { - /// - /// A value indicating whether this instance of the given entity has been disposed. - /// - /// if this instance has been disposed; otherwise, . - /// - /// If the entity is disposed, it must not be disposed a second time. The isDisposed field is set the first time the entity - /// is disposed. If the isDisposed field is true, then the Dispose() method will not dispose again. This help not to prolong the entity's - /// life in the Garbage Collector. - /// - private bool isDisposed; - - /// - /// The underlying buffer containing the raw pixel data. - /// - private readonly Buffer byteBuffer; - - /// - /// Initializes a new instance of the class. - /// - /// The width. - /// The bytes. - /// The component order. - /// - /// Thrown if is the incorrect length. - /// - public PixelArea(int width, byte[] bytes, ComponentOrder componentOrder) - : this(width, 1, bytes, componentOrder) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The width. - /// The height. - /// The bytes. - /// The component order. - /// - /// Thrown if is the incorrect length. - /// - public PixelArea(int width, int height, byte[] bytes, ComponentOrder componentOrder) - { - this.CheckBytesLength(width, height, bytes, componentOrder); - - this.Width = width; - this.Height = height; - this.ComponentOrder = componentOrder; - this.RowStride = width * GetComponentCount(componentOrder); - this.Length = bytes.Length; // TODO: Is this the right value for Length? - - this.byteBuffer = new Buffer(bytes); - } - - /// - /// Initializes a new instance of the class. - /// - /// The width. - /// The component order. - public PixelArea(int width, ComponentOrder componentOrder) - : this(width, 1, componentOrder, 0) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The width. - /// The component order. - /// The number of bytes to pad each row. - public PixelArea(int width, ComponentOrder componentOrder, int padding) - : this(width, 1, componentOrder, padding) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The width. - /// The height. - /// The component order. - public PixelArea(int width, int height, ComponentOrder componentOrder) - : this(width, height, componentOrder, 0) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The width. - /// The height. - /// The component order. - /// The number of bytes to pad each row. - public PixelArea(int width, int height, ComponentOrder componentOrder, int padding) - { - this.Width = width; - this.Height = height; - this.ComponentOrder = componentOrder; - this.RowStride = (width * GetComponentCount(componentOrder)) + padding; - this.Length = this.RowStride * height; - - this.byteBuffer = Buffer.CreateClean(this.Length); - } - - /// - /// Gets the data in bytes. - /// - public byte[] Bytes => this.byteBuffer.Array; - - /// - /// Gets the length of the buffer. - /// - public int Length { get; } - - /// - /// Gets the component order. - /// - public ComponentOrder ComponentOrder { get; } - - /// - /// Gets the height. - /// - public int Height { get; } - - /// - /// Gets the width of one row in the number of bytes. - /// - public int RowStride { get; } - - /// - /// Gets the width. - /// - public int Width { get; } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - if (this.isDisposed) - { - return; - } - - this.byteBuffer.Dispose(); - this.isDisposed = true; - } - - /// - /// Reads the stream to the area. - /// - /// The stream. - public void Read(Stream stream) - { - stream.Read(this.Bytes, 0, this.Length); - } - - /// - /// Writes the area to the stream. - /// - /// The stream. - public void Write(Stream stream) - { - stream.Write(this.Bytes, 0, this.Length); - } - - /// - /// Resets the bytes of the array to it's initial value. - /// - public void Reset() - { - this.byteBuffer.Clear(); - } - - /// - /// Gets a to the row y. - /// - /// The y coordinate - /// The - internal Span GetRowSpan(int y) - { - return this.byteBuffer.Slice(y * this.RowStride); - } - - /// - /// Gets component count for the given order. - /// - /// The component order. - /// - /// The . - /// - /// - /// Thrown if an invalid order is given. - /// - private static int GetComponentCount(ComponentOrder componentOrder) - { - switch (componentOrder) - { - case ComponentOrder.Zyx: - case ComponentOrder.Xyz: - return 3; - case ComponentOrder.Zyxw: - case ComponentOrder.Xyzw: - return 4; - } - - throw new NotSupportedException(); - } - - /// - /// Checks that the length of the byte array to ensure that it matches the given width and height. - /// - /// The width. - /// The height. - /// The byte array. - /// The component order. - /// - /// Thrown if the byte array is th incorrect length. - /// - [Conditional("DEBUG")] - private void CheckBytesLength(int width, int height, byte[] bytes, ComponentOrder componentOrder) - { - int requiredLength = (width * GetComponentCount(componentOrder)) * height; - if (bytes.Length != requiredLength) - { - throw new ArgumentOutOfRangeException( - nameof(bytes), - $"Invalid byte array length. Length {bytes.Length}; Should be {requiredLength}."); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Image/ReadOrigin.cs b/src/ImageSharp/Image/ReadOrigin.cs new file mode 100644 index 0000000000..f17bc82f18 --- /dev/null +++ b/src/ImageSharp/Image/ReadOrigin.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp +{ + /// + /// Specifies the position in a stream to use for reading. + /// + public enum ReadOrigin + { + /// + /// Specifies the beginning of a stream. + /// + Begin, + + /// + /// Specifies the current position within a stream. + /// + Current + } +} diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 9d30030da6..86a0ab7ea5 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -29,6 +29,7 @@ portable True IOperation + 7.2 @@ -40,12 +41,11 @@ All - - + + - @@ -61,6 +61,10 @@ TextTemplatingFileGenerator Block8x8F.Generated.cs + + TextTemplatingFileGenerator + GenericBlock8x8.Generated.cs + TextTemplatingFileGenerator Block8x8F.Generated.cs @@ -91,6 +95,11 @@ True Block8x8F.Generated.tt + + True + True + GenericBlock8x8.Generated.tt + True True @@ -117,4 +126,7 @@ PorterDuffFunctions.Generated.tt + + + \ No newline at end of file diff --git a/src/ImageSharp/Memory/ArrayPoolMemoryManager.Buffer{T}.cs b/src/ImageSharp/Memory/ArrayPoolMemoryManager.Buffer{T}.cs new file mode 100644 index 0000000000..d4f58fb6fb --- /dev/null +++ b/src/ImageSharp/Memory/ArrayPoolMemoryManager.Buffer{T}.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Contains and + /// + public partial class ArrayPoolMemoryManager + { + /// + /// The buffer implementation of + /// + private class Buffer : IBuffer + where T : struct + { + /// + /// The length of the buffer + /// + private readonly int length; + + /// + /// A weak reference to the source pool. + /// + /// + /// By using a weak reference here, we are making sure that array pools and their retained arrays are always GC-ed + /// after a call to , regardless of having buffer instances still being in use. + /// + private WeakReference> sourcePoolReference; + + public Buffer(byte[] data, int length, ArrayPool sourcePool) + { + this.Data = data; + this.length = length; + this.sourcePoolReference = new WeakReference>(sourcePool); + } + + /// + /// Gets the buffer as a byte array. + /// + protected byte[] Data { get; private set; } + + /// + public Span Span => this.Data.AsSpan().NonPortableCast().Slice(0, this.length); + + /// + public void Dispose() + { + if (this.Data == null || this.sourcePoolReference == null) + { + return; + } + + if (this.sourcePoolReference.TryGetTarget(out ArrayPool pool)) + { + pool.Return(this.Data); + } + + this.sourcePoolReference = null; + this.Data = null; + } + } + + /// + /// The implementation of . + /// + private class ManagedByteBuffer : Buffer, IManagedByteBuffer + { + public ManagedByteBuffer(byte[] data, int length, ArrayPool sourcePool) + : base(data, length, sourcePool) + { + } + + public byte[] Array => this.Data; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/ArrayPoolMemoryManager.CommonFactoryMethods.cs b/src/ImageSharp/Memory/ArrayPoolMemoryManager.CommonFactoryMethods.cs new file mode 100644 index 0000000000..d1424870da --- /dev/null +++ b/src/ImageSharp/Memory/ArrayPoolMemoryManager.CommonFactoryMethods.cs @@ -0,0 +1,69 @@ +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Contains common factory methods and configuration constants. + /// + public partial class ArrayPoolMemoryManager + { + /// + /// The default value for: maximum size of pooled arrays in bytes. + /// Currently set to 24MB, which is equivalent to 8 megapixels of raw data. + /// + internal const int DefaultMaxPooledBufferSizeInBytes = 24 * 1024 * 1024; + + /// + /// The value for: The threshold to pool arrays in which has less buckets for memory safety. + /// + private const int DefaultBufferSelectorThresholdInBytes = 8 * 1024 * 1024; + + /// + /// The default bucket count for . + /// + private const int DefaultLargePoolBucketCount = 6; + + /// + /// The default bucket count for . + /// + private const int DefaultNormalPoolBucketCount = 16; + + /// + /// This is the default. Should be good for most use cases. + /// + /// The memory manager + public static ArrayPoolMemoryManager CreateDefault() + { + return new ArrayPoolMemoryManager( + DefaultMaxPooledBufferSizeInBytes, + DefaultBufferSelectorThresholdInBytes, + DefaultLargePoolBucketCount, + DefaultNormalPoolBucketCount); + } + + /// + /// For environments with limited memory capabilities. Only small images are pooled, which can result in reduced througput. + /// + /// The memory manager + public static ArrayPoolMemoryManager CreateWithModeratePooling() + { + return new ArrayPoolMemoryManager(1024 * 1024, 32 * 1024, 16, 24); + } + + /// + /// Only pool small buffers like image rows. + /// + /// The memory manager + public static ArrayPoolMemoryManager CreateWithMinimalPooling() + { + return new ArrayPoolMemoryManager(64 * 1024, 32 * 1024, 8, 24); + } + + /// + /// RAM is not an issue for me, gimme maximum througput! + /// + /// The memory manager + public static ArrayPoolMemoryManager CreateWithAggressivePooling() + { + return new ArrayPoolMemoryManager(128 * 1024 * 1024, 32 * 1024 * 1024, 16, 32); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/ArrayPoolMemoryManager.cs b/src/ImageSharp/Memory/ArrayPoolMemoryManager.cs new file mode 100644 index 0000000000..7b8c7ab326 --- /dev/null +++ b/src/ImageSharp/Memory/ArrayPoolMemoryManager.cs @@ -0,0 +1,140 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Buffers; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Implements by allocating memory from . + /// + public partial class ArrayPoolMemoryManager : MemoryManager + { + /// + /// The for small-to-medium buffers which is not kept clean. + /// + private ArrayPool normalArrayPool; + + /// + /// The for huge buffers, which is not kept clean. + /// + private ArrayPool largeArrayPool; + + private readonly int maxArraysPerBucketNormalPool; + + private readonly int maxArraysPerBucketLargePool; + + /// + /// Initializes a new instance of the class. + /// + public ArrayPoolMemoryManager() + : this(DefaultMaxPooledBufferSizeInBytes, DefaultBufferSelectorThresholdInBytes) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated. + public ArrayPoolMemoryManager(int maxPoolSizeInBytes) + : this(maxPoolSizeInBytes, GetLargeBufferThresholdInBytes(maxPoolSizeInBytes)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated. + /// Arrays over this threshold will be pooled in which has less buckets for memory safety. + public ArrayPoolMemoryManager(int maxPoolSizeInBytes, int poolSelectorThresholdInBytes) + : this(maxPoolSizeInBytes, poolSelectorThresholdInBytes, DefaultLargePoolBucketCount, DefaultNormalPoolBucketCount) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated. + /// The threshold to pool arrays in which has less buckets for memory safety. + /// Max arrays per bucket for the large array pool + /// Max arrays per bucket for the normal array pool + public ArrayPoolMemoryManager(int maxPoolSizeInBytes, int poolSelectorThresholdInBytes, int maxArraysPerBucketLargePool, int maxArraysPerBucketNormalPool) + { + Guard.MustBeGreaterThan(maxPoolSizeInBytes, 0, nameof(maxPoolSizeInBytes)); + Guard.MustBeLessThanOrEqualTo(poolSelectorThresholdInBytes, maxPoolSizeInBytes, nameof(poolSelectorThresholdInBytes)); + + this.MaxPoolSizeInBytes = maxPoolSizeInBytes; + this.PoolSelectorThresholdInBytes = poolSelectorThresholdInBytes; + this.maxArraysPerBucketLargePool = maxArraysPerBucketLargePool; + this.maxArraysPerBucketNormalPool = maxArraysPerBucketNormalPool; + + this.InitArrayPools(); + } + + /// + /// Gets the maximum size of pooled arrays in bytes. + /// + public int MaxPoolSizeInBytes { get; } + + /// + /// Gets the threshold to pool arrays in which has less buckets for memory safety. + /// + public int PoolSelectorThresholdInBytes { get; } + + /// + public override void ReleaseRetainedResources() + { + this.InitArrayPools(); + } + + /// + internal override IBuffer Allocate(int length, bool clear) + { + int itemSizeBytes = Unsafe.SizeOf(); + int bufferSizeInBytes = length * itemSizeBytes; + + ArrayPool pool = this.GetArrayPool(bufferSizeInBytes); + byte[] byteArray = pool.Rent(bufferSizeInBytes); + + var buffer = new Buffer(byteArray, length, pool); + if (clear) + { + buffer.Clear(); + } + + return buffer; + } + + /// + internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear) + { + ArrayPool pool = this.GetArrayPool(length); + byte[] byteArray = pool.Rent(length); + + var buffer = new ManagedByteBuffer(byteArray, length, pool); + if (clear) + { + buffer.Clear(); + } + + return buffer; + } + + private static int GetLargeBufferThresholdInBytes(int maxPoolSizeInBytes) + { + return maxPoolSizeInBytes / 4; + } + + private ArrayPool GetArrayPool(int bufferSizeInBytes) + { + return bufferSizeInBytes <= this.PoolSelectorThresholdInBytes ? this.normalArrayPool : this.largeArrayPool; + } + + private void InitArrayPools() + { + this.largeArrayPool = ArrayPool.Create(this.MaxPoolSizeInBytes, this.maxArraysPerBucketLargePool); + this.normalArrayPool = ArrayPool.Create(this.PoolSelectorThresholdInBytes, this.maxArraysPerBucketNormalPool); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/BasicArrayBuffer.cs b/src/ImageSharp/Memory/BasicArrayBuffer.cs new file mode 100644 index 0000000000..30ca210ac4 --- /dev/null +++ b/src/ImageSharp/Memory/BasicArrayBuffer.cs @@ -0,0 +1,51 @@ +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Exposes an array through the interface. + /// + internal class BasicArrayBuffer : IBuffer + where T : struct + { + public BasicArrayBuffer(T[] array, int length) + { + DebugGuard.MustBeLessThanOrEqualTo(length, array.Length, nameof(length)); + this.Array = array; + this.Length = length; + } + + public BasicArrayBuffer(T[] array) + : this(array, array.Length) + { + } + + public T[] Array { get; } + + public int Length { get; } + + public Span Span => this.Array.AsSpan().Slice(0, this.Length); + + /// + /// Returns a reference to specified element of the buffer. + /// + /// The index + /// The reference to the specified element + public ref T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + DebugGuard.MustBeLessThan(index, this.Length, nameof(index)); + + Span span = this.Span; + return ref span[index]; + } + } + + public void Dispose() + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/BasicByteBuffer.cs b/src/ImageSharp/Memory/BasicByteBuffer.cs new file mode 100644 index 0000000000..96b69ad3bf --- /dev/null +++ b/src/ImageSharp/Memory/BasicByteBuffer.cs @@ -0,0 +1,10 @@ +namespace SixLabors.ImageSharp.Memory +{ + internal class BasicByteBuffer : BasicArrayBuffer, IManagedByteBuffer + { + internal BasicByteBuffer(byte[] array) + : base(array) + { + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 99b10cae7e..dc992368c9 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; using SixLabors.Primitives; @@ -11,44 +12,37 @@ namespace SixLabors.ImageSharp.Memory /// interpreted as a 2D region of x elements. /// /// The value type. - internal class Buffer2D : Buffer, IBuffer2D + internal class Buffer2D : IBuffer2D, IDisposable where T : struct { - public Buffer2D(Size size) - : this(size.Width, size.Height) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The number of elements in a row - /// The number of rows - public Buffer2D(int width, int height) - : base(width * height) - { - this.Width = width; - this.Height = height; - } - /// /// Initializes a new instance of the class. /// - /// The array to pin + /// The buffer to wrap /// The number of elements in a row /// The number of rows - public Buffer2D(T[] array, int width, int height) - : base(array, width * height) + public Buffer2D(IBuffer wrappedBuffer, int width, int height) { + this.Buffer = wrappedBuffer; this.Width = width; this.Height = height; } /// - public int Width { get; } + public int Width { get; private set; } /// - public int Height { get; } + public int Height { get; private set; } + + /// + /// Gets the span to the whole area. + /// + public Span Span => this.Buffer.Span; + + /// + /// Gets the backing + /// + public IBuffer Buffer { get; private set; } /// /// Gets a reference to the element at the specified position. @@ -63,29 +57,39 @@ namespace SixLabors.ImageSharp.Memory { DebugGuard.MustBeLessThan(x, this.Width, nameof(x)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); - - return ref this.Array[(this.Width * y) + x]; + Span span = this.Buffer.Span; + return ref span[(this.Width * y) + x]; } } /// - /// Creates a clean instance of initializing it's elements with 'default(T)'. + /// Disposes the instance /// - /// The number of elements in a row - /// The number of rows - /// The instance - public static Buffer2D CreateClean(int width, int height) + public void Dispose() { - var buffer = new Buffer2D(width, height); - buffer.Clear(); - return buffer; + this.Buffer?.Dispose(); } /// - /// Creates a clean instance of initializing it's elements with 'default(T)'. + /// Swap the contents (, , ) of the two buffers. + /// Useful to transfer the contents of a temporary to a persistent /// - /// The size of the buffer - /// The instance - public static Buffer2D CreateClean(Size size) => CreateClean(size.Width, size.Height); + /// The first buffer + /// The second buffer + public static void SwapContents(Buffer2D a, Buffer2D b) + { + Size aSize = a.Size(); + Size bSize = b.Size(); + + IBuffer temp = a.Buffer; + a.Buffer = b.Buffer; + b.Buffer = temp; + + b.Width = aSize.Width; + b.Height = aSize.Height; + + a.Width = bSize.Width; + a.Height = bSize.Height; + } } } \ No newline at end of file diff --git a/src/ImageSharp/Memory/BufferArea{T}.cs b/src/ImageSharp/Memory/BufferArea{T}.cs index b5ed3566fa..990b494fc7 100644 --- a/src/ImageSharp/Memory/BufferArea{T}.cs +++ b/src/ImageSharp/Memory/BufferArea{T}.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Memory /// This type is kind-of 2D Span, but it can live on heap. /// /// The element type - internal struct BufferArea + internal readonly struct BufferArea where T : struct { /// @@ -45,11 +45,26 @@ namespace SixLabors.ImageSharp.Memory /// public Size Size => this.Rectangle.Size; + /// + /// Gets the width + /// + public int Width => this.Rectangle.Width; + + /// + /// Gets the height + /// + public int Height => this.Rectangle.Height; + /// /// Gets the pixel stride which is equal to the width of . /// public int Stride => this.DestinationBuffer.Width; + /// + /// Gets a value indicating whether the area refers to the entire + /// + public bool IsFullBufferArea => this.Size == this.DestinationBuffer.Size(); + /// /// Gets or sets a value at the given index. /// @@ -63,7 +78,7 @@ namespace SixLabors.ImageSharp.Memory /// /// The reference to the [0,0] element [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T GetReferenceToOrigo() => + public ref T GetReferenceToOrigin() => ref this.DestinationBuffer.Span[(this.Rectangle.Y * this.DestinationBuffer.Width) + this.Rectangle.X]; /// @@ -122,9 +137,25 @@ namespace SixLabors.ImageSharp.Memory } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int GetRowIndex(int y) + internal int GetRowIndex(int y) { return (y + this.Rectangle.Y) * this.DestinationBuffer.Width; } + + public void Clear() + { + // Optimization for when the size of the area is the same as the buffer size. + if (this.IsFullBufferArea) + { + this.DestinationBuffer.Span.Clear(); + return; + } + + for (int y = 0; y < this.Rectangle.Height; y++) + { + Span row = this.GetRowSpan(y); + row.Clear(); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/Memory/BufferExtensions.cs b/src/ImageSharp/Memory/BufferExtensions.cs new file mode 100644 index 0000000000..dd3114c21c --- /dev/null +++ b/src/ImageSharp/Memory/BufferExtensions.cs @@ -0,0 +1,69 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Memory +{ + internal static class BufferExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Length(this IBuffer buffer) + where T : struct => buffer.Span.Length; + + /// + /// Gets a to an offseted position inside the buffer. + /// + /// The buffer + /// The start + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span Slice(this IBuffer buffer, int start) + where T : struct + { + return buffer.Span.Slice(start); + } + + /// + /// Gets a to an offsetted position inside the buffer. + /// + /// The buffer + /// The start + /// The length of the slice + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span Slice(this IBuffer buffer, int start, int length) + where T : struct + { + return buffer.Span.Slice(start, length); + } + + /// + /// Clears the contents of this buffer. + /// + /// The buffer + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clear(this IBuffer buffer) + where T : struct + { + buffer.Span.Clear(); + } + + public static ref T DangerousGetPinnableReference(this IBuffer buffer) + where T : struct => + ref MemoryMarshal.GetReference(buffer.Span); + + public static void Read(this Stream stream, IManagedByteBuffer buffer) + { + stream.Read(buffer.Array, 0, buffer.Length()); + } + + public static void Write(this Stream stream, IManagedByteBuffer buffer) + { + stream.Write(buffer.Array, 0, buffer.Length()); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/Buffer{T}.cs b/src/ImageSharp/Memory/Buffer{T}.cs deleted file mode 100644 index 67af23426a..0000000000 --- a/src/ImageSharp/Memory/Buffer{T}.cs +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Memory -{ - /// - /// - /// Manages a buffer of value type objects as a Disposable resource. - /// The backing array is either pooled or comes from the outside. - /// - /// The value type. - internal class Buffer : IBuffer - where T : struct - { - /// - /// A pointer to the first element of when pinned. - /// - private IntPtr pointer; - - /// - /// A handle that allows to access the managed as an unmanaged memory by pinning. - /// - private GCHandle handle; - - /// - /// A value indicating wheter should be returned to - /// when disposing this instance. - /// - private bool isPoolingOwner; - - /// - /// Initializes a new instance of the class. - /// - /// The desired count of elements. (Minimum size for ) - public Buffer(int length) - { - this.Length = length; - this.Array = PixelDataPool.Rent(length); - this.isPoolingOwner = true; - } - - /// - /// Initializes a new instance of the class. - /// - /// The array to pin. - public Buffer(T[] array) - { - this.Length = array.Length; - this.Array = array; - this.isPoolingOwner = false; - } - - /// - /// Initializes a new instance of the class. - /// - /// The array to pin. - /// The count of "relevant" elements in 'array'. - public Buffer(T[] array, int length) - { - if (array.Length < length) - { - throw new ArgumentException("Can't initialize a PinnedBuffer with array.Length < count", nameof(array)); - } - - this.Length = length; - this.Array = array; - this.isPoolingOwner = false; - } - - /// - /// Finalizes an instance of the class. - /// - ~Buffer() - { - this.UnPin(); - } - - /// - /// Gets a value indicating whether this instance is disposed, or has lost ownership of . - /// - public bool IsDisposedOrLostArrayOwnership { get; private set; } - - /// - /// Gets the count of "relevant" elements. It's usually smaller than 'Array.Length' when is pooled. - /// - public int Length { get; private set; } - - /// - /// Gets the backing pinned array. - /// - public T[] Array { get; private set; } - - /// - /// Gets a to the backing buffer. - /// - public Span Span => this; - - /// - /// Returns a reference to specified element of the buffer. - /// - /// The index - /// The reference to the specified element - public ref T this[int index] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - DebugGuard.MustBeLessThan(index, this.Length, nameof(index)); - return ref this.Array[index]; - } - } - - /// - /// Converts to an . - /// - /// The to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(Buffer buffer) - { - return new ReadOnlySpan(buffer.Array, 0, buffer.Length); - } - - /// - /// Converts to an . - /// - /// The to convert. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator Span(Buffer buffer) - { - return new Span(buffer.Array, 0, buffer.Length); - } - - /// - /// Creates a clean instance of initializing it's elements with 'default(T)'. - /// - /// The desired count of elements. (Minimum size for ) - /// The instance - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Buffer CreateClean(int count) - { - var buffer = new Buffer(count); - buffer.Clear(); - return buffer; - } - - /// - /// Gets a to an offseted position inside the buffer. - /// - /// The start - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span Slice(int start) - { - return new Span(this.Array, start, this.Length - start); - } - - /// - /// Gets a to an offsetted position inside the buffer. - /// - /// The start - /// The length of the slice - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span Slice(int start, int length) - { - return new Span(this.Array, start, length); - } - - /// - /// Disposes the instance by unpinning the array, and returning the pooled buffer when necessary. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dispose() - { - if (this.IsDisposedOrLostArrayOwnership) - { - return; - } - - this.IsDisposedOrLostArrayOwnership = true; - this.UnPin(); - - if (this.isPoolingOwner) - { - PixelDataPool.Return(this.Array); - } - - this.isPoolingOwner = false; - this.Array = null; - this.Length = 0; - - GC.SuppressFinalize(this); - } - - /// - /// Unpins and makes the object "quasi-disposed" so the array is no longer owned by this object. - /// If is rented, it's the callers responsibility to return it to it's pool. (Most likely ) - /// - /// The unpinned - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public T[] TakeArrayOwnership() - { - if (this.IsDisposedOrLostArrayOwnership) - { - throw new InvalidOperationException( - "TakeArrayOwnership() is invalid: either Buffer is disposed or TakeArrayOwnership() has been called multiple times!"); - } - - this.IsDisposedOrLostArrayOwnership = true; - this.UnPin(); - T[] array = this.Array; - this.Array = null; - this.isPoolingOwner = false; - return array; - } - - /// - /// Clears the contents of this buffer. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear() - { - this.Span.Clear(); - } - - /// - /// Pins . - /// - /// The pinned pointer - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public IntPtr Pin() - { - if (this.IsDisposedOrLostArrayOwnership) - { - throw new InvalidOperationException( - "Pin() is invalid on a buffer with IsDisposedOrLostArrayOwnership == true!"); - } - - if (this.pointer == IntPtr.Zero) - { - this.handle = GCHandle.Alloc(this.Array, GCHandleType.Pinned); - this.pointer = this.handle.AddrOfPinnedObject(); - } - - return this.pointer; - } - - /// - /// Unpins . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void UnPin() - { - if (this.pointer == IntPtr.Zero || !this.handle.IsAllocated) - { - return; - } - - this.handle.Free(); - this.pointer = IntPtr.Zero; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Memory/Fast2DArray{T}.cs b/src/ImageSharp/Memory/Fast2DArray{T}.cs index 14ac58baf2..38ccdd279d 100644 --- a/src/ImageSharp/Memory/Fast2DArray{T}.cs +++ b/src/ImageSharp/Memory/Fast2DArray{T}.cs @@ -28,6 +28,20 @@ namespace SixLabors.ImageSharp.Memory /// public int Height; + /// + /// Gets the number of items in the 2D array + /// + public int Count; + + /// + /// Initializes a new instance of the struct. + /// + /// The length of each dimension. + public Fast2DArray(int length) + : this(length, length) + { + } + /// /// Initializes a new instance of the struct. /// @@ -41,7 +55,8 @@ namespace SixLabors.ImageSharp.Memory Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - this.Data = new T[this.Width * this.Height]; + this.Count = width * height; + this.Data = new T[this.Count]; } /// @@ -57,7 +72,8 @@ namespace SixLabors.ImageSharp.Memory Guard.MustBeGreaterThan(this.Width, 0, nameof(this.Width)); Guard.MustBeGreaterThan(this.Height, 0, nameof(this.Height)); - this.Data = new T[this.Width * this.Height]; + this.Count = this.Width * this.Height; + this.Data = new T[this.Count]; for (int y = 0; y < this.Height; y++) { @@ -96,7 +112,7 @@ namespace SixLabors.ImageSharp.Memory /// /// The source array. /// - /// The represenation on the source data. + /// The representation on the source data. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator Fast2DArray(T[,] data) diff --git a/src/ImageSharp/Memory/IBuffer{T}.cs b/src/ImageSharp/Memory/IBuffer{T}.cs index a0f80063f8..db6bf5b389 100644 --- a/src/ImageSharp/Memory/IBuffer{T}.cs +++ b/src/ImageSharp/Memory/IBuffer{T}.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; namespace SixLabors.ImageSharp.Memory diff --git a/src/ImageSharp/Memory/IManagedByteBuffer.cs b/src/ImageSharp/Memory/IManagedByteBuffer.cs new file mode 100644 index 0000000000..4d159ce863 --- /dev/null +++ b/src/ImageSharp/Memory/IManagedByteBuffer.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Represents a byte buffer backed by a managed array. Useful for interop with classic .NET API-s. + /// + internal interface IManagedByteBuffer : IBuffer + { + /// + /// Gets the managed array backing this buffer instance. + /// + byte[] Array { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/MemoryManager.cs b/src/ImageSharp/Memory/MemoryManager.cs new file mode 100644 index 0000000000..52bdc897fc --- /dev/null +++ b/src/ImageSharp/Memory/MemoryManager.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Memory managers are used to allocate memory for image processing operations. + /// + public abstract class MemoryManager + { + /// + /// Allocates an of size , optionally + /// clearing the buffer before it gets returned. + /// + /// Type of the data stored in the buffer + /// Size of the buffer to allocate + /// True to clear the backing memory of the buffer + /// A buffer of values of type . + internal abstract IBuffer Allocate(int length, bool clear) + where T : struct; + + /// + /// Allocates an + /// + /// The requested buffer length + /// A value indicating whether to clean the buffer + /// The + internal abstract IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear); + + /// + /// Temporal workaround. A method providing a "Buffer" based on a generic array without the 'Unsafe.As()' hackery. + /// Should be replaced with 'Allocate()' as soon as SixLabors.Shapes has Span-based API-s! + /// + internal BasicArrayBuffer AllocateFake(int length, bool dummy = false) + where T : struct + { + return new BasicArrayBuffer(new T[length]); + } + + /// + /// Releases all retained resources not being in use. + /// Eg: by resetting array pools and letting GC to free the arrays. + /// + public virtual void ReleaseRetainedResources() + { + } + } +} diff --git a/src/ImageSharp/Memory/MemoryManagerExtensions.cs b/src/ImageSharp/Memory/MemoryManagerExtensions.cs new file mode 100644 index 0000000000..2060002450 --- /dev/null +++ b/src/ImageSharp/Memory/MemoryManagerExtensions.cs @@ -0,0 +1,79 @@ +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Extension methods for . + /// + internal static class MemoryManagerExtensions + { + /// + /// Allocates a of size . + /// Note: Depending on the implementation, the buffer may not cleared before + /// returning, so it may contain data from an earlier use. + /// + /// Type of the data stored in the buffer + /// The + /// Size of the buffer to allocate + /// A buffer of values of type . + public static IBuffer Allocate(this MemoryManager memoryManager, int length) + where T : struct + { + return memoryManager.Allocate(length, false); + } + + public static IBuffer AllocateClean(this MemoryManager memoryManager, int length) + where T : struct + { + return memoryManager.Allocate(length, true); + } + + public static IManagedByteBuffer AllocateManagedByteBuffer(this MemoryManager memoryManager, int length) + { + return memoryManager.AllocateManagedByteBuffer(length, false); + } + + public static IManagedByteBuffer AllocateCleanManagedByteBuffer(this MemoryManager memoryManager, int length) + { + return memoryManager.AllocateManagedByteBuffer(length, true); + } + + public static Buffer2D Allocate2D(this MemoryManager memoryManager, int width, int height, bool clear) + where T : struct + { + IBuffer buffer = memoryManager.Allocate(width * height, clear); + + return new Buffer2D(buffer, width, height); + } + + public static Buffer2D Allocate2D(this MemoryManager memoryManager, Size size) + where T : struct => + Allocate2D(memoryManager, size.Width, size.Height, false); + + public static Buffer2D Allocate2D(this MemoryManager memoryManager, int width, int height) + where T : struct => + Allocate2D(memoryManager, width, height, false); + + public static Buffer2D AllocateClean2D(this MemoryManager memoryManager, int width, int height) + where T : struct => + Allocate2D(memoryManager, width, height, true); + + /// + /// Allocates padded buffers for BMP encoder/decoder. (Replacing old PixelRow/PixelArea) + /// + /// The + /// Pixel count in the row + /// The pixel size in bytes, eg. 3 for RGB + /// The padding + /// A + public static IManagedByteBuffer AllocatePaddedPixelRowBuffer( + this MemoryManager memoryManager, + int width, + int pixelSizeInBytes, + int paddingInBytes) + { + int length = (width * pixelSizeInBytes) + paddingInBytes; + return memoryManager.AllocateManagedByteBuffer(length); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Memory/PixelDataPool{T}.cs b/src/ImageSharp/Memory/PixelDataPool{T}.cs deleted file mode 100644 index 80c9c410e8..0000000000 --- a/src/ImageSharp/Memory/PixelDataPool{T}.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Buffers; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Memory -{ - /// - /// Provides a resource pool that enables reusing instances of value type arrays for image data . - /// - /// The value type. - internal class PixelDataPool - where T : struct - { - /// - /// The maximum size of pooled arrays in bytes. - /// Currently set to 32MB, which is equivalent to 8 megapixels of raw data. - /// - internal const int MaxPooledBufferSizeInBytes = 32 * 1024 * 1024; - - /// - /// The threshold to pool arrays in which has less buckets for memory safety. - /// - private const int LargeBufferThresholdInBytes = 8 * 1024 * 1024; - - /// - /// The maximum array length of the . - /// - private static readonly int MaxLargeArrayLength = MaxPooledBufferSizeInBytes / Unsafe.SizeOf(); - - /// - /// The maximum array length of the . - /// - private static readonly int MaxNormalArrayLength = LargeBufferThresholdInBytes / Unsafe.SizeOf(); - - /// - /// The for huge buffers, which is not kept clean. - /// - private static readonly ArrayPool LargeArrayPool = ArrayPool.Create(MaxLargeArrayLength, 8); - - /// - /// The for small-to-medium buffers which is not kept clean. - /// - private static readonly ArrayPool NormalArrayPool = ArrayPool.Create(MaxNormalArrayLength, 24); - - /// - /// Rents the pixel array from the pool. - /// - /// The minimum length of the array to return. - /// The - public static T[] Rent(int minimumLength) - { - if (minimumLength <= MaxNormalArrayLength) - { - return NormalArrayPool.Rent(minimumLength); - } - else - { - return LargeArrayPool.Rent(minimumLength); - } - } - - /// - /// Returns the rented pixel array back to the pool. - /// - /// The array to return to the buffer pool. - public static void Return(T[] array) - { - if (array.Length <= MaxNormalArrayLength) - { - NormalArrayPool.Return(array); - } - else - { - LargeArrayPool.Return(array); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Memory/SimpleGcMemoryManager.cs b/src/ImageSharp/Memory/SimpleGcMemoryManager.cs new file mode 100644 index 0000000000..f4518bbb9d --- /dev/null +++ b/src/ImageSharp/Memory/SimpleGcMemoryManager.cs @@ -0,0 +1,19 @@ +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Implements by newing up arrays by the GC on every allocation requests. + /// + public class SimpleGcMemoryManager : MemoryManager + { + /// + internal override IBuffer Allocate(int length, bool clear) + { + return new BasicArrayBuffer(new T[length]); + } + + internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear) + { + return new BasicByteBuffer(new byte[length]); + } + } +} diff --git a/src/ImageSharp/Memory/SpanHelper.cs b/src/ImageSharp/Memory/SpanHelper.cs index 73bc5f55d8..0c327484a0 100644 --- a/src/ImageSharp/Memory/SpanHelper.cs +++ b/src/ImageSharp/Memory/SpanHelper.cs @@ -4,6 +4,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Memory { @@ -22,7 +23,7 @@ namespace SixLabors.ImageSharp.Memory public static ref Vector FetchVector(this Span span) where T : struct { - return ref Unsafe.As>(ref span.DangerousGetPinnableReference()); + return ref Unsafe.As>(ref MemoryMarshal.GetReference(span)); } /// @@ -39,8 +40,8 @@ namespace SixLabors.ImageSharp.Memory DebugGuard.MustBeLessThanOrEqualTo(count, source.Length, nameof(count)); DebugGuard.MustBeLessThanOrEqualTo(count, destination.Length, nameof(count)); - ref byte srcRef = ref Unsafe.As(ref source.DangerousGetPinnableReference()); - ref byte destRef = ref Unsafe.As(ref destination.DangerousGetPinnableReference()); + ref byte srcRef = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref byte destRef = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); int byteCount = Unsafe.SizeOf() * count; diff --git a/src/ImageSharp/MetaData/ImageProperty.cs b/src/ImageSharp/MetaData/ImageProperty.cs index 62ae9d4790..c60aaecfba 100644 --- a/src/ImageSharp/MetaData/ImageProperty.cs +++ b/src/ImageSharp/MetaData/ImageProperty.cs @@ -71,7 +71,12 @@ namespace SixLabors.ImageSharp.MetaData /// public static bool operator ==(ImageProperty left, ImageProperty right) { - return Equals(left, right); + if (ReferenceEquals(left, right)) + { + return true; + } + + return left.Equals(right); } /// @@ -90,7 +95,7 @@ namespace SixLabors.ImageSharp.MetaData /// public static bool operator !=(ImageProperty left, ImageProperty right) { - return !Equals(left, right); + return !(left == right); } /// diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs index 64508137b4..7ffe9d48fe 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifValue.cs @@ -188,7 +188,12 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// public static bool operator ==(ExifValue left, ExifValue right) { - return ExifValue.Equals(left, right); + if (ReferenceEquals(left, right)) + { + return true; + } + + return left.Equals(right); } /// @@ -205,7 +210,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// public static bool operator !=(ExifValue left, ExifValue right) { - return !ExifValue.Equals(left, right); + return !(left == right); } /// diff --git a/src/ImageSharp/MetaData/Profiles/Exif/Rational.cs b/src/ImageSharp/MetaData/Profiles/Exif/Rational.cs index 6d62a623f9..0f47870d24 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/Rational.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/Rational.cs @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// The public static bool operator ==(Rational left, Rational right) { - return Rational.Equals(left, right); + return left.Equals(right); } /// @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// The public static bool operator !=(Rational left, Rational right) { - return !Rational.Equals(left, right); + return !left.Equals(right); } /// diff --git a/src/ImageSharp/MetaData/Profiles/Exif/SignedRational.cs b/src/ImageSharp/MetaData/Profiles/Exif/SignedRational.cs index f2fe359242..17f1b568b3 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/SignedRational.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/SignedRational.cs @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// The public static bool operator ==(SignedRational left, SignedRational right) { - return SignedRational.Equals(left, right); + return left.Equals(right); } /// @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif /// The public static bool operator !=(SignedRational left, SignedRational right) { - return !SignedRational.Equals(left, right); + return !left.Equals(right); } /// diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs index 17127110d6..c5b005ea09 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccColorantTableEntry.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// /// Entry of ICC colorant table /// - internal struct IccColorantTableEntry : IEquatable + internal readonly struct IccColorantTableEntry : IEquatable { /// /// Initializes a new instance of the struct. diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs index 98107e8281..22916c1344 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccNamedColor.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// /// A specific color with a name /// - internal struct IccNamedColor : IEquatable + internal readonly struct IccNamedColor : IEquatable { /// /// Initializes a new instance of the struct. diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs index 6258ca2f36..d886dc099c 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccPositionNumber.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// /// Position of an object within an ICC profile /// - internal struct IccPositionNumber : IEquatable + internal readonly struct IccPositionNumber : IEquatable { /// /// Initializes a new instance of the struct. diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs index 1f96540df3..4070f835d6 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccProfileId.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// /// ICC Profile ID /// - public struct IccProfileId : IEquatable + public readonly struct IccProfileId : IEquatable { /// /// A profile ID with all values set to zero diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs index a10c55f4e4..c786a0fd45 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccResponseNumber.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// /// Associates a normalized device code with a measurement value /// - internal struct IccResponseNumber : IEquatable + internal readonly struct IccResponseNumber : IEquatable { /// /// Initializes a new instance of the struct. diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccScreeningChannel.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccScreeningChannel.cs index f41858f303..e1f1bb32fe 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccScreeningChannel.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccScreeningChannel.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// /// A single channel of a /// - internal struct IccScreeningChannel : IEquatable + internal readonly struct IccScreeningChannel : IEquatable { /// /// Initializes a new instance of the struct. diff --git a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs index 5464de9c5f..7cb5c7901e 100644 --- a/src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs +++ b/src/ImageSharp/MetaData/Profiles/ICC/Various/IccTagTableEntry.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc /// /// Entry of ICC tag table /// - internal struct IccTagTableEntry : IEquatable + internal readonly struct IccTagTableEntry : IEquatable { /// /// Initializes a new instance of the struct. diff --git a/src/ImageSharp/Numerics/ValueSize.cs b/src/ImageSharp/Numerics/ValueSize.cs index 659e0ebfe1..fcf61a586d 100644 --- a/src/ImageSharp/Numerics/ValueSize.cs +++ b/src/ImageSharp/Numerics/ValueSize.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp /// /// Represents a value in relation to a value on the image /// - internal struct ValueSize : IEquatable + internal readonly struct ValueSize : IEquatable { /// /// Initializes a new instance of the struct. diff --git a/src/ImageSharp/PixelFormats/ColorConstants.cs b/src/ImageSharp/PixelFormats/ColorConstants.cs index f97d3b190e..bac05c53d2 100644 --- a/src/ImageSharp/PixelFormats/ColorConstants.cs +++ b/src/ImageSharp/PixelFormats/ColorConstants.cs @@ -1,9 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Collections.Generic; - namespace SixLabors.ImageSharp.PixelFormats { /// @@ -11,23 +8,17 @@ namespace SixLabors.ImageSharp.PixelFormats /// public static class ColorConstants { - /// - /// Provides a lazy, one time method of returning the colors. - /// - private static readonly Lazy SafeColors = new Lazy(GetWebSafeColors); - /// /// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4. /// - public static Rgba32[] WebSafeColors => SafeColors.Value; + public static readonly Rgba32[] WebSafeColors = GetWebSafeColors(); /// /// Returns an array of web safe colors. /// /// The private static Rgba32[] GetWebSafeColors() - { - return new List + => new Rgba32[] { Rgba32.AliceBlue, Rgba32.AntiqueWhite, @@ -171,7 +162,6 @@ namespace SixLabors.ImageSharp.PixelFormats Rgba32.WhiteSmoke, Rgba32.Yellow, Rgba32.YellowGreen - }.ToArray(); - } + }; } -} +} \ 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 9553ec82d6..9505ee6cf7 100644 --- a/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.cs +++ b/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.cs @@ -6,7 +6,8 @@ namespace SixLabors.ImageSharp.PixelFormats { using System; using System.Runtime.CompilerServices; - + using System.Runtime.InteropServices; + public partial class PixelOperations { @@ -20,8 +21,8 @@ namespace SixLabors.ImageSharp.PixelFormats { GuardSpans(source, nameof(source), destPixels, nameof(destPixels), count); - ref Rgba32 sourceRef = ref source.DangerousGetPinnableReference(); - ref TPixel destRef = ref destPixels.DangerousGetPinnableReference(); + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(source); + ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); Rgba32 rgba = new Rgba32(0, 0, 0, 255); @@ -57,8 +58,8 @@ namespace SixLabors.ImageSharp.PixelFormats { GuardSpans(sourcePixels, nameof(sourcePixels), dest, nameof(dest), count); - ref TPixel sourceBaseRef = ref sourcePixels.DangerousGetPinnableReference(); - ref Rgba32 destBaseRef = ref dest.DangerousGetPinnableReference(); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba32 destBaseRef = ref MemoryMarshal.GetReference(dest); for (int i = 0; i < count; i++) { @@ -91,8 +92,8 @@ namespace SixLabors.ImageSharp.PixelFormats { GuardSpans(source, nameof(source), destPixels, nameof(destPixels), count); - ref Bgra32 sourceRef = ref source.DangerousGetPinnableReference(); - ref TPixel destRef = ref destPixels.DangerousGetPinnableReference(); + ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(source); + ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); Rgba32 rgba = new Rgba32(0, 0, 0, 255); @@ -128,8 +129,8 @@ namespace SixLabors.ImageSharp.PixelFormats { GuardSpans(sourcePixels, nameof(sourcePixels), dest, nameof(dest), count); - ref TPixel sourceBaseRef = ref sourcePixels.DangerousGetPinnableReference(); - ref Bgra32 destBaseRef = ref dest.DangerousGetPinnableReference(); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destBaseRef = ref MemoryMarshal.GetReference(dest); for (int i = 0; i < count; i++) { @@ -162,8 +163,8 @@ namespace SixLabors.ImageSharp.PixelFormats { GuardSpans(source, nameof(source), destPixels, nameof(destPixels), count); - ref Rgb24 sourceRef = ref source.DangerousGetPinnableReference(); - ref TPixel destRef = ref destPixels.DangerousGetPinnableReference(); + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(source); + ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); Rgba32 rgba = new Rgba32(0, 0, 0, 255); @@ -199,8 +200,8 @@ namespace SixLabors.ImageSharp.PixelFormats { GuardSpans(sourcePixels, nameof(sourcePixels), dest, nameof(dest), count); - ref TPixel sourceBaseRef = ref sourcePixels.DangerousGetPinnableReference(); - ref Rgb24 destBaseRef = ref dest.DangerousGetPinnableReference(); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destBaseRef = ref MemoryMarshal.GetReference(dest); for (int i = 0; i < count; i++) { @@ -233,8 +234,8 @@ namespace SixLabors.ImageSharp.PixelFormats { GuardSpans(source, nameof(source), destPixels, nameof(destPixels), count); - ref Bgr24 sourceRef = ref source.DangerousGetPinnableReference(); - ref TPixel destRef = ref destPixels.DangerousGetPinnableReference(); + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(source); + ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); Rgba32 rgba = new Rgba32(0, 0, 0, 255); @@ -270,8 +271,8 @@ namespace SixLabors.ImageSharp.PixelFormats { GuardSpans(sourcePixels, nameof(sourcePixels), dest, nameof(dest), count); - ref TPixel sourceBaseRef = ref sourcePixels.DangerousGetPinnableReference(); - ref Bgr24 destBaseRef = ref dest.DangerousGetPinnableReference(); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destBaseRef = ref MemoryMarshal.GetReference(dest); for (int i = 0; i < count; i++) { diff --git a/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.tt b/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.tt index aa88b6606c..365f5cb514 100644 --- a/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.tt +++ b/src/ImageSharp/PixelFormats/Generated/PixelOperations{TPixel}.Generated.tt @@ -7,6 +7,7 @@ <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.Runtime.InteropServices" #> <#@ output extension=".cs" #> <# void GenerateToDestFormatMethods(string pixelType) @@ -24,8 +25,8 @@ { GuardSpans(sourcePixels, nameof(sourcePixels), dest, nameof(dest), count); - ref TPixel sourceBaseRef = ref sourcePixels.DangerousGetPinnableReference(); - ref <#=pixelType#> destBaseRef = ref dest.DangerousGetPinnableReference(); + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref <#=pixelType#> destBaseRef = ref MemoryMarshal.GetReference(dest); for (int i = 0; i < count; i++) { @@ -64,8 +65,8 @@ { GuardSpans(source, nameof(source), destPixels, nameof(destPixels), count); - ref <#=pixelType#> sourceRef = ref source.DangerousGetPinnableReference(); - ref TPixel destRef = ref destPixels.DangerousGetPinnableReference(); + ref <#=pixelType#> sourceRef = ref MemoryMarshal.GetReference(source); + ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); Rgba32 rgba = new Rgba32(0, 0, 0, 255); @@ -101,7 +102,8 @@ namespace SixLabors.ImageSharp.PixelFormats { using System; using System.Runtime.CompilerServices; - + using System.Runtime.InteropServices; + public partial class PixelOperations { <# diff --git a/src/ImageSharp/PixelFormats/Generated/Rgba32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/Generated/Rgba32.PixelOperations.Generated.cs index 659e702281..edf6a88e1f 100644 --- a/src/ImageSharp/PixelFormats/Generated/Rgba32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/Generated/Rgba32.PixelOperations.Generated.cs @@ -6,9 +6,9 @@ namespace SixLabors.ImageSharp { using System; using System.Runtime.CompilerServices; - + using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; - + /// /// Provides optimized overrides for bulk operations. /// @@ -22,8 +22,8 @@ namespace SixLabors.ImageSharp { GuardSpans(source, nameof(source), destPixels, nameof(destPixels), count); - ref Rgb24 sourceRef = ref source.DangerousGetPinnableReference(); - ref Rgba32 destRef = ref destPixels.DangerousGetPinnableReference(); + ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destPixels); for (int i = 0; i < count; i++) { @@ -38,8 +38,8 @@ namespace SixLabors.ImageSharp { GuardSpans(sourcePixels, nameof(sourcePixels), dest, nameof(dest), count); - ref Rgba32 sourceRef = ref sourcePixels.DangerousGetPinnableReference(); - ref Rgb24 destRef = ref dest.DangerousGetPinnableReference(); + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb24 destRef = ref MemoryMarshal.GetReference(dest); for (int i = 0; i < count; i++) { @@ -54,8 +54,8 @@ namespace SixLabors.ImageSharp { GuardSpans(source, nameof(source), destPixels, nameof(destPixels), count); - ref Bgr24 sourceRef = ref source.DangerousGetPinnableReference(); - ref Rgba32 destRef = ref destPixels.DangerousGetPinnableReference(); + ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destPixels); for (int i = 0; i < count; i++) { @@ -70,8 +70,8 @@ namespace SixLabors.ImageSharp { GuardSpans(sourcePixels, nameof(sourcePixels), dest, nameof(dest), count); - ref Rgba32 sourceRef = ref sourcePixels.DangerousGetPinnableReference(); - ref Bgr24 destRef = ref dest.DangerousGetPinnableReference(); + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgr24 destRef = ref MemoryMarshal.GetReference(dest); for (int i = 0; i < count; i++) { @@ -86,8 +86,8 @@ namespace SixLabors.ImageSharp { GuardSpans(source, nameof(source), destPixels, nameof(destPixels), count); - ref Bgra32 sourceRef = ref source.DangerousGetPinnableReference(); - ref Rgba32 destRef = ref destPixels.DangerousGetPinnableReference(); + ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destPixels); for (int i = 0; i < count; i++) { @@ -102,8 +102,8 @@ namespace SixLabors.ImageSharp { GuardSpans(sourcePixels, nameof(sourcePixels), dest, nameof(dest), count); - ref Rgba32 sourceRef = ref sourcePixels.DangerousGetPinnableReference(); - ref Bgra32 destRef = ref dest.DangerousGetPinnableReference(); + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra32 destRef = ref MemoryMarshal.GetReference(dest); for (int i = 0; i < count; i++) { diff --git a/src/ImageSharp/PixelFormats/Generated/Rgba32.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/Generated/Rgba32.PixelOperations.Generated.tt index 9d22293947..d83e49f770 100644 --- a/src/ImageSharp/PixelFormats/Generated/Rgba32.PixelOperations.Generated.tt +++ b/src/ImageSharp/PixelFormats/Generated/Rgba32.PixelOperations.Generated.tt @@ -18,8 +18,8 @@ { GuardSpans(source, nameof(source), destPixels, nameof(destPixels), count); - ref <#=pixelType#> sourceRef = ref source.DangerousGetPinnableReference(); - ref Rgba32 destRef = ref destPixels.DangerousGetPinnableReference(); + ref <#=pixelType#> sourceRef = ref MemoryMarshal.GetReference(source); + ref Rgba32 destRef = ref MemoryMarshal.GetReference(destPixels); for (int i = 0; i < count; i++) { @@ -40,8 +40,8 @@ { GuardSpans(sourcePixels, nameof(sourcePixels), dest, nameof(dest), count); - ref Rgba32 sourceRef = ref sourcePixels.DangerousGetPinnableReference(); - ref <#=pixelType#> destRef = ref dest.DangerousGetPinnableReference(); + ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref <#=pixelType#> destRef = ref MemoryMarshal.GetReference(dest); for (int i = 0; i < count; i++) { @@ -61,9 +61,9 @@ namespace SixLabors.ImageSharp { using System; using System.Runtime.CompilerServices; - + using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; - + /// /// Provides optimized overrides for bulk operations. /// diff --git a/src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs b/src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs index 45050de72e..6a2902f9b1 100644 --- a/src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/NamedColors{TPixel}.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; + namespace SixLabors.ImageSharp.PixelFormats { /// @@ -10,6 +12,11 @@ namespace SixLabors.ImageSharp.PixelFormats public static class NamedColors where TPixel : struct, IPixel { + /// + /// Thread-safe backing field for . + /// + private static readonly Lazy WebSafePaletteLazy = new Lazy(GetWebSafePalette, true); + /// /// Represents a matching the W3C definition that has an hex value of #F0F8FF. /// @@ -719,5 +726,20 @@ namespace SixLabors.ImageSharp.PixelFormats /// Represents a matching the W3C definition that has an hex value of #9ACD32. /// public static readonly TPixel YellowGreen = ColorBuilder.FromRGBA(154, 205, 50, 255); + + /// + /// Gets a matching the W3C definition of web safe colors. + /// + public static TPixel[] WebSafePalette => WebSafePaletteLazy.Value; + + private static TPixel[] GetWebSafePalette() + { + Rgba32[] constants = ColorConstants.WebSafeColors; + TPixel[] safe = new TPixel[constants.Length + 1]; + + Span constantsBytes = constants.AsSpan().NonPortableCast(); + PixelOperations.Instance.PackFromRgba32Bytes(constantsBytes, safe, constants.Length); + return safe; + } } } \ 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 99a20516d2..d3c6cf16ce 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs @@ -26,7 +26,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders internal class Normal : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -39,13 +38,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -63,9 +62,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Multiply : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -78,13 +77,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -102,9 +101,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Add : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -117,13 +116,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -141,9 +140,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Substract : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -156,13 +155,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -180,9 +179,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Screen : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -195,13 +194,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -219,9 +218,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Darken : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -234,13 +233,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -258,9 +257,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Lighten : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -273,13 +272,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -297,9 +296,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Overlay : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -312,13 +311,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -336,9 +335,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class HardLight : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -351,13 +350,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -375,9 +374,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Src : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -390,13 +389,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -414,9 +413,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Atop : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -429,13 +428,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -453,9 +452,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Over : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -468,13 +467,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -492,9 +491,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class In : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -507,13 +506,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -531,9 +530,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Out : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -546,13 +545,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -570,9 +569,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Dest : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -585,13 +584,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -609,9 +608,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class DestAtop : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -624,13 +623,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -648,9 +647,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class DestOver : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -663,13 +662,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -687,9 +686,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class DestIn : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -702,13 +701,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -726,9 +725,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class DestOut : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -741,13 +740,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -765,9 +764,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Clear : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -780,13 +779,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -804,9 +803,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + internal class Xor : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -819,13 +818,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -843,5 +842,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + } } \ 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 9d7d73db99..eebee676fc 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt @@ -68,7 +68,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders #> internal class <#=blender#> : PixelBlender { - /// /// Gets the static instance of this blender. /// @@ -81,13 +80,13 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } /// - public override void Blend(Span destination, Span background, Span source, Span amount) + public override void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span 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 (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = memoryManager.Allocate(destination.Length * 3, false)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -105,6 +104,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders } } } + <# } diff --git a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs index 54cb09c28a..666fb38913 100644 --- a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.PixelFormats { @@ -27,6 +28,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// Blend 2 pixels together. /// + /// The /// The destination span. /// The background span. /// The source span. @@ -34,6 +36,6 @@ namespace SixLabors.ImageSharp.PixelFormats /// 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(Span destination, Span background, Span source, Span amount); + public abstract void Blend(MemoryManager memoryManager, Span destination, Span background, Span source, Span amount); } } diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index 4f879fbdc7..6f79752406 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -4,6 +4,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.PixelFormats { @@ -30,8 +31,8 @@ namespace SixLabors.ImageSharp.PixelFormats { GuardSpans(sourceVectors, nameof(sourceVectors), destColors, nameof(destColors), count); - ref Vector4 sourceRef = ref sourceVectors.DangerousGetPinnableReference(); - ref TPixel destRef = ref destColors.DangerousGetPinnableReference(); + ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceVectors); + ref TPixel destRef = ref MemoryMarshal.GetReference(destColors); for (int i = 0; i < count; i++) { @@ -51,8 +52,8 @@ namespace SixLabors.ImageSharp.PixelFormats { GuardSpans(sourceColors, nameof(sourceColors), destVectors, nameof(destVectors), count); - ref TPixel sourceRef = ref sourceColors.DangerousGetPinnableReference(); - ref Vector4 destRef = ref destVectors.DangerousGetPinnableReference(); + ref TPixel sourceRef = ref MemoryMarshal.GetReference(sourceColors); + ref Vector4 destRef = ref MemoryMarshal.GetReference(destVectors); for (int i = 0; i < count; i++) { diff --git a/src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs b/src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs index 552ac0a018..89a4aba264 100644 --- a/src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs @@ -57,8 +57,8 @@ namespace SixLabors.ImageSharp int unpackedRawCount = count * 4; - ref uint sourceBase = ref Unsafe.As(ref sourceColors.DangerousGetPinnableReference()); - ref UnpackedRGBA destBaseAsUnpacked = ref Unsafe.As(ref destVectors.DangerousGetPinnableReference()); + ref uint sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(sourceColors)); + ref UnpackedRGBA destBaseAsUnpacked = ref Unsafe.As(ref MemoryMarshal.GetReference(destVectors)); ref Vector destBaseAsUInt = ref Unsafe.As>(ref destBaseAsUnpacked); ref Vector destBaseAsFloat = ref Unsafe.As>(ref destBaseAsUnpacked); diff --git a/src/ImageSharp/Processing/Binarization/BinaryDiffuse.cs b/src/ImageSharp/Processing/Binarization/BinaryDiffuse.cs new file mode 100644 index 0000000000..eb50087570 --- /dev/null +++ b/src/ImageSharp/Processing/Binarization/BinaryDiffuse.cs @@ -0,0 +1,86 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Dithers the image reducing it to two colors using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The . + public static IImageProcessingContext BinaryDiffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold)); + return source; + } + + /// + /// Dithers the image reducing it to two colors using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext BinaryDiffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold), rectangle); + return source; + } + + /// + /// Dithers the image reducing it to two colors using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// The . + public static IImageProcessingContext BinaryDiffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, TPixel upperColor, TPixel lowerColor) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold, upperColor, lowerColor)); + return source; + } + + /// + /// Dithers the image reducing it to two colors using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext BinaryDiffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, TPixel upperColor, TPixel lowerColor, Rectangle rectangle) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold, upperColor, lowerColor), rectangle); + return source; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Binarization/BinaryDither.cs b/src/ImageSharp/Processing/Binarization/BinaryDither.cs new file mode 100644 index 0000000000..715dff472d --- /dev/null +++ b/src/ImageSharp/Processing/Binarization/BinaryDither.cs @@ -0,0 +1,82 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The pixel format. + /// The image this method extends. + /// The ordered ditherer. + /// The . + public static IImageProcessingContext BinaryDither(this IImageProcessingContext source, IOrderedDither dither) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither)); + return source; + } + + /// + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The pixel format. + /// The image this method extends. + /// The ordered ditherer. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// The . + public static IImageProcessingContext BinaryDither(this IImageProcessingContext source, IOrderedDither dither, TPixel upperColor, TPixel lowerColor) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither, upperColor, lowerColor)); + return source; + } + + /// + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The pixel format. + /// The image this method extends. + /// The ordered ditherer. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext BinaryDither(this IImageProcessingContext source, IOrderedDither dither, Rectangle rectangle) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither), rectangle); + return source; + } + + /// + /// Dithers the image reducing it to two colors using ordered dithering. + /// + /// The pixel format. + /// The image this method extends. + /// The ordered ditherer. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext BinaryDither(this IImageProcessingContext source, IOrderedDither dither, TPixel upperColor, TPixel lowerColor, Rectangle rectangle) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither, upperColor, lowerColor), rectangle); + return source; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Binarization/BinaryThreshold.cs b/src/ImageSharp/Processing/Binarization/BinaryThreshold.cs index 5a165659b8..3f86528f51 100644 --- a/src/ImageSharp/Processing/Binarization/BinaryThreshold.cs +++ b/src/ImageSharp/Processing/Binarization/BinaryThreshold.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors; using SixLabors.Primitives; @@ -43,5 +42,40 @@ namespace SixLabors.ImageSharp source.ApplyProcessor(new BinaryThresholdProcessor(threshold), rectangle); return source; } + + /// + /// Applies binarization to the image splitting the pixels at the given threshold. + /// + /// The pixel format. + /// The image this method extends. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// The . + public static IImageProcessingContext BinaryThreshold(this IImageProcessingContext source, float threshold, TPixel upperColor, TPixel lowerColor) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor)); + return source; + } + + /// + /// Applies binarization to the image splitting the pixels at the given threshold. + /// + /// The pixel format. + /// The image this method extends. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext BinaryThreshold(this IImageProcessingContext source, float threshold, TPixel upperColor, TPixel lowerColor, Rectangle rectangle) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BinaryThresholdProcessor(threshold, upperColor, lowerColor), rectangle); + return source; + } } } diff --git a/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs b/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs index 947e531578..ca79841c76 100644 --- a/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs +++ b/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp public static IImageProcessingContext Lomograph(this IImageProcessingContext source, GraphicsOptions options) where TPixel : struct, IPixel { - source.ApplyProcessor(new LomographProcessor(options)); + source.ApplyProcessor(new LomographProcessor(source.MemoryManager, options)); return source; } @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp public static IImageProcessingContext Lomograph(this IImageProcessingContext source, Rectangle rectangle, GraphicsOptions options) where TPixel : struct, IPixel { - source.ApplyProcessor(new LomographProcessor(options), rectangle); + source.ApplyProcessor(new LomographProcessor(source.MemoryManager, options), rectangle); return source; } } diff --git a/src/ImageSharp/Processing/ColorMatrix/Opacity.cs b/src/ImageSharp/Processing/ColorMatrix/Opacity.cs index b310b4b915..b59c068905 100644 --- a/src/ImageSharp/Processing/ColorMatrix/Opacity.cs +++ b/src/ImageSharp/Processing/ColorMatrix/Opacity.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp public static partial class ImageExtensions { /// - /// Alters the alpha component of the image. + /// Multiplies the alpha component of the image. /// /// The pixel format. /// The image this method extends. @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp => source.ApplyProcessor(new OpacityProcessor(amount)); /// - /// Alters the alpha component of the image. + /// Multiplies the alpha component of the image. /// /// The pixel format. /// The image this method extends. diff --git a/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs b/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs index c96087d57e..d12cc76122 100644 --- a/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs +++ b/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp public static IImageProcessingContext Polaroid(this IImageProcessingContext source, GraphicsOptions options) where TPixel : struct, IPixel { - source.ApplyProcessor(new PolaroidProcessor(options)); + source.ApplyProcessor(new PolaroidProcessor(source.MemoryManager, options)); return source; } @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp public static IImageProcessingContext Polaroid(this IImageProcessingContext source, Rectangle rectangle, GraphicsOptions options) where TPixel : struct, IPixel { - source.ApplyProcessor(new PolaroidProcessor(options), rectangle); + source.ApplyProcessor(new PolaroidProcessor(source.MemoryManager, options), rectangle); return source; } } diff --git a/src/ImageSharp/Processing/Dithering/Diffuse.cs b/src/ImageSharp/Processing/Dithering/Diffuse.cs new file mode 100644 index 0000000000..e6b82d3134 --- /dev/null +++ b/src/ImageSharp/Processing/Dithering/Diffuse.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Dithers the image reducing it to a web-safe palette using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The . + public static IImageProcessingContext Diffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold)); + return source; + } + + /// + /// Dithers the image reducing it to a web-safe palette using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Diffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold), rectangle); + return source; + } + + /// + /// Dithers the image reducing it to the given palette using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The palette to select substitute colors from. + /// The . + public static IImageProcessingContext Diffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, TPixel[] palette) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette)); + return source; + } + + /// + /// Dithers the image reducing it to the given palette using error diffusion. + /// + /// The pixel format. + /// The image this method extends. + /// The diffusion algorithm to apply. + /// The threshold to apply binarization of the image. Must be between 0 and 1. + /// The palette to select substitute colors from. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static IImageProcessingContext Diffuse(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, TPixel[] palette, Rectangle rectangle) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette), rectangle); + return source; + } + } +} diff --git a/src/ImageSharp/Processing/Binarization/Dither.cs b/src/ImageSharp/Processing/Dithering/Dither.cs similarity index 50% rename from src/ImageSharp/Processing/Binarization/Dither.cs rename to src/ImageSharp/Processing/Dithering/Dither.cs index f21ccf0bd3..85fdef24b5 100644 --- a/src/ImageSharp/Processing/Binarization/Dither.cs +++ b/src/ImageSharp/Processing/Dithering/Dither.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using SixLabors.ImageSharp.Dithering; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors; @@ -15,7 +14,7 @@ namespace SixLabors.ImageSharp public static partial class ImageExtensions { /// - /// Dithers the image reducing it to two colors using ordered dithering. + /// Dithers the image reducing it to a web-safe palette using ordered dithering. /// /// The pixel format. /// The image this method extends. @@ -24,27 +23,27 @@ namespace SixLabors.ImageSharp public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither) where TPixel : struct, IPixel { - source.ApplyProcessor(new OrderedDitherProcessor(dither, 0)); + source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither)); return source; } /// - /// Dithers the image reducing it to two colors using ordered dithering. + /// Dithers the image reducing it to the given palette using ordered dithering. /// /// The pixel format. /// The image this method extends. /// The ordered ditherer. - /// The component index to test the threshold against. Must range from 0 to 3. + /// The palette to select substitute colors from. /// The . - public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither, int index) + public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither, TPixel[] palette) where TPixel : struct, IPixel { - source.ApplyProcessor(new OrderedDitherProcessor(dither, index)); + source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither, palette)); return source; } /// - /// Dithers the image reducing it to two colors using ordered dithering. + /// Dithers the image reducing it to a web-safe palette using ordered dithering. /// /// The pixel format. /// The image this method extends. @@ -56,58 +55,25 @@ namespace SixLabors.ImageSharp public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither, Rectangle rectangle) where TPixel : struct, IPixel { - source.ApplyProcessor(new OrderedDitherProcessor(dither, 0), rectangle); + source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither), rectangle); return source; } /// - /// Dithers the image reducing it to two colors using ordered dithering. + /// Dithers the image reducing it to the given palette using ordered dithering. /// /// The pixel format. /// The image this method extends. /// The ordered ditherer. + /// The palette to select substitute colors from. /// /// The structure that specifies the portion of the image object to alter. /// - /// The component index to test the threshold against. Must range from 0 to 3. /// The . - public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither, Rectangle rectangle, int index) + public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither, TPixel[] palette, Rectangle rectangle) where TPixel : struct, IPixel { - source.ApplyProcessor(new OrderedDitherProcessor(dither, index), rectangle); - return source; - } - - /// - /// Dithers the image reducing it to two colors using error diffusion. - /// - /// The pixel format. - /// The image this method extends. - /// The diffusion algorithm to apply. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The . - public static IImageProcessingContext Dither(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold) - where TPixel : struct, IPixel - { - source.ApplyProcessor(new ErrorDiffusionDitherProcessor(diffuser, threshold)); - return source; - } - - /// - /// Dithers the image reducing it to two colors using error diffusion. - /// - /// The pixel format. - /// The image this method extends. - /// The diffusion algorithm to apply. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The . - public static IImageProcessingContext Dither(this IImageProcessingContext source, IErrorDiffuser diffuser, float threshold, Rectangle rectangle) - where TPixel : struct, IPixel - { - source.ApplyProcessor(new ErrorDiffusionDitherProcessor(diffuser, threshold), rectangle); + source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither, palette), rectangle); return source; } } diff --git a/src/ImageSharp/Processing/Effects/BackgroundColor.cs b/src/ImageSharp/Processing/Effects/BackgroundColor.cs index da00b4ddd0..22aad9ca6a 100644 --- a/src/ImageSharp/Processing/Effects/BackgroundColor.cs +++ b/src/ImageSharp/Processing/Effects/BackgroundColor.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp /// The . public static IImageProcessingContext BackgroundColor(this IImageProcessingContext source, TPixel color, GraphicsOptions options) where TPixel : struct, IPixel - => source.ApplyProcessor(new BackgroundColorProcessor(color, options)); + => source.ApplyProcessor(new BackgroundColorProcessor(source.MemoryManager, color, options)); /// /// Replaces the background color of image with the given one. @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp /// The . public static IImageProcessingContext BackgroundColor(this IImageProcessingContext source, TPixel color, Rectangle rectangle, GraphicsOptions options) where TPixel : struct, IPixel - => source.ApplyProcessor(new BackgroundColorProcessor(color, options), rectangle); + => source.ApplyProcessor(new BackgroundColorProcessor(source.MemoryManager, color, options), rectangle); /// /// Replaces the background color of image with the given one. diff --git a/src/ImageSharp/Processing/Overlays/Glow.cs b/src/ImageSharp/Processing/Overlays/Glow.cs index ee35963487..0c3552b4d2 100644 --- a/src/ImageSharp/Processing/Overlays/Glow.cs +++ b/src/ImageSharp/Processing/Overlays/Glow.cs @@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp /// The . private static IImageProcessingContext Glow(this IImageProcessingContext source, TPixel color, ValueSize radius, Rectangle rectangle, GraphicsOptions options) where TPixel : struct, IPixel - => source.ApplyProcessor(new GlowProcessor(color, radius, options), rectangle); + => source.ApplyProcessor(new GlowProcessor(source.MemoryManager, color, radius, options), rectangle); /// /// Applies a radial glow effect to an image. @@ -170,6 +170,6 @@ namespace SixLabors.ImageSharp /// The . private static IImageProcessingContext Glow(this IImageProcessingContext source, TPixel color, ValueSize radius, GraphicsOptions options) where TPixel : struct, IPixel - => source.ApplyProcessor(new GlowProcessor(color, radius, options)); + => source.ApplyProcessor(new GlowProcessor(source.MemoryManager, color, radius, options)); } } diff --git a/src/ImageSharp/Processing/Overlays/Vignette.cs b/src/ImageSharp/Processing/Overlays/Vignette.cs index cc93ccedc6..4b9f2f866c 100644 --- a/src/ImageSharp/Processing/Overlays/Vignette.cs +++ b/src/ImageSharp/Processing/Overlays/Vignette.cs @@ -151,10 +151,10 @@ namespace SixLabors.ImageSharp private static IImageProcessingContext VignetteInternal(this IImageProcessingContext source, TPixel color, ValueSize radiusX, ValueSize radiusY, Rectangle rectangle, GraphicsOptions options) where TPixel : struct, IPixel - => source.ApplyProcessor(new VignetteProcessor(color, radiusX, radiusY, options), rectangle); + => source.ApplyProcessor(new VignetteProcessor(source.MemoryManager, color, radiusX, radiusY, options), rectangle); private static IImageProcessingContext VignetteInternal(this IImageProcessingContext source, TPixel color, ValueSize radiusX, ValueSize radiusY, GraphicsOptions options) where TPixel : struct, IPixel - => source.ApplyProcessor(new VignetteProcessor(color, radiusX, radiusY, options)); + => source.ApplyProcessor(new VignetteProcessor(source.MemoryManager, color, radiusX, radiusY, options)); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs new file mode 100644 index 0000000000..80a4236452 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs @@ -0,0 +1,123 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.Helpers; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors +{ + /// + /// Performs binary threshold filtering against an image using error diffusion. + /// + /// The pixel format. + internal class BinaryErrorDiffusionProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The error diffuser + public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser) + : this(diffuser, .5F) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error diffuser + /// The threshold to split the image. Must be between 0 and 1. + public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser, float threshold) + : this(diffuser, threshold, NamedColors.White, NamedColors.Black) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error diffuser + /// The threshold to split the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold. + public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser, float threshold, TPixel upperColor, TPixel lowerColor) + { + Guard.NotNull(diffuser, nameof(diffuser)); + Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); + + this.Diffuser = diffuser; + this.Threshold = threshold; + this.UpperColor = upperColor; + this.LowerColor = lowerColor; + } + + /// + /// Gets the error diffuser. + /// + public IErrorDiffuser Diffuser { get; } + + /// + /// Gets the threshold value. + /// + public float Threshold { get; } + + /// + /// Gets the color to use for pixels that are above the threshold. + /// + public TPixel UpperColor { get; } + + /// + /// Gets the color to use for pixels that fall below the threshold. + /// + public TPixel LowerColor { get; } + + /// + protected override void OnApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + float threshold = this.Threshold * 255F; + var rgba = default(Rgba32); + bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); + + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; + + // Collect the values before looping so we can reduce our calculation count for identical sibling pixels + TPixel sourcePixel = source[startX, startY]; + TPixel previousPixel = sourcePixel; + sourcePixel.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); + + for (int y = startY; y < endY; y++) + { + Span row = source.GetPixelRowSpan(y); + + for (int x = startX; x < endX; x++) + { + sourcePixel = row[x]; + + // Check if this is the same as the last pixel. If so use that value + // rather than calculating it again. This is an inexpensive optimization. + if (!previousPixel.Equals(sourcePixel)) + { + sourcePixel.ToRgba32(ref rgba); + luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B); + + // Setup the previous pointer + previousPixel = sourcePixel; + } + + TPixel transformedPixel = luminance >= threshold ? this.UpperColor : this.LowerColor; + this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs new file mode 100644 index 0000000000..baa8df8cca --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.Helpers; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors +{ + /// + /// Performs binary threshold filtering against an image using ordered dithering. + /// + /// The pixel format. + internal class BinaryOrderedDitherProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The ordered ditherer. + public BinaryOrderedDitherProcessor(IOrderedDither dither) + : this(dither, NamedColors.White, NamedColors.Black) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The ordered ditherer. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold. + public BinaryOrderedDitherProcessor(IOrderedDither dither, TPixel upperColor, TPixel lowerColor) + { + Guard.NotNull(dither, nameof(dither)); + + this.Dither = dither; + this.UpperColor = upperColor; + this.LowerColor = lowerColor; + } + + /// + /// Gets the ditherer. + /// + public IOrderedDither Dither { get; } + + /// + /// Gets the color to use for pixels that are above the threshold. + /// + public TPixel UpperColor { get; } + + /// + /// Gets the color to use for pixels that fall below the threshold. + /// + public TPixel LowerColor { get; } + + /// + protected override void OnApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + var rgba = default(Rgba32); + bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); + + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; + + // Collect the values before looping so we can reduce our calculation count for identical sibling pixels + TPixel sourcePixel = source[startX, startY]; + TPixel previousPixel = sourcePixel; + sourcePixel.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); + + for (int y = startY; y < endY; y++) + { + Span row = source.GetPixelRowSpan(y); + + for (int x = startX; x < endX; x++) + { + sourcePixel = row[x]; + + // Check if this is the same as the last pixel. If so use that value + // rather than calculating it again. This is an inexpensive optimization. + if (!previousPixel.Equals(sourcePixel)) + { + sourcePixel.ToRgba32(ref rgba); + luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B); + + // Setup the previous pointer + previousPixel = sourcePixel; + } + + this.Dither.Dither(source, sourcePixel, this.UpperColor, this.LowerColor, luminance, x, y); + } + } + } + } +} \ 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 434ed02698..609b090923 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs @@ -4,14 +4,14 @@ using System; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Helpers; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors { /// - /// An to perform binary threshold filtering against an - /// . The image will be converted to grayscale before thresholding occurs. + /// Performs simple binary threshold filtering against an image. /// /// The pixel format. internal class BinaryThresholdProcessor : ImageProcessor @@ -22,14 +22,22 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The threshold to split the image. Must be between 0 and 1. public BinaryThresholdProcessor(float threshold) + : this(threshold, NamedColors.White, NamedColors.Black) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The threshold to split the image. Must be between 0 and 1. + /// The color to use for pixels that are above the threshold. + /// The color to use for pixels that are below the threshold. + public BinaryThresholdProcessor(float threshold, TPixel upperColor, TPixel lowerColor) { - // TODO: Check thresholding limit. Colors should probably have Max/Min/Middle properties. Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); this.Threshold = threshold; - - // Default to white/black for upper/lower. - this.UpperColor = NamedColors.White; - this.LowerColor = NamedColors.Black; + this.UpperColor = upperColor; + this.LowerColor = lowerColor; } /// @@ -47,55 +55,38 @@ namespace SixLabors.ImageSharp.Processing.Processors /// public TPixel LowerColor { get; set; } - /// - protected override void BeforeApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - new GrayscaleBt709Processor(1F).Apply(source, sourceRectangle, configuration); - } - /// protected override void OnApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { - float threshold = this.Threshold; + float threshold = this.Threshold * 255F; TPixel upper = this.UpperColor; TPixel lower = this.LowerColor; - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - - // Align start/end positions. - int minX = Math.Max(0, startX); - int maxX = Math.Min(source.Width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(source.Height, endY); - - // Reset offset if necessary. - if (minX > 0) - { - startX = 0; - } + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; - if (minY > 0) - { - startY = 0; - } + bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); Parallel.For( - minY, - maxY, + startY, + endY, configuration.ParallelOptions, y => { - Span row = source.GetPixelRowSpan(y - startY); + Span row = source.GetPixelRowSpan(y); + var rgba = default(Rgba32); - for (int x = minX; x < maxX; x++) + for (int x = startX; x < endX; x++) { - ref TPixel color = ref row[x - startX]; + ref TPixel color = ref row[x]; + color.ToRgba32(ref rgba); - // Any channel will do since it's Grayscale. - color = color.ToVector4().X >= 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/Binarization/ErrorDiffusionDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs deleted file mode 100644 index 01cba15c4b..0000000000 --- a/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Dithering; -using SixLabors.ImageSharp.Helpers; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors -{ - /// - /// An that dithers an image using error diffusion. - /// - /// The pixel format. - internal class ErrorDiffusionDitherProcessor : ImageProcessor - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The error diffuser - /// The threshold to split the image. Must be between 0 and 1. - public ErrorDiffusionDitherProcessor(IErrorDiffuser diffuser, float threshold) - { - Guard.NotNull(diffuser, nameof(diffuser)); - - this.Diffuser = diffuser; - this.Threshold = threshold; - - // Default to white/black for upper/lower. - this.UpperColor = NamedColors.White; - this.LowerColor = NamedColors.Black; - } - - /// - /// Gets the error diffuser. - /// - public IErrorDiffuser Diffuser { get; } - - /// - /// Gets the threshold value. - /// - public float Threshold { get; } - - /// - /// Gets or sets the color to use for pixels that are above the threshold. - /// - public TPixel UpperColor { get; set; } - - /// - /// Gets or sets the color to use for pixels that fall below the threshold. - /// - public TPixel LowerColor { get; set; } - - /// - protected override void BeforeApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - new GrayscaleBt709Processor(1F).Apply(source, sourceRectangle, configuration); - } - - /// - protected override void OnApply(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; - - for (int y = startY; y < endY; y++) - { - Span row = source.GetPixelRowSpan(y); - - for (int x = startX; x < endX; x++) - { - TPixel sourceColor = row[x]; - TPixel transformedColor = sourceColor.ToVector4().X >= this.Threshold ? this.UpperColor : this.LowerColor; - this.Diffuser.Dither(source, sourceColor, transformedColor, x, y, startX, startY, endX, endY); - } - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs deleted file mode 100644 index a37d12f18c..0000000000 --- a/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Dithering; -using SixLabors.ImageSharp.Helpers; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors -{ - /// - /// An that dithers an image using error diffusion. - /// - /// The pixel format. - internal class OrderedDitherProcessor : ImageProcessor - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The ordered ditherer. - /// The component index to test the threshold against. Must range from 0 to 3. - public OrderedDitherProcessor(IOrderedDither dither, int index) - { - Guard.NotNull(dither, nameof(dither)); - Guard.MustBeBetweenOrEqualTo(index, 0, 3, nameof(index)); - - // Alpha8 only stores the pixel data in the alpha channel. - if (typeof(TPixel) == typeof(Alpha8)) - { - index = 3; - } - - this.Dither = dither; - this.Index = index; - - // Default to white/black for upper/lower. - this.UpperColor = NamedColors.White; - this.LowerColor = NamedColors.Black; - } - - /// - /// Gets the ditherer. - /// - public IOrderedDither Dither { get; } - - /// - /// Gets the component index to test the threshold against. - /// - public int Index { get; } - - /// - /// Gets or sets the color to use for pixels that are above the threshold. - /// - public TPixel UpperColor { get; set; } - - /// - /// Gets or sets the color to use for pixels that fall below the threshold. - /// - public TPixel LowerColor { get; set; } - - /// - protected override void BeforeApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) - { - new GrayscaleBt709Processor(1F).Apply(source, sourceRectangle, configuration); - } - - /// - protected override void OnApply(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; - - var rgba = default(Rgba32); - for (int y = startY; y < endY; y++) - { - Span row = source.GetPixelRowSpan(y); - - for (int x = startX; x < endX; x++) - { - TPixel sourceColor = row[x]; - this.Dither.Dither(source, sourceColor, this.UpperColor, this.LowerColor, ref rgba, this.Index, x, y); - } - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs b/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs index 4672b2ad45..7257bd6643 100644 --- a/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs @@ -96,6 +96,7 @@ namespace SixLabors.ImageSharp.Processing /// /// This method is called before the process is applied to prepare the processor. + /// TODO: We should probably name this 'BeforeFrameApply' /// /// The source image. Cannot be null. /// The cloned/destination image. Cannot be null. @@ -108,6 +109,7 @@ namespace SixLabors.ImageSharp.Processing /// /// Applies the process to the specified portion of the specified at the specified location /// and with the specified size. + /// TODO: We should probably name this 'ApplyToFrame' /// /// The source image. Cannot be null. /// The cloned/destination image. Cannot be null. @@ -117,6 +119,7 @@ namespace SixLabors.ImageSharp.Processing /// /// This method is called after the process is applied to prepare the processor. + /// TODO: We should probably name this 'AfterFrameApply' /// /// The source image. Cannot be null. /// The cloned/destination image. Cannot be null. diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs index cf9b7be9c8..2c13ead162 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Processors int maxY = endY - 1; int maxX = endX - 1; - using (var targetPixels = new PixelAccessor(source.Width, source.Height)) + using (Buffer2D targetPixels = configuration.MemoryManager.Allocate2D(source.Width, source.Height)) { source.CopyTo(targetPixels); @@ -122,7 +122,7 @@ namespace SixLabors.ImageSharp.Processing.Processors } }); - source.SwapPixelsBuffers(targetPixels); + Buffer2D.SwapContents(source.PixelBuffer, targetPixels); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs index 57f434ee08..0d87aa1dc4 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -43,15 +43,12 @@ namespace SixLabors.ImageSharp.Processing.Processors /// protected override void OnApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { - int width = source.Width; - int height = source.Height; ParallelOptions parallelOptions = configuration.ParallelOptions; - using (var firstPassPixels = new PixelAccessor(width, height)) - using (PixelAccessor sourcePixels = source.Lock()) + using (Buffer2D firstPassPixels = configuration.MemoryManager.Allocate2D(source.Size())) { - this.ApplyConvolution(firstPassPixels, sourcePixels, source.Bounds(), this.KernelX, parallelOptions); - this.ApplyConvolution(sourcePixels, firstPassPixels, sourceRectangle, this.KernelY, parallelOptions); + this.ApplyConvolution(firstPassPixels, source.PixelBuffer, source.Bounds(), this.KernelX, parallelOptions); + this.ApplyConvolution(source.PixelBuffer, firstPassPixels, sourceRectangle, this.KernelY, parallelOptions); } } @@ -67,8 +64,8 @@ namespace SixLabors.ImageSharp.Processing.Processors /// The kernel operator. /// The parellel options private void ApplyConvolution( - PixelAccessor targetPixels, - PixelAccessor sourcePixels, + Buffer2D targetPixels, + Buffer2D sourcePixels, Rectangle sourceRectangle, Fast2DArray kernel, ParallelOptions parallelOptions) diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs index 96db9a4ce0..2f369113d9 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -5,6 +5,7 @@ using System; using System.Numerics; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Helpers; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; @@ -45,7 +46,7 @@ namespace SixLabors.ImageSharp.Processing.Processors int maxY = endY - 1; int maxX = endX - 1; - using (var targetPixels = new PixelAccessor(source.Width, source.Height)) + using (Buffer2D targetPixels = configuration.MemoryManager.Allocate2D(source.Size())) { source.CopyTo(targetPixels); @@ -94,7 +95,7 @@ namespace SixLabors.ImageSharp.Processing.Processors } }); - source.SwapPixelsBuffers(targetPixels); + Buffer2D.SwapContents(source.PixelBuffer, targetPixels); } } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs new file mode 100644 index 0000000000..152959cb73 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs @@ -0,0 +1,113 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.Helpers; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors +{ + /// + /// An that dithers an image using error diffusion. + /// + /// The pixel format. + internal class ErrorDiffusionPaletteProcessor : PaletteDitherProcessorBase + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The error diffuser + public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser) + : this(diffuser, .5F) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error diffuser + /// The threshold to split the image. Must be between 0 and 1. + public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold) + : this(diffuser, threshold, NamedColors.WebSafePalette) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error diffuser + /// The threshold to split the image. Must be between 0 and 1. + /// The palette to select substitute colors from. + public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold, TPixel[] palette) + : base(palette) + { + Guard.NotNull(diffuser, nameof(diffuser)); + Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); + + this.Diffuser = diffuser; + this.Threshold = threshold; + } + + /// + /// Gets the error diffuser. + /// + public IErrorDiffuser Diffuser { get; } + + /// + /// Gets the threshold value. + /// + public float Threshold { get; } + + /// + protected override void OnApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + float threshold = this.Threshold * 255F; + var rgba = default(Rgba32); + bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); + + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; + + // Collect the values before looping so we can reduce our calculation count for identical sibling pixels + TPixel sourcePixel = source[startX, startY]; + TPixel previousPixel = sourcePixel; + PixelPair pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette); + sourcePixel.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); + + for (int y = startY; y < endY; y++) + { + Span row = source.GetPixelRowSpan(y); + + for (int x = startX; x < endX; x++) + { + sourcePixel = row[x]; + + // Check if this is the same as the last pixel. If so use that value + // rather than calculating it again. This is an inexpensive optimization. + if (!previousPixel.Equals(sourcePixel)) + { + pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette); + sourcePixel.ToRgba32(ref rgba); + luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B); + + // Setup the previous pointer + previousPixel = sourcePixel; + } + + TPixel transformedPixel = luminance >= threshold ? pair.Second : pair.First; + this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs new file mode 100644 index 0000000000..4fc59585a9 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs @@ -0,0 +1,93 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.Helpers; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.Primitives; + +namespace SixLabors.ImageSharp.Processing.Processors +{ + /// + /// An that dithers an image using error diffusion. + /// If no palette is given this will default to the web safe colors defined in the CSS Color Module Level 4. + /// + /// The pixel format. + internal class OrderedDitherPaletteProcessor : PaletteDitherProcessorBase + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The ordered ditherer. + public OrderedDitherPaletteProcessor(IOrderedDither dither) + : this(dither, NamedColors.WebSafePalette) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The ordered ditherer. + /// The palette to select substitute colors from. + public OrderedDitherPaletteProcessor(IOrderedDither dither, TPixel[] palette) + : base(palette) + { + Guard.NotNull(dither, nameof(dither)); + this.Dither = dither; + } + + /// + /// Gets the ditherer. + /// + public IOrderedDither Dither { get; } + + /// + protected override void OnApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) + { + var rgba = default(Rgba32); + bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); + + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; + + // Collect the values before looping so we can reduce our calculation count for identical sibling pixels + TPixel sourcePixel = source[startX, startY]; + TPixel previousPixel = sourcePixel; + PixelPair pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette); + sourcePixel.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); + + for (int y = startY; y < endY; y++) + { + Span row = source.GetPixelRowSpan(y); + + for (int x = startX; x < endX; x++) + { + sourcePixel = row[x]; + + // Check if this is the same as the last pixel. If so use that value + // rather than calculating it again. This is an inexpensive optimization. + if (!previousPixel.Equals(sourcePixel)) + { + pair = this.GetClosestPixelPair(ref sourcePixel, this.Palette); + sourcePixel.ToRgba32(ref rgba); + luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B); + + // Setup the previous pointer + previousPixel = sourcePixel; + } + + this.Dither.Dither(source, sourcePixel, pair.Second, pair.First, luminance, x, y); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs new file mode 100644 index 0000000000..4e6b7bec0e --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessorBase.cs @@ -0,0 +1,75 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors +{ + /// + /// The base class for dither and diffusion processors that consume a palette. + /// + internal abstract class PaletteDitherProcessorBase : ImageProcessor + where TPixel : struct, IPixel + { + private readonly Dictionary> cache = new Dictionary>(); + + /// + /// Initializes a new instance of the class. + /// + /// The palette to select substitute colors from. + public PaletteDitherProcessorBase(TPixel[] palette) + { + Guard.NotNull(palette, nameof(palette)); + this.Palette = palette; + } + + /// + /// Gets the palette to select substitute colors from. + /// + public TPixel[] Palette { get; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected PixelPair GetClosestPixelPair(ref TPixel pixel, TPixel[] colorPalette) + { + // Check if the color is in the lookup table + if (this.cache.ContainsKey(pixel)) + { + return this.cache[pixel]; + } + + // Not found - loop through the palette and find the nearest match. + float leastDistance = int.MaxValue; + float secondLeastDistance = int.MaxValue; + var vector = pixel.ToVector4(); + + var closest = default(TPixel); + var secondClosest = default(TPixel); + for (int index = 0; index < colorPalette.Length; index++) + { + TPixel temp = colorPalette[index]; + float distance = Vector4.DistanceSquared(vector, temp.ToVector4()); + + if (distance < leastDistance) + { + leastDistance = distance; + secondClosest = closest; + closest = temp; + } + else if (distance < secondLeastDistance) + { + secondLeastDistance = distance; + secondClosest = temp; + } + } + + // Pop it into the cache for next time + var pair = new PixelPair(closest, secondClosest); + this.cache.Add(pixel, pair); + + return pair; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs b/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs new file mode 100644 index 0000000000..07045bb5ab --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors +{ + /// + /// Represents a composite pair of pixels. Used for caching color distance lookups. + /// + /// The pixel format. + internal readonly struct PixelPair : IEquatable> + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the struct. + /// + /// The first pixel color + /// The second pixel color + public PixelPair(TPixel first, TPixel second) + { + this.First = first; + this.Second = second; + } + + /// + /// Gets the first pixel color + /// + public TPixel First { get; } + + /// + /// Gets the second pixel color + /// + public TPixel Second { get; } + + /// + public bool Equals(PixelPair other) + => this.First.Equals(other.First) && this.Second.Equals(other.Second); + + /// + public override bool Equals(object obj) + => obj is PixelPair other && this.First.Equals(other.First) && this.Second.Equals(other.Second); + + /// + public override int GetHashCode() + => HashHelpers.Combine(this.First.GetHashCode(), this.Second.GetHashCode()); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs index 72e9b8f555..720b876913 100644 --- a/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs @@ -17,16 +17,20 @@ namespace SixLabors.ImageSharp.Processing.Processors internal class BackgroundColorProcessor : ImageProcessor where TPixel : struct, IPixel { + private readonly MemoryManager memoryManager; + private readonly GraphicsOptions options; /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The to set the background color to. /// The options defining blending algorithum and amount. - public BackgroundColorProcessor(TPixel color, GraphicsOptions options) + public BackgroundColorProcessor(MemoryManager memoryManager, TPixel color, GraphicsOptions options) { this.Value = color; + this.memoryManager = memoryManager; this.options = options; } @@ -67,13 +71,17 @@ namespace SixLabors.ImageSharp.Processing.Processors int width = maxX - minX; - using (var colors = new Buffer(width)) - using (var amount = new Buffer(width)) + using (IBuffer colors = this.memoryManager.Allocate(width)) + using (IBuffer amount = this.memoryManager.Allocate(width)) { + // Be careful! Do not capture colorSpan & amountSpan in the lambda below! + Span colorSpan = colors.Span; + Span amountSpan = amount.Span; + for (int i = 0; i < width; i++) { - colors[i] = this.Value; - amount[i] = this.options.BlendPercentage; + colorSpan[i] = this.Value; + amountSpan[i] = this.options.BlendPercentage; } PixelBlender blender = PixelOperations.Instance.GetPixelBlender(this.options.BlenderMode); @@ -86,7 +94,7 @@ namespace SixLabors.ImageSharp.Processing.Processors 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 colour under the current one - blender.Blend(destination, colors, destination, amount); + blender.Blend(this.memoryManager, destination, colors.Span, destination, amount.Span); }); } } diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs index b22a497987..c199a32c8a 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs @@ -5,6 +5,7 @@ using System; using System.Numerics; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Helpers; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; @@ -65,7 +66,7 @@ namespace SixLabors.ImageSharp.Processing.Processors int radius = this.BrushSize >> 1; int levels = this.Levels; - using (var targetPixels = new PixelAccessor(source.Width, source.Height)) + using (Buffer2D targetPixels = configuration.MemoryManager.Allocate2D(source.Size())) { source.CopyTo(targetPixels); @@ -133,7 +134,7 @@ namespace SixLabors.ImageSharp.Processing.Processors } }); - source.SwapPixelsBuffers(targetPixels); + Buffer2D.SwapContents(source.PixelBuffer, targetPixels); } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs index 2d97f65842..fcd7b2e8f5 100644 --- a/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/GrayscaleBt709Processor.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors { /// - /// Applies a greyscale filter matrix using the given amount and the formula as specified by ITU-R Recommendation BT.709 + /// Applies a grayscale filter matrix using the given amount and the formula as specified by ITU-R Recommendation BT.709 /// /// The pixel format. internal class GrayscaleBt709Processor : FilterProcessor diff --git a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs index 5ea57fd27b..33c4338d58 100644 --- a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System.Numerics; + +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; @@ -15,22 +17,27 @@ namespace SixLabors.ImageSharp.Processing.Processors where TPixel : struct, IPixel { private static readonly TPixel VeryDarkGreen = ColorBuilder.FromRGBA(0, 10, 0, 255); + + private readonly MemoryManager memoryManager; + private readonly GraphicsOptions options; /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The options effecting blending and composition. - public LomographProcessor(GraphicsOptions options) + public LomographProcessor(MemoryManager memoryManager, GraphicsOptions options) : base(MatrixFilters.LomographFilter) { + this.memoryManager = memoryManager; this.options = options; } /// protected override void AfterApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { - new VignetteProcessor(VeryDarkGreen, this.options).Apply(source, sourceRectangle, configuration); + new VignetteProcessor(this.memoryManager, VeryDarkGreen, this.options).Apply(source, sourceRectangle, configuration); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs index 5491db7efe..152d586afe 100644 --- a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; @@ -15,23 +16,28 @@ namespace SixLabors.ImageSharp.Processing.Processors { private static readonly TPixel VeryDarkOrange = ColorBuilder.FromRGB(102, 34, 0); private static readonly TPixel LightOrange = ColorBuilder.FromRGBA(255, 153, 102, 128); + + private readonly MemoryManager memoryManager; + private readonly GraphicsOptions options; /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The options effecting blending and composition. - public PolaroidProcessor(GraphicsOptions options) + public PolaroidProcessor(MemoryManager memoryManager, GraphicsOptions options) : base(MatrixFilters.PolaroidFilter) { + this.memoryManager = memoryManager; this.options = options; } /// protected override void AfterApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { - new VignetteProcessor(VeryDarkOrange, this.options).Apply(source, sourceRectangle, configuration); - new GlowProcessor(LightOrange, source.Width / 4F, this.options).Apply(source, sourceRectangle, configuration); + new VignetteProcessor(this.memoryManager, VeryDarkOrange, this.options).Apply(source, sourceRectangle, configuration); + new GlowProcessor(this.memoryManager, LightOrange, source.Width / 4F, this.options).Apply(source, sourceRectangle, configuration); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs index b02585d8fd..9ab301718c 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs @@ -19,17 +19,21 @@ namespace SixLabors.ImageSharp.Processing.Processors internal class GlowProcessor : ImageProcessor where TPixel : struct, IPixel { + private readonly MemoryManager memoryManager; + private readonly GraphicsOptions options; private readonly PixelBlender blender; /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The color or the glow. /// The radius of the glow. /// The options effecting blending and composition. - public GlowProcessor(TPixel color, ValueSize radius, GraphicsOptions options) + public GlowProcessor(MemoryManager memoryManager, TPixel color, ValueSize radius, GraphicsOptions options) { + this.memoryManager = memoryManager; this.options = options; this.GlowColor = color; this.Radius = radius; @@ -61,7 +65,7 @@ namespace SixLabors.ImageSharp.Processing.Processors TPixel glowColor = this.GlowColor; Vector2 centre = Rectangle.Center(sourceRectangle); - var finalRadius = this.Radius.Calculate(source.Size()); + float finalRadius = this.Radius.Calculate(source.Size()); float maxDistance = finalRadius > 0 ? MathF.Min(finalRadius, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; @@ -83,11 +87,14 @@ namespace SixLabors.ImageSharp.Processing.Processors } int width = maxX - minX; - using (var rowColors = new Buffer(width)) + using (IBuffer rowColors = this.memoryManager.Allocate(width)) { + // Be careful! Do not capture rowColorsSpan in the lambda below! + Span rowColorsSpan = rowColors.Span; + for (int i = 0; i < width; i++) { - rowColors[i] = glowColor; + rowColorsSpan[i] = glowColor; } Parallel.For( @@ -96,19 +103,20 @@ namespace SixLabors.ImageSharp.Processing.Processors configuration.ParallelOptions, y => { - using (var amounts = new Buffer(width)) + using (IBuffer amounts = this.memoryManager.Allocate(width)) { + Span amountsSpan = amounts.Span; int offsetY = y - startY; int offsetX = minX - startX; for (int i = 0; i < width; i++) { float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); - amounts[i] = (this.options.BlendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1); + amountsSpan[i] = (this.options.BlendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1); } Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width); - this.blender.Blend(destination, destination, rowColors, amounts); + this.blender.Blend(this.memoryManager, destination, destination, rowColors.Span, amountsSpan); } }); } diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs index 7b592a6a4d..d47211f0cb 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs @@ -19,21 +19,25 @@ namespace SixLabors.ImageSharp.Processing.Processors internal class VignetteProcessor : ImageProcessor where TPixel : struct, IPixel { + private readonly MemoryManager memoryManager; + private readonly GraphicsOptions options; private readonly PixelBlender blender; /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The color of the vignette. /// The x-radius. /// The y-radius. /// The options effecting blending and composition. - public VignetteProcessor(TPixel color, ValueSize radiusX, ValueSize radiusY, GraphicsOptions options) + public VignetteProcessor(MemoryManager memoryManager, TPixel color, ValueSize radiusX, ValueSize radiusY, GraphicsOptions options) { this.VignetteColor = color; this.RadiusX = radiusX; this.RadiusY = radiusY; + this.memoryManager = memoryManager; this.options = options; this.blender = PixelOperations.Instance.GetPixelBlender(this.options.BlenderMode); } @@ -41,11 +45,13 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// Initializes a new instance of the class. /// + /// The to use for buffer allocations. /// The color of the vignette. /// The options effecting blending and composition. - public VignetteProcessor(TPixel color, GraphicsOptions options) + public VignetteProcessor(MemoryManager memoryManager, TPixel color, GraphicsOptions options) { this.VignetteColor = color; + this.memoryManager = memoryManager; this.options = options; this.blender = PixelOperations.Instance.GetPixelBlender(this.options.BlenderMode); } @@ -80,8 +86,8 @@ namespace SixLabors.ImageSharp.Processing.Processors TPixel vignetteColor = this.VignetteColor; Vector2 centre = Rectangle.Center(sourceRectangle); - var finalradiusX = this.RadiusX.Calculate(source.Size()); - var finalradiusY = this.RadiusY.Calculate(source.Size()); + float finalradiusX = this.RadiusX.Calculate(source.Size()); + float finalradiusY = this.RadiusY.Calculate(source.Size()); float rX = finalradiusX > 0 ? MathF.Min(finalradiusX, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; float rY = finalradiusY > 0 ? MathF.Min(finalradiusY, sourceRectangle.Height * .5F) : sourceRectangle.Height * .5F; float maxDistance = MathF.Sqrt((rX * rX) + (rY * rY)); @@ -104,11 +110,14 @@ namespace SixLabors.ImageSharp.Processing.Processors } int width = maxX - minX; - using (var rowColors = new Buffer(width)) + using (IBuffer rowColors = this.memoryManager.Allocate(width)) { + // Be careful! Do not capture rowColorsSpan in the lambda below! + Span rowColorsSpan = rowColors.Span; + for (int i = 0; i < width; i++) { - rowColors[i] = vignetteColor; + rowColorsSpan[i] = vignetteColor; } Parallel.For( @@ -117,19 +126,20 @@ namespace SixLabors.ImageSharp.Processing.Processors configuration.ParallelOptions, y => { - using (var amounts = new Buffer(width)) + using (IBuffer amounts = this.memoryManager.Allocate(width)) { + Span amountsSpan = amounts.Span; int offsetY = y - startY; int offsetX = minX - startX; for (int i = 0; i < width; i++) { float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); - amounts[i] = (this.options.BlendPercentage * (.9F * (distance / maxDistance))).Clamp(0, 1); + amountsSpan[i] = (this.options.BlendPercentage * (.9F * (distance / maxDistance))).Clamp(0, 1); } Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width); - this.blender.Blend(destination, destination, rowColors, amounts); + this.blender.Blend(this.memoryManager, destination, destination, rowColors.Span, amountsSpan); } }); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs index 8595e86922..ac11e0dfb3 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs @@ -21,27 +21,6 @@ namespace SixLabors.ImageSharp.Processing.Processors internal class AffineTransformProcessor : InterpolatedTransformProcessorBase where TPixel : struct, IPixel { - private Size targetDimensions; - - /// - /// Initializes a new instance of the class. - /// - /// The transform matrix - public AffineTransformProcessor(Matrix3x2 matrix) - : this(matrix, KnownResamplers.Bicubic) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The transform matrix - /// The sampler to perform the transform operation. - public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler) - : this(matrix, sampler, Size.Empty) - { - } - /// /// Initializes a new instance of the class. /// @@ -51,10 +30,8 @@ namespace SixLabors.ImageSharp.Processing.Processors public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size targetDimensions) : base(sampler) { - // Transforms are inverted else the output is the opposite of the expected. - Matrix3x2.Invert(matrix, out matrix); this.TransformMatrix = matrix; - this.targetDimensions = targetDimensions; + this.TargetDimensions = targetDimensions; } /// @@ -62,18 +39,17 @@ namespace SixLabors.ImageSharp.Processing.Processors /// public Matrix3x2 TransformMatrix { get; } + /// + /// Gets the target dimensions to constrain the transformed image to + /// + public Size TargetDimensions { get; } + /// protected override Image CreateDestination(Image source, Rectangle sourceRectangle) { - if (this.targetDimensions == Size.Empty) - { - // TODO: CreateDestination() should not modify the processors state! (kinda CQRS) - this.targetDimensions = this.GetTransformedDimensions(sourceRectangle.Size, this.TransformMatrix); - } - // 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(this.targetDimensions, x.MetaData.Clone())); + source.Frames.Select(x => new ImageFrame(source.GetMemoryManager(), this.TargetDimensions, x.MetaData.Clone())); // Use the overload to prevent an extra frame being added return new Image(source.GetConfiguration(), source.MetaData.Clone(), frames); @@ -86,8 +62,8 @@ namespace SixLabors.ImageSharp.Processing.Processors Rectangle sourceRectangle, Configuration configuration) { - int height = this.targetDimensions.Height; - int width = this.targetDimensions.Width; + int height = this.TargetDimensions.Height; + int width = this.TargetDimensions.Width; Rectangle sourceBounds = source.Bounds(); var targetBounds = new Rectangle(0, 0, width, height); @@ -95,6 +71,9 @@ namespace SixLabors.ImageSharp.Processing.Processors // Since could potentially be resizing the canvas we might need to re-calculate the matrix Matrix3x2 matrix = this.GetProcessingMatrix(sourceBounds, targetBounds); + // Convert from screen to world space. + Matrix3x2.Invert(matrix, out matrix); + if (this.Sampler is NearestNeighborResampler) { Parallel.For( @@ -130,8 +109,10 @@ namespace SixLabors.ImageSharp.Processing.Processors int xLength = (int)MathF.Ceiling((radius.X * 2) + 2); int yLength = (int)MathF.Ceiling((radius.Y * 2) + 2); - using (var yBuffer = new Buffer2D(yLength, height)) - using (var xBuffer = new Buffer2D(xLength, height)) + MemoryManager memoryManager = configuration.MemoryManager; + + using (Buffer2D yBuffer = memoryManager.Allocate2D(yLength, height)) + using (Buffer2D xBuffer = memoryManager.Allocate2D(xLength, height)) { Parallel.For( 0, @@ -204,8 +185,8 @@ namespace SixLabors.ImageSharp.Processing.Processors var vector = source[i, j].ToVector4(); // Values are first premultiplied to prevent darkening of edge pixels - Vector4 mupltiplied = vector.Premultiply(); - sum += mupltiplied * xWeight * yWeight; + Vector4 multiplied = vector.Premultiply(); + sum += multiplied * xWeight * yWeight; } } @@ -230,16 +211,5 @@ namespace SixLabors.ImageSharp.Processing.Processors { return this.TransformMatrix; } - - /// - /// Gets the bounding relative to the source for the given transformation matrix. - /// - /// The source rectangle. - /// The transformation matrix. - /// The - protected virtual Size GetTransformedDimensions(Size sourceDimensions, Matrix3x2 matrix) - { - return sourceDimensions; - } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs index c39311bc33..7f811eebc1 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Processing.Processors protected override void BeforeImageApply(Image source, Rectangle sourceRectangle) { Orientation orientation = GetExifOrientation(source); - + Size size = sourceRectangle.Size; switch (orientation) { case Orientation.TopRight: @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Processing.Processors break; case Orientation.BottomRight: - new RotateProcessor((int)RotateType.Rotate180).Apply(source, sourceRectangle); + new RotateProcessor((int)RotateType.Rotate180, size).Apply(source, sourceRectangle); break; case Orientation.BottomLeft: @@ -35,21 +35,21 @@ namespace SixLabors.ImageSharp.Processing.Processors break; case Orientation.LeftTop: - new RotateProcessor((int)RotateType.Rotate90).Apply(source, sourceRectangle); + new RotateProcessor((int)RotateType.Rotate90, size).Apply(source, sourceRectangle); new FlipProcessor(FlipType.Horizontal).Apply(source, sourceRectangle); break; case Orientation.RightTop: - new RotateProcessor((int)RotateType.Rotate90).Apply(source, sourceRectangle); + new RotateProcessor((int)RotateType.Rotate90, size).Apply(source, sourceRectangle); break; case Orientation.RightBottom: new FlipProcessor(FlipType.Vertical).Apply(source, sourceRectangle); - new RotateProcessor((int)RotateType.Rotate270).Apply(source, sourceRectangle); + new RotateProcessor((int)RotateType.Rotate270, size).Apply(source, sourceRectangle); break; case Orientation.LeftBottom: - new RotateProcessor((int)RotateType.Rotate270).Apply(source, sourceRectangle); + new RotateProcessor((int)RotateType.Rotate270, size).Apply(source, sourceRectangle); break; case Orientation.Unknown: diff --git a/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs index 34a0866615..6b8314d172 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs @@ -19,31 +19,22 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The transform matrix /// The sampler to perform the transform operation. - protected CenteredAffineTransformProcessor(Matrix3x2 matrix, IResampler sampler) - : base(matrix, sampler) + /// The source image size + protected CenteredAffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size sourceSize) + : base(matrix, sampler, GetTransformedDimensions(sourceSize, matrix)) { } /// protected override Matrix3x2 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle) { - var translationToTargetCenter = Matrix3x2.CreateTranslation(-destinationRectangle.Width * .5F, -destinationRectangle.Height * .5F); - var translateToSourceCenter = Matrix3x2.CreateTranslation(sourceRectangle.Width * .5F, sourceRectangle.Height * .5F); - return translationToTargetCenter * this.TransformMatrix * translateToSourceCenter; + return TransformHelpers.GetCenteredTransformMatrix(sourceRectangle, destinationRectangle, this.TransformMatrix); } - /// - protected override Size GetTransformedDimensions(Size sourceDimensions, Matrix3x2 matrix) + private static Size GetTransformedDimensions(Size sourceDimensions, Matrix3x2 matrix) { var sourceRectangle = new Rectangle(0, 0, sourceDimensions.Width, sourceDimensions.Height); - - if (!Matrix3x2.Invert(this.TransformMatrix, out Matrix3x2 sizeMatrix)) - { - // TODO: Shouldn't we throw an exception instead? - return sourceDimensions; - } - - return TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, sizeMatrix).Size; + return TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, matrix).Size; } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/CenteredProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CenteredProjectiveTransformProcessor.cs index dc2dd28ab1..081ea84610 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CenteredProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CenteredProjectiveTransformProcessor.cs @@ -19,25 +19,22 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The transform matrix /// The sampler to perform the transform operation. - protected CenteredProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler) - : base(matrix, sampler) + /// The source image size + protected CenteredProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Size sourceSize) + : base(matrix, sampler, GetTransformedDimensions(sourceSize, matrix)) { } /// protected override Matrix4x4 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle) { - var translationToTargetCenter = Matrix4x4.CreateTranslation(-destinationRectangle.Width * .5F, -destinationRectangle.Height * .5F, 0); - var translateToSourceCenter = Matrix4x4.CreateTranslation(sourceRectangle.Width * .5F, sourceRectangle.Height * .5F, 0); - return translationToTargetCenter * this.TransformMatrix * translateToSourceCenter; + return TransformHelpers.GetCenteredTransformMatrix(sourceRectangle, destinationRectangle, this.TransformMatrix); } - /// - protected override Rectangle GetTransformedBoundingRectangle(Rectangle sourceRectangle, Matrix4x4 matrix) + private static Size GetTransformedDimensions(Size sourceDimensions, Matrix4x4 matrix) { - return Matrix4x4.Invert(this.TransformMatrix, out Matrix4x4 sizeMatrix) - ? TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, sizeMatrix) - : sourceRectangle; + var sourceRectangle = new Rectangle(0, 0, sourceDimensions.Width, sourceDimensions.Height); + return TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, matrix).Size; } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs index 00547d0147..c04014ae35 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs @@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; +// TODO: Convert this into a cloning processor inheriting TransformProcessor once Anton's memory PR is merged namespace SixLabors.ImageSharp.Processing.Processors { /// @@ -44,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors int minX = Math.Max(this.CropRectangle.X, sourceRectangle.X); int maxX = Math.Min(this.CropRectangle.Right, sourceRectangle.Right); - using (var targetPixels = new PixelAccessor(this.CropRectangle.Width, this.CropRectangle.Height)) + using (Buffer2D targetPixels = configuration.MemoryManager.Allocate2D(this.CropRectangle.Size)) { Parallel.For( minY, @@ -57,7 +58,7 @@ namespace SixLabors.ImageSharp.Processing.Processors SpanHelper.Copy(sourceRow, targetRow, maxX - minX); }); - source.SwapPixelsBuffers(targetPixels); + Buffer2D.SwapContents(source.PixelBuffer, targetPixels); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs index de60177f2f..9b8b785b58 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs @@ -4,6 +4,7 @@ using System; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Helpers; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; @@ -54,11 +55,10 @@ namespace SixLabors.ImageSharp.Processing.Processors /// The configuration. private void FlipX(ImageFrame source, Configuration configuration) { - int width = source.Width; int height = source.Height; int halfHeight = (int)Math.Ceiling(source.Height * .5F); - using (var targetPixels = new PixelAccessor(width, height)) + using (Buffer2D targetPixels = configuration.MemoryManager.Allocate2D(source.Size())) { Parallel.For( 0, @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Processing.Processors altSourceRow.CopyTo(targetRow); }); - source.SwapPixelsBuffers(targetPixels); + Buffer2D.SwapContents(source.PixelBuffer, targetPixels); } } @@ -92,7 +92,7 @@ namespace SixLabors.ImageSharp.Processing.Processors int height = source.Height; int halfWidth = (int)Math.Ceiling(width * .5F); - using (var targetPixels = new PixelAccessor(width, height)) + using (Buffer2D targetPixels = configuration.MemoryManager.Allocate2D(source.Size())) { Parallel.For( 0, @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Processing.Processors } }); - source.SwapPixelsBuffers(targetPixels); + Buffer2D.SwapContents(source.PixelBuffer, targetPixels); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs index 7e547727e6..e16cd9ee84 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs @@ -22,59 +22,35 @@ namespace SixLabors.ImageSharp.Processing.Processors internal class ProjectiveTransformProcessor : InterpolatedTransformProcessorBase where TPixel : struct, IPixel { - // TODO: We should use a Size instead! (See AffineTransformProcessor) - private Rectangle targetRectangle; - - /// - /// Initializes a new instance of the class. - /// - /// The transform matrix - public ProjectiveTransformProcessor(Matrix4x4 matrix) - : this(matrix, KnownResamplers.Bicubic) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The transform matrix - /// The sampler to perform the transform operation. - public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler) - : this(matrix, sampler, Rectangle.Empty) - { - } - /// /// Initializes a new instance of the class. /// /// The transform matrix /// The sampler to perform the transform operation. - /// The rectangle to constrain the transformed image to. - public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Rectangle rectangle) + /// The target dimensions to constrain the transformed image to. + public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Size targetDimensions) : base(sampler) { - // Transforms are inverted else the output is the opposite of the expected. - Matrix4x4.Invert(matrix, out matrix); this.TransformMatrix = matrix; - this.targetRectangle = rectangle; + this.TargetDimensions = targetDimensions; } /// - /// Gets the matrix used to supply the non-affine transform + /// Gets the matrix used to supply the projective transform /// public Matrix4x4 TransformMatrix { get; } + /// + /// Gets the target dimensions to constrain the transformed image to + /// + public Size TargetDimensions { get; } + /// protected override Image CreateDestination(Image source, Rectangle sourceRectangle) { - if (this.targetRectangle == Rectangle.Empty) - { - this.targetRectangle = this.GetTransformedBoundingRectangle(sourceRectangle, this.TransformMatrix); - } - // 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(this.targetRectangle.Width, this.targetRectangle.Height, x.MetaData.Clone())); + source.Frames.Select(x => new ImageFrame(source.GetMemoryManager(), this.TargetDimensions.Width, this.TargetDimensions.Height, x.MetaData.Clone())); // Use the overload to prevent an extra frame being added return new Image(source.GetConfiguration(), source.MetaData.Clone(), frames); @@ -83,12 +59,17 @@ namespace SixLabors.ImageSharp.Processing.Processors /// protected override void OnApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) { - int height = this.targetRectangle.Height; - int width = this.targetRectangle.Width; + int height = this.TargetDimensions.Height; + int width = this.TargetDimensions.Width; + Rectangle sourceBounds = source.Bounds(); + var targetBounds = new Rectangle(0, 0, width, height); // Since could potentially be resizing the canvas we might need to re-calculate the matrix - Matrix4x4 matrix = this.GetProcessingMatrix(sourceBounds, this.targetRectangle); + Matrix4x4 matrix = this.GetProcessingMatrix(sourceBounds, targetBounds); + + // Convert from screen to world space. + Matrix4x4.Invert(matrix, out matrix); if (this.Sampler is NearestNeighborResampler) { @@ -125,8 +106,10 @@ namespace SixLabors.ImageSharp.Processing.Processors int xLength = (int)MathF.Ceiling((radius.X * 2) + 2); int yLength = (int)MathF.Ceiling((radius.Y * 2) + 2); - using (var yBuffer = new Buffer2D(yLength, height)) - using (var xBuffer = new Buffer2D(xLength, height)) + MemoryManager memoryManager = configuration.MemoryManager; + + using (Buffer2D yBuffer = memoryManager.Allocate2D(yLength, height)) + using (Buffer2D xBuffer = memoryManager.Allocate2D(xLength, height)) { Parallel.For( 0, @@ -199,8 +182,8 @@ namespace SixLabors.ImageSharp.Processing.Processors var vector = source[i, j].ToVector4(); // Values are first premultiplied to prevent darkening of edge pixels - Vector4 mupltiplied = vector.Premultiply(); - sum += mupltiplied * xWeight * yWeight; + Vector4 multiplied = vector.Premultiply(); + sum += multiplied * xWeight * yWeight; } } @@ -225,16 +208,5 @@ namespace SixLabors.ImageSharp.Processing.Processors { return this.TransformMatrix; } - - /// - /// Gets the bounding relative to the source for the given transformation matrix. - /// - /// The source rectangle. - /// The transformation matrix. - /// The - protected virtual Rectangle GetTransformedBoundingRectangle(Rectangle sourceRectangle, Matrix4x4 matrix) - { - return sourceRectangle; - } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs deleted file mode 100644 index b9cb58707c..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; - -namespace SixLabors.ImageSharp.Processing.Processors -{ - /// - /// Provides methods that allow the resizing of images using various algorithms. - /// Adapted from - /// - /// The pixel format. - internal abstract class ResamplingWeightedProcessor : TransformProcessorBase - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The sampler to perform the resize operation. - /// The target width. - /// The target height. - /// - /// The structure that specifies the portion of the target image object to draw to. - /// - protected ResamplingWeightedProcessor(IResampler sampler, int width, int height, Rectangle resizeRectangle) - { - Guard.NotNull(sampler, nameof(sampler)); - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - this.Sampler = sampler; - this.Width = width; - this.Height = height; - this.ResizeRectangle = resizeRectangle; - } - - /// - /// Gets the sampler to perform the resize operation. - /// - public IResampler Sampler { get; } - - /// - /// Gets or sets the width. - /// - public int Width { get; protected set; } - - /// - /// Gets or sets the height. - /// - public int Height { get; protected set; } - - /// - /// Gets or sets the resize rectangle. - /// - public Rectangle ResizeRectangle { get; protected set; } - - /// - /// Gets or sets the horizontal weights. - /// - protected WeightsBuffer HorizontalWeights { get; set; } - - /// - /// Gets or sets the vertical weights. - /// - protected WeightsBuffer VerticalWeights { get; set; } - - /// - /// Computes the weights to apply at each pixel when resizing. - /// - /// The destination size - /// The source size - /// The - // TODO: Made internal to simplify experimenting with weights data. Make it protected again when finished figuring out how to optimize all the stuff! - internal unsafe WeightsBuffer PrecomputeWeights(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(sourceSize, destinationSize); - - for (int i = 0; i < destinationSize; i++) - { - float center = ((i + .5F) * ratio) - .5F; - - // Keep inside bounds. - int left = (int)Math.Ceiling(center - radius); - if (left < 0) - { - left = 0; - } - - int right = (int)Math.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; - } - - // Normalise, 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 = wRef / sum; - } - } - } - - return result; - } - - /// - protected override void BeforeApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) - { - if (!(this.Sampler is NearestNeighborResampler)) - { - this.HorizontalWeights = this.PrecomputeWeights( - this.ResizeRectangle.Width, - sourceRectangle.Width); - - this.VerticalWeights = this.PrecomputeWeights( - this.ResizeRectangle.Height, - sourceRectangle.Height); - } - } - - /// - protected override void AfterApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) - { - base.AfterApply(source, destination, sourceRectangle, configuration); - this.HorizontalWeights?.Dispose(); - this.HorizontalWeights = null; - this.VerticalWeights?.Dispose(); - this.VerticalWeights = null; - } - } -} \ 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 b05d77868f..2e73920baa 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -5,10 +5,10 @@ using System; using System.Collections.Generic; using System.Linq; using System.Numerics; +using System.Runtime.CompilerServices; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; @@ -16,19 +16,62 @@ namespace SixLabors.ImageSharp.Processing.Processors { /// /// Provides methods that allow the resizing of images using various algorithms. + /// Adapted from /// /// The pixel format. - internal class ResizeProcessor : ResamplingWeightedProcessor + internal class ResizeProcessor : TransformProcessorBase where TPixel : struct, IPixel { + // The following fields are not immutable but are optionally created on demand. + private WeightsBuffer horizontalWeights; + private WeightsBuffer verticalWeights; + + /// + /// Initializes a new instance of the class. + /// + /// The resize options + /// The source image size + public ResizeProcessor(ResizeOptions options, Size sourceSize) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(options.Sampler, nameof(options.Sampler)); + + int tempWidth = options.Size.Width; + int tempHeight = 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) + { + tempWidth = (int)MathF.Round(sourceSize.Width * tempHeight / (float)sourceSize.Height); + } + + if (tempHeight == 0 && tempWidth > 0) + { + tempHeight = (int)MathF.Round(sourceSize.Height * tempWidth / (float)sourceSize.Width); + } + + Guard.MustBeGreaterThan(tempWidth, 0, nameof(tempWidth)); + Guard.MustBeGreaterThan(tempHeight, 0, nameof(tempHeight)); + + (Size size, Rectangle rectangle) locationBounds = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options, tempWidth, tempHeight); + + this.Sampler = options.Sampler; + this.Width = locationBounds.size.Width; + this.Height = locationBounds.size.Height; + this.ResizeRectangle = locationBounds.rectangle; + this.Compand = options.Compand; + } + /// /// Initializes a new instance of the class. /// /// The sampler to perform the resize operation. /// The target width. /// The target height. - public ResizeProcessor(IResampler sampler, int width, int height) - : base(sampler, width, height, new Rectangle(0, 0, width, height)) + /// The source image size + public ResizeProcessor(IResampler sampler, int width, int height, Size sourceSize) + : this(sampler, width, height, sourceSize, new Rectangle(0, 0, width, height), false) { } @@ -38,30 +81,161 @@ namespace SixLabors.ImageSharp.Processing.Processors /// The sampler to perform the resize operation. /// The target width. /// The target height. + /// The source image size /// /// The structure that specifies the portion of the target image object to draw to. /// - public ResizeProcessor(IResampler sampler, int width, int height, Rectangle resizeRectangle) - : base(sampler, width, height, resizeRectangle) + /// Whether to compress or expand individual pixel color values on processing. + public ResizeProcessor(IResampler sampler, int width, int height, Size sourceSize, Rectangle resizeRectangle, bool compand) { + Guard.NotNull(sampler, nameof(sampler)); + + // Ensure size is populated across both dimensions. + if (width == 0 && height > 0) + { + width = (int)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); + resizeRectangle.Height = height; + } + + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + this.Sampler = sampler; + this.Width = width; + this.Height = height; + this.ResizeRectangle = resizeRectangle; + this.Compand = compand; } /// - /// Gets or sets a value indicating whether to compress or expand individual pixel color values on processing. + /// Gets the sampler to perform the resize operation. + /// + public IResampler Sampler { get; } + + /// + /// Gets the target width. + /// + public int Width { get; } + + /// + /// Gets the target height. + /// + public int Height { get; } + + /// + /// Gets the resize rectangle. + /// + public Rectangle ResizeRectangle { get; } + + /// + /// Gets a value indicating whether to compress or expand individual pixel color values on processing. + /// + public bool Compand { get; } + + /// + /// Computes the weights to apply at each pixel when resizing. /// - public bool Compand { get; set; } + /// 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(MemoryManager memoryManager, 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(memoryManager, sourceSize, destinationSize); + + for (int i = 0; i < destinationSize; i++) + { + float center = ((i + .5F) * ratio) - .5F; + + // Keep inside bounds. + int left = (int)Math.Ceiling(center - radius); + if (left < 0) + { + left = 0; + } + + int right = (int)Math.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 = 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(this.Width, this.Height, x.MetaData.Clone())); + IEnumerable> frames = source.Frames.Select(x => new ImageFrame(source.GetMemoryManager(), this.Width, this.Height, x.MetaData.Clone())); // this will create places holders // Use the overload to prevent an extra frame being added return new Image(source.GetConfiguration(), source.MetaData.Clone(), frames); } + /// + protected override void BeforeApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) + { + if (!(this.Sampler is NearestNeighborResampler)) + { + // TODO: Optimization opportunity: if we could assume that all frames are of the same size, we can move this into 'BeforeImageApply()` + this.horizontalWeights = this.PrecomputeWeights( + source.MemoryManager, + this.ResizeRectangle.Width, + sourceRectangle.Width); + + this.verticalWeights = this.PrecomputeWeights( + source.MemoryManager, + this.ResizeRectangle.Height, + sourceRectangle.Height); + } + } + /// protected override void OnApply(ImageFrame source, ImageFrame cloned, Rectangle sourceRectangle, Configuration configuration) { @@ -118,38 +292,37 @@ namespace SixLabors.ImageSharp.Processing.Processors // 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 (var firstPassPixels = new Buffer2D(width, source.Height)) + using (Buffer2D firstPassPixels = source.MemoryManager.Allocate2D(width, source.Height)) { - firstPassPixels.Clear(); + firstPassPixels.Buffer.Clear(); - Parallel.For( + ParallelFor.WithTemporaryBuffer( 0, sourceRectangle.Bottom, - configuration.ParallelOptions, - y => + configuration, + source.Width, + (int y, IBuffer tempRowBuffer) => { - // TODO: Without Parallel.For() this buffer object could be reused: - using (var tempRowBuffer = new Buffer(source.Width)) - { - Span firstPassRow = firstPassPixels.GetRowSpan(y); - Span sourceRow = source.GetPixelRowSpan(y); - PixelOperations.Instance.ToVector4(sourceRow, tempRowBuffer, sourceRow.Length); + Span firstPassRow = firstPassPixels.GetRowSpan(y); + Span sourceRow = source.GetPixelRowSpan(y); + Span tempRowSpan = tempRowBuffer.Span; + + PixelOperations.Instance.ToVector4(sourceRow, tempRowSpan, sourceRow.Length); - if (this.Compand) + if (this.Compand) + { + for (int x = minX; x < maxX; x++) { - for (int x = minX; x < maxX; x++) - { - WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; - firstPassRow[x] = window.ComputeExpandedWeightedRowSum(tempRowBuffer, sourceX); - } + WeightsWindow window = this.horizontalWeights.Weights[x - startX]; + firstPassRow[x] = window.ComputeExpandedWeightedRowSum(tempRowSpan, sourceX); } - else + } + else + { + for (int x = minX; x < maxX; x++) { - for (int x = minX; x < maxX; x++) - { - WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; - firstPassRow[x] = window.ComputeWeightedRowSum(tempRowBuffer, sourceX); - } + WeightsWindow window = this.horizontalWeights.Weights[x - startX]; + firstPassRow[x] = window.ComputeWeightedRowSum(tempRowSpan, sourceX); } } }); @@ -161,8 +334,8 @@ namespace SixLabors.ImageSharp.Processing.Processors configuration.ParallelOptions, y => { - // Ensure offsets are normalised for cropping and padding. - WeightsWindow window = this.VerticalWeights.Weights[y - startY]; + // Ensure offsets are normalized for cropping and padding. + WeightsWindow window = this.verticalWeights.Weights[y - startY]; Span targetRow = cloned.GetPixelRowSpan(y); if (this.Compand) @@ -191,5 +364,17 @@ namespace SixLabors.ImageSharp.Processing.Processors }); } } + + /// + protected override void AfterApply(ImageFrame source, ImageFrame destination, Rectangle sourceRectangle, Configuration configuration) + { + base.AfterApply(source, destination, sourceRectangle, configuration); + + // 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; + } } } \ 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 b93c869152..824ae4310f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs @@ -22,8 +22,9 @@ namespace SixLabors.ImageSharp.Processing.Processors /// Initializes a new instance of the class. /// /// The angle of rotation in degrees. - public RotateProcessor(float degrees) - : this(degrees, KnownResamplers.Bicubic) + /// The source image size + public RotateProcessor(float degrees, Size sourceSize) + : this(degrees, KnownResamplers.Bicubic, sourceSize) { } @@ -32,8 +33,9 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The angle of rotation in degrees. /// The sampler to perform the rotating operation. - public RotateProcessor(float degrees, IResampler sampler) - : base(Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty), sampler) + /// The source image size + public RotateProcessor(float degrees, IResampler sampler, Size sourceSize) + : base(Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty), sampler, sourceSize) { this.Degrees = degrees; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs index 8c47b35274..8e3ab7c34f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs @@ -18,8 +18,9 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The angle in degrees to perform the skew along the x-axis. /// The angle in degrees to perform the skew along the y-axis. - public SkewProcessor(float degreesX, float degreesY) - : this(degreesX, degreesY, KnownResamplers.Bicubic) + /// The source image size + public SkewProcessor(float degreesX, float degreesY, Size sourceSize) + : this(degreesX, degreesY, KnownResamplers.Bicubic, sourceSize) { } @@ -29,8 +30,9 @@ namespace SixLabors.ImageSharp.Processing.Processors /// The angle in degrees to perform the skew along the x-axis. /// The angle in degrees to perform the skew along the y-axis. /// The sampler to perform the skew operation. - public SkewProcessor(float degreesX, float degreesY, IResampler sampler) - : base(Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty), sampler) + /// The source image size + public SkewProcessor(float degreesX, float degreesY, IResampler sampler, Size sourceSize) + : base(Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty), sampler, sourceSize) { this.DegreesX = degreesX; this.DegreesY = degreesY; diff --git a/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs b/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs index 0e91087f90..d633a3869f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/WeightsBuffer.cs @@ -17,11 +17,12 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// Initializes a new instance of the class. /// + /// The MemoryManager to use for allocations. /// The size of the source window /// The size of the destination window - public WeightsBuffer(int sourceSize, int destinationSize) + public WeightsBuffer(MemoryManager memoryManager, int sourceSize, int destinationSize) { - this.dataBuffer = Buffer2D.CreateClean(sourceSize, destinationSize); + this.dataBuffer = memoryManager.Allocate2D(sourceSize, destinationSize, true); this.Weights = new WeightsWindow[destinationSize]; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs b/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs index b0a530514e..26aaec502f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/WeightsWindow.cs @@ -4,7 +4,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; - +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Processing.Processors @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The buffer containing the weights values. /// - private readonly Buffer buffer; + private readonly IBuffer buffer; /// /// Initializes a new instance of the struct. @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Processing.Processors { this.flatStartIndex = (index * buffer.Width) + left; this.Left = left; - this.buffer = buffer; + this.buffer = buffer.Buffer; this.Length = length; } @@ -57,7 +57,8 @@ namespace SixLabors.ImageSharp.Processing.Processors [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref float GetStartReference() { - return ref this.buffer[this.flatStartIndex]; + Span span = this.buffer.Span; + return ref span[this.flatStartIndex]; } /// @@ -78,7 +79,7 @@ namespace SixLabors.ImageSharp.Processing.Processors { ref float horizontalValues = ref this.GetStartReference(); int left = this.Left; - ref Vector4 vecPtr = ref Unsafe.Add(ref rowSpan.DangerousGetPinnableReference(), left + sourceX); + ref Vector4 vecPtr = ref Unsafe.Add(ref MemoryMarshal.GetReference(rowSpan), left + sourceX); // Destination color components Vector4 result = Vector4.Zero; @@ -105,7 +106,7 @@ namespace SixLabors.ImageSharp.Processing.Processors { ref float horizontalValues = ref this.GetStartReference(); int left = this.Left; - ref Vector4 vecPtr = ref Unsafe.Add(ref rowSpan.DangerousGetPinnableReference(), left + sourceX); + ref Vector4 vecPtr = ref Unsafe.Add(ref MemoryMarshal.GetReference(rowSpan), left + sourceX); // Destination color components Vector4 result = Vector4.Zero; diff --git a/src/ImageSharp/Processing/Transforms/Options/ResizeHelper.cs b/src/ImageSharp/Processing/Transforms/Options/ResizeHelper.cs index 17a0cc428f..ba6f4509d8 100644 --- a/src/ImageSharp/Processing/Transforms/Options/ResizeHelper.cs +++ b/src/ImageSharp/Processing/Transforms/Options/ResizeHelper.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing @@ -17,52 +16,39 @@ namespace SixLabors.ImageSharp.Processing /// /// Calculates the target location and bounds to perform the resize operation against. /// - /// The pixel format. - /// The source image. + /// The source image size. /// The resize options. + /// The target width + /// The target height /// - /// The . + /// The . /// - public static Rectangle CalculateTargetLocationAndBounds(ImageFrame source, ResizeOptions options) - where TPixel : struct, IPixel + public static (Size, Rectangle) CalculateTargetLocationAndBounds(Size sourceSize, ResizeOptions options, int width, int height) { switch (options.Mode) { case ResizeMode.Crop: - return CalculateCropRectangle(source, options); + return CalculateCropRectangle(sourceSize, options, width, height); case ResizeMode.Pad: - return CalculatePadRectangle(source, options); + return CalculatePadRectangle(sourceSize, options, width, height); case ResizeMode.BoxPad: - return CalculateBoxPadRectangle(source, options); + return CalculateBoxPadRectangle(sourceSize, options, width, height); case ResizeMode.Max: - return CalculateMaxRectangle(source, options); + return CalculateMaxRectangle(sourceSize, options, width, height); case ResizeMode.Min: - return CalculateMinRectangle(source, options); + return CalculateMinRectangle(sourceSize, options, width, height); // Last case ResizeMode.Stretch: default: - return new Rectangle(0, 0, options.Size.Width, options.Size.Height); + return (new Size(width, height), new Rectangle(0, 0, width, height)); } } - /// - /// Calculates the target rectangle for crop mode. - /// - /// The pixel format. - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculateCropRectangle(ImageFrame source, ResizeOptions options) - where TPixel : struct, IPixel + private static (Size, Rectangle) CalculateCropRectangle(Size source, ResizeOptions options, int width, int height) { - int width = options.Size.Width; - int height = options.Size.Height; - if (width <= 0 || height <= 0) { - return new Rectangle(0, 0, source.Width, source.Height); + return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height)); } float ratio; @@ -161,27 +147,14 @@ namespace SixLabors.ImageSharp.Processing destinationWidth = (int)MathF.Ceiling(sourceWidth * percentHeight); } - return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); + return (new Size(width, height), new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); } - /// - /// Calculates the target rectangle for pad mode. - /// - /// The pixel format. - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculatePadRectangle(ImageFrame source, ResizeOptions options) - where TPixel : struct, IPixel + private static (Size, Rectangle) CalculatePadRectangle(Size source, ResizeOptions options, int width, int height) { - int width = options.Size.Width; - int height = options.Size.Height; - if (width <= 0 || height <= 0) { - return new Rectangle(0, 0, source.Width, source.Height); + return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height)); } float ratio; @@ -242,27 +215,14 @@ namespace SixLabors.ImageSharp.Processing } } - return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); + return (new Size(width, height), new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); } - /// - /// Calculates the target rectangle for box pad mode. - /// - /// The pixel format. - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculateBoxPadRectangle(ImageFrame source, ResizeOptions options) - where TPixel : struct, IPixel + private static (Size, Rectangle) CalculateBoxPadRectangle(Size source, ResizeOptions options, int width, int height) { - int width = options.Size.Width; - int height = options.Size.Height; - if (width <= 0 || height <= 0) { - return new Rectangle(0, 0, source.Width, source.Height); + return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height)); } int sourceWidth = source.Width; @@ -325,27 +285,15 @@ namespace SixLabors.ImageSharp.Processing break; } - return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); + return (new Size(width, height), new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); } // Switch to pad mode to downscale and calculate from there. - return CalculatePadRectangle(source, options); + return CalculatePadRectangle(source, options, width, height); } - /// - /// Calculates the target rectangle for max mode. - /// - /// The pixel format. - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculateMaxRectangle(ImageFrame source, ResizeOptions options) - where TPixel : struct, IPixel + private static (Size, Rectangle) CalculateMaxRectangle(Size source, ResizeOptions options, int width, int height) { - int width = options.Size.Width; - int height = options.Size.Height; int destinationWidth = width; int destinationHeight = height; @@ -369,24 +317,11 @@ namespace SixLabors.ImageSharp.Processing } // Replace the size to match the rectangle. - options.Size = new Size(width, height); - return new Rectangle(0, 0, destinationWidth, destinationHeight); + return (new Size(width, height), new Rectangle(0, 0, destinationWidth, destinationHeight)); } - /// - /// Calculates the target rectangle for min mode. - /// - /// The pixel format. - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculateMinRectangle(ImageFrame source, ResizeOptions options) - where TPixel : struct, IPixel + private static (Size, Rectangle) CalculateMinRectangle(Size source, ResizeOptions options, int width, int height) { - int width = options.Size.Width; - int height = options.Size.Height; int sourceWidth = source.Width; int sourceHeight = source.Height; int destinationWidth; @@ -395,8 +330,7 @@ namespace SixLabors.ImageSharp.Processing // Don't upscale if (width > sourceWidth || height > sourceHeight) { - options.Size = new Size(sourceWidth, sourceHeight); - return new Rectangle(0, 0, sourceWidth, sourceHeight); + return (new Size(sourceWidth, sourceWidth), new Rectangle(0, 0, sourceWidth, sourceHeight)); } // Fractional variants for preserving aspect ratio. @@ -438,8 +372,7 @@ namespace SixLabors.ImageSharp.Processing } // Replace the size to match the rectangle. - options.Size = new Size(width, height); - return new Rectangle(0, 0, destinationWidth, destinationHeight); + return (new Size(width, height), new Rectangle(0, 0, destinationWidth, destinationHeight)); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Transforms/Options/ResizeOptions.cs b/src/ImageSharp/Processing/Transforms/Options/ResizeOptions.cs index d20eaefb11..f13fa77c99 100644 --- a/src/ImageSharp/Processing/Transforms/Options/ResizeOptions.cs +++ b/src/ImageSharp/Processing/Transforms/Options/ResizeOptions.cs @@ -41,6 +41,6 @@ namespace SixLabors.ImageSharp.Processing /// Gets or sets a value indicating whether to compress /// or expand individual pixel colors the value on processing. /// - public bool Compand { get; set; } + public bool Compand { get; set; } = false; } } diff --git a/src/ImageSharp/Processing/Transforms/Pad.cs b/src/ImageSharp/Processing/Transforms/Pad.cs index eb0f2e9c27..2f637aa162 100644 --- a/src/ImageSharp/Processing/Transforms/Pad.cs +++ b/src/ImageSharp/Processing/Transforms/Pad.cs @@ -1,10 +1,8 @@ // 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; using SixLabors.Primitives; namespace SixLabors.ImageSharp @@ -25,11 +23,11 @@ namespace SixLabors.ImageSharp public static IImageProcessingContext Pad(this IImageProcessingContext source, int width, int height) where TPixel : struct, IPixel { - ResizeOptions options = new ResizeOptions + var options = new ResizeOptions { Size = new Size(width, height), Mode = ResizeMode.BoxPad, - Sampler = new NearestNeighborResampler() + Sampler = KnownResamplers.NearestNeighbor }; return Resize(source, options); diff --git a/src/ImageSharp/Processing/Transforms/Resize.cs b/src/ImageSharp/Processing/Transforms/Resize.cs index 832b02dea7..285f3206dc 100644 --- a/src/ImageSharp/Processing/Transforms/Resize.cs +++ b/src/ImageSharp/Processing/Transforms/Resize.cs @@ -1,7 +1,6 @@ // 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; @@ -24,29 +23,10 @@ namespace SixLabors.ImageSharp /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image public static IImageProcessingContext Resize(this IImageProcessingContext source, ResizeOptions options) where TPixel : struct, IPixel - { - return source.Apply(img => - { - // Cheat and bound through a run, inside here we should just be mutating, this really needs moving over to a processor - // Ensure size is populated across both dimensions. - if (options.Size.Width == 0 && options.Size.Height > 0) - { - options.Size = new Size((int)MathF.Round(img.Width * options.Size.Height / (float)img.Height), options.Size.Height); - } - - if (options.Size.Height == 0 && options.Size.Width > 0) - { - options.Size = new Size(options.Size.Width, (int)MathF.Round(img.Height * options.Size.Width / (float)img.Width)); - } - - Rectangle targetRectangle = ResizeHelper.CalculateTargetLocationAndBounds(img.Frames.RootFrame, options); - - img.Mutate(x => Resize(x, options.Size.Width, options.Size.Height, options.Sampler, targetRectangle, options.Compand)); - }); - } + => source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize())); /// - /// Resizes an image to the given . + /// Resizes an image to the given . /// /// The pixel format. /// The image to resize. @@ -55,12 +35,10 @@ namespace SixLabors.ImageSharp /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size) where TPixel : struct, IPixel - { - return Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, false); - } + => Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, false); /// - /// Resizes an image to the given . + /// Resizes an image to the given . /// /// The pixel format. /// The image to resize. @@ -70,9 +48,7 @@ namespace SixLabors.ImageSharp /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size, bool compand) where TPixel : struct, IPixel - { - return Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, compand); - } + => Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, compand); /// /// Resizes an image to the given width and height. @@ -85,9 +61,7 @@ namespace SixLabors.ImageSharp /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height) where TPixel : struct, IPixel - { - return Resize(source, width, height, KnownResamplers.Bicubic, false); - } + => Resize(source, width, height, KnownResamplers.Bicubic, false); /// /// Resizes an image to the given width and height. @@ -101,9 +75,7 @@ namespace SixLabors.ImageSharp /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, bool compand) where TPixel : struct, IPixel - { - return Resize(source, width, height, KnownResamplers.Bicubic, compand); - } + => Resize(source, width, height, KnownResamplers.Bicubic, compand); /// /// Resizes an image to the given width and height with the given sampler. @@ -117,9 +89,7 @@ namespace SixLabors.ImageSharp /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler) where TPixel : struct, IPixel - { - return Resize(source, width, height, sampler, false); - } + => Resize(source, width, height, sampler, false); /// /// Resizes an image to the given width and height with the given sampler. @@ -133,9 +103,7 @@ namespace SixLabors.ImageSharp /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size, IResampler sampler, bool compand) where TPixel : struct, IPixel - { - return Resize(source, size.Width, size.Height, sampler, new Rectangle(0, 0, size.Width, size.Height), compand); - } + => Resize(source, size.Width, size.Height, sampler, new Rectangle(0, 0, size.Width, size.Height), compand); /// /// Resizes an image to the given width and height with the given sampler. @@ -150,9 +118,7 @@ namespace SixLabors.ImageSharp /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler, bool compand) where TPixel : struct, IPixel - { - return Resize(source, width, height, sampler, new Rectangle(0, 0, width, height), compand); - } + => Resize(source, width, height, sampler, new Rectangle(0, 0, width, height), compand); /// /// Resizes an image to the given width and height with the given sampler and @@ -172,34 +138,19 @@ namespace SixLabors.ImageSharp /// 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 - public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand) + public static IImageProcessingContext Resize( + this IImageProcessingContext source, + int width, + int height, + IResampler sampler, + Rectangle sourceRectangle, + Rectangle targetRectangle, + bool compand) where TPixel : struct, IPixel - { - return source.Apply(img => - { - // TODO : Stop cheating here and move this stuff into the processors itself - if (width == 0 && height > 0) - { - width = (int)MathF.Round(img.Width * height / (float)img.Height); - targetRectangle.Width = width; - } - - if (height == 0 && width > 0) - { - height = (int)MathF.Round(img.Height * width / (float)img.Width); - targetRectangle.Height = height; - } - - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - img.Mutate(x => x.ApplyProcessor(new ResizeProcessor(sampler, width, height, targetRectangle) { Compand = compand }, sourceRectangle)); - }); - } + => source.ApplyProcessor(new ResizeProcessor(sampler, width, height, source.GetCurrentSize(), targetRectangle, compand), sourceRectangle); /// - /// Resizes an image to the given width and height with the given sampler and - /// source rectangle. + /// Resizes an image to the given width and height with the given sampler and source rectangle. /// /// The pixel format. /// The image to resize. @@ -212,29 +163,14 @@ namespace SixLabors.ImageSharp /// 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 - public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler, Rectangle targetRectangle, bool compand) + public static IImageProcessingContext Resize( + this IImageProcessingContext source, + int width, + int height, + IResampler sampler, + Rectangle targetRectangle, + bool compand) where TPixel : struct, IPixel - { - return source.Apply(img => - { - // TODO : stop cheating here and move this stuff into the processors itself - if (width == 0 && height > 0) - { - width = (int)MathF.Round(img.Width * height / (float)img.Height); - targetRectangle.Width = width; - } - - if (height == 0 && width > 0) - { - height = (int)MathF.Round(img.Height * width / (float)img.Width); - targetRectangle.Height = height; - } - - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - img.Mutate(x => x.ApplyProcessor(new ResizeProcessor(sampler, width, height, targetRectangle) { Compand = compand })); - }); - } + => source.ApplyProcessor(new ResizeProcessor(sampler, width, height, source.GetCurrentSize(), targetRectangle, compand)); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Transforms/Rotate.cs b/src/ImageSharp/Processing/Transforms/Rotate.cs index 69fb7ebf0c..faecdf91f9 100644 --- a/src/ImageSharp/Processing/Transforms/Rotate.cs +++ b/src/ImageSharp/Processing/Transforms/Rotate.cs @@ -44,6 +44,6 @@ namespace SixLabors.ImageSharp /// The public static IImageProcessingContext Rotate(this IImageProcessingContext source, float degrees, IResampler sampler) where TPixel : struct, IPixel - => source.ApplyProcessor(new RotateProcessor(degrees, sampler)); + => source.ApplyProcessor(new RotateProcessor(degrees, sampler, source.GetCurrentSize())); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Transforms/Skew.cs b/src/ImageSharp/Processing/Transforms/Skew.cs index 0613a690b8..fb054211a0 100644 --- a/src/ImageSharp/Processing/Transforms/Skew.cs +++ b/src/ImageSharp/Processing/Transforms/Skew.cs @@ -35,6 +35,6 @@ namespace SixLabors.ImageSharp /// The public static IImageProcessingContext Skew(this IImageProcessingContext source, float degreesX, float degreesY, IResampler sampler) where TPixel : struct, IPixel - => source.ApplyProcessor(new SkewProcessor(degreesX, degreesY, sampler)); + => source.ApplyProcessor(new SkewProcessor(degreesX, degreesY, sampler, source.GetCurrentSize())); } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Transforms/Transform.cs b/src/ImageSharp/Processing/Transforms/Transform.cs index e39da8dc0f..66d5d5de7e 100644 --- a/src/ImageSharp/Processing/Transforms/Transform.cs +++ b/src/ImageSharp/Processing/Transforms/Transform.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp /// The public static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix3x2 matrix) where TPixel : struct, IPixel - => Transform(source, matrix, KnownResamplers.NearestNeighbor); + => Transform(source, matrix, KnownResamplers.Bicubic); /// /// Transforms an image by the given matrix using the specified sampling algorithm. @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp /// The public static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix3x2 matrix, IResampler sampler) where TPixel : struct, IPixel - => Transform(source, matrix, sampler, Size.Empty); + => source.ApplyProcessor(new AffineTransformProcessor(matrix, sampler, source.GetCurrentSize())); /// /// Transforms an image by the given matrix using the specified sampling algorithm @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp /// The internal static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix4x4 matrix) where TPixel : struct, IPixel - => Transform(source, matrix, KnownResamplers.NearestNeighbor); + => Transform(source, matrix, KnownResamplers.Bicubic); /// /// Applies a projective transform to the image by the given matrix using the specified sampling algorithm. @@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp /// The internal static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix4x4 matrix, IResampler sampler) where TPixel : struct, IPixel - => Transform(source, matrix, sampler, Rectangle.Empty); + => source.ApplyProcessor(new ProjectiveTransformProcessor(matrix, sampler, source.GetCurrentSize())); /// /// Applies a projective transform to the image by the given matrix using the specified sampling algorithm. @@ -117,6 +117,10 @@ namespace SixLabors.ImageSharp /// The internal static IImageProcessingContext Transform(this IImageProcessingContext source, Matrix4x4 matrix, IResampler sampler, Rectangle rectangle) where TPixel : struct, IPixel - => source.ApplyProcessor(new ProjectiveTransformProcessor(matrix, sampler, rectangle)); + { + var t = Matrix4x4.CreateTranslation(new Vector3(-rectangle.Location, 0)); + Matrix4x4 combinedMatrix = t * matrix; + return source.ApplyProcessor(new ProjectiveTransformProcessor(combinedMatrix, sampler, rectangle.Size)); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Transforms/TransformHelpers.cs b/src/ImageSharp/Processing/Transforms/TransformHelpers.cs index bfb06c4707..1567c11619 100644 --- a/src/ImageSharp/Processing/Transforms/TransformHelpers.cs +++ b/src/ImageSharp/Processing/Transforms/TransformHelpers.cs @@ -59,7 +59,51 @@ namespace SixLabors.ImageSharp } /// - /// Returns the bounding relative to the source for the given transformation matrix. + /// Gets the centered transform matrix based upon the source and destination rectangles + /// + /// The source image bounds. + /// The destination image bounds. + /// The transformation matrix. + /// The + public static Matrix3x2 GetCenteredTransformMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle, Matrix3x2 matrix) + { + // We invert the matrix to handle the transformation from screen to world space. + // This ensures scaling matrices are correct. + Matrix3x2.Invert(matrix, out Matrix3x2 inverted); + + var translationToTargetCenter = Matrix3x2.CreateTranslation(-destinationRectangle.Width * .5F, -destinationRectangle.Height * .5F); + var translateToSourceCenter = Matrix3x2.CreateTranslation(sourceRectangle.Width * .5F, sourceRectangle.Height * .5F); + + Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered); + + // Translate back to world to pass to the Transform method. + return centered; + } + + /// + /// Gets the centered transform matrix based upon the source and destination rectangles + /// + /// The source image bounds. + /// The destination image bounds. + /// The transformation matrix. + /// The + public static Matrix4x4 GetCenteredTransformMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle, Matrix4x4 matrix) + { + // We invert the matrix to handle the transformation from screen to world space. + // This ensures scaling matrices are correct. + Matrix4x4.Invert(matrix, out Matrix4x4 inverted); + + var translationToTargetCenter = Matrix4x4.CreateTranslation(-destinationRectangle.Width * .5F, -destinationRectangle.Height * .5F, 0); + var translateToSourceCenter = Matrix4x4.CreateTranslation(sourceRectangle.Width * .5F, sourceRectangle.Height * .5F, 0); + + Matrix4x4.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix4x4 centered); + + // Translate back to world to pass to the Transform method. + return centered; + } + + /// + /// Returns the bounding rectangle relative to the source for the given transformation matrix. /// /// The source rectangle. /// The transformation matrix. @@ -79,7 +123,7 @@ namespace SixLabors.ImageSharp } /// - /// Returns the bounding relative to the source for the given transformation matrix. + /// Returns the bounding rectangle relative to the source for the given transformation matrix. /// /// The source rectangle. /// The transformation matrix. diff --git a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs index d646a680ea..8b8db61777 100644 --- a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs @@ -107,7 +107,7 @@ namespace SixLabors.ImageSharp.Quantizers if (this.Dither) { // Apply the dithering matrix. We have to reapply the value now as the original has changed. - this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height, false); + this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height); } output[(y * source.Width) + x] = pixelValue; diff --git a/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs index 0b95c09a62..cd1b4b07b1 100644 --- a/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs @@ -12,6 +12,7 @@ namespace SixLabors.ImageSharp.Quantizers { /// /// Encapsulates methods to create a quantized image based upon the given palette. + /// If no palette is given this will default to the web safe colors defined in the CSS Color Module Level 4. /// /// /// The pixel format. @@ -31,27 +32,20 @@ namespace SixLabors.ImageSharp.Quantizers /// /// Initializes a new instance of the class. /// - /// - /// The color palette. If none is given this will default to the web safe colors defined - /// in the CSS Color Module Level 4. - /// + public PaletteQuantizer() + : this(NamedColors.WebSafePalette) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The palette to select substitute colors from. public PaletteQuantizer(TPixel[] palette = null) : base(true) { - if (palette == null) - { - Rgba32[] constants = ColorConstants.WebSafeColors; - TPixel[] safe = new TPixel[constants.Length + 1]; - - Span constantsBytes = constants.AsSpan().NonPortableCast(); - - PixelOperations.Instance.PackFromRgba32Bytes(constantsBytes, safe, constants.Length); - this.colors = safe; - } - else - { - this.colors = palette; - } + Guard.NotNull(palette, nameof(palette)); + this.colors = palette; } /// @@ -102,7 +96,7 @@ namespace SixLabors.ImageSharp.Quantizers if (this.Dither) { // Apply the dithering matrix. We have to reapply the value now as the original has changed. - this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height, false); + this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height); } output[(y * source.Width) + x] = pixelValue; diff --git a/src/ImageSharp/Quantizers/Quantize.cs b/src/ImageSharp/Quantizers/Quantize.cs index d99d4af34c..f2a09abb77 100644 --- a/src/ImageSharp/Quantizers/Quantize.cs +++ b/src/ImageSharp/Quantizers/Quantize.cs @@ -4,6 +4,7 @@ using System; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Quantizers; @@ -61,23 +62,25 @@ namespace SixLabors.ImageSharp QuantizedImage quantized = quantizer.Quantize(img.Frames.RootFrame, maxColors); int palleteCount = quantized.Palette.Length - 1; - using (var pixels = new PixelAccessor(quantized.Width, quantized.Height)) + using (Buffer2D pixels = source.MemoryManager.Allocate2D(quantized.Width, quantized.Height)) { Parallel.For( 0, pixels.Height, img.GetConfiguration().ParallelOptions, y => - { - for (int x = 0; x < pixels.Width; x++) { - int i = x + (y * pixels.Width); - TPixel color = quantized.Palette[Math.Min(palleteCount, quantized.Pixels[i])]; - pixels[x, y] = color; - } - }); + Span row = pixels.GetRowSpan(y); + int yy = y * pixels.Width; + for (int x = 0; x < pixels.Width; x++) + { + int i = x + yy; + TPixel color = quantized.Palette[Math.Min(palleteCount, quantized.Pixels[i])]; + row[x] = color; + } + }); - img.Frames[0].SwapPixelsBuffers(pixels); + Buffer2D.SwapContents(img.Frames[0].PixelBuffer, pixels); } }); } diff --git a/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs b/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs index 20ba2e637e..0b41edf98d 100644 --- a/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs +++ b/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Quantizers.Base public bool Dither { get; set; } = true; /// - public IErrorDiffuser DitherType { get; set; } = new FloydSteinbergDiffuser(); + public IErrorDiffuser DitherType { get; set; } = KnownDiffusers.FloydSteinberg; /// public virtual QuantizedImage Quantize(ImageFrame image, int maxColors) @@ -115,6 +115,7 @@ namespace SixLabors.ImageSharp.Quantizers.Base /// /// Override this to process the pixel in the first pass of the algorithm + /// TODO: We really should do this on a per-row basis! Shouldn't we internalize this method? /// /// The pixel to quantize /// diff --git a/src/ImageSharp/Quantizers/WuArrayPool.cs b/src/ImageSharp/Quantizers/WuArrayPool.cs deleted file mode 100644 index 04a70637b6..0000000000 --- a/src/ImageSharp/Quantizers/WuArrayPool.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Buffers; - -namespace SixLabors.ImageSharp.Quantizers -{ - /// - /// Provides array pooling for the . - /// This is a separate class so that the pools can be shared accross multiple generic quantizer instaces. - /// - internal static class WuArrayPool - { - /// - /// The long array pool. - /// - public static readonly ArrayPool LongPool = ArrayPool.Create(TableLength, 25); - - /// - /// The float array pool. - /// - public static readonly ArrayPool FloatPool = ArrayPool.Create(TableLength, 5); - - /// - /// The byte array pool. - /// - public static readonly ArrayPool BytePool = ArrayPool.Create(TableLength, 5); - - /// - /// The table length. Matches the calculated value in - /// - private const int TableLength = 2471625; - } -} \ No newline at end of file diff --git a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs index f08114574e..0aadf49732 100644 --- a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Quantizers.Base; @@ -35,6 +36,14 @@ namespace SixLabors.ImageSharp.Quantizers public class WuQuantizer : QuantizerBase where TPixel : struct, IPixel { + // TODO: The WuQuantizer code is rising several questions: + // - Do we really need to ALWAYS allocate the whole table of size TableLength? (~ 2471625 * sizeof(long) * 5 bytes ) + // - 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! + // - There are per-pixel virtual calls in InitialQuantizePixel, why not do it on a per-row basis? + // - 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! + // https://github.com/JeremyAnsel/JeremyAnsel.ColorQuant/blob/master/JeremyAnsel.ColorQuant/JeremyAnsel.ColorQuant.Tests/WuColorQuantizerTests.cs + /// /// The index bits. /// @@ -68,37 +77,37 @@ namespace SixLabors.ImageSharp.Quantizers /// /// Moment of P(c). /// - private long[] vwt; + private IBuffer vwt; /// /// Moment of r*P(c). /// - private long[] vmr; + private IBuffer vmr; /// /// Moment of g*P(c). /// - private long[] vmg; + private IBuffer vmg; /// /// Moment of b*P(c). /// - private long[] vmb; + private IBuffer vmb; /// /// Moment of a*P(c). /// - private long[] vma; + private IBuffer vma; /// /// Moment of c^2*P(c). /// - private float[] m2; + private IBuffer m2; /// /// Color space tag. /// - private byte[] tag; + private IBuffer tag; /// /// Maximum allowed color depth @@ -136,27 +145,29 @@ namespace SixLabors.ImageSharp.Quantizers this.palette = null; this.colorMap.Clear(); + MemoryManager memoryManager = image.MemoryManager; + try { - this.vwt = WuArrayPool.LongPool.Rent(TableLength); - this.vmr = WuArrayPool.LongPool.Rent(TableLength); - this.vmg = WuArrayPool.LongPool.Rent(TableLength); - this.vmb = WuArrayPool.LongPool.Rent(TableLength); - this.vma = WuArrayPool.LongPool.Rent(TableLength); - this.m2 = WuArrayPool.FloatPool.Rent(TableLength); - this.tag = WuArrayPool.BytePool.Rent(TableLength); + this.vwt = memoryManager.AllocateClean(TableLength); + this.vmr = memoryManager.AllocateClean(TableLength); + this.vmg = memoryManager.AllocateClean(TableLength); + this.vmb = memoryManager.AllocateClean(TableLength); + this.vma = memoryManager.AllocateClean(TableLength); + this.m2 = memoryManager.AllocateClean(TableLength); + this.tag = memoryManager.AllocateClean(TableLength); return base.Quantize(image, this.colors); } finally { - WuArrayPool.LongPool.Return(this.vwt, true); - WuArrayPool.LongPool.Return(this.vmr, true); - WuArrayPool.LongPool.Return(this.vmg, true); - WuArrayPool.LongPool.Return(this.vmb, true); - WuArrayPool.LongPool.Return(this.vma, true); - WuArrayPool.FloatPool.Return(this.m2, true); - WuArrayPool.BytePool.Return(this.tag, true); + this.vwt.Dispose(); + this.vmr.Dispose(); + this.vmg.Dispose(); + this.vmb.Dispose(); + this.vma.Dispose(); + this.m2.Dispose(); + this.tag.Dispose(); } } @@ -170,14 +181,14 @@ namespace SixLabors.ImageSharp.Quantizers { this.Mark(ref this.colorCube[k], (byte)k); - float weight = Volume(ref this.colorCube[k], this.vwt); + float weight = Volume(ref this.colorCube[k], this.vwt.Span); if (MathF.Abs(weight) > Constants.Epsilon) { - float r = Volume(ref this.colorCube[k], this.vmr); - float g = Volume(ref this.colorCube[k], this.vmg); - float b = Volume(ref this.colorCube[k], this.vmb); - float a = Volume(ref this.colorCube[k], this.vma); + float r = Volume(ref this.colorCube[k], this.vmr.Span); + float g = Volume(ref this.colorCube[k], this.vmg.Span); + float b = Volume(ref this.colorCube[k], this.vmb.Span); + float a = Volume(ref this.colorCube[k], this.vma.Span); ref TPixel color = ref this.palette[k]; color.PackFromVector4(new Vector4(r, g, b, a) / weight / 255F); @@ -203,14 +214,21 @@ namespace SixLabors.ImageSharp.Quantizers int index = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); - this.vwt[index]++; - this.vmr[index] += rgba.R; - this.vmg[index] += rgba.G; - this.vmb[index] += rgba.B; - this.vma[index] += rgba.A; + Span vwtSpan = this.vwt.Span; + Span vmrSpan = this.vmr.Span; + Span vmgSpan = this.vmg.Span; + Span vmbSpan = this.vmb.Span; + Span vmaSpan = this.vma.Span; + Span m2Span = this.m2.Span; + + 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); - this.m2[index] += Vector4.Dot(vector, vector); + m2Span[index] += Vector4.Dot(vector, vector); } /// @@ -228,7 +246,7 @@ namespace SixLabors.ImageSharp.Quantizers } } - this.Get3DMoments(); + this.Get3DMoments(source.MemoryManager); this.BuildCube(); } @@ -272,7 +290,7 @@ namespace SixLabors.ImageSharp.Quantizers if (this.Dither) { // Apply the dithering matrix. We have to reapply the value now as the original has changed. - this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height, false); + this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, 0, 0, width, height); } output[(y * source.Width) + x] = pixelValue; @@ -302,7 +320,7 @@ namespace SixLabors.ImageSharp.Quantizers /// The cube. /// The moment. /// The result. - private static float Volume(ref Box cube, long[] moment) + private static float Volume(ref Box cube, Span moment) { return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] @@ -329,7 +347,7 @@ namespace SixLabors.ImageSharp.Quantizers /// The direction. /// The moment. /// The result. - private static long Bottom(ref Box cube, int direction, long[] moment) + private static long Bottom(ref Box cube, int direction, Span moment) { switch (direction) { @@ -390,7 +408,7 @@ namespace SixLabors.ImageSharp.Quantizers /// The position. /// The moment. /// The result. - private static long Top(ref Box cube, int direction, int position, long[] moment) + private static long Top(ref Box cube, int direction, int position, Span moment) { switch (direction) { @@ -446,41 +464,60 @@ namespace SixLabors.ImageSharp.Quantizers /// /// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box. /// - private void Get3DMoments() + private void Get3DMoments(MemoryManager memoryManager) { - long[] volume = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount); - long[] volumeR = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount); - long[] volumeG = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount); - long[] volumeB = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount); - long[] volumeA = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount); - float[] volume2 = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount); - - long[] area = ArrayPool.Shared.Rent(IndexAlphaCount); - long[] areaR = ArrayPool.Shared.Rent(IndexAlphaCount); - long[] areaG = ArrayPool.Shared.Rent(IndexAlphaCount); - long[] areaB = ArrayPool.Shared.Rent(IndexAlphaCount); - long[] areaA = ArrayPool.Shared.Rent(IndexAlphaCount); - float[] area2 = ArrayPool.Shared.Rent(IndexAlphaCount); - - try + Span vwtSpan = this.vwt.Span; + Span vmrSpan = this.vmr.Span; + Span vmgSpan = this.vmg.Span; + Span vmbSpan = this.vmb.Span; + Span vmaSpan = this.vma.Span; + Span m2Span = this.m2.Span; + + using (IBuffer volume = memoryManager.Allocate(IndexCount * IndexAlphaCount)) + using (IBuffer volumeR = memoryManager.Allocate(IndexCount * IndexAlphaCount)) + using (IBuffer volumeG = memoryManager.Allocate(IndexCount * IndexAlphaCount)) + using (IBuffer volumeB = memoryManager.Allocate(IndexCount * IndexAlphaCount)) + using (IBuffer volumeA = memoryManager.Allocate(IndexCount * IndexAlphaCount)) + using (IBuffer volume2 = memoryManager.Allocate(IndexCount * IndexAlphaCount)) + + using (IBuffer area = memoryManager.Allocate(IndexAlphaCount)) + using (IBuffer areaR = memoryManager.Allocate(IndexAlphaCount)) + using (IBuffer areaG = memoryManager.Allocate(IndexAlphaCount)) + using (IBuffer areaB = memoryManager.Allocate(IndexAlphaCount)) + using (IBuffer areaA = memoryManager.Allocate(IndexAlphaCount)) + using (IBuffer area2 = memoryManager.Allocate(IndexAlphaCount)) { + Span volumeSpan = volume.Span; + Span volumeRSpan = volumeR.Span; + Span volumeGSpan = volumeG.Span; + Span volumeBSpan = volumeB.Span; + Span volumeASpan = volumeA.Span; + Span volume2Span = volume2.Span; + + Span areaSpan = area.Span; + Span areaRSpan = areaR.Span; + Span areaGSpan = areaG.Span; + Span areaBSpan = areaB.Span; + Span areaASpan = areaA.Span; + Span area2Span = area2.Span; + for (int r = 1; r < IndexCount; r++) { - Array.Clear(volume, 0, IndexCount * IndexAlphaCount); - Array.Clear(volumeR, 0, IndexCount * IndexAlphaCount); - Array.Clear(volumeG, 0, IndexCount * IndexAlphaCount); - Array.Clear(volumeB, 0, IndexCount * IndexAlphaCount); - Array.Clear(volumeA, 0, IndexCount * IndexAlphaCount); - Array.Clear(volume2, 0, IndexCount * IndexAlphaCount); + volume.Clear(); + volumeR.Clear(); + volumeG.Clear(); + volumeB.Clear(); + volumeA.Clear(); + volume2.Clear(); for (int g = 1; g < IndexCount; g++) { - Array.Clear(area, 0, IndexAlphaCount); - Array.Clear(areaR, 0, IndexAlphaCount); - Array.Clear(areaG, 0, IndexAlphaCount); - Array.Clear(areaB, 0, IndexAlphaCount); - Array.Clear(areaA, 0, IndexAlphaCount); - Array.Clear(area2, 0, IndexAlphaCount); + area.Clear(); + areaR.Clear(); + areaG.Clear(); + areaB.Clear(); + areaA.Clear(); + area2.Clear(); for (int b = 1; b < IndexCount; b++) { @@ -495,58 +532,42 @@ namespace SixLabors.ImageSharp.Quantizers { int ind1 = GetPaletteIndex(r, g, b, a); - line += this.vwt[ind1]; - lineR += this.vmr[ind1]; - lineG += this.vmg[ind1]; - lineB += this.vmb[ind1]; - lineA += this.vma[ind1]; - line2 += this.m2[ind1]; + line += vwtSpan[ind1]; + lineR += vmrSpan[ind1]; + lineG += vmgSpan[ind1]; + lineB += vmbSpan[ind1]; + lineA += vmaSpan[ind1]; + line2 += m2Span[ind1]; - area[a] += line; - areaR[a] += lineR; - areaG[a] += lineG; - areaB[a] += lineB; - areaA[a] += lineA; - area2[a] += line2; + areaSpan[a] += line; + areaRSpan[a] += lineR; + areaGSpan[a] += lineG; + areaBSpan[a] += lineB; + areaASpan[a] += lineA; + area2Span[a] += line2; int inv = (b * IndexAlphaCount) + a; - volume[inv] += area[a]; - volumeR[inv] += areaR[a]; - volumeG[inv] += areaG[a]; - volumeB[inv] += areaB[a]; - volumeA[inv] += areaA[a]; - volume2[inv] += area2[a]; + volumeSpan[inv] += areaSpan[a]; + volumeRSpan[inv] += areaRSpan[a]; + volumeGSpan[inv] += areaGSpan[a]; + volumeBSpan[inv] += areaBSpan[a]; + volumeASpan[inv] += areaASpan[a]; + volume2Span[inv] += area2Span[a]; int ind2 = ind1 - GetPaletteIndex(1, 0, 0, 0); - this.vwt[ind1] = this.vwt[ind2] + volume[inv]; - this.vmr[ind1] = this.vmr[ind2] + volumeR[inv]; - this.vmg[ind1] = this.vmg[ind2] + volumeG[inv]; - this.vmb[ind1] = this.vmb[ind2] + volumeB[inv]; - this.vma[ind1] = this.vma[ind2] + volumeA[inv]; - this.m2[ind1] = this.m2[ind2] + volume2[inv]; + vwtSpan[ind1] = vwtSpan[ind2] + volumeSpan[inv]; + vmrSpan[ind1] = vmrSpan[ind2] + volumeRSpan[inv]; + vmgSpan[ind1] = vmgSpan[ind2] + volumeGSpan[inv]; + vmbSpan[ind1] = vmbSpan[ind2] + volumeBSpan[inv]; + vmaSpan[ind1] = vmaSpan[ind2] + volumeASpan[inv]; + m2Span[ind1] = m2Span[ind2] + volume2Span[inv]; } } } } } - finally - { - ArrayPool.Shared.Return(volume); - ArrayPool.Shared.Return(volumeR); - ArrayPool.Shared.Return(volumeG); - ArrayPool.Shared.Return(volumeB); - ArrayPool.Shared.Return(volumeA); - ArrayPool.Shared.Return(volume2); - - ArrayPool.Shared.Return(area); - ArrayPool.Shared.Return(areaR); - ArrayPool.Shared.Return(areaG); - ArrayPool.Shared.Return(areaB); - ArrayPool.Shared.Return(areaA); - ArrayPool.Shared.Return(area2); - } } /// @@ -556,31 +577,33 @@ namespace SixLabors.ImageSharp.Quantizers /// The . private float Variance(ref Box cube) { - float dr = Volume(ref cube, this.vmr); - float dg = Volume(ref cube, this.vmg); - float db = Volume(ref cube, this.vmb); - float da = Volume(ref cube, this.vma); + float dr = Volume(ref cube, this.vmr.Span); + float dg = Volume(ref cube, this.vmg.Span); + float db = Volume(ref cube, this.vmb.Span); + float da = Volume(ref cube, this.vma.Span); + + Span m2Span = this.m2.Span; float xx = - this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] - - this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] - - this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] - + this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] - - this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] - + this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] - + this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] - - this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - - this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)] - + this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] - + this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] - - this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] - + this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] - - this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - - this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] - + this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; + m2Span[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] + - m2Span[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] + - m2Span[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] + + m2Span[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] + - m2Span[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] + + m2Span[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] + + m2Span[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] + - m2Span[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] + - m2Span[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)] + + m2Span[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] + + m2Span[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] + - m2Span[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] + + m2Span[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] + - m2Span[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] + - m2Span[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] + + m2Span[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; var vector = new Vector4(dr, dg, db, da); - return xx - (Vector4.Dot(vector, vector) / Volume(ref cube, this.vwt)); + return xx - (Vector4.Dot(vector, vector) / Volume(ref cube, this.vwt.Span)); } /// @@ -603,22 +626,22 @@ namespace SixLabors.ImageSharp.Quantizers /// 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); - long baseG = Bottom(ref cube, direction, this.vmg); - long baseB = Bottom(ref cube, direction, this.vmb); - long baseA = Bottom(ref cube, direction, this.vma); - long baseW = Bottom(ref cube, direction, this.vwt); + long baseR = Bottom(ref cube, direction, this.vmr.Span); + long baseG = Bottom(ref cube, direction, this.vmg.Span); + long baseB = Bottom(ref cube, direction, this.vmb.Span); + long baseA = Bottom(ref cube, direction, this.vma.Span); + long baseW = Bottom(ref cube, direction, this.vwt.Span); float max = 0F; cut = -1; for (int i = first; i < last; i++) { - float halfR = baseR + Top(ref cube, direction, i, this.vmr); - float halfG = baseG + Top(ref cube, direction, i, this.vmg); - float halfB = baseB + Top(ref cube, direction, i, this.vmb); - float halfA = baseA + Top(ref cube, direction, i, this.vma); - float halfW = baseW + Top(ref cube, direction, i, this.vwt); + float halfR = baseR + Top(ref cube, direction, i, this.vmr.Span); + float halfG = baseG + Top(ref cube, direction, i, this.vmg.Span); + float halfB = baseB + Top(ref cube, direction, i, this.vmb.Span); + float halfA = baseA + Top(ref cube, direction, i, this.vma.Span); + float halfW = baseW + Top(ref cube, direction, i, this.vwt.Span); if (MathF.Abs(halfW) < Constants.Epsilon) { @@ -662,11 +685,11 @@ namespace SixLabors.ImageSharp.Quantizers /// Returns a value indicating whether the box has been split. private bool Cut(ref Box set1, ref Box set2) { - float wholeR = Volume(ref set1, this.vmr); - float wholeG = Volume(ref set1, this.vmg); - float wholeB = Volume(ref set1, this.vmb); - float wholeA = Volume(ref set1, this.vma); - float wholeW = Volume(ref set1, this.vwt); + float wholeR = Volume(ref set1, this.vmr.Span); + float wholeG = Volume(ref set1, this.vmg.Span); + float wholeB = Volume(ref set1, this.vmb.Span); + float wholeA = Volume(ref set1, this.vma.Span); + float wholeW = Volume(ref set1, this.vwt.Span); float maxr = this.Maximize(ref set1, 3, set1.R0 + 1, set1.R1, out int cutr, wholeR, wholeG, wholeB, wholeA, wholeW); float maxg = this.Maximize(ref set1, 2, set1.G0 + 1, set1.G1, out int cutg, wholeR, wholeG, wholeB, wholeA, wholeW); @@ -750,6 +773,8 @@ namespace SixLabors.ImageSharp.Quantizers /// A label. private void Mark(ref Box cube, byte label) { + Span tagSpan = this.tag.Span; + for (int r = cube.R0 + 1; r <= cube.R1; r++) { for (int g = cube.G0 + 1; g <= cube.G1; g++) @@ -758,7 +783,7 @@ namespace SixLabors.ImageSharp.Quantizers { for (int a = cube.A0 + 1; a <= cube.A1; a++) { - this.tag[GetPaletteIndex(r, g, b, a)] = label; + tagSpan[GetPaletteIndex(r, g, b, a)] = label; } } } @@ -841,7 +866,9 @@ namespace SixLabors.ImageSharp.Quantizers int b = rgba.B >> (8 - IndexBits); int a = rgba.A >> (8 - IndexAlphaBits); - return this.tag[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; + Span tagSpan = this.tag.Span; + + return tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; } } } \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4.cs index 83c2a2ee89..7bac44a982 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4.cs @@ -2,7 +2,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk { using System.Numerics; - + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Memory; @@ -12,9 +13,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk public abstract class PackFromVector4 where TPixel : struct, IPixel { - private Buffer source; + private IBuffer source; - private Buffer destination; + private IBuffer destination; [Params(16, 128, 512)] public int Count { get; set; } @@ -22,8 +23,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [GlobalSetup] public void Setup() { - this.destination = new Buffer(this.Count); - this.source = new Buffer(this.Count); + this.destination = Configuration.Default.MemoryManager.Allocate(this.Count); + this.source = Configuration.Default.MemoryManager.Allocate(this.Count); } [GlobalCleanup] @@ -36,25 +37,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [Benchmark(Baseline = true)] public void PerElement() { - Vector4[] s = this.source.Array; - TPixel[] d = this.destination.Array; + ref Vector4 s = ref MemoryMarshal.GetReference(this.source.Span); + ref TPixel d = ref MemoryMarshal.GetReference(this.destination.Span); for (int i = 0; i < this.Count; i++) { - d[i].PackFromVector4(s[i]); + Unsafe.Add(ref d, i).PackFromVector4(Unsafe.Add(ref s, i)); } } [Benchmark] public void CommonBulk() { - new PixelOperations().PackFromVector4(this.source, this.destination, this.Count); + new PixelOperations().PackFromVector4(this.source.Span, this.destination.Span, this.Count); } [Benchmark] public void OptimizedBulk() { - PixelOperations.Instance.PackFromVector4(this.source, this.destination, this.Count); + PixelOperations.Instance.PackFromVector4(this.source.Span, this.destination.Span, this.Count); } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs deleted file mode 100644 index b4f6ea9c06..0000000000 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs +++ /dev/null @@ -1,77 +0,0 @@ -namespace SixLabors.ImageSharp.Benchmarks -{ - using System.Numerics; - using System.Runtime.CompilerServices; - - using BenchmarkDotNet.Attributes; - - using SixLabors.ImageSharp; - using SixLabors.ImageSharp.Memory; - - /// - /// Compares two implementation candidates for general BulkPixelOperations.ToVector4(): - /// - One iterating with pointers - /// - One iterating with ref locals - /// - public unsafe class PackFromVector4ReferenceVsPointer - { - private Buffer destination; - - private Buffer source; - - [Params(16, 128, 1024)] - public int Count { get; set; } - - [GlobalSetup] - public void Setup() - { - this.destination = new Buffer(this.Count); - this.source = new Buffer(this.Count * 4); - this.source.Pin(); - this.destination.Pin(); - } - - [GlobalCleanup] - public void Cleanup() - { - this.source.Dispose(); - this.destination.Dispose(); - } - - [Benchmark(Baseline = true)] - public void PackUsingPointers() - { - Vector4* sp = (Vector4*)this.source.Pin(); - byte* dp = (byte*)this.destination.Pin(); - int count = this.Count; - int size = sizeof(Rgba32); - - for (int i = 0; i < count; i++) - { - Vector4 v = Unsafe.Read(sp); - Rgba32 c = default(Rgba32); - c.PackFromVector4(v); - Unsafe.Write(dp, c); - - sp++; - dp += size; - } - } - - [Benchmark] - public void PackUsingReferences() - { - ref Vector4 sp = ref this.source.Array[0]; - ref Rgba32 dp = ref this.destination.Array[0]; - int count = this.Count; - - for (int i = 0; i < count; i++) - { - dp.PackFromVector4(sp); - - sp = Unsafe.Add(ref sp, 1); - dp = Unsafe.Add(ref dp, 1); - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs index 5c3648c2d8..882d77dd12 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs @@ -1,6 +1,8 @@ // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk { + using System; + using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Memory; @@ -9,9 +11,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk public abstract class PackFromXyzw where TPixel : struct, IPixel { - private Buffer destination; + private IBuffer destination; - private Buffer source; + private IBuffer source; [Params(16, 128, 1024)] public int Count { get; set; } @@ -19,8 +21,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [GlobalSetup] public void Setup() { - this.destination = new Buffer(this.Count); - this.source = new Buffer(this.Count * 4); + this.destination = Configuration.Default.MemoryManager.Allocate(this.Count); + this.source = Configuration.Default.MemoryManager.Allocate(this.Count * 4); } [GlobalCleanup] @@ -33,13 +35,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [Benchmark(Baseline = true)] public void PerElement() { - byte[] s = this.source.Array; - TPixel[] d = this.destination.Array; + Span s = this.source.Span; + Span d = this.destination.Span; for (int i = 0; i < this.Count; i++) { int i4 = i * 4; - TPixel c = default(TPixel); + var c = default(TPixel); c.PackFromRgba32(new Rgba32(s[i4], s[i4 + 1], s[i4 + 2], s[i4 + 3])); d[i] = c; } @@ -48,13 +50,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [Benchmark] public void CommonBulk() { - new PixelOperations().PackFromRgba32Bytes(this.source, this.destination, this.Count); + new PixelOperations().PackFromRgba32Bytes(this.source.Span, this.destination.Span, this.Count); } [Benchmark] public void OptimizedBulk() { - PixelOperations.Instance.PackFromRgba32Bytes(this.source, this.destination, this.Count); + PixelOperations.Instance.PackFromRgba32Bytes(this.source.Span, this.destination.Span, this.Count); } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs index 2bf4e0da67..6537141501 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs @@ -1,6 +1,7 @@ // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk { + using System; using System.Numerics; using BenchmarkDotNet.Attributes; @@ -11,9 +12,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk public abstract class ToVector4 where TPixel : struct, IPixel { - private Buffer source; + private IBuffer source; - private Buffer destination; + private IBuffer destination; [Params(64, 300, 1024)] public int Count { get; set; } @@ -21,8 +22,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [GlobalSetup] public void Setup() { - this.source = new Buffer(this.Count); - this.destination = new Buffer(this.Count); + this.source = Configuration.Default.MemoryManager.Allocate(this.Count); + this.destination = Configuration.Default.MemoryManager.Allocate(this.Count); } [GlobalCleanup] @@ -35,8 +36,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [Benchmark(Baseline = true)] public void PerElement() { - TPixel[] s = this.source.Array; - Vector4[] d = this.destination.Array; + Span s = this.source.Span; + Span d = this.destination.Span; for (int i = 0; i < this.Count; i++) { @@ -48,13 +49,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [Benchmark] public void CommonBulk() { - new PixelOperations().ToVector4(this.source, this.destination, this.Count); + new PixelOperations().ToVector4(this.source.Span, this.destination.Span, this.Count); } [Benchmark] public void OptimizedBulk() { - PixelOperations.Instance.ToVector4(this.source, this.destination, this.Count); + PixelOperations.Instance.ToVector4(this.source.Span, this.destination.Span, this.Count); } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs index 2d624c19f1..b2def64ace 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs @@ -1,6 +1,9 @@ // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk { + using System; + using System.Numerics; + using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Memory; @@ -9,9 +12,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk public abstract class ToXyz where TPixel : struct, IPixel { - private Buffer source; + private IBuffer source; - private Buffer destination; + private IBuffer destination; [Params(16, 128, 1024)] public int Count { get; set; } @@ -19,8 +22,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [GlobalSetup] public void Setup() { - this.source = new Buffer(this.Count); - this.destination = new Buffer(this.Count * 3); + this.source = Configuration.Default.MemoryManager.Allocate(this.Count); + this.destination = Configuration.Default.MemoryManager.Allocate(this.Count * 3); } [GlobalCleanup] @@ -33,8 +36,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [Benchmark(Baseline = true)] public void PerElement() { - TPixel[] s = this.source.Array; - byte[] d = this.destination.Array; + Span s = this.source.Span; + Span d = this.destination.Span; + var rgb = default(Rgb24); for (int i = 0; i < this.Count; i++) @@ -51,13 +55,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [Benchmark] public void CommonBulk() { - new PixelOperations().ToRgb24Bytes(this.source, this.destination, this.Count); + new PixelOperations().ToRgb24Bytes(this.source.Span, this.destination.Span, this.Count); } [Benchmark] public void OptimizedBulk() { - PixelOperations.Instance.ToRgb24Bytes(this.source, this.destination, this.Count); + PixelOperations.Instance.ToRgb24Bytes(this.source.Span, this.destination.Span, this.Count); } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs index 150b55aed0..dd9a628c25 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs @@ -14,9 +14,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk public abstract class ToXyzw where TPixel : struct, IPixel { - private Buffer source; + private IBuffer source; - private Buffer destination; + private IBuffer destination; [Params(16, 128, 1024)] public int Count { get; set; } @@ -24,8 +24,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [GlobalSetup] public void Setup() { - this.source = new Buffer(this.Count); - this.destination = new Buffer(this.Count * 4); + this.source = Configuration.Default.MemoryManager.Allocate(this.Count); + this.destination = Configuration.Default.MemoryManager.Allocate(this.Count * 4); } [GlobalCleanup] @@ -38,8 +38,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [Benchmark(Baseline = true)] public void PerElement() { - TPixel[] s = this.source.Array; - byte[] d = this.destination.Array; + Span s = this.source.Span; + Span d = this.destination.Span; + var rgba = default(Rgba32); for (int i = 0; i < this.Count; i++) @@ -57,13 +58,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Color.Bulk [Benchmark] public void CommonBulk() { - new PixelOperations().ToRgba32Bytes(this.source, this.destination, this.Count); + new PixelOperations().ToRgba32Bytes(this.source.Span, this.destination.Span, this.Count); } [Benchmark] public void OptimizedBulk() { - PixelOperations.Instance.ToRgba32Bytes(this.source, this.destination, this.Count); + PixelOperations.Instance.ToRgba32Bytes(this.source.Span, this.destination.Span, this.Count); } } diff --git a/tests/ImageSharp.Benchmarks/General/ClearBuffer.cs b/tests/ImageSharp.Benchmarks/General/ClearBuffer.cs deleted file mode 100644 index 0ac1413be0..0000000000 --- a/tests/ImageSharp.Benchmarks/General/ClearBuffer.cs +++ /dev/null @@ -1,42 +0,0 @@ -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Benchmarks.General -{ - using System; - using System.Runtime.CompilerServices; - - using BenchmarkDotNet.Attributes; - - using SixLabors.ImageSharp.Memory; - - public unsafe class ClearBuffer - { - private Buffer buffer; - - [Params(32, 128, 512)] - public int Count { get; set; } - - [GlobalSetup] - public void Setup() - { - this.buffer = new Buffer(this.Count); - } - - [GlobalCleanup] - public void Cleanup() - { - this.buffer.Dispose(); - } - - [Benchmark(Baseline = true)] - public void Array_Clear() - { - Array.Clear(this.buffer.Array, 0, this.Count); - } - - [Benchmark] - public void Unsafe_InitBlock() - { - Unsafe.InitBlock((void*)this.buffer.Pin(), default(byte), (uint)this.Count * sizeof(uint)); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/IterateArray.cs b/tests/ImageSharp.Benchmarks/General/IterateArray.cs deleted file mode 100644 index 48ee266fe0..0000000000 --- a/tests/ImageSharp.Benchmarks/General/IterateArray.cs +++ /dev/null @@ -1,73 +0,0 @@ -namespace SixLabors.ImageSharp.Benchmarks.General -{ - using System.Numerics; - using System.Runtime.CompilerServices; - - using BenchmarkDotNet.Attributes; - - using SixLabors.ImageSharp.Memory; - - public class IterateArray - { - // Usual pinned stuff - private Buffer buffer; - - // An array that's not pinned by intent! - private Vector4[] array; - - [Params(64, 1024)] - public int Length { get; set; } - - [GlobalSetup] - public void Setup() - { - this.buffer = new Buffer(this.Length); - this.buffer.Pin(); - this.array = new Vector4[this.Length]; - } - - [Benchmark(Baseline = true)] - public Vector4 IterateIndexed() - { - Vector4 sum = new Vector4(); - Vector4[] a = this.array; - - for (int i = 0; i < a.Length; i++) - { - sum += a[i]; - } - return sum; - } - - [Benchmark] - public unsafe Vector4 IterateUsingPointers() - { - Vector4 sum = new Vector4(); - - Vector4* ptr = (Vector4*) this.buffer.Pin(); - Vector4* end = ptr + this.Length; - - for (; ptr < end; ptr++) - { - sum += *ptr; - } - - return sum; - } - - [Benchmark] - public Vector4 IterateUsingReferences() - { - Vector4 sum = new Vector4(); - - ref Vector4 start = ref this.array[0]; - - for (int i = 0; i < this.Length; i++) - { - sum += Unsafe.Add(ref start, i); - } - - return sum; - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/PixelIndexing.cs b/tests/ImageSharp.Benchmarks/General/PixelIndexing.cs deleted file mode 100644 index 0e21caffbc..0000000000 --- a/tests/ImageSharp.Benchmarks/General/PixelIndexing.cs +++ /dev/null @@ -1,362 +0,0 @@ -namespace SixLabors.ImageSharp.Benchmarks.General -{ - using System.Numerics; - using System.Runtime.CompilerServices; - - using BenchmarkDotNet.Attributes; - - using SixLabors.ImageSharp.Memory; - - // Pixel indexing benchmarks compare different methods for getting/setting all pixel values in a subsegment of a single pixel row. - public abstract unsafe class PixelIndexing - { - /// - /// https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/Pinnable.cs - /// - protected class Pinnable - { - public T Data; - } - - /// - /// The indexer methods are encapsulated into a struct to make sure everything is inlined. - /// - internal struct Data - { - private Vector4* pointer; - - private Pinnable pinnable; - - private Vector4[] array; - - private int width; - - public Data(Buffer2D buffer) - { - this.pointer = (Vector4*)buffer.Pin(); - this.pinnable = Unsafe.As>(buffer.Array); - this.array = buffer.Array; - this.width = buffer.Width; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 GetPointersBasicImpl(int x, int y) - { - return this.pointer[y * this.width + x]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 GetPointersSrcsUnsafeImpl(int x, int y) - { - // This is the original solution in PixelAccessor: - return Unsafe.Read((byte*)this.pointer + (((y * this.width) + x) * Unsafe.SizeOf())); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 GetReferencesImpl(int x, int y) - { - int elementOffset = (y * this.width) + x; - return Unsafe.Add(ref this.pinnable.Data, elementOffset); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref Vector4 GetReferencesRefReturnsImpl(int x, int y) - { - int elementOffset = (y * this.width) + x; - return ref Unsafe.Add(ref this.pinnable.Data, elementOffset); - } - - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void IndexWithPointersBasicImpl(int x, int y, Vector4 v) - { - this.pointer[y * this.width + x] = v; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void IndexWithPointersSrcsUnsafeImpl(int x, int y, Vector4 v) - { - Unsafe.Write((byte*)this.pointer + (((y * this.width) + x) * Unsafe.SizeOf()), v); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void IndexWithReferencesOnPinnableIncorrectImpl(int x, int y, Vector4 v) - { - int elementOffset = (y * this.width) + x; - // Incorrect, because also should add a runtime-specific byte offset here. See https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/Span.cs#L68 - Unsafe.Add(ref this.pinnable.Data, elementOffset) = v; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref Vector4 IndexWithReferencesOnPinnableIncorrectRefReturnImpl(int x, int y) - { - int elementOffset = (y * this.width) + x; - // Incorrect, because also should add a runtime-specific byte offset here. See https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/Span.cs#L68 - return ref Unsafe.Add(ref this.pinnable.Data, elementOffset); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void IndexWithUnsafeReferenceArithmeticsOnArray0Impl(int x, int y, Vector4 v) - { - int elementOffset = (y * this.width) + x; - Unsafe.Add(ref this.array[0], elementOffset) = v; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref Vector4 IndexWithUnsafeReferenceArithmeticsOnArray0RefReturnImpl(int x, int y) - { - int elementOffset = (y * this.width) + x; - return ref Unsafe.Add(ref this.array[0], elementOffset); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void IndexSetArrayStraightforward(int x, int y, Vector4 v) - { - // No magic. - // We just index right into the array as normal people do. - // And it looks like this is the fastest way! - this.array[(y * this.width) + x] = v; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref Vector4 IndexWithReferencesOnArrayStraightforwardRefReturnImpl(int x, int y) - { - // No magic. - // We just index right into the array as normal people do. - // And it looks like this is the fastest way! - return ref this.array[(y * this.width) + x]; - } - } - - internal Buffer2D buffer; - - protected int width; - - protected int startIndex; - - protected int endIndex; - - protected Vector4* pointer; - - protected Vector4[] array; - - protected Pinnable pinnable; - - // [Params(1024)] - public int Count { get; set; } = 1024; - - [GlobalSetup] - public void Setup() - { - this.width = 2048; - this.buffer = new Buffer2D(2048, 2048); - this.pointer = (Vector4*)this.buffer.Pin(); - this.array = this.buffer.Array; - this.pinnable = Unsafe.As>(this.array); - - this.startIndex = 2048 / 2 - (this.Count / 2); - this.endIndex = 2048 / 2 + (this.Count / 2); - } - - [GlobalCleanup] - public void Cleanup() - { - this.buffer.Dispose(); - } - - } - - public class PixelIndexingGetter : PixelIndexing - { - [Benchmark(Description = "Index.Get: Pointers+arithmetics", Baseline = true)] - public Vector4 IndexWithPointersBasic() - { - Vector4 sum = Vector4.Zero; - Data data = new Data(this.buffer); - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - sum += data.GetPointersBasicImpl(x, y); - } - - return sum; - } - - [Benchmark(Description = "Index.Get: Pointers+SRCS.Unsafe")] - public Vector4 IndexWithPointersSrcsUnsafe() - { - Vector4 sum = Vector4.Zero; - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - sum += data.GetPointersSrcsUnsafeImpl(x, y); - } - - return sum; - } - - [Benchmark(Description = "Index.Get: References")] - public Vector4 IndexWithReferences() - { - Vector4 sum = Vector4.Zero; - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - sum += data.GetReferencesImpl(x, y); - } - - return sum; - } - - [Benchmark(Description = "Index.Get: References|refreturns")] - public Vector4 IndexWithReferencesRefReturns() - { - Vector4 sum = Vector4.Zero; - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - sum += data.GetReferencesRefReturnsImpl(x, y); - } - - return sum; - } - } - - public class PixelIndexingSetter : PixelIndexing - { - [Benchmark(Description = "!!! Index.Set: Pointers|arithmetics", Baseline = true)] - public void IndexWithPointersBasic() - { - Vector4 v = new Vector4(1, 2, 3, 4); - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - data.IndexWithPointersBasicImpl(x, y, v); - } - } - - [Benchmark(Description = "Index.Set: Pointers|SRCS.Unsafe")] - public void IndexWithPointersSrcsUnsafe() - { - Vector4 v = new Vector4(1, 2, 3, 4); - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - data.IndexWithPointersSrcsUnsafeImpl(x, y, v); - } - } - - [Benchmark(Description = "Index.Set: References|IncorrectPinnable")] - public void IndexWithReferencesPinnableBasic() - { - Vector4 v = new Vector4(1, 2, 3, 4); - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - data.IndexWithReferencesOnPinnableIncorrectImpl(x, y, v); - } - } - - [Benchmark(Description = "Index.Set: References|IncorrectPinnable|refreturn")] - public void IndexWithReferencesPinnableRefReturn() - { - Vector4 v = new Vector4(1, 2, 3, 4); - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - data.IndexWithReferencesOnPinnableIncorrectRefReturnImpl(x, y) = v; - } - } - - [Benchmark(Description = "Index.Set: References|Array[0]Unsafe")] - public void IndexWithReferencesArrayBasic() - { - Vector4 v = new Vector4(1, 2, 3, 4); - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - data.IndexWithUnsafeReferenceArithmeticsOnArray0Impl(x, y, v); - } - } - - [Benchmark(Description = "Index.Set: References|Array[0]Unsafe|refreturn")] - public void IndexWithReferencesArrayRefReturn() - { - Vector4 v = new Vector4(1, 2, 3, 4); - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - data.IndexWithUnsafeReferenceArithmeticsOnArray0RefReturnImpl(x, y) = v; - } - } - - [Benchmark(Description = "!!! Index.Set: References|Array+Straight")] - public void IndexWithReferencesArrayStraightforward() - { - Vector4 v = new Vector4(1, 2, 3, 4); - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - // No magic. - // We just index right into the array as normal people do. - // And it looks like this is the fastest way! - data.IndexSetArrayStraightforward(x, y, v); - } - } - - - [Benchmark(Description = "!!! Index.Set: References|Array+Straight|refreturn")] - public void IndexWithReferencesArrayStraightforwardRefReturn() - { - Vector4 v = new Vector4(1, 2, 3, 4); - Data data = new Data(this.buffer); - - int y = this.startIndex; - for (int x = this.startIndex; x < this.endIndex; x++) - { - // No magic. - // We just index right into the array as normal people do. - // And it looks like this is the fastest way! - data.IndexWithReferencesOnArrayStraightforwardRefReturnImpl(x, y) = v; - } - } - - [Benchmark(Description = "!!! Index.Set: SmartUnsafe")] - public void SmartUnsafe() - { - Vector4 v = new Vector4(1, 2, 3, 4); - Data data = new Data(this.buffer); - - // This method is basically an unsafe variant of .GetRowSpan(y) + indexing individual pixels in the row. - // If a user seriously needs by-pixel manipulation to be performant, we should provide this option. - - ref Vector4 rowStart = ref data.IndexWithReferencesOnArrayStraightforwardRefReturnImpl(this.startIndex, this.startIndex); - - for (int i = 0; i < this.Count; i++) - { - // We don't have to add 'Width * y' here! - Unsafe.Add(ref rowStart, i) = v; - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs b/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs index 70ea164d69..e5eb295449 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream()) { - PngEncoder options = new PngEncoder { Quantizer = new OctreeQuantizer { Dither = false }, PaletteSize = 256 }; + PngEncoder options = new PngEncoder() { Quantizer = new OctreeQuantizer { Dither = false }, PaletteSize = 256 }; this.bmpCore.SaveAsPng(memoryStream, options); } @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream()) { - PngEncoder options = new PngEncoder { Quantizer = new PaletteQuantizer(), PaletteSize = 256 }; + PngEncoder options = new PngEncoder() { Quantizer = new PaletteQuantizer(), PaletteSize = 256 }; this.bmpCore.SaveAsPng(memoryStream, options); } @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream()) { - PngEncoder options = new PngEncoder { Quantizer = new PaletteQuantizer { Dither = false }, PaletteSize = 256 }; + PngEncoder options = new PngEncoder() { Quantizer = new PaletteQuantizer { Dither = false }, PaletteSize = 256 }; this.bmpCore.SaveAsPng(memoryStream, options); } diff --git a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs index b3c1e4ee95..53522a51f3 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Image new OctreeQuantizer() : new PaletteQuantizer(); - var options = new PngEncoder { Quantizer = quantizer }; + var options = new PngEncoder() { Quantizer = quantizer }; this.bmpCore.SaveAsPng(memoryStream, options); } } diff --git a/tests/ImageSharp.Benchmarks/Image/Jpeg/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Image/Jpeg/YCbCrColorConversion.cs index 93420aacf8..c47aff9cf4 100644 --- a/tests/ImageSharp.Benchmarks/Image/Jpeg/YCbCrColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Image/Jpeg/YCbCrColorConversion.cs @@ -76,7 +76,7 @@ } // no need to dispose when buffer is not array owner - buffers[i] = new Buffer2D(values, values.Length, 1); + buffers[i] = Configuration.Default.MemoryManager.Allocate2D(values.Length, 1); } return buffers; diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 417e849be1..6dcfbaf818 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -1,6 +1,6 @@  - netcoreapp1.1;net461 + netcoreapp2.0;net461 Exe True SixLabors.ImageSharp.Benchmarks @@ -15,18 +15,13 @@ + - - - - - - - - - - + + + + diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs index 5a3131f796..c088e8eed4 100644 --- a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Benchmarks Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (Buffer buffer = new Buffer(destination.Length * 3)) + using (IBuffer buffer = Configuration.Default.MemoryManager.Allocate(destination.Length * 3)) { Span destinationSpan = buffer.Slice(0, destination.Length); Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); @@ -59,21 +59,20 @@ namespace SixLabors.ImageSharp.Benchmarks { using (Image image = new Image(800, 800)) { - Buffer amounts = new Buffer(image.Width); - - for (int x = 0; x < image.Width; x++) - { - amounts[x] = 1; - } - using (PixelAccessor pixels = image.Lock()) + using (IBuffer amounts = Configuration.Default.MemoryManager.Allocate(image.Width)) { - for (int y = 0; y < image.Height; y++) + amounts.Span.Fill(1); + + using (PixelAccessor pixels = image.Lock()) { - Span span = pixels.GetRowSpan(y); - BulkVectorConvert(span, span, span, amounts); + for (int y = 0; y < image.Height; y++) + { + Span span = pixels.GetRowSpan(y); + this.BulkVectorConvert(span, span, span, amounts.Span); + } } + return new CoreSize(image.Width, image.Height); } - return new CoreSize(image.Width, image.Height); } } @@ -82,21 +81,20 @@ namespace SixLabors.ImageSharp.Benchmarks { using (Image image = new Image(800, 800)) { - Buffer amounts = new Buffer(image.Width); - - for (int x = 0; x < image.Width; x++) + using (IBuffer amounts = Configuration.Default.MemoryManager.Allocate(image.Width)) { - amounts[x] = 1; - } - using (PixelAccessor pixels = image.Lock()) - { - for (int y = 0; y < image.Height; y++) + amounts.Span.Fill(1); + using (PixelAccessor pixels = image.Lock()) { - Span span = pixels.GetRowSpan(y); - BulkPixelConvert(span, span, span, amounts); + for (int y = 0; y < image.Height; y++) + { + Span span = pixels.GetRowSpan(y); + this.BulkPixelConvert(span, span, span, amounts.Span); + } } + + return new CoreSize(image.Width, image.Height); } - return new CoreSize(image.Width, image.Height); } } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs index 1f88c4fbfa..5f4e2d75b4 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Benchmarks [GlobalSetup] public void Setup() { - this.bulk = new GlowProcessor(NamedColors.Beige, 800 * .5f, GraphicsOptions.Default); + this.bulk = new GlowProcessor(Configuration.Default.MemoryManager, NamedColors.Beige, 800 * .5f, GraphicsOptions.Default); this.parallel = new GlowProcessorParallel(NamedColors.Beige) { Radius = 800 * .5f, }; } @@ -103,13 +103,10 @@ namespace SixLabors.ImageSharp.Benchmarks } int width = maxX - minX; - using (Buffer rowColors = new Buffer(width)) + using (IBuffer rowColors = Configuration.Default.MemoryManager.Allocate(width)) using (PixelAccessor sourcePixels = source.Lock()) { - for (int i = 0; i < width; i++) - { - rowColors[i] = glowColor; - } + rowColors.Span.Fill(glowColor); Parallel.For( minY, diff --git a/tests/ImageSharp.Benchmarks/benchmark.sh b/tests/ImageSharp.Benchmarks/benchmark.sh index 1966475bca..f51a9833aa 100755 --- a/tests/ImageSharp.Benchmarks/benchmark.sh +++ b/tests/ImageSharp.Benchmarks/benchmark.sh @@ -1,7 +1,7 @@ #!/bin/bash # Build in release mode -dotnet build -c Release -f netcoreapp1.1 +dotnet build -c Release -f netcoreapp2.0 # Run benchmarks -dotnet bin/Release/netcoreapp1.1/ImageSharp.Benchmarks.dll \ No newline at end of file +dotnet bin/Release/netcoreapp2.0/ImageSharp.Benchmarks.dll \ No newline at end of file diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index b186ff4df9..7d56686eb4 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -18,8 +18,8 @@ - - + + diff --git a/tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs b/tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs index 37696987cc..ec46e66107 100644 --- a/tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs +++ b/tests/ImageSharp.Tests/BaseImageOperationsExtensionTest.cs @@ -16,29 +16,31 @@ namespace SixLabors.ImageSharp.Tests private readonly FakeImageOperationsProvider.FakeImageOperations internalOperations; protected readonly Rectangle rect; protected readonly GraphicsOptions options; + private Image source; public BaseImageOperationsExtensionTest() { - this.options = new GraphicsOptions(false) { }; + this.options = new GraphicsOptions(false); + this.source = new Image(91 + 324, 123 + 56); this.rect = new Rectangle(91, 123, 324, 56); // make this random? - this.internalOperations = new FakeImageOperationsProvider.FakeImageOperations(null, false); + this.internalOperations = new FakeImageOperationsProvider.FakeImageOperations(this.source, false); this.operations = this.internalOperations; } public T Verify(int index = 0) { - Assert.InRange(index, 0, this.internalOperations.applied.Count - 1); + Assert.InRange(index, 0, this.internalOperations.Applied.Count - 1); - var operation = this.internalOperations.applied[index]; + var operation = this.internalOperations.Applied[index]; return Assert.IsType(operation.Processor); } public T Verify(Rectangle rect, int index = 0) { - Assert.InRange(index, 0, this.internalOperations.applied.Count - 1); + Assert.InRange(index, 0, this.internalOperations.Applied.Count - 1); - var operation = this.internalOperations.applied[index]; + var operation = this.internalOperations.Applied[index]; Assert.Equal(rect, operation.Rectangle); return Assert.IsType(operation.Processor); diff --git a/tests/ImageSharp.Tests/Colorspaces/ColorConverterAdaptTest.cs b/tests/ImageSharp.Tests/Colorspaces/ColorConverterAdaptTest.cs index 87dc59907b..6cb32be47b 100644 --- a/tests/ImageSharp.Tests/Colorspaces/ColorConverterAdaptTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/ColorConverterAdaptTest.cs @@ -19,6 +19,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces { 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)] @@ -34,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces Rgb output = converter.Adapt(input); // Assert - Assert.Equal(expectedOutput.WorkingSpace, output.WorkingSpace); + 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); @@ -55,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces Rgb output = converter.Adapt(input); // Assert - Assert.Equal(expectedOutput.WorkingSpace, output.WorkingSpace); + 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); diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndCieXyzConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/RgbAndCieXyzConversionTest.cs index ee71eefc17..48c91dd6d0 100644 --- a/tests/ImageSharp.Tests/Colorspaces/RgbAndCieXyzConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/RgbAndCieXyzConversionTest.cs @@ -19,6 +19,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces { private static readonly IEqualityComparer FloatRoundingComparer = new FloatRoundingComparer(6); + private static readonly ApproximateFloatComparer ApproximateComparer = new ApproximateFloatComparer(0.0001F); + /// /// Tests conversion from () /// to (default sRGB working space). @@ -40,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces Rgb output = converter.ToRgb(input); // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + 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); @@ -68,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces Rgb output = converter.ToRgb(input); // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + 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); diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndCmykConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/RgbAndCmykConversionTest.cs index 6c3d579b4e..aa1f9c5743 100644 --- a/tests/ImageSharp.Tests/Colorspaces/RgbAndCmykConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/RgbAndCmykConversionTest.cs @@ -22,6 +22,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + private static readonly ApproximateFloatComparer ApproximateComparer = new ApproximateFloatComparer(0.0001F); + /// /// Tests conversion from to . /// @@ -38,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces Rgb output = Converter.ToRgb(input); // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + 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); diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndHslConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/RgbAndHslConversionTest.cs index a7071e883d..f658ddaae5 100644 --- a/tests/ImageSharp.Tests/Colorspaces/RgbAndHslConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/RgbAndHslConversionTest.cs @@ -22,6 +22,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + private static readonly ApproximateFloatComparer ApproximateComparer = new ApproximateFloatComparer(0.0001F); + /// /// Tests conversion from to . /// @@ -41,7 +43,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces Rgb output = Converter.ToRgb(input); // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + 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); diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndHsvConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/RgbAndHsvConversionTest.cs index 0dc58a0a3e..63b3d9b749 100644 --- a/tests/ImageSharp.Tests/Colorspaces/RgbAndHsvConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/RgbAndHsvConversionTest.cs @@ -21,6 +21,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + private static readonly ApproximateFloatComparer ApproximateComparer = new ApproximateFloatComparer(0.0001F); + /// /// Tests conversion from to . /// @@ -40,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces Rgb output = Converter.ToRgb(input); // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + 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); diff --git a/tests/ImageSharp.Tests/Colorspaces/RgbAndYCbCrConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/RgbAndYCbCrConversionTest.cs index 0eb1f620bf..96c302e25b 100644 --- a/tests/ImageSharp.Tests/Colorspaces/RgbAndYCbCrConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/RgbAndYCbCrConversionTest.cs @@ -20,6 +20,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces private static readonly ColorSpaceConverter Converter = new ColorSpaceConverter(); + private static readonly ApproximateFloatComparer ApproximateComparer = new ApproximateFloatComparer(0.0001F); + /// /// Tests conversion from to . /// @@ -36,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces Rgb output = Converter.ToRgb(input); // Assert - Assert.Equal(Rgb.DefaultWorkingSpace, output.WorkingSpace); + 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); diff --git a/tests/ImageSharp.Tests/ComplexIntegrationTests.cs b/tests/ImageSharp.Tests/ComplexIntegrationTests.cs new file mode 100644 index 0000000000..ad4676872f --- /dev/null +++ b/tests/ImageSharp.Tests/ComplexIntegrationTests.cs @@ -0,0 +1,35 @@ +namespace SixLabors.ImageSharp.Tests +{ + using SixLabors.ImageSharp.Formats.Jpeg; + using SixLabors.ImageSharp.PixelFormats; + using SixLabors.ImageSharp.Processing; + using SixLabors.Primitives; + + using Xunit; + + /// + /// Might be useful to catch complex bugs + /// + public class ComplexIntegrationTests + { + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Snake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio420)] + [WithFile(TestImages.Jpeg.Baseline.Lake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio420)] + [WithFile(TestImages.Jpeg.Baseline.Snake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio444)] + [WithFile(TestImages.Jpeg.Baseline.Lake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio444)] + public void LoadResizeSave(TestImageProvider provider, int quality, JpegSubsample subsample) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(x => x.Resize(new ResizeOptions { Size = new Size(150, 100), Mode = ResizeMode.Max }))) + { + + image.MetaData.ExifProfile = null; // Reduce the size of the file + JpegEncoder options = new JpegEncoder { Subsample = subsample, Quality = quality }; + + provider.Utility.TestName += $"{subsample}_Q{quality}"; + provider.Utility.SaveTestOutputFile(image, "png"); + provider.Utility.SaveTestOutputFile(image, "jpg", options); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 18d4abdd1b..cf348569ce 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -22,8 +22,10 @@ namespace SixLabors.ImageSharp.Tests public Configuration DefaultConfiguration { get; private set; } public ConfigurationTests() - { - this.DefaultConfiguration = Configuration.CreateDefaultInstance(); + { + // 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.ConfigurationEmpty = new Configuration(); } @@ -32,14 +34,7 @@ namespace SixLabors.ImageSharp.Tests { Assert.IsType(this.DefaultConfiguration.FileSystem); Assert.IsType(this.ConfigurationEmpty.FileSystem); - } - - [Fact] - public void IfAutoloadWellknwonFormatesIsTrueAllFormateAreLoaded() - { - Assert.Equal(4, this.DefaultConfiguration.ImageEncoders.Count()); - Assert.Equal(4, this.DefaultConfiguration.ImageDecoders.Count()); - } + } /// /// Test that the default configuration is not null. @@ -60,89 +55,24 @@ namespace SixLabors.ImageSharp.Tests } /// - /// Test that the default configuration parallel options max degrees of parallelism matches the - /// environment processor count. + /// Test that the default configuration read origin options is set to begin. /// [Fact] - public void TestDefultConfigurationMaxDegreeOfParallelism() - { - Assert.True(Configuration.Default.ParallelOptions.MaxDegreeOfParallelism == Environment.ProcessorCount); - } - - [Fact] - public void AddImageFormatDetectorNullthrows() - { - Assert.Throws(() => - { - this.DefaultConfiguration.AddImageFormatDetector(null); - }); - } - - [Fact] - public void RegisterNullMimeTypeEncoder() + public void TestDefultConfigurationReadOriginIsCurrent() { - Assert.Throws(() => - { - this.DefaultConfiguration.SetEncoder(null, new Mock().Object); - }); - Assert.Throws(() => - { - this.DefaultConfiguration.SetEncoder(ImageFormats.Bmp, null); - }); - Assert.Throws(() => - { - this.DefaultConfiguration.SetEncoder(null, null); - }); - } - - [Fact] - public void RegisterNullSetDecoder() - { - Assert.Throws(() => - { - this.DefaultConfiguration.SetDecoder(null, new Mock().Object); - }); - Assert.Throws(() => - { - this.DefaultConfiguration.SetDecoder(ImageFormats.Bmp, null); - }); - Assert.Throws(() => - { - this.DefaultConfiguration.SetDecoder(null, null); - }); - } - - [Fact] - public void RegisterMimeTypeEncoderReplacesLast() - { - IImageEncoder encoder1 = new Mock().Object; - this.ConfigurationEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder1); - IImageEncoder found = this.ConfigurationEmpty.FindEncoder(TestFormat.GlobalTestFormat); - Assert.Equal(encoder1, found); - - IImageEncoder encoder2 = new Mock().Object; - this.ConfigurationEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder2); - IImageEncoder found2 = this.ConfigurationEmpty.FindEncoder(TestFormat.GlobalTestFormat); - Assert.Equal(encoder2, found2); - Assert.NotEqual(found, found2); + Assert.True(Configuration.Default.ReadOrigin == ReadOrigin.Current); } + /// + /// Test that the default configuration parallel options max degrees of parallelism matches the + /// environment processor count. + /// [Fact] - public void RegisterMimeTypeDecoderReplacesLast() + public void TestDefultConfigurationMaxDegreeOfParallelism() { - IImageDecoder decoder1 = new Mock().Object; - this.ConfigurationEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder1); - IImageDecoder found = this.ConfigurationEmpty.FindDecoder(TestFormat.GlobalTestFormat); - Assert.Equal(decoder1, found); - - IImageDecoder decoder2 = new Mock().Object; - this.ConfigurationEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder2); - IImageDecoder found2 = this.ConfigurationEmpty.FindDecoder(TestFormat.GlobalTestFormat); - Assert.Equal(decoder2, found2); - Assert.NotEqual(found, found2); + Assert.True(Configuration.Default.ParallelOptions.MaxDegreeOfParallelism == Environment.ProcessorCount); } - [Fact] public void ConstructorCallConfigureOnFormatProvider() { diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs index 6a55d8a561..5fcb860be5 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs @@ -1,17 +1,16 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System.IO; -using System.Linq; +using System; +using System.Numerics; +using SixLabors.ImageSharp.Helpers; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; using Xunit; namespace SixLabors.ImageSharp.Tests { - using System; - using System.Numerics; - public class DrawImageTest : FileTestBase { private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32; @@ -23,8 +22,6 @@ namespace SixLabors.ImageSharp.Tests TestImages.Gif.Rings }; - object[][] Modes = System.Enum.GetValues(typeof(PixelBlenderMode)).Cast().Select(x => new object[] { x }).ToArray(); - [Theory] [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Normal)] [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Multiply)] @@ -39,9 +36,10 @@ namespace SixLabors.ImageSharp.Tests where TPixel : struct, IPixel { using (Image image = provider.GetImage()) - using (Image blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes)) + using (var blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes)) { - image.Mutate(x => x.DrawImage(blend, mode, .75f, new Size(image.Width / 2, image.Height / 2), new Point(image.Width / 4, image.Height / 4))); + blend.Mutate(x => x.Resize(image.Width / 2, image.Height / 2)); + image.Mutate(x => x.DrawImage(blend, mode, .75f, new Point(image.Width / 4, image.Height / 4))); image.DebugSave(provider, new { mode }); } } @@ -52,15 +50,25 @@ namespace SixLabors.ImageSharp.Tests where TPixel : struct, IPixel { using (Image image = provider.GetImage()) - using (Image blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes)) + using (var blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes)) { Matrix3x2 rotate = Matrix3x2Extensions.CreateRotationDegrees(45F); Matrix3x2 scale = Matrix3x2Extensions.CreateScale(new SizeF(.25F, .25F)); + Matrix3x2 matrix = rotate * scale; + + // Lets center the matrix so we can tell whether any cut-off issues we may have belong to the drawing processor + Rectangle srcBounds = blend.Bounds(); + Rectangle destBounds = TransformHelpers.GetTransformedBoundingRectangle(srcBounds, matrix); + Matrix3x2 centeredMatrix = TransformHelpers.GetCenteredTransformMatrix(srcBounds, destBounds, matrix); - blend.Mutate(x => x.Transform(rotate * scale)); + // We pass a new rectangle here based on the dest bounds since we've offset the matrix + blend.Mutate(x => x.Transform( + centeredMatrix, + KnownResamplers.Bicubic, + new Rectangle(0, 0, destBounds.Width, destBounds.Height))); var position = new Point((image.Width - blend.Width) / 2, (image.Height - blend.Height) / 2); - image.Mutate(x => x.DrawImage(blend, mode, .75F, new Size(blend.Width, blend.Height), position)); + image.Mutate(x => x.DrawImage(blend, mode, .75F, position)); image.DebugSave(provider, new[] { "Transformed" }); } } @@ -78,7 +86,7 @@ namespace SixLabors.ImageSharp.Tests Rgba32 backgroundPixel = background[0, 0]; Rgba32 overlayPixel = overlay[Math.Abs(xy) + 1, Math.Abs(xy) + 1]; - background.Mutate(x => x.DrawImage(overlay, PixelBlenderMode.Normal, 1F, new Size(overlay.Width, overlay.Height), new Point(xy, xy))); + background.Mutate(x => x.DrawImage(overlay, PixelBlenderMode.Normal, 1F, new Point(xy, xy))); Assert.Equal(Rgba32.White, backgroundPixel); Assert.Equal(overlayPixel, background[0, 0]); @@ -100,7 +108,7 @@ namespace SixLabors.ImageSharp.Tests Rgba32 backgroundPixel = background[xy - 1, xy - 1]; Rgba32 overlayPixel = overlay[0, 0]; - background.Mutate(x => x.DrawImage(overlay, PixelBlenderMode.Normal, 1F, new Size(overlay.Width, overlay.Height), new Point(xy, xy))); + background.Mutate(x => x.DrawImage(overlay, PixelBlenderMode.Normal, 1F, new Point(xy, xy))); Assert.Equal(Rgba32.White, backgroundPixel); Assert.Equal(overlayPixel, background[xy, xy]); @@ -109,4 +117,4 @@ namespace SixLabors.ImageSharp.Tests } } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs index db6c1157c4..79ebf47787 100644 --- a/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs @@ -1,16 +1,14 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.Numerics; -using SixLabors.ImageSharp; using SixLabors.ImageSharp.Drawing; using SixLabors.ImageSharp.Drawing.Pens; using SixLabors.ImageSharp.Drawing.Processors; -using SixLabors.ImageSharp.PixelFormats; using Moq; using Xunit; using SixLabors.ImageSharp.Drawing.Brushes; +using SixLabors.Primitives; namespace SixLabors.ImageSharp.Tests.Drawing { @@ -25,18 +23,18 @@ namespace SixLabors.ImageSharp.Tests.Drawing [InlineData(false, 16, 4)] // we always do 4 sub=pixels when antialising is off. public void MinimumAntialiasSubpixelDepth(bool antialias, int antialiasSubpixelDepth, int expectedAntialiasSubpixelDepth) { - SixLabors.Primitives.Rectangle bounds = new SixLabors.Primitives.Rectangle(0, 0, 1, 1); + var bounds = new SixLabors.Primitives.Rectangle(0, 0, 1, 1); - Mock> brush = new Mock>(); - Mock region = new Mock(); + var brush = new Mock>(); + var region = new Mock(); region.Setup(x => x.Bounds).Returns(bounds); - GraphicsOptions options = new GraphicsOptions(antialias) + var options = new GraphicsOptions(antialias) { AntialiasSubpixelDepth = 1 }; - FillRegionProcessor processor = new FillRegionProcessor(brush.Object, region.Object, options); - Image img = new Image(1, 1); + var processor = new FillRegionProcessor(brush.Object, region.Object, options); + var img = new Image(1, 1); processor.Apply(img, bounds); region.Verify(x => x.Scan(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(4)); @@ -45,31 +43,11 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Fact] public void FillOffCanvas() { - - SixLabors.Primitives.Rectangle bounds = new SixLabors.Primitives.Rectangle(-100, -10, 10, 10); - - Mock> brush = new Mock>(); - Mock region = new Mock(); - region.Setup(x => x.Bounds).Returns(bounds); - - region.Setup(x => x.MaxIntersections).Returns(10); - region.Setup(x => x.Scan(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns>((y, span) => - { - if (y < 5) - { - span[0] = -10f; - span[1] = 100f; - return 2; - } - return 0; - }); - - GraphicsOptions options = new GraphicsOptions(true) - { - }; - FillRegionProcessor processor = new FillRegionProcessor(brush.Object, region.Object, options); - Image img = new Image(10, 10); + var bounds = new Rectangle(-100, -10, 10, 10); + var brush = new Mock>(); + var options = new GraphicsOptions(true); + var processor = new FillRegionProcessor(brush.Object, new MockRegion(), options); + var img = new Image(10, 10); processor.Apply(img, bounds); } @@ -85,5 +63,24 @@ namespace SixLabors.ImageSharp.Tests.Drawing })); } } + + // Mocking the region throws an error in netcore2.0 + private class MockRegion : Region + { + public override Rectangle Bounds => new Rectangle(-100, -10, 10, 10); + + public override int MaxIntersections => 10; + + public override int Scan(float y, float[] buffer, int offset) + { + if (y < 5) + { + buffer[0] = -10f; + buffer[1] = 100f; + return 2; + } + return 0; + } + } } } diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs index 941807f542..5ca09ad1d2 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs @@ -1,131 +1,144 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.IO; -using System.Numerics; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Drawing; -using SixLabors.ImageSharp.Drawing.Brushes; -using SixLabors.ImageSharp.Drawing.Pens; -using SixLabors.ImageSharp.Drawing.Processors; -using SixLabors.ImageSharp.Processing; -using Moq; -using SixLabors.Primitives; -using SixLabors.Shapes; -using Xunit; - namespace SixLabors.ImageSharp.Tests.Drawing.Paths { - public class ShapeRegionTests + using System; + + using Moq; + + using SixLabors.ImageSharp.Drawing; + using SixLabors.Primitives; + using SixLabors.Shapes; + + using Xunit; + + public class ShapeRegionTests { private readonly Mock pathMock; - private readonly SixLabors.Primitives.RectangleF bounds; + + private readonly RectangleF bounds; public ShapeRegionTests() { this.pathMock = new Mock(); this.bounds = new RectangleF(10.5f, 10, 10, 10); - pathMock.Setup(x => x.Bounds).Returns(this.bounds); + this.pathMock.Setup(x => x.Bounds).Returns(this.bounds); // wire up the 2 mocks to reference eachother - pathMock.Setup(x => x.AsClosedPath()).Returns(() => pathMock.Object); + this.pathMock.Setup(x => x.AsClosedPath()).Returns(() => this.pathMock.Object); } [Fact] public void ShapeRegionWithPathCallsAsShape() { - new ShapeRegion(pathMock.Object); + new ShapeRegion(this.pathMock.Object); - pathMock.Verify(x => x.AsClosedPath()); + this.pathMock.Verify(x => x.AsClosedPath()); } [Fact] public void ShapeRegionWithPathRetainsShape() { - ShapeRegion region = new ShapeRegion(pathMock.Object); + ShapeRegion region = new ShapeRegion(this.pathMock.Object); - Assert.Equal(pathMock.Object, region.Shape); + Assert.Equal(this.pathMock.Object, region.Shape); } [Fact] public void ShapeRegionFromPathConvertsBoundsProxyToShape() { - ShapeRegion region = new ShapeRegion(pathMock.Object); + ShapeRegion region = new ShapeRegion(this.pathMock.Object); - Assert.Equal(Math.Floor(bounds.Left), region.Bounds.Left); - Assert.Equal(Math.Ceiling(bounds.Right), region.Bounds.Right); + Assert.Equal(Math.Floor(this.bounds.Left), region.Bounds.Left); + Assert.Equal(Math.Ceiling(this.bounds.Right), region.Bounds.Right); - pathMock.Verify(x => x.Bounds); + this.pathMock.Verify(x => x.Bounds); } [Fact] public void ShapeRegionFromPathMaxIntersectionsProxyToShape() { - ShapeRegion region = new ShapeRegion(pathMock.Object); + ShapeRegion region = new ShapeRegion(this.pathMock.Object); int i = region.MaxIntersections; - pathMock.Verify(x => x.MaxIntersections); + this.pathMock.Verify(x => x.MaxIntersections); } [Fact] public void ShapeRegionFromPathScanYProxyToShape() { int yToScan = 10; - ShapeRegion region = new ShapeRegion(pathMock.Object); - - pathMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((s, e, b, o) => { - Assert.Equal(yToScan, s.Y); - Assert.Equal(yToScan, e.Y); - Assert.True(s.X < bounds.Left); - Assert.True(e.X > bounds.Right); - }).Returns(0); + ShapeRegion region = new ShapeRegion(this.pathMock.Object); + + this.pathMock + .Setup( + x => x.FindIntersections( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())).Callback( + (s, e, b, o) => + { + Assert.Equal(yToScan, s.Y); + Assert.Equal(yToScan, e.Y); + Assert.True(s.X < this.bounds.Left); + Assert.True(e.X > this.bounds.Right); + }).Returns(0); int i = region.Scan(yToScan, new float[0], 0); - pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + this.pathMock.Verify( + x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.Once); } [Fact] public void ShapeRegionFromShapeScanYProxyToShape() { int yToScan = 10; - ShapeRegion region = new ShapeRegion(pathMock.Object); - - pathMock.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((s, e, b, o) => { - Assert.Equal(yToScan, s.Y); - Assert.Equal(yToScan, e.Y); - Assert.True(s.X < bounds.Left); - Assert.True(e.X > bounds.Right); - }).Returns(0); + ShapeRegion region = new ShapeRegion(this.pathMock.Object); + + this.pathMock + .Setup( + x => x.FindIntersections( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())).Callback( + (s, e, b, o) => + { + Assert.Equal(yToScan, s.Y); + Assert.Equal(yToScan, e.Y); + Assert.True(s.X < this.bounds.Left); + Assert.True(e.X > this.bounds.Right); + }).Returns(0); int i = region.Scan(yToScan, new float[0], 0); - pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + this.pathMock.Verify( + x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.Once); } [Fact] public void ShapeRegionFromShapeConvertsBoundsProxyToShape() { - ShapeRegion region = new ShapeRegion(pathMock.Object); + ShapeRegion region = new ShapeRegion(this.pathMock.Object); - Assert.Equal(Math.Floor(bounds.Left), region.Bounds.Left); - Assert.Equal(Math.Ceiling(bounds.Right), region.Bounds.Right); + Assert.Equal(Math.Floor(this.bounds.Left), region.Bounds.Left); + Assert.Equal(Math.Ceiling(this.bounds.Right), region.Bounds.Right); - pathMock.Verify(x => x.Bounds); + this.pathMock.Verify(x => x.Bounds); } [Fact] public void ShapeRegionFromShapeMaxIntersectionsProxyToShape() { - ShapeRegion region = new ShapeRegion(pathMock.Object); + ShapeRegion region = new ShapeRegion(this.pathMock.Object); int i = region.MaxIntersections; - pathMock.Verify(x => x.MaxIntersections); + this.pathMock.Verify(x => x.MaxIntersections); } } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs index 07e75acf43..70badd34c5 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs @@ -9,71 +9,56 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Drawing { - public class SolidBezierTests : FileTestBase + [GroupOutput("Drawing")] + public class SolidBezierTests { - [Fact] - public void ImageShouldBeOverlayedByFilledPolygon() + [Theory] + [WithBlankImages(500, 500, PixelTypes.Rgba32)] + public void FilledBezier(TestImageProvider provider) + where TPixel : struct, IPixel { - string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledBezier"); - SixLabors.Primitives.PointF[] simplePath = new SixLabors.Primitives.PointF[] { + Primitives.PointF[] simplePath = { new Vector2(10, 400), new Vector2(30, 10), new Vector2(240, 30), new Vector2(300, 400) }; - using (Image image = new Image(500, 500)) - { - image.Mutate(x => x - .BackgroundColor(Rgba32.Blue) - .Fill(Rgba32.HotPink, new Polygon(new CubicBezierLineSegment(simplePath)))); - image.Save($"{path}/Simple.png"); - using (PixelAccessor sourcePixels = image.Lock()) - { - Assert.Equal(Rgba32.HotPink, sourcePixels[150, 300]); + TPixel blue = NamedColors.Blue; + TPixel hotPink = NamedColors.HotPink; - //curve points should not be never be set - Assert.Equal(Rgba32.Blue, sourcePixels[240, 30]); - - // inside shape should not be empty - Assert.Equal(Rgba32.HotPink, sourcePixels[200, 250]); - } + using (Image image = provider.GetImage()) + { + + image.Mutate(x => x + .BackgroundColor(blue) + .Fill(hotPink, new Polygon(new CubicBezierLineSegment(simplePath)))); + image.DebugSave(provider); + image.CompareToReferenceOutput(provider); } } - [Fact] - public void ImageShouldBeOverlayedByFilledPolygonOpacity() + [Theory] + [WithBlankImages(500, 500, PixelTypes.Rgba32)] + public void OverlayByFilledPolygonOpacity(TestImageProvider provider) + where TPixel : struct, IPixel { - string path = TestEnvironment.CreateOutputDirectory("Drawing", "FilledBezier"); - SixLabors.Primitives.PointF[] simplePath = new SixLabors.Primitives.PointF[] { + Primitives.PointF[] simplePath = { new Vector2(10, 400), new Vector2(30, 10), new Vector2(240, 30), new Vector2(300, 400) }; + Rgba32 color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); - using (Image image = new Image(500, 500)) + using (var image = provider.GetImage() as Image) { image.Mutate(x => x .BackgroundColor(Rgba32.Blue) .Fill(color, new Polygon(new CubicBezierLineSegment(simplePath)))); - image.Save($"{path}/Opacity.png"); - - //shift background color towards forground color by the opacity amount - Rgba32 mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f)); - - using (PixelAccessor sourcePixels = image.Lock()) - { - //top of curve - Assert.Equal(mergedColor, sourcePixels[138, 116]); - - //curve points should not be never be set - Assert.Equal(Rgba32.Blue, sourcePixels[240, 30]); - - // inside shape should not be empty - Assert.Equal(mergedColor, sourcePixels[200, 250]); - } + image.DebugSave(provider); + image.CompareToReferenceOutput(provider); } } } diff --git a/tests/ImageSharp.Tests/FakeImageOperationsProvider.cs b/tests/ImageSharp.Tests/FakeImageOperationsProvider.cs index f750bfcfad..db1e7903de 100644 --- a/tests/ImageSharp.Tests/FakeImageOperationsProvider.cs +++ b/tests/ImageSharp.Tests/FakeImageOperationsProvider.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.Collections.Generic; using System.Linq; -using System.Text; +using SixLabors.ImageSharp.Helpers; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Tests @@ -23,13 +23,13 @@ namespace SixLabors.ImageSharp.Tests public IEnumerable> Created(Image source) where TPixel : struct, IPixel { return this.ImageOperators.OfType>() - .Where(x => x.source == source); + .Where(x => x.Source == source); } - public IEnumerable.AppliedOpperation> AppliedOperations(Image source) where TPixel : struct, IPixel + public IEnumerable.AppliedOperation> AppliedOperations(Image source) where TPixel : struct, IPixel { return Created(source) - .SelectMany(x => x.applied); + .SelectMany(x => x.Applied); } public IInternalImageProcessingContext CreateImageProcessingContext(Image source, bool mutate) where TPixel : struct, IPixel @@ -39,36 +39,36 @@ namespace SixLabors.ImageSharp.Tests return op; } - public class FakeImageOperations : IInternalImageProcessingContext where TPixel : struct, IPixel { - public Image source; - - public List applied = new List(); - public bool mutate; + private bool mutate; public FakeImageOperations(Image source, bool mutate) { this.mutate = mutate; - if (mutate) - { - this.source = source; - } - else - { - this.source = source?.Clone(); - } + this.Source = mutate ? source : source?.Clone(); } + public Image Source { get; } + + public List Applied { get; } = new List(); + + public MemoryManager MemoryManager => this.Source.GetConfiguration().MemoryManager; + public Image Apply() { - return source; + return this.Source; + } + + public Size GetCurrentSize() + { + return this.Source.Size(); } public IImageProcessingContext ApplyProcessor(IImageProcessor processor, Rectangle rectangle) { - applied.Add(new AppliedOpperation + this.Applied.Add(new AppliedOperation { Processor = processor, Rectangle = rectangle @@ -78,13 +78,14 @@ namespace SixLabors.ImageSharp.Tests public IImageProcessingContext ApplyProcessor(IImageProcessor processor) { - applied.Add(new AppliedOpperation + this.Applied.Add(new AppliedOperation { Processor = processor }); return this; } - public struct AppliedOpperation + + public struct AppliedOperation { public Rectangle? Rectangle { get; set; } public IImageProcessor Processor { get; set; } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 2c0121803b..d958278f6e 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -1,19 +1,27 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats; +using System.IO; +using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.PixelFormats; using Xunit; + // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { - using System.IO; - - using SixLabors.ImageSharp.Formats.Bmp; + using static TestImages.Bmp; - public class BmpDecoderTests : FileTestBase + public class BmpDecoderTests { + public const PixelTypes CommonNonDefaultPixelTypes = + PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; + + public static readonly string[] AllBmpFiles = + { + Car, F, NegHeight, CoreHeader, V5Header, RLE, RLEInverted, Bit8, Bit8Inverted, Bit16, Bit16Inverted + }; + [Theory] [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32)] public void DecodeBmp(TestImageProvider provider) @@ -27,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests } [Theory] - [WithFile(TestImages.Bmp.F, CommonNonDefaultPixelTypes)] + [WithFile(F, CommonNonDefaultPixelTypes)] public void BmpDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) where TPixel : struct, IPixel { @@ -39,18 +47,18 @@ namespace SixLabors.ImageSharp.Tests } [Theory] - [InlineData(TestImages.Bmp.Car, 24)] - [InlineData(TestImages.Bmp.F, 24)] - [InlineData(TestImages.Bmp.NegHeight, 24)] - [InlineData(TestImages.Bmp.Bit8, 8)] - [InlineData(TestImages.Bmp.Bit8Inverted, 8)] + [InlineData(Car, 24)] + [InlineData(F, 24)] + [InlineData(NegHeight, 24)] + [InlineData(Bit8, 8)] + [InlineData(Bit8Inverted, 8)] public void DetectPixelSize(string imagePath, int expectedPixelSize) { - TestFile testFile = TestFile.Create(imagePath); + var testFile = TestFile.Create(imagePath); using (var stream = new MemoryStream(testFile.Bytes, false)) { Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); } } } -} +} \ 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 d96d3def5e..3a5fbe8387 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -5,31 +5,62 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.PixelFormats; using Xunit; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + + using Xunit.Abstractions; + public class BmpEncoderTests : FileTestBase { - public static readonly TheoryData BitsPerPixel - = new TheoryData + public static readonly TheoryData BitsPerPixel = + new TheoryData + { + BmpBitsPerPixel.Pixel24, + BmpBitsPerPixel.Pixel32 + }; + + public BmpEncoderTests(ITestOutputHelper output) { - BmpBitsPerPixel.Pixel24, - BmpBitsPerPixel.Pixel32 - }; + this.Output = output; + } + + private ITestOutputHelper Output { get; } [Theory] - [MemberData(nameof(BitsPerPixel))] - public void BitmapCanEncodeDifferentBitRates(BmpBitsPerPixel bitsPerPixel) + [WithTestPatternImages(nameof(BitsPerPixel), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] + public void Encode_IsNotBoundToSinglePixelType(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel { - string path = TestEnvironment.CreateOutputDirectory("Bmp"); + TestBmpEncoderCore(provider, bitsPerPixel); + } - foreach (TestFile file in Files) + [Theory] + [WithTestPatternImages(nameof(BitsPerPixel), 48, 24, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel), 47, 8, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel), 49, 7, PixelTypes.Rgba32)] + [WithSolidFilledImages(nameof(BitsPerPixel), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel), 7, 5, PixelTypes.Rgba32)] + public void Encode_WorksWithDifferentSizes(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel + { + TestBmpEncoderCore(provider, bitsPerPixel); + } + + private static void TestBmpEncoderCore(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) { - string filename = file.GetFileNameWithoutExtension(bitsPerPixel); - using (Image image = file.CreateImage()) - { - image.Save($"{path}/{filename}.bmp", new BmpEncoder { BitsPerPixel = bitsPerPixel }); - } + // there is no alpha in bmp! + image.Mutate(c => c.MakeOpaque()); + + var encoder = new BmpEncoder { BitsPerPixel = bitsPerPixel }; + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder); } } } diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 97128e2c93..22a811feee 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -14,8 +14,6 @@ namespace SixLabors.ImageSharp.Tests { using System; - - public class GeneralFormatTests : FileTestBase { [Theory] diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index 9a095548a7..9cdb9f8b1a 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -6,47 +6,89 @@ using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; using Xunit; +using System.IO; +using SixLabors.ImageSharp.Advanced; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { - using System.IO; - using SixLabors.ImageSharp.Advanced; + using System.Collections.Generic; + + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; public class GifDecoderTests { - private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; + private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; + + public static readonly string[] MultiFrameTestFiles = + { + TestImages.Gif.Giphy, TestImages.Gif.Kumin + }; + + public static readonly string[] BasicVerificationFiles = + { + TestImages.Gif.Cheers, + TestImages.Gif.Rings, + + // previously DecodeBadApplicationExtensionLength: + TestImages.Gif.Issues.BadAppExtLength, + TestImages.Gif.Issues.BadAppExtLength_2, - public static readonly string[] TestFiles = { TestImages.Gif.Giphy, TestImages.Gif.Rings, TestImages.Gif.Trans }; + // previously DecodeBadDescriptorDimensionsLength: + TestImages.Gif.Issues.BadDescriptorWidth + }; + + private static readonly Dictionary BasicVerificationFrameCount = + new Dictionary + { + [TestImages.Gif.Cheers] = 93, + [TestImages.Gif.Issues.BadDescriptorWidth] = 36, + }; public static readonly string[] BadAppExtFiles = { TestImages.Gif.Issues.BadAppExtLength, TestImages.Gif.Issues.BadAppExtLength_2 }; [Theory] - [WithFileCollection(nameof(TestFiles), PixelTypes)] - public void DecodeAndReSave(TestImageProvider imageProvider) + [WithFileCollection(nameof(MultiFrameTestFiles), PixelTypes.Rgba32)] + public void Decode_VerifyAllFrames(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = imageProvider.GetImage()) + using (Image image = provider.GetImage()) { - imageProvider.Utility.SaveTestOutputFile(image, "bmp"); - imageProvider.Utility.SaveTestOutputFile(image, "gif"); + image.DebugSaveMultiFrame(provider); + image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); } } [Theory] - [WithFileCollection(nameof(TestFiles), PixelTypes)] - public void DecodeResizeAndSave(TestImageProvider imageProvider) + [WithFile(TestImages.Gif.Trans, TestPixelTypes)] + public void GifDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = imageProvider.GetImage()) + using (Image image = provider.GetImage()) { - image.Mutate(x => x.Resize(new Size(image.Width / 2, image.Height / 2))); - - imageProvider.Utility.SaveTestOutputFile(image, "bmp"); - imageProvider.Utility.SaveTestOutputFile(image, "gif"); + image.DebugSave(provider); + image.CompareFirstFrameToReferenceOutput(provider, ImageComparer.Exact); } } + [Theory] + [WithFileCollection(nameof(BasicVerificationFiles), PixelTypes.Rgba32)] + public void Decode_VerifyRootFrameAndFrameCount(TestImageProvider provider) + where TPixel : struct, IPixel + { + if (!BasicVerificationFrameCount.TryGetValue(provider.SourceFileOrDescription, out int expectedFrameCount)) + { + expectedFrameCount = 1; + } + + using (Image image = provider.GetImage()) + { + Assert.Equal(expectedFrameCount, image.Frames.Count); + image.DebugSave(provider); + image.CompareFirstFrameToReferenceOutput(provider, ImageComparer.Exact); + } + } + [Fact] public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() { @@ -149,27 +191,5 @@ namespace SixLabors.ImageSharp.Tests } } } - - [Theory] - [WithFileCollection(nameof(BadAppExtFiles), PixelTypes.Rgba32)] - public void DecodeBadApplicationExtensionLength(TestImageProvider imageProvider) - where TPixel : struct, IPixel - { - using (Image image = imageProvider.GetImage()) - { - imageProvider.Utility.SaveTestOutputFile(image, "bmp"); - } - } - - [Theory] - [WithFile(TestImages.Gif.Issues.BadDescriptorWidth, PixelTypes.Rgba32)] - public void DecodeBadDescriptorDimensionsLength(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - provider.Utility.SaveTestOutputFile(image, "bmp"); - } - } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index a06e36e2a6..a2f4806f37 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -7,15 +7,16 @@ using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.PixelFormats; using Xunit; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { public class GifEncoderTests { - private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; + private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; [Theory] - [WithTestPatternImages(100, 100, PixelTypes)] + [WithTestPatternImages(100, 100, TestPixelTypes)] public void EncodeGeneratedPatterns(TestImageProvider provider) where TPixel : struct, IPixel { @@ -78,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public void Encode_CommentIsToLong_CommentIsTrimmed() + public void Encode_WhenCommentIsTooLong_CommentIsTrimmed() { using (Image input = new Image(1, 1)) { diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs new file mode 100644 index 0000000000..a6f6600f05 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs @@ -0,0 +1,129 @@ +// 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.Linq; +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 Xunit; + + +namespace SixLabors.ImageSharp.Tests +{ + public class ImageFormatManagerTests + { + public ImageFormatManager FormatsManagerEmpty { get; private set; } + public ImageFormatManager DefaultFormatsManager { get; private set; } + + public ImageFormatManagerTests() + { + this.DefaultFormatsManager = Configuration.Default.ImageFormatsManager; + this.FormatsManagerEmpty = new ImageFormatManager(); + } + + [Fact] + public void IfAutoloadWellKnownFormatsIsTrueAllFormatsAreLoaded() + { + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + } + + [Fact] + public void AddImageFormatDetectorNullthrows() + { + Assert.Throws(() => + { + this.DefaultFormatsManager.AddImageFormatDetector(null); + }); + } + + [Fact] + public void RegisterNullMimeTypeEncoder() + { + Assert.Throws(() => + { + this.DefaultFormatsManager.SetEncoder(null, new Mock().Object); + }); + Assert.Throws(() => + { + this.DefaultFormatsManager.SetEncoder(ImageFormats.Bmp, null); + }); + Assert.Throws(() => + { + this.DefaultFormatsManager.SetEncoder(null, null); + }); + } + + [Fact] + public void RegisterNullSetDecoder() + { + Assert.Throws(() => + { + this.DefaultFormatsManager.SetDecoder(null, new Mock().Object); + }); + Assert.Throws(() => + { + this.DefaultFormatsManager.SetDecoder(ImageFormats.Bmp, null); + }); + Assert.Throws(() => + { + this.DefaultFormatsManager.SetDecoder(null, null); + }); + } + + [Fact] + public void RegisterMimeTypeEncoderReplacesLast() + { + IImageEncoder encoder1 = new Mock().Object; + this.FormatsManagerEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder1); + IImageEncoder found = this.FormatsManagerEmpty.FindEncoder(TestFormat.GlobalTestFormat); + Assert.Equal(encoder1, found); + + IImageEncoder encoder2 = new Mock().Object; + this.FormatsManagerEmpty.SetEncoder(TestFormat.GlobalTestFormat, encoder2); + IImageEncoder found2 = this.FormatsManagerEmpty.FindEncoder(TestFormat.GlobalTestFormat); + Assert.Equal(encoder2, found2); + Assert.NotEqual(found, found2); + } + + [Fact] + public void RegisterMimeTypeDecoderReplacesLast() + { + IImageDecoder decoder1 = new Mock().Object; + this.FormatsManagerEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder1); + IImageDecoder found = this.FormatsManagerEmpty.FindDecoder(TestFormat.GlobalTestFormat); + Assert.Equal(decoder1, found); + + IImageDecoder decoder2 = new Mock().Object; + this.FormatsManagerEmpty.SetDecoder(TestFormat.GlobalTestFormat, decoder2); + IImageDecoder found2 = this.FormatsManagerEmpty.FindDecoder(TestFormat.GlobalTestFormat); + Assert.Equal(decoder2, found2); + Assert.NotEqual(found, found2); + } + + [Fact] + public void AddFormatCallsConfig() + { + var provider = new Mock(); + var config = new Configuration(); + config.Configure(provider.Object); + + provider.Verify(x => x.Configure(config)); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs index 191cfec731..e50d84852a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { Block8x8F block = CreateRandomFloatBlock(0, 100); - using (var buffer = new Buffer2D(20, 20)) + using (var buffer = Configuration.Default.MemoryManager.Allocate2D(20, 20)) { BufferArea area = buffer.GetArea(5, 10, 8, 8); block.CopyTo(area); @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var start = new Point(50, 50); - using (var buffer = new Buffer2D(100, 100)) + using (var buffer = Configuration.Default.MemoryManager.Allocate2D(100, 100)) { BufferArea area = buffer.GetArea(start.X, start.Y, 8 * horizontalFactor, 8 * verticalFactor); block.CopyTo(area, horizontalFactor, verticalFactor); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs similarity index 51% rename from tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs rename to tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs index 887e9d7e95..193e26fcbd 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs @@ -1,19 +1,17 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - - - // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { + using System; using System.Numerics; - using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; + using SixLabors.ImageSharp.Formats.Jpeg.Common; + using SixLabors.ImageSharp.Helpers; using SixLabors.ImageSharp.PixelFormats; + using SixLabors.Primitives; using Xunit; - public class JpegUtilsTests + public class GenericBlock8x8Tests { public static Image CreateTestImage() where TPixel : struct, IPixel @@ -25,10 +23,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { for (int j = 0; j < 10; j++) { - var v = new Vector4(i / 10f, j / 10f, 0, 1); - + var rgba = new Rgba32((byte)(i+1), (byte)(j+1), (byte)200, (byte)255); var color = default(TPixel); - color.PackFromVector4(v); + color.PackFromRgba32(rgba); pixels[i, j] = color; } @@ -39,40 +36,38 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [Theory] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgba32| PixelTypes.Rgba32 | PixelTypes.Argb32)] - public void CopyStretchedRGBTo_FromOrigo(TestImageProvider provider) + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32 /* | PixelTypes.Rgba32 | PixelTypes.Argb32*/)] + public void LoadAndStretchCorners_FromOrigo(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image src = provider.GetImage()) - using (Image dest = new Image(8,8)) - using (PixelArea area = new PixelArea(8, 8, ComponentOrder.Xyz)) - using (PixelAccessor s = src.Lock()) - using (PixelAccessor d = dest.Lock()) + using (Image s = provider.GetImage()) { - s.CopyRGBBytesStretchedTo(area, 0, 0); - d.CopyFrom(area, 0, 0); + var d = default(GenericBlock8x8); + d.LoadAndStretchEdges(s.Frames.RootFrame, 0, 0); + + TPixel a = s.Frames.RootFrame[0, 0]; + TPixel b = d[0, 0]; Assert.Equal(s[0, 0], d[0, 0]); + Assert.Equal(s[1, 0], d[1, 0]); + Assert.Equal(s[7, 0], d[7, 0]); + Assert.Equal(s[0, 1], d[0, 1]); + Assert.Equal(s[1, 1], d[1, 1]); Assert.Equal(s[7, 0], d[7, 0]); Assert.Equal(s[0, 7], d[0, 7]); Assert.Equal(s[7, 7], d[7, 7]); } - } [Theory] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgba32| PixelTypes.Rgba32 | PixelTypes.Argb32)] - public void CopyStretchedRGBTo_WithOffset(TestImageProvider provider) + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32)] + public unsafe void LoadAndStretchCorners_WithOffset(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image src = provider.GetImage()) - using (PixelArea area = new PixelArea(8, 8, ComponentOrder.Xyz)) - using (Image dest = new Image(8, 8)) - using (PixelAccessor s = src.Lock()) - using (PixelAccessor d = dest.Lock()) + using (Image s = provider.GetImage()) { - s.CopyRGBBytesStretchedTo(area, 7, 6); - d.CopyFrom(area, 0, 0); + var d = default(GenericBlock8x8); + d.LoadAndStretchEdges(s.Frames.RootFrame, 6, 7); Assert.Equal(s[6, 7], d[0, 0]); Assert.Equal(s[6, 8], d[0, 1]); @@ -102,5 +97,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(s[9, 9], d[7, 7]); } } + + [Fact] + public void Indexer() + { + var block = default(GenericBlock8x8); + Span span = block.AsSpanUnsafe(); + Assert.Equal(64, span.Length); + + for (int i = 0; i < 64; i++) + { + span[i] = new Rgb24((byte)i, (byte)(2 * i), (byte)(3 * i)); + } + + Rgb24 expected00 = new Rgb24(0, 0, 0); + Rgb24 expected07 = new Rgb24(7, 14, 21); + Rgb24 expected11 = new Rgb24(9, 18, 27); + Rgb24 expected77 = new Rgb24(63, 126, 189); + Rgb24 expected67 = new Rgb24(62, 124, 186); + + Assert.Equal(expected00, block[0, 0]); + Assert.Equal(expected07, block[7, 0]); + Assert.Equal(expected11, block[1, 1]); + Assert.Equal(expected67, block[6, 7]); + Assert.Equal(expected77, block[7, 7]); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 7e0dc915ce..bf6b1f4ab2 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -39,10 +39,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [MemberData(nameof(CommonConversionData))] public void ConvertFromYCbCrBasic(int inputBufferLength, int resultBufferLength, int seed) { - ValidateConversion(new JpegColorConverter.FromYCbCrBasic(), 3, inputBufferLength, resultBufferLength, seed, ValidateYCbCr); + ValidateRgbToYCbCrConversion( + new JpegColorConverter.FromYCbCrBasic(), + 3, + inputBufferLength, + resultBufferLength, + seed); } - private static void ValidateYCbCr(JpegColorConverter.ComponentValues values, Span result, int i) + private static void ValidateYCbCr(JpegColorConverter.ComponentValues values, Vector4[] result, int i) { float y = values.Component0[i]; float cb = values.Component1[i]; @@ -63,20 +68,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(8, 3)] public void FromYCbCrSimd_ConvertCore(int size, int seed) { - ValidateConversion(JpegColorConverter.FromYCbCrSimd.ConvertCore, 3, size, size, seed, ValidateYCbCr); + JpegColorConverter.ComponentValues values = CreateRandomValues(3, size, seed); + Vector4[] result = new Vector4[size]; + + JpegColorConverter.FromYCbCrSimd.ConvertCore(values, result); + + for (int i = 0; i < size; i++) + { + ValidateYCbCr(values, result, i); + } } [Theory] [MemberData(nameof(CommonConversionData))] public void FromYCbCrSimd(int inputBufferLength, int resultBufferLength, int seed) { - ValidateConversion( + ValidateRgbToYCbCrConversion( new JpegColorConverter.FromYCbCrSimd(), 3, inputBufferLength, resultBufferLength, - seed, - ValidateYCbCr); + seed); } [Theory] @@ -91,13 +103,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg //JpegColorConverter.FromYCbCrSimdAvx2.LogPlz = s => this.Output.WriteLine(s); - ValidateConversion( + ValidateRgbToYCbCrConversion( new JpegColorConverter.FromYCbCrSimdAvx2(), 3, inputBufferLength, resultBufferLength, - seed, - ValidateYCbCr); + seed); } @@ -105,10 +116,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [MemberData(nameof(CommonConversionData))] public void ConvertFromYCbCr_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) { - ValidateConversion(JpegColorSpace.YCbCr, 3, inputBufferLength, resultBufferLength, seed, ValidateYCbCr); + ValidateConversion( + JpegColorSpace.YCbCr, + 3, + inputBufferLength, + resultBufferLength, + seed); } - // Becnhmark, for local execution only + // Benchmark, for local execution only //[Theory] //[InlineData(false)] //[InlineData(true)] @@ -120,11 +136,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg JpegColorConverter.ComponentValues values = CreateRandomValues(3, count, 1); Vector4[] result = new Vector4[count]; - JpegColorConverter converter = simd ? (JpegColorConverter)new JpegColorConverter.FromYCbCrSimd() : new JpegColorConverter.FromYCbCrBasic(); - + JpegColorConverter converter = simd ? (JpegColorConverter)new JpegColorConverter.FromYCbCrSimd() : new JpegColorConverter.FromYCbCrBasic(); + // Warm up: converter.ConvertToRGBA(values, result); - + using (new MeasureGuard(this.Output, $"{converter.GetType().Name} x {times}")) { for (int i = 0; i < times; i++) @@ -141,79 +157,79 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var v = new Vector4(0, 0, 0, 1F); var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); - ValidateConversion( - JpegColorSpace.Cmyk, - 4, - inputBufferLength, - resultBufferLength, - seed, - (values, result, i) => - { - float c = values.Component0[i]; - float m = values.Component1[i]; - float y = values.Component2[i]; - float k = values.Component3[i] / 255F; - - v.X = c * k; - v.Y = m * k; - v.Z = y * k; - v.W = 1F; - - v *= scale; - - Vector4 rgba = result[i]; - 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(1, rgba.W); - }); + var converter = JpegColorConverter.GetConverter(JpegColorSpace.Cmyk); + JpegColorConverter.ComponentValues values = CreateRandomValues(4, inputBufferLength, seed); + Vector4[] result = new Vector4[resultBufferLength]; + + converter.ConvertToRGBA(values, result); + + for (int i = 0; i < resultBufferLength; i++) + { + float c = values.Component0[i]; + float m = values.Component1[i]; + float y = values.Component2[i]; + float k = values.Component3[i] / 255F; + + v.X = c * k; + v.Y = m * k; + v.Z = y * k; + v.W = 1F; + + v *= scale; + + Vector4 rgba = result[i]; + 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(1, rgba.W); + } } [Theory] [MemberData(nameof(CommonConversionData))] public void ConvertFromGrayScale(int inputBufferLength, int resultBufferLength, int seed) { - ValidateConversion( - JpegColorSpace.GrayScale, - 1, - inputBufferLength, - resultBufferLength, - seed, - (values, result, i) => - { - float y = values.Component0[i]; - Vector4 rgba = result[i]; - 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(1, rgba.W); - }); + var converter = JpegColorConverter.GetConverter(JpegColorSpace.GrayScale); + JpegColorConverter.ComponentValues values = CreateRandomValues(1, inputBufferLength, seed); + Vector4[] result = new Vector4[resultBufferLength]; + + converter.ConvertToRGBA(values, result); + + for (int i = 0; i < resultBufferLength; i++) + { + float y = values.Component0[i]; + Vector4 rgba = result[i]; + 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(1, rgba.W); + } } [Theory] [MemberData(nameof(CommonConversionData))] public void ConvertFromRgb(int inputBufferLength, int resultBufferLength, int seed) { - ValidateConversion( - JpegColorSpace.RGB, - 3, - inputBufferLength, - resultBufferLength, - seed, - (values, result, i) => - { - float r = values.Component0[i]; - float g = values.Component1[i]; - float b = values.Component2[i]; - Vector4 rgba = result[i]; - 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(1, rgba.W); - }); + var converter = JpegColorConverter.GetConverter(JpegColorSpace.RGB); + JpegColorConverter.ComponentValues values = CreateRandomValues(3, inputBufferLength, seed); + Vector4[] result = new Vector4[resultBufferLength]; + + converter.ConvertToRGBA(values, result); + + for (int i = 0; i < resultBufferLength; i++) + { + float r = values.Component0[i]; + float g = values.Component1[i]; + float b = values.Component2[i]; + Vector4 rgba = result[i]; + 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(1, rgba.W); + } } [Theory] @@ -223,35 +239,35 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var v = new Vector4(0, 0, 0, 1F); var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); - ValidateConversion( - JpegColorSpace.Ycck, - 4, - inputBufferLength, - resultBufferLength, - seed, - (values, result, i) => - { - float y = values.Component0[i]; - float cb = values.Component1[i] - 128F; - float cr = values.Component2[i] - 128F; - float k = values.Component3[i] / 255F; - - v.X = (255F - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; - v.Y = (255F - (float)Math.Round( - y - (0.344136F * cb) - (0.714136F * cr), - MidpointRounding.AwayFromZero)) * k; - v.Z = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; - v.W = 1F; - - v *= scale; - - Vector4 rgba = result[i]; - 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(1, rgba.W); - }); + var converter = JpegColorConverter.GetConverter(JpegColorSpace.Ycck); + JpegColorConverter.ComponentValues values = CreateRandomValues(4, inputBufferLength, seed); + Vector4[] result = new Vector4[resultBufferLength]; + + converter.ConvertToRGBA(values, result); + + for (int i = 0; i < resultBufferLength; i++) + { + float y = values.Component0[i]; + float cb = values.Component1[i] - 128F; + float cr = values.Component2[i] - 128F; + float k = values.Component3[i] / 255F; + + v.X = (255F - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; + v.Y = (255F - (float)Math.Round( + y - (0.344136F * cb) - (0.714136F * cr), + MidpointRounding.AwayFromZero)) * k; + v.Z = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; + v.W = 1F; + + v *= scale; + + Vector4 rgba = result[i]; + 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(1, rgba.W); + } } private static JpegColorConverter.ComponentValues CreateRandomValues( @@ -269,11 +285,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg for (int j = 0; j < inputBufferLength; j++) { - values[j] = (float)rnd.NextDouble() * (maxVal-minVal)+minVal; + values[j] = (float)rnd.NextDouble() * (maxVal - minVal) + minVal; } // no need to dispose when buffer is not array owner - buffers[i] = new Buffer2D(values, values.Length, 1); + buffers[i] = new Buffer2D(new BasicArrayBuffer(values), values.Length, 1); } return new JpegColorConverter.ComponentValues(buffers, 0); } @@ -283,51 +299,31 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg int componentCount, int inputBufferLength, int resultBufferLength, - int seed, - Action, int> validatePixelValue) + int seed) { - ValidateConversion( + ValidateRgbToYCbCrConversion( JpegColorConverter.GetConverter(colorSpace), componentCount, inputBufferLength, resultBufferLength, - seed, - validatePixelValue); + seed); } - private static void ValidateConversion( + private static void ValidateRgbToYCbCrConversion( JpegColorConverter converter, int componentCount, int inputBufferLength, int resultBufferLength, - int seed, - Action, int> validatePixelValue) - { - ValidateConversion( - converter.ConvertToRGBA, - componentCount, - inputBufferLength, - resultBufferLength, - seed, - validatePixelValue); - } - - private static void ValidateConversion( - Action> doConvert, - int componentCount, - int inputBufferLength, - int resultBufferLength, - int seed, - Action, int> validatePixelValue) + int seed) { JpegColorConverter.ComponentValues values = CreateRandomValues(componentCount, inputBufferLength, seed); Vector4[] result = new Vector4[resultBufferLength]; - doConvert(values, result); + converter.ConvertToRGBA(values, result); for (int i = 0; i < resultBufferLength; i++) { - validatePixelValue(values, result, i); + ValidateYCbCr(values, result, i); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index cb1987aef4..95ee40e807 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -41,8 +41,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Bad.BadEOF, - TestImages.Jpeg.Baseline.Bad.ExifUndefType, TestImages.Jpeg.Issues.MultiHuffmanBaseline394, + TestImages.Jpeg.Baseline.MultiScanBaselineCMYK, + TestImages.Jpeg.Baseline.Bad.BadRST }; public static string[] ProgressiveTestJpegs = @@ -51,16 +52,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Progressive.Festzug, TestImages.Jpeg.Progressive.Bad.BadEOF, TestImages.Jpeg.Issues.BadCoeffsProgressive178, TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159, - TestImages.Jpeg.Issues.BadZigZagProgressive385 + TestImages.Jpeg.Issues.BadZigZagProgressive385, + TestImages.Jpeg.Progressive.Bad.ExifUndefType }; private static readonly Dictionary CustomToleranceValues = new Dictionary { // Baseline: [TestImages.Jpeg.Baseline.Calliphora] = 0.00002f / 100, - [TestImages.Jpeg.Baseline.Bad.ExifUndefType] = 0.011f / 100, [TestImages.Jpeg.Baseline.Bad.BadEOF] = 0.38f / 100, [TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100, + [TestImages.Jpeg.Baseline.Bad.BadRST] = 0.0589f / 100, // Progressive: [TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159] = 0.34f / 100, @@ -70,6 +72,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [TestImages.Jpeg.Progressive.Fb] = 0.16f / 100, [TestImages.Jpeg.Progressive.Progress] = 0.31f / 100, [TestImages.Jpeg.Issues.BadZigZagProgressive385] = 0.23f / 100, + [TestImages.Jpeg.Progressive.Bad.ExifUndefType] = 0.011f / 100, }; public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector; @@ -119,7 +122,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public const string DecodeBaselineJpegOutputName = "DecodeBaselineJpeg"; - [Theory] [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, false)] [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, true)] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index c8d416beaf..cc030bbf7c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -10,117 +10,134 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using System.Collections.Generic; using System.IO; + using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; - using SixLabors.ImageSharp.Processing; + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using SixLabors.Primitives; using Xunit; using Xunit.Abstractions; - public class JpegEncoderTests : MeasureFixture + public class JpegEncoderTests { - public static IEnumerable AllBmpFiles => TestImages.Bmp.All; + public static readonly TheoryData BitsPerPixel_Quality = + new TheoryData + { + { JpegSubsample.Ratio420, 40 }, + { JpegSubsample.Ratio420, 60 }, + { JpegSubsample.Ratio420, 100 }, + + { JpegSubsample.Ratio444, 40 }, + { JpegSubsample.Ratio444, 60 }, + { JpegSubsample.Ratio444, 100 }, + }; - public JpegEncoderTests(ITestOutputHelper output) - : base(output) + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 73, 71, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 24, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 46, 8, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)] + [WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)] + public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegSubsample subsample, int quality) + where TPixel : struct, IPixel { + TestJpegEncoderCore(provider, subsample, quality); } [Theory] - [WithFile(TestImages.Jpeg.Baseline.Snake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio420)] - [WithFile(TestImages.Jpeg.Baseline.Lake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio420)] - [WithFile(TestImages.Jpeg.Baseline.Snake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio444)] - [WithFile(TestImages.Jpeg.Baseline.Lake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio444)] - public void LoadResizeSave(TestImageProvider provider, int quality, JpegSubsample subsample) + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)] + public void EncodeBaseline_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegSubsample subsample, int quality) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(x => x.Resize(new ResizeOptions { Size = new Size(150, 100), Mode = ResizeMode.Max }))) - { - - image.MetaData.ExifProfile = null; // Reduce the size of the file - JpegEncoder options = new JpegEncoder { Subsample = subsample, Quality = quality }; - - provider.Utility.TestName += $"{subsample}_Q{quality}"; - provider.Utility.SaveTestOutputFile(image, "png"); - provider.Utility.SaveTestOutputFile(image, "jpg", options); - } + TestJpegEncoderCore(provider, subsample, quality); } - [Theory] - [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32 | PixelTypes.Rgba32 | PixelTypes.Argb32, JpegSubsample.Ratio420, 75)] - [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32 | PixelTypes.Rgba32 | PixelTypes.Argb32, JpegSubsample.Ratio444, 75)] - public void OpenBmp_SaveJpeg(TestImageProvider provider, JpegSubsample subSample, int quality) - where TPixel : struct, IPixel + /// + /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation + /// + private static ImageComparer GetComparer(int quality, JpegSubsample subsample) { - using (Image image = provider.GetImage()) - { - ImagingTestCaseUtility utility = provider.Utility; - utility.TestName += "_" + subSample + "_Q" + quality; + float tolerance = 0.015f; // ~1.5% - using (FileStream outputStream = File.OpenWrite(utility.GetTestOutputFileName("jpg"))) + if (quality < 50) + { + tolerance *= 10f; + } + else if (quality < 75 || subsample == JpegSubsample.Ratio420) + { + tolerance *= 5f; + if (subsample == JpegSubsample.Ratio420) { - image.Save(outputStream, new JpegEncoder() - { - Subsample = subSample, - Quality = quality - }); + tolerance *= 2f; } } + + return ImageComparer.Tolerant(tolerance); } - [Fact] - public void Encode_IgnoreMetadataIsFalse_ExifProfileIsWritten() + private static void TestJpegEncoderCore( + TestImageProvider provider, + JpegSubsample subsample, + int quality = 100) + where TPixel : struct, IPixel { - JpegEncoder options = new JpegEncoder() - { - IgnoreMetadata = false - }; - - TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); - - using (Image input = testFile.CreateImage()) + using (Image image = provider.GetImage()) { - using (MemoryStream memStream = new MemoryStream()) - { - input.Save(memStream, options); - - memStream.Position = 0; - using (Image output = Image.Load(memStream)) - { - Assert.NotNull(output.MetaData.ExifProfile); - } - } + // There is no alpha in Jpeg! + image.Mutate(c => c.MakeOpaque()); + + var encoder = new JpegEncoder() + { + Subsample = subsample, + Quality = quality + }; + string info = $"{subsample}-Q{quality}"; + ImageComparer comparer = GetComparer(quality, subsample); + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png"); } } + - [Fact] - public void Encode_IgnoreMetadataIsTrue_ExifProfileIgnored() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void IgnoreMetadata_ControlsIfExifProfileIsWritten(bool ignoreMetaData) { - JpegEncoder options = new JpegEncoder() + var encoder = new JpegEncoder() { - IgnoreMetadata = true + IgnoreMetadata = ignoreMetaData }; - - TestFile testFile = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan); - - using (Image input = testFile.CreateImage()) + + using (Image input = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage()) { - using (MemoryStream memStream = new MemoryStream()) + using (var memStream = new MemoryStream()) { - input.SaveAsJpeg(memStream, options); + input.Save(memStream, encoder); memStream.Position = 0; - using (Image output = Image.Load(memStream)) + using (var output = Image.Load(memStream)) { - Assert.Null(output.MetaData.ExifProfile); + if (ignoreMetaData) + { + Assert.Null(output.MetaData.ExifProfile); + } + else + { + Assert.NotNull(output.MetaData.ExifProfile); + } } } } } - + [Fact] - public void Encode_Quality_0_And_1_Are_Identical() + public void Quality_0_And_1_Are_Identical() { var options = new JpegEncoder { @@ -143,7 +160,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [Fact] - public void Encode_Quality_0_And_100_Are_Not_Identical() + public void Quality_0_And_100_Are_Not_Identical() { var options = new JpegEncoder { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs index df6d1ef1bb..ec4a42104a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs @@ -21,13 +21,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Bad.BadEOF, - TestImages.Jpeg.Baseline.Bad.ExifUndefType, }; public static string[] ProgressiveTestJpegs = { TestImages.Jpeg.Progressive.Fb, TestImages.Jpeg.Progressive.Progress, - TestImages.Jpeg.Progressive.Festzug, TestImages.Jpeg.Progressive.Bad.BadEOF + TestImages.Jpeg.Progressive.Festzug, TestImages.Jpeg.Progressive.Bad.BadEOF, + TestImages.Jpeg.Progressive.Bad.ExifUndefType, }; public JpegImagePostProcessorTests(ITestOutputHelper output) @@ -55,8 +55,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { string imageFile = provider.SourceFileOrDescription; using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile)) - using (var pp = new JpegImagePostProcessor(decoder)) - using (var imageFrame = new ImageFrame(decoder.ImageWidth, decoder.ImageHeight)) + using (var pp = new JpegImagePostProcessor(Configuration.Default.MemoryManager, decoder)) + using (var imageFrame = new ImageFrame(Configuration.Default.MemoryManager, decoder.ImageWidth, decoder.ImageHeight)) { pp.DoPostProcessorStep(imageFrame); @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { string imageFile = provider.SourceFileOrDescription; using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile)) - using (var pp = new JpegImagePostProcessor(decoder)) + using (var pp = new JpegImagePostProcessor(Configuration.Default.MemoryManager, decoder)) using (var image = new Image(decoder.ImageWidth, decoder.ImageHeight)) { pp.PostProcess(image.Frames.RootFrame); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs index 26ec454f91..0276e17085 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; - using Xunit.Abstractions; // ReSharper disable InconsistentNaming diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 6e68c43f21..6816b84656 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -28,13 +28,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Jpeg400, TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Testorig420, TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Baseline.Bad.BadEOF, - TestImages.Jpeg.Baseline.Bad.ExifUndefType, + TestImages.Jpeg.Baseline.MultiScanBaselineCMYK }; public static readonly string[] ProgressiveTestJpegs = { TestImages.Jpeg.Progressive.Fb, TestImages.Jpeg.Progressive.Progress, - TestImages.Jpeg.Progressive.Festzug, TestImages.Jpeg.Progressive.Bad.BadEOF + TestImages.Jpeg.Progressive.Festzug, TestImages.Jpeg.Progressive.Bad.BadEOF, + TestImages.Jpeg.Progressive.Bad.ExifUndefType, }; public static readonly string[] AllTestJpegs = BaselineTestJpegs.Concat(ProgressiveTestJpegs).ToArray(); @@ -106,7 +107,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.Output.WriteLine($"Component{i}: {diff}"); averageDifference += diff.average; totalDifference += diff.total; - tolerance += libJpegComponent.SpectralBlocks.Length; + tolerance += libJpegComponent.SpectralBlocks.Buffer.Span.Length; } averageDifference /= componentCount; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs index 40b41b9cba..9e287eb2d7 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils this.HeightInBlocks = heightInBlocks; this.WidthInBlocks = widthInBlocks; this.Index = index; - this.SpectralBlocks = new Buffer2D(this.WidthInBlocks, this.HeightInBlocks); + this.SpectralBlocks = Configuration.Default.MemoryManager.Allocate2D(this.WidthInBlocks, this.HeightInBlocks); } public Size Size => new Size(this.WidthInBlocks, this.HeightInBlocks); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs index ae7a9c046f..5a4db87b9b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils this.ComponentCount = components.Length; this.Components = components; } - + public static SpectralData LoadFromImageSharpDecoder(PdfJsJpegDecoderCore decoder) { PdfJsFrameComponent[] srcComponents = decoder.Frame.Components; @@ -137,12 +137,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public static bool operator ==(SpectralData left, SpectralData right) { - return Object.Equals(left, right); + if (ReferenceEquals(left, right)) + { + return true; + } + + return left.Equals(right); } public static bool operator !=(SpectralData left, SpectralData right) { - return !Object.Equals(left, right); + return !(left == right); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs index ef9a73d12d..e18323f848 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs @@ -6,7 +6,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Jpeg.Common; - using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Utils; using Xunit.Abstractions; @@ -132,7 +131,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// /// Original: https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L239 - /// Applyies IDCT transformation on "s" copying transformed values to "d", using temporal block "temp" + /// Applyies IDCT transformation on "s" copying transformed values to "d", using temporary block "temp" /// /// /// diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 1566ddf442..0010bb41d3 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -10,36 +10,137 @@ using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.PixelFormats; using Xunit; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { - public class PngEncoderTests : FileTestBase + using SixLabors.ImageSharp.Quantizers; + using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + + public class PngEncoderTests { - private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; + private const float ToleranceThresholdForPaletteEncoder = 0.01f / 100; + + /// + /// All types except Palette + /// + public static readonly TheoryData PngColorTypes = new TheoryData() + { + PngColorType.RgbWithAlpha, + PngColorType.Rgb, + PngColorType.Grayscale, + PngColorType.GrayscaleWithAlpha, + }; + + /// + /// All types except Palette + /// + public static readonly TheoryData CompressionLevels = new TheoryData() + { + 1, 2, 3, 4, 5, 6, 7, 8, 9 + }; + + public static readonly TheoryData PaletteSizes = new TheoryData() + { + 30, 55, 100, 201, 255 + }; + + public static readonly TheoryData PaletteLargeOnly = new TheoryData() + { + 80, 100, 120, 230 + }; [Theory] - [WithTestPatternImages(100, 100, PixelTypes, PngColorType.RgbWithAlpha)] - [WithTestPatternImages(100, 100, PixelTypes, PngColorType.Rgb)] - [WithTestPatternImages(100, 100, PixelTypes, PngColorType.Palette)] - [WithTestPatternImages(100, 100, PixelTypes, PngColorType.Grayscale)] - [WithTestPatternImages(100, 100, PixelTypes, PngColorType.GrayscaleWithAlpha)] - public void EncodeGeneratedPatterns(TestImageProvider provider, PngColorType pngColorType) + [WithFile(TestImages.Png.Palette8Bpp, nameof(PngColorTypes), PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(PngColorTypes), 48, 24, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(PngColorTypes), 47, 8, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(PngColorTypes), 49, 7, PixelTypes.Rgba32)] + [WithSolidFilledImages(nameof(PngColorTypes), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(PngColorTypes), 7, 5, PixelTypes.Rgba32)] + public void WorksWithDifferentSizes(TestImageProvider provider, PngColorType pngColorType) + where TPixel : struct, IPixel + { + TestPngEncoderCore(provider, pngColorType, appendPngColorType: true); + } + + [Theory] + [WithTestPatternImages(nameof(PngColorTypes), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] + public void IsNotBoundToSinglePixelType(TestImageProvider provider, PngColorType pngColorType) + where TPixel : struct, IPixel + { + TestPngEncoderCore(provider, pngColorType, appendPixelType: true, appendPngColorType: true); + } + + [Theory] + [WithTestPatternImages(nameof(CompressionLevels), 24, 24, PixelTypes.Rgba32)] + public void WorksWithAllCompressionLevels(TestImageProvider provider, int compressionLevel) + where TPixel : struct, IPixel + { + TestPngEncoderCore(provider, PngColorType.RgbWithAlpha, compressionLevel, appendCompressionLevel: true); + } + + [Theory] + [WithFile(TestImages.Png.Palette8Bpp, nameof(PaletteLargeOnly), PixelTypes.Rgba32)] + public void PaletteColorType_WuQuantizer(TestImageProvider provider, int paletteSize) + where TPixel : struct, IPixel + { + TestPngEncoderCore(provider, PngColorType.Palette, paletteSize: paletteSize, appendPaletteSize: true); + } + + private static bool HasAlpha(PngColorType pngColorType) => + pngColorType == PngColorType.GrayscaleWithAlpha || pngColorType == PngColorType.RgbWithAlpha; + + private static void TestPngEncoderCore( + TestImageProvider provider, + PngColorType pngColorType, + int compressionLevel = 6, + int paletteSize = 0, + bool appendPngColorType = false, + bool appendPixelType = false, + bool appendCompressionLevel = false, + bool appendPaletteSize = false) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) { - var options = new PngEncoder() + if (!HasAlpha(pngColorType)) { - PngColorType = pngColorType - }; - provider.Utility.TestName += "_" + pngColorType; + image.Mutate(c => c.MakeOpaque()); + } + + var encoder = new PngEncoder + { + PngColorType = pngColorType, + CompressionLevel = compressionLevel, + PaletteSize = paletteSize + }; + + string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : ""; + string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : ""; + string paletteSizeInfo = appendPaletteSize ? $"_PaletteSize-{paletteSize}" : ""; + string debugInfo = $"{pngColorTypeInfo}{compressionLevelInfo}{paletteSizeInfo}"; + //string referenceInfo = $"{pngColorTypeInfo}"; - provider.Utility.SaveTestOutputFile(image, "png", options); + // Does DebugSave & load reference CompareToReferenceInput(): + string actualOutputFile = ((ITestImageProvider)provider).Utility.SaveTestOutputFile(image, "png", encoder, debugInfo, appendPixelType); + + IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); + string referenceOutputFile = ((ITestImageProvider)provider).Utility.GetReferenceOutputFileName("png", debugInfo, appendPixelType); + + using (var actualImage = Image.Load(actualOutputFile, referenceDecoder)) + using (var referenceImage = Image.Load(referenceOutputFile, referenceDecoder)) + { + ImageComparer comparer = pngColorType == PngColorType.Palette + ? ImageComparer.Tolerant(ToleranceThresholdForPaletteEncoder) + : ImageComparer.Exact; + + comparer.VerifySimilarity(referenceImage, actualImage); + } } } - + [Theory] - [WithBlankImages(1, 1, PixelTypes.All)] + [WithBlankImages(1, 1, PixelTypes.Rgba32)] public void WritesFileMarker(TestImageProvider provider) where TPixel : struct, IPixel { diff --git a/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs b/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs index aefa32f469..1a2275062e 100644 --- a/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs +++ b/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs @@ -5,7 +5,6 @@ using System; using System.IO; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.IO; -using SixLabors.ImageSharp.PixelFormats; using Moq; using Xunit; @@ -18,10 +17,10 @@ namespace SixLabors.ImageSharp.Tests { private readonly Mock fileSystem; private readonly string FilePath; - private readonly Mock localMimeTypeDetector; + private readonly IImageFormatDetector localMimeTypeDetector; private readonly Mock localImageFormatMock; - public IImageFormat localImageFormat => localImageFormatMock.Object; + public IImageFormat localImageFormat => this.localImageFormatMock.Object; public Configuration LocalConfiguration { get; private set; } public byte[] Marker { get; private set; } public MemoryStream DataStream { get; private set; } @@ -32,9 +31,7 @@ namespace SixLabors.ImageSharp.Tests { this.localImageFormatMock = new Mock(); - this.localMimeTypeDetector = new Mock(); - this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1); - this.localMimeTypeDetector.Setup(x => x.DetectFormat(It.IsAny>())).Returns(localImageFormatMock.Object); + this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object); this.fileSystem = new Mock(); @@ -42,7 +39,8 @@ namespace SixLabors.ImageSharp.Tests { FileSystem = this.fileSystem.Object }; - this.LocalConfiguration.AddImageFormatDetector(this.localMimeTypeDetector.Object); + + this.LocalConfiguration.ImageFormatsManager.AddImageFormatDetector(this.localMimeTypeDetector); TestFormat.RegisterGlobalTestFormat(); this.Marker = Guid.NewGuid().ToByteArray(); @@ -58,49 +56,49 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void DiscoverImageFormatByteArray() { - var type = Image.DetectFormat(DataStream.ToArray()); + IImageFormat type = Image.DetectFormat(this.DataStream.ToArray()); Assert.Equal(TestFormat.GlobalTestFormat, type); } [Fact] public void DiscoverImageFormatByteArray_WithConfig() { - var type = Image.DetectFormat(this.LocalConfiguration, DataStream.ToArray()); - Assert.Equal(localImageFormat, type); + IImageFormat type = Image.DetectFormat(this.LocalConfiguration, this.DataStream.ToArray()); + Assert.Equal(this.localImageFormat, type); } [Fact] public void DiscoverImageFormatFile() { - var type = Image.DetectFormat(this.FilePath); + IImageFormat type = Image.DetectFormat(this.FilePath); Assert.Equal(TestFormat.GlobalTestFormat, type); } [Fact] public void DiscoverImageFormatFilePath_WithConfig() { - var type = Image.DetectFormat(this.LocalConfiguration, FilePath); - Assert.Equal(localImageFormat, type); + IImageFormat type = Image.DetectFormat(this.LocalConfiguration, this.FilePath); + Assert.Equal(this.localImageFormat, type); } [Fact] public void DiscoverImageFormatStream() { - var type = Image.DetectFormat(this.DataStream); + IImageFormat type = Image.DetectFormat(this.DataStream); Assert.Equal(TestFormat.GlobalTestFormat, type); } [Fact] public void DiscoverImageFormatFileStream_WithConfig() { - var type = Image.DetectFormat(this.LocalConfiguration, DataStream); - Assert.Equal(localImageFormat, type); + IImageFormat type = Image.DetectFormat(this.LocalConfiguration, this.DataStream); + Assert.Equal(this.localImageFormat, type); } [Fact] public void DiscoverImageFormatNoDetectorsRegisterdShouldReturnNull() { - var type = Image.DetectFormat(new Configuration(), DataStream); + IImageFormat type = Image.DetectFormat(new Configuration(), this.DataStream); Assert.Null(type); } } diff --git a/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs b/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs index afae9cae8c..1f2137070f 100644 --- a/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Tests { ArgumentException ex = Assert.Throws(() => { - this.collection.AddFrame(new ImageFrame(1, 1)); + this.collection.AddFrame(new ImageFrame(Configuration.Default.MemoryManager, 1, 1)); }); Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests ArgumentException ex = Assert.Throws(() => { - this.collection.InsertFrame(1, new ImageFrame(1, 1)); + this.collection.InsertFrame(1, new ImageFrame(Configuration.Default.MemoryManager, 1, 1)); }); Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); @@ -103,8 +103,8 @@ namespace SixLabors.ImageSharp.Tests ArgumentException ex = Assert.Throws(() => { var collection = new ImageFrameCollection(this.image, new[] { - new ImageFrame(10,10), - new ImageFrame(1,1), + new ImageFrame(Configuration.Default.MemoryManager,10,10), + new ImageFrame(Configuration.Default.MemoryManager,1,1), }); }); @@ -115,7 +115,7 @@ namespace SixLabors.ImageSharp.Tests public void RemoveAtFrame_ThrowIfRemovingLastFrame() { var collection = new ImageFrameCollection(this.image, new[] { - new ImageFrame(10,10) + new ImageFrame(Configuration.Default.MemoryManager,10,10) }); InvalidOperationException ex = Assert.Throws(() => @@ -130,8 +130,8 @@ namespace SixLabors.ImageSharp.Tests { var collection = new ImageFrameCollection(this.image, new[] { - new ImageFrame(10,10), - new ImageFrame(10,10), + new ImageFrame(Configuration.Default.MemoryManager,10,10), + new ImageFrame(Configuration.Default.MemoryManager,10,10), }); collection.RemoveFrame(0); @@ -142,8 +142,8 @@ namespace SixLabors.ImageSharp.Tests public void RootFrameIsFrameAtIndexZero() { var collection = new ImageFrameCollection(this.image, new[] { - new ImageFrame(10,10), - new ImageFrame(10,10), + new ImageFrame(Configuration.Default.MemoryManager,10,10), + new ImageFrame(Configuration.Default.MemoryManager,10,10), }); Assert.Equal(collection.RootFrame, collection[0]); @@ -153,8 +153,8 @@ namespace SixLabors.ImageSharp.Tests public void ConstructorPopulatesFrames() { var collection = new ImageFrameCollection(this.image, new[] { - new ImageFrame(10,10), - new ImageFrame(10,10), + new ImageFrame(Configuration.Default.MemoryManager,10,10), + new ImageFrame(Configuration.Default.MemoryManager,10,10), }); Assert.Equal(2, collection.Count); @@ -164,8 +164,8 @@ namespace SixLabors.ImageSharp.Tests public void DisposeClearsCollection() { var collection = new ImageFrameCollection(this.image, new[] { - new ImageFrame(10,10), - new ImageFrame(10,10), + new ImageFrame(Configuration.Default.MemoryManager,10,10), + new ImageFrame(Configuration.Default.MemoryManager,10,10), }); collection.Dispose(); @@ -177,8 +177,8 @@ namespace SixLabors.ImageSharp.Tests public void Dispose_DisposesAllInnerFrames() { var collection = new ImageFrameCollection(this.image, new[] { - new ImageFrame(10,10), - new ImageFrame(10,10), + new ImageFrame(Configuration.Default.MemoryManager,10,10), + new ImageFrame(Configuration.Default.MemoryManager,10,10), }); IPixelSource[] framesSnapShot = collection.OfType>().ToArray(); @@ -198,7 +198,7 @@ namespace SixLabors.ImageSharp.Tests { using (Image img = provider.GetImage()) { - img.Frames.AddFrame(new ImageFrame(10, 10));// add a frame anyway + img.Frames.AddFrame(new ImageFrame(Configuration.Default.MemoryManager,10, 10));// add a frame anyway using (Image cloned = img.Frames.CloneFrame(0)) { Assert.Equal(2, img.Frames.Count); @@ -216,7 +216,7 @@ namespace SixLabors.ImageSharp.Tests { var sourcePixelData = img.GetPixelSpan().ToArray(); - img.Frames.AddFrame(new ImageFrame(10, 10)); + img.Frames.AddFrame(new ImageFrame(Configuration.Default.MemoryManager,10, 10)); using (Image cloned = img.Frames.ExportFrame(0)) { Assert.Equal(1, img.Frames.Count); @@ -244,7 +244,7 @@ namespace SixLabors.ImageSharp.Tests public void AddFrame_clones_sourceFrame() { var pixelData = this.image.Frames.RootFrame.GetPixelSpan().ToArray(); - var otherFRame = new ImageFrame(10, 10); + var otherFRame = new ImageFrame(Configuration.Default.MemoryManager,10, 10); var addedFrame = this.image.Frames.AddFrame(otherFRame); addedFrame.ComparePixelBufferTo(otherFRame.GetPixelSpan()); Assert.NotEqual(otherFRame, addedFrame); @@ -254,7 +254,7 @@ namespace SixLabors.ImageSharp.Tests public void InsertFrame_clones_sourceFrame() { var pixelData = this.image.Frames.RootFrame.GetPixelSpan().ToArray(); - var otherFRame = new ImageFrame(10, 10); + var otherFRame = new ImageFrame(Configuration.Default.MemoryManager,10, 10); var addedFrame = this.image.Frames.InsertFrame(0, otherFRame); addedFrame.ComparePixelBufferTo(otherFRame.GetPixelSpan()); Assert.NotEqual(otherFRame, addedFrame); @@ -308,7 +308,7 @@ namespace SixLabors.ImageSharp.Tests this.image.Frames.CreateFrame(); } - var frame = new ImageFrame(10, 10); + var frame = new ImageFrame(Configuration.Default.MemoryManager,10, 10); Assert.False(this.image.Frames.Contains(frame)); } diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs index 2c0a30b154..97274e98b3 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -14,13 +14,13 @@ namespace SixLabors.ImageSharp.Tests /// /// Tests the class. /// - public class ImageLoadTests : IDisposable + public partial class ImageLoadTests : IDisposable { private readonly Mock fileSystem; private Image returnImage; private Mock localDecoder; private readonly string FilePath; - private readonly Mock localMimeTypeDetector; + private readonly IImageFormatDetector localMimeTypeDetector; private readonly Mock localImageFormatMock; public Configuration LocalConfiguration { get; private set; } @@ -35,10 +35,7 @@ namespace SixLabors.ImageSharp.Tests this.localImageFormatMock = new Mock(); this.localDecoder = new Mock(); - this.localMimeTypeDetector = new Mock(); - this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1); - this.localMimeTypeDetector.Setup(x => x.DetectFormat(It.IsAny>())).Returns(localImageFormatMock.Object); - + this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object); this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) .Callback((c, s) => @@ -57,8 +54,8 @@ namespace SixLabors.ImageSharp.Tests { FileSystem = this.fileSystem.Object }; - this.LocalConfiguration.AddImageFormatDetector(this.localMimeTypeDetector.Object); - this.LocalConfiguration.SetDecoder(localImageFormatMock.Object, this.localDecoder.Object); + this.LocalConfiguration.ImageFormatsManager.AddImageFormatDetector(this.localMimeTypeDetector); + this.LocalConfiguration.ImageFormatsManager.SetDecoder(this.localImageFormatMock.Object, this.localDecoder.Object); TestFormat.RegisterGlobalTestFormat(); this.Marker = Guid.NewGuid().ToByteArray(); diff --git a/tests/ImageSharp.Tests/Image/ImageProcessingContextTests.cs b/tests/ImageSharp.Tests/Image/ImageProcessingContextTests.cs new file mode 100644 index 0000000000..f8f7b6758a --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageProcessingContextTests.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.Primitives; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class ImageProcessingContextTests + { + [Fact] + public void MutatedSizeIsAccuratePerOperation() + { + var x500 = new Size(500, 500); + var x400 = new Size(400, 400); + var x300 = new Size(300, 300); + var x200 = new Size(200, 200); + var x100 = new Size(100, 100); + using (var image = new Image(500, 500)) + { + image.Mutate(x => + x.AssertSize(x500) + .Resize(x400).AssertSize(x400) + .Resize(x300).AssertSize(x300) + .Resize(x200).AssertSize(x200) + .Resize(x100).AssertSize(x100)); + } + } + + [Fact] + public void ClonedSizeIsAccuratePerOperation() + { + var x500 = new Size(500, 500); + var x400 = new Size(400, 400); + var x300 = new Size(300, 300); + var x200 = new Size(200, 200); + var x100 = new Size(100, 100); + using (var image = new Image(500, 500)) + { + image.Clone(x => + x.AssertSize(x500) + .Resize(x400).AssertSize(x400) + .Resize(x300).AssertSize(x300) + .Resize(x200).AssertSize(x200) + .Resize(x100).AssertSize(x100)); + } + } + } + + public static class SizeAssertationExtensions + { + public static IImageProcessingContext AssertSize(this IImageProcessingContext context, Size size) + { + Assert.Equal(size, context.GetCurrentSize()); + return context; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs index 5b672059c2..028313e631 100644 --- a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -24,17 +24,14 @@ namespace SixLabors.ImageSharp.Tests private readonly Mock fileSystem; private readonly Mock encoder; private readonly Mock encoderNotInFormat; - private Mock localMimeTypeDetector; + private IImageFormatDetector localMimeTypeDetector; private Mock localImageFormat; public ImageSaveTests() { this.localImageFormat = new Mock(); this.localImageFormat.Setup(x => x.FileExtensions).Returns(new[] { "png" }); - - this.localMimeTypeDetector = new Mock(); - this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1); - this.localMimeTypeDetector.Setup(x => x.DetectFormat(It.IsAny>())).Returns(localImageFormat.Object); + this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormat.Object); this.encoder = new Mock(); @@ -45,8 +42,8 @@ namespace SixLabors.ImageSharp.Tests { FileSystem = this.fileSystem.Object }; - config.AddImageFormatDetector(this.localMimeTypeDetector.Object); - config.SetEncoder(localImageFormat.Object, this.encoder.Object); + config.ImageFormatsManager.AddImageFormatDetector(this.localMimeTypeDetector); + config.ImageFormatsManager.SetEncoder(this.localImageFormat.Object, this.encoder.Object); this.Image = new Image(config, 1, 1); } @@ -57,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests { using (Image image = provider.GetImage()) { - TPixel[] buffer = new TPixel[image.Width*image.Height]; + TPixel[] buffer = new TPixel[image.Width * image.Height]; image.SavePixelData(buffer); image.ComparePixelBufferTo(buffer); @@ -73,14 +70,14 @@ namespace SixLabors.ImageSharp.Tests { using (Image image = provider.GetImage()) { - byte[] buffer = new byte[image.Width*image.Height*Unsafe.SizeOf()]; + byte[] buffer = new byte[image.Width * image.Height * Unsafe.SizeOf()]; image.SavePixelData(buffer); image.ComparePixelBufferTo(buffer.AsSpan().NonPortableCast()); } } - + [Fact] public void SavePixelData_Rgba32_WhenBufferIsTooSmall_Throws() { @@ -91,7 +88,7 @@ namespace SixLabors.ImageSharp.Tests img[0, 1] = Rgba32.Red; img[1, 1] = Rgba32.Blue; - var buffer = new byte[2 * 2]; // width * height * bytes per pixel + byte[] buffer = new byte[2 * 2]; // width * height * bytes per pixel Assert.Throws(() => { @@ -125,7 +122,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void ToBase64String() { - var str = this.Image.ToBase64String(localImageFormat.Object); + string str = this.Image.ToBase64String(this.localImageFormat.Object); this.encoder.Verify(x => x.Encode(this.Image, It.IsAny())); } @@ -134,7 +131,7 @@ namespace SixLabors.ImageSharp.Tests public void SaveStreamWithMime() { Stream stream = new MemoryStream(); - this.Image.Save(stream, localImageFormat.Object); + this.Image.Save(stream, this.localImageFormat.Object); this.encoder.Verify(x => x.Encode(this.Image, stream)); } diff --git a/tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs b/tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs new file mode 100644 index 0000000000..cb09fa010c --- /dev/null +++ b/tests/ImageSharp.Tests/Image/MockImageFormatDetector.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats; + +namespace SixLabors.ImageSharp.Tests +{ + /// + /// You can't mock the "DetectFormat" method due to the ReadOnlySpan{byte} parameter. + /// + public class MockImageFormatDetector : IImageFormatDetector + { + private IImageFormat localImageFormatMock; + + public MockImageFormatDetector(IImageFormat imageFormat) + { + this.localImageFormatMock = imageFormat; + } + + public int HeaderSize => 1; + + public IImageFormat DetectFormat(ReadOnlySpan header) + { + return this.localImageFormatMock; + } + } +} diff --git a/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs b/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs deleted file mode 100644 index 1ab3f2ce9f..0000000000 --- a/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; -using Xunit; - -namespace SixLabors.ImageSharp.Tests -{ - /// - /// Tests the class. - /// - public class PixelAccessorTests - { - public static Image CreateTestImage() - where TPixel : struct, IPixel - { - var image = new Image(10, 10); - using (PixelAccessor pixels = image.Lock()) - { - for (int i = 0; i < 10; i++) - { - for (int j = 0; j < 10; j++) - { - var v = new Vector4(i, j, 0, 1); - v /= 10; - - var color = default(TPixel); - color.PackFromVector4(v); - - pixels[i, j] = color; - } - } - } - return image; - } - - [Theory] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All, ComponentOrder.Xyz)] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All, ComponentOrder.Zyx)] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All, ComponentOrder.Xyzw)] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All, ComponentOrder.Zyxw)] - internal void CopyTo_Then_CopyFrom_OnFullImageRect(TestImageProvider provider, ComponentOrder order) - where TPixel : struct, IPixel - { - using (Image src = provider.GetImage()) - { - using (Image dest = new Image(src.Width, src.Height)) - { - using (PixelArea area = new PixelArea(src.Width, src.Height, order)) - { - using (PixelAccessor srcPixels = src.Lock()) - { - srcPixels.CopyTo(area, 0, 0); - } - - using (PixelAccessor destPixels = dest.Lock()) - { - destPixels.CopyFrom(area, 0, 0); - } - } - - Assert.True(src.IsEquivalentTo(dest, false)); - } - } - } - - [Theory] - [WithBlankImages(16, 16, PixelTypes.All, ComponentOrder.Xyz)] - [WithBlankImages(16, 16, PixelTypes.All, ComponentOrder.Zyx)] - [WithBlankImages(16, 16, PixelTypes.All, ComponentOrder.Xyzw)] - [WithBlankImages(16, 16, PixelTypes.All, ComponentOrder.Zyxw)] - internal void CopyToThenCopyFromWithOffset(TestImageProvider provider, ComponentOrder order) - where TPixel : struct, IPixel - { - using (Image destImage = new Image(8, 8)) - { - using (Image srcImage = provider.GetImage()) - { - srcImage.Mutate(x => x.Fill(NamedColors.Red, new Rectangle(4, 4, 8, 8))); - using (PixelAccessor srcPixels = srcImage.Lock()) - { - using (PixelArea area = new PixelArea(8, 8, order)) - { - srcPixels.CopyTo(area, 4, 4); - - using (PixelAccessor destPixels = destImage.Lock()) - { - destPixels.CopyFrom(area, 0, 0); - } - } - } - } - - provider.Utility.SourceFileOrDescription = order.ToString(); - provider.Utility.SaveTestOutputFile(destImage, "bmp"); - - using (Image expectedImage = new Image(8, 8)) - { - expectedImage.Mutate(x => x.Fill(NamedColors.Red)); - Assert.True(destImage.IsEquivalentTo(expectedImage)); - } - } - } - - - [Fact] - public void CopyFromZYX() - { - using (Image image = new Image(1, 1)) - { - CopyFromZYXImpl(image); - } - } - - [Fact] - public void CopyFromZYXW() - { - using (Image image = new Image(1, 1)) - { - CopyFromZYXWImpl(image); - } - } - - [Fact] - public void CopyToZYX() - { - using (Image image = new Image(1, 1)) - { - CopyToZYXImpl(image); - } - } - - [Fact] - public void CopyToZYXW() - { - using (Image image = new Image(1, 1)) - { - CopyToZYXWImpl(image); - } - } - - private static void CopyFromZYXImpl(Image image) - where TPixel : struct, IPixel - { - using (PixelAccessor pixels = image.Lock()) - { - byte red = 1; - byte green = 2; - byte blue = 3; - byte alpha = 255; - - using (PixelArea row = new PixelArea(1, ComponentOrder.Zyx)) - { - row.Bytes[0] = blue; - row.Bytes[1] = green; - row.Bytes[2] = red; - - pixels.CopyFrom(row, 0); - - Rgba32 color = (Rgba32)(object)pixels[0, 0]; - Assert.Equal(red, color.R); - Assert.Equal(green, color.G); - Assert.Equal(blue, color.B); - Assert.Equal(alpha, color.A); - } - } - } - - private static void CopyFromZYXWImpl(Image image) - where TPixel : struct, IPixel - { - using (PixelAccessor pixels = image.Lock()) - { - byte red = 1; - byte green = 2; - byte blue = 3; - byte alpha = 4; - - using (PixelArea row = new PixelArea(1, ComponentOrder.Zyxw)) - { - row.Bytes[0] = blue; - row.Bytes[1] = green; - row.Bytes[2] = red; - row.Bytes[3] = alpha; - - pixels.CopyFrom(row, 0); - - Rgba32 color = (Rgba32)(object)pixels[0, 0]; - Assert.Equal(red, color.R); - Assert.Equal(green, color.G); - Assert.Equal(blue, color.B); - Assert.Equal(alpha, color.A); - } - } - } - - private static void CopyToZYXImpl(Image image) - where TPixel : struct, IPixel - { - using (PixelAccessor pixels = image.Lock()) - { - byte red = 1; - byte green = 2; - byte blue = 3; - - using (PixelArea row = new PixelArea(1, ComponentOrder.Zyx)) - { - pixels[0, 0] = (TPixel)(object)new Rgba32(red, green, blue); - - pixels.CopyTo(row, 0); - - Assert.Equal(blue, row.Bytes[0]); - Assert.Equal(green, row.Bytes[1]); - Assert.Equal(red, row.Bytes[2]); - } - } - } - - private static void CopyToZYXWImpl(Image image) - where TPixel : struct, IPixel - { - using (PixelAccessor pixels = image.Lock()) - { - byte red = 1; - byte green = 2; - byte blue = 3; - byte alpha = 4; - - using (PixelArea row = new PixelArea(1, ComponentOrder.Zyxw)) - { - pixels[0, 0] = (TPixel)(object)new Rgba32(red, green, blue, alpha); - - pixels.CopyTo(row, 0); - - Assert.Equal(blue, row.Bytes[0]); - Assert.Equal(green, row.Bytes[1]); - Assert.Equal(red, row.Bytes[2]); - Assert.Equal(alpha, row.Bytes[3]); - } - } - } - } -} diff --git a/tests/ImageSharp.Tests/ImageOperationTests.cs b/tests/ImageSharp.Tests/ImageOperationTests.cs index 59722a84d2..a5d6d2eb96 100644 --- a/tests/ImageSharp.Tests/ImageOperationTests.cs +++ b/tests/ImageSharp.Tests/ImageOperationTests.cs @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Tests { var operations = new FakeImageOperationsProvider.FakeImageOperations(null, false); operations.ApplyProcessors(this.processor); - Assert.Contains(this.processor, operations.applied.Select(x => x.Processor)); + Assert.Contains(this.processor, operations.Applied.Select(x => x.Processor)); } public void Dispose() diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 6dc5d21200..878ce1a0d6 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -1,6 +1,6 @@  - netcoreapp1.1 + netcoreapp2.0 True full portable @@ -20,13 +20,15 @@ + + - - - + + + diff --git a/tests/ImageSharp.Tests/Memory/ArrayPoolMemoryManagerTests.cs b/tests/ImageSharp.Tests/Memory/ArrayPoolMemoryManagerTests.cs new file mode 100644 index 0000000000..c73ce96313 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/ArrayPoolMemoryManagerTests.cs @@ -0,0 +1,198 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Memory +{ + using System; + using System.Linq; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + using SixLabors.ImageSharp.Memory; + + using Xunit; + + public class ArrayPoolMemoryManagerTests + { + private const int MaxPooledBufferSizeInBytes = 2048; + + private const int PoolSelectorThresholdInBytes = MaxPooledBufferSizeInBytes / 2; + + private MemoryManager MemoryManager { get; set; } = new ArrayPoolMemoryManager(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes); + + /// + /// Rent a buffer -> return it -> re-rent -> verify if it's span points to the previous location + /// + private bool CheckIsRentingPooledBuffer(int length) + where T : struct + { + IBuffer buffer = this.MemoryManager.Allocate(length); + ref T ptrToPrevPosition0 = ref buffer.DangerousGetPinnableReference(); + buffer.Dispose(); + + buffer = this.MemoryManager.Allocate(length); + bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref buffer.DangerousGetPinnableReference()); + buffer.Dispose(); + + return sameBuffers; + } + + public class BufferTests : BufferTestSuite + { + public BufferTests() + : base(new ArrayPoolMemoryManager(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes)) + { + } + } + + public class Constructor + { + [Fact] + public void WhenBothParametersPassedByUser() + { + var mgr = new ArrayPoolMemoryManager(1111, 666); + Assert.Equal(1111, mgr.MaxPoolSizeInBytes); + Assert.Equal(666, mgr.PoolSelectorThresholdInBytes); + } + + [Fact] + public void WhenPassedOnly_MaxPooledBufferSizeInBytes_SmallerThresholdValueIsAutoCalculated() + { + var mgr = new ArrayPoolMemoryManager(5000); + Assert.Equal(5000, mgr.MaxPoolSizeInBytes); + Assert.True(mgr.PoolSelectorThresholdInBytes < mgr.MaxPoolSizeInBytes); + } + + [Fact] + public void When_PoolSelectorThresholdInBytes_IsGreaterThan_MaxPooledBufferSizeInBytes_ExceptionIsThrown() + { + Assert.ThrowsAny(() => { new ArrayPoolMemoryManager(100, 200); }); + } + } + + [StructLayout(LayoutKind.Explicit, Size = MaxPooledBufferSizeInBytes / 5)] + struct LargeStruct + { + } + + [Theory] + [InlineData(32)] + [InlineData(512)] + [InlineData(MaxPooledBufferSizeInBytes - 1)] + public void SmallBuffersArePooled_OfByte(int size) + { + Assert.True(this.CheckIsRentingPooledBuffer(size)); + } + + + [Theory] + [InlineData(128 * 1024 * 1024)] + [InlineData(MaxPooledBufferSizeInBytes + 1)] + public void LargeBuffersAreNotPooled_OfByte(int size) + { + Assert.False(this.CheckIsRentingPooledBuffer(size)); + } + + [Fact] + public unsafe void SmallBuffersArePooled_OfBigValueType() + { + int count = MaxPooledBufferSizeInBytes / sizeof(LargeStruct) - 1; + + Assert.True(this.CheckIsRentingPooledBuffer(count)); + } + + [Fact] + public unsafe void LaregeBuffersAreNotPooled_OfBigValueType() + { + int count = MaxPooledBufferSizeInBytes / sizeof(LargeStruct) + 1; + + Assert.False(this.CheckIsRentingPooledBuffer(count)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CleaningRequests_AreControlledByAllocationParameter_Clean(bool clean) + { + using (IBuffer firstAlloc = this.MemoryManager.Allocate(42)) + { + firstAlloc.Span.Fill(666); + } + + using (IBuffer secondAlloc = this.MemoryManager.Allocate(42, clean)) + { + int expected = clean ? 0 : 666; + Assert.Equal(expected, secondAlloc.Span[0]); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ReleaseRetainedResources_ReplacesInnerArrayPool(bool keepBufferAlive) + { + IBuffer buffer = this.MemoryManager.Allocate(32); + ref int ptrToPrev0 = ref MemoryMarshal.GetReference(buffer.Span); + + if (!keepBufferAlive) + { + buffer.Dispose(); + } + + this.MemoryManager.ReleaseRetainedResources(); + + buffer = this.MemoryManager.Allocate(32); + + Assert.False(Unsafe.AreSame(ref ptrToPrev0, ref buffer.DangerousGetPinnableReference())); + } + + [Fact] + public void ReleaseRetainedResources_DisposingPreviouslyAllocatedBuffer_IsAllowed() + { + IBuffer buffer = this.MemoryManager.Allocate(32); + this.MemoryManager.ReleaseRetainedResources(); + buffer.Dispose(); + } + + [Fact] + public void AllocationOverLargeArrayThreshold_UsesDifferentPool() + { + int arrayLengthThreshold = PoolSelectorThresholdInBytes / sizeof(int); + + IBuffer small = this.MemoryManager.Allocate(arrayLengthThreshold - 1); + ref int ptr2Small = ref small.DangerousGetPinnableReference(); + small.Dispose(); + + IBuffer large = this.MemoryManager.Allocate(arrayLengthThreshold + 1); + + Assert.False(Unsafe.AreSame(ref ptr2Small, ref large.DangerousGetPinnableReference())); + } + + [Fact] + public void CreateWithAggressivePooling() + { + this.MemoryManager = ArrayPoolMemoryManager.CreateWithAggressivePooling(); + + Assert.True(this.CheckIsRentingPooledBuffer(4096 * 4096)); + } + + [Fact] + public void CreateDefault() + { + this.MemoryManager = ArrayPoolMemoryManager.CreateDefault(); + + Assert.False(this.CheckIsRentingPooledBuffer(2 * 4096 * 4096)); + Assert.True(this.CheckIsRentingPooledBuffer(2048 * 2048)); + } + + [Fact] + public void CreateWithModeratePooling() + { + this.MemoryManager = ArrayPoolMemoryManager.CreateWithModeratePooling(); + + Assert.False(this.CheckIsRentingPooledBuffer(2048 * 2048)); + Assert.True(this.CheckIsRentingPooledBuffer(1024 * 16)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index d662a1b3ef..82163d2bb4 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -5,9 +5,10 @@ namespace SixLabors.ImageSharp.Tests.Memory { using System; using System.Runtime.CompilerServices; - + using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Tests.Common; + using SixLabors.Primitives; using Xunit; @@ -16,56 +17,64 @@ namespace SixLabors.ImageSharp.Tests.Memory // ReSharper disable once ClassNeverInstantiated.Local private class Assert : Xunit.Assert { - public static void SpanPointsTo(Span span, Buffer buffer, int bufferOffset = 0) + public static void SpanPointsTo(Span span, IBuffer buffer, int bufferOffset = 0) where T : struct { - ref T actual = ref span.DangerousGetPinnableReference(); - ref T expected = ref Unsafe.Add(ref buffer[0], bufferOffset); + ref T actual = ref MemoryMarshal.GetReference(span); + ref T expected = ref Unsafe.Add(ref buffer.DangerousGetPinnableReference(), bufferOffset); Assert.True(Unsafe.AreSame(ref expected, ref actual), "span does not point to the expected position"); } } - [Theory] - [InlineData(7, 42)] - [InlineData(1025, 17)] - public void Construct(int width, int height) + private MemoryManager MemoryManager { get; } = new MockMemoryManager(); + + private class MockMemoryManager : MemoryManager { - using (Buffer2D buffer = new Buffer2D(width, height)) + internal override IBuffer Allocate(int length, bool clear) { - Assert.Equal(width, buffer.Width); - Assert.Equal(height, buffer.Height); - Assert.Equal(width * height, buffer.Length); + T[] array = new T[length + 42]; + + if (!clear) + { + Span data = array.AsSpan().NonPortableCast(); + for (int i = 0; i < data.Length; i++) + { + data[i] = 42; + } + } + + return new BasicArrayBuffer(array, length); + } + + internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear) + { + throw new NotImplementedException(); } } [Theory] [InlineData(7, 42)] [InlineData(1025, 17)] - public void Construct_FromExternalArray(int width, int height) + public void Construct(int width, int height) { - TestStructs.Foo[] array = new TestStructs.Foo[width * height + 10]; - using (Buffer2D buffer = new Buffer2D(array, width, height)) + using (Buffer2D buffer = this.MemoryManager.Allocate2D(width, height)) { Assert.Equal(width, buffer.Width); Assert.Equal(height, buffer.Height); - Assert.Equal(width * height, buffer.Length); + Assert.Equal(width * height, buffer.Buffer.Length()); } } - [Fact] public void CreateClean() { - for (int i = 0; i < 100; i++) + using (Buffer2D buffer = this.MemoryManager.Allocate2D(42, 42, true)) { - using (Buffer2D buffer = Buffer2D.CreateClean(42, 42)) + Span span = buffer.Span; + for (int j = 0; j < span.Length; j++) { - for (int j = 0; j < buffer.Length; j++) - { - Assert.Equal(0, buffer.Array[j]); - buffer.Array[j] = 666; - } + Assert.Equal(0, span[j]); } } } @@ -76,13 +85,13 @@ namespace SixLabors.ImageSharp.Tests.Memory [InlineData(17, 42, 41)] public void GetRowSpanY(int width, int height, int y) { - using (Buffer2D buffer = new Buffer2D(width, height)) + using (Buffer2D buffer = this.MemoryManager.Allocate2D(width, height)) { Span span = buffer.GetRowSpan(y); // Assert.Equal(width * y, span.Start); Assert.Equal(width, span.Length); - Assert.SpanPointsTo(span, buffer, width * y); + Assert.SpanPointsTo(span, buffer.Buffer, width * y); } } @@ -92,13 +101,13 @@ namespace SixLabors.ImageSharp.Tests.Memory [InlineData(17, 42, 0, 41)] public void GetRowSpanXY(int width, int height, int x, int y) { - using (Buffer2D buffer = new Buffer2D(width, height)) + using (Buffer2D buffer = this.MemoryManager.Allocate2D(width, height)) { Span span = buffer.GetRowSpan(x, y); // Assert.Equal(width * y + x, span.Start); Assert.Equal(width - x, span.Length); - Assert.SpanPointsTo(span, buffer, width * y + x); + Assert.SpanPointsTo(span, buffer.Buffer, width * y + x); } } @@ -108,16 +117,33 @@ namespace SixLabors.ImageSharp.Tests.Memory [InlineData(99, 88, 98, 87)] public void Indexer(int width, int height, int x, int y) { - using (Buffer2D buffer = new Buffer2D(width, height)) + using (Buffer2D buffer = this.MemoryManager.Allocate2D(width, height)) { - TestStructs.Foo[] array = buffer.Array; + Span span = buffer.Buffer.Span; ref TestStructs.Foo actual = ref buffer[x, y]; - ref TestStructs.Foo expected = ref array[y * width + x]; + ref TestStructs.Foo expected = ref span[y * width + x]; Assert.True(Unsafe.AreSame(ref expected, ref actual)); } } + + [Fact] + public void SwapContents() + { + using (Buffer2D a = this.MemoryManager.Allocate2D(10, 5)) + using (Buffer2D b = this.MemoryManager.Allocate2D(3, 7)) + { + IBuffer aa = a.Buffer; + IBuffer bb = b.Buffer; + + Buffer2D.SwapContents(a, b); + + Assert.Equal(bb, a.Buffer); + Assert.Equal(new Size(3, 7), a.Size()); + Assert.Equal(new Size(10, 5), b.Size()); + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs index 026b694981..db7367d972 100644 --- a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs +++ b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Memory [Fact] public void Construct() { - using (var buffer = new Buffer2D(10, 20)) + using (var buffer = Configuration.Default.MemoryManager.Allocate2D(10, 20)) { var rectangle = new Rectangle(3,2, 5, 6); var area = new BufferArea(buffer, rectangle); @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Memory private static Buffer2D CreateTestBuffer(int w, int h) { - var buffer = new Buffer2D(w, h); + var buffer = Configuration.Default.MemoryManager.Allocate2D(w, h); for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) @@ -104,11 +104,44 @@ namespace SixLabors.ImageSharp.Tests.Memory { BufferArea area0 = buffer.GetArea(6, 8, 10, 10); - ref int r = ref area0.GetReferenceToOrigo(); + ref int r = ref area0.GetReferenceToOrigin(); int expected = buffer[6, 8]; Assert.Equal(expected, r); } } + + [Fact] + public void Clear_FullArea() + { + using (Buffer2D buffer = CreateTestBuffer(22, 13)) + { + buffer.GetArea().Clear(); + Span fullSpan = buffer.Span; + Assert.True(fullSpan.SequenceEqual(new int[fullSpan.Length])); + } + } + + [Fact] + public void Clear_SubArea() + { + using (Buffer2D buffer = CreateTestBuffer(20, 30)) + { + BufferArea area = buffer.GetArea(5, 5, 10, 10); + area.Clear(); + + Assert.NotEqual(0, buffer[4, 4]); + Assert.NotEqual(0, buffer[15, 15]); + + Assert.Equal(0, buffer[5, 5]); + Assert.Equal(0, buffer[14, 14]); + + for (int y = area.Rectangle.Y; y < area.Rectangle.Bottom; y++) + { + Span span = buffer.GetRowSpan(y).Slice(area.Rectangle.X, area.Width); + Assert.True(span.SequenceEqual(new int[area.Width])); + } + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/BufferTestSuite.cs b/tests/ImageSharp.Tests/Memory/BufferTestSuite.cs new file mode 100644 index 0000000000..eff1f197a0 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/BufferTestSuite.cs @@ -0,0 +1,286 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; +using Xunit; +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests.Memory +{ + + + /// + /// Inherit this class to test an implementation (provided by ). + /// + public abstract class BufferTestSuite + { + protected BufferTestSuite(MemoryManager memoryManager) + { + this.MemoryManager = memoryManager; + } + + protected MemoryManager MemoryManager { get; } + + public struct CustomStruct : IEquatable + { + public long A; + + public byte B; + + public float C; + + public CustomStruct(long a, byte b, float c) + { + this.A = a; + this.B = b; + this.C = c; + } + + public bool Equals(CustomStruct other) + { + return this.A == other.A && this.B == other.B && this.C.Equals(other.C); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is CustomStruct && this.Equals((CustomStruct)obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = this.A.GetHashCode(); + hashCode = (hashCode * 397) ^ this.B.GetHashCode(); + hashCode = (hashCode * 397) ^ this.C.GetHashCode(); + return hashCode; + } + } + } + + public static readonly TheoryData LenthValues = new TheoryData { 0, 1, 7, 1023, 1024 }; + + [Theory] + [MemberData(nameof(LenthValues))] + public void HasCorrectLength_byte(int desiredLength) + { + this.TestHasCorrectLength(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void HasCorrectLength_float(int desiredLength) + { + this.TestHasCorrectLength(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void HasCorrectLength_CustomStruct(int desiredLength) + { + this.TestHasCorrectLength(desiredLength); + } + + private void TestHasCorrectLength(int desiredLength) + where T : struct + { + using (IBuffer buffer = this.MemoryManager.Allocate(desiredLength)) + { + Assert.Equal(desiredLength, buffer.Span.Length); + } + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void CanAllocateCleanBuffer_byte(int desiredLength) + { + this.TestCanAllocateCleanBuffer(desiredLength, false); + this.TestCanAllocateCleanBuffer(desiredLength, true); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void CanAllocateCleanBuffer_double(int desiredLength) + { + this.TestCanAllocateCleanBuffer(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void CanAllocateCleanBuffer_CustomStruct(int desiredLength) + { + this.TestCanAllocateCleanBuffer(desiredLength); + } + + private IBuffer Allocate(int desiredLength, bool clean, bool managedByteBuffer) + where T : struct + { + if (managedByteBuffer) + { + if (!(this.MemoryManager.AllocateManagedByteBuffer(desiredLength, clean) is IBuffer buffer)) + { + throw new InvalidOperationException("typeof(T) != typeof(byte)"); + } + + return buffer; + } + + return this.MemoryManager.Allocate(desiredLength, clean); + } + + private void TestCanAllocateCleanBuffer(int desiredLength, bool testManagedByteBuffer = false) + where T : struct, IEquatable + { + ReadOnlySpan expected = new T[desiredLength]; + + for (int i = 0; i < 10; i++) + { + using (IBuffer buffer = this.Allocate(desiredLength, true, testManagedByteBuffer)) + { + Assert.True(buffer.Span.SequenceEqual(expected)); + } + } + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void SpanPropertyIsAlwaysTheSame_int(int desiredLength) + { + this.TestSpanPropertyIsAlwaysTheSame(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void SpanPropertyIsAlwaysTheSame_byte(int desiredLength) + { + this.TestSpanPropertyIsAlwaysTheSame(desiredLength, false); + this.TestSpanPropertyIsAlwaysTheSame(desiredLength, true); + } + + private void TestSpanPropertyIsAlwaysTheSame(int desiredLength, bool testManagedByteBuffer = false) + where T : struct + { + using (IBuffer buffer = this.Allocate(desiredLength, false, testManagedByteBuffer)) + { + ref T a = ref MemoryMarshal.GetReference(buffer.Span); + ref T b = ref MemoryMarshal.GetReference(buffer.Span); + ref T c = ref MemoryMarshal.GetReference(buffer.Span); + + Assert.True(Unsafe.AreSame(ref a, ref b)); + Assert.True(Unsafe.AreSame(ref b, ref c)); + } + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void WriteAndReadElements_float(int desiredLength) + { + this.TestWriteAndReadElements(desiredLength, x => x * 1.2f); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void WriteAndReadElements_byte(int desiredLength) + { + this.TestWriteAndReadElements(desiredLength, x => (byte)(x+1), false); + this.TestWriteAndReadElements(desiredLength, x => (byte)(x + 1), true); + } + + private void TestWriteAndReadElements(int desiredLength, Func getExpectedValue, bool testManagedByteBuffer = false) + where T : struct + { + using (IBuffer buffer = this.Allocate(desiredLength, false, testManagedByteBuffer)) + { + T[] expectedVals = new T[buffer.Length()]; + + for (int i = 0; i < buffer.Length(); i++) + { + Span span = buffer.Span; + expectedVals[i] = getExpectedValue(i); + span[i] = expectedVals[i]; + } + + for (int i = 0; i < buffer.Length(); i++) + { + Span span = buffer.Span; + Assert.Equal(expectedVals[i], span[i]); + } + } + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void IndexingSpan_WhenOutOfRange_Throws_byte(int desiredLength) + { + this.TestIndexOutOfRangeShouldThrow(desiredLength, false); + this.TestIndexOutOfRangeShouldThrow(desiredLength, true); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void IndexingSpan_WhenOutOfRange_Throws_long(int desiredLength) + { + this.TestIndexOutOfRangeShouldThrow(desiredLength); + } + + [Theory] + [MemberData(nameof(LenthValues))] + public void IndexingSpan_WhenOutOfRange_Throws_CustomStruct(int desiredLength) + { + this.TestIndexOutOfRangeShouldThrow(desiredLength); + } + + private T TestIndexOutOfRangeShouldThrow(int desiredLength, bool testManagedByteBuffer = false) + where T : struct, IEquatable + { + var dummy = default(T); + + using (IBuffer buffer = this.Allocate(desiredLength, false, testManagedByteBuffer)) + { + Assert.ThrowsAny( + () => + { + Span span = buffer.Span; + dummy = span[desiredLength]; + }); + + Assert.ThrowsAny( + () => + { + Span span = buffer.Span; + dummy = span[desiredLength + 1]; + }); + + Assert.ThrowsAny( + () => + { + Span span = buffer.Span; + dummy = span[desiredLength + 42]; + }); + } + + return dummy; + } + + [Theory] + [InlineData(1)] + [InlineData(7)] + [InlineData(1024)] + [InlineData(6666)] + public void ManagedByteBuffer_ArrayIsCorrect(int desiredLength) + { + using (IManagedByteBuffer buffer = this.MemoryManager.AllocateManagedByteBuffer(desiredLength)) + { + ref byte array0 = ref buffer.Array[0]; + ref byte span0 = ref buffer.DangerousGetPinnableReference(); + + Assert.True(Unsafe.AreSame(ref span0, ref array0)); + Assert.True(buffer.Array.Length >= buffer.Span.Length); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/BufferTests.cs b/tests/ImageSharp.Tests/Memory/BufferTests.cs deleted file mode 100644 index e1efeb24e8..0000000000 --- a/tests/ImageSharp.Tests/Memory/BufferTests.cs +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Tests.Memory -{ - using System; - using System.Runtime.CompilerServices; - - using SixLabors.ImageSharp.Memory; - - using Xunit; - - public unsafe class BufferTests - { - // ReSharper disable once ClassNeverInstantiated.Local - private class Assert : Xunit.Assert - { - public static void SpanPointsTo(Span span, Buffer buffer, int bufferOffset = 0) - where T : struct - { - ref T actual = ref span.DangerousGetPinnableReference(); - ref T expected = ref Unsafe.Add(ref buffer[0], bufferOffset); - - Assert.True(Unsafe.AreSame(ref expected, ref actual), "span does not point to the expected position"); - } - - public static void Equal(void* expected, void* actual) - { - Assert.Equal((IntPtr)expected, (IntPtr)actual); - } - } - - [Theory] - [InlineData(42)] - [InlineData(1111)] - public void ConstructWithOwnArray(int count) - { - using (Buffer buffer = new Buffer(count)) - { - Assert.False(buffer.IsDisposedOrLostArrayOwnership); - Assert.NotNull(buffer.Array); - Assert.Equal(count, buffer.Length); - Assert.True(buffer.Array.Length >= count); - } - } - - [Theory] - [InlineData(42)] - [InlineData(1111)] - public void ConstructWithExistingArray(int count) - { - TestStructs.Foo[] array = new TestStructs.Foo[count]; - using (Buffer buffer = new Buffer(array)) - { - Assert.False(buffer.IsDisposedOrLostArrayOwnership); - Assert.Equal(array, buffer.Array); - Assert.Equal(count, buffer.Length); - } - } - - [Fact] - public void Clear() - { - TestStructs.Foo[] a = { new TestStructs.Foo() { A = 1, B = 2 }, new TestStructs.Foo() { A = 3, B = 4 } }; - using (Buffer buffer = new Buffer(a)) - { - buffer.Clear(); - - Assert.Equal(default(TestStructs.Foo), a[0]); - Assert.Equal(default(TestStructs.Foo), a[1]); - } - } - - [Fact] - public void CreateClean() - { - for (int i = 0; i < 100; i++) - { - using (Buffer buffer = Buffer.CreateClean(42)) - { - for (int j = 0; j < buffer.Length; j++) - { - Assert.Equal(0, buffer.Array[j]); - buffer.Array[j] = 666; - } - } - } - } - - public class Indexer - { - public static readonly TheoryData IndexerData = - new TheoryData() - { - { 10, 0 }, - { 16, 3 }, - { 10, 9 } - }; - - [Theory] - [MemberData(nameof(IndexerData))] - public void Read(int length, int index) - { - TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length); - - using (Buffer buffer = new Buffer(a)) - { - TestStructs.Foo element = buffer[index]; - - Assert.Equal(a[index], element); - } - } - - [Theory] - [MemberData(nameof(IndexerData))] - public void Write(int length, int index) - { - TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length); - - using (Buffer buffer = new Buffer(a)) - { - buffer[index] = new TestStructs.Foo(666, 666); - - Assert.Equal(new TestStructs.Foo(666, 666), a[index]); - } - } - } - - [Fact] - public void Dispose() - { - Buffer buffer = new Buffer(42); - buffer.Dispose(); - - Assert.True(buffer.IsDisposedOrLostArrayOwnership); - } - - [Theory] - [InlineData(7)] - [InlineData(123)] - public void CastToSpan(int bufferLength) - { - using (Buffer buffer = new Buffer(bufferLength)) - { - Span span = buffer; - - //Assert.Equal(buffer.Array, span.ToArray()); - //Assert.Equal(0, span.Start); - Assert.SpanPointsTo(span, buffer); - Assert.Equal(span.Length, bufferLength); - } - } - - [Fact] - public void Span() - { - using (Buffer buffer = new Buffer(42)) - { - Span span = buffer.Span; - - // Assert.Equal(buffer.Array, span.ToArray()); - // Assert.Equal(0, span.Start); - Assert.SpanPointsTo(span, buffer); - Assert.Equal(42, span.Length); - } - } - - public class Slice - { - - [Theory] - [InlineData(7, 2)] - [InlineData(123, 17)] - public void WithStartOnly(int bufferLength, int start) - { - using (Buffer buffer = new Buffer(bufferLength)) - { - Span span = buffer.Slice(start); - - Assert.SpanPointsTo(span, buffer, start); - Assert.Equal(span.Length, bufferLength - start); - } - } - - [Theory] - [InlineData(7, 2, 5)] - [InlineData(123, 17, 42)] - public void WithStartAndLength(int bufferLength, int start, int spanLength) - { - using (Buffer buffer = new Buffer(bufferLength)) - { - Span span = buffer.Slice(start, spanLength); - - Assert.SpanPointsTo(span, buffer, start); - Assert.Equal(span.Length, spanLength); - } - } - } - - [Fact] - public void UnPinAndTakeArrayOwnership() - { - TestStructs.Foo[] data = null; - using (Buffer buffer = new Buffer(42)) - { - data = buffer.TakeArrayOwnership(); - Assert.True(buffer.IsDisposedOrLostArrayOwnership); - } - - Assert.NotNull(data); - Assert.True(data.Length >= 42); - } - - public class Pin - { - [Fact] - public void ReturnsPinnedPointerToTheBeginningOfArray() - { - using (Buffer buffer = new Buffer(42)) - { - TestStructs.Foo* actual = (TestStructs.Foo*)buffer.Pin(); - fixed (TestStructs.Foo* expected = buffer.Array) - { - Assert.Equal(expected, actual); - } - } - } - - [Fact] - public void SecondCallReturnsTheSamePointer() - { - using (Buffer buffer = new Buffer(42)) - { - IntPtr ptr1 = buffer.Pin(); - IntPtr ptr2 = buffer.Pin(); - - Assert.Equal(ptr1, ptr2); - } - } - - [Fact] - public void WhenCalledOnDisposedBuffer_ThrowsInvalidOperationException() - { - Buffer buffer = new Buffer(42); - buffer.Dispose(); - - Assert.Throws(() => buffer.Pin()); - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/Fast2DArrayTests.cs b/tests/ImageSharp.Tests/Memory/Fast2DArrayTests.cs index 5cdbe638a6..a5364db721 100644 --- a/tests/ImageSharp.Tests/Memory/Fast2DArrayTests.cs +++ b/tests/ImageSharp.Tests/Memory/Fast2DArrayTests.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Memory { Assert.Throws(() => { - Fast2DArray fast = new Fast2DArray(null); + var fast = new Fast2DArray(null); }); } @@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests.Memory { Assert.Throws(() => { - Fast2DArray fast = new Fast2DArray(0, 10); + var fast = new Fast2DArray(0, 10); }); } @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Memory { Assert.Throws(() => { - Fast2DArray fast = new Fast2DArray(10, 0); + var fast = new Fast2DArray(10, 0); }); } @@ -49,14 +49,14 @@ namespace SixLabors.ImageSharp.Tests.Memory { Assert.Throws(() => { - Fast2DArray fast = new Fast2DArray(new float[0, 0]); + var fast = new Fast2DArray(new float[0, 0]); }); } [Fact] public void Fast2DArrayReturnsCorrectDimensions() { - Fast2DArray fast = new Fast2DArray(FloydSteinbergMatrix); + var fast = new Fast2DArray(FloydSteinbergMatrix); Assert.True(fast.Width == FloydSteinbergMatrix.GetLength(1)); Assert.True(fast.Height == FloydSteinbergMatrix.GetLength(0)); } @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests.Memory [Fact] public void Fast2DArrayGetSetReturnsCorrectResults() { - Fast2DArray fast = new Fast2DArray(4, 4); + var fast = new Fast2DArray(4, 4); const float Val = 5F; fast[3, 3] = Val; diff --git a/tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs b/tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs deleted file mode 100644 index caba9a4647..0000000000 --- a/tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Linq; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; -using Xunit; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Memory -{ - using System; - - /// - /// Tests the class. - /// - public class PixelDataPoolTests - { - private const int MaxPooledBufferSizeInBytes = PixelDataPool.MaxPooledBufferSizeInBytes; - - readonly object monitor = new object(); - - [Theory] - [InlineData(1)] - [InlineData(1024)] - public void PixelDataPoolRentsMinimumSize(int size) - { - Rgba32[] pixels = PixelDataPool.Rent(size); - - Assert.True(pixels.Length >= size); - } - - [Fact] - public void PixelDataPoolDoesNotThrowWhenReturningNonPooled() - { - Rgba32[] pixels = new Rgba32[1024]; - - PixelDataPool.Return(pixels); - - Assert.True(pixels.Length >= 1024); - } - - /// - /// Rent 'n' buffers -> return all -> re-rent, verify if there is at least one in common. - /// - private bool CheckIsPooled(int n, int count) - where T : struct - { - lock (this.monitor) - { - T[][] original = new T[n][]; - - for (int i = 0; i < n; i++) - { - original[i] = PixelDataPool.Rent(count); - } - - for (int i = 0; i < n; i++) - { - PixelDataPool.Return(original[i]); - } - - T[][] verification = new T[n][]; - - for (int i = 0; i < n; i++) - { - verification[i] = PixelDataPool.Rent(count); - } - - return original.Intersect(verification).Any(); - } - } - - [Theory] - [InlineData(32)] - [InlineData(512)] - [InlineData(MaxPooledBufferSizeInBytes-1)] - public void SmallBuffersArePooled(int size) - { - Assert.True(this.CheckIsPooled(5, size)); - } - - [Theory] - [InlineData(128 * 1024 * 1024)] - [InlineData(MaxPooledBufferSizeInBytes+1)] - public void LargeBuffersAreNotPooled_OfByte(int size) - { - Assert.False(this.CheckIsPooled(2, size)); - } - - [StructLayout(LayoutKind.Explicit, Size = 512)] - struct TestStruct - { - } - - [Fact] - public unsafe void LaregeBuffersAreNotPooled_OfBigValueType() - { - const int mb128 = 128 * 1024 * 1024; - int count = mb128 / sizeof(TestStruct); - - Assert.False(this.CheckIsPooled(2, count)); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/SimpleGcMemoryManagerTests.cs b/tests/ImageSharp.Tests/Memory/SimpleGcMemoryManagerTests.cs new file mode 100644 index 0000000000..0d1c2beb8f --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/SimpleGcMemoryManagerTests.cs @@ -0,0 +1,15 @@ +namespace SixLabors.ImageSharp.Tests.Memory +{ + using SixLabors.ImageSharp.Memory; + + public class SimpleGcMemoryManagerTests + { + public class BufferTests : BufferTestSuite + { + public BufferTests() + : base(new SimpleGcMemoryManager()) + { + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/SpanUtilityTests.cs b/tests/ImageSharp.Tests/Memory/SpanUtilityTests.cs index 395c325461..23bc297436 100644 --- a/tests/ImageSharp.Tests/Memory/SpanUtilityTests.cs +++ b/tests/ImageSharp.Tests/Memory/SpanUtilityTests.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +// ReSharper disable InconsistentNaming +// ReSharper disable AccessToStaticMemberViaDerivedType namespace SixLabors.ImageSharp.Tests.Memory { using System; @@ -30,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Memory { float[] stuff = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; - Span span = new Span(stuff); + var span = new Span(stuff); ref Vector v = ref span.FetchVector(); @@ -39,199 +41,8 @@ namespace SixLabors.ImageSharp.Tests.Memory Assert.Equal(2, v[2]); Assert.Equal(3, v[3]); } - - [Fact] - public void AsBytes() - { - TestStructs.Foo[] fooz = { new TestStructs.Foo(1, 2), new TestStructs.Foo(3, 4), new TestStructs.Foo(5, 6) }; - - using (Buffer colorBuf = new Buffer(fooz)) - { - Span orig = colorBuf.Slice(1); - Span asBytes = orig.AsBytes(); - - // Assert.Equal(asBytes.Start, sizeof(Foo)); - Assert.Equal(orig.Length * Unsafe.SizeOf(), asBytes.Length); - Assert.SameRefs(ref orig.DangerousGetPinnableReference(), ref asBytes.DangerousGetPinnableReference()); - } - } - - public class Construct - { - [Fact] - public void Basic() - { - TestStructs.Foo[] array = TestStructs.Foo.CreateArray(3); - - // Act: - Span span = new Span(array); - - // Assert: - Assert.Equal(array, span.ToArray()); - Assert.Equal(3, span.Length); - Assert.SameRefs(ref array[0], ref span.DangerousGetPinnableReference()); - } - - [Fact] - public void WithStart() - { - TestStructs.Foo[] array = TestStructs.Foo.CreateArray(4); - int start = 2; - - // Act: - Span span = new Span(array, start); - - // Assert: - Assert.SameRefs(ref array[start], ref span.DangerousGetPinnableReference()); - Assert.Equal(array.Length - start, span.Length); - } - - [Fact] - public void WithStartAndLength() - { - TestStructs.Foo[] array = TestStructs.Foo.CreateArray(10); - int start = 2; - int length = 3; - // Act: - Span span = new Span(array, start, length); - - // Assert: - Assert.SameRefs(ref array[start], ref span.DangerousGetPinnableReference()); - Assert.Equal(length, span.Length); - } - } - - public class Slice - { - [Fact] - public void StartOnly() - { - TestStructs.Foo[] array = TestStructs.Foo.CreateArray(5); - int start0 = 2; - int start1 = 2; - int totalOffset = start0 + start1; - - Span span = new Span(array, start0); - - // Act: - span = span.Slice(start1); - - // Assert: - Assert.SameRefs(ref array[totalOffset], ref span.DangerousGetPinnableReference()); - Assert.Equal(array.Length - totalOffset, span.Length); - } - - [Fact] - public void StartAndLength() - { - TestStructs.Foo[] array = TestStructs.Foo.CreateArray(10); - int start0 = 2; - int start1 = 2; - int totalOffset = start0 + start1; - int sliceLength = 3; - - Span span = new Span(array, start0); - - // Act: - span = span.Slice(start1, sliceLength); - - // Assert: - Assert.SameRefs(ref array[totalOffset], ref span.DangerousGetPinnableReference()); - Assert.Equal(sliceLength, span.Length); - } - } - - //[Theory] - //[InlineData(4)] - //[InlineData(1500)] - //public void Clear(int count) - //{ - // Foo[] array = Foo.CreateArray(count + 42); - - // int offset = 2; - // Span ap = new Span(array, offset); - - // // Act: - // ap.Clear(count); - - // Assert.NotEqual(default(Foo), array[offset - 1]); - // Assert.Equal(default(Foo), array[offset]); - // Assert.Equal(default(Foo), array[offset + count - 1]); - // Assert.NotEqual(default(Foo), array[offset + count]); - //} - - public class Indexer - { - public static readonly TheoryData IndexerData = - new TheoryData() - { - { 10, 0, 0 }, - { 10, 2, 0 }, - { 16, 0, 3 }, - { 16, 2, 3 }, - { 10, 0, 9 }, - { 10, 1, 8 } - }; - - [Theory] - [MemberData(nameof(IndexerData))] - public void Read(int length, int start, int index) - { - TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length); - Span span = new Span(a, start); - - TestStructs.Foo element = span[index]; - - Assert.Equal(a[start + index], element); - } - - [Theory] - [MemberData(nameof(IndexerData))] - public void Write(int length, int start, int index) - { - TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length); - Span span = new Span(a, start); - - span[index] = new TestStructs.Foo(666, 666); - - Assert.Equal(new TestStructs.Foo(666, 666), a[start + index]); - } - - [Theory] - [InlineData(10, 0, 0, 5)] - [InlineData(10, 1, 1, 5)] - [InlineData(10, 1, 1, 6)] - [InlineData(10, 1, 1, 7)] - public void AsBytes_Read(int length, int start, int index, int byteOffset) - { - TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length); - Span span = new Span(a, start); - - Span bytes = span.AsBytes(); - - byte actual = bytes[index * Unsafe.SizeOf() + byteOffset]; - - ref byte baseRef = ref Unsafe.As(ref a[0]); - byte expected = Unsafe.Add(ref baseRef, (start + index) * Unsafe.SizeOf() + byteOffset); - - Assert.Equal(expected, actual); - } - } - - [Theory] - [InlineData(0, 4)] - [InlineData(2, 4)] - [InlineData(3, 4)] - public void DangerousGetPinnableReference(int start, int length) - { - TestStructs.Foo[] a = TestStructs.Foo.CreateArray(length); - Span span = new Span(a, start); - ref TestStructs.Foo r = ref span.DangerousGetPinnableReference(); - - Assert.True(Unsafe.AreSame(ref a[start], ref r)); - } - - public class Copy + + public class SpanHelper_Copy { private static void AssertNotDefault(T[] data, int idx) where T : struct @@ -267,8 +78,8 @@ namespace SixLabors.ImageSharp.Tests.Memory TestStructs.Foo[] source = TestStructs.Foo.CreateArray(count + 2); TestStructs.Foo[] dest = new TestStructs.Foo[count + 5]; - Span apSource = new Span(source, 1); - Span apDest = new Span(dest, 1); + var apSource = new Span(source, 1, source.Length - 1); + var apDest = new Span(dest, 1, dest.Length - 1); SpanHelper.Copy(apSource, apDest, count - 1); @@ -290,8 +101,8 @@ namespace SixLabors.ImageSharp.Tests.Memory TestStructs.AlignedFoo[] source = TestStructs.AlignedFoo.CreateArray(count + 2); TestStructs.AlignedFoo[] dest = new TestStructs.AlignedFoo[count + 5]; - Span apSource = new Span(source, 1); - Span apDest = new Span(dest, 1); + var apSource = new Span(source, 1, source.Length - 1); + var apDest = new Span(dest, 1, dest.Length - 1); SpanHelper.Copy(apSource, apDest, count - 1); @@ -313,8 +124,8 @@ namespace SixLabors.ImageSharp.Tests.Memory int[] source = CreateTestInts(count + 2); int[] dest = new int[count + 5]; - Span apSource = new Span(source, 1); - Span apDest = new Span(dest, 1); + var apSource = new Span(source, 1, source.Length - 1); + var apDest = new Span(dest, 1, dest.Length - 1); SpanHelper.Copy(apSource, apDest, count - 1); @@ -337,8 +148,8 @@ namespace SixLabors.ImageSharp.Tests.Memory TestStructs.Foo[] source = TestStructs.Foo.CreateArray(count + 2); byte[] dest = new byte[destCount + sizeof(TestStructs.Foo) * 2]; - Span apSource = new Span(source, 1); - Span apDest = new Span(dest, sizeof(TestStructs.Foo)); + var apSource = new Span(source, 1, source.Length - 1); + var apDest = new Span(dest, sizeof(TestStructs.Foo), dest.Length - sizeof(TestStructs.Foo)); SpanHelper.Copy(apSource.AsBytes(), apDest, (count - 1) * sizeof(TestStructs.Foo)); @@ -360,8 +171,8 @@ namespace SixLabors.ImageSharp.Tests.Memory TestStructs.AlignedFoo[] source = TestStructs.AlignedFoo.CreateArray(count + 2); byte[] dest = new byte[destCount + sizeof(TestStructs.AlignedFoo) * 2]; - Span apSource = new Span(source, 1); - Span apDest = new Span(dest, sizeof(TestStructs.AlignedFoo)); + var apSource = new Span(source, 1, source.Length - 1); + var apDest = new Span(dest, sizeof(TestStructs.AlignedFoo), dest.Length - sizeof(TestStructs.AlignedFoo)); SpanHelper.Copy(apSource.AsBytes(), apDest, (count - 1) * sizeof(TestStructs.AlignedFoo)); @@ -383,8 +194,8 @@ namespace SixLabors.ImageSharp.Tests.Memory int[] source = CreateTestInts(count + 2); byte[] dest = new byte[destCount + sizeof(int) + 1]; - Span apSource = new Span(source); - Span apDest = new Span(dest); + var apSource = new Span(source); + var apDest = new Span(dest); SpanHelper.Copy(apSource.AsBytes(), apDest, count * sizeof(int)); @@ -404,8 +215,8 @@ namespace SixLabors.ImageSharp.Tests.Memory byte[] source = CreateTestBytes(srcCount); TestStructs.Foo[] dest = new TestStructs.Foo[count + 2]; - Span apSource = new Span(source); - Span apDest = new Span(dest); + var apSource = new Span(source); + var apDest = new Span(dest); SpanHelper.Copy(apSource, apDest.AsBytes(), count * sizeof(TestStructs.Foo)); @@ -417,26 +228,7 @@ namespace SixLabors.ImageSharp.Tests.Memory Assert.True((bool)ElementsAreEqual(dest, source, count - 1)); Assert.False((bool)ElementsAreEqual(dest, source, count)); } - - [Fact] - public void Color32ToBytes() - { - Rgba32[] colors = { new Rgba32(0, 1, 2, 3), new Rgba32(4, 5, 6, 7), new Rgba32(8, 9, 10, 11), }; - - using (Buffer colorBuf = new Buffer(colors)) - using (Buffer byteBuf = new Buffer(colors.Length * 4)) - { - SpanHelper.Copy(colorBuf.Span.AsBytes(), byteBuf, colorBuf.Length * sizeof(Rgba32)); - - byte[] a = byteBuf.Array; - - for (int i = 0; i < byteBuf.Length; i++) - { - Assert.Equal((byte)i, a[i]); - } - } - } - + internal static bool ElementsAreEqual(TestStructs.Foo[] array, byte[] rawArray, int index) { fixed (TestStructs.Foo* pArray = array) diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index edeeebd28e..5e7e9e3a79 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -272,7 +272,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void ExifTypeUndefined() { - Image image = TestFile.Create(TestImages.Jpeg.Baseline.Bad.ExifUndefType).CreateImage(); + Image image = TestFile.Create(TestImages.Jpeg.Progressive.Bad.ExifUndefType).CreateImage(); Assert.NotNull(image); ExifProfile profile = image.MetaData.ExifProfile; diff --git a/tests/ImageSharp.Tests/PixelFormats/ColorDefinitionTests.cs b/tests/ImageSharp.Tests/PixelFormats/ColorDefinitionTests.cs index af4181cde2..302e56ec71 100644 --- a/tests/ImageSharp.Tests/PixelFormats/ColorDefinitionTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/ColorDefinitionTests.cs @@ -16,7 +16,8 @@ namespace SixLabors.ImageSharp.Tests get { var result = new TheoryData(); - foreach (string name in typeof(NamedColors).GetTypeInfo().GetFields().Select(x => x.Name )) + foreach (string name in typeof(NamedColors).GetTypeInfo() + .GetFields().Where(x => x.Name != nameof(NamedColors.WebSafePalette)).Select(x => x.Name)) { result.Add(name); } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs index b95f8fdf61..50babde69a 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs @@ -12,6 +12,8 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders { + using SixLabors.ImageSharp.Memory; + public class PorterDuffFunctionsTests_TPixel { private static Span AsSpan(T value) @@ -25,6 +27,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1) }, }; + private MemoryManager MemoryManager { get; } = Configuration.Default.MemoryManager; + [Theory] [MemberData(nameof(NormalBlendFunctionData))] public void NormalBlendFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) @@ -49,7 +53,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.Normal().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Normal().Blend(this.MemoryManager, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -88,7 +92,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.Multiply().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Multiply().Blend(this.MemoryManager, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -127,7 +131,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.Add().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Add().Blend(this.MemoryManager, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -166,7 +170,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.Substract().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Substract().Blend(this.MemoryManager, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -205,7 +209,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.Screen().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Screen().Blend(this.MemoryManager, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -244,7 +248,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.Darken().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Darken().Blend(this.MemoryManager, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -283,7 +287,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.Lighten().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Lighten().Blend(this.MemoryManager, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -322,7 +326,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.Overlay().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.Overlay().Blend(this.MemoryManager, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -361,7 +365,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { Span dest = new Span(new TPixel[1]); - new DefaultPixelBlenders.HardLight().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultPixelBlenders.HardLight().Blend(this.MemoryManager, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs index 0fde67d28e..c0039bb37c 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.cs @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => ImageSharp.Rgba32.PixelOperations.ToVector4SimdAligned(s, d, 64) + (s, d) => ImageSharp.Rgba32.PixelOperations.ToVector4SimdAligned(s, d.Span, 64) ); } @@ -50,14 +50,14 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats int times = 200000; int count = 1024; - using (Buffer source = new Buffer(count)) - using (Buffer dest = new Buffer(count)) + using (IBuffer source = Configuration.Default.MemoryManager.Allocate(count)) + using (IBuffer dest = Configuration.Default.MemoryManager.Allocate(count)) { this.Measure( times, () => { - PixelOperations.Instance.ToVector4(source, dest, count); + PixelOperations.Instance.ToVector4(source.Span, dest.Span, count); }); } } @@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => Operations.PackFromVector4(s, d, count) + (s, d) => Operations.PackFromVector4(s, d.Span, count) ); } @@ -141,7 +141,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => Operations.ToVector4(s, d, count) + (s, d) => Operations.ToVector4(s, d.Span, count) ); } @@ -163,7 +163,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => Operations.PackFromRgb24Bytes(s, d, count) + (s, d) => Operations.PackFromRgb24Bytes(s, d.Span, count) ); } @@ -187,7 +187,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => Operations.ToRgb24Bytes(s, d, count) + (s, d) => Operations.ToRgb24Bytes(s, d.Span, count) ); } @@ -208,7 +208,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => Operations.PackFromRgba32Bytes(s, d, count) + (s, d) => Operations.PackFromRgba32Bytes(s, d.Span, count) ); } @@ -233,7 +233,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => Operations.ToRgba32Bytes(s, d, count) + (s, d) => Operations.ToRgba32Bytes(s, d.Span, count) ); } @@ -254,7 +254,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => Operations.PackFromBgr24Bytes(s, d, count) + (s, d) => Operations.PackFromBgr24Bytes(s, d.Span, count) ); } @@ -278,7 +278,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => Operations.ToBgr24Bytes(s, d, count) + (s, d) => Operations.ToBgr24Bytes(s, d.Span, count) ); } @@ -299,7 +299,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => Operations.PackFromBgra32Bytes(s, d, count) + (s, d) => Operations.PackFromBgra32Bytes(s, d.Span, count) ); } @@ -324,7 +324,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats TestOperation( source, expected, - (s, d) => Operations.ToBgra32Bytes(s, d, count) + (s, d) => Operations.ToBgra32Bytes(s, d.Span, count) ); } @@ -333,25 +333,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats where TSource : struct where TDest : struct { - public Buffer SourceBuffer { get; } - public Buffer ActualDestBuffer { get; } - public Buffer ExpectedDestBuffer { get; } - - public Span Source => this.SourceBuffer; - public Span ActualDest => this.ActualDestBuffer; - + public TSource[] SourceBuffer { get; } + public IBuffer ActualDestBuffer { get; } + public TDest[] ExpectedDestBuffer { get; } + public TestBuffers(TSource[] source, TDest[] expectedDest) { - this.SourceBuffer = new Buffer(source); - this.ExpectedDestBuffer = new Buffer(expectedDest); - this.ActualDestBuffer = new Buffer(expectedDest.Length); + this.SourceBuffer = source; + this.ExpectedDestBuffer = expectedDest; + this.ActualDestBuffer = Configuration.Default.MemoryManager.Allocate(expectedDest.Length); } public void Dispose() { - this.SourceBuffer.Dispose(); this.ActualDestBuffer.Dispose(); - this.ExpectedDestBuffer.Dispose(); } private const float Tolerance = 0.0001f; @@ -362,8 +357,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats if (typeof(TDest) == typeof(Vector4)) { - Vector4[] expected = this.ExpectedDestBuffer.Array as Vector4[]; - Vector4[] actual = this.ActualDestBuffer.Array as Vector4[]; + + Span expected = this.ExpectedDestBuffer.AsSpan().NonPortableCast(); + Span actual = this.ActualDestBuffer.Span.NonPortableCast(); for (int i = 0; i < count; i++) { @@ -374,8 +370,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats } else { - TDest[] expected = this.ExpectedDestBuffer.Array; - TDest[] actual = this.ActualDestBuffer.Array; + Span expected = this.ExpectedDestBuffer.AsSpan(); + Span actual = this.ActualDestBuffer.Span; for (int i = 0; i < count; i++) { Assert.Equal(expected[i], actual[i]); @@ -387,13 +383,13 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats internal static void TestOperation( TSource[] source, TDest[] expected, - Action, Buffer> action) + Action> action) where TSource : struct where TDest : struct { - using (TestBuffers buffers = new TestBuffers(source, expected)) + using (var buffers = new TestBuffers(source, expected)) { - action(buffers.Source, buffers.ActualDestBuffer); + action(buffers.SourceBuffer, buffers.ActualDestBuffer); buffers.Verify(); } } @@ -401,7 +397,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats internal static Vector4[] CreateVector4TestData(int length) { Vector4[] result = new Vector4[length]; - Random rnd = new Random(42); // Deterministic random values + var rnd = new Random(42); // Deterministic random values for (int i = 0; i < result.Length; i++) { @@ -414,7 +410,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { TPixel[] result = new TPixel[length]; - Random rnd = new Random(42); // Deterministic random values + var rnd = new Random(42); // Deterministic random values for (int i = 0; i < result.Length; i++) { @@ -428,7 +424,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats internal static byte[] CreateByteTestData(int length) { byte[] result = new byte[length]; - Random rnd = new Random(42); // Deterministic random values + var rnd = new Random(42); // Deterministic random values for (int i = 0; i < result.Length; i++) { diff --git a/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs b/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs new file mode 100644 index 0000000000..003f998d88 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs @@ -0,0 +1,105 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Binarization +{ + public class BinaryDitherTest : BaseImageOperationsExtensionTest + { + private readonly IOrderedDither orderedDither; + private readonly IErrorDiffuser errorDiffuser; + + public BinaryDitherTest() + { + this.orderedDither = KnownDitherers.BayerDither4x4; + this.errorDiffuser = KnownDiffusers.FloydSteinberg; + } + + [Fact] + public void BinaryDither_CorrectProcessor() + { + this.operations.BinaryDither(this.orderedDither); + BinaryOrderedDitherProcessor p = this.Verify>(); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(NamedColors.White, p.UpperColor); + Assert.Equal(NamedColors.Black, p.LowerColor); + } + + [Fact] + public void BinaryDither_rect_CorrectProcessor() + { + this.operations.BinaryDither(this.orderedDither, this.rect); + BinaryOrderedDitherProcessor p = this.Verify>(this.rect); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(NamedColors.White, p.UpperColor); + Assert.Equal(NamedColors.Black, p.LowerColor); + } + [Fact] + public void BinaryDither_index_CorrectProcessor() + { + this.operations.BinaryDither(this.orderedDither, NamedColors.Yellow, NamedColors.HotPink); + BinaryOrderedDitherProcessor p = this.Verify>(); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(NamedColors.Yellow, p.UpperColor); + Assert.Equal(NamedColors.HotPink, p.LowerColor); + } + + [Fact] + public void BinaryDither_index_rect_CorrectProcessor() + { + this.operations.BinaryDither(this.orderedDither, NamedColors.Yellow, NamedColors.HotPink, this.rect); + BinaryOrderedDitherProcessor p = this.Verify>(this.rect); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(NamedColors.HotPink, p.LowerColor); + } + + + [Fact] + public void BinaryDither_ErrorDiffuser_CorrectProcessor() + { + this.operations.BinaryDiffuse(this.errorDiffuser, .4F); + BinaryErrorDiffusionProcessor p = this.Verify>(); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.4F, p.Threshold); + Assert.Equal(NamedColors.White, p.UpperColor); + Assert.Equal(NamedColors.Black, p.LowerColor); + } + + [Fact] + public void BinaryDither_ErrorDiffuser_rect_CorrectProcessor() + { + this.operations.BinaryDiffuse(this.errorDiffuser, .3F, this.rect); + BinaryErrorDiffusionProcessor p = this.Verify>(this.rect); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.3F, p.Threshold); + Assert.Equal(NamedColors.White, p.UpperColor); + Assert.Equal(NamedColors.Black, p.LowerColor); + } + + [Fact] + public void BinaryDither_ErrorDiffuser_CorrectProcessorWithColors() + { + this.operations.BinaryDiffuse(this.errorDiffuser, .5F, NamedColors.HotPink, NamedColors.Yellow); + BinaryErrorDiffusionProcessor p = this.Verify>(); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.5F, p.Threshold); + Assert.Equal(NamedColors.HotPink, p.UpperColor); + Assert.Equal(NamedColors.Yellow, p.LowerColor); + } + + [Fact] + public void BinaryDither_ErrorDiffuser_rect_CorrectProcessorWithColors() + { + this.operations.BinaryDiffuse(this.errorDiffuser, .5F, NamedColors.HotPink, NamedColors.Yellow, this.rect); + BinaryErrorDiffusionProcessor p = this.Verify>(this.rect); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.5F, p.Threshold); + Assert.Equal(NamedColors.HotPink, p.UpperColor); + Assert.Equal(NamedColors.Yellow, p.LowerColor); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs b/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs index 221b4a9bff..488b3d18b1 100644 --- a/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs +++ b/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs @@ -15,16 +15,40 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public void BinaryThreshold_CorrectProcessor() { this.operations.BinaryThreshold(.23f); - var p = this.Verify>(); + BinaryThresholdProcessor p = this.Verify>(); Assert.Equal(.23f, p.Threshold); + Assert.Equal(NamedColors.White, p.UpperColor); + Assert.Equal(NamedColors.Black, p.LowerColor); } [Fact] public void BinaryThreshold_rect_CorrectProcessor() { this.operations.BinaryThreshold(.93f, this.rect); - var p = this.Verify>(this.rect); + BinaryThresholdProcessor p = this.Verify>(this.rect); Assert.Equal(.93f, p.Threshold); + Assert.Equal(NamedColors.White, p.UpperColor); + Assert.Equal(NamedColors.Black, p.LowerColor); + } + + [Fact] + public void BinaryThreshold_CorrectProcessorWithUpperLower() + { + this.operations.BinaryThreshold(.23f, NamedColors.HotPink, NamedColors.Yellow); + BinaryThresholdProcessor p = this.Verify>(); + Assert.Equal(.23f, p.Threshold); + Assert.Equal(NamedColors.HotPink, p.UpperColor); + Assert.Equal(NamedColors.Yellow, p.LowerColor); + } + + [Fact] + public void BinaryThreshold_rect_CorrectProcessorWithUpperLower() + { + this.operations.BinaryThreshold(.93f, NamedColors.HotPink, NamedColors.Yellow, this.rect); + BinaryThresholdProcessor p = this.Verify>(this.rect); + Assert.Equal(.93f, p.Threshold); + Assert.Equal(NamedColors.HotPink, p.UpperColor); + Assert.Equal(NamedColors.Yellow, p.LowerColor); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Binarization/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Binarization/DitherTests.cs deleted file mode 100644 index 94241d0071..0000000000 --- a/tests/ImageSharp.Tests/Processing/Binarization/DitherTests.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Dithering; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors; -using Moq; -using SixLabors.Primitives; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Processing.Binarization -{ - public class DitherTest : BaseImageOperationsExtensionTest - { - private readonly IOrderedDither orderedDither; - private readonly IErrorDiffuser errorDiffuser; - - public DitherTest() - { - this.orderedDither = new Mock().Object; - this.errorDiffuser = new Mock().Object; - } - [Fact] - public void Dither_CorrectProcessor() - { - this.operations.Dither(orderedDither); - var p = this.Verify>(); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(0, p.Index); - } - - [Fact] - public void Dither_rect_CorrectProcessor() - { - this.operations.Dither(orderedDither, this.rect); - var p = this.Verify>(this.rect); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(0, p.Index); - } - [Fact] - public void Dither_index_CorrectProcessor() - { - this.operations.Dither(orderedDither, 2); - var p = this.Verify>(); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(2, p.Index); - } - - [Fact] - public void Dither_index_rect_CorrectProcessor() - { - this.operations.Dither(orderedDither, this.rect, 2); - var p = this.Verify>(this.rect); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(2, p.Index); - } - - - [Fact] - public void Dither_ErrorDifuser_CorrectProcessor() - { - this.operations.Dither(errorDiffuser, 4); - var p = this.Verify>(); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(4, p.Threshold); - } - - [Fact] - public void Dither_ErrorDifuser_rect_CorrectProcessor() - { - this.operations.Dither(this.errorDiffuser, 3, this.rect); - var p = this.Verify>(this.rect); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(3, p.Threshold); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs b/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs new file mode 100644 index 0000000000..a0ddc2c7cc --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs @@ -0,0 +1,102 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Binarization +{ + public class OrderedDitherFactoryTests + { + private static readonly Fast2DArray Expected2x2Matrix = new Fast2DArray( + new uint[2, 2] + { + { 0, 2 }, + { 3, 1 } + }); + + private static readonly Fast2DArray Expected3x3Matrix = new Fast2DArray( + new uint[3, 3] + { + { 0, 5, 2 }, + { 7, 4, 8 }, + { 3, 6, 1 } + }); + + private static readonly Fast2DArray Expected4x4Matrix = new Fast2DArray( + new uint[4, 4] + { + { 0, 8, 2, 10 }, + { 12, 4, 14, 6 }, + { 3, 11, 1, 9 }, + { 15, 7, 13, 5 } + }); + + private static readonly Fast2DArray Expected8x8Matrix = new Fast2DArray( + new uint[8, 8] + { + { 0, 32, 8, 40, 2, 34, 10, 42 }, + { 48, 16, 56, 24, 50, 18, 58, 26 }, + { 12, 44, 4, 36, 14, 46, 6, 38 }, + { 60, 28, 52, 20, 62, 30, 54, 22 }, + { 3, 35, 11, 43, 1, 33, 9, 41 }, + { 51, 19, 59, 27, 49, 17, 57, 25 }, + { 15, 47, 7, 39, 13, 45, 5, 37 }, + { 63, 31, 55, 23, 61, 29, 53, 21 } + }); + + + [Fact] + public void OrderedDitherFactoryCreatesCorrect2x2Matrix() + { + Fast2DArray actual = OrderedDitherFactory.CreateDitherMatrix(2); + for (int y = 0; y < actual.Height; y++) + { + for (int x = 0; x < actual.Width; x++) + { + Assert.Equal(Expected2x2Matrix[y, x], actual[y, x]); + } + } + } + + [Fact] + public void OrderedDitherFactoryCreatesCorrect3x3Matrix() + { + Fast2DArray actual = OrderedDitherFactory.CreateDitherMatrix(3); + for (int y = 0; y < actual.Height; y++) + { + for (int x = 0; x < actual.Width; x++) + { + Assert.Equal(Expected3x3Matrix[y, x], actual[y, x]); + } + } + } + + [Fact] + public void OrderedDitherFactoryCreatesCorrect4x4Matrix() + { + Fast2DArray actual = OrderedDitherFactory.CreateDitherMatrix(4); + for (int y = 0; y < actual.Height; y++) + { + for (int x = 0; x < actual.Width; x++) + { + Assert.Equal(Expected4x4Matrix[y, x], actual[y, x]); + } + } + } + + [Fact] + public void OrderedDitherFactoryCreatesCorrect8x8Matrix() + { + Fast2DArray actual = OrderedDitherFactory.CreateDitherMatrix(8); + for (int y = 0; y < actual.Height; y++) + { + for (int x = 0; x < actual.Width; x++) + { + Assert.Equal(Expected8x8Matrix[y, x], actual[y, x]); + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs new file mode 100644 index 0000000000..03ae17848c --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs @@ -0,0 +1,104 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Binarization +{ + public class DitherTest : BaseImageOperationsExtensionTest + { + private readonly IOrderedDither orderedDither; + private readonly IErrorDiffuser errorDiffuser; + private readonly Rgba32[] TestPalette = + { + Rgba32.Red, + Rgba32.Green, + Rgba32.Blue + }; + + public DitherTest() + { + this.orderedDither = KnownDitherers.BayerDither4x4; + this.errorDiffuser = KnownDiffusers.FloydSteinberg; + } + + [Fact] + public void Dither_CorrectProcessor() + { + this.operations.Dither(this.orderedDither); + OrderedDitherPaletteProcessor p = this.Verify>(); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(NamedColors.WebSafePalette, p.Palette); + } + + [Fact] + public void Dither_rect_CorrectProcessor() + { + this.operations.Dither(this.orderedDither, this.rect); + OrderedDitherPaletteProcessor p = this.Verify>(this.rect); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(NamedColors.WebSafePalette, p.Palette); + } + [Fact] + public void Dither_index_CorrectProcessor() + { + this.operations.Dither(this.orderedDither, this.TestPalette); + OrderedDitherPaletteProcessor p = this.Verify>(); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(this.TestPalette, p.Palette); + } + + [Fact] + public void Dither_index_rect_CorrectProcessor() + { + this.operations.Dither(this.orderedDither, this.TestPalette, this.rect); + OrderedDitherPaletteProcessor p = this.Verify>(this.rect); + Assert.Equal(this.orderedDither, p.Dither); + Assert.Equal(this.TestPalette, p.Palette); + } + + + [Fact] + public void Dither_ErrorDiffuser_CorrectProcessor() + { + this.operations.Diffuse(this.errorDiffuser, .4F); + ErrorDiffusionPaletteProcessor p = this.Verify>(); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.4F, p.Threshold); + Assert.Equal(NamedColors.WebSafePalette, p.Palette); + } + + [Fact] + public void Dither_ErrorDiffuser_rect_CorrectProcessor() + { + this.operations.Diffuse(this.errorDiffuser, .3F, this.rect); + ErrorDiffusionPaletteProcessor p = this.Verify>(this.rect); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.3F, p.Threshold); + Assert.Equal(NamedColors.WebSafePalette, p.Palette); + } + + [Fact] + public void Dither_ErrorDiffuser_CorrectProcessorWithColors() + { + this.operations.Diffuse(this.errorDiffuser, .5F, this.TestPalette); + ErrorDiffusionPaletteProcessor p = this.Verify>(); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.5F, p.Threshold); + Assert.Equal(this.TestPalette, p.Palette); + } + + [Fact] + public void Dither_ErrorDiffuser_rect_CorrectProcessorWithColors() + { + this.operations.Diffuse(this.errorDiffuser, .5F, this.TestPalette, this.rect); + ErrorDiffusionPaletteProcessor p = this.Verify>(this.rect); + Assert.Equal(this.errorDiffuser, p.Diffuser); + Assert.Equal(.5F, p.Threshold); + Assert.Equal(this.TestPalette, p.Palette); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs new file mode 100644 index 0000000000..aec201239d --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs @@ -0,0 +1,131 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + +using SixLabors.Primitives; +using Xunit; +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization +{ + public class BinaryDitherTests : FileTestBase + { + public static readonly string[] CommonTestImages = + { + TestImages.Png.CalliphoraPartial, TestImages.Png.Bike + }; + + public static readonly TheoryData Ditherers = new TheoryData + { + { "Bayer8x8", KnownDitherers.BayerDither8x8 }, + { "Bayer4x4", KnownDitherers.BayerDither4x4 }, + { "Ordered3x3", KnownDitherers.OrderedDither3x3 }, + { "Bayer2x2", KnownDitherers.BayerDither2x2 } + }; + + public static readonly TheoryData ErrorDiffusers = new TheoryData + { + { "Atkinson", KnownDiffusers.Atkinson }, + { "Burks", KnownDiffusers.Burks }, + { "FloydSteinberg", KnownDiffusers.FloydSteinberg }, + { "JarvisJudiceNinke", KnownDiffusers.JarvisJudiceNinke }, + { "Sierra2", KnownDiffusers.Sierra2 }, + { "Sierra3", KnownDiffusers.Sierra3 }, + { "SierraLite", KnownDiffusers.SierraLite }, + { "StevensonArce", KnownDiffusers.StevensonArce }, + { "Stucki", KnownDiffusers.Stucki }, + }; + + + private static IOrderedDither DefaultDitherer => KnownDitherers.BayerDither4x4; + + private static IErrorDiffuser DefaultErrorDiffuser => KnownDiffusers.Atkinson; + + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(Ditherers), DefaultPixelType)] + [WithTestPatternImages(nameof(Ditherers), 100, 100, DefaultPixelType)] + public void BinaryDitherFilter_WorksWithAllDitherers(TestImageProvider provider, string name, IOrderedDither ditherer) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.BinaryDither(ditherer)); + image.DebugSave(provider, name); + } + } + + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), DefaultPixelType)] + [WithTestPatternImages(nameof(ErrorDiffusers), 100, 100, DefaultPixelType)] + public void DiffusionFilter_WorksWithAllErrorDiffusers(TestImageProvider provider, string name, IErrorDiffuser diffuser) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.BinaryDiffuse(diffuser, .5F)); + image.DebugSave(provider, name); + } + } + + [Theory] + [WithFile(TestImages.Png.Bike, CommonNonDefaultPixelTypes)] + public void BinaryDitherFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.BinaryDither(DefaultDitherer)); + image.DebugSave(provider); + } + } + + [Theory] + [WithFile(TestImages.Png.Bike, CommonNonDefaultPixelTypes)] + public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.BinaryDiffuse(DefaultErrorDiffuser, 0.5f)); + image.DebugSave(provider); + } + } + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, DefaultPixelType)] + public void ApplyDitherFilterInBox(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (Image image = source.Clone()) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.Mutate(x => x.BinaryDither(DefaultDitherer, bounds)); + image.DebugSave(provider); + + ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); + } + } + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, DefaultPixelType)] + public void ApplyDiffusionFilterInBox(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (Image image = source.Clone()) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.Mutate(x => x.BinaryDiffuse(DefaultErrorDiffuser, .5F, bounds)); + image.DebugSave(provider); + + ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs index 3508d544be..89e9a13b5c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs @@ -11,12 +11,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { public class GaussianBlurTest : FileTestBase { - public static readonly TheoryData GaussianBlurValues - = new TheoryData - { - 3, - 5 - }; + public static readonly TheoryData GaussianBlurValues = new TheoryData { 3, 5 }; [Theory] [WithFileCollection(nameof(DefaultFiles), nameof(GaussianBlurValues), DefaultPixelType)] @@ -36,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution where TPixel : struct, IPixel { using (Image source = provider.GetImage()) - using (var image = source.Clone()) + using (Image image = source.Clone()) { var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs similarity index 78% rename from tests/ImageSharp.Tests/Processing/Processors/Binarization/DitherTests.cs rename to tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs index 9a6d24226b..58c63d48c7 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/DitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Dithering; +using SixLabors.ImageSharp.Dithering; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -11,8 +11,6 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { - using System.Linq; - public class DitherTests : FileTestBase { public static readonly string[] CommonTestImages = @@ -22,26 +20,29 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization public static readonly TheoryData Ditherers = new TheoryData { - { "Ordered", new OrderedDither() }, - { "Bayer", new BayerDither() } + { "Bayer8x8", KnownDitherers.BayerDither8x8 }, + { "Bayer4x4", KnownDitherers.BayerDither4x4 }, + { "Ordered3x3", KnownDitherers.OrderedDither3x3 }, + { "Bayer2x2", KnownDitherers.BayerDither2x2 } }; public static readonly TheoryData ErrorDiffusers = new TheoryData { - { "Atkinson", new AtkinsonDiffuser() }, - { "Burks", new BurksDiffuser() }, - { "FloydSteinberg", new FloydSteinbergDiffuser() }, - { "JarvisJudiceNinke", new JarvisJudiceNinkeDiffuser() }, - { "Sierra2", new Sierra2Diffuser() }, - { "Sierra3", new Sierra3Diffuser() }, - { "SierraLite", new SierraLiteDiffuser() }, - { "Stucki", new StuckiDiffuser() }, + { "Atkinson", KnownDiffusers.Atkinson }, + { "Burks", KnownDiffusers.Burks }, + { "FloydSteinberg", KnownDiffusers.FloydSteinberg }, + { "JarvisJudiceNinke", KnownDiffusers.JarvisJudiceNinke }, + { "Sierra2", KnownDiffusers.Sierra2 }, + { "Sierra3", KnownDiffusers.Sierra3 }, + { "SierraLite", KnownDiffusers.SierraLite }, + { "StevensonArce", KnownDiffusers.StevensonArce }, + { "Stucki", KnownDiffusers.Stucki }, }; - private static IOrderedDither DefaultDitherer => new OrderedDither(); + private static IOrderedDither DefaultDitherer => KnownDitherers.BayerDither4x4; - private static IErrorDiffuser DefaultErrorDiffuser => new AtkinsonDiffuser(); + private static IErrorDiffuser DefaultErrorDiffuser => KnownDiffusers.Atkinson; [Theory] [WithFileCollection(nameof(CommonTestImages), nameof(Ditherers), DefaultPixelType)] @@ -64,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { using (Image image = provider.GetImage()) { - image.Mutate(x => x.Dither(diffuser, .5F)); + image.Mutate(x => x.Diffuse(diffuser, .5F)); image.DebugSave(provider, name); } } @@ -80,7 +81,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization image.DebugSave(provider); } } - + [Theory] [WithFile(TestImages.Png.Bike, CommonNonDefaultPixelTypes)] public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) @@ -88,7 +89,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { using (Image image = provider.GetImage()) { - image.Mutate(x => x.Dither(DefaultErrorDiffuser, 0.5f)); + image.Mutate(x => x.Diffuse(DefaultErrorDiffuser, 0.5f)); image.DebugSave(provider); } } @@ -120,7 +121,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); - image.Mutate(x => x.Dither(DefaultErrorDiffuser, .5F, bounds)); + image.Mutate(x => x.Diffuse(DefaultErrorDiffuser, .5F, bounds)); image.DebugSave(provider); ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs index 608fcf10cf..a9127f61dc 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs @@ -11,12 +11,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects { public class OilPaintTest : FileTestBase { - public static readonly TheoryData OilPaintValues - = new TheoryData - { - { 15, 10 }, - { 6, 5 } - }; + public static readonly TheoryData OilPaintValues = new TheoryData + { + { 15, 10 }, { 6, 5 } + }; [Theory] [WithFileCollection(nameof(DefaultFiles), nameof(OilPaintValues), DefaultPixelType)] diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs index d4de4c3d2e..9ca3994986 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms using (Image image = provider.GetImage()) { image.Mutate(x => x.Flip(flipType)); - image.DebugSave(provider, flipType, Extensions.Bmp); + image.DebugSave(provider, flipType); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs index 98dbbadaba..2e1b43c388 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs @@ -6,7 +6,8 @@ using System.IO; using System.Text; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors; -using Xunit; +using SixLabors.Primitives; + using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms @@ -38,9 +39,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms // [Fact] public void PrintWeightsData() { - var proc = new ResizeProcessor(KnownResamplers.Bicubic, 200, 200); + var size = new Size(500, 500); + var proc = new ResizeProcessor(KnownResamplers.Bicubic, 200, 200, size); - WeightsBuffer weights = proc.PrecomputeWeights(200, 500); + WeightsBuffer weights = proc.PrecomputeWeights(Configuration.Default.MemoryManager, proc.Width, size.Width); var bld = new StringBuilder(); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs index 58fc7fa802..a9c2922d48 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs @@ -1,20 +1,30 @@ // 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; + using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { + + public class PadTest : BaseImageOperationsExtensionTest { -#pragma warning disable xUnit1004 // Test methods should not be skipped - [Fact(Skip = "Skip this is a helper around resize, skip until resize can be refactord")] -#pragma warning restore xUnit1004 // Test methods should not be skipped - public void Pad_width_height_ResizeProcessorWithCorrectOPtionsSet() + [Fact] + public void PadWidthHeightResizeProcessorWithCorrectOptionsSet() { - throw new NotImplementedException("Write test here"); + int width = 500; + int height = 565; + IResampler sampler = KnownResamplers.NearestNeighbor; + + this.operations.Pad(width, height); + ResizeProcessor resizeProcessor = this.Verify>(); + + Assert.Equal(width, resizeProcessor.Width); + Assert.Equal(height, resizeProcessor.Height); + Assert.Equal(sampler, resizeProcessor.Sampler); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs index d2f5cb2c3d..b51d342cf1 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs @@ -1,23 +1,91 @@ // 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.Primitives; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Transforms { + using SixLabors.ImageSharp.Processing.Processors; + public class ResizeTests : BaseImageOperationsExtensionTest { -#pragma warning disable xUnit1004 // Test methods should not be skipped - [Fact(Skip = "Skip resize tests as they need refactoring to be simpler and just pass data into the resize processor.")] -#pragma warning restore xUnit1004 // Test methods should not be skipped - public void TestMissing() + [Fact] + public void ResizeWidthAndHeight() + { + int width = 50; + int height = 100; + this.operations.Resize(width, height); + ResizeProcessor resizeProcessor = this.Verify>(); + + Assert.Equal(width, resizeProcessor.Width); + Assert.Equal(height, resizeProcessor.Height); + } + + [Fact] + public void ResizeWidthAndHeightAndSampler() + { + int width = 50; + int height = 100; + IResampler sampler = KnownResamplers.Lanczos3; + this.operations.Resize(width, height, sampler); + ResizeProcessor resizeProcessor = this.Verify>(); + + Assert.Equal(width, resizeProcessor.Width); + Assert.Equal(height, resizeProcessor.Height); + Assert.Equal(sampler, resizeProcessor.Sampler); + } + + [Fact] + public void ResizeWidthAndHeightAndSamplerAndCompand() { - // - throw new NotImplementedException("Write test here"); + int width = 50; + int height = 100; + IResampler sampler = KnownResamplers.Lanczos3; + bool compand = true; + + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + this.operations.Resize(width, height, sampler, compand); + ResizeProcessor resizeProcessor = this.Verify>(); + + Assert.Equal(width, resizeProcessor.Width); + Assert.Equal(height, resizeProcessor.Height); + Assert.Equal(sampler, resizeProcessor.Sampler); + Assert.Equal(compand, resizeProcessor.Compand); + } + + [Fact] + public void ResizeWithOptions() + { + int width = 50; + int height = 100; + IResampler sampler = KnownResamplers.Lanczos3; + bool compand = true; + ResizeMode mode = ResizeMode.Stretch; + + var resizeOptions = new ResizeOptions + { + Size = new Size(width, height), + Sampler = sampler, + Compand = compand, + Mode = mode + }; + + this.operations.Resize(resizeOptions); + ResizeProcessor resizeProcessor = this.Verify>(); + + Assert.Equal(width, resizeProcessor.Width); + Assert.Equal(height, resizeProcessor.Height); + Assert.Equal(sampler, resizeProcessor.Sampler); + Assert.Equal(compand, resizeProcessor.Compand); + + // Ensure options are not altered. + Assert.Equal(width, resizeOptions.Size.Width); + Assert.Equal(height, resizeOptions.Size.Height); + Assert.Equal(sampler, resizeOptions.Sampler); + Assert.Equal(compand, resizeOptions.Compand); + Assert.Equal(mode, resizeOptions.Mode); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index b5a8d1265c..18fd29237c 100644 --- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -73,7 +73,7 @@ { Assert.True(image[0, 0].Equals(default(TPixel))); - IQuantizer quantizer = new WuQuantizer { Dither = dither }; + IQuantizer quantizer = new WuQuantizer() { Dither = dither }; foreach (ImageFrame frame in image.Frames) { diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 078b708dfd..445ace9812 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -116,9 +116,9 @@ namespace SixLabors.ImageSharp.Tests public void Configure(Configuration host) { - host.AddImageFormatDetector(new TestHeader(this)); - host.SetEncoder(this, new TestEncoder(this)); - host.SetDecoder(this, new TestDecoder(this)); + host.ImageFormatsManager.AddImageFormatDetector(new TestHeader(this)); + host.ImageFormatsManager.SetEncoder(this, new TestEncoder(this)); + host.ImageFormatsManager.SetDecoder(this, new TestDecoder(this)); } public struct DecodeOperation diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 864c963327..db469f87e1 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -85,6 +85,7 @@ namespace SixLabors.ImageSharp.Tests public static class Bad { public const string BadEOF = "Jpg/progressive/BadEofProgressive.jpg"; + public const string ExifUndefType = "Jpg/progressive/ExifUndefType.jpg"; } public static readonly string[] All = { Fb, Progress, Festzug }; @@ -95,7 +96,7 @@ namespace SixLabors.ImageSharp.Tests public static class Bad { public const string BadEOF = "Jpg/baseline/badeof.jpg"; - public const string ExifUndefType = "Jpg/baseline/ExifUndefType.jpg"; + public const string BadRST = "Jpg/baseline/badrst.jpg"; } public const string Cmyk = "Jpg/baseline/cmyk.jpg"; @@ -113,6 +114,7 @@ namespace SixLabors.ImageSharp.Tests public const string Jpeg444 = "Jpg/baseline/jpeg444.jpg"; public const string Jpeg420Small = "Jpg/baseline/jpeg420small.jpg"; public const string Testorig420 = "Jpg/baseline/testorig.jpg"; + public const string MultiScanBaselineCMYK = "Jpg/baseline/MultiScanBaselineCMYK.jpg"; public static readonly string[] All = { diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs index 70d4df273e..24363173ae 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs @@ -4,10 +4,18 @@ 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 : IEqualityComparer, IEqualityComparer + internal struct ApproximateFloatComparer : + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer, + IEqualityComparer { private readonly float Eps; @@ -37,5 +45,52 @@ namespace SixLabors.ImageSharp.Tests { throw new InvalidOperationException(); } + + 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(IRgbWorkingSpace x, IRgbWorkingSpace y) + { + if (x is IRgbWorkingSpace g1 && y is IRgbWorkingSpace 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(IRgbWorkingSpace obj) + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs index 0b48170879..7fc9e58d4e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs @@ -86,9 +86,10 @@ namespace SixLabors.ImageSharp.Tests NamedColors.HotPink, NamedColors.Blue }; - int p = 0; + for (int y = top; y < bottom; y++) { + int p = 0; for (int x = left; x < right; x++) { if (x % stride == 0) diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index e7dfe54881..cde8ec9e47 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -157,16 +157,77 @@ namespace SixLabors.ImageSharp.Tests return path; } + public IEnumerable GetTestOutputFileNamesMultiFrame( + int frameCount, + string extension = null, + object testOutputDetails = null, + bool appendPixelTypeToFileName = true) + { + string baseDir = this.GetTestOutputFileName("", testOutputDetails, appendPixelTypeToFileName); + + if (!Directory.Exists(baseDir)) + { + Directory.CreateDirectory(baseDir); + } + + for (int i = 0; i < frameCount; i++) + { + string filePath = $"{baseDir}/{i:D2}.{extension}"; + yield return filePath; + } + } + + public string[] SaveTestOutputFileMultiFrame( + Image image, + string extension = "png", + IImageEncoder encoder = null, + object testOutputDetails = null, + bool appendPixelTypeToFileName = true) + where TPixel : struct, IPixel + { + encoder = encoder ?? TestEnvironment.GetReferenceEncoder($"foo.{extension}"); + + string[] files = this.GetTestOutputFileNamesMultiFrame( + image.Frames.Count, + extension, + testOutputDetails, + appendPixelTypeToFileName).ToArray(); + + for (int i = 0; i < image.Frames.Count; i++) + { + using (Image frameImage = image.Frames.CloneFrame(i)) + { + string filePath = files[i]; + using (FileStream stream = File.OpenWrite(filePath)) + { + frameImage.Save(stream, encoder); + } + } + } + + return files; + } + internal string GetReferenceOutputFileName( string extension, - object settings, + object testOutputDetails, bool appendPixelTypeToFileName) { return TestEnvironment.GetReferenceOutputFileName( - this.GetTestOutputFileName(extension, settings, appendPixelTypeToFileName) + this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName) ); } + public string[] GetReferenceOutputFileNamesMultiFrame( + int frameCount, + string extension, + object testOutputDetails, + bool appendPixelTypeToFileName = true) + { + return this.GetTestOutputFileNamesMultiFrame(frameCount, extension, testOutputDetails) + .Select(TestEnvironment.GetReferenceOutputFileName).ToArray(); + } + internal void Init(string typeName, string methodName, string outputSubfolderName) { this.TestGroupName = typeName; diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs index a201b39bd2..d1270dcfd7 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs @@ -19,13 +19,14 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs int length = source.Length; Guard.MustBeSizedAtLeast(dest, length, nameof(dest)); - using (var rgbaBuffer = new Buffer(length)) + using (IBuffer rgbaBuffer = Configuration.Default.MemoryManager.Allocate(length)) { - PixelOperations.Instance.ToRgba32(source, rgbaBuffer, length); + Span rgbaSpan = rgbaBuffer.Span; + PixelOperations.Instance.ToRgba32(source, rgbaSpan, length); for (int i = 0; i < length; i++) { - ref Rgba32 s = ref rgbaBuffer[i]; + ref Rgba32 s = ref rgbaSpan[i]; ref Argb32 d = ref dest[i]; d.PackFromRgba32(s); @@ -39,13 +40,14 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs int length = source.Length; Guard.MustBeSizedAtLeast(dest, length, nameof(dest)); - using (var rgbaBuffer = new Buffer(length)) + using (IBuffer rgbaBuffer = Configuration.Default.MemoryManager.Allocate(length)) { - PixelOperations.Instance.ToRgba32(source, rgbaBuffer, length); + Span rgbaSpan = rgbaBuffer.Span; + PixelOperations.Instance.ToRgba32(source, rgbaSpan, length); for (int i = 0; i < length; i++) { - ref Rgba32 s = ref rgbaBuffer[i]; + ref Rgba32 s = ref rgbaSpan[i]; ref TPixel d = ref dest[i]; d.PackFromRgba32(s); @@ -59,13 +61,14 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs int length = source.Length; Guard.MustBeSizedAtLeast(dest, length, nameof(dest)); - using (var rgbaBuffer = new Buffer(length)) + using (IBuffer rgbBuffer = Configuration.Default.MemoryManager.Allocate(length)) { - PixelOperations.Instance.ToRgb24(source, rgbaBuffer, length); + Span rgbSpan = rgbBuffer.Span; + PixelOperations.Instance.ToRgb24(source, rgbSpan, length); for (int i = 0; i < length; i++) { - ref Rgb24 s = ref rgbaBuffer[i]; + ref Rgb24 s = ref rgbSpan[i]; ref TPixel d = ref dest[i]; var rgba = default(Rgba32); s.ToRgba32(ref rgba); @@ -96,18 +99,20 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs var image = new Image(w, h); - using (var workBuffer = new Buffer(w)) + using (IBuffer workBuffer = Configuration.Default.MemoryManager.Allocate(w)) { - var destPtr = (Argb32*)workBuffer.Pin(); - for (int y = 0; y < h; y++) + fixed (Argb32* destPtr = &workBuffer.DangerousGetPinnableReference()) { - Span row = image.Frames.RootFrame.GetPixelRowSpan(y); + for (int y = 0; y < h; y++) + { + Span row = image.Frames.RootFrame.GetPixelRowSpan(y); - byte* sourcePtr = sourcePtrBase + data.Stride * y; + byte* sourcePtr = sourcePtrBase + data.Stride * y; - Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); + Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); - FromArgb32(workBuffer, row); + FromArgb32(workBuffer.Span, row); + } } } @@ -138,18 +143,20 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs var image = new Image(w, h); - using (var workBuffer = new Buffer(w)) + using (IBuffer workBuffer = Configuration.Default.MemoryManager.Allocate(w)) { - var destPtr = (Rgb24*)workBuffer.Pin(); - for (int y = 0; y < h; y++) + fixed (Rgb24* destPtr = &workBuffer.DangerousGetPinnableReference()) { - Span row = image.Frames.RootFrame.GetPixelRowSpan(y); + for (int y = 0; y < h; y++) + { + Span row = image.Frames.RootFrame.GetPixelRowSpan(y); - byte* sourcePtr = sourcePtrBase + data.Stride * y; + byte* sourcePtr = sourcePtrBase + data.Stride * y; - Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); + Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); - FromRgb24(workBuffer, row); + FromRgb24(workBuffer.Span, row); + } } } @@ -170,17 +177,19 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs long destRowByteCount = data.Stride; long sourceRowByteCount = w * sizeof(Argb32); - using (var workBuffer = new Buffer(w)) + using (IBuffer workBuffer = image.GetConfiguration().MemoryManager.Allocate(w)) { - var sourcePtr = (Argb32*)workBuffer.Pin(); - - for (int y = 0; y < h; y++) + fixed (Argb32* sourcePtr = &workBuffer.DangerousGetPinnableReference()) { - Span row = image.Frames.RootFrame.GetPixelRowSpan(y); - ToArgb32(row, workBuffer); - byte* destPtr = destPtrBase + data.Stride * y; - Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); + for (int y = 0; y < h; y++) + { + Span row = image.Frames.RootFrame.GetPixelRowSpan(y); + ToArgb32(row, workBuffer.Span); + byte* destPtr = destPtrBase + data.Stride * y; + + Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); + } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 089fed6b0a..fa9497a8f8 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -21,20 +21,20 @@ namespace SixLabors.ImageSharp.Tests internal static IImageDecoder GetReferenceDecoder(string filePath) { IImageFormat format = GetImageFormat(filePath); - return Configuration.FindDecoder(format); + return Configuration.ImageFormatsManager.FindDecoder(format); } internal static IImageEncoder GetReferenceEncoder(string filePath) { IImageFormat format = GetImageFormat(filePath); - return Configuration.FindEncoder(format); + return Configuration.ImageFormatsManager.FindEncoder(format); } internal static IImageFormat GetImageFormat(string filePath) { string extension = Path.GetExtension(filePath).ToLower(); if (extension[0] == '.') extension = extension.Substring(1); - IImageFormat format = Configuration.FindFormatByFileExtension(extension); + IImageFormat format = Configuration.ImageFormatsManager.FindFormatByFileExtension(extension); return format; } @@ -45,9 +45,9 @@ namespace SixLabors.ImageSharp.Tests IImageEncoder encoder, IImageFormatDetector detector) { - cfg.SetDecoder(imageFormat, decoder); - cfg.SetEncoder(imageFormat, encoder); - cfg.AddImageFormatDetector(detector); + cfg.ImageFormatsManager.SetDecoder(imageFormat, decoder); + cfg.ImageFormatsManager.SetEncoder(imageFormat, encoder); + cfg.ImageFormatsManager.AddImageFormatDetector(detector); } private static Configuration CreateDefaultConfiguration() diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 2b3cb1bcc3..f03f9db09a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -16,11 +16,45 @@ namespace SixLabors.ImageSharp.Tests using System.Numerics; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; + using SixLabors.ImageSharp.MetaData; using Xunit; public static class TestImageExtensions { + /// + /// TODO: This should be a common processing method! The image.Opacity(val) multiplies the alpha channel! + /// + /// + /// + public static void MakeOpaque(this IImageProcessingContext ctx) + where TPixel : struct, IPixel + { + MemoryManager memoryManager = ctx.MemoryManager; + ctx.Apply( + img => + { + using (Buffer2D temp = memoryManager.Allocate2D(img.Width, img.Height)) + { + Span tempSpan = temp.Span; + foreach (ImageFrame frame in img.Frames) + { + Span pixelSpan = frame.GetPixelSpan(); + + PixelOperations.Instance.ToVector4(pixelSpan, tempSpan, pixelSpan.Length); + + for (int i = 0; i < tempSpan.Length; i++) + { + ref Vector4 v = ref tempSpan[i]; + v.W = 1.0f; + } + + PixelOperations.Instance.PackFromVector4(tempSpan, pixelSpan, pixelSpan.Length); + } + } + }); + } + /// /// Saves the image only when not running in the CI server. /// @@ -52,6 +86,28 @@ namespace SixLabors.ImageSharp.Tests return image; } + public static Image DebugSaveMultiFrame( + this Image image, + ITestImageProvider provider, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = true) + where TPixel : struct, IPixel + { + if (TestEnvironment.RunsOnCI) + { + return image; + } + + // We are running locally then we want to save it out + provider.Utility.SaveTestOutputFileMultiFrame( + image, + extension, + testOutputDetails: testOutputDetails, + appendPixelTypeToFileName: appendPixelTypeToFileName); + return image; + } + /// /// Compares the image against the expected Reference output, throws an exception if the images are not similar enough. /// The output file should be named identically to the output produced by . @@ -118,6 +174,55 @@ namespace SixLabors.ImageSharp.Tests return image; } + public static Image CompareFirstFrameToReferenceOutput( + this Image image, + ITestImageProvider provider, + ImageComparer comparer, + object testOutputDetails = null, + string extension = "png", + bool grayscale = false, + bool appendPixelTypeToFileName = true) + where TPixel : struct, IPixel + { + using (var firstFrameOnlyImage = new Image(image.Width, image.Height)) + using (Image referenceImage = GetReferenceOutputImage( + provider, + testOutputDetails, + extension, + appendPixelTypeToFileName)) + { + firstFrameOnlyImage.Frames.AddFrame(image.Frames.RootFrame); + firstFrameOnlyImage.Frames.RemoveFrame(0); + + comparer.VerifySimilarity(referenceImage, firstFrameOnlyImage); + } + + return image; + } + + public static Image CompareToReferenceOutputMultiFrame( + this Image image, + ITestImageProvider provider, + ImageComparer comparer, + object testOutputDetails = null, + string extension = "png", + bool grayscale = false, + bool appendPixelTypeToFileName = true) + where TPixel : struct, IPixel + { + using (Image referenceImage = GetReferenceOutputImageMultiFrame( + provider, + image.Frames.Count, + testOutputDetails, + extension, + appendPixelTypeToFileName)) + { + comparer.VerifySimilarity(referenceImage, image); + } + + return image; + } + public static Image GetReferenceOutputImage(this ITestImageProvider provider, object testOutputDetails = null, string extension = "png", @@ -136,6 +241,49 @@ namespace SixLabors.ImageSharp.Tests return Image.Load(referenceOutputFile, decoder); } + public static Image GetReferenceOutputImageMultiFrame(this ITestImageProvider provider, + int frameCount, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = true) + where TPixel : struct, IPixel + { + string[] frameFiles = provider.Utility.GetReferenceOutputFileNamesMultiFrame( + frameCount, + extension, + testOutputDetails, + appendPixelTypeToFileName); + + var temporaryFrameImages = new List>(); + + IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(frameFiles[0]); + + foreach (string path in frameFiles) + { + if (!File.Exists(path)) + { + throw new Exception("Reference output file missing: " + path); + } + + var tempImage = Image.Load(path, decoder); + temporaryFrameImages.Add(tempImage); + } + + Image firstTemp = temporaryFrameImages[0]; + + var result = new Image(firstTemp.Width, firstTemp.Height); + + foreach (Image fi in temporaryFrameImages) + { + result.Frames.AddFrame(fi.Frames.RootFrame); + fi.Dispose(); + } + + // remove the initial empty frame: + result.Frames.RemoveFrame(0); + return result; + } + public static IEnumerable GetReferenceOutputSimilarityReports( this Image image, ITestImageProvider provider, @@ -212,7 +360,7 @@ namespace SixLabors.ImageSharp.Tests IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(path); IImageFormat format = TestEnvironment.GetImageFormat(path); - IImageDecoder defaultDecoder = Configuration.Default.FindDecoder(format); + IImageDecoder defaultDecoder = Configuration.Default.ImageFormatsManager.FindDecoder(format); //if (referenceDecoder.GetType() == defaultDecoder.GetType()) //{ @@ -227,20 +375,48 @@ namespace SixLabors.ImageSharp.Tests return image; } + /// + /// Loads the expected image with a reference decoder + compares it to . + /// Also performs a debug save using . + /// + internal static void VerifyEncoder(this Image image, + ITestImageProvider provider, + string extension, + object testOutputDetails, + IImageEncoder encoder, + ImageComparer customComparer = null, + bool appendPixelTypeToFileName = true, + string referenceImageExtension = null + ) + where TPixel : struct, IPixel + { + string actualOutputFile = provider.Utility.SaveTestOutputFile(image, extension, encoder, testOutputDetails, appendPixelTypeToFileName); + IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); + + using (var actualImage = Image.Load(actualOutputFile, referenceDecoder)) + { + ImageComparer comparer = customComparer ?? ImageComparer.Exact; + comparer.VerifySimilarity(actualImage, image); + } + } + internal static Image ToGrayscaleImage(this Buffer2D buffer, float scale) { var image = new Image(buffer.Width, buffer.Height); Span pixels = image.Frames.RootFrame.GetPixelSpan(); - for (int i = 0; i < buffer.Length; i++) + Span bufferSpan = buffer.Span; + + for (int i = 0; i < bufferSpan.Length; i++) { - float value = buffer[i] * scale; + float value = bufferSpan[i] * scale; var v = new Vector4(value, value, value, 1f); pixels[i].PackFromVector4(v); } return image; } + } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceCodecTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceCodecTests.cs index dde34fcc43..c67e4e06bf 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceCodecTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceCodecTests.cs @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Tests { if (pngColorType != PngColorType.RgbWithAlpha) { - sourceImage.Mutate(c => c.Opacity(1)); + sourceImage.Mutate(c => c.MakeOpaque()); } var encoder = new PngEncoder() { PngColorType = pngColorType }; @@ -93,12 +93,12 @@ namespace SixLabors.ImageSharp.Tests using (Image original = provider.GetImage()) { - original.Mutate(c => c.Opacity(1)); + original.Mutate(c => c.MakeOpaque()); using (var sdBitmap = new System.Drawing.Bitmap(path)) { using (Image resaved = SystemDrawingBridge.FromFromRgb24SystemDrawingBitmap(sdBitmap)) { - resaved.Mutate(c => c.Opacity(1)); + resaved.Mutate(c => c.MakeOpaque()); ImageComparer comparer = ImageComparer.Exact; comparer.VerifySimilarity(original, resaved); } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index f0adeb7534..2f306e9494 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -239,8 +239,28 @@ namespace SixLabors.ImageSharp.Tests where TPixel : struct, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); - Image image = provider.GetImage(); - provider.Utility.SaveTestOutputFile(image, "png"); + using (Image image = provider.GetImage()) + { + provider.Utility.SaveTestOutputFile(image, "png"); + } + } + + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void SaveTestOutputFileMultiFrame(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + string[] files = provider.Utility.SaveTestOutputFileMultiFrame(image); + + Assert.True(files.Length > 2); + foreach (string path in files) + { + this.Output.WriteLine(path); + Assert.True(File.Exists(path)); + } + } } [Theory] diff --git a/tests/Images/External b/tests/Images/External index 376605e05b..653f0c7e3c 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 376605e05bb704d425b2d17bf5b310f5376da22e +Subproject commit 653f0c7e3c84657f68dd46e5a380186b3696b956 diff --git a/tests/Images/Input/Jpg/baseline/MultiScanBaselineCMYK.jpg b/tests/Images/Input/Jpg/baseline/MultiScanBaselineCMYK.jpg new file mode 100644 index 0000000000..a158e0ba2c --- /dev/null +++ b/tests/Images/Input/Jpg/baseline/MultiScanBaselineCMYK.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96f29bee2175f34b9637d684f8336bc3e5d2bb2711cef352bc9def6ed4424d04 +size 47443 diff --git a/tests/Images/Input/Jpg/baseline/badrst.jpg b/tests/Images/Input/Jpg/baseline/badrst.jpg new file mode 100644 index 0000000000..61805b42d3 --- /dev/null +++ b/tests/Images/Input/Jpg/baseline/badrst.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af18f0bf30231d2c4c0e6b80d4636237c2851f67763788de12931fd1960c4ff3 +size 74497 diff --git a/tests/Images/Input/Jpg/baseline/ExifUndefType.jpg b/tests/Images/Input/Jpg/progressive/ExifUndefType.jpg similarity index 100% rename from tests/Images/Input/Jpg/baseline/ExifUndefType.jpg rename to tests/Images/Input/Jpg/progressive/ExifUndefType.jpg