Browse Source

A few updates

- Some predefined colors
- Source image should not  get overwritten
- Begin EntropyCrop - unfinished.


Former-commit-id: 15096078a8c4c81e5c38b07111496b9382e98583
Former-commit-id: 1525d80b8094e74ee0b1a64a291e9fdc606b0438
Former-commit-id: 7ca269955bd18bb8c1793b3038c95f8f976694a1
af/merge-core
James Jackson-South 11 years ago
parent
commit
f64574f3bc
  1. 4
      src/ImageProcessor/Colors/Color.cs
  2. 121
      src/ImageProcessor/Colors/ColorDefinitions.cs
  3. 33
      src/ImageProcessor/Colors/RgbaComponent.cs
  4. 135
      src/ImageProcessor/Common/Helpers/ImageMaths.cs
  5. 69
      src/ImageProcessor/Filters/Binarization/Threshold.cs
  6. 2
      src/ImageProcessor/Filters/ColorMatrix/Saturation.cs
  7. 2
      src/ImageProcessor/Filters/Convolution/EdgeDetection/EdgeDetector2DFilter.cs
  8. 2
      src/ImageProcessor/Filters/Convolution/EdgeDetection/EdgeDetectorFilter.cs
  9. 2
      src/ImageProcessor/Filters/Convolution/GuassianBlur.cs
  10. 2
      src/ImageProcessor/Filters/Convolution/GuassianSharpen.cs
  11. 26
      src/ImageProcessor/ParallelImageProcessor.cs
  12. 8
      src/ImageProcessor/Samplers/Crop.cs
  13. 77
      src/ImageProcessor/Samplers/EntropyCrop.cs
  14. 87
      src/ImageProcessor/Samplers/ImageSampleExtensions.cs
  15. 2
      src/ImageProcessor/Samplers/Resampler.cs
  16. 2
      src/ImageProcessor/project.json
  17. 4
      tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
  18. 4
      tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs
  19. 22
      tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs

4
src/ImageProcessor/Colors/Color.cs

