Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 36 additions & 14 deletions src/ImageSharp/Processing/Extensions/ResizeExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using SixLabors.ImageSharp.Processing.Processors.Transforms;
Expand All @@ -12,16 +12,6 @@ namespace SixLabors.ImageSharp.Processing
/// </summary>
public static class ResizeExtensions
{
/// <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>
/// Resizes an image to the given <see cref="Size"/>.
/// </summary>
Expand Down Expand Up @@ -128,7 +118,18 @@ public static IImageProcessingContext Resize(
Rectangle sourceRectangle,
Rectangle targetRectangle,
bool compand)
=> source.ApplyProcessor(new ResizeProcessor(sampler, width, height, source.GetCurrentSize(), targetRectangle, compand), sourceRectangle);
{
var options = new ResizeOptions
{
Size = new Size(width, height),
Mode = ResizeMode.Manual,
Sampler = sampler,
TargetRectangle = targetRectangle,
Compand = compand
};

return source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize()), sourceRectangle);
}

/// <summary>
/// Resizes an image to the given width and height with the given sampler and source rectangle.
Expand All @@ -150,6 +151,27 @@ public static IImageProcessingContext Resize(
IResampler sampler,
Rectangle targetRectangle,
bool compand)
=> source.ApplyProcessor(new ResizeProcessor(sampler, width, height, source.GetCurrentSize(), targetRectangle, compand));
{
var options = new ResizeOptions
{
Size = new Size(width, height),
Mode = ResizeMode.Manual,
Sampler = sampler,
TargetRectangle = targetRectangle,
Compand = compand
};

return Resize(source, options);
}

/// <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()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public static (Size, Rectangle) CalculateTargetLocationAndBounds(Size sourceSize
int width = options.Size.Width;
int height = options.Size.Height;

if (width <= 0 && height <= 0)
{
ThrowInvalid($"Target width {width} and height {height} must be greater than zero.");
}

// Ensure target size is populated across both dimensions.
// These dimensions are used to calculate the final dimensions determined by the mode algorithm.
// If only one of the incoming dimensions is 0, it will be modified here to maintain aspect ratio.
Expand All @@ -51,9 +56,6 @@ public static (Size, Rectangle) CalculateTargetLocationAndBounds(Size sourceSize
height = (int)MathF.Max(Min, MathF.Round(sourceSize.Height * width / (float)sourceSize.Width));
}

Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));

switch (options.Mode)
{
case ResizeMode.Crop:
Expand All @@ -66,8 +68,10 @@ public static (Size, Rectangle) CalculateTargetLocationAndBounds(Size sourceSize
return CalculateMaxRectangle(sourceSize, width, height);
case ResizeMode.Min:
return CalculateMinRectangle(sourceSize, width, height);
case ResizeMode.Manual:
return CalculateManualRectangle(options, width, height);

// Last case ResizeMode.Stretch:
// case ResizeMode.Stretch:
default:
return (new Size(width, height), new Rectangle(0, 0, width, height));
}
Expand Down Expand Up @@ -397,5 +401,28 @@ private static (Size, Rectangle) CalculatePadRectangle(
// Target image width and height can be different to the rectangle width and height.
return (new Size(width, height), new Rectangle(targetX, targetY, targetWidth, targetHeight));
}

private static (Size, Rectangle) CalculateManualRectangle(
ResizeOptions options,
int width,
int height)
{
if (!options.TargetRectangle.HasValue)
{
ThrowInvalid("Manual resizing requires a target location and size.");
}

Rectangle targetRectangle = options.TargetRectangle.Value;

int targetX = targetRectangle.X;
int targetY = targetRectangle.Y;
int targetWidth = targetRectangle.Width > 0 ? targetRectangle.Width : width;
int targetHeight = targetRectangle.Height > 0 ? targetRectangle.Height : height;

// Target image width and height can be different to the rectangle width and height.
return (new Size(width, height), new Rectangle(targetX, targetY, targetWidth, targetHeight));
}

private static void ThrowInvalid(string message) => throw new InvalidOperationException(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,45 +13,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary>
public class ResizeProcessor : IImageProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="ResizeProcessor"/> class.
/// </summary>
/// <param name="sampler">The <see cref="IResampler"/>.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="sourceSize">The size of the source image.</param>
/// <param name="targetRectangle">The target rectangle to resize into.</param>
/// <param name="compand">A value indicating whether to apply RGBA companding.</param>
public ResizeProcessor(IResampler sampler, int width, int height, Size sourceSize, Rectangle targetRectangle, bool compand)
{
Guard.NotNull(sampler, nameof(sampler));

// Ensure target size is populated across both dimensions.
// If only one of the incoming dimensions is 0, it will be modified here to maintain aspect ratio.
// If it is not possible to keep aspect ratio, make sure at least the minimum is is kept.
const int Min = 1;
if (width == 0 && height > 0)
{
width = (int)MathF.Max(Min, MathF.Round(sourceSize.Width * height / (float)sourceSize.Height));
targetRectangle.Width = width;
}

if (height == 0 && width > 0)
{
height = (int)MathF.Max(Min, MathF.Round(sourceSize.Height * width / (float)sourceSize.Width));
targetRectangle.Height = height;
}

Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));

this.Sampler = sampler;
this.TargetWidth = width;
this.TargetHeight = height;
this.TargetRectangle = targetRectangle;
this.Compand = compand;
}

/// <summary>
/// Initializes a new instance of the <see cref="ResizeProcessor"/> class.
/// </summary>
Expand All @@ -71,18 +32,6 @@ public ResizeProcessor(ResizeOptions options, Size sourceSize)
this.Compand = options.Compand;
}

/// <summary>
/// Initializes a new instance of the <see cref="ResizeProcessor"/> class.
/// </summary>
/// <param name="sampler">The sampler to perform the resize operation.</param>
/// <param name="width">The target width.</param>
/// <param name="height">The target height.</param>
/// <param name="sourceSize">The source image size</param>
public ResizeProcessor(IResampler sampler, int width, int height, Size sourceSize)
: this(sampler, width, height, sourceSize, new Rectangle(0, 0, width, height), false)
{
}

/// <summary>
/// Gets the sampler to perform the resize operation.
/// </summary>
Expand Down
9 changes: 7 additions & 2 deletions src/ImageSharp/Processing/ResizeMode.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

namespace SixLabors.ImageSharp.Processing
Expand Down Expand Up @@ -42,6 +42,11 @@ public enum ResizeMode
/// <summary>
/// Stretches the resized image to fit the bounds of its container.
/// </summary>
Stretch
Stretch,

/// <summary>
/// The target location and size of the resized image has been manually set.
/// </summary>
Manual
}
}
5 changes: 5 additions & 0 deletions src/ImageSharp/Processing/ResizeOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,10 @@ public class ResizeOptions
/// or expand individual pixel colors the value on processing.
/// </summary>
public bool Compand { get; set; } = false;

/// <summary>
/// Gets or sets the target rectangle to resize into.
/// </summary>
public Rectangle? TargetRectangle { get; set; }
}
}