Exemplo n.º 1
0
        /// <summary>
        /// Calculates the target rectangle for crop mode.
        /// </summary>
        /// <typeparam name="TPixel">The pixel format.</typeparam>
        /// <param name="source">The source image.</param>
        /// <param name="options">The resize options.</param>
        /// <returns>
        /// The <see cref="Rectangle"/>.
        /// </returns>
        private static Rectangle CalculateCropRectangle <TPixel>(ImageFrame <TPixel> source, ResizeOptions options)
            where TPixel : struct, IPixel <TPixel>
        {
            int width  = options.Size.Width;
            int height = options.Size.Height;

            if (width <= 0 || height <= 0)
            {
                return(new Rectangle(0, 0, source.Width, source.Height));
            }

            float ratio;
            int   sourceWidth  = source.Width;
            int   sourceHeight = source.Height;

            int destinationX      = 0;
            int destinationY      = 0;
            int destinationWidth  = width;
            int destinationHeight = height;

            // Fractional variants for preserving aspect ratio.
            float percentHeight = Math.Abs(height / (float)sourceHeight);
            float percentWidth  = Math.Abs(width / (float)sourceWidth);

            if (percentHeight < percentWidth)
            {
                ratio = percentWidth;

                if (options.CenterCoordinates.Any())
                {
                    float center = -(ratio * sourceHeight) * options.CenterCoordinates.ToArray()[1];
                    destinationY = (int)Math.Round(center + (height / 2F));

                    if (destinationY > 0)
                    {
                        destinationY = 0;
                    }

                    if (destinationY < (int)Math.Round(height - (sourceHeight * ratio)))
                    {
                        destinationY = (int)Math.Round(height - (sourceHeight * ratio));
                    }
                }
                else
                {
                    switch (options.Position)
                    {
                    case AnchorPosition.Top:
                    case AnchorPosition.TopLeft:
                    case AnchorPosition.TopRight:
                        destinationY = 0;
                        break;

                    case AnchorPosition.Bottom:
                    case AnchorPosition.BottomLeft:
                    case AnchorPosition.BottomRight:
                        destinationY = (int)Math.Round(height - (sourceHeight * ratio));
                        break;

                    default:
                        destinationY = (int)Math.Round((height - (sourceHeight * ratio)) / 2F);
                        break;
                    }
                }

                destinationHeight = (int)Math.Ceiling(sourceHeight * percentWidth);
            }
            else
            {
                ratio = percentHeight;

                if (options.CenterCoordinates.Any())
                {
                    float center = -(ratio * sourceWidth) * options.CenterCoordinates.First();
                    destinationX = (int)Math.Round(center + (width / 2F));

                    if (destinationX > 0)
                    {
                        destinationX = 0;
                    }

                    if (destinationX < (int)Math.Round(width - (sourceWidth * ratio)))
                    {
                        destinationX = (int)Math.Round(width - (sourceWidth * ratio));
                    }
                }
                else
                {
                    switch (options.Position)
                    {
                    case AnchorPosition.Left:
                    case AnchorPosition.TopLeft:
                    case AnchorPosition.BottomLeft:
                        destinationX = 0;
                        break;

                    case AnchorPosition.Right:
                    case AnchorPosition.TopRight:
                    case AnchorPosition.BottomRight:
                        destinationX = (int)Math.Round(width - (sourceWidth * ratio));
                        break;

                    default:
                        destinationX = (int)Math.Round((width - (sourceWidth * ratio)) / 2F);
                        break;
                    }
                }

                destinationWidth = (int)Math.Ceiling(sourceWidth * percentHeight);
            }

            return(new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight));
        }
Exemplo n.º 2
0
        /// <summary>
        /// Calculates the target rectangle for max mode.
        /// </summary>
        /// <typeparam name="TPixel">The pixel format.</typeparam>
        /// <param name="source">The source image.</param>
        /// <param name="options">The resize options.</param>
        /// <returns>
        /// The <see cref="Rectangle"/>.
        /// </returns>
        private static Rectangle CalculateMaxRectangle <TPixel>(ImageFrame <TPixel> source, ResizeOptions options)
            where TPixel : struct, IPixel <TPixel>
        {
            int width             = options.Size.Width;
            int height            = options.Size.Height;
            int destinationWidth  = width;
            int destinationHeight = height;

            // Fractional variants for preserving aspect ratio.
            float percentHeight = Math.Abs(height / (float)source.Height);
            float percentWidth  = Math.Abs(width / (float)source.Width);

            // Integers must be cast to floats to get needed precision
            float ratio       = options.Size.Height / (float)options.Size.Width;
            float sourceRatio = source.Height / (float)source.Width;

            if (sourceRatio < ratio)
            {
                destinationHeight = (int)Math.Round(source.Height * percentWidth);
                height            = destinationHeight;
            }
            else
            {
                destinationWidth = (int)Math.Round(source.Width * percentHeight);
                width            = destinationWidth;
            }

            // Replace the size to match the rectangle.
            options.Size = new Size(width, height);
            return(new Rectangle(0, 0, destinationWidth, destinationHeight));
        }