@ -17,10 +17,10 @@ namespace ImageProcessor
/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
/// as it avoids the need to create new values for modification operations.
/// </remarks>
public struct Color : IEquatable<Color>
public partial struct Color : IEquatable<Color>
{
/// <summary>
/// Represents a <see cref="Color"/> that has R, G, B, and A values set to zero.
/// Represents an empty <see cref="Color"/> that has R, G, B, and A values set to zero.
/// </summary>
public static readonly Color Empty = default(Color);

121
src/ImageProcessor/Colors/ColorDefinitions.cs

@ -0,0 +1,121 @@
// <copyright file="ColorDefinitions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessor
{
public partial struct Color
{
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #F0F8FF.
/// </summary>
public static readonly Color AliceBlue = new Color(240 / 255f, 248 / 255f, 1);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #FAEBD7.
/// </summary>
public static readonly Color AntiqueWhite = new Color(250 / 255f, 235 / 255f, 215 / 255f);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #00FFFF.
/// </summary>
public static readonly Color Aqua = new Color(0, 1, 1);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #7FFFD4.
/// </summary>
public static readonly Color AquaMarine = new Color(127 / 255f, 1, 212 / 255f);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #F0FFFF.
/// </summary>
public static readonly Color Azure = new Color(240 / 255f, 1, 1);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #F5F5DC.
/// </summary>
public static readonly Color Beige = new Color(245 / 255f, 245 / 255f, 220 / 255f);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #FFE4C4.
/// </summary>
public static readonly Color Bisque = new Color(1, 228 / 255f, 196 / 255f);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #000000.
/// </summary>
public static readonly Color Black = new Color(0, 0, 0);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #0000FF.
/// </summary>
public static readonly Color Blue = new Color(0, 0, 1);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #C0C0C0.
/// </summary>
public static readonly Color Silver = new Color(192 / 255f, 192 / 255f, 192 / 255f);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #808080.
/// </summary>
public static readonly Color Gray = new Color(128 / 255f, 128 / 255f, 128 / 255f);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #FFFFFF.
/// </summary>
public static readonly Color White = new Color(1, 1, 1);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #800000.
/// </summary>
public static readonly Color Maroon = new Color(128 / 255f, 0, 0);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #FF0000.
/// </summary>
public static readonly Color Red = new Color(1, 0, 0);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #800080.
/// </summary>
public static readonly Color Purple = new Color(128 / 255f, 0, 128 / 255f);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #FF00FF.
/// </summary>
public static readonly Color Fuchsia = new Color(1, 0, 1);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #008000.
/// </summary>
public static readonly Color Green = new Color(0, 128 / 255f, 0);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #00FF00.
/// </summary>
public static readonly Color Lime = new Color(0, 1, 0);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #808000.
/// </summary>
public static readonly Color Olive = new Color(128 / 255f, 128 / 255f, 0);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #FFFF00.
/// </summary>
public static readonly Color Yellow = new Color(1, 1, 0);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #000080.
/// </summary>
public static readonly Color Navy = new Color(0, 0, 128 / 255f);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #008080.
/// </summary>
public static readonly Color Teal = new Color(0, 128 / 255f, 128 / 255f);
}
}

33
src/ImageProcessor/Colors/RgbaComponent.cs

@ -0,0 +1,33 @@
// <copyright file="RgbaComponent.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessor
{
/// <summary>
/// Enumerates the RGBA (red, green, blue, alpha) color components.
/// </summary>
public enum RgbaComponent
{
/// <summary>
/// The blue component.
/// </summary>
B = 0,
/// <summary>
/// The green component.
/// </summary>
G = 1,
/// <summary>
/// The red component.
/// </summary>
R = 2,
/// <summary>
/// The alpha component.
/// </summary>
A = 3
}
}

135
src/ImageProcessor/Common/Helpers/ImageMaths.cs

@ -137,6 +137,23 @@ namespace ImageProcessor
return translatedPoint;
}
/// <summary>
/// Gets the bounding <see cref="Rectangle"/> from the given points.
/// </summary>
/// <param name="topLeft">
/// The <see cref="Point"/> designating the top left position.
/// </param>
/// <param name="bottomRight">
/// The <see cref="Point"/> designating the bottom right position.
/// </param>
/// <returns>
/// The bounding <see cref="Rectangle"/>.
/// </returns>
public static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight)
{
return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y);
}
/// <summary>
/// Calculates the new size after rotation.
/// </summary>
@ -169,6 +186,124 @@ namespace ImageProcessor
return result;
}
/// <summary>
/// Finds the bounding rectangle based on the first instance of any color component other
/// than the given one.
/// </summary>
/// <param name="bitmap">
/// The <see cref="Image"/> to search within.
/// </param>
/// <param name="componentValue">
/// The color component value to remove.
/// </param>
/// <param name="channel">
/// The <see cref="RgbaComponent"/> channel to test against.
/// </param>
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>
public static Rectangle GetFilteredBoundingRectangle(ImageBase bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B)
{
const float Epsilon = .00001f;
int width = bitmap.Width;
int height = bitmap.Height;
Point topLeft = new Point();
Point bottomRight = new Point();
Func<ImageBase, int, int, float, bool> delegateFunc;
// Determine which channel to check against
switch (channel)
{
case RgbaComponent.R:
delegateFunc = (imageBase, x, y, b) => imageBase[x, y].R != b;
break;
case RgbaComponent.G:
delegateFunc = (imageBase, x, y, b) => imageBase[x, y].G != b;
break;
case RgbaComponent.A:
delegateFunc = (imageBase, x, y, b) => imageBase[x, y].A != b;
break;
default:
delegateFunc = (imageBase, x, y, b) => imageBase[x, y].B != b;
break;
}
Func<ImageBase, int> getMinY = imageBase =>
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
if (delegateFunc(imageBase, x, y, componentValue))
{
return y;
}
}
}
return 0;
};
Func<ImageBase, int> getMaxY = imageBase =>
{
for (int y = height - 1; y > -1; y--)
{
for (int x = 0; x < width; x++)
{
if (delegateFunc(imageBase, x, y, componentValue))
{
return y;
}
}
}
return height;
};
Func<ImageBase, int> getMinX = imageBase =>
{
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
if (delegateFunc(imageBase, x, y, componentValue))
{
return x;
}
}
}
return 0;
};
Func<ImageBase, int> getMaxX = imageBase =>
{
for (int x = width - 1; x > -1; x--)
{
for (int y = 0; y < height; y++)
{
if (delegateFunc(imageBase, x, y, componentValue))
{
return x;
}
}
}
return height;
};
topLeft.Y = getMinY(bitmap);
topLeft.X = getMinX(bitmap);
bottomRight.Y = getMaxY(bitmap) + 1;
bottomRight.X = getMaxX(bitmap) + 1;
return GetBoundingRectangle(topLeft, bottomRight);
}
/// <summary>
/// Ensures that any passed double is correctly rounded to zero
/// </summary>

