From a8dd90d9d61538edb5069491afeaa7662f338407 Mon Sep 17 00:00:00 2001 From: Mole Date: Thu, 8 Jul 2021 13:48:44 +0200 Subject: [PATCH 01/36] Add CropWebProcessor --- .../UmbracoBuilder.ImageSharp.cs | 3 + .../ImageProcessors/CropWebProcessor.cs | 78 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs index 96bf7017cb..775d36eaf5 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs @@ -10,6 +10,7 @@ using SixLabors.ImageSharp.Web.Processors; using SixLabors.ImageSharp.Web.Providers; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Web.Common.ImageProcessors; namespace Umbraco.Extensions { @@ -51,6 +52,8 @@ namespace Umbraco.Extensions .SetCache() .SetCacheHash() .AddProvider() + .ClearProcessors() // ImageSharp includes the processors by default, so we have to clear and re-add to control the order + .AddProcessor() .AddProcessor() .AddProcessor() .AddProcessor(); diff --git a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs new file mode 100644 index 0000000000..6393e9455f --- /dev/null +++ b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs @@ -0,0 +1,78 @@ +using System.Collections.Generic; +using System.Globalization; +using Microsoft.Extensions.Logging; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Web; +using SixLabors.ImageSharp.Web.Commands; +using SixLabors.ImageSharp.Web.Processors; +using ImageCropperCropCoordinates = Umbraco.Cms.Core.PropertyEditors.ValueConverters.ImageCropperValue.ImageCropperCropCoordinates; + +namespace Umbraco.Cms.Web.Common.ImageProcessors +{ + public class CropWebProcessor : IImageWebProcessor + { + /// + /// The command constant for the crop definition + /// + public const string Crop = "crop"; + + public FormattedImage Process( + FormattedImage image, + ILogger logger, + IDictionary commands, + CommandParser parser, + CultureInfo culture) + { + ImageCropperCropCoordinates cropCoordinates = GetCropCoordinates(commands); + if (cropCoordinates is null) + { + return image; + } + + Size size = image.Image.Size(); + Rectangle crop = GetCropRectangle(size.Width, size.Height, cropCoordinates); + image.Image.Mutate(x => x.Crop(crop)); + return image; + } + + private static readonly IEnumerable CropCommands = new[] {Crop}; + + public IEnumerable Commands { get; } = CropCommands; + + private static Rectangle GetCropRectangle(int width, int height, ImageCropperCropCoordinates coordinates) + { + + // Get coordinates of top left corner of the rectangle + var topX = decimal.ToInt32(width * coordinates.X1); + var topY = decimal.ToInt32(height * coordinates.Y1); + // Get coordinated of bottom right corner + var bottomX = decimal.ToInt32(width - (width * coordinates.X2)); + var bottomY = decimal.ToInt32(height - (height * coordinates.Y2)); + + // Get width and height of crop + var cropWidth = bottomX - topX; + var cropHeight = bottomY - topY; + + return new Rectangle(topX, topY, cropWidth, cropHeight); + } + + private static ImageCropperCropCoordinates GetCropCoordinates(IDictionary commands) + { + if (!commands.TryGetValue(Crop, out var crop)) + { + return null; + } + + var crops = crop.Split(','); + + return new ImageCropperCropCoordinates() + { + X1 = decimal.Parse(crops[0], CultureInfo.InvariantCulture), + Y1 = decimal.Parse(crops[1], CultureInfo.InvariantCulture), + X2 = decimal.Parse(crops[2], CultureInfo.InvariantCulture), + Y2 = decimal.Parse(crops[3], CultureInfo.InvariantCulture) + }; + } + } +} From 865a5b999ca30d036329d16a38477f8237c0ad4e Mon Sep 17 00:00:00 2001 From: Mole Date: Thu, 8 Jul 2021 14:08:06 +0200 Subject: [PATCH 02/36] Add ParseDecimal method --- .../ImageProcessors/CropWebProcessor.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs index 6393e9455f..e06f93a806 100644 --- a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs +++ b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs @@ -42,7 +42,6 @@ namespace Umbraco.Cms.Web.Common.ImageProcessors private static Rectangle GetCropRectangle(int width, int height, ImageCropperCropCoordinates coordinates) { - // Get coordinates of top left corner of the rectangle var topX = decimal.ToInt32(width * coordinates.X1); var topY = decimal.ToInt32(height * coordinates.Y1); @@ -68,11 +67,13 @@ namespace Umbraco.Cms.Web.Common.ImageProcessors return new ImageCropperCropCoordinates() { - X1 = decimal.Parse(crops[0], CultureInfo.InvariantCulture), - Y1 = decimal.Parse(crops[1], CultureInfo.InvariantCulture), - X2 = decimal.Parse(crops[2], CultureInfo.InvariantCulture), - Y2 = decimal.Parse(crops[3], CultureInfo.InvariantCulture) + X1 = ParseDecimal(crops[0]), + Y1 = ParseDecimal(crops[1]), + X2 = ParseDecimal(crops[2]), + Y2 = ParseDecimal(crops[3]) }; } + + private static decimal ParseDecimal(string decimalString) => decimal.Parse(decimalString, CultureInfo.InvariantCulture); } } From 753f6f6b1d30db9c704725d2c5417ed4561aa97a Mon Sep 17 00:00:00 2001 From: Mole Date: Thu, 8 Jul 2021 14:11:30 +0200 Subject: [PATCH 03/36] Clean url generator a bit --- .../Media/ImageSharpImageUrlGenerator.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs index e1bf2c197b..0d5f5202b0 100644 --- a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs +++ b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs @@ -49,10 +49,9 @@ namespace Umbraco.Cms.Infrastructure.Media private void AppendFocalPoint(StringBuilder imageProcessorUrl, ImageUrlGenerationOptions options) { - imageProcessorUrl.Append("?center="); + imageProcessorUrl.Append("?rxy="); imageProcessorUrl.Append(options.FocalPoint.Top.ToString(CultureInfo.InvariantCulture)).Append(","); imageProcessorUrl.Append(options.FocalPoint.Left.ToString(CultureInfo.InvariantCulture)); - imageProcessorUrl.Append("&mode=crop"); } private void AppendCrop(StringBuilder imageProcessorUrl, ImageUrlGenerationOptions options) @@ -62,7 +61,6 @@ namespace Umbraco.Cms.Infrastructure.Media imageProcessorUrl.Append(options.Crop.Y1.ToString(CultureInfo.InvariantCulture)).Append(","); imageProcessorUrl.Append(options.Crop.X2.ToString(CultureInfo.InvariantCulture)).Append(","); imageProcessorUrl.Append(options.Crop.Y2.ToString(CultureInfo.InvariantCulture)); - imageProcessorUrl.Append("&cropmode=percentage"); } } } From d7795cf316929779f4508f3a4a9471c3e8420767 Mon Sep 17 00:00:00 2001 From: Mole Date: Thu, 8 Jul 2021 15:53:48 +0200 Subject: [PATCH 04/36] Fix unit tests --- .../Media/ImageSharpImageUrlGeneratorTests.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs index 52ee1ed0e4..fcf466411d 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs @@ -20,28 +20,28 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media public void GetCropUrl_CropAliasTest() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Crop = s_crop, Width = 100, Height = 100 }); - Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); + Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&width=100&height=100", urlString); } [Test] public void GetCropUrl_WidthHeightTest() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus1, Width = 200, Height = 300 }); - Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=200&height=300", urlString); + Assert.AreEqual(MediaPath + "?rxy=0.80827067669172936,0.96&width=200&height=300", urlString); } [Test] public void GetCropUrl_FocalPointTest() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus1, Width = 100, Height = 100 }); - Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=100&height=100", urlString); + Assert.AreEqual(MediaPath + "?rxy=0.80827067669172936,0.96&width=100&height=100", urlString); } [Test] public void GetCropUrlFurtherOptionsTest() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus1, Width = 200, Height = 300, FurtherOptions = "&filter=comic&roundedcorners=radius-26|bgcolor-fff" }); - Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=200&height=300&filter=comic&roundedcorners=radius-26|bgcolor-fff", urlString); + Assert.AreEqual(MediaPath + "?rxy=0.80827067669172936,0.96&width=200&height=300&filter=comic&roundedcorners=radius-26|bgcolor-fff", urlString); } /// @@ -71,7 +71,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media public void GetBaseCropUrlFromModelTest() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(null) { Crop = s_crop, Width = 100, Height = 100 }); - Assert.AreEqual("?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); + Assert.AreEqual("?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&width=100&height=100", urlString); } /// @@ -81,7 +81,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media public void GetCropUrl_CropAliasHeightRatioModeTest() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Crop = s_crop, Width = 100, HeightRatio = 1 }); - Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&heightratio=1&width=100", urlString); + Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&heightratio=1&width=100", urlString); } /// @@ -91,7 +91,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media public void GetCropUrl_WidthHeightRatioModeTest() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus1, Width = 300, HeightRatio = 0.5m }); - Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&heightratio=0.5&width=300", urlString); + Assert.AreEqual(MediaPath + "?rxy=0.80827067669172936,0.96&heightratio=0.5&width=300", urlString); } /// @@ -101,7 +101,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media public void GetCropUrl_HeightWidthRatioModeTest() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus1, Height = 150, WidthRatio = 2 }); - Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&widthratio=2&height=150", urlString); + Assert.AreEqual(MediaPath + "?rxy=0.80827067669172936,0.96&widthratio=2&height=150", urlString); } /// @@ -160,7 +160,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPoint() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus2, Width = 200, HeightRatio = 0.5962962962962962962962962963m }); - Assert.AreEqual(MediaPath + "?center=0.41,0.4275&mode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); + Assert.AreEqual(MediaPath + "?rxy=0.41,0.4275&heightratio=0.5962962962962962962962962963&width=200", urlString); } /// @@ -170,7 +170,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPointIgnore() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus2, Width = 270, Height = 161 }); - Assert.AreEqual(MediaPath + "?center=0.41,0.4275&mode=crop&width=270&height=161", urlString); + Assert.AreEqual(MediaPath + "?rxy=0.41,0.4275&width=270&height=161", urlString); } /// From 6376a32131283dbd40a0f3bcfa5a1ec8769a2207 Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 9 Jul 2021 08:04:20 +0200 Subject: [PATCH 05/36] Use CommandParser to parse commands Co-authored-by: Ronald Barendse --- .../ImageProcessors/CropWebProcessor.cs | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs index e06f93a806..9f8a1139e6 100644 --- a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs +++ b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs @@ -24,7 +24,7 @@ namespace Umbraco.Cms.Web.Common.ImageProcessors CommandParser parser, CultureInfo culture) { - ImageCropperCropCoordinates cropCoordinates = GetCropCoordinates(commands); + ImageCropperCropCoordinates cropCoordinates = GetCropCoordinates(commands, parser, culture); if (cropCoordinates is null) { return image; @@ -56,24 +56,22 @@ namespace Umbraco.Cms.Web.Common.ImageProcessors return new Rectangle(topX, topY, cropWidth, cropHeight); } - private static ImageCropperCropCoordinates GetCropCoordinates(IDictionary commands) + private static ImageCropperCropCoordinates GetCropCoordinates(IDictionary commands, CommandParser parser, CultureInfo culture) { - if (!commands.TryGetValue(Crop, out var crop)) + float[] crops = parser.ParseValue(commands.GetValueOrDefault(Crop), culture); + + if (crops.Length != 4) { return null; } - var crops = crop.Split(','); - return new ImageCropperCropCoordinates() { - X1 = ParseDecimal(crops[0]), - Y1 = ParseDecimal(crops[1]), - X2 = ParseDecimal(crops[2]), - Y2 = ParseDecimal(crops[3]) + X1 = crops[0], + Y1 = crops[1], + X2 = crops[2], + Y2 = crops[3] }; } - - private static decimal ParseDecimal(string decimalString) => decimal.Parse(decimalString, CultureInfo.InvariantCulture); } } From ba2e4cd760fd112a3f65d088ac2c0db94022a54a Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 9 Jul 2021 08:08:41 +0200 Subject: [PATCH 06/36] Use decimal instead of float for coordinates --- src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs index 9f8a1139e6..83c90d21f4 100644 --- a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs +++ b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs @@ -58,8 +58,8 @@ namespace Umbraco.Cms.Web.Common.ImageProcessors private static ImageCropperCropCoordinates GetCropCoordinates(IDictionary commands, CommandParser parser, CultureInfo culture) { - float[] crops = parser.ParseValue(commands.GetValueOrDefault(Crop), culture); - + decimal[] crops = parser.ParseValue(commands.GetValueOrDefault(Crop), culture); + if (crops.Length != 4) { return null; From 1814b70bafd8389bcf505d6d71f1506d055b9feb Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 9 Jul 2021 08:13:49 +0200 Subject: [PATCH 07/36] Only remove the resize processor --- .../DependencyInjection/UmbracoBuilder.ImageSharp.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs index 775d36eaf5..688e18fd90 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs @@ -52,7 +52,7 @@ namespace Umbraco.Extensions .SetCache() .SetCacheHash() .AddProvider() - .ClearProcessors() // ImageSharp includes the processors by default, so we have to clear and re-add to control the order + .RemoveProcessor() // The Resize processor is added by default, so remove it to ensure that the crop processor runs first .AddProcessor() .AddProcessor() .AddProcessor() From cacef54ef77bdfe725aafc7af64a02048e51a2e7 Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 9 Jul 2021 09:04:07 +0200 Subject: [PATCH 08/36] Round to int instead of just converting --- .../ImageProcessors/CropWebProcessor.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs index 83c90d21f4..fec27a8591 100644 --- a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs +++ b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Globalization; using Microsoft.Extensions.Logging; @@ -43,11 +44,11 @@ namespace Umbraco.Cms.Web.Common.ImageProcessors private static Rectangle GetCropRectangle(int width, int height, ImageCropperCropCoordinates coordinates) { // Get coordinates of top left corner of the rectangle - var topX = decimal.ToInt32(width * coordinates.X1); - var topY = decimal.ToInt32(height * coordinates.Y1); + var topX = RoundToInt(width * coordinates.X1); + var topY = RoundToInt(height * coordinates.Y1); // Get coordinated of bottom right corner - var bottomX = decimal.ToInt32(width - (width * coordinates.X2)); - var bottomY = decimal.ToInt32(height - (height * coordinates.Y2)); + var bottomX = RoundToInt(width - (width * coordinates.X2)); + var bottomY = RoundToInt(height - (height * coordinates.Y2)); // Get width and height of crop var cropWidth = bottomX - topX; @@ -56,6 +57,8 @@ namespace Umbraco.Cms.Web.Common.ImageProcessors return new Rectangle(topX, topY, cropWidth, cropHeight); } + private static int RoundToInt(decimal number) => decimal.ToInt32(Math.Round(number)); + private static ImageCropperCropCoordinates GetCropCoordinates(IDictionary commands, CommandParser parser, CultureInfo culture) { decimal[] crops = parser.ParseValue(commands.GetValueOrDefault(Crop), culture); From c93fe0d2e77f134e160b33cef735a6cecad13dc6 Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 9 Jul 2021 09:08:26 +0200 Subject: [PATCH 09/36] Add method descriptions --- .../ImageProcessors/CropWebProcessor.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs index fec27a8591..3fe4db8344 100644 --- a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs +++ b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs @@ -41,6 +41,13 @@ namespace Umbraco.Cms.Web.Common.ImageProcessors public IEnumerable Commands { get; } = CropCommands; + /// + /// Gets a rectangle that is calculated from crop coordinates. + /// + /// The width of the image being cropped. + /// The height of the image being cropped. + /// Coordinate set to calculate rectangle from. + /// Rectangle with the position and sized described in coordinates. private static Rectangle GetCropRectangle(int width, int height, ImageCropperCropCoordinates coordinates) { // Get coordinates of top left corner of the rectangle @@ -57,8 +64,20 @@ namespace Umbraco.Cms.Web.Common.ImageProcessors return new Rectangle(topX, topY, cropWidth, cropHeight); } + /// + /// Converts a decimal to an int with rounding. + /// + /// Decimal to convert. + /// The decimal rounded to an int. private static int RoundToInt(decimal number) => decimal.ToInt32(Math.Round(number)); + /// + /// Gets the crop coordinates from the query string. + /// + /// Commands dictionary to parse the coordinates from. + /// Parser provided by imagesharp. + /// Culture to use for parsing. + /// Coordinates of the crop. private static ImageCropperCropCoordinates GetCropCoordinates(IDictionary commands, CommandParser parser, CultureInfo culture) { decimal[] crops = parser.ParseValue(commands.GetValueOrDefault(Crop), culture); From c255729be00a80737ebecde790eb2fa98c57cc1b Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 9 Jul 2021 09:26:19 +0200 Subject: [PATCH 10/36] Change back to ClearProcessors --- .../DependencyInjection/UmbracoBuilder.ImageSharp.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs index 688e18fd90..368d6f5116 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs @@ -51,7 +51,7 @@ namespace Umbraco.Extensions }) .SetCache() .SetCacheHash() - .AddProvider() + .ClearProcessors() .RemoveProcessor() // The Resize processor is added by default, so remove it to ensure that the crop processor runs first .AddProcessor() .AddProcessor() From ca5af1e9a7bcef3aead07f486fa8c6b25cfce3fb Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 6 Aug 2021 12:56:49 +0200 Subject: [PATCH 11/36] Only add our custom CropWebProcessor to ImageSharp --- .../DependencyInjection/UmbracoBuilder.ImageSharp.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs index 368d6f5116..f6bb54ac98 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs @@ -51,12 +51,7 @@ namespace Umbraco.Extensions }) .SetCache() .SetCacheHash() - .ClearProcessors() - .RemoveProcessor() // The Resize processor is added by default, so remove it to ensure that the crop processor runs first - .AddProcessor() - .AddProcessor() - .AddProcessor() - .AddProcessor(); + .AddProcessor(); return services; } From 20007db7f2d2a99d085b8fc2fd90f915db65007a Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 6 Aug 2021 12:59:23 +0200 Subject: [PATCH 12/36] Add cropmode support and align logic to ImageProcessors Crop processor --- .../ImageProcessors/CropWebProcessor.cs | 137 +++++++++--------- 1 file changed, 72 insertions(+), 65 deletions(-) diff --git a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs index 3fe4db8344..d5e2adb2e5 100644 --- a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs +++ b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Globalization; using Microsoft.Extensions.Logging; @@ -7,93 +6,101 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Web; using SixLabors.ImageSharp.Web.Commands; using SixLabors.ImageSharp.Web.Processors; -using ImageCropperCropCoordinates = Umbraco.Cms.Core.PropertyEditors.ValueConverters.ImageCropperValue.ImageCropperCropCoordinates; namespace Umbraco.Cms.Web.Common.ImageProcessors { + /// + /// Allows the cropping of images. + /// public class CropWebProcessor : IImageWebProcessor { /// - /// The command constant for the crop definition + /// The command constant for the crop definition. /// public const string Crop = "crop"; - public FormattedImage Process( - FormattedImage image, - ILogger logger, - IDictionary commands, - CommandParser parser, - CultureInfo culture) + /// + /// The command constant for the crop mode. + /// + public const string Mode = "cropmode"; + + + /// + public IEnumerable Commands { get; } = new[] { - ImageCropperCropCoordinates cropCoordinates = GetCropCoordinates(commands, parser, culture); - if (cropCoordinates is null) + Crop, + Mode + }; + + /// + public FormattedImage Process(FormattedImage image, ILogger logger, IDictionary commands, CommandParser parser, CultureInfo culture) + { + if (GetCrop(commands, parser, culture) is RectangleF crop) { - return image; + Size size = image.Image.Size(); + + // Convert from percentages to pixels based on crop mode + if (GetMode(commands, parser, culture) is CropMode.Percentage) + { + // Fix for whole numbers + float percentageLeft = crop.Left > 1 ? crop.Left / 100 : crop.Left; + float percentageRight = crop.Right > 1 ? crop.Right / 100 : crop.Right; + float percentageTop = crop.Top > 1 ? crop.Top / 100 : crop.Top; + float percentageBottom = crop.Bottom > 1 ? crop.Bottom / 100 : crop.Bottom; + + // Work out the percentages + float left = percentageLeft * size.Width; + float top = percentageTop * size.Height; + float width = percentageRight < 1 ? (1 - percentageLeft - percentageRight) * size.Width : size.Width; + float height = percentageBottom < 1 ? (1 - percentageTop - percentageBottom) * size.Height : size.Height; + + crop = new RectangleF(left, top, width, height); + } + + // Round and validate crop rectangle + var rectangle = Rectangle.Round(crop); + if (rectangle.X < size.Width && rectangle.Y < size.Height) + { + if (rectangle.Width > (size.Width - rectangle.X)) + { + rectangle.Width = size.Width - rectangle.X; + } + + if (rectangle.Height > (size.Height - rectangle.Y)) + { + rectangle.Height = size.Height - rectangle.Y; + } + + image.Image.Mutate(x => x.Crop(rectangle)); + } } - Size size = image.Image.Size(); - Rectangle crop = GetCropRectangle(size.Width, size.Height, cropCoordinates); - image.Image.Mutate(x => x.Crop(crop)); return image; } - private static readonly IEnumerable CropCommands = new[] {Crop}; - - public IEnumerable Commands { get; } = CropCommands; - - /// - /// Gets a rectangle that is calculated from crop coordinates. - /// - /// The width of the image being cropped. - /// The height of the image being cropped. - /// Coordinate set to calculate rectangle from. - /// Rectangle with the position and sized described in coordinates. - private static Rectangle GetCropRectangle(int width, int height, ImageCropperCropCoordinates coordinates) + private static RectangleF? GetCrop(IDictionary commands, CommandParser parser, CultureInfo culture) { - // Get coordinates of top left corner of the rectangle - var topX = RoundToInt(width * coordinates.X1); - var topY = RoundToInt(height * coordinates.Y1); - // Get coordinated of bottom right corner - var bottomX = RoundToInt(width - (width * coordinates.X2)); - var bottomY = RoundToInt(height - (height * coordinates.Y2)); + float[] crops = parser.ParseValue(commands.GetValueOrDefault(Crop), culture); - // Get width and height of crop - var cropWidth = bottomX - topX; - var cropHeight = bottomY - topY; - - return new Rectangle(topX, topY, cropWidth, cropHeight); + return (crops.Length != 4) ? null : new RectangleF(crops[0], crops[1], crops[2], crops[3]); } + private static CropMode GetMode(IDictionary commands, CommandParser parser, CultureInfo culture) + => parser.ParseValue(commands.GetValueOrDefault(Mode), culture); + } + + /// + /// Enumerated cop modes to apply to cropped images. + /// + public enum CropMode + { /// - /// Converts a decimal to an int with rounding. + /// Crops the image using the standard rectangle model of x, y, width, height. /// - /// Decimal to convert. - /// The decimal rounded to an int. - private static int RoundToInt(decimal number) => decimal.ToInt32(Math.Round(number)); - + Pixels, /// - /// Gets the crop coordinates from the query string. + /// Crops the image using percentages model left, top, right, bottom. /// - /// Commands dictionary to parse the coordinates from. - /// Parser provided by imagesharp. - /// Culture to use for parsing. - /// Coordinates of the crop. - private static ImageCropperCropCoordinates GetCropCoordinates(IDictionary commands, CommandParser parser, CultureInfo culture) - { - decimal[] crops = parser.ParseValue(commands.GetValueOrDefault(Crop), culture); - - if (crops.Length != 4) - { - return null; - } - - return new ImageCropperCropCoordinates() - { - X1 = crops[0], - Y1 = crops[1], - X2 = crops[2], - Y2 = crops[3] - }; - } + Percentage } } From 760c15e5ce3a57d6a12a09aab3632fc34ed3a09a Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 6 Aug 2021 12:59:56 +0200 Subject: [PATCH 13/36] Add cropmode paramerter back to tests --- .../Media/ImageSharpImageUrlGenerator.cs | 3 ++- .../Media/ImageSharpImageUrlGeneratorTests.cs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs index 0d5f5202b0..084dee0177 100644 --- a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs +++ b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Globalization; using System.Text; using Umbraco.Cms.Core.Media; @@ -61,6 +61,7 @@ namespace Umbraco.Cms.Infrastructure.Media imageProcessorUrl.Append(options.Crop.Y1.ToString(CultureInfo.InvariantCulture)).Append(","); imageProcessorUrl.Append(options.Crop.X2.ToString(CultureInfo.InvariantCulture)).Append(","); imageProcessorUrl.Append(options.Crop.Y2.ToString(CultureInfo.InvariantCulture)); + imageProcessorUrl.Append("&cropmode=percentage"); } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs index fcf466411d..db46b6a827 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs @@ -20,7 +20,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media public void GetCropUrl_CropAliasTest() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Crop = s_crop, Width = 100, Height = 100 }); - Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&width=100&height=100", urlString); + Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); } [Test] @@ -71,7 +71,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media public void GetBaseCropUrlFromModelTest() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(null) { Crop = s_crop, Width = 100, Height = 100 }); - Assert.AreEqual("?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&width=100&height=100", urlString); + Assert.AreEqual("?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); } /// @@ -81,7 +81,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media public void GetCropUrl_CropAliasHeightRatioModeTest() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Crop = s_crop, Width = 100, HeightRatio = 1 }); - Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&heightratio=1&width=100", urlString); + Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&heightratio=1&width=100", urlString); } /// From 4b85edd1d91db27f65bcf4db790780c9a6758c32 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 6 Aug 2021 14:22:27 +0200 Subject: [PATCH 14/36] Update ImageSharpImageUrlGenerator --- .../Media/ImageSharpImageUrlGenerator.cs | 117 ++++++++++++------ .../UmbracoBuilder.ImageSharp.cs | 2 - 2 files changed, 80 insertions(+), 39 deletions(-) diff --git a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs index 084dee0177..d556b0f314 100644 --- a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs +++ b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs @@ -9,59 +9,102 @@ namespace Umbraco.Cms.Infrastructure.Media { public class ImageSharpImageUrlGenerator : IImageUrlGenerator { - public IEnumerable SupportedImageFileTypes => new[] { "jpeg", "jpg", "gif", "bmp", "png" }; + public IEnumerable SupportedImageFileTypes => new[] + { + "jpeg", + "jpg", + "gif", + "bmp", + "png" + }; public string GetImageUrl(ImageUrlGenerationOptions options) { - if (options == null) return null; + if (options == null) + { + return null; + } - var imageProcessorUrl = new StringBuilder(options.ImageUrl ?? string.Empty); + var imageUrl = new StringBuilder(options.ImageUrl); - if (options.FocalPoint != null) AppendFocalPoint(imageProcessorUrl, options); - else if (options.Crop != null) AppendCrop(imageProcessorUrl, options); - else if (options.DefaultCrop) imageProcessorUrl.Append("?anchor=center&mode=crop"); + if (options.FocalPoint != null) + { + imageUrl.AppendFormat(CultureInfo.InvariantCulture, "?rxy={0},{1}", options.FocalPoint.Top, options.FocalPoint.Left); + } + else if (options.Crop != null) + { + imageUrl.AppendFormat(CultureInfo.InvariantCulture, "?crop={0},{1},{2},{3}&cropmode=percentage", options.Crop.X1, options.Crop.Y1, options.Crop.X2, options.Crop.Y2); + } + else if (options.DefaultCrop) + { + imageUrl.Append("?anchor=center&mode=crop"); + } else { - imageProcessorUrl.Append("?mode=").Append((options.ImageCropMode ?? ImageCropMode.Crop).ToString().ToLower()); + imageUrl.Append("?mode=").Append((options.ImageCropMode ?? ImageCropMode.Crop).ToString().ToLowerInvariant()); - if (options.ImageCropAnchor != null) imageProcessorUrl.Append("&anchor=").Append(options.ImageCropAnchor.ToString().ToLower()); + if (options.ImageCropAnchor != null) + { + imageUrl.Append("&anchor=").Append(options.ImageCropAnchor.ToString().ToLowerInvariant()); + } } var hasFormat = options.FurtherOptions != null && options.FurtherOptions.InvariantContains("&format="); - //Only put quality here, if we don't have a format specified. - //Otherwise we need to put quality at the end to avoid it being overridden by the format. - if (options.Quality.HasValue && hasFormat == false) imageProcessorUrl.Append("&quality=").Append(options.Quality); - if (options.HeightRatio.HasValue) imageProcessorUrl.Append("&heightratio=").Append(options.HeightRatio.Value.ToString(CultureInfo.InvariantCulture)); - if (options.WidthRatio.HasValue) imageProcessorUrl.Append("&widthratio=").Append(options.WidthRatio.Value.ToString(CultureInfo.InvariantCulture)); - if (options.Width.HasValue) imageProcessorUrl.Append("&width=").Append(options.Width); - if (options.Height.HasValue) imageProcessorUrl.Append("&height=").Append(options.Height); - if (options.UpScale == false) imageProcessorUrl.Append("&upscale=false"); - if (!string.IsNullOrWhiteSpace(options.AnimationProcessMode)) imageProcessorUrl.Append("&animationprocessmode=").Append(options.AnimationProcessMode); - if (!string.IsNullOrWhiteSpace(options.FurtherOptions)) imageProcessorUrl.Append(options.FurtherOptions); + // Only put quality here, if we don't have a format specified. + // Otherwise we need to put quality at the end to avoid it being overridden by the format. + if (options.Quality.HasValue && hasFormat == false) + { + imageUrl.AppendFormat(CultureInfo.InvariantCulture, "&quality={0}", options.Quality.Value); + } - //If furtherOptions contains a format, we need to put the quality after the format. - if (options.Quality.HasValue && hasFormat) imageProcessorUrl.Append("&quality=").Append(options.Quality); - if (!string.IsNullOrWhiteSpace(options.CacheBusterValue)) imageProcessorUrl.Append("&rnd=").Append(options.CacheBusterValue); + if (options.HeightRatio.HasValue) + { + imageUrl.AppendFormat(CultureInfo.InvariantCulture, "&heightratio={0}", options.HeightRatio.Value); + } - return imageProcessorUrl.ToString(); - } + if (options.WidthRatio.HasValue) + { + imageUrl.AppendFormat(CultureInfo.InvariantCulture, "&widthratio={0}", options.WidthRatio.Value); + } - private void AppendFocalPoint(StringBuilder imageProcessorUrl, ImageUrlGenerationOptions options) - { - imageProcessorUrl.Append("?rxy="); - imageProcessorUrl.Append(options.FocalPoint.Top.ToString(CultureInfo.InvariantCulture)).Append(","); - imageProcessorUrl.Append(options.FocalPoint.Left.ToString(CultureInfo.InvariantCulture)); - } + if (options.Width.HasValue) + { + imageUrl.AppendFormat(CultureInfo.InvariantCulture, "&width={0}", options.Width.Value); + } - private void AppendCrop(StringBuilder imageProcessorUrl, ImageUrlGenerationOptions options) - { - imageProcessorUrl.Append("?crop="); - imageProcessorUrl.Append(options.Crop.X1.ToString(CultureInfo.InvariantCulture)).Append(","); - imageProcessorUrl.Append(options.Crop.Y1.ToString(CultureInfo.InvariantCulture)).Append(","); - imageProcessorUrl.Append(options.Crop.X2.ToString(CultureInfo.InvariantCulture)).Append(","); - imageProcessorUrl.Append(options.Crop.Y2.ToString(CultureInfo.InvariantCulture)); - imageProcessorUrl.Append("&cropmode=percentage"); + if (options.Height.HasValue) + { + imageUrl.AppendFormat(CultureInfo.InvariantCulture, "&height={0}", options.Height.Value); + } + + if (options.UpScale == false) + { + imageUrl.Append("&upscale=false"); + } + + if (!string.IsNullOrWhiteSpace(options.AnimationProcessMode)) + { + imageUrl.Append("&animationprocessmode=").Append(options.AnimationProcessMode); + } + + if (!string.IsNullOrWhiteSpace(options.FurtherOptions)) + { + imageUrl.Append(options.FurtherOptions); + } + + // If furtherOptions contains a format, we need to put the quality after the format. + if (options.Quality.HasValue && hasFormat) + { + imageUrl.AppendFormat(CultureInfo.InvariantCulture, "&quality={0}", options.Quality.Value); + } + + if (!string.IsNullOrWhiteSpace(options.CacheBusterValue)) + { + imageUrl.Append("&rnd=").Append(options.CacheBusterValue); + } + + return imageUrl.ToString(); } } } diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs index f6bb54ac98..3d753975fe 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs @@ -2,12 +2,10 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Web.Caching; using SixLabors.ImageSharp.Web.Commands; using SixLabors.ImageSharp.Web.DependencyInjection; using SixLabors.ImageSharp.Web.Processors; -using SixLabors.ImageSharp.Web.Providers; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Web.Common.ImageProcessors; From 6430b36a226e90a8061c440a0ef5a83c56236a86 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 6 Aug 2021 14:44:16 +0200 Subject: [PATCH 15/36] Update resize mode and anchor query string parameters --- .../Media/ImageSharpImageUrlGenerator.cs | 6 ++--- .../Media/ImageSharpImageUrlGeneratorTests.cs | 26 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs index d556b0f314..d72ee2b4eb 100644 --- a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs +++ b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs @@ -37,15 +37,15 @@ namespace Umbraco.Cms.Infrastructure.Media } else if (options.DefaultCrop) { - imageUrl.Append("?anchor=center&mode=crop"); + imageUrl.Append("?ranchor=center&rmode=crop"); } else { - imageUrl.Append("?mode=").Append((options.ImageCropMode ?? ImageCropMode.Crop).ToString().ToLowerInvariant()); + imageUrl.Append("?rmode=").Append((options.ImageCropMode ?? ImageCropMode.Crop).ToString().ToLowerInvariant()); if (options.ImageCropAnchor != null) { - imageUrl.Append("&anchor=").Append(options.ImageCropAnchor.ToString().ToLowerInvariant()); + imageUrl.Append("&ranchor=").Append(options.ImageCropAnchor.ToString().ToLowerInvariant()); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs index db46b6a827..c57c53b52e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs @@ -61,7 +61,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media public void GetCropUrlEmptyTest() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(null)); - Assert.AreEqual("?mode=crop", urlString); + Assert.AreEqual("?rmode=crop", urlString); } /// @@ -116,11 +116,11 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media var urlStringMax = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Max, Width = 300, Height = 150 }); var urlStringStretch = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Stretch, Width = 300, Height = 150 }); - Assert.AreEqual(MediaPath + "?mode=min&width=300&height=150", urlStringMin); - Assert.AreEqual(MediaPath + "?mode=boxpad&width=300&height=150", urlStringBoxPad); - Assert.AreEqual(MediaPath + "?mode=pad&width=300&height=150", urlStringPad); - Assert.AreEqual(MediaPath + "?mode=max&width=300&height=150", urlStringMax); - Assert.AreEqual(MediaPath + "?mode=stretch&width=300&height=150", urlStringStretch); + Assert.AreEqual(MediaPath + "?rmode=min&width=300&height=150", urlStringMin); + Assert.AreEqual(MediaPath + "?rmode=boxpad&width=300&height=150", urlStringBoxPad); + Assert.AreEqual(MediaPath + "?rmode=pad&width=300&height=150", urlStringPad); + Assert.AreEqual(MediaPath + "?rmode=max&width=300&height=150", urlStringMax); + Assert.AreEqual(MediaPath + "?rmode=stretch&width=300&height=150", urlStringStretch); } /// @@ -130,7 +130,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media public void GetCropUrl_UploadTypeTest() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Crop, ImageCropAnchor = ImageCropAnchor.Center, Width = 100, Height = 270 }); - Assert.AreEqual(MediaPath + "?mode=crop&anchor=center&width=100&height=270", urlString); + Assert.AreEqual(MediaPath + "?rmode=crop&ranchor=center&width=100&height=270", urlString); } /// @@ -140,7 +140,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media public void GetCropUrl_PreferFocalPointCenter() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Width = 300, Height = 150 }); - Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&width=300&height=150", urlString); + Assert.AreEqual(MediaPath + "?ranchor=center&rmode=crop&width=300&height=150", urlString); } /// @@ -150,7 +150,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidth() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Width = 200, HeightRatio = 0.5962962962962962962962962963m }); - Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); + Assert.AreEqual(MediaPath + "?ranchor=center&rmode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); } /// @@ -180,7 +180,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media public void GetCropUrl_PreDefinedCropNoCoordinatesWithHeight() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Height = 200, WidthRatio = 1.6770186335403726708074534161m }); - Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&widthratio=1.6770186335403726708074534161&height=200", urlString); + Assert.AreEqual(MediaPath + "?ranchor=center&rmode=crop&widthratio=1.6770186335403726708074534161&height=200", urlString); } /// @@ -190,7 +190,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media public void GetCropUrl_WidthOnlyParameter() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Width = 200 }); - Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&width=200", urlString); + Assert.AreEqual(MediaPath + "?ranchor=center&rmode=crop&width=200", urlString); } /// @@ -200,7 +200,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media public void GetCropUrl_HeightOnlyParameter() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Height = 200 }); - Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&height=200", urlString); + Assert.AreEqual(MediaPath + "?ranchor=center&rmode=crop&height=200", urlString); } /// @@ -210,7 +210,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media public void GetCropUrl_BackgroundColorParameter() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Pad, Width = 400, Height = 400, FurtherOptions = "&bgcolor=fff" }); - Assert.AreEqual(MediaPath + "?mode=pad&width=400&height=400&bgcolor=fff", urlString); + Assert.AreEqual(MediaPath + "?rmode=pad&width=400&height=400&bgcolor=fff", urlString); } } } From fd93248d112ad93d3dbcab9efa0d2bcb1c052581 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Mon, 9 Aug 2021 11:59:23 +0200 Subject: [PATCH 16/36] Move CropMode enum into seperate file --- .../ImageProcessors/CropMode.cs | 18 ++++++++++++++++++ .../ImageProcessors/CropWebProcessor.cs | 15 --------------- 2 files changed, 18 insertions(+), 15 deletions(-) create mode 100644 src/Umbraco.Web.Common/ImageProcessors/CropMode.cs diff --git a/src/Umbraco.Web.Common/ImageProcessors/CropMode.cs b/src/Umbraco.Web.Common/ImageProcessors/CropMode.cs new file mode 100644 index 0000000000..6e08c6e05c --- /dev/null +++ b/src/Umbraco.Web.Common/ImageProcessors/CropMode.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Cms.Web.Common.ImageProcessors +{ + /// + /// Represents the mode used to calculate a crop. + /// + public enum CropMode + { + /// + /// Crops the image using the standard rectangle model of x, y, width, height. + /// + Pixels, + + /// + /// Crops the image using the percentages model of left, top, right, bottom. + /// + Percentage + } +} diff --git a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs index d5e2adb2e5..828154447c 100644 --- a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs +++ b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs @@ -88,19 +88,4 @@ namespace Umbraco.Cms.Web.Common.ImageProcessors private static CropMode GetMode(IDictionary commands, CommandParser parser, CultureInfo culture) => parser.ParseValue(commands.GetValueOrDefault(Mode), culture); } - - /// - /// Enumerated cop modes to apply to cropped images. - /// - public enum CropMode - { - /// - /// Crops the image using the standard rectangle model of x, y, width, height. - /// - Pixels, - /// - /// Crops the image using percentages model left, top, right, bottom. - /// - Percentage - } } From 5cdcd021fa5afc9e3bcd016cb8bf9c84036ae484 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Mon, 9 Aug 2021 12:00:37 +0200 Subject: [PATCH 17/36] Remove support for whole percentage crop values --- .../ImageProcessors/CropWebProcessor.cs | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs index 828154447c..c523d6e424 100644 --- a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs +++ b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs @@ -38,40 +38,34 @@ namespace Umbraco.Cms.Web.Common.ImageProcessors if (GetCrop(commands, parser, culture) is RectangleF crop) { Size size = image.Image.Size(); + CropMode mode = GetMode(commands, parser, culture); - // Convert from percentages to pixels based on crop mode - if (GetMode(commands, parser, culture) is CropMode.Percentage) + if (mode == CropMode.Percentage) { - // Fix for whole numbers - float percentageLeft = crop.Left > 1 ? crop.Left / 100 : crop.Left; - float percentageRight = crop.Right > 1 ? crop.Right / 100 : crop.Right; - float percentageTop = crop.Top > 1 ? crop.Top / 100 : crop.Top; - float percentageBottom = crop.Bottom > 1 ? crop.Bottom / 100 : crop.Bottom; + // Convert the percentage based model of left, top, right, bottom to x, y, width, height + float x = crop.Left * size.Width; + float y = crop.Top * size.Height; + float width = crop.Right < 1 ? (1 - crop.Left - crop.Right) * size.Width : size.Width; + float height = crop.Bottom < 1 ? (1 - crop.Top - crop.Bottom) * size.Height : size.Height; - // Work out the percentages - float left = percentageLeft * size.Width; - float top = percentageTop * size.Height; - float width = percentageRight < 1 ? (1 - percentageLeft - percentageRight) * size.Width : size.Width; - float height = percentageBottom < 1 ? (1 - percentageTop - percentageBottom) * size.Height : size.Height; - - crop = new RectangleF(left, top, width, height); + crop = new RectangleF(x, y, width, height); } - // Round and validate crop rectangle - var rectangle = Rectangle.Round(crop); - if (rectangle.X < size.Width && rectangle.Y < size.Height) + // Round and validate/clamp crop rectangle + var cropRectangle = Rectangle.Round(crop); + if (cropRectangle.X < size.Width && cropRectangle.Y < size.Height) { - if (rectangle.Width > (size.Width - rectangle.X)) + if (cropRectangle.Width > (size.Width - cropRectangle.X)) { - rectangle.Width = size.Width - rectangle.X; + cropRectangle.Width = size.Width - cropRectangle.X; } - if (rectangle.Height > (size.Height - rectangle.Y)) + if (cropRectangle.Height > (size.Height - cropRectangle.Y)) { - rectangle.Height = size.Height - rectangle.Y; + cropRectangle.Height = size.Height - cropRectangle.Y; } - image.Image.Mutate(x => x.Crop(rectangle)); + image.Image.Mutate(x => x.Crop(cropRectangle)); } } @@ -80,9 +74,14 @@ namespace Umbraco.Cms.Web.Common.ImageProcessors private static RectangleF? GetCrop(IDictionary commands, CommandParser parser, CultureInfo culture) { - float[] crops = parser.ParseValue(commands.GetValueOrDefault(Crop), culture); + float[] coordinates = parser.ParseValue(commands.GetValueOrDefault(Crop), culture); - return (crops.Length != 4) ? null : new RectangleF(crops[0], crops[1], crops[2], crops[3]); + if (coordinates.Length != 4) + { + return null; + } + + return new RectangleF(coordinates[0], coordinates[1], coordinates[2], coordinates[3]); } private static CropMode GetMode(IDictionary commands, CommandParser parser, CultureInfo culture) From c21656aff2d4d8ade73f5c8f897e7e9aacdae056 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Mon, 9 Aug 2021 14:52:07 +0200 Subject: [PATCH 18/36] Change processor order and fix crop percentage calculation --- .../UmbracoBuilder.ImageSharp.cs | 4 ++- .../ImageProcessors/CropWebProcessor.cs | 35 ++++++------------- 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs index 3d753975fe..ec14091092 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs @@ -49,7 +49,9 @@ namespace Umbraco.Extensions }) .SetCache() .SetCacheHash() - .AddProcessor(); + .RemoveProcessor() + .AddProcessor() + .AddProcessor(); return services; } diff --git a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs index c523d6e424..c51af9a532 100644 --- a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs +++ b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs @@ -37,36 +37,23 @@ namespace Umbraco.Cms.Web.Common.ImageProcessors { if (GetCrop(commands, parser, culture) is RectangleF crop) { - Size size = image.Image.Size(); - CropMode mode = GetMode(commands, parser, culture); - - if (mode == CropMode.Percentage) + if (GetMode(commands, parser, culture) == CropMode.Percentage) { // Convert the percentage based model of left, top, right, bottom to x, y, width, height - float x = crop.Left * size.Width; - float y = crop.Top * size.Height; - float width = crop.Right < 1 ? (1 - crop.Left - crop.Right) * size.Width : size.Width; - float height = crop.Bottom < 1 ? (1 - crop.Top - crop.Bottom) * size.Height : size.Height; + int sourceWidth = image.Image.Width; + int sourceHeight = image.Image.Height; - crop = new RectangleF(x, y, width, height); + float left = crop.Left * sourceWidth; + float top = crop.Top * sourceHeight; + float width = sourceWidth - (sourceWidth * crop.Width) - left; + float height = sourceHeight - (sourceHeight * crop.Height) - top; + + crop = new RectangleF(left, top, width, height); } - // Round and validate/clamp crop rectangle var cropRectangle = Rectangle.Round(crop); - if (cropRectangle.X < size.Width && cropRectangle.Y < size.Height) - { - if (cropRectangle.Width > (size.Width - cropRectangle.X)) - { - cropRectangle.Width = size.Width - cropRectangle.X; - } - - if (cropRectangle.Height > (size.Height - cropRectangle.Y)) - { - cropRectangle.Height = size.Height - cropRectangle.Y; - } - - image.Image.Mutate(x => x.Crop(cropRectangle)); - } + + image.Image.Mutate(x => x.Crop(cropRectangle)); } return image; From 50d8e74b5bb3fb8af09a49729d855f2ea26ed6c1 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Mon, 9 Aug 2021 15:52:55 +0200 Subject: [PATCH 19/36] Obsolete unsupported image URL generation options and update ImageSharp implementation --- .../Models/ImageUrlGenerationOptions.cs | 45 +++++-- .../Media/ImageSharpImageUrlGenerator.cs | 87 +++++-------- .../Media/ImageSharpImageUrlGeneratorTests.cs | 77 ++--------- .../Umbraco.Web.Common/ImageCropperTest.cs | 120 ++++++++---------- .../ImageCropperTemplateCoreExtensions.cs | 45 ++++--- 5 files changed, 159 insertions(+), 215 deletions(-) diff --git a/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs b/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs index 99f42bbefa..3118162786 100644 --- a/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs +++ b/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs @@ -1,55 +1,71 @@ -namespace Umbraco.Cms.Core.Models +using System; + +namespace Umbraco.Cms.Core.Models { /// - /// These are options that are passed to the IImageUrlGenerator implementation to determine - /// the propery URL that is needed + /// These are options that are passed to the IImageUrlGenerator implementation to determine the URL that is generated. /// public class ImageUrlGenerationOptions { - public ImageUrlGenerationOptions (string imageUrl) - { - ImageUrl = imageUrl; - } + public ImageUrlGenerationOptions(string imageUrl) => ImageUrl = imageUrl; public string ImageUrl { get; } + public int? Width { get; set; } + public int? Height { get; set; } + + [Obsolete("This property is unsupported by the default implementation, manually calculate the width based on the height instead.")] public decimal? WidthRatio { get; set; } + + [Obsolete("This property is unsupported by the default implementation, manually calculate the height based on the width instead.")] public decimal? HeightRatio { get; set; } + public int? Quality { get; set; } + public ImageCropMode? ImageCropMode { get; set; } + public ImageCropAnchor? ImageCropAnchor { get; set; } + + [Obsolete("Images are already cropped from the center to the specified width/height by default.")] public bool DefaultCrop { get; set; } + public FocalPointPosition FocalPoint { get; set; } + public CropCoordinates Crop { get; set; } + public string CacheBusterValue { get; set; } + public string FurtherOptions { get; set; } + + [Obsolete("This property is unsupported by the default implementation, images should always be resized to the specified dimensions (within the configured maximums) to prevent different sizes depending on the source image.")] public bool UpScale { get; set; } = true; + + [Obsolete("This property is unsupported by the default implementation, all frames should be processed by default.")] public string AnimationProcessMode { get; set; } /// - /// The focal point position, in whatever units the registered IImageUrlGenerator uses, - /// typically a percentage of the total image from 0.0 to 1.0. + /// The focal point position, in whatever units the registered IImageUrlGenerator uses, typically a percentage of the total image from 0.0 to 1.0. /// public class FocalPointPosition { - public FocalPointPosition (decimal top, decimal left) + public FocalPointPosition(decimal top, decimal left) { Left = left; Top = top; } public decimal Left { get; } + public decimal Top { get; } } /// - /// The bounds of the crop within the original image, in whatever units the registered - /// IImageUrlGenerator uses, typically a percentage between 0 and 100. + /// The bounds of the crop within the original image, in whatever units the registered IImageUrlGenerator uses, typically a percentage between 0.0 and 1.0. /// public class CropCoordinates { - public CropCoordinates (decimal x1, decimal y1, decimal x2, decimal y2) + public CropCoordinates(decimal x1, decimal y1, decimal x2, decimal y2) { X1 = x1; Y1 = y1; @@ -58,8 +74,11 @@ } public decimal X1 { get; } + public decimal Y1 { get; } + public decimal X2 { get; } + public decimal Y2 { get; } } } diff --git a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs index d72ee2b4eb..8e0a8eb350 100644 --- a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs +++ b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs @@ -1,22 +1,19 @@ +using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Text; +using SixLabors.ImageSharp; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; -using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Media { public class ImageSharpImageUrlGenerator : IImageUrlGenerator { - public IEnumerable SupportedImageFileTypes => new[] - { - "jpeg", - "jpg", - "gif", - "bmp", - "png" - }; + private static readonly string[] s_supportedImageFileTypes = Configuration.Default.ImageFormats.SelectMany(f => f.FileExtensions).ToArray(); + + public IEnumerable SupportedImageFileTypes { get; } = s_supportedImageFileTypes; public string GetImageUrl(ImageUrlGenerationOptions options) { @@ -27,84 +24,66 @@ namespace Umbraco.Cms.Infrastructure.Media var imageUrl = new StringBuilder(options.ImageUrl); + bool queryStringHasStarted = false; + void AppendQueryString(string value) + { + imageUrl.Append(queryStringHasStarted ? '&' : '?'); + queryStringHasStarted = true; + + imageUrl.Append(value); + } + void AddQueryString(string key, params IConvertible[] values) + => AppendQueryString(key + '=' + string.Join(",", values.Select(x => x.ToString(CultureInfo.InvariantCulture)))); + if (options.FocalPoint != null) { - imageUrl.AppendFormat(CultureInfo.InvariantCulture, "?rxy={0},{1}", options.FocalPoint.Top, options.FocalPoint.Left); - } - else if (options.Crop != null) - { - imageUrl.AppendFormat(CultureInfo.InvariantCulture, "?crop={0},{1},{2},{3}&cropmode=percentage", options.Crop.X1, options.Crop.Y1, options.Crop.X2, options.Crop.Y2); - } - else if (options.DefaultCrop) - { - imageUrl.Append("?ranchor=center&rmode=crop"); - } - else - { - imageUrl.Append("?rmode=").Append((options.ImageCropMode ?? ImageCropMode.Crop).ToString().ToLowerInvariant()); - - if (options.ImageCropAnchor != null) - { - imageUrl.Append("&ranchor=").Append(options.ImageCropAnchor.ToString().ToLowerInvariant()); - } + AddQueryString("rxy", options.FocalPoint.Top, options.FocalPoint.Left); } - var hasFormat = options.FurtherOptions != null && options.FurtherOptions.InvariantContains("&format="); - - // Only put quality here, if we don't have a format specified. - // Otherwise we need to put quality at the end to avoid it being overridden by the format. - if (options.Quality.HasValue && hasFormat == false) + if (options.Crop != null) { - imageUrl.AppendFormat(CultureInfo.InvariantCulture, "&quality={0}", options.Quality.Value); + AddQueryString("crop", options.Crop.X1, options.Crop.Y1, options.Crop.X2, options.Crop.Y2); + AddQueryString("cropmode", "percentage"); } - if (options.HeightRatio.HasValue) + if (options.ImageCropMode.HasValue) { - imageUrl.AppendFormat(CultureInfo.InvariantCulture, "&heightratio={0}", options.HeightRatio.Value); + AddQueryString("rmode", options.ImageCropMode.Value.ToString().ToLowerInvariant()); } - if (options.WidthRatio.HasValue) + if (options.ImageCropAnchor.HasValue) { - imageUrl.AppendFormat(CultureInfo.InvariantCulture, "&widthratio={0}", options.WidthRatio.Value); + AddQueryString("ranchor", options.ImageCropAnchor.Value.ToString().ToLowerInvariant()); } if (options.Width.HasValue) { - imageUrl.AppendFormat(CultureInfo.InvariantCulture, "&width={0}", options.Width.Value); + AddQueryString("width", options.Width.Value); } if (options.Height.HasValue) { - imageUrl.AppendFormat(CultureInfo.InvariantCulture, "&height={0}", options.Height.Value); + AddQueryString("height", options.Height.Value); } - if (options.UpScale == false) + if (options.Quality.HasValue) { - imageUrl.Append("&upscale=false"); - } - - if (!string.IsNullOrWhiteSpace(options.AnimationProcessMode)) - { - imageUrl.Append("&animationprocessmode=").Append(options.AnimationProcessMode); + AddQueryString("quality", options.Quality.Value); } if (!string.IsNullOrWhiteSpace(options.FurtherOptions)) { - imageUrl.Append(options.FurtherOptions); - } - - // If furtherOptions contains a format, we need to put the quality after the format. - if (options.Quality.HasValue && hasFormat) - { - imageUrl.AppendFormat(CultureInfo.InvariantCulture, "&quality={0}", options.Quality.Value); + AppendQueryString(options.FurtherOptions.TrimStart('?', '&')); } if (!string.IsNullOrWhiteSpace(options.CacheBusterValue)) { - imageUrl.Append("&rnd=").Append(options.CacheBusterValue); + AddQueryString("rnd", options.CacheBusterValue); } return imageUrl.ToString(); } + + } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs index c57c53b52e..f6d87ef5a1 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs @@ -45,7 +45,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media } /// - /// Test that if a crop alias has been specified that doesn't exist the method returns null + /// Test that if options is null, the generated image URL is also null. /// [Test] public void GetCropUrlNullTest() @@ -55,13 +55,13 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media } /// - /// Test that if a crop alias has been specified that doesn't exist the method returns null + /// Test that if the image URL is null, the generated image URL is empty. /// [Test] public void GetCropUrlEmptyTest() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(null)); - Assert.AreEqual("?rmode=crop", urlString); + Assert.AreEqual(string.Empty, urlString); } /// @@ -74,35 +74,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media Assert.AreEqual("?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); } - /// - /// Test the height ratio mode with predefined crop dimensions - /// - [Test] - public void GetCropUrl_CropAliasHeightRatioModeTest() - { - var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Crop = s_crop, Width = 100, HeightRatio = 1 }); - Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&heightratio=1&width=100", urlString); - } - - /// - /// Test the height ratio mode with manual width/height dimensions - /// - [Test] - public void GetCropUrl_WidthHeightRatioModeTest() - { - var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus1, Width = 300, HeightRatio = 0.5m }); - Assert.AreEqual(MediaPath + "?rxy=0.80827067669172936,0.96&heightratio=0.5&width=300", urlString); - } - - /// - /// Test the height ratio mode with width/height dimensions - /// - [Test] - public void GetCropUrl_HeightWidthRatioModeTest() - { - var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus1, Height = 150, WidthRatio = 2 }); - Assert.AreEqual(MediaPath + "?rxy=0.80827067669172936,0.96&widthratio=2&height=150", urlString); - } /// /// Test that if Crop mode is specified as anything other than Crop the image doesn't use the crop @@ -139,28 +110,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media [Test] public void GetCropUrl_PreferFocalPointCenter() { - var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Width = 300, Height = 150 }); - Assert.AreEqual(MediaPath + "?ranchor=center&rmode=crop&width=300&height=150", urlString); - } - - /// - /// Test to check if height ratio is returned for a predefined crop without coordinates and focal point in centre when a width parameter is passed - /// - [Test] - public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidth() - { - var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Width = 200, HeightRatio = 0.5962962962962962962962962963m }); - Assert.AreEqual(MediaPath + "?ranchor=center&rmode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); - } - - /// - /// Test to check if height ratio is returned for a predefined crop without coordinates and focal point is custom when a width parameter is passed - /// - [Test] - public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPoint() - { - var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus2, Width = 200, HeightRatio = 0.5962962962962962962962962963m }); - Assert.AreEqual(MediaPath + "?rxy=0.41,0.4275&heightratio=0.5962962962962962962962962963&width=200", urlString); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Width = 300, Height = 150 }); + Assert.AreEqual(MediaPath + "?width=300&height=150", urlString); } /// @@ -173,24 +124,14 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media Assert.AreEqual(MediaPath + "?rxy=0.41,0.4275&width=270&height=161", urlString); } - /// - /// Test to check if width ratio is returned for a predefined crop without coordinates and focal point in centre when a height parameter is passed - /// - [Test] - public void GetCropUrl_PreDefinedCropNoCoordinatesWithHeight() - { - var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Height = 200, WidthRatio = 1.6770186335403726708074534161m }); - Assert.AreEqual(MediaPath + "?ranchor=center&rmode=crop&widthratio=1.6770186335403726708074534161&height=200", urlString); - } - /// /// Test to check result when only a width parameter is passed, effectivly a resize only /// [Test] public void GetCropUrl_WidthOnlyParameter() { - var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Width = 200 }); - Assert.AreEqual(MediaPath + "?ranchor=center&rmode=crop&width=200", urlString); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Width = 200 }); + Assert.AreEqual(MediaPath + "?width=200", urlString); } /// @@ -199,8 +140,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media [Test] public void GetCropUrl_HeightOnlyParameter() { - var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Height = 200 }); - Assert.AreEqual(MediaPath + "?ranchor=center&rmode=crop&height=200", urlString); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Height = 200 }); + Assert.AreEqual(MediaPath + "?height=200", urlString); } /// diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageCropperTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageCropperTest.cs index 1873b30c99..16b2268a47 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageCropperTest.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageCropperTest.cs @@ -1,8 +1,10 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Text; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -130,21 +132,21 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common public void GetCropUrl_WidthHeightTest() { var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 200, height: 300); - Assert.AreEqual(MediaPath + "?f=0.80827067669172936x0.96&w=200&h=300", urlString); + Assert.AreEqual(MediaPath + "?f=0.80827067669172936,0.96&w=200&h=300", urlString); } [Test] public void GetCropUrl_FocalPointTest() { var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, cropAlias: "thumb", preferFocalPoint: true, useCropDimensions: true); - Assert.AreEqual(MediaPath + "?f=0.80827067669172936x0.96&w=100&h=100", urlString); + Assert.AreEqual(MediaPath + "?f=0.80827067669172936,0.96&w=100&h=100", urlString); } [Test] public void GetCropUrlFurtherOptionsTest() { var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 200, height: 300, furtherOptions: "&filter=comic&roundedcorners=radius-26|bgcolor-fff"); - Assert.AreEqual(MediaPath + "?f=0.80827067669172936x0.96&w=200&h=300&filter=comic&roundedcorners=radius-26|bgcolor-fff", urlString); + Assert.AreEqual(MediaPath + "?f=0.80827067669172936,0.96&w=200&h=300&filter=comic&roundedcorners=radius-26|bgcolor-fff", urlString); } /// @@ -175,7 +177,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common public void GetCropUrl_CropAliasHeightRatioModeTest() { var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, cropAlias: "Thumb", useCropDimensions: true, ratioMode: ImageCropRatioMode.Height); - Assert.AreEqual(MediaPath + "?c=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&hr=1&w=100", urlString); + Assert.AreEqual(MediaPath + "?c=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&w=100&h=100", urlString); } /// @@ -185,7 +187,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common public void GetCropUrl_WidthHeightRatioModeTest() { var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, ratioMode: ImageCropRatioMode.Height); - Assert.AreEqual(MediaPath + "?f=0.80827067669172936x0.96&hr=0.5&w=300", urlString); + Assert.AreEqual(MediaPath + "?f=0.80827067669172936,0.96&w=300&h=150", urlString); } /// @@ -195,7 +197,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common public void GetCropUrl_HeightWidthRatioModeTest() { var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, ratioMode: ImageCropRatioMode.Width); - Assert.AreEqual(MediaPath + "?f=0.80827067669172936x0.96&wr=2&h=150", urlString); + Assert.AreEqual(MediaPath + "?f=0.80827067669172936,0.96&w=300&h=150", urlString); } /// @@ -236,7 +238,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common const string cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\":\"thumb\",\"width\": 100,\"height\": 100,\"coordinates\": {\"x1\": 0.58729977382575338,\"y1\": 0.055768992440203169,\"x2\": 0,\"y2\": 0.32457553600198386}}]}"; var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, width: 300, height: 150, preferFocalPoint: true); - Assert.AreEqual(MediaPath + "?m=defaultcrop&w=300&h=150", urlString); + Assert.AreEqual(MediaPath + "?w=300&h=150", urlString); } /// @@ -248,7 +250,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common const string cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, cropAlias: "home", width: 200); - Assert.AreEqual(MediaPath + "?m=defaultcrop&hr=0.5962962962962962962962962963&w=200", urlString); + Assert.AreEqual(MediaPath + "?w=200&h=119", urlString); } /// @@ -260,7 +262,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common const string cropperJson = "{\"focalPoint\": {\"left\": 0.4275,\"top\": 0.41},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, cropAlias: "home", width: 200); - Assert.AreEqual(MediaPath + "?f=0.41x0.4275&hr=0.5962962962962962962962962963&w=200", urlString); + Assert.AreEqual(MediaPath + "?f=0.41,0.4275&w=200&h=119", urlString); } /// @@ -272,7 +274,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common const string cropperJson = "{\"focalPoint\": {\"left\": 0.4275,\"top\": 0.41},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, cropAlias: "home", width: 200, useCropDimensions: true); - Assert.AreEqual(MediaPath + "?f=0.41x0.4275&w=270&h=161", urlString); + Assert.AreEqual(MediaPath + "?f=0.41,0.4275&w=270&h=161", urlString); } /// @@ -284,7 +286,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common const string cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, cropAlias: "home", height: 200); - Assert.AreEqual(MediaPath + "?m=defaultcrop&wr=1.6770186335403726708074534161&h=200", urlString); + Assert.AreEqual(MediaPath + "?w=335&h=200", urlString); } /// @@ -296,7 +298,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common const string cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, width: 200); - Assert.AreEqual(MediaPath + "?m=defaultcrop&w=200", urlString); + Assert.AreEqual(MediaPath + "?w=200", urlString); } /// @@ -308,7 +310,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common const string cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, height: 200); - Assert.AreEqual(MediaPath + "?m=defaultcrop&h=200", urlString); + Assert.AreEqual(MediaPath + "?h=200", urlString); } /// @@ -325,92 +327,82 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common internal class TestImageUrlGenerator : IImageUrlGenerator { - public IEnumerable SupportedImageFileTypes => new[] { "jpeg", "jpg", "gif", "bmp", "png", "tiff", "tif" }; + public IEnumerable SupportedImageFileTypes => new[] + { + "jpeg", + "jpg", + "gif", + "bmp", + "png", + "tiff", + "tif" + }; public string GetImageUrl(ImageUrlGenerationOptions options) { - var imageProcessorUrl = new StringBuilder(options.ImageUrl ?? string.Empty); + if (options == null) + { + return null; + } + + var imageUrl = new StringBuilder(options.ImageUrl); + + bool queryStringHasStarted = false; + void AppendQueryString(string value) + { + imageUrl.Append(queryStringHasStarted ? '&' : '?'); + queryStringHasStarted = true; + + imageUrl.Append(value); + } + void AddQueryString(string key, params IConvertible[] values) + => AppendQueryString(key + '=' + string.Join(",", values.Select(x => x.ToString(CultureInfo.InvariantCulture)))); if (options.FocalPoint != null) { - imageProcessorUrl.Append("?f="); - imageProcessorUrl.Append(options.FocalPoint.Top.ToString(CultureInfo.InvariantCulture)); - imageProcessorUrl.Append("x"); - imageProcessorUrl.Append(options.FocalPoint.Left.ToString(CultureInfo.InvariantCulture)); + AddQueryString("f", options.FocalPoint.Top, options.FocalPoint.Left); } else if (options.Crop != null) { - imageProcessorUrl.Append("?c="); - imageProcessorUrl.Append(options.Crop.X1.ToString(CultureInfo.InvariantCulture)).Append(","); - imageProcessorUrl.Append(options.Crop.Y1.ToString(CultureInfo.InvariantCulture)).Append(","); - imageProcessorUrl.Append(options.Crop.X2.ToString(CultureInfo.InvariantCulture)).Append(","); - imageProcessorUrl.Append(options.Crop.Y2.ToString(CultureInfo.InvariantCulture)); - } - else if (options.DefaultCrop) - { - imageProcessorUrl.Append("?m=defaultcrop"); - } - else - { - imageProcessorUrl.Append("?m=" + options.ImageCropMode.ToString().ToLower()); - if (options.ImageCropAnchor != null) - { - imageProcessorUrl.Append("&a=" + options.ImageCropAnchor.ToString().ToLower()); - } + AddQueryString("c", options.Crop.X1, options.Crop.Y1, options.Crop.X2, options.Crop.Y2); } - var hasFormat = options.FurtherOptions != null && options.FurtherOptions.InvariantContains("&f="); - if (options.Quality != null && hasFormat == false) + if (options.ImageCropMode.HasValue) { - imageProcessorUrl.Append("&q=" + options.Quality); + AddQueryString("m", options.ImageCropMode.Value.ToString().ToLowerInvariant()); } - if (options.HeightRatio != null) + if (options.ImageCropAnchor.HasValue) { - imageProcessorUrl.Append("&hr=" + options.HeightRatio.Value.ToString(CultureInfo.InvariantCulture)); - } - - if (options.WidthRatio != null) - { - imageProcessorUrl.Append("&wr=" + options.WidthRatio.Value.ToString(CultureInfo.InvariantCulture)); + AddQueryString("a", options.ImageCropAnchor.Value.ToString().ToLowerInvariant()); } if (options.Width != null) { - imageProcessorUrl.Append("&w=" + options.Width); + AddQueryString("w", options.Width.Value); } if (options.Height != null) { - imageProcessorUrl.Append("&h=" + options.Height); + AddQueryString("h", options.Height.Value); } - - if (options.UpScale == false) + + if (options.Quality.HasValue) { - imageProcessorUrl.Append("&u=no"); - } - - if (options.AnimationProcessMode != null) - { - imageProcessorUrl.Append("&apm=" + options.AnimationProcessMode); + AddQueryString("q", options.Quality.Value); } if (options.FurtherOptions != null) { - imageProcessorUrl.Append(options.FurtherOptions); - } - - if (options.Quality != null && hasFormat) - { - imageProcessorUrl.Append("&q=" + options.Quality); + AppendQueryString(options.FurtherOptions.TrimStart('?', '&')); } if (options.CacheBusterValue != null) { - imageProcessorUrl.Append("&r=").Append(options.CacheBusterValue); + AddQueryString("r", options.CacheBusterValue); } - return imageProcessorUrl.ToString(); + return imageUrl.ToString(); } } } diff --git a/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs b/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs index 879a70cdb5..27de0d22b2 100644 --- a/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using Newtonsoft.Json.Linq; using Umbraco.Cms.Core; @@ -449,16 +449,17 @@ namespace Umbraco.Extensions height = crop.Height; } - // If a predefined crop has been specified & there are no coordinates & no ratio mode, but a width parameter has been passed we can get the crop ratio for the height - if (crop != null && string.IsNullOrEmpty(cropAlias) == false && crop.Coordinates == null && ratioMode == null && width != null && height == null) + // Calculate missing dimension if a predefined crop has been specified, there are no coordinates and no ratio mode + if (crop != null && string.IsNullOrEmpty(cropAlias) == false && crop.Coordinates == null && ratioMode == null) { - options.HeightRatio = (decimal)crop.Height / crop.Width; - } - - // If a predefined crop has been specified & there are no coordinates & no ratio mode, but a height parameter has been passed we can get the crop ratio for the width - if (crop != null && string.IsNullOrEmpty(cropAlias) == false && crop.Coordinates == null && ratioMode == null && width == null && height != null) - { - options.WidthRatio = (decimal)crop.Width / crop.Height; + if (width != null && height == null) + { + height = (int)MathF.Round(width.Value * ((float)crop.Height / crop.Width)); + } + else if (width == null && height != null) + { + width = (int)MathF.Round(height.Value * ((float)crop.Width / crop.Height)); + } } } else @@ -477,16 +478,28 @@ namespace Umbraco.Extensions if (ratioMode == ImageCropRatioMode.Width && height != null) { - // if only height specified then assume a square - if (width == null) width = height; - options.WidthRatio = (decimal)width / (decimal)height; + // If only height specified then assume a square + if (width == null) + { + options.Width = height; + } + else + { + options.Width = (int)MathF.Round(height.Value * ((float)width.Value / height.Value)); + } } if (ratioMode == ImageCropRatioMode.Height && width != null) { - // if only width specified then assume a square - if (height == null) height = width; - options.HeightRatio = (decimal)height / (decimal)width; + // If only width specified then assume a square + if (height == null) + { + options.Height = width; + } + else + { + options.Height = (int)MathF.Round(width.Value * ((float)height.Value / width.Value)); + } } options.UpScale = upScale; From 0b9f1f4f868b08c25eec717fe41417814d079af7 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 10 Aug 2021 11:23:37 +0200 Subject: [PATCH 20/36] Remove usage of obsolete image URL generation properties (width/height ratio, default crop, upscale and animation process mode) --- .../ValueConverters/ImageCropperValue.cs | 4 +- .../Controllers/BackOfficeServerVariables.cs | 2 +- .../ImageUrlGeneratorController.cs | 21 +- .../Controllers/ImagesController.cs | 45 +-- .../FriendlyImageCropperTemplateExtensions.cs | 249 ++++---------- .../ImageCropperTemplateCoreExtensions.cs | 315 ++++++------------ .../Extensions/UrlHelperExtensions.cs | 12 +- .../resources/imageurlgenerator.resource.js | 6 +- .../common/services/mediahelper.service.js | 26 +- .../src/common/services/tinymce.service.js | 20 +- 10 files changed, 218 insertions(+), 482 deletions(-) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs index 32101d6cf7..09e080e0b0 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -77,7 +77,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters } else { - return new ImageUrlGenerationOptions(url) { DefaultCrop = true }; + return new ImageUrlGenerationOptions(url); } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs index c3fb203ec1..c822b61d67 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs @@ -365,7 +365,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers }, { "imageUrlGeneratorApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( - controller => controller.GetCropUrl(null, null, null, null, null)) + controller => controller.GetCropUrl(null, null, null, null)) }, { "elementTypeApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( diff --git a/src/Umbraco.Web.BackOffice/Controllers/ImageUrlGeneratorController.cs b/src/Umbraco.Web.BackOffice/Controllers/ImageUrlGeneratorController.cs index 1d72c80ad8..7546fdf38d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ImageUrlGeneratorController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ImageUrlGeneratorController.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Media; +using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Web.Common.Attributes; using Constants = Umbraco.Cms.Core.Constants; @@ -21,20 +21,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers { private readonly IImageUrlGenerator _imageUrlGenerator; - public ImageUrlGeneratorController(IImageUrlGenerator imageUrlGenerator) - { - _imageUrlGenerator = imageUrlGenerator; - } + public ImageUrlGeneratorController(IImageUrlGenerator imageUrlGenerator) => _imageUrlGenerator = imageUrlGenerator; - public string GetCropUrl(string mediaPath, int? width = null, int? height = null, ImageCropMode? imageCropMode = null, string animationProcessMode = null) + public string GetCropUrl(string mediaPath, int? width = null, int? height = null, ImageCropMode? imageCropMode = null) => _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(mediaPath) { - return _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(mediaPath) - { - Width = width, - Height = height, - ImageCropMode = imageCropMode, - AnimationProcessMode = animationProcessMode - }); - } + Width = width, + Height = height, + ImageCropMode = imageCropMode + }); } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs index 24135bcbe6..00a18ec8b7 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.IO; @@ -76,9 +76,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers var rnd = imageLastModified.HasValue ? $"&rnd={imageLastModified:yyyyMMddHHmmss}" : null; var imageUrl = _imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(imagePath) { - UpScale = false, Width = width, - AnimationProcessMode = "first", ImageCropMode = ImageCropMode.Max, CacheBusterValue = rnd }); @@ -94,9 +92,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// /// /// - /// /// - /// /// /// /// If there is no media, image property or image file is found then this will return not found. @@ -106,9 +102,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers int? height = null, decimal? focalPointLeft = null, decimal? focalPointTop = null, - string animationProcessMode = "first", ImageCropMode mode = ImageCropMode.Max, - bool upscale = false, string cacheBusterValue = "", decimal? cropX1 = null, decimal? cropX2 = null, @@ -116,45 +110,24 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers decimal? cropY2 = null ) { - - var options = new ImageUrlGenerationOptions(imagePath) { - AnimationProcessMode = animationProcessMode, - CacheBusterValue = cacheBusterValue, + Width = width, Height = height, ImageCropMode = mode, - UpScale = upscale, - Width = width, - Crop = (cropX1.HasValue && cropX2.HasValue && cropY1.HasValue && cropY2.HasValue) ? new ImageUrlGenerationOptions.CropCoordinates(cropX1.Value, cropY1.Value, cropX2.Value, cropY2.Value) : null, - FocalPoint = new ImageUrlGenerationOptions.FocalPointPosition(focalPointTop.GetValueOrDefault(0.5m), focalPointLeft.GetValueOrDefault(0.5m)), + CacheBusterValue = cacheBusterValue }; + if (focalPointLeft.HasValue && focalPointTop.HasValue) { - options.FocalPoint = - new ImageUrlGenerationOptions.FocalPointPosition(focalPointTop.Value, focalPointLeft.Value); + options.FocalPoint = new ImageUrlGenerationOptions.FocalPointPosition(focalPointTop.Value, focalPointLeft.Value); + } + else if (cropX1.HasValue && cropX2.HasValue && cropY1.HasValue && cropY2.HasValue) + { + options.Crop = new ImageUrlGenerationOptions.CropCoordinates(cropX1.Value, cropY1.Value, cropX2.Value, cropY2.Value); } return _imageUrlGenerator.GetImageUrl(options); } - - public class FocalPointPositionModel - { - public decimal Left { get; set; } - public decimal Top { get; set; } - } - - /// - /// The bounds of the crop within the original image, in whatever units the registered - /// IImageUrlGenerator uses, typically a percentage between 0 and 100. - /// - public class CropCoordinatesModel - { - - public decimal X1 { get; set; } - public decimal Y1 { get; set; } - public decimal X2 { get; set;} - public decimal Y2 { get; set;} - } } } diff --git a/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs b/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs index 94e24a5027..19090ee12e 100644 --- a/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; @@ -11,25 +11,17 @@ namespace Umbraco.Extensions { public static class FriendlyImageCropperTemplateExtensions { - private static IImageUrlGenerator ImageUrlGenerator { get; } = - StaticServiceProvider.Instance.GetRequiredService(); + private static IImageUrlGenerator ImageUrlGenerator { get; } = StaticServiceProvider.Instance.GetRequiredService(); - private static IPublishedValueFallback PublishedValueFallback { get; } = - StaticServiceProvider.Instance.GetRequiredService(); - - private static IPublishedUrlProvider PublishedUrlProvider { get; } = - StaticServiceProvider.Instance.GetRequiredService(); + private static IPublishedValueFallback PublishedValueFallback { get; } = StaticServiceProvider.Instance.GetRequiredService(); + private static IPublishedUrlProvider PublishedUrlProvider { get; } = StaticServiceProvider.Instance.GetRequiredService(); /// - /// Gets the underlying image processing service URL by the crop alias (from the "umbracoFile" property alias) on the IPublishedContent item + /// Gets the underlying image processing service URL by the crop alias (from the "umbracoFile" property alias) on the IPublishedContent item. /// - /// - /// The IPublishedContent item. - /// - /// - /// The crop alias e.g. thumbnail - /// + /// The IPublishedContent item. + /// The crop alias e.g. thumbnail. /// /// The URL of the cropped image. /// @@ -57,17 +49,11 @@ namespace Umbraco.Extensions => ImageCropperTemplateCoreExtensions.GetCropUrl(mediaItem, imageCropperValue, cropAlias, ImageUrlGenerator, PublishedValueFallback, PublishedUrlProvider); /// - /// Gets the underlying image processing service URL by the crop alias using the specified property containing the image cropper Json data on the IPublishedContent item. + /// Gets the underlying image processing service URL by the crop alias using the specified property containing the image cropper JSON data on the IPublishedContent item. /// - /// - /// The IPublishedContent item. - /// - /// - /// The property alias of the property containing the Json data e.g. umbracoFile - /// - /// - /// The crop alias e.g. thumbnail - /// + /// The IPublishedContent item. + /// The property alias of the property containing the JSON data e.g. umbracoFile. + /// The crop alias e.g. thumbnail. /// /// The URL of the cropped image. /// @@ -83,53 +69,22 @@ namespace Umbraco.Extensions /// /// Gets the underlying image processing service URL from the IPublishedContent item. /// - /// - /// The IPublishedContent item. - /// - /// - /// The width of the output image. - /// - /// - /// The height of the output image. - /// - /// - /// Property alias of the property containing the Json data. - /// - /// - /// The crop alias. - /// - /// - /// Quality percentage of the output image. - /// - /// - /// The image crop mode. - /// - /// - /// The image crop anchor. - /// - /// - /// Use focal point, to generate an output image using the focal point instead of the predefined crop - /// - /// - /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters. - /// - /// - /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated - /// - /// - /// These are any query string parameters (formatted as query strings) that the underlying image processing service supports. For example: - /// - /// The IPublishedContent item. + /// The width of the output image. + /// The height of the output image. + /// Property alias of the property containing the JSON data. + /// The crop alias. + /// Quality percentage of the output image. + /// The image crop mode. + /// The image crop anchor. + /// Use focal point, to generate an output image using the focal point instead of the predefined crop. + /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters. + /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated. + /// These are any query string parameters (formatted as query strings) that the underlying image processing service supports. For example: + /// - /// - /// - /// - /// Use a dimension as a ratio - /// - /// - /// If the image should be upscaled to requested dimensions - /// + /// ]]> + /// Use a dimension as a ratio. /// /// The URL of the cropped image. /// @@ -146,8 +101,7 @@ namespace Umbraco.Extensions bool useCropDimensions = false, bool cacheBuster = true, string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true) + ImageCropRatioMode? ratioMode = null) => mediaItem.GetCropUrl( ImageUrlGenerator, PublishedValueFallback, @@ -163,62 +117,30 @@ namespace Umbraco.Extensions useCropDimensions, cacheBuster, furtherOptions, - ratioMode, - upScale + ratioMode ); /// /// Gets the underlying image processing service URL from the image path. /// - /// - /// The image URL. - /// - /// - /// The width of the output image. - /// - /// - /// The height of the output image. - /// - /// - /// The Json data from the Umbraco Core Image Cropper property editor - /// - /// - /// The crop alias. - /// - /// - /// Quality percentage of the output image. - /// - /// - /// The image crop mode. - /// - /// - /// The image crop anchor. - /// - /// - /// Use focal point to generate an output image using the focal point instead of the predefined crop if there is one - /// - /// - /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters - /// - /// - /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated - /// - /// - /// These are any query string parameters (formatted as query strings) that the underlying image processing service supports. For example: - /// - /// The image URL. + /// The width of the output image. + /// The height of the output image. + /// The JSON data from the Umbraco Core Image Cropper property editor. + /// The crop alias. + /// Quality percentage of the output image. + /// The image crop mode. + /// The image crop anchor. + /// Use focal point to generate an output image using the focal point instead of the predefined crop if there is one. + /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters. + /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated. + /// These are any query string parameters (formatted as query strings) that the underlying image processing service supports. For example: + /// - /// - /// - /// - /// Use a dimension as a ratio - /// - /// - /// If the image should be upscaled to requested dimensions - /// + /// ]]> + /// Use a dimension as a ratio. /// - /// The the URL of the cropped image. + /// The URL of the cropped image. /// public static string GetCropUrl( this string imageUrl, @@ -233,8 +155,7 @@ namespace Umbraco.Extensions bool useCropDimensions = false, string cacheBusterValue = null, string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true) + ImageCropRatioMode? ratioMode = null) => imageUrl.GetCropUrl( ImageUrlGenerator, width, @@ -248,60 +169,30 @@ namespace Umbraco.Extensions useCropDimensions, cacheBusterValue, furtherOptions, - ratioMode, - upScale - ); + ratioMode + ); /// /// Gets the underlying image processing service URL from the image path. /// - /// - /// The image URL. - /// - /// - /// - /// The width of the output image. - /// - /// - /// The height of the output image. - /// - /// - /// The crop alias. - /// - /// - /// Quality percentage of the output image. - /// - /// - /// The image crop mode. - /// - /// - /// The image crop anchor. - /// - /// - /// Use focal point to generate an output image using the focal point instead of the predefined crop if there is one - /// - /// - /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters - /// - /// - /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated - /// - /// - /// These are any query string parameters (formatted as query strings) that the underlying image processing service supports. For example: - /// - /// The image URL. + /// The crop data set. + /// The width of the output image. + /// The height of the output image. + /// The crop alias. + /// Quality percentage of the output image. + /// The image crop mode. + /// The image crop anchor. + /// Use focal point to generate an output image using the focal point instead of the predefined crop if there is one. + /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters. + /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated. + /// These are any query string parameters (formatted as query strings) that the underlying image processing service supports. For example: + /// - /// - /// - /// - /// Use a dimension as a ratio - /// - /// - /// If the image should be upscaled to requested dimensions - /// + /// ]]> + /// Use a dimension as a ratio /// - /// The the URL of the cropped image. + /// The URL of the cropped image. /// public static string GetCropUrl( this string imageUrl, @@ -316,9 +207,7 @@ namespace Umbraco.Extensions bool useCropDimensions = false, string cacheBusterValue = null, string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true, - string animationProcessMode = null) + ImageCropRatioMode? ratioMode = null) => imageUrl.GetCropUrl( ImageUrlGenerator, cropDataSet, @@ -331,9 +220,7 @@ namespace Umbraco.Extensions useCropDimensions, cacheBusterValue, furtherOptions, - ratioMode, - upScale, - animationProcessMode + ratioMode ); @@ -341,10 +228,6 @@ namespace Umbraco.Extensions public static string GetLocalCropUrl( this MediaWithCrops mediaWithCrops, string alias, - string cacheBusterValue = null) - { - return mediaWithCrops.LocalCrops.Src + - mediaWithCrops.LocalCrops.GetCropUrl(alias, ImageUrlGenerator, cacheBusterValue: cacheBusterValue); - } + string cacheBusterValue = null) => mediaWithCrops.LocalCrops.Src + mediaWithCrops.LocalCrops.GetCropUrl(alias, ImageUrlGenerator, cacheBusterValue: cacheBusterValue); } } diff --git a/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs b/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs index 27de0d22b2..00d4dcdb77 100644 --- a/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs @@ -13,17 +13,13 @@ namespace Umbraco.Extensions public static class ImageCropperTemplateCoreExtensions { /// - /// Gets the underlying image processing service URL by the crop alias (from the "umbracoFile" property alias) on the IPublishedContent item + /// Gets the underlying image processing service URL by the crop alias (from the "umbracoFile" property alias) on the IPublishedContent item. /// - /// - /// The IPublishedContent item. - /// - /// - /// The crop alias e.g. thumbnail - /// - /// The image url generator. + /// The IPublishedContent item. + /// The crop alias e.g. thumbnail. + /// The image URL generator. /// The published value fallback. - /// The published url provider. + /// The published URL provider. /// /// The URL of the cropped image. /// @@ -32,20 +28,14 @@ namespace Umbraco.Extensions string cropAlias, IImageUrlGenerator imageUrlGenerator, IPublishedValueFallback publishedValueFallback, - IPublishedUrlProvider publishedUrlProvider) - { - return mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, cropAlias: cropAlias, useCropDimensions: true); - } + IPublishedUrlProvider publishedUrlProvider) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, cropAlias: cropAlias, useCropDimensions: true); public static string GetCropUrl( this MediaWithCrops mediaWithCrops, string cropAlias, IImageUrlGenerator imageUrlGenerator, IPublishedValueFallback publishedValueFallback, - IPublishedUrlProvider publishedUrlProvider) - { - return mediaWithCrops.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, cropAlias: cropAlias, useCropDimensions: true); - } + IPublishedUrlProvider publishedUrlProvider) => mediaWithCrops.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, cropAlias: cropAlias, useCropDimensions: true); /// /// Gets the crop URL by using only the specified . @@ -54,6 +44,8 @@ namespace Umbraco.Extensions /// The image cropper value. /// The crop alias. /// The image URL generator. + /// The published value fallback. + /// The published URL provider. /// /// The image crop URL. /// @@ -63,26 +55,17 @@ namespace Umbraco.Extensions string cropAlias, IImageUrlGenerator imageUrlGenerator, IPublishedValueFallback publishedValueFallback, - IPublishedUrlProvider publishedUrlProvider) - { - return mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, imageCropperValue, true, cropAlias: cropAlias, useCropDimensions: true); - } + IPublishedUrlProvider publishedUrlProvider) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, imageCropperValue, true, cropAlias: cropAlias, useCropDimensions: true); /// - /// Gets the underlying image processing service URL by the crop alias using the specified property containing the image cropper Json data on the IPublishedContent item. + /// Gets the underlying image processing service URL by the crop alias using the specified property containing the image cropper JSON data on the IPublishedContent item. /// - /// - /// The IPublishedContent item. - /// - /// - /// The property alias of the property containing the Json data e.g. umbracoFile - /// - /// - /// The crop alias e.g. thumbnail - /// - /// The image url generator. + /// The IPublishedContent item. + /// The property alias of the property containing the JSON data e.g. umbracoFile. + /// The crop alias e.g. thumbnail. + /// The image URL generator. /// The published value fallback. - /// The published url provider. + /// The published URL provider. /// /// The URL of the cropped image. /// @@ -92,74 +75,37 @@ namespace Umbraco.Extensions string cropAlias, IImageUrlGenerator imageUrlGenerator, IPublishedValueFallback publishedValueFallback, - IPublishedUrlProvider publishedUrlProvider) - { - return mediaItem.GetCropUrl( imageUrlGenerator, publishedValueFallback, publishedUrlProvider, propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true); - } + IPublishedUrlProvider publishedUrlProvider) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true); public static string GetCropUrl(this MediaWithCrops mediaWithCrops, IPublishedValueFallback publishedValueFallback, IPublishedUrlProvider publishedUrlProvider, string propertyAlias, string cropAlias, - IImageUrlGenerator imageUrlGenerator) - { - return mediaWithCrops.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true); - } + IImageUrlGenerator imageUrlGenerator) => mediaWithCrops.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true); /// /// Gets the underlying image processing service URL from the IPublishedContent item. /// - /// - /// The IPublishedContent item. - /// - /// The image url generator. + /// The IPublishedContent item. + /// The image URL generator. /// The published value fallback. - /// The published url provider. - /// - /// The width of the output image. - /// - /// - /// The height of the output image. - /// - /// - /// Property alias of the property containing the Json data. - /// - /// - /// The crop alias. - /// - /// - /// Quality percentage of the output image. - /// - /// - /// The image crop mode. - /// - /// - /// The image crop anchor. - /// - /// - /// Use focal point, to generate an output image using the focal point instead of the predefined crop - /// - /// - /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters. - /// - /// - /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated - /// - /// - /// These are any query string parameters (formatted as query strings) that ImageProcessor supports. For example: - /// - /// The published URL provider. + /// The width of the output image. + /// The height of the output image. + /// Property alias of the property containing the JSON data. + /// The crop alias. + /// Quality percentage of the output image. + /// The image crop mode. + /// The image crop anchor. + /// Use focal point, to generate an output image using the focal point instead of the predefined crop. + /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters. + /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated. + /// These are any query string parameters (formatted as query strings) that ImageProcessor supports. For example: + /// - /// - /// - /// - /// Use a dimension as a ratio - /// - /// - /// If the image should be upscaled to requested dimensions - /// + /// ]]> + /// Use a dimension as a ratio. /// /// The URL of the cropped image. /// @@ -179,11 +125,7 @@ namespace Umbraco.Extensions bool useCropDimensions = false, bool cacheBuster = true, string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true) - { - return mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, null, false, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode, upScale); - } + ImageCropRatioMode? ratioMode = null) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, null, false, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode); public static string GetCropUrl( this MediaWithCrops mediaWithCrops, @@ -201,12 +143,14 @@ namespace Umbraco.Extensions bool useCropDimensions = false, bool cacheBuster = true, string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true) + ImageCropRatioMode? ratioMode = null) { - if (mediaWithCrops == null) throw new ArgumentNullException(nameof(mediaWithCrops)); + if (mediaWithCrops == null) + { + throw new ArgumentNullException(nameof(mediaWithCrops)); + } - return mediaWithCrops.Content.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, mediaWithCrops.LocalCrops, false, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode, upScale); + return mediaWithCrops.Content.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, mediaWithCrops.LocalCrops, false, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode); } private static string GetCropUrl( @@ -227,15 +171,17 @@ namespace Umbraco.Extensions bool useCropDimensions = false, bool cacheBuster = true, string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true) + ImageCropRatioMode? ratioMode = null) { - if (mediaItem == null) throw new ArgumentNullException(nameof(mediaItem)); - - var cacheBusterValue = cacheBuster ? mediaItem.UpdateDate.ToFileTimeUtc().ToString(CultureInfo.InvariantCulture) : null; + if (mediaItem == null) + { + throw new ArgumentNullException(nameof(mediaItem)); + } if (mediaItem.HasProperty(propertyAlias) == false || mediaItem.HasValue(propertyAlias) == false) - return string.Empty; + { + return null; + } var mediaItemUrl = mediaItem.MediaUrl(publishedUrlProvider, propertyAlias: propertyAlias); @@ -269,63 +215,35 @@ namespace Umbraco.Extensions } } + var cacheBusterValue = cacheBuster ? mediaItem.UpdateDate.ToFileTimeUtc().ToString(CultureInfo.InvariantCulture) : null; + return GetCropUrl( mediaItemUrl, imageUrlGenerator, localCrops, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, - cacheBusterValue, furtherOptions, ratioMode, upScale); + cacheBusterValue, furtherOptions, ratioMode); } /// /// Gets the underlying image processing service URL from the image path. /// - /// - /// The image URL. - /// - /// - /// The width of the output image. - /// - /// - /// The height of the output image. - /// - /// - /// The Json data from the Umbraco Core Image Cropper property editor - /// - /// - /// The crop alias. - /// - /// - /// Quality percentage of the output image. - /// - /// - /// The image crop mode. - /// - /// - /// The image crop anchor. - /// - /// - /// Use focal point to generate an output image using the focal point instead of the predefined crop if there is one - /// - /// - /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters - /// - /// - /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated - /// - /// - /// These are any query string parameters (formatted as query strings) that the underlying image processing service supports. For example: - /// - /// The image URL. + /// The image URL generator. + /// The width of the output image. + /// The height of the output image. + /// The Json data from the Umbraco Core Image Cropper property editor. + /// The crop alias. + /// Quality percentage of the output image. + /// The image crop mode. + /// The image crop anchor. + /// Use focal point to generate an output image using the focal point instead of the predefined crop if there is one. + /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters. + /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated. + /// These are any query string parameters (formatted as query strings) that the underlying image processing service supports. For example: + /// - /// - /// - /// - /// Use a dimension as a ratio - /// - /// - /// If the image should be upscaled to requested dimensions - /// + /// ]]> + /// Use a dimension as a ratio. /// - /// The the URL of the cropped image. + /// The URL of the cropped image. /// public static string GetCropUrl( this string imageUrl, @@ -341,10 +259,12 @@ namespace Umbraco.Extensions bool useCropDimensions = false, string cacheBusterValue = null, string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true) + ImageCropRatioMode? ratioMode = null) { - if (string.IsNullOrEmpty(imageUrl)) return string.Empty; + if (string.IsNullOrWhiteSpace(imageUrl)) + { + return null; + } ImageCropperValue cropDataSet = null; if (string.IsNullOrEmpty(imageCropperValue) == false && imageCropperValue.DetectIsJson() && (imageCropMode == ImageCropMode.Crop || imageCropMode == null)) @@ -354,62 +274,31 @@ namespace Umbraco.Extensions return GetCropUrl( imageUrl, imageUrlGenerator, cropDataSet, width, height, cropAlias, quality, imageCropMode, - imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, upScale); + imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode); } /// /// Gets the underlying image processing service URL from the image path. /// - /// - /// The image URL. - /// - /// - /// The generator that will process all the options and the image URL to return a full image URLs with all processing options appended - /// - /// - /// - /// The width of the output image. - /// - /// - /// The height of the output image. - /// - /// - /// The crop alias. - /// - /// - /// Quality percentage of the output image. - /// - /// - /// The image crop mode. - /// - /// - /// The image crop anchor. - /// - /// - /// Use focal point to generate an output image using the focal point instead of the predefined crop if there is one - /// - /// - /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters - /// - /// - /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated - /// - /// - /// These are any query string parameters (formatted as query strings) that the underlying image processing service supports. For example: - /// - /// The image URL. + /// The generator that will process all the options and the image URL to return a full image URLs with all processing options appended. + /// The crop data set. + /// The width of the output image. + /// The height of the output image. + /// The crop alias. + /// Quality percentage of the output image. + /// The image crop mode. + /// The image crop anchor. + /// Use focal point to generate an output image using the focal point instead of the predefined crop if there is one. + /// Use crop dimensions to have the output image sized according to the predefined crop sizes, this will override the width and height parameters. + /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated. + /// These are any query string parameters (formatted as query strings) that the underlying image processing service supports. For example: + /// - /// - /// - /// - /// Use a dimension as a ratio - /// - /// - /// If the image should be upscaled to requested dimensions - /// + /// ]]> + /// Use a dimension as a ratio. /// - /// The . + /// The URL of the cropped image. /// public static string GetCropUrl( this string imageUrl, @@ -425,21 +314,23 @@ namespace Umbraco.Extensions bool useCropDimensions = false, string cacheBusterValue = null, string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true, - string animationProcessMode = null) + ImageCropRatioMode? ratioMode = null) { - if (string.IsNullOrEmpty(imageUrl)) return string.Empty; + if (string.IsNullOrWhiteSpace(imageUrl)) + { + return null; + } ImageUrlGenerationOptions options; - if (cropDataSet != null && (imageCropMode == ImageCropMode.Crop || imageCropMode == null)) { var crop = cropDataSet.GetCrop(cropAlias); - // if a crop was specified, but not found, return null + // If a crop was specified, but not found, return null if (crop == null && !string.IsNullOrWhiteSpace(cropAlias)) + { return null; + } options = cropDataSet.GetCropBaseOptions(imageUrl, crop, string.IsNullOrWhiteSpace(cropAlias), preferFocalPoint); @@ -464,7 +355,7 @@ namespace Umbraco.Extensions } else { - options = new ImageUrlGenerationOptions (imageUrl) + options = new ImageUrlGenerationOptions(imageUrl) { ImageCropMode = (imageCropMode ?? ImageCropMode.Pad), ImageCropAnchor = imageCropAnchor @@ -474,7 +365,6 @@ namespace Umbraco.Extensions options.Quality = quality; options.Width = ratioMode != null && ratioMode.Value == ImageCropRatioMode.Width ? null : width; options.Height = ratioMode != null && ratioMode.Value == ImageCropRatioMode.Height ? null : height; - options.AnimationProcessMode = animationProcessMode; if (ratioMode == ImageCropRatioMode.Width && height != null) { @@ -502,7 +392,6 @@ namespace Umbraco.Extensions } } - options.UpScale = upScale; options.FurtherOptions = furtherOptions; options.CacheBusterValue = cacheBusterValue; diff --git a/src/Umbraco.Web.Common/Extensions/UrlHelperExtensions.cs b/src/Umbraco.Web.Common/Extensions/UrlHelperExtensions.cs index e7dd5248e1..a6dd4ac1ca 100644 --- a/src/Umbraco.Web.Common/Extensions/UrlHelperExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UrlHelperExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -247,7 +247,6 @@ namespace Umbraco.Extensions bool cacheBuster = true, string furtherOptions = null, ImageCropRatioMode? ratioMode = null, - bool upScale = true, bool htmlEncode = true) { if (mediaItem == null) @@ -256,8 +255,8 @@ namespace Umbraco.Extensions } var url = mediaItem.GetCropUrl(width, height, propertyAlias, cropAlias, quality, imageCropMode, - imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode, - upScale); + imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode); + return CreateHtmlString(url, htmlEncode); } @@ -274,15 +273,14 @@ namespace Umbraco.Extensions string cacheBusterValue = null, string furtherOptions = null, ImageCropRatioMode? ratioMode = null, - bool upScale = true, bool htmlEncode = true) { if (imageCropperValue == null) return HtmlString.Empty; var imageUrl = imageCropperValue.Src; var url = imageUrl.GetCropUrl(imageCropperValue, width, height, cropAlias, quality, imageCropMode, - imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, - upScale); + imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode); + return CreateHtmlString(url, htmlEncode); } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/imageurlgenerator.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/imageurlgenerator.resource.js index a937cd2675..dd65f89526 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/imageurlgenerator.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/imageurlgenerator.resource.js @@ -1,4 +1,4 @@ -/** +/** * @ngdoc service * @name umbraco.resources.imageUrlGeneratorResource * @function @@ -11,14 +11,14 @@ function imageUrlGeneratorResource($http, umbRequestHelper) { - function getCropUrl(mediaPath, width, height, imageCropMode, animationProcessMode) { + function getCropUrl(mediaPath, width, height, imageCropMode) { return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "imageUrlGeneratorApiBaseUrl", "GetCropUrl", - { mediaPath, width, height, imageCropMode, animationProcessMode })), + { mediaPath, width, height, imageCropMode })), 'Failed to get crop URL'); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js index 1b3765a5f5..e98a597e76 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js @@ -1,4 +1,4 @@ -/** +/** * @ngdoc service * @name umbraco.services.mediaHelper * @description A helper object used for dealing with media items @@ -408,16 +408,20 @@ function mediaHelper(umbRequestHelper, $http, $log) { * @param {string} imagePath Raw image path * @param {object} options Object describing image generation parameters: * { - * animationProcessMode: - * cacheBusterValue: + * width: + * height: * focalPoint: { * left: * top: * }, - * height: * mode: - * upscale: - * width: + * cacheBusterValue: + * crop: { + * x1: + * x2: + * y1: + * y2: + * }, * } */ getProcessedImageUrl: function (imagePath, options) { @@ -433,18 +437,16 @@ function mediaHelper(umbRequestHelper, $http, $log) { "GetProcessedImageUrl", { imagePath, - animationProcessMode: options.animationProcessMode, - cacheBusterValue: options.cacheBusterValue, + width: options.width, + height: options.height, focalPointLeft: options.focalPoint ? options.focalPoint.left : null, focalPointTop: options.focalPoint ? options.focalPoint.top : null, - height: options.height, mode: options.mode, - upscale: options.upscale || false, - width: options.width, + cacheBusterValue: options.cacheBusterValue, cropX1: options.crop ? options.crop.x1 : null, cropX2: options.crop ? options.crop.x2 : null, cropY1: options.crop ? options.crop.y1 : null, - cropY2: options.crop ? options.crop.y : null + cropY2: options.crop ? options.crop.y2 : null })), "Failed to retrieve processed image URL for image: " + imagePath); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index b83367ef6e..2738883b15 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -306,8 +306,8 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s if (imgUrl) { mediaHelper.getProcessedImageUrl(imgUrl, { - height: newSize.height, - width: newSize.width + width: newSize.width, + height: newSize.height }) .then(function (resizedImgUrl) { editor.dom.setAttrib(imageDomElement, 'data-mce-src', resizedImgUrl); @@ -1522,15 +1522,13 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s args.editor.on('ObjectResized', function (e) { var srcAttr = $(e.target).attr("src"); var path = srcAttr.split("?")[0]; - mediaHelper.getProcessedImageUrl(path, - { - height: e.height, - moded: "max", - width: e.width - }) - .then(function (resizedPath) { - $(e.target).attr("data-mce-src", resizedPath); - }); + mediaHelper.getProcessedImageUrl(path, { + width: e.width, + height: e.height, + mode: "max" + }).then(function (resizedPath) { + $(e.target).attr("data-mce-src", resizedPath); + }); syncContent(); }); From 474b63a59032fdc98afe9cbc58ce7237f5b4ef27 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 10 Aug 2021 13:35:04 +0200 Subject: [PATCH 21/36] Update AngularJS image URL parameters --- .../views/components/blockcard/umbBlockCard.component.js | 2 +- .../propertyeditors/fileupload/fileupload.controller.js | 4 ++-- .../propertyeditors/grid/editors/media.controller.js | 9 +++------ .../imagecropper/imagecropper.controller.js | 2 +- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umbBlockCard.component.js b/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umbBlockCard.component.js index 0c75bfbee3..a9993a0498 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umbBlockCard.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umbBlockCard.component.js @@ -42,7 +42,7 @@ var path = umbRequestHelper.convertVirtualToAbsolutePath(vm.blockConfigModel.thumbnail); if (path.toLowerCase().endsWith(".svg") === false) { - path += "?upscale=false&width=400"; + path += "?width=400"; } vm.styleBackgroundImage = 'url(\''+path+'\')'; } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js index 4f1016e680..b4d59c683c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js @@ -1,4 +1,4 @@ -(function () { +(function () { 'use strict'; /** @@ -55,7 +55,7 @@ if (thumbnail) { if (mediaHelper.detectIfImageByExtension(property.value)) { //get default big thumbnail from image processor - var thumbnailUrl = property.value + "?rnd=" + moment(entity.updateDate).format("YYYYMMDDHHmmss") + "&width=500&animationprocessmode=first"; + var thumbnailUrl = property.value + "?width=500&rnd=" + moment(entity.updateDate).format("YYYYMMDDHHmmss"); return thumbnailUrl; } else { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index 91da54d4ad..d87d2c30bf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -73,25 +73,22 @@ angular.module("umbraco") url += `?crop=${coords.x1},${coords.y1},${coords.x2},${coords.y2}&cropmode=percentage`; } else { // Here in order not to break existing content where focalPoint were used. - // For some reason width/height have to come first when mode=crop. if ($scope.control.value.focalPoint) { - url += `?center=${$scope.control.value.focalPoint.top},${$scope.control.value.focalPoint.left}`; - url += '&mode=crop'; + url += `?rxy=${$scope.control.value.focalPoint.top},${$scope.control.value.focalPoint.left}`; } else { // Prevent black padding and no crop when focal point not set / changed from default - url += '?center=0.5,0.5&mode=crop'; + url += '?rxy=0.5,0.5'; } } url += '&width=' + $scope.control.editor.config.size.width; url += '&height=' + $scope.control.editor.config.size.height; - url += '&animationprocessmode=first'; } // set default size if no crop present (moved from the view) if (url.includes('?') === false) { - url += '?width=800&upscale=false&animationprocessmode=false' + url += '?width=800' } return url; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js index cbaf843d35..8bb50a07dc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js @@ -234,7 +234,7 @@ angular.module('umbraco') if (property.value && property.value.src) { if (thumbnail === true) { - return property.value.src + "?width=500&mode=max&animationprocessmode=first"; + return property.value.src + "?width=500"; } else { return property.value.src; From 0753353cf7e7ca5d899b3210d16f8d0d111f7c13 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 10 Aug 2021 13:36:50 +0200 Subject: [PATCH 22/36] Remove obsolete/unused properties and parameters --- .../Models/ImageUrlGenerationOptions.cs | 17 ----------------- .../ValueConverters/ImageCropperValue.cs | 12 +++++------- .../ImageCropperTemplateCoreExtensions.cs | 2 +- 3 files changed, 6 insertions(+), 25 deletions(-) diff --git a/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs b/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs index 3118162786..9f4943dc68 100644 --- a/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs +++ b/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs @@ -1,5 +1,3 @@ -using System; - namespace Umbraco.Cms.Core.Models { /// @@ -15,21 +13,12 @@ namespace Umbraco.Cms.Core.Models public int? Height { get; set; } - [Obsolete("This property is unsupported by the default implementation, manually calculate the width based on the height instead.")] - public decimal? WidthRatio { get; set; } - - [Obsolete("This property is unsupported by the default implementation, manually calculate the height based on the width instead.")] - public decimal? HeightRatio { get; set; } - public int? Quality { get; set; } public ImageCropMode? ImageCropMode { get; set; } public ImageCropAnchor? ImageCropAnchor { get; set; } - [Obsolete("Images are already cropped from the center to the specified width/height by default.")] - public bool DefaultCrop { get; set; } - public FocalPointPosition FocalPoint { get; set; } public CropCoordinates Crop { get; set; } @@ -38,12 +27,6 @@ namespace Umbraco.Cms.Core.Models public string FurtherOptions { get; set; } - [Obsolete("This property is unsupported by the default implementation, images should always be resized to the specified dimensions (within the configured maximums) to prevent different sizes depending on the source image.")] - public bool UpScale { get; set; } = true; - - [Obsolete("This property is unsupported by the default implementation, all frames should be processed by default.")] - public string AnimationProcessMode { get; set; } - /// /// The focal point position, in whatever units the registered IImageUrlGenerator uses, typically a percentage of the total image from 0.0 to 1.0. /// diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs index 09e080e0b0..6cce8899fb 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs @@ -63,11 +63,9 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters : Crops.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); } - public ImageUrlGenerationOptions GetCropBaseOptions(string url, ImageCropperCrop crop, bool defaultCrop, bool preferFocalPoint) + public ImageUrlGenerationOptions GetCropBaseOptions(string url, ImageCropperCrop crop, bool preferFocalPoint) { - if (preferFocalPoint && HasFocalPoint() - || crop != null && crop.Coordinates == null && HasFocalPoint() - || defaultCrop && HasFocalPoint()) + if ((preferFocalPoint && HasFocalPoint()) || (crop != null && crop.Coordinates == null && HasFocalPoint())) { return new ImageUrlGenerationOptions(url) { FocalPoint = new ImageUrlGenerationOptions.FocalPointPosition(FocalPoint.Top, FocalPoint.Left) }; } @@ -92,7 +90,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters if (crop == null && !string.IsNullOrWhiteSpace(alias)) return null; - var options = GetCropBaseOptions(string.Empty, crop, string.IsNullOrWhiteSpace(alias), useFocalPoint); + var options = GetCropBaseOptions(null, crop, useFocalPoint || string.IsNullOrWhiteSpace(alias)); if (crop != null && useCropDimensions) { @@ -108,9 +106,9 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters /// /// Gets the value image URL for a specific width and height. /// - public string GetCropUrl(int width, int height, IImageUrlGenerator imageUrlGenerator, bool useFocalPoint = false, string cacheBusterValue = null) + public string GetCropUrl(int width, int height, IImageUrlGenerator imageUrlGenerator, string cacheBusterValue = null) { - var options = GetCropBaseOptions(string.Empty, null, true, useFocalPoint); + var options = GetCropBaseOptions(null, null, false); options.Width = width; options.Height = height; diff --git a/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs b/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs index 00d4dcdb77..ae7e391bba 100644 --- a/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs @@ -332,7 +332,7 @@ namespace Umbraco.Extensions return null; } - options = cropDataSet.GetCropBaseOptions(imageUrl, crop, string.IsNullOrWhiteSpace(cropAlias), preferFocalPoint); + options = cropDataSet.GetCropBaseOptions(imageUrl, crop, preferFocalPoint || string.IsNullOrWhiteSpace(cropAlias)); if (crop != null & useCropDimensions) { From 766530fcd3880df48bd384f6f3119bd91086da42 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 10 Aug 2021 17:17:40 +0200 Subject: [PATCH 23/36] Swap focal point order to left,top to match resize X,Y coordinates --- .../Media/ImageSharpImageUrlGenerator.cs | 2 +- .../Media/ImageSharpImageUrlGeneratorTests.cs | 10 +++++----- .../propertyeditors/grid/editors/media.controller.js | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs index 8e0a8eb350..1f72a68aec 100644 --- a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs +++ b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs @@ -37,7 +37,7 @@ namespace Umbraco.Cms.Infrastructure.Media if (options.FocalPoint != null) { - AddQueryString("rxy", options.FocalPoint.Top, options.FocalPoint.Left); + AddQueryString("rxy", options.FocalPoint.Left, options.FocalPoint.Top); } if (options.Crop != null) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs index f6d87ef5a1..ab3ca6b23c 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs @@ -20,28 +20,28 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media public void GetCropUrl_CropAliasTest() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Crop = s_crop, Width = 100, Height = 100 }); - Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); + Assert.AreEqual(MediaPath + "?cc=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&width=100&height=100", urlString); } [Test] public void GetCropUrl_WidthHeightTest() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus1, Width = 200, Height = 300 }); - Assert.AreEqual(MediaPath + "?rxy=0.80827067669172936,0.96&width=200&height=300", urlString); + Assert.AreEqual(MediaPath + "?rxy=0.96,0.80827067669172936&width=200&height=300", urlString); } [Test] public void GetCropUrl_FocalPointTest() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus1, Width = 100, Height = 100 }); - Assert.AreEqual(MediaPath + "?rxy=0.80827067669172936,0.96&width=100&height=100", urlString); + Assert.AreEqual(MediaPath + "?rxy=0.96,0.80827067669172936&width=100&height=100", urlString); } [Test] public void GetCropUrlFurtherOptionsTest() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus1, Width = 200, Height = 300, FurtherOptions = "&filter=comic&roundedcorners=radius-26|bgcolor-fff" }); - Assert.AreEqual(MediaPath + "?rxy=0.80827067669172936,0.96&width=200&height=300&filter=comic&roundedcorners=radius-26|bgcolor-fff", urlString); + Assert.AreEqual(MediaPath + "?rxy=0.96,0.80827067669172936&width=200&height=300&filter=comic&roundedcorners=radius-26|bgcolor-fff", urlString); } /// @@ -121,7 +121,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPointIgnore() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus2, Width = 270, Height = 161 }); - Assert.AreEqual(MediaPath + "?rxy=0.41,0.4275&width=270&height=161", urlString); + Assert.AreEqual(MediaPath + "?rxy=0.4275,0.41&width=270&height=161", urlString); } /// diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index d87d2c30bf..81a548a116 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -70,11 +70,11 @@ angular.module("umbraco") if ($scope.control.value.coordinates) { // New way, crop by percent must come before width/height. var coords = $scope.control.value.coordinates; - url += `?crop=${coords.x1},${coords.y1},${coords.x2},${coords.y2}&cropmode=percentage`; + url += `?cc=${coords.x1},${coords.y1},${coords.x2},${coords.y2}`; } else { // Here in order not to break existing content where focalPoint were used. if ($scope.control.value.focalPoint) { - url += `?rxy=${$scope.control.value.focalPoint.top},${$scope.control.value.focalPoint.left}`; + url += `?rxy=${$scope.control.value.focalPoint.left},${$scope.control.value.focalPoint.top}`; } else { // Prevent black padding and no crop when focal point not set / changed from default url += '?rxy=0.5,0.5'; From 22814036a1e7d54b8ffa0d70c286045d1fb8e2fa Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 10 Aug 2021 17:20:03 +0200 Subject: [PATCH 24/36] Update FocalPointPosition and CropCoordinates constructors/properties --- .../Models/ImageUrlGenerationOptions.cs | 20 +++++++++---------- .../Media/ImageSharpImageUrlGenerator.cs | 2 +- .../ValueConverters/ImageCropperValue.cs | 2 +- .../Media/ImageSharpImageUrlGeneratorTests.cs | 6 +++--- .../Umbraco.Web.Common/ImageCropperTest.cs | 2 +- .../Controllers/ImagesController.cs | 2 +- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs b/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs index 9f4943dc68..ba5c1d0e30 100644 --- a/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs +++ b/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs @@ -32,7 +32,7 @@ namespace Umbraco.Cms.Core.Models /// public class FocalPointPosition { - public FocalPointPosition(decimal top, decimal left) + public FocalPointPosition(decimal left, decimal top) { Left = left; Top = top; @@ -48,21 +48,21 @@ namespace Umbraco.Cms.Core.Models /// public class CropCoordinates { - public CropCoordinates(decimal x1, decimal y1, decimal x2, decimal y2) + public CropCoordinates(decimal left, decimal top, decimal right, decimal bottom) { - X1 = x1; - Y1 = y1; - X2 = x2; - Y2 = y2; + Left = left; + Top = top; + Right = right; + Bottom = bottom; } - public decimal X1 { get; } + public decimal Left { get; } - public decimal Y1 { get; } + public decimal Top { get; } - public decimal X2 { get; } + public decimal Right { get; } - public decimal Y2 { get; } + public decimal Bottom { get; } } } } diff --git a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs index 1f72a68aec..b6906d304b 100644 --- a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs +++ b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs @@ -42,7 +42,7 @@ namespace Umbraco.Cms.Infrastructure.Media if (options.Crop != null) { - AddQueryString("crop", options.Crop.X1, options.Crop.Y1, options.Crop.X2, options.Crop.Y2); + AddQueryString("crop", options.Crop.Left, options.Crop.Top, options.Crop.Right, options.Crop.Bottom); AddQueryString("cropmode", "percentage"); } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs index 6cce8899fb..c0efaac4ae 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs @@ -67,7 +67,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters { if ((preferFocalPoint && HasFocalPoint()) || (crop != null && crop.Coordinates == null && HasFocalPoint())) { - return new ImageUrlGenerationOptions(url) { FocalPoint = new ImageUrlGenerationOptions.FocalPointPosition(FocalPoint.Top, FocalPoint.Left) }; + return new ImageUrlGenerationOptions(url) { FocalPoint = new ImageUrlGenerationOptions.FocalPointPosition(FocalPoint.Left, FocalPoint.Top) }; } else if (crop != null && crop.Coordinates != null && preferFocalPoint == false) { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs index ab3ca6b23c..91dd1a8c53 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs @@ -12,8 +12,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media { private const string MediaPath = "/media/1005/img_0671.jpg"; private static readonly ImageUrlGenerationOptions.CropCoordinates s_crop = new ImageUrlGenerationOptions.CropCoordinates(0.58729977382575338m, 0.055768992440203169m, 0m, 0.32457553600198386m); - private static readonly ImageUrlGenerationOptions.FocalPointPosition s_focus1 = new ImageUrlGenerationOptions.FocalPointPosition(0.80827067669172936m, 0.96m); - private static readonly ImageUrlGenerationOptions.FocalPointPosition s_focus2 = new ImageUrlGenerationOptions.FocalPointPosition(0.41m, 0.4275m); + private static readonly ImageUrlGenerationOptions.FocalPointPosition s_focus1 = new ImageUrlGenerationOptions.FocalPointPosition(0.96m, 0.80827067669172936m); + private static readonly ImageUrlGenerationOptions.FocalPointPosition s_focus2 = new ImageUrlGenerationOptions.FocalPointPosition(0.4275m, 0.41m); private static readonly ImageSharpImageUrlGenerator s_generator = new ImageSharpImageUrlGenerator(); [Test] @@ -71,7 +71,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media public void GetBaseCropUrlFromModelTest() { var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(null) { Crop = s_crop, Width = 100, Height = 100 }); - Assert.AreEqual("?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); + Assert.AreEqual("?cc=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&width=100&height=100", urlString); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageCropperTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageCropperTest.cs index 16b2268a47..599324b040 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageCropperTest.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageCropperTest.cs @@ -364,7 +364,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common } else if (options.Crop != null) { - AddQueryString("c", options.Crop.X1, options.Crop.Y1, options.Crop.X2, options.Crop.Y2); + AddQueryString("c", options.Crop.Left, options.Crop.Top, options.Crop.Right, options.Crop.Bottom); } if (options.ImageCropMode.HasValue) diff --git a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs index 00a18ec8b7..564d0dcdd9 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ImagesController.cs @@ -120,7 +120,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (focalPointLeft.HasValue && focalPointTop.HasValue) { - options.FocalPoint = new ImageUrlGenerationOptions.FocalPointPosition(focalPointTop.Value, focalPointLeft.Value); + options.FocalPoint = new ImageUrlGenerationOptions.FocalPointPosition(focalPointLeft.Value, focalPointTop.Value); } else if (cropX1.HasValue && cropX2.HasValue && cropY1.HasValue && cropY2.HasValue) { From 01559ede97bd4f95eaa911c548d05caacd5fe2fd Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Tue, 10 Aug 2021 23:55:37 +0200 Subject: [PATCH 25/36] Only support crop coordinates in CropWebProcessor --- .../Media/ImageSharpImageUrlGenerator.cs | 3 +- .../ImageProcessors/CropMode.cs | 18 -------- .../ImageProcessors/CropWebProcessor.cs | 46 +++++++------------ 3 files changed, 17 insertions(+), 50 deletions(-) delete mode 100644 src/Umbraco.Web.Common/ImageProcessors/CropMode.cs diff --git a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs index b6906d304b..12e85dfb88 100644 --- a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs +++ b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs @@ -42,8 +42,7 @@ namespace Umbraco.Cms.Infrastructure.Media if (options.Crop != null) { - AddQueryString("crop", options.Crop.Left, options.Crop.Top, options.Crop.Right, options.Crop.Bottom); - AddQueryString("cropmode", "percentage"); + AddQueryString("cc", options.Crop.Left, options.Crop.Top, options.Crop.Right, options.Crop.Bottom); } if (options.ImageCropMode.HasValue) diff --git a/src/Umbraco.Web.Common/ImageProcessors/CropMode.cs b/src/Umbraco.Web.Common/ImageProcessors/CropMode.cs deleted file mode 100644 index 6e08c6e05c..0000000000 --- a/src/Umbraco.Web.Common/ImageProcessors/CropMode.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Umbraco.Cms.Web.Common.ImageProcessors -{ - /// - /// Represents the mode used to calculate a crop. - /// - public enum CropMode - { - /// - /// Crops the image using the standard rectangle model of x, y, width, height. - /// - Pixels, - - /// - /// Crops the image using the percentages model of left, top, right, bottom. - /// - Percentage - } -} diff --git a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs index c51af9a532..8945ce7f45 100644 --- a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs +++ b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Globalization; using Microsoft.Extensions.Logging; @@ -15,43 +16,31 @@ namespace Umbraco.Cms.Web.Common.ImageProcessors public class CropWebProcessor : IImageWebProcessor { /// - /// The command constant for the crop definition. + /// The command constant for the crop coordinates. /// - public const string Crop = "crop"; - - /// - /// The command constant for the crop mode. - /// - public const string Mode = "cropmode"; - + public const string Coordinates = "cc"; /// public IEnumerable Commands { get; } = new[] { - Crop, - Mode + Coordinates }; /// public FormattedImage Process(FormattedImage image, ILogger logger, IDictionary commands, CommandParser parser, CultureInfo culture) { - if (GetCrop(commands, parser, culture) is RectangleF crop) + RectangleF? coordinates = GetCoordinates(commands, parser, culture); + if (coordinates != null) { - if (GetMode(commands, parser, culture) == CropMode.Percentage) - { - // Convert the percentage based model of left, top, right, bottom to x, y, width, height - int sourceWidth = image.Image.Width; - int sourceHeight = image.Image.Height; + // Convert the percentage based model of left, top, right, bottom to x, y, width, height + int sourceWidth = image.Image.Width; + int sourceHeight = image.Image.Height; + int x = (int)MathF.Round(coordinates.Value.Left * sourceWidth); + int y = (int)MathF.Round(coordinates.Value.Top * sourceHeight); + int width = sourceWidth - (int)MathF.Round(coordinates.Value.Right * sourceWidth); + int height = sourceHeight - (int)MathF.Round(coordinates.Value.Bottom * sourceHeight); - float left = crop.Left * sourceWidth; - float top = crop.Top * sourceHeight; - float width = sourceWidth - (sourceWidth * crop.Width) - left; - float height = sourceHeight - (sourceHeight * crop.Height) - top; - - crop = new RectangleF(left, top, width, height); - } - - var cropRectangle = Rectangle.Round(crop); + var cropRectangle = new Rectangle(x, y, width, height); image.Image.Mutate(x => x.Crop(cropRectangle)); } @@ -59,9 +48,9 @@ namespace Umbraco.Cms.Web.Common.ImageProcessors return image; } - private static RectangleF? GetCrop(IDictionary commands, CommandParser parser, CultureInfo culture) + private static RectangleF? GetCoordinates(IDictionary commands, CommandParser parser, CultureInfo culture) { - float[] coordinates = parser.ParseValue(commands.GetValueOrDefault(Crop), culture); + float[] coordinates = parser.ParseValue(commands.GetValueOrDefault(Coordinates), culture); if (coordinates.Length != 4) { @@ -70,8 +59,5 @@ namespace Umbraco.Cms.Web.Common.ImageProcessors return new RectangleF(coordinates[0], coordinates[1], coordinates[2], coordinates[3]); } - - private static CropMode GetMode(IDictionary commands, CommandParser parser, CultureInfo culture) - => parser.ParseValue(commands.GetValueOrDefault(Mode), culture); } } From c5361ec54e309acfee206cca274cc6f5e4a35a37 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 11 Aug 2021 09:53:58 +0200 Subject: [PATCH 26/36] Improve crop coordinate calculations --- .../ImageProcessors/CropWebProcessor.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs index 8945ce7f45..7b3cc817f2 100644 --- a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs +++ b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs @@ -32,13 +32,13 @@ namespace Umbraco.Cms.Web.Common.ImageProcessors RectangleF? coordinates = GetCoordinates(commands, parser, culture); if (coordinates != null) { - // Convert the percentage based model of left, top, right, bottom to x, y, width, height + // Convert the coordinates to a pixel based rectangle int sourceWidth = image.Image.Width; int sourceHeight = image.Image.Height; - int x = (int)MathF.Round(coordinates.Value.Left * sourceWidth); - int y = (int)MathF.Round(coordinates.Value.Top * sourceHeight); - int width = sourceWidth - (int)MathF.Round(coordinates.Value.Right * sourceWidth); - int height = sourceHeight - (int)MathF.Round(coordinates.Value.Bottom * sourceHeight); + int x = (int)MathF.Round(coordinates.Value.X * sourceWidth); + int y = (int)MathF.Round(coordinates.Value.Y * sourceHeight); + int width = (int)MathF.Round(coordinates.Value.Width * sourceWidth); + int height = (int)MathF.Round(coordinates.Value.Height * sourceHeight); var cropRectangle = new Rectangle(x, y, width, height); @@ -57,7 +57,8 @@ namespace Umbraco.Cms.Web.Common.ImageProcessors return null; } - return new RectangleF(coordinates[0], coordinates[1], coordinates[2], coordinates[3]); + // The right and bottom values are actually the distance from those sides, so convert them into real coordinates + return RectangleF.FromLTRB(coordinates[0], coordinates[1], 1 - coordinates[2], 1 - coordinates[3]); } } } From 902984f977831099857911361b20dc4efbc7ebca Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Wed, 11 Aug 2021 17:18:23 +0200 Subject: [PATCH 27/36] Added unit tests for CropWebProcessor and ImageCropperTemplateCoreExtensions. --- .../Media/ImageSharpImageUrlGenerator.cs | 4 +- ...ImageCropperTemplateCoreExtensionsTests.cs | 192 ++++++++++++++++++ .../ImageProcessors/CropWebProcessorTests.cs | 80 ++++++++ .../ImageCropperTemplateCoreExtensions.cs | 2 +- 4 files changed, 275 insertions(+), 3 deletions(-) create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensionsTests.cs create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageProcessors/CropWebProcessorTests.cs diff --git a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs index 12e85dfb88..6331ad24e1 100644 --- a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs +++ b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs @@ -70,12 +70,12 @@ namespace Umbraco.Cms.Infrastructure.Media AddQueryString("quality", options.Quality.Value); } - if (!string.IsNullOrWhiteSpace(options.FurtherOptions)) + if (string.IsNullOrWhiteSpace(options.FurtherOptions) == false) { AppendQueryString(options.FurtherOptions.TrimStart('?', '&')); } - if (!string.IsNullOrWhiteSpace(options.CacheBusterValue)) + if (string.IsNullOrWhiteSpace(options.CacheBusterValue) == false) { AddQueryString("rnd", options.CacheBusterValue); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensionsTests.cs new file mode 100644 index 0000000000..0389c51726 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensionsTests.cs @@ -0,0 +1,192 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Media; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors.ValueConverters; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Extensions +{ + [TestFixture] + public class ImageCropperTemplateCoreExtensionsTests + { + [Test] + public void GetCropUrl_WithCropSpecifiedButNotFound_ReturnsNull() + { + var imageUrl = "/test.jpg"; + Mock imageUrlGenerator = CreateMockImageUrlGenerator(); + var result = imageUrl.GetCropUrl( + imageUrlGenerator.Object, + new ImageCropperValue { }, + imageCropMode: ImageCropMode.Crop, + cropAlias: "Missing"); + + Assert.IsNull(result); + } + + [Test] + public void GetCropUrl_WithCropSpecifiedAndUsingCropDimensions_CallsImageGeneratorWithCorrectParameters() + { + var imageUrl = "/test.jpg"; + Mock imageUrlGenerator = CreateMockImageUrlGenerator(); + var result = imageUrl.GetCropUrl( + imageUrlGenerator.Object, + CreateImageCropperValueWithCrops(), + imageCropMode: ImageCropMode.Crop, + cropAlias: "TestCrop", + useCropDimensions: true); + + imageUrlGenerator + .Verify(x => x.GetImageUrl( + It.Is(y => y.Width == 100 && + y.Height == 200))); + } + + [Test] + public void GetCropUrl_WithCropSpecifiedAndWidthAndHeightProvided_CallsImageGeneratorWithCorrectParameters() + { + var imageUrl = "/test.jpg"; + Mock imageUrlGenerator = CreateMockImageUrlGenerator(); + var result = imageUrl.GetCropUrl( + imageUrlGenerator.Object, + CreateImageCropperValueWithCrops(), + imageCropMode: ImageCropMode.Crop, + cropAlias: "TestCrop", + width: 50, + height: 80); + + imageUrlGenerator + .Verify(x => x.GetImageUrl( + It.Is(y => y.Width == 50 && + y.Height == 80))); + } + + [Test] + public void GetCropUrl_WithCropSpecifiedAndWidthOnlyProvided_CallsImageGeneratorWithCorrectParameters() + { + var imageUrl = "/test.jpg"; + Mock imageUrlGenerator = CreateMockImageUrlGenerator(); + var result = imageUrl.GetCropUrl( + imageUrlGenerator.Object, + CreateImageCropperValueWithCrops(), + imageCropMode: ImageCropMode.Crop, + cropAlias: "TestCrop", + width: 50); + + imageUrlGenerator + .Verify(x => x.GetImageUrl( + It.Is(y => y.Width == 50 && + y.Height == 100))); + } + + [Test] + public void GetCropUrl_WithCropSpecifiedAndHeightOnlyProvided_CallsImageGeneratorWithCorrectParameters() + { + var imageUrl = "/test.jpg"; + Mock imageUrlGenerator = CreateMockImageUrlGenerator(); + var result = imageUrl.GetCropUrl( + imageUrlGenerator.Object, + CreateImageCropperValueWithCrops(), + imageCropMode: ImageCropMode.Crop, + cropAlias: "TestCrop", + height: 50); + + imageUrlGenerator + .Verify(x => x.GetImageUrl( + It.Is(y => y.Width == 25 && + y.Height == 50))); + } + + [Test] + public void GetCropUrl_WithCropSpecifiedAndWidthRatioModeProvidedWithHeight_CallsImageGeneratorWithCorrectParameters() + { + var imageUrl = "/test.jpg"; + Mock imageUrlGenerator = CreateMockImageUrlGenerator(); + var result = imageUrl.GetCropUrl( + imageUrlGenerator.Object, + CreateImageCropperValueWithCrops(), + imageCropMode: ImageCropMode.Crop, + cropAlias: "TestCrop", + ratioMode: ImageCropRatioMode.Width, + height: 50); + + imageUrlGenerator + .Verify(x => x.GetImageUrl( + It.Is(y => y.Width == 50 && + y.Height == 50))); + } + + [Test] + public void GetCropUrl_WithCropSpecifiedAndWidthRatioModeProvidedWithWidthAndHeight_CallsImageGeneratorWithCorrectParameters() + { + var imageUrl = "/test.jpg"; + Mock imageUrlGenerator = CreateMockImageUrlGenerator(); + var result = imageUrl.GetCropUrl( + imageUrlGenerator.Object, + CreateImageCropperValueWithCrops(), + imageCropMode: ImageCropMode.Crop, + cropAlias: "TestCrop", + ratioMode: ImageCropRatioMode.Width, + width: 35, + height: 50); + + imageUrlGenerator + .Verify(x => x.GetImageUrl( + It.Is(y => y.Width == 35 && + y.Height == 50))); + } + + [Test] + public void GetCropUrl_WithCropSpecifiedAndHeightRatioModeProvidedWithWidth_CallsImageGeneratorWithCorrectParameters() + { + var imageUrl = "/test.jpg"; + Mock imageUrlGenerator = CreateMockImageUrlGenerator(); + var result = imageUrl.GetCropUrl( + imageUrlGenerator.Object, + CreateImageCropperValueWithCrops(), + imageCropMode: ImageCropMode.Crop, + cropAlias: "TestCrop", + ratioMode: ImageCropRatioMode.Height, + width: 60); + + imageUrlGenerator + .Verify(x => x.GetImageUrl( + It.Is(y => y.Width == 60 && + y.Height == 60))); + } + + [Test] + public void GetCropUrl_WithCropSpecifiedAndHeightRatioModeProvidedWithWidthAndHeight_CallsImageGeneratorWithCorrectParameters() + { + var imageUrl = "/test.jpg"; + Mock imageUrlGenerator = CreateMockImageUrlGenerator(); + var result = imageUrl.GetCropUrl( + imageUrlGenerator.Object, + CreateImageCropperValueWithCrops(), + imageCropMode: ImageCropMode.Crop, + cropAlias: "TestCrop", + ratioMode: ImageCropRatioMode.Height, + width: 60, + height: 40); + + imageUrlGenerator + .Verify(x => x.GetImageUrl( + It.Is(y => y.Width == 60 && + y.Height == 40))); + } + + private static Mock CreateMockImageUrlGenerator() => new Mock(); + + private static ImageCropperValue CreateImageCropperValueWithCrops() => new ImageCropperValue + { + Crops = new List + { + new ImageCropperValue.ImageCropperCrop { Alias = "TestCrop", Width = 100, Height = 200 }, + } + }; + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageProcessors/CropWebProcessorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageProcessors/CropWebProcessorTests.cs new file mode 100644 index 0000000000..2c508d97d2 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageProcessors/CropWebProcessorTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using NUnit.Framework; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Web; +using SixLabors.ImageSharp.Web.Commands; +using SixLabors.ImageSharp.Web.Commands.Converters; +using Umbraco.Cms.Web.Common.ImageProcessors; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.ImageProcessors +{ + [TestFixture] + public class CropWebProcessorTests + { + [Test] + public void CropWebProcessor_CropsImage() + { + var converters = new List + { + CreateArrayConverterOfFloat(), + CreateSimpleCommandConverterOfFloat(), + }; + + var parser = new CommandParser(converters); + CultureInfo culture = CultureInfo.InvariantCulture; + + var commands = new Dictionary + { + { CropWebProcessor.Coordinates, "0.1,0.2,0.1,0.4" }, // left, top, right, bottom + }; + + using var image = new Image(50, 80); + using FormattedImage formatted = CreateFormattedImage(image, PngFormat.Instance); + new CropWebProcessor().Process(formatted, null, commands, parser, culture); + + Assert.AreEqual(40, image.Width); // Cropped 5 pixels from each side. + Assert.AreEqual(32, image.Height); // Cropped 16 pixels from the top and 32 from the bottom. + } + + private static ICommandConverter CreateArrayConverterOfFloat() + { + // ImageSharp.Web's ArrayConverter is internal, so we need to use reflection to instantiate. + var type = Type.GetType("SixLabors.ImageSharp.Web.Commands.Converters.ArrayConverter`1, SixLabors.ImageSharp.Web"); + Type[] typeArgs = { typeof(float) }; + Type genericType = type.MakeGenericType(typeArgs); + return (ICommandConverter)Activator.CreateInstance(genericType); + } + + private static ICommandConverter CreateSimpleCommandConverterOfFloat() + { + // ImageSharp.Web's SimpleCommandConverter is internal, so we need to use reflection to instantiate. + var type = Type.GetType("SixLabors.ImageSharp.Web.Commands.Converters.SimpleCommandConverter`1, SixLabors.ImageSharp.Web"); + Type[] typeArgs = { typeof(float) }; + Type genericType = type.MakeGenericType(typeArgs); + return (ICommandConverter)Activator.CreateInstance(genericType); + } + + private FormattedImage CreateFormattedImage(Image image, PngFormat format) + { + // Again, the constructor of FormattedImage useful for tests is internal, so we need to use reflection. + Type type = typeof(FormattedImage); + var instance = type.Assembly.CreateInstance( + type.FullName, + false, + BindingFlags.Instance | BindingFlags.NonPublic, + null, + new object[] { image, format }, + null, + null); + return (FormattedImage)instance; + } + } +} diff --git a/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs b/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs index ae7e391bba..a6d82e3352 100644 --- a/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs @@ -324,7 +324,7 @@ namespace Umbraco.Extensions ImageUrlGenerationOptions options; if (cropDataSet != null && (imageCropMode == ImageCropMode.Crop || imageCropMode == null)) { - var crop = cropDataSet.GetCrop(cropAlias); + ImageCropperValue.ImageCropperCrop crop = cropDataSet.GetCrop(cropAlias); // If a crop was specified, but not found, return null if (crop == null && !string.IsNullOrWhiteSpace(cropAlias)) From 8b1bd14df1bcccb4bf536a6c5a65fae765d94c4b Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 12 Aug 2021 09:03:02 +0200 Subject: [PATCH 28/36] Remove ImageCropRatioMode and clean up GetCropUrl overloads --- src/Umbraco.Core/Models/ImageCropRatioMode.cs | 8 --- ...ImageCropperTemplateCoreExtensionsTests.cs | 40 ----------- .../Umbraco.Web.Common/ImageCropperTest.cs | 10 +-- .../FriendlyImageCropperTemplateExtensions.cs | 27 +++----- .../ImageCropperTemplateCoreExtensions.cs | 69 +++++-------------- .../Extensions/UrlHelperExtensions.cs | 6 +- 6 files changed, 33 insertions(+), 127 deletions(-) delete mode 100644 src/Umbraco.Core/Models/ImageCropRatioMode.cs diff --git a/src/Umbraco.Core/Models/ImageCropRatioMode.cs b/src/Umbraco.Core/Models/ImageCropRatioMode.cs deleted file mode 100644 index 19f69cbeac..0000000000 --- a/src/Umbraco.Core/Models/ImageCropRatioMode.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Cms.Core.Models -{ - public enum ImageCropRatioMode - { - Width, - Height - } -} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensionsTests.cs index 0389c51726..2d16805a89 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensionsTests.cs @@ -101,25 +101,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Extensions y.Height == 50))); } - [Test] - public void GetCropUrl_WithCropSpecifiedAndWidthRatioModeProvidedWithHeight_CallsImageGeneratorWithCorrectParameters() - { - var imageUrl = "/test.jpg"; - Mock imageUrlGenerator = CreateMockImageUrlGenerator(); - var result = imageUrl.GetCropUrl( - imageUrlGenerator.Object, - CreateImageCropperValueWithCrops(), - imageCropMode: ImageCropMode.Crop, - cropAlias: "TestCrop", - ratioMode: ImageCropRatioMode.Width, - height: 50); - - imageUrlGenerator - .Verify(x => x.GetImageUrl( - It.Is(y => y.Width == 50 && - y.Height == 50))); - } - [Test] public void GetCropUrl_WithCropSpecifiedAndWidthRatioModeProvidedWithWidthAndHeight_CallsImageGeneratorWithCorrectParameters() { @@ -130,7 +111,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Extensions CreateImageCropperValueWithCrops(), imageCropMode: ImageCropMode.Crop, cropAlias: "TestCrop", - ratioMode: ImageCropRatioMode.Width, width: 35, height: 50); @@ -140,25 +120,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Extensions y.Height == 50))); } - [Test] - public void GetCropUrl_WithCropSpecifiedAndHeightRatioModeProvidedWithWidth_CallsImageGeneratorWithCorrectParameters() - { - var imageUrl = "/test.jpg"; - Mock imageUrlGenerator = CreateMockImageUrlGenerator(); - var result = imageUrl.GetCropUrl( - imageUrlGenerator.Object, - CreateImageCropperValueWithCrops(), - imageCropMode: ImageCropMode.Crop, - cropAlias: "TestCrop", - ratioMode: ImageCropRatioMode.Height, - width: 60); - - imageUrlGenerator - .Verify(x => x.GetImageUrl( - It.Is(y => y.Width == 60 && - y.Height == 60))); - } - [Test] public void GetCropUrl_WithCropSpecifiedAndHeightRatioModeProvidedWithWidthAndHeight_CallsImageGeneratorWithCorrectParameters() { @@ -169,7 +130,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Extensions CreateImageCropperValueWithCrops(), imageCropMode: ImageCropMode.Crop, cropAlias: "TestCrop", - ratioMode: ImageCropRatioMode.Height, width: 60, height: 40); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageCropperTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageCropperTest.cs index 599324b040..ce5e62d799 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageCropperTest.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageCropperTest.cs @@ -145,7 +145,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common [Test] public void GetCropUrlFurtherOptionsTest() { - var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 200, height: 300, furtherOptions: "&filter=comic&roundedcorners=radius-26|bgcolor-fff"); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 200, height: 300, furtherOptions: "filter=comic&roundedcorners=radius-26|bgcolor-fff"); Assert.AreEqual(MediaPath + "?f=0.80827067669172936,0.96&w=200&h=300&filter=comic&roundedcorners=radius-26|bgcolor-fff", urlString); } @@ -176,7 +176,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common [Test] public void GetCropUrl_CropAliasHeightRatioModeTest() { - var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, cropAlias: "Thumb", useCropDimensions: true, ratioMode: ImageCropRatioMode.Height); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, cropAlias: "Thumb", useCropDimensions: true); Assert.AreEqual(MediaPath + "?c=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&w=100&h=100", urlString); } @@ -186,7 +186,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common [Test] public void GetCropUrl_WidthHeightRatioModeTest() { - var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, ratioMode: ImageCropRatioMode.Height); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150); Assert.AreEqual(MediaPath + "?f=0.80827067669172936,0.96&w=300&h=150", urlString); } @@ -196,7 +196,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common [Test] public void GetCropUrl_HeightWidthRatioModeTest() { - var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, ratioMode: ImageCropRatioMode.Width); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150); Assert.AreEqual(MediaPath + "?f=0.80827067669172936,0.96&w=300&h=150", urlString); } @@ -321,7 +321,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common { var cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"" + MediaPath + "\",\"crops\": [{\"alias\": \"home\",\"width\": 270,\"height\": 161}]}"; - var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), 400, 400, cropperJson, imageCropMode: ImageCropMode.Pad, furtherOptions: "&bgcolor=fff"); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), 400, 400, cropperJson, imageCropMode: ImageCropMode.Pad, furtherOptions: "bgcolor=fff"); Assert.AreEqual(MediaPath + "?m=pad&w=400&h=400&bgcolor=fff", urlString); } diff --git a/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs b/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs index 19090ee12e..38f6f47235 100644 --- a/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs @@ -82,9 +82,8 @@ namespace Umbraco.Extensions /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated. /// These are any query string parameters (formatted as query strings) that the underlying image processing service supports. For example: /// - /// Use a dimension as a ratio. /// /// The URL of the cropped image. /// @@ -100,8 +99,7 @@ namespace Umbraco.Extensions bool preferFocalPoint = false, bool useCropDimensions = false, bool cacheBuster = true, - string furtherOptions = null, - ImageCropRatioMode? ratioMode = null) + string furtherOptions = null) => mediaItem.GetCropUrl( ImageUrlGenerator, PublishedValueFallback, @@ -116,8 +114,7 @@ namespace Umbraco.Extensions preferFocalPoint, useCropDimensions, cacheBuster, - furtherOptions, - ratioMode + furtherOptions ); /// @@ -136,9 +133,8 @@ namespace Umbraco.Extensions /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated. /// These are any query string parameters (formatted as query strings) that the underlying image processing service supports. For example: /// - /// Use a dimension as a ratio. /// /// The URL of the cropped image. /// @@ -154,8 +150,7 @@ namespace Umbraco.Extensions bool preferFocalPoint = false, bool useCropDimensions = false, string cacheBusterValue = null, - string furtherOptions = null, - ImageCropRatioMode? ratioMode = null) + string furtherOptions = null) => imageUrl.GetCropUrl( ImageUrlGenerator, width, @@ -168,8 +163,7 @@ namespace Umbraco.Extensions preferFocalPoint, useCropDimensions, cacheBusterValue, - furtherOptions, - ratioMode + furtherOptions ); /// @@ -188,9 +182,8 @@ namespace Umbraco.Extensions /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated. /// These are any query string parameters (formatted as query strings) that the underlying image processing service supports. For example: /// - /// Use a dimension as a ratio /// /// The URL of the cropped image. /// @@ -206,8 +199,7 @@ namespace Umbraco.Extensions bool preferFocalPoint = false, bool useCropDimensions = false, string cacheBusterValue = null, - string furtherOptions = null, - ImageCropRatioMode? ratioMode = null) + string furtherOptions = null) => imageUrl.GetCropUrl( ImageUrlGenerator, cropDataSet, @@ -219,8 +211,7 @@ namespace Umbraco.Extensions preferFocalPoint, useCropDimensions, cacheBusterValue, - furtherOptions, - ratioMode + furtherOptions ); diff --git a/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs b/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs index a6d82e3352..ae367b1cf9 100644 --- a/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs @@ -103,9 +103,8 @@ namespace Umbraco.Extensions /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated. /// These are any query string parameters (formatted as query strings) that ImageProcessor supports. For example: /// - /// Use a dimension as a ratio. /// /// The URL of the cropped image. /// @@ -124,8 +123,7 @@ namespace Umbraco.Extensions bool preferFocalPoint = false, bool useCropDimensions = false, bool cacheBuster = true, - string furtherOptions = null, - ImageCropRatioMode? ratioMode = null) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, null, false, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode); + string furtherOptions = null) => mediaItem.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, null, false, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions); public static string GetCropUrl( this MediaWithCrops mediaWithCrops, @@ -142,15 +140,14 @@ namespace Umbraco.Extensions bool preferFocalPoint = false, bool useCropDimensions = false, bool cacheBuster = true, - string furtherOptions = null, - ImageCropRatioMode? ratioMode = null) + string furtherOptions = null) { if (mediaWithCrops == null) { throw new ArgumentNullException(nameof(mediaWithCrops)); } - return mediaWithCrops.Content.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, mediaWithCrops.LocalCrops, false, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode); + return mediaWithCrops.Content.GetCropUrl(imageUrlGenerator, publishedValueFallback, publishedUrlProvider, mediaWithCrops.LocalCrops, false, width, height, propertyAlias, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions); } private static string GetCropUrl( @@ -170,8 +167,7 @@ namespace Umbraco.Extensions bool preferFocalPoint = false, bool useCropDimensions = false, bool cacheBuster = true, - string furtherOptions = null, - ImageCropRatioMode? ratioMode = null) + string furtherOptions = null) { if (mediaItem == null) { @@ -219,7 +215,7 @@ namespace Umbraco.Extensions return GetCropUrl( mediaItemUrl, imageUrlGenerator, localCrops, width, height, cropAlias, quality, imageCropMode, imageCropAnchor, preferFocalPoint, useCropDimensions, - cacheBusterValue, furtherOptions, ratioMode); + cacheBusterValue, furtherOptions); } /// @@ -239,9 +235,8 @@ namespace Umbraco.Extensions /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated. /// These are any query string parameters (formatted as query strings) that the underlying image processing service supports. For example: /// - /// Use a dimension as a ratio. /// /// The URL of the cropped image. /// @@ -258,8 +253,7 @@ namespace Umbraco.Extensions bool preferFocalPoint = false, bool useCropDimensions = false, string cacheBusterValue = null, - string furtherOptions = null, - ImageCropRatioMode? ratioMode = null) + string furtherOptions = null) { if (string.IsNullOrWhiteSpace(imageUrl)) { @@ -274,7 +268,7 @@ namespace Umbraco.Extensions return GetCropUrl( imageUrl, imageUrlGenerator, cropDataSet, width, height, cropAlias, quality, imageCropMode, - imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode); + imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions); } /// @@ -294,9 +288,8 @@ namespace Umbraco.Extensions /// Add a serialized date of the last edit of the item to ensure client cache refresh when updated. /// These are any query string parameters (formatted as query strings) that the underlying image processing service supports. For example: /// - /// Use a dimension as a ratio. /// /// The URL of the cropped image. /// @@ -313,8 +306,7 @@ namespace Umbraco.Extensions bool preferFocalPoint = false, bool useCropDimensions = false, string cacheBusterValue = null, - string furtherOptions = null, - ImageCropRatioMode? ratioMode = null) + string furtherOptions = null) { if (string.IsNullOrWhiteSpace(imageUrl)) { @@ -334,14 +326,14 @@ namespace Umbraco.Extensions options = cropDataSet.GetCropBaseOptions(imageUrl, crop, preferFocalPoint || string.IsNullOrWhiteSpace(cropAlias)); - if (crop != null & useCropDimensions) + if (crop != null && useCropDimensions) { width = crop.Width; height = crop.Height; } - // Calculate missing dimension if a predefined crop has been specified, there are no coordinates and no ratio mode - if (crop != null && string.IsNullOrEmpty(cropAlias) == false && crop.Coordinates == null && ratioMode == null) + // Calculate missing dimension if a predefined crop has been specified, but has no coordinates + if (crop != null && string.IsNullOrEmpty(cropAlias) == false && crop.Coordinates == null) { if (width != null && height == null) { @@ -357,41 +349,14 @@ namespace Umbraco.Extensions { options = new ImageUrlGenerationOptions(imageUrl) { - ImageCropMode = (imageCropMode ?? ImageCropMode.Pad), + ImageCropMode = (imageCropMode ?? ImageCropMode.Pad), // Not sure why we default to Pad ImageCropAnchor = imageCropAnchor }; } options.Quality = quality; - options.Width = ratioMode != null && ratioMode.Value == ImageCropRatioMode.Width ? null : width; - options.Height = ratioMode != null && ratioMode.Value == ImageCropRatioMode.Height ? null : height; - - if (ratioMode == ImageCropRatioMode.Width && height != null) - { - // If only height specified then assume a square - if (width == null) - { - options.Width = height; - } - else - { - options.Width = (int)MathF.Round(height.Value * ((float)width.Value / height.Value)); - } - } - - if (ratioMode == ImageCropRatioMode.Height && width != null) - { - // If only width specified then assume a square - if (height == null) - { - options.Height = width; - } - else - { - options.Height = (int)MathF.Round(width.Value * ((float)height.Value / width.Value)); - } - } - + options.Width = width; + options.Height = height; options.FurtherOptions = furtherOptions; options.CacheBusterValue = cacheBusterValue; diff --git a/src/Umbraco.Web.Common/Extensions/UrlHelperExtensions.cs b/src/Umbraco.Web.Common/Extensions/UrlHelperExtensions.cs index a6dd4ac1ca..aefd102725 100644 --- a/src/Umbraco.Web.Common/Extensions/UrlHelperExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UrlHelperExtensions.cs @@ -246,7 +246,6 @@ namespace Umbraco.Extensions bool useCropDimensions = false, bool cacheBuster = true, string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, bool htmlEncode = true) { if (mediaItem == null) @@ -255,7 +254,7 @@ namespace Umbraco.Extensions } var url = mediaItem.GetCropUrl(width, height, propertyAlias, cropAlias, quality, imageCropMode, - imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode); + imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions); return CreateHtmlString(url, htmlEncode); } @@ -272,14 +271,13 @@ namespace Umbraco.Extensions bool useCropDimensions = true, string cacheBusterValue = null, string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, bool htmlEncode = true) { if (imageCropperValue == null) return HtmlString.Empty; var imageUrl = imageCropperValue.Src; var url = imageUrl.GetCropUrl(imageCropperValue, width, height, cropAlias, quality, imageCropMode, - imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode); + imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions); return CreateHtmlString(url, htmlEncode); } From 8ca35f598b0ebc0525bc45cb0d85303e5eb0480f Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 12 Aug 2021 10:08:41 +0200 Subject: [PATCH 29/36] Removed two effectively duplicate tests. --- ...ImageCropperTemplateCoreExtensionsTests.cs | 38 ------------------- 1 file changed, 38 deletions(-) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensionsTests.cs index 2d16805a89..b3b1e64f94 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensionsTests.cs @@ -101,44 +101,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Extensions y.Height == 50))); } - [Test] - public void GetCropUrl_WithCropSpecifiedAndWidthRatioModeProvidedWithWidthAndHeight_CallsImageGeneratorWithCorrectParameters() - { - var imageUrl = "/test.jpg"; - Mock imageUrlGenerator = CreateMockImageUrlGenerator(); - var result = imageUrl.GetCropUrl( - imageUrlGenerator.Object, - CreateImageCropperValueWithCrops(), - imageCropMode: ImageCropMode.Crop, - cropAlias: "TestCrop", - width: 35, - height: 50); - - imageUrlGenerator - .Verify(x => x.GetImageUrl( - It.Is(y => y.Width == 35 && - y.Height == 50))); - } - - [Test] - public void GetCropUrl_WithCropSpecifiedAndHeightRatioModeProvidedWithWidthAndHeight_CallsImageGeneratorWithCorrectParameters() - { - var imageUrl = "/test.jpg"; - Mock imageUrlGenerator = CreateMockImageUrlGenerator(); - var result = imageUrl.GetCropUrl( - imageUrlGenerator.Object, - CreateImageCropperValueWithCrops(), - imageCropMode: ImageCropMode.Crop, - cropAlias: "TestCrop", - width: 60, - height: 40); - - imageUrlGenerator - .Verify(x => x.GetImageUrl( - It.Is(y => y.Width == 60 && - y.Height == 40))); - } - private static Mock CreateMockImageUrlGenerator() => new Mock(); private static ImageCropperValue CreateImageCropperValueWithCrops() => new ImageCropperValue From 74ea311e3c2f6a9aca5b56c4a8886ba869d4fdc6 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 13 Aug 2021 14:23:27 +0200 Subject: [PATCH 30/36] Remove width/height when both are above the configured maximums, add comments/documentation --- src/Umbraco.Core/Media/IImageUrlGenerator.cs | 18 ++++++++++- .../Media/ImageSharpImageUrlGenerator.cs | 11 +++++-- .../UmbracoBuilder.ImageSharp.cs | 31 +++++++++---------- 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Core/Media/IImageUrlGenerator.cs b/src/Umbraco.Core/Media/IImageUrlGenerator.cs index c8628f3147..bf61127561 100644 --- a/src/Umbraco.Core/Media/IImageUrlGenerator.cs +++ b/src/Umbraco.Core/Media/IImageUrlGenerator.cs @@ -1,12 +1,28 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Media { + /// + /// Exposes a method that generates an image URL based on the specified options. + /// public interface IImageUrlGenerator { + /// + /// Gets the supported image file types/extensions. + /// + /// + /// The supported image file types/extensions. + /// IEnumerable SupportedImageFileTypes { get; } + /// + /// Gets the image URL based on the specified . + /// + /// The image URL generation options. + /// + /// The generated image URL. + /// string GetImageUrl(ImageUrlGenerationOptions options); } } diff --git a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs index 6331ad24e1..6fc5e46f2c 100644 --- a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs +++ b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs @@ -9,12 +9,21 @@ using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Infrastructure.Media { + /// + /// Exposes a method that generates an image URL based on the specified options that can be processed by ImageSharp. + /// + /// public class ImageSharpImageUrlGenerator : IImageUrlGenerator { private static readonly string[] s_supportedImageFileTypes = Configuration.Default.ImageFormats.SelectMany(f => f.FileExtensions).ToArray(); + /// + /// + /// This uses the default instance of the ImageSharp configuration, so we need to ensure we don't new up a different instance when configuring the middleware. + /// public IEnumerable SupportedImageFileTypes { get; } = s_supportedImageFileTypes; + /// public string GetImageUrl(ImageUrlGenerationOptions options) { if (options == null) @@ -82,7 +91,5 @@ namespace Umbraco.Cms.Infrastructure.Media return imageUrl.ToString(); } - - } } diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs index ec14091092..6a6231471e 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs @@ -27,14 +27,27 @@ namespace Umbraco.Extensions services.AddImageSharp(options => { + // We use the same default configuration instance in ImageSharpImageUrlGenerator, so we don't want to create a new instance here options.Configuration = SixLabors.ImageSharp.Configuration.Default; options.BrowserMaxAge = imagingSettings.Cache.BrowserMaxAge; options.CacheMaxAge = imagingSettings.Cache.CacheMaxAge; options.CachedNameLength = imagingSettings.Cache.CachedNameLength; + + // Use configurable maximum width and height (overwrite ImageSharps default) options.OnParseCommandsAsync = context => { - RemoveIntParamenterIfValueGreatherThen(context.Commands, ResizeWebProcessor.Width, imagingSettings.Resize.MaxWidth); - RemoveIntParamenterIfValueGreatherThen(context.Commands, ResizeWebProcessor.Height, imagingSettings.Resize.MaxHeight); + if (context.Commands.Count == 0) + { + return Task.CompletedTask; + } + + uint width = context.Parser.ParseValue(context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), context.Culture); + uint height = context.Parser.ParseValue(context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), context.Culture); + if (width > imagingSettings.Resize.MaxWidth && height > imagingSettings.Resize.MaxHeight) + { + context.Commands.Remove(ResizeWebProcessor.Width); + context.Commands.Remove(ResizeWebProcessor.Height); + } return Task.CompletedTask; }; @@ -55,19 +68,5 @@ namespace Umbraco.Extensions return services; } - - private static void RemoveIntParamenterIfValueGreatherThen(IDictionary commands, string parameter, int maxValue) - { - if (commands.TryGetValue(parameter, out var command)) - { - if (int.TryParse(command, out var i)) - { - if (i > maxValue) - { - commands.Remove(parameter); - } - } - } - } } } From 36568a0fdc173136cf36210a69d1d87975e3a5ed Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 13 Aug 2021 15:53:36 +0200 Subject: [PATCH 31/36] Move ImageSharpImageUrlGenerator to Web.Common and add NoopImageUrlGenerator --- .../UmbracoBuilder.CoreServices.cs | 2 +- .../Media/NoopImageUrlGenerator.cs | 16 ++++++++ .../Media/ImageSharpImageUrlGeneratorTests.cs | 6 +-- .../UmbracoBuilder.ImageSharp.cs | 5 ++- .../Media/ImageSharpImageUrlGenerator.cs | 41 +++++++++++++------ 5 files changed, 52 insertions(+), 18 deletions(-) create mode 100644 src/Umbraco.Infrastructure/Media/NoopImageUrlGenerator.cs rename src/Umbraco.Tests.UnitTests/{Umbraco.Infrastructure => Umbraco.Web.Common}/Media/ImageSharpImageUrlGeneratorTests.cs (98%) rename src/{Umbraco.Infrastructure => Umbraco.Web.Common}/Media/ImageSharpImageUrlGenerator.cs (56%) diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 5b7e4a336c..d8e3625591 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -144,7 +144,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.PropertyValueConverters() .Remove(); - builder.Services.AddUnique(); + builder.Services.AddUnique(); // register *all* checks, except those marked [HideFromTypeFinder] of course builder.Services.AddUnique(); diff --git a/src/Umbraco.Infrastructure/Media/NoopImageUrlGenerator.cs b/src/Umbraco.Infrastructure/Media/NoopImageUrlGenerator.cs new file mode 100644 index 0000000000..158f7d6c04 --- /dev/null +++ b/src/Umbraco.Infrastructure/Media/NoopImageUrlGenerator.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Cms.Core.Media; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Media +{ + public class NoopImageUrlGenerator : IImageUrlGenerator + { + /// + public IEnumerable SupportedImageFileTypes => Enumerable.Empty(); + + /// + public string GetImageUrl(ImageUrlGenerationOptions options) => options?.ImageUrl; + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Media/ImageSharpImageUrlGeneratorTests.cs similarity index 98% rename from src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Media/ImageSharpImageUrlGeneratorTests.cs index 91dd1a8c53..d113f144a3 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Media/ImageSharpImageUrlGeneratorTests.cs @@ -3,9 +3,9 @@ using NUnit.Framework; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Infrastructure.Media; +using Umbraco.Cms.Web.Common.Media; -namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Media { [TestFixture] public class ImageSharpImageUrlGeneratorTests @@ -14,7 +14,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media private static readonly ImageUrlGenerationOptions.CropCoordinates s_crop = new ImageUrlGenerationOptions.CropCoordinates(0.58729977382575338m, 0.055768992440203169m, 0m, 0.32457553600198386m); private static readonly ImageUrlGenerationOptions.FocalPointPosition s_focus1 = new ImageUrlGenerationOptions.FocalPointPosition(0.96m, 0.80827067669172936m); private static readonly ImageUrlGenerationOptions.FocalPointPosition s_focus2 = new ImageUrlGenerationOptions.FocalPointPosition(0.4275m, 0.41m); - private static readonly ImageSharpImageUrlGenerator s_generator = new ImageSharpImageUrlGenerator(); + private static readonly ImageSharpImageUrlGenerator s_generator = new ImageSharpImageUrlGenerator(new string[0]); [Test] public void GetCropUrl_CropAliasTest() diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs index 6a6231471e..72fe74bbe6 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs @@ -8,7 +8,9 @@ using SixLabors.ImageSharp.Web.DependencyInjection; using SixLabors.ImageSharp.Web.Processors; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Media; using Umbraco.Cms.Web.Common.ImageProcessors; +using Umbraco.Cms.Web.Common.Media; namespace Umbraco.Extensions { @@ -27,7 +29,6 @@ namespace Umbraco.Extensions services.AddImageSharp(options => { - // We use the same default configuration instance in ImageSharpImageUrlGenerator, so we don't want to create a new instance here options.Configuration = SixLabors.ImageSharp.Configuration.Default; options.BrowserMaxAge = imagingSettings.Cache.BrowserMaxAge; options.CacheMaxAge = imagingSettings.Cache.CacheMaxAge; @@ -66,6 +67,8 @@ namespace Umbraco.Extensions .AddProcessor() .AddProcessor(); + builder.Services.AddUnique(); + return services; } } diff --git a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs b/src/Umbraco.Web.Common/Media/ImageSharpImageUrlGenerator.cs similarity index 56% rename from src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs rename to src/Umbraco.Web.Common/Media/ImageSharpImageUrlGenerator.cs index 6fc5e46f2c..ead8d3916b 100644 --- a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs +++ b/src/Umbraco.Web.Common/Media/ImageSharpImageUrlGenerator.cs @@ -3,11 +3,14 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; -using SixLabors.ImageSharp; +using Microsoft.Extensions.Options; +using SixLabors.ImageSharp.Web.Middleware; +using SixLabors.ImageSharp.Web.Processors; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Web.Common.ImageProcessors; -namespace Umbraco.Cms.Infrastructure.Media +namespace Umbraco.Cms.Web.Common.Media { /// /// Exposes a method that generates an image URL based on the specified options that can be processed by ImageSharp. @@ -15,13 +18,25 @@ namespace Umbraco.Cms.Infrastructure.Media /// public class ImageSharpImageUrlGenerator : IImageUrlGenerator { - private static readonly string[] s_supportedImageFileTypes = Configuration.Default.ImageFormats.SelectMany(f => f.FileExtensions).ToArray(); - /// + public IEnumerable SupportedImageFileTypes { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The options. + public ImageSharpImageUrlGenerator(IOptions options) + : this(options.Value.Configuration.ImageFormats.SelectMany(f => f.FileExtensions).ToArray()) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The supported image file types/extensions. /// - /// This uses the default instance of the ImageSharp configuration, so we need to ensure we don't new up a different instance when configuring the middleware. + /// This constructor is only used for testing. /// - public IEnumerable SupportedImageFileTypes { get; } = s_supportedImageFileTypes; + internal ImageSharpImageUrlGenerator(IEnumerable supportedImageFileTypes) => SupportedImageFileTypes = supportedImageFileTypes; /// public string GetImageUrl(ImageUrlGenerationOptions options) @@ -46,37 +61,37 @@ namespace Umbraco.Cms.Infrastructure.Media if (options.FocalPoint != null) { - AddQueryString("rxy", options.FocalPoint.Left, options.FocalPoint.Top); + AddQueryString(ResizeWebProcessor.Xy, options.FocalPoint.Left, options.FocalPoint.Top); } if (options.Crop != null) { - AddQueryString("cc", options.Crop.Left, options.Crop.Top, options.Crop.Right, options.Crop.Bottom); + AddQueryString(CropWebProcessor.Coordinates, options.Crop.Left, options.Crop.Top, options.Crop.Right, options.Crop.Bottom); } if (options.ImageCropMode.HasValue) { - AddQueryString("rmode", options.ImageCropMode.Value.ToString().ToLowerInvariant()); + AddQueryString(ResizeWebProcessor.Mode, options.ImageCropMode.Value.ToString().ToLowerInvariant()); } if (options.ImageCropAnchor.HasValue) { - AddQueryString("ranchor", options.ImageCropAnchor.Value.ToString().ToLowerInvariant()); + AddQueryString(ResizeWebProcessor.Anchor, options.ImageCropAnchor.Value.ToString().ToLowerInvariant()); } if (options.Width.HasValue) { - AddQueryString("width", options.Width.Value); + AddQueryString(ResizeWebProcessor.Width, options.Width.Value); } if (options.Height.HasValue) { - AddQueryString("height", options.Height.Value); + AddQueryString(ResizeWebProcessor.Height, options.Height.Value); } if (options.Quality.HasValue) { - AddQueryString("quality", options.Quality.Value); + AddQueryString(JpegQualityWebProcessor.Quality, options.Quality.Value); } if (string.IsNullOrWhiteSpace(options.FurtherOptions) == false) From a0184d555678dee7b27eeb3faec91c58da645388 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 13 Aug 2021 16:04:44 +0200 Subject: [PATCH 32/36] Remove repeated ImageSharp default configuration --- .../DependencyInjection/UmbracoBuilder.ImageSharp.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs index 72fe74bbe6..df27b5c442 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs @@ -29,7 +29,6 @@ namespace Umbraco.Extensions services.AddImageSharp(options => { - options.Configuration = SixLabors.ImageSharp.Configuration.Default; options.BrowserMaxAge = imagingSettings.Cache.BrowserMaxAge; options.CacheMaxAge = imagingSettings.Cache.CacheMaxAge; options.CachedNameLength = imagingSettings.Cache.CachedNameLength; @@ -52,17 +51,12 @@ namespace Umbraco.Extensions return Task.CompletedTask; }; - options.OnBeforeSaveAsync = _ => Task.CompletedTask; - options.OnProcessedAsync = _ => Task.CompletedTask; - options.OnPrepareResponseAsync = _ => Task.CompletedTask; }) - .SetRequestParser() .Configure(options => { options.CacheFolder = imagingSettings.Cache.CacheFolder; }) - .SetCache() - .SetCacheHash() + // We need to add CropWebProcessor before ResizeWebProcessor (until https://github.com/SixLabors/ImageSharp.Web/issues/182 is fixed) .RemoveProcessor() .AddProcessor() .AddProcessor(); From 42c6aa8d565966a9c7af269e1cf1bdc90bb0c9c5 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Mon, 16 Aug 2021 10:35:37 +0200 Subject: [PATCH 33/36] Register default ImageSharp configuration for application-wide use --- .../UmbracoBuilder.CoreServices.cs | 2 ++ .../Media/ImageDimensionExtractor.cs | 13 +++++++- .../ImageSharpConfigurationOptions.cs | 30 +++++++++++++++++++ .../UmbracoBuilder.ImageSharp.cs | 10 ++++--- .../Media/ImageSharpImageUrlGenerator.cs | 9 +++--- 5 files changed, 54 insertions(+), 10 deletions(-) create mode 100644 src/Umbraco.Web.Common/DependencyInjection/ImageSharpConfigurationOptions.cs diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index d8e3625591..393fb60df4 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -180,6 +180,8 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(); + // Add default ImageSharp configuration + builder.Services.AddUnique(SixLabors.ImageSharp.Configuration.Default); builder.Services.AddUnique(); builder.Services.AddUnique(); diff --git a/src/Umbraco.Infrastructure/Media/ImageDimensionExtractor.cs b/src/Umbraco.Infrastructure/Media/ImageDimensionExtractor.cs index f8dc089a0d..380704c26c 100644 --- a/src/Umbraco.Infrastructure/Media/ImageDimensionExtractor.cs +++ b/src/Umbraco.Infrastructure/Media/ImageDimensionExtractor.cs @@ -8,6 +8,17 @@ namespace Umbraco.Cms.Infrastructure.Media { internal class ImageDimensionExtractor : IImageDimensionExtractor { + /// + /// The ImageSharp configuration. + /// + private readonly Configuration _configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The ImageSharp configuration. + public ImageDimensionExtractor(Configuration configuration) => _configuration = configuration; + /// /// Gets the dimensions of an image. /// @@ -39,7 +50,7 @@ namespace Umbraco.Cms.Infrastructure.Media stream.Seek(0, SeekOrigin.Begin); } - using (var image = Image.Load(stream)) + using (var image = Image.Load(_configuration, stream)) { var fileWidth = image.Width; var fileHeight = image.Height; diff --git a/src/Umbraco.Web.Common/DependencyInjection/ImageSharpConfigurationOptions.cs b/src/Umbraco.Web.Common/DependencyInjection/ImageSharpConfigurationOptions.cs new file mode 100644 index 0000000000..86b1dd8a5c --- /dev/null +++ b/src/Umbraco.Web.Common/DependencyInjection/ImageSharpConfigurationOptions.cs @@ -0,0 +1,30 @@ +using Microsoft.Extensions.Options; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Web.Middleware; + +namespace Umbraco.Cms.Web.Common.DependencyInjection +{ + /// + /// Configures the ImageSharp middleware options to use the registered configuration. + /// + /// + internal class ImageSharpConfigurationOptions : IConfigureOptions + { + /// + /// The ImageSharp configuration. + /// + private readonly Configuration _configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The ImageSharp configuration. + public ImageSharpConfigurationOptions(Configuration configuration) => _configuration = configuration; + + /// + /// Invoked to configure a instance. + /// + /// The options instance to configure. + public void Configure(ImageSharpMiddlewareOptions options) => options.Configuration = _configuration; + } +} diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs index df27b5c442..5661fd1487 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs @@ -2,13 +2,16 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using SixLabors.ImageSharp.Web.Caching; using SixLabors.ImageSharp.Web.Commands; using SixLabors.ImageSharp.Web.DependencyInjection; +using SixLabors.ImageSharp.Web.Middleware; using SixLabors.ImageSharp.Web.Processors; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Media; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Cms.Web.Common.ImageProcessors; using Umbraco.Cms.Web.Common.Media; @@ -29,6 +32,7 @@ namespace Umbraco.Extensions services.AddImageSharp(options => { + // The configuration is set using ImageSharpConfigurationOptions options.BrowserMaxAge = imagingSettings.Cache.BrowserMaxAge; options.CacheMaxAge = imagingSettings.Cache.CacheMaxAge; options.CachedNameLength = imagingSettings.Cache.CachedNameLength; @@ -52,15 +56,13 @@ namespace Umbraco.Extensions return Task.CompletedTask; }; }) - .Configure(options => - { - options.CacheFolder = imagingSettings.Cache.CacheFolder; - }) + .Configure(options => options.CacheFolder = imagingSettings.Cache.CacheFolder) // We need to add CropWebProcessor before ResizeWebProcessor (until https://github.com/SixLabors/ImageSharp.Web/issues/182 is fixed) .RemoveProcessor() .AddProcessor() .AddProcessor(); + builder.Services.AddTransient, ImageSharpConfigurationOptions>(); builder.Services.AddUnique(); return services; diff --git a/src/Umbraco.Web.Common/Media/ImageSharpImageUrlGenerator.cs b/src/Umbraco.Web.Common/Media/ImageSharpImageUrlGenerator.cs index ead8d3916b..276a3ab492 100644 --- a/src/Umbraco.Web.Common/Media/ImageSharpImageUrlGenerator.cs +++ b/src/Umbraco.Web.Common/Media/ImageSharpImageUrlGenerator.cs @@ -3,8 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; -using Microsoft.Extensions.Options; -using SixLabors.ImageSharp.Web.Middleware; +using SixLabors.ImageSharp; using SixLabors.ImageSharp.Web.Processors; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; @@ -24,9 +23,9 @@ namespace Umbraco.Cms.Web.Common.Media /// /// Initializes a new instance of the class. /// - /// The options. - public ImageSharpImageUrlGenerator(IOptions options) - : this(options.Value.Configuration.ImageFormats.SelectMany(f => f.FileExtensions).ToArray()) + /// The ImageSharp configuration. + public ImageSharpImageUrlGenerator(Configuration configuration) + : this(configuration.ImageFormats.SelectMany(f => f.FileExtensions).ToArray()) { } /// From 992eb5802326a1282d277d73e7e4d073b35fc522 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Mon, 16 Aug 2021 11:08:09 +0200 Subject: [PATCH 34/36] Move ImageSharpImageUrlGenerator back to Infrastructure and remove NoopImageUrlGenerator --- .../UmbracoBuilder.CoreServices.cs | 5 ++--- .../Media/ImageSharpImageUrlGenerator.cs | 18 ++++++++---------- .../Media/NoopImageUrlGenerator.cs | 16 ---------------- .../Media/ImageSharpImageUrlGeneratorTests.cs | 5 ++--- .../UmbracoBuilder.ImageSharp.cs | 12 +++--------- 5 files changed, 15 insertions(+), 41 deletions(-) rename src/{Umbraco.Web.Common => Umbraco.Infrastructure}/Media/ImageSharpImageUrlGenerator.cs (78%) delete mode 100644 src/Umbraco.Infrastructure/Media/NoopImageUrlGenerator.cs rename src/Umbraco.Tests.UnitTests/{Umbraco.Web.Common => Umbraco.Infrastructure}/Media/ImageSharpImageUrlGeneratorTests.cs (98%) diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 393fb60df4..8388841e17 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -144,8 +144,6 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.PropertyValueConverters() .Remove(); - builder.Services.AddUnique(); - // register *all* checks, except those marked [HideFromTypeFinder] of course builder.Services.AddUnique(); @@ -180,9 +178,10 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection builder.Services.AddUnique(); - // Add default ImageSharp configuration + // Add default ImageSharp configuration and service implementations builder.Services.AddUnique(SixLabors.ImageSharp.Configuration.Default); builder.Services.AddUnique(); + builder.Services.AddUnique(); builder.Services.AddUnique(); diff --git a/src/Umbraco.Web.Common/Media/ImageSharpImageUrlGenerator.cs b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs similarity index 78% rename from src/Umbraco.Web.Common/Media/ImageSharpImageUrlGenerator.cs rename to src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs index 276a3ab492..ecde6790d2 100644 --- a/src/Umbraco.Web.Common/Media/ImageSharpImageUrlGenerator.cs +++ b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs @@ -4,12 +4,10 @@ using System.Globalization; using System.Linq; using System.Text; using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Web.Processors; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Web.Common.ImageProcessors; -namespace Umbraco.Cms.Web.Common.Media +namespace Umbraco.Cms.Infrastructure.Media { /// /// Exposes a method that generates an image URL based on the specified options that can be processed by ImageSharp. @@ -60,37 +58,37 @@ namespace Umbraco.Cms.Web.Common.Media if (options.FocalPoint != null) { - AddQueryString(ResizeWebProcessor.Xy, options.FocalPoint.Left, options.FocalPoint.Top); + AddQueryString("rxy", options.FocalPoint.Left, options.FocalPoint.Top); } if (options.Crop != null) { - AddQueryString(CropWebProcessor.Coordinates, options.Crop.Left, options.Crop.Top, options.Crop.Right, options.Crop.Bottom); + AddQueryString("cc", options.Crop.Left, options.Crop.Top, options.Crop.Right, options.Crop.Bottom); } if (options.ImageCropMode.HasValue) { - AddQueryString(ResizeWebProcessor.Mode, options.ImageCropMode.Value.ToString().ToLowerInvariant()); + AddQueryString("rmode", options.ImageCropMode.Value.ToString().ToLowerInvariant()); } if (options.ImageCropAnchor.HasValue) { - AddQueryString(ResizeWebProcessor.Anchor, options.ImageCropAnchor.Value.ToString().ToLowerInvariant()); + AddQueryString("ranchor", options.ImageCropAnchor.Value.ToString().ToLowerInvariant()); } if (options.Width.HasValue) { - AddQueryString(ResizeWebProcessor.Width, options.Width.Value); + AddQueryString("width", options.Width.Value); } if (options.Height.HasValue) { - AddQueryString(ResizeWebProcessor.Height, options.Height.Value); + AddQueryString("height", options.Height.Value); } if (options.Quality.HasValue) { - AddQueryString(JpegQualityWebProcessor.Quality, options.Quality.Value); + AddQueryString("quality", options.Quality.Value); } if (string.IsNullOrWhiteSpace(options.FurtherOptions) == false) diff --git a/src/Umbraco.Infrastructure/Media/NoopImageUrlGenerator.cs b/src/Umbraco.Infrastructure/Media/NoopImageUrlGenerator.cs deleted file mode 100644 index 158f7d6c04..0000000000 --- a/src/Umbraco.Infrastructure/Media/NoopImageUrlGenerator.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.Media; -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Cms.Infrastructure.Media -{ - public class NoopImageUrlGenerator : IImageUrlGenerator - { - /// - public IEnumerable SupportedImageFileTypes => Enumerable.Empty(); - - /// - public string GetImageUrl(ImageUrlGenerationOptions options) => options?.ImageUrl; - } -} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Media/ImageSharpImageUrlGeneratorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs similarity index 98% rename from src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Media/ImageSharpImageUrlGeneratorTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs index d113f144a3..a531aa6bbd 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Media/ImageSharpImageUrlGeneratorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs @@ -3,9 +3,9 @@ using NUnit.Framework; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Web.Common.Media; +using Umbraco.Cms.Infrastructure.Media; -namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Media +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Media { [TestFixture] public class ImageSharpImageUrlGeneratorTests @@ -74,7 +74,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Media Assert.AreEqual("?cc=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&width=100&height=100", urlString); } - /// /// Test that if Crop mode is specified as anything other than Crop the image doesn't use the crop /// diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs index 5661fd1487..5fb8fe2260 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs @@ -10,10 +10,8 @@ using SixLabors.ImageSharp.Web.Middleware; using SixLabors.ImageSharp.Web.Processors; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.Media; using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Cms.Web.Common.ImageProcessors; -using Umbraco.Cms.Web.Common.Media; namespace Umbraco.Extensions { @@ -24,13 +22,10 @@ namespace Umbraco.Extensions /// public static IServiceCollection AddUmbracoImageSharp(this IUmbracoBuilder builder) { - IConfiguration configuration = builder.Config; - IServiceCollection services = builder.Services; - - ImagingSettings imagingSettings = configuration.GetSection(Cms.Core.Constants.Configuration.ConfigImaging) + ImagingSettings imagingSettings = builder.Config.GetSection(Cms.Core.Constants.Configuration.ConfigImaging) .Get() ?? new ImagingSettings(); - services.AddImageSharp(options => + builder.Services.AddImageSharp(options => { // The configuration is set using ImageSharpConfigurationOptions options.BrowserMaxAge = imagingSettings.Cache.BrowserMaxAge; @@ -63,9 +58,8 @@ namespace Umbraco.Extensions .AddProcessor(); builder.Services.AddTransient, ImageSharpConfigurationOptions>(); - builder.Services.AddUnique(); - return services; + return builder.Services; } } } From 43704da869651967cd8bc636267adb1a9306be3c Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Mon, 16 Aug 2021 11:12:54 +0200 Subject: [PATCH 35/36] Remove dimensions if either one is above the configured maximum --- .../DependencyInjection/UmbracoBuilder.ImageSharp.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs index 5fb8fe2260..280d48f64b 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs @@ -42,7 +42,7 @@ namespace Umbraco.Extensions uint width = context.Parser.ParseValue(context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), context.Culture); uint height = context.Parser.ParseValue(context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), context.Culture); - if (width > imagingSettings.Resize.MaxWidth && height > imagingSettings.Resize.MaxHeight) + if (width > imagingSettings.Resize.MaxWidth || height > imagingSettings.Resize.MaxHeight) { context.Commands.Remove(ResizeWebProcessor.Width); context.Commands.Remove(ResizeWebProcessor.Height); From 0b53249d5742404e7511d129ae55503ce8673c85 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Mon, 16 Aug 2021 12:11:30 +0200 Subject: [PATCH 36/36] Make ImageSharpConfigurationOptions public --- .../DependencyInjection/ImageSharpConfigurationOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.Common/DependencyInjection/ImageSharpConfigurationOptions.cs b/src/Umbraco.Web.Common/DependencyInjection/ImageSharpConfigurationOptions.cs index 86b1dd8a5c..628345dcd6 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/ImageSharpConfigurationOptions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/ImageSharpConfigurationOptions.cs @@ -8,7 +8,7 @@ namespace Umbraco.Cms.Web.Common.DependencyInjection /// Configures the ImageSharp middleware options to use the registered configuration. /// /// - internal class ImageSharpConfigurationOptions : IConfigureOptions + public sealed class ImageSharpConfigurationOptions : IConfigureOptions { /// /// The ImageSharp configuration.