Exemplo n.º 3
0
        /// <summary>
        /// Calculates the target rectangle for min mode.
        /// </summary>
        /// <typeparam name="TPixel">The pixel format.</typeparam>
        /// <param name="source">The source image.</param>
        /// <param name="options">The resize options.</param>
        /// <returns>
        /// The <see cref="Rectangle"/>.
        /// </returns>
        private static Rectangle CalculateMinRectangle <TPixel>(ImageFrame <TPixel> source, ResizeOptions options)
            where TPixel : struct, IPixel <TPixel>
        {
            int width        = options.Size.Width;
            int height       = options.Size.Height;
            int sourceWidth  = source.Width;
            int sourceHeight = source.Height;
            int destinationWidth;
            int destinationHeight;

            // Don't upscale
            if (width > sourceWidth || height > sourceHeight)
            {
                options.Size = new Size(sourceWidth, sourceHeight);
                return(new Rectangle(0, 0, sourceWidth, sourceHeight));
            }

            // Fractional variants for preserving aspect ratio.
            float percentHeight = Math.Abs(height / (float)sourceHeight);
            float percentWidth  = Math.Abs(width / (float)sourceWidth);

            float sourceRatio = (float)sourceHeight / sourceWidth;

            // Find the shortest distance to go.
            int widthDiff  = sourceWidth - width;
            int heightDiff = sourceHeight - height;

            if (widthDiff < heightDiff)
            {
                destinationHeight = (int)Math.Round(width * sourceRatio);
                height            = destinationHeight;
                destinationWidth  = width;
            }
            else if (widthDiff > heightDiff)
            {
                destinationWidth  = (int)Math.Round(height / sourceRatio);
                destinationHeight = height;
                width             = destinationWidth;
            }
            else
            {
                if (height > width)
                {
                    destinationWidth  = width;
                    destinationHeight = (int)Math.Round(sourceHeight * percentWidth);
                    height            = destinationHeight;
                }
                else
                {
                    destinationHeight = height;
                    destinationWidth  = (int)Math.Round(sourceWidth * percentHeight);
                    width             = destinationWidth;
                }
            }

            // Replace the size to match the rectangle.
            options.Size = new Size(width, height);
            return(new Rectangle(0, 0, destinationWidth, destinationHeight));
        }
Exemplo n.º 4
0
        /// <summary>
        /// Calculates the target location and bounds to perform the resize operation against.
        /// </summary>
        /// <typeparam name="TPixel">The pixel format.</typeparam>
        /// <param name="source">The source image.</param>
        /// <param name="options">The resize options.</param>
        /// <returns>
        /// The <see cref="Rectangle"/>.
        /// </returns>
        public static Rectangle CalculateTargetLocationAndBounds <TPixel>(ImageFrame <TPixel> source, ResizeOptions options)
            where TPixel : struct, IPixel <TPixel>
        {
            switch (options.Mode)
            {
            case ResizeMode.Crop:
                return(CalculateCropRectangle(source, options));

            case ResizeMode.Pad:
                return(CalculatePadRectangle(source, options));

            case ResizeMode.BoxPad:
                return(CalculateBoxPadRectangle(source, options));

            case ResizeMode.Max:
                return(CalculateMaxRectangle(source, options));

            case ResizeMode.Min:
                return(CalculateMinRectangle(source, options));

            // Last case ResizeMode.Stretch:
            default:
                return(new Rectangle(0, 0, options.Size.Width, options.Size.Height));
            }
        }
