From aba0dcee5e1b1dec82bb88d32aa452e2612be54f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 7 Dec 2015 00:59:38 +1100 Subject: [PATCH] Add RotateFlip Fix #261 Also add ClonePixel method to ImageBase Former-commit-id: e25004eacf2aec54273a98f02a208c0fb60e12ae Former-commit-id: 6bacacf8cb0bd8315961485b640809e1931e7388 Former-commit-id: f91314805b2aac54b3bab50a9bce782d15d854b1 --- README.md | 2 +- src/ImageProcessor/IImageBase.cs | 25 ++- src/ImageProcessor/ImageBase.cs | 26 +++ src/ImageProcessor/Samplers/FlipType.cs | 28 +++ .../Samplers/ImageSampleExtensions.cs | 12 ++ src/ImageProcessor/Samplers/Resampler.cs | 2 +- src/ImageProcessor/Samplers/RotateFlip.cs | 194 ++++++++++++++++++ src/ImageProcessor/Samplers/RotateType.cs | 33 +++ .../Processors/Samplers/SamplerTests.cs | 36 ++++ 9 files changed, 351 insertions(+), 7 deletions(-) create mode 100644 src/ImageProcessor/Samplers/FlipType.cs create mode 100644 src/ImageProcessor/Samplers/RotateFlip.cs create mode 100644 src/ImageProcessor/Samplers/RotateType.cs diff --git a/README.md b/README.md index 18b58d18c..036058e3a 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ git clone https://github.com/JimBobSquarePants/ImageProcessor - [ ] Elliptical Crop - [x] Entropy Crop - Rotation - - [ ] Flip (90, 270, FlipType etc. Need help) [#261](https://github.com/JimBobSquarePants/ImageProcessor/issues/261) + - [x] Flip (90, 270, FlipType etc. Need help) - [ ] Rotate by angle (Need help with Paeth approach) [#258](https://github.com/JimBobSquarePants/ImageProcessor/issues/258) - ColorMatrix operations (Uses Matrix4x4) - [x] BlackWhite diff --git a/src/ImageProcessor/IImageBase.cs b/src/ImageProcessor/IImageBase.cs index 431368c4b..bc920175a 100644 --- a/src/ImageProcessor/IImageBase.cs +++ b/src/ImageProcessor/IImageBase.cs @@ -70,14 +70,12 @@ namespace ImageProcessor Color this[int x, int y] { get; set; } /// - /// Sets the pixel array of the image. + /// Sets the pixel array of the image to the given value. /// - /// - /// The new width of the image. Must be greater than zero. + /// The new width of the image. Must be greater than zero. /// The new height of the image. Must be greater than zero. /// - /// The array with colors. Must be a multiple - /// of four, width and height. + /// The array with colors. Must be a multiple of four times the width and height. /// /// /// Thrown if either or are less than or equal to 0. @@ -86,5 +84,22 @@ namespace ImageProcessor /// Thrown if the length is not equal to Width * Height * 4. /// void SetPixels(int width, int height, float[] pixels); + + /// + /// Sets the pixel array of the image to the given value, creating a copy of + /// the original pixels. + /// + /// The new width of the image. Must be greater than zero. + /// The new height of the image. Must be greater than zero. + /// + /// The array with colors. Must be a multiple of four times the width and height. + /// + /// + /// Thrown if either or are less than or equal to 0. + /// + /// + /// Thrown if the length is not equal to Width * Height * 4. + /// + void ClonePixels(int width, int height, float[] pixels); } } diff --git a/src/ImageProcessor/ImageBase.cs b/src/ImageProcessor/ImageBase.cs index 656aaba85..d425a9903 100644 --- a/src/ImageProcessor/ImageBase.cs +++ b/src/ImageProcessor/ImageBase.cs @@ -183,5 +183,31 @@ namespace ImageProcessor this.Height = height; this.Pixels = pixels; } + + /// + public void ClonePixels(int width, int height, float[] pixels) + { +#if DEBUG + if (width <= 0) + { + throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than or equals than zero."); + } + + if (height <= 0) + { + throw new ArgumentOutOfRangeException(nameof(height), "Height must be greater than or equal than zero."); + } + + if (pixels.Length != width * height * 4) + { + throw new ArgumentException("Pixel array must have the length of Width * Height * 4."); + } +#endif + this.Width = width; + this.Height = height; + float[] clonedPixels = new float[pixels.Length]; + Array.Copy(pixels, clonedPixels, pixels.Length); + this.Pixels = clonedPixels; + } } } diff --git a/src/ImageProcessor/Samplers/FlipType.cs b/src/ImageProcessor/Samplers/FlipType.cs new file mode 100644 index 000000000..c99ecf332 --- /dev/null +++ b/src/ImageProcessor/Samplers/FlipType.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Samplers +{ + /// + /// Provides enumeration over how a image should be flipped. + /// + public enum FlipType + { + /// + /// Dont flip the image. + /// + None, + + /// + /// Flip the image horizontally. + /// + Horizontal, + + /// + /// Flip the image vertically. + /// + Vertical, + } +} diff --git a/src/ImageProcessor/Samplers/ImageSampleExtensions.cs b/src/ImageProcessor/Samplers/ImageSampleExtensions.cs index 78ab22a52..9b560c9ea 100644 --- a/src/ImageProcessor/Samplers/ImageSampleExtensions.cs +++ b/src/ImageProcessor/Samplers/ImageSampleExtensions.cs @@ -123,5 +123,17 @@ namespace ImageProcessor.Samplers { return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, new Resampler(sampler) { Angle = degrees }); } + + /// + /// Rotates and flips an image by the given instructions. + /// + /// The image to resize. + /// The to perform the rotation. + /// The to perform the flip. + /// The + public static Image RotateFlip(this Image source, RotateType rotateType, FlipType flipType) + { + return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, new RotateFlip(rotateType, flipType)); + } } } diff --git a/src/ImageProcessor/Samplers/Resampler.cs b/src/ImageProcessor/Samplers/Resampler.cs index 60d22cae2..6e95359b5 100644 --- a/src/ImageProcessor/Samplers/Resampler.cs +++ b/src/ImageProcessor/Samplers/Resampler.cs @@ -120,7 +120,7 @@ namespace ImageProcessor.Samplers { if (source.Bounds == target.Bounds) { - target.SetPixels(target.Width, target.Height, source.Pixels); + target.ClonePixels(target.Width, target.Height, source.Pixels); return; } diff --git a/src/ImageProcessor/Samplers/RotateFlip.cs b/src/ImageProcessor/Samplers/RotateFlip.cs new file mode 100644 index 000000000..62ccff8b1 --- /dev/null +++ b/src/ImageProcessor/Samplers/RotateFlip.cs @@ -0,0 +1,194 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Samplers +{ + using System; + using System.Threading.Tasks; + + /// + /// Provides methods that allow the rotation and flipping of an image around its center point. + /// + public class RotateFlip : ParallelImageProcessor + { + /// + /// 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: + Rotate90(target, source); + break; + case RotateType.Rotate180: + Rotate180(target, source); + break; + case RotateType.Rotate270: + 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: + FlipX(target); + break; + case FlipType.Horizontal: + FlipY(target); + break; + } + } + + /// + /// Rotates the image 270 degrees clockwise at the centre point. + /// + /// The target image. + /// The source image. + private static 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]; + } + }); + + target.SetPixels(height, width, temp.Pixels); + } + + /// + /// Rotates the image 180 degrees clockwise at the centre point. + /// + /// The target image. + /// The source image. + private static 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]; + } + }); + } + + /// + /// Rotates the image 90 degrees clockwise at the centre point. + /// + /// The target image. + /// The source image. + private static 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]; + } + }); + + 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 static void FlipX(ImageBase target) + { + int width = target.Width; + int height = target.Height; + int halfHeight = (int)Math.Ceiling(target.Height / 2d); + 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]; + } + }); + } + + /// + /// 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 static 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]; + } + }); + } + } +} diff --git a/src/ImageProcessor/Samplers/RotateType.cs b/src/ImageProcessor/Samplers/RotateType.cs new file mode 100644 index 000000000..4ebb7b180 --- /dev/null +++ b/src/ImageProcessor/Samplers/RotateType.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Samplers +{ + /// + /// Provides enumeration over how the image should be rotated. + /// + public enum RotateType + { + /// + /// Do not rotate the image. + /// + None, + + /// + /// Rotate the image by 90 degrees clockwise. + /// + Rotate90, + + /// + /// Rotate the image by 180 degrees clockwise. + /// + Rotate180, + + /// + /// Rotate the image by 270 degrees clockwise. + /// + Rotate270 + } +} diff --git a/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs index 266112f0b..a525c84d4 100644 --- a/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs +++ b/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs @@ -29,6 +29,15 @@ { "Welch", new WelchResampler() } }; + public static readonly TheoryData RotateFlips = new TheoryData + { + { RotateType.None, FlipType.Vertical }, + { RotateType.None, FlipType.Horizontal }, + { RotateType.Rotate90, FlipType.None }, + { RotateType.Rotate180, FlipType.None }, + { RotateType.Rotate270, FlipType.None }, + }; + [Theory] [MemberData("Samplers")] public void ImageShouldResize(string name, IResampler sampler) @@ -56,6 +65,33 @@ } } + [Theory] + [MemberData("RotateFlips")] + public void ImageShouldRotateFlip(RotateType rotateType, FlipType flipType) + { + if (!Directory.Exists("TestOutput/RotateFlip")) + { + Directory.CreateDirectory("TestOutput/RotateFlip"); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + Stopwatch watch = Stopwatch.StartNew(); + Image image = new Image(stream); + string filename = Path.GetFileNameWithoutExtension(file) + "-" + rotateType + flipType + Path.GetExtension(file); + using (FileStream output = File.OpenWrite($"TestOutput/RotateFlip/{filename}")) + { + image.RotateFlip(rotateType, flipType) + .Save(output); + } + + Trace.WriteLine($"{rotateType + "-" + flipType}: {watch.ElapsedMilliseconds}ms"); + } + } + } + [Theory] [MemberData("Samplers")] public void ImageShouldRotate(string name, IResampler sampler)