<commit_before>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;
using ImageCropperCropCoordinates = Umbraco.Cms.Core.PropertyEditors.ValueConverters.ImageCropperValue.ImageCropperCropCoordinates;

namespace Umbraco.Cms.Web.Common.ImageProcessors
{
    public class CropWebProcessor : IImageWebProcessor
    {
        /// <summary>
        /// The command constant for the crop definition
        /// </summary>
        public const string Crop = "crop";

        public FormattedImage Process(
            FormattedImage image,
            ILogger logger,
            IDictionary<string, string> commands,
            CommandParser parser,
            CultureInfo culture)
        {
            ImageCropperCropCoordinates cropCoordinates = GetCropCoordinates(commands, parser, culture);
            if (cropCoordinates is null)
            {
                return image;
            }

            Size size = image.Image.Size();
            Rectangle crop = GetCropRectangle(size.Width, size.Height, cropCoordinates);
            image.Image.Mutate(x => x.Crop(crop));
            return image;
        }

        private static readonly IEnumerable<string> CropCommands = new[] {Crop};

        public IEnumerable<string> Commands { get; } = CropCommands;

        /// <summary>
        /// Gets a rectangle that is calculated from crop coordinates.
        /// </summary>
        /// <param name="width">The width of the image being cropped.</param>
        /// <param name="height">The height of the image being cropped.</param>
        /// <param name="coordinates">Coordinate set to calculate rectangle from.</param>
        /// <returns>Rectangle with the position and sized described in coordinates.</returns>
        private static Rectangle GetCropRectangle(int width, int height, ImageCropperCropCoordinates coordinates)
        {
            // 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));

            // Get width and height of crop
            var cropWidth = bottomX - topX;
            var cropHeight = bottomY - topY;

            return new Rectangle(topX, topY, cropWidth, cropHeight);
        }

        /// <summary>
        /// Converts a decimal to an int with rounding.
        /// </summary>
        /// <param name="number">Decimal to convert.</param>
        /// <returns>The decimal rounded to an int.</returns>
        private static int RoundToInt(decimal number) => decimal.ToInt32(Math.Round(number));

        /// <summary>
        /// Gets the crop coordinates from the query string.
        /// </summary>
        /// <param name="commands">Commands dictionary to parse the coordinates from.</param>
        /// <param name="parser">Parser provided by imagesharp.</param>
        /// <param name="culture">Culture to use for parsing.</param>
        /// <returns>Coordinates of the crop.</returns>
        private static ImageCropperCropCoordinates GetCropCoordinates(IDictionary<string, string> commands, CommandParser parser, CultureInfo culture)
        {
            decimal[] crops = parser.ParseValue<decimal[]>(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]
            };
        }
    }
}
<commit_msg>Add cropmode support and align logic to ImageProcessors Crop processor<commit_after>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
{
    /// <summary>
    /// Allows the cropping of images.
    /// </summary>
    public class CropWebProcessor : IImageWebProcessor
    {
        /// <summary>
        /// The command constant for the crop definition.
        /// </summary>
        public const string Crop = "crop";

        /// <summary>
        /// The command constant for the crop mode.
        /// </summary>
        public const string Mode = "cropmode";


        /// <inheritdoc/>
        public IEnumerable<string> Commands { get; } = new[]
        {
            Crop,
            Mode
        };

        /// <inheritdoc/>
        public FormattedImage Process(FormattedImage image, ILogger logger, IDictionary<string, string> commands, CommandParser parser, CultureInfo culture)
        {
            if (GetCrop(commands, parser, culture) is RectangleF crop)
            {
                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));
                }
            }

            return image;
        }

        private static RectangleF? GetCrop(IDictionary<string, string> commands, CommandParser parser, CultureInfo culture)
        {
            float[] crops = parser.ParseValue<float[]>(commands.GetValueOrDefault(Crop), culture);

            return (crops.Length != 4) ? null : new RectangleF(crops[0], crops[1], crops[2], crops[3]);
        }

        private static CropMode GetMode(IDictionary<string, string> commands, CommandParser parser, CultureInfo culture)
            => parser.ParseValue<CropMode>(commands.GetValueOrDefault(Mode), culture);
    }

    /// <summary>
    /// Enumerated cop modes to apply to cropped images.
    /// </summary>
    public enum CropMode
    {
        /// <summary>
        /// Crops the image using the standard rectangle model of x, y, width, height.
        /// </summary>
        Pixels,
        /// <summary>
        /// Crops the image using percentages model left, top, right, bottom.
        /// </summary>
        Percentage
    }
}

{'fraction_non_alphanumeric': 0.09993823347745522, 'fraction_numerical': 0.004941321803582459, 'mean_word_length': 1.9059583632447954, 'pattern_counts': {'":': 0, '<': 60, '<?xml version=': 0, '>': 66, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3519843', 'n_tokens_mistral': 2127, 'n_tokens_neox': 2018, 'n_words': 573}