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 +}