mirror of https://github.com/SixLabors/ImageSharp
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
429 lines
17 KiB
429 lines
17 KiB
// <copyright file="ResizeHelper.cs" company="James Jackson-South">
|
|
// Copyright (c) James Jackson-South and contributors.
|
|
// Licensed under the Apache License, Version 2.0.
|
|
// </copyright>
|
|
|
|
namespace ImageSharp.Processing
|
|
{
|
|
using System;
|
|
using System.Linq;
|
|
|
|
/// <summary>
|
|
/// Provides methods to help calculate the target rectangle when resizing using the
|
|
/// <see cref="ResizeMode"/> enumeration.
|
|
/// </summary>
|
|
internal static class ResizeHelper
|
|
{
|
|
/// <summary>
|
|
/// Calculates the target location and bounds to perform the resize operation against.
|
|
/// </summary>
|
|
/// <typeparam name="TColor">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<TColor>(ImageBase<TColor> source, ResizeOptions options)
|
|
where TColor : struct, IPixel<TColor>
|
|
{
|
|
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 crop mode.
|
|
/// </summary>
|
|
/// <typeparam name="TColor">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<TColor>(ImageBase<TColor> source, ResizeOptions options)
|
|
where TColor : struct, IPixel<TColor>
|
|
{
|
|
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 = MathF.Abs(height / (float)sourceHeight);
|
|
float percentWidth = MathF.Abs(width / (float)sourceWidth);
|
|
|
|
if (percentHeight < percentWidth)
|
|
{
|
|
ratio = percentWidth;
|
|
|
|
if (options.CenterCoordinates.Any())
|
|
{
|
|
float center = -(ratio * sourceHeight) * options.CenterCoordinates.First();
|
|
destinationY = (int)center + (height / 2);
|
|
|
|
if (destinationY > 0)
|
|
{
|
|
destinationY = 0;
|
|
}
|
|
|
|
if (destinationY < (int)(height - (sourceHeight * ratio)))
|
|
{
|
|
destinationY = (int)(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)(height - (sourceHeight * ratio));
|
|
break;
|
|
default:
|
|
destinationY = (int)((height - (sourceHeight * ratio)) / 2);
|
|
break;
|
|
}
|
|
}
|
|
|
|
destinationHeight = (int)MathF.Ceiling(sourceHeight * percentWidth);
|
|
}
|
|
else
|
|
{
|
|
ratio = percentHeight;
|
|
|
|
if (options.CenterCoordinates.Any())
|
|
{
|
|
float center = -(ratio * sourceWidth) * options.CenterCoordinates.ToArray()[1];
|
|
destinationX = (int)center + (width / 2);
|
|
|
|
if (destinationX > 0)
|
|
{
|
|
destinationX = 0;
|
|
}
|
|
|
|
if (destinationX < (int)(width - (sourceWidth * ratio)))
|
|
{
|
|
destinationX = (int)(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)(width - (sourceWidth * ratio));
|
|
break;
|
|
default:
|
|
destinationX = (int)((width - (sourceWidth * ratio)) / 2);
|
|
break;
|
|
}
|
|
}
|
|
|
|
destinationWidth = (int)MathF.Ceiling(sourceWidth * percentHeight);
|
|
}
|
|
|
|
return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the target rectangle for pad mode.
|
|
/// </summary>
|
|
/// <typeparam name="TColor">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<TColor>(ImageBase<TColor> source, ResizeOptions options)
|
|
where TColor : struct, IPixel<TColor>
|
|
{
|
|
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 = MathF.Abs(height / (float)sourceHeight);
|
|
float percentWidth = MathF.Abs(width / (float)sourceWidth);
|
|
|
|
if (percentHeight < percentWidth)
|
|
{
|
|
ratio = percentHeight;
|
|
destinationWidth = Convert.ToInt32(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)(width - (sourceWidth * ratio));
|
|
break;
|
|
default:
|
|
destinationX = Convert.ToInt32((width - (sourceWidth * ratio)) / 2);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ratio = percentWidth;
|
|
destinationHeight = Convert.ToInt32(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)(height - (sourceHeight * ratio));
|
|
break;
|
|
default:
|
|
destinationY = (int)((height - (sourceHeight * ratio)) / 2);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the target rectangle for box pad mode.
|
|
/// </summary>
|
|
/// <typeparam name="TColor">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<TColor>(ImageBase<TColor> source, ResizeOptions options)
|
|
where TColor : struct, IPixel<TColor>
|
|
{
|
|
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 = MathF.Abs(height / (float)sourceHeight);
|
|
float percentWidth = MathF.Abs(width / (float)sourceWidth);
|
|
|
|
int boxPadHeight = height > 0 ? height : Convert.ToInt32(sourceHeight * percentWidth);
|
|
int boxPadWidth = width > 0 ? width : Convert.ToInt32(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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the target rectangle for max mode.
|
|
/// </summary>
|
|
/// <typeparam name="TColor">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<TColor>(ImageBase<TColor> source, ResizeOptions options)
|
|
where TColor : struct, IPixel<TColor>
|
|
{
|
|
int width = options.Size.Width;
|
|
int height = options.Size.Height;
|
|
int destinationWidth = width;
|
|
int destinationHeight = height;
|
|
|
|
// Fractional variants for preserving aspect ratio.
|
|
float percentHeight = MathF.Abs(height / (float)source.Height);
|
|
float percentWidth = MathF.Abs(width / (float)source.Width);
|
|
|
|
// Integers must be cast to floats to get needed precision
|
|
float ratio = (float)options.Size.Height / options.Size.Width;
|
|
float sourceRatio = (float)source.Height / source.Width;
|
|
|
|
if (sourceRatio < ratio)
|
|
{
|
|
destinationHeight = Convert.ToInt32(source.Height * percentWidth);
|
|
height = destinationHeight;
|
|
}
|
|
else
|
|
{
|
|
destinationWidth = Convert.ToInt32(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="TColor">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<TColor>(ImageBase<TColor> source, ResizeOptions options)
|
|
where TColor : struct, IPixel<TColor>
|
|
{
|
|
int width = options.Size.Width;
|
|
int height = options.Size.Height;
|
|
int destinationWidth;
|
|
int destinationHeight;
|
|
|
|
// Don't upscale
|
|
if (width > source.Width || height > source.Height)
|
|
{
|
|
options.Size = new Size(source.Width, source.Height);
|
|
return new Rectangle(0, 0, source.Width, source.Height);
|
|
}
|
|
|
|
float sourceRatio = (float)source.Height / source.Width;
|
|
|
|
// Find the shortest distance to go.
|
|
int widthDiff = source.Width - width;
|
|
int heightDiff = source.Height - height;
|
|
|
|
if (widthDiff < heightDiff)
|
|
{
|
|
destinationHeight = Convert.ToInt32(width * sourceRatio);
|
|
height = destinationHeight;
|
|
destinationWidth = width;
|
|
}
|
|
else if (widthDiff > heightDiff)
|
|
{
|
|
destinationWidth = Convert.ToInt32(height / sourceRatio);
|
|
destinationHeight = height;
|
|
width = destinationWidth;
|
|
}
|
|
else
|
|
{
|
|
destinationWidth = width;
|
|
destinationHeight = height;
|
|
}
|
|
|
|
// Replace the size to match the rectangle.
|
|
options.Size = new Size(width, height);
|
|
return new Rectangle(0, 0, destinationWidth, destinationHeight);
|
|
}
|
|
}
|
|
}
|
|
|