Browse Source

Add RotateFlip Fix #261

Also add ClonePixel method to ImageBase


Former-commit-id: e25004eacf2aec54273a98f02a208c0fb60e12ae
Former-commit-id: 6bacacf8cb0bd8315961485b640809e1931e7388
Former-commit-id: f91314805b2aac54b3bab50a9bce782d15d854b1
af/merge-core
James Jackson-South 10 years ago
parent
commit
aba0dcee5e
  1. 2
      README.md
  2. 25
      src/ImageProcessor/IImageBase.cs
  3. 26
      src/ImageProcessor/ImageBase.cs
  4. 28
      src/ImageProcessor/Samplers/FlipType.cs
  5. 12
      src/ImageProcessor/Samplers/ImageSampleExtensions.cs
  6. 2
      src/ImageProcessor/Samplers/Resampler.cs
  7. 194
      src/ImageProcessor/Samplers/RotateFlip.cs
  8. 33
      src/ImageProcessor/Samplers/RotateType.cs
  9. 36
      tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs

2
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

25
src/ImageProcessor/IImageBase.cs

@ -70,14 +70,12 @@ namespace ImageProcessor
Color this[int x, int y] { get; set; }
/// <summary>
/// Sets the pixel array of the image.
/// Sets the pixel array of the image to the given value.
/// </summary>
/// <param name="width">
/// The new width of the image. Must be greater than zero.</param>
/// <param name="width">The new width of the image. Must be greater than zero.</param>
/// <param name="height">The new height of the image. Must be greater than zero.</param>
/// <param name="pixels">
/// 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.
/// </param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if either <paramref name="width"/> or <paramref name="height"/> are less than or equal to 0.
@ -86,5 +84,22 @@ namespace ImageProcessor
/// Thrown if the <paramref name="pixels"/> length is not equal to Width * Height * 4.
/// </exception>
void SetPixels(int width, int height, float[] pixels);
/// <summary>
/// Sets the pixel array of the image to the given value, creating a copy of
/// the original pixels.
/// </summary>
/// <param name="width">The new width of the image. Must be greater than zero.</param>
/// <param name="height">The new height of the image. Must be greater than zero.</param>
/// <param name="pixels">
/// The array with colors. Must be a multiple of four times the width and height.
/// </param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if either <paramref name="width"/> or <paramref name="height"/> are less than or equal to 0.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown if the <paramref name="pixels"/> length is not equal to Width * Height * 4.
/// </exception>
void ClonePixels(int width, int height, float[] pixels);
}
}

26
src/ImageProcessor/ImageBase.cs

@ -183,5 +183,31 @@ namespace ImageProcessor
this.Height = height;
this.Pixels = pixels;
}
/// <inheritdoc/>
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;
}
}
}

28
src/ImageProcessor/Samplers/FlipType.cs

@ -0,0 +1,28 @@
// <copyright file="FlipType.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessor.Samplers
{
/// <summary>
/// Provides enumeration over how a image should be flipped.
/// </summary>
public enum FlipType
{
/// <summary>
/// Dont flip the image.
/// </summary>
None,
/// <summary>
/// Flip the image horizontally.
/// </summary>
Horizontal,
/// <summary>
/// Flip the image vertically.
/// </summary>
Vertical,
}
}

12
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 });
}
/// <summary>
/// Rotates and flips an image by the given instructions.
/// </summary>
/// <param name="source">The image to resize.</param>
/// <param name="rotateType">The <see cref="RotateType"/> to perform the rotation.</param>
/// <param name="flipType">The <see cref="FlipType"/> to perform the flip.</param>
/// <returns>The <see cref="Image"/></returns>
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));
}
}
}

2
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;
}

194
src/ImageProcessor/Samplers/RotateFlip.cs

@ -0,0 +1,194 @@
// <copyright file="RotateFlip.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessor.Samplers
{
using System;
using System.Threading.Tasks;
/// <summary>
/// Provides methods that allow the rotation and flipping of an image around its center point.
/// </summary>
public class RotateFlip : ParallelImageProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="RotateFlip"/> class.
/// </summary>
/// <param name="rotateType">The <see cref="RotateType"/> used to perform rotation.</param>
/// <param name="flipType">The <see cref="FlipType"/> used to perform flipping.</param>
public RotateFlip(RotateType rotateType, FlipType flipType)
{
this.RotateType = rotateType;
this.FlipType = flipType;
}
/// <summary>
/// Gets the <see cref="FlipType"/> used to perform flipping.
/// </summary>
public FlipType FlipType { get; }
/// <summary>
/// Gets the <see cref="RotateType"/> used to perform rotation.
/// </summary>
public RotateType RotateType { get; }
/// <inheritdoc/>
public override int Parallelism { get; set; } = 1;
/// <inheritdoc/>
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
switch (this.RotateType)
{
case RotateType.Rotate90:
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;
}
}
/// <summary>
/// Rotates the image 270 degrees clockwise at the centre point.
/// </summary>
/// <param name="target">The target image.</param>
/// <param name="source">The source image.</param>
private 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);
}
/// <summary>
/// Rotates the image 180 degrees clockwise at the centre point.
/// </summary>
/// <param name="target">The target image.</param>
/// <param name="source">The source image.</param>
private 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];
}
});
}
/// <summary>
/// Rotates the image 90 degrees clockwise at the centre point.
/// </summary>
/// <param name="target">The target image.</param>
/// <param name="source">The source image.</param>
private 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);
}
/// <summary>
/// Swaps the image at the X-axis, which goes horizontally through the middle
/// at half the height of the image.
/// </summary>
/// <param name="target">Target image to apply the process to.</param>
private 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];
}
});
}
/// <summary>
/// Swaps the image at the Y-axis, which goes vertically through the middle
/// at half of the width of the image.
/// </summary>
/// <param name="target">Target image to apply the process to.</param>
private 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];
}
});
}
}
}

33
src/ImageProcessor/Samplers/RotateType.cs

@ -0,0 +1,33 @@
// <copyright file="RotateType.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessor.Samplers
{
/// <summary>
/// Provides enumeration over how the image should be rotated.
/// </summary>
public enum RotateType
{
/// <summary>
/// Do not rotate the image.
/// </summary>
None,
/// <summary>
/// Rotate the image by 90 degrees clockwise.
/// </summary>
Rotate90,
/// <summary>
/// Rotate the image by 180 degrees clockwise.
/// </summary>
Rotate180,
/// <summary>
/// Rotate the image by 270 degrees clockwise.
/// </summary>
Rotate270
}
}

36
tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs

@ -29,6 +29,15 @@
{ "Welch", new WelchResampler() }
};
public static readonly TheoryData<RotateType, FlipType> RotateFlips = new TheoryData<RotateType, FlipType>
{
{ 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)

Loading…
Cancel
Save