Exemplo n.º 5
0
        /// <summary>
        /// Calculates the target rectangle for box pad mode.
        /// </summary>
        /// <typeparam name="TPixel">The pixel format.</typeparam>
        /// <param name="source">The source image.</param>
        /// <param name="options">The resize options.</param>
        /// <returns>
        /// The <see cref="Rectangle"/>.
        /// </returns>
        private static Rectangle CalculateBoxPadRectangle <TPixel>(ImageFrame <TPixel> source, ResizeOptions options)
            where TPixel : struct, IPixel <TPixel>
        {
            int width  = options.Size.Width;
            int height = options.Size.Height;

            if (width <= 0 || height <= 0)
            {
                return(new Rectangle(0, 0, source.Width, source.Height));
            }

            int sourceWidth  = source.Width;
            int sourceHeight = source.Height;

            // Fractional variants for preserving aspect ratio.
            float percentHeight = Math.Abs(height / (float)sourceHeight);
            float percentWidth  = Math.Abs(width / (float)sourceWidth);

            int boxPadHeight = height > 0 ? height : (int)Math.Round(sourceHeight * percentWidth);
            int boxPadWidth  = width > 0 ? width : (int)Math.Round(sourceWidth * percentHeight);

            // Only calculate if upscaling.
            if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight)
            {
                int destinationX;
                int destinationY;
                int destinationWidth  = sourceWidth;
                int destinationHeight = sourceHeight;
                width  = boxPadWidth;
                height = boxPadHeight;

                switch (options.Position)
                {
                case AnchorPosition.Left:
                    destinationY = (height - sourceHeight) / 2;
                    destinationX = 0;
                    break;

                case AnchorPosition.Right:
                    destinationY = (height - sourceHeight) / 2;
                    destinationX = width - sourceWidth;
                    break;

                case AnchorPosition.TopRight:
                    destinationY = 0;
                    destinationX = width - sourceWidth;
                    break;

                case AnchorPosition.Top:
                    destinationY = 0;
                    destinationX = (width - sourceWidth) / 2;
                    break;

                case AnchorPosition.TopLeft:
                    destinationY = 0;
                    destinationX = 0;
                    break;

                case AnchorPosition.BottomRight:
                    destinationY = height - sourceHeight;
                    destinationX = width - sourceWidth;
                    break;

                case AnchorPosition.Bottom:
                    destinationY = height - sourceHeight;
                    destinationX = (width - sourceWidth) / 2;
                    break;

                case AnchorPosition.BottomLeft:
                    destinationY = height - sourceHeight;
                    destinationX = 0;
                    break;

                default:
                    destinationY = (height - sourceHeight) / 2;
                    destinationX = (width - sourceWidth) / 2;
                    break;
                }

                return(new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight));
            }

            // Switch to pad mode to downscale and calculate from there.
            return(CalculatePadRectangle(source, options));
        }
Exemplo n.º 6
0
        public Task CreateThumbnailAsync(Stream source, Stream destination, ResizeOptions options)
        {
            Guard.NotNull(options);

            return(Task.Run(() =>
            {
                var w = options.Width ?? 0;
                var h = options.Height ?? 0;

                if (w <= 0 && h <= 0 && !options.Quality.HasValue)
                {
                    source.CopyTo(destination);

                    return;
                }

                using (var sourceImage = Image.Load(source, out var format))
                {
                    var encoder = Configuration.Default.ImageFormatsManager.FindEncoder(format);

                    if (options.Quality.HasValue)
                    {
                        encoder = new JpegEncoder {
                            Quality = options.Quality.Value
                        };
                    }

                    if (encoder == null)
                    {
                        throw new NotSupportedException();
                    }

                    if (w > 0 || h > 0)
                    {
                        var isCropUpsize = options.Mode == ResizeMode.CropUpsize;

                        if (!Enum.TryParse <ISResizeMode>(options.Mode.ToString(), true, out var resizeMode))
                        {
                            resizeMode = ISResizeMode.Max;
                        }

                        if (isCropUpsize)
                        {
                            resizeMode = ISResizeMode.Crop;
                        }

                        if (w >= sourceImage.Width && h >= sourceImage.Height && resizeMode == ISResizeMode.Crop && !isCropUpsize)
                        {
                            resizeMode = ISResizeMode.BoxPad;
                        }

                        var resizeOptions = new ISResizeOptions {
                            Size = new Size(w, h), Mode = resizeMode
                        };

                        if (options.FocusX.HasValue && options.FocusY.HasValue)
                        {
                            resizeOptions.CenterCoordinates = new float[]
                            {
                                +(options.FocusX.Value / 2f) + 0.5f,
                                -(options.FocusX.Value / 2f) + 0.5f
                            };
                        }

                        sourceImage.Mutate(x => x.Resize(resizeOptions));
                    }

                    sourceImage.Save(destination, encoder);
                }
            }));
        }