69
src/ImageProcessor/Filters/Binarization/Threshold.cs

@ -0,0 +1,69 @@
// <copyright file="Threshold.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessor.Filters
{
using System;
using System.Threading.Tasks;
/// <summary>
/// An <see cref="IImageProcessor"/> to perform binary threshold filtering against an
/// <see cref="Image"/>. The mage will be converted to greyscale before thresholding
/// occurrs.
/// </summary>
public class Threshold : ParallelImageProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="Threshold"/> class.
/// </summary>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
/// <exception cref="ArgumentException">
/// <paramref name="threshold"/> is less than 0 or is greater than 1.
/// </exception>
public Threshold(float threshold)
{
Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold));
this.Value = threshold;
}
/// <summary>
/// Gets the threshold value.
/// </summary>
public float Value { get; }
/// <inheritdoc/>
protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
{
new GreyscaleBt709().Apply(source, source, sourceRectangle);
}
/// <inheritdoc/>
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
float threshold = this.Value;
int sourceY = sourceRectangle.Y;
int sourceBottom = sourceRectangle.Bottom;
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
Parallel.For(
startY,
endY,
y =>
{
if (y >= sourceY && y < sourceBottom)
{
for (int x = startX; x < endX; x++)
{
Color color = source[x, y];
// Any channel will do since it's greyscale.
target[x, y] = color.B >= threshold ? Color.White : Color.Black;
}
}
});
}
}
}

2
src/ImageProcessor/Filters/ColorMatrix/Saturation.cs

