diff --git a/src/ImageProcessorCore/ImageExtensions.cs b/src/ImageProcessorCore/ImageExtensions.cs
index f34bab750..f194b54c3 100644
--- a/src/ImageProcessorCore/ImageExtensions.cs
+++ b/src/ImageProcessorCore/ImageExtensions.cs
@@ -9,13 +9,12 @@ namespace ImageProcessorCore
using System.IO;
using Formats;
-
- using ImageProcessorCore.Samplers;
+ using Processors;
///
- /// Exstension methods for the type.
+ /// Extension methods for the type.
///
- public static class ImageExtensions
+ public static partial class ImageExtensions
{
///
/// Saves the image to the given stream with the bmp format.
@@ -34,7 +33,7 @@ namespace ImageProcessorCore
/// Anything equal to 256 and below will cause the encoder to save the image in an indexed format.
///
/// Thrown if the stream is null.
- public static void SaveAsPng(this ImageBase source, Stream stream, int quality = int.MaxValue) => new PngEncoder { Quality = quality }.Encode(source, stream);
+ public static void SaveAsPng(this ImageBase source, Stream stream, int quality = Int32.MaxValue) => new PngEncoder { Quality = quality }.Encode(source, stream);
///
/// Saves the image to the given stream with the jpeg format.
diff --git a/src/ImageProcessorCore/Samplers/Crop.cs b/src/ImageProcessorCore/Samplers/Crop.cs
index 3ee47149b..6347d8e98 100644
--- a/src/ImageProcessorCore/Samplers/Crop.cs
+++ b/src/ImageProcessorCore/Samplers/Crop.cs
@@ -1,42 +1,68 @@
//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
-//
+// -------------------------------------------------------------------------------------------------------------------
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
- using System.Threading.Tasks;
+ using Processors;
///
- /// Provides methods to allow the cropping of an image.
+ /// Extension methods for the type.
///
- public class Crop : ImageSampler
+ public static partial class ImageExtensions
{
- ///
- 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.
+ ///
+ /// The image to resize.
+ /// The target image width.
+ /// The target image height.
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ 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);
+ }
+
+ ///
+ /// Crops an image to the given width and height with the given source rectangle.
+ ///
+ /// If the source rectangle is smaller than the target dimensions then the
+ /// area within the source is resized performing a zoomed crop.
+ ///
+ ///
+ /// The image to crop.
+ /// The target image width.
+ /// The target image height.
+ ///
+ /// The structure that specifies the portion of the image object to draw.
+ ///
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ 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;
+ }
}
}
}
diff --git a/src/ImageProcessorCore/Samplers/EntropyCrop.cs b/src/ImageProcessorCore/Samplers/EntropyCrop.cs
index 05332123e..9e96d5b3e 100644
--- a/src/ImageProcessorCore/Samplers/EntropyCrop.cs
+++ b/src/ImageProcessorCore/Samplers/EntropyCrop.cs
@@ -1,102 +1,36 @@
//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
-//
+// -------------------------------------------------------------------------------------------------------------------
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
- using System;
- using System.Threading.Tasks;
-
- using ImageProcessorCore.Filters;
+ using Processors;
///
- /// Provides methods to allow the cropping of an image to preserve areas of highest
- /// entropy.
+ /// Extension methods for the type.
///
- public class EntropyCrop : ImageSampler
+ public static partial class ImageExtensions
{
///
- /// The rectangle for cropping
- ///
- private Rectangle cropRectangle;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The threshold to split the image. Must be between 0 and 1.
- ///
- /// is less than 0 or is greater than 1.
- ///
- public EntropyCrop(float threshold)
- {
- Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold));
- this.Value = threshold;
- }
-
- ///
- /// Gets the threshold value.
+ /// Crops an image to the area of greatest entropy.
///
- public float Value { get; }
-
- ///
- protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
+ /// The image to crop.
+ /// The threshold for entropic density.
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ public static Image EntropyCrop(this Image source, float threshold = .5f, ProgressEventHandler progressHandler = null)
{
- 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);
+ EntropyCrop processor = new EntropyCrop(threshold);
+ processor.OnProgress += progressHandler;
- // 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;
- }
- }
-
- ///
- 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)
+ try
{
- return;
+ return source.Process(source.Width, source.Height, source.Bounds, Rectangle.Empty, processor);
}
-
- 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();
- });
- }
-
- ///
- protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
- {
- // Copy the pixels over.
- if (source.Bounds == target.Bounds)
+ finally
{
- target.ClonePixels(target.Width, target.Height, source.Pixels);
+ processor.OnProgress -= progressHandler;
}
}
}
diff --git a/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs b/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs
deleted file mode 100644
index 4fe463cc1..000000000
--- a/src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs
+++ /dev/null
@@ -1,324 +0,0 @@
-//
-// Copyright (c) James Jackson-South and contributors.
-// Licensed under the Apache License, Version 2.0.
-//
-
-namespace ImageProcessorCore.Samplers
-{
- ///
- /// Extensions methods for to apply samplers to the image.
- ///
- public static class ImageSamplerExtensions
- {
- ///
- /// Crops an image to the given width and height.
- ///
- /// The image to resize.
- /// The target image width.
- /// The target image height.
- /// A delegate which is called as progress is made processing the image.
- /// The
- public static Image Crop(this Image source, int width, int height, ProgressEventHandler progressHandler = null)
- {
- return Crop(source, width, height, source.Bounds, progressHandler);
- }
-
- ///
- /// Crops an image to the given width and height with the given source rectangle.
- ///
- /// If the source rectangle is smaller than the target dimensions then the
- /// area within the source is resized performing a zoomed crop.
- ///
- ///
- /// The image to crop.
- /// The target image width.
- /// The target image height.
- ///
- /// The structure that specifies the portion of the image object to draw.
- ///
- /// A delegate which is called as progress is made processing the image.
- /// The
- 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;
- }
- }
-
- ///
- /// Crops an image to the area of greatest entropy.
- ///
- /// The image to crop.
- /// The threshold for entropic density.
- /// A delegate which is called as progress is made processing the image.
- /// The
- 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;
- }
- }
-
- ///
- /// Evenly pads an image to fit the new dimensions.
- ///
- /// The source image to pad.
- /// The new width.
- /// The new height.
- /// A delegate which is called as progress is made processing the image.
- /// The .
- 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);
- }
-
- ///
- /// Resizes an image in accordance with the given .
- ///
- /// The image to resize.
- /// The resize options.
- /// A delegate which is called as progress is made processing the image.
- /// The
- /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image
- 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);
- }
-
- ///
- /// Resizes an image to the given width and height.
- ///
- /// The image to resize.
- /// The target image width.
- /// The target image height.
- /// A delegate which is called as progress is made processing the image.
- /// The
- /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image
- public static Image Resize(this Image source, int width, int height, ProgressEventHandler progressHandler = null)
- {
- return Resize(source, width, height, new BicubicResampler(), false, progressHandler);
- }
-
- ///
- /// Resizes an image to the given width and height.
- ///
- /// The image to resize.
- /// The target image width.
- /// The target image height.
- /// Whether to compress and expand the image color-space to gamma correct the image during processing.
- /// A delegate which is called as progress is made processing the image.
- /// The
- /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image
- 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);
- }
-
- ///
- /// Resizes an image to the given width and height with the given sampler.
- ///
- /// The image to resize.
- /// The target image width.
- /// The target image height.
- /// The to perform the resampling.
- /// Whether to compress and expand the image color-space to gamma correct the image during processing.
- /// A delegate which is called as progress is made processing the image.
- /// The
- /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image
- 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);
- }
-
- ///
- /// Resizes an image to the given width and height with the given sampler and
- /// source rectangle.
- ///
- /// The image to resize.
- /// The target image width.
- /// The target image height.
- /// The to perform the resampling.
- ///
- /// The structure that specifies the portion of the image object to draw.
- ///
- ///
- /// The structure that specifies the portion of the target image object to draw to.
- ///
- /// Whether to compress and expand the image color-space to gamma correct the image during processing.
- /// A delegate which is called as progress is made processing the image.
- /// The
- /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image
- 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;
- }
- }
-
- ///
- /// Rotates an image by the given angle in degrees, expanding the image to fit the rotated result.
- ///
- /// The image to rotate.
- /// The angle in degrees to perform the rotation.
- /// A delegate which is called as progress is made processing the image.
- /// The
- public static Image Rotate(this Image source, float degrees, ProgressEventHandler progressHandler = null)
- {
- return Rotate(source, degrees, Point.Empty, true, progressHandler);
- }
-
- ///
- /// Rotates an image by the given angle in degrees around the given center point.
- ///
- /// The image to rotate.
- /// The angle in degrees to perform the rotation.
- /// The center point at which to rotate the image.
- /// Whether to expand the image to fit the rotated result.
- /// A delegate which is called as progress is made processing the image.
- /// The
- 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;
- }
- }
-
- ///
- /// Rotates and flips an image by the given instructions.
- ///
- /// The image to rotate, flip, or both.
- /// The to perform the rotation.
- /// The to perform the flip.
- /// A delegate which is called as progress is made processing the image.
- /// The
- 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;
- }
- }
-
- ///
- /// Skews an image by the given angles in degrees, expanding the image to fit the skewed result.
- ///
- /// The image to skew.
- /// The angle in degrees to perform the rotation along the x-axis.
- /// The angle in degrees to perform the rotation along the y-axis.
- /// A delegate which is called as progress is made processing the image.
- /// The
- public static Image Skew(this Image source, float degreesX, float degreesY, ProgressEventHandler progressHandler = null)
- {
- return Skew(source, degreesX, degreesY, Point.Empty, true, progressHandler);
- }
-
- ///
- /// Skews an image by the given angles in degrees around the given center point.
- ///
- /// The image to skew.
- /// The angle in degrees to perform the rotation along the x-axis.
- /// The angle in degrees to perform the rotation along the y-axis.
- /// The center point at which to skew the image.
- /// Whether to expand the image to fit the skewed result.
- /// A delegate which is called as progress is made processing the image.
- /// The
- 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;
- }
- }
- }
-}
diff --git a/src/ImageProcessorCore/Samplers/AnchorPosition.cs b/src/ImageProcessorCore/Samplers/Options/AnchorPosition.cs
similarity index 97%
rename from src/ImageProcessorCore/Samplers/AnchorPosition.cs
rename to src/ImageProcessorCore/Samplers/Options/AnchorPosition.cs
index ee9e7255f..af840b292 100644
--- a/src/ImageProcessorCore/Samplers/AnchorPosition.cs
+++ b/src/ImageProcessorCore/Samplers/Options/AnchorPosition.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
///
/// Enumerated anchor positions to apply to resized images.
diff --git a/src/ImageProcessorCore/Samplers/FlipType.cs b/src/ImageProcessorCore/Samplers/Options/FlipType.cs
similarity index 89%
rename from src/ImageProcessorCore/Samplers/FlipType.cs
rename to src/ImageProcessorCore/Samplers/Options/FlipType.cs
index 066f86189..c9b21a37e 100644
--- a/src/ImageProcessorCore/Samplers/FlipType.cs
+++ b/src/ImageProcessorCore/Samplers/Options/FlipType.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
///
/// Provides enumeration over how a image should be flipped.
@@ -11,7 +11,7 @@ namespace ImageProcessorCore.Samplers
public enum FlipType
{
///
- /// Dont flip the image.
+ /// Don't flip the image.
///
None,
diff --git a/src/ImageProcessorCore/Samplers/ResizeHelper.cs b/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs
similarity index 99%
rename from src/ImageProcessorCore/Samplers/ResizeHelper.cs
rename to src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs
index 95ed97192..a80ea4777 100644
--- a/src/ImageProcessorCore/Samplers/ResizeHelper.cs
+++ b/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
using System;
using System.Linq;
diff --git a/src/ImageProcessorCore/Samplers/ResizeMode.cs b/src/ImageProcessorCore/Samplers/Options/ResizeMode.cs
similarity index 97%
rename from src/ImageProcessorCore/Samplers/ResizeMode.cs
rename to src/ImageProcessorCore/Samplers/Options/ResizeMode.cs
index 15dc0a71c..a0ce94341 100644
--- a/src/ImageProcessorCore/Samplers/ResizeMode.cs
+++ b/src/ImageProcessorCore/Samplers/Options/ResizeMode.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
///
/// Enumerated resize modes to apply to resized images.
diff --git a/src/ImageProcessorCore/Samplers/ResizeOptions.cs b/src/ImageProcessorCore/Samplers/Options/ResizeOptions.cs
similarity index 96%
rename from src/ImageProcessorCore/Samplers/ResizeOptions.cs
rename to src/ImageProcessorCore/Samplers/Options/ResizeOptions.cs
index 01923a0cf..8a5716f6b 100644
--- a/src/ImageProcessorCore/Samplers/ResizeOptions.cs
+++ b/src/ImageProcessorCore/Samplers/Options/ResizeOptions.cs
@@ -3,8 +3,9 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
+ using Processors
using System.Collections.Generic;
using System.Linq;
diff --git a/src/ImageProcessorCore/Samplers/RotateType.cs b/src/ImageProcessorCore/Samplers/Options/RotateType.cs
similarity index 95%
rename from src/ImageProcessorCore/Samplers/RotateType.cs
rename to src/ImageProcessorCore/Samplers/Options/RotateType.cs
index 7a71d1710..43644de85 100644
--- a/src/ImageProcessorCore/Samplers/RotateType.cs
+++ b/src/ImageProcessorCore/Samplers/Options/RotateType.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
///
/// Provides enumeration over how the image should be rotated.
diff --git a/src/ImageProcessorCore/Samplers/Pad.cs b/src/ImageProcessorCore/Samplers/Pad.cs
new file mode 100644
index 000000000..de973d345
--- /dev/null
+++ b/src/ImageProcessorCore/Samplers/Pad.cs
@@ -0,0 +1,35 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+// -------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessorCore
+{
+ using Processors;
+
+ ///
+ /// Extension methods for the type.
+ ///
+ public static partial class ImageExtensions
+ {
+ ///
+ /// Evenly pads an image to fit the new dimensions.
+ ///
+ /// The source image to pad.
+ /// The new width.
+ /// The new height.
+ /// A delegate which is called as progress is made processing the image.
+ /// The .
+ 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);
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Samplers/Processors/Crop.cs b/src/ImageProcessorCore/Samplers/Processors/Crop.cs
new file mode 100644
index 000000000..0fdef35fc
--- /dev/null
+++ b/src/ImageProcessorCore/Samplers/Processors/Crop.cs
@@ -0,0 +1,42 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ using System.Threading.Tasks;
+
+ ///
+ /// Provides methods to allow the cropping of an image.
+ ///
+ public class Crop : ImageSampler
+ {
+ ///
+ 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();
+ }
+ });
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Samplers/Processors/EntropyCrop.cs b/src/ImageProcessorCore/Samplers/Processors/EntropyCrop.cs
new file mode 100644
index 000000000..763433d9c
--- /dev/null
+++ b/src/ImageProcessorCore/Samplers/Processors/EntropyCrop.cs
@@ -0,0 +1,103 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ using System;
+ using System.Threading.Tasks;
+
+ using ImageProcessorCore.Filters;
+
+ ///
+ /// Provides methods to allow the cropping of an image to preserve areas of highest
+ /// entropy.
+ ///
+ public class EntropyCrop : ImageSampler
+ {
+ ///
+ /// The rectangle for cropping
+ ///
+ private Rectangle cropRectangle;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The threshold to split the image. Must be between 0 and 1.
+ ///
+ /// is less than 0 or is greater than 1.
+ ///
+ public EntropyCrop(float threshold)
+ {
+ Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold));
+ this.Value = threshold;
+ }
+
+ ///
+ /// Gets the threshold value.
+ ///
+ public float Value { get; }
+
+ ///
+ 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;
+ }
+ }
+
+ ///
+ 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();
+ });
+ }
+
+ ///
+ 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);
+ }
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Samplers/IImageSampler.cs b/src/ImageProcessorCore/Samplers/Processors/IImageSampler.cs
similarity index 93%
rename from src/ImageProcessorCore/Samplers/IImageSampler.cs
rename to src/ImageProcessorCore/Samplers/Processors/IImageSampler.cs
index ba18b9788..76a2c5a4d 100644
--- a/src/ImageProcessorCore/Samplers/IImageSampler.cs
+++ b/src/ImageProcessorCore/Samplers/Processors/IImageSampler.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore.Processors
{
///
/// Acts as a marker for generic parameters that require an image sampler.
diff --git a/src/ImageProcessorCore/Samplers/ImageSampler.cs b/src/ImageProcessorCore/Samplers/Processors/ImageSampler.cs
similarity index 93%
rename from src/ImageProcessorCore/Samplers/ImageSampler.cs
rename to src/ImageProcessorCore/Samplers/Processors/ImageSampler.cs
index 5a15f9966..cc8bfe4cc 100644
--- a/src/ImageProcessorCore/Samplers/ImageSampler.cs
+++ b/src/ImageProcessorCore/Samplers/Processors/ImageSampler.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore.Processors
{
///
/// Applies sampling methods to an image.
diff --git a/src/ImageProcessorCore/Samplers/Resampler.cs b/src/ImageProcessorCore/Samplers/Processors/Resampler.cs
similarity index 99%
rename from src/ImageProcessorCore/Samplers/Resampler.cs
rename to src/ImageProcessorCore/Samplers/Processors/Resampler.cs
index 656b45780..283d64cfa 100644
--- a/src/ImageProcessorCore/Samplers/Resampler.cs
+++ b/src/ImageProcessorCore/Samplers/Processors/Resampler.cs
@@ -2,7 +2,7 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore.Processors
{
using System;
diff --git a/src/ImageProcessorCore/Samplers/Processors/Resize.cs b/src/ImageProcessorCore/Samplers/Processors/Resize.cs
new file mode 100644
index 000000000..e8bd9bbab
--- /dev/null
+++ b/src/ImageProcessorCore/Samplers/Processors/Resize.cs
@@ -0,0 +1,192 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ using System.Threading.Tasks;
+
+ ///
+ /// Provides methods that allow the resizing of images using various algorithms.
+ ///
+ public class Resize : Resampler
+ {
+ ///
+ /// The image used for storing the first pass pixels.
+ ///
+ private Image firstPass;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The sampler to perform the resize operation.
+ ///
+ public Resize(IResampler sampler)
+ : base(sampler)
+ {
+ }
+
+ ///
+ public override int Parallelism { get; set; } = 1;
+
+ ///
+ 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);
+ }
+
+ ///
+ 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();
+ });
+ }
+
+ ///
+ 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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Samplers/Processors/Rotate.cs b/src/ImageProcessorCore/Samplers/Processors/Rotate.cs
new file mode 100644
index 000000000..e98117a17
--- /dev/null
+++ b/src/ImageProcessorCore/Samplers/Processors/Rotate.cs
@@ -0,0 +1,133 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ using System.Numerics;
+ using System.Threading.Tasks;
+
+ ///
+ /// Provides methods that allow the rotating of images.
+ ///
+ public class Rotate : ImageSampler
+ {
+ ///
+ /// The image used for storing the first pass pixels.
+ ///
+ private Image firstPass;
+
+ ///
+ /// The angle of rotation in degrees.
+ ///
+ private float angle;
+
+ ///
+ public override int Parallelism { get; set; } = 1;
+
+ ///
+ /// Gets or sets the angle of rotation in degrees.
+ ///
+ public float Angle
+ {
+ get
+ {
+ return this.angle;
+ }
+
+ set
+ {
+ if (value > 360)
+ {
+ value -= 360;
+ }
+
+ if (value < 0)
+ {
+ value += 360;
+ }
+
+ this.angle = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the center point.
+ ///
+ public Point Center { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether to expand the canvas to fit the rotated image.
+ ///
+ public bool Expand { get; set; }
+
+ ///
+ 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);
+ }
+ }
+
+ ///
+ 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();
+ });
+ }
+
+ ///
+ protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
+ {
+ // Cleanup.
+ this.firstPass.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Samplers/Processors/RotateFlip.cs b/src/ImageProcessorCore/Samplers/Processors/RotateFlip.cs
new file mode 100644
index 000000000..dfcc3e69e
--- /dev/null
+++ b/src/ImageProcessorCore/Samplers/Processors/RotateFlip.cs
@@ -0,0 +1,203 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+namespace ImageProcessorCore.Processors
+{
+ using System;
+ using System.Threading.Tasks;
+
+ ///
+ /// Provides methods that allow the rotation and flipping of an image around its center point.
+ ///
+ public class RotateFlip : ImageSampler
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The used to perform rotation.
+ /// The used to perform flipping.
+ public RotateFlip(RotateType rotateType, FlipType flipType)
+ {
+ this.RotateType = rotateType;
+ this.FlipType = flipType;
+ }
+
+ ///
+ /// Gets the used to perform flipping.
+ ///
+ public FlipType FlipType { get; }
+
+ ///
+ /// Gets the used to perform rotation.
+ ///
+ public RotateType RotateType { get; }
+
+ ///
+ public override int Parallelism { get; set; } = 1;
+
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// Rotates the image 270 degrees clockwise at the centre point.
+ ///
+ /// The target image.
+ /// The source image.
+ 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);
+ }
+
+ ///
+ /// Rotates the image 180 degrees clockwise at the centre point.
+ ///
+ /// The target image.
+ /// The source image.
+ 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();
+ });
+ }
+
+ ///
+ /// Rotates the image 90 degrees clockwise at the centre point.
+ ///
+ /// The target image.
+ /// The source image.
+ 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);
+ }
+
+ ///
+ /// Swaps the image at the X-axis, which goes horizontally through the middle
+ /// at half the height of the image.
+ ///
+ /// Target image to apply the process to.
+ 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();
+ });
+ }
+
+ ///
+ /// Swaps the image at the Y-axis, which goes vertically through the middle
+ /// at half of the width of the image.
+ ///
+ /// Target image to apply the process to.
+ 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();
+ });
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Samplers/Processors/Skew.cs b/src/ImageProcessorCore/Samplers/Processors/Skew.cs
new file mode 100644
index 000000000..fd457ece4
--- /dev/null
+++ b/src/ImageProcessorCore/Samplers/Processors/Skew.cs
@@ -0,0 +1,164 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Processors
+{
+ using System.Numerics;
+ using System.Threading.Tasks;
+
+ ///
+ /// Provides methods that allow the skewing of images.
+ ///
+ public class Skew : ImageSampler
+ {
+ ///
+ /// The image used for storing the first pass pixels.
+ ///
+ private Image firstPass;
+
+ ///
+ /// The angle of rotation along the x-axis.
+ ///
+ private float angleX;
+
+ ///
+ /// The angle of rotation along the y-axis.
+ ///
+ private float angleY;
+
+ ///
+ public override int Parallelism { get; set; } = 1;
+
+ ///
+ /// Gets or sets the angle of rotation along the x-axis in degrees.
+ ///
+ public float AngleX
+ {
+ get
+ {
+ return this.angleX;
+ }
+
+ set
+ {
+ if (value > 360)
+ {
+ value -= 360;
+ }
+
+ if (value < 0)
+ {
+ value += 360;
+ }
+
+ this.angleX = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the angle of rotation along the y-axis in degrees.
+ ///
+ public float AngleY
+ {
+ get
+ {
+ return this.angleY;
+ }
+
+ set
+ {
+ if (value > 360)
+ {
+ value -= 360;
+ }
+
+ if (value < 0)
+ {
+ value += 360;
+ }
+
+ this.angleY = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the center point.
+ ///
+ public Point Center { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether to expand the canvas to fit the skewed image.
+ ///
+ public bool Expand { get; set; }
+
+ ///
+ 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);
+ }
+ }
+
+ ///
+ 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();
+ });
+ }
+
+ ///
+ protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
+ {
+ // Cleanup.
+ this.firstPass.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Samplers/Resamplers/BicubicResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/BicubicResampler.cs
index 420806daf..8aecac7a6 100644
--- a/src/ImageProcessorCore/Samplers/Resamplers/BicubicResampler.cs
+++ b/src/ImageProcessorCore/Samplers/Resamplers/BicubicResampler.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
///
/// The function implements the bicubic kernel algorithm W(x) as described on
diff --git a/src/ImageProcessorCore/Samplers/Resamplers/BoxResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/BoxResampler.cs
index 82c405114..b1234e415 100644
--- a/src/ImageProcessorCore/Samplers/Resamplers/BoxResampler.cs
+++ b/src/ImageProcessorCore/Samplers/Resamplers/BoxResampler.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
///
/// The function implements the box algorithm. Similar to nearest neighbour when upscaling.
diff --git a/src/ImageProcessorCore/Samplers/Resamplers/CatmullRomResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/CatmullRomResampler.cs
index 804d310f9..03af3272f 100644
--- a/src/ImageProcessorCore/Samplers/Resamplers/CatmullRomResampler.cs
+++ b/src/ImageProcessorCore/Samplers/Resamplers/CatmullRomResampler.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
///
/// The function implements the Catmull-Rom algorithm.
diff --git a/src/ImageProcessorCore/Samplers/Resamplers/HermiteResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/HermiteResampler.cs
index 62001d510..6c1540a19 100644
--- a/src/ImageProcessorCore/Samplers/Resamplers/HermiteResampler.cs
+++ b/src/ImageProcessorCore/Samplers/Resamplers/HermiteResampler.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore.Processors
{
///
/// The function implements the hermite algorithm.
diff --git a/src/ImageProcessorCore/Samplers/Resamplers/IResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/IResampler.cs
index 2879c34ac..0dea58440 100644
--- a/src/ImageProcessorCore/Samplers/Resamplers/IResampler.cs
+++ b/src/ImageProcessorCore/Samplers/Resamplers/IResampler.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
///
/// Encapsulates an interpolation algorithm for resampling images.
diff --git a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos3Resampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos3Resampler.cs
index 093935018..9bc842f61 100644
--- a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos3Resampler.cs
+++ b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos3Resampler.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
///
/// The function implements the Lanczos kernel algorithm as described on
diff --git a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos5Resampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos5Resampler.cs
index 45dabda50..67b704fc4 100644
--- a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos5Resampler.cs
+++ b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos5Resampler.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
///
/// The function implements the Lanczos kernel algorithm as described on
diff --git a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos8Resampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos8Resampler.cs
index 5c58653bb..28a305c65 100644
--- a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos8Resampler.cs
+++ b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos8Resampler.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
///
/// The function implements the Lanczos kernel algorithm as described on
diff --git a/src/ImageProcessorCore/Samplers/Resamplers/MitchellNetravaliResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/MitchellNetravaliResampler.cs
index 794f10a10..f609f2645 100644
--- a/src/ImageProcessorCore/Samplers/Resamplers/MitchellNetravaliResampler.cs
+++ b/src/ImageProcessorCore/Samplers/Resamplers/MitchellNetravaliResampler.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
///
/// The function implements the mitchell algorithm as described on
diff --git a/src/ImageProcessorCore/Samplers/Resamplers/NearestNeighborResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/NearestNeighborResampler.cs
index 013527289..58b6a9d58 100644
--- a/src/ImageProcessorCore/Samplers/Resamplers/NearestNeighborResampler.cs
+++ b/src/ImageProcessorCore/Samplers/Resamplers/NearestNeighborResampler.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
///
/// The function implements the nearest neighbour algorithm. This uses an unscaled filter
diff --git a/src/ImageProcessorCore/Samplers/Resamplers/RobidouxResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/RobidouxResampler.cs
index c2cead95e..caead12d5 100644
--- a/src/ImageProcessorCore/Samplers/Resamplers/RobidouxResampler.cs
+++ b/src/ImageProcessorCore/Samplers/Resamplers/RobidouxResampler.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
///
/// The function implements the Robidoux algorithm.
diff --git a/src/ImageProcessorCore/Samplers/Resamplers/RobidouxSharpResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/RobidouxSharpResampler.cs
index b86ff86a8..633503cd1 100644
--- a/src/ImageProcessorCore/Samplers/Resamplers/RobidouxSharpResampler.cs
+++ b/src/ImageProcessorCore/Samplers/Resamplers/RobidouxSharpResampler.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
///
/// The function implements the Robidoux Sharp algorithm.
diff --git a/src/ImageProcessorCore/Samplers/Resamplers/RobidouxSoftResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/RobidouxSoftResampler.cs
index 06b6178a0..8706f492b 100644
--- a/src/ImageProcessorCore/Samplers/Resamplers/RobidouxSoftResampler.cs
+++ b/src/ImageProcessorCore/Samplers/Resamplers/RobidouxSoftResampler.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
///
/// The function implements the Robidoux Soft algorithm.
diff --git a/src/ImageProcessorCore/Samplers/Resamplers/SplineResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/SplineResampler.cs
index bf1d46842..55ef5656a 100644
--- a/src/ImageProcessorCore/Samplers/Resamplers/SplineResampler.cs
+++ b/src/ImageProcessorCore/Samplers/Resamplers/SplineResampler.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
///
/// The function implements the spline algorithm.
diff --git a/src/ImageProcessorCore/Samplers/Resamplers/TriangleResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/TriangleResampler.cs
index 2fbd110f9..cb404b736 100644
--- a/src/ImageProcessorCore/Samplers/Resamplers/TriangleResampler.cs
+++ b/src/ImageProcessorCore/Samplers/Resamplers/TriangleResampler.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
///
/// The function implements the triangle (bilinear) algorithm.
diff --git a/src/ImageProcessorCore/Samplers/Resamplers/WelchResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/WelchResampler.cs
index d62c6cc07..3ecaa6a74 100644
--- a/src/ImageProcessorCore/Samplers/Resamplers/WelchResampler.cs
+++ b/src/ImageProcessorCore/Samplers/Resamplers/WelchResampler.cs
@@ -3,7 +3,7 @@
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
///
/// The function implements the welch algorithm.
diff --git a/src/ImageProcessorCore/Samplers/Resize.cs b/src/ImageProcessorCore/Samplers/Resize.cs
index 5cbf45f35..fbf86081a 100644
--- a/src/ImageProcessorCore/Samplers/Resize.cs
+++ b/src/ImageProcessorCore/Samplers/Resize.cs
@@ -1,192 +1,134 @@
//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
-//
+// -------------------------------------------------------------------------------------------------------------------
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
- using System.Threading.Tasks;
+ using Processors;
///
- /// Provides methods that allow the resizing of images using various algorithms.
+ /// Extension methods for the type.
///
- public class Resize : Resampler
+ public static partial class ImageExtensions
{
///
- /// The image used for storing the first pass pixels.
+ /// Resizes an image in accordance with the given .
///
- private Image firstPass;
+ /// The image to resize.
+ /// The resize options.
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image
+ 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);
+ }
///
- /// Initializes a new instance of the class.
+ /// Resizes an image to the given width and height.
///
- ///
- /// The sampler to perform the resize operation.
- ///
- public Resize(IResampler sampler)
- : base(sampler)
+ /// The image to resize.
+ /// The target image width.
+ /// The target image height.
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image
+ public static Image Resize(this Image source, int width, int height, ProgressEventHandler progressHandler = null)
{
+ return Resize(source, width, height, new BicubicResampler(), false, progressHandler);
}
- ///
- public override int Parallelism { get; set; } = 1;
-
- ///
- protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
+ ///
+ /// Resizes an image to the given width and height.
+ ///
+ /// The image to resize.
+ /// The target image width.
+ /// The target image height.
+ /// Whether to compress and expand the image color-space to gamma correct the image during processing.
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image
+ 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);
+ ///
+ /// Resizes an image to the given width and height with the given sampler.
+ ///
+ /// The image to resize.
+ /// The target image width.
+ /// The target image height.
+ /// The to perform the resampling.
+ /// Whether to compress and expand the image color-space to gamma correct the image during processing.
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image
+ 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);
}
- ///
- 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.
+ ///
+ /// The image to resize.
+ /// The target image width.
+ /// The target image height.
+ /// The to perform the resampling.
+ ///
+ /// The structure that specifies the portion of the image object to draw.
+ ///
+ ///
+ /// The structure that specifies the portion of the target image object to draw to.
+ ///
+ /// Whether to compress and expand the image color-space to gamma correct the image during processing.
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image
+ 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();
- });
- }
-
- ///
- 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();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageProcessorCore/Samplers/Rotate.cs b/src/ImageProcessorCore/Samplers/Rotate.cs
index edec0f6bd..3d338230f 100644
--- a/src/ImageProcessorCore/Samplers/Rotate.cs
+++ b/src/ImageProcessorCore/Samplers/Rotate.cs
@@ -1,133 +1,51 @@
//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
-//
+// -------------------------------------------------------------------------------------------------------------------
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
- using System.Numerics;
- using System.Threading.Tasks;
+ using Processors;
///
- /// Provides methods that allow the rotating of images.
+ /// Extension methods for the type.
///
- public class Rotate : ImageSampler
+ public static partial class ImageExtensions
{
///
- /// 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.
///
- private Image firstPass;
-
- ///
- /// The angle of rotation in degrees.
- ///
- private float angle;
-
- ///
- public override int Parallelism { get; set; } = 1;
-
- ///
- /// Gets or sets the angle of rotation in degrees.
- ///
- public float Angle
+ /// The image to rotate.
+ /// The angle in degrees to perform the rotation.
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ 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);
}
///
- /// Gets or sets the center point.
+ /// Rotates an image by the given angle in degrees around the given center point.
///
- public Point Center { get; set; }
-
- ///
- /// Gets or sets a value indicating whether to expand the canvas to fit the rotated image.
- ///
- public bool Expand { get; set; }
-
- ///
- protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
+ /// The image to rotate.
+ /// The angle in degrees to perform the rotation.
+ /// The center point at which to rotate the image.
+ /// Whether to expand the image to fit the rotated result.
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ 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;
}
}
-
- ///
- 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();
- });
- }
-
- ///
- protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
- {
- // Cleanup.
- this.firstPass.Dispose();
- }
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageProcessorCore/Samplers/RotateFlip.cs b/src/ImageProcessorCore/Samplers/RotateFlip.cs
index aec9b9bbf..a7051fec5 100644
--- a/src/ImageProcessorCore/Samplers/RotateFlip.cs
+++ b/src/ImageProcessorCore/Samplers/RotateFlip.cs
@@ -2,202 +2,37 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
-namespace ImageProcessorCore.Samplers
+
+namespace ImageProcessorCore
{
- using System;
- using System.Threading.Tasks;
+ using Processors;
///
- /// Provides methods that allow the rotation and flipping of an image around its center point.
+ /// Extension methods for the type.
///
- public class RotateFlip : ImageSampler
+ public static partial class ImageExtensions
{
///
- /// Initializes a new instance of the class.
+ /// Rotates and flips an image by the given instructions.
///
- /// The used to perform rotation.
- /// The used to perform flipping.
- public RotateFlip(RotateType rotateType, FlipType flipType)
+ /// The image to rotate, flip, or both.
+ /// The to perform the rotation.
+ /// The to perform the flip.
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ public static Image RotateFlip(this Image source, RotateType rotateType, FlipType flipType, ProgressEventHandler progressHandler = null)
{
- this.RotateType = rotateType;
- this.FlipType = flipType;
- }
-
- ///
- /// Gets the used to perform flipping.
- ///
- public FlipType FlipType { get; }
-
- ///
- /// Gets the used to perform rotation.
- ///
- public RotateType RotateType { get; }
-
- ///
- public override int Parallelism { get; set; } = 1;
+ RotateFlip processor = new RotateFlip(rotateType, flipType);
+ processor.OnProgress += progressHandler;
- ///
- protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
- {
- switch (this.RotateType)
+ try
{
- 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;
+ return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor);
}
-
- switch (this.FlipType)
+ finally
{
- // No default needed as we have already set the pixels.
- case FlipType.Vertical:
- this.FlipX(target);
- break;
- case FlipType.Horizontal:
- this.FlipY(target);
- break;
+ processor.OnProgress -= progressHandler;
}
}
-
- ///
- /// Rotates the image 270 degrees clockwise at the centre point.
- ///
- /// The target image.
- /// The source image.
- 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);
- }
-
- ///
- /// Rotates the image 180 degrees clockwise at the centre point.
- ///
- /// The target image.
- /// The source image.
- 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();
- });
- }
-
- ///
- /// Rotates the image 90 degrees clockwise at the centre point.
- ///
- /// The target image.
- /// The source image.
- 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);
- }
-
- ///
- /// Swaps the image at the X-axis, which goes horizontally through the middle
- /// at half the height of the image.
- ///
- /// Target image to apply the process to.
- 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();
- });
- }
-
- ///
- /// Swaps the image at the Y-axis, which goes vertically through the middle
- /// at half of the width of the image.
- ///
- /// Target image to apply the process to.
- 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();
- });
- }
}
}
diff --git a/src/ImageProcessorCore/Samplers/Skew.cs b/src/ImageProcessorCore/Samplers/Skew.cs
index cef3b9216..5150bea89 100644
--- a/src/ImageProcessorCore/Samplers/Skew.cs
+++ b/src/ImageProcessorCore/Samplers/Skew.cs
@@ -1,164 +1,53 @@
//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
-//
+// -------------------------------------------------------------------------------------------------------------------
-namespace ImageProcessorCore.Samplers
+namespace ImageProcessorCore
{
- using System.Numerics;
- using System.Threading.Tasks;
+ using Processors;
///
- /// Provides methods that allow the skewing of images.
+ /// Extension methods for the type.
///
- public class Skew : ImageSampler
+ public static partial class ImageExtensions
{
///
- /// 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.
///
- private Image firstPass;
-
- ///
- /// The angle of rotation along the x-axis.
- ///
- private float angleX;
-
- ///
- /// The angle of rotation along the y-axis.
- ///
- private float angleY;
-
- ///
- public override int Parallelism { get; set; } = 1;
-
- ///
- /// Gets or sets the angle of rotation along the x-axis in degrees.
- ///
- public float AngleX
+ /// The image to skew.
+ /// The angle in degrees to perform the rotation along the x-axis.
+ /// The angle in degrees to perform the rotation along the y-axis.
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ 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);
}
///
- /// 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.
///
- public float AngleY
+ /// The image to skew.
+ /// The angle in degrees to perform the rotation along the x-axis.
+ /// The angle in degrees to perform the rotation along the y-axis.
+ /// The center point at which to skew the image.
+ /// Whether to expand the image to fit the skewed result.
+ /// A delegate which is called as progress is made processing the image.
+ /// The
+ 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);
}
- }
-
- ///
- /// Gets or sets the center point.
- ///
- public Point Center { get; set; }
-
- ///
- /// Gets or sets a value indicating whether to expand the canvas to fit the skewed image.
- ///
- public bool Expand { get; set; }
-
- ///
- 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;
}
}
-
- ///
- 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();
- });
- }
-
- ///
- protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
- {
- // Cleanup.
- this.firstPass.Dispose();
- }
}
-}
\ No newline at end of file
+}