/// <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)); }
/// <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)); }
/// <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)); }
/// <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)); } }
/// <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)); }
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); } })); }
/// <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)); }
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(); } }
/// <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()));
/// <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)); } }
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(); } }
/// <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()));
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); } }