mirror of https://github.com/SixLabors/ImageSharp
Browse Source
Former-commit-id: 01e9ff7cd68ee13a29e9b8f574ab9bf4c4190c0d Former-commit-id: be5c2dc437493de0e9a77a49ab367816f6030baa Former-commit-id: adb5ccf166b13bf8d88431f61ce77a155c469f82pull/1/head
40 changed files with 1147 additions and 1055 deletions
@ -1,42 +1,68 @@ |
|||
// <copyright file="Crop.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// </copyright>-------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessorCore.Samplers |
|||
namespace ImageProcessorCore |
|||
{ |
|||
using System.Threading.Tasks; |
|||
using Processors; |
|||
|
|||
/// <summary>
|
|||
/// Provides methods to allow the cropping of an image.
|
|||
/// Extension methods for the <see cref="Image"/> type.
|
|||
/// </summary>
|
|||
public class Crop : ImageSampler |
|||
public static partial class ImageExtensions |
|||
{ |
|||
/// <inheritdoc/>
|
|||
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) |
|||
/// <summary>
|
|||
/// Crops an image to the given width and height.
|
|||
/// </summary>
|
|||
/// <param name="source">The image to resize.</param>
|
|||
/// <param name="width">The target image width.</param>
|
|||
/// <param name="height">The target image height.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="ImageProcessorCore.Image"/></returns>
|
|||
public static Image Crop(this Image source, int width, int height, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
int targetY = targetRectangle.Y; |
|||
int targetBottom = targetRectangle.Bottom; |
|||
int startX = targetRectangle.X; |
|||
int endX = targetRectangle.Right; |
|||
int sourceX = sourceRectangle.X; |
|||
int sourceY = sourceRectangle.Y; |
|||
return Crop(source, width, height, source.Bounds, progressHandler); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Crops an image to the given width and height with the given source rectangle.
|
|||
/// <remarks>
|
|||
/// If the source rectangle is smaller than the target dimensions then the
|
|||
/// area within the source is resized performing a zoomed crop.
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
/// <param name="source">The image to crop.</param>
|
|||
/// <param name="width">The target image width.</param>
|
|||
/// <param name="height">The target image height.</param>
|
|||
/// <param name="sourceRectangle">
|
|||
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
|
|||
/// </param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
public static Image Crop(this Image source, int width, int height, Rectangle sourceRectangle, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
Guard.MustBeGreaterThan(width, 0, nameof(width)); |
|||
Guard.MustBeGreaterThan(height, 0, nameof(height)); |
|||
|
|||
Parallel.For( |
|||
startY, |
|||
endY, |
|||
y => |
|||
if (sourceRectangle.Width < width || sourceRectangle.Height < height) |
|||
{ |
|||
if (y >= targetY && y < targetBottom) |
|||
{ |
|||
for (int x = startX; x < endX; x++) |
|||
{ |
|||
target[x, y] = source[x + sourceX, y + sourceY]; |
|||
} |
|||
// If the source rectangle is smaller than the target perform a
|
|||
// cropped zoom.
|
|||
source = source.Resize(sourceRectangle.Width, sourceRectangle.Height); |
|||
} |
|||
|
|||
this.OnRowProcessed(); |
|||
} |
|||
}); |
|||
Crop processor = new Crop(); |
|||
processor.OnProgress += progressHandler; |
|||
|
|||
try |
|||
{ |
|||
return source.Process(width, height, sourceRectangle, new Rectangle(0, 0, width, height), processor); |
|||
} |
|||
finally |
|||
{ |
|||
processor.OnProgress -= progressHandler; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -1,324 +0,0 @@ |
|||
// <copyright file="ImageSamplerExtensions.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.Samplers |
|||
{ |
|||
/// <summary>
|
|||
/// Extensions methods for <see cref="Image"/> to apply samplers to the image.
|
|||
/// </summary>
|
|||
public static class ImageSamplerExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Crops an image to the given width and height.
|
|||
/// </summary>
|
|||
/// <param name="source">The image to resize.</param>
|
|||
/// <param name="width">The target image width.</param>
|
|||
/// <param name="height">The target image height.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
public static Image Crop(this Image source, int width, int height, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
return Crop(source, width, height, source.Bounds, progressHandler); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Crops an image to the given width and height with the given source rectangle.
|
|||
/// <remarks>
|
|||
/// If the source rectangle is smaller than the target dimensions then the
|
|||
/// area within the source is resized performing a zoomed crop.
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
/// <param name="source">The image to crop.</param>
|
|||
/// <param name="width">The target image width.</param>
|
|||
/// <param name="height">The target image height.</param>
|
|||
/// <param name="sourceRectangle">
|
|||
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
|
|||
/// </param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
public static Image Crop(this Image source, int width, int height, Rectangle sourceRectangle, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
Guard.MustBeGreaterThan(width, 0, nameof(width)); |
|||
Guard.MustBeGreaterThan(height, 0, nameof(height)); |
|||
|
|||
if (sourceRectangle.Width < width || sourceRectangle.Height < height) |
|||
{ |
|||
// If the source rectangle is smaller than the target perform a
|
|||
// cropped zoom.
|
|||
source = source.Resize(sourceRectangle.Width, sourceRectangle.Height); |
|||
} |
|||
|
|||
Crop processor = new Crop(); |
|||
processor.OnProgress += progressHandler; |
|||
|
|||
try |
|||
{ |
|||
return source.Process(width, height, sourceRectangle, new Rectangle(0, 0, width, height), processor); |
|||
} |
|||
finally |
|||
{ |
|||
processor.OnProgress -= progressHandler; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Crops an image to the area of greatest entropy.
|
|||
/// </summary>
|
|||
/// <param name="source">The image to crop.</param>
|
|||
/// <param name="threshold">The threshold for entropic density.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
public static Image EntropyCrop(this Image source, float threshold = .5f, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
EntropyCrop processor = new EntropyCrop(threshold); |
|||
processor.OnProgress += progressHandler; |
|||
|
|||
try |
|||
{ |
|||
return source.Process(source.Width, source.Height, source.Bounds, Rectangle.Empty, processor); |
|||
} |
|||
finally |
|||
{ |
|||
processor.OnProgress -= progressHandler; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Evenly pads an image to fit the new dimensions.
|
|||
/// </summary>
|
|||
/// <param name="source">The source image to pad.</param>
|
|||
/// <param name="width">The new width.</param>
|
|||
/// <param name="height">The new height.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/>.</returns>
|
|||
public static Image Pad(this Image source, int width, int height, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
ResizeOptions options = new ResizeOptions |
|||
{ |
|||
Size = new Size(width, height), |
|||
Mode = ResizeMode.BoxPad, |
|||
Sampler = new NearestNeighborResampler() |
|||
}; |
|||
|
|||
return Resize(source, options, progressHandler); |
|||
} |
|||
|
|||
/// <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>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
/// <remarks>Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image</remarks>
|
|||
public static Image Resize(this Image source, ResizeOptions options, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
// Ensure size is populated across both dimensions.
|
|||
if (options.Size.Width == 0 && options.Size.Height > 0) |
|||
{ |
|||
options.Size = new Size(source.Width * options.Size.Height / source.Height, options.Size.Height); |
|||
} |
|||
|
|||
if (options.Size.Height == 0 && options.Size.Width > 0) |
|||
{ |
|||
options.Size = new Size(options.Size.Width, source.Height * options.Size.Width / source.Width); |
|||
} |
|||
|
|||
Rectangle targetRectangle = ResizeHelper.CalculateTargetLocationAndBounds(source, options); |
|||
|
|||
return Resize(source, options.Size.Width, options.Size.Height, options.Sampler, source.Bounds, targetRectangle, options.Compand, progressHandler); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Resizes an image to the given width and height.
|
|||
/// </summary>
|
|||
/// <param name="source">The image to resize.</param>
|
|||
/// <param name="width">The target image width.</param>
|
|||
/// <param name="height">The target image height.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
|
|||
public static Image Resize(this Image source, int width, int height, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
return Resize(source, width, height, new BicubicResampler(), false, progressHandler); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Resizes an image to the given width and height.
|
|||
/// </summary>
|
|||
/// <param name="source">The image to resize.</param>
|
|||
/// <param name="width">The target image width.</param>
|
|||
/// <param name="height">The target image height.</param>
|
|||
/// <param name="compand">Whether to compress and expand the image color-space to gamma correct the image during processing.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
|
|||
public static Image Resize(this Image source, int width, int height, bool compand, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
return Resize(source, width, height, new BicubicResampler(), compand, progressHandler); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Resizes an image to the given width and height with the given sampler.
|
|||
/// </summary>
|
|||
/// <param name="source">The image to resize.</param>
|
|||
/// <param name="width">The target image width.</param>
|
|||
/// <param name="height">The target image height.</param>
|
|||
/// <param name="sampler">The <see cref="IResampler"/> to perform the resampling.</param>
|
|||
/// <param name="compand">Whether to compress and expand the image color-space to gamma correct the image during processing.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
|
|||
public static Image Resize(this Image source, int width, int height, IResampler sampler, bool compand, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
return Resize(source, width, height, sampler, source.Bounds, new Rectangle(0, 0, width, height), compand, progressHandler); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Resizes an image to the given width and height with the given sampler and
|
|||
/// source rectangle.
|
|||
/// </summary>
|
|||
/// <param name="source">The image to resize.</param>
|
|||
/// <param name="width">The target image width.</param>
|
|||
/// <param name="height">The target image height.</param>
|
|||
/// <param name="sampler">The <see cref="IResampler"/> to perform the resampling.</param>
|
|||
/// <param name="sourceRectangle">
|
|||
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
|
|||
/// </param>
|
|||
/// <param name="targetRectangle">
|
|||
/// The <see cref="Rectangle"/> structure that specifies the portion of the target image object to draw to.
|
|||
/// </param>
|
|||
/// <param name="compand">Whether to compress and expand the image color-space to gamma correct the image during processing.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
|
|||
public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand = false, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
if (width == 0 && height > 0) |
|||
{ |
|||
width = source.Width * height / source.Height; |
|||
targetRectangle.Width = width; |
|||
} |
|||
|
|||
if (height == 0 && width > 0) |
|||
{ |
|||
height = source.Height * width / source.Width; |
|||
targetRectangle.Height = height; |
|||
} |
|||
|
|||
Guard.MustBeGreaterThan(width, 0, nameof(width)); |
|||
Guard.MustBeGreaterThan(height, 0, nameof(height)); |
|||
|
|||
Resize processor = new Resize(sampler) { Compand = compand }; |
|||
processor.OnProgress += progressHandler; |
|||
|
|||
try |
|||
{ |
|||
return source.Process(width, height, sourceRectangle, targetRectangle, processor); |
|||
} |
|||
finally |
|||
{ |
|||
processor.OnProgress -= progressHandler; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotates an image by the given angle in degrees, expanding the image to fit the rotated result.
|
|||
/// </summary>
|
|||
/// <param name="source">The image to rotate.</param>
|
|||
/// <param name="degrees">The angle in degrees to perform the rotation.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
public static Image Rotate(this Image source, float degrees, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
return Rotate(source, degrees, Point.Empty, true, progressHandler); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotates an image by the given angle in degrees around the given center point.
|
|||
/// </summary>
|
|||
/// <param name="source">The image to rotate.</param>
|
|||
/// <param name="degrees">The angle in degrees to perform the rotation.</param>
|
|||
/// <param name="center">The center point at which to rotate the image.</param>
|
|||
/// <param name="expand">Whether to expand the image to fit the rotated result.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
public static Image Rotate(this Image source, float degrees, Point center, bool expand, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
Rotate processor = new Rotate { Angle = degrees, Center = center, Expand = expand }; |
|||
processor.OnProgress += progressHandler; |
|||
|
|||
try |
|||
{ |
|||
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor); |
|||
} |
|||
finally |
|||
{ |
|||
processor.OnProgress -= progressHandler; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotates and flips an image by the given instructions.
|
|||
/// </summary>
|
|||
/// <param name="source">The image to rotate, flip, or both.</param>
|
|||
/// <param name="rotateType">The <see cref="RotateType"/> to perform the rotation.</param>
|
|||
/// <param name="flipType">The <see cref="FlipType"/> to perform the flip.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
public static Image RotateFlip(this Image source, RotateType rotateType, FlipType flipType, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
RotateFlip processor = new RotateFlip(rotateType, flipType); |
|||
processor.OnProgress += progressHandler; |
|||
|
|||
try |
|||
{ |
|||
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor); |
|||
} |
|||
finally |
|||
{ |
|||
processor.OnProgress -= progressHandler; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Skews an image by the given angles in degrees, expanding the image to fit the skewed result.
|
|||
/// </summary>
|
|||
/// <param name="source">The image to skew.</param>
|
|||
/// <param name="degreesX">The angle in degrees to perform the rotation along the x-axis.</param>
|
|||
/// <param name="degreesY">The angle in degrees to perform the rotation along the y-axis.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
public static Image Skew(this Image source, float degreesX, float degreesY, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
return Skew(source, degreesX, degreesY, Point.Empty, true, progressHandler); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Skews an image by the given angles in degrees around the given center point.
|
|||
/// </summary>
|
|||
/// <param name="source">The image to skew.</param>
|
|||
/// <param name="degreesX">The angle in degrees to perform the rotation along the x-axis.</param>
|
|||
/// <param name="degreesY">The angle in degrees to perform the rotation along the y-axis.</param>
|
|||
/// <param name="center">The center point at which to skew the image.</param>
|
|||
/// <param name="expand">Whether to expand the image to fit the skewed result.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
public static Image Skew(this Image source, float degreesX, float degreesY, Point center, bool expand, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
Skew processor = new Skew { AngleX = degreesX, AngleY = degreesY, Center = center, Expand = expand }; |
|||
processor.OnProgress += progressHandler; |
|||
|
|||
try |
|||
{ |
|||
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor); |
|||
} |
|||
finally |
|||
{ |
|||
processor.OnProgress -= progressHandler; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
// <copyright file="Pad.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>-------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessorCore |
|||
{ |
|||
using Processors; |
|||
|
|||
/// <summary>
|
|||
/// Extension methods for the <see cref="Image"/> type.
|
|||
/// </summary>
|
|||
public static partial class ImageExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Evenly pads an image to fit the new dimensions.
|
|||
/// </summary>
|
|||
/// <param name="source">The source image to pad.</param>
|
|||
/// <param name="width">The new width.</param>
|
|||
/// <param name="height">The new height.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/>.</returns>
|
|||
public static Image Pad(this Image source, int width, int height, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
ResizeOptions options = new ResizeOptions |
|||
{ |
|||
Size = new Size(width, height), |
|||
Mode = ResizeMode.BoxPad, |
|||
Sampler = new NearestNeighborResampler() |
|||
}; |
|||
|
|||
return Resize(source, options, progressHandler); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
// <copyright file="Crop.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.Processors |
|||
{ |
|||
using System.Threading.Tasks; |
|||
|
|||
/// <summary>
|
|||
/// Provides methods to allow the cropping of an image.
|
|||
/// </summary>
|
|||
public class Crop : ImageSampler |
|||
{ |
|||
/// <inheritdoc/>
|
|||
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) |
|||
{ |
|||
int targetY = targetRectangle.Y; |
|||
int targetBottom = targetRectangle.Bottom; |
|||
int startX = targetRectangle.X; |
|||
int endX = targetRectangle.Right; |
|||
int sourceX = sourceRectangle.X; |
|||
int sourceY = sourceRectangle.Y; |
|||
|
|||
Parallel.For( |
|||
startY, |
|||
endY, |
|||
y => |
|||
{ |
|||
if (y >= targetY && y < targetBottom) |
|||
{ |
|||
for (int x = startX; x < endX; x++) |
|||
{ |
|||
target[x, y] = source[x + sourceX, y + sourceY]; |
|||
} |
|||
|
|||
this.OnRowProcessed(); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,103 @@ |
|||
// <copyright file="EntropyCrop.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.Processors |
|||
{ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
using ImageProcessorCore.Filters; |
|||
|
|||
/// <summary>
|
|||
/// Provides methods to allow the cropping of an image to preserve areas of highest
|
|||
/// entropy.
|
|||
/// </summary>
|
|||
public class EntropyCrop : ImageSampler |
|||
{ |
|||
/// <summary>
|
|||
/// The rectangle for cropping
|
|||
/// </summary>
|
|||
private Rectangle cropRectangle; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="EntropyCrop"/> class.
|
|||
/// </summary>
|
|||
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
|
|||
/// <exception cref="ArgumentException">
|
|||
/// <paramref name="threshold"/> is less than 0 or is greater than 1.
|
|||
/// </exception>
|
|||
public EntropyCrop(float threshold) |
|||
{ |
|||
Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); |
|||
this.Value = threshold; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the threshold value.
|
|||
/// </summary>
|
|||
public float Value { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) |
|||
{ |
|||
using (ImageBase temp = new Image(source.Width, source.Height)) |
|||
{ |
|||
// Detect the edges.
|
|||
new Sobel().Apply(temp, source, sourceRectangle); |
|||
|
|||
// Apply threshold binarization filter.
|
|||
new Threshold(.5f).Apply(temp, temp, sourceRectangle); |
|||
|
|||
// Search for the first white pixels
|
|||
Rectangle rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0); |
|||
|
|||
// Reset the target pixel to the correct size.
|
|||
target.SetPixels(rectangle.Width, rectangle.Height, new float[rectangle.Width * rectangle.Height * 4]); |
|||
this.cropRectangle = rectangle; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) |
|||
{ |
|||
// Jump out, we'll deal with that later.
|
|||
if (source.Bounds == target.Bounds) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
int targetY = this.cropRectangle.Y; |
|||
int targetBottom = this.cropRectangle.Bottom; |
|||
int startX = this.cropRectangle.X; |
|||
int endX = this.cropRectangle.Right; |
|||
|
|||
Parallel.For( |
|||
startY, |
|||
endY, |
|||
y => |
|||
{ |
|||
if (y >= targetY && y < targetBottom) |
|||
{ |
|||
for (int x = startX; x < endX; x++) |
|||
{ |
|||
target[x - startX, y - targetY] = source[x, y]; |
|||
} |
|||
} |
|||
|
|||
this.OnRowProcessed(); |
|||
}); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) |
|||
{ |
|||
// Copy the pixels over.
|
|||
if (source.Bounds == target.Bounds) |
|||
{ |
|||
target.ClonePixels(target.Width, target.Height, source.Pixels); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,192 @@ |
|||
// <copyright file="Resize.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.Processors |
|||
{ |
|||
using System.Threading.Tasks; |
|||
|
|||
/// <summary>
|
|||
/// Provides methods that allow the resizing of images using various algorithms.
|
|||
/// </summary>
|
|||
public class Resize : Resampler |
|||
{ |
|||
/// <summary>
|
|||
/// The image used for storing the first pass pixels.
|
|||
/// </summary>
|
|||
private Image firstPass; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Resize"/> class.
|
|||
/// </summary>
|
|||
/// <param name="sampler">
|
|||
/// The sampler to perform the resize operation.
|
|||
/// </param>
|
|||
public Resize(IResampler sampler) |
|||
: base(sampler) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int Parallelism { get; set; } = 1; |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) |
|||
{ |
|||
if (!(this.Sampler is NearestNeighborResampler)) |
|||
{ |
|||
this.HorizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width); |
|||
this.VerticalWeights = this.PrecomputeWeights(targetRectangle.Height, sourceRectangle.Height); |
|||
} |
|||
|
|||
this.firstPass = new Image(target.Width, source.Height); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) |
|||
{ |
|||
// Jump out, we'll deal with that later.
|
|||
if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
int width = target.Width; |
|||
int height = target.Height; |
|||
int sourceHeight = sourceRectangle.Height; |
|||
int targetX = target.Bounds.X; |
|||
int targetY = target.Bounds.Y; |
|||
int targetRight = target.Bounds.Right; |
|||
int targetBottom = target.Bounds.Bottom; |
|||
int startX = targetRectangle.X; |
|||
int endX = targetRectangle.Right; |
|||
bool compand = this.Compand; |
|||
|
|||
if (this.Sampler is NearestNeighborResampler) |
|||
{ |
|||
// Scaling factors
|
|||
float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width; |
|||
float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height; |
|||
|
|||
Parallel.For( |
|||
startY, |
|||
endY, |
|||
y => |
|||
{ |
|||
if (targetY <= y && y < targetBottom) |
|||
{ |
|||
// Y coordinates of source points
|
|||
int originY = (int)((y - startY) * heightFactor); |
|||
|
|||
for (int x = startX; x < endX; x++) |
|||
{ |
|||
if (targetX <= x && x < targetRight) |
|||
{ |
|||
// X coordinates of source points
|
|||
int originX = (int)((x - startX) * widthFactor); |
|||
|
|||
target[x, y] = source[originX, originY]; |
|||
} |
|||
} |
|||
|
|||
this.OnRowProcessed(); |
|||
} |
|||
}); |
|||
|
|||
// Break out now.
|
|||
return; |
|||
} |
|||
|
|||
// Interpolate the image using the calculated weights.
|
|||
// A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm
|
|||
// First process the columns. Since we are not using multiple threads startY and endY
|
|||
// are the upper and lower bounds of the source rectangle.
|
|||
Parallel.For( |
|||
0, |
|||
sourceHeight, |
|||
y => |
|||
{ |
|||
for (int x = startX; x < endX; x++) |
|||
{ |
|||
if (x >= 0 && x < width) |
|||
{ |
|||
// Ensure offsets are normalised for cropping and padding.
|
|||
int offsetX = x - startX; |
|||
float sum = this.HorizontalWeights[offsetX].Sum; |
|||
Weight[] horizontalValues = this.HorizontalWeights[offsetX].Values; |
|||
|
|||
// Destination color components
|
|||
Color destination = new Color(); |
|||
|
|||
for (int i = 0; i < sum; i++) |
|||
{ |
|||
Weight xw = horizontalValues[i]; |
|||
int originX = xw.Index; |
|||
Color sourceColor = compand ? Color.Expand(source[originX, y]) : source[originX, y]; |
|||
destination += sourceColor * xw.Value; |
|||
} |
|||
|
|||
if (compand) |
|||
{ |
|||
destination = Color.Compress(destination); |
|||
} |
|||
|
|||
this.firstPass[x, y] = destination; |
|||
} |
|||
} |
|||
}); |
|||
|
|||
// Now process the rows.
|
|||
Parallel.For( |
|||
startY, |
|||
endY, |
|||
y => |
|||
{ |
|||
if (y >= 0 && y < height) |
|||
{ |
|||
// Ensure offsets are normalised for cropping and padding.
|
|||
int offsetY = y - startY; |
|||
float sum = this.VerticalWeights[offsetY].Sum; |
|||
Weight[] verticalValues = this.VerticalWeights[offsetY].Values; |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
// Destination color components
|
|||
Color destination = new Color(); |
|||
|
|||
for (int i = 0; i < sum; i++) |
|||
{ |
|||
Weight yw = verticalValues[i]; |
|||
int originY = yw.Index; |
|||
Color sourceColor = compand ? Color.Expand(this.firstPass[x, originY]) : this.firstPass[x, originY]; |
|||
destination += sourceColor * yw.Value; |
|||
} |
|||
|
|||
if (compand) |
|||
{ |
|||
destination = Color.Compress(destination); |
|||
} |
|||
|
|||
target[x, y] = destination; |
|||
} |
|||
} |
|||
|
|||
this.OnRowProcessed(); |
|||
}); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) |
|||
{ |
|||
// Copy the pixels over.
|
|||
if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) |
|||
{ |
|||
target.ClonePixels(target.Width, target.Height, source.Pixels); |
|||
} |
|||
|
|||
// Clean up
|
|||
this.firstPass?.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,133 @@ |
|||
// <copyright file="Rotate.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.Processors |
|||
{ |
|||
using System.Numerics; |
|||
using System.Threading.Tasks; |
|||
|
|||
/// <summary>
|
|||
/// Provides methods that allow the rotating of images.
|
|||
/// </summary>
|
|||
public class Rotate : ImageSampler |
|||
{ |
|||
/// <summary>
|
|||
/// The image used for storing the first pass pixels.
|
|||
/// </summary>
|
|||
private Image firstPass; |
|||
|
|||
/// <summary>
|
|||
/// The angle of rotation in degrees.
|
|||
/// </summary>
|
|||
private float angle; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int Parallelism { get; set; } = 1; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the angle of rotation in degrees.
|
|||
/// </summary>
|
|||
public float Angle |
|||
{ |
|||
get |
|||
{ |
|||
return this.angle; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
if (value > 360) |
|||
{ |
|||
value -= 360; |
|||
} |
|||
|
|||
if (value < 0) |
|||
{ |
|||
value += 360; |
|||
} |
|||
|
|||
this.angle = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the center point.
|
|||
/// </summary>
|
|||
public Point Center { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether to expand the canvas to fit the rotated image.
|
|||
/// </summary>
|
|||
public bool Expand { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) |
|||
{ |
|||
// If we are expanding we need to pad the bounds of the source rectangle.
|
|||
// We can use the resizer in nearest neighbor mode to do this fairly quickly.
|
|||
if (this.Expand) |
|||
{ |
|||
// First find out how big the target rectangle should be.
|
|||
Point centre = this.Center == Point.Empty ? Rectangle.Center(sourceRectangle) : this.Center; |
|||
Matrix3x2 rotation = Point.CreateRotation(centre, -this.angle); |
|||
Rectangle rectangle = ImageMaths.GetBoundingRectangle(sourceRectangle, rotation); |
|||
ResizeOptions options = new ResizeOptions |
|||
{ |
|||
Size = new Size(rectangle.Width, rectangle.Height), |
|||
Mode = ResizeMode.BoxPad |
|||
}; |
|||
|
|||
// Get the padded bounds and resize the image.
|
|||
Rectangle bounds = ResizeHelper.CalculateTargetLocationAndBounds(source, options); |
|||
this.firstPass = new Image(rectangle.Width, rectangle.Height); |
|||
target.SetPixels(rectangle.Width, rectangle.Height, new float[rectangle.Width * rectangle.Height * 4]); |
|||
new Resize(new NearestNeighborResampler()).Apply(this.firstPass, source, rectangle.Width, rectangle.Height, bounds, sourceRectangle); |
|||
} |
|||
else |
|||
{ |
|||
// Just clone the pixels across.
|
|||
this.firstPass = new Image(source.Width, source.Height); |
|||
this.firstPass.ClonePixels(source.Width, source.Height, source.Pixels); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) |
|||
{ |
|||
int height = this.firstPass.Height; |
|||
int startX = 0; |
|||
int endX = this.firstPass.Width; |
|||
Point centre = this.Center == Point.Empty ? Rectangle.Center(this.firstPass.Bounds) : this.Center; |
|||
Matrix3x2 rotation = Point.CreateRotation(centre, -this.angle); |
|||
|
|||
// Since we are not working in parallel we use full height and width
|
|||
// of the first pass image.
|
|||
Parallel.For( |
|||
0, |
|||
height, |
|||
y => |
|||
{ |
|||
for (int x = startX; x < endX; x++) |
|||
{ |
|||
// Rotate at the centre point
|
|||
Point rotated = Point.Rotate(new Point(x, y), rotation); |
|||
if (this.firstPass.Bounds.Contains(rotated.X, rotated.Y)) |
|||
{ |
|||
target[x, y] = this.firstPass[rotated.X, rotated.Y]; |
|||
} |
|||
} |
|||
|
|||
this.OnRowProcessed(); |
|||
}); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) |
|||
{ |
|||
// Cleanup.
|
|||
this.firstPass.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,203 @@ |
|||
// <copyright file="RotateFlip.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
namespace ImageProcessorCore.Processors |
|||
{ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
|
|||
/// <summary>
|
|||
/// Provides methods that allow the rotation and flipping of an image around its center point.
|
|||
/// </summary>
|
|||
public class RotateFlip : ImageSampler |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="RotateFlip"/> class.
|
|||
/// </summary>
|
|||
/// <param name="rotateType">The <see cref="RotateType"/> used to perform rotation.</param>
|
|||
/// <param name="flipType">The <see cref="FlipType"/> used to perform flipping.</param>
|
|||
public RotateFlip(RotateType rotateType, FlipType flipType) |
|||
{ |
|||
this.RotateType = rotateType; |
|||
this.FlipType = flipType; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="FlipType"/> used to perform flipping.
|
|||
/// </summary>
|
|||
public FlipType FlipType { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="RotateType"/> used to perform rotation.
|
|||
/// </summary>
|
|||
public RotateType RotateType { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int Parallelism { get; set; } = 1; |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) |
|||
{ |
|||
switch (this.RotateType) |
|||
{ |
|||
case RotateType.Rotate90: |
|||
this.Rotate90(target, source); |
|||
break; |
|||
case RotateType.Rotate180: |
|||
this.Rotate180(target, source); |
|||
break; |
|||
case RotateType.Rotate270: |
|||
this.Rotate270(target, source); |
|||
break; |
|||
default: |
|||
target.ClonePixels(target.Width, target.Height, source.Pixels); |
|||
break; |
|||
} |
|||
|
|||
switch (this.FlipType) |
|||
{ |
|||
// No default needed as we have already set the pixels.
|
|||
case FlipType.Vertical: |
|||
this.FlipX(target); |
|||
break; |
|||
case FlipType.Horizontal: |
|||
this.FlipY(target); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotates the image 270 degrees clockwise at the centre point.
|
|||
/// </summary>
|
|||
/// <param name="target">The target image.</param>
|
|||
/// <param name="source">The source image.</param>
|
|||
private void Rotate270(ImageBase target, ImageBase source) |
|||
{ |
|||
int width = source.Width; |
|||
int height = source.Height; |
|||
Image temp = new Image(height, width); |
|||
|
|||
Parallel.For(0, height, |
|||
y => |
|||
{ |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
int newX = height - y - 1; |
|||
newX = height - newX - 1; |
|||
int newY = width - x - 1; |
|||
newY = width - newY - 1; |
|||
temp[newX, newY] = source[x, y]; |
|||
} |
|||
|
|||
this.OnRowProcessed(); |
|||
}); |
|||
|
|||
target.SetPixels(height, width, temp.Pixels); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotates the image 180 degrees clockwise at the centre point.
|
|||
/// </summary>
|
|||
/// <param name="target">The target image.</param>
|
|||
/// <param name="source">The source image.</param>
|
|||
private void Rotate180(ImageBase target, ImageBase source) |
|||
{ |
|||
int width = source.Width; |
|||
int height = source.Height; |
|||
|
|||
Parallel.For(0, height, |
|||
y => |
|||
{ |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
int newX = width - x - 1; |
|||
int newY = height - y - 1; |
|||
target[newX, newY] = source[x, y]; |
|||
} |
|||
|
|||
this.OnRowProcessed(); |
|||
}); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rotates the image 90 degrees clockwise at the centre point.
|
|||
/// </summary>
|
|||
/// <param name="target">The target image.</param>
|
|||
/// <param name="source">The source image.</param>
|
|||
private void Rotate90(ImageBase target, ImageBase source) |
|||
{ |
|||
int width = source.Width; |
|||
int height = source.Height; |
|||
Image temp = new Image(height, width); |
|||
|
|||
Parallel.For(0, height, |
|||
y => |
|||
{ |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
int newX = height - y - 1; |
|||
temp[newX, x] = source[x, y]; |
|||
} |
|||
|
|||
this.OnRowProcessed(); |
|||
}); |
|||
|
|||
target.SetPixels(height, width, temp.Pixels); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Swaps the image at the X-axis, which goes horizontally through the middle
|
|||
/// at half the height of the image.
|
|||
/// </summary>
|
|||
/// <param name="target">Target image to apply the process to.</param>
|
|||
private void FlipX(ImageBase target) |
|||
{ |
|||
int width = target.Width; |
|||
int height = target.Height; |
|||
int halfHeight = (int)Math.Ceiling(target.Height * .5); |
|||
ImageBase temp = new Image(width, height); |
|||
temp.ClonePixels(width, height, target.Pixels); |
|||
|
|||
Parallel.For(0, halfHeight, |
|||
y => |
|||
{ |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
int newY = height - y - 1; |
|||
target[x, y] = temp[x, newY]; |
|||
target[x, newY] = temp[x, y]; |
|||
} |
|||
|
|||
this.OnRowProcessed(); |
|||
}); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Swaps the image at the Y-axis, which goes vertically through the middle
|
|||
/// at half of the width of the image.
|
|||
/// </summary>
|
|||
/// <param name="target">Target image to apply the process to.</param>
|
|||
private void FlipY(ImageBase target) |
|||
{ |
|||
int width = target.Width; |
|||
int height = target.Height; |
|||
int halfWidth = (int)Math.Ceiling(width / 2d); |
|||
ImageBase temp = new Image(width, height); |
|||
temp.ClonePixels(width, height, target.Pixels); |
|||
|
|||
Parallel.For(0, height, |
|||
y => |
|||
{ |
|||
for (int x = 0; x < halfWidth; x++) |
|||
{ |
|||
int newX = width - x - 1; |
|||
target[x, y] = temp[newX, y]; |
|||
target[newX, y] = temp[x, y]; |
|||
} |
|||
|
|||
this.OnRowProcessed(); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,164 @@ |
|||
// <copyright file="Skew.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageProcessorCore.Processors |
|||
{ |
|||
using System.Numerics; |
|||
using System.Threading.Tasks; |
|||
|
|||
/// <summary>
|
|||
/// Provides methods that allow the skewing of images.
|
|||
/// </summary>
|
|||
public class Skew : ImageSampler |
|||
{ |
|||
/// <summary>
|
|||
/// The image used for storing the first pass pixels.
|
|||
/// </summary>
|
|||
private Image firstPass; |
|||
|
|||
/// <summary>
|
|||
/// The angle of rotation along the x-axis.
|
|||
/// </summary>
|
|||
private float angleX; |
|||
|
|||
/// <summary>
|
|||
/// The angle of rotation along the y-axis.
|
|||
/// </summary>
|
|||
private float angleY; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int Parallelism { get; set; } = 1; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the angle of rotation along the x-axis in degrees.
|
|||
/// </summary>
|
|||
public float AngleX |
|||
{ |
|||
get |
|||
{ |
|||
return this.angleX; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
if (value > 360) |
|||
{ |
|||
value -= 360; |
|||
} |
|||
|
|||
if (value < 0) |
|||
{ |
|||
value += 360; |
|||
} |
|||
|
|||
this.angleX = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the angle of rotation along the y-axis in degrees.
|
|||
/// </summary>
|
|||
public float AngleY |
|||
{ |
|||
get |
|||
{ |
|||
return this.angleY; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
if (value > 360) |
|||
{ |
|||
value -= 360; |
|||
} |
|||
|
|||
if (value < 0) |
|||
{ |
|||
value += 360; |
|||
} |
|||
|
|||
this.angleY = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the center point.
|
|||
/// </summary>
|
|||
public Point Center { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether to expand the canvas to fit the skewed image.
|
|||
/// </summary>
|
|||
public bool Expand { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) |
|||
{ |
|||
// If we are expanding we need to pad the bounds of the source rectangle.
|
|||
// We can use the resizer in nearest neighbor mode to do this fairly quickly.
|
|||
if (this.Expand) |
|||
{ |
|||
// First find out how big the target rectangle should be.
|
|||
Point centre = this.Center == Point.Empty ? Rectangle.Center(sourceRectangle) : this.Center; |
|||
Matrix3x2 skew = Point.CreateSkew(centre, -this.angleX, -this.angleY); |
|||
Rectangle rectangle = ImageMaths.GetBoundingRectangle(sourceRectangle, skew); |
|||
ResizeOptions options = new ResizeOptions |
|||
{ |
|||
Size = new Size(rectangle.Width, rectangle.Height), |
|||
Mode = ResizeMode.BoxPad |
|||
}; |
|||
|
|||
// Get the padded bounds and resize the image.
|
|||
Rectangle bounds = ResizeHelper.CalculateTargetLocationAndBounds(source, options); |
|||
this.firstPass = new Image(rectangle.Width, rectangle.Height); |
|||
target.SetPixels(rectangle.Width, rectangle.Height, new float[rectangle.Width * rectangle.Height * 4]); |
|||
new Resize(new NearestNeighborResampler()).Apply(this.firstPass, source, rectangle.Width, rectangle.Height, bounds, sourceRectangle); |
|||
} |
|||
else |
|||
{ |
|||
// Just clone the pixels across.
|
|||
this.firstPass = new Image(source.Width, source.Height); |
|||
this.firstPass.ClonePixels(source.Width, source.Height, source.Pixels); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) |
|||
{ |
|||
int height = this.firstPass.Height; |
|||
int startX = 0; |
|||
int endX = this.firstPass.Width; |
|||
Point centre = this.Center == Point.Empty ? Rectangle.Center(this.firstPass.Bounds) : this.Center; |
|||
Matrix3x2 skew = Point.CreateSkew(centre, -this.angleX, -this.angleY); |
|||
|
|||
// Since we are not working in parallel we use full height and width
|
|||
// of the first pass image.
|
|||
Parallel.For( |
|||
0, |
|||
height, |
|||
y => |
|||
{ |
|||
for (int x = startX; x < endX; x++) |
|||
{ |
|||
// Skew at the centre point
|
|||
Point skewed = Point.Skew(new Point(x, y), skew); |
|||
if (this.firstPass.Bounds.Contains(skewed.X, skewed.Y)) |
|||
{ |
|||
target[x, y] = this.firstPass[skewed.X, skewed.Y]; |
|||
} |
|||
} |
|||
|
|||
this.OnRowProcessed(); |
|||
}); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) |
|||
{ |
|||
// Cleanup.
|
|||
this.firstPass.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,192 +1,134 @@ |
|||
// <copyright file="Resize.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// </copyright>-------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessorCore.Samplers |
|||
namespace ImageProcessorCore |
|||
{ |
|||
using System.Threading.Tasks; |
|||
using Processors; |
|||
|
|||
/// <summary>
|
|||
/// Provides methods that allow the resizing of images using various algorithms.
|
|||
/// Extension methods for the <see cref="Image"/> type.
|
|||
/// </summary>
|
|||
public class Resize : Resampler |
|||
public static partial class ImageExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// The image used for storing the first pass pixels.
|
|||
/// Resizes an image in accordance with the given <see cref="ResizeOptions"/>.
|
|||
/// </summary>
|
|||
private Image firstPass; |
|||
/// <param name="source">The image to resize.</param>
|
|||
/// <param name="options">The resize options.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
/// <remarks>Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image</remarks>
|
|||
public static Image Resize(this Image source, ResizeOptions options, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
// Ensure size is populated across both dimensions.
|
|||
if (options.Size.Width == 0 && options.Size.Height > 0) |
|||
{ |
|||
options.Size = new Size(source.Width * options.Size.Height / source.Height, options.Size.Height); |
|||
} |
|||
|
|||
if (options.Size.Height == 0 && options.Size.Width > 0) |
|||
{ |
|||
options.Size = new Size(options.Size.Width, source.Height * options.Size.Width / source.Width); |
|||
} |
|||
|
|||
Rectangle targetRectangle = ResizeHelper.CalculateTargetLocationAndBounds(source, options); |
|||
|
|||
return Resize(source, options.Size.Width, options.Size.Height, options.Sampler, source.Bounds, targetRectangle, options.Compand, progressHandler); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Resize"/> class.
|
|||
/// Resizes an image to the given width and height.
|
|||
/// </summary>
|
|||
/// <param name="sampler">
|
|||
/// The sampler to perform the resize operation.
|
|||
/// </param>
|
|||
public Resize(IResampler sampler) |
|||
: base(sampler) |
|||
/// <param name="source">The image to resize.</param>
|
|||
/// <param name="width">The target image width.</param>
|
|||
/// <param name="height">The target image height.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
|
|||
public static Image Resize(this Image source, int width, int height, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
return Resize(source, width, height, new BicubicResampler(), false, progressHandler); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int Parallelism { get; set; } = 1; |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) |
|||
/// <summary>
|
|||
/// Resizes an image to the given width and height.
|
|||
/// </summary>
|
|||
/// <param name="source">The image to resize.</param>
|
|||
/// <param name="width">The target image width.</param>
|
|||
/// <param name="height">The target image height.</param>
|
|||
/// <param name="compand">Whether to compress and expand the image color-space to gamma correct the image during processing.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
|
|||
public static Image Resize(this Image source, int width, int height, bool compand, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
if (!(this.Sampler is NearestNeighborResampler)) |
|||
{ |
|||
this.HorizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width); |
|||
this.VerticalWeights = this.PrecomputeWeights(targetRectangle.Height, sourceRectangle.Height); |
|||
} |
|||
return Resize(source, width, height, new BicubicResampler(), compand, progressHandler); |
|||
} |
|||
|
|||
this.firstPass = new Image(target.Width, source.Height); |
|||
/// <summary>
|
|||
/// Resizes an image to the given width and height with the given sampler.
|
|||
/// </summary>
|
|||
/// <param name="source">The image to resize.</param>
|
|||
/// <param name="width">The target image width.</param>
|
|||
/// <param name="height">The target image height.</param>
|
|||
/// <param name="sampler">The <see cref="IResampler"/> to perform the resampling.</param>
|
|||
/// <param name="compand">Whether to compress and expand the image color-space to gamma correct the image during processing.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
|
|||
public static Image Resize(this Image source, int width, int height, IResampler sampler, bool compand, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
return Resize(source, width, height, sampler, source.Bounds, new Rectangle(0, 0, width, height), compand, progressHandler); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) |
|||
/// <summary>
|
|||
/// Resizes an image to the given width and height with the given sampler and
|
|||
/// source rectangle.
|
|||
/// </summary>
|
|||
/// <param name="source">The image to resize.</param>
|
|||
/// <param name="width">The target image width.</param>
|
|||
/// <param name="height">The target image height.</param>
|
|||
/// <param name="sampler">The <see cref="IResampler"/> to perform the resampling.</param>
|
|||
/// <param name="sourceRectangle">
|
|||
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
|
|||
/// </param>
|
|||
/// <param name="targetRectangle">
|
|||
/// The <see cref="Rectangle"/> structure that specifies the portion of the target image object to draw to.
|
|||
/// </param>
|
|||
/// <param name="compand">Whether to compress and expand the image color-space to gamma correct the image during processing.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
/// <remarks>Passing zero for one of height or width will automatically preserve the aspect ratio of the original image</remarks>
|
|||
public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand = false, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
// Jump out, we'll deal with that later.
|
|||
if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) |
|||
if (width == 0 && height > 0) |
|||
{ |
|||
return; |
|||
width = source.Width * height / source.Height; |
|||
targetRectangle.Width = width; |
|||
} |
|||
|
|||
int width = target.Width; |
|||
int height = target.Height; |
|||
int sourceHeight = sourceRectangle.Height; |
|||
int targetX = target.Bounds.X; |
|||
int targetY = target.Bounds.Y; |
|||
int targetRight = target.Bounds.Right; |
|||
int targetBottom = target.Bounds.Bottom; |
|||
int startX = targetRectangle.X; |
|||
int endX = targetRectangle.Right; |
|||
bool compand = this.Compand; |
|||
|
|||
if (this.Sampler is NearestNeighborResampler) |
|||
if (height == 0 && width > 0) |
|||
{ |
|||
// Scaling factors
|
|||
float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width; |
|||
float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height; |
|||
|
|||
Parallel.For( |
|||
startY, |
|||
endY, |
|||
y => |
|||
{ |
|||
if (targetY <= y && y < targetBottom) |
|||
{ |
|||
// Y coordinates of source points
|
|||
int originY = (int)((y - startY) * heightFactor); |
|||
|
|||
for (int x = startX; x < endX; x++) |
|||
{ |
|||
if (targetX <= x && x < targetRight) |
|||
{ |
|||
// X coordinates of source points
|
|||
int originX = (int)((x - startX) * widthFactor); |
|||
|
|||
target[x, y] = source[originX, originY]; |
|||
} |
|||
} |
|||
|
|||
this.OnRowProcessed(); |
|||
} |
|||
}); |
|||
|
|||
// Break out now.
|
|||
return; |
|||
height = source.Height * width / source.Width; |
|||
targetRectangle.Height = height; |
|||
} |
|||
|
|||
// Interpolate the image using the calculated weights.
|
|||
// A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm
|
|||
// First process the columns. Since we are not using multiple threads startY and endY
|
|||
// are the upper and lower bounds of the source rectangle.
|
|||
Parallel.For( |
|||
0, |
|||
sourceHeight, |
|||
y => |
|||
{ |
|||
for (int x = startX; x < endX; x++) |
|||
{ |
|||
if (x >= 0 && x < width) |
|||
{ |
|||
// Ensure offsets are normalised for cropping and padding.
|
|||
int offsetX = x - startX; |
|||
float sum = this.HorizontalWeights[offsetX].Sum; |
|||
Weight[] horizontalValues = this.HorizontalWeights[offsetX].Values; |
|||
|
|||
// Destination color components
|
|||
Color destination = new Color(); |
|||
|
|||
for (int i = 0; i < sum; i++) |
|||
{ |
|||
Weight xw = horizontalValues[i]; |
|||
int originX = xw.Index; |
|||
Color sourceColor = compand ? Color.Expand(source[originX, y]) : source[originX, y]; |
|||
destination += sourceColor * xw.Value; |
|||
} |
|||
|
|||
if (compand) |
|||
{ |
|||
destination = Color.Compress(destination); |
|||
} |
|||
|
|||
this.firstPass[x, y] = destination; |
|||
} |
|||
} |
|||
}); |
|||
Guard.MustBeGreaterThan(width, 0, nameof(width)); |
|||
Guard.MustBeGreaterThan(height, 0, nameof(height)); |
|||
|
|||
// Now process the rows.
|
|||
Parallel.For( |
|||
startY, |
|||
endY, |
|||
y => |
|||
{ |
|||
if (y >= 0 && y < height) |
|||
{ |
|||
// Ensure offsets are normalised for cropping and padding.
|
|||
int offsetY = y - startY; |
|||
float sum = this.VerticalWeights[offsetY].Sum; |
|||
Weight[] verticalValues = this.VerticalWeights[offsetY].Values; |
|||
Resize processor = new Resize(sampler) { Compand = compand }; |
|||
processor.OnProgress += progressHandler; |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
// Destination color components
|
|||
Color destination = new Color(); |
|||
|
|||
for (int i = 0; i < sum; i++) |
|||
{ |
|||
Weight yw = verticalValues[i]; |
|||
int originY = yw.Index; |
|||
Color sourceColor = compand ? Color.Expand(this.firstPass[x, originY]) : this.firstPass[x, originY]; |
|||
destination += sourceColor * yw.Value; |
|||
} |
|||
|
|||
if (compand) |
|||
{ |
|||
destination = Color.Compress(destination); |
|||
} |
|||
|
|||
target[x, y] = destination; |
|||
} |
|||
} |
|||
|
|||
this.OnRowProcessed(); |
|||
}); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) |
|||
{ |
|||
// Copy the pixels over.
|
|||
if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) |
|||
try |
|||
{ |
|||
target.ClonePixels(target.Width, target.Height, source.Pixels); |
|||
return source.Process(width, height, sourceRectangle, targetRectangle, processor); |
|||
} |
|||
finally |
|||
{ |
|||
processor.OnProgress -= progressHandler; |
|||
} |
|||
|
|||
// Clean up
|
|||
this.firstPass?.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -1,133 +1,51 @@ |
|||
// <copyright file="Rotate.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// </copyright>-------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessorCore.Samplers |
|||
namespace ImageProcessorCore |
|||
{ |
|||
using System.Numerics; |
|||
using System.Threading.Tasks; |
|||
using Processors; |
|||
|
|||
/// <summary>
|
|||
/// Provides methods that allow the rotating of images.
|
|||
/// Extension methods for the <see cref="Image"/> type.
|
|||
/// </summary>
|
|||
public class Rotate : ImageSampler |
|||
public static partial class ImageExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// The image used for storing the first pass pixels.
|
|||
/// Rotates an image by the given angle in degrees, expanding the image to fit the rotated result.
|
|||
/// </summary>
|
|||
private Image firstPass; |
|||
|
|||
/// <summary>
|
|||
/// The angle of rotation in degrees.
|
|||
/// </summary>
|
|||
private float angle; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int Parallelism { get; set; } = 1; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the angle of rotation in degrees.
|
|||
/// </summary>
|
|||
public float Angle |
|||
/// <param name="source">The image to rotate.</param>
|
|||
/// <param name="degrees">The angle in degrees to perform the rotation.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
public static Image Rotate(this Image source, float degrees, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
get |
|||
{ |
|||
return this.angle; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
if (value > 360) |
|||
{ |
|||
value -= 360; |
|||
} |
|||
|
|||
if (value < 0) |
|||
{ |
|||
value += 360; |
|||
} |
|||
|
|||
this.angle = value; |
|||
} |
|||
return Rotate(source, degrees, Point.Empty, true, progressHandler); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the center point.
|
|||
/// Rotates an image by the given angle in degrees around the given center point.
|
|||
/// </summary>
|
|||
public Point Center { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether to expand the canvas to fit the rotated image.
|
|||
/// </summary>
|
|||
public bool Expand { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) |
|||
/// <param name="source">The image to rotate.</param>
|
|||
/// <param name="degrees">The angle in degrees to perform the rotation.</param>
|
|||
/// <param name="center">The center point at which to rotate the image.</param>
|
|||
/// <param name="expand">Whether to expand the image to fit the rotated result.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
public static Image Rotate(this Image source, float degrees, Point center, bool expand, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
// If we are expanding we need to pad the bounds of the source rectangle.
|
|||
// We can use the resizer in nearest neighbor mode to do this fairly quickly.
|
|||
if (this.Expand) |
|||
{ |
|||
// First find out how big the target rectangle should be.
|
|||
Point centre = this.Center == Point.Empty ? Rectangle.Center(sourceRectangle) : this.Center; |
|||
Matrix3x2 rotation = Point.CreateRotation(centre, -this.angle); |
|||
Rectangle rectangle = ImageMaths.GetBoundingRectangle(sourceRectangle, rotation); |
|||
ResizeOptions options = new ResizeOptions |
|||
{ |
|||
Size = new Size(rectangle.Width, rectangle.Height), |
|||
Mode = ResizeMode.BoxPad |
|||
}; |
|||
Rotate processor = new Rotate { Angle = degrees, Center = center, Expand = expand }; |
|||
processor.OnProgress += progressHandler; |
|||
|
|||
// Get the padded bounds and resize the image.
|
|||
Rectangle bounds = ResizeHelper.CalculateTargetLocationAndBounds(source, options); |
|||
this.firstPass = new Image(rectangle.Width, rectangle.Height); |
|||
target.SetPixels(rectangle.Width, rectangle.Height, new float[rectangle.Width * rectangle.Height * 4]); |
|||
new Resize(new NearestNeighborResampler()).Apply(this.firstPass, source, rectangle.Width, rectangle.Height, bounds, sourceRectangle); |
|||
try |
|||
{ |
|||
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor); |
|||
} |
|||
else |
|||
finally |
|||
{ |
|||
// Just clone the pixels across.
|
|||
this.firstPass = new Image(source.Width, source.Height); |
|||
this.firstPass.ClonePixels(source.Width, source.Height, source.Pixels); |
|||
processor.OnProgress -= progressHandler; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) |
|||
{ |
|||
int height = this.firstPass.Height; |
|||
int startX = 0; |
|||
int endX = this.firstPass.Width; |
|||
Point centre = this.Center == Point.Empty ? Rectangle.Center(this.firstPass.Bounds) : this.Center; |
|||
Matrix3x2 rotation = Point.CreateRotation(centre, -this.angle); |
|||
|
|||
// Since we are not working in parallel we use full height and width
|
|||
// of the first pass image.
|
|||
Parallel.For( |
|||
0, |
|||
height, |
|||
y => |
|||
{ |
|||
for (int x = startX; x < endX; x++) |
|||
{ |
|||
// Rotate at the centre point
|
|||
Point rotated = Point.Rotate(new Point(x, y), rotation); |
|||
if (this.firstPass.Bounds.Contains(rotated.X, rotated.Y)) |
|||
{ |
|||
target[x, y] = this.firstPass[rotated.X, rotated.Y]; |
|||
} |
|||
} |
|||
|
|||
this.OnRowProcessed(); |
|||
}); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) |
|||
{ |
|||
// Cleanup.
|
|||
this.firstPass.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -1,164 +1,53 @@ |
|||
// <copyright file="Skew.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// </copyright>-------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessorCore.Samplers |
|||
namespace ImageProcessorCore |
|||
{ |
|||
using System.Numerics; |
|||
using System.Threading.Tasks; |
|||
using Processors; |
|||
|
|||
/// <summary>
|
|||
/// Provides methods that allow the skewing of images.
|
|||
/// Extension methods for the <see cref="Image"/> type.
|
|||
/// </summary>
|
|||
public class Skew : ImageSampler |
|||
public static partial class ImageExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// The image used for storing the first pass pixels.
|
|||
/// Skews an image by the given angles in degrees, expanding the image to fit the skewed result.
|
|||
/// </summary>
|
|||
private Image firstPass; |
|||
|
|||
/// <summary>
|
|||
/// The angle of rotation along the x-axis.
|
|||
/// </summary>
|
|||
private float angleX; |
|||
|
|||
/// <summary>
|
|||
/// The angle of rotation along the y-axis.
|
|||
/// </summary>
|
|||
private float angleY; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int Parallelism { get; set; } = 1; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the angle of rotation along the x-axis in degrees.
|
|||
/// </summary>
|
|||
public float AngleX |
|||
/// <param name="source">The image to skew.</param>
|
|||
/// <param name="degreesX">The angle in degrees to perform the rotation along the x-axis.</param>
|
|||
/// <param name="degreesY">The angle in degrees to perform the rotation along the y-axis.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
public static Image Skew(this Image source, float degreesX, float degreesY, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
get |
|||
{ |
|||
return this.angleX; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
if (value > 360) |
|||
{ |
|||
value -= 360; |
|||
} |
|||
|
|||
if (value < 0) |
|||
{ |
|||
value += 360; |
|||
} |
|||
|
|||
this.angleX = value; |
|||
} |
|||
return Skew(source, degreesX, degreesY, Point.Empty, true, progressHandler); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the angle of rotation along the y-axis in degrees.
|
|||
/// Skews an image by the given angles in degrees around the given center point.
|
|||
/// </summary>
|
|||
public float AngleY |
|||
/// <param name="source">The image to skew.</param>
|
|||
/// <param name="degreesX">The angle in degrees to perform the rotation along the x-axis.</param>
|
|||
/// <param name="degreesY">The angle in degrees to perform the rotation along the y-axis.</param>
|
|||
/// <param name="center">The center point at which to skew the image.</param>
|
|||
/// <param name="expand">Whether to expand the image to fit the skewed result.</param>
|
|||
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
|||
/// <returns>The <see cref="Image"/></returns>
|
|||
public static Image Skew(this Image source, float degreesX, float degreesY, Point center, bool expand, ProgressEventHandler progressHandler = null) |
|||
{ |
|||
get |
|||
{ |
|||
return this.angleY; |
|||
} |
|||
Skew processor = new Skew { AngleX = degreesX, AngleY = degreesY, Center = center, Expand = expand }; |
|||
processor.OnProgress += progressHandler; |
|||
|
|||
set |
|||
try |
|||
{ |
|||
if (value > 360) |
|||
{ |
|||
value -= 360; |
|||
} |
|||
|
|||
if (value < 0) |
|||
{ |
|||
value += 360; |
|||
} |
|||
|
|||
this.angleY = value; |
|||
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the center point.
|
|||
/// </summary>
|
|||
public Point Center { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether to expand the canvas to fit the skewed image.
|
|||
/// </summary>
|
|||
public bool Expand { get; set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) |
|||
{ |
|||
// If we are expanding we need to pad the bounds of the source rectangle.
|
|||
// We can use the resizer in nearest neighbor mode to do this fairly quickly.
|
|||
if (this.Expand) |
|||
{ |
|||
// First find out how big the target rectangle should be.
|
|||
Point centre = this.Center == Point.Empty ? Rectangle.Center(sourceRectangle) : this.Center; |
|||
Matrix3x2 skew = Point.CreateSkew(centre, -this.angleX, -this.angleY); |
|||
Rectangle rectangle = ImageMaths.GetBoundingRectangle(sourceRectangle, skew); |
|||
ResizeOptions options = new ResizeOptions |
|||
{ |
|||
Size = new Size(rectangle.Width, rectangle.Height), |
|||
Mode = ResizeMode.BoxPad |
|||
}; |
|||
|
|||
// Get the padded bounds and resize the image.
|
|||
Rectangle bounds = ResizeHelper.CalculateTargetLocationAndBounds(source, options); |
|||
this.firstPass = new Image(rectangle.Width, rectangle.Height); |
|||
target.SetPixels(rectangle.Width, rectangle.Height, new float[rectangle.Width * rectangle.Height * 4]); |
|||
new Resize(new NearestNeighborResampler()).Apply(this.firstPass, source, rectangle.Width, rectangle.Height, bounds, sourceRectangle); |
|||
} |
|||
else |
|||
finally |
|||
{ |
|||
// Just clone the pixels across.
|
|||
this.firstPass = new Image(source.Width, source.Height); |
|||
this.firstPass.ClonePixels(source.Width, source.Height, source.Pixels); |
|||
processor.OnProgress -= progressHandler; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) |
|||
{ |
|||
int height = this.firstPass.Height; |
|||
int startX = 0; |
|||
int endX = this.firstPass.Width; |
|||
Point centre = this.Center == Point.Empty ? Rectangle.Center(this.firstPass.Bounds) : this.Center; |
|||
Matrix3x2 skew = Point.CreateSkew(centre, -this.angleX, -this.angleY); |
|||
|
|||
// Since we are not working in parallel we use full height and width
|
|||
// of the first pass image.
|
|||
Parallel.For( |
|||
0, |
|||
height, |
|||
y => |
|||
{ |
|||
for (int x = startX; x < endX; x++) |
|||
{ |
|||
// Skew at the centre point
|
|||
Point skewed = Point.Skew(new Point(x, y), skew); |
|||
if (this.firstPass.Bounds.Contains(skewed.X, skewed.Y)) |
|||
{ |
|||
target[x, y] = this.firstPass[skewed.X, skewed.Y]; |
|||
} |
|||
} |
|||
|
|||
this.OnRowProcessed(); |
|||
}); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) |
|||
{ |
|||
// Cleanup.
|
|||
this.firstPass.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
Loading…
Reference in new issue