Browse Source

Enhancements.

Add pad (It rhymes 😸)
Expand canvas on rotate. Fix #370


Former-commit-id: a8eb68c244d2aa76b4b7781474ba52ec39627b89
Former-commit-id: 9787dc29aae164752692f611fb18a6be4530a9f2
Former-commit-id: 0a2030e8d3c692394875f8014128715959aaa0bc
af/merge-core
James Jackson-South 10 years ago
parent
commit
115cb922ba
  1. 9
      README.md
  2. 33
      src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs
  3. 106
      src/ImageProcessorCore/Samplers/Rotate.cs
  4. 28
      tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs

9
README.md

@ -72,7 +72,7 @@ git clone https://github.com/JimBobSquarePants/ImageProcessor
- [x] Size
- [x] Point
- [x] Ellipse
- Resampling algorithms. (Optional gamma correction, Performance improvements?)
- Resampling algorithms. (Optional gamma correction, resize modes, Performance improvements?)
- [x] Box
- [x] Bicubic
- [x] Lanczos3
@ -86,13 +86,18 @@ git clone https://github.com/JimBobSquarePants/ImageProcessor
- [x] Spline
- [x] Triangle
- [x] Welch
- Padding
- [x] Pad
- [x] ResizeMode.Pad
- [x] ResizeMode.BoxPad
- Cropping
- [x] Rectangular Crop
- [ ] Elliptical Crop
- [x] Entropy Crop
- [x] ResizeMode.Crop
- Rotation/Skew
- [x] Flip (90, 270, FlipType etc)
- [x] Rotate by angle and center point.
- [x] Rotate by angle and center point (Expandable canvas).
- [x] Skew by x/y angles and center point.
- ColorMatrix operations (Uses Matrix4x4)
- [x] BlackWhite

33
src/ImageProcessorCore/Samplers/ImageSamplerExtensions.cs