@ -40,7 +40,7 @@ namespace ImageProcessor.Filters
public override Matrix4x4 Matrix => this.matrix;
/// <inheritdoc/>
protected override void OnApply(ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
{
float saturationFactor = this.saturation / 100f;

2
src/ImageProcessor/Filters/Convolution/EdgeDetection/EdgeDetector2DFilter.cs

@ -15,7 +15,7 @@ namespace ImageProcessor.Filters
public bool Greyscale { get; set; }
/// <inheritdoc/>
protected override void OnApply(ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
{
if (this.Greyscale)
{

2
src/ImageProcessor/Filters/Convolution/EdgeDetection/EdgeDetectorFilter.cs

@ -15,7 +15,7 @@ namespace ImageProcessor.Filters
public bool Greyscale { get; set; }
/// <inheritdoc/>
protected override void OnApply(ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
{
if (this.Greyscale)
{

2
src/ImageProcessor/Filters/Convolution/GuassianBlur.cs

@ -82,7 +82,7 @@ namespace ImageProcessor.Filters
public override int Parallelism => 1;
/// <inheritdoc/>
protected override void OnApply(ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
{
if (this.kernelY == null)
{

2
src/ImageProcessor/Filters/Convolution/GuassianSharpen.cs

@ -82,7 +82,7 @@ namespace ImageProcessor.Filters
public override int Parallelism => 1;
/// <inheritdoc/>
protected override void OnApply(ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
{
if (this.kernelY == null)
{

26
src/ImageProcessor/ParallelImageProcessor.cs

@ -21,7 +21,9 @@ namespace ImageProcessor
/// <inheritdoc/>
public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle)
{
this.OnApply(source, target.Bounds, sourceRectangle);
// We don't want to affect the original source pixels so we make clone here.
Image temp = new Image((Image)source);
this.OnApply(temp, target, target.Bounds, sourceRectangle);
if (this.Parallelism > 1)
{
@ -38,7 +40,7 @@ namespace ImageProcessor
int yStart = sourceRectangle.Y + (current * batchSize);
int yEnd = current == partitionCount - 1 ? sourceRectangle.Bottom : yStart + batchSize;
this.Apply(target, source, target.Bounds, sourceRectangle, yStart, yEnd);
this.Apply(target, temp, target.Bounds, sourceRectangle, yStart, yEnd);
});
}
@ -46,7 +48,7 @@ namespace ImageProcessor
}
else
{
this.Apply(target, source, target.Bounds, sourceRectangle, sourceRectangle.Y, sourceRectangle.Bottom);
this.Apply(target, temp, target.Bounds, sourceRectangle, sourceRectangle.Y, sourceRectangle.Bottom);
}
}
@ -56,17 +58,16 @@ namespace ImageProcessor
float[] pixels = new float[width * height * 4];
target.SetPixels(width, height, pixels);
if (targetRectangle == Rectangle.Empty)
{
targetRectangle = target.Bounds;
}
if (sourceRectangle == Rectangle.Empty)
{
sourceRectangle = source.Bounds;
}
this.OnApply(source, target.Bounds, sourceRectangle);
// We don't want to affect the original source pixels so we make clone here.
Image temp = new Image((Image)source);
this.OnApply(temp, target, target.Bounds, sourceRectangle);
targetRectangle = target.Bounds;
if (this.Parallelism > 1)
{
@ -83,7 +84,7 @@ namespace ImageProcessor
int yStart = current * batchSize;
int yEnd = current == partitionCount - 1 ? targetRectangle.Bottom : yStart + batchSize;
this.Apply(target, source, targetRectangle, sourceRectangle, yStart, yEnd);
this.Apply(target, temp, targetRectangle, sourceRectangle, yStart, yEnd);
});
}
@ -91,7 +92,7 @@ namespace ImageProcessor
}
else
{
this.Apply(target, source, targetRectangle, sourceRectangle, targetRectangle.Y, targetRectangle.Bottom);
this.Apply(target, temp, targetRectangle, sourceRectangle, targetRectangle.Y, targetRectangle.Bottom);
}
}
@ -99,6 +100,7 @@ namespace ImageProcessor
/// This method is called before the process is applied to prepare the processor.
/// </summary>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="target">Target image to apply the process to.</param>
/// <param name="targetRectangle">
/// The <see cref="Rectangle"/> structure that specifies the location and size of the drawn image.
/// The image is scaled to fit the rectangle.
@ -106,7 +108,7 @@ namespace ImageProcessor
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
protected virtual void OnApply(ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
protected virtual void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
{
}

8
src/ImageProcessor/Samplers/Crop.cs

@ -13,13 +13,7 @@ namespace ImageProcessor.Samplers
public class Crop : ParallelImageProcessor
{
/// <inheritdoc/>
protected override void Apply(
ImageBase target,
ImageBase source,
Rectangle targetRectangle,
Rectangle sourceRectangle,
int startY,
int endY)
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
int targetY = targetRectangle.Y;
int targetBottom = targetRectangle.Bottom;

77
src/ImageProcessor/Samplers/EntropyCrop.cs

@ -0,0 +1,77 @@
// <copyright file="EntropyCrop.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;
using ImageProcessor.Filters;
/// <summary>
/// Provides methods to allow the cropping of an image to preserve areas of highest
/// entropy.
/// </summary>
public class EntropyCrop : ParallelImageProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="EntropyCrop"/> class.
/// </summary>
/// <param name="threshold">The threshold to split the image. Must be between 0 and 1.</param>
/// <exception cref="ArgumentException">
/// <paramref name="threshold"/> is less than 0 or is greater than 1.
/// </exception>
public EntropyCrop(float threshold)
{
Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold));
this.Value = threshold;
}
/// <summary>
/// Gets the threshold value.
/// </summary>
public float Value { get; }
/// <inheritdoc/>
protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
{
ImageBase temp = new Image(source.Width, source.Height);
// TODO: Should we detect edges on a grayscale image?
new Sobel() { Greyscale = true }.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);
target.SetPixels(rectangle.Width, rectangle.Height, new float[rectangle.Width * rectangle.Height * 4]);
}
/// <inheritdoc/>
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
int targetY = targetRectangle.Y;
int targetBottom = targetRectangle.Height;
int startX = targetRectangle.X;
int endX = targetRectangle.Width;
Parallel.For(
startY,
endY,
y =>
{
if (y >= targetY && y < targetBottom)
{
for (int x = startX; x < endX; x++)
{
target[x, y] = source[x, y];
}
}
});
}
}
}

87
src/ImageProcessor/Samplers/ImageSampleExtensions.cs

@ -10,6 +10,55 @@ namespace ImageProcessor.Samplers
/// </summary>
public static class ImageSampleExtensions
{
/// <summary>
/// Crops an image to the given width and height.
/// </summary>
/// <param name="source">The image to resize.</param>
/// <param name="width">The target image width.</param>
/// <param name="height">The target image height.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image Crop(this Image source, int width, int height)
{
return Crop(source, width, height, source.Bounds);
}
/// <summary>
/// Crops an image to the given width and height with the given source rectangle.
/// <remarks>
/// If the source rectangle is smaller than the target dimensions then the
/// area within the source is resized performing a zoomed crop.
/// </remarks>
/// </summary>
/// <param name="source">The image to crop.</param>
/// <param name="width">The target image width.</param>
/// <param name="height">The target image height.</param>
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
/// <returns>The <see cref="Image"/></returns>
public static Image Crop(this Image source, int width, int height, Rectangle sourceRectangle)
{
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);
}
return source.Process(width, height, sourceRectangle, new Rectangle(0, 0, width, height), new Crop());
}
/// <summary>
/// Crops an image to the area of greatest entropy.
/// </summary>
/// <param name="source">The image to crop.</param>
/// <param name="threshold">The threshold for entropic density.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image EntropyCrop(this Image source, float threshold = .5f)
{
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, new EntropyCrop(threshold));
}
/// <summary>
/// Resizes an image to the given width and height.
/// </summary>
@ -62,43 +111,5 @@ namespace ImageProcessor.Samplers
{
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, new Resampler(new RobidouxResampler()) { Angle = degrees });
}
/// <summary>
/// Crops an image to the given width and height.
/// </summary>
/// <param name="source">The image to resize.</param>
/// <param name="width">The target image width.</param>
/// <param name="height">The target image height.</param>
/// <returns>The <see cref="Image"/></returns>
public static Image Crop(this Image source, int width, int height)
{
return Crop(source, width, height, source.Bounds);
}
/// <summary>
/// Crops an image to the given width and height with the given source rectangle.
/// <remarks>
/// If the source rectangle is smaller than the target dimensions then the
/// area within the source is resized performing a zoomed crop.
/// </remarks>
/// </summary>
/// <param name="source">The image to resize.</param>
/// <param name="width">The target image width.</param>
/// <param name="height">The target image height.</param>
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
/// <returns>The <see cref="Image"/></returns>
public static Image Crop(this Image source, int width, int height, Rectangle sourceRectangle)
{
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);
}
return source.Process(width, height, sourceRectangle, new Rectangle(0, 0, width, height), new Crop());
}
}
}

