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)