diff --git a/build/NuSpecs/ImageProcessor.Web.nuspec b/build/NuSpecs/ImageProcessor.Web.nuspec index 835a7e0c3..ef29e2489 100644 --- a/build/NuSpecs/ImageProcessor.Web.nuspec +++ b/build/NuSpecs/ImageProcessor.Web.nuspec @@ -25,7 +25,7 @@ Image Resize Crop Rotate Quality Watermark Gif Jpg Jpeg Bitmap Png Tiff ASP Cache EXIF - + diff --git a/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/PointConverter.cs b/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/PointConverter.cs index 4c5d5d717..6bd8f7a60 100644 --- a/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/PointConverter.cs +++ b/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/PointConverter.cs @@ -36,7 +36,7 @@ public override object ConvertFrom(CultureInfo culture, object value, Type prope { object result = base.ConvertFrom(culture, value, propertyType); - return result is int[] list ? new Point(list[0], list[1]) : result; + return result is int[] list && list.Length == 2 ? new Point(list[0], list[1]) : result; } } } diff --git a/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/PointFConverter.cs b/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/PointFConverter.cs new file mode 100644 index 000000000..7cb1dca28 --- /dev/null +++ b/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/PointFConverter.cs @@ -0,0 +1,42 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James Jackson-South. +// Licensed under the Apache License, Version 2.0. +// +// +// The point converter. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessor.Web.Helpers +{ + using System; + using System.Drawing; + using System.Globalization; + + /// + /// The PointF converter. + /// + public class PointFConverter : GenericArrayTypeConverter + { + /// + /// Converts the given object to the type of this converter, using the specified context and culture + /// information. + /// + /// + /// An that represents the converted value. + /// + /// + /// The to use as the current culture. + /// + /// The to convert. + /// The property type that the converter will convert to. + /// The conversion cannot be performed. + public override object ConvertFrom(CultureInfo culture, object value, Type propertyType) + { + object result = base.ConvertFrom(culture, value, propertyType); + + return result is float[] list && list.Length == 2 ? new PointF(list[0], list[1]) : result; + } + } +} diff --git a/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/SizeConverter.cs b/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/SizeConverter.cs index 98d38c23b..8e579f49e 100644 --- a/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/SizeConverter.cs +++ b/src/ImageProcessor.Web/Helpers/QuerystringParser/Converters/SizeConverter.cs @@ -36,7 +36,7 @@ public override object ConvertFrom(CultureInfo culture, object value, Type prope { object result = base.ConvertFrom(culture, value, propertyType); - return result is int[] list ? new Size(list[0], list[1]) : result; + return result is int[] list && list.Length == 2 ? new Size(list[0], list[1]) : result; } } } diff --git a/src/ImageProcessor.Web/Helpers/QuerystringParser/QueryParamParser.cs b/src/ImageProcessor.Web/Helpers/QuerystringParser/QueryParamParser.cs index 9e5bbb665..272d463f7 100644 --- a/src/ImageProcessor.Web/Helpers/QuerystringParser/QueryParamParser.cs +++ b/src/ImageProcessor.Web/Helpers/QuerystringParser/QueryParamParser.cs @@ -22,13 +22,13 @@ namespace ImageProcessor.Web.Helpers public sealed class QueryParamParser { /// - /// A new instance of the class. + /// A new instance of the class. /// with lazy initialization. /// private static readonly Lazy Lazy = new Lazy(() => new QueryParamParser()); /// - /// Prevents a default instance of the class from being created. + /// Prevents a default instance of the class from being created. /// private QueryParamParser() { @@ -42,25 +42,22 @@ private QueryParamParser() } /// - /// Gets the current instance. + /// Gets the current instance. /// + /// + /// The instance. + /// public static QueryParamParser Instance => Lazy.Value; /// /// Parses the given string value converting it to the given type. /// - /// - /// The value to parse. - /// - /// - /// The to use as the current culture. - /// If not set will parse using - /// - /// - /// The to convert the string to. - /// + /// The to convert the string to. + /// The value to parse. + /// The to use as the current culture. + /// If not set will parse using /// - /// The . + /// The . /// public T ParseValue(string value, CultureInfo culture = null) { @@ -72,6 +69,12 @@ public T ParseValue(string value, CultureInfo culture = null) Type type = typeof(T); IQueryParamConverter converter = QueryTypeDescriptor.GetConverter(type); + if (converter == null && Nullable.GetUnderlyingType(type) is Type underlyingType) + { + type = underlyingType; + converter = QueryTypeDescriptor.GetConverter(type); + } + try { // ReSharper disable once AssignNullToNotNullAttribute @@ -87,12 +90,8 @@ public T ParseValue(string value, CultureInfo culture = null) /// /// Adds a type converter to the parser. /// - /// - /// The to add a converter for. - /// - /// - /// The type of to add. - /// + /// The to add a converter for. + /// The type of to add. public void AddTypeConverter(Type type, Type converterType) => QueryTypeDescriptor.AddConverter(type, converterType); /// @@ -108,15 +107,19 @@ public T ParseValue(string value, CultureInfo culture = null) /// /// Adds point converters. /// - private void AddPointConverters() => this.AddTypeConverter(typeof(Point), typeof(PointConverter)); + private void AddPointConverters() + { + this.AddTypeConverter(typeof(Point), typeof(PointConverter)); + this.AddTypeConverter(typeof(PointF), typeof(PointFConverter)); + } /// - /// Adds point converters. + /// Adds size converters. /// private void AddSizeConverters() => this.AddTypeConverter(typeof(Size), typeof(SizeConverter)); /// - /// Add the generic converters + /// Add the generic converters. /// private void AddGenericConverters() { diff --git a/src/ImageProcessor.Web/ImageProcessor.Web.csproj b/src/ImageProcessor.Web/ImageProcessor.Web.csproj index 720e0c83c..897a404f8 100644 --- a/src/ImageProcessor.Web/ImageProcessor.Web.csproj +++ b/src/ImageProcessor.Web/ImageProcessor.Web.csproj @@ -72,6 +72,7 @@ + diff --git a/src/ImageProcessor.Web/Processors/Resize.cs b/src/ImageProcessor.Web/Processors/Resize.cs index 36d4f6208..10fe2a464 100644 --- a/src/ImageProcessor.Web/Processors/Resize.cs +++ b/src/ImageProcessor.Web/Processors/Resize.cs @@ -75,16 +75,19 @@ public int MatchRegexIndex(string queryString) ResizeMode mode = QueryParamParser.Instance.ParseValue(queryCollection["mode"]); AnchorPosition position = QueryParamParser.Instance.ParseValue(queryCollection["anchor"]); bool upscale = queryCollection["upscale"] == null || QueryParamParser.Instance.ParseValue(queryCollection["upscale"]); - float[] center = queryCollection["center"] != null - ? QueryParamParser.Instance.ParseValue(queryCollection["center"]) : - new float[] { }; + PointF? center = QueryParamParser.Instance.ParseValue(queryCollection["center"]); + if (center.HasValue) + { + // Swap X/Y for backwards compatibility + center = new PointF(center.Value.Y, center.Value.X); + } this.Processor.DynamicParameter = new ResizeLayer(size) { ResizeMode = mode, AnchorPosition = position, Upscale = upscale, - CenterCoordinates = center + Center = center }; // Correctly parse any restrictions. diff --git a/src/ImageProcessor/Common/Extensions/FloatExtensions.cs b/src/ImageProcessor/Common/Extensions/FloatExtensions.cs index ab0a80ecc..b7067046f 100644 --- a/src/ImageProcessor/Common/Extensions/FloatExtensions.cs +++ b/src/ImageProcessor/Common/Extensions/FloatExtensions.cs @@ -32,6 +32,6 @@ public static class FloatExtensions /// /// The . /// - public static byte ToByte(this float value) => Convert.ToByte(ImageMaths.Clamp(value, 0, 255)); + public static byte ToByte(this float value) => Convert.ToByte(ImageMaths.Clamp(value, byte.MinValue, byte.MaxValue)); } } diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs index b5c0f14d8..8837c9381 100644 --- a/src/ImageProcessor/ImageFactory.cs +++ b/src/ImageProcessor/ImageFactory.cs @@ -75,7 +75,7 @@ public class ImageFactory : IDisposable /// Whether to preserve exif metadata. Defaults to false. /// public ImageFactory(bool preserveExifData = false) - : this(preserveExifData, true) + : this(preserveExifData, false) { } @@ -119,7 +119,7 @@ public ImageFactory(MetaDataMode metaDataMode, bool fixGamma) } /// - /// Finalizes an instance of the ImageFactory class. + /// Finalizes an instance of the class. /// /// /// Use C# destructor syntax for finalization code. diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj index de8e37d34..aad9be49c 100644 --- a/src/ImageProcessor/ImageProcessor.csproj +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -151,6 +151,7 @@ + diff --git a/src/ImageProcessor/Imaging/Helpers/Adjustments.cs b/src/ImageProcessor/Imaging/Helpers/Adjustments.cs index bf60cd9ab..1a42df3b5 100644 --- a/src/ImageProcessor/Imaging/Helpers/Adjustments.cs +++ b/src/ImageProcessor/Imaging/Helpers/Adjustments.cs @@ -13,6 +13,7 @@ namespace ImageProcessor.Imaging.Helpers using System; using System.Drawing; using System.Drawing.Imaging; + using System.Runtime.CompilerServices; using System.Threading.Tasks; using ImageProcessor.Common.Extensions; @@ -329,7 +330,7 @@ private static byte[] GetLinearBytes() byte[] ramp = new byte[256]; for (int x = 0; x < 256; ++x) { - ramp[x] = (255f * SRGBToLinear(x / 255f)).ToByte(); + ramp[x] = ((255 * SRGBToLinear(x / 255D)) + .5).ToByte(); } return ramp; @@ -347,7 +348,7 @@ private static byte[] GetSRGBBytes() byte[] ramp = new byte[256]; for (int x = 0; x < 256; ++x) { - ramp[x] = (255f * LinearToSRGB(x / 255f)).ToByte(); + ramp[x] = ((255 * LinearToSRGB(x / 255D)) + .5).ToByte(); } return ramp; @@ -362,16 +363,15 @@ private static byte[] GetSRGBBytes() /// /// The . /// - private static float SRGBToLinear(float signal) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static double SRGBToLinear(double signal) { - const float a = 0.055f; - if (signal <= 0.04045) { - return signal / 12.92f; + return signal / 12.92; } - return (float)Math.Pow((signal + a) / (1 + a), 2.4); + return Math.Pow((signal + 0.055) / 1.055, 2.4); } /// @@ -381,18 +381,17 @@ private static float SRGBToLinear(float signal) /// /// The signal value to convert. /// - /// The . + /// The . /// - private static float LinearToSRGB(float signal) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static double LinearToSRGB(double signal) { - const float a = 0.055f; - - if (signal <= 0.0031308) + if (signal <= (0.04045 / 12.92)) { - return signal * 12.92f; + return signal * 12.92; } - return ((float)((1 + a) * Math.Pow(signal, 0.4166667F))) - a; + return (1.055 * Math.Pow(signal, 1.0 / 2.4)) - 0.055; } } } diff --git a/src/ImageProcessor/Imaging/Helpers/GraphicsHelper.cs b/src/ImageProcessor/Imaging/Helpers/GraphicsHelper.cs index 8c1360fb9..bc8eac7c6 100644 --- a/src/ImageProcessor/Imaging/Helpers/GraphicsHelper.cs +++ b/src/ImageProcessor/Imaging/Helpers/GraphicsHelper.cs @@ -28,15 +28,15 @@ public static void SetGraphicsOptions(Graphics graphics, bool blending = false, { // Highest quality resampling algorithm. graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - - // Ensure pixel offset is set. - graphics.PixelOffsetMode = PixelOffsetMode.Half; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingQuality = CompositingQuality.HighQuality; if (smoothing) { // We want smooth edges when drawing. graphics.SmoothingMode = SmoothingMode.AntiAlias; } + if (blending || smoothing) { // Best combination for blending pixels. diff --git a/src/ImageProcessor/Imaging/ResizeHelper.cs b/src/ImageProcessor/Imaging/ResizeHelper.cs new file mode 100644 index 000000000..1a726a1fe --- /dev/null +++ b/src/ImageProcessor/Imaging/ResizeHelper.cs @@ -0,0 +1,394 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James Jackson-South. +// Licensed under the Apache License, Version 2.0. +// +// +// Resizes an image to the given dimensions. +// +// -------------------------------------------------------------------------------------------------------------------- + +using System; +using System.Drawing; + +namespace ImageProcessor.Imaging +{ + /// + /// Provides methods to help calculate the target rectangle when resizing using the + /// enumeration. + /// + internal static class ResizeHelper + { + /// + /// Calculates the target location and bounds to perform the resize operation against. + /// + /// The source image size. + /// The resize options. + /// The target width. + /// The target height. + /// + /// The tuple representing the location and the bounds. + /// + public static (Size, Rectangle) CalculateTargetLocationAndBounds( + Size sourceSize, + ResizeLayer options, + int width, + int height) + { + if (width <= 0 && height <= 0) + { + ThrowInvalid($"Target width {width} and height {height} must be greater than zero."); + } + + switch (options.ResizeMode) + { + case ResizeMode.Crop: + return CalculateCropRectangle(sourceSize, options, width, height); + case ResizeMode.Pad: + return CalculatePadRectangle(sourceSize, options, width, height); + case ResizeMode.BoxPad: + return CalculateBoxPadRectangle(sourceSize, options, width, height); + case ResizeMode.Max: + return CalculateMaxRectangle(sourceSize, width, height); + case ResizeMode.Min: + return CalculateMinRectangle(sourceSize, width, height); + + // Last case ResizeMode.Stretch: + default: + return (new Size(width, height), new Rectangle(0, 0, width, height)); + } + } + + private static (Size, Rectangle) CalculateBoxPadRectangle( + Size source, + ResizeLayer 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 = Math.Abs(height / (float)sourceHeight); + float percentWidth = Math.Abs(width / (float)sourceWidth); + + int boxPadHeight = height > 0 ? height : (int)Math.Round(sourceHeight * percentWidth); + int boxPadWidth = width > 0 ? width : (int)Math.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.AnchorPosition) + { + case AnchorPosition.Left: + destinationY = (height - sourceHeight) / 2; + destinationX = 0; + break; + case AnchorPosition.Right: + destinationY = (height - sourceHeight) / 2; + destinationX = width - sourceWidth; + break; + case AnchorPosition.TopRight: + destinationY = 0; + destinationX = width - sourceWidth; + break; + case AnchorPosition.Top: + destinationY = 0; + destinationX = (width - sourceWidth) / 2; + break; + case AnchorPosition.TopLeft: + destinationY = 0; + destinationX = 0; + break; + case AnchorPosition.BottomRight: + destinationY = height - sourceHeight; + destinationX = width - sourceWidth; + break; + case AnchorPosition.Bottom: + destinationY = height - sourceHeight; + destinationX = (width - sourceWidth) / 2; + break; + case AnchorPosition.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, + ResizeLayer options, + int width, + int height) + { + float ratio; + int sourceWidth = source.Width; + int sourceHeight = source.Height; + + int targetX = 0; + int targetY = 0; + int targetWidth = width; + int targetHeight = height; + + // Fractional variants for preserving aspect ratio. + float percentHeight = Math.Abs(height / (float)sourceHeight); + float percentWidth = Math.Abs(width / (float)sourceWidth); + + if (percentHeight < percentWidth) + { + ratio = percentWidth; + + if (options.Center is var center && center.HasValue) + { + float centerRatio = -(ratio * sourceHeight) * center.Value.Y; + targetY = (int)Math.Round(centerRatio + (height / 2F)); + + if (targetY > 0) + { + targetY = 0; + } + + if (targetY < (int)Math.Round(height - (sourceHeight * ratio))) + { + targetY = (int)Math.Round(height - (sourceHeight * ratio)); + } + } + else + { + switch (options.AnchorPosition) + { + case AnchorPosition.Top: + case AnchorPosition.TopLeft: + case AnchorPosition.TopRight: + targetY = 0; + break; + case AnchorPosition.Bottom: + case AnchorPosition.BottomLeft: + case AnchorPosition.BottomRight: + targetY = (int)Math.Round(height - (sourceHeight * ratio)); + break; + default: + targetY = (int)Math.Round((height - (sourceHeight * ratio)) / 2F); + break; + } + } + + targetHeight = (int)Math.Ceiling(sourceHeight * percentWidth); + } + else + { + ratio = percentHeight; + + if (options.Center is var center && center.HasValue) + { + float centerRatio = -(ratio * sourceWidth) * center.Value.X; + targetX = (int)Math.Round(centerRatio + (width / 2F)); + + if (targetX > 0) + { + targetX = 0; + } + + if (targetX < (int)Math.Round(width - (sourceWidth * ratio))) + { + targetX = (int)Math.Round(width - (sourceWidth * ratio)); + } + } + else + { + switch (options.AnchorPosition) + { + case AnchorPosition.Left: + case AnchorPosition.TopLeft: + case AnchorPosition.BottomLeft: + targetX = 0; + break; + case AnchorPosition.Right: + case AnchorPosition.TopRight: + case AnchorPosition.BottomRight: + targetX = (int)Math.Round(width - (sourceWidth * ratio)); + break; + default: + targetX = (int)Math.Round((width - (sourceWidth * ratio)) / 2F); + break; + } + } + + targetWidth = (int)Math.Ceiling(sourceWidth * percentHeight); + } + + // Target image width and height can be different to the rectangle width and height. + return (new Size(width, height), new Rectangle(targetX, targetY, targetWidth, targetHeight)); + } + + private static (Size, Rectangle) CalculateMaxRectangle( + Size source, + int width, + int height) + { + int targetWidth = width; + int targetHeight = height; + + // Fractional variants for preserving aspect ratio. + float percentHeight = Math.Abs(height / (float)source.Height); + float percentWidth = Math.Abs(width / (float)source.Width); + + // Integers must be cast to floats to get needed precision + float ratio = height / (float)width; + float sourceRatio = source.Height / (float)source.Width; + + if (sourceRatio < ratio) + { + targetHeight = (int)Math.Round(source.Height * percentWidth); + } + else + { + targetWidth = (int)Math.Round(source.Width * percentHeight); + } + + // Replace the size to match the rectangle. + return (new Size(targetWidth, targetHeight), new Rectangle(0, 0, targetWidth, targetHeight)); + } + + private static (Size, Rectangle) CalculateMinRectangle( + Size source, + int width, + int height) + { + int sourceWidth = source.Width; + int sourceHeight = source.Height; + int targetWidth = width; + int targetHeight = height; + + // Don't upscale + if (width > sourceWidth || height > sourceHeight) + { + return (new Size(sourceWidth, sourceHeight), new Rectangle(0, 0, sourceWidth, sourceHeight)); + } + + // Find the shortest distance to go. + int widthDiff = sourceWidth - width; + int heightDiff = sourceHeight - height; + + if (widthDiff < heightDiff) + { + float sourceRatio = (float)sourceHeight / sourceWidth; + targetHeight = (int)Math.Round(width * sourceRatio); + } + else if (widthDiff > heightDiff) + { + float sourceRatioInverse = (float)sourceWidth / sourceHeight; + targetWidth = (int)Math.Round(height * sourceRatioInverse); + } + else + { + if (height > width) + { + float percentWidth = Math.Abs(width / (float)sourceWidth); + targetHeight = (int)Math.Round(sourceHeight * percentWidth); + } + else + { + float percentHeight = Math.Abs(height / (float)sourceHeight); + targetWidth = (int)Math.Round(sourceWidth * percentHeight); + } + } + + // Replace the size to match the rectangle. + return (new Size(targetWidth, targetHeight), new Rectangle(0, 0, targetWidth, targetHeight)); + } + + private static (Size, Rectangle) CalculatePadRectangle( + Size sourceSize, + ResizeLayer options, + int width, + int height) + { + float ratio; + int sourceWidth = sourceSize.Width; + int sourceHeight = sourceSize.Height; + + int targetX = 0; + int targetY = 0; + int targetWidth = width; + int targetHeight = height; + + // Fractional variants for preserving aspect ratio. + float percentHeight = Math.Abs(height / (float)sourceHeight); + float percentWidth = Math.Abs(width / (float)sourceWidth); + + if (percentHeight < percentWidth) + { + ratio = percentHeight; + targetWidth = (int)Math.Round(sourceWidth * percentHeight); + + switch (options.AnchorPosition) + { + case AnchorPosition.Left: + case AnchorPosition.TopLeft: + case AnchorPosition.BottomLeft: + targetX = 0; + break; + case AnchorPosition.Right: + case AnchorPosition.TopRight: + case AnchorPosition.BottomRight: + targetX = (int)Math.Round(width - (sourceWidth * ratio)); + break; + default: + targetX = (int)Math.Round((width - (sourceWidth * ratio)) / 2F); + break; + } + } + else + { + ratio = percentWidth; + targetHeight = (int)Math.Round(sourceHeight * percentWidth); + + switch (options.AnchorPosition) + { + case AnchorPosition.Top: + case AnchorPosition.TopLeft: + case AnchorPosition.TopRight: + targetY = 0; + break; + case AnchorPosition.Bottom: + case AnchorPosition.BottomLeft: + case AnchorPosition.BottomRight: + targetY = (int)Math.Round(height - (sourceHeight * ratio)); + break; + default: + targetY = (int)Math.Round((height - (sourceHeight * ratio)) / 2F); + break; + } + } + + // Target image width and height can be different to the rectangle width and height. + return (new Size(width, height), new Rectangle(targetX, targetY, targetWidth, targetHeight)); + } + + private static void ThrowInvalid(string message) => throw new InvalidOperationException(message); + } +} diff --git a/src/ImageProcessor/Imaging/ResizeLayer.cs b/src/ImageProcessor/Imaging/ResizeLayer.cs index 685144ce7..94c01d7e2 100644 --- a/src/ImageProcessor/Imaging/ResizeLayer.cs +++ b/src/ImageProcessor/Imaging/ResizeLayer.cs @@ -22,34 +22,16 @@ namespace ImageProcessor.Imaging public class ResizeLayer : IEquatable { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// - /// The containing the width and height to set the image to. - /// - /// - /// The resize mode to apply to resized image. (Default ResizeMode.Pad) - /// - /// - /// The to apply to resized image. (Default AnchorPosition.Center) - /// - /// - /// Whether to allow up-scaling of images. (Default true) - /// - /// - /// The center coordinates (Default null) - /// - /// - /// The maximum size to resize an image to. - /// Used to restrict resizing based on calculated resizing - /// - /// - /// The range of sizes to restrict resizing an image to. - /// Used to restrict resizing based on calculated resizing - /// - /// - /// The anchor point (Default null) - /// + /// The containing the width and height to resize the image to. + /// The resize mode to apply to the resized image. + /// The anchor position to apply to the resized image. + /// Whether to allow up-scaling of images. + /// The center coordinates (Y,X). + /// The maximum size to resize an image to. Used to restrict resizing based on calculated resizing. + /// The range of sizes to restrict resizing an image to. Used to restrict resizing based on calculated resizing. + /// The anchor point. public ResizeLayer( Size size, ResizeMode resizeMode = ResizeMode.Pad, @@ -64,51 +46,104 @@ public ResizeLayer( this.Upscale = upscale; this.ResizeMode = resizeMode; this.AnchorPosition = anchorPosition; - this.CenterCoordinates = centerCoordinates ?? new float[] { }; + if (centerCoordinates != null && centerCoordinates.Length == 2) + { + this.Center = new PointF(centerCoordinates[1], centerCoordinates[0]); + } this.MaxSize = maxSize; - this.RestrictedSizes = restrictedSizes ?? new List(); + this.RestrictedSizes = restrictedSizes; this.AnchorPoint = anchorPoint; } /// /// Gets or sets the size. /// + /// + /// The size. + /// public Size Size { get; set; } /// - /// Gets or sets the max size. + /// Gets or sets the maximum size. /// + /// + /// The maximum size. + /// public Size? MaxSize { get; set; } /// - /// Gets or sets the restricted range of sizes. to restrict resizing methods to. + /// Gets or sets the restricted range of sizes to restrict resizing methods to. /// + /// + /// The restricted sizes. + /// public List RestrictedSizes { get; set; } /// /// Gets or sets the resize mode. /// + /// + /// The resize mode. + /// public ResizeMode ResizeMode { get; set; } /// /// Gets or sets the anchor position. /// + /// + /// The anchor position. + /// public AnchorPosition AnchorPosition { get; set; } /// /// Gets or sets a value indicating whether to allow up-scaling of images. - /// For this is always true. + /// For this is always true. /// + /// + /// true if up-scaling is allowed; otherwise, false. + /// public bool Upscale { get; set; } /// - /// Gets or sets the center coordinates. + /// Gets or sets the center coordinates (Y,X). /// - public float[] CenterCoordinates { get; set; } + /// + /// The center coordinates (Y,X). + /// + [Obsolete("Use the Center property instead.")] + public float[] CenterCoordinates + { + get + { + return this.Center is PointF center ? new float[] { center.Y, center.X } : null; + } + set + { + if (value != null && value.Length == 2) + { + this.Center = new PointF(value[1], value[0]); + } + else + { + this.Center = null; + } + } + } + + /// + /// Gets the center coordinates as . + /// + /// + /// The center coordinates as . + /// + public PointF? Center { get; set; } /// /// Gets or sets the anchor point. /// + /// + /// The anchor point. + /// public Point? AnchorPoint { get; set; } /// @@ -134,7 +169,7 @@ public bool Equals(ResizeLayer other) => other != null && this.ResizeMode == other.ResizeMode && this.AnchorPosition == other.AnchorPosition && this.Upscale == other.Upscale - && (this.CenterCoordinates == null || other.CenterCoordinates == null ? this.CenterCoordinates == other.CenterCoordinates : this.CenterCoordinates.SequenceEqual(other.CenterCoordinates)) + && this.Center == other.Center && this.AnchorPoint == other.AnchorPoint; /// @@ -143,6 +178,6 @@ public bool Equals(ResizeLayer other) => other != null /// /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// - public override int GetHashCode() => (this.Size, this.MaxSize, this.ResizeMode, this.AnchorPosition, this.Upscale, this.AnchorPoint).GetHashCode(); + public override int GetHashCode() => (this.Size, this.MaxSize, this.ResizeMode, this.AnchorPosition, this.Upscale, this.Center, this.AnchorPoint).GetHashCode(); } -} \ No newline at end of file +} diff --git a/src/ImageProcessor/Imaging/Resizer.cs b/src/ImageProcessor/Imaging/Resizer.cs index cdfef07e7..1b53f89d5 100644 --- a/src/ImageProcessor/Imaging/Resizer.cs +++ b/src/ImageProcessor/Imaging/Resizer.cs @@ -10,17 +10,16 @@ namespace ImageProcessor.Imaging { + using ImageProcessor.Common.Exceptions; + using ImageProcessor.Common.Extensions; + using ImageProcessor.Imaging.Formats; + using ImageProcessor.Imaging.Helpers; using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; - using ImageProcessor.Common.Exceptions; - using ImageProcessor.Common.Extensions; - using ImageProcessor.Imaging.Formats; - using ImageProcessor.Imaging.Helpers; - /// /// Provides methods to resize images. /// @@ -65,472 +64,54 @@ public class Resizer /// /// The resized . /// - public Bitmap ResizeImage(Image source, bool linear) - { - int width = this.ResizeLayer.Size.Width; - int height = this.ResizeLayer.Size.Height; - ResizeMode mode = this.ResizeLayer.ResizeMode; - AnchorPosition anchor = this.ResizeLayer.AnchorPosition; - bool upscale = this.ResizeLayer.Upscale; - float[] centerCoordinates = this.ResizeLayer.CenterCoordinates; - int maxWidth = this.ResizeLayer.MaxSize?.Width ?? int.MaxValue; - int maxHeight = this.ResizeLayer.MaxSize?.Height ?? int.MaxValue; - List restrictedSizes = this.ResizeLayer.RestrictedSizes; - Point? anchorPoint = this.ResizeLayer.AnchorPoint; - - return this.ResizeImage(source, width, height, maxWidth, maxHeight, restrictedSizes, mode, anchor, upscale, centerCoordinates, linear, anchorPoint); - } - - /// - /// Gets an image resized using the composite color space without any gamma correction adjustments. - /// - /// The source image. - /// The width to resize to. - /// The height to resize to. - /// The destination rectangle. - /// - /// The . - /// - protected virtual Bitmap ResizeComposite(Image source, int width, int height, Rectangle destination) - { - var resized = new Bitmap(width, height, PixelFormat.Format32bppPArgb); - resized.SetResolution(source.HorizontalResolution, source.VerticalResolution); - - using (var graphics = Graphics.FromImage(resized)) - { - GraphicsHelper.SetGraphicsOptions(graphics); - using (var attributes = new ImageAttributes()) - { - attributes.SetWrapMode(WrapMode.TileFlipXY); - graphics.DrawImage(source, destination, 0, 0, source.Width, source.Height, GraphicsUnit.Pixel, attributes); - } - } - - return resized; - } - - /// - /// Gets an image resized using the linear color space with gamma correction adjustments. - /// - /// The source image. - /// The width to resize to. - /// The height to resize to. - /// The destination rectangle. - /// - /// The . - /// - protected virtual Bitmap ResizeLinear(Image source, int width, int height, Rectangle destination) => this.ResizeLinear(source, width, height, destination, this.AnimationProcessMode); - - /// - /// Gets an image resized using the linear color space with gamma correction adjustments. - /// - /// The source image. - /// The width to resize to. - /// The height to resize to. - /// The destination rectangle. - /// The process mode for frames in animated images. - /// - /// The . - /// - protected virtual Bitmap ResizeLinear(Image source, int width, int height, Rectangle destination, AnimationProcessMode animationProcessMode) - { - // Adjust the gamma value so that the image is in the linear color space. - Bitmap linear = Adjustments.ToLinear(source.Copy(animationProcessMode)); - - var resized = new Bitmap(width, height, PixelFormat.Format32bppPArgb); - resized.SetResolution(source.HorizontalResolution, source.VerticalResolution); - - using (var graphics = Graphics.FromImage(resized)) - { - GraphicsHelper.SetGraphicsOptions(graphics); - using (var attributes = new ImageAttributes()) - { - attributes.SetWrapMode(WrapMode.TileFlipXY); - graphics.DrawImage(linear, destination, 0, 0, source.Width, source.Height, GraphicsUnit.Pixel, attributes); - } - } - - // Return to composite color space. - resized = Adjustments.ToSRGB(resized); - - linear.Dispose(); - return resized; - } - - /// - /// Resizes the given image. - /// - /// The source to resize - /// The width to resize the image to. - /// The height to resize the image to. - /// The default max width to resize the image to. - /// The default max height to resize the image to. - /// A containing image resizing restrictions. - /// The mode with which to resize the image. - /// The anchor position to place the image at. - /// Whether to allow up-scaling of images. (Default true) - /// - /// If the resize mode is crop, you can set a specific center coordinate, use as alternative to anchorPosition - /// - /// Whether to resize the image using the linear color space. - /// - /// If resize mode is box pad, you can set a specific anchor coordinate, use as alternative to anchorPosition. - /// - /// - /// The resized . - /// - private Bitmap ResizeImage( + public Bitmap ResizeImage( Image source, - int width, - int height, - int maxWidth, - int maxHeight, - List restrictedSizes, - ResizeMode resizeMode = ResizeMode.Pad, - AnchorPosition anchorPosition = AnchorPosition.Center, - bool upscale = true, - float[] centerCoordinates = null, - bool linear = false, - Point? anchorPoint = null) + bool linear) { Bitmap newImage = null; try { - int sourceWidth = source.Width; - int sourceHeight = source.Height; - - int destinationWidth = width; - int destinationHeight = height; - - maxWidth = maxWidth > 0 ? maxWidth : int.MaxValue; - maxHeight = maxHeight > 0 ? maxHeight : int.MaxValue; - - // Fractional variants for preserving aspect ratio. - double percentHeight = Math.Abs(height / (double)sourceHeight); - double percentWidth = Math.Abs(width / (double)sourceWidth); - - int destinationX = 0; - int destinationY = 0; - - // Change the destination rectangle coordinates if box padding. - if (resizeMode == ResizeMode.BoxPad) + Size sourceSize = source.Size; + int targetWidth = this.ResizeLayer.Size.Width; + int targetHeight = this.ResizeLayer.Size.Height; + int maxWidth = this.ResizeLayer.MaxSize?.Width ?? int.MaxValue; + int maxHeight = this.ResizeLayer.MaxSize?.Height ?? int.MaxValue; + + // Ensure size is populated across both dimensions. + // These dimensions are used to calculate the final dimensions determined by the mode algorithm. + // If only one of the incoming dimensions is 0, it will be modified here to maintain aspect ratio. + // If it is not possible to keep aspect ratio, make sure at least the minimum is is kept. + const int Min = 1; + if (targetWidth == 0 && targetHeight > 0) { - int boxPadHeight = height > 0 ? height : Convert.ToInt32(sourceHeight * percentWidth); - int boxPadWidth = width > 0 ? width : Convert.ToInt32(sourceWidth * percentHeight); - - // Only calculate if upscaling. - if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight) - { - destinationWidth = sourceWidth; - destinationHeight = sourceHeight; - width = boxPadWidth; - height = boxPadHeight; - - upscale = true; - - if (anchorPoint.HasValue) - { - if (anchorPoint.Value.Y < 0) - { - destinationY = 0; - } - else if (anchorPoint.Value.Y + sourceHeight > boxPadHeight) - { - destinationY = boxPadHeight - sourceHeight; - } - else - { - destinationY = anchorPoint.Value.Y; - } - - if (anchorPoint.Value.X < 0) - { - destinationX = 0; - } - else if (anchorPoint.Value.X + sourceWidth > boxPadWidth) - { - destinationX = boxPadWidth - sourceWidth; - } - else - { - destinationX = anchorPoint.Value.X; - } - } - else - { - switch (anchorPosition) - { - case AnchorPosition.Left: - destinationY = (height - sourceHeight) / 2; - destinationX = 0; - break; - case AnchorPosition.Right: - destinationY = (height - sourceHeight) / 2; - destinationX = width - sourceWidth; - break; - case AnchorPosition.TopRight: - destinationY = 0; - destinationX = width - sourceWidth; - break; - case AnchorPosition.Top: - destinationY = 0; - destinationX = (width - sourceWidth) / 2; - break; - case AnchorPosition.TopLeft: - destinationY = 0; - destinationX = 0; - break; - case AnchorPosition.BottomRight: - destinationY = height - sourceHeight; - destinationX = width - sourceWidth; - break; - case AnchorPosition.Bottom: - destinationY = height - sourceHeight; - destinationX = (width - sourceWidth) / 2; - break; - case AnchorPosition.BottomLeft: - destinationY = height - sourceHeight; - destinationX = 0; - break; - default: - destinationY = (height - sourceHeight) / 2; - destinationX = (width - sourceWidth) / 2; - break; - } - } - } - else - { - // Switch to pad mode to downscale and calculate from there. - resizeMode = ResizeMode.Pad; - } + targetWidth = (int)Math.Max(Min, Math.Round(sourceSize.Width * targetHeight / (float)sourceSize.Height)); } - // Change the destination rectangle coordinates if padding and - // there has been a set width and height. - if (resizeMode == ResizeMode.Pad && width > 0 && height > 0) + if (targetHeight == 0 && targetWidth > 0) { - double ratio; - - if (percentHeight < percentWidth) - { - ratio = percentHeight; - destinationWidth = Convert.ToInt32(sourceWidth * percentHeight); - - switch (anchorPosition) - { - case AnchorPosition.Left: - case AnchorPosition.TopLeft: - case AnchorPosition.BottomLeft: - destinationX = 0; - break; - case AnchorPosition.Right: - case AnchorPosition.TopRight: - case AnchorPosition.BottomRight: - destinationX = (int)(width - (sourceWidth * ratio)); - break; - default: - destinationX = Convert.ToInt32((width - (sourceWidth * ratio)) / 2); - break; - } - } - else - { - ratio = percentWidth; - destinationHeight = Convert.ToInt32(sourceHeight * percentWidth); - - switch (anchorPosition) - { - case AnchorPosition.Top: - case AnchorPosition.TopLeft: - case AnchorPosition.TopRight: - destinationY = 0; - break; - case AnchorPosition.Bottom: - case AnchorPosition.BottomLeft: - case AnchorPosition.BottomRight: - destinationY = (int)(height - (sourceHeight * ratio)); - break; - default: - destinationY = (int)((height - (sourceHeight * ratio)) / 2); - break; - } - } + targetHeight = (int)Math.Max(Min, Math.Round(sourceSize.Height * targetWidth / (float)sourceSize.Width)); } - // Change the destination rectangle coordinates if cropping and - // there has been a set width and height. - if (resizeMode == ResizeMode.Crop && width > 0 && height > 0) - { - double ratio; - - if (percentHeight < percentWidth) - { - ratio = percentWidth; - - if (centerCoordinates?.Length > 0) - { - double center = -(ratio * sourceHeight) * centerCoordinates[0]; - destinationY = (int)center + (height / 2); - - if (destinationY > 0) - { - destinationY = 0; - } - - if (destinationY < (int)(height - (sourceHeight * ratio))) - { - destinationY = (int)(height - (sourceHeight * ratio)); - } - } - else - { - switch (anchorPosition) - { - case AnchorPosition.Top: - case AnchorPosition.TopLeft: - case AnchorPosition.TopRight: - destinationY = 0; - break; - case AnchorPosition.Bottom: - case AnchorPosition.BottomLeft: - case AnchorPosition.BottomRight: - destinationY = (int)(height - (sourceHeight * ratio)); - break; - default: - destinationY = (int)((height - (sourceHeight * ratio)) / 2); - break; - } - } - - destinationHeight = (int)Math.Ceiling(sourceHeight * percentWidth); - } - else - { - ratio = percentHeight; - - if (centerCoordinates?.Length > 0) - { - double center = -(ratio * sourceWidth) * centerCoordinates[1]; - destinationX = (int)center + (width / 2); - - if (destinationX > 0) - { - destinationX = 0; - } - - if (destinationX < (int)(width - (sourceWidth * ratio))) - { - destinationX = (int)(width - (sourceWidth * ratio)); - } - } - else - { - switch (anchorPosition) - { - case AnchorPosition.Left: - case AnchorPosition.TopLeft: - case AnchorPosition.BottomLeft: - destinationX = 0; - break; - case AnchorPosition.Right: - case AnchorPosition.TopRight: - case AnchorPosition.BottomRight: - destinationX = (int)(width - (sourceWidth * ratio)); - break; - default: - destinationX = (int)((width - (sourceWidth * ratio)) / 2); - break; - } - } - - destinationWidth = (int)Math.Ceiling(sourceWidth * percentHeight); - } - } + (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(source.Size, this.ResizeLayer, targetWidth, targetHeight); - // Constrain the image to fit the maximum possible height or width. - if (resizeMode == ResizeMode.Max) - { - // If either is 0, we don't need to figure out orientation - if (width > 0 && height > 0) - { - // Integers must be cast to doubles to get needed precision - double ratio = (double)height / width; - double sourceRatio = (double)sourceHeight / sourceWidth; - - if (sourceRatio < ratio) - { - height = 0; - } - else - { - width = 0; - } - } - } + int sourceWidth = source.Width; + int sourceHeight = source.Height; + int width = size.Width; + int height = size.Height; + bool upscale = this.ResizeLayer.Upscale; - // Resize the image until the shortest side reaches the set given dimension. - if (resizeMode == ResizeMode.Min) + if (ResizeLayer.ResizeMode == ResizeMode.Min) { - height = height > 0 ? height : Convert.ToInt32(sourceHeight * percentWidth); - width = width > 0 ? width : Convert.ToInt32(sourceWidth * percentHeight); - - double sourceRatio = (double)sourceHeight / sourceWidth; - // Ensure we can't upscale. maxHeight = sourceHeight; maxWidth = sourceWidth; upscale = false; - - // Find the shortest distance to go. - int widthDiff = sourceWidth - width; - int heightDiff = sourceHeight - height; - - if (widthDiff < heightDiff) - { - destinationHeight = Convert.ToInt32(width * sourceRatio); - height = destinationHeight; - destinationWidth = width; - } - else if (widthDiff > heightDiff) - { - destinationHeight = height; - destinationWidth = Convert.ToInt32(height / sourceRatio); - width = destinationWidth; - } - else - { - if (height > width) - { - destinationHeight = Convert.ToInt32(sourceHeight * percentWidth); - height = destinationHeight; - } - else if (width > height) - { - destinationWidth = Convert.ToInt32(sourceWidth * percentHeight); - width = destinationWidth; - } - else - { - destinationWidth = width; - destinationHeight = height; - } - } } - // If height or width is not passed we assume that the standard ratio is to be kept. - if (height == 0) - { - destinationHeight = Convert.ToInt32(sourceHeight * percentWidth); - height = destinationHeight; - } - - if (width == 0) - { - destinationWidth = Convert.ToInt32(sourceWidth * percentHeight); - width = destinationWidth; - } + maxWidth = maxWidth > 0 ? maxWidth : int.MaxValue; + maxHeight = maxHeight > 0 ? maxHeight : int.MaxValue; + List restrictedSizes = this.ResizeLayer.RestrictedSizes; // Restrict sizes if (restrictedSizes?.Count > 0) @@ -560,15 +141,15 @@ private Bitmap ResizeImage( if (width > 0 && height > 0 && width <= maxWidth && height <= maxHeight) { // Exit if upscaling is not allowed. - if ((width > sourceWidth || height > sourceHeight) && !upscale && resizeMode != ResizeMode.Stretch) + if ((width > sourceWidth || height > sourceHeight) + && !upscale + && this.ResizeLayer.ResizeMode != ResizeMode.Stretch) { return (Bitmap)source; } - // Do the resize. - var destination = new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); - - newImage = linear ? this.ResizeLinear(source, width, height, destination, this.AnimationProcessMode) : this.ResizeComposite(source, width, height, destination); + newImage = linear ? this.ResizeLinear(source, width, height, rectangle, this.AnimationProcessMode) + : this.ResizeComposite(source, width, height, rectangle); // Reassign the image. source.Dispose(); @@ -584,5 +165,103 @@ private Bitmap ResizeImage( return (Bitmap)source; } + + /// + /// Gets an image resized using the composite color space without any gamma correction adjustments. + /// + /// The source image. + /// The target width to resize to. + /// The target height to resize to. + /// The destination rectangle. + /// + /// The . + /// + protected virtual Bitmap ResizeComposite(Image source, int width, int height, Rectangle destination) + { + var resized = new Bitmap(width, height, source.PixelFormat); + resized.SetResolution(source.HorizontalResolution, source.VerticalResolution); + + using (var graphics = Graphics.FromImage(resized)) + { + GraphicsHelper.SetGraphicsOptions(graphics); + using (var attributes = new ImageAttributes()) + { + attributes.SetWrapMode(WrapMode.TileFlipXY); + graphics.DrawImage( + source, + destination, + 0, + 0, + source.Width, + source.Height, + GraphicsUnit.Pixel, + attributes); + } + } + + return resized; + } + + /// + /// Gets an image resized using the linear color space with gamma correction adjustments. + /// + /// The source image. + /// The width to resize to. + /// The height to resize to. + /// The destination rectangle. + /// + /// The . + /// + protected virtual Bitmap ResizeLinear(Image source, int targetWidth, int targetHeight, Rectangle destination) + => this.ResizeLinear(source, targetWidth, targetHeight, destination, this.AnimationProcessMode); + + /// + /// Gets an image resized using the linear color space with gamma correction adjustments. + /// + /// The source image. + /// The width to resize to. + /// The height to resize to. + /// The destination rectangle. + /// The process mode for frames in animated images. + /// + /// The . + /// + protected virtual Bitmap ResizeLinear( + Image source, + int width, + int height, + Rectangle destination, + AnimationProcessMode animationProcessMode) + { + // Adjust the gamma value so that the image is in the linear color space. + Bitmap linear = Adjustments.ToLinear(source.Copy(animationProcessMode)); + + var resized = new Bitmap(width, height, source.PixelFormat); + resized.SetResolution(source.HorizontalResolution, source.VerticalResolution); + + using (var graphics = Graphics.FromImage(resized)) + { + GraphicsHelper.SetGraphicsOptions(graphics); + using (var attributes = new ImageAttributes()) + { + attributes.SetWrapMode(WrapMode.TileFlipXY); + graphics.DrawImage( + linear, + destination, + 0, + 0, + source.Width, + source.Height, + GraphicsUnit.Pixel, + attributes); + } + } + + // Return to composite color space. + resized = Adjustments.ToSRGB(resized); + + linear.Dispose(); + return resized; + } } } diff --git a/src/ImageProcessor/Processors/Resize.cs b/src/ImageProcessor/Processors/Resize.cs index ae53745fc..6f896c314 100644 --- a/src/ImageProcessor/Processors/Resize.cs +++ b/src/ImageProcessor/Processors/Resize.cs @@ -77,7 +77,7 @@ public Image ProcessImage(ImageFactory factory) // Augment the layer with the extra information. resizeLayer.RestrictedSizes = this.RestrictedSizes; - var maxSize = new Size(); + Size maxSize = default; int.TryParse(this.Settings["MaxWidth"], NumberStyles.Any, CultureInfo.InvariantCulture, out int maxWidth); int.TryParse(this.Settings["MaxHeight"], NumberStyles.Any, CultureInfo.InvariantCulture, out int maxHeight); diff --git a/tests/ImageProcessor.Playground/Program.cs b/tests/ImageProcessor.Playground/Program.cs index 225f798cd..74e59d8eb 100644 --- a/tests/ImageProcessor.Playground/Program.cs +++ b/tests/ImageProcessor.Playground/Program.cs @@ -68,7 +68,8 @@ public static void Main(string[] args) try { imageFactory.Load(inStream) - .Crop(new Rectangle(0, 0, imageFactory.Image.Width / 2, imageFactory.Image.Height / 2)) + .Resize(new Size(426, 0)) + //.Crop(new Rectangle(0, 0, imageFactory.Image.Width / 2, imageFactory.Image.Height / 2)) .Save(Path.GetFullPath(Path.Combine(outPath, fileInfo.Name))); } catch (Exception ex) diff --git a/tests/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs b/tests/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs index 31e5fc607..049a87497 100644 --- a/tests/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs +++ b/tests/ImageProcessor.UnitTests/ImageFactoryUnitTests.cs @@ -1008,7 +1008,7 @@ public void ResizeIsApplied() // Check we min correctly using the shortest size. imageFactory.Resize(minLayer); Assert.AreEqual(imageFactory.Image.Size, new Size(400, 300)); - imageFactory.Save(OutputPath + "resize-crop-" + i + ".jpg"); + imageFactory.Save(OutputPath + "resize-min-" + i + ".jpg"); // Check padding with only a single dimension specified (width) imageFactory.Resize(paddedSingleDimensionWidthLayer); diff --git a/tests/ImageProcessor.UnitTests/Images/792.jpeg b/tests/ImageProcessor.UnitTests/Images/792.jpeg new file mode 100644 index 000000000..4e0112eb9 Binary files /dev/null and b/tests/ImageProcessor.UnitTests/Images/792.jpeg differ diff --git a/tests/ImageProcessor.Web.UnitTests/RegularExpressionUnitTests.cs b/tests/ImageProcessor.Web.UnitTests/RegularExpressionUnitTests.cs index ca47f7782..2a0f83e5e 100644 --- a/tests/ImageProcessor.Web.UnitTests/RegularExpressionUnitTests.cs +++ b/tests/ImageProcessor.Web.UnitTests/RegularExpressionUnitTests.cs @@ -273,30 +273,18 @@ public void TestResizeRegex() { Dictionary data = new Dictionary { - { - "width=300", new ResizeLayer(new Size(300, 0)) - }, - { - "height=300", new ResizeLayer(new Size(0, 300)) - }, - { - "height=300.6", new ResizeLayer(new Size(0, 301)) - }, - { - "height=300&mode=crop", new ResizeLayer(new Size(0, 300), ResizeMode.Crop) - }, - { - "width=300&mode=crop", new ResizeLayer(new Size(300, 0), ResizeMode.Crop) - }, - { - "width=300.2&mode=crop", new ResizeLayer(new Size(300, 0), ResizeMode.Crop) - }, - { - "width=600&heightratio=0.416", new ResizeLayer(new Size(600, 250)) - }, - { - "width=600&height=250&mode=max", new ResizeLayer(new Size(600, 250), ResizeMode.Max) - } + { "width=300", new ResizeLayer(new Size(300, 0)) }, + { "height=300", new ResizeLayer(new Size(0, 300)) }, + { "height=300.6", new ResizeLayer(new Size(0, 301)) }, // Round size units + { "height=300&mode=crop", new ResizeLayer(new Size(0, 300), ResizeMode.Crop) }, + { "width=300&mode=crop", new ResizeLayer(new Size(300, 0), ResizeMode.Crop) }, + { "width=300.2&mode=crop", new ResizeLayer(new Size(300, 0), ResizeMode.Crop) }, // Round size units + { "width=600&heightratio=0.416", new ResizeLayer(new Size(600, 250)) }, + { "width=600&height=250&mode=max", new ResizeLayer(new Size(600, 250), ResizeMode.Max) }, + { "width=600&height=250¢er=1,0.5", new ResizeLayer(new Size(600, 250), centerCoordinates: new [] { 1f, 0.5f }) }, // Center coordinates (Y,X) + { "width=600&height=250¢er=0.5,0.25", new ResizeLayer(new Size(600, 250)) { Center = new PointF(0.25f, 0.5f) } }, // Center coordinates (Y,X) to PointF + { "width=600&height=250¢er=0.5", new ResizeLayer(new Size(600, 250)) }, // Invalid center coordinates should not result in 0,0 + { "width=600&height=250¢er=y,x", new ResizeLayer(new Size(600, 250)) } // Invalid center coordinates should not result in 0,0 }; Processors.Resize resize = new Processors.Resize();