2
src/ImageProcessor/Samplers/Resampler.cs

@ -75,7 +75,7 @@ namespace ImageProcessor.Samplers
}
/// <inheritdoc/>
protected override void OnApply(ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
{
this.horizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width);
this.verticalWeights = this.PrecomputeWeights(targetRectangle.Height, sourceRectangle.Height);

2
src/ImageProcessor/project.json

@ -19,7 +19,7 @@
"System.Runtime.Extensions": "4.0.11-beta-23516",
"System.Reflection": "4.1.0-beta-23516",
"System.IO": "4.0.11-beta-23516",
"StyleCop.Analyzers": "1.0.0-beta016",
"StyleCop.Analyzers": "1.0.0-beta017",
"Microsoft.NETCore": "5.0.1-beta-23516",
"Microsoft.NETCore.Platforms": "1.0.1-beta-23516"
},

4
tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs

@ -18,7 +18,7 @@ namespace ImageProcessor.Tests
//{ "Contrast-50", new Contrast(50) },
//{ "Contrast--50", new Contrast(-50) },
//{ "BackgroundColor", new BackgroundColor(new Color(243 / 255f, 87 / 255f, 161 / 255f))},
{ "Blend", new Blend(new Image(File.OpenRead("TestImages/Formats/Bmp/Car.bmp")),50)},
//{ "Blend", new Blend(new Image(File.OpenRead("TestImages/Formats/Bmp/Car.bmp")),50)},
//{ "Saturation-50", new Saturation(50) },
//{ "Saturation--50", new Saturation(-50) },
//{ "Alpha--50", new Alpha(50) },
@ -38,7 +38,7 @@ namespace ImageProcessor.Tests
//{ "Prewitt", new Prewitt() },
//{ "RobertsCross", new RobertsCross() },
//{ "Scharr", new Scharr() },
//{ "Sobel", new Sobel {Greyscale = true} },
{ "Sobel", new Sobel {Greyscale = true} },
//{ "GuassianBlur", new GuassianBlur(10) },
//{ "GuassianSharpen", new GuassianSharpen(10) }
};

