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();