Exemplo n.º 7
0
        /// <summary>
        /// Calculates the target rectangle for pad mode.
        /// </summary>
        /// <typeparam name="TPixel">The pixel format.</typeparam>
        /// <param name="source">The source image.</param>
        /// <param name="options">The resize options.</param>
        /// <returns>
        /// The <see cref="Rectangle"/>.
        /// </returns>
        private static Rectangle CalculatePadRectangle <TPixel>(ImageFrame <TPixel> source, ResizeOptions options)
            where TPixel : struct, IPixel <TPixel>
        {
            int width  = options.Size.Width;
            int height = options.Size.Height;

            if (width <= 0 || height <= 0)
            {
                return(new Rectangle(0, 0, source.Width, source.Height));
            }

            float ratio;
            int   sourceWidth  = source.Width;
            int   sourceHeight = source.Height;

            int destinationX      = 0;
            int destinationY      = 0;
            int destinationWidth  = width;
            int destinationHeight = height;

            // Fractional variants for preserving aspect ratio.
            float percentHeight = Math.Abs(height / (float)sourceHeight);
            float percentWidth  = Math.Abs(width / (float)sourceWidth);

            if (percentHeight < percentWidth)
            {
                ratio            = percentHeight;
                destinationWidth = (int)Math.Round(sourceWidth * percentHeight);

                switch (options.Position)
                {
                case AnchorPosition.Left:
                case AnchorPosition.TopLeft:
                case AnchorPosition.BottomLeft:
                    destinationX = 0;
                    break;

                case AnchorPosition.Right:
                case AnchorPosition.TopRight:
                case AnchorPosition.BottomRight:
                    destinationX = (int)Math.Round(width - (sourceWidth * ratio));
                    break;

                default:
                    destinationX = (int)Math.Round((width - (sourceWidth * ratio)) / 2F);
                    break;
                }
            }
            else
            {
                ratio             = percentWidth;
                destinationHeight = (int)Math.Round(sourceHeight * percentWidth);

                switch (options.Position)
                {
                case AnchorPosition.Top:
                case AnchorPosition.TopLeft:
                case AnchorPosition.TopRight:
                    destinationY = 0;
                    break;

                case AnchorPosition.Bottom:
                case AnchorPosition.BottomLeft:
                case AnchorPosition.BottomRight:
                    destinationY = (int)Math.Round(height - (sourceHeight * ratio));
                    break;

                default:
                    destinationY = (int)Math.Round((height - (sourceHeight * ratio)) / 2F);
                    break;
                }
            }

            return(new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight));
        }
Exemplo n.º 8
0
        public async Task CreateThumbnailAsync(Stream source, Stream destination, ResizeOptions options)
        {
            Guard.NotNull(source, nameof(source));
            Guard.NotNull(destination, nameof(destination));
            Guard.NotNull(options, nameof(options));

            if (!options.IsValid)
            {
                source.CopyTo(destination);

                return;
            }

            var w = options.Width ?? 0;
            var h = options.Height ?? 0;

            await semaphoreSlim.WaitAsync();

            try
            {
                using (var image = Image.Load(source, out var format))
                {
                    var encoder = Configuration.Default.ImageFormatsManager.FindEncoder(format);

                    if (encoder == null)
                    {
                        throw new NotSupportedException();
                    }

                    if (options.Quality.HasValue && (encoder is JpegEncoder || !options.KeepFormat))
                    {
                        encoder = new JpegEncoder {
                            Quality = options.Quality.Value
                        };
                    }

                    image.Mutate(x => x.AutoOrient());

                    if (w > 0 || h > 0)
                    {
                        var isCropUpsize = options.Mode == ResizeMode.CropUpsize;

                        if (!Enum.TryParse <ISResizeMode>(options.Mode.ToString(), true, out var resizeMode))
                        {
                            resizeMode = ISResizeMode.Max;
                        }

                        if (isCropUpsize)
                        {
                            resizeMode = ISResizeMode.Crop;
                        }

                        if (w >= image.Width && h >= image.Height && resizeMode == ISResizeMode.Crop && !isCropUpsize)
                        {
                            resizeMode = ISResizeMode.BoxPad;
                        }

                        var resizeOptions = new ISResizeOptions {
                            Size = new Size(w, h), Mode = resizeMode
                        };

                        if (options.FocusX.HasValue && options.FocusY.HasValue)
                        {
                            resizeOptions.CenterCoordinates = new PointF(
                                +(options.FocusX.Value / 2f) + 0.5f,
                                -(options.FocusY.Value / 2f) + 0.5f
                                );
                        }

                        image.Mutate(x => x.Resize(resizeOptions));
                    }

                    image.Save(destination, encoder);
                }
            }
            finally
            {
                semaphoreSlim.Release();
            }
        }
