From 20007db7f2d2a99d085b8fc2fd90f915db65007a Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 6 Aug 2021 12:59:23 +0200 Subject: [PATCH] Add cropmode support and align logic to ImageProcessors Crop processor --- .../ImageProcessors/CropWebProcessor.cs | 137 +++++++++--------- 1 file changed, 72 insertions(+), 65 deletions(-) diff --git a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs index 3fe4db8344..d5e2adb2e5 100644 --- a/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs +++ b/src/Umbraco.Web.Common/ImageProcessors/CropWebProcessor.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Globalization; using Microsoft.Extensions.Logging; @@ -7,93 +6,101 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Web; using SixLabors.ImageSharp.Web.Commands; using SixLabors.ImageSharp.Web.Processors; -using ImageCropperCropCoordinates = Umbraco.Cms.Core.PropertyEditors.ValueConverters.ImageCropperValue.ImageCropperCropCoordinates; namespace Umbraco.Cms.Web.Common.ImageProcessors { + /// + /// Allows the cropping of images. + /// public class CropWebProcessor : IImageWebProcessor { /// - /// The command constant for the crop definition + /// The command constant for the crop definition. /// public const string Crop = "crop"; - public FormattedImage Process( - FormattedImage image, - ILogger logger, - IDictionary commands, - CommandParser parser, - CultureInfo culture) + /// + /// The command constant for the crop mode. + /// + public const string Mode = "cropmode"; + + + /// + public IEnumerable Commands { get; } = new[] { - ImageCropperCropCoordinates cropCoordinates = GetCropCoordinates(commands, parser, culture); - if (cropCoordinates is null) + Crop, + Mode + }; + + /// + public FormattedImage Process(FormattedImage image, ILogger logger, IDictionary commands, CommandParser parser, CultureInfo culture) + { + if (GetCrop(commands, parser, culture) is RectangleF crop) { - return image; + Size size = image.Image.Size(); + + // Convert from percentages to pixels based on crop mode + if (GetMode(commands, parser, culture) is CropMode.Percentage) + { + // Fix for whole numbers + float percentageLeft = crop.Left > 1 ? crop.Left / 100 : crop.Left; + float percentageRight = crop.Right > 1 ? crop.Right / 100 : crop.Right; + float percentageTop = crop.Top > 1 ? crop.Top / 100 : crop.Top; + float percentageBottom = crop.Bottom > 1 ? crop.Bottom / 100 : crop.Bottom; + + // Work out the percentages + float left = percentageLeft * size.Width; + float top = percentageTop * size.Height; + float width = percentageRight < 1 ? (1 - percentageLeft - percentageRight) * size.Width : size.Width; + float height = percentageBottom < 1 ? (1 - percentageTop - percentageBottom) * size.Height : size.Height; + + crop = new RectangleF(left, top, width, height); + } + + // Round and validate crop rectangle + var rectangle = Rectangle.Round(crop); + if (rectangle.X < size.Width && rectangle.Y < size.Height) + { + if (rectangle.Width > (size.Width - rectangle.X)) + { + rectangle.Width = size.Width - rectangle.X; + } + + if (rectangle.Height > (size.Height - rectangle.Y)) + { + rectangle.Height = size.Height - rectangle.Y; + } + + image.Image.Mutate(x => x.Crop(rectangle)); + } } - Size size = image.Image.Size(); - Rectangle crop = GetCropRectangle(size.Width, size.Height, cropCoordinates); - image.Image.Mutate(x => x.Crop(crop)); return image; } - private static readonly IEnumerable CropCommands = new[] {Crop}; - - public IEnumerable Commands { get; } = CropCommands; - - /// - /// Gets a rectangle that is calculated from crop coordinates. - /// - /// The width of the image being cropped. - /// The height of the image being cropped. - /// Coordinate set to calculate rectangle from. - /// Rectangle with the position and sized described in coordinates. - private static Rectangle GetCropRectangle(int width, int height, ImageCropperCropCoordinates coordinates) + private static RectangleF? GetCrop(IDictionary commands, CommandParser parser, CultureInfo culture) { - // Get coordinates of top left corner of the rectangle - var topX = RoundToInt(width * coordinates.X1); - var topY = RoundToInt(height * coordinates.Y1); - // Get coordinated of bottom right corner - var bottomX = RoundToInt(width - (width * coordinates.X2)); - var bottomY = RoundToInt(height - (height * coordinates.Y2)); + float[] crops = parser.ParseValue(commands.GetValueOrDefault(Crop), culture); - // Get width and height of crop - var cropWidth = bottomX - topX; - var cropHeight = bottomY - topY; - - return new Rectangle(topX, topY, cropWidth, cropHeight); + return (crops.Length != 4) ? null : new RectangleF(crops[0], crops[1], crops[2], crops[3]); } + private static CropMode GetMode(IDictionary commands, CommandParser parser, CultureInfo culture) + => parser.ParseValue(commands.GetValueOrDefault(Mode), culture); + } + + /// + /// Enumerated cop modes to apply to cropped images. + /// + public enum CropMode + { /// - /// Converts a decimal to an int with rounding. + /// Crops the image using the standard rectangle model of x, y, width, height. /// - /// Decimal to convert. - /// The decimal rounded to an int. - private static int RoundToInt(decimal number) => decimal.ToInt32(Math.Round(number)); - + Pixels, /// - /// Gets the crop coordinates from the query string. + /// Crops the image using percentages model left, top, right, bottom. /// - /// Commands dictionary to parse the coordinates from. - /// Parser provided by imagesharp. - /// Culture to use for parsing. - /// Coordinates of the crop. - private static ImageCropperCropCoordinates GetCropCoordinates(IDictionary commands, CommandParser parser, CultureInfo culture) - { - decimal[] crops = parser.ParseValue(commands.GetValueOrDefault(Crop), culture); - - if (crops.Length != 4) - { - return null; - } - - return new ImageCropperCropCoordinates() - { - X1 = crops[0], - Y1 = crops[1], - X2 = crops[2], - Y2 = crops[3] - }; - } + Percentage } }