@ -42,7 +42,7 @@ namespace ImageProcessorCore.Samplers
{
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
@ -85,6 +85,26 @@ namespace ImageProcessorCore.Samplers
}
}
/// <summary>
/// Evenly pads an image to fit the new dimensions.
/// </summary>
/// <param name="source">The source image to pad.</param>
/// <param name="width">The new width.</param>
/// <param name="height">The new height.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/>.</returns>
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);
}
/// <summary>
/// Resizes an image in accordance with the given <see cref="ResizeOptions"/>.
/// </summary>
@ -95,7 +115,7 @@ namespace ImageProcessorCore.Samplers
/// <remarks>Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image</remarks>
public static Image Resize(this Image source, ResizeOptions options, ProgressEventHandler progressHandler = null)
{
// Ensure size is populated acros both dimensions.
// 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);
@ -203,7 +223,7 @@ namespace ImageProcessorCore.Samplers
}
/// <summary>
/// Rotates an image by the given angle in degrees.
/// Rotates an image by the given angle in degrees, expanding the image to fit the rotated result.
/// </summary>
/// <param name="source">The image to rotate.</param>
/// <param name="degrees">The angle in degrees to perform the rotation.</param>
@ -211,7 +231,7 @@ namespace ImageProcessorCore.Samplers
/// <returns>The <see cref="Image"/></returns>
public static Image Rotate(this Image source, float degrees, ProgressEventHandler progressHandler = null)
{
return Rotate(source, degrees, Rectangle.Center(source.Bounds), progressHandler);
return Rotate(source, degrees, Point.Empty, true, progressHandler);
}
/// <summary>
@ -220,11 +240,12 @@ namespace ImageProcessorCore.Samplers
/// <param name="source">The image to rotate.</param>
/// <param name="degrees">The angle in degrees to perform the rotation.</param>
/// <param name="center">The center point at which to skew the image.</param>
/// <param name="expand">Whether to expand the image to fit the rotated result.</param>
/// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image Rotate(this Image source, float degrees, Point center, ProgressEventHandler progressHandler = null)
public static Image Rotate(this Image source, float degrees, Point center, bool expand, ProgressEventHandler progressHandler = null)
{
Rotate processor = new Rotate { Angle = degrees, Center = center };
Rotate processor = new Rotate { Angle = degrees, Center = center, Expand = expand };
processor.OnProgress += progressHandler;
try

106
src/ImageProcessorCore/Samplers/Rotate.cs

@ -3,10 +3,9 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
using System.Numerics;
namespace ImageProcessorCore.Samplers
{
using System.Numerics;
using System.Threading.Tasks;
/// <summary>
@ -15,12 +14,20 @@ namespace ImageProcessorCore.Samplers
public class Rotate : ImageSampler
{
/// <summary>
/// The angle of rotation.
/// The image used for storing the first pass pixels.
/// </summary>
private Image firstPass;
/// <summary>
/// The angle of rotation in degrees.
/// </summary>
private float angle;
/// <inheritdoc/>
public override int Parallelism { get; set; } = 1;
/// <summary>
/// Gets or sets the angle of rotation.
/// Gets or sets the angle of rotation in degrees.
/// </summary>
public float Angle
{
@ -50,47 +57,84 @@ namespace ImageProcessorCore.Samplers
/// </summary>
public Point Center { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to expand the canvas to fit the rotated image.
/// </summary>
public bool Expand { get; set; }
/// <inheritdoc/>
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
{
int targetY = targetRectangle.Y;
int targetBottom = targetRectangle.Bottom;
int startX = targetRectangle.X;
int endX = targetRectangle.Right;
float negativeAngle = -this.angle;
Point centre = this.Center == Point.Empty ? Rectangle.Center(sourceRectangle) : this.Center;
// 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 the target rectangle should be.
Rectangle rectangle = ImageMaths.GetBoundingRotatedRectangle(source.Width, source.Height, -this.angle);
ResizeOptions options = new ResizeOptions
{
Size = new Size(rectangle.Width, rectangle.Height),
Mode = ResizeMode.BoxPad,
Sampler = new NearestNeighborResampler()
};
// Scaling factors
float widthFactor = source.Width / (float)target.Width;
float heightFactor = source.Height / (float)target.Height;
// 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);
}
}
Matrix3x2 rotation = Point.CreateRotatation( centre, negativeAngle );
/// <inheritdoc/>
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
int targetY = this.firstPass.Bounds.Y;
int targetHeight = this.firstPass.Height;
int startX = this.firstPass.Bounds.X;
int endX = this.firstPass.Bounds.Right;
float negativeAngle = -this.angle;
Point centre = this.Center == Point.Empty ? Rectangle.Center(this.firstPass.Bounds) : this.Center;
Matrix3x2 rotation = Point.CreateRotatation(centre, negativeAngle);
// Since we are not working in parallel we use full height and width of the first pass image.
Parallel.For(
startY,
endY,
0,
targetHeight,
y =>
{
if (y >= targetY && y < targetBottom)
// Y coordinates of source points
int originY = y - targetY;
for (int x = startX; x < endX; x++)
{
// Y coordinates of source points
int originY = (int)((y - targetY) * heightFactor);
// X coordinates of source points
int originX = x - startX;
for (int x = startX; x < endX; x++)
// Rotate at the centre point
Point rotated = Point.Rotate(new Point(originX, originY), rotation);
if (this.firstPass.Bounds.Contains(rotated.X, rotated.Y))
{
// X coordinates of source points
int originX = (int)((x - startX) * widthFactor);
// Rotate at the centre point
Point rotated = Point.Rotate(new Point(originX, originY), rotation);
if (sourceRectangle.Contains(rotated.X, rotated.Y))
{
target[x, y] = source[rotated.X, rotated.Y];
}
target[x, y] = this.firstPass[rotated.X, rotated.Y];
}
this.OnRowProcessed();
}
this.OnRowProcessed();
});
}
/// <inheritdoc/>
protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
{
// Cleanup.
this.firstPass.Dispose();
}
}
}

28
tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs

@ -76,6 +76,34 @@
}
}
[Fact]
public void ImageShouldPad()
{
if (!Directory.Exists("TestOutput/Pad"))
{
Directory.CreateDirectory("TestOutput/Pad");
}
foreach (string file in Files)
{
using (FileStream stream = File.OpenRead(file))
{
Stopwatch watch = Stopwatch.StartNew();
string filename = Path.GetFileName(file);
using (Image image = new Image(stream))
using (FileStream output = File.OpenWrite($"TestOutput/Pad/{filename}"))
{
image.Pad(image.Width + 50, image.Height + 50, this.ProgressUpdate)
.Save(output);
}
Trace.WriteLine($"{watch.ElapsedMilliseconds}ms");
}
}
}
[Theory]
[MemberData("ReSamplers")]
public void ImageShouldResize(string name, IResampler sampler)

Loading…
Cancel
Save