diff --git a/src/ImageProcessorCore - Copy/Bootstrapper.cs b/src/ImageProcessorCore - Copy/Bootstrapper.cs new file mode 100644 index 0000000000..5f2978534c --- /dev/null +++ b/src/ImageProcessorCore - Copy/Bootstrapper.cs @@ -0,0 +1,124 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Text; + + using ImageProcessorCore.Formats; + + /// + /// Provides initialization code which allows extending the library. + /// + public class Bootstrapper + { + /// + /// A new instance Initializes a new instance of the class. + /// with lazy initialization. + /// + private static readonly Lazy Lazy = new Lazy(() => new Bootstrapper()); + + /// + /// The default list of supported + /// + private readonly List imageFormats; + + private readonly Dictionary pixelAccessors; + + /// + /// Prevents a default instance of the class from being created. + /// + private Bootstrapper() + { + this.imageFormats = new List + { + new BmpFormat(), + new JpegFormat(), + new PngFormat(), + new GifFormat() + }; + + this.pixelAccessors = new Dictionary + { + { typeof(Bgra32), typeof(Bgra32PixelAccessor) } + }; + } + + /// + /// Gets the current bootstrapper instance. + /// + public static Bootstrapper Instance = Lazy.Value; + + /// + /// Gets the list of supported + /// + public IReadOnlyCollection ImageFormats => new ReadOnlyCollection(this.imageFormats); + + /// + /// Adds a new to the collection of supported image formats. + /// + /// The new format to add. + public void AddImageFormat(IImageFormat format) + { + this.imageFormats.Add(format); + } + + /// + /// Gets an instance of the correct for the packed vector. + /// + /// The type of pixel data. + /// The image + /// The + public IPixelAccessor GetPixelAccessor(Image image) + where TPackedVector : IPackedVector + { + Type packed = typeof(TPackedVector); + if (!this.pixelAccessors.ContainsKey(packed)) + { + // TODO: Double check this. It should work... + return (IPixelAccessor)Activator.CreateInstance(this.pixelAccessors[packed], image); + } + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("PixelAccessor cannot be loaded. Available accessors:"); + + foreach (Type value in this.pixelAccessors.Values) + { + stringBuilder.AppendLine("-" + value.Name); + } + + throw new NotSupportedException(stringBuilder.ToString()); + } + + /// + /// Gets an instance of the correct for the packed vector. + /// + /// The type of pixel data. + /// The image + /// The + public IPixelAccessor GetPixelAccessor(ImageFrame image) + where TPackedVector : IPackedVector + { + Type packed = typeof(TPackedVector); + if (!this.pixelAccessors.ContainsKey(packed)) + { + return (IPixelAccessor)Activator.CreateInstance(this.pixelAccessors[packed], image); + } + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("PixelAccessor cannot be loaded. Available accessors:"); + + foreach (Type value in this.pixelAccessors.Values) + { + stringBuilder.AppendLine("-" + value.Name); + } + + throw new NotSupportedException(stringBuilder.ToString()); + } + } +} diff --git a/src/ImageProcessorCore/Colors/Color.cs b/src/ImageProcessorCore - Copy/Colors/Color.cs similarity index 100% rename from src/ImageProcessorCore/Colors/Color.cs rename to src/ImageProcessorCore - Copy/Colors/Color.cs diff --git a/src/ImageProcessorCore/Colors/ColorConstants.cs b/src/ImageProcessorCore - Copy/Colors/ColorConstants.cs similarity index 100% rename from src/ImageProcessorCore/Colors/ColorConstants.cs rename to src/ImageProcessorCore - Copy/Colors/ColorConstants.cs diff --git a/src/ImageProcessorCore/Colors/ColorDefinitions.cs b/src/ImageProcessorCore - Copy/Colors/ColorDefinitions.cs similarity index 100% rename from src/ImageProcessorCore/Colors/ColorDefinitions.cs rename to src/ImageProcessorCore - Copy/Colors/ColorDefinitions.cs diff --git a/src/ImageProcessorCore/Colors/ColorTransforms.cs b/src/ImageProcessorCore - Copy/Colors/ColorTransforms.cs similarity index 100% rename from src/ImageProcessorCore/Colors/ColorTransforms.cs rename to src/ImageProcessorCore - Copy/Colors/ColorTransforms.cs diff --git a/src/ImageProcessorCore/Colors/ColorspaceTransforms.cs b/src/ImageProcessorCore - Copy/Colors/ColorspaceTransforms.cs similarity index 100% rename from src/ImageProcessorCore/Colors/ColorspaceTransforms.cs rename to src/ImageProcessorCore - Copy/Colors/ColorspaceTransforms.cs diff --git a/src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs b/src/ImageProcessorCore - Copy/Colors/Colorspaces/Bgra32.cs similarity index 100% rename from src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs rename to src/ImageProcessorCore - Copy/Colors/Colorspaces/Bgra32.cs diff --git a/src/ImageProcessorCore/Colors/Colorspaces/CieLab.cs b/src/ImageProcessorCore - Copy/Colors/Colorspaces/CieLab.cs similarity index 100% rename from src/ImageProcessorCore/Colors/Colorspaces/CieLab.cs rename to src/ImageProcessorCore - Copy/Colors/Colorspaces/CieLab.cs diff --git a/src/ImageProcessorCore/Colors/Colorspaces/CieXyz.cs b/src/ImageProcessorCore - Copy/Colors/Colorspaces/CieXyz.cs similarity index 100% rename from src/ImageProcessorCore/Colors/Colorspaces/CieXyz.cs rename to src/ImageProcessorCore - Copy/Colors/Colorspaces/CieXyz.cs diff --git a/src/ImageProcessorCore/Colors/Colorspaces/Cmyk.cs b/src/ImageProcessorCore - Copy/Colors/Colorspaces/Cmyk.cs similarity index 100% rename from src/ImageProcessorCore/Colors/Colorspaces/Cmyk.cs rename to src/ImageProcessorCore - Copy/Colors/Colorspaces/Cmyk.cs diff --git a/src/ImageProcessorCore/Colors/Colorspaces/Hsl.cs b/src/ImageProcessorCore - Copy/Colors/Colorspaces/Hsl.cs similarity index 100% rename from src/ImageProcessorCore/Colors/Colorspaces/Hsl.cs rename to src/ImageProcessorCore - Copy/Colors/Colorspaces/Hsl.cs diff --git a/src/ImageProcessorCore/Colors/Colorspaces/Hsv.cs b/src/ImageProcessorCore - Copy/Colors/Colorspaces/Hsv.cs similarity index 100% rename from src/ImageProcessorCore/Colors/Colorspaces/Hsv.cs rename to src/ImageProcessorCore - Copy/Colors/Colorspaces/Hsv.cs diff --git a/src/ImageProcessorCore/Colors/Colorspaces/YCbCr.cs b/src/ImageProcessorCore - Copy/Colors/Colorspaces/YCbCr.cs similarity index 100% rename from src/ImageProcessorCore/Colors/Colorspaces/YCbCr.cs rename to src/ImageProcessorCore - Copy/Colors/Colorspaces/YCbCr.cs diff --git a/src/ImageProcessorCore/Colors/IAlmostEquatable.cs b/src/ImageProcessorCore - Copy/Colors/IAlmostEquatable.cs similarity index 100% rename from src/ImageProcessorCore/Colors/IAlmostEquatable.cs rename to src/ImageProcessorCore - Copy/Colors/IAlmostEquatable.cs diff --git a/src/ImageProcessorCore/Colors/RgbaComponent.cs b/src/ImageProcessorCore - Copy/Colors/RgbaComponent.cs similarity index 100% rename from src/ImageProcessorCore/Colors/RgbaComponent.cs rename to src/ImageProcessorCore - Copy/Colors/RgbaComponent.cs diff --git a/src/ImageProcessorCore - Copy/Common/Exceptions/ImageFormatException.cs b/src/ImageProcessorCore - Copy/Common/Exceptions/ImageFormatException.cs new file mode 100644 index 0000000000..87fb381597 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Common/Exceptions/ImageFormatException.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + + /// + /// The exception that is thrown when the library tries to load + /// an image, which has an invalid format. + /// + public class ImageFormatException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public ImageFormatException() + { + } + + /// + /// Initializes a new instance of the class with the name of the + /// parameter that causes this exception. + /// + /// The error message that explains the reason for this exception. + public ImageFormatException(string errorMessage) + : base(errorMessage) + { + } + + /// + /// Initializes a new instance of the class with a specified + /// error message and the exception that is the cause of this exception. + /// + /// The error message that explains the reason for this exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) + /// if no inner exception is specified. + public ImageFormatException(string errorMessage, Exception innerException) + : base(errorMessage, innerException) + { + } + } +} diff --git a/src/ImageProcessorCore - Copy/Common/Exceptions/ImageProcessingException.cs b/src/ImageProcessorCore - Copy/Common/Exceptions/ImageProcessingException.cs new file mode 100644 index 0000000000..7899dcf44c --- /dev/null +++ b/src/ImageProcessorCore - Copy/Common/Exceptions/ImageProcessingException.cs @@ -0,0 +1,44 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + + /// + /// The exception that is thrown when an error occurs when applying a process to an image. + /// + public class ImageProcessingException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public ImageProcessingException() + { + } + + /// + /// Initializes a new instance of the class with the name of the + /// parameter that causes this exception. + /// + /// The error message that explains the reason for this exception. + public ImageProcessingException(string errorMessage) + : base(errorMessage) + { + } + + /// + /// Initializes a new instance of the class with a specified + /// error message and the exception that is the cause of this exception. + /// + /// The error message that explains the reason for this exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) + /// if no inner exception is specified. + public ImageProcessingException(string errorMessage, Exception innerException) + : base(errorMessage, innerException) + { + } + } +} diff --git a/src/ImageProcessorCore - Copy/Common/Extensions/ByteExtensions.cs b/src/ImageProcessorCore - Copy/Common/Extensions/ByteExtensions.cs new file mode 100644 index 0000000000..05b71bb2f6 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Common/Extensions/ByteExtensions.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + + /// + /// Extension methods for the struct. + /// + internal static class ByteExtensions + { + /// + /// Converts a byte array to a new array where each value in the original array is represented + /// by a the specified number of bits. + /// + /// The bytes to convert from. Cannot be null. + /// The number of bits per value. + /// The resulting array. Is never null. + /// is null. + /// is less than or equals than zero. + public static byte[] ToArrayByBitsLength(this byte[] bytes, int bits) + { + Guard.NotNull(bytes, "bytes"); + Guard.MustBeGreaterThan(bits, 0, "bits"); + + byte[] result; + + if (bits < 8) + { + result = new byte[bytes.Length * 8 / bits]; + + // BUGFIX I dont think it should be there, but I am not sure if it breaks something else + // int factor = (int)Math.Pow(2, bits) - 1; + int mask = 0xFF >> (8 - bits); + int resultOffset = 0; + + foreach (byte b in bytes) + { + for (int shift = 0; shift < 8; shift += bits) + { + int colorIndex = (b >> (8 - bits - shift)) & mask; // * (255 / factor); + + result[resultOffset] = (byte)colorIndex; + + resultOffset++; + } + } + } + else + { + result = bytes; + } + + return result; + } + } +} diff --git a/src/ImageProcessorCore - Copy/Common/Extensions/ComparableExtensions.cs b/src/ImageProcessorCore - Copy/Common/Extensions/ComparableExtensions.cs new file mode 100644 index 0000000000..cb0288fb7b --- /dev/null +++ b/src/ImageProcessorCore - Copy/Common/Extensions/ComparableExtensions.cs @@ -0,0 +1,145 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + + /// + /// Extension methods for classes that implement . + /// + internal static class ComparableExtensions + { + /// + /// Restricts a to be within a specified range. + /// + /// The The value to clamp. + /// The minimum value. If value is less than min, min will be returned. + /// The maximum value. If value is greater than max, max will be returned. + /// + /// The representing the clamped value. + /// + public static byte Clamp(this byte value, byte min, byte max) + { + // Order is important here as someone might set min to higher than max. + if (value > max) + { + return max; + } + + if (value < min) + { + return min; + } + + return value; + } + + /// + /// Restricts a to be within a specified range. + /// + /// The The value to clamp. + /// The minimum value. If value is less than min, min will be returned. + /// The maximum value. If value is greater than max, max will be returned. + /// + /// The representing the clamped value. + /// + public static int Clamp(this int value, int min, int max) + { + if (value > max) + { + return max; + } + + if (value < min) + { + return min; + } + + return value; + } + + /// + /// Restricts a to be within a specified range. + /// + /// The The value to clamp. + /// The minimum value. If value is less than min, min will be returned. + /// The maximum value. If value is greater than max, max will be returned. + /// + /// The representing the clamped value. + /// + public static float Clamp(this float value, float min, float max) + { + if (value > max) + { + return max; + } + + if (value < min) + { + return min; + } + + return value; + } + + /// + /// Restricts a to be within a specified range. + /// + /// The The value to clamp. + /// The minimum value. If value is less than min, min will be returned. + /// The maximum value. If value is greater than max, max will be returned. + /// + /// The representing the clamped value. + /// + public static double Clamp(this double value, double min, double max) + { + if (value > max) + { + return max; + } + + if (value < min) + { + return min; + } + + return value; + } + + /// + /// Converts an to a first restricting the value between the + /// minimum and maximum allowable ranges. + /// + /// The this method extends. + /// The + public static byte ToByte(this int value) + { + return (byte)value.Clamp(0, 255); + } + + /// + /// Converts an to a first restricting the value between the + /// minimum and maximum allowable ranges. + /// + /// The this method extends. + /// The + public static byte ToByte(this float value) + { + return (byte)value.Clamp(0, 255); + } + + /// + /// Converts an to a first restricting the value between the + /// minimum and maximum allowable ranges. + /// + /// The this method extends. + /// The + public static byte ToByte(this double value) + { + return (byte)value.Clamp(0, 255); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Common/Extensions/EnumerableExtensions.cs b/src/ImageProcessorCore - Copy/Common/Extensions/EnumerableExtensions.cs new file mode 100644 index 0000000000..107320412e --- /dev/null +++ b/src/ImageProcessorCore - Copy/Common/Extensions/EnumerableExtensions.cs @@ -0,0 +1,88 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.Collections.Generic; + + /// + /// Encapsulates a series of time saving extension methods to the interface. + /// + public static class EnumerableExtensions + { + /// + /// Generates a sequence of integral numbers within a specified range. + /// + /// + /// The start index, inclusive. + /// + /// + /// The end index, exclusive. + /// + /// + /// The incremental step. + /// + /// + /// The that contains a range of sequential integral numbers. + /// + public static IEnumerable SteppedRange(int fromInclusive, int toExclusive, int step) + { + // Borrowed from Enumerable.Range + long num = (fromInclusive + toExclusive) - 1L; + if ((toExclusive < 0) || (num > 0x7fffffffL)) + { + throw new ArgumentOutOfRangeException(nameof(toExclusive)); + } + + return RangeIterator(fromInclusive, i => i < toExclusive, step); + } + + /// + /// Generates a sequence of integral numbers within a specified range. + /// + /// + /// The start index, inclusive. + /// + /// + /// A method that has one parameter and returns a calculating the end index + /// + /// + /// The incremental step. + /// + /// + /// The that contains a range of sequential integral numbers. + /// + public static IEnumerable SteppedRange(int fromInclusive, Func toDelegate, int step) + { + return RangeIterator(fromInclusive, toDelegate, step); + } + + /// + /// Generates a sequence of integral numbers within a specified range. + /// + /// + /// The start index, inclusive. + /// + /// + /// A method that has one parameter and returns a calculating the end index + /// + /// + /// The incremental step. + /// + /// + /// The that contains a range of sequential integral numbers. + /// + private static IEnumerable RangeIterator(int fromInclusive, Func toDelegate, int step) + { + int i = fromInclusive; + while (toDelegate(i)) + { + yield return i; + i += step; + } + } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/Common/Helpers/Guard.cs b/src/ImageProcessorCore - Copy/Common/Helpers/Guard.cs new file mode 100644 index 0000000000..96c7023d43 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Common/Helpers/Guard.cs @@ -0,0 +1,192 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +// +// Provides methods to protect against invalid parameters. +// +// -------------------------------------------------------------------------------------------------------------------- + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("ImageProcessorCore.Tests")] +namespace ImageProcessorCore +{ + using System; + using System.Diagnostics; + + /// + /// Provides methods to protect against invalid parameters. + /// + [DebuggerStepThrough] + internal static class Guard + { + /// + /// Verifies, that the method parameter with specified object value is not null + /// and throws an exception if it is found to be so. + /// + /// + /// The target object, which cannot be null. + /// + /// + /// The name of the parameter that is to be checked. + /// + /// + /// The error message, if any to add to the exception. + /// + /// + /// is null + /// + public static void NotNull(object target, string parameterName, string message = "") + { + if (target == null) + { + if (string.IsNullOrWhiteSpace(message)) + { + throw new ArgumentNullException(parameterName, message); + } + + throw new ArgumentNullException(parameterName); + } + } + + /// + /// Verifies, that the string method parameter with specified object value and message + /// is not null, not empty and does not contain only blanks and throws an exception + /// if the object is null. + /// + /// The target string, which should be checked against being null or empty. + /// Name of the parameter. + /// + /// is null. + /// + /// + /// is + /// empty or contains only blanks. + /// + public static void NotNullOrEmpty(string target, string parameterName) + { + if (target == null) + { + throw new ArgumentNullException(parameterName); + } + + if (string.IsNullOrWhiteSpace(target)) + { + throw new ArgumentException("Value cannot be null or empty and cannot contain only blanks.", parameterName); + } + } + + /// + /// Verifies that the specified value is less than a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is greater than the maximum value. + /// + public static void MustBeLessThan(TValue value, TValue max, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(max) >= 0) + { + throw new ArgumentOutOfRangeException( + parameterName, + $"Value must be less than {max}."); + } + } + + /// + /// Verifies that the specified value is less than or equal to a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is greater than the maximum value. + /// + public static void MustBeLessThanOrEqualTo(TValue value, TValue max, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(max) > 0) + { + throw new ArgumentOutOfRangeException( + parameterName, + $"Value must be less than or equal to {max}."); + } + } + + /// + /// Verifies that the specified value is greater than a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is less than the minimum value. + /// + public static void MustBeGreaterThan(TValue value, TValue min, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(min) <= 0) + { + throw new ArgumentOutOfRangeException( + parameterName, + $"Value must be greater than {min}."); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is less than the minimum value. + /// + public static void MustBeGreaterThanOrEqualTo(TValue value, TValue min, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(min) < 0) + { + throw new ArgumentOutOfRangeException( + parameterName, + $"Value must be greater than or equal to {min}."); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value and less than + /// or equal to a maximum value and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is less than the minimum value of greater than the maximum value. + /// + public static void MustBeBetweenOrEqualTo(TValue value, TValue min, TValue max, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(min) < 0 || value.CompareTo(max) > 0) + { + throw new ArgumentOutOfRangeException( + parameterName, + $"Value must be greater than or equal to {min} and less than or equal to {max}."); + } + } + } +} diff --git a/src/ImageProcessorCore - Copy/Common/Helpers/ImageMaths.cs b/src/ImageProcessorCore - Copy/Common/Helpers/ImageMaths.cs new file mode 100644 index 0000000000..051b75f70f --- /dev/null +++ b/src/ImageProcessorCore - Copy/Common/Helpers/ImageMaths.cs @@ -0,0 +1,289 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.Linq; + using System.Numerics; + + /// + /// Provides common mathematical methods. + /// + internal static class ImageMaths + { + /// + /// Returns how many bits are required to store the specified number of colors. + /// Performs a Log2() on the value. + /// + /// The number of colors. + /// + /// The + /// + public static int GetBitsNeededForColorDepth(int colors) + { + return (int)Math.Ceiling(Math.Log(colors, 2)); + } + + /// + /// Implementation of 1D Gaussian G(x) function + /// + /// The x provided to G(x). + /// The spread of the blur. + /// The Gaussian G(x) + public static float Gaussian(float x, float sigma) + { + const float Numerator = 1.0f; + float denominator = (float)(Math.Sqrt(2 * Math.PI) * sigma); + + float exponentNumerator = -x * x; + float exponentDenominator = (float)(2 * Math.Pow(sigma, 2)); + + float left = Numerator / denominator; + float right = (float)Math.Exp(exponentNumerator / exponentDenominator); + + return left * right; + } + + /// + /// Returns the result of a B-C filter against the given value. + /// + /// + /// The value to process. + /// The B-Spline curve variable. + /// The Cardinal curve variable. + /// + /// The . + /// + public static float GetBcValue(float x, float b, float c) + { + float temp; + + if (x < 0) + { + x = -x; + } + + temp = x * x; + if (x < 1) + { + x = ((12 - (9 * b) - (6 * c)) * (x * temp)) + ((-18 + (12 * b) + (6 * c)) * temp) + (6 - (2 * b)); + return x / 6; + } + + if (x < 2) + { + x = ((-b - (6 * c)) * (x * temp)) + (((6 * b) + (30 * c)) * temp) + (((-12 * b) - (48 * c)) * x) + ((8 * b) + (24 * c)); + return x / 6; + } + + return 0; + } + + /// + /// Gets the result of a sine cardinal function for the given value. + /// + /// The value to calculate the result for. + /// + /// The . + /// + public static float SinC(float x) + { + const float Epsilon = .00001f; + + if (Math.Abs(x) > Epsilon) + { + x *= (float)Math.PI; + return Clean((float)Math.Sin(x) / x); + } + + return 1.0f; + } + + /// + /// Returns the given degrees converted to radians. + /// + /// The angle in degrees. + /// + /// The representing the degree as radians. + /// + public static float DegreesToRadians(float degrees) + { + return degrees * (float)(Math.PI / 180); + } + + /// + /// 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); + } + + /// + /// Gets the bounding from the given matrix. + /// + /// The source rectangle. + /// The transformation matrix. + /// + /// The . + /// + public static Rectangle GetBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix) + { + Vector2 leftTop = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix); + Vector2 rightTop = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix); + Vector2 leftBottom = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix); + Vector2 rightBottom = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix); + + Vector2[] allCorners = { leftTop, rightTop, leftBottom, rightBottom }; + float extentX = allCorners.Select(v => v.X).Max() - allCorners.Select(v => v.X).Min(); + float extentY = allCorners.Select(v => v.Y).Max() - allCorners.Select(v => v.Y).Min(); + return new Rectangle(0, 0, (int)extentX, (int)extentY); + } + + /// + /// 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 = (pixels, x, y, b) => Math.Abs(pixels[x, y].R - b) > Epsilon; + break; + + case RgbaComponent.G: + delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].G - b) > Epsilon; + break; + + case RgbaComponent.A: + delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].A - b) > Epsilon; + break; + + default: + delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].B - b) > Epsilon; + break; + } + + Func getMinY = pixels => + { + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + if (delegateFunc(pixels, x, y, componentValue)) + { + return y; + } + } + } + + return 0; + }; + + Func getMaxY = pixels => + { + for (int y = height - 1; y > -1; y--) + { + for (int x = 0; x < width; x++) + { + if (delegateFunc(pixels, x, y, componentValue)) + { + return y; + } + } + } + + return height; + }; + + Func getMinX = pixels => + { + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + if (delegateFunc(pixels, x, y, componentValue)) + { + return x; + } + } + } + + return 0; + }; + + Func getMaxX = pixels => + { + for (int x = width - 1; x > -1; x--) + { + for (int y = 0; y < height; y++) + { + if (delegateFunc(pixels, x, y, componentValue)) + { + return x; + } + } + } + + return height; + }; + + using (PixelAccessor bitmapPixels = bitmap.Lock()) + { + topLeft.Y = getMinY(bitmapPixels); + topLeft.X = getMinX(bitmapPixels); + bottomRight.Y = (getMaxY(bitmapPixels) + 1).Clamp(0, height); + bottomRight.X = (getMaxX(bitmapPixels) + 1).Clamp(0, width); + } + + return GetBoundingRectangle(topLeft, bottomRight); + } + + /// + /// Ensures that any passed double is correctly rounded to zero + /// + /// The value to clean. + /// + /// The + /// . + private static float Clean(float x) + { + const float Epsilon = .00001f; + + if (Math.Abs(x) < Epsilon) + { + return 0f; + } + + return x; + } + } +} diff --git a/src/ImageProcessorCore/Filters/Alpha.cs b/src/ImageProcessorCore - Copy/Filters/Alpha.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Alpha.cs rename to src/ImageProcessorCore - Copy/Filters/Alpha.cs diff --git a/src/ImageProcessorCore/Filters/BackgroundColor.cs b/src/ImageProcessorCore - Copy/Filters/BackgroundColor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/BackgroundColor.cs rename to src/ImageProcessorCore - Copy/Filters/BackgroundColor.cs diff --git a/src/ImageProcessorCore/Filters/BlackWhite.cs b/src/ImageProcessorCore - Copy/Filters/BlackWhite.cs similarity index 100% rename from src/ImageProcessorCore/Filters/BlackWhite.cs rename to src/ImageProcessorCore - Copy/Filters/BlackWhite.cs diff --git a/src/ImageProcessorCore/Filters/Blend.cs b/src/ImageProcessorCore - Copy/Filters/Blend.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Blend.cs rename to src/ImageProcessorCore - Copy/Filters/Blend.cs diff --git a/src/ImageProcessorCore/Filters/BoxBlur.cs b/src/ImageProcessorCore - Copy/Filters/BoxBlur.cs similarity index 100% rename from src/ImageProcessorCore/Filters/BoxBlur.cs rename to src/ImageProcessorCore - Copy/Filters/BoxBlur.cs diff --git a/src/ImageProcessorCore/Filters/Brightness.cs b/src/ImageProcessorCore - Copy/Filters/Brightness.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Brightness.cs rename to src/ImageProcessorCore - Copy/Filters/Brightness.cs diff --git a/src/ImageProcessorCore/Filters/ColorBlindness.cs b/src/ImageProcessorCore - Copy/Filters/ColorBlindness.cs similarity index 100% rename from src/ImageProcessorCore/Filters/ColorBlindness.cs rename to src/ImageProcessorCore - Copy/Filters/ColorBlindness.cs diff --git a/src/ImageProcessorCore/Filters/Contrast.cs b/src/ImageProcessorCore - Copy/Filters/Contrast.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Contrast.cs rename to src/ImageProcessorCore - Copy/Filters/Contrast.cs diff --git a/src/ImageProcessorCore/Filters/DetectEdges.cs b/src/ImageProcessorCore - Copy/Filters/DetectEdges.cs similarity index 100% rename from src/ImageProcessorCore/Filters/DetectEdges.cs rename to src/ImageProcessorCore - Copy/Filters/DetectEdges.cs diff --git a/src/ImageProcessorCore/Filters/Greyscale.cs b/src/ImageProcessorCore - Copy/Filters/Greyscale.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Greyscale.cs rename to src/ImageProcessorCore - Copy/Filters/Greyscale.cs diff --git a/src/ImageProcessorCore/Filters/GuassianBlur.cs b/src/ImageProcessorCore - Copy/Filters/GuassianBlur.cs similarity index 100% rename from src/ImageProcessorCore/Filters/GuassianBlur.cs rename to src/ImageProcessorCore - Copy/Filters/GuassianBlur.cs diff --git a/src/ImageProcessorCore/Filters/GuassianSharpen.cs b/src/ImageProcessorCore - Copy/Filters/GuassianSharpen.cs similarity index 100% rename from src/ImageProcessorCore/Filters/GuassianSharpen.cs rename to src/ImageProcessorCore - Copy/Filters/GuassianSharpen.cs diff --git a/src/ImageProcessorCore/Filters/Hue.cs b/src/ImageProcessorCore - Copy/Filters/Hue.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Hue.cs rename to src/ImageProcessorCore - Copy/Filters/Hue.cs diff --git a/src/ImageProcessorCore/Filters/Invert.cs b/src/ImageProcessorCore - Copy/Filters/Invert.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Invert.cs rename to src/ImageProcessorCore - Copy/Filters/Invert.cs diff --git a/src/ImageProcessorCore/Filters/Kodachrome.cs b/src/ImageProcessorCore - Copy/Filters/Kodachrome.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Kodachrome.cs rename to src/ImageProcessorCore - Copy/Filters/Kodachrome.cs diff --git a/src/ImageProcessorCore/Filters/Lomograph.cs b/src/ImageProcessorCore - Copy/Filters/Lomograph.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Lomograph.cs rename to src/ImageProcessorCore - Copy/Filters/Lomograph.cs diff --git a/src/ImageProcessorCore/Filters/Options/ColorBlindness.cs b/src/ImageProcessorCore - Copy/Filters/Options/ColorBlindness.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Options/ColorBlindness.cs rename to src/ImageProcessorCore - Copy/Filters/Options/ColorBlindness.cs diff --git a/src/ImageProcessorCore/Filters/Pixelate.cs b/src/ImageProcessorCore - Copy/Filters/Pixelate.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Pixelate.cs rename to src/ImageProcessorCore - Copy/Filters/Pixelate.cs diff --git a/src/ImageProcessorCore/Filters/Polaroid.cs b/src/ImageProcessorCore - Copy/Filters/Polaroid.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Polaroid.cs rename to src/ImageProcessorCore - Copy/Filters/Polaroid.cs diff --git a/src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/AlphaProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/AlphaProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/BackgroundColorProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/BackgroundColorProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/BackgroundColorProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/BackgroundColorProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Binarization/ThresholdProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Binarization/ThresholdProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/BlendProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/BlendProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/BlendProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/BlendProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/BrightnessProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/BrightnessProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/README.md b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/README.md similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/README.md rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/README.md diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleMode.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleMode.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleMode.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleMode.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/HueProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/HueProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/KodachromeProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/KodachromeProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/KodachromeProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/KodachromeProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/LomographProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/LomographProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/LomographProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/LomographProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/PolaroidProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/PolaroidProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/PolaroidProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/PolaroidProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/SaturationProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/SaturationProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SepiaProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/SepiaProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/SepiaProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/SepiaProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ContrastProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ContrastProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/BoxBlurProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/BoxBlurProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/BoxBlurProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/Convolution2DFilter.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/Convolution2DFilter.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2PassFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/Convolution2PassFilter.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2PassFilter.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/Convolution2PassFilter.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/ConvolutionFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/ConvolutionFilter.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/ConvolutionFilter.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/ConvolutionFilter.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KirschProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/KirschProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KirschProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/KirschProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/PrewittProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/PrewittProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/PrewittProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/PrewittProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/ScharrProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/ScharrProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/ScharrProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/ScharrProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianBlurProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/GuassianBlurProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/GuassianBlurProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/GuassianBlurProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianSharpenProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/GuassianSharpenProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/GuassianSharpenProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/GuassianSharpenProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/GlowProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/GlowProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/InvertProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/InvertProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/PixelateProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/PixelateProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/PixelateProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/PixelateProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/VignetteProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/VignetteProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Saturation.cs b/src/ImageProcessorCore - Copy/Filters/Saturation.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Saturation.cs rename to src/ImageProcessorCore - Copy/Filters/Saturation.cs diff --git a/src/ImageProcessorCore/Filters/Sepia.cs b/src/ImageProcessorCore - Copy/Filters/Sepia.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Sepia.cs rename to src/ImageProcessorCore - Copy/Filters/Sepia.cs diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpBitsPerPixel.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpBitsPerPixel.cs new file mode 100644 index 0000000000..e7de3bc295 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpBitsPerPixel.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Enumerates the available bits per pixel for bitmap. + /// + public enum BmpBitsPerPixel + { + /// + /// 24 bits per pixel. Each pixel consists of 3 bytes. + /// + Pixel24 = 3, + + /// + /// 32 bits per pixel. Each pixel consists of 4 bytes. + /// + Pixel32 = 4, + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpCompression.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpCompression.cs new file mode 100644 index 0000000000..de3c66495d --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpCompression.cs @@ -0,0 +1,63 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Defines how the compression type of the image data + /// in the bitmap file. + /// + internal enum BmpCompression + { + /// + /// Each image row has a multiple of four elements. If the + /// row has less elements, zeros will be added at the right side. + /// The format depends on the number of bits, stored in the info header. + /// If the number of bits are one, four or eight each pixel data is + /// a index to the palette. If the number of bits are sixteen, + /// twenty-four or thirty-two each pixel contains a color. + /// + RGB = 0, + + /// + /// Two bytes are one data record. If the first byte is not zero, the + /// next two half bytes will be repeated as much as the value of the first byte. + /// If the first byte is zero, the record has different meanings, depending + /// on the second byte. If the second byte is zero, it is the end of the row, + /// if it is one, it is the end of the image. + /// Not supported at the moment. + /// + RLE8 = 1, + + /// + /// Two bytes are one data record. If the first byte is not zero, the + /// next byte will be repeated as much as the value of the first byte. + /// If the first byte is zero, the record has different meanings, depending + /// on the second byte. If the second byte is zero, it is the end of the row, + /// if it is one, it is the end of the image. + /// Not supported at the moment. + /// + RLE4 = 2, + + /// + /// Each image row has a multiple of four elements. If the + /// row has less elements, zeros will be added at the right side. + /// Not supported at the moment. + /// + BitFields = 3, + + /// + /// The bitmap contains a JPG image. + /// Not supported at the moment. + /// + JPEG = 4, + + /// + /// The bitmap contains a PNG image. + /// Not supported at the moment. + /// + PNG = 5 + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoder.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoder.cs new file mode 100644 index 0000000000..03d45f1122 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoder.cs @@ -0,0 +1,82 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + + /// + /// Image decoder for generating an image out of a Windows bitmap stream. + /// + /// + /// Does not support the following formats at the moment: + /// + /// JPG + /// PNG + /// RLE4 + /// RLE8 + /// BitFields + /// + /// Formats will be supported in a later releases. We advise always + /// to use only 24 Bit Windows bitmaps. + /// + public class BmpDecoder : IImageDecoder + { + /// + /// Gets the size of the header for this image type. + /// + /// The size of the header. + public int HeaderSize => 2; + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file extension. + /// + /// True if the decoder supports the file extension; otherwise, false. + /// + public bool IsSupportedFileExtension(string extension) + { + Guard.NotNullOrEmpty(extension, "extension"); + + extension = extension.StartsWith(".") ? extension.Substring(1) : extension; + + return extension.Equals("BMP", StringComparison.OrdinalIgnoreCase) + || extension.Equals("DIP", StringComparison.OrdinalIgnoreCase); + } + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file header. + /// + /// True if the decoder supports the file header; otherwise, false. + /// + public bool IsSupportedFileFormat(byte[] header) + { + bool isBmp = false; + if (header.Length >= 2) + { + isBmp = header[0] == 0x42 && // B + header[1] == 0x4D; // M + } + + return isBmp; + } + + /// + /// Decodes the image from the specified stream to the . + /// + /// The to decode to. + /// The containing image data. + public void Decode(Image image, Stream stream) + { + new BmpDecoderCore().Decode(image, stream); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoderCore.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoderCore.cs new file mode 100644 index 0000000000..9c2bc45b9a --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoderCore.cs @@ -0,0 +1,445 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + using System.Threading.Tasks; + + /// + /// Performs the bmp decoding operation. + /// + internal sealed class BmpDecoderCore + { + /// + /// The mask for the red part of the color for 16 bit rgb bitmaps. + /// + private const int Rgb16RMask = 0x00007C00; + + /// + /// The mask for the green part of the color for 16 bit rgb bitmaps. + /// + private const int Rgb16GMask = 0x000003E0; + + /// + /// The mask for the blue part of the color for 16 bit rgb bitmaps. + /// + private const int Rgb16BMask = 0x0000001F; + + /// + /// The stream to decode from. + /// + private Stream currentStream; + + /// + /// The file header containing general information. + /// TODO: Why is this not used? We advance the stream but do not use the values parsed. + /// + private BmpFileHeader fileHeader; + + /// + /// The info header containing detailed information about the bitmap. + /// + private BmpInfoHeader infoHeader; + + /// + /// Decodes the image from the specified this._stream and sets + /// the data to image. + /// + /// The type of pixels contained within the image. + /// The image, where the data should be set to. + /// Cannot be null (Nothing in Visual Basic). + /// The this._stream, where the image should be + /// decoded from. Cannot be null (Nothing in Visual Basic). + /// + /// is null. + /// - or - + /// is null. + /// + public void Decode(Image image, Stream stream) + where TPackedVector : IPackedVector, new() + { + this.currentStream = stream; + + try + { + this.ReadFileHeader(); + this.ReadInfoHeader(); + + // see http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517 + // If the height is negative, then this is a Windows bitmap whose origin + // is the upper-left corner and not the lower-left.The inverted flag + // indicates a lower-left origin.Our code will be outputting an + // upper-left origin pixel array. + bool inverted = false; + if (this.infoHeader.Height < 0) + { + inverted = true; + this.infoHeader.Height = -this.infoHeader.Height; + } + + int colorMapSize = -1; + + if (this.infoHeader.ClrUsed == 0) + { + if (this.infoHeader.BitsPerPixel == 1 || + this.infoHeader.BitsPerPixel == 4 || + this.infoHeader.BitsPerPixel == 8) + { + colorMapSize = (int)Math.Pow(2, this.infoHeader.BitsPerPixel) * 4; + } + } + else + { + colorMapSize = this.infoHeader.ClrUsed * 4; + } + + byte[] palette = null; + + if (colorMapSize > 0) + { + // 255 * 4 + if (colorMapSize > 1020) + { + throw new ImageFormatException($"Invalid bmp colormap size '{colorMapSize}'"); + } + + palette = new byte[colorMapSize]; + + this.currentStream.Read(palette, 0, colorMapSize); + } + + if (this.infoHeader.Width > image.MaxWidth || this.infoHeader.Height > image.MaxHeight) + { + throw new ArgumentOutOfRangeException( + $"The input bitmap '{this.infoHeader.Width}x{this.infoHeader.Height}' is " + + $"bigger then the max allowed size '{image.MaxWidth}x{image.MaxHeight}'"); + } + + TPackedVector[] imageData = new TPackedVector[this.infoHeader.Width * this.infoHeader.Height]; + + switch (this.infoHeader.Compression) + { + case BmpCompression.RGB: + if (this.infoHeader.HeaderSize != 40) + { + throw new ImageFormatException( + $"Header Size value '{this.infoHeader.HeaderSize}' is not valid."); + } + + if (this.infoHeader.BitsPerPixel == 32) + { + this.ReadRgb32(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted); + } + else if (this.infoHeader.BitsPerPixel == 24) + { + this.ReadRgb24(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted); + } + else if (this.infoHeader.BitsPerPixel == 16) + { + this.ReadRgb16(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted); + } + else if (this.infoHeader.BitsPerPixel <= 8) + { + this.ReadRgbPalette( + imageData, + palette, + this.infoHeader.Width, + this.infoHeader.Height, + this.infoHeader.BitsPerPixel, + inverted); + } + + break; + default: + throw new NotSupportedException("Does not support this kind of bitmap files."); + } + + image.SetPixels(this.infoHeader.Width, this.infoHeader.Height, imageData); + } + catch (IndexOutOfRangeException e) + { + throw new ImageFormatException("Bitmap does not have a valid format.", e); + } + } + + /// + /// Returns the y- value based on the given height. + /// + /// The y- value representing the current row. + /// The height of the bitmap. + /// The representing the inverted value. + private static int Invert(int y, int height, bool inverted) + { + int row; + + if (!inverted) + { + row = height - y - 1; + } + else + { + row = y; + } + + return row; + } + + /// + /// Reads the color palette from the stream. + /// + /// The image data to assign the palette to. + /// The containing the colors. + /// The width of the bitmap. + /// The height of the bitmap. + /// The number of bits per pixel. + private void ReadRgbPalette(float[] imageData, byte[] colors, int width, int height, int bits, bool inverted) + { + // Pixels per byte (bits per pixel) + int ppb = 8 / bits; + + int arrayWidth = (width + ppb - 1) / ppb; + + // Bit mask + int mask = 0xFF >> (8 - bits); + + byte[] data = new byte[arrayWidth * height]; + + this.currentStream.Read(data, 0, data.Length); + + // Rows are aligned on 4 byte boundaries + int alignment = arrayWidth % 4; + if (alignment != 0) + { + alignment = 4 - alignment; + } + + Parallel.For( + 0, + height, + y => + { + int rowOffset = y * (arrayWidth + alignment); + + for (int x = 0; x < arrayWidth; x++) + { + int offset = rowOffset + x; + + // Revert the y value, because bitmaps are saved from down to top + int row = Invert(y, height, inverted); + + int colOffset = x * ppb; + + for (int shift = 0; shift < ppb && (colOffset + shift) < width; shift++) + { + int colorIndex = ((data[offset] >> (8 - bits - (shift * bits))) & mask) * 4; + int arrayOffset = ((row * width) + (colOffset + shift)) * 4; + + // We divide by 255 as we will store the colors in our floating point format. + // Stored in r-> g-> b-> a order. + imageData[arrayOffset] = colors[colorIndex + 2] / 255f; // r + imageData[arrayOffset + 1] = colors[colorIndex + 1] / 255f; // g + imageData[arrayOffset + 2] = colors[colorIndex] / 255f; // b + imageData[arrayOffset + 3] = 1; // a + } + } + }); + } + + /// + /// Reads the 16 bit color palette from the stream + /// + /// The type of pixels contained within the image. + /// The image data to assign the palette to. + /// The width of the bitmap. + /// The height of the bitmap. + private void ReadRgb16(TPackedVector[] imageData, int width, int height, bool inverted) + where TPackedVector : IPackedVector, new() + { + // We divide here as we will store the colors in our floating point format. + const int ScaleR = 8; // 256/32 + const int ScaleG = 4; // 256/64 + + int alignment; + byte[] data = this.GetImageArray(width, height, 2, out alignment); + + Parallel.For( + 0, + height, + y => + { + int rowOffset = y * ((width * 2) + alignment); + + // Revert the y value, because bitmaps are saved from down to top + int row = Invert(y, height, inverted); + + for (int x = 0; x < width; x++) + { + int offset = rowOffset + (x * 2); + + short temp = BitConverter.ToInt16(data, offset); + + byte r = (byte)(((temp & Rgb16RMask) >> 11) * ScaleR); + byte g = (byte)(((temp & Rgb16GMask) >> 5) * ScaleG); + byte b = (byte)((temp & Rgb16BMask) * ScaleR); + + int arrayOffset = ((row * width) + x); + + // Stored in b-> g-> r-> a order. + TPackedVector packed = new TPackedVector(); + packed.PackBytes(b, g, r, 255); + imageData[arrayOffset] = packed; + } + }); + } + + /// + /// Reads the 24 bit color palette from the stream + /// + /// The type of pixels contained within the image. + /// The image data to assign the palette to. + /// The width of the bitmap. + /// The height of the bitmap. + private void ReadRgb24(TPackedVector[] imageData, int width, int height, bool inverted) + where TPackedVector : IPackedVector, new() + { + int alignment; + byte[] data = this.GetImageArray(width, height, 3, out alignment); + + Parallel.For( + 0, + height, + y => + { + int rowOffset = y * ((width * 3) + alignment); + + // Revert the y value, because bitmaps are saved from down to top + int row = Invert(y, height, inverted); + + for (int x = 0; x < width; x++) + { + int offset = rowOffset + (x * 3); + int arrayOffset = ((row * width) + x); + + // We divide by 255 as we will store the colors in our floating point format. + // Stored in b-> g-> r-> a order. + TPackedVector packed = new TPackedVector(); + packed.PackBytes(data[offset], data[offset + 1], data[offset + 2], 255); + imageData[arrayOffset] = packed; + } + }); + } + + /// + /// Reads the 32 bit color palette from the stream + /// + /// The type of pixels contained within the image. + /// The image data to assign the palette to. + /// The width of the bitmap. + /// The height of the bitmap. + private void ReadRgb32(TPackedVector[] imageData, int width, int height, bool inverted) + where TPackedVector : IPackedVector, new() + { + int alignment; + byte[] data = this.GetImageArray(width, height, 4, out alignment); + + Parallel.For( + 0, + height, + y => + { + int rowOffset = y * ((width * 4) + alignment); + + // Revert the y value, because bitmaps are saved from down to top + int row = Invert(y, height, inverted); + + for (int x = 0; x < width; x++) + { + int offset = rowOffset + (x * 4); + int arrayOffset = ((row * width) + x); + + // Stored in b-> g-> r-> a order. + TPackedVector packed = new TPackedVector(); + packed.PackBytes(data[offset], data[offset + 1], data[offset + 2], data[offset + 3]); + imageData[arrayOffset] = packed; + } + }); + } + + /// + /// Returns a containing the pixels for the current bitmap. + /// + /// The width of the bitmap. + /// The height. + /// The number of bytes per pixel. + /// The alignment of the pixels. + /// + /// The containing the pixels. + /// + private byte[] GetImageArray(int width, int height, int bytes, out int alignment) + { + int dataWidth = width; + + alignment = (width * bytes) % 4; + + if (alignment != 0) + { + alignment = 4 - alignment; + } + + int size = ((dataWidth * bytes) + alignment) * height; + + byte[] data = new byte[size]; + + this.currentStream.Read(data, 0, size); + + return data; + } + + /// + /// Reads the from the stream. + /// + private void ReadInfoHeader() + { + byte[] data = new byte[BmpInfoHeader.Size]; + + this.currentStream.Read(data, 0, BmpInfoHeader.Size); + + this.infoHeader = new BmpInfoHeader + { + HeaderSize = BitConverter.ToInt32(data, 0), + Width = BitConverter.ToInt32(data, 4), + Height = BitConverter.ToInt32(data, 8), + Planes = BitConverter.ToInt16(data, 12), + BitsPerPixel = BitConverter.ToInt16(data, 14), + ImageSize = BitConverter.ToInt32(data, 20), + XPelsPerMeter = BitConverter.ToInt32(data, 24), + YPelsPerMeter = BitConverter.ToInt32(data, 28), + ClrUsed = BitConverter.ToInt32(data, 32), + ClrImportant = BitConverter.ToInt32(data, 36), + Compression = (BmpCompression)BitConverter.ToInt32(data, 16) + }; + } + + /// + /// Reads the from the stream. + /// + private void ReadFileHeader() + { + byte[] data = new byte[BmpFileHeader.Size]; + + this.currentStream.Read(data, 0, BmpFileHeader.Size); + + this.fileHeader = new BmpFileHeader + { + Type = BitConverter.ToInt16(data, 0), + FileSize = BitConverter.ToInt32(data, 2), + Reserved = BitConverter.ToInt32(data, 6), + Offset = BitConverter.ToInt32(data, 10) + }; + } + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoder.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoder.cs new file mode 100644 index 0000000000..4b212d4ea0 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoder.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + + /// + /// Image encoder for writing an image to a stream as a Windows bitmap. + /// + /// The encoder can currently only write 24-bit rgb images to streams. + public class BmpEncoder : IImageEncoder + { + /// + /// Gets or sets the quality of output for images. + /// + /// Bitmap is a lossless format so this is not used in this encoder. + public int Quality { get; set; } + + /// + public string MimeType => "image/bmp"; + + /// + public string Extension => "bmp"; + + /// + /// Gets or sets the number of bits per pixel. + /// + public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; + + /// + public bool IsSupportedFileExtension(string extension) + { + Guard.NotNullOrEmpty(extension, nameof(extension)); + + extension = extension.StartsWith(".") ? extension.Substring(1) : extension; + + return extension.Equals(this.Extension, StringComparison.OrdinalIgnoreCase) + || extension.Equals("dip", StringComparison.OrdinalIgnoreCase); + } + + /// + public void Encode(ImageBase image, Stream stream) + where TPackedVector: IPackedVector + { + BmpEncoderCore encoder = new BmpEncoderCore(); + encoder.Encode(image, stream, this.BitsPerPixel); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoderCore.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoderCore.cs new file mode 100644 index 0000000000..2ad8e24e6e --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoderCore.cs @@ -0,0 +1,205 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + + using IO; + + /// + /// Image encoder for writing an image to a stream as a Windows bitmap. + /// + /// The encoder can currently only write 24-bit rgb images to streams. + internal sealed class BmpEncoderCore + { + /// + /// The number of bits per pixel. + /// + private BmpBitsPerPixel bmpBitsPerPixel; + + /// + /// Encodes the image to the specified stream from the . + /// + /// The type of pixels contained within the image. + /// The to encode from. + /// The to encode the image data to. + /// The + public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) + where TPackedVector : IPackedVector + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + this.bmpBitsPerPixel = bitsPerPixel; + + int rowWidth = image.Width; + + // TODO: Check this for varying file formats. + int amount = (image.Width * (int)this.bmpBitsPerPixel) % 4; + if (amount != 0) + { + rowWidth += 4 - amount; + } + + // Do not use IDisposable pattern here as we want to preserve the stream. + EndianBinaryWriter writer = new EndianBinaryWriter(EndianBitConverter.Little, stream); + + int bpp = (int)this.bmpBitsPerPixel; + + BmpFileHeader fileHeader = new BmpFileHeader + { + Type = 19778, // BM + Offset = 54, + FileSize = 54 + (image.Height * rowWidth * bpp) + }; + + BmpInfoHeader infoHeader = new BmpInfoHeader + { + HeaderSize = 40, + Height = image.Height, + Width = image.Width, + BitsPerPixel = (short)(8 * bpp), + Planes = 1, + ImageSize = image.Height * rowWidth * bpp, + ClrUsed = 0, + ClrImportant = 0 + }; + + WriteHeader(writer, fileHeader); + this.WriteInfo(writer, infoHeader); + this.WriteImage(writer, image); + + writer.Flush(); + } + + /// + /// Writes the bitmap header data to the binary stream. + /// + /// + /// The containing the stream to write to. + /// + /// + /// The containing the header data. + /// + private static void WriteHeader(EndianBinaryWriter writer, BmpFileHeader fileHeader) + { + writer.Write(fileHeader.Type); + writer.Write(fileHeader.FileSize); + writer.Write(fileHeader.Reserved); + writer.Write(fileHeader.Offset); + } + + /// + /// Writes the bitmap information to the binary stream. + /// + /// + /// The containing the stream to write to. + /// + /// + /// The containing the detailed information about the image. + /// + private void WriteInfo(EndianBinaryWriter writer, BmpInfoHeader infoHeader) + { + writer.Write(infoHeader.HeaderSize); + writer.Write(infoHeader.Width); + writer.Write(infoHeader.Height); + writer.Write(infoHeader.Planes); + writer.Write(infoHeader.BitsPerPixel); + writer.Write((int)infoHeader.Compression); + writer.Write(infoHeader.ImageSize); + writer.Write(infoHeader.XPelsPerMeter); + writer.Write(infoHeader.YPelsPerMeter); + writer.Write(infoHeader.ClrUsed); + writer.Write(infoHeader.ClrImportant); + } + + /// + /// Writes the pixel data to the binary stream. + /// + /// The type of pixels contained within the image. + /// + /// The containing the stream to write to. + /// + /// + /// The containing pixel data. + /// + private void WriteImage(EndianBinaryWriter writer, ImageBase image) + where TPackedVector : IPackedVector + { + // TODO: Add more compression formats. + int amount = (image.Width * (int)this.bmpBitsPerPixel) % 4; + if (amount != 0) + { + amount = 4 - amount; + } + + using (IPixelAccessor pixels = image.Lock()) + { + switch (this.bmpBitsPerPixel) + { + case BmpBitsPerPixel.Pixel32: + this.Write32bit(writer, pixels, amount); + break; + + case BmpBitsPerPixel.Pixel24: + this.Write24bit(writer, pixels, amount); + break; + } + } + } + + /// + /// Writes the 32bit color palette to the stream. + /// + /// The containing the stream to write to. + /// The containing pixel data. + /// The amount to pad each row by. + private void Write32bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) + { + for (int y = pixels.Height - 1; y >= 0; y--) + { + for (int x = 0; x < pixels.Width; x++) + { + // Convert back to b-> g-> r-> a order. + byte[] bytes = pixels[x, y].ToBytes(); + writer.Write(bytes); + } + + // Pad + for (int i = 0; i < amount; i++) + { + writer.Write((byte)0); + } + } + } + + /// + /// Writes the 24bit color palette to the stream. + /// + /// The containing the stream to write to. + /// The containing pixel data. + /// The amount to pad each row by. + private void Write24bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) + { + for (int y = pixels.Height - 1; y >= 0; y--) + { + for (int x = 0; x < pixels.Width; x++) + { + // Convert back to b-> g-> r-> a order. + byte[] bytes = pixels[x, y].ToBytes(); + writer.Write(new[] { bytes[0], bytes[1], bytes[2] }); + } + + // Pad + for (int i = 0; i < amount; i++) + { + writer.Write((byte)0); + } + } + } + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpFileHeader.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpFileHeader.cs new file mode 100644 index 0000000000..6f626ee703 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpFileHeader.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Stores general information about the Bitmap file. + /// + /// + /// + /// The first two bytes of the Bitmap file format + /// (thus the Bitmap header) are stored in big-endian order. + /// All of the other integer values are stored in little-endian format + /// (i.e. least-significant byte first). + /// + internal class BmpFileHeader + { + /// + /// Defines of the data structure in the bitmap file. + /// + public const int Size = 14; + + /// + /// Gets or sets the Bitmap identifier. + /// The field used to identify the bitmap file: 0x42 0x4D + /// (Hex code points for B and M) + /// + public short Type { get; set; } + + /// + /// Gets or sets the size of the bitmap file in bytes. + /// + public int FileSize { get; set; } + + /// + /// Gets or sets any reserved data; actual value depends on the application + /// that creates the image. + /// + public int Reserved { get; set; } + + /// + /// Gets or sets the offset, i.e. starting address, of the byte where + /// the bitmap data can be found. + /// + public int Offset { get; set; } + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpFormat.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpFormat.cs new file mode 100644 index 0000000000..6f640c4b9e --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpFormat.cs @@ -0,0 +1,19 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Encapsulates the means to encode and decode bitmap images. + /// + public class BmpFormat : IImageFormat + { + /// + public IImageDecoder Decoder => new BmpDecoder(); + + /// + public IImageEncoder Encoder => new BmpEncoder(); + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpInfoHeader.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpInfoHeader.cs new file mode 100644 index 0000000000..c21a52d21b --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpInfoHeader.cs @@ -0,0 +1,82 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageProcessorCore.Formats +{ + /// + /// This block of bytes tells the application detailed information + /// about the image, which will be used to display the image on + /// the screen. + /// + /// + internal class BmpInfoHeader + { + /// + /// Defines of the data structure in the bitmap file. + /// + public const int Size = 40; + + /// + /// Gets or sets the size of this header (40 bytes) + /// + public int HeaderSize { get; set; } + + /// + /// Gets or sets the bitmap width in pixels (signed integer). + /// + public int Width { get; set; } + + /// + /// Gets or sets the bitmap height in pixels (signed integer). + /// + public int Height { get; set; } + + /// + /// Gets or sets the number of color planes being used. Must be set to 1. + /// + public short Planes { get; set; } + + /// + /// Gets or sets the number of bits per pixel, which is the color depth of the image. + /// Typical values are 1, 4, 8, 16, 24 and 32. + /// + public short BitsPerPixel { get; set; } + + /// + /// Gets or sets the compression method being used. + /// See the next table for a list of possible values. + /// + public BmpCompression Compression { get; set; } + + /// + /// Gets or sets the image size. This is the size of the raw bitmap data (see below), + /// and should not be confused with the file size. + /// + public int ImageSize { get; set; } + + /// + /// Gets or sets the horizontal resolution of the image. + /// (pixel per meter, signed integer) + /// + public int XPelsPerMeter { get; set; } + + /// + /// Gets or sets the vertical resolution of the image. + /// (pixel per meter, signed integer) + /// + public int YPelsPerMeter { get; set; } + + /// + /// Gets or sets the number of colors in the color palette, + /// or 0 to default to 2^n. + /// + public int ClrUsed { get; set; } + + /// + /// Gets or sets the number of important colors used, + /// or 0 when every color is important{ get; set; } generally ignored. + /// + public int ClrImportant { get; set; } + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/README.md b/src/ImageProcessorCore - Copy/Formats/Bmp/README.md new file mode 100644 index 0000000000..d072838438 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/Bmp/README.md @@ -0,0 +1,8 @@ +Encoder/Decoder adapted from: + +https://github.com/yufeih/Nine.Imaging/ +https://imagetools.codeplex.com/ + +TODO: + +- Add support for all bitmap formats. diff --git a/src/ImageProcessorCore/Formats/Gif/BitEncoder.cs b/src/ImageProcessorCore - Copy/Formats/Gif/BitEncoder.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/BitEncoder.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/BitEncoder.cs diff --git a/src/ImageProcessorCore/Formats/Gif/DisposalMethod.cs b/src/ImageProcessorCore - Copy/Formats/Gif/DisposalMethod.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/DisposalMethod.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/DisposalMethod.cs diff --git a/src/ImageProcessorCore/Formats/Gif/GifConstants.cs b/src/ImageProcessorCore - Copy/Formats/Gif/GifConstants.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/GifConstants.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/GifConstants.cs diff --git a/src/ImageProcessorCore/Formats/Gif/GifDecoder.cs b/src/ImageProcessorCore - Copy/Formats/Gif/GifDecoder.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/GifDecoder.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/GifDecoder.cs diff --git a/src/ImageProcessorCore/Formats/Gif/GifDecoderCore.cs b/src/ImageProcessorCore - Copy/Formats/Gif/GifDecoderCore.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/GifDecoderCore.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/GifDecoderCore.cs diff --git a/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs b/src/ImageProcessorCore - Copy/Formats/Gif/GifEncoder.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/GifEncoder.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/GifEncoder.cs diff --git a/src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs b/src/ImageProcessorCore - Copy/Formats/Gif/GifEncoderCore.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/GifEncoderCore.cs diff --git a/src/ImageProcessorCore/Formats/Gif/GifFormat.cs b/src/ImageProcessorCore - Copy/Formats/Gif/GifFormat.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/GifFormat.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/GifFormat.cs diff --git a/src/ImageProcessorCore/Formats/Gif/LzwDecoder.cs b/src/ImageProcessorCore - Copy/Formats/Gif/LzwDecoder.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/LzwDecoder.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/LzwDecoder.cs diff --git a/src/ImageProcessorCore/Formats/Gif/LzwEncoder.cs b/src/ImageProcessorCore - Copy/Formats/Gif/LzwEncoder.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/LzwEncoder.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/LzwEncoder.cs diff --git a/src/ImageProcessorCore/Formats/Gif/PackedField.cs b/src/ImageProcessorCore - Copy/Formats/Gif/PackedField.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/PackedField.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/PackedField.cs diff --git a/src/ImageProcessorCore/Formats/Gif/README.md b/src/ImageProcessorCore - Copy/Formats/Gif/README.md similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/README.md rename to src/ImageProcessorCore - Copy/Formats/Gif/README.md diff --git a/src/ImageProcessorCore/Formats/Gif/Sections/GifGraphicsControlExtension.cs b/src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifGraphicsControlExtension.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/Sections/GifGraphicsControlExtension.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifGraphicsControlExtension.cs diff --git a/src/ImageProcessorCore/Formats/Gif/Sections/GifImageDescriptor.cs b/src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifImageDescriptor.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/Sections/GifImageDescriptor.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifImageDescriptor.cs diff --git a/src/ImageProcessorCore/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs b/src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs diff --git a/src/ImageProcessorCore - Copy/Formats/IImageDecoder.cs b/src/ImageProcessorCore - Copy/Formats/IImageDecoder.cs new file mode 100644 index 0000000000..0f3a8504c9 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/IImageDecoder.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System.IO; + + /// + /// Encapsulates properties and methods required for decoding an image from a stream. + /// + public interface IImageDecoder + { + /// + /// Gets the size of the header for this image type. + /// + /// The size of the header. + int HeaderSize { get; } + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file extension. + /// + /// True if the decoder supports the file extension; otherwise, false. + /// + bool IsSupportedFileExtension(string extension); + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file header. + /// + /// True if the decoder supports the file header; otherwise, false. + /// + bool IsSupportedFileFormat(byte[] header); + + /// + /// Decodes the image from the specified stream to the . + /// + /// The type of pixels contained within the image. + /// The to decode to. + /// The containing image data. + void Decode(Image image, Stream stream) where TPackedVector : IPackedVector; + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/IImageEncoder.cs b/src/ImageProcessorCore - Copy/Formats/IImageEncoder.cs new file mode 100644 index 0000000000..df7234aad0 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/IImageEncoder.cs @@ -0,0 +1,53 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +// +// Encapsulates properties and methods required for decoding an image to a stream. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessorCore.Formats +{ + using System.IO; + + /// + /// Encapsulates properties and methods required for encoding an image to a stream. + /// + public interface IImageEncoder + { + /// + /// Gets or sets the quality of output for images. + /// + int Quality { get; set; } + + /// + /// Gets the standard identifier used on the Internet to indicate the type of data that a file contains. + /// + string MimeType { get; } + + /// + /// Gets the default file extension for this encoder. + /// + string Extension { get; } + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file extension. + /// + /// True if the decoder supports the file extension; otherwise, false. + /// + bool IsSupportedFileExtension(string extension); + + /// + /// Encodes the image to the specified stream from the . + /// + /// The type of pixels contained within the image. + /// The to encode from. + /// The to encode the image data to. + void Encode(ImageBase image, Stream stream) where TPackedVector : IPackedVector; + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/IImageFormat.cs b/src/ImageProcessorCore - Copy/Formats/IImageFormat.cs new file mode 100644 index 0000000000..62b4b78916 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/IImageFormat.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Encapsulates a supported image format, providing means to encode and decode an image. + /// + public interface IImageFormat + { + /// + /// Gets the image encoder for encoding an image from a stream. + /// + IImageEncoder Encoder { get; } + + /// + /// Gets the image decoder for decoding an image from a stream. + /// + IImageDecoder Decoder { get; } + } +} diff --git a/src/ImageProcessorCore/Formats/Jpg/Block.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/Block.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Jpg/Block.cs rename to src/ImageProcessorCore - Copy/Formats/Jpg/Block.cs diff --git a/src/ImageProcessorCore/Formats/Jpg/FDCT.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/FDCT.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Jpg/FDCT.cs rename to src/ImageProcessorCore - Copy/Formats/Jpg/FDCT.cs diff --git a/src/ImageProcessorCore/Formats/Jpg/IDCT.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/IDCT.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Jpg/IDCT.cs rename to src/ImageProcessorCore - Copy/Formats/Jpg/IDCT.cs diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegDecoder.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/JpegDecoder.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Jpg/JpegDecoder.cs rename to src/ImageProcessorCore - Copy/Formats/Jpg/JpegDecoder.cs diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id b/src/ImageProcessorCore - Copy/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id similarity index 100% rename from src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id rename to src/ImageProcessorCore - Copy/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoder.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/JpegEncoder.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Jpg/JpegEncoder.cs rename to src/ImageProcessorCore - Copy/Formats/Jpg/JpegEncoder.cs diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/JpegEncoderCore.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs rename to src/ImageProcessorCore - Copy/Formats/Jpg/JpegEncoderCore.cs diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegFormat.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/JpegFormat.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Jpg/JpegFormat.cs rename to src/ImageProcessorCore - Copy/Formats/Jpg/JpegFormat.cs diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegSubsample.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/JpegSubsample.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Jpg/JpegSubsample.cs rename to src/ImageProcessorCore - Copy/Formats/Jpg/JpegSubsample.cs diff --git a/src/ImageProcessorCore/Formats/Jpg/README.md b/src/ImageProcessorCore - Copy/Formats/Jpg/README.md similarity index 100% rename from src/ImageProcessorCore/Formats/Jpg/README.md rename to src/ImageProcessorCore - Copy/Formats/Jpg/README.md diff --git a/src/ImageProcessorCore/Formats/Png/GrayscaleReader.cs b/src/ImageProcessorCore - Copy/Formats/Png/GrayscaleReader.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/GrayscaleReader.cs rename to src/ImageProcessorCore - Copy/Formats/Png/GrayscaleReader.cs diff --git a/src/ImageProcessorCore/Formats/Png/IColorReader.cs b/src/ImageProcessorCore - Copy/Formats/Png/IColorReader.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/IColorReader.cs rename to src/ImageProcessorCore - Copy/Formats/Png/IColorReader.cs diff --git a/src/ImageProcessorCore/Formats/Png/PaletteIndexReader.cs b/src/ImageProcessorCore - Copy/Formats/Png/PaletteIndexReader.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/PaletteIndexReader.cs rename to src/ImageProcessorCore - Copy/Formats/Png/PaletteIndexReader.cs diff --git a/src/ImageProcessorCore/Formats/Png/PngChunk.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngChunk.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/PngChunk.cs rename to src/ImageProcessorCore - Copy/Formats/Png/PngChunk.cs diff --git a/src/ImageProcessorCore/Formats/Png/PngChunkTypes.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngChunkTypes.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/PngChunkTypes.cs rename to src/ImageProcessorCore - Copy/Formats/Png/PngChunkTypes.cs diff --git a/src/ImageProcessorCore/Formats/Png/PngColorTypeInformation.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngColorTypeInformation.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/PngColorTypeInformation.cs rename to src/ImageProcessorCore - Copy/Formats/Png/PngColorTypeInformation.cs diff --git a/src/ImageProcessorCore/Formats/Png/PngDecoder.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngDecoder.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/PngDecoder.cs rename to src/ImageProcessorCore - Copy/Formats/Png/PngDecoder.cs diff --git a/src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngDecoderCore.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs rename to src/ImageProcessorCore - Copy/Formats/Png/PngDecoderCore.cs diff --git a/src/ImageProcessorCore/Formats/Png/PngEncoder.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngEncoder.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/PngEncoder.cs rename to src/ImageProcessorCore - Copy/Formats/Png/PngEncoder.cs diff --git a/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngEncoderCore.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs rename to src/ImageProcessorCore - Copy/Formats/Png/PngEncoderCore.cs diff --git a/src/ImageProcessorCore/Formats/Png/PngFormat.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngFormat.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/PngFormat.cs rename to src/ImageProcessorCore - Copy/Formats/Png/PngFormat.cs diff --git a/src/ImageProcessorCore/Formats/Png/PngHeader.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngHeader.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/PngHeader.cs rename to src/ImageProcessorCore - Copy/Formats/Png/PngHeader.cs diff --git a/src/ImageProcessorCore/Formats/Png/README.md b/src/ImageProcessorCore - Copy/Formats/Png/README.md similarity index 100% rename from src/ImageProcessorCore/Formats/Png/README.md rename to src/ImageProcessorCore - Copy/Formats/Png/README.md diff --git a/src/ImageProcessorCore/Formats/Png/TrueColorReader.cs b/src/ImageProcessorCore - Copy/Formats/Png/TrueColorReader.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/TrueColorReader.cs rename to src/ImageProcessorCore - Copy/Formats/Png/TrueColorReader.cs diff --git a/src/ImageProcessorCore/Formats/Png/Zlib/Adler32.cs b/src/ImageProcessorCore - Copy/Formats/Png/Zlib/Adler32.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/Zlib/Adler32.cs rename to src/ImageProcessorCore - Copy/Formats/Png/Zlib/Adler32.cs diff --git a/src/ImageProcessorCore/Formats/Png/Zlib/Crc32.cs b/src/ImageProcessorCore - Copy/Formats/Png/Zlib/Crc32.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/Zlib/Crc32.cs rename to src/ImageProcessorCore - Copy/Formats/Png/Zlib/Crc32.cs diff --git a/src/ImageProcessorCore/Formats/Png/Zlib/IChecksum.cs b/src/ImageProcessorCore - Copy/Formats/Png/Zlib/IChecksum.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/Zlib/IChecksum.cs rename to src/ImageProcessorCore - Copy/Formats/Png/Zlib/IChecksum.cs diff --git a/src/ImageProcessorCore/Formats/Png/Zlib/README.md b/src/ImageProcessorCore - Copy/Formats/Png/Zlib/README.md similarity index 100% rename from src/ImageProcessorCore/Formats/Png/Zlib/README.md rename to src/ImageProcessorCore - Copy/Formats/Png/Zlib/README.md diff --git a/src/ImageProcessorCore/Formats/Png/Zlib/ZlibDeflateStream.cs b/src/ImageProcessorCore - Copy/Formats/Png/Zlib/ZlibDeflateStream.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/Zlib/ZlibDeflateStream.cs rename to src/ImageProcessorCore - Copy/Formats/Png/Zlib/ZlibDeflateStream.cs diff --git a/src/ImageProcessorCore/Formats/Png/Zlib/ZlibInflateStream.cs b/src/ImageProcessorCore - Copy/Formats/Png/Zlib/ZlibInflateStream.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/Zlib/ZlibInflateStream.cs rename to src/ImageProcessorCore - Copy/Formats/Png/Zlib/ZlibInflateStream.cs diff --git a/src/ImageProcessorCore - Copy/IImageBase.cs b/src/ImageProcessorCore - Copy/IImageBase.cs new file mode 100644 index 0000000000..a721033770 --- /dev/null +++ b/src/ImageProcessorCore - Copy/IImageBase.cs @@ -0,0 +1,29 @@ +namespace ImageProcessorCore +{ + public interface IImageBase + where TPackedVector : IPackedVector + { + Rectangle Bounds { get; } + int FrameDelay { get; set; } + int Height { get; } + double PixelRatio { get; } + TPackedVector[] Pixels { get; } + int Quality { get; set; } + + /// + /// Gets or sets the maximum allowable width in pixels. + /// + int MaxWidth { get; set; } + + /// + /// Gets or sets the maximum allowable height in pixels. + /// + int MaxHeight { get; set; } + + int Width { get; } + + void ClonePixels(int width, int height, TPackedVector[] pixels); + IPixelAccessor Lock(); + void SetPixels(int width, int height, TPackedVector[] pixels); + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/IImageFrame.cs b/src/ImageProcessorCore - Copy/IImageFrame.cs new file mode 100644 index 0000000000..1565879a76 --- /dev/null +++ b/src/ImageProcessorCore - Copy/IImageFrame.cs @@ -0,0 +1,7 @@ +namespace ImageProcessorCore +{ + public interface IImageFrame : IImageBase + where TPacked : IPackedVector + { + } +} diff --git a/src/ImageProcessorCore - Copy/IImageProcessor.cs b/src/ImageProcessorCore - Copy/IImageProcessor.cs new file mode 100644 index 0000000000..ad6e4ac761 --- /dev/null +++ b/src/ImageProcessorCore - Copy/IImageProcessor.cs @@ -0,0 +1,72 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// A delegate which is called as progress is made processing an image. + /// + /// The source of the event. + /// An object that contains the event data. + public delegate void ProgressEventHandler(object sender, ProgressEventArgs e); + + /// + /// Encapsulates methods to alter the pixels of an image. + /// + public interface IImageProcessor + { + /// + /// Event fires when each row of the source image has been processed. + /// + /// + /// This event may be called from threads other than the client thread, and from multiple threads simultaneously. + /// Individual row notifications may arrived out of order. + /// + event ProgressEventHandler OnProgress; + + /// + /// Applies the process to the specified portion of the specified . + /// + /// The type of pixels contained within the image. + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// The method keeps the source image unchanged and returns the + /// the result of image processing filter as new image. + /// + /// + /// is null or is null. + /// + /// + /// doesnt fit the dimension of the image. + /// + void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) where TPackedVector : IPackedVector; + + /// + /// Applies the process to the specified portion of the specified at the specified + /// location and with the specified size. + /// + /// The type of pixels contained within the image. + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// The target width. + /// The target height. + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. + /// + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// The method keeps the source image unchanged and returns the + /// the result of image process as new image. + /// + void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle) where TPackedVector : IPackedVector; + } +} diff --git a/src/ImageProcessorCore - Copy/IO/BigEndianBitConverter.cs b/src/ImageProcessorCore - Copy/IO/BigEndianBitConverter.cs new file mode 100644 index 0000000000..6c624c6a6b --- /dev/null +++ b/src/ImageProcessorCore - Copy/IO/BigEndianBitConverter.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.IO +{ + /// + /// Implementation of EndianBitConverter which converts to/from big-endian + /// byte arrays. + /// + /// Adapted from Miscellaneous Utility Library + /// This product includes software developed by Jon Skeet and Marc Gravell. Contact , or see + /// . + /// + /// + internal sealed class BigEndianBitConverter : EndianBitConverter + { + /// + public override Endianness Endianness => Endianness.BigEndian; + + /// + public override bool IsLittleEndian() => false; + + /// + protected internal override void CopyBytesImpl(long value, int bytes, byte[] buffer, int index) + { + int endOffset = index + bytes - 1; + for (int i = 0; i < bytes; i++) + { + buffer[endOffset - i] = unchecked((byte)(value & 0xff)); + value = value >> 8; + } + } + + /// + protected internal override long FromBytes(byte[] buffer, int startIndex, int bytesToConvert) + { + long ret = 0; + for (int i = 0; i < bytesToConvert; i++) + { + ret = unchecked((ret << 8) | buffer[startIndex + i]); + } + + return ret; + } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/IO/EndianBinaryReader.cs b/src/ImageProcessorCore - Copy/IO/EndianBinaryReader.cs new file mode 100644 index 0000000000..e2fc14a050 --- /dev/null +++ b/src/ImageProcessorCore - Copy/IO/EndianBinaryReader.cs @@ -0,0 +1,616 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.IO +{ + using System; + using System.IO; + using System.Text; + + /// + /// Equivalent of , but with either endianness, depending on + /// the EndianBitConverter it is constructed with. No data is buffered in the + /// reader; the client may seek within the stream at will. + /// + internal class EndianBinaryReader : IDisposable + { + /// + /// Decoder to use for string conversions. + /// + private readonly Decoder decoder; + + /// + /// Buffer used for temporary storage before conversion into primitives + /// + private readonly byte[] buffer = new byte[16]; + + /// + /// Buffer used for temporary storage when reading a single character + /// + private readonly char[] charBuffer = new char[1]; + + /// + /// Minimum number of bytes used to encode a character + /// + private readonly int minBytesPerChar; + + /// + /// Whether or not this reader has been disposed yet. + /// + private bool disposed; + + /// + /// Equivalent of System.IO.BinaryWriter, but with either endianness, depending on + /// the EndianBitConverter it is constructed with. + /// + /// Converter to use when reading data + /// Stream to read data from + public EndianBinaryReader(EndianBitConverter bitConverter, Stream stream) + : this(bitConverter, stream, Encoding.UTF8) + { + } + + /// + /// Constructs a new binary reader with the given bit converter, reading + /// to the given stream, using the given encoding. + /// + /// Converter to use when reading data + /// Stream to read data from + /// Encoding to use when reading character data + public EndianBinaryReader(EndianBitConverter bitConverter, Stream stream, Encoding encoding) + { + // TODO: Use Guard + if (bitConverter == null) + { + throw new ArgumentNullException("bitConverter"); + } + + if (stream == null) + { + throw new ArgumentNullException("stream"); + } + + if (encoding == null) + { + throw new ArgumentNullException("encoding"); + } + + if (!stream.CanRead) + { + throw new ArgumentException("Stream isn't writable", "stream"); + } + + this.BaseStream = stream; + this.BitConverter = bitConverter; + this.Encoding = encoding; + this.decoder = encoding.GetDecoder(); + this.minBytesPerChar = 1; + + if (encoding is UnicodeEncoding) + { + this.minBytesPerChar = 2; + } + } + + /// + /// Gets the bit converter used to read values from the stream. + /// + public EndianBitConverter BitConverter { get; } + + /// + /// Gets the encoding used to read strings + /// + public Encoding Encoding { get; } + + /// + /// Gets the underlying stream of the EndianBinaryReader. + /// + public Stream BaseStream { get; } + + /// + /// Closes the reader, including the underlying stream. + /// + public void Close() + { + this.Dispose(); + } + + /// + /// Seeks within the stream. + /// + /// Offset to seek to. + /// Origin of seek operation. + public void Seek(int offset, SeekOrigin origin) + { + this.CheckDisposed(); + this.BaseStream.Seek(offset, origin); + } + + /// + /// Reads a single byte from the stream. + /// + /// The byte read + public byte ReadByte() + { + this.ReadInternal(this.buffer, 1); + return this.buffer[0]; + } + + /// + /// Reads a single signed byte from the stream. + /// + /// The byte read + public sbyte ReadSByte() + { + this.ReadInternal(this.buffer, 1); + return unchecked((sbyte)this.buffer[0]); + } + + /// + /// Reads a boolean from the stream. 1 byte is read. + /// + /// The boolean read + public bool ReadBoolean() + { + this.ReadInternal(this.buffer, 1); + return this.BitConverter.ToBoolean(this.buffer, 0); + } + + /// + /// Reads a 16-bit signed integer from the stream, using the bit converter + /// for this reader. 2 bytes are read. + /// + /// The 16-bit integer read + public short ReadInt16() + { + this.ReadInternal(this.buffer, 2); + return this.BitConverter.ToInt16(this.buffer, 0); + } + + /// + /// Reads a 32-bit signed integer from the stream, using the bit converter + /// for this reader. 4 bytes are read. + /// + /// The 32-bit integer read + public int ReadInt32() + { + this.ReadInternal(this.buffer, 4); + return this.BitConverter.ToInt32(this.buffer, 0); + } + + /// + /// Reads a 64-bit signed integer from the stream, using the bit converter + /// for this reader. 8 bytes are read. + /// + /// The 64-bit integer read + public long ReadInt64() + { + this.ReadInternal(this.buffer, 8); + return this.BitConverter.ToInt64(this.buffer, 0); + } + + /// + /// Reads a 16-bit unsigned integer from the stream, using the bit converter + /// for this reader. 2 bytes are read. + /// + /// The 16-bit unsigned integer read + public ushort ReadUInt16() + { + this.ReadInternal(this.buffer, 2); + return this.BitConverter.ToUInt16(this.buffer, 0); + } + + /// + /// Reads a 32-bit unsigned integer from the stream, using the bit converter + /// for this reader. 4 bytes are read. + /// + /// The 32-bit unsigned integer read + public uint ReadUInt32() + { + this.ReadInternal(this.buffer, 4); + return this.BitConverter.ToUInt32(this.buffer, 0); + } + + /// + /// Reads a 64-bit unsigned integer from the stream, using the bit converter + /// for this reader. 8 bytes are read. + /// + /// The 64-bit unsigned integer read + public ulong ReadUInt64() + { + this.ReadInternal(this.buffer, 8); + return this.BitConverter.ToUInt64(this.buffer, 0); + } + + /// + /// Reads a single-precision floating-point value from the stream, using the bit converter + /// for this reader. 4 bytes are read. + /// + /// The floating point value read + public float ReadSingle() + { + this.ReadInternal(this.buffer, 4); + return this.BitConverter.ToSingle(this.buffer, 0); + } + + /// + /// Reads a double-precision floating-point value from the stream, using the bit converter + /// for this reader. 8 bytes are read. + /// + /// The floating point value read + public double ReadDouble() + { + this.ReadInternal(this.buffer, 8); + return this.BitConverter.ToDouble(this.buffer, 0); + } + + /// + /// Reads a decimal value from the stream, using the bit converter + /// for this reader. 16 bytes are read. + /// + /// The decimal value read + public decimal ReadDecimal() + { + this.ReadInternal(this.buffer, 16); + return this.BitConverter.ToDecimal(this.buffer, 0); + } + + /// + /// Reads a single character from the stream, using the character encoding for + /// this reader. If no characters have been fully read by the time the stream ends, + /// -1 is returned. + /// + /// The character read, or -1 for end of stream. + public int Read() + { + int charsRead = this.Read(this.charBuffer, 0, 1); + if (charsRead == 0) + { + return -1; + } + else + { + return this.charBuffer[0]; + } + } + + /// + /// Reads the specified number of characters into the given buffer, starting at + /// the given index. + /// + /// The buffer to copy data into + /// The first index to copy data into + /// The number of characters to read + /// The number of characters actually read. This will only be less than + /// the requested number of characters if the end of the stream is reached. + /// + public int Read(char[] data, int index, int count) + { + this.CheckDisposed(); + + // TODO: Use Guard + if (this.buffer == null) + { + throw new ArgumentNullException("buffer"); + } + + if (index < 0) + { + throw new ArgumentOutOfRangeException("index"); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException("index"); + } + + if (count + index > data.Length) + { + throw new ArgumentException("Not enough space in buffer for specified number of characters starting at specified index"); + } + + int read = 0; + bool firstTime = true; + + // Use the normal buffer if we're only reading a small amount, otherwise + // use at most 4K at a time. + byte[] byteBuffer = this.buffer; + + if (byteBuffer.Length < count * this.minBytesPerChar) + { + byteBuffer = new byte[4096]; + } + + while (read < count) + { + int amountToRead; + + // First time through we know we haven't previously read any data + if (firstTime) + { + amountToRead = count * this.minBytesPerChar; + firstTime = false; + } + + // After that we can only assume we need to fully read 'chars left -1' characters + // and a single byte of the character we may be in the middle of + else + { + amountToRead = ((count - read - 1) * this.minBytesPerChar) + 1; + } + + if (amountToRead > byteBuffer.Length) + { + amountToRead = byteBuffer.Length; + } + + int bytesRead = this.TryReadInternal(byteBuffer, amountToRead); + if (bytesRead == 0) + { + return read; + } + + int decoded = this.decoder.GetChars(byteBuffer, 0, bytesRead, data, index); + read += decoded; + index += decoded; + } + + return read; + } + + /// + /// Reads the specified number of bytes into the given buffer, starting at + /// the given index. + /// + /// The buffer to copy data into + /// The first index to copy data into + /// The number of bytes to read + /// The number of bytes actually read. This will only be less than + /// the requested number of bytes if the end of the stream is reached. + /// + public int Read(byte[] buffer, int index, int count) + { + this.CheckDisposed(); + if (buffer == null) + { + throw new ArgumentNullException("buffer"); + } + + if (index < 0) + { + throw new ArgumentOutOfRangeException("index"); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException("index"); + } + + if (count + index > buffer.Length) + { + throw new ArgumentException("Not enough space in buffer for specified number of bytes starting at specified index"); + } + + int read = 0; + while (count > 0) + { + int block = this.BaseStream.Read(buffer, index, count); + if (block == 0) + { + return read; + } + + index += block; + read += block; + count -= block; + } + + return read; + } + + /// + /// Reads the specified number of bytes, returning them in a new byte array. + /// If not enough bytes are available before the end of the stream, this + /// method will return what is available. + /// + /// The number of bytes to read + /// The bytes read + public byte[] ReadBytes(int count) + { + this.CheckDisposed(); + if (count < 0) + { + throw new ArgumentOutOfRangeException("count"); + } + + byte[] ret = new byte[count]; + int index = 0; + while (index < count) + { + int read = this.BaseStream.Read(ret, index, count - index); + + // Stream has finished half way through. That's fine, return what we've got. + if (read == 0) + { + byte[] copy = new byte[index]; + Buffer.BlockCopy(ret, 0, copy, 0, index); + return copy; + } + + index += read; + } + + return ret; + } + + /// + /// Reads the specified number of bytes, returning them in a new byte array. + /// If not enough bytes are available before the end of the stream, this + /// method will throw an IOException. + /// + /// The number of bytes to read + /// The bytes read + public byte[] ReadBytesOrThrow(int count) + { + byte[] ret = new byte[count]; + this.ReadInternal(ret, count); + return ret; + } + + /// + /// Reads a 7-bit encoded integer from the stream. This is stored with the least significant + /// information first, with 7 bits of information per byte of value, and the top + /// bit as a continuation flag. This method is not affected by the endianness + /// of the bit converter. + /// + /// The 7-bit encoded integer read from the stream. + public int Read7BitEncodedInt() + { + this.CheckDisposed(); + + int ret = 0; + for (int shift = 0; shift < 35; shift += 7) + { + int b = this.BaseStream.ReadByte(); + if (b == -1) + { + throw new EndOfStreamException(); + } + + ret = ret | ((b & 0x7f) << shift); + if ((b & 0x80) == 0) + { + return ret; + } + } + + // Still haven't seen a byte with the high bit unset? Dodgy data. + throw new IOException("Invalid 7-bit encoded integer in stream."); + } + + /// + /// Reads a 7-bit encoded integer from the stream. This is stored with the most significant + /// information first, with 7 bits of information per byte of value, and the top + /// bit as a continuation flag. This method is not affected by the endianness + /// of the bit converter. + /// + /// The 7-bit encoded integer read from the stream. + public int ReadBigEndian7BitEncodedInt() + { + this.CheckDisposed(); + + int ret = 0; + for (int i = 0; i < 5; i++) + { + int b = this.BaseStream.ReadByte(); + if (b == -1) + { + throw new EndOfStreamException(); + } + + ret = (ret << 7) | (b & 0x7f); + if ((b & 0x80) == 0) + { + return ret; + } + } + + // Still haven't seen a byte with the high bit unset? Dodgy data. + throw new IOException("Invalid 7-bit encoded integer in stream."); + } + + /// + /// Reads a length-prefixed string from the stream, using the encoding for this reader. + /// A 7-bit encoded integer is first read, which specifies the number of bytes + /// to read from the stream. These bytes are then converted into a string with + /// the encoding for this reader. + /// + /// The string read from the stream. + public string ReadString() + { + int bytesToRead = this.Read7BitEncodedInt(); + + byte[] data = new byte[bytesToRead]; + this.ReadInternal(data, bytesToRead); + return this.Encoding.GetString(data, 0, data.Length); + } + + /// + /// Disposes of the underlying stream. + /// + public void Dispose() + { + if (!this.disposed) + { + this.disposed = true; + ((IDisposable)this.BaseStream).Dispose(); + } + } + + /// + /// Checks whether or not the reader has been disposed, throwing an exception if so. + /// + private void CheckDisposed() + { + if (this.disposed) + { + throw new ObjectDisposedException("EndianBinaryReader"); + } + } + + /// + /// Reads the given number of bytes from the stream, throwing an exception + /// if they can't all be read. + /// + /// Buffer to read into + /// Number of bytes to read + private void ReadInternal(byte[] data, int size) + { + this.CheckDisposed(); + int index = 0; + while (index < size) + { + int read = this.BaseStream.Read(data, index, size - index); + if (read == 0) + { + throw new EndOfStreamException + ( + string.Format( + "End of stream reached with {0} byte{1} left to read.", + size - index, + size - index == 1 ? "s" : string.Empty)); + } + + index += read; + } + } + + /// + /// Reads the given number of bytes from the stream if possible, returning + /// the number of bytes actually read, which may be less than requested if + /// (and only if) the end of the stream is reached. + /// + /// Buffer to read into + /// Number of bytes to read + /// Number of bytes actually read + private int TryReadInternal(byte[] data, int size) + { + this.CheckDisposed(); + int index = 0; + while (index < size) + { + int read = this.BaseStream.Read(data, index, size - index); + if (read == 0) + { + return index; + } + + index += read; + } + + return index; + } + } +} diff --git a/src/ImageProcessorCore - Copy/IO/EndianBinaryWriter.cs b/src/ImageProcessorCore - Copy/IO/EndianBinaryWriter.cs new file mode 100644 index 0000000000..0f37b9a13d --- /dev/null +++ b/src/ImageProcessorCore - Copy/IO/EndianBinaryWriter.cs @@ -0,0 +1,385 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.IO +{ + using System; + using System.IO; + using System.Text; + + /// + /// Equivalent of , but with either endianness, depending on + /// the it is constructed with. + /// + internal class EndianBinaryWriter : IDisposable + { + /// + /// Buffer used for temporary storage during conversion from primitives + /// + private readonly byte[] buffer = new byte[16]; + + /// + /// Buffer used for Write(char) + /// + private readonly char[] charBuffer = new char[1]; + + /// + /// Whether or not this writer has been disposed yet. + /// + private bool disposed; + + /// + /// Initializes a new instance of the class + /// with the given bit converter, writing to the given stream, using UTF-8 encoding. + /// + /// Converter to use when writing data + /// Stream to write data to + public EndianBinaryWriter(EndianBitConverter bitConverter, Stream stream) + : this(bitConverter, stream, Encoding.UTF8) + { + } + + /// + /// Initializes a new instance of the class + /// with the given bit converter, writing to the given stream, using the given encoding. + /// + /// Converter to use when writing data + /// Stream to write data to + /// + /// Encoding to use when writing character data + /// + public EndianBinaryWriter(EndianBitConverter bitConverter, Stream stream, Encoding encoding) + { + // TODO: Use Guard + if (bitConverter == null) + { + throw new ArgumentNullException("bitConverter"); + } + + if (stream == null) + { + throw new ArgumentNullException("stream"); + } + + if (encoding == null) + { + throw new ArgumentNullException("encoding"); + } + + if (!stream.CanWrite) + { + throw new ArgumentException("Stream isn't writable", "stream"); + } + + this.BaseStream = stream; + this.BitConverter = bitConverter; + this.Encoding = encoding; + } + + /// + /// Gets the bit converter used to write values to the stream + /// + public EndianBitConverter BitConverter { get; } + + /// + /// Gets the encoding used to write strings + /// + public Encoding Encoding { get; } + + /// + /// Gets the underlying stream of the EndianBinaryWriter. + /// + public Stream BaseStream { get; } + + /// + /// Closes the writer, including the underlying stream. + /// + public void Close() + { + this.Dispose(); + } + + /// + /// Flushes the underlying stream. + /// + public void Flush() + { + this.CheckDisposed(); + this.BaseStream.Flush(); + } + + /// + /// Seeks within the stream. + /// + /// Offset to seek to. + /// Origin of seek operation. + public void Seek(int offset, SeekOrigin origin) + { + this.CheckDisposed(); + this.BaseStream.Seek(offset, origin); + } + + /// + /// Writes a boolean value to the stream. 1 byte is written. + /// + /// The value to write + public void Write(bool value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 1); + } + + /// + /// Writes a 16-bit signed integer to the stream, using the bit converter + /// for this writer. 2 bytes are written. + /// + /// The value to write + public void Write(short value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 2); + } + + /// + /// Writes a 32-bit signed integer to the stream, using the bit converter + /// for this writer. 4 bytes are written. + /// + /// The value to write + public void Write(int value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 4); + } + + /// + /// Writes a 64-bit signed integer to the stream, using the bit converter + /// for this writer. 8 bytes are written. + /// + /// The value to write + public void Write(long value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 8); + } + + /// + /// Writes a 16-bit unsigned integer to the stream, using the bit converter + /// for this writer. 2 bytes are written. + /// + /// The value to write + public void Write(ushort value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 2); + } + + /// + /// Writes a 32-bit unsigned integer to the stream, using the bit converter + /// for this writer. 4 bytes are written. + /// + /// The value to write + public void Write(uint value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 4); + } + + /// + /// Writes a 64-bit unsigned integer to the stream, using the bit converter + /// for this writer. 8 bytes are written. + /// + /// The value to write + public void Write(ulong value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 8); + } + + /// + /// Writes a single-precision floating-point value to the stream, using the bit converter + /// for this writer. 4 bytes are written. + /// + /// The value to write + public void Write(float value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 4); + } + + /// + /// Writes a double-precision floating-point value to the stream, using the bit converter + /// for this writer. 8 bytes are written. + /// + /// The value to write + public void Write(double value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 8); + } + + /// + /// Writes a decimal value to the stream, using the bit converter for this writer. + /// 16 bytes are written. + /// + /// The value to write + public void Write(decimal value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 16); + } + + /// + /// Writes a signed byte to the stream. + /// + /// The value to write + public void Write(byte value) + { + this.buffer[0] = value; + this.WriteInternal(this.buffer, 1); + } + + /// + /// Writes an unsigned byte to the stream. + /// + /// The value to write + public void Write(sbyte value) + { + this.buffer[0] = unchecked((byte)value); + this.WriteInternal(this.buffer, 1); + } + + /// + /// Writes an array of bytes to the stream. + /// + /// The values to write + public void Write(byte[] value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + this.WriteInternal(value, value.Length); + } + + /// + /// Writes a portion of an array of bytes to the stream. + /// + /// An array containing the bytes to write + /// The index of the first byte to write within the array + /// The number of bytes to write + public void Write(byte[] value, int offset, int count) + { + this.CheckDisposed(); + this.BaseStream.Write(value, offset, count); + } + + /// + /// Writes a single character to the stream, using the encoding for this writer. + /// + /// The value to write + public void Write(char value) + { + this.charBuffer[0] = value; + this.Write(this.charBuffer); + } + + /// + /// Writes an array of characters to the stream, using the encoding for this writer. + /// + /// An array containing the characters to write + public void Write(char[] value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + this.CheckDisposed(); + byte[] data = this.Encoding.GetBytes(value, 0, value.Length); + this.WriteInternal(data, data.Length); + } + + /// + /// Writes a string to the stream, using the encoding for this writer. + /// + /// The value to write. Must not be null. + /// value is null + public void Write(string value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + this.CheckDisposed(); + byte[] data = this.Encoding.GetBytes(value); + this.Write7BitEncodedInt(data.Length); + this.WriteInternal(data, data.Length); + } + + /// + /// Writes a 7-bit encoded integer from the stream. This is stored with the least significant + /// information first, with 7 bits of information per byte of value, and the top + /// bit as a continuation flag. + /// + /// The 7-bit encoded integer to write to the stream + public void Write7BitEncodedInt(int value) + { + this.CheckDisposed(); + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "Value must be greater than or equal to 0."); + } + + int index = 0; + while (value >= 128) + { + this.buffer[index++] = (byte)((value & 0x7f) | 0x80); + value = value >> 7; + index++; + } + + this.buffer[index++] = (byte)value; + this.BaseStream.Write(this.buffer, 0, index); + } + + /// + /// Checks whether or not the writer has been disposed, throwing an exception if so. + /// + private void CheckDisposed() + { + if (this.disposed) + { + throw new ObjectDisposedException("EndianBinaryWriter"); + } + } + + /// + /// Writes the specified number of bytes from the start of the given byte array, + /// after checking whether or not the writer has been disposed. + /// + /// The array of bytes to write from + /// The number of bytes to write + private void WriteInternal(byte[] bytes, int length) + { + this.CheckDisposed(); + this.BaseStream.Write(bytes, 0, length); + } + + /// + /// Disposes of the underlying stream. + /// + public void Dispose() + { + if (!this.disposed) + { + this.Flush(); + this.disposed = true; + ((IDisposable)this.BaseStream).Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/IO/EndianBitConverter.cs b/src/ImageProcessorCore - Copy/IO/EndianBitConverter.cs new file mode 100644 index 0000000000..d95527002b --- /dev/null +++ b/src/ImageProcessorCore - Copy/IO/EndianBitConverter.cs @@ -0,0 +1,724 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.IO +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.Runtime.InteropServices; + + /// + /// Equivalent of , but with either endianness. + /// + /// Adapted from Miscellaneous Utility Library + /// This product includes software developed by Jon Skeet and Marc Gravell. Contact , or see + /// . + /// + /// + [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:ElementsMustAppearInTheCorrectOrder", Justification = "Reviewed. Suppression is OK here. Better readability.")] + [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:ElementsMustBeOrderedByAccess", Justification = "Reviewed. Suppression is OK here. Better readability.")] + [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204:StaticElementsMustAppearBeforeInstanceElements", Justification = "Reviewed. Suppression is OK here. Better readability.")] + internal abstract class EndianBitConverter + { + #region Endianness of this converter + /// + /// Indicates the byte order ("endianness") in which data is converted using this class. + /// + /// + /// Different computer architectures store data using different byte orders. "Big-endian" + /// means the most significant byte is on the left end of a word. "Little-endian" means the + /// most significant byte is on the right end of a word. + /// + /// true if this converter is little-endian, false otherwise. + public abstract bool IsLittleEndian(); + + /// + /// Gets the byte order ("endianness") in which data is converted using this class. + /// + public abstract Endianness Endianness { get; } + #endregion + + #region Factory properties + /// + /// The little-endian bit converter. + /// + private static readonly LittleEndianBitConverter LittleConverter = new LittleEndianBitConverter(); + + /// + /// Gets a little-endian bit converter instance. The same instance is + /// always returned. + /// + public static LittleEndianBitConverter Little => LittleConverter; + + /// + /// The big-endian bit converter. + /// + private static readonly BigEndianBitConverter BigConverter = new BigEndianBitConverter(); + + /// + /// Gets a big-endian bit converter instance. The same instance is + /// always returned. + /// + public static BigEndianBitConverter Big => BigConverter; + #endregion + + #region Double/primitive conversions + /// + /// Converts the specified double-precision floating point number to a + /// 64-bit signed integer. Note: the endianness of this converter does not + /// affect the returned value. + /// + /// The number to convert. + /// A 64-bit signed integer whose value is equivalent to value. + public long DoubleToInt64Bits(double value) + { + return BitConverter.DoubleToInt64Bits(value); + } + + /// + /// Converts the specified 64-bit signed integer to a double-precision + /// floating point number. Note: the endianness of this converter does not + /// affect the returned value. + /// + /// The number to convert. + /// A double-precision floating point number whose value is equivalent to value. + public double Int64BitsToDouble(long value) + { + return BitConverter.Int64BitsToDouble(value); + } + + /// + /// Converts the specified single-precision floating point number to a + /// 32-bit signed integer. Note: the endianness of this converter does not + /// affect the returned value. + /// + /// The number to convert. + /// A 32-bit signed integer whose value is equivalent to value. + public int SingleToInt32Bits(float value) + { + return new Int32SingleUnion(value).AsInt32; + } + + /// + /// Converts the specified 32-bit signed integer to a single-precision floating point + /// number. Note: the endianness of this converter does not + /// affect the returned value. + /// + /// The number to convert. + /// A single-precision floating point number whose value is equivalent to value. + public float Int32BitsToSingle(int value) + { + return new Int32SingleUnion(value).AsSingle; + } + #endregion + + #region To(PrimitiveType) conversions + /// + /// Returns a Boolean value converted from one byte at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// true if the byte at startIndex in value is nonzero; otherwise, false. + public bool ToBoolean(byte[] value, int startIndex) + { + CheckByteArgument(value, startIndex, 1); + return BitConverter.ToBoolean(value, startIndex); + } + + /// + /// Returns a Unicode character converted from two bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A character formed by two bytes beginning at startIndex. + public char ToChar(byte[] value, int startIndex) + { + return unchecked((char)this.CheckedFromBytes(value, startIndex, 2)); + } + + /// + /// Returns a double-precision floating point number converted from eight bytes + /// at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A double precision floating point number formed by eight bytes beginning at startIndex. + public double ToDouble(byte[] value, int startIndex) + { + return this.Int64BitsToDouble(this.ToInt64(value, startIndex)); + } + + /// + /// Returns a single-precision floating point number converted from four bytes + /// at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A single precision floating point number formed by four bytes beginning at startIndex. + public float ToSingle(byte[] value, int startIndex) + { + return this.Int32BitsToSingle(this.ToInt32(value, startIndex)); + } + + /// + /// Returns a 16-bit signed integer converted from two bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 16-bit signed integer formed by two bytes beginning at startIndex. + public short ToInt16(byte[] value, int startIndex) + { + return unchecked((short)this.CheckedFromBytes(value, startIndex, 2)); + } + + /// + /// Returns a 32-bit signed integer converted from four bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 32-bit signed integer formed by four bytes beginning at startIndex. + public int ToInt32(byte[] value, int startIndex) + { + return unchecked((int)this.CheckedFromBytes(value, startIndex, 4)); + } + + /// + /// Returns a 64-bit signed integer converted from eight bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 64-bit signed integer formed by eight bytes beginning at startIndex. + public long ToInt64(byte[] value, int startIndex) + { + return this.CheckedFromBytes(value, startIndex, 8); + } + + /// + /// Returns a 16-bit unsigned integer converted from two bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 16-bit unsigned integer formed by two bytes beginning at startIndex. + public ushort ToUInt16(byte[] value, int startIndex) + { + return unchecked((ushort)this.CheckedFromBytes(value, startIndex, 2)); + } + + /// + /// Returns a 32-bit unsigned integer converted from four bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 32-bit unsigned integer formed by four bytes beginning at startIndex. + public uint ToUInt32(byte[] value, int startIndex) + { + return unchecked((uint)this.CheckedFromBytes(value, startIndex, 4)); + } + + /// + /// Returns a 64-bit unsigned integer converted from eight bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 64-bit unsigned integer formed by eight bytes beginning at startIndex. + public ulong ToUInt64(byte[] value, int startIndex) + { + return unchecked((ulong)this.CheckedFromBytes(value, startIndex, 8)); + } + + /// + /// Convert the given number of bytes from the given array, from the given start + /// position, into a long, using the bytes as the least significant part of the long. + /// By the time this is called, the arguments have been checked for validity. + /// + /// The bytes to convert + /// The index of the first byte to convert + /// The number of bytes to use in the conversion + /// The converted number + protected internal abstract long FromBytes(byte[] value, int startIndex, int bytesToConvert); + + /// + /// Checks the given argument for validity. + /// + /// The byte array passed in + /// The start index passed in + /// The number of bytes required + /// value is a null reference + /// + /// startIndex is less than zero or greater than the length of value minus bytesRequired. + /// + [SuppressMessage("ReSharper", "UnusedParameter.Local", Justification = "Keeps code DRY")] + private static void CheckByteArgument(byte[] value, int startIndex, int bytesRequired) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (startIndex < 0 || startIndex > value.Length - bytesRequired) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + } + + /// + /// Checks the arguments for validity before calling FromBytes + /// (which can therefore assume the arguments are valid). + /// + /// The bytes to convert after checking + /// The index of the first byte to convert + /// The number of bytes to convert + /// The + private long CheckedFromBytes(byte[] value, int startIndex, int bytesToConvert) + { + CheckByteArgument(value, startIndex, bytesToConvert); + return this.FromBytes(value, startIndex, bytesToConvert); + } + #endregion + + #region ToString conversions + /// + /// Returns a String converted from the elements of a byte array. + /// + /// An array of bytes. + /// All the elements of value are converted. + /// + /// A String of hexadecimal pairs separated by hyphens, where each pair + /// represents the corresponding element in value; for example, "7F-2C-4A". + /// + public static string ToString(byte[] value) + { + return BitConverter.ToString(value); + } + + /// + /// Returns a String converted from the elements of a byte array starting at a specified array position. + /// + /// An array of bytes. + /// The starting position within value. + /// The elements from array position startIndex to the end of the array are converted. + /// + /// A String of hexadecimal pairs separated by hyphens, where each pair + /// represents the corresponding element in value; for example, "7F-2C-4A". + /// + public static string ToString(byte[] value, int startIndex) + { + return BitConverter.ToString(value, startIndex); + } + + /// + /// Returns a String converted from a specified number of bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// The number of bytes to convert. + /// The length elements from array position startIndex are converted. + /// + /// A String of hexadecimal pairs separated by hyphens, where each pair + /// represents the corresponding element in value; for example, "7F-2C-4A". + /// + public static string ToString(byte[] value, int startIndex, int length) + { + return BitConverter.ToString(value, startIndex, length); + } + #endregion + + #region Decimal conversions + /// + /// Returns a decimal value converted from sixteen bytes + /// at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A decimal formed by sixteen bytes beginning at startIndex. + public decimal ToDecimal(byte[] value, int startIndex) + { + // HACK: This always assumes four parts, each in their own endianness, + // starting with the first part at the start of the byte array. + // On the other hand, there's no real format specified... + int[] parts = new int[4]; + for (int i = 0; i < 4; i++) + { + parts[i] = this.ToInt32(value, startIndex + (i * 4)); + } + + return new decimal(parts); + } + + /// + /// Returns the specified decimal value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 16. + public byte[] GetBytes(decimal value) + { + byte[] bytes = new byte[16]; + int[] parts = decimal.GetBits(value); + for (int i = 0; i < 4; i++) + { + this.CopyBytesImpl(parts[i], 4, bytes, i * 4); + } + + return bytes; + } + + /// + /// Copies the specified decimal value into the specified byte array, + /// beginning at the specified index. + /// + /// A character to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(decimal value, byte[] buffer, int index) + { + int[] parts = decimal.GetBits(value); + for (int i = 0; i < 4; i++) + { + this.CopyBytesImpl(parts[i], 4, buffer, (i * 4) + index); + } + } + #endregion + + #region GetBytes conversions + /// + /// Returns an array with the given number of bytes formed + /// from the least significant bytes of the specified value. + /// This is used to implement the other GetBytes methods. + /// + /// The value to get bytes for + /// The number of significant bytes to return + /// + /// The . + /// + private byte[] GetBytes(long value, int bytes) + { + byte[] buffer = new byte[bytes]; + this.CopyBytes(value, bytes, buffer, 0); + return buffer; + } + + /// + /// Returns the specified Boolean value as an array of bytes. + /// + /// A Boolean value. + /// An array of bytes with length 1. + /// + /// The . + /// + public byte[] GetBytes(bool value) + { + return BitConverter.GetBytes(value); + } + + /// + /// Returns the specified Unicode character value as an array of bytes. + /// + /// A character to convert. + /// An array of bytes with length 2. + /// + /// The . + /// + public byte[] GetBytes(char value) + { + return this.GetBytes(value, 2); + } + + /// + /// Returns the specified double-precision floating point value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 8. + public byte[] GetBytes(double value) + { + return this.GetBytes(this.DoubleToInt64Bits(value), 8); + } + + /// + /// Returns the specified 16-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 2. + public byte[] GetBytes(short value) + { + return this.GetBytes(value, 2); + } + + /// + /// Returns the specified 32-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 4. + public byte[] GetBytes(int value) + { + return this.GetBytes(value, 4); + } + + /// + /// Returns the specified 64-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 8. + public byte[] GetBytes(long value) + { + return this.GetBytes(value, 8); + } + + /// + /// Returns the specified single-precision floating point value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 4. + public byte[] GetBytes(float value) + { + return this.GetBytes(this.SingleToInt32Bits(value), 4); + } + + /// + /// Returns the specified 16-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 2. + public byte[] GetBytes(ushort value) + { + return this.GetBytes(value, 2); + } + + /// + /// Returns the specified 32-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 4. + public byte[] GetBytes(uint value) + { + return this.GetBytes(value, 4); + } + + /// + /// Returns the specified 64-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 8. + public byte[] GetBytes(ulong value) + { + return this.GetBytes(unchecked((long)value), 8); + } + + #endregion + + #region CopyBytes conversions + /// + /// Copies the given number of bytes from the least-specific + /// end of the specified value into the specified byte array, beginning + /// at the specified index. + /// This is used to implement the other CopyBytes methods. + /// + /// The value to copy bytes for + /// The number of significant bytes to copy + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + private void CopyBytes(long value, int bytes, byte[] buffer, int index) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer), "Byte array must not be null"); + } + + if (buffer.Length < index + bytes) + { + throw new ArgumentOutOfRangeException(nameof(buffer), "Buffer not big enough for value"); + } + + this.CopyBytesImpl(value, bytes, buffer, index); + } + + /// + /// Copies the given number of bytes from the least-specific + /// end of the specified value into the specified byte array, beginning + /// at the specified index. + /// This must be implemented in concrete derived classes, but the implementation + /// may assume that the value will fit into the buffer. + /// + /// The value to copy bytes for + /// The number of significant bytes to copy + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + protected internal abstract void CopyBytesImpl(long value, int bytes, byte[] buffer, int index); + + /// + /// Copies the specified Boolean value into the specified byte array, + /// beginning at the specified index. + /// + /// A Boolean value. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(bool value, byte[] buffer, int index) + { + this.CopyBytes(value ? 1 : 0, 1, buffer, index); + } + + /// + /// Copies the specified Unicode character value into the specified byte array, + /// beginning at the specified index. + /// + /// A character to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(char value, byte[] buffer, int index) + { + this.CopyBytes(value, 2, buffer, index); + } + + /// + /// Copies the specified double-precision floating point value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(double value, byte[] buffer, int index) + { + this.CopyBytes(this.DoubleToInt64Bits(value), 8, buffer, index); + } + + /// + /// Copies the specified 16-bit signed integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(short value, byte[] buffer, int index) + { + this.CopyBytes(value, 2, buffer, index); + } + + /// + /// Copies the specified 32-bit signed integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(int value, byte[] buffer, int index) + { + this.CopyBytes(value, 4, buffer, index); + } + + /// + /// Copies the specified 64-bit signed integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(long value, byte[] buffer, int index) + { + this.CopyBytes(value, 8, buffer, index); + } + + /// + /// Copies the specified single-precision floating point value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(float value, byte[] buffer, int index) + { + this.CopyBytes(this.SingleToInt32Bits(value), 4, buffer, index); + } + + /// + /// Copies the specified 16-bit unsigned integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(ushort value, byte[] buffer, int index) + { + this.CopyBytes(value, 2, buffer, index); + } + + /// + /// Copies the specified 32-bit unsigned integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(uint value, byte[] buffer, int index) + { + this.CopyBytes(value, 4, buffer, index); + } + + /// + /// Copies the specified 64-bit unsigned integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(ulong value, byte[] buffer, int index) + { + this.CopyBytes(unchecked((long)value), 8, buffer, index); + } + + #endregion + + #region Private struct used for Single/Int32 conversions + /// + /// Union used solely for the equivalent of DoubleToInt64Bits and vice versa. + /// + [StructLayout(LayoutKind.Explicit)] + private struct Int32SingleUnion + { + /// + /// Int32 version of the value. + /// + [FieldOffset(0)] + private readonly int i; + + /// + /// Single version of the value. + /// + [FieldOffset(0)] + private readonly float f; + + /// + /// Initializes a new instance of the struct. + /// + /// The integer value of the new instance. + internal Int32SingleUnion(int i) + { + this.f = 0; // Just to keep the compiler happy + this.i = i; + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The floating point value of the new instance. + /// + internal Int32SingleUnion(float f) + { + this.i = 0; // Just to keep the compiler happy + this.f = f; + } + + /// + /// Gets the value of the instance as an integer. + /// + internal int AsInt32 => this.i; + + /// + /// Gets the value of the instance as a floating point number. + /// + internal float AsSingle => this.f; + } + #endregion + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/IO/Endianness.cs b/src/ImageProcessorCore - Copy/IO/Endianness.cs new file mode 100644 index 0000000000..e8e4ef4c2d --- /dev/null +++ b/src/ImageProcessorCore - Copy/IO/Endianness.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.IO +{ + /// + /// Endianness of a converter + /// + internal enum Endianness + { + /// + /// Little endian - least significant byte first + /// + LittleEndian, + + /// + /// Big endian - most significant byte first + /// + BigEndian + } +} diff --git a/src/ImageProcessorCore - Copy/IO/LittleEndianBitConverter.cs b/src/ImageProcessorCore - Copy/IO/LittleEndianBitConverter.cs new file mode 100644 index 0000000000..70e65d9091 --- /dev/null +++ b/src/ImageProcessorCore - Copy/IO/LittleEndianBitConverter.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.IO +{ + /// + /// Implementation of EndianBitConverter which converts to/from little-endian + /// byte arrays. + /// + /// Adapted from Miscellaneous Utility Library + /// This product includes software developed by Jon Skeet and Marc Gravell. Contact , or see + /// . + /// + /// + internal sealed class LittleEndianBitConverter : EndianBitConverter + { + /// + public override Endianness Endianness => Endianness.LittleEndian; + + /// + public override bool IsLittleEndian() => true; + + /// + protected internal override void CopyBytesImpl(long value, int bytes, byte[] buffer, int index) + { + for (int i = 0; i < bytes; i++) + { + buffer[i + index] = unchecked((byte)(value & 0xff)); + value = value >> 8; + } + } + + /// + protected internal override long FromBytes(byte[] buffer, int startIndex, int bytesToConvert) + { + long ret = 0; + for (int i = 0; i < bytesToConvert; i++) + { + ret = unchecked((ret << 8) | buffer[startIndex + bytesToConvert - 1 - i]); + } + + return ret; + } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/Image.cs b/src/ImageProcessorCore - Copy/Image.cs new file mode 100644 index 0000000000..c8dc861d6a --- /dev/null +++ b/src/ImageProcessorCore - Copy/Image.cs @@ -0,0 +1,291 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System.IO; + using System.Text; + + using System; + using System.Collections.Generic; + using System.Linq; + + using Formats; + + /// + /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. + /// + /// + /// The packed vector containing pixel information. + /// + public class Image : ImageBase + where TPackedVector : IPackedVector + { + /// + /// The default horizontal resolution value (dots per inch) in x direction. + /// The default value is 96 dots per inch. + /// + public const double DefaultHorizontalResolution = 96; + + /// + /// The default vertical resolution value (dots per inch) in y direction. + /// The default value is 96 dots per inch. + /// + public const double DefaultVerticalResolution = 96; + + /// + /// Initializes a new instance of the class. + /// + public Image() + { + this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat)); + } + + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + /// The width of the image in pixels. + /// The height of the image in pixels. + public Image(int width, int height) + : base(width, height) + { + this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat)); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// Thrown if the is null. + public Image(Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + this.Load(stream); + } + + /// + /// Initializes a new instance of the class + /// by making a copy from another image. + /// + /// The other image, where the clone should be made from. + /// is null. + public Image(Image other) + { + foreach (ImageFrame frame in other.Frames) + { + if (frame != null) + { + this.Frames.Add(new ImageFrame(frame)); + } + } + + this.RepeatCount = other.RepeatCount; + this.HorizontalResolution = other.HorizontalResolution; + this.VerticalResolution = other.VerticalResolution; + this.CurrentImageFormat = other.CurrentImageFormat; + } + + /// + /// Gets a list of supported image formats. + /// + public IReadOnlyCollection Formats { get; } = Bootstrapper.Instance.ImageFormats; + + /// + /// Gets or sets the resolution of the image in x- direction. It is defined as + /// number of dots per inch and should be an positive value. + /// + /// The density of the image in x- direction. + public double HorizontalResolution { get; set; } = DefaultHorizontalResolution; + + /// + /// Gets or sets the resolution of the image in y- direction. It is defined as + /// number of dots per inch and should be an positive value. + /// + /// The density of the image in y- direction. + public double VerticalResolution { get; set; } = DefaultVerticalResolution; + + /// + /// Gets the width of the image in inches. It is calculated as the width of the image + /// in pixels multiplied with the density. When the density is equals or less than zero + /// the default value is used. + /// + /// The width of the image in inches. + public double InchWidth + { + get + { + double resolution = this.HorizontalResolution; + + if (resolution <= 0) + { + resolution = DefaultHorizontalResolution; + } + + return this.Width / resolution; + } + } + + /// + /// Gets the height of the image in inches. It is calculated as the height of the image + /// in pixels multiplied with the density. When the density is equals or less than zero + /// the default value is used. + /// + /// The height of the image in inches. + public double InchHeight + { + get + { + double resolution = this.VerticalResolution; + + if (resolution <= 0) + { + resolution = DefaultVerticalResolution; + } + + return this.Height / resolution; + } + } + + /// + /// Gets a value indicating whether this image is animated. + /// + /// + /// True if this image is animated; otherwise, false. + /// + public bool IsAnimated => this.Frames.Count > 0; + + /// + /// Gets or sets the number of times any animation is repeated. + /// 0 means to repeat indefinitely. + /// + public ushort RepeatCount { get; set; } + + /// + /// Gets the other frames for the animation. + /// + /// The list of frame images. + public IList> Frames { get; } = new List>(); + + /// + /// Gets the list of properties for storing meta information about this image. + /// + /// A list of image properties. + public IList Properties { get; } = new List(); + + /// + /// Gets the currently loaded image format. + /// + public IImageFormat CurrentImageFormat { get; internal set; } + + /// + public override IPixelAccessor Lock() + { + return Bootstrapper.Instance.GetPixelAccessor(this); + } + + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The stream to save the image to. + /// Thrown if the stream is null. + public void Save(Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + this.CurrentImageFormat.Encoder.Encode(this, stream); + } + + /// + /// Saves the image to the given stream using the given image format. + /// + /// The stream to save the image to. + /// The format to save the image as. + /// Thrown if the stream is null. + public void Save(Stream stream, IImageFormat format) + { + Guard.NotNull(stream, nameof(stream)); + format.Encoder.Encode(this, stream); + } + + /// + /// Saves the image to the given stream using the given image encoder. + /// + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + public void Save(Stream stream, IImageEncoder encoder) + { + Guard.NotNull(stream, nameof(stream)); + encoder.Encode(this, stream); + } + + /// + /// Returns a Base64 encoded string from the given image. + /// + /// data:image/gif;base64,R0lGODlhAQABAIABAEdJRgAAACwAAAAAAQABAAACAkQBAA== + /// The + public override string ToString() + { + using (MemoryStream stream = new MemoryStream()) + { + this.Save(stream); + stream.Flush(); + return $"data:{this.CurrentImageFormat.Encoder.MimeType};base64,{Convert.ToBase64String(stream.ToArray())}"; + } + } + + /// + /// Loads the image from the given stream. + /// + /// The stream containing image information. + /// + /// Thrown if the stream is not readable nor seekable. + /// + private void Load(Stream stream) + { + if (!this.Formats.Any()) { return; } + + if (!stream.CanRead) + { + throw new NotSupportedException("Cannot read from the stream."); + } + + if (!stream.CanSeek) + { + throw new NotSupportedException("The stream does not support seeking."); + } + + int maxHeaderSize = this.Formats.Max(x => x.Decoder.HeaderSize); + if (maxHeaderSize > 0) + { + byte[] header = new byte[maxHeaderSize]; + + stream.Position = 0; + stream.Read(header, 0, maxHeaderSize); + stream.Position = 0; + + IImageFormat format = this.Formats.FirstOrDefault(x => x.Decoder.IsSupportedFileFormat(header)); + if (format != null) + { + format.Decoder.Decode(this, stream); + this.CurrentImageFormat = format; + return; + } + } + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("Image cannot be loaded. Available formats:"); + + foreach (IImageFormat format in this.Formats) + { + stringBuilder.AppendLine("-" + format); + } + + throw new NotSupportedException(stringBuilder.ToString()); + } + } +} diff --git a/src/ImageProcessorCore - Copy/ImageBase.cs b/src/ImageProcessorCore - Copy/ImageBase.cs new file mode 100644 index 0000000000..5fa83df4c7 --- /dev/null +++ b/src/ImageProcessorCore - Copy/ImageBase.cs @@ -0,0 +1,201 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + + /// + /// The base class of all images. Encapsulates the basic properties and methods required to manipulate images + /// in different pixel formats. + /// + /// + /// The packed vector pixels format. + /// + public abstract class ImageBase : IImageBase + where TPacked : IPackedVector + { + /// + /// Initializes a new instance of the class. + /// + protected ImageBase() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The width of the image in pixels. + /// The height of the image in pixels. + /// + /// Thrown if either or are less than or equal to 0. + /// + protected ImageBase(int width, int height) + { + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + this.Width = width; + this.Height = height; + this.Pixels = new TPacked[width * height]; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The other to create this instance from. + /// + /// + /// Thrown if the given is null. + /// + protected ImageBase(ImageBase other) + { + Guard.NotNull(other, nameof(other), "Other image cannot be null."); + + this.Width = other.Width; + this.Height = other.Height; + this.Quality = other.Quality; + this.FrameDelay = other.FrameDelay; + + // Copy the pixels. + this.Pixels = new TPacked[this.Width * this.Height]; + Array.Copy(other.Pixels, this.Pixels, other.Pixels.Length); + } + + /// + /// Gets or sets the maximum allowable width in pixels. + /// + public int MaxWidth { get; set; } = int.MaxValue; + + /// + /// Gets or sets the maximum allowable height in pixels. + /// + public int MaxHeight { get; set; } = int.MaxValue; + + /// + /// Gets the pixels as an array of the given packed pixel format. + /// + public TPacked[] Pixels { get; private set; } + + /// + /// Gets the width in pixels. + /// + public int Width { get; private set; } + + /// + /// Gets the height in pixels. + /// + public int Height { get; private set; } + + /// + /// Gets the pixel ratio made up of the width and height. + /// + public double PixelRatio => (double)this.Width / this.Height; + + /// + /// Gets the representing the bounds of the image. + /// + public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height); + + /// + /// Gets or sets th quality of the image. This affects the output quality of lossy image formats. + /// + public int Quality { get; set; } + + /// + /// Gets or sets the frame delay for animated images. + /// If not 0, this field specifies the number of hundredths (1/100) of a second to + /// wait before continuing with the processing of the Data Stream. + /// The clock starts ticking immediately after the graphic is rendered. + /// + public int FrameDelay { get; set; } + + /// + /// Sets the pixel array of the image to the given value. + /// + /// 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 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. + /// + public void SetPixels(int width, int height, TPacked[] pixels) + { + 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) + { + throw new ArgumentException("Pixel array must have the length of Width * Height."); + } + + this.Width = width; + this.Height = height; + this.Pixels = 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. + /// + public void ClonePixels(int width, int height, TPacked[] pixels) + { + 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) + { + throw new ArgumentException("Pixel array must have the length of Width * Height."); + } + + this.Width = width; + this.Height = height; + + // Copy the pixels. + this.Pixels = new TPacked[pixels.Length]; + Array.Copy(pixels, this.Pixels, pixels.Length); + } + + /// + /// Locks the image providing access to the pixels. + /// + /// It is imperative that the accessor is correctly disposed off after use. + /// + /// + /// The + public abstract IPixelAccessor Lock(); + } +} diff --git a/src/ImageProcessorCore - Copy/ImageExtensions.cs b/src/ImageProcessorCore - Copy/ImageExtensions.cs new file mode 100644 index 0000000000..e7b261d608 --- /dev/null +++ b/src/ImageProcessorCore - Copy/ImageExtensions.cs @@ -0,0 +1,166 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.IO; + + using Formats; + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Saves the image to the given stream with the bmp format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsBmp(this ImageBase source, Stream stream) => new BmpEncoder().Encode(source, stream); + + /// + /// Saves the image to the given stream with the png format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The quality to save the image to representing the number of colors. + /// Anything equal to 256 and below will cause the encoder to save the image in an indexed format. + /// + /// Thrown if the stream is null. + public static void SaveAsPng(this ImageBase source, Stream stream, int quality = Int32.MaxValue) => new PngEncoder { Quality = quality }.Encode(source, stream); + + /// + /// Saves the image to the given stream with the jpeg format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The quality to save the image to. Between 1 and 100. + /// Thrown if the stream is null. + public static void SaveAsJpeg(this ImageBase source, Stream stream, int quality = 75) => new JpegEncoder { Quality = quality }.Encode(source, stream); + + /// + /// Saves the image to the given stream with the gif format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The quality to save the image to representing the number of colors. Between 1 and 256. + /// Thrown if the stream is null. + public static void SaveAsGif(this ImageBase source, Stream stream, int quality = 256) => new GifEncoder { Quality = quality }.Encode(source, stream); + + /// + /// Applies the collection of processors to the image. + /// This method does not resize the target image. + /// + /// The image this method extends. + /// The processor to apply to the image. + /// The . + public static Image Process(this Image source, IImageProcessor processor) + { + return Process(source, source.Bounds, processor); + } + + /// + /// Applies the collection of processors to the image. + /// This method does not resize the target image. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The processors to apply to the image. + /// The . + public static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor) + { + return PerformAction(source, true, (sourceImage, targetImage) => processor.Apply(targetImage, sourceImage, sourceRectangle)); + } + + /// + /// Applies the collection of processors to the image. + /// + /// This method is not chainable. + /// + /// + /// The source image. Cannot be null. + /// The target image width. + /// The target image height. + /// The processor to apply to the image. + /// The . + public static Image Process(this Image source, int width, int height, IImageSampler sampler) + { + return Process(source, width, height, source.Bounds, default(Rectangle), sampler); + } + + /// + /// Applies the collection of processors to the image. + /// + /// This method does will resize the target image if the source and target rectangles are different. + /// + /// + /// The source image. Cannot be null. + /// The target image width. + /// The target image height. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. + /// + /// The processor to apply to the image. + /// The . + public static Image Process(this Image source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler) + { + return PerformAction(source, false, (sourceImage, targetImage) => sampler.Apply(targetImage, sourceImage, width, height, targetRectangle, sourceRectangle)); + } + + /// + /// Performs the given action on the source image. + /// + /// The image to perform the action against. + /// Whether to clone the image. + /// The to perform against the image. + /// The . + /// Thrown if the has been disposed. + private static Image PerformAction(Image source, bool clone, Action action) + { + Image transformedImage = clone + ? new Image(source) + : new Image + { + // Several properties require copying + // TODO: Check why we need to set these? + HorizontalResolution = source.HorizontalResolution, + VerticalResolution = source.VerticalResolution, + CurrentImageFormat = source.CurrentImageFormat, + RepeatCount = source.RepeatCount + }; + + action(source, transformedImage); + + for (int i = 0; i < source.Frames.Count; i++) + { + ImageFrame sourceFrame = source.Frames[i]; + ImageFrame tranformedFrame = clone ? new ImageFrame(sourceFrame) : new ImageFrame { FrameDelay = sourceFrame.FrameDelay }; + action(sourceFrame, tranformedFrame); + + if (!clone) + { + transformedImage.Frames.Add(tranformedFrame); + } + else + { + transformedImage.Frames[i] = tranformedFrame; + } + } + + source = transformedImage; + return source; + } + } +} diff --git a/src/ImageProcessorCore - Copy/ImageFrame.cs b/src/ImageProcessorCore - Copy/ImageFrame.cs new file mode 100644 index 0000000000..479d9dd9b0 --- /dev/null +++ b/src/ImageProcessorCore - Copy/ImageFrame.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// Represents a single frame in a animation. + /// + /// + /// The packed vector containing pixel information. + /// + public class ImageFrame : ImageBase + where TPackedVector : IPackedVector + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The frame to create the frame from. + /// + public ImageFrame(ImageFrame frame) + : base(frame) + { + } + + /// + public override IPixelAccessor Lock() + { + return Bootstrapper.Instance.GetPixelAccessor(this); + } + } +} diff --git a/src/ImageProcessorCore - Copy/ImageProcessor.cs b/src/ImageProcessorCore - Copy/ImageProcessor.cs new file mode 100644 index 0000000000..e7057a3f9c --- /dev/null +++ b/src/ImageProcessorCore - Copy/ImageProcessor.cs @@ -0,0 +1,163 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System; + using System.Threading; + + /// + /// Allows the application of processors to images. + /// + public abstract class ImageProcessor : IImageProcessor + { + /// + public event ProgressEventHandler OnProgress; + + /// + /// The number of rows processed by a derived class. + /// + private int numRowsProcessed; + + /// + /// The total number of rows that will be processed by a derived class. + /// + private int totalRows; + + /// + public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) + where TPackedVector : IPackedVector + { + try + { + this.OnApply(target, source, target.Bounds, sourceRectangle); + + this.numRowsProcessed = 0; + this.totalRows = sourceRectangle.Height; + + this.Apply(target, source, target.Bounds, sourceRectangle, sourceRectangle.Y, sourceRectangle.Bottom); + + this.AfterApply(target, source, target.Bounds, sourceRectangle); + } + catch (Exception ex) + { + + throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); + } + } + + /// + public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) + where TPackedVector : IPackedVector + { + try + { + TPackedVector[] pixels = new TPackedVector[width * height]; + target.SetPixels(width, height, pixels); + + // Ensure we always have bounds. + if (sourceRectangle == Rectangle.Empty) + { + sourceRectangle = source.Bounds; + } + + if (targetRectangle == Rectangle.Empty) + { + targetRectangle = target.Bounds; + } + + this.OnApply(target, source, targetRectangle, sourceRectangle); + + this.numRowsProcessed = 0; + this.totalRows = targetRectangle.Height; + + this.Apply(target, source, targetRectangle, sourceRectangle, targetRectangle.Y, targetRectangle.Bottom); + + this.AfterApply(target, source, target.Bounds, sourceRectangle); + } + catch (Exception ex) + { + throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); + } + } + + /// + /// This method is called before the process is applied to prepare the processor. + /// + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. + /// + /// + /// The structure that specifies the portion of the image object to draw. + /// + protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + where TPackedVector : IPackedVector + { + } + + /// + /// Applies the process to the specified portion of the specified at the specified location + /// and with the specified size. + /// + /// The type of pixels contained within the image. + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. + /// + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The index of the row within the source image to start processing. + /// The index of the row within the source image to end processing. + /// + /// The method keeps the source image unchanged and returns the + /// the result of image process as new image. + /// + protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) where TPackedVector : IPackedVector; + + /// + /// This method is called after the process is applied to prepare the processor. + /// + /// The type of pixels contained within the image. + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. + /// + /// + /// The structure that specifies the portion of the image object to draw. + /// + protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + where TPackedVector : IPackedVector + { + } + + /// + /// Must be called by derived classes after processing a single row. + /// + protected void OnRowProcessed() + { + if (this.OnProgress != null) + { + int currThreadNumRows = Interlocked.Add(ref this.numRowsProcessed, 1); + + // Multi-pass filters process multiple times more rows than totalRows, so update totalRows on the fly + if (currThreadNumRows > this.totalRows) + { + this.totalRows = currThreadNumRows; + } + + // Report progress. This may be on the client's thread, or on a Task library thread. + this.OnProgress(this, new ProgressEventArgs { RowsProcessed = currThreadNumRows, TotalRows = this.totalRows }); + } + } + } +} diff --git a/src/ImageProcessorCore - Copy/ImageProcessorCore.xproj b/src/ImageProcessorCore - Copy/ImageProcessorCore.xproj new file mode 100644 index 0000000000..ffe5b1cea0 --- /dev/null +++ b/src/ImageProcessorCore - Copy/ImageProcessorCore.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 2aa31a1f-142c-43f4-8687-09abca4b3a26 + ImageProcessorCore + .\obj + .\bin\ + v4.5.1 + + + 2.0 + + + True + + + \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/ImageProperty.cs b/src/ImageProcessorCore - Copy/ImageProperty.cs new file mode 100644 index 0000000000..1d8f5bb8ef --- /dev/null +++ b/src/ImageProcessorCore - Copy/ImageProperty.cs @@ -0,0 +1,153 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +// +// Stores meta information about a image, like the name of the author, +// the copyright information, the date, where the image was created +// or some other information. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessorCore +{ + using System; + + /// + /// Stores meta information about a image, like the name of the author, + /// the copyright information, the date, where the image was created + /// or some other information. + /// + public struct ImageProperty : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// + /// The name of the property. + /// + /// + /// The value of the property. + /// + public ImageProperty(string name, string value) + { + this.Name = name; + this.Value = value; + } + + /// + /// Gets the name of this indicating which kind of + /// information this property stores. + /// + /// + /// Typical properties are the author, copyright + /// information or other meta information. + /// + public string Name { get; } + + /// + /// The value of this . + /// + public string Value { get; } + + /// + /// Compares two objects. The result specifies whether the values + /// of the or properties of the two + /// objects are equal. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(ImageProperty left, ImageProperty right) + { + return left.Equals(right); + } + + /// + /// Compares two objects. The result specifies whether the values + /// of the or properties of the two + /// objects are unequal. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(ImageProperty left, ImageProperty right) + { + return !left.Equals(right); + } + + /// + /// Indicates whether this instance and a specified object are equal. + /// + /// + /// The object to compare with the current instance. + /// + /// + /// true if and this instance are the same type and represent the + /// same value; otherwise, false. + /// + public override bool Equals(object obj) + { + if (!(obj is ImageProperty)) + { + return false; + } + + ImageProperty other = (ImageProperty)obj; + + return other.Name == this.Name && other.Value == this.Value; + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = this.Name.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Value.GetHashCode(); + return hashCode; + } + } + + /// + /// Returns the fully qualified type name of this instance. + /// + /// + /// A containing a fully qualified type name. + /// + public override string ToString() + { + return $"ImageProperty [ Name={this.Name}, Value={this.Value} ]"; + } + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// + /// True if the current object is equal to the parameter; otherwise, false. + /// + /// An object to compare with this object. + public bool Equals(ImageProperty other) + { + return this.Name.Equals(other.Name) && this.Value.Equals(other.Value); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Numerics/Ellipse.cs b/src/ImageProcessorCore - Copy/Numerics/Ellipse.cs new file mode 100644 index 0000000000..5d87d1247b --- /dev/null +++ b/src/ImageProcessorCore - Copy/Numerics/Ellipse.cs @@ -0,0 +1,174 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.ComponentModel; + using System.Numerics; + + public struct Ellipse : IEquatable + { + /// + /// The center point. + /// + private Point center; + + /// + /// Represents a that has X and Y values set to zero. + /// + public static readonly Ellipse Empty = default(Ellipse); + + public Ellipse(Point center, float radiusX, float radiusY) + { + this.center = center; + this.RadiusX = radiusX; + this.RadiusY = radiusY; + } + + /// + /// Gets the x-radius of this . + /// + public float RadiusX { get; } + + /// + /// Gets the y-radius of this . + /// + public float RadiusY { get; } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Ellipse left, Ellipse right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(Ellipse left, Ellipse right) + { + return !left.Equals(right); + } + + /// + /// Returns the center point of the given + /// + /// The ellipse + /// + public static Vector2 Center(Ellipse ellipse) + { + return new Vector2(ellipse.center.X, ellipse.center.Y); + } + + /// + /// Determines if the specfied point is contained within the rectangular region defined by + /// this . + /// + /// The x-coordinate of the given point. + /// The y-coordinate of the given point. + /// The + public bool Contains(int x, int y) + { + if (this.RadiusX <= 0 || this.RadiusY <= 0) + { + return false; + } + + // TODO: SIMD? + // This is a more general form of the circle equation + // X^2/a^2 + Y^2/b^2 <= 1 + Point normalized = new Point(x - this.center.X, y - this.center.Y); + int nX = normalized.X; + int nY = normalized.Y; + + return (double)(nX * nX) / (this.RadiusX * this.RadiusX) + + (double)(nY * nY) / (this.RadiusY * this.RadiusY) + <= 1.0; + } + + /// + public override int GetHashCode() + { + return this.GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Ellipse [ Empty ]"; + } + + return + $"Ellipse [ RadiusX={this.RadiusX}, RadiusY={this.RadiusX}, Centre={this.center.X},{this.center.Y} ]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is Ellipse) + { + return this.Equals((Ellipse)obj); + } + + return false; + } + + /// + public bool Equals(Ellipse other) + { + return this.center.Equals(other.center) + && this.RadiusX.Equals(other.RadiusX) + && this.RadiusY.Equals(other.RadiusY); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private int GetHashCode(Ellipse ellipse) + { + unchecked + { + int hashCode = ellipse.center.GetHashCode(); + hashCode = (hashCode * 397) ^ ellipse.RadiusX.GetHashCode(); + hashCode = (hashCode * 397) ^ ellipse.RadiusY.GetHashCode(); + return hashCode; + } + } + } +} diff --git a/src/ImageProcessorCore - Copy/Numerics/Point.cs b/src/ImageProcessorCore - Copy/Numerics/Point.cs new file mode 100644 index 0000000000..818002f9ef --- /dev/null +++ b/src/ImageProcessorCore - Copy/Numerics/Point.cs @@ -0,0 +1,281 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageProcessorCore +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Represents an ordered pair of integer x- and y-coordinates that defines a point in + /// a two-dimensional plane. + /// + /// + /// 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 Point : IEquatable + { + /// + /// Represents a that has X and Y values set to zero. + /// + public static readonly Point Empty = default(Point); + + /// + /// The backing vector for SIMD support. + /// + private Vector2 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The horizontal position of the point. + /// The vertical position of the point. + public Point(int x, int y) + : this() + { + this.backingVector = new Vector2(x, y); + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector representing the width and height. + /// + public Point(Vector2 vector) + { + this.backingVector = new Vector2(vector.X, vector.Y); + } + + /// + /// The x-coordinate of this . + /// + public int X + { + get + { + return (int)this.backingVector.X; + } + + set + { + this.backingVector.X = value; + } + } + + /// + /// The y-coordinate of this . + /// + public int Y + { + get + { + return (int)this.backingVector.Y; + } + + set + { + this.backingVector.Y = value; + } + } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Computes the sum of adding two points. + /// + /// The point on the left hand of the operand. + /// The point on the right hand of the operand. + /// + /// The + /// + public static Point operator +(Point left, Point right) + { + return new Point(left.backingVector + right.backingVector); + } + + /// + /// Computes the difference left by subtracting one point from another. + /// + /// The point on the left hand of the operand. + /// The point on the right hand of the operand. + /// + /// The + /// + public static Point operator -(Point left, Point right) + { + return new Point(left.backingVector - right.backingVector); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Point left, Point right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(Point left, Point right) + { + return !left.Equals(right); + } + + /// + /// Gets a representation for this . + /// + /// A representation for this object. + public Vector2 ToVector2() + { + return new Vector2(this.X, this.Y); + } + + /// + /// Creates a rotation matrix for the given point and angle. + /// + /// The origin point to rotate around + /// Rotation in degrees + /// The rotation + public static Matrix3x2 CreateRotation(Point origin, float degrees) + { + float radians = ImageMaths.DegreesToRadians(degrees); + return Matrix3x2.CreateRotation(radians, origin.backingVector); + } + + /// + /// Rotates a point around a given a rotation matrix. + /// + /// The point to rotate + /// Rotation matrix used + /// The rotated + public static Point Rotate(Point point, Matrix3x2 rotation) + { + return new Point(Vector2.Transform(point.backingVector, rotation)); + } + + /// + /// Rotates a point around a given origin by the specified angle in degrees. + /// + /// The point to rotate + /// The center point to rotate around. + /// The angle in degrees. + /// The rotated + public static Point Rotate(Point point, Point origin, float degrees) + { + return new Point(Vector2.Transform(point.backingVector, CreateRotation(origin, degrees))); + } + + /// + /// Creates a skew matrix for the given point and angle. + /// + /// The origin point to rotate around + /// The x-angle in degrees. + /// The y-angle in degrees. + /// The rotation + public static Matrix3x2 CreateSkew(Point origin, float degreesX, float degreesY) + { + float radiansX = ImageMaths.DegreesToRadians(degreesX); + float radiansY = ImageMaths.DegreesToRadians(degreesY); + return Matrix3x2.CreateSkew(radiansX, radiansY, origin.backingVector); + } + + /// + /// Skews a point using a given a skew matrix. + /// + /// The point to rotate + /// Rotation matrix used + /// The rotated + public static Point Skew(Point point, Matrix3x2 skew) + { + return new Point(Vector2.Transform(point.backingVector, skew)); + } + + /// + /// Skews a point around a given origin by the specified angles in degrees. + /// + /// The point to skew. + /// The center point to rotate around. + /// The x-angle in degrees. + /// The y-angle in degrees. + /// The skewed + public static Point Skew(Point point, Point origin, float degreesX, float degreesY) + { + return new Point(Vector2.Transform(point.backingVector, CreateSkew(origin, degreesX, degreesY))); + } + + /// + public override int GetHashCode() + { + return this.GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Point [ Empty ]"; + } + + return $"Point [ X={this.X}, Y={this.Y} ]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is Point) + { + return this.Equals((Point)obj); + } + + return false; + } + + /// + public bool Equals(Point other) + { + return this.backingVector.Equals(other.backingVector); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private int GetHashCode(Point point) + { + return point.backingVector.GetHashCode(); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Numerics/Rectangle.cs b/src/ImageProcessorCore - Copy/Numerics/Rectangle.cs new file mode 100644 index 0000000000..6fe2c3a497 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Numerics/Rectangle.cs @@ -0,0 +1,291 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Stores a set of four integers that represent the location and size of a rectangle. + /// + /// + /// 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 Rectangle : IEquatable + { + /// + /// Represents a that has X, Y, Width, and Height values set to zero. + /// + public static readonly Rectangle Empty = default(Rectangle); + + /// + /// The backing vector for SIMD support. + /// + private Vector4 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The horizontal position of the rectangle. + /// The vertical position of the rectangle. + /// The width of the rectangle. + /// The height of the rectangle. + public Rectangle(int x, int y, int width, int height) + { + this.backingVector = new Vector4(x, y, width, height); + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The which specifies the rectangles point in a two-dimensional plane. + /// + /// + /// The which specifies the rectangles height and width. + /// + public Rectangle(Point point, Size size) + { + this.backingVector = new Vector4(point.X, point.Y, size.Width, size.Height); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector. + public Rectangle(Vector4 vector) + { + this.backingVector = vector; + } + + /// + /// The x-coordinate of this . + /// + public int X + { + get + { + return (int)this.backingVector.X; + } + + set + { + this.backingVector.X = value; + } + } + + /// + /// The y-coordinate of this . + /// + public int Y + { + get + { + return (int)this.backingVector.Y; + } + + set + { + this.backingVector.Y = value; + } + } + + /// + /// The width of this . + /// + public int Width + { + get + { + return (int)this.backingVector.Z; + } + + set + { + this.backingVector.Z = value; + } + } + + /// + /// The height of this . + /// + public int Height + { + get + { + return (int)this.backingVector.W; + } + + set + { + this.backingVector.W = value; + } + } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Gets the y-coordinate of the top edge of this . + /// + public int Top => this.Y; + + /// + /// Gets the x-coordinate of the right edge of this . + /// + public int Right => this.X + this.Width; + + /// + /// Gets the y-coordinate of the bottom edge of this . + /// + public int Bottom => this.Y + this.Height; + + /// + /// Gets the x-coordinate of the left edge of this . + /// + public int Left => this.X; + + /// + /// Computes the sum of adding two rectangles. + /// + /// The rectangle on the left hand of the operand. + /// The rectangle on the right hand of the operand. + /// + /// The + /// + public static Rectangle operator +(Rectangle left, Rectangle right) + { + return new Rectangle(left.backingVector + right.backingVector); + } + + /// + /// Computes the difference left by subtracting one rectangle from another. + /// + /// The rectangle on the left hand of the operand. + /// The rectangle on the right hand of the operand. + /// + /// The + /// + public static Rectangle operator -(Rectangle left, Rectangle right) + { + return new Rectangle(left.backingVector - right.backingVector); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Rectangle left, Rectangle right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(Rectangle left, Rectangle right) + { + return !left.Equals(right); + } + + /// + /// Determines if the specfied point is contained within the rectangular region defined by + /// this . + /// + /// The x-coordinate of the given point. + /// The y-coordinate of the given point. + /// The + public bool Contains(int x, int y) + { + // TODO: SIMD? + return this.X <= x + && x < this.Right + && this.Y <= y + && y < this.Bottom; + } + + /// + /// Returns the center point of the given + /// + /// The rectangle + /// + public static Point Center(Rectangle rectangle) + { + return new Point(rectangle.Left + rectangle.Width / 2, rectangle.Top + rectangle.Height / 2); + } + + /// + public override int GetHashCode() + { + return this.GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Rectangle [ Empty ]"; + } + + return + $"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is Rectangle) + { + return this.Equals((Rectangle)obj); + } + + return false; + } + + /// + public bool Equals(Rectangle other) + { + return this.backingVector.Equals(other.backingVector); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private int GetHashCode(Rectangle rectangle) + { + return rectangle.backingVector.GetHashCode(); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Numerics/Size.cs b/src/ImageProcessorCore - Copy/Numerics/Size.cs new file mode 100644 index 0000000000..4b416b2182 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Numerics/Size.cs @@ -0,0 +1,208 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Stores an ordered pair of integers, which specify a height and width. + /// + /// + /// 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 Size : IEquatable + { + /// + /// Represents a that has Width and Height values set to zero. + /// + public static readonly Size Empty = default(Size); + + /// + /// The backing vector for SIMD support. + /// + private Vector2 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The width of the size. + /// The height of the size. + public Size(int width, int height) + { + this.backingVector = new Vector2(width, height); + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector representing the width and height. + /// + public Size(Vector2 vector) + { + this.backingVector = new Vector2(vector.X, vector.Y); + } + + /// + /// The width of this . + /// + public int Width + { + get + { + return (int)this.backingVector.X; + } + + set + { + this.backingVector.X = value; + } + } + + /// + /// The height of this . + /// + public int Height + { + get + { + return (int)this.backingVector.Y; + } + + set + { + this.backingVector.Y = value; + } + } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Computes the sum of adding two sizes. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The + /// + public static Size operator +(Size left, Size right) + { + return new Size(left.backingVector + right.backingVector); + } + + /// + /// Computes the difference left by subtracting one size from another. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The + /// + public static Size operator -(Size left, Size right) + { + return new Size(left.backingVector - right.backingVector); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Size left, Size right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(Size left, Size right) + { + return !left.Equals(right); + } + + /// + /// Gets a representation for this . + /// + /// A representation for this object. + public Vector2 ToVector2() + { + return new Vector2(this.Width, this.Height); + } + + /// + public override int GetHashCode() + { + return this.GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Size [ Empty ]"; + } + + return + $"Size [ Width={this.Width}, Height={this.Height} ]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is Size) + { + return this.Equals((Size)obj); + } + + return false; + } + + /// + public bool Equals(Size other) + { + return this.backingVector.Equals(other.backingVector); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private int GetHashCode(Size size) + { + return size.backingVector.GetHashCode(); + } + } +} diff --git a/src/ImageProcessorCore - Copy/PackedVector/Bgra32.cs b/src/ImageProcessorCore - Copy/PackedVector/Bgra32.cs new file mode 100644 index 0000000000..fc6a788b08 --- /dev/null +++ b/src/ImageProcessorCore - Copy/PackedVector/Bgra32.cs @@ -0,0 +1,168 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.Numerics; + + /// + /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 1. + /// + public struct Bgra32 : IPackedVector, IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// The blue component. + /// The green component. + /// The red component. + /// The alpha component. + public Bgra32(float b, float g, float r, float a) + { + Vector4 clamped = Vector4.Clamp(new Vector4(b, g, r, a), Vector4.Zero, Vector4.One) * 255f; + this.PackedValue = Pack(ref clamped); + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + public Bgra32(Vector4 vector) + { + Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; + this.PackedValue = Pack(ref clamped); + } + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Bgra32 left, Bgra32 right) + { + return left.PackedValue == right.PackedValue; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator !=(Bgra32 left, Bgra32 right) + { + return left.PackedValue != right.PackedValue; + } + + /// + public void PackVector(Vector4 vector) + { + Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; + this.PackedValue = Pack(ref clamped); + } + + /// + public void PackBytes(byte x, byte y, byte z, byte w) + { + Vector4 vector = new Vector4(x, y, z, w); + this.PackedValue = Pack(ref vector); + } + + /// + public Vector4 ToVector4() + { + return new Vector4( + this.PackedValue & 0xFF, + (this.PackedValue >> 8) & 0xFF, + (this.PackedValue >> 16) & 0xFF, + (this.PackedValue >> 24) & 0xFF) / 255f; + } + + /// + public byte[] ToBytes() + { + return new[] + { + (byte)(this.PackedValue & 0xFF), + (byte)((this.PackedValue >> 8) & 0xFF), + (byte)((this.PackedValue >> 16) & 0xFF), + (byte)((this.PackedValue >> 24) & 0xFF) + }; + } + + /// + public override bool Equals(object obj) + { + return (obj is Bgra32) && this.Equals((Bgra32)obj); + } + + /// + public bool Equals(Bgra32 other) + { + return this.PackedValue == other.PackedValue; + } + + /// + /// Gets a string representation of the packed vector. + /// + /// A string representation of the packed vector. + public override string ToString() + { + return this.ToVector4().ToString(); + } + + /// + public override int GetHashCode() + { + return this.GetHashCode(this); + } + + /// + /// Sets the packed representation from the given component values. + /// + /// + /// The vector containing the components for the packed vector. + /// + /// + /// The . + /// + private static uint Pack(ref Vector4 vector) + { + return (uint)Math.Round(vector.X) | + ((uint)Math.Round(vector.Y) << 8) | + ((uint)Math.Round(vector.Z) << 16) | + ((uint)Math.Round(vector.W) << 24); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private int GetHashCode(Bgra32 packed) + { + return packed.PackedValue.GetHashCode(); + } + } +} diff --git a/src/ImageProcessorCore - Copy/PackedVector/IPackedVector.cs b/src/ImageProcessorCore - Copy/PackedVector/IPackedVector.cs new file mode 100644 index 0000000000..02e10cc3a5 --- /dev/null +++ b/src/ImageProcessorCore - Copy/PackedVector/IPackedVector.cs @@ -0,0 +1,61 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System.Numerics; + + /// + /// An interface that converts packed vector types to and from values, + /// allowing multiple encodings to be manipulated in a generic way. + /// + /// + /// The type of object representing the packed value. + /// + public interface IPackedVector : IPackedVector + where TPacked : struct + { + /// + /// Gets or sets the packed representation of the value. + /// Typically packed in least to greatest significance order. + /// + TPacked PackedValue { get; set; } + } + + /// + /// An interface that converts packed vector types to and from values. + /// + public interface IPackedVector + { + /// + /// Sets the packed representation from a . + /// + /// The vector to pack. + void PackVector(Vector4 vector); + + /// + /// Sets the packed representation from a . + /// + /// The x-component. + /// The y-component. + /// The z-component. + /// The w-component. + void PackBytes(byte x, byte y, byte z, byte w); + + /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + Vector4 ToVector4(); + + /// + /// Expands the packed representation into a . + /// The bytes are typically expanded in least to greatest significance order. + /// + /// The . + byte[] ToBytes(); + } +} diff --git a/src/ImageProcessorCore - Copy/PixelAccessor/Bgra32PixelAccessor.cs b/src/ImageProcessorCore - Copy/PixelAccessor/Bgra32PixelAccessor.cs new file mode 100644 index 0000000000..cecc148b93 --- /dev/null +++ b/src/ImageProcessorCore - Copy/PixelAccessor/Bgra32PixelAccessor.cs @@ -0,0 +1,155 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.Runtime.InteropServices; + + /// + /// Provides per-pixel access to an images pixels. + /// + /// + /// The image data is always stored in format, where the blue, green, red, and + /// alpha values are 8 bit unsigned bytes. + /// + public sealed unsafe class Bgra32PixelAccessor : IPixelAccessor + { + /// + /// The position of the first pixel in the bitmap. + /// + private Bgra32* pixelsBase; + + /// + /// Provides a way to access the pixels from unmanaged memory. + /// + private GCHandle pixelsHandle; + + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// If the entity is disposed, it must not be disposed a second + /// time. The isDisposed field is set the first time the entity + /// is disposed. If the isDisposed field is true, then the Dispose() + /// method will not dispose again. This help not to prolong the entity's + /// life in the Garbage Collector. + /// + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The image to provide pixel access for. + /// + public Bgra32PixelAccessor(IImageBase image) + { + Guard.NotNull(image, nameof(image)); + Guard.MustBeGreaterThan(image.Width, 0, "image width"); + Guard.MustBeGreaterThan(image.Height, 0, "image height"); + + this.Width = image.Width; + this.Height = image.Height; + + this.pixelsHandle = GCHandle.Alloc(image.Pixels, GCHandleType.Pinned); + this.pixelsBase = (Bgra32*)this.pixelsHandle.AddrOfPinnedObject().ToPointer(); + } + + /// + /// Finalizes an instance of the class. + /// + ~Bgra32PixelAccessor() + { + this.Dispose(); + } + + /// + /// Gets the width of the image. + /// + public int Width { get; } + + /// + /// Gets the height of the image. + /// + public int Height { get; } + + /// + /// Gets or sets the color of a pixel at the specified position. + /// + /// + /// The x-coordinate of the pixel. Must be greater + /// than zero and smaller than the width of the pixel. + /// + /// + /// The y-coordinate of the pixel. Must be greater + /// than zero and smaller than the width of the pixel. + /// + /// The at the specified position. + public IPackedVector this[int x, int y] + { + get + { +#if DEBUG + if ((x < 0) || (x >= this.Width)) + { + throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width."); + } + + if ((y < 0) || (y >= this.Height)) + { + throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); + } +#endif + return *(this.pixelsBase + ((y * this.Width) + x)); + } + + set + { +#if DEBUG + if ((x < 0) || (x >= this.Width)) + { + throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width."); + } + + if ((y < 0) || (y >= this.Height)) + { + throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); + } +#endif + *(this.pixelsBase + ((y * this.Width) + x)) = (Bgra32)value; + } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + if (this.pixelsHandle.IsAllocated) + { + this.pixelsHandle.Free(); + } + + this.pixelsBase = null; + + // Note disposing is done. + this.isDisposed = true; + + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SuppressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + } +} diff --git a/src/ImageProcessorCore - Copy/PixelAccessor/IPixelAccessor.cs b/src/ImageProcessorCore - Copy/PixelAccessor/IPixelAccessor.cs new file mode 100644 index 0000000000..bf3a4067c4 --- /dev/null +++ b/src/ImageProcessorCore - Copy/PixelAccessor/IPixelAccessor.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + + /// + /// Encapsulates properties to provides per-pixel access to an images pixels. + /// + public interface IPixelAccessor : IDisposable + { + /// + /// Gets the width of the image in pixels. + /// + int Width { get; } + + /// + /// Gets the height of the image in pixels. + /// + int Height { get; } + + /// + /// Gets or sets the pixel at the specified position. + /// + /// + /// The x-coordinate of the pixel. Must be greater + /// than zero and smaller than the width of the pixel. + /// + /// + /// The y-coordinate of the pixel. Must be greater + /// than zero and smaller than the width of the pixel. + /// + /// The at the specified position. + IPackedVector this[int x, int y] + { + get; + set; + } + } +} diff --git a/src/ImageProcessorCore - Copy/ProgressEventArgs.cs b/src/ImageProcessorCore - Copy/ProgressEventArgs.cs new file mode 100644 index 0000000000..0f4c027f5e --- /dev/null +++ b/src/ImageProcessorCore - Copy/ProgressEventArgs.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// Contains event data related to the progress made processing an image. + /// + public class ProgressEventArgs : System.EventArgs + { + /// + /// Gets or sets the number of rows processed. + /// + public int RowsProcessed { get; set; } + + /// + /// Gets or sets the total number of rows. + /// + public int TotalRows { get; set; } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/Properties/AssemblyInfo.cs b/src/ImageProcessorCore - Copy/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..ea81ff60c6 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Properties/AssemblyInfo.cs @@ -0,0 +1,34 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ImageProcessorCore")] +[assembly: AssemblyDescription("A cross-platform library for processing of image files written in C#")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("James Jackson-South")] +[assembly: AssemblyProduct("ImageProcessorCore")] +[assembly: AssemblyCopyright("Copyright (c) James Jackson-South and contributors.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +// Ensure the internals can be tested. +[assembly: InternalsVisibleTo("ImageProcessorCore.Benchmarks")] +[assembly: InternalsVisibleTo("ImageProcessorCore.Tests")] diff --git a/src/ImageProcessorCore/Quantizers/IQuantizer.cs b/src/ImageProcessorCore - Copy/Quantizers/IQuantizer.cs similarity index 100% rename from src/ImageProcessorCore/Quantizers/IQuantizer.cs rename to src/ImageProcessorCore - Copy/Quantizers/IQuantizer.cs diff --git a/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageProcessorCore - Copy/Quantizers/Octree/OctreeQuantizer.cs similarity index 100% rename from src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs rename to src/ImageProcessorCore - Copy/Quantizers/Octree/OctreeQuantizer.cs diff --git a/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs b/src/ImageProcessorCore - Copy/Quantizers/Octree/Quantizer.cs similarity index 100% rename from src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs rename to src/ImageProcessorCore - Copy/Quantizers/Octree/Quantizer.cs diff --git a/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs b/src/ImageProcessorCore - Copy/Quantizers/Palette/PaletteQuantizer.cs similarity index 100% rename from src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs rename to src/ImageProcessorCore - Copy/Quantizers/Palette/PaletteQuantizer.cs diff --git a/src/ImageProcessorCore/Quantizers/QuantizedImage.cs b/src/ImageProcessorCore - Copy/Quantizers/QuantizedImage.cs similarity index 100% rename from src/ImageProcessorCore/Quantizers/QuantizedImage.cs rename to src/ImageProcessorCore - Copy/Quantizers/QuantizedImage.cs diff --git a/src/ImageProcessorCore/Quantizers/Wu/Box.cs b/src/ImageProcessorCore - Copy/Quantizers/Wu/Box.cs similarity index 100% rename from src/ImageProcessorCore/Quantizers/Wu/Box.cs rename to src/ImageProcessorCore - Copy/Quantizers/Wu/Box.cs diff --git a/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs b/src/ImageProcessorCore - Copy/Quantizers/Wu/WuQuantizer.cs similarity index 100% rename from src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs rename to src/ImageProcessorCore - Copy/Quantizers/Wu/WuQuantizer.cs diff --git a/src/ImageProcessorCore/Samplers/Crop.cs b/src/ImageProcessorCore - Copy/Samplers/Crop.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/Crop.cs rename to src/ImageProcessorCore - Copy/Samplers/Crop.cs diff --git a/src/ImageProcessorCore/Samplers/EntropyCrop.cs b/src/ImageProcessorCore - Copy/Samplers/EntropyCrop.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/EntropyCrop.cs rename to src/ImageProcessorCore - Copy/Samplers/EntropyCrop.cs diff --git a/src/ImageProcessorCore - Copy/Samplers/Options/AnchorPosition.cs b/src/ImageProcessorCore - Copy/Samplers/Options/AnchorPosition.cs new file mode 100644 index 0000000000..af840b292a --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Options/AnchorPosition.cs @@ -0,0 +1,58 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// Enumerated anchor positions to apply to resized images. + /// + public enum AnchorPosition + { + /// + /// Anchors the position of the image to the center of it's bounding container. + /// + Center, + + /// + /// Anchors the position of the image to the top of it's bounding container. + /// + Top, + + /// + /// Anchors the position of the image to the bottom of it's bounding container. + /// + Bottom, + + /// + /// Anchors the position of the image to the left of it's bounding container. + /// + Left, + + /// + /// Anchors the position of the image to the right of it's bounding container. + /// + Right, + + /// + /// Anchors the position of the image to the top left side of it's bounding container. + /// + TopLeft, + + /// + /// Anchors the position of the image to the top right side of it's bounding container. + /// + TopRight, + + /// + /// Anchors the position of the image to the bottom right side of it's bounding container. + /// + BottomRight, + + /// + /// Anchors the position of the image to the bottom left side of it's bounding container. + /// + BottomLeft + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Options/FlipType.cs b/src/ImageProcessorCore - Copy/Samplers/Options/FlipType.cs new file mode 100644 index 0000000000..c9b21a37e5 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Options/FlipType.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// Provides enumeration over how a image should be flipped. + /// + public enum FlipType + { + /// + /// Don't flip the image. + /// + None, + + /// + /// Flip the image horizontally. + /// + Horizontal, + + /// + /// Flip the image vertically. + /// + Vertical, + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Options/ResizeHelper.cs b/src/ImageProcessorCore - Copy/Samplers/Options/ResizeHelper.cs new file mode 100644 index 0000000000..a80ea47778 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Options/ResizeHelper.cs @@ -0,0 +1,430 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.Linq; + + /// + /// Provides methods to help calculate the target rectangle when resizing using the + /// enumeration. + /// + internal static class ResizeHelper + { + /// + /// Calculates the target location and bounds to perform the resize operation against. + /// + /// The source image. + /// The resize options. + /// + /// The . + /// + public static Rectangle CalculateTargetLocationAndBounds(ImageBase source, ResizeOptions options) + { + switch (options.Mode) + { + case ResizeMode.Crop: + return CalculateCropRectangle(source, options); + case ResizeMode.Pad: + return CalculatePadRectangle(source, options); + case ResizeMode.BoxPad: + return CalculateBoxPadRectangle(source, options); + case ResizeMode.Max: + return CalculateMaxRectangle(source, options); + case ResizeMode.Min: + return CalculateMinRectangle(source, options); + + // Last case ResizeMode.Stretch: + default: + return CalculateStretchRectangle(source, options); + } + } + + /// + /// Calculates the target rectangle for crop mode. + /// + /// The source image. + /// The resize options. + /// + /// The . + /// + private static Rectangle CalculateCropRectangle(ImageBase source, ResizeOptions options) + { + int width = options.Size.Width; + int height = options.Size.Height; + + if (width <= 0 || height <= 0) + { + return new Rectangle(0, 0, source.Width, source.Height); + } + + double ratio; + int sourceWidth = source.Width; + int sourceHeight = source.Height; + + int destinationX = 0; + int destinationY = 0; + int destinationWidth = width; + int destinationHeight = height; + + // Fractional variants for preserving aspect ratio. + double percentHeight = Math.Abs(height / (double)sourceHeight); + double percentWidth = Math.Abs(width / (double)sourceWidth); + + if (percentHeight < percentWidth) + { + ratio = percentWidth; + + if (options.CenterCoordinates.Any()) + { + double center = -(ratio * sourceHeight) * options.CenterCoordinates.First(); + destinationY = (int)center + (height / 2); + + if (destinationY > 0) + { + destinationY = 0; + } + + if (destinationY < (int)(height - (sourceHeight * ratio))) + { + destinationY = (int)(height - (sourceHeight * ratio)); + } + } + else + { + switch (options.Position) + { + case AnchorPosition.Top: + case AnchorPosition.TopLeft: + case AnchorPosition.TopRight: + destinationY = 0; + break; + case AnchorPosition.Bottom: + case AnchorPosition.BottomLeft: + case AnchorPosition.BottomRight: + destinationY = (int)(height - (sourceHeight * ratio)); + break; + default: + destinationY = (int)((height - (sourceHeight * ratio)) / 2); + break; + } + } + + destinationHeight = (int)Math.Ceiling(sourceHeight * percentWidth); + } + else + { + ratio = percentHeight; + + if (options.CenterCoordinates.Any()) + { + double center = -(ratio * sourceWidth) * options.CenterCoordinates.ToArray()[1]; + destinationX = (int)center + (width / 2); + + if (destinationX > 0) + { + destinationX = 0; + } + + if (destinationX < (int)(width - (sourceWidth * ratio))) + { + destinationX = (int)(width - (sourceWidth * ratio)); + } + } + else + { + switch (options.Position) + { + case AnchorPosition.Left: + case AnchorPosition.TopLeft: + case AnchorPosition.BottomLeft: + destinationX = 0; + break; + case AnchorPosition.Right: + case AnchorPosition.TopRight: + case AnchorPosition.BottomRight: + destinationX = (int)(width - (sourceWidth * ratio)); + break; + default: + destinationX = (int)((width - (sourceWidth * ratio)) / 2); + break; + } + } + + destinationWidth = (int)Math.Ceiling(sourceWidth * percentHeight); + } + + return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); + } + + /// + /// Calculates the target rectangle for pad mode. + /// + /// The source image. + /// The resize options. + /// + /// The . + /// + private static Rectangle CalculatePadRectangle(ImageBase source, ResizeOptions options) + { + int width = options.Size.Width; + int height = options.Size.Height; + + if (width <= 0 || height <= 0) + { + return new Rectangle(0, 0, source.Width, source.Height); + } + + double ratio; + int sourceWidth = source.Width; + int sourceHeight = source.Height; + + int destinationX = 0; + int destinationY = 0; + int destinationWidth = width; + int destinationHeight = height; + + // Fractional variants for preserving aspect ratio. + double percentHeight = Math.Abs(height / (double)sourceHeight); + double percentWidth = Math.Abs(width / (double)sourceWidth); + + if (percentHeight < percentWidth) + { + ratio = percentHeight; + destinationWidth = Convert.ToInt32(sourceWidth * percentHeight); + + switch (options.Position) + { + case AnchorPosition.Left: + case AnchorPosition.TopLeft: + case AnchorPosition.BottomLeft: + destinationX = 0; + break; + case AnchorPosition.Right: + case AnchorPosition.TopRight: + case AnchorPosition.BottomRight: + destinationX = (int)(width - (sourceWidth * ratio)); + break; + default: + destinationX = Convert.ToInt32((width - (sourceWidth * ratio)) / 2); + break; + } + } + else + { + ratio = percentWidth; + destinationHeight = Convert.ToInt32(sourceHeight * percentWidth); + + switch (options.Position) + { + case AnchorPosition.Top: + case AnchorPosition.TopLeft: + case AnchorPosition.TopRight: + destinationY = 0; + break; + case AnchorPosition.Bottom: + case AnchorPosition.BottomLeft: + case AnchorPosition.BottomRight: + destinationY = (int)(height - (sourceHeight * ratio)); + break; + default: + destinationY = (int)((height - (sourceHeight * ratio)) / 2); + break; + } + } + + return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); + } + + /// + /// Calculates the target rectangle for box pad mode. + /// + /// The source image. + /// The resize options. + /// + /// The . + /// + private static Rectangle CalculateBoxPadRectangle(ImageBase source, ResizeOptions options) + { + int width = options.Size.Width; + int height = options.Size.Height; + + if (width <= 0 || height <= 0) + { + return new Rectangle(0, 0, source.Width, source.Height); + } + + int sourceWidth = source.Width; + int sourceHeight = source.Height; + + // Fractional variants for preserving aspect ratio. + double percentHeight = Math.Abs(height / (double)sourceHeight); + double percentWidth = Math.Abs(width / (double)sourceWidth); + + int boxPadHeight = height > 0 ? height : Convert.ToInt32(sourceHeight * percentWidth); + int boxPadWidth = width > 0 ? width : Convert.ToInt32(sourceWidth * percentHeight); + + // Only calculate if upscaling. + if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight) + { + int destinationX; + int destinationY; + int destinationWidth = sourceWidth; + int destinationHeight = sourceHeight; + width = boxPadWidth; + height = boxPadHeight; + + switch (options.Position) + { + case AnchorPosition.Left: + destinationY = (height - sourceHeight) / 2; + destinationX = 0; + break; + case AnchorPosition.Right: + destinationY = (height - sourceHeight) / 2; + destinationX = width - sourceWidth; + break; + case AnchorPosition.TopRight: + destinationY = 0; + destinationX = width - sourceWidth; + break; + case AnchorPosition.Top: + destinationY = 0; + destinationX = (width - sourceWidth) / 2; + break; + case AnchorPosition.TopLeft: + destinationY = 0; + destinationX = 0; + break; + case AnchorPosition.BottomRight: + destinationY = height - sourceHeight; + destinationX = width - sourceWidth; + break; + case AnchorPosition.Bottom: + destinationY = height - sourceHeight; + destinationX = (width - sourceWidth) / 2; + break; + case AnchorPosition.BottomLeft: + destinationY = height - sourceHeight; + destinationX = 0; + break; + default: + destinationY = (height - sourceHeight) / 2; + destinationX = (width - sourceWidth) / 2; + break; + } + + return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); + } + + // Switch to pad mode to downscale and calculate from there. + return CalculatePadRectangle(source, options); + } + + /// + /// Calculates the target rectangle for max mode. + /// + /// The source image. + /// The resize options. + /// + /// The . + /// + private static Rectangle CalculateMaxRectangle(ImageBase source, ResizeOptions options) + { + int width = options.Size.Width; + int height = options.Size.Height; + int destinationWidth = width; + int destinationHeight = height; + + // Fractional variants for preserving aspect ratio. + double percentHeight = Math.Abs(height / (double)source.Height); + double percentWidth = Math.Abs(width / (double)source.Width); + + // Integers must be cast to doubles to get needed precision + double ratio = (double)options.Size.Height / options.Size.Width; + double sourceRatio = (double)source.Height / source.Width; + + if (sourceRatio < ratio) + { + destinationHeight = Convert.ToInt32(source.Height * percentWidth); + height = destinationHeight; + } + else + { + destinationWidth = Convert.ToInt32(source.Width * percentHeight); + width = destinationWidth; + } + + // Replace the size to match the rectangle. + options.Size = new Size(width, height); + return new Rectangle(0, 0, destinationWidth, destinationHeight); + } + + /// + /// Calculates the target rectangle for min mode. + /// + /// The source image. + /// The resize options. + /// + /// The . + /// + private static Rectangle CalculateMinRectangle(ImageBase source, ResizeOptions options) + { + int width = options.Size.Width; + int height = options.Size.Height; + int destinationWidth; + int destinationHeight; + + // Don't upscale + if (width > source.Width || height > source.Height) + { + options.Size = new Size(source.Width, source.Height); + return new Rectangle(0, 0, source.Width, source.Height); + } + + double sourceRatio = (double)source.Height / source.Width; + + // Find the shortest distance to go. + int widthDiff = source.Width - width; + int heightDiff = source.Height - height; + + if (widthDiff < heightDiff) + { + destinationHeight = Convert.ToInt32(width * sourceRatio); + height = destinationHeight; + destinationWidth = width; + } + else if (widthDiff > heightDiff) + { + destinationWidth = Convert.ToInt32(height / sourceRatio); + destinationHeight = height; + width = destinationWidth; + } + else + { + destinationWidth = width; + destinationHeight = height; + } + + // Replace the size to match the rectangle. + options.Size = new Size(width, height); + return new Rectangle(0, 0, destinationWidth, destinationHeight); + } + + /// + /// Calculates the target rectangle for stretch mode. + /// + /// The source image. + /// The resize options. + /// + /// The . + /// + private static Rectangle CalculateStretchRectangle(ImageBase source, ResizeOptions options) + { + return new Rectangle(0, 0, options.Size.Width, options.Size.Height); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Options/ResizeMode.cs b/src/ImageProcessorCore - Copy/Samplers/Options/ResizeMode.cs new file mode 100644 index 0000000000..a0ce943417 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Options/ResizeMode.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// Enumerated resize modes to apply to resized images. + /// + public enum ResizeMode + { + /// + /// Crops the resized image to fit the bounds of its container. + /// + Crop, + + /// + /// Pads the resized image to fit the bounds of its container. + /// If only one dimension is passed, will maintain the original aspect ratio. + /// + Pad, + + /// + /// Pads the image to fit the bound of the container without resizing the + /// original source. + /// When downscaling, performs the same functionality as + /// + BoxPad, + + /// + /// Constrains the resized image to fit the bounds of its container maintaining + /// the original aspect ratio. + /// + Max, + + /// + /// Resizes the image until the shortest side reaches the set given dimension. + /// Upscaling is disabled in this mode and the original image will be returned + /// if attempted. + /// + Min, + + /// + /// Stretches the resized image to fit the bounds of its container. + /// + Stretch + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Options/ResizeOptions.cs b/src/ImageProcessorCore - Copy/Samplers/Options/ResizeOptions.cs new file mode 100644 index 0000000000..82db8ed860 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Options/ResizeOptions.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System.Collections.Generic; + using System.Linq; + + /// + /// The resize options for resizing images against certain modes. + /// + public class ResizeOptions + { + /// + /// Gets or sets the resize mode. + /// + public ResizeMode Mode { get; set; } = ResizeMode.Crop; + + /// + /// Gets or sets the anchor position. + /// + public AnchorPosition Position { get; set; } = AnchorPosition.Center; + + /// + /// Gets or sets the center coordinates. + /// + public IEnumerable CenterCoordinates { get; set; } = Enumerable.Empty(); + + /// + /// Gets or sets the target size. + /// + public Size Size { get; set; } + + /// + /// Gets or sets the sampler to perform the resize operation. + /// + public IResampler Sampler { get; set; } = new BicubicResampler(); + + /// + /// Gets or sets a value indicating whether to compress + /// or expand individual pixel colors the value on processing. + /// + public bool Compand { get; set; } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Options/RotateType.cs b/src/ImageProcessorCore - Copy/Samplers/Options/RotateType.cs new file mode 100644 index 0000000000..43644de858 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Options/RotateType.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// 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/src/ImageProcessorCore/Samplers/Pad.cs b/src/ImageProcessorCore - Copy/Samplers/Pad.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/Pad.cs rename to src/ImageProcessorCore - Copy/Samplers/Pad.cs diff --git a/src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/CropProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs rename to src/ImageProcessorCore - Copy/Samplers/Processors/CropProcessor.cs diff --git a/src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/EntropyCropProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs rename to src/ImageProcessorCore - Copy/Samplers/Processors/EntropyCropProcessor.cs diff --git a/src/ImageProcessorCore - Copy/Samplers/Processors/IImageSampler.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/IImageSampler.cs new file mode 100644 index 0000000000..76a2c5a4d4 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Processors/IImageSampler.cs @@ -0,0 +1,19 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// Acts as a marker for generic parameters that require an image sampler. + /// + public interface IImageSampler : IImageProcessor + { + /// + /// Gets or sets a value indicating whether to compress + /// or expand individual pixel colors the value on processing. + /// + bool Compand { get; set; } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Processors/ImageSampler.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/ImageSampler.cs new file mode 100644 index 0000000000..adfe77432f --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Processors/ImageSampler.cs @@ -0,0 +1,17 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// Applies sampling methods to an image. + /// All processors requiring resampling or resizing should inherit from this. + /// + public abstract class ImageSampler : ImageProcessor, IImageSampler + { + /// + public virtual bool Compand { get; set; } = false; + } +} diff --git a/src/ImageProcessorCore/Samplers/Processors/Matrix3x2Processor.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/Matrix3x2Processor.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/Processors/Matrix3x2Processor.cs rename to src/ImageProcessorCore - Copy/Samplers/Processors/Matrix3x2Processor.cs diff --git a/src/ImageProcessorCore - Copy/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/ResizeProcessor.cs new file mode 100644 index 0000000000..95ead2bf04 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Processors/ResizeProcessor.cs @@ -0,0 +1,362 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System; + using System.Threading.Tasks; + + /// + /// Provides methods that allow the resizing of images using various algorithms. + /// + public class ResizeProcessor : ImageSampler + { + /// + /// The image used for storing the first pass pixels. + /// + private Image firstPass; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The sampler to perform the resize operation. + /// + public ResizeProcessor(IResampler sampler) + { + Guard.NotNull(sampler, nameof(sampler)); + + this.Sampler = sampler; + } + + /// + /// Gets the sampler to perform the resize operation. + /// + public IResampler Sampler { get; } + + /// + /// Gets or sets the horizontal weights. + /// + protected Weights[] HorizontalWeights { get; set; } + + /// + /// Gets or sets the vertical weights. + /// + protected Weights[] VerticalWeights { get; set; } + + /// + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + if (!(this.Sampler is NearestNeighborResampler)) + { + this.HorizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width); + this.VerticalWeights = this.PrecomputeWeights(targetRectangle.Height, sourceRectangle.Height); + } + + this.firstPass = new Image(target.Width, source.Height); + } + + /// + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + // Jump out, we'll deal with that later. + if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) + { + return; + } + + int width = target.Width; + int height = target.Height; + int sourceHeight = sourceRectangle.Height; + int targetX = target.Bounds.X; + int targetY = target.Bounds.Y; + int targetRight = target.Bounds.Right; + int targetBottom = target.Bounds.Bottom; + int startX = targetRectangle.X; + int endX = targetRectangle.Right; + bool compand = this.Compand; + + if (this.Sampler is NearestNeighborResampler) + { + // Scaling factors + float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width; + float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height; + + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => + { + if (targetY <= y && y < targetBottom) + { + // Y coordinates of source points + int originY = (int)((y - startY) * heightFactor); + + for (int x = startX; x < endX; x++) + { + if (targetX <= x && x < targetRight) + { + // X coordinates of source points + int originX = (int)((x - startX) * widthFactor); + + targetPixels[x, y] = sourcePixels[originX, originY]; + } + } + + this.OnRowProcessed(); + } + }); + } + + // Break out now. + return; + } + + // Interpolate the image using the calculated weights. + // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm + // First process the columns. Since we are not using multiple threads startY and endY + // are the upper and lower bounds of the source rectangle. + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor firstPassPixels = this.firstPass.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + 0, + sourceHeight, + y => + { + for (int x = startX; x < endX; x++) + { + if (x >= 0 && x < width) + { + // Ensure offsets are normalised for cropping and padding. + int offsetX = x - startX; + float sum = this.HorizontalWeights[offsetX].Sum; + Weight[] horizontalValues = this.HorizontalWeights[offsetX].Values; + + // Destination color components + Color destination = new Color(); + + for (int i = 0; i < sum; i++) + { + Weight xw = horizontalValues[i]; + int originX = xw.Index; + Color sourceColor = compand + ? Color.Expand(sourcePixels[originX, y]) + : sourcePixels[originX, y]; + + destination += sourceColor * xw.Value; + } + + if (compand) + { + destination = Color.Compress(destination); + } + + firstPassPixels[x, y] = destination; + } + } + }); + + // Now process the rows. + Parallel.For( + startY, + endY, + y => + { + if (y >= 0 && y < height) + { + // Ensure offsets are normalised for cropping and padding. + int offsetY = y - startY; + float sum = this.VerticalWeights[offsetY].Sum; + Weight[] verticalValues = this.VerticalWeights[offsetY].Values; + + for (int x = 0; x < width; x++) + { + // Destination color components + Color destination = new Color(); + + for (int i = 0; i < sum; i++) + { + Weight yw = verticalValues[i]; + int originY = yw.Index; + Color sourceColor = compand + ? Color.Expand(firstPassPixels[x, originY]) + : firstPassPixels[x, originY]; + + destination += sourceColor * yw.Value; + } + + if (compand) + { + destination = Color.Compress(destination); + } + + targetPixels[x, y] = destination; + } + } + + this.OnRowProcessed(); + }); + + } + } + + /// + protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + // Copy the pixels over. + if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) + { + target.ClonePixels(target.Width, target.Height, source.Pixels); + } + } + + /// + /// Computes the weights to apply at each pixel when resizing. + /// + /// The destination section size. + /// The source section size. + /// + /// The . + /// + protected Weights[] PrecomputeWeights(int destinationSize, int sourceSize) + { + float scale = (float)destinationSize / sourceSize; + IResampler sampler = this.Sampler; + float radius = sampler.Radius; + double left; + double right; + float weight; + int index; + int sum; + + Weights[] result = new Weights[destinationSize]; + + // When shrinking, broaden the effective kernel support so that we still + // visit every source pixel. + if (scale < 1) + { + float width = radius / scale; + float filterScale = 1 / scale; + + // Make the weights slices, one source for each column or row. + for (int i = 0; i < destinationSize; i++) + { + float centre = i / scale; + left = Math.Ceiling(centre - width); + right = Math.Floor(centre + width); + + result[i] = new Weights + { + Values = new Weight[(int)(right - left + 1)] + }; + + for (double j = left; j <= right; j++) + { + weight = sampler.GetValue((float)((centre - j) / filterScale)) / filterScale; + if (j < 0) + { + index = (int)-j; + } + else if (j >= sourceSize) + { + index = (int)((sourceSize - j) + sourceSize - 1); + } + else + { + index = (int)j; + } + + sum = (int)result[i].Sum++; + result[i].Values[sum] = new Weight(index, weight); + } + } + } + else + { + // Make the weights slices, one source for each column or row. + for (int i = 0; i < destinationSize; i++) + { + float centre = i / scale; + left = Math.Ceiling(centre - radius); + right = Math.Floor(centre + radius); + result[i] = new Weights + { + Values = new Weight[(int)(right - left + 1)] + }; + + for (double j = left; j <= right; j++) + { + weight = sampler.GetValue((float)(centre - j)); + if (j < 0) + { + index = (int)-j; + } + else if (j >= sourceSize) + { + index = (int)((sourceSize - j) + sourceSize - 1); + } + else + { + index = (int)j; + } + + sum = (int)result[i].Sum++; + result[i].Values[sum] = new Weight(index, weight); + } + } + } + + return result; + } + + /// + /// Represents the weight to be added to a scaled pixel. + /// + protected struct Weight + { + /// + /// Initializes a new instance of the struct. + /// + /// The index. + /// The value. + public Weight(int index, float value) + { + this.Index = index; + this.Value = value; + } + + /// + /// Gets the pixel index. + /// + public int Index { get; } + + /// + /// Gets the result of the interpolation algorithm. + /// + public float Value { get; } + } + + /// + /// Represents a collection of weights and their sum. + /// + protected class Weights + { + /// + /// Gets or sets the values. + /// + public Weight[] Values { get; set; } + + /// + /// Gets or sets the sum. + /// + public float Sum { get; set; } + } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/RotateFlipProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs rename to src/ImageProcessorCore - Copy/Samplers/Processors/RotateFlipProcessor.cs diff --git a/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/RotateProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs rename to src/ImageProcessorCore - Copy/Samplers/Processors/RotateProcessor.cs diff --git a/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/SkewProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs rename to src/ImageProcessorCore - Copy/Samplers/Processors/SkewProcessor.cs diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/BicubicResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/BicubicResampler.cs new file mode 100644 index 0000000000..8aecac7a60 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/BicubicResampler.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the bicubic kernel algorithm W(x) as described on + /// Wikipedia + /// A commonly used algorithm within imageprocessing that preserves sharpness better than triangle interpolation. + /// + public class BicubicResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + // The coefficient. + float a = -0.5f; + + if (x < 0) + { + x = -x; + } + + float result = 0; + + if (x <= 1) + { + result = (((1.5f * x) - 2.5f) * x * x) + 1; + } + else if (x < 2) + { + result = (((((a * x) + 2.5f) * x) - 4) * x) + 2; + } + + return result; + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/BoxResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/BoxResampler.cs new file mode 100644 index 0000000000..b1234e415d --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/BoxResampler.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the box algorithm. Similar to nearest neighbour when upscaling. + /// When downscaling the pixels will average, merging together. + /// + public class BoxResampler : IResampler + { + /// + public float Radius => 0.5F; + + /// + public float GetValue(float x) + { + if (x > -0.5 && x <= 0.5) + { + return 1; + } + + return 0; + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/CatmullRomResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/CatmullRomResampler.cs new file mode 100644 index 0000000000..0b5031df88 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/CatmullRomResampler.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The Catmull-Rom filter is a well known standard Cubic Filter often used as a interpolation function. + /// This filter produces a reasonably sharp edge, but without a the pronounced gradient change on large + /// scale image enlargements that a 'Lagrange' filter can produce. + /// + /// + public class CatmullRomResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 0; + const float C = 1 / 2f; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/HermiteResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/HermiteResampler.cs new file mode 100644 index 0000000000..49193a3de3 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/HermiteResampler.cs @@ -0,0 +1,27 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// The Hermite filter is type of smoothed triangular interpolation Filter, + /// This filter rounds off strong edges while preserving flat 'color levels' in the original image. + /// + /// + public class HermiteResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 0; + const float C = 0; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/IResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/IResampler.cs new file mode 100644 index 0000000000..0dea58440c --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/IResampler.cs @@ -0,0 +1,27 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// Encapsulates an interpolation algorithm for resampling images. + /// + public interface IResampler + { + /// + /// Gets the radius in which to sample pixels. + /// + float Radius { get; } + + /// + /// Gets the result of the interpolation algorithm. + /// + /// The value to process. + /// + /// The + /// + float GetValue(float x); + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos3Resampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos3Resampler.cs new file mode 100644 index 0000000000..a78b6c066a --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos3Resampler.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the Lanczos kernel algorithm as described on + /// Wikipedia + /// with a radius of 3 pixels. + /// + public class Lanczos3Resampler : IResampler + { + /// + public float Radius => 3; + + /// + public float GetValue(float x) + { + if (x < 0) + { + x = -x; + } + + if (x < 3) + { + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 3f); + } + + return 0; + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos5Resampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos5Resampler.cs new file mode 100644 index 0000000000..05af2dd7f2 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos5Resampler.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the Lanczos kernel algorithm as described on + /// Wikipedia + /// with a radius of 5 pixels. + /// + public class Lanczos5Resampler : IResampler + { + /// + public float Radius => 5; + + /// + public float GetValue(float x) + { + if (x < 0) + { + x = -x; + } + + if (x < 5) + { + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 5f); + } + + return 0; + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos8Resampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos8Resampler.cs new file mode 100644 index 0000000000..8c9a9237d9 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos8Resampler.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the Lanczos kernel algorithm as described on + /// Wikipedia + /// with a radius of 8 pixels. + /// + public class Lanczos8Resampler : IResampler + { + /// + public float Radius => 8; + + /// + public float GetValue(float x) + { + if (x < 0) + { + x = -x; + } + + if (x < 8) + { + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 8f); + } + + return 0; + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/MitchellNetravaliResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/MitchellNetravaliResampler.cs new file mode 100644 index 0000000000..f609f26450 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/MitchellNetravaliResampler.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the mitchell algorithm as described on + /// Wikipedia + /// + public class MitchellNetravaliResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 1 / 3f; + const float C = 1 / 3f; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/NearestNeighborResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/NearestNeighborResampler.cs new file mode 100644 index 0000000000..58b6a9d584 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/NearestNeighborResampler.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the nearest neighbour algorithm. This uses an unscaled filter + /// which will select the closest pixel to the new pixels position. + /// + public class NearestNeighborResampler : IResampler + { + /// + public float Radius => 1; + + /// + public float GetValue(float x) + { + return x; + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxResampler.cs new file mode 100644 index 0000000000..caead12d5d --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxResampler.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the Robidoux algorithm. + /// + /// + public class RobidouxResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 0.3782158F; + const float C = 0.3108921F; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxSharpResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxSharpResampler.cs new file mode 100644 index 0000000000..633503cd16 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxSharpResampler.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the Robidoux Sharp algorithm. + /// + /// + public class RobidouxSharpResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 0.26201451F; + const float C = 0.36899274F; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxSoftResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxSoftResampler.cs new file mode 100644 index 0000000000..8706f492bb --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxSoftResampler.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the Robidoux Soft algorithm. + /// + /// + public class RobidouxSoftResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 0.6796f; + const float C = 0.1602f; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/SplineResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/SplineResampler.cs new file mode 100644 index 0000000000..55ef5656a9 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/SplineResampler.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the spline algorithm. + /// + /// + public class SplineResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 1; + const float C = 0; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/TriangleResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/TriangleResampler.cs new file mode 100644 index 0000000000..cb404b7369 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/TriangleResampler.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the triangle (bilinear) algorithm. + /// Bilinear interpolation can be used where perfect image transformation with pixel matching is impossible, + /// so that one can calculate and assign appropriate intensity values to pixels. + /// + public class TriangleResampler : IResampler + { + /// + public float Radius => 1; + + /// + public float GetValue(float x) + { + if (x < 0) + { + x = -x; + } + + if (x < 1) + { + return 1 - x; + } + + return 0; + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/WelchResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/WelchResampler.cs new file mode 100644 index 0000000000..3ecaa6a747 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/WelchResampler.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the welch algorithm. + /// + /// + public class WelchResampler : IResampler + { + /// + public float Radius => 3; + + /// + public float GetValue(float x) + { + if (x < 0) + { + x = -x; + } + + if (x < 3) + { + return ImageMaths.SinC(x) * (1.0f - (x * x / 9.0f)); + } + + return 0; + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resize.cs b/src/ImageProcessorCore - Copy/Samplers/Resize.cs new file mode 100644 index 0000000000..2eadd7a11c --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resize.cs @@ -0,0 +1,134 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// ------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessorCore +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Resizes an image in accordance with the given . + /// + /// The image to resize. + /// The resize options. + /// A delegate which is called as progress is made processing the image. + /// The + /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image + public static Image Resize(this Image source, ResizeOptions options, ProgressEventHandler progressHandler = null) + { + // 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); + } + + if (options.Size.Height == 0 && options.Size.Width > 0) + { + options.Size = new Size(options.Size.Width, source.Height * options.Size.Width / source.Width); + } + + Rectangle targetRectangle = ResizeHelper.CalculateTargetLocationAndBounds(source, options); + + return Resize(source, options.Size.Width, options.Size.Height, options.Sampler, source.Bounds, targetRectangle, options.Compand, progressHandler); + } + + /// + /// Resizes an image to the given width and height. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// A delegate which is called as progress is made processing the image. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + public static Image Resize(this Image source, int width, int height, ProgressEventHandler progressHandler = null) + { + return Resize(source, width, height, new BicubicResampler(), false, progressHandler); + } + + /// + /// Resizes an image to the given width and height. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// A delegate which is called as progress is made processing the image. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + public static Image Resize(this Image source, int width, int height, bool compand, ProgressEventHandler progressHandler = null) + { + return Resize(source, width, height, new BicubicResampler(), compand, progressHandler); + } + + /// + /// Resizes an image to the given width and height with the given sampler. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to perform the resampling. + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// A delegate which is called as progress is made processing the image. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + public static Image Resize(this Image source, int width, int height, IResampler sampler, bool compand, ProgressEventHandler progressHandler = null) + { + return Resize(source, width, height, sampler, source.Bounds, new Rectangle(0, 0, width, height), compand, progressHandler); + } + + /// + /// Resizes an image to the given width and height with the given sampler and + /// source rectangle. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to perform the resampling. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// The structure that specifies the portion of the target image object to draw to. + /// + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// A delegate which is called as progress is made processing the image. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand = false, ProgressEventHandler progressHandler = null) + { + if (width == 0 && height > 0) + { + width = source.Width * height / source.Height; + targetRectangle.Width = width; + } + + if (height == 0 && width > 0) + { + height = source.Height * width / source.Width; + targetRectangle.Height = height; + } + + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + ResizeProcessor processor = new ResizeProcessor(sampler) { Compand = compand }; + processor.OnProgress += progressHandler; + + try + { + return source.Process(width, height, sourceRectangle, targetRectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Samplers/Rotate.cs b/src/ImageProcessorCore - Copy/Samplers/Rotate.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/Rotate.cs rename to src/ImageProcessorCore - Copy/Samplers/Rotate.cs diff --git a/src/ImageProcessorCore/Samplers/RotateFlip.cs b/src/ImageProcessorCore - Copy/Samplers/RotateFlip.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/RotateFlip.cs rename to src/ImageProcessorCore - Copy/Samplers/RotateFlip.cs diff --git a/src/ImageProcessorCore/Samplers/Skew.cs b/src/ImageProcessorCore - Copy/Samplers/Skew.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/Skew.cs rename to src/ImageProcessorCore - Copy/Samplers/Skew.cs diff --git a/src/ImageProcessorCore - Copy/project.json b/src/ImageProcessorCore - Copy/project.json new file mode 100644 index 0000000000..e3800d495e --- /dev/null +++ b/src/ImageProcessorCore - Copy/project.json @@ -0,0 +1,38 @@ +{ + "version": "1.0.0-*", + "title": "ImageProcessorCore", + "description": "A cross-platform library for processing of image files written in C#", + "authors": [ + "James Jackson-South and contributors" + ], + "packOptions": { + "projectUrl": "https://github.com/JimBobSquarePants/ImageProcessor", + "licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0", + "tags": [ + "Image Resize Crop Quality Gif Jpg Jpeg Bitmap Png Fluent Animated" + ] + }, + "buildOptions": { + "allowUnsafe": true, + "debugType": "portable" + }, + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Diagnostics.Tools": "4.0.1", + "System.IO": "4.1.0", + "System.IO.Compression": "4.1.0", + "System.Linq": "4.1.0", + "System.Numerics.Vectors": "4.1.1", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.InteropServices": "4.1.0", + "System.Text.Encoding.Extensions": "4.0.11", + "System.Threading": "4.0.11", + "System.Threading.Tasks": "4.0.11", + "System.Threading.Tasks.Parallel": "4.0.1" + }, + "frameworks": { + "netstandard1.1": {} + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore/Bootstrapper.cs b/src/ImageProcessorCore/Bootstrapper.cs index 5f2978534c..08698af320 100644 --- a/src/ImageProcessorCore/Bootstrapper.cs +++ b/src/ImageProcessorCore/Bootstrapper.cs @@ -38,9 +38,9 @@ namespace ImageProcessorCore this.imageFormats = new List { new BmpFormat(), - new JpegFormat(), - new PngFormat(), - new GifFormat() + //new JpegFormat(), + //new PngFormat(), + //new GifFormat() }; this.pixelAccessors = new Dictionary @@ -74,14 +74,16 @@ namespace ImageProcessorCore /// The type of pixel data. /// The image /// The - public IPixelAccessor GetPixelAccessor(Image image) - where TPackedVector : IPackedVector + public IPixelAccessor GetPixelAccessor(IImageBase image) + where TPackedVector : IPackedVector, new() { Type packed = typeof(TPackedVector); - if (!this.pixelAccessors.ContainsKey(packed)) + if (this.pixelAccessors.ContainsKey(packed)) { // TODO: Double check this. It should work... - return (IPixelAccessor)Activator.CreateInstance(this.pixelAccessors[packed], image); + + return (IPixelAccessor)new Bgra32PixelAccessor(image); + //return (IPixelAccessor)Activator.CreateInstance(this.pixelAccessors[packed], image); } StringBuilder stringBuilder = new StringBuilder(); diff --git a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs index 051b75f70f..2a55286a31 100644 --- a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs +++ b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs @@ -162,110 +162,110 @@ namespace ImageProcessorCore /// /// 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 = (pixels, x, y, b) => Math.Abs(pixels[x, y].R - b) > Epsilon; - break; - - case RgbaComponent.G: - delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].G - b) > Epsilon; - break; - - case RgbaComponent.A: - delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].A - b) > Epsilon; - break; - - default: - delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].B - b) > Epsilon; - break; - } - - Func getMinY = pixels => - { - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - if (delegateFunc(pixels, x, y, componentValue)) - { - return y; - } - } - } - - return 0; - }; - - Func getMaxY = pixels => - { - for (int y = height - 1; y > -1; y--) - { - for (int x = 0; x < width; x++) - { - if (delegateFunc(pixels, x, y, componentValue)) - { - return y; - } - } - } - - return height; - }; - - Func getMinX = pixels => - { - for (int x = 0; x < width; x++) - { - for (int y = 0; y < height; y++) - { - if (delegateFunc(pixels, x, y, componentValue)) - { - return x; - } - } - } - - return 0; - }; - - Func getMaxX = pixels => - { - for (int x = width - 1; x > -1; x--) - { - for (int y = 0; y < height; y++) - { - if (delegateFunc(pixels, x, y, componentValue)) - { - return x; - } - } - } - - return height; - }; - - using (PixelAccessor bitmapPixels = bitmap.Lock()) - { - topLeft.Y = getMinY(bitmapPixels); - topLeft.X = getMinX(bitmapPixels); - bottomRight.Y = (getMaxY(bitmapPixels) + 1).Clamp(0, height); - bottomRight.X = (getMaxX(bitmapPixels) + 1).Clamp(0, width); - } - - return GetBoundingRectangle(topLeft, bottomRight); - } + //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 = (pixels, x, y, b) => Math.Abs(pixels[x, y].R - b) > Epsilon; + // break; + + // case RgbaComponent.G: + // delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].G - b) > Epsilon; + // break; + + // case RgbaComponent.A: + // delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].A - b) > Epsilon; + // break; + + // default: + // delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].B - b) > Epsilon; + // break; + // } + + // Func getMinY = pixels => + // { + // for (int y = 0; y < height; y++) + // { + // for (int x = 0; x < width; x++) + // { + // if (delegateFunc(pixels, x, y, componentValue)) + // { + // return y; + // } + // } + // } + + // return 0; + // }; + + // Func getMaxY = pixels => + // { + // for (int y = height - 1; y > -1; y--) + // { + // for (int x = 0; x < width; x++) + // { + // if (delegateFunc(pixels, x, y, componentValue)) + // { + // return y; + // } + // } + // } + + // return height; + // }; + + // Func getMinX = pixels => + // { + // for (int x = 0; x < width; x++) + // { + // for (int y = 0; y < height; y++) + // { + // if (delegateFunc(pixels, x, y, componentValue)) + // { + // return x; + // } + // } + // } + + // return 0; + // }; + + // Func getMaxX = pixels => + // { + // for (int x = width - 1; x > -1; x--) + // { + // for (int y = 0; y < height; y++) + // { + // if (delegateFunc(pixels, x, y, componentValue)) + // { + // return x; + // } + // } + // } + + // return height; + // }; + + // using (PixelAccessor bitmapPixels = bitmap.Lock()) + // { + // topLeft.Y = getMinY(bitmapPixels); + // topLeft.X = getMinX(bitmapPixels); + // bottomRight.Y = (getMaxY(bitmapPixels) + 1).Clamp(0, height); + // bottomRight.X = (getMaxX(bitmapPixels) + 1).Clamp(0, width); + // } + + // return GetBoundingRectangle(topLeft, bottomRight); + //} /// /// Ensures that any passed double is correctly rounded to zero diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs index 03d45f1122..4872ba89dc 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs @@ -70,11 +70,12 @@ namespace ImageProcessorCore.Formats } /// - /// Decodes the image from the specified stream to the . + /// Decodes the image from the specified stream to the . /// - /// The to decode to. + /// The to decode to. /// The containing image data. - public void Decode(Image image, Stream stream) + public void Decode(Image image, Stream stream) + where TPackedVector : IPackedVector, new() { new BmpDecoderCore().Decode(image, stream); } diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs index da92e6a63c..4a36a07e31 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs @@ -49,6 +49,7 @@ namespace ImageProcessorCore.Formats /// Decodes the image from the specified this._stream and sets /// the data to image. /// + /// The type of pixels contained within the image. /// The image, where the data should be set to. /// Cannot be null (Nothing in Visual Basic). /// The this._stream, where the image should be @@ -58,7 +59,8 @@ namespace ImageProcessorCore.Formats /// - or - /// is null. /// - public void Decode(Image image, Stream stream) + public void Decode(Image image, Stream stream) + where TPackedVector : IPackedVector, new() { this.currentStream = stream; @@ -110,14 +112,14 @@ namespace ImageProcessorCore.Formats this.currentStream.Read(palette, 0, colorMapSize); } - if (this.infoHeader.Width > ImageBase.MaxWidth || this.infoHeader.Height > ImageBase.MaxHeight) + if (this.infoHeader.Width > image.MaxWidth || this.infoHeader.Height > image.MaxHeight) { throw new ArgumentOutOfRangeException( $"The input bitmap '{this.infoHeader.Width}x{this.infoHeader.Height}' is " - + $"bigger then the max allowed size '{ImageBase.MaxWidth}x{ImageBase.MaxHeight}'"); + + $"bigger then the max allowed size '{image.MaxWidth}x{image.MaxHeight}'"); } - float[] imageData = new float[this.infoHeader.Width * this.infoHeader.Height * 4]; + TPackedVector[] imageData = new TPackedVector[this.infoHeader.Width * this.infoHeader.Height]; switch (this.infoHeader.Compression) { @@ -169,6 +171,7 @@ namespace ImageProcessorCore.Formats /// /// The y- value representing the current row. /// The height of the bitmap. + /// Whether the bitmap is inverted. /// The representing the inverted value. private static int Invert(int y, int height, bool inverted) { @@ -189,12 +192,15 @@ namespace ImageProcessorCore.Formats /// /// Reads the color palette from the stream. /// - /// The image data to assign the palette to. + /// The type of pixels contained within the image. + /// The image data to assign the palette to. /// The containing the colors. /// The width of the bitmap. /// The height of the bitmap. /// The number of bits per pixel. - private void ReadRgbPalette(float[] imageData, byte[] colors, int width, int height, int bits, bool inverted) + /// Whether the bitmap is inverted. + private void ReadRgbPalette(TPackedVector[] imageData, byte[] colors, int width, int height, int bits, bool inverted) + where TPackedVector : IPackedVector, new() { // Pixels per byte (bits per pixel) int ppb = 8 / bits; @@ -234,14 +240,12 @@ namespace ImageProcessorCore.Formats for (int shift = 0; shift < ppb && (colOffset + shift) < width; shift++) { int colorIndex = ((data[offset] >> (8 - bits - (shift * bits))) & mask) * 4; - int arrayOffset = ((row * width) + (colOffset + shift)) * 4; - - // We divide by 255 as we will store the colors in our floating point format. - // Stored in r-> g-> b-> a order. - imageData[arrayOffset] = colors[colorIndex + 2] / 255f; // r - imageData[arrayOffset + 1] = colors[colorIndex + 1] / 255f; // g - imageData[arrayOffset + 2] = colors[colorIndex] / 255f; // b - imageData[arrayOffset + 3] = 1; // a + int arrayOffset = (row * width) + (colOffset + shift); + + // Stored in b-> g-> r-> a order. + TPackedVector packed = new TPackedVector(); + packed.PackBytes(colors[colorIndex], colors[colorIndex + 1], colors[colorIndex + 2], 255); + imageData[arrayOffset] = packed; } } }); @@ -250,14 +254,17 @@ namespace ImageProcessorCore.Formats /// /// Reads the 16 bit color palette from the stream /// - /// The image data to assign the palette to. + /// The type of pixels contained within the image. + /// The image data to assign the palette to. /// The width of the bitmap. /// The height of the bitmap. - private void ReadRgb16(float[] imageData, int width, int height, bool inverted) + /// Whether the bitmap is inverted. + private void ReadRgb16(TPackedVector[] imageData, int width, int height, bool inverted) + where TPackedVector : IPackedVector, new() { // We divide here as we will store the colors in our floating point format. - const float ScaleR = 0.25F; // (256 / 32) / 32 - const float ScaleG = 0.0625F; // (256 / 64) / 64 + const int ScaleR = 8; // 256/32 + const int ScaleG = 4; // 256/64 int alignment; byte[] data = this.GetImageArray(width, height, 2, out alignment); @@ -278,17 +285,16 @@ namespace ImageProcessorCore.Formats short temp = BitConverter.ToInt16(data, offset); - float r = ((temp & Rgb16RMask) >> 11) * ScaleR; - float g = ((temp & Rgb16GMask) >> 5) * ScaleG; - float b = (temp & Rgb16BMask) * ScaleR; + byte r = (byte)(((temp & Rgb16RMask) >> 11) * ScaleR); + byte g = (byte)(((temp & Rgb16GMask) >> 5) * ScaleG); + byte b = (byte)((temp & Rgb16BMask) * ScaleR); - int arrayOffset = ((row * width) + x) * 4; + int arrayOffset = ((row * width) + x); - // Stored in r-> g-> b-> a order. - imageData[arrayOffset] = r; - imageData[arrayOffset + 1] = g; - imageData[arrayOffset + 2] = b; - imageData[arrayOffset + 3] = 1; + // Stored in b-> g-> r-> a order. + TPackedVector packed = new TPackedVector(); + packed.PackBytes(b, g, r, 255); + imageData[arrayOffset] = packed; } }); } @@ -296,10 +302,13 @@ namespace ImageProcessorCore.Formats /// /// Reads the 24 bit color palette from the stream /// - /// The image data to assign the palette to. + /// The type of pixels contained within the image. + /// The image data to assign the palette to. /// The width of the bitmap. /// The height of the bitmap. - private void ReadRgb24(float[] imageData, int width, int height, bool inverted) + /// Whether the bitmap is inverted. + private void ReadRgb24(TPackedVector[] imageData, int width, int height, bool inverted) + where TPackedVector : IPackedVector, new() { int alignment; byte[] data = this.GetImageArray(width, height, 3, out alignment); @@ -317,14 +326,13 @@ namespace ImageProcessorCore.Formats for (int x = 0; x < width; x++) { int offset = rowOffset + (x * 3); - int arrayOffset = ((row * width) + x) * 4; + int arrayOffset = ((row * width) + x); // We divide by 255 as we will store the colors in our floating point format. - // Stored in r-> g-> b-> a order. - imageData[arrayOffset] = data[offset + 2] / 255f; - imageData[arrayOffset + 1] = data[offset + 1] / 255f; - imageData[arrayOffset + 2] = data[offset] / 255f; - imageData[arrayOffset + 3] = 1; + // Stored in b-> g-> r-> a order. + TPackedVector packed = new TPackedVector(); + packed.PackBytes(data[offset], data[offset + 1], data[offset + 2], 255); + imageData[arrayOffset] = packed; } }); } @@ -332,10 +340,13 @@ namespace ImageProcessorCore.Formats /// /// Reads the 32 bit color palette from the stream /// - /// The image data to assign the palette to. + /// The type of pixels contained within the image. + /// The image data to assign the palette to. /// The width of the bitmap. /// The height of the bitmap. - private void ReadRgb32(float[] imageData, int width, int height, bool inverted) + /// Whether the bitmap is inverted. + private void ReadRgb32(TPackedVector[] imageData, int width, int height, bool inverted) + where TPackedVector : IPackedVector, new() { int alignment; byte[] data = this.GetImageArray(width, height, 4, out alignment); @@ -353,14 +364,12 @@ namespace ImageProcessorCore.Formats for (int x = 0; x < width; x++) { int offset = rowOffset + (x * 4); - int arrayOffset = ((row * width) + x) * 4; + int arrayOffset = ((row * width) + x); - // We divide by 255 as we will store the colors in our floating point format. - // Stored in r-> g-> b-> a order. - imageData[arrayOffset] = data[offset + 2] / 255f; - imageData[arrayOffset + 1] = data[offset + 1] / 255f; - imageData[arrayOffset + 2] = data[offset] / 255f; - imageData[arrayOffset + 3] = 1; // TODO: Can we use our real alpha here? + // Stored in b-> g-> r-> a order. + TPackedVector packed = new TPackedVector(); + packed.PackBytes(data[offset], data[offset + 1], data[offset + 2], data[offset + 3]); + imageData[arrayOffset] = packed; } }); } diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs index 944154df21..4b212d4ea0 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs @@ -43,7 +43,8 @@ namespace ImageProcessorCore.Formats } /// - public void Encode(ImageBase image, Stream stream) + public void Encode(ImageBase image, Stream stream) + where TPackedVector: IPackedVector { BmpEncoderCore encoder = new BmpEncoderCore(); encoder.Encode(image, stream, this.BitsPerPixel); diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs index 8c16b4e40c..2ad8e24e6e 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs @@ -8,7 +8,7 @@ namespace ImageProcessorCore.Formats using System; using System.IO; - using ImageProcessorCore.IO; + using IO; /// /// Image encoder for writing an image to a stream as a Windows bitmap. @@ -22,12 +22,14 @@ namespace ImageProcessorCore.Formats private BmpBitsPerPixel bmpBitsPerPixel; /// - /// Encodes the image to the specified stream from the . + /// Encodes the image to the specified stream from the . /// - /// The to encode from. + /// The type of pixels contained within the image. + /// The to encode from. /// The to encode the image data to. /// The - public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) + public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) + where TPackedVector : IPackedVector { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -36,6 +38,7 @@ namespace ImageProcessorCore.Formats int rowWidth = image.Width; + // TODO: Check this for varying file formats. int amount = (image.Width * (int)this.bmpBitsPerPixel) % 4; if (amount != 0) { @@ -117,13 +120,15 @@ namespace ImageProcessorCore.Formats /// /// Writes the pixel data to the binary stream. /// + /// The type of pixels contained within the image. /// /// The containing the stream to write to. /// /// - /// The containing pixel data. + /// The containing pixel data. /// - private void WriteImage(EndianBinaryWriter writer, ImageBase image) + private void WriteImage(EndianBinaryWriter writer, ImageBase image) + where TPackedVector : IPackedVector { // TODO: Add more compression formats. int amount = (image.Width * (int)this.bmpBitsPerPixel) % 4; @@ -132,7 +137,7 @@ namespace ImageProcessorCore.Formats amount = 4 - amount; } - using (PixelAccessor pixels = image.Lock()) + using (IPixelAccessor pixels = image.Lock()) { switch (this.bmpBitsPerPixel) { @@ -151,21 +156,17 @@ namespace ImageProcessorCore.Formats /// Writes the 32bit color palette to the stream. /// /// The containing the stream to write to. - /// The containing pixel data. + /// The containing pixel data. /// The amount to pad each row by. - private void Write32bit(EndianBinaryWriter writer, PixelAccessor pixels, int amount) + private void Write32bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) { for (int y = pixels.Height - 1; y >= 0; y--) { for (int x = 0; x < pixels.Width; x++) { - // Limit the output range and multiply out from our floating point. // Convert back to b-> g-> r-> a order. - // Convert to non-premultiplied color. - Bgra32 color = Color.ToNonPremultiplied(pixels[x, y]); - - // We can take advantage of BGRA here - writer.Write(color.Bgra); + byte[] bytes = pixels[x, y].ToBytes(); + writer.Write(bytes); } // Pad @@ -180,21 +181,17 @@ namespace ImageProcessorCore.Formats /// Writes the 24bit color palette to the stream. /// /// The containing the stream to write to. - /// The containing pixel data. + /// The containing pixel data. /// The amount to pad each row by. - private void Write24bit(EndianBinaryWriter writer, PixelAccessor pixels, int amount) + private void Write24bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) { for (int y = pixels.Height - 1; y >= 0; y--) { for (int x = 0; x < pixels.Width; x++) { - // Limit the output range and multiply out from our floating point. // Convert back to b-> g-> r-> a order. - // Convert to non-premultiplied color. - Bgra32 color = Color.ToNonPremultiplied(pixels[x, y]); - - // Allocate 1 array instead of allocating 3. - writer.Write(new[] { color.B, color.G, color.R }); + byte[] bytes = pixels[x, y].ToBytes(); + writer.Write(new[] { bytes[0], bytes[1], bytes[2] }); } // Pad diff --git a/src/ImageProcessorCore/Formats/IImageDecoder.cs b/src/ImageProcessorCore/Formats/IImageDecoder.cs index 761f3a68a7..f394843caa 100644 --- a/src/ImageProcessorCore/Formats/IImageDecoder.cs +++ b/src/ImageProcessorCore/Formats/IImageDecoder.cs @@ -39,10 +39,11 @@ namespace ImageProcessorCore.Formats bool IsSupportedFileFormat(byte[] header); /// - /// Decodes the image from the specified stream to the . + /// Decodes the image from the specified stream to the . /// - /// The to decode to. + /// The type of pixels contained within the image. + /// The to decode to. /// The containing image data. - void Decode(Image image, Stream stream); + void Decode(Image image, Stream stream) where TPackedVector : IPackedVector, new(); } } diff --git a/src/ImageProcessorCore/Formats/IImageEncoder.cs b/src/ImageProcessorCore/Formats/IImageEncoder.cs index 4acceff996..df7234aad0 100644 --- a/src/ImageProcessorCore/Formats/IImageEncoder.cs +++ b/src/ImageProcessorCore/Formats/IImageEncoder.cs @@ -43,10 +43,11 @@ namespace ImageProcessorCore.Formats bool IsSupportedFileExtension(string extension); /// - /// Encodes the image to the specified stream from the . + /// Encodes the image to the specified stream from the . /// - /// The to encode from. + /// The type of pixels contained within the image. + /// The to encode from. /// The to encode the image data to. - void Encode(ImageBase image, Stream stream); + void Encode(ImageBase image, Stream stream) where TPackedVector : IPackedVector; } } diff --git a/src/ImageProcessorCore/IImageBase.cs b/src/ImageProcessorCore/IImageBase.cs index b01ee7c9c4..c8b762382c 100644 --- a/src/ImageProcessorCore/IImageBase.cs +++ b/src/ImageProcessorCore/IImageBase.cs @@ -1,18 +1,35 @@ -namespace ImageProcessorCore +using System.Collections.Generic; + +namespace ImageProcessorCore { - public interface IImageBase - where TPackedVector : IPackedVector + public interface IImageBase : IImageBase + where TPackedVector : IPackedVector, new() + { + TPackedVector[] Pixels { get; } + void ClonePixels(int width, int height, IEnumerable pixels); + IPixelAccessor Lock(); + void SetPixels(int width, int height, TPackedVector[] pixels); + } + + public interface IImageBase { Rectangle Bounds { get; } int FrameDelay { get; set; } int Height { get; } double PixelRatio { get; } - TPackedVector[] Pixels { get; } + int Quality { get; set; } - int Width { get; } - void ClonePixels(int width, int height, TPackedVector[] pixels); - IPixelAccessor Lock(); - void SetPixels(int width, int height, TPackedVector[] pixels); + /// + /// Gets or sets the maximum allowable width in pixels. + /// + int MaxWidth { get; set; } + + /// + /// Gets or sets the maximum allowable height in pixels. + /// + int MaxHeight { get; set; } + + int Width { get; } } } \ No newline at end of file diff --git a/src/ImageProcessorCore/IImageFrame.cs b/src/ImageProcessorCore/IImageFrame.cs index 1565879a76..a3c82d9325 100644 --- a/src/ImageProcessorCore/IImageFrame.cs +++ b/src/ImageProcessorCore/IImageFrame.cs @@ -1,7 +1,7 @@ namespace ImageProcessorCore { public interface IImageFrame : IImageBase - where TPacked : IPackedVector + where TPacked : IPackedVector, new() { } } diff --git a/src/ImageProcessorCore/IImageProcessor.cs b/src/ImageProcessorCore/IImageProcessor.cs index ad6e4ac761..153dd0a84a 100644 --- a/src/ImageProcessorCore/IImageProcessor.cs +++ b/src/ImageProcessorCore/IImageProcessor.cs @@ -45,7 +45,8 @@ namespace ImageProcessorCore.Processors /// /// doesnt fit the dimension of the image. /// - void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) where TPackedVector : IPackedVector; + void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) + where TPackedVector : IPackedVector, new(); /// /// Applies the process to the specified portion of the specified at the specified @@ -67,6 +68,7 @@ namespace ImageProcessorCore.Processors /// The method keeps the source image unchanged and returns the /// the result of image process as new image. /// - void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle) where TPackedVector : IPackedVector; + void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle) + where TPackedVector : IPackedVector, new(); } } diff --git a/src/ImageProcessorCore/Image.cs b/src/ImageProcessorCore/Image.cs index c8dc861d6a..acd49d5514 100644 --- a/src/ImageProcessorCore/Image.cs +++ b/src/ImageProcessorCore/Image.cs @@ -21,7 +21,7 @@ namespace ImageProcessorCore /// The packed vector containing pixel information. /// public class Image : ImageBase - where TPackedVector : IPackedVector + where TPackedVector : IPackedVector, new() { /// /// The default horizontal resolution value (dots per inch) in x direction. @@ -40,7 +40,7 @@ namespace ImageProcessorCore /// public Image() { - this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat)); + this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(BmpFormat)); } /// @@ -52,7 +52,8 @@ namespace ImageProcessorCore public Image(int width, int height) : base(width, height) { - this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat)); + // TODO: Change to PNG + this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(BmpFormat)); } /// @@ -183,7 +184,7 @@ namespace ImageProcessorCore public IImageFormat CurrentImageFormat { get; internal set; } /// - public override IPixelAccessor Lock() + public override IPixelAccessor Lock() { return Bootstrapper.Instance.GetPixelAccessor(this); } diff --git a/src/ImageProcessorCore/ImageBase.cs b/src/ImageProcessorCore/ImageBase.cs index 4dcb73174c..0c0bb6fac6 100644 --- a/src/ImageProcessorCore/ImageBase.cs +++ b/src/ImageProcessorCore/ImageBase.cs @@ -11,11 +11,11 @@ namespace ImageProcessorCore /// The base class of all images. Encapsulates the basic properties and methods required to manipulate images /// in different pixel formats. /// - /// + /// /// The packed vector pixels format. /// - public abstract class ImageBase : IImageBase - where TPacked : IPackedVector + public abstract class ImageBase : IImageBase + where TPackedVector : IPackedVector, new() { /// /// Initializes a new instance of the class. @@ -39,7 +39,7 @@ namespace ImageProcessorCore this.Width = width; this.Height = height; - this.Pixels = new TPacked[width * height]; + this.Pixels = new TPackedVector[width * height]; } /// @@ -51,7 +51,7 @@ namespace ImageProcessorCore /// /// Thrown if the given is null. /// - protected ImageBase(ImageBase other) + protected ImageBase(ImageBase other) { Guard.NotNull(other, nameof(other), "Other image cannot be null."); @@ -61,14 +61,24 @@ namespace ImageProcessorCore this.FrameDelay = other.FrameDelay; // Copy the pixels. - this.Pixels = new TPacked[this.Width * this.Height]; + this.Pixels = new TPackedVector[this.Width * this.Height]; Array.Copy(other.Pixels, this.Pixels, other.Pixels.Length); } + /// + /// Gets or sets the maximum allowable width in pixels. + /// + public int MaxWidth { get; set; } = int.MaxValue; + + /// + /// Gets or sets the maximum allowable height in pixels. + /// + public int MaxHeight { get; set; } = int.MaxValue; + /// /// Gets the pixels as an array of the given packed pixel format. /// - public TPacked[] Pixels { get; private set; } + public TPackedVector[] Pixels { get; private set; } /// /// Gets the width in pixels. @@ -117,7 +127,7 @@ namespace ImageProcessorCore /// /// Thrown if the length is not equal to Width * Height. /// - public void SetPixels(int width, int height, TPacked[] pixels) + public void SetPixels(int width, int height, TPackedVector[] pixels) { if (width <= 0) { @@ -154,7 +164,7 @@ namespace ImageProcessorCore /// /// Thrown if the length is not equal to Width * Height. /// - public void ClonePixels(int width, int height, TPacked[] pixels) + public void ClonePixels(int width, int height, TPackedVector[] pixels) { if (width <= 0) { @@ -175,7 +185,7 @@ namespace ImageProcessorCore this.Height = height; // Copy the pixels. - this.Pixels = new TPacked[pixels.Length]; + this.Pixels = new TPackedVector[pixels.Length]; Array.Copy(pixels, this.Pixels, pixels.Length); } @@ -186,6 +196,6 @@ namespace ImageProcessorCore /// /// /// The - public abstract IPixelAccessor Lock(); + public abstract IPixelAccessor Lock(); } } diff --git a/src/ImageProcessorCore/ImageExtensions.cs b/src/ImageProcessorCore/ImageExtensions.cs index e7b261d608..526ec8d554 100644 --- a/src/ImageProcessorCore/ImageExtensions.cs +++ b/src/ImageProcessorCore/ImageExtensions.cs @@ -12,55 +12,57 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { - /// - /// Saves the image to the given stream with the bmp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsBmp(this ImageBase source, Stream stream) => new BmpEncoder().Encode(source, stream); + ///// + ///// Saves the image to the given stream with the bmp format. + ///// + ///// The image this method extends. + ///// The stream to save the image to. + ///// Thrown if the stream is null. + //public static void SaveAsBmp(this ImageBase source, Stream stream) => new BmpEncoder().Encode(source, stream); - /// - /// Saves the image to the given stream with the png format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The quality to save the image to representing the number of colors. - /// Anything equal to 256 and below will cause the encoder to save the image in an indexed format. - /// - /// Thrown if the stream is null. - public static void SaveAsPng(this ImageBase source, Stream stream, int quality = Int32.MaxValue) => new PngEncoder { Quality = quality }.Encode(source, stream); + ///// + ///// Saves the image to the given stream with the png format. + ///// + ///// The image this method extends. + ///// The stream to save the image to. + ///// The quality to save the image to representing the number of colors. + ///// Anything equal to 256 and below will cause the encoder to save the image in an indexed format. + ///// + ///// Thrown if the stream is null. + //public static void SaveAsPng(this ImageBase source, Stream stream, int quality = Int32.MaxValue) => new PngEncoder { Quality = quality }.Encode(source, stream); - /// - /// Saves the image to the given stream with the jpeg format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The quality to save the image to. Between 1 and 100. - /// Thrown if the stream is null. - public static void SaveAsJpeg(this ImageBase source, Stream stream, int quality = 75) => new JpegEncoder { Quality = quality }.Encode(source, stream); + ///// + ///// Saves the image to the given stream with the jpeg format. + ///// + ///// The image this method extends. + ///// The stream to save the image to. + ///// The quality to save the image to. Between 1 and 100. + ///// Thrown if the stream is null. + //public static void SaveAsJpeg(this ImageBase source, Stream stream, int quality = 75) => new JpegEncoder { Quality = quality }.Encode(source, stream); - /// - /// Saves the image to the given stream with the gif format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The quality to save the image to representing the number of colors. Between 1 and 256. - /// Thrown if the stream is null. - public static void SaveAsGif(this ImageBase source, Stream stream, int quality = 256) => new GifEncoder { Quality = quality }.Encode(source, stream); + ///// + ///// Saves the image to the given stream with the gif format. + ///// + ///// The image this method extends. + ///// The stream to save the image to. + ///// The quality to save the image to representing the number of colors. Between 1 and 256. + ///// Thrown if the stream is null. + //public static void SaveAsGif(this ImageBase source, Stream stream, int quality = 256) => new GifEncoder { Quality = quality }.Encode(source, stream); /// /// Applies the collection of processors to the image. /// This method does not resize the target image. /// + /// The type of pixels contained within the image. /// The image this method extends. /// The processor to apply to the image. - /// The . - public static Image Process(this Image source, IImageProcessor processor) + /// The . + public static Image Process(this Image source, IImageProcessor processor) + where TPackedVector : IPackedVector, new() { return Process(source, source.Bounds, processor); } @@ -69,13 +71,15 @@ namespace ImageProcessorCore /// Applies the collection of processors to the image. /// This method does not resize the target image. /// + /// The type of pixels contained within the image. /// The image this method extends. /// /// The structure that specifies the portion of the image object to draw. /// /// The processors to apply to the image. - /// The . - public static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor) + /// The . + public static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor) + where TPackedVector : IPackedVector, new() { return PerformAction(source, true, (sourceImage, targetImage) => processor.Apply(targetImage, sourceImage, sourceRectangle)); } @@ -86,12 +90,14 @@ namespace ImageProcessorCore /// This method is not chainable. /// /// + /// The type of pixels contained within the image. /// The source image. Cannot be null. /// The target image width. /// The target image height. /// The processor to apply to the image. - /// The . - public static Image Process(this Image source, int width, int height, IImageSampler sampler) + /// The . + public static Image Process(this Image source, int width, int height, IImageSampler sampler) + where TPackedVector : IPackedVector, new() { return Process(source, width, height, source.Bounds, default(Rectangle), sampler); } @@ -102,6 +108,7 @@ namespace ImageProcessorCore /// This method does will resize the target image if the source and target rectangles are different. /// /// + /// The type of pixels contained within the image. /// The source image. Cannot be null. /// The target image width. /// The target image height. @@ -113,25 +120,27 @@ namespace ImageProcessorCore /// The image is scaled to fit the rectangle. /// /// The processor to apply to the image. - /// The . - public static Image Process(this Image source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler) + /// The . + public static Image Process(this Image source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler) + where TPackedVector : IPackedVector, new() { - return PerformAction(source, false, (sourceImage, targetImage) => sampler.Apply(targetImage, sourceImage, width, height, targetRectangle, sourceRectangle)); + return PerformAction(source, false, (sourceImage, targetImage) => sampler.Apply(targetImage, sourceImage, width, height, targetRectangle, sourceRectangle)); } /// /// Performs the given action on the source image. /// + /// The type of pixels contained within the image. /// The image to perform the action against. /// Whether to clone the image. /// The to perform against the image. - /// The . - /// Thrown if the has been disposed. - private static Image PerformAction(Image source, bool clone, Action action) + /// The . + private static Image PerformAction(Image source, bool clone, Action, ImageBase> action) + where TPackedVector : IPackedVector, new() { - Image transformedImage = clone - ? new Image(source) - : new Image + Image transformedImage = clone + ? new Image(source) + : new Image { // Several properties require copying // TODO: Check why we need to set these? @@ -145,8 +154,11 @@ namespace ImageProcessorCore for (int i = 0; i < source.Frames.Count; i++) { - ImageFrame sourceFrame = source.Frames[i]; - ImageFrame tranformedFrame = clone ? new ImageFrame(sourceFrame) : new ImageFrame { FrameDelay = sourceFrame.FrameDelay }; + ImageFrame sourceFrame = source.Frames[i]; + ImageFrame tranformedFrame = clone + ? new ImageFrame(sourceFrame) + : new ImageFrame { FrameDelay = sourceFrame.FrameDelay }; + action(sourceFrame, tranformedFrame); if (!clone) diff --git a/src/ImageProcessorCore/ImageFrame.cs b/src/ImageProcessorCore/ImageFrame.cs index 479d9dd9b0..cc65739c13 100644 --- a/src/ImageProcessorCore/ImageFrame.cs +++ b/src/ImageProcessorCore/ImageFrame.cs @@ -12,8 +12,15 @@ namespace ImageProcessorCore /// The packed vector containing pixel information. /// public class ImageFrame : ImageBase - where TPackedVector : IPackedVector + where TPackedVector : IPackedVector, new() { + /// + /// Initializes a new instance of the class. + /// + public ImageFrame() + { + } + /// /// Initializes a new instance of the class. /// @@ -26,7 +33,7 @@ namespace ImageProcessorCore } /// - public override IPixelAccessor Lock() + public override IPixelAccessor Lock() { return Bootstrapper.Instance.GetPixelAccessor(this); } diff --git a/src/ImageProcessorCore/ImageProcessor.cs b/src/ImageProcessorCore/ImageProcessor.cs index e7057a3f9c..0206e667ee 100644 --- a/src/ImageProcessorCore/ImageProcessor.cs +++ b/src/ImageProcessorCore/ImageProcessor.cs @@ -28,7 +28,7 @@ namespace ImageProcessorCore.Processors /// public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) - where TPackedVector : IPackedVector + where TPackedVector : IPackedVector, new() { try { @@ -50,7 +50,7 @@ namespace ImageProcessorCore.Processors /// public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) - where TPackedVector : IPackedVector + where TPackedVector : IPackedVector, new() { try { @@ -96,7 +96,7 @@ namespace ImageProcessorCore.Processors /// The structure that specifies the portion of the image object to draw. /// protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - where TPackedVector : IPackedVector + where TPackedVector : IPackedVector, new() { } @@ -120,7 +120,8 @@ namespace ImageProcessorCore.Processors /// The method keeps the source image unchanged and returns the /// the result of image process as new image. /// - protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) where TPackedVector : IPackedVector; + protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + where TPackedVector : IPackedVector, new(); /// /// This method is called after the process is applied to prepare the processor. @@ -136,7 +137,7 @@ namespace ImageProcessorCore.Processors /// The structure that specifies the portion of the image object to draw. /// protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - where TPackedVector : IPackedVector + where TPackedVector : IPackedVector, new() { } diff --git a/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs index cecc148b93..0645ff429f 100644 --- a/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs @@ -15,7 +15,8 @@ namespace ImageProcessorCore /// The image data is always stored in format, where the blue, green, red, and /// alpha values are 8 bit unsigned bytes. /// - public sealed unsafe class Bgra32PixelAccessor : IPixelAccessor + public sealed unsafe class Bgra32PixelAccessor : IPixelAccessor + { /// /// The position of the first pixel in the bitmap. @@ -46,7 +47,7 @@ namespace ImageProcessorCore /// /// The image to provide pixel access for. /// - public Bgra32PixelAccessor(IImageBase image) + public Bgra32PixelAccessor(IImageBase image) { Guard.NotNull(image, nameof(image)); Guard.MustBeGreaterThan(image.Width, 0, "image width"); @@ -55,7 +56,7 @@ namespace ImageProcessorCore this.Width = image.Width; this.Height = image.Height; - this.pixelsHandle = GCHandle.Alloc(image.Pixels, GCHandleType.Pinned); + this.pixelsHandle = GCHandle.Alloc(((ImageBase)image).Pixels, GCHandleType.Pinned); this.pixelsBase = (Bgra32*)this.pixelsHandle.AddrOfPinnedObject().ToPointer(); } @@ -89,7 +90,7 @@ namespace ImageProcessorCore /// than zero and smaller than the width of the pixel. /// /// The at the specified position. - public IPackedVector this[int x, int y] + public Bgra32 this[int x, int y] { get { @@ -120,7 +121,7 @@ namespace ImageProcessorCore throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); } #endif - *(this.pixelsBase + ((y * this.Width) + x)) = (Bgra32)value; + *(this.pixelsBase + ((y * this.Width) + x)) = value; } } diff --git a/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs index 10838726ca..f64fcf231d 100644 --- a/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs @@ -10,8 +10,18 @@ namespace ImageProcessorCore /// /// Encapsulates properties to provides per-pixel access to an images pixels. /// - public interface IPixelAccessor : IDisposable + public interface IPixelAccessor : IDisposable where TPackedVector : IPackedVector, new() { + /// + /// Gets the width of the image in pixels. + /// + int Width { get; } + + /// + /// Gets the height of the image in pixels. + /// + int Height { get; } + /// /// Gets or sets the pixel at the specified position. /// @@ -23,8 +33,8 @@ namespace ImageProcessorCore /// The y-coordinate of the pixel. Must be greater /// than zero and smaller than the width of the pixel. /// - /// The at the specified position. - IPackedVector this[int x, int y] + /// The at the specified position. + TPackedVector this[int x, int y] { get; set; diff --git a/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs b/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs index a80ea47778..2c3221ec6c 100644 --- a/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs +++ b/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs @@ -17,12 +17,14 @@ namespace ImageProcessorCore /// /// Calculates the target location and bounds to perform the resize operation against. /// + /// The type of pixels contained within the image. /// The source image. /// The resize options. /// /// The . /// - public static Rectangle CalculateTargetLocationAndBounds(ImageBase source, ResizeOptions options) + public static Rectangle CalculateTargetLocationAndBounds(ImageBase source, ResizeOptions options) + where TPackedVector : IPackedVector, new() { switch (options.Mode) { @@ -39,19 +41,21 @@ namespace ImageProcessorCore // Last case ResizeMode.Stretch: default: - return CalculateStretchRectangle(source, options); + return new Rectangle(0, 0, options.Size.Width, options.Size.Height); } } /// /// Calculates the target rectangle for crop mode. /// + /// The type of pixels contained within the image. /// The source image. /// The resize options. /// /// The . /// - private static Rectangle CalculateCropRectangle(ImageBase source, ResizeOptions options) + private static Rectangle CalculateCropRectangle(ImageBase source, ResizeOptions options) + where TPackedVector : IPackedVector, new() { int width = options.Size.Width; int height = options.Size.Height; @@ -163,12 +167,14 @@ namespace ImageProcessorCore /// /// Calculates the target rectangle for pad mode. /// + /// The type of pixels contained within the image. /// The source image. /// The resize options. /// /// The . /// - private static Rectangle CalculatePadRectangle(ImageBase source, ResizeOptions options) + private static Rectangle CalculatePadRectangle(ImageBase source, ResizeOptions options) + where TPackedVector : IPackedVector, new() { int width = options.Size.Width; int height = options.Size.Height; @@ -242,12 +248,14 @@ namespace ImageProcessorCore /// /// Calculates the target rectangle for box pad mode. /// + /// The type of pixels contained within the image. /// The source image. /// The resize options. /// /// The . /// - private static Rectangle CalculateBoxPadRectangle(ImageBase source, ResizeOptions options) + private static Rectangle CalculateBoxPadRectangle(ImageBase source, ResizeOptions options) + where TPackedVector : IPackedVector, new() { int width = options.Size.Width; int height = options.Size.Height; @@ -327,12 +335,14 @@ namespace ImageProcessorCore /// /// Calculates the target rectangle for max mode. /// + /// The type of pixels contained within the image. /// The source image. /// The resize options. /// /// The . /// - private static Rectangle CalculateMaxRectangle(ImageBase source, ResizeOptions options) + private static Rectangle CalculateMaxRectangle(ImageBase source, ResizeOptions options) + where TPackedVector : IPackedVector, new() { int width = options.Size.Width; int height = options.Size.Height; @@ -366,12 +376,14 @@ namespace ImageProcessorCore /// /// Calculates the target rectangle for min mode. /// + /// The type of pixels contained within the image. /// The source image. /// The resize options. /// /// The . /// - private static Rectangle CalculateMinRectangle(ImageBase source, ResizeOptions options) + private static Rectangle CalculateMinRectangle(ImageBase source, ResizeOptions options) + where TPackedVector : IPackedVector, new() { int width = options.Size.Width; int height = options.Size.Height; @@ -413,18 +425,5 @@ namespace ImageProcessorCore options.Size = new Size(width, height); return new Rectangle(0, 0, destinationWidth, destinationHeight); } - - /// - /// Calculates the target rectangle for stretch mode. - /// - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculateStretchRectangle(ImageBase source, ResizeOptions options) - { - return new Rectangle(0, 0, options.Size.Width, options.Size.Height); - } } } diff --git a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs index 95ead2bf04..a6e787714d 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs @@ -6,6 +6,7 @@ namespace ImageProcessorCore.Processors { using System; + using System.Numerics; using System.Threading.Tasks; /// @@ -16,7 +17,7 @@ namespace ImageProcessorCore.Processors /// /// The image used for storing the first pass pixels. /// - private Image firstPass; + private object firstPass; /// /// Initializes a new instance of the class. @@ -47,7 +48,7 @@ namespace ImageProcessorCore.Processors protected Weights[] VerticalWeights { get; set; } /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { if (!(this.Sampler is NearestNeighborResampler)) { @@ -55,11 +56,11 @@ namespace ImageProcessorCore.Processors this.VerticalWeights = this.PrecomputeWeights(targetRectangle.Height, sourceRectangle.Height); } - this.firstPass = new Image(target.Width, source.Height); + this.firstPass = new Image(target.Width, source.Height); } /// - 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) { // Jump out, we'll deal with that later. if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) @@ -78,14 +79,17 @@ namespace ImageProcessorCore.Processors int endX = targetRectangle.Right; bool compand = this.Compand; + // TODO: Yuck! Fix this boxing nonsense + Image fp = (Image)this.firstPass; + if (this.Sampler is NearestNeighborResampler) { // Scaling factors float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width; float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, @@ -103,7 +107,6 @@ namespace ImageProcessorCore.Processors { // X coordinates of source points int originX = (int)((x - startX) * widthFactor); - targetPixels[x, y] = sourcePixels[originX, originY]; } } @@ -121,9 +124,9 @@ namespace ImageProcessorCore.Processors // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm // First process the columns. Since we are not using multiple threads startY and endY // are the upper and lower bounds of the source rectangle. - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor firstPassPixels = this.firstPass.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor firstPassPixels = fp.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( 0, @@ -140,25 +143,27 @@ namespace ImageProcessorCore.Processors Weight[] horizontalValues = this.HorizontalWeights[offsetX].Values; // Destination color components - Color destination = new Color(); + Vector4 destination = new Vector4(); for (int i = 0; i < sum; i++) { Weight xw = horizontalValues[i]; int originX = xw.Index; - Color sourceColor = compand - ? Color.Expand(sourcePixels[originX, y]) - : sourcePixels[originX, y]; + Vector4 sourceColor = sourcePixels[originX, y].ToVector4(); + //Color sourceColor = compand + // ? Color.Expand(sourcePixels[originX, y]) + // : sourcePixels[originX, y]; destination += sourceColor * xw.Value; } - if (compand) - { - destination = Color.Compress(destination); - } - - firstPassPixels[x, y] = destination; + //if (compand) + //{ + // destination = Color.Compress(destination); + //} + TPackedVector packed = new TPackedVector(); + packed.PackVector(destination); + firstPassPixels[x, y] = packed; } } }); @@ -179,25 +184,29 @@ namespace ImageProcessorCore.Processors for (int x = 0; x < width; x++) { // Destination color components - Color destination = new Color(); + Vector4 destination = new Vector4(); for (int i = 0; i < sum; i++) { Weight yw = verticalValues[i]; int originY = yw.Index; - Color sourceColor = compand - ? Color.Expand(firstPassPixels[x, originY]) - : firstPassPixels[x, originY]; + + Vector4 sourceColor = sourcePixels[x, originY].ToVector4(); + //Color sourceColor = compand + // ? Color.Expand(firstPassPixels[x, originY]) + // : firstPassPixels[x, originY]; destination += sourceColor * yw.Value; } - if (compand) - { - destination = Color.Compress(destination); - } + //if (compand) + //{ + // destination = Color.Compress(destination); + //} - targetPixels[x, y] = destination; + TPackedVector packed = new TPackedVector(); + packed.PackVector(destination); + targetPixels[x, y] = packed; } } @@ -208,7 +217,7 @@ namespace ImageProcessorCore.Processors } /// - protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { // Copy the pixels over. if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) diff --git a/src/ImageProcessorCore/Samplers/Resize.cs b/src/ImageProcessorCore/Samplers/Resize.cs index 2eadd7a11c..3d81f41da8 100644 --- a/src/ImageProcessorCore/Samplers/Resize.cs +++ b/src/ImageProcessorCore/Samplers/Resize.cs @@ -8,19 +8,21 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Resizes an image in accordance with the given . /// + /// The type of pixels contained within the image. /// The image to resize. /// The resize options. /// A delegate which is called as progress is made processing the image. - /// The + /// The /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, ResizeOptions options, ProgressEventHandler progressHandler = null) + public static Image Resize(this Image source, ResizeOptions options, ProgressEventHandler progressHandler = null) + where TPackedVector : IPackedVector, new() { // Ensure size is populated across both dimensions. if (options.Size.Width == 0 && options.Size.Height > 0) @@ -41,13 +43,15 @@ namespace ImageProcessorCore /// /// Resizes an image to the given width and height. /// + /// The type of pixels contained within the image. /// The image to resize. /// The target image width. /// The target image height. /// A delegate which is called as progress is made processing the image. - /// The + /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, ProgressEventHandler progressHandler = null) + public static Image Resize(this Image source, int width, int height, ProgressEventHandler progressHandler = null) + where TPackedVector : IPackedVector, new() { return Resize(source, width, height, new BicubicResampler(), false, progressHandler); } @@ -55,14 +59,16 @@ namespace ImageProcessorCore /// /// Resizes an image to the given width and height. /// + /// The type of pixels contained within the image. /// The image to resize. /// The target image width. /// The target image height. /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// A delegate which is called as progress is made processing the image. - /// The + /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, bool compand, ProgressEventHandler progressHandler = null) + public static Image Resize(this Image source, int width, int height, bool compand, ProgressEventHandler progressHandler = null) + where TPackedVector : IPackedVector, new() { return Resize(source, width, height, new BicubicResampler(), compand, progressHandler); } @@ -70,15 +76,17 @@ namespace ImageProcessorCore /// /// Resizes an image to the given width and height with the given sampler. /// + /// The type of pixels contained within the image. /// The image to resize. /// The target image width. /// The target image height. /// The to perform the resampling. /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// A delegate which is called as progress is made processing the image. - /// The + /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, IResampler sampler, bool compand, ProgressEventHandler progressHandler = null) + public static Image Resize(this Image source, int width, int height, IResampler sampler, bool compand, ProgressEventHandler progressHandler = null) + where TPackedVector : IPackedVector, new() { return Resize(source, width, height, sampler, source.Bounds, new Rectangle(0, 0, width, height), compand, progressHandler); } @@ -87,6 +95,7 @@ namespace ImageProcessorCore /// Resizes an image to the given width and height with the given sampler and /// source rectangle. /// + /// The type of pixels contained within the image. /// The image to resize. /// The target image width. /// The target image height. @@ -99,9 +108,10 @@ namespace ImageProcessorCore /// /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// A delegate which is called as progress is made processing the image. - /// The + /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand = false, ProgressEventHandler progressHandler = null) + public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand = false, ProgressEventHandler progressHandler = null) + where TPackedVector : IPackedVector, new() { if (width == 0 && height > 0) { diff --git a/tests/ImageProcessorCore.Benchmarks/Color/ColorEquality.cs b/tests/ImageProcessorCore.Benchmarks/Color/ColorEquality.cs index e9ef2c1835..1ab1d2c80c 100644 --- a/tests/ImageProcessorCore.Benchmarks/Color/ColorEquality.cs +++ b/tests/ImageProcessorCore.Benchmarks/Color/ColorEquality.cs @@ -1,22 +1,22 @@ -namespace ImageProcessorCore.Benchmarks -{ - using BenchmarkDotNet.Attributes; +//namespace ImageProcessorCore.Benchmarks +//{ +// using BenchmarkDotNet.Attributes; - using CoreColor = ImageProcessorCore.Color; - using SystemColor = System.Drawing.Color; +// using CoreColor = ImageProcessorCore.Color; +// using SystemColor = System.Drawing.Color; - public class ColorEquality - { - [Benchmark(Baseline = true, Description = "System.Drawing Color Equals")] - public bool SystemDrawingColorEqual() - { - return SystemColor.FromArgb(128, 128, 128, 128).Equals(SystemColor.FromArgb(128, 128, 128, 128)); - } +// public class ColorEquality +// { +// [Benchmark(Baseline = true, Description = "System.Drawing Color Equals")] +// public bool SystemDrawingColorEqual() +// { +// return SystemColor.FromArgb(128, 128, 128, 128).Equals(SystemColor.FromArgb(128, 128, 128, 128)); +// } - [Benchmark(Description = "ImageProcessorCore Color Equals")] - public bool ColorEqual() - { - return new CoreColor(.5f, .5f, .5f, .5f).Equals(new CoreColor(.5f, .5f, .5f, .5f)); - } - } -} +// [Benchmark(Description = "ImageProcessorCore Color Equals")] +// public bool ColorEqual() +// { +// return new CoreColor(.5f, .5f, .5f, .5f).Equals(new CoreColor(.5f, .5f, .5f, .5f)); +// } +// } +//} diff --git a/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs b/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs index 64d7896362..98bcf80493 100644 --- a/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs +++ b/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs @@ -4,8 +4,8 @@ using BenchmarkDotNet.Attributes; - using CoreColor = ImageProcessorCore.Color; - using CoreImage = ImageProcessorCore.Image; + //using CoreColor = ImageProcessorCore.Color; + //using CoreImage = ImageProcessorCore.Image; using SystemColor = System.Drawing.Color; public class GetSetPixel @@ -21,13 +21,13 @@ } [Benchmark(Description = "ImageProcessorCore GetSet Pixel")] - public CoreColor ResizeCore() + public Bgra32 ResizeCore() { - CoreImage image = new CoreImage(400, 400); - using (PixelAccessor imagePixels = image.Lock()) + Image image = new Image(400, 400); + using (IPixelAccessor imagePixels = image.Lock()) { - imagePixels[200, 200] = CoreColor.White; - return imagePixels[200, 200]; + imagePixels[200, 200] = new Bgra32(1, 1, 1, 1); + return (Bgra32)imagePixels[200, 200]; } } } diff --git a/tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs b/tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs index 4ffce9d48d..5eae5cdefb 100644 --- a/tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs +++ b/tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs @@ -1,40 +1,40 @@ -namespace ImageProcessorCore.Benchmarks -{ - using System.Drawing; - using System.Drawing.Drawing2D; +//namespace ImageProcessorCore.Benchmarks +//{ +// using System.Drawing; +// using System.Drawing.Drawing2D; - using BenchmarkDotNet.Attributes; - using CoreImage = ImageProcessorCore.Image; - using CoreSize = ImageProcessorCore.Size; +// using BenchmarkDotNet.Attributes; +// using CoreImage = ImageProcessorCore.Image; +// using CoreSize = ImageProcessorCore.Size; - public class Crop - { - [Benchmark(Baseline = true, Description = "System.Drawing Crop")] - public Size CropSystemDrawing() - { - using (Bitmap source = new Bitmap(400, 400)) - { - using (Bitmap destination = new Bitmap(100, 100)) - { - using (Graphics graphics = Graphics.FromImage(destination)) - { - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - graphics.CompositingQuality = CompositingQuality.HighQuality; - graphics.DrawImage(source, new Rectangle(0, 0, 100, 100), 0, 0, 100, 100, GraphicsUnit.Pixel); - } +// public class Crop +// { +// [Benchmark(Baseline = true, Description = "System.Drawing Crop")] +// public Size CropSystemDrawing() +// { +// using (Bitmap source = new Bitmap(400, 400)) +// { +// using (Bitmap destination = new Bitmap(100, 100)) +// { +// using (Graphics graphics = Graphics.FromImage(destination)) +// { +// graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; +// graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; +// graphics.CompositingQuality = CompositingQuality.HighQuality; +// graphics.DrawImage(source, new Rectangle(0, 0, 100, 100), 0, 0, 100, 100, GraphicsUnit.Pixel); +// } - return destination.Size; - } - } - } +// return destination.Size; +// } +// } +// } - [Benchmark(Description = "ImageProcessorCore Crop")] - public CoreSize CropResizeCore() - { - CoreImage image = new CoreImage(400, 400); - image.Crop(100, 100); - return new CoreSize(image.Width, image.Height); - } - } -} +// [Benchmark(Description = "ImageProcessorCore Crop")] +// public CoreSize CropResizeCore() +// { +// CoreImage image = new CoreImage(400, 400); +// image.Crop(100, 100); +// return new CoreSize(image.Width, image.Height); +// } +// } +//} diff --git a/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs b/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs index 479392cc8e..21a9033e0d 100644 --- a/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs +++ b/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs @@ -4,7 +4,6 @@ using System.Drawing.Drawing2D; using BenchmarkDotNet.Attributes; - using CoreImage = ImageProcessorCore.Image; using CoreSize = ImageProcessorCore.Size; public class Resize @@ -32,7 +31,7 @@ [Benchmark(Description = "ImageProcessorCore Resize")] public CoreSize ResizeCore() { - CoreImage image = new CoreImage(400, 400); + Image image = new Image(400, 400); image.Resize(100, 100); return new CoreSize(image.Width, image.Height); } diff --git a/tests/ImageProcessorCore.Tests/Colors/ColorConversionTests.cs b/tests/ImageProcessorCore.Tests/Colors/ColorConversionTests.cs deleted file mode 100644 index c7a252b03e..0000000000 --- a/tests/ImageProcessorCore.Tests/Colors/ColorConversionTests.cs +++ /dev/null @@ -1,493 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Tests -{ - using System; - using System.Diagnostics.CodeAnalysis; - - using Xunit; - - /// - /// Test conversion between the various color structs. - /// - /// - /// Output values have been compared with - /// and for accuracy. - /// - public class ColorConversionTests - { - /// - /// Tests the implicit conversion from to . - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", - Justification = "Reviewed. Suppression is OK here.")] - public void ColorToYCbCr() - { - // White - Color color = new Color(1, 1, 1); - YCbCr yCbCr = color; - - Assert.Equal(255, yCbCr.Y, 0); - Assert.Equal(128, yCbCr.Cb, 0); - Assert.Equal(128, yCbCr.Cr, 0); - - // Black - Color color2 = new Color(0, 0, 0); - YCbCr yCbCr2 = color2; - Assert.Equal(0, yCbCr2.Y, 0); - Assert.Equal(128, yCbCr2.Cb, 0); - Assert.Equal(128, yCbCr2.Cr, 0); - - // Grey - Color color3 = new Color(.5f, .5f, .5f); - YCbCr yCbCr3 = color3; - Assert.Equal(128, yCbCr3.Y, 0); - Assert.Equal(128, yCbCr3.Cb, 0); - Assert.Equal(128, yCbCr3.Cr, 0); - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", - Justification = "Reviewed. Suppression is OK here.")] - public void YCbCrToColor() - { - // White - YCbCr yCbCr = new YCbCr(255, 128, 128); - Color color = yCbCr; - - Assert.Equal(1f, color.R, 1); - Assert.Equal(1f, color.G, 1); - Assert.Equal(1f, color.B, 1); - Assert.Equal(1f, color.A, 1); - - // Black - YCbCr yCbCr2 = new YCbCr(0, 128, 128); - Color color2 = yCbCr2; - - Assert.Equal(0, color2.R); - Assert.Equal(0, color2.G); - Assert.Equal(0, color2.B); - Assert.Equal(1, color2.A); - - // Grey - YCbCr yCbCr3 = new YCbCr(128, 128, 128); - Color color3 = yCbCr3; - - Assert.Equal(.5f, color3.R, 1); - Assert.Equal(.5f, color3.G, 1); - Assert.Equal(.5f, color3.B, 1); - Assert.Equal(1f, color3.A, 1); - } - - /// - /// Tests the implicit conversion from to . - /// Comparison values obtained from - /// http://colormine.org/convert/rgb-to-xyz - /// - [Fact] - public void ColorToCieXyz() - { - // White - Color color = new Color(1, 1, 1); - CieXyz ciexyz = color; - - Assert.Equal(95.05f, ciexyz.X, 3); - Assert.Equal(100.0f, ciexyz.Y, 3); - Assert.Equal(108.900f, ciexyz.Z, 3); - - // Black - Color color2 = new Color(0, 0, 0); - CieXyz ciexyz2 = color2; - Assert.Equal(0, ciexyz2.X, 3); - Assert.Equal(0, ciexyz2.Y, 3); - Assert.Equal(0, ciexyz2.Z, 3); - - //// Grey - Color color3 = new Color(128 / 255f, 128 / 255f, 128 / 255f); - CieXyz ciexyz3 = color3; - Assert.Equal(20.518, ciexyz3.X, 3); - Assert.Equal(21.586, ciexyz3.Y, 3); - Assert.Equal(23.507, ciexyz3.Z, 3); - - //// Cyan - Color color4 = new Color(0, 1, 1); - CieXyz ciexyz4 = color4; - Assert.Equal(53.810f, ciexyz4.X, 3); - Assert.Equal(78.740f, ciexyz4.Y, 3); - Assert.Equal(106.970f, ciexyz4.Z, 3); - } - - /// - /// Tests the implicit conversion from to . - /// Comparison values obtained from - /// http://colormine.org/convert/rgb-to-xyz - /// - [Fact] - public void CieXyzToColor() - { - // Dark moderate pink. - CieXyz ciexyz = new CieXyz(13.337f, 9.297f, 14.727f); - Color color = ciexyz; - - Assert.Equal(128 / 255f, color.R, 3); - Assert.Equal(64 / 255f, color.G, 3); - Assert.Equal(106 / 255f, color.B, 3); - - // Ochre - CieXyz ciexyz2 = new CieXyz(31.787f, 26.147f, 4.885f); - Color color2 = ciexyz2; - - Assert.Equal(204 / 255f, color2.R, 3); - Assert.Equal(119 / 255f, color2.G, 3); - Assert.Equal(34 / 255f, color2.B, 3); - - //// White - CieXyz ciexyz3 = new CieXyz(0, 0, 0); - Color color3 = ciexyz3; - - Assert.Equal(0f, color3.R, 3); - Assert.Equal(0f, color3.G, 3); - Assert.Equal(0f, color3.B, 3); - - //// Check others. - //Random random = new Random(0); - //for (int i = 0; i < 1000; i++) - //{ - // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); - // CieXyz ciexyz4 = color4; - // Assert.Equal(color4, (Color)ciexyz4); - //} - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", - Justification = "Reviewed. Suppression is OK here.")] - public void ColorToHsv() - { - // Black - Color b = new Color(0, 0, 0); - Hsv h = b; - - Assert.Equal(0, h.H, 1); - Assert.Equal(0, h.S, 1); - Assert.Equal(0, h.V, 1); - - // White - Color color = new Color(1, 1, 1); - Hsv hsv = color; - - Assert.Equal(0f, hsv.H, 1); - Assert.Equal(0f, hsv.S, 1); - Assert.Equal(1f, hsv.V, 1); - - // Dark moderate pink. - Color color2 = new Color(128 / 255f, 64 / 255f, 106 / 255f); - Hsv hsv2 = color2; - - Assert.Equal(320.6f, hsv2.H, 1); - Assert.Equal(0.5f, hsv2.S, 1); - Assert.Equal(0.502f, hsv2.V, 2); - - // Ochre. - Color color3 = new Color(204 / 255f, 119 / 255f, 34 / 255f); - Hsv hsv3 = color3; - - Assert.Equal(30f, hsv3.H, 1); - Assert.Equal(0.833f, hsv3.S, 3); - Assert.Equal(0.8f, hsv3.V, 1); - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - public void HsvToColor() - { - // Dark moderate pink. - Hsv hsv = new Hsv(320.6f, 0.5f, 0.502f); - Color color = hsv; - - Assert.Equal(color.B, 106 / 255f, 1); - Assert.Equal(color.G, 64 / 255f, 1); - Assert.Equal(color.R, 128 / 255f, 1); - - // Ochre - Hsv hsv2 = new Hsv(30, 0.833f, 0.8f); - Color color2 = hsv2; - - Assert.Equal(color2.B, 34 / 255f, 1); - Assert.Equal(color2.G, 119 / 255f, 1); - Assert.Equal(color2.R, 204 / 255f, 1); - - // White - Hsv hsv3 = new Hsv(0, 0, 1); - Color color3 = hsv3; - - Assert.Equal(color3.B, 1, 1); - Assert.Equal(color3.G, 1, 1); - Assert.Equal(color3.R, 1, 1); - - // Check others. - //Random random = new Random(0); - //for (int i = 0; i < 1000; i++) - //{ - // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); - // Hsv hsv4 = color4; - // Assert.Equal(color4, (Color)hsv4); - //} - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", - Justification = "Reviewed. Suppression is OK here.")] - public void ColorToHsl() - { - // Black - Color b = new Color(0, 0, 0); - Hsl h = b; - - Assert.Equal(0, h.H, 1); - Assert.Equal(0, h.S, 1); - Assert.Equal(0, h.L, 1); - - // White - Color color = new Color(1, 1, 1); - Hsl hsl = color; - - Assert.Equal(0f, hsl.H, 1); - Assert.Equal(0f, hsl.S, 1); - Assert.Equal(1f, hsl.L, 1); - - // Dark moderate pink. - Color color2 = new Color(128 / 255f, 64 / 255f, 106 / 255f); - Hsl hsl2 = color2; - - Assert.Equal(320.6f, hsl2.H, 1); - Assert.Equal(0.33f, hsl2.S, 1); - Assert.Equal(0.376f, hsl2.L, 2); - - // Ochre. - Color color3 = new Color(204 / 255f, 119 / 255f, 34 / 255f); - Hsl hsl3 = color3; - - Assert.Equal(30f, hsl3.H, 1); - Assert.Equal(0.714f, hsl3.S, 3); - Assert.Equal(0.467f, hsl3.L, 3); - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - public void HslToColor() - { - // Dark moderate pink. - Hsl hsl = new Hsl(320.6f, 0.33f, 0.376f); - Color color = hsl; - - Assert.Equal(color.B, 106 / 255f, 1); - Assert.Equal(color.G, 64 / 255f, 1); - Assert.Equal(color.R, 128 / 255f, 1); - - // Ochre - Hsl hsl2 = new Hsl(30, 0.714f, 0.467f); - Color color2 = hsl2; - - Assert.Equal(color2.B, 34 / 255f, 1); - Assert.Equal(color2.G, 119 / 255f, 1); - Assert.Equal(color2.R, 204 / 255f, 1); - - // White - Hsl hsl3 = new Hsl(0, 0, 1); - Color color3 = hsl3; - - Assert.Equal(color3.B, 1, 1); - Assert.Equal(color3.G, 1, 1); - Assert.Equal(color3.R, 1, 1); - - // Check others. - //Random random = new Random(0); - //for (int i = 0; i < 1000; i++) - //{ - // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); - // Hsl hsl4 = color4; - // Assert.Equal(color4, (Color)hsl4); - //} - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", - Justification = "Reviewed. Suppression is OK here.")] - public void ColorToCmyk() - { - // White - Color color = new Color(1, 1, 1); - Cmyk cmyk = color; - - Assert.Equal(0, cmyk.C, 1); - Assert.Equal(0, cmyk.M, 1); - Assert.Equal(0, cmyk.Y, 1); - Assert.Equal(0, cmyk.K, 1); - - // Black - Color color2 = new Color(0, 0, 0); - Cmyk cmyk2 = color2; - Assert.Equal(0, cmyk2.C, 1); - Assert.Equal(0, cmyk2.M, 1); - Assert.Equal(0, cmyk2.Y, 1); - Assert.Equal(1, cmyk2.K, 1); - - // Grey - Color color3 = new Color(128 / 255f, 128 / 255f, 128 / 255f); - Cmyk cmyk3 = color3; - Assert.Equal(0f, cmyk3.C, 1); - Assert.Equal(0f, cmyk3.M, 1); - Assert.Equal(0f, cmyk3.Y, 1); - Assert.Equal(0.498, cmyk3.K, 2); // Checked with other online converters. - - // Cyan - Color color4 = new Color(0, 1, 1); - Cmyk cmyk4 = color4; - Assert.Equal(1, cmyk4.C, 1); - Assert.Equal(0f, cmyk4.M, 1); - Assert.Equal(0f, cmyk4.Y, 1); - Assert.Equal(0f, cmyk4.K, 1); - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - public void CmykToColor() - { - // Dark moderate pink. - Cmyk cmyk = new Cmyk(0f, .5f, .171f, .498f); - Color color = cmyk; - - Assert.Equal(color.R, 128 / 255f, 1); - Assert.Equal(color.G, 64 / 255f, 1); - Assert.Equal(color.B, 106 / 255f, 1); - - // Ochre - Cmyk cmyk2 = new Cmyk(0, .416f, .833f, .199f); - Color color2 = cmyk2; - - Assert.Equal(color2.R, 204 / 255f, 1); - Assert.Equal(color2.G, 119 / 255f, 1); - Assert.Equal(color2.B, 34 / 255f, 1); - - // White - Cmyk cmyk3 = new Cmyk(0, 0, 0, 0); - Color color3 = cmyk3; - - Assert.Equal(color3.R, 1f, 1); - Assert.Equal(color3.G, 1f, 1); - Assert.Equal(color3.B, 1f, 1); - - // Check others. - //Random random = new Random(0); - //for (int i = 0; i < 1000; i++) - //{ - // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); - // Cmyk cmyk4 = color4; - // Assert.Equal(color4, (Color)cmyk4); - //} - } - - /// - /// Tests the implicit conversion from to . - /// Comparison values obtained from - /// http://colormine.org/convert/rgb-to-lab - /// - [Fact] - public void ColorToCieLab() - { - // White - Color color = new Color(1, 1, 1); - CieLab cielab = color; - - Assert.Equal(100, cielab.L, 3); - Assert.Equal(0.005, cielab.A, 3); - Assert.Equal(-0.010, cielab.B, 3); - - // Black - Color color2 = new Color(0, 0, 0); - CieLab cielab2 = color2; - Assert.Equal(0, cielab2.L, 3); - Assert.Equal(0, cielab2.A, 3); - Assert.Equal(0, cielab2.B, 3); - - //// Grey - Color color3 = new Color(128 / 255f, 128 / 255f, 128 / 255f); - CieLab cielab3 = color3; - Assert.Equal(53.585, cielab3.L, 3); - Assert.Equal(0.003, cielab3.A, 3); - Assert.Equal(-0.006, cielab3.B, 3); - - //// Cyan - Color color4 = new Color(0, 1, 1); - CieLab cielab4 = color4; - Assert.Equal(91.117, cielab4.L, 3); - Assert.Equal(-48.080, cielab4.A, 3); - Assert.Equal(-14.138, cielab4.B, 3); - } - - /// - /// Tests the implicit conversion from to . - /// - /// Comparison values obtained from - /// http://colormine.org/convert/rgb-to-lab - [Fact] - public void CieLabToColor() - { - // Dark moderate pink. - CieLab cielab = new CieLab(36.5492f, 33.3173f, -12.0615f); - Color color = cielab; - - Assert.Equal(color.R, 128 / 255f, 3); - Assert.Equal(color.G, 64 / 255f, 3); - Assert.Equal(color.B, 106 / 255f, 3); - - // Ochre - CieLab cielab2 = new CieLab(58.1758f, 27.3399f, 56.8240f); - Color color2 = cielab2; - - Assert.Equal(color2.R, 204 / 255f, 3); - Assert.Equal(color2.G, 119 / 255f, 3); - Assert.Equal(color2.B, 34 / 255f, 3); - - // White - CieLab cielab3 = new CieLab(0, 0, 0); - Color color3 = cielab3; - - Assert.Equal(color3.R, 0f, 3); - Assert.Equal(color3.G, 0f, 3); - Assert.Equal(color3.B, 0f, 3); - - // Check others. - //Random random = new Random(0); - //for (int i = 0; i < 1000; i++) - //{ - // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); - // CieLab cielab4 = color4; - // Assert.Equal(color4, (Color)cielab4); - //} - } - } -} diff --git a/tests/ImageProcessorCore.Tests/Colors/ColorSpacialTransformTests.cs b/tests/ImageProcessorCore.Tests/Colors/ColorSpacialTransformTests.cs deleted file mode 100644 index a4371cf54c..0000000000 --- a/tests/ImageProcessorCore.Tests/Colors/ColorSpacialTransformTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Tests -{ - using Xunit; - - public class ColorSpacialTransformTests - { - public class MultiplyTests - { - [Fact] - public void MultiplyBlendConvertsRedBackdropAndGreenOverlayToBlack() - { - var backdrop = Color.Red; - var overlay = Color.Green; - - var result = Color.Multiply(backdrop, overlay); - - Assert.Equal(Color.Black, result); - } - [Fact] - public void MultiplyBlendConvertsBlueBackdropAndWhiteOverlayToBlue() - { - var backdrop = Color.Blue; - var overlay = Color.White; - - var result = Color.Multiply(backdrop, overlay); - - Assert.Equal(Color.Blue, result); - } - [Fact] - public void MultiplyBlendConvertsBlueBackdropAndBlackOverlayToBlack() - { - var backdrop = Color.Blue; - var overlay = Color.Black; - - var result = Color.Multiply(backdrop, overlay); - - Assert.Equal(Color.Black, result); - } - [Fact] - public void MultiplyBlendConvertsBlueBackdropAndGrayOverlayToBlueBlack() - { - var backdrop = Color.Blue; - var overlay = Color.Gray; - - var result = Color.Multiply(backdrop, overlay); - - var expected = new Color(0, 0, 0.5f, 1); - - Assert.True(expected.AlmostEquals(result,.01f)); - } - } - } -} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Colors/ColorTests.cs b/tests/ImageProcessorCore.Tests/Colors/ColorTests.cs deleted file mode 100644 index e7d86012d0..0000000000 --- a/tests/ImageProcessorCore.Tests/Colors/ColorTests.cs +++ /dev/null @@ -1,108 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -using System.Numerics; - -namespace ImageProcessorCore.Tests -{ - using Xunit; - - /// - /// Tests the struct. - /// - public class ColorTests - { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Color color1 = new Color(0, 0, 0); - Color color2 = new Color(0, 0, 0, 1); - Color color3 = new Color("#000"); - Color color4 = new Color("#000000"); - Color color5 = new Color("#FF000000"); - - Assert.Equal(color1, color2); - Assert.Equal(color1, color3); - Assert.Equal(color1, color4); - Assert.Equal(color1, color5); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Color color1 = new Color(255, 0, 0, 255); - Color color2 = new Color(0, 0, 0, 255); - Color color3 = new Color("#000"); - Color color4 = new Color("#000000"); - Color color5 = new Color("#FF000000"); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color1, color3); - Assert.NotEqual(color1, color4); - Assert.NotEqual(color1, color5); - } - - /// - /// Tests whether the color constructor correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - Color color1 = new Color(1, .1f, .133f, .864f); - Assert.Equal(1, color1.R, 1); - Assert.Equal(.1f, color1.G, 1); - Assert.Equal(.133f, color1.B, 3); - Assert.Equal(.864f, color1.A, 3); - - Color color2 = new Color(1, .1f, .133f); - Assert.Equal(1, color2.R, 1); - Assert.Equal(.1f, color2.G, 1); - Assert.Equal(.133f, color2.B, 3); - Assert.Equal(1, color2.A, 1); - - Color color3 = new Color("#FF0000"); - Assert.Equal(1, color3.R, 1); - Assert.Equal(0, color3.G, 1); - Assert.Equal(0, color3.B, 3); - Assert.Equal(1, color3.A, 1); - - Color color4 = new Color(new Vector3(1, .1f, .133f)); - Assert.Equal(1, color4.R, 1); - Assert.Equal(.1f, color4.G, 1); - Assert.Equal(.133f, color4.B, 3); - Assert.Equal(1, color4.A, 1); - - Color color5 = new Color(new Vector3(1, .1f, .133f), .5f); - Assert.Equal(1, color5.R, 1); - Assert.Equal(.1f, color5.G, 1); - Assert.Equal(.133f, color5.B, 3); - Assert.Equal(.5f, color5.A, 1); - - Color color6 = new Color(new Vector4(1, .1f, .133f, .5f)); - Assert.Equal(1, color5.R, 1); - Assert.Equal(.1f, color6.G, 1); - Assert.Equal(.133f, color6.B, 3); - Assert.Equal(.5f, color6.A, 1); - } - - /// - /// Tests to see that in the input hex matches that of the output. - /// - [Fact] - public void ConvertHex() - { - const string First = "FF000000"; - Bgra32 bgra = new Color(0, 0, 0, 1); - string second = bgra.Bgra.ToString("X"); - Assert.Equal(First, second); - } - } -} diff --git a/tests/ImageProcessorCore.Tests/FileTestBase.cs b/tests/ImageProcessorCore.Tests/FileTestBase.cs index 100e74d0ff..05136a1c87 100644 --- a/tests/ImageProcessorCore.Tests/FileTestBase.cs +++ b/tests/ImageProcessorCore.Tests/FileTestBase.cs @@ -20,7 +20,7 @@ namespace ImageProcessorCore.Tests protected static readonly List Files = new List { //"TestImages/Formats/Jpg/Floorplan.jpeg", // Perf: Enable for local testing only - "TestImages/Formats/Jpg/Calliphora.jpg", + //"TestImages/Formats/Jpg/Calliphora.jpg", //"TestImages/Formats/Jpg/fb.jpg", // Perf: Enable for local testing only //"TestImages/Formats/Jpg/progress.jpg", // Perf: Enable for local testing only //"TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg", // Perf: Enable for local testing only @@ -28,8 +28,8 @@ namespace ImageProcessorCore.Tests // "TestImages/Formats/Bmp/neg_height.bmp", // Perf: Enable for local testing only //"TestImages/Formats/Png/blur.png", // Perf: Enable for local testing only //"TestImages/Formats/Png/indexed.png", // Perf: Enable for local testing only - "TestImages/Formats/Png/splash.png", - "TestImages/Formats/Gif/rings.gif", + //"TestImages/Formats/Png/splash.png", + //"TestImages/Formats/Gif/rings.gif", //"TestImages/Formats/Gif/giphy.gif" // Perf: Enable for local testing only }; diff --git a/tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs b/tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs deleted file mode 100644 index 6ff3a0f713..0000000000 --- a/tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Tests -{ - using System.Diagnostics; - using System.IO; - - using Formats; - - using Xunit; - - public class BitmapTests : FileTestBase - { - [Fact] - public void BitmapCanEncodeDifferentBitRates() - { - if (!Directory.Exists("TestOutput/Encode/Bitmap")) - { - Directory.CreateDirectory("TestOutput/Encode/Bitmap"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - Image image = new Image(stream); - string encodeFilename = "TestOutput/Encode/Bitmap/" + "24-" + Path.GetFileNameWithoutExtension(file) + ".bmp"; - - using (FileStream output = File.OpenWrite(encodeFilename)) - { - image.Save(output, new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel24 }); - } - - encodeFilename = "TestOutput/Encode/Bitmap/" + "32-" + Path.GetFileNameWithoutExtension(file) + ".bmp"; - - using (FileStream output = File.OpenWrite(encodeFilename)) - { - image.Save(output, new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel32 }); - } - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Formats/EncoderDecoderTests.cs b/tests/ImageProcessorCore.Tests/Formats/EncoderDecoderTests.cs deleted file mode 100644 index b26ef68093..0000000000 --- a/tests/ImageProcessorCore.Tests/Formats/EncoderDecoderTests.cs +++ /dev/null @@ -1,174 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Tests -{ - using System.Diagnostics; - using System.IO; - - using Formats; - - using Xunit; - using System.Linq; - - using ImageProcessorCore.Quantizers; - - public class EncoderDecoderTests : FileTestBase - { - [Fact] - public void ImageCanEncodeToString() - { - if (!Directory.Exists("TestOutput/ToString")) - { - Directory.CreateDirectory("TestOutput/ToString"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - Image image = new Image(stream); - string filename = "TestOutput/ToString/" + Path.GetFileNameWithoutExtension(file) + ".txt"; - File.WriteAllText(filename, image.ToString()); - } - } - } - - [Fact] - public void DecodeThenEncodeImageFromStreamShouldSucceed() - { - if (!Directory.Exists("TestOutput/Encode")) - { - Directory.CreateDirectory("TestOutput/Encode"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - Image image = new Image(stream); - string encodeFilename = "TestOutput/Encode/" + Path.GetFileName(file); - - using (FileStream output = File.OpenWrite(encodeFilename)) - { - image.Save(output); - } - } - } - } - - [Fact] - public void QuantizeImageShouldPreserveMaximumColorPrecision() - { - if (!Directory.Exists("TestOutput/Quantize")) - { - Directory.CreateDirectory("TestOutput/Quantize"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - Image image = new Image(stream); - IQuantizer quantizer = new OctreeQuantizer(); - QuantizedImage quantizedImage = quantizer.Quantize(image, 256); - - using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Octree-{Path.GetFileName(file)}")) - { - Image qi = quantizedImage.ToImage(); - qi.Save(output, image.CurrentImageFormat); - - } - - quantizer = new WuQuantizer(); - quantizedImage = quantizer.Quantize(image, 256); - - using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Wu-{Path.GetFileName(file)}")) - { - quantizedImage.ToImage().Save(output, image.CurrentImageFormat); - } - - quantizer = new PaletteQuantizer(); - quantizedImage = quantizer.Quantize(image, 256); - - using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Palette-{Path.GetFileName(file)}")) - { - Image qi = quantizedImage.ToImage(); - qi.Save(output, image.CurrentImageFormat); - } - } - } - } - - [Fact] - public void ImageCanConvertFormat() - { - if (!Directory.Exists("TestOutput/Format")) - { - Directory.CreateDirectory("TestOutput/Format"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.gif")) - { - image.SaveAsGif(output); - } - - using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.bmp")) - { - image.SaveAsBmp(output); - } - - using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.jpg")) - { - image.SaveAsJpeg(output); - } - - using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.png")) - { - image.SaveAsPng(output); - } - } - } - } - - [Fact] - public void ImageShouldPreservePixelByteOrderWhenSerialized() - { - if (!Directory.Exists("TestOutput/Serialized")) - { - Directory.CreateDirectory("TestOutput/Serialized"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - Image image = new Image(stream); - byte[] serialized; - using (MemoryStream memoryStream = new MemoryStream()) - { - image.Save(memoryStream); - memoryStream.Flush(); - serialized = memoryStream.ToArray(); - } - - using (MemoryStream memoryStream = new MemoryStream(serialized)) - { - Image image2 = new Image(memoryStream); - using (FileStream output = File.OpenWrite($"TestOutput/Serialized/{Path.GetFileName(file)}")) - { - image2.Save(output); - } - } - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Formats/PngTests.cs b/tests/ImageProcessorCore.Tests/Formats/PngTests.cs deleted file mode 100644 index e824a62f93..0000000000 --- a/tests/ImageProcessorCore.Tests/Formats/PngTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Tests -{ - using System.IO; - - using Formats; - - using Xunit; - - public class PngTests : FileTestBase - { - [Fact] - public void ImageCanSaveIndexedPng() - { - if (!Directory.Exists("TestOutput/Encode/Png")) - { - Directory.CreateDirectory("TestOutput/Encode/Png"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/Encode/Png/{Path.GetFileNameWithoutExtension(file)}.png")) - { - image.Quality = 256; - image.Save(output, new PngFormat()); - } - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Helpers/GuardTests.cs b/tests/ImageProcessorCore.Tests/Helpers/GuardTests.cs deleted file mode 100644 index bb93f928ac..0000000000 --- a/tests/ImageProcessorCore.Tests/Helpers/GuardTests.cs +++ /dev/null @@ -1,209 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright © James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// -// -// Tests the helper. -// -// -------------------------------------------------------------------------------------------------------------------- - -namespace ImageProcessorCore.Tests.Helpers -{ - using System; - using System.Diagnostics.CodeAnalysis; - - using Xunit; - - /// - /// Tests the helper. - /// - public class GuardTests - { - /// - /// Tests that the method throws when the argument is null. - /// - [Fact] - public void NotNullThrowsWhenArgIsNull() - { - Assert.Throws(() => Guard.NotNull(null, "foo")); - } - - /// - /// Tests that the method throws when the argument name is empty. - /// - [Fact] - public void NotNullThrowsWhenArgNameEmpty() - { - Assert.Throws(() => Guard.NotNull(null, string.Empty)); - } - - /// - /// Tests that the method throws when the argument is empty. - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1122:UseStringEmptyForEmptyStrings", Justification = "Reviewed. Suppression is OK here.")] - public void NotEmptyThrowsWhenEmpty() - { - Assert.Throws(() => Guard.NotNullOrEmpty("", string.Empty)); - } - - /// - /// Tests that the method throws when the argument is whitespace. - /// - [Fact] - public void NotEmptyThrowsWhenWhitespace() - { - Assert.Throws(() => Guard.NotNullOrEmpty(" ", string.Empty)); - } - - /// - /// Tests that the method throws when the argument name is null. - /// - [Fact] - public void NotEmptyThrowsWhenParameterNameNull() - { - Assert.Throws(() => Guard.NotNullOrEmpty(null, null)); - } - - /// - /// Tests that the method throws when the argument is greater. - /// - [Fact] - public void LessThanThrowsWhenArgIsGreater() - { - Assert.Throws(() => Guard.MustBeLessThan(1, 0, "foo")); - } - - /// - /// Tests that the method throws when the argument is equal. - /// - [Fact] - public void LessThanThrowsWhenArgIsEqual() - { - Assert.Throws(() => Guard.MustBeLessThan(1, 1, "foo")); - } - - /// - /// Tests that the method throws when the argument is greater. - /// - [Fact] - public void LessThanOrEqualToThrowsWhenArgIsGreater() - { - Assert.Throws(() => Guard.MustBeLessThanOrEqualTo(1, 0, "foo")); - } - - /// - /// Tests that the method does not throw when the argument - /// is less. - /// - [Fact] - public void LessThanOrEqualToDoesNotThrowWhenArgIsLess() - { - Exception ex = Record.Exception(() => Guard.MustBeLessThanOrEqualTo(0, 1, "foo")); - Assert.Null(ex); - } - - /// - /// Tests that the method does not throw when the argument - /// is equal. - /// - [Fact] - public void LessThanOrEqualToDoesNotThrowWhenArgIsEqual() - { - Exception ex = Record.Exception(() => Guard.MustBeLessThanOrEqualTo(1, 1, "foo")); - Assert.Equal(1, 1); - Assert.Null(ex); - } - - /// - /// Tests that the method throws when the argument is greater. - /// - [Fact] - public void GreaterThanThrowsWhenArgIsLess() - { - Assert.Throws(() => Guard.MustBeGreaterThan(0, 1, "foo")); - } - - /// - /// Tests that the method throws when the argument is greater. - /// - [Fact] - public void GreaterThanThrowsWhenArgIsEqual() - { - Assert.Throws(() => Guard.MustBeGreaterThan(1, 1, "foo")); - } - - /// - /// Tests that the method throws when the argument name is greater. - /// - [Fact] - public void GreaterThanOrEqualToThrowsWhenArgIsLess() - { - Assert.Throws(() => Guard.MustBeGreaterThanOrEqualTo(0, 1, "foo")); - } - - /// - /// Tests that the method does not throw when the argument - /// is less. - /// - [Fact] - public void GreaterThanOrEqualToDoesNotThrowWhenArgIsGreater() - { - Exception ex = Record.Exception(() => Guard.MustBeGreaterThanOrEqualTo(1, 0, "foo")); - Assert.Null(ex); - } - - /// - /// Tests that the method does not throw when the argument - /// is equal. - /// - [Fact] - public void GreaterThanOrEqualToDoesNotThrowWhenArgIsEqual() - { - Exception ex = Record.Exception(() => Guard.MustBeGreaterThanOrEqualTo(1, 1, "foo")); - Assert.Equal(1, 1); - Assert.Null(ex); - } - - /// - /// Tests that the method throws when the argument is less. - /// - [Fact] - public void BetweenOrEqualToThrowsWhenArgIsLess() - { - Assert.Throws(() => Guard.MustBeBetweenOrEqualTo(-2, -1, 1, "foo")); - } - - /// - /// Tests that the method throws when the argument is greater. - /// - [Fact] - public void BetweenOrEqualToThrowsWhenArgIsGreater() - { - Assert.Throws(() => Guard.MustBeBetweenOrEqualTo(2, -1, 1, "foo")); - } - - /// - /// Tests that the method does not throw when the argument - /// is equal. - /// - [Fact] - public void BetweenOrEqualToDoesNotThrowWhenArgIsEqual() - { - Exception ex = Record.Exception(() => Guard.MustBeBetweenOrEqualTo(1, 1, 1, "foo")); - Assert.Null(ex); - } - - /// - /// Tests that the method does not throw when the argument - /// is equal. - /// - [Fact] - public void BetweenOrEqualToDoesNotThrowWhenArgIsBetween() - { - Exception ex = Record.Exception(() => Guard.MustBeBetweenOrEqualTo(0, -1, 1, "foo")); - Assert.Null(ex); - } - } -} diff --git a/tests/ImageProcessorCore.Tests/Numerics/PointTests.cs b/tests/ImageProcessorCore.Tests/Numerics/PointTests.cs deleted file mode 100644 index 0108fa5905..0000000000 --- a/tests/ImageProcessorCore.Tests/Numerics/PointTests.cs +++ /dev/null @@ -1,55 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright © James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// -// -// Tests the struct. -// -// -------------------------------------------------------------------------------------------------------------------- - -namespace ImageProcessorCore.Tests -{ - using Xunit; - - /// - /// Tests the struct. - /// - public class PointTests - { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Point first = new Point(100, 100); - Point second = new Point(100, 100); - - Assert.Equal(first, second); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Point first = new Point(0, 100); - Point second = new Point(100, 100); - - Assert.NotEqual(first, second); - } - - /// - /// Tests whether the point constructor correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - Point first = new Point(4, 5); - Assert.Equal(4, first.X); - Assert.Equal(5, first.Y); - } - } -} diff --git a/tests/ImageProcessorCore.Tests/Numerics/RectangleTests.cs b/tests/ImageProcessorCore.Tests/Numerics/RectangleTests.cs deleted file mode 100644 index 3b76bfa51b..0000000000 --- a/tests/ImageProcessorCore.Tests/Numerics/RectangleTests.cs +++ /dev/null @@ -1,71 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright © James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// -// -// Tests the struct. -// -// -------------------------------------------------------------------------------------------------------------------- - -namespace ImageProcessorCore.Tests -{ - using Xunit; - - /// - /// Tests the struct. - /// - public class RectangleTests - { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Rectangle first = new Rectangle(1, 1, 100, 100); - Rectangle second = new Rectangle(1, 1, 100, 100); - - Assert.Equal(first, second); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Rectangle first = new Rectangle(1, 1, 0, 100); - Rectangle second = new Rectangle(1, 1, 100, 100); - - Assert.NotEqual(first, second); - } - - /// - /// Tests whether the rectangle constructors correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - Rectangle first = new Rectangle(1, 1, 50, 100); - Assert.Equal(1, first.X); - Assert.Equal(1, first.Y); - Assert.Equal(50, first.Width); - Assert.Equal(100, first.Height); - Assert.Equal(1, first.Top); - Assert.Equal(51, first.Right); - Assert.Equal(101, first.Bottom); - Assert.Equal(1, first.Left); - - Rectangle second = new Rectangle(new Point(1, 1), new Size(50, 100)); - Assert.Equal(1, second.X); - Assert.Equal(1, second.Y); - Assert.Equal(50, second.Width); - Assert.Equal(100, second.Height); - Assert.Equal(1, second.Top); - Assert.Equal(51, second.Right); - Assert.Equal(101, second.Bottom); - Assert.Equal(1, second.Left); - } - } -} diff --git a/tests/ImageProcessorCore.Tests/Numerics/SizeTests.cs b/tests/ImageProcessorCore.Tests/Numerics/SizeTests.cs deleted file mode 100644 index 3d90b2bceb..0000000000 --- a/tests/ImageProcessorCore.Tests/Numerics/SizeTests.cs +++ /dev/null @@ -1,55 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright © James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// -// -// Tests the struct. -// -// -------------------------------------------------------------------------------------------------------------------- - -namespace ImageProcessorCore.Tests -{ - using Xunit; - - /// - /// Tests the struct. - /// - public class SizeTests - { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Size first = new Size(100, 100); - Size second = new Size(100, 100); - - Assert.Equal(first, second); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Size first = new Size(0, 100); - Size second = new Size(100, 100); - - Assert.NotEqual(first, second); - } - - /// - /// Tests whether the size constructor correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - Size first = new Size(4, 5); - Assert.Equal(4, first.Width); - Assert.Equal(5, first.Height); - } - } -} diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/FilterTests.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/FilterTests.cs deleted file mode 100644 index e9414393f3..0000000000 --- a/tests/ImageProcessorCore.Tests/Processors/Filters/FilterTests.cs +++ /dev/null @@ -1,89 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Tests -{ - using System.Diagnostics; - using System.IO; - - using Processors; - - using Xunit; - public class FilterTests : FileTestBase - { - public static readonly TheoryData Filters = new TheoryData - { - { "Brightness-50", new BrightnessProcessor(50) }, - { "Brightness--50", new BrightnessProcessor(-50) }, - { "Contrast-50", new ContrastProcessor(50) }, - { "Contrast--50", new ContrastProcessor(-50) }, - { "BackgroundColor", new BackgroundColorProcessor(new Color(243 / 255f, 87 / 255f, 161 / 255f,.5f))}, - { "Blend", new BlendProcessor(new Image(File.OpenRead("TestImages/Formats/Bmp/Car.bmp")),50)}, - { "Saturation-50", new SaturationProcessor(50) }, - { "Saturation--50", new SaturationProcessor(-50) }, - { "Alpha--50", new AlphaProcessor(50) }, - { "Invert", new InvertProcessor() }, - { "Sepia", new SepiaProcessor() }, - { "BlackWhite", new BlackWhiteProcessor() }, - { "Lomograph", new LomographProcessor() }, - { "Polaroid", new PolaroidProcessor() }, - { "Kodachrome", new KodachromeProcessor() }, - { "GreyscaleBt709", new GreyscaleBt709Processor() }, - { "GreyscaleBt601", new GreyscaleBt601Processor() }, - { "Kayyali", new KayyaliProcessor() }, - { "Kirsch", new KirschProcessor() }, - { "Laplacian3X3", new Laplacian3X3Processor() }, - { "Laplacian5X5", new Laplacian5X5Processor() }, - { "LaplacianOfGaussian", new LaplacianOfGaussianProcessor() }, - { "Prewitt", new PrewittProcessor() }, - { "RobertsCross", new RobertsCrossProcessor() }, - { "Scharr", new ScharrProcessor() }, - { "Sobel", new SobelProcessor {Greyscale = true} }, - { "Pixelate", new PixelateProcessor(8) }, - { "GuassianBlur", new GuassianBlurProcessor(10) }, - { "GuassianSharpen", new GuassianSharpenProcessor(10) }, - { "Hue-180", new HueProcessor(180) }, - { "Hue--180", new HueProcessor(-180) }, - { "BoxBlur", new BoxBlurProcessor(10) }, - { "Vignette", new VignetteProcessor() }, - { "Protanopia", new ProtanopiaProcessor() }, - { "Protanomaly", new ProtanomalyProcessor() }, - { "Deuteranopia", new DeuteranopiaProcessor() }, - { "Deuteranomaly", new DeuteranomalyProcessor() }, - { "Tritanopia", new TritanopiaProcessor() }, - { "Tritanomaly", new TritanomalyProcessor() }, - { "Achromatopsia", new AchromatopsiaProcessor() }, - { "Achromatomaly", new AchromatomalyProcessor() } - - }; - - [Theory] - [MemberData("Filters")] - public void FilterImage(string name, IImageProcessor processor) - { - if (!Directory.Exists("TestOutput/Filter")) - { - Directory.CreateDirectory("TestOutput/Filter"); - } - - 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) + "-" + name + Path.GetExtension(file); - using (FileStream output = File.OpenWrite($"TestOutput/Filter/{Path.GetFileName(filename)}")) - { - processor.OnProgress += this.ProgressUpdate; - image.Process(processor).Save(output); - processor.OnProgress -= this.ProgressUpdate; - } - } - } - } - } -} diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs index e81607b585..96f6da0daf 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs @@ -36,7 +36,7 @@ namespace ImageProcessorCore.Tests public static readonly TheoryData Samplers = new TheoryData { { "Resize", new ResizeProcessor(new BicubicResampler()) }, - { "Crop", new CropProcessor() } + //{ "Crop", new CropProcessor() } }; public static readonly TheoryData RotateFlips = new TheoryData @@ -48,56 +48,56 @@ namespace ImageProcessorCore.Tests { RotateType.Rotate270, FlipType.None }, }; - [Theory] - [MemberData("Samplers")] - public void SampleImage(string name, IImageSampler processor) - { - if (!Directory.Exists("TestOutput/Sample")) - { - Directory.CreateDirectory("TestOutput/Sample"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - Image image = new Image(stream); - string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); - - using (FileStream output = File.OpenWrite($"TestOutput/Sample/{ Path.GetFileName(filename) }")) - { - processor.OnProgress += this.ProgressUpdate; - image = image.Process(image.Width / 2, image.Height / 2, processor); - image.Save(output); - processor.OnProgress -= this.ProgressUpdate; - } - } - } - } - - [Fact] - public void ImageShouldPad() - { - if (!Directory.Exists("TestOutput/Pad")) - { - Directory.CreateDirectory("TestOutput/Pad"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileName(file); - - 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); - } - } - } - } + //[Theory] + //[MemberData("Samplers")] + //public void SampleImage(string name, IImageSampler processor) + //{ + // if (!Directory.Exists("TestOutput/Sample")) + // { + // Directory.CreateDirectory("TestOutput/Sample"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // Image image = new Image(stream); + // string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); + + // using (FileStream output = File.OpenWrite($"TestOutput/Sample/{ Path.GetFileName(filename) }")) + // { + // processor.OnProgress += this.ProgressUpdate; + // image = image.Process(image.Width / 2, image.Height / 2, processor); + // image.Save(output); + // processor.OnProgress -= this.ProgressUpdate; + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldPad() + //{ + // if (!Directory.Exists("TestOutput/Pad")) + // { + // Directory.CreateDirectory("TestOutput/Pad"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileName(file); + + // 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); + // } + // } + // } + //} [Theory] [MemberData("ReSamplers")] @@ -114,7 +114,7 @@ namespace ImageProcessorCore.Tests { string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); - Image image = new Image(stream); + Image image = new Image(stream); using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) { image.Resize(image.Width / 2, image.Height / 2, sampler, false, this.ProgressUpdate) @@ -124,403 +124,403 @@ namespace ImageProcessorCore.Tests } } - [Fact] - public void ImageShouldResizeWidthAndKeepAspect() - { - if (!Directory.Exists("TestOutput/Resize")) - { - Directory.CreateDirectory("TestOutput/Resize"); - } - - var name = "FixedWidth"; - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) - { - image.Resize(image.Width / 3, 0, new TriangleResampler(), false, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldResizeHeightAndKeepAspect() - { - if (!Directory.Exists("TestOutput/Resize")) - { - Directory.CreateDirectory("TestOutput/Resize"); - } - - string name = "FixedHeight"; - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) - { - image.Resize(0, image.Height / 3, new TriangleResampler(), false, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldResizeWithCropMode() - { - if (!Directory.Exists("TestOutput/ResizeCrop")) - { - Directory.CreateDirectory("TestOutput/ResizeCrop"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileName(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/ResizeCrop/{filename}")) - { - ResizeOptions options = new ResizeOptions() - { - Size = new Size(image.Width / 2, image.Height) - }; - - image.Resize(options, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldResizeWithPadMode() - { - if (!Directory.Exists("TestOutput/ResizePad")) - { - Directory.CreateDirectory("TestOutput/ResizePad"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileName(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/ResizePad/{filename}")) - { - ResizeOptions options = new ResizeOptions() - { - Size = new Size(image.Width + 200, image.Height), - Mode = ResizeMode.Pad - }; - - image.Resize(options, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldResizeWithBoxPadMode() - { - if (!Directory.Exists("TestOutput/ResizeBoxPad")) - { - Directory.CreateDirectory("TestOutput/ResizeBoxPad"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileName(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/ResizeBoxPad/{filename}")) - { - ResizeOptions options = new ResizeOptions() - { - Size = new Size(image.Width + 200, image.Height + 200), - Mode = ResizeMode.BoxPad - }; - - image.Resize(options, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldResizeWithMaxMode() - { - if (!Directory.Exists("TestOutput/ResizeMax")) - { - Directory.CreateDirectory("TestOutput/ResizeMax"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileName(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/ResizeMax/{filename}")) - { - ResizeOptions options = new ResizeOptions() - { - Size = new Size(300, 300), - Mode = ResizeMode.Max, - //Sampler = new NearestNeighborResampler() - }; - - image.Resize(options, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldResizeWithMinMode() - { - if (!Directory.Exists("TestOutput/ResizeMin")) - { - Directory.CreateDirectory("TestOutput/ResizeMin"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileName(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/ResizeMin/{filename}")) - { - ResizeOptions options = new ResizeOptions() - { - Size = new Size(image.Width - 50, image.Height - 25), - Mode = ResizeMode.Min - }; - - image.Resize(options, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldResizeWithStretchMode() - { - if (!Directory.Exists("TestOutput/ResizeStretch")) - { - Directory.CreateDirectory("TestOutput/ResizeStretch"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileName(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/ResizeStretch/{filename}")) - { - ResizeOptions options = new ResizeOptions() - { - Size = new Size(image.Width - 200, image.Height), - Mode = ResizeMode.Stretch - }; - - image.Resize(options, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldNotDispose() - { - if (!Directory.Exists("TestOutput/Dispose")) - { - Directory.CreateDirectory("TestOutput/Dispose"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileName(file); - - Image image = new Image(stream); - image = image.BackgroundColor(Color.RebeccaPurple); - using (FileStream output = File.OpenWrite($"TestOutput/Dispose/{filename}")) - { - ResizeOptions options = new ResizeOptions() - { - Size = new Size(image.Width - 10, image.Height), - Mode = ResizeMode.Stretch - }; - - image.Resize(options, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [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)) - { - string filename = Path.GetFileNameWithoutExtension(file) + "-" + rotateType + flipType + Path.GetExtension(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/RotateFlip/{filename}")) - { - image.RotateFlip(rotateType, flipType, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldRotate() - { - if (!Directory.Exists("TestOutput/Rotate")) - { - Directory.CreateDirectory("TestOutput/Rotate"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileName(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}")) - { - image.Rotate(-170, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldSkew() - { - if (!Directory.Exists("TestOutput/Skew")) - { - Directory.CreateDirectory("TestOutput/Skew"); - } - - // Matches live example http://www.w3schools.com/css/tryit.asp?filename=trycss3_transform_skew - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileName(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/Skew/{filename}")) - { - image.Skew(-20, -10, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldEntropyCrop() - { - if (!Directory.Exists("TestOutput/EntropyCrop")) - { - Directory.CreateDirectory("TestOutput/EntropyCrop"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileNameWithoutExtension(file) + "-EntropyCrop" + Path.GetExtension(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/EntropyCrop/{filename}")) - { - image.EntropyCrop(.5f, this.ProgressUpdate).Save(output); - } - } - } - } - - [Fact] - public void ImageShouldCrop() - { - if (!Directory.Exists("TestOutput/Crop")) - { - Directory.CreateDirectory("TestOutput/Crop"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - - string filename = Path.GetFileNameWithoutExtension(file) + "-Crop" + Path.GetExtension(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/Crop/{filename}")) - { - image.Crop(image.Width / 2, image.Height / 2, this.ProgressUpdate).Save(output); - } - } - } - } - - [Theory] - [InlineData(-2, 0)] - [InlineData(-1, 0)] - [InlineData(0, 1)] - [InlineData(1, 0)] - [InlineData(2, 0)] - [InlineData(2, 0)] - public static void Lanczos3WindowOscillatesCorrectly(float x, float expected) - { - Lanczos3Resampler sampler = new Lanczos3Resampler(); - float result = sampler.GetValue(x); - - Assert.Equal(result, expected); - } + //[Fact] + //public void ImageShouldResizeWidthAndKeepAspect() + //{ + // if (!Directory.Exists("TestOutput/Resize")) + // { + // Directory.CreateDirectory("TestOutput/Resize"); + // } + + // var name = "FixedWidth"; + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) + // { + // image.Resize(image.Width / 3, 0, new TriangleResampler(), false, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldResizeHeightAndKeepAspect() + //{ + // if (!Directory.Exists("TestOutput/Resize")) + // { + // Directory.CreateDirectory("TestOutput/Resize"); + // } + + // string name = "FixedHeight"; + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) + // { + // image.Resize(0, image.Height / 3, new TriangleResampler(), false, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldResizeWithCropMode() + //{ + // if (!Directory.Exists("TestOutput/ResizeCrop")) + // { + // Directory.CreateDirectory("TestOutput/ResizeCrop"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileName(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/ResizeCrop/{filename}")) + // { + // ResizeOptions options = new ResizeOptions() + // { + // Size = new Size(image.Width / 2, image.Height) + // }; + + // image.Resize(options, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldResizeWithPadMode() + //{ + // if (!Directory.Exists("TestOutput/ResizePad")) + // { + // Directory.CreateDirectory("TestOutput/ResizePad"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileName(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/ResizePad/{filename}")) + // { + // ResizeOptions options = new ResizeOptions() + // { + // Size = new Size(image.Width + 200, image.Height), + // Mode = ResizeMode.Pad + // }; + + // image.Resize(options, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldResizeWithBoxPadMode() + //{ + // if (!Directory.Exists("TestOutput/ResizeBoxPad")) + // { + // Directory.CreateDirectory("TestOutput/ResizeBoxPad"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileName(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/ResizeBoxPad/{filename}")) + // { + // ResizeOptions options = new ResizeOptions() + // { + // Size = new Size(image.Width + 200, image.Height + 200), + // Mode = ResizeMode.BoxPad + // }; + + // image.Resize(options, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldResizeWithMaxMode() + //{ + // if (!Directory.Exists("TestOutput/ResizeMax")) + // { + // Directory.CreateDirectory("TestOutput/ResizeMax"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileName(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/ResizeMax/{filename}")) + // { + // ResizeOptions options = new ResizeOptions() + // { + // Size = new Size(300, 300), + // Mode = ResizeMode.Max, + // //Sampler = new NearestNeighborResampler() + // }; + + // image.Resize(options, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldResizeWithMinMode() + //{ + // if (!Directory.Exists("TestOutput/ResizeMin")) + // { + // Directory.CreateDirectory("TestOutput/ResizeMin"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileName(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/ResizeMin/{filename}")) + // { + // ResizeOptions options = new ResizeOptions() + // { + // Size = new Size(image.Width - 50, image.Height - 25), + // Mode = ResizeMode.Min + // }; + + // image.Resize(options, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldResizeWithStretchMode() + //{ + // if (!Directory.Exists("TestOutput/ResizeStretch")) + // { + // Directory.CreateDirectory("TestOutput/ResizeStretch"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileName(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/ResizeStretch/{filename}")) + // { + // ResizeOptions options = new ResizeOptions() + // { + // Size = new Size(image.Width - 200, image.Height), + // Mode = ResizeMode.Stretch + // }; + + // image.Resize(options, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldNotDispose() + //{ + // if (!Directory.Exists("TestOutput/Dispose")) + // { + // Directory.CreateDirectory("TestOutput/Dispose"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileName(file); + + // Image image = new Image(stream); + // image = image.BackgroundColor(Color.RebeccaPurple); + // using (FileStream output = File.OpenWrite($"TestOutput/Dispose/{filename}")) + // { + // ResizeOptions options = new ResizeOptions() + // { + // Size = new Size(image.Width - 10, image.Height), + // Mode = ResizeMode.Stretch + // }; + + // image.Resize(options, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[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)) + // { + // string filename = Path.GetFileNameWithoutExtension(file) + "-" + rotateType + flipType + Path.GetExtension(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/RotateFlip/{filename}")) + // { + // image.RotateFlip(rotateType, flipType, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldRotate() + //{ + // if (!Directory.Exists("TestOutput/Rotate")) + // { + // Directory.CreateDirectory("TestOutput/Rotate"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileName(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}")) + // { + // image.Rotate(-170, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldSkew() + //{ + // if (!Directory.Exists("TestOutput/Skew")) + // { + // Directory.CreateDirectory("TestOutput/Skew"); + // } + + // // Matches live example http://www.w3schools.com/css/tryit.asp?filename=trycss3_transform_skew + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileName(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/Skew/{filename}")) + // { + // image.Skew(-20, -10, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldEntropyCrop() + //{ + // if (!Directory.Exists("TestOutput/EntropyCrop")) + // { + // Directory.CreateDirectory("TestOutput/EntropyCrop"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileNameWithoutExtension(file) + "-EntropyCrop" + Path.GetExtension(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/EntropyCrop/{filename}")) + // { + // image.EntropyCrop(.5f, this.ProgressUpdate).Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldCrop() + //{ + // if (!Directory.Exists("TestOutput/Crop")) + // { + // Directory.CreateDirectory("TestOutput/Crop"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + + // string filename = Path.GetFileNameWithoutExtension(file) + "-Crop" + Path.GetExtension(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/Crop/{filename}")) + // { + // image.Crop(image.Width / 2, image.Height / 2, this.ProgressUpdate).Save(output); + // } + // } + // } + //} + + //[Theory] + //[InlineData(-2, 0)] + //[InlineData(-1, 0)] + //[InlineData(0, 1)] + //[InlineData(1, 0)] + //[InlineData(2, 0)] + //[InlineData(2, 0)] + //public static void Lanczos3WindowOscillatesCorrectly(float x, float expected) + //{ + // Lanczos3Resampler sampler = new Lanczos3Resampler(); + // float result = sampler.GetValue(x); + + // Assert.Equal(result, expected); + //} } }