diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index c0064d1877..b1767101d4 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -10,6 +10,7 @@ using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.Memory; namespace SixLabors.ImageSharp @@ -102,6 +103,15 @@ namespace SixLabors.ImageSharp /// internal IFileSystem FileSystem { get; set; } = new LocalFileSystem(); + /// + /// Gets or sets the working buffer size hint for image processors. + /// The default value is 1MB. + /// + /// + /// Currently only used by . + /// + internal int WorkingBufferSizeHintInBytes { get; set; } = 1 * 1024 * 1024; + /// /// Gets or sets the image operations provider factory. /// @@ -130,7 +140,8 @@ namespace SixLabors.ImageSharp MemoryAllocator = this.MemoryAllocator, ImageOperationsProvider = this.ImageOperationsProvider, ReadOrigin = this.ReadOrigin, - FileSystem = this.FileSystem + FileSystem = this.FileSystem, + WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInBytes, }; } diff --git a/src/ImageSharp/Processing/ResizeHelper.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs similarity index 88% rename from src/ImageSharp/Processing/ResizeHelper.cs rename to src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs index 3ae632162f..0bc8cfda02 100644 --- a/src/ImageSharp/Processing/ResizeHelper.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs @@ -1,11 +1,13 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Linq; +using System.Numerics; + using SixLabors.Primitives; -namespace SixLabors.ImageSharp.Processing +namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// /// Provides methods to help calculate the target rectangle when resizing using the @@ -13,6 +15,16 @@ namespace SixLabors.ImageSharp.Processing /// internal static class ResizeHelper { + public static unsafe int CalculateResizeWorkerWindowCount( + int windowDiameter, + int width, + int sizeLimitHintInBytes) + { + int sizeLimitHint = sizeLimitHintInBytes / sizeof(Vector4); + int sizeOfOneWindow = windowDiameter * width; + return Math.Max(2, sizeLimitHint / sizeOfOneWindow); + } + /// /// Calculates the target location and bounds to perform the resize operation against. /// @@ -21,9 +33,13 @@ namespace SixLabors.ImageSharp.Processing /// The target width /// The target height /// - /// The . + /// The tuple representing the location and the bounds /// - public static (Size, Rectangle) CalculateTargetLocationAndBounds(Size sourceSize, ResizeOptions options, int width, int height) + public static (Size, Rectangle) CalculateTargetLocationAndBounds( + Size sourceSize, + ResizeOptions options, + int width, + int height) { switch (options.Mode) { @@ -44,7 +60,90 @@ namespace SixLabors.ImageSharp.Processing } } - private static (Size, Rectangle) CalculateCropRectangle(Size source, ResizeOptions options, int width, int height) + private static (Size, Rectangle) CalculateBoxPadRectangle( + Size source, + ResizeOptions options, + int width, + int height) + { + if (width <= 0 || height <= 0) + { + return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height)); + } + + int sourceWidth = source.Width; + int sourceHeight = source.Height; + + // Fractional variants for preserving aspect ratio. + float percentHeight = MathF.Abs(height / (float)sourceHeight); + float percentWidth = MathF.Abs(width / (float)sourceWidth); + + int boxPadHeight = height > 0 ? height : (int)MathF.Round(sourceHeight * percentWidth); + int boxPadWidth = width > 0 ? width : (int)MathF.Round(sourceWidth * percentHeight); + + // Only calculate if upscaling. + if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight) + { + int destinationX; + int destinationY; + int destinationWidth = sourceWidth; + int destinationHeight = sourceHeight; + width = boxPadWidth; + height = boxPadHeight; + + switch (options.Position) + { + case AnchorPositionMode.Left: + destinationY = (height - sourceHeight) / 2; + destinationX = 0; + break; + case AnchorPositionMode.Right: + destinationY = (height - sourceHeight) / 2; + destinationX = width - sourceWidth; + break; + case AnchorPositionMode.TopRight: + destinationY = 0; + destinationX = width - sourceWidth; + break; + case AnchorPositionMode.Top: + destinationY = 0; + destinationX = (width - sourceWidth) / 2; + break; + case AnchorPositionMode.TopLeft: + destinationY = 0; + destinationX = 0; + break; + case AnchorPositionMode.BottomRight: + destinationY = height - sourceHeight; + destinationX = width - sourceWidth; + break; + case AnchorPositionMode.Bottom: + destinationY = height - sourceHeight; + destinationX = (width - sourceWidth) / 2; + break; + case AnchorPositionMode.BottomLeft: + destinationY = height - sourceHeight; + destinationX = 0; + break; + default: + destinationY = (height - sourceHeight) / 2; + destinationX = (width - sourceWidth) / 2; + break; + } + + 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, width, height); + } + + private static (Size, Rectangle) CalculateCropRectangle( + Size source, + ResizeOptions options, + int width, + int height) { if (width <= 0 || height <= 0) { @@ -147,152 +246,15 @@ namespace SixLabors.ImageSharp.Processing destinationWidth = (int)MathF.Ceiling(sourceWidth * percentHeight); } - return (new Size(width, height), new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); - } - - private static (Size, Rectangle) CalculatePadRectangle(Size source, ResizeOptions options, int width, int height) - { - if (width <= 0 || height <= 0) - { - return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height)); - } - - float ratio; - int sourceWidth = source.Width; - int sourceHeight = source.Height; - - int destinationX = 0; - int destinationY = 0; - int destinationWidth = width; - int destinationHeight = height; - - // Fractional variants for preserving aspect ratio. - float percentHeight = MathF.Abs(height / (float)sourceHeight); - float percentWidth = MathF.Abs(width / (float)sourceWidth); - - if (percentHeight < percentWidth) - { - ratio = percentHeight; - destinationWidth = (int)MathF.Round(sourceWidth * percentHeight); - - switch (options.Position) - { - case AnchorPositionMode.Left: - case AnchorPositionMode.TopLeft: - case AnchorPositionMode.BottomLeft: - destinationX = 0; - break; - case AnchorPositionMode.Right: - case AnchorPositionMode.TopRight: - case AnchorPositionMode.BottomRight: - destinationX = (int)MathF.Round(width - (sourceWidth * ratio)); - break; - default: - destinationX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F); - break; - } - } - else - { - ratio = percentWidth; - destinationHeight = (int)MathF.Round(sourceHeight * percentWidth); - - switch (options.Position) - { - case AnchorPositionMode.Top: - case AnchorPositionMode.TopLeft: - case AnchorPositionMode.TopRight: - destinationY = 0; - break; - case AnchorPositionMode.Bottom: - case AnchorPositionMode.BottomLeft: - case AnchorPositionMode.BottomRight: - destinationY = (int)MathF.Round(height - (sourceHeight * ratio)); - break; - default: - destinationY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F); - break; - } - } - - return (new Size(width, height), new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); - } - - private static (Size, Rectangle) CalculateBoxPadRectangle(Size source, ResizeOptions options, int width, int height) - { - if (width <= 0 || height <= 0) - { - return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height)); - } - - int sourceWidth = source.Width; - int sourceHeight = source.Height; - - // Fractional variants for preserving aspect ratio. - float percentHeight = MathF.Abs(height / (float)sourceHeight); - float percentWidth = MathF.Abs(width / (float)sourceWidth); - - int boxPadHeight = height > 0 ? height : (int)MathF.Round(sourceHeight * percentWidth); - int boxPadWidth = width > 0 ? width : (int)MathF.Round(sourceWidth * percentHeight); - - // Only calculate if upscaling. - if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight) - { - int destinationX; - int destinationY; - int destinationWidth = sourceWidth; - int destinationHeight = sourceHeight; - width = boxPadWidth; - height = boxPadHeight; - - switch (options.Position) - { - case AnchorPositionMode.Left: - destinationY = (height - sourceHeight) / 2; - destinationX = 0; - break; - case AnchorPositionMode.Right: - destinationY = (height - sourceHeight) / 2; - destinationX = width - sourceWidth; - break; - case AnchorPositionMode.TopRight: - destinationY = 0; - destinationX = width - sourceWidth; - break; - case AnchorPositionMode.Top: - destinationY = 0; - destinationX = (width - sourceWidth) / 2; - break; - case AnchorPositionMode.TopLeft: - destinationY = 0; - destinationX = 0; - break; - case AnchorPositionMode.BottomRight: - destinationY = height - sourceHeight; - destinationX = width - sourceWidth; - break; - case AnchorPositionMode.Bottom: - destinationY = height - sourceHeight; - destinationX = (width - sourceWidth) / 2; - break; - case AnchorPositionMode.BottomLeft: - destinationY = height - sourceHeight; - destinationX = 0; - break; - default: - destinationY = (height - sourceHeight) / 2; - destinationX = (width - sourceWidth) / 2; - break; - } - - 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, width, height); + return (new Size(width, height), + new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); } - private static (Size, Rectangle) CalculateMaxRectangle(Size source, ResizeOptions options, int width, int height) + private static (Size, Rectangle) CalculateMaxRectangle( + Size source, + ResizeOptions options, + int width, + int height) { int destinationWidth = width; int destinationHeight = height; @@ -320,7 +282,11 @@ namespace SixLabors.ImageSharp.Processing return (new Size(width, height), new Rectangle(0, 0, destinationWidth, destinationHeight)); } - private static (Size, Rectangle) CalculateMinRectangle(Size source, ResizeOptions options, int width, int height) + private static (Size, Rectangle) CalculateMinRectangle( + Size source, + ResizeOptions options, + int width, + int height) { int sourceWidth = source.Width; int sourceHeight = source.Height; @@ -372,5 +338,78 @@ namespace SixLabors.ImageSharp.Processing // Replace the size to match the rectangle. return (new Size(width, height), new Rectangle(0, 0, destinationWidth, destinationHeight)); } + + private static (Size, Rectangle) CalculatePadRectangle( + Size source, + ResizeOptions options, + int width, + int height) + { + if (width <= 0 || height <= 0) + { + return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height)); + } + + float ratio; + int sourceWidth = source.Width; + int sourceHeight = source.Height; + + int destinationX = 0; + int destinationY = 0; + int destinationWidth = width; + int destinationHeight = height; + + // Fractional variants for preserving aspect ratio. + float percentHeight = MathF.Abs(height / (float)sourceHeight); + float percentWidth = MathF.Abs(width / (float)sourceWidth); + + if (percentHeight < percentWidth) + { + ratio = percentHeight; + destinationWidth = (int)MathF.Round(sourceWidth * percentHeight); + + switch (options.Position) + { + case AnchorPositionMode.Left: + case AnchorPositionMode.TopLeft: + case AnchorPositionMode.BottomLeft: + destinationX = 0; + break; + case AnchorPositionMode.Right: + case AnchorPositionMode.TopRight: + case AnchorPositionMode.BottomRight: + destinationX = (int)MathF.Round(width - (sourceWidth * ratio)); + break; + default: + destinationX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F); + break; + } + } + else + { + ratio = percentWidth; + destinationHeight = (int)MathF.Round(sourceHeight * percentWidth); + + switch (options.Position) + { + case AnchorPositionMode.Top: + case AnchorPositionMode.TopLeft: + case AnchorPositionMode.TopRight: + destinationY = 0; + break; + case AnchorPositionMode.Bottom: + case AnchorPositionMode.BottomLeft: + case AnchorPositionMode.BottomRight: + destinationY = (int)MathF.Round(height - (sourceHeight * ratio)); + break; + default: + destinationY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F); + break; + } + } + + return (new Size(width, height), + new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 208387e6d1..6f68d04288 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -39,13 +39,13 @@ namespace SixLabors.ImageSharp.Tests /// Test that the default configuration is not null. /// [Fact] - public void TestDefaultConfigurationIsNotNull() => Assert.True(Configuration.Default != null); + public void TestDefaultConfigurationIsNotNull() => Assert.True(this.DefaultConfiguration != null); /// /// Test that the default configuration read origin options is set to begin. /// [Fact] - public void TestDefaultConfigurationReadOriginIsCurrent() => Assert.True(Configuration.Default.ReadOrigin == ReadOrigin.Current); + public void TestDefaultConfigurationReadOriginIsCurrent() => Assert.True(this.DefaultConfiguration.ReadOrigin == ReadOrigin.Current); /// /// Test that the default configuration parallel options max degrees of parallelism matches the @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void TestDefaultConfigurationMaxDegreeOfParallelism() { - Assert.True(Configuration.Default.MaxDegreeOfParallelism == Environment.ProcessorCount); + Assert.True(this.DefaultConfiguration.MaxDegreeOfParallelism == Environment.ProcessorCount); var cfg = new Configuration(); Assert.True(cfg.MaxDegreeOfParallelism == Environment.ProcessorCount); @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Tests public void ConfigurationCannotAddDuplicates() { const int count = 4; - Configuration config = Configuration.Default; + Configuration config = this.DefaultConfiguration; Assert.Equal(count, config.ImageFormats.Count()); @@ -105,9 +105,16 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void DefaultConfigurationHasCorrectFormatCount() { - Configuration config = Configuration.Default; + Configuration config = Configuration.CreateDefaultInstance(); Assert.Equal(4, config.ImageFormats.Count()); } + + [Fact] + public void WorkingBufferSizeHint_DefaultIsCorrect() + { + Configuration config = this.DefaultConfiguration; + Assert.True(config.WorkingBufferSizeHintInBytes > 1024); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 3d6f08a152..ec0092f340 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -35,6 +35,25 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.07F); + [Theory] + [InlineData(20, 100, 1, 2)] + [InlineData(20, 100, 20*100*16, 2)] + [InlineData(20, 100, 40*100*16, 2)] + [InlineData(20, 100, 59*100*16, 2)] + [InlineData(20, 100, 60*100*16, 3)] + [InlineData(17, 63, 5*17*63*16, 5)] + [InlineData(17, 63, 5*17*63*16+1, 5)] + [InlineData(17, 63, 6*17*63*16-1, 5)] + public void CalculateResizeWorkerWindowCount( + int windowDiameter, + int width, + int sizeLimitHintInBytes, + int expectedCount) + { + int actualCount = ResizeHelper.CalculateResizeWorkerWindowCount(windowDiameter, width, sizeLimitHintInBytes); + Assert.Equal(expectedCount, actualCount); + } + [Theory] [InlineData(-2, 0)] [InlineData(-1, 0)]