From f64574f3bcc0fc873611224e3c0d0b39a03baf82 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 30 Nov 2015 07:51:57 +1100 Subject: [PATCH] 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 --- src/ImageProcessor/Colors/Color.cs | 4 +- src/ImageProcessor/Colors/ColorDefinitions.cs | 121 ++++++++++++++++ src/ImageProcessor/Colors/RgbaComponent.cs | 33 +++++ .../Common/Helpers/ImageMaths.cs | 135 ++++++++++++++++++ .../Filters/Binarization/Threshold.cs | 69 +++++++++ .../Filters/ColorMatrix/Saturation.cs | 2 +- .../EdgeDetection/EdgeDetector2DFilter.cs | 2 +- .../EdgeDetection/EdgeDetectorFilter.cs | 2 +- .../Filters/Convolution/GuassianBlur.cs | 2 +- .../Filters/Convolution/GuassianSharpen.cs | 2 +- src/ImageProcessor/ParallelImageProcessor.cs | 26 ++-- src/ImageProcessor/Samplers/Crop.cs | 8 +- src/ImageProcessor/Samplers/EntropyCrop.cs | 77 ++++++++++ .../Samplers/ImageSampleExtensions.cs | 87 ++++++----- src/ImageProcessor/Samplers/Resampler.cs | 2 +- src/ImageProcessor/project.json | 2 +- .../Processors/Filters/FilterTests.cs | 4 +- .../Processors/ProcessorTestBase.cs | 4 +- .../Processors/Samplers/SamplerTests.cs | 22 +++ 19 files changed, 534 insertions(+), 70 deletions(-) create mode 100644 src/ImageProcessor/Colors/ColorDefinitions.cs create mode 100644 src/ImageProcessor/Colors/RgbaComponent.cs create mode 100644 src/ImageProcessor/Filters/Binarization/Threshold.cs create mode 100644 src/ImageProcessor/Samplers/EntropyCrop.cs diff --git a/src/ImageProcessor/Colors/Color.cs b/src/ImageProcessor/Colors/Color.cs index a1ca1817f4..d6a169af5e 100644 --- a/src/ImageProcessor/Colors/Color.cs +++ b/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. /// - public struct Color : IEquatable + public partial struct Color : IEquatable { /// - /// Represents a that has R, G, B, and A values set to zero. + /// Represents an empty that has R, G, B, and A values set to zero. /// public static readonly Color Empty = default(Color); diff --git a/src/ImageProcessor/Colors/ColorDefinitions.cs b/src/ImageProcessor/Colors/ColorDefinitions.cs new file mode 100644 index 0000000000..cc56919408 --- /dev/null +++ b/src/ImageProcessor/Colors/ColorDefinitions.cs @@ -0,0 +1,121 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor +{ + public partial struct Color + { + /// + /// Represents a matching the W3C definition that has an hex value of #F0F8FF. + /// + public static readonly Color AliceBlue = new Color(240 / 255f, 248 / 255f, 1); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAEBD7. + /// + public static readonly Color AntiqueWhite = new Color(250 / 255f, 235 / 255f, 215 / 255f); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FFFF. + /// + public static readonly Color Aqua = new Color(0, 1, 1); + + /// + /// Represents a matching the W3C definition that has an hex value of #7FFFD4. + /// + public static readonly Color AquaMarine = new Color(127 / 255f, 1, 212 / 255f); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0FFFF. + /// + public static readonly Color Azure = new Color(240 / 255f, 1, 1); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5F5DC. + /// + public static readonly Color Beige = new Color(245 / 255f, 245 / 255f, 220 / 255f); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4C4. + /// + public static readonly Color Bisque = new Color(1, 228 / 255f, 196 / 255f); + + /// + /// Represents a matching the W3C definition that has an hex value of #000000. + /// + public static readonly Color Black = new Color(0, 0, 0); + + /// + /// Represents a matching the W3C definition that has an hex value of #0000FF. + /// + public static readonly Color Blue = new Color(0, 0, 1); + + /// + /// Represents a matching the W3C definition that has an hex value of #C0C0C0. + /// + public static readonly Color Silver = new Color(192 / 255f, 192 / 255f, 192 / 255f); + + /// + /// Represents a matching the W3C definition that has an hex value of #808080. + /// + public static readonly Color Gray = new Color(128 / 255f, 128 / 255f, 128 / 255f); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFFF. + /// + public static readonly Color White = new Color(1, 1, 1); + + /// + /// Represents a matching the W3C definition that has an hex value of #800000. + /// + public static readonly Color Maroon = new Color(128 / 255f, 0, 0); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF0000. + /// + public static readonly Color Red = new Color(1, 0, 0); + + /// + /// Represents a matching the W3C definition that has an hex value of #800080. + /// + public static readonly Color Purple = new Color(128 / 255f, 0, 128 / 255f); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF00FF. + /// + public static readonly Color Fuchsia = new Color(1, 0, 1); + + /// + /// Represents a matching the W3C definition that has an hex value of #008000. + /// + public static readonly Color Green = new Color(0, 128 / 255f, 0); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FF00. + /// + public static readonly Color Lime = new Color(0, 1, 0); + + /// + /// Represents a matching the W3C definition that has an hex value of #808000. + /// + public static readonly Color Olive = new Color(128 / 255f, 128 / 255f, 0); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFF00. + /// + public static readonly Color Yellow = new Color(1, 1, 0); + + /// + /// Represents a matching the W3C definition that has an hex value of #000080. + /// + public static readonly Color Navy = new Color(0, 0, 128 / 255f); + + /// + /// Represents a matching the W3C definition that has an hex value of #008080. + /// + public static readonly Color Teal = new Color(0, 128 / 255f, 128 / 255f); + + } +} diff --git a/src/ImageProcessor/Colors/RgbaComponent.cs b/src/ImageProcessor/Colors/RgbaComponent.cs new file mode 100644 index 0000000000..526e43d822 --- /dev/null +++ b/src/ImageProcessor/Colors/RgbaComponent.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor +{ + /// + /// Enumerates the RGBA (red, green, blue, alpha) color components. + /// + public enum RgbaComponent + { + /// + /// The blue component. + /// + B = 0, + + /// + /// The green component. + /// + G = 1, + + /// + /// The red component. + /// + R = 2, + + /// + /// The alpha component. + /// + A = 3 + } +} diff --git a/src/ImageProcessor/Common/Helpers/ImageMaths.cs b/src/ImageProcessor/Common/Helpers/ImageMaths.cs index 97a441d066..405b43d791 100644 --- a/src/ImageProcessor/Common/Helpers/ImageMaths.cs +++ b/src/ImageProcessor/Common/Helpers/ImageMaths.cs @@ -137,6 +137,23 @@ namespace ImageProcessor return translatedPoint; } + /// + /// Gets the bounding from the given points. + /// + /// + /// The designating the top left position. + /// + /// + /// The designating the bottom right position. + /// + /// + /// The bounding . + /// + public static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) + { + return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y); + } + /// /// Calculates the new size after rotation. /// @@ -169,6 +186,124 @@ namespace ImageProcessor return result; } + /// + /// Finds the bounding rectangle based on the first instance of any color component other + /// than the given one. + /// + /// + /// The to search within. + /// + /// + /// The color component value to remove. + /// + /// + /// The channel to test against. + /// + /// + /// The . + /// + 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 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 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 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 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 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); + } + /// /// Ensures that any passed double is correctly rounded to zero /// diff --git a/src/ImageProcessor/Filters/Binarization/Threshold.cs b/src/ImageProcessor/Filters/Binarization/Threshold.cs new file mode 100644 index 0000000000..71deaa25b6 --- /dev/null +++ b/src/ImageProcessor/Filters/Binarization/Threshold.cs @@ -0,0 +1,69 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Filters +{ + using System; + using System.Threading.Tasks; + + /// + /// An to perform binary threshold filtering against an + /// . The mage will be converted to greyscale before thresholding + /// occurrs. + /// + public class Threshold : ParallelImageProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// The threshold to split the image. Must be between 0 and 1. + /// + /// is less than 0 or is greater than 1. + /// + public Threshold(float threshold) + { + Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); + this.Value = threshold; + } + + /// + /// Gets the threshold value. + /// + public float Value { get; } + + /// + protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) + { + new GreyscaleBt709().Apply(source, source, sourceRectangle); + } + + /// + 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; + } + } + }); + } + } +} diff --git a/src/ImageProcessor/Filters/ColorMatrix/Saturation.cs b/src/ImageProcessor/Filters/ColorMatrix/Saturation.cs index 7359698bf4..42b80835d5 100644 --- a/src/ImageProcessor/Filters/ColorMatrix/Saturation.cs +++ b/src/ImageProcessor/Filters/ColorMatrix/Saturation.cs @@ -40,7 +40,7 @@ namespace ImageProcessor.Filters public override Matrix4x4 Matrix => this.matrix; /// - 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; diff --git a/src/ImageProcessor/Filters/Convolution/EdgeDetection/EdgeDetector2DFilter.cs b/src/ImageProcessor/Filters/Convolution/EdgeDetection/EdgeDetector2DFilter.cs index 0c59491456..053afb01c0 100644 --- a/src/ImageProcessor/Filters/Convolution/EdgeDetection/EdgeDetector2DFilter.cs +++ b/src/ImageProcessor/Filters/Convolution/EdgeDetection/EdgeDetector2DFilter.cs @@ -15,7 +15,7 @@ namespace ImageProcessor.Filters public bool Greyscale { get; set; } /// - 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) { diff --git a/src/ImageProcessor/Filters/Convolution/EdgeDetection/EdgeDetectorFilter.cs b/src/ImageProcessor/Filters/Convolution/EdgeDetection/EdgeDetectorFilter.cs index d55be476ec..05eca3fd06 100644 --- a/src/ImageProcessor/Filters/Convolution/EdgeDetection/EdgeDetectorFilter.cs +++ b/src/ImageProcessor/Filters/Convolution/EdgeDetection/EdgeDetectorFilter.cs @@ -15,7 +15,7 @@ namespace ImageProcessor.Filters public bool Greyscale { get; set; } /// - 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) { diff --git a/src/ImageProcessor/Filters/Convolution/GuassianBlur.cs b/src/ImageProcessor/Filters/Convolution/GuassianBlur.cs index 0a8cfe34df..41dec9d1c1 100644 --- a/src/ImageProcessor/Filters/Convolution/GuassianBlur.cs +++ b/src/ImageProcessor/Filters/Convolution/GuassianBlur.cs @@ -82,7 +82,7 @@ namespace ImageProcessor.Filters public override int Parallelism => 1; /// - 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) { diff --git a/src/ImageProcessor/Filters/Convolution/GuassianSharpen.cs b/src/ImageProcessor/Filters/Convolution/GuassianSharpen.cs index 8da64d8702..edc3924e68 100644 --- a/src/ImageProcessor/Filters/Convolution/GuassianSharpen.cs +++ b/src/ImageProcessor/Filters/Convolution/GuassianSharpen.cs @@ -82,7 +82,7 @@ namespace ImageProcessor.Filters public override int Parallelism => 1; /// - 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) { diff --git a/src/ImageProcessor/ParallelImageProcessor.cs b/src/ImageProcessor/ParallelImageProcessor.cs index 998b4e12ad..e005054d38 100644 --- a/src/ImageProcessor/ParallelImageProcessor.cs +++ b/src/ImageProcessor/ParallelImageProcessor.cs @@ -21,7 +21,9 @@ namespace ImageProcessor /// 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. /// /// The source image. Cannot be null. + /// Target image to apply the process to. /// /// The 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 /// /// The structure that specifies the portion of the image object to draw. /// - protected virtual void OnApply(ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + protected virtual void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) { } diff --git a/src/ImageProcessor/Samplers/Crop.cs b/src/ImageProcessor/Samplers/Crop.cs index cd781761d1..bae7a8dd49 100644 --- a/src/ImageProcessor/Samplers/Crop.cs +++ b/src/ImageProcessor/Samplers/Crop.cs @@ -13,13 +13,7 @@ namespace ImageProcessor.Samplers public class Crop : ParallelImageProcessor { /// - 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; diff --git a/src/ImageProcessor/Samplers/EntropyCrop.cs b/src/ImageProcessor/Samplers/EntropyCrop.cs new file mode 100644 index 0000000000..f71067a64d --- /dev/null +++ b/src/ImageProcessor/Samplers/EntropyCrop.cs @@ -0,0 +1,77 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessor.Samplers +{ + using System; + using System.Threading.Tasks; + + using ImageProcessor.Filters; + + /// + /// Provides methods to allow the cropping of an image to preserve areas of highest + /// entropy. + /// + public class EntropyCrop : ParallelImageProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// The threshold to split the image. Must be between 0 and 1. + /// + /// is less than 0 or is greater than 1. + /// + public EntropyCrop(float threshold) + { + Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); + this.Value = threshold; + } + + /// + /// Gets the threshold value. + /// + public float Value { get; } + + /// + 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]); + + } + + /// + 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]; + } + } + }); + } + } +} diff --git a/src/ImageProcessor/Samplers/ImageSampleExtensions.cs b/src/ImageProcessor/Samplers/ImageSampleExtensions.cs index 96e0b3c5f3..02b77e423b 100644 --- a/src/ImageProcessor/Samplers/ImageSampleExtensions.cs +++ b/src/ImageProcessor/Samplers/ImageSampleExtensions.cs @@ -10,6 +10,55 @@ namespace ImageProcessor.Samplers /// public static class ImageSampleExtensions { + /// + /// Crops an image to the given width and height. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// The + public static Image Crop(this Image source, int width, int height) + { + return Crop(source, width, height, source.Bounds); + } + + /// + /// Crops an image to the given width and height with the given source rectangle. + /// + /// If the source rectangle is smaller than the target dimensions then the + /// area within the source is resized performing a zoomed crop. + /// + /// + /// The image to crop. + /// The target image width. + /// The target image height. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The + 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()); + } + + /// + /// Crops an image to the area of greatest entropy. + /// + /// The image to crop. + /// The threshold for entropic density. + /// The + public static Image EntropyCrop(this Image source, float threshold = .5f) + { + return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, new EntropyCrop(threshold)); + } + /// /// Resizes an image to the given width and height. /// @@ -62,43 +111,5 @@ namespace ImageProcessor.Samplers { return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, new Resampler(new RobidouxResampler()) { Angle = degrees }); } - - /// - /// Crops an image to the given width and height. - /// - /// The image to resize. - /// The target image width. - /// The target image height. - /// The - public static Image Crop(this Image source, int width, int height) - { - return Crop(source, width, height, source.Bounds); - } - - /// - /// Crops an image to the given width and height with the given source rectangle. - /// - /// If the source rectangle is smaller than the target dimensions then the - /// area within the source is resized performing a zoomed crop. - /// - /// - /// The image to resize. - /// The target image width. - /// The target image height. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The - 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()); - } } } diff --git a/src/ImageProcessor/Samplers/Resampler.cs b/src/ImageProcessor/Samplers/Resampler.cs index 2b81db34e8..c18843c250 100644 --- a/src/ImageProcessor/Samplers/Resampler.cs +++ b/src/ImageProcessor/Samplers/Resampler.cs @@ -75,7 +75,7 @@ namespace ImageProcessor.Samplers } /// - 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); diff --git a/src/ImageProcessor/project.json b/src/ImageProcessor/project.json index eb027b73ad..22e1e36a49 100644 --- a/src/ImageProcessor/project.json +++ b/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" }, diff --git a/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs b/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs index 496ae75e49..75508e3d3c 100644 --- a/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs +++ b/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) } }; diff --git a/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs b/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs index f901c441d6..f7e848dfe7 100644 --- a/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs +++ b/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs @@ -20,7 +20,7 @@ namespace ImageProcessor.Tests public static readonly List Files = new List { //"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", diff --git a/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs index 7c8b6e36b6..b5ff5225fc 100644 --- a/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs +++ b/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() {