diff --git a/src/Umbraco.Core/Media/IImageUrlGenerator.cs b/src/Umbraco.Core/Media/IImageUrlGenerator.cs index c8628f3147b0..bf6112756111 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.Core/Models/ImageCropRatioMode.cs b/src/Umbraco.Core/Models/ImageCropRatioMode.cs deleted file mode 100644 index 19f69cbeac1d..000000000000 --- 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.Core/Models/ImageUrlGenerationOptions.cs b/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs index 99f42bbefa5f..ba5c1d0e307f 100644 --- a/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs +++ b/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs @@ -1,66 +1,68 @@ -namespace Umbraco.Cms.Core.Models +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; } - public decimal? WidthRatio { get; set; } - public decimal? HeightRatio { get; set; } + public int? Quality { get; set; } + public ImageCropMode? ImageCropMode { get; set; } + public ImageCropAnchor? ImageCropAnchor { get; set; } - 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; } - public bool UpScale { get; set; } = true; - 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 left, decimal top) { 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 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 Y1 { get; } - public decimal X2 { get; } - public decimal Y2 { get; } + public decimal Left { get; } + + public decimal Top { get; } + + public decimal Right { get; } + + public decimal Bottom { get; } } } } diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 5b7e4a336c6c..8388841e179e 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -144,8 +144,6 @@ public static IUmbracoBuilder AddCoreInitialServices(this IUmbracoBuilder builde builder.PropertyValueConverters() .Remove(); - builder.Services.AddUnique(); - // register *all* checks, except those marked [HideFromTypeFinder] of course builder.Services.AddUnique(); @@ -180,7 +178,10 @@ public static IUmbracoBuilder AddCoreInitialServices(this IUmbracoBuilder builde builder.Services.AddUnique(); + // 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.Infrastructure/Media/ImageDimensionExtractor.cs b/src/Umbraco.Infrastructure/Media/ImageDimensionExtractor.cs index f8dc089a0d73..380704c26c3e 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 @@ public ImageSize GetDimensions(Stream stream) 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.Infrastructure/Media/ImageSharpImageUrlGenerator.cs b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs index e1bf2c197b2d..ecde6790d2d7 100644 --- a/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs +++ b/src/Umbraco.Infrastructure/Media/ImageSharpImageUrlGenerator.cs @@ -1,68 +1,107 @@ -using System.Collections.Generic; +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 { + /// + /// Exposes a method that generates an image URL based on the specified options that can be processed by ImageSharp. + /// + /// public class ImageSharpImageUrlGenerator : IImageUrlGenerator { - public IEnumerable SupportedImageFileTypes => new[] { "jpeg", "jpg", "gif", "bmp", "png" }; + /// + public IEnumerable SupportedImageFileTypes { get; } + /// + /// Initializes a new instance of the class. + /// + /// The ImageSharp configuration. + public ImageSharpImageUrlGenerator(Configuration configuration) + : this(configuration.ImageFormats.SelectMany(f => f.FileExtensions).ToArray()) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The supported image file types/extensions. + /// + /// This constructor is only used for testing. + /// + internal ImageSharpImageUrlGenerator(IEnumerable supportedImageFileTypes) => SupportedImageFileTypes = supportedImageFileTypes; + + /// 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"); - else + bool queryStringHasStarted = false; + void AppendQueryString(string value) { - imageProcessorUrl.Append("?mode=").Append((options.ImageCropMode ?? ImageCropMode.Crop).ToString().ToLower()); + imageUrl.Append(queryStringHasStarted ? '&' : '?'); + queryStringHasStarted = true; - if (options.ImageCropAnchor != null) imageProcessorUrl.Append("&anchor=").Append(options.ImageCropAnchor.ToString().ToLower()); + imageUrl.Append(value); } + void AddQueryString(string key, params IConvertible[] values) + => AppendQueryString(key + '=' + string.Join(",", values.Select(x => x.ToString(CultureInfo.InvariantCulture)))); - var hasFormat = options.FurtherOptions != null && options.FurtherOptions.InvariantContains("&format="); + if (options.FocalPoint != null) + { + AddQueryString("rxy", options.FocalPoint.Left, options.FocalPoint.Top); + } - //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); + if (options.Crop != null) + { + AddQueryString("cc", options.Crop.Left, options.Crop.Top, options.Crop.Right, options.Crop.Bottom); + } - //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.ImageCropMode.HasValue) + { + AddQueryString("rmode", options.ImageCropMode.Value.ToString().ToLowerInvariant()); + } - return imageProcessorUrl.ToString(); - } + if (options.ImageCropAnchor.HasValue) + { + AddQueryString("ranchor", options.ImageCropAnchor.Value.ToString().ToLowerInvariant()); + } - private void AppendFocalPoint(StringBuilder imageProcessorUrl, ImageUrlGenerationOptions options) - { - imageProcessorUrl.Append("?center="); - imageProcessorUrl.Append(options.FocalPoint.Top.ToString(CultureInfo.InvariantCulture)).Append(","); - imageProcessorUrl.Append(options.FocalPoint.Left.ToString(CultureInfo.InvariantCulture)); - imageProcessorUrl.Append("&mode=crop"); - } + if (options.Width.HasValue) + { + AddQueryString("width", 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) + { + AddQueryString("height", options.Height.Value); + } + + if (options.Quality.HasValue) + { + AddQueryString("quality", options.Quality.Value); + } + + if (string.IsNullOrWhiteSpace(options.FurtherOptions) == false) + { + AppendQueryString(options.FurtherOptions.TrimStart('?', '&')); + } + + if (string.IsNullOrWhiteSpace(options.CacheBusterValue) == false) + { + AddQueryString("rnd", options.CacheBusterValue); + } + + return imageUrl.ToString(); } } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs index 32101d6cf766..c0efaac4ae57 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; @@ -63,13 +63,11 @@ public ImageCropperCrop GetCrop(string alias) : 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) }; + return new ImageUrlGenerationOptions(url) { FocalPoint = new ImageUrlGenerationOptions.FocalPointPosition(FocalPoint.Left, FocalPoint.Top) }; } else if (crop != null && crop.Coordinates != null && preferFocalPoint == false) { @@ -77,7 +75,7 @@ public ImageUrlGenerationOptions GetCropBaseOptions(string url, ImageCropperCrop } else { - return new ImageUrlGenerationOptions(url) { DefaultCrop = true }; + return new ImageUrlGenerationOptions(url); } } @@ -92,7 +90,7 @@ public string GetCropUrl(string alias, IImageUrlGenerator imageUrlGenerator, boo 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 @@ public string GetCropUrl(string alias, IImageUrlGenerator imageUrlGenerator, boo /// /// 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.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs index 52ee1ed0e4d1..a531aa6bbd01 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs @@ -12,40 +12,40 @@ public class ImageSharpImageUrlGeneratorTests { 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 ImageSharpImageUrlGenerator s_generator = new ImageSharpImageUrlGenerator(); + 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(new string[0]); [Test] 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 + "?center=0.80827067669172936,0.96&mode=crop&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 + "?center=0.80827067669172936,0.96&mode=crop&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 + "?center=0.80827067669172936,0.96&mode=crop&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); } /// - /// 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 @@ public void GetCropUrlNullTest() } /// - /// 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("?mode=crop", urlString); + Assert.AreEqual(string.Empty, urlString); } /// @@ -71,37 +71,7 @@ public void GetCropUrlEmptyTest() 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); - } - - /// - /// 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 + "?center=0.80827067669172936,0.96&mode=crop&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 + "?center=0.80827067669172936,0.96&mode=crop&widthratio=2&height=150", urlString); + Assert.AreEqual("?cc=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&width=100&height=100", urlString); } /// @@ -116,11 +86,11 @@ public void GetCropUrl_SpecifiedCropModeTest() 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 +100,7 @@ public void GetCropUrl_SpecifiedCropModeTest() 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); } /// @@ -139,28 +109,8 @@ public void GetCropUrl_UploadTypeTest() [Test] 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); - } - - /// - /// 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 + "?anchor=center&mode=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 + "?center=0.41,0.4275&mode=crop&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); } /// @@ -170,17 +120,7 @@ public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPoint() 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); - } - - /// - /// 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 + "?anchor=center&mode=crop&widthratio=1.6770186335403726708074534161&height=200", urlString); + Assert.AreEqual(MediaPath + "?rxy=0.4275,0.41&width=270&height=161", urlString); } /// @@ -189,8 +129,8 @@ public void GetCropUrl_PreDefinedCropNoCoordinatesWithHeight() [Test] 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); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Width = 200 }); + Assert.AreEqual(MediaPath + "?width=200", urlString); } /// @@ -199,8 +139,8 @@ public void GetCropUrl_WidthOnlyParameter() [Test] 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); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Height = 200 }); + Assert.AreEqual(MediaPath + "?height=200", urlString); } /// @@ -210,7 +150,7 @@ public void GetCropUrl_HeightOnlyParameter() 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); } } } 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 000000000000..b3b1e64f947c --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensionsTests.cs @@ -0,0 +1,114 @@ +// 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))); + } + + 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/ImageCropperTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageCropperTest.cs index 1873b30c9961..ce5e62d799f1 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 @@ public void GetCropUrl_CropAliasIgnoreWidthHeightTest() 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); + 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); } /// @@ -174,8 +176,8 @@ public void GetBaseCropUrlFromModelTest() [Test] 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); + 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); } /// @@ -184,8 +186,8 @@ public void GetCropUrl_CropAliasHeightRatioModeTest() [Test] 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); + 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); } /// @@ -194,8 +196,8 @@ public void GetCropUrl_WidthHeightRatioModeTest() [Test] 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); + 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); } /// @@ -236,7 +238,7 @@ public void GetCropUrl_PreferFocalPointCenter() 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 @@ public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidth() 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 @@ public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPoint() 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 @@ public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPointIgnore() 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 @@ public void GetCropUrl_PreDefinedCropNoCoordinatesWithHeight() 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 @@ public void GetCropUrl_WidthOnlyParameter() 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 @@ public void GetCropUrl_HeightOnlyParameter() 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); } /// @@ -319,98 +321,88 @@ public void GetCropUrl_BackgroundColorParameter() { 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); } 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.FocalPoint != null) + if (options == null) { - imageProcessorUrl.Append("?f="); - imageProcessorUrl.Append(options.FocalPoint.Top.ToString(CultureInfo.InvariantCulture)); - imageProcessorUrl.Append("x"); - imageProcessorUrl.Append(options.FocalPoint.Left.ToString(CultureInfo.InvariantCulture)); + return null; } - 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) + + var imageUrl = new StringBuilder(options.ImageUrl); + + bool queryStringHasStarted = false; + void AppendQueryString(string value) { - imageProcessorUrl.Append("?m=defaultcrop"); + imageUrl.Append(queryStringHasStarted ? '&' : '?'); + queryStringHasStarted = true; + + imageUrl.Append(value); } - else + void AddQueryString(string key, params IConvertible[] values) + => AppendQueryString(key + '=' + string.Join(",", values.Select(x => x.ToString(CultureInfo.InvariantCulture)))); + + if (options.FocalPoint != null) { - imageProcessorUrl.Append("?m=" + options.ImageCropMode.ToString().ToLower()); - if (options.ImageCropAnchor != null) - { - imageProcessorUrl.Append("&a=" + options.ImageCropAnchor.ToString().ToLower()); - } + AddQueryString("f", options.FocalPoint.Top, options.FocalPoint.Left); } - - var hasFormat = options.FurtherOptions != null && options.FurtherOptions.InvariantContains("&f="); - if (options.Quality != null && hasFormat == false) + else if (options.Crop != null) { - imageProcessorUrl.Append("&q=" + options.Quality); + AddQueryString("c", options.Crop.Left, options.Crop.Top, options.Crop.Right, options.Crop.Bottom); } - if (options.HeightRatio != null) + if (options.ImageCropMode.HasValue) { - imageProcessorUrl.Append("&hr=" + options.HeightRatio.Value.ToString(CultureInfo.InvariantCulture)); + AddQueryString("m", options.ImageCropMode.Value.ToString().ToLowerInvariant()); } - if (options.WidthRatio != null) + if (options.ImageCropAnchor.HasValue) { - 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.Tests.UnitTests/Umbraco.Web.Common/ImageProcessors/CropWebProcessorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageProcessors/CropWebProcessorTests.cs new file mode 100644 index 000000000000..2c508d97d200 --- /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.BackOffice/Controllers/BackOfficeServerVariables.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs index c3fb203ec121..c822b61d6786 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs @@ -365,7 +365,7 @@ internal Task> GetServerVariablesAsync() }, { "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 1d72c80ad8d7..7546fdf38de2 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 @@ public class ImageUrlGeneratorController : UmbracoAuthorizedJsonController { 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 24135bcbe63b..564d0dcdd93e 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 @@ public IActionResult GetResized(string imagePath, int width) 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 @@ public IActionResult GetResized(string imagePath, int width) /// /// /// - /// /// - /// /// /// /// If there is no media, image property or image file is found then this will return not found. @@ -106,9 +102,7 @@ public string GetProcessedImageUrl(string imagePath, 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 @@ public string GetProcessedImageUrl(string imagePath, 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(focalPointLeft.Value, focalPointTop.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/DependencyInjection/ImageSharpConfigurationOptions.cs b/src/Umbraco.Web.Common/DependencyInjection/ImageSharpConfigurationOptions.cs new file mode 100644 index 000000000000..628345dcd67a --- /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. + /// + /// + public sealed 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 96bf7017cb0e..280d48f64bd9 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilder.ImageSharp.cs @@ -2,14 +2,16 @@ using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using SixLabors.ImageSharp.Memory; +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 SixLabors.ImageSharp.Web.Providers; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Web.Common.DependencyInjection; +using Umbraco.Cms.Web.Common.ImageProcessors; namespace Umbraco.Extensions { @@ -20,56 +22,44 @@ public static partial class UmbracoBuilderExtensions /// 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 => { - options.Configuration = SixLabors.ImageSharp.Configuration.Default; + // The configuration is set using ImageSharpConfigurationOptions 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; }; - options.OnBeforeSaveAsync = _ => Task.CompletedTask; - options.OnProcessedAsync = _ => Task.CompletedTask; - options.OnPrepareResponseAsync = _ => Task.CompletedTask; }) - .SetRequestParser() - .Configure(options => - { - options.CacheFolder = imagingSettings.Cache.CacheFolder; - }) - .SetCache() - .SetCacheHash() - .AddProvider() - .AddProcessor() - .AddProcessor() - .AddProcessor(); + .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(); - return services; - } + builder.Services.AddTransient, ImageSharpConfigurationOptions>(); - 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); - } - } - } + return builder.Services; } } } diff --git a/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs b/src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs index 94e24a502762..38f6f472356d 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 @@ public static string GetCropUrl( => 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,21 @@ public static string GetCropUrl(this MediaWithCrops mediaWithCrops, string prope /// /// 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: - /// - /// - /// - /// - /// - /// Use a dimension as a ratio - /// - /// - /// If the image should be upscaled to requested dimensions - /// + /// 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 URL of the cropped image. /// @@ -145,9 +99,7 @@ public static string GetCropUrl( bool preferFocalPoint = false, bool useCropDimensions = false, bool cacheBuster = true, - string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true) + string furtherOptions = null) => mediaItem.GetCropUrl( ImageUrlGenerator, PublishedValueFallback, @@ -162,63 +114,29 @@ public static string GetCropUrl( preferFocalPoint, useCropDimensions, cacheBuster, - furtherOptions, - ratioMode, - upScale + furtherOptions ); /// /// 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: - /// - /// - /// - /// - /// - /// Use a dimension as a ratio - /// - /// - /// If the image should be upscaled to requested dimensions - /// + /// 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 the URL of the cropped image. + /// The URL of the cropped image. /// public static string GetCropUrl( this string imageUrl, @@ -232,9 +150,7 @@ public static string GetCropUrl( bool preferFocalPoint = false, bool useCropDimensions = false, string cacheBusterValue = null, - string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true) + string furtherOptions = null) => imageUrl.GetCropUrl( ImageUrlGenerator, width, @@ -247,61 +163,29 @@ public static string GetCropUrl( preferFocalPoint, useCropDimensions, cacheBusterValue, - furtherOptions, - ratioMode, - upScale - ); + furtherOptions + ); /// /// 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: - /// - /// - /// - /// - /// - /// Use a dimension as a ratio - /// - /// - /// If the image should be upscaled to requested dimensions - /// + /// 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: + /// /// - /// The the URL of the cropped image. + /// The URL of the cropped image. /// public static string GetCropUrl( this string imageUrl, @@ -315,10 +199,7 @@ public static string GetCropUrl( bool preferFocalPoint = false, bool useCropDimensions = false, string cacheBusterValue = null, - string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true, - string animationProcessMode = null) + string furtherOptions = null) => imageUrl.GetCropUrl( ImageUrlGenerator, cropDataSet, @@ -330,10 +211,7 @@ public static string GetCropUrl( preferFocalPoint, useCropDimensions, cacheBusterValue, - furtherOptions, - ratioMode, - upScale, - animationProcessMode + furtherOptions ); @@ -341,10 +219,6 @@ public static string GetCropUrl( 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 879a70cdb594..ae367b1cf951 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; @@ -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 @@ public static string GetCropUrl( 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 @@ public static string GetCropUrl( /// 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 @@ public static string GetCropUrl( 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,36 @@ public static string GetCropUrl( 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: - /// - /// - /// - /// - /// - /// Use a dimension as a ratio - /// - /// - /// If the image should be upscaled to requested dimensions - /// + /// 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 URL of the cropped image. /// @@ -178,12 +123,7 @@ public static string GetCropUrl( bool preferFocalPoint = false, 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); - } + 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, @@ -200,13 +140,14 @@ public static string GetCropUrl( bool preferFocalPoint = false, bool useCropDimensions = false, bool cacheBuster = true, - string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true) + string furtherOptions = 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); } private static string GetCropUrl( @@ -226,16 +167,17 @@ private static string GetCropUrl( bool preferFocalPoint = false, bool useCropDimensions = false, bool cacheBuster = true, - string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true) + string furtherOptions = 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 +211,34 @@ private static string GetCropUrl( } } + 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); } /// /// 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: - /// - /// - /// - /// - /// - /// Use a dimension as a ratio - /// - /// - /// If the image should be upscaled to requested dimensions - /// + /// 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: + /// /// - /// The the URL of the cropped image. + /// The URL of the cropped image. /// public static string GetCropUrl( this string imageUrl, @@ -340,11 +253,12 @@ public static string GetCropUrl( bool preferFocalPoint = false, bool useCropDimensions = false, string cacheBusterValue = null, - string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true) + string furtherOptions = 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 +268,30 @@ public static string GetCropUrl( return GetCropUrl( imageUrl, imageUrlGenerator, cropDataSet, width, height, cropAlias, quality, imageCropMode, - imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, upScale); + imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions); } /// /// 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: - /// - /// - /// - /// - /// - /// Use a dimension as a ratio - /// - /// - /// If the image should be upscaled to requested dimensions - /// + /// 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: + /// /// - /// The . + /// The URL of the cropped image. /// public static string GetCropUrl( this string imageUrl, @@ -424,72 +306,57 @@ public static string GetCropUrl( bool preferFocalPoint = false, bool useCropDimensions = false, string cacheBusterValue = null, - string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true, - string animationProcessMode = null) + string furtherOptions = 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); + ImageCropperValue.ImageCropperCrop 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); + options = cropDataSet.GetCropBaseOptions(imageUrl, crop, preferFocalPoint || string.IsNullOrWhiteSpace(cropAlias)); - if (crop != null & useCropDimensions) + if (crop != null && useCropDimensions) { width = crop.Width; 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) - { - 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) + // Calculate missing dimension if a predefined crop has been specified, but has no coordinates + if (crop != null && string.IsNullOrEmpty(cropAlias) == false && crop.Coordinates == 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 { - options = new ImageUrlGenerationOptions (imageUrl) + 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; - options.AnimationProcessMode = animationProcessMode; - - 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 (ratioMode == ImageCropRatioMode.Height && width != null) - { - // if only width specified then assume a square - if (height == null) height = width; - options.HeightRatio = (decimal)height / (decimal)width; - } - - options.UpScale = upScale; + 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 e7dd5248e19e..aefd1027256c 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; @@ -246,8 +246,6 @@ public static IHtmlContent GetCropUrl(this IUrlHelper urlHelper, bool useCropDimensions = false, bool cacheBuster = true, string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true, bool htmlEncode = true) { if (mediaItem == null) @@ -256,8 +254,8 @@ public static IHtmlContent GetCropUrl(this IUrlHelper urlHelper, } var url = mediaItem.GetCropUrl(width, height, propertyAlias, cropAlias, quality, imageCropMode, - imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode, - upScale); + imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions); + return CreateHtmlString(url, htmlEncode); } @@ -273,16 +271,14 @@ public static IHtmlContent GetCropUrl(this IUrlHelper urlHelper, bool useCropDimensions = true, 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); + return CreateHtmlString(url, htmlEncode); } diff --git a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs new file mode 100644 index 000000000000..7b3cc817f232 --- /dev/null +++ b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs @@ -0,0 +1,64 @@ +using System; +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; + +namespace Umbraco.Cms.Web.Common.ImageProcessors +{ + /// + /// Allows the cropping of images. + /// + public class CropWebProcessor : IImageWebProcessor + { + /// + /// The command constant for the crop coordinates. + /// + public const string Coordinates = "cc"; + + /// + public IEnumerable Commands { get; } = new[] + { + Coordinates + }; + + /// + public FormattedImage Process(FormattedImage image, ILogger logger, IDictionary commands, CommandParser parser, CultureInfo culture) + { + RectangleF? coordinates = GetCoordinates(commands, parser, culture); + if (coordinates != null) + { + // 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.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); + + image.Image.Mutate(x => x.Crop(cropRectangle)); + } + + return image; + } + + private static RectangleF? GetCoordinates(IDictionary commands, CommandParser parser, CultureInfo culture) + { + float[] coordinates = parser.ParseValue(commands.GetValueOrDefault(Coordinates), culture); + + if (coordinates.Length != 4) + { + return null; + } + + // 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]); + } + } +} 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 a937cd2675a9..dd65f89526d5 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 1b3765a5f514..e98a597e764b 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 b83367ef6e97..2738883b151f 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(); }); 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 0c75bfbee311..a9993a049848 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 4f1016e68028..b4d59c683c73 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 91da54d4ad26..81a548a1169a 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,28 +70,25 @@ 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. - // 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.left},${$scope.control.value.focalPoint.top}`; } 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 cbaf843d3554..8bb50a07dc01 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;