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 file="Crop.cs" company="James Jackson-South">
|
||||
// Copyright (c) James Jackson-South and contributors.
|
// Copyright (c) James Jackson-South and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
// </copyright>
|
// </copyright>-------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
namespace ImageProcessorCore.Samplers |
namespace ImageProcessorCore |
||||
{ |
{ |
||||
using System.Threading.Tasks; |
using Processors; |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Provides methods to allow the cropping of an image.
|
/// Extension methods for the <see cref="Image"/> type.
|
||||
/// </summary>
|
/// </summary>
|
||||
public class Crop : ImageSampler |
public static partial class ImageExtensions |
||||
{ |
{ |
||||
/// <inheritdoc/>
|
/// <summary>
|
||||
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) |
/// 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; |
return Crop(source, width, height, source.Bounds, progressHandler); |
||||
int targetBottom = targetRectangle.Bottom; |
} |
||||
int startX = targetRectangle.X; |
|
||||
int endX = targetRectangle.Right; |
/// <summary>
|
||||
int sourceX = sourceRectangle.X; |
/// Crops an image to the given width and height with the given source rectangle.
|
||||
int sourceY = sourceRectangle.Y; |
/// <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( |
if (sourceRectangle.Width < width || sourceRectangle.Height < height) |
||||
startY, |
|
||||
endY, |
|
||||
y => |
|
||||
{ |
{ |
||||
if (y >= targetY && y < targetBottom) |
// If the source rectangle is smaller than the target perform a
|
||||
{ |
// cropped zoom.
|
||||
for (int x = startX; x < endX; x++) |
source = source.Resize(sourceRectangle.Width, sourceRectangle.Height); |
||||
{ |
} |
||||
target[x, y] = source[x + sourceX, y + sourceY]; |
|
||||
} |
|
||||
|
|
||||
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 file="Resize.cs" company="James Jackson-South">
|
||||
// Copyright (c) James Jackson-South and contributors.
|
// Copyright (c) James Jackson-South and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
// </copyright>
|
// </copyright>-------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
namespace ImageProcessorCore.Samplers |
namespace ImageProcessorCore |
||||
{ |
{ |
||||
using System.Threading.Tasks; |
using Processors; |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Provides methods that allow the resizing of images using various algorithms.
|
/// Extension methods for the <see cref="Image"/> type.
|
||||
/// </summary>
|
/// </summary>
|
||||
public class Resize : Resampler |
public static partial class ImageExtensions |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// The image used for storing the first pass pixels.
|
/// Resizes an image in accordance with the given <see cref="ResizeOptions"/>.
|
||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Resize"/> class.
|
/// Resizes an image to the given width and height.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <param name="sampler">
|
/// <param name="source">The image to resize.</param>
|
||||
/// The sampler to perform the resize operation.
|
/// <param name="width">The target image width.</param>
|
||||
/// </param>
|
/// <param name="height">The target image height.</param>
|
||||
public Resize(IResampler sampler) |
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
||||
: base(sampler) |
/// <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/>
|
/// <summary>
|
||||
public override int Parallelism { get; set; } = 1; |
/// Resizes an image to the given width and height.
|
||||
|
/// </summary>
|
||||
/// <inheritdoc/>
|
/// <param name="source">The image to resize.</param>
|
||||
protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) |
/// <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)) |
return Resize(source, width, height, new BicubicResampler(), compand, progressHandler); |
||||
{ |
} |
||||
this.HorizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width); |
|
||||
this.VerticalWeights = this.PrecomputeWeights(targetRectangle.Height, sourceRectangle.Height); |
|
||||
} |
|
||||
|
|
||||
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/>
|
/// <summary>
|
||||
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) |
/// 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 (width == 0 && height > 0) |
||||
if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) |
|
||||
{ |
{ |
||||
return; |
width = source.Width * height / source.Height; |
||||
|
targetRectangle.Width = width; |
||||
} |
} |
||||
|
|
||||
int width = target.Width; |
if (height == 0 && width > 0) |
||||
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
|
height = source.Height * width / source.Width; |
||||
float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width; |
targetRectangle.Height = height; |
||||
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.
|
Guard.MustBeGreaterThan(width, 0, nameof(width)); |
||||
// A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm
|
Guard.MustBeGreaterThan(height, 0, nameof(height)); |
||||
// 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.
|
Resize processor = new Resize(sampler) { Compand = compand }; |
||||
Parallel.For( |
processor.OnProgress += progressHandler; |
||||
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++) |
try |
||||
{ |
|
||||
// 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); |
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 file="Rotate.cs" company="James Jackson-South">
|
||||
// Copyright (c) James Jackson-South and contributors.
|
// Copyright (c) James Jackson-South and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
// </copyright>
|
// </copyright>-------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
namespace ImageProcessorCore.Samplers |
namespace ImageProcessorCore |
||||
{ |
{ |
||||
using System.Numerics; |
using Processors; |
||||
using System.Threading.Tasks; |
|
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Provides methods that allow the rotating of images.
|
/// Extension methods for the <see cref="Image"/> type.
|
||||
/// </summary>
|
/// </summary>
|
||||
public class Rotate : ImageSampler |
public static partial class ImageExtensions |
||||
{ |
{ |
||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||
private Image firstPass; |
/// <param name="source">The image to rotate.</param>
|
||||
|
/// <param name="degrees">The angle in degrees to perform the rotation.</param>
|
||||
/// <summary>
|
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
||||
/// The angle of rotation in degrees.
|
/// <returns>The <see cref="Image"/></returns>
|
||||
/// </summary>
|
public static Image Rotate(this Image source, float degrees, ProgressEventHandler progressHandler = null) |
||||
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 Rotate(source, degrees, Point.Empty, true, progressHandler); |
||||
{ |
|
||||
return this.angle; |
|
||||
} |
|
||||
|
|
||||
set |
|
||||
{ |
|
||||
if (value > 360) |
|
||||
{ |
|
||||
value -= 360; |
|
||||
} |
|
||||
|
|
||||
if (value < 0) |
|
||||
{ |
|
||||
value += 360; |
|
||||
} |
|
||||
|
|
||||
this.angle = value; |
|
||||
} |
|
||||
} |
} |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Gets or sets the center point.
|
/// Rotates an image by the given angle in degrees around the given center point.
|
||||
/// </summary>
|
/// </summary>
|
||||
public Point Center { get; set; } |
/// <param name="source">The image to rotate.</param>
|
||||
|
/// <param name="degrees">The angle in degrees to perform the rotation.</param>
|
||||
/// <summary>
|
/// <param name="center">The center point at which to rotate the image.</param>
|
||||
/// Gets or sets a value indicating whether to expand the canvas to fit the rotated image.
|
/// <param name="expand">Whether to expand the image to fit the rotated result.</param>
|
||||
/// </summary>
|
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
||||
public bool Expand { get; set; } |
/// <returns>The <see cref="Image"/></returns>
|
||||
|
public static Image Rotate(this Image source, float degrees, Point center, bool expand, ProgressEventHandler progressHandler = null) |
||||
/// <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.
|
Rotate processor = new Rotate { Angle = degrees, Center = center, Expand = expand }; |
||||
// We can use the resizer in nearest neighbor mode to do this fairly quickly.
|
processor.OnProgress += progressHandler; |
||||
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.
|
try |
||||
Rectangle bounds = ResizeHelper.CalculateTargetLocationAndBounds(source, options); |
{ |
||||
this.firstPass = new Image(rectangle.Width, rectangle.Height); |
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor); |
||||
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.
|
processor.OnProgress -= progressHandler; |
||||
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(); |
|
||||
} |
|
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,164 +1,53 @@ |
|||||
// <copyright file="Skew.cs" company="James Jackson-South">
|
// <copyright file="Skew.cs" company="James Jackson-South">
|
||||
// Copyright (c) James Jackson-South and contributors.
|
// Copyright (c) James Jackson-South and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
// </copyright>
|
// </copyright>-------------------------------------------------------------------------------------------------------------------
|
||||
|
|
||||
namespace ImageProcessorCore.Samplers |
namespace ImageProcessorCore |
||||
{ |
{ |
||||
using System.Numerics; |
using Processors; |
||||
using System.Threading.Tasks; |
|
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Provides methods that allow the skewing of images.
|
/// Extension methods for the <see cref="Image"/> type.
|
||||
/// </summary>
|
/// </summary>
|
||||
public class Skew : ImageSampler |
public static partial class ImageExtensions |
||||
{ |
{ |
||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||
private Image firstPass; |
/// <param name="source">The image to skew.</param>
|
||||
|
/// <param name="degreesX">The angle in degrees to perform the rotation along the x-axis.</param>
|
||||
/// <summary>
|
/// <param name="degreesY">The angle in degrees to perform the rotation along the y-axis.</param>
|
||||
/// The angle of rotation along the x-axis.
|
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
|
||||
/// </summary>
|
/// <returns>The <see cref="Image"/></returns>
|
||||
private float angleX; |
public static Image Skew(this Image source, float degreesX, float degreesY, ProgressEventHandler progressHandler = null) |
||||
|
|
||||
/// <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 Skew(source, degreesX, degreesY, Point.Empty, true, progressHandler); |
||||
{ |
|
||||
return this.angleX; |
|
||||
} |
|
||||
|
|
||||
set |
|
||||
{ |
|
||||
if (value > 360) |
|
||||
{ |
|
||||
value -= 360; |
|
||||
} |
|
||||
|
|
||||
if (value < 0) |
|
||||
{ |
|
||||
value += 360; |
|
||||
} |
|
||||
|
|
||||
this.angleX = value; |
|
||||
} |
|
||||
} |
} |
||||
|
|
||||
/// <summary>
|
/// <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>
|
/// </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 |
Skew processor = new Skew { AngleX = degreesX, AngleY = degreesY, Center = center, Expand = expand }; |
||||
{ |
processor.OnProgress += progressHandler; |
||||
return this.angleY; |
|
||||
} |
|
||||
|
|
||||
set |
try |
||||
{ |
{ |
||||
if (value > 360) |
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor); |
||||
{ |
|
||||
value -= 360; |
|
||||
} |
|
||||
|
|
||||
if (value < 0) |
|
||||
{ |
|
||||
value += 360; |
|
||||
} |
|
||||
|
|
||||
this.angleY = value; |
|
||||
} |
} |
||||
} |
finally |
||||
|
|
||||
/// <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.
|
processor.OnProgress -= progressHandler; |
||||
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(); |
|
||||
} |
|
||||
} |
} |
||||
} |
} |
||||
|
|||||
Loading…
Reference in new issue