diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index 76abc6499..53cd9e9d3 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -40,25 +40,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Guard.NotNull(options, nameof(options)); Guard.NotNull(options.Sampler, nameof(options.Sampler)); - int tempWidth = options.Size.Width; - int tempHeight = options.Size.Height; + int targetWidth = options.Size.Width; + int targetHeight = options.Size.Height; // Ensure size is populated across both dimensions. // These dimensions are used to calculate the final dimensions determined by the mode algorithm. - if (tempWidth == 0 && tempHeight > 0) + // If only one of the incoming dimensions is 0, it will be modified here to maintain aspect ratio. + // If it is not possible to keep aspect ratio, make sure at least the minimum is is kept. + const int min = 1; + if (targetWidth == 0 && targetHeight > 0) { - tempWidth = (int)MathF.Round(sourceSize.Width * tempHeight / (float)sourceSize.Height); + targetWidth = (int)MathF.Max(min, MathF.Round(sourceSize.Width * targetHeight / (float)sourceSize.Height)); } - if (tempHeight == 0 && tempWidth > 0) + if (targetHeight == 0 && targetWidth > 0) { - tempHeight = (int)MathF.Round(sourceSize.Height * tempWidth / (float)sourceSize.Width); + targetHeight = (int)MathF.Max(min, MathF.Round(sourceSize.Height * targetWidth / (float)sourceSize.Width)); } - Guard.MustBeGreaterThan(tempWidth, 0, nameof(tempWidth)); - Guard.MustBeGreaterThan(tempHeight, 0, nameof(tempHeight)); + Guard.MustBeGreaterThan(targetWidth, 0, nameof(targetWidth)); + Guard.MustBeGreaterThan(targetHeight, 0, nameof(targetHeight)); - (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options, tempWidth, tempHeight); + (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options, targetWidth, targetHeight); this.Sampler = options.Sampler; this.Width = size.Width; @@ -95,15 +98,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Guard.NotNull(sampler, nameof(sampler)); // Ensure size is populated across both dimensions. + // If only one of the incoming dimensions is 0, it will be modified here to maintain aspect ratio. + // If it is not possible to keep aspect ratio, make sure at least the minimum is is kept. + const int min = 1; if (width == 0 && height > 0) { - width = (int)MathF.Round(sourceSize.Width * height / (float)sourceSize.Height); + width = (int)MathF.Max(min, MathF.Round(sourceSize.Width * height / (float)sourceSize.Height)); resizeRectangle.Width = width; } if (height == 0 && width > 0) { - height = (int)MathF.Round(sourceSize.Height * width / (float)sourceSize.Width); + height = (int)MathF.Max(min, MathF.Round(sourceSize.Height * width / (float)sourceSize.Width)); resizeRectangle.Height = height; } diff --git a/src/ImageSharp/Processing/ResizeExtensions.cs b/src/ImageSharp/Processing/ResizeExtensions.cs index 8a370db69..7b6c14d7d 100644 --- a/src/ImageSharp/Processing/ResizeExtensions.cs +++ b/src/ImageSharp/Processing/ResizeExtensions.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Processing /// The image to resize. /// The resize options. /// The - /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image + /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image or the nearest possible ratio. public static IImageProcessingContext Resize(this IImageProcessingContext source, ResizeOptions options) where TPixel : struct, IPixel => source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize())); @@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Processing /// The image to resize. /// The target image size. /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size) where TPixel : struct, IPixel => Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, false); @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing /// The target image size. /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size, bool compand) where TPixel : struct, IPixel => Resize(source, size.Width, size.Height, KnownResamplers.Bicubic, compand); @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Processing /// The target image width. /// The target image height. /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height) where TPixel : struct, IPixel => Resize(source, width, height, KnownResamplers.Bicubic, false); @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing /// The target image height. /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, bool compand) where TPixel : struct, IPixel => Resize(source, width, height, KnownResamplers.Bicubic, compand); @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Processing /// The target image height. /// The to perform the resampling. /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler) where TPixel : struct, IPixel => Resize(source, width, height, sampler, false); @@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp.Processing /// The to perform the resampling. /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. public static IImageProcessingContext Resize(this IImageProcessingContext source, Size size, IResampler sampler, bool compand) where TPixel : struct, IPixel => Resize(source, size.Width, size.Height, sampler, new Rectangle(0, 0, size.Width, size.Height), compand); @@ -114,7 +114,7 @@ namespace SixLabors.ImageSharp.Processing /// The to perform the resampling. /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. public static IImageProcessingContext Resize(this IImageProcessingContext source, int width, int height, IResampler sampler, bool compand) where TPixel : struct, IPixel => Resize(source, width, height, sampler, new Rectangle(0, 0, width, height), compand); @@ -136,7 +136,7 @@ namespace SixLabors.ImageSharp.Processing /// /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. public static IImageProcessingContext Resize( this IImageProcessingContext source, int width, @@ -161,7 +161,7 @@ namespace SixLabors.ImageSharp.Processing /// /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image or the nearest possible ratio. public static IImageProcessingContext Resize( this IImageProcessingContext source, int width, diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 746d8da16..d1d473bbd 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -178,6 +178,32 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } } + [Theory] + [WithTestPatternImages(100, 10, DefaultPixelType)] + public void ResizeWidthCannotKeepAspectKeepsOnePixel(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.Resize(5, 0)); + Assert.Equal(5, image.Width); + Assert.Equal(1, image.Height); + } + } + + [Theory] + [WithTestPatternImages(10, 100, DefaultPixelType)] + public void ResizeHeightCannotKeepAspectKeepsOnePixel(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Mutate(x => x.Resize(0, 5)); + Assert.Equal(1, image.Width); + Assert.Equal(5, image.Height); + } + } + [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeWithCropWidthMode(TestImageProvider provider) @@ -324,7 +350,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [InlineData(2, 0)] public static void BicubicWindowOscillatesCorrectly(float x, float expected) { - var sampler = KnownResamplers.Bicubic; + IResampler sampler = KnownResamplers.Bicubic; float result = sampler.GetValue(x); Assert.Equal(result, expected); @@ -338,7 +364,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [InlineData(2, 0)] public static void TriangleWindowOscillatesCorrectly(float x, float expected) { - var sampler = KnownResamplers.Triangle; + IResampler sampler = KnownResamplers.Triangle; float result = sampler.GetValue(x); Assert.Equal(result, expected); @@ -352,7 +378,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [InlineData(2, 0)] public static void Lanczos3WindowOscillatesCorrectly(float x, float expected) { - var sampler = KnownResamplers.Lanczos3; + IResampler sampler = KnownResamplers.Lanczos3; float result = sampler.GetValue(x); Assert.Equal(result, expected); @@ -366,7 +392,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [InlineData(4, 0)] public static void Lanczos5WindowOscillatesCorrectly(float x, float expected) { - var sampler = KnownResamplers.Lanczos5; + IResampler sampler = KnownResamplers.Lanczos5; float result = sampler.GetValue(x); Assert.Equal(result, expected);