Exemplo n.º 9
0
 /// <summary>
 /// Resizes an image in accordance with the given <see cref="ResizeOptions"/>.
 /// </summary>
 /// <param name="source">The image to resize.</param>
 /// <param name="options">The resize options.</param>
 /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
 /// <remarks>Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image or the nearest possible ratio.</remarks>
 public static IImageProcessingContext Resize(this IImageProcessingContext source, ResizeOptions options)
 => source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize()));
Exemplo n.º 10
0
        /// <summary>
        /// Calculates the target location and bounds to perform the resize operation against.
        /// </summary>
        /// <param name="sourceSize">The source image size.</param>
        /// <param name="options">The resize options.</param>
        /// <param name="width">The target width</param>
        /// <param name="height">The target height</param>
        /// <returns>
        /// The <see cref="ValueTuple{Size,Rectangle}"/>.
        /// </returns>
        public static (Size, Rectangle) CalculateTargetLocationAndBounds(Size sourceSize, ResizeOptions options, int width, int height)
        {
            switch (options.Mode)
            {
            case ResizeMode.Crop:
                return(CalculateCropRectangle(sourceSize, options, width, height));

            case ResizeMode.Pad:
                return(CalculatePadRectangle(sourceSize, options, width, height));

            case ResizeMode.BoxPad:
                return(CalculateBoxPadRectangle(sourceSize, options, width, height));

            case ResizeMode.Max:
                return(CalculateMaxRectangle(sourceSize, options, width, height));

            case ResizeMode.Min:
                return(CalculateMinRectangle(sourceSize, options, width, height));

            // Last case ResizeMode.Stretch:
            default:
                return(new Size(width, height), new Rectangle(0, 0, width, height));
            }
        }
Exemplo n.º 11
0
        private static (Size, Rectangle) CalculatePadRectangle(Size source, ResizeOptions options, int width, int height)
        {
            if (width <= 0 || height <= 0)
            {
                return(new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height));
            }

            float ratio;
            int   sourceWidth  = source.Width;
            int   sourceHeight = source.Height;

            int destinationX      = 0;
            int destinationY      = 0;
            int destinationWidth  = width;
            int destinationHeight = height;

            // Fractional variants for preserving aspect ratio.
            float percentHeight = MathF.Abs(height / (float)sourceHeight);
            float percentWidth  = MathF.Abs(width / (float)sourceWidth);

            if (percentHeight < percentWidth)
            {
                ratio            = percentHeight;
                destinationWidth = (int)MathF.Round(sourceWidth * percentHeight);

                switch (options.Position)
                {
                case AnchorPositionMode.Left:
                case AnchorPositionMode.TopLeft:
                case AnchorPositionMode.BottomLeft:
                    destinationX = 0;
                    break;

                case AnchorPositionMode.Right:
                case AnchorPositionMode.TopRight:
                case AnchorPositionMode.BottomRight:
                    destinationX = (int)MathF.Round(width - (sourceWidth * ratio));
                    break;

                default:
                    destinationX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F);
                    break;
                }
            }
            else
            {
                ratio             = percentWidth;
                destinationHeight = (int)MathF.Round(sourceHeight * percentWidth);

                switch (options.Position)
                {
                case AnchorPositionMode.Top:
                case AnchorPositionMode.TopLeft:
                case AnchorPositionMode.TopRight:
                    destinationY = 0;
                    break;

                case AnchorPositionMode.Bottom:
                case AnchorPositionMode.BottomLeft:
                case AnchorPositionMode.BottomRight:
                    destinationY = (int)MathF.Round(height - (sourceHeight * ratio));
                    break;

                default:
                    destinationY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F);
                    break;
                }
            }

            return(new Size(width, height), new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight));
        }
        public async Task CreateThumbnailAsync(Stream source, Stream destination, ResizeOptions options)
        {
            Guard.NotNull(source, nameof(source));
            Guard.NotNull(destination, nameof(destination));
            Guard.NotNull(options, nameof(options));

            if (!options.IsValid)
            {
                await source.CopyToAsync(destination);

                return;
            }

            var w = options.Width ?? 0;
            var h = options.Height ?? 0;

            await maxTasks.WaitAsync();

            try
            {
                using (var image = Image.Load(source, out var format))
                {
                    var encoder = GetEncoder(options, format);

                    image.Mutate(x => x.AutoOrient());

                    if (w > 0 || h > 0)
                    {
                        var isCropUpsize = options.Mode == ResizeMode.CropUpsize;

                        if (!Enum.TryParse <ISResizeMode>(options.Mode.ToString(), true, out var resizeMode))
                        {
                            resizeMode = ISResizeMode.Max;
                        }

                        if (isCropUpsize)
                        {
                            resizeMode = ISResizeMode.Crop;
                        }

                        if (w >= image.Width && h >= image.Height && resizeMode == ISResizeMode.Crop && !isCropUpsize)
                        {
                            resizeMode = ISResizeMode.BoxPad;
                        }

                        var resizeOptions = new ISResizeOptions {
                            Size = new Size(w, h), Mode = resizeMode
                        };

                        if (options.FocusX.HasValue && options.FocusY.HasValue)
                        {
                            resizeOptions.CenterCoordinates = new PointF(
                                +(options.FocusX.Value / 2f) + 0.5f,
                                -(options.FocusY.Value / 2f) + 0.5f
                                );
                        }

                        image.Mutate(x => x.Resize(resizeOptions));
                    }

                    await image.SaveAsync(destination, encoder);
                }
            }
            finally
            {
                maxTasks.Release();
            }
        }