4
tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs

@ -20,7 +20,7 @@ namespace ImageProcessor.Tests
public static readonly List<string> Files = new List<string>
{
//"TestImages/Formats/Jpg/Backdrop.jpg",
"TestImages/Formats/Jpg/Calliphora.jpg",
//"TestImages/Formats/Jpg/Calliphora.jpg",
//"TestImages/Formats/Jpg/china.jpg",
//"TestImages/Formats/Jpg/ant.jpg",
//"TestImages/Formats/Jpg/parachute.jpg",
@ -28,7 +28,7 @@ namespace ImageProcessor.Tests
//"TestImages/Formats/Jpg/shaftesbury.jpg",
//"TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg",
//"TestImages/Formats/Jpg/greyscale.jpg",
//"TestImages/Formats/Bmp/Car.bmp",
"TestImages/Formats/Bmp/Car.bmp",
//"TestImages/Formats/Png/cballs.png",
//"TestImages/Formats/Png/blur.png",
//"TestImages/Formats/Png/cmyk.png",

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

@ -56,6 +56,28 @@ namespace ImageProcessor.Tests
}
}
[Fact]
public void ImageShouldEntropyCrop()
{
if (!Directory.Exists("TestOutput/EntropyCropped"))
{
Directory.CreateDirectory("TestOutput/EntropyCropped");
}
foreach (string file in Files)
{
using (FileStream stream = File.OpenRead(file))
{
Image image = new Image(stream);
string filename = Path.GetFileNameWithoutExtension(file) + "-EntropyCropped" + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/EntropyCropped/{filename}"))
{
image.EntropyCrop().Save(output);
}
}
}
}
[Fact]
public void ImageShouldCrop()
{

Loading…
Cancel
Save