Exemplo n.º 13
0
 /// <summary>
 /// Resizes an image in accordance with the given <see cref="ResizeOptions"/>.
 /// </summary>
 /// <typeparam name="TPixel">The pixel format.</typeparam>
 /// <param name="source">The image to resize.</param>
 /// <param name="options">The resize options.</param>
 /// <returns>The <see cref="Image{TPixel}"/></returns>
 /// <remarks>Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image or the nearest possible ratio.</remarks>
 public static IImageProcessingContext <TPixel> Resize <TPixel>(this IImageProcessingContext <TPixel> source, ResizeOptions options)
     where TPixel : struct, IPixel <TPixel>
 => source.ApplyProcessor(new ResizeProcessor <TPixel>(options, source.GetCurrentSize()));
Exemplo n.º 14
0
        public async Task CreateThumbnailAsync(Stream source, string mimeType, Stream destination, ResizeOptions options,
                                               CancellationToken ct = default)
        {
            Guard.NotNull(source, nameof(source));
            Guard.NotNull(destination, nameof(destination));
            Guard.NotNull(options, nameof(options));

            if (!options.IsValid)
            {
                await source.CopyToAsync(destination, ct);

                return;
            }

            var w = options.TargetWidth ?? 0;
            var h = options.TargetHeight ?? 0;

            using (var image = Image.Load(source, out var format))
            {
                image.Mutate(x => x.AutoOrient());

                if (w > 0 || h > 0)
                {
                    var isCropUpsize = options.Mode == ResizeMode.CropUpsize;

                    if (!Enum.TryParse <ISResizeMode>(options.Mode.ToString(), true, out var resizeMode))
                    {
                        resizeMode = ISResizeMode.Max;
                    }

                    if (isCropUpsize)
                    {
                        resizeMode = ISResizeMode.Crop;
                    }

                    if (w >= image.Width && h >= image.Height && resizeMode == ISResizeMode.Crop && !isCropUpsize)
                    {
                        resizeMode = ISResizeMode.BoxPad;
                    }

                    var resizeOptions = new ISResizeOptions {
                        Size = new Size(w, h), Mode = resizeMode, PremultiplyAlpha = true
                    };

                    if (options.FocusX.HasValue && options.FocusY.HasValue)
                    {
                        resizeOptions.CenterCoordinates = new PointF(
                            +(options.FocusX.Value / 2f) + 0.5f,
                            -(options.FocusY.Value / 2f) + 0.5f
                            );
                    }

                    image.Mutate(operation =>
                    {
                        operation.Resize(resizeOptions);

                        if (Color.TryParse(options.Background, out var color))
                        {
                            operation.BackgroundColor(color);
                        }
                        else
                        {
                            operation.BackgroundColor(Color.Transparent);
                        }
                    });
                }

                var encoder = options.GetEncoder(format);

                await image.SaveAsync(destination, encoder, ct);
            }
        }