diff --git a/ImageProcessorCore.sln b/ImageProcessorCore.sln index 9d828a74a0..28b7b107c3 100644 --- a/ImageProcessorCore.sln +++ b/ImageProcessorCore.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.25123.0 +VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ImageProcessorCore", "src\ImageProcessorCore\ImageProcessorCore.xproj", "{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}" EndProject diff --git a/NuGet.config b/NuGet.config index 554c2f634b..05430a8ee7 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,6 +1,7 @@ - + + \ No newline at end of file diff --git a/README.md b/README.md index 85edc99565..65168b6bde 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ + # ImageProcessorCore @@ -74,6 +75,7 @@ git clone https://github.com/JimBobSquarePants/ImageProcessor - Resampling algorithms. (Optional gamma correction, resize modes, Performance improvements?) - [x] Box - [x] Bicubic + - [x] Lanczos2 - [x] Lanczos3 - [x] Lanczos5 - [x] Lanczos8 @@ -81,7 +83,6 @@ git clone https://github.com/JimBobSquarePants/ImageProcessor - [x] Nearest Neighbour - [x] Robidoux - [x] Robidoux Sharp - - [x] Robidoux Soft - [x] Spline - [x] Triangle - [x] Welch @@ -100,8 +101,8 @@ git clone https://github.com/JimBobSquarePants/ImageProcessor - [x] Skew by x/y angles and center point. - ColorMatrix operations (Uses Matrix4x4) - [x] BlackWhite - - [x] Greyscale BT709 - - [x] Greyscale BT601 + - [x] Grayscale BT709 + - [x] Grayscale BT601 - [x] Hue - [x] Saturation - [x] Lomograph @@ -160,7 +161,7 @@ With this version the API will change dramatically. Without the constraints of ` Image methods are also fluent which allow chaining much like the `ImageFactory` class in the Framework version. -Here's an example of the code required to resize an image using the default Bicubic resampler then turn the colors into their greyscale equivalent using the BT709 standard matrix. +Here's an example of the code required to resize an image using the default Bicubic resampler then turn the colors into their grayscale equivalent using the BT709 standard matrix. ```csharp using (FileStream stream = File.OpenRead("foo.jpg")) @@ -168,32 +169,12 @@ using (FileStream output = File.OpenWrite("bar.jpg")) { Image image = new Image(stream); image.Resize(image.Width / 2, image.Height / 2) - .Greyscale() + .Grayscale() .Save(output); } ``` -It will also be possible to pass collections of processors as params to manipulate images. For example here I am applying a Gaussian blur with a sigma of 5 to an image, then detecting the edges using a Sobel operator working in greyscale mode. - -```csharp -using (FileStream stream = File.OpenRead("foo.jpg")) -using (FileStream output = File.OpenWrite("bar.jpg")) -{ - Image image = new Image(stream); - List processors = new List() - { - new GuassianBlur(5), - new Sobel { Greyscale = true } - }; - - foreach (IImageProcessor processor in processors){ - - image.Process(processor) - .Save(output); - } -} -``` -Individual processors can be initialised and apply processing against images. This allows nesting which will allow the powerful combination of processing methods: +Individual processors can be initialised and apply processing against images. This allows nesting which brings the potential for powerful combinations of processing methods: ```csharp new Brightness(50).Apply(sourceImage, targetImage, sourceImage.Bounds); diff --git a/src/ImageProcessorCore/Bootstrapper.cs b/src/ImageProcessorCore/Bootstrapper.cs index a9c7dc8318..99fa6f1555 100644 --- a/src/ImageProcessorCore/Bootstrapper.cs +++ b/src/ImageProcessorCore/Bootstrapper.cs @@ -8,7 +8,10 @@ namespace ImageProcessorCore using System; using System.Collections.Generic; using System.Collections.ObjectModel; - using ImageProcessorCore.Formats; + using System.Reflection; + using System.Threading.Tasks; + + using Formats; /// /// Provides initialization code which allows extending the library. @@ -26,18 +29,25 @@ namespace ImageProcessorCore /// 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 List + this.imageFormats = new List { new BmpFormat(), new JpegFormat(), new PngFormat(), new GifFormat() - }); + }; + + this.pixelAccessors = new Dictionary> + { + { typeof(Color), i=> new ColorPixelAccessor(i) } + }; } /// @@ -46,9 +56,21 @@ namespace ImageProcessorCore public static Bootstrapper Instance = Lazy.Value; /// - /// Gets the list of supported + /// Gets the collection of supported + /// + public IReadOnlyCollection ImageFormats => + new ReadOnlyCollection(this.imageFormats); + + /// + /// Gets the collection of supported pixel accessors + /// + public IReadOnlyDictionary> PixelAccessors => + new ReadOnlyDictionary>(this.pixelAccessors); + + /// + /// Gets or sets the global parallel options for processing tasks in parallel. /// - public IReadOnlyCollection ImageFormats => new ReadOnlyCollection(this.imageFormats); + public ParallelOptions ParallelOptions { get; set; } = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; /// /// Adds a new to the collection of supported image formats. @@ -58,5 +80,40 @@ namespace ImageProcessorCore { this.imageFormats.Add(format); } + + /// + /// Adds a pixel accessor for the given pixel format. + /// + /// The packed format type, must implement + /// The function to return a new instance of the pixel accessor. + public void AddPixelAccessor(Type packedType, Func initializer) + { + if (!typeof(IPackedVector).GetTypeInfo().IsAssignableFrom(packedType.GetTypeInfo())) + { + throw new ArgumentException($"Type {packedType} must implement {nameof(IPackedVector)}"); + } + + this.pixelAccessors.Add(packedType, initializer); + } + + /// + /// Gets an instance of the correct for the packed vector. + /// + /// The type of pixel data. + /// The packed format. long, float. + /// The image + /// The + public IPixelAccessor GetPixelAccessor(IImageBase image) + where T : IPackedVector + where TP : struct + { + Type packed = typeof(T); + if (this.pixelAccessors.ContainsKey(packed)) + { + return (IPixelAccessor)this.pixelAccessors[packed].Invoke(image); + } + + throw new NotSupportedException($"PixelAccessor cannot be loaded for {packed}:"); + } } } diff --git a/src/ImageProcessorCore/Colors/Color.cs b/src/ImageProcessorCore/Colors/Color.cs index 0e47e03e1b..69bce48536 100644 --- a/src/ImageProcessorCore/Colors/Color.cs +++ b/src/ImageProcessorCore/Colors/Color.cs @@ -6,46 +6,65 @@ namespace ImageProcessorCore { using System; - using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; /// - /// Represents a four-component color using red, green, blue, and alpha data. - /// Each component is stored in premultiplied format multiplied by the alpha component. + /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 255. + /// The color components are stored in red, green, blue, and alpha order. /// /// /// 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 partial struct Color : IEquatable, IAlmostEquatable + [StructLayout(LayoutKind.Explicit)] + public partial struct Color : IPackedVector, IEquatable { /// - /// Represents an empty that has R, G, B, and A values set to zero. + /// Gets or sets the blue component. /// - public static readonly Color Empty = default(Color); + [FieldOffset(0)] + public byte R; /// - /// The epsilon for comparing floating point numbers. + /// Gets or sets the green component. /// - private const float Epsilon = 0.001f; + [FieldOffset(1)] + public byte G; /// - /// The backing vector for SIMD support. + /// Gets or sets the red component. /// - private Vector4 backingVector; + [FieldOffset(2)] + public byte B; /// - /// Initializes a new instance of the struct. + /// Gets or sets the alpha component. /// - /// The red component of this . - /// The green component of this . - /// The blue component of this . - /// The alpha component of this . - public Color(float r, float g, float b, float a = 1) + [FieldOffset(3)] + public byte A; + + /// + /// The packed value. + /// + [FieldOffset(0)] + private readonly uint packedValue; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + public Color(byte r, byte g, byte b, byte a = 255) : this() { - this.backingVector = new Vector4(r, g, b, a); + this.R = r; + this.G = g; + this.B = b; + this.A = a; } /// @@ -68,25 +87,17 @@ namespace ImageProcessorCore if (hex.Length == 8) { - float r = Convert.ToByte(hex.Substring(2, 2), 16); - float g = Convert.ToByte(hex.Substring(4, 2), 16); - float b = Convert.ToByte(hex.Substring(6, 2), 16); - float a = Convert.ToByte(hex.Substring(0, 2), 16); - - // Do division of Vector4 instead of each component to utilize SIMD optimizations - this.backingVector = new Vector4(r, g, b, a) / 255f; - this.backingVector = FromNonPremultiplied(this.backingVector, this.A); - + this.R = Convert.ToByte(hex.Substring(2, 2), 16); + this.G = Convert.ToByte(hex.Substring(4, 2), 16); + this.B = Convert.ToByte(hex.Substring(6, 2), 16); + this.A = Convert.ToByte(hex.Substring(0, 2), 16); } else if (hex.Length == 6) { - float r = Convert.ToByte(hex.Substring(0, 2), 16); - float g = Convert.ToByte(hex.Substring(2, 2), 16); - float b = Convert.ToByte(hex.Substring(4, 2), 16); - float a = 255f; - - // Do division of Vector4 instead of each component to utilize SIMD optimizations - this.backingVector = new Vector4(r, g, b, a) / 255f; + this.R = Convert.ToByte(hex.Substring(0, 2), 16); + this.G = Convert.ToByte(hex.Substring(2, 2), 16); + this.B = Convert.ToByte(hex.Substring(4, 2), 16); + this.A = 255; } else { @@ -94,185 +105,60 @@ namespace ImageProcessorCore string gh = char.ToString(hex[1]); string bh = char.ToString(hex[2]); - float r = Convert.ToByte(rh + rh, 16); - float g = Convert.ToByte(gh + gh, 16); - float b = Convert.ToByte(bh + bh, 16); - float a = 255f; - - this.backingVector = new Vector4(r, g, b, a) / 255f; + this.R = Convert.ToByte(rh + rh, 16); + this.G = Convert.ToByte(gh + gh, 16); + this.B = Convert.ToByte(bh + bh, 16); + this.A = 255; } } /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// - /// The vector. - public Color(Vector4 vector) + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + public Color(float r, float g, float b, float a = 1) + : this() { - this.backingVector = vector; + Vector4 clamped = Vector4.Clamp(new Vector4(r, g, b, a), Vector4.Zero, Vector4.One) * 255F; + this.R = (byte)Math.Round(clamped.X); + this.G = (byte)Math.Round(clamped.Y); + this.B = (byte)Math.Round(clamped.Z); + this.A = (byte)Math.Round(clamped.W); } /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// /// - /// The vector representing the red, green, and blue componenets. + /// The vector containing the components for the packed vector. /// public Color(Vector3 vector) + : this() { - this.backingVector = new Vector4(vector, 1); + Vector3 clamped = Vector3.Clamp(vector, Vector3.Zero, Vector3.One) * 255F; + this.R = (byte)Math.Round(clamped.X); + this.G = (byte)Math.Round(clamped.Y); + this.B = (byte)Math.Round(clamped.Z); + this.A = 255; } /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// /// - /// The vector representing the red, green, and blue componenets. + /// The vector containing the components for the packed vector. /// - /// The alpha component. - public Color(Vector3 vector, float alpha) - { - this.backingVector = new Vector4(vector, alpha); - } - - /// - /// Gets or sets the red component of the color. - /// - public float R - { - get - { - return this.backingVector.X; - } - - set - { - this.backingVector.X = value; - } - } - - /// - /// Gets or sets the green component of the color. - /// - public float G - { - get - { - return this.backingVector.Y; - } - - set - { - this.backingVector.Y = value; - } - } - - /// - /// Gets or sets the blue component of the color. - /// - public float B - { - get - { - return this.backingVector.Z; - } - - set - { - this.backingVector.Z = value; - } - } - - /// - /// Gets or sets the alpha component of the color. - /// - public float A - { - get - { - return 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 this color with the component values clamped from 0 to 1. - /// - public Color Limited => new Color(Vector4.Clamp(this.backingVector, Vector4.Zero, Vector4.One)); - - /// - /// Computes the product of multiplying a color by a given factor. - /// - /// The color. - /// The multiplication factor. - /// - /// The - /// - public static Color operator *(Color color, float factor) - { - return new Color(color.backingVector * factor); - } - - /// - /// Computes the product of multiplying a color by a given factor. - /// - /// The multiplication factor. - /// The color. - /// - /// The - /// - public static Color operator *(float factor, Color color) - { - return new Color(color.backingVector * factor); - } - - /// - /// Computes the product of multiplying two colors. - /// - /// The color on the left hand of the operand. - /// The color on the right hand of the operand. - /// - /// The - /// - public static Color operator *(Color left, Color right) - { - return new Color(left.backingVector * right.backingVector); - } - - /// - /// Computes the sum of adding two colors. - /// - /// The color on the left hand of the operand. - /// The color on the right hand of the operand. - /// - /// The - /// - public static Color operator +(Color left, Color right) - { - return new Color(left.backingVector + right.backingVector); - } - - /// - /// Computes the difference left by subtracting one color from another. - /// - /// The color on the left hand of the operand. - /// The color on the right hand of the operand. - /// - /// The - /// - public static Color operator -(Color left, Color right) + public Color(Vector4 vector) + : this() { - return new Color(left.backingVector - right.backingVector); + Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255F; + this.R = (byte)Math.Round(clamped.X); + this.G = (byte)Math.Round(clamped.Y); + this.B = (byte)Math.Round(clamped.Z); + this.A = (byte)Math.Round(clamped.W); } /// @@ -289,230 +175,99 @@ namespace ImageProcessorCore /// public static bool operator ==(Color left, Color right) { - return left.Equals(right); + return left.packedValue == right.packedValue; } /// - /// Compares two objects for inequality. + /// Compares two objects for equality. /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// + /// 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. + /// True if the current left is equal to the parameter; otherwise, false. /// public static bool operator !=(Color left, Color right) { - return !left.Equals(right); - } - - /// - /// Returns a new color whose components are the average of the components of first and second. - /// - /// The first color. - /// The second color. - /// - /// The - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Color Average(Color first, Color second) - { - return new Color((first.backingVector + second.backingVector) * .5f); - } - - /// - /// Compresses a linear color signal to its sRGB equivalent. - /// - /// - /// - /// The whose signal to compress. - /// The . - public static Color Compress(Color linear) - { - // TODO: Is there a faster way to do this? - float r = Compress(linear.R); - float g = Compress(linear.G); - float b = Compress(linear.B); - - return new Color(r, g, b, linear.A); - } - - /// - /// Expands an sRGB color signal to its linear equivalent. - /// - /// - /// - /// The whose signal to expand. - /// The . - public static Color Expand(Color gamma) - { - // TODO: Is there a faster way to do this? - float r = Expand(gamma.R); - float g = Expand(gamma.G); - float b = Expand(gamma.B); - - return new Color(r, g, b, gamma.A); - } - - /// - /// Converts a non-premultipled alpha to a - /// that contains premultiplied alpha. - /// - /// The to convert. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Color FromNonPremultiplied(Color color) - { - return new Color(FromNonPremultiplied(color.backingVector, color.A)); - } - - /// - /// Converts a non-premultiplied alpha Vector4 to a Vector4 that contains premultiplied alpha. - /// - /// The vector to convert. - /// The alpha to use in conversion. - /// The Vector4 with premultiplied alpha. - private static Vector4 FromNonPremultiplied(Vector4 vector, float alpha) - { - return vector * new Vector4(alpha, alpha, alpha, 1); + return left.packedValue != right.packedValue; } - /// - /// Converts a premultipled alpha to a - /// that contains non-premultiplied alpha. - /// - /// The to convert. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Color ToNonPremultiplied(Color color) + /// + public uint PackedValue() { - float a = color.A; - if (Math.Abs(a) < Epsilon) - { - return new Color(color.backingVector); - } - - return new Color(color.backingVector / new Vector4(a, a, a, 1)); + return this.packedValue; } - /// - /// Gets a representation for this . - /// - /// A representation for this object. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() + /// + public void PackVector(Vector4 vector) { - return new Vector4(this.R, this.G, this.B, this.A); + Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255F; + this.R = (byte)Math.Round(clamped.X); + this.G = (byte)Math.Round(clamped.Y); + this.B = (byte)Math.Round(clamped.Z); + this.A = (byte)Math.Round(clamped.W); } - /// - /// Gets a representation for this . - /// - /// A representation for this object. - public Vector3 ToVector3() + /// + public void PackBytes(byte x, byte y, byte z, byte w) { - return new Vector3(this.R, this.G, this.B); + this.R = x; + this.G = y; + this.B = z; + this.A = w; } /// - public override int GetHashCode() + public Vector4 ToVector4() { - return GetHashCode(this); + return new Vector4(this.R, this.G, this.B, this.A) / 255F; } /// - public override string ToString() + public byte[] ToBytes() { - if (this.IsEmpty) - { - return "Color [ Empty ]"; - } - - return $"Color [ R={this.R:#0.##}, G={this.G:#0.##}, B={this.B:#0.##}, A={this.A:#0.##} ]"; + return new[] { this.R, this.G, this.B, this.A }; } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object obj) { - if (obj is Color) - { - return this.Equals((Color)obj); - } - - return false; + return (obj is Color) && this.Equals((Color)obj); } /// public bool Equals(Color other) { - return this.AlmostEquals(other, Epsilon); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool AlmostEquals(Color other, float precision) - { - Vector4 result = Vector4.Abs(this.backingVector - other.backingVector); - - return result.X < precision - && result.Y < precision - && result.Z < precision - && result.W < precision; + return this.packedValue == other.packedValue; } /// - /// Gets the compressed sRGB value from an linear signal. - /// - /// + /// Gets a string representation of the packed vector. /// - /// The signal value to compress. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float Compress(float signal) + /// A string representation of the packed vector. + public override string ToString() { - if (signal <= 0.0031308f) - { - return signal * 12.92f; - } - - return (1.055f * (float)Math.Pow(signal, 0.41666666f)) - 0.055f; + return this.ToVector4().ToString(); } - /// - /// Gets the expanded linear value from an sRGB signal. - /// - /// - /// - /// The signal value to expand. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float Expand(float signal) + /// + public override int GetHashCode() { - if (signal <= 0.04045f) - { - return signal / 12.92f; - } - - return (float)Math.Pow((signal + 0.055f) / 1.055f, 2.4f); + return this.GetHashCode(this); } /// /// 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 static int GetHashCode(Color color) => color.backingVector.GetHashCode(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetHashCode(Color packed) + { + return packed.packedValue.GetHashCode(); + } } -} +} \ No newline at end of file diff --git a/src/ImageProcessorCore/Colors/ColorDefinitions.cs b/src/ImageProcessorCore/Colors/ColorDefinitions.cs index aaf99bb1b3..1c853949f6 100644 --- a/src/ImageProcessorCore/Colors/ColorDefinitions.cs +++ b/src/ImageProcessorCore/Colors/ColorDefinitions.cs @@ -6,8 +6,8 @@ namespace ImageProcessorCore { /// - /// Represents a four-component color using red, green, blue, and alpha data. - /// Each component is stored in premultiplied format multiplied by the alpha component. + /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 255. + /// The color components are stored in red, green, blue, and alpha order. /// /// /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, @@ -16,713 +16,714 @@ namespace ImageProcessorCore public partial struct Color { /// - /// Represents a matching the W3C definition that has a hex triplet value of #F0F8FF. + /// Represents a matching the W3C definition that has an hex value of #F0F8FF. /// - public static readonly Color AliceBlue = new Color(240 / 255f, 248 / 255f, 1); + public static readonly Color AliceBlue = new Color(240, 248, 255, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FAEBD7. + /// Represents a matching the W3C definition that has an hex value of #FAEBD7. /// - public static readonly Color AntiqueWhite = new Color(250 / 255f, 235 / 255f, 215 / 255f); + public static readonly Color AntiqueWhite = new Color(250, 235, 215, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #00FFFF. + /// Represents a matching the W3C definition that has an hex value of #00FFFF. /// - public static readonly Color Aqua = new Color(0, 1, 1); + public static readonly Color Aqua = new Color(0, 255, 255, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #7FFFD4. + /// Represents a matching the W3C definition that has an hex value of #7FFFD4. /// - public static readonly Color Aquamarine = new Color(127 / 255f, 1, 212 / 255f); + public static readonly Color Aquamarine = new Color(127, 255, 212, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #F0FFFF. + /// Represents a matching the W3C definition that has an hex value of #F0FFFF. /// - public static readonly Color Azure = new Color(240 / 255f, 1, 1); + public static readonly Color Azure = new Color(240, 255, 255, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #F5F5DC. + /// Represents a matching the W3C definition that has an hex value of #F5F5DC. /// - public static readonly Color Beige = new Color(245 / 255f, 245 / 255f, 220 / 255f); + public static readonly Color Beige = new Color(245, 245, 220, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFE4C4. + /// Represents a matching the W3C definition that has an hex value of #FFE4C4. /// - public static readonly Color Bisque = new Color(1, 228 / 255f, 196 / 255f); + public static readonly Color Bisque = new Color(255, 228, 196, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #000000. + /// Represents a matching the W3C definition that has an hex value of #000000. /// - public static readonly Color Black = new Color(0, 0, 0); + public static readonly Color Black = new Color(0, 0, 0, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFEBCD. + /// Represents a matching the W3C definition that has an hex value of #FFEBCD. /// - public static readonly Color BlanchedAlmond = new Color(1, 235 / 255f, 205 / 255f); + public static readonly Color BlanchedAlmond = new Color(255, 235, 205, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #0000FF. + /// Represents a matching the W3C definition that has an hex value of #0000FF. /// - public static readonly Color Blue = new Color(0, 0, 1); + public static readonly Color Blue = new Color(0, 0, 255, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #8A2BE2. + /// Represents a matching the W3C definition that has an hex value of #8A2BE2. /// - public static readonly Color BlueViolet = new Color(138 / 255f, 43 / 255f, 226 / 255f); + public static readonly Color BlueViolet = new Color(138, 43, 226, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #A52A2A. + /// Represents a matching the W3C definition that has an hex value of #A52A2A. /// - public static readonly Color Brown = new Color(165 / 255f, 42 / 255f, 42 / 255f); + public static readonly Color Brown = new Color(165, 42, 42, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #DEB887. + /// Represents a matching the W3C definition that has an hex value of #DEB887. /// - public static readonly Color BurlyWood = new Color(222 / 255f, 184 / 255f, 135 / 255f); + public static readonly Color BurlyWood = new Color(222, 184, 135, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #5F9EA0. + /// Represents a matching the W3C definition that has an hex value of #5F9EA0. /// - public static readonly Color CadetBlue = new Color(95 / 255f, 158 / 255f, 160 / 255f); + public static readonly Color CadetBlue = new Color(95, 158, 160, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #7FFF00. + /// Represents a matching the W3C definition that has an hex value of #7FFF00. /// - public static readonly Color Chartreuse = new Color(127 / 255f, 1, 0); + public static readonly Color Chartreuse = new Color(127, 255, 0, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #D2691E. + /// Represents a matching the W3C definition that has an hex value of #D2691E. /// - public static readonly Color Chocolate = new Color(210 / 255f, 105 / 255f, 30 / 255f); + public static readonly Color Chocolate = new Color(210, 105, 30, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FF7F50. + /// Represents a matching the W3C definition that has an hex value of #FF7F50. /// - public static readonly Color Coral = new Color(1, 127 / 255f, 80 / 255f); + public static readonly Color Coral = new Color(255, 127, 80, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #6495ED. + /// Represents a matching the W3C definition that has an hex value of #6495ED. /// - public static readonly Color CornflowerBlue = new Color(100 / 255f, 149 / 255f, 237 / 255f); + public static readonly Color CornflowerBlue = new Color(100, 149, 237, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFF8DC. + /// Represents a matching the W3C definition that has an hex value of #FFF8DC. /// - public static readonly Color Cornsilk = new Color(1, 248 / 255f, 220 / 255f); + public static readonly Color Cornsilk = new Color(255, 248, 220, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #DC143C. + /// Represents a matching the W3C definition that has an hex value of #DC143C. /// - public static readonly Color Crimson = new Color(220 / 255f, 20 / 255f, 60 / 255f); + public static readonly Color Crimson = new Color(220, 20, 60, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #00FFFF. + /// Represents a matching the W3C definition that has an hex value of #00FFFF. /// - public static readonly Color Cyan = new Color(0, 1, 1); + public static readonly Color Cyan = new Color(0, 255, 255, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #00008B. + /// Represents a matching the W3C definition that has an hex value of #00008B. /// - public static readonly Color DarkBlue = new Color(0, 0, 139 / 255f); + public static readonly Color DarkBlue = new Color(0, 0, 139, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #008B8B. + /// Represents a matching the W3C definition that has an hex value of #008B8B. /// - public static readonly Color DarkCyan = new Color(0, 139 / 255f, 139 / 255f); + public static readonly Color DarkCyan = new Color(0, 139, 139, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #B8860B. + /// Represents a matching the W3C definition that has an hex value of #B8860B. /// - public static readonly Color DarkGoldenrod = new Color(184 / 255f, 134 / 255f, 11 / 255f); + public static readonly Color DarkGoldenrod = new Color(184, 134, 11, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #A9A9A9. + /// Represents a matching the W3C definition that has an hex value of #A9A9A9. /// - public static readonly Color DarkGray = new Color(169 / 255f, 169 / 255f, 169 / 255f); + public static readonly Color DarkGray = new Color(169, 169, 169, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #006400. + /// Represents a matching the W3C definition that has an hex value of #006400. /// - public static readonly Color DarkGreen = new Color(0, 100 / 255f, 0); + public static readonly Color DarkGreen = new Color(0, 100, 0, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #BDB76B. + /// Represents a matching the W3C definition that has an hex value of #BDB76B. /// - public static readonly Color DarkKhaki = new Color(189 / 255f, 183 / 255f, 107 / 255f); + public static readonly Color DarkKhaki = new Color(189, 183, 107, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #8B008B. + /// Represents a matching the W3C definition that has an hex value of #8B008B. /// - public static readonly Color DarkMagenta = new Color(139 / 255f, 0, 139 / 255f); + public static readonly Color DarkMagenta = new Color(139, 0, 139, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #556B2F. + /// Represents a matching the W3C definition that has an hex value of #556B2F. /// - public static readonly Color DarkOliveGreen = new Color(85 / 255f, 107 / 255f, 47 / 255f); + public static readonly Color DarkOliveGreen = new Color(85, 107, 47, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FF8C00. + /// Represents a matching the W3C definition that has an hex value of #FF8C00. /// - public static readonly Color DarkOrange = new Color(1, 140 / 255f, 0); + public static readonly Color DarkOrange = new Color(255, 140, 0, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #9932CC. + /// Represents a matching the W3C definition that has an hex value of #9932CC. /// - public static readonly Color DarkOrchid = new Color(153 / 255f, 50 / 255f, 204 / 255f); + public static readonly Color DarkOrchid = new Color(153, 50, 204, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #8B0000. + /// Represents a matching the W3C definition that has an hex value of #8B0000. /// - public static readonly Color DarkRed = new Color(139 / 255f, 0, 0); + public static readonly Color DarkRed = new Color(139, 0, 0, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #E9967A. + /// Represents a matching the W3C definition that has an hex value of #E9967A. /// - public static readonly Color DarkSalmon = new Color(233 / 255f, 150 / 255f, 122 / 255f); + public static readonly Color DarkSalmon = new Color(233, 150, 122, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #8FBC8B. + /// Represents a matching the W3C definition that has an hex value of #8FBC8B. /// - public static readonly Color DarkSeaGreen = new Color(143 / 255f, 188 / 255f, 139 / 255f); + public static readonly Color DarkSeaGreen = new Color(143, 188, 139, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #483D8B. + /// Represents a matching the W3C definition that has an hex value of #483D8B. /// - public static readonly Color DarkSlateBlue = new Color(72 / 255f, 61 / 255f, 139 / 255f); + public static readonly Color DarkSlateBlue = new Color(72, 61, 139, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #2F4F4F. + /// Represents a matching the W3C definition that has an hex value of #2F4F4F. /// - public static readonly Color DarkSlateGray = new Color(47 / 255f, 79 / 255f, 79 / 255f); + public static readonly Color DarkSlateGray = new Color(47, 79, 79, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #00CED1. + /// Represents a matching the W3C definition that has an hex value of #00CED1. /// - public static readonly Color DarkTurquoise = new Color(0, 206 / 255f, 209 / 255f); + public static readonly Color DarkTurquoise = new Color(0, 206, 209, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #9400D3. + /// Represents a matching the W3C definition that has an hex value of #9400D3. /// - public static readonly Color DarkViolet = new Color(148 / 255f, 0, 211 / 255f); + public static readonly Color DarkViolet = new Color(148, 0, 211, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FF1493. + /// Represents a matching the W3C definition that has an hex value of #FF1493. /// - public static readonly Color DeepPink = new Color(1, 20 / 255f, 147 / 255f); + public static readonly Color DeepPink = new Color(255, 20, 147, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #00BFFF. + /// Represents a matching the W3C definition that has an hex value of #00BFFF. /// - public static readonly Color DeepSkyBlue = new Color(0, 191 / 255f, 1); + public static readonly Color DeepSkyBlue = new Color(0, 191, 255, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #696969. + /// Represents a matching the W3C definition that has an hex value of #696969. /// - public static readonly Color DimGray = new Color(105 / 255f, 105 / 255f, 105 / 255f); + public static readonly Color DimGray = new Color(105, 105, 105, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #1E90FF. + /// Represents a matching the W3C definition that has an hex value of #1E90FF. /// - public static readonly Color DodgerBlue = new Color(30 / 255f, 144 / 255f, 1); + public static readonly Color DodgerBlue = new Color(30, 144, 255, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #B22222. + /// Represents a matching the W3C definition that has an hex value of #B22222. /// - public static readonly Color Firebrick = new Color(178 / 255f, 34 / 255f, 34 / 255f); + public static readonly Color Firebrick = new Color(178, 34, 34, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFFAF0. + /// Represents a matching the W3C definition that has an hex value of #FFFAF0. /// - public static readonly Color FloralWhite = new Color(1, 250 / 255f, 240 / 255f); + public static readonly Color FloralWhite = new Color(255, 250, 240, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #228B22. + /// Represents a matching the W3C definition that has an hex value of #228B22. /// - public static readonly Color ForestGreen = new Color(34 / 255f, 139 / 255f, 34 / 255f); + public static readonly Color ForestGreen = new Color(34, 139, 34, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FF00FF. + /// Represents a matching the W3C definition that has an hex value of #FF00FF. /// - public static readonly Color Fuchsia = new Color(1, 0, 1); + public static readonly Color Fuchsia = new Color(255, 0, 255, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #DCDCDC. + /// Represents a matching the W3C definition that has an hex value of #DCDCDC. /// - public static readonly Color Gainsboro = new Color(220 / 255f, 220 / 255f, 220 / 255f); + public static readonly Color Gainsboro = new Color(220, 220, 220, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #F8F8FF. + /// Represents a matching the W3C definition that has an hex value of #F8F8FF. /// - public static readonly Color GhostWhite = new Color(248 / 255f, 248 / 255f, 1); + public static readonly Color GhostWhite = new Color(248, 248, 255, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFD700. + /// Represents a matching the W3C definition that has an hex value of #FFD700. /// - public static readonly Color Gold = new Color(1, 215 / 255f, 0); + public static readonly Color Gold = new Color(255, 215, 0, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #DAA520. + /// Represents a matching the W3C definition that has an hex value of #DAA520. /// - public static readonly Color Goldenrod = new Color(218 / 255f, 165 / 255f, 32 / 255f); + public static readonly Color Goldenrod = new Color(218, 165, 32, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #808080. + /// Represents a matching the W3C definition that has an hex value of #808080. /// - public static readonly Color Gray = new Color(128 / 255f, 128 / 255f, 128 / 255f); + public static readonly Color Gray = new Color(128, 128, 128, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #008000. + /// Represents a matching the W3C definition that has an hex value of #008000. /// - public static readonly Color Green = new Color(0, 128 / 255f, 0); + public static readonly Color Green = new Color(0, 128, 0, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #ADFF2F. + /// Represents a matching the W3C definition that has an hex value of #ADFF2F. /// - public static readonly Color GreenYellow = new Color(173 / 255f, 1, 47 / 255f); + public static readonly Color GreenYellow = new Color(173, 255, 47, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #F0FFF0. + /// Represents a matching the W3C definition that has an hex value of #F0FFF0. /// - public static readonly Color Honeydew = new Color(240 / 255f, 1, 240 / 255f); + public static readonly Color Honeydew = new Color(240, 255, 240, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FF69B4. + /// Represents a matching the W3C definition that has an hex value of #FF69B4. /// - public static readonly Color HotPink = new Color(1, 105 / 255f, 180 / 255f); + public static readonly Color HotPink = new Color(255, 105, 180, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #CD5C5C. + /// Represents a matching the W3C definition that has an hex value of #CD5C5C. /// - public static readonly Color IndianRed = new Color(205 / 255f, 92 / 255f, 92 / 255f); + public static readonly Color IndianRed = new Color(205, 92, 92, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #4B0082. + /// Represents a matching the W3C definition that has an hex value of #4B0082. /// - public static readonly Color Indigo = new Color(75 / 255f, 0, 130 / 255f); + public static readonly Color Indigo = new Color(75, 0, 130, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFFFF0. + /// Represents a matching the W3C definition that has an hex value of #FFFFF0. /// - public static readonly Color Ivory = new Color(1, 1, 240 / 255f); + public static readonly Color Ivory = new Color(255, 255, 240, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #F0E68C. + /// Represents a matching the W3C definition that has an hex value of #F0E68C. /// - public static readonly Color Khaki = new Color(240 / 255f, 230 / 255f, 140 / 255f); + public static readonly Color Khaki = new Color(240, 230, 140, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #E6E6FA. + /// Represents a matching the W3C definition that has an hex value of #E6E6FA. /// - public static readonly Color Lavender = new Color(230 / 255f, 230 / 255f, 250 / 255f); + public static readonly Color Lavender = new Color(230, 230, 250, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFF0F5. + /// Represents a matching the W3C definition that has an hex value of #FFF0F5. /// - public static readonly Color LavenderBlush = new Color(1, 240 / 255f, 245 / 255f); + public static readonly Color LavenderBlush = new Color(255, 240, 245, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #7CFC00. + /// Represents a matching the W3C definition that has an hex value of #7CFC00. /// - public static readonly Color LawnGreen = new Color(124 / 255f, 252 / 255f, 0); + public static readonly Color LawnGreen = new Color(124, 252, 0, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFFACD. + /// Represents a matching the W3C definition that has an hex value of #FFFACD. /// - public static readonly Color LemonChiffon = new Color(1, 250 / 255f, 205 / 255f); + public static readonly Color LemonChiffon = new Color(255, 250, 205, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #ADD8E6. + /// Represents a matching the W3C definition that has an hex value of #ADD8E6. /// - public static readonly Color LightBlue = new Color(173 / 255f, 216 / 255f, 230 / 255f); + public static readonly Color LightBlue = new Color(173, 216, 230, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #F08080. + /// Represents a matching the W3C definition that has an hex value of #F08080. /// - public static readonly Color LightCoral = new Color(240 / 255f, 128 / 255f, 128 / 255f); + public static readonly Color LightCoral = new Color(240, 128, 128, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #E0FFFF. + /// Represents a matching the W3C definition that has an hex value of #E0FFFF. /// - public static readonly Color LightCyan = new Color(224 / 255f, 1, 1); + public static readonly Color LightCyan = new Color(224, 255, 255, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FAFAD2. + /// Represents a matching the W3C definition that has an hex value of #FAFAD2. /// - public static readonly Color LightGoldenrodYellow = new Color(250 / 255f, 250 / 255f, 210 / 255f); + public static readonly Color LightGoldenrodYellow = new Color(250, 250, 210, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #90EE90. + /// Represents a matching the W3C definition that has an hex value of #D3D3D3. /// - public static readonly Color LightGreen = new Color(144 / 255f, 238 / 255f, 144 / 255f); + public static readonly Color LightGray = new Color(211, 211, 211, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #D3D3D3. + /// Represents a matching the W3C definition that has an hex value of #90EE90. /// - public static readonly Color LightGray = new Color(211 / 255f, 211 / 255f, 211 / 255f); + public static readonly Color LightGreen = new Color(144, 238, 144, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFB6C1. + /// Represents a matching the W3C definition that has an hex value of #FFB6C1. /// - public static readonly Color LightPink = new Color(1, 182 / 255f, 193 / 255f); + public static readonly Color LightPink = new Color(255, 182, 193, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFA07A. + /// Represents a matching the W3C definition that has an hex value of #FFA07A. /// - public static readonly Color LightSalmon = new Color(1, 160 / 255f, 122 / 255f); + public static readonly Color LightSalmon = new Color(255, 160, 122, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #20B2AA. + /// Represents a matching the W3C definition that has an hex value of #20B2AA. /// - public static readonly Color LightSeaGreen = new Color(32 / 255f, 178 / 255f, 170 / 255f); + public static readonly Color LightSeaGreen = new Color(32, 178, 170, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #87CEFA. + /// Represents a matching the W3C definition that has an hex value of #87CEFA. /// - public static readonly Color LightSkyBlue = new Color(135 / 255f, 206 / 255f, 250 / 255f); + public static readonly Color LightSkyBlue = new Color(135, 206, 250, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #778899. + /// Represents a matching the W3C definition that has an hex value of #778899. /// - public static readonly Color LightSlateGray = new Color(119 / 255f, 136 / 255f, 153 / 255f); + public static readonly Color LightSlateGray = new Color(119, 136, 153, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #B0C4DE. + /// Represents a matching the W3C definition that has an hex value of #B0C4DE. /// - public static readonly Color LightSteelBlue = new Color(176 / 255f, 196 / 255f, 222 / 255f); + public static readonly Color LightSteelBlue = new Color(176, 196, 222, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFFFE0. + /// Represents a matching the W3C definition that has an hex value of #FFFFE0. /// - public static readonly Color LightYellow = new Color(1, 1, 224 / 255f); + public static readonly Color LightYellow = new Color(255, 255, 224, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #00FF00. + /// Represents a matching the W3C definition that has an hex value of #00FF00. /// - public static readonly Color Lime = new Color(0, 1, 0); + public static readonly Color Lime = new Color(0, 255, 0, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #32CD32. + /// Represents a matching the W3C definition that has an hex value of #32CD32. /// - public static readonly Color LimeGreen = new Color(50 / 255f, 205 / 255f, 50 / 255f); + public static readonly Color LimeGreen = new Color(50, 205, 50, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FAF0E6. + /// Represents a matching the W3C definition that has an hex value of #FAF0E6. /// - public static readonly Color Linen = new Color(250 / 255f, 240 / 255f, 230 / 255f); + public static readonly Color Linen = new Color(250, 240, 230, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FF00FF. + /// Represents a matching the W3C definition that has an hex value of #FF00FF. /// - public static readonly Color Magenta = new Color(1, 0, 1); + public static readonly Color Magenta = new Color(255, 0, 255, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #800000. + /// Represents a matching the W3C definition that has an hex value of #800000. /// - public static readonly Color Maroon = new Color(128 / 255f, 0, 0); + public static readonly Color Maroon = new Color(128, 0, 0, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #66CDAA. + /// Represents a matching the W3C definition that has an hex value of #66CDAA. /// - public static readonly Color MediumAquamarine = new Color(102 / 255f, 205 / 255f, 170 / 255f); + public static readonly Color MediumAquamarine = new Color(102, 205, 170, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #0000CD. + /// Represents a matching the W3C definition that has an hex value of #0000CD. /// - public static readonly Color MediumBlue = new Color(0, 0, 205 / 255f); + public static readonly Color MediumBlue = new Color(0, 0, 205, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #BA55D3. + /// Represents a matching the W3C definition that has an hex value of #BA55D3. /// - public static readonly Color MediumOrchid = new Color(186 / 255f, 85 / 255f, 211 / 255f); + public static readonly Color MediumOrchid = new Color(186, 85, 211, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #9370DB. + /// Represents a matching the W3C definition that has an hex value of #9370DB. /// - public static readonly Color MediumPurple = new Color(147 / 255f, 112 / 255f, 219 / 255f); + public static readonly Color MediumPurple = new Color(147, 112, 219, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #3CB371. + /// Represents a matching the W3C definition that has an hex value of #3CB371. /// - public static readonly Color MediumSeaGreen = new Color(60 / 255f, 179 / 255f, 113 / 255f); + public static readonly Color MediumSeaGreen = new Color(60, 179, 113, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #7B68EE. + /// Represents a matching the W3C definition that has an hex value of #7B68EE. /// - public static readonly Color MediumSlateBlue = new Color(123 / 255f, 104 / 255f, 238 / 255f); + public static readonly Color MediumSlateBlue = new Color(123, 104, 238, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #00FA9A. + /// Represents a matching the W3C definition that has an hex value of #00FA9A. /// - public static readonly Color MediumSpringGreen = new Color(0, 250 / 255f, 154 / 255f); + public static readonly Color MediumSpringGreen = new Color(0, 250, 154, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #48D1CC. + /// Represents a matching the W3C definition that has an hex value of #48D1CC. /// - public static readonly Color MediumTurquoise = new Color(72 / 255f, 209 / 255f, 204 / 255f); + public static readonly Color MediumTurquoise = new Color(72, 209, 204, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #C71585. + /// Represents a matching the W3C definition that has an hex value of #C71585. /// - public static readonly Color MediumVioletRed = new Color(199 / 255f, 21 / 255f, 133 / 255f); + public static readonly Color MediumVioletRed = new Color(199, 21, 133, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #191970. + /// Represents a matching the W3C definition that has an hex value of #191970. /// - public static readonly Color MidnightBlue = new Color(25 / 255f, 25 / 255f, 112 / 255f); + public static readonly Color MidnightBlue = new Color(25, 25, 112, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #F5FFFA. + /// Represents a matching the W3C definition that has an hex value of #F5FFFA. /// - public static readonly Color MintCream = new Color(245 / 255f, 1, 250 / 255f); + public static readonly Color MintCream = new Color(245, 255, 250, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFE4E1. + /// Represents a matching the W3C definition that has an hex value of #FFE4E1. /// - public static readonly Color MistyRose = new Color(1, 228 / 255f, 225 / 255f); + public static readonly Color MistyRose = new Color(255, 228, 225, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFE4B5. + /// Represents a matching the W3C definition that has an hex value of #FFE4B5. /// - public static readonly Color Moccasin = new Color(1, 228 / 255f, 181 / 255f); + public static readonly Color Moccasin = new Color(255, 228, 181, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFDEAD. + /// Represents a matching the W3C definition that has an hex value of #FFDEAD. /// - public static readonly Color NavajoWhite = new Color(1, 222 / 255f, 173 / 255f); + public static readonly Color NavajoWhite = new Color(255, 222, 173, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #000080. + /// Represents a matching the W3C definition that has an hex value of #000080. /// - public static readonly Color Navy = new Color(0, 0, 128 / 255f); + public static readonly Color Navy = new Color(0, 0, 128, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FDF5E6. + /// Represents a matching the W3C definition that has an hex value of #FDF5E6. /// - public static readonly Color OldLace = new Color(253 / 255f, 245 / 255f, 230 / 255f); + public static readonly Color OldLace = new Color(253, 245, 230, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #808000. + /// Represents a matching the W3C definition that has an hex value of #808000. /// - public static readonly Color Olive = new Color(128 / 255f, 128 / 255f, 0); + public static readonly Color Olive = new Color(128, 128, 0, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #6B8E23. + /// Represents a matching the W3C definition that has an hex value of #6B8E23. /// - public static readonly Color OliveDrab = new Color(107 / 255f, 142 / 255f, 35 / 255f); + public static readonly Color OliveDrab = new Color(107, 142, 35, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFA500. + /// Represents a matching the W3C definition that has an hex value of #FFA500. /// - public static readonly Color Orange = new Color(1, 165 / 255f, 0); + public static readonly Color Orange = new Color(255, 165, 0, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FF4500. + /// Represents a matching the W3C definition that has an hex value of #FF4500. /// - public static readonly Color OrangeRed = new Color(1, 69 / 255f, 0); + public static readonly Color OrangeRed = new Color(255, 69, 0, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #DA70D6. + /// Represents a matching the W3C definition that has an hex value of #DA70D6. /// - public static readonly Color Orchid = new Color(218 / 255f, 112 / 255f, 214 / 255f); + public static readonly Color Orchid = new Color(218, 112, 214, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #EEE8AA. + /// Represents a matching the W3C definition that has an hex value of #EEE8AA. /// - public static readonly Color PaleGoldenrod = new Color(238 / 255f, 232 / 255f, 170 / 255f); + public static readonly Color PaleGoldenrod = new Color(238, 232, 170, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #98FB98. + /// Represents a matching the W3C definition that has an hex value of #98FB98. /// - public static readonly Color PaleGreen = new Color(152 / 255f, 251 / 255f, 152 / 255f); + public static readonly Color PaleGreen = new Color(152, 251, 152, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #AFEEEE. + /// Represents a matching the W3C definition that has an hex value of #AFEEEE. /// - public static readonly Color PaleTurquoise = new Color(175 / 255f, 238 / 255f, 238 / 255f); + public static readonly Color PaleTurquoise = new Color(175, 238, 238, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #DB7093. + /// Represents a matching the W3C definition that has an hex value of #DB7093. /// - public static readonly Color PaleVioletRed = new Color(219 / 255f, 112 / 255f, 147 / 255f); + public static readonly Color PaleVioletRed = new Color(219, 112, 147, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFEFD5. + /// Represents a matching the W3C definition that has an hex value of #FFEFD5. /// - public static readonly Color PapayaWhip = new Color(1, 239 / 255f, 213 / 255f); + public static readonly Color PapayaWhip = new Color(255, 239, 213, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFDAB9. + /// Represents a matching the W3C definition that has an hex value of #FFDAB9. /// - public static readonly Color PeachPuff = new Color(1, 218 / 255f, 185 / 255f); + public static readonly Color PeachPuff = new Color(255, 218, 185, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #CD853F. + /// Represents a matching the W3C definition that has an hex value of #CD853F. /// - public static readonly Color Peru = new Color(205 / 255f, 133 / 255f, 63 / 255f); + public static readonly Color Peru = new Color(205, 133, 63, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFC0CB. + /// Represents a matching the W3C definition that has an hex value of #FFC0CB. /// - public static readonly Color Pink = new Color(1, 192 / 255f, 203 / 255f); + public static readonly Color Pink = new Color(255, 192, 203, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #DDA0DD. + /// Represents a matching the W3C definition that has an hex value of #DDA0DD. /// - public static readonly Color Plum = new Color(221 / 255f, 160 / 255f, 221 / 255f); + public static readonly Color Plum = new Color(221, 160, 221, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #B0E0E6. + /// Represents a matching the W3C definition that has an hex value of #B0E0E6. /// - public static readonly Color PowderBlue = new Color(176 / 255f, 224 / 255f, 230 / 255f); + public static readonly Color PowderBlue = new Color(176, 224, 230, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #800080. + /// Represents a matching the W3C definition that has an hex value of #800080. /// - public static readonly Color Purple = new Color(128 / 255f, 0, 128 / 255f); + public static readonly Color Purple = new Color(128, 0, 128, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #663399. + /// Represents a matching the W3C definition that has an hex value of #0. /// - public static readonly Color RebeccaPurple = new Color(102 / 255f, 51 / 255f, 153 / 255f); + public static readonly Color RebeccaPurple = new Color(102, 51, 153, 255); + /// - /// Represents a matching the W3C definition that has a hex triplet value of #FF0000. + /// Represents a matching the W3C definition that has an hex value of #FF0000. /// - public static readonly Color Red = new Color(1, 0, 0); + public static readonly Color Red = new Color(255, 0, 0, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #BC8F8F. + /// Represents a matching the W3C definition that has an hex value of #BC8F8F. /// - public static readonly Color RosyBrown = new Color(188 / 255f, 143 / 255f, 143 / 255f); + public static readonly Color RosyBrown = new Color(188, 143, 143, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #4169E1. + /// Represents a matching the W3C definition that has an hex value of #4169E1. /// - public static readonly Color RoyalBlue = new Color(65 / 255f, 105 / 255f, 225 / 255f); + public static readonly Color RoyalBlue = new Color(65, 105, 225, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #8B4513. + /// Represents a matching the W3C definition that has an hex value of #8B4513. /// - public static readonly Color SaddleBrown = new Color(139 / 255f, 69 / 255f, 19 / 255f); + public static readonly Color SaddleBrown = new Color(139, 69, 19, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FA8072. + /// Represents a matching the W3C definition that has an hex value of #FA8072. /// - public static readonly Color Salmon = new Color(250 / 255f, 128 / 255f, 114 / 255f); + public static readonly Color Salmon = new Color(250, 128, 114, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #F4A460. + /// Represents a matching the W3C definition that has an hex value of #F4A460. /// - public static readonly Color SandyBrown = new Color(244 / 255f, 164 / 255f, 96 / 255f); + public static readonly Color SandyBrown = new Color(244, 164, 96, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #2E8B57. + /// Represents a matching the W3C definition that has an hex value of #2E8B57. /// - public static readonly Color SeaGreen = new Color(46 / 255f, 139 / 255f, 87 / 255f); + public static readonly Color SeaGreen = new Color(46, 139, 87, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFF5EE. + /// Represents a matching the W3C definition that has an hex value of #FFF5EE. /// - public static readonly Color SeaShell = new Color(1, 245 / 255f, 238 / 255f); + public static readonly Color SeaShell = new Color(255, 245, 238, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #A0522D. + /// Represents a matching the W3C definition that has an hex value of #A0522D. /// - public static readonly Color Sienna = new Color(160 / 255f, 82 / 255f, 45 / 255f); + public static readonly Color Sienna = new Color(160, 82, 45, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #C0C0C0. + /// Represents a matching the W3C definition that has an hex value of #C0C0C0. /// - public static readonly Color Silver = new Color(192 / 255f, 192 / 255f, 192 / 255f); + public static readonly Color Silver = new Color(192, 192, 192, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #87CEEB. + /// Represents a matching the W3C definition that has an hex value of #87CEEB. /// - public static readonly Color SkyBlue = new Color(135 / 255f, 206 / 255f, 235 / 255f); + public static readonly Color SkyBlue = new Color(135, 206, 235, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #6A5ACD. + /// Represents a matching the W3C definition that has an hex value of #6A5ACD. /// - public static readonly Color SlateBlue = new Color(106 / 255f, 90 / 255f, 205 / 255f); + public static readonly Color SlateBlue = new Color(106, 90, 205, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #708090. + /// Represents a matching the W3C definition that has an hex value of #708090. /// - public static readonly Color SlateGray = new Color(112 / 255f, 128 / 255f, 144 / 255f); + public static readonly Color SlateGray = new Color(112, 128, 144, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFFAFA. + /// Represents a matching the W3C definition that has an hex value of #FFFAFA. /// - public static readonly Color Snow = new Color(1, 250 / 255f, 250 / 255f); + public static readonly Color Snow = new Color(255, 250, 250, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #00FF7F. + /// Represents a matching the W3C definition that has an hex value of #00FF7F. /// - public static readonly Color SpringGreen = new Color(0, 1, 127 / 255f); + public static readonly Color SpringGreen = new Color(0, 255, 127, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #4682B4. + /// Represents a matching the W3C definition that has an hex value of #4682B4. /// - public static readonly Color SteelBlue = new Color(70 / 255f, 130 / 255f, 180 / 255f); + public static readonly Color SteelBlue = new Color(70, 130, 180, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #D2B48C. + /// Represents a matching the W3C definition that has an hex value of #D2B48C. /// - public static readonly Color Tan = new Color(210 / 255f, 180 / 255f, 140 / 255f); + public static readonly Color Tan = new Color(210, 180, 140, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #008080. + /// Represents a matching the W3C definition that has an hex value of #008080. /// - public static readonly Color Teal = new Color(0, 128 / 255f, 128 / 255f); + public static readonly Color Teal = new Color(0, 128, 128, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #D8BFD8. + /// Represents a matching the W3C definition that has an hex value of #D8BFD8. /// - public static readonly Color Thistle = new Color(216 / 255f, 191 / 255f, 216 / 255f); + public static readonly Color Thistle = new Color(216, 191, 216, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FF6347. + /// Represents a matching the W3C definition that has an hex value of #FF6347. /// - public static readonly Color Tomato = new Color(1, 99 / 255f, 71 / 255f); + public static readonly Color Tomato = new Color(255, 99, 71, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #40E0D0. + /// Represents a matching the W3C definition that has an hex value of #FFFFFF. /// - public static readonly Color Turquoise = new Color(64 / 255f, 224 / 255f, 208 / 255f); + public static readonly Color Transparent = new Color(255, 255, 255, 0); /// - /// Represents a matching the W3C definition that has a hex triplet value of #EE82EE. + /// Represents a matching the W3C definition that has an hex value of #40E0D0. /// - public static readonly Color Violet = new Color(238 / 255f, 130 / 255f, 238 / 255f); + public static readonly Color Turquoise = new Color(64, 224, 208, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #F5DEB3. + /// Represents a matching the W3C definition that has an hex value of #EE82EE. /// - public static readonly Color Wheat = new Color(245 / 255f, 222 / 255f, 179 / 255f); + public static readonly Color Violet = new Color(238, 130, 238, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFFFFF. + /// Represents a matching the W3C definition that has an hex value of #F5DEB3. /// - public static readonly Color White = new Color(1, 1, 1); + public static readonly Color Wheat = new Color(245, 222, 179, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #F5F5F5. + /// Represents a matching the W3C definition that has an hex value of #FFFFFF. /// - public static readonly Color WhiteSmoke = new Color(245 / 255f, 245 / 255f, 245 / 255f); + public static readonly Color White = new Color(255, 255, 255, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFFF00. + /// Represents a matching the W3C definition that has an hex value of #F5F5F5. /// - public static readonly Color Yellow = new Color(1, 1, 0); + public static readonly Color WhiteSmoke = new Color(245, 245, 245, 255); /// - /// Represents a matching the W3C definition that has a hex triplet value of #9ACD32. + /// Represents a matching the W3C definition that has an hex value of #FFFF00. /// - public static readonly Color YellowGreen = new Color(154 / 255f, 205 / 255f, 50 / 255f); + public static readonly Color Yellow = new Color(255, 255, 0, 255); /// - /// Represents a system-defined that has an ARGB value of #00FFFFFF. + /// Represents a matching the W3C definition that has an hex value of #9ACD32. /// - public static readonly Color Transparent = new Color(1, 1, 1, 0); + public static readonly Color YellowGreen = new Color(154, 205, 50, 255); } -} +} \ No newline at end of file diff --git a/src/ImageProcessorCore/Colors/ColorTransforms.cs b/src/ImageProcessorCore/Colors/ColorTransforms.cs index 2a0da9407f..cc05f6bb49 100644 --- a/src/ImageProcessorCore/Colors/ColorTransforms.cs +++ b/src/ImageProcessorCore/Colors/ColorTransforms.cs @@ -6,10 +6,11 @@ namespace ImageProcessorCore { using System; + using System.Numerics; /// - /// Represents a four-component color using red, green, blue, and alpha data. - /// Each component is stored in premultiplied format multiplied by the alpha component. + /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 255. + /// The color components are stored in red, green, blue, and alpha order. /// /// /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, @@ -43,31 +44,31 @@ namespace ImageProcessorCore return source; } - return new Color(source.backingVector * destination.backingVector); + // TODO: This will use less memory than using Vector4 + // but we should test speed vs memory to see which is best balance. + byte r = (byte)(source.R * destination.R).Clamp(0, 255); + byte g = (byte)(source.G * destination.G).Clamp(0, 255); + byte b = (byte)(source.B * destination.B).Clamp(0, 255); + byte a = (byte)(source.A * destination.A).Clamp(0, 255); + + return new Color(r, g, b, a); } /// - /// Linearly interpolates from one color to another based on the given amount. + /// Linearly interpolates from one color to another based on the given weighting. /// - /// The first color value. - /// The second color value. + /// The first color value. + /// The second color value. /// - /// The weight value. At amount = 0, "from" is returned, at amount = 1, "to" is returned. + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. /// /// /// The /// - public static Color Lerp(Color source, Color destination, float amount) + public static Color Lerp(Color from, Color to, float amount) { - amount = amount.Clamp(0f, 1f); - - if (Math.Abs(source.A - 1) < Epsilon && Math.Abs(destination.A - 1) < Epsilon) - { - return source + ((destination - source) * amount); - } - - // Premultiplied. - return (source * (1 - amount)) + destination; + return new Color(Vector4.Lerp(from.ToVector4(), to.ToVector4(), amount)); } } } diff --git a/src/ImageProcessorCore/Colors/ColorspaceTransforms.cs b/src/ImageProcessorCore/Colors/ColorspaceTransforms.cs index 50d0af3150..db7e7536fc 100644 --- a/src/ImageProcessorCore/Colors/ColorspaceTransforms.cs +++ b/src/ImageProcessorCore/Colors/ColorspaceTransforms.cs @@ -6,10 +6,11 @@ namespace ImageProcessorCore { using System; + using System.Numerics; /// - /// Represents a four-component color using red, green, blue, and alpha data. - /// Each component is stored in premultiplied format multiplied by the alpha component. + /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 255. + /// The color components are stored in red, green, blue, and alpha order. /// /// /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, @@ -17,6 +18,11 @@ namespace ImageProcessorCore /// public partial struct Color { + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.001F; + /// /// Allows the implicit conversion of an instance of to a /// . @@ -27,7 +33,7 @@ namespace ImageProcessorCore /// public static implicit operator Color(Bgra32 color) { - return new Color(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f); + return new Color(color.R, color.G, color.B, color.A); } /// @@ -43,7 +49,7 @@ namespace ImageProcessorCore float r = (1 - cmykColor.C) * (1 - cmykColor.K); float g = (1 - cmykColor.M) * (1 - cmykColor.K); float b = (1 - cmykColor.Y) * (1 - cmykColor.K); - return new Color(r, g, b); + return new Color(r, g, b, 1); } /// @@ -60,11 +66,11 @@ namespace ImageProcessorCore float cb = color.Cb - 128; float cr = color.Cr - 128; - float r = (float)(y + (1.402 * cr)).Clamp(0, 255) / 255f; - float g = (float)(y - (0.34414 * cb) - (0.71414 * cr)).Clamp(0, 255) / 255f; - float b = (float)(y + (1.772 * cb)).Clamp(0, 255) / 255f; + byte r = (byte)(y + (1.402 * cr)).Clamp(0, 255); + byte g = (byte)(y - (0.34414 * cb) - (0.71414 * cr)).Clamp(0, 255); + byte b = (byte)(y + (1.772 * cb)).Clamp(0, 255); - return new Color(r, g, b); + return new Color(r, g, b, 255); } /// @@ -86,7 +92,8 @@ namespace ImageProcessorCore float g = (x * -0.9689F) + (y * 1.8758F) + (z * 0.0415F); float b = (x * 0.0557F) + (y * -0.2040F) + (z * 1.0570F); - return Color.Compress(new Color(r, g, b)); + Vector4 vector = new Vector4(r, g, b, 1).Compress(); + return new Color(vector); } /// @@ -111,9 +118,9 @@ namespace ImageProcessorCore int i = (int)Math.Truncate(h); float f = h - i; - float p = v * (1.0f - s); - float q = v * (1.0f - (s * f)); - float t = v * (1.0f - (s * (1.0f - f))); + float p = v * (1.0F - s); + float q = v * (1.0F - (s * f)); + float t = v * (1.0F - (s * (1.0F - f))); float r, g, b; switch (i) @@ -155,7 +162,7 @@ namespace ImageProcessorCore break; } - return new Color(r, g, b); + return new Color(r, g, b, 1); } /// @@ -168,7 +175,7 @@ namespace ImageProcessorCore /// public static implicit operator Color(Hsl color) { - float rangedH = color.H / 360f; + float rangedH = color.H / 360F; float r = 0; float g = 0; float b = 0; @@ -192,7 +199,7 @@ namespace ImageProcessorCore } } - return new Color(r, g, b); + return new Color(r, g, b, 1); } /// @@ -226,7 +233,7 @@ namespace ImageProcessorCore float g = (x * -0.9689F) + (y * 1.8758F) + (z * 0.0415F); float b = (x * 0.0557F) + (y * -0.2040F) + (z * 1.0570F); - return Color.Compress(new Color(r, g, b)); + return new Color(new Vector4(r, g, b, 1F).Compress()); } /// diff --git a/src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs b/src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs index e31d693bb8..76a37a7a7b 100644 --- a/src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs +++ b/src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs @@ -80,8 +80,7 @@ namespace ImageProcessorCore /// public static implicit operator Bgra32(Color color) { - color = color.Limited * 255f; - return new Bgra32((byte)color.B, (byte)color.G, (byte)color.R, (byte)color.A); + return new Bgra32(color.B, color.G, color.R, color.A); } /// @@ -158,7 +157,7 @@ namespace ImageProcessorCore /// Returns the hash code for this instance. /// /// - /// The instance of to return the hash code for. + /// The instance of to return the hash code for. /// /// /// A 32-bit signed integer that is the hash code for this instance. diff --git a/src/ImageProcessorCore/Colors/Colorspaces/CieLab.cs b/src/ImageProcessorCore/Colors/Colorspaces/CieLab.cs index cce92601ad..2480262e00 100644 --- a/src/ImageProcessorCore/Colors/Colorspaces/CieLab.cs +++ b/src/ImageProcessorCore/Colors/Colorspaces/CieLab.cs @@ -71,7 +71,7 @@ namespace ImageProcessorCore /// . /// /// - /// The instance of to convert. + /// The instance of to convert. /// /// /// An instance of . @@ -79,11 +79,10 @@ namespace ImageProcessorCore public static implicit operator CieLab(Color color) { // First convert to CIE XYZ - color = Color.Expand(color); - - float x = (color.R * 0.4124F) + (color.G * 0.3576F) + (color.B * 0.1805F); - float y = (color.R * 0.2126F) + (color.G * 0.7152F) + (color.B * 0.0722F); - float z = (color.R * 0.0193F) + (color.G * 0.1192F) + (color.B * 0.9505F); + Vector4 vector = color.ToVector4().Expand(); + float x = (vector.X * 0.4124F) + (vector.Y * 0.3576F) + (vector.Z * 0.1805F); + float y = (vector.X * 0.2126F) + (vector.Y * 0.7152F) + (vector.Z * 0.0722F); + float z = (vector.X * 0.0193F) + (vector.Y * 0.1192F) + (vector.Z * 0.9505F); // Now to LAB x /= 0.95047F; diff --git a/src/ImageProcessorCore/Colors/Colorspaces/CieXyz.cs b/src/ImageProcessorCore/Colors/Colorspaces/CieXyz.cs index 8f41c8abbe..528e4faf34 100644 --- a/src/ImageProcessorCore/Colors/Colorspaces/CieXyz.cs +++ b/src/ImageProcessorCore/Colors/Colorspaces/CieXyz.cs @@ -79,11 +79,11 @@ namespace ImageProcessorCore /// public static implicit operator CieXyz(Color color) { - color = Color.Expand(color); + Vector4 vector = color.ToVector4().Expand(); - float x = (color.R * 0.4124F) + (color.G * 0.3576F) + (color.B * 0.1805F); - float y = (color.R * 0.2126F) + (color.G * 0.7152F) + (color.B * 0.0722F); - float z = (color.R * 0.0193F) + (color.G * 0.1192F) + (color.B * 0.9505F); + float x = (vector.X * 0.4124F) + (vector.Y * 0.3576F) + (vector.Z * 0.1805F); + float y = (vector.X * 0.2126F) + (vector.Y * 0.7152F) + (vector.Z * 0.0722F); + float z = (vector.X * 0.0193F) + (vector.Y * 0.1192F) + (vector.Z * 0.9505F); x *= 100F; y *= 100F; diff --git a/src/ImageProcessorCore/Colors/Colorspaces/Cmyk.cs b/src/ImageProcessorCore/Colors/Colorspaces/Cmyk.cs index b343288a68..6cb717e624 100644 --- a/src/ImageProcessorCore/Colors/Colorspaces/Cmyk.cs +++ b/src/ImageProcessorCore/Colors/Colorspaces/Cmyk.cs @@ -84,11 +84,9 @@ namespace ImageProcessorCore /// public static implicit operator Cmyk(Color color) { - color = color.Limited; - - float c = 1f - color.R; - float m = 1f - color.G; - float y = 1f - color.B; + float c = 1f - color.R / 255F; + float m = 1f - color.G / 255F; + float y = 1f - color.B / 255F; float k = Math.Min(c, Math.Min(m, y)); diff --git a/src/ImageProcessorCore/Colors/Colorspaces/Hsl.cs b/src/ImageProcessorCore/Colors/Colorspaces/Hsl.cs index e6eee42e35..0e8812a257 100644 --- a/src/ImageProcessorCore/Colors/Colorspaces/Hsl.cs +++ b/src/ImageProcessorCore/Colors/Colorspaces/Hsl.cs @@ -22,7 +22,7 @@ namespace ImageProcessorCore /// /// The epsilon for comparing floating point numbers. /// - private const float Epsilon = 0.001f; + private const float Epsilon = 0.001F; /// /// The backing vector for SIMD support. @@ -74,10 +74,9 @@ namespace ImageProcessorCore /// public static implicit operator Hsl(Color color) { - color = Color.ToNonPremultiplied(color.Limited); - float r = color.R; - float g = color.G; - float b = color.B; + float r = color.R / 255F; + float g = color.G / 255F; + float b = color.B / 255F; float max = Math.Max(r, Math.Max(g, b)); float min = Math.Min(r, Math.Min(g, b)); @@ -114,7 +113,8 @@ namespace ImageProcessorCore { s = chroma / (max + min); } - else { + else + { s = chroma / (2 - chroma); } diff --git a/src/ImageProcessorCore/Colors/Colorspaces/Hsv.cs b/src/ImageProcessorCore/Colors/Colorspaces/Hsv.cs index 914df51c97..0a22ac2731 100644 --- a/src/ImageProcessorCore/Colors/Colorspaces/Hsv.cs +++ b/src/ImageProcessorCore/Colors/Colorspaces/Hsv.cs @@ -22,7 +22,7 @@ namespace ImageProcessorCore /// /// The epsilon for comparing floating point numbers. /// - private const float Epsilon = 0.001f; + private const float Epsilon = 0.001F; /// /// The backing vector for SIMD support. @@ -74,10 +74,9 @@ namespace ImageProcessorCore /// public static implicit operator Hsv(Color color) { - color = Color.ToNonPremultiplied(color.Limited); - float r = color.R; - float g = color.G; - float b = color.B; + float r = color.R / 255F; + float g = color.G / 255F; + float b = color.B / 255F; float max = Math.Max(r, Math.Max(g, b)); float min = Math.Min(r, Math.Min(g, b)); diff --git a/src/ImageProcessorCore/Colors/IAlmostEquatable.cs b/src/ImageProcessorCore/Colors/Colorspaces/IAlmostEquatable.cs similarity index 100% rename from src/ImageProcessorCore/Colors/IAlmostEquatable.cs rename to src/ImageProcessorCore/Colors/Colorspaces/IAlmostEquatable.cs diff --git a/src/ImageProcessorCore/Colors/Colorspaces/YCbCr.cs b/src/ImageProcessorCore/Colors/Colorspaces/YCbCr.cs index 5c47c6c087..64560d7d0d 100644 --- a/src/ImageProcessorCore/Colors/Colorspaces/YCbCr.cs +++ b/src/ImageProcessorCore/Colors/Colorspaces/YCbCr.cs @@ -24,7 +24,7 @@ namespace ImageProcessorCore /// /// The epsilon for comparing floating point numbers. /// - private const float Epsilon = 0.001f; + private const float Epsilon = 0.001F; /// /// The backing vector for SIMD support. @@ -79,7 +79,6 @@ namespace ImageProcessorCore /// public static implicit operator YCbCr(Color color) { - color = Color.ToNonPremultiplied(color.Limited) * 255f; float r = color.R; float g = color.G; float b = color.B; @@ -173,7 +172,7 @@ namespace ImageProcessorCore /// Returns the hash code for this instance. /// /// - /// The instance of to return the hash code for. + /// The instance of to return the hash code for. /// /// /// A 32-bit signed integer that is the hash code for this instance. diff --git a/src/ImageProcessorCore/Colors/PackedVector/IPackedVector.cs b/src/ImageProcessorCore/Colors/PackedVector/IPackedVector.cs new file mode 100644 index 0000000000..99cd70d748 --- /dev/null +++ b/src/ImageProcessorCore/Colors/PackedVector/IPackedVector.cs @@ -0,0 +1,63 @@ +// +// 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 packed format. long, float. + public interface IPackedVector : IPackedVector + where TP : struct + { + /// + /// Gets the packed representation of the value. + /// Typically packed in least to greatest significance order. + /// + /// + /// The . + /// + TP PackedValue(); + } + + /// + /// 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. + /// Red -> Green -> Blue -> Alpha + /// + /// The . + byte[] ToBytes(); + } +} diff --git a/src/ImageProcessorCore/Colors/RgbaComponent.cs b/src/ImageProcessorCore/Colors/RgbaComponent.cs index a4b668cb62..946c47a377 100644 --- a/src/ImageProcessorCore/Colors/RgbaComponent.cs +++ b/src/ImageProcessorCore/Colors/RgbaComponent.cs @@ -11,9 +11,9 @@ namespace ImageProcessorCore public enum RgbaComponent { /// - /// The blue component. + /// The red component. /// - B = 0, + R = 0, /// /// The green component. @@ -21,9 +21,9 @@ namespace ImageProcessorCore G = 1, /// - /// The red component. + /// The blue component. /// - R = 2, + B = 2, /// /// The alpha component. diff --git a/src/ImageProcessorCore/Common/Extensions/Vector4Extensions.cs b/src/ImageProcessorCore/Common/Extensions/Vector4Extensions.cs new file mode 100644 index 0000000000..97d2b66d70 --- /dev/null +++ b/src/ImageProcessorCore/Common/Extensions/Vector4Extensions.cs @@ -0,0 +1,91 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Extension methods for the struct. + /// + public static class Vector4Extensions + { + /// + /// Compresses a linear color signal to its sRGB equivalent. + /// + /// + /// + /// The whose signal to compress. + /// The . + public static Vector4 Compress(this Vector4 linear) + { + // TODO: Is there a faster way to do this? + float r = Compress(linear.X); + float g = Compress(linear.Y); + float b = Compress(linear.Z); + + return new Vector4(r, g, b, linear.W); + } + + /// + /// Expands an sRGB color signal to its linear equivalent. + /// + /// + /// + /// The whose signal to expand. + /// The . + public static Vector4 Expand(this Vector4 gamma) + { + // TODO: Is there a faster way to do this? + float r = Expand(gamma.X); + float g = Expand(gamma.Y); + float b = Expand(gamma.Z); + + return new Vector4(r, g, b, gamma.W); + } + + /// + /// Gets the compressed sRGB value from an linear signal. + /// + /// + /// + /// The signal value to compress. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float Compress(float signal) + { + if (signal <= 0.0031308f) + { + return signal * 12.92f; + } + + return (1.055f * (float)Math.Pow(signal, 0.41666666f)) - 0.055f; + } + + /// + /// Gets the expanded linear value from an sRGB signal. + /// + /// + /// + /// The signal value to expand. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float Expand(float signal) + { + if (signal <= 0.04045f) + { + return signal / 12.92f; + } + + return (float)Math.Pow((signal + 0.055f) / 1.055f, 2.4f); + } + } +} diff --git a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs index 051b75f70f..9b890d1c0b 100644 --- a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs +++ b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs @@ -61,25 +61,25 @@ namespace ImageProcessorCore { float temp; - if (x < 0) + if (x < 0F) { x = -x; } temp = x * x; - if (x < 1) + if (x < 1F) { x = ((12 - (9 * b) - (6 * c)) * (x * temp)) + ((-18 + (12 * b) + (6 * c)) * temp) + (6 - (2 * b)); - return x / 6; + return x / 6F; } - if (x < 2) + if (x < 2F) { x = ((-b - (6 * c)) * (x * temp)) + (((6 * b) + (30 * c)) * temp) + (((-12 * b) - (48 * c)) * x) + ((8 * b) + (24 * c)); - return x / 6; + return x / 6F; } - return 0; + return 0F; } /// @@ -91,7 +91,7 @@ namespace ImageProcessorCore /// public static float SinC(float x) { - const float Epsilon = .00001f; + const float Epsilon = .00001F; if (Math.Abs(x) > Epsilon) { @@ -156,13 +156,17 @@ namespace ImageProcessorCore /// Finds the bounding rectangle based on the first instance of any color component other /// than the given one. /// + /// The pixel format. + /// The packed format. long, float. /// 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) + public static Rectangle GetFilteredBoundingRectangle(ImageBase bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B) + where T : IPackedVector + where TP : struct { const float Epsilon = .00001f; int width = bitmap.Width; @@ -170,29 +174,29 @@ namespace ImageProcessorCore Point topLeft = new Point(); Point bottomRight = new Point(); - Func delegateFunc; + Func, int, int, float, bool> 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; + delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].ToBytes()[0] - b) > Epsilon; break; case RgbaComponent.G: - delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].G - b) > Epsilon; + delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].ToBytes()[1] - b) > Epsilon; break; - case RgbaComponent.A: - delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].A - b) > Epsilon; + case RgbaComponent.B: + delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].ToBytes()[2] - b) > Epsilon; break; default: - delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].B - b) > Epsilon; + delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].ToBytes()[3] - b) > Epsilon; break; } - Func getMinY = pixels => + Func, int> getMinY = pixels => { for (int y = 0; y < height; y++) { @@ -208,7 +212,7 @@ namespace ImageProcessorCore return 0; }; - Func getMaxY = pixels => + Func, int> getMaxY = pixels => { for (int y = height - 1; y > -1; y--) { @@ -224,7 +228,7 @@ namespace ImageProcessorCore return height; }; - Func getMinX = pixels => + Func, int> getMinX = pixels => { for (int x = 0; x < width; x++) { @@ -240,7 +244,7 @@ namespace ImageProcessorCore return 0; }; - Func getMaxX = pixels => + Func, int> getMaxX = pixels => { for (int x = width - 1; x > -1; x--) { @@ -256,7 +260,7 @@ namespace ImageProcessorCore return height; }; - using (PixelAccessor bitmapPixels = bitmap.Lock()) + using (IPixelAccessor bitmapPixels = bitmap.Lock()) { topLeft.Y = getMinY(bitmapPixels); topLeft.X = getMinX(bitmapPixels); @@ -276,11 +280,11 @@ namespace ImageProcessorCore /// . private static float Clean(float x) { - const float Epsilon = .00001f; + const float Epsilon = .00001F; if (Math.Abs(x) < Epsilon) { - return 0f; + return 0F; } return x; diff --git a/src/ImageProcessorCore/Filters/Alpha.cs b/src/ImageProcessorCore/Filters/Alpha.cs index 3532bb675d..545639afa0 100644 --- a/src/ImageProcessorCore/Filters/Alpha.cs +++ b/src/ImageProcessorCore/Filters/Alpha.cs @@ -1,7 +1,7 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { @@ -15,11 +15,15 @@ namespace ImageProcessorCore /// /// Alters the alpha component of the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The new opacity of the image. Must be between 0 and 100. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Alpha(this Image source, int percent, ProgressEventHandler progressHandler = null) + /// The . + public static Image Alpha(this Image source, int percent, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { return Alpha(source, percent, source.Bounds, progressHandler); } @@ -27,6 +31,8 @@ namespace ImageProcessorCore /// /// Alters the alpha component of the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The new opacity of the image. Must be between 0 and 100. /// @@ -34,9 +40,11 @@ namespace ImageProcessorCore /// /// A delegate which is called as progress is made processing the image. /// The . - public static Image Alpha(this Image source, int percent, Rectangle rectangle, ProgressEventHandler progressHandler = null) + public static Image Alpha(this Image source, int percent, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - AlphaProcessor processor = new AlphaProcessor(percent); + AlphaProcessor processor = new AlphaProcessor(percent); processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Filters/BackgroundColor.cs b/src/ImageProcessorCore/Filters/BackgroundColor.cs index 98709cb32c..44f46598b5 100644 --- a/src/ImageProcessorCore/Filters/BackgroundColor.cs +++ b/src/ImageProcessorCore/Filters/BackgroundColor.cs @@ -1,27 +1,31 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// - /// Combines the given image together with the current one by blending their pixels. + /// Replaces the background color of image with the given one. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The color to set as the background. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image BackgroundColor(this Image source, Color color, ProgressEventHandler progressHandler = null) + /// The . + public static Image BackgroundColor(this Image source, T color, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - BackgroundColorProcessor processor = new BackgroundColorProcessor(color); + BackgroundColorProcessor processor = new BackgroundColorProcessor(color); processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Filters/BinaryThreshold.cs b/src/ImageProcessorCore/Filters/BinaryThreshold.cs new file mode 100644 index 0000000000..5d244ead4d --- /dev/null +++ b/src/ImageProcessorCore/Filters/BinaryThreshold.cs @@ -0,0 +1,60 @@ +// +// 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 + { + /// + /// Applies binerization to the image splitting the pixels at the given threshold. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image this method extends. + /// The threshold to apply binerization of the image. Must be between 0 and 1. + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image BinaryThreshold(this Image source, float threshold, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + return BinaryThreshold(source, threshold, source.Bounds, progressHandler); + } + + /// + /// Applies binerization to the image splitting the pixels at the given threshold. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image this method extends. + /// The threshold to apply binerization of the image. Must be between 0 and 1. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image BinaryThreshold(this Image source, float threshold, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + BinaryThresholdProcessor processor = new BinaryThresholdProcessor(threshold); + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/BlackWhite.cs b/src/ImageProcessorCore/Filters/BlackWhite.cs index 98c3b7744a..8799cfe64f 100644 --- a/src/ImageProcessorCore/Filters/BlackWhite.cs +++ b/src/ImageProcessorCore/Filters/BlackWhite.cs @@ -1,24 +1,28 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Applies black and white toning to the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image BlackWhite(this Image source, ProgressEventHandler progressHandler = null) + /// The . + public static Image BlackWhite(this Image source, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { return BlackWhite(source, source.Bounds, progressHandler); } @@ -26,15 +30,19 @@ namespace ImageProcessorCore /// /// Applies black and white toning to the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// /// The structure that specifies the portion of the image object to alter. /// /// A delegate which is called as progress is made processing the image. - /// The . - public static Image BlackWhite(this Image source, Rectangle rectangle, ProgressEventHandler progressHandler = null) + /// The . + public static Image BlackWhite(this Image source, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - BlackWhiteProcessor processor = new BlackWhiteProcessor(); + BlackWhiteProcessor processor = new BlackWhiteProcessor(); processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Filters/Blend.cs b/src/ImageProcessorCore/Filters/Blend.cs index 77a94e3fa3..96f8f60cda 100644 --- a/src/ImageProcessorCore/Filters/Blend.cs +++ b/src/ImageProcessorCore/Filters/Blend.cs @@ -16,14 +16,15 @@ namespace ImageProcessorCore /// Combines the given image together with the current one by blending their pixels. /// /// The image this method extends. - /// - /// The image to blend with the currently processing image. - /// Disposal of this image is the responsibility of the developer. - /// + /// The image to blend with the currently processing image. + /// The pixel format. + /// The packed format. long, float. /// The opacity of the image image to blend. Must be between 0 and 100. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Blend(this Image source, ImageBase image, int percent = 50, ProgressEventHandler progressHandler = null) + /// The . + public static Image Blend(this Image source, ImageBase image, int percent = 50, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { return Blend(source, image, percent, source.Bounds, progressHandler); } @@ -32,19 +33,20 @@ namespace ImageProcessorCore /// Combines the given image together with the current one by blending their pixels. /// /// The image this method extends. - /// - /// The image to blend with the currently processing image. - /// Disposal of this image is the responsibility of the developer. - /// + /// The image to blend with the currently processing image. + /// The pixel format. + /// The packed format. long, float. /// The opacity of the image image to blend. Must be between 0 and 100. /// /// The structure that specifies the portion of the image object to alter. /// /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Blend(this Image source, ImageBase image, int percent, Rectangle rectangle, ProgressEventHandler progressHandler = null) + /// The . + public static Image Blend(this Image source, ImageBase image, int percent, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - BlendProcessor processor = new BlendProcessor(image, percent); + BlendProcessor processor = new BlendProcessor(image, percent); processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Filters/BoxBlur.cs b/src/ImageProcessorCore/Filters/BoxBlur.cs index 474f0d6e77..e1970228f3 100644 --- a/src/ImageProcessorCore/Filters/BoxBlur.cs +++ b/src/ImageProcessorCore/Filters/BoxBlur.cs @@ -1,25 +1,29 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Applies a box blur to the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The 'radius' value representing the size of the area to sample. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image BoxBlur(this Image source, int radius = 7, ProgressEventHandler progressHandler = null) + /// The . + public static Image BoxBlur(this Image source, int radius = 7, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { return BoxBlur(source, radius, source.Bounds, progressHandler); } @@ -27,16 +31,20 @@ namespace ImageProcessorCore /// /// Applies a box blur to the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The 'radius' value representing the size of the area to sample. /// /// The structure that specifies the portion of the image object to alter. /// /// A delegate which is called as progress is made processing the image. - /// The . - public static Image BoxBlur(this Image source, int radius, Rectangle rectangle, ProgressEventHandler progressHandler = null) + /// The . + public static Image BoxBlur(this Image source, int radius, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - BoxBlurProcessor processor = new BoxBlurProcessor(radius); + BoxBlurProcessor processor = new BoxBlurProcessor(radius); processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Filters/Brightness.cs b/src/ImageProcessorCore/Filters/Brightness.cs index 52944c8010..fb29ff0d9c 100644 --- a/src/ImageProcessorCore/Filters/Brightness.cs +++ b/src/ImageProcessorCore/Filters/Brightness.cs @@ -1,25 +1,29 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Alters the brightness component of the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The new brightness of the image. Must be between -100 and 100. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Brightness(this Image source, int amount, ProgressEventHandler progressHandler = null) + /// The . + public static Image Brightness(this Image source, int amount, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { return Brightness(source, amount, source.Bounds, progressHandler); } @@ -27,16 +31,20 @@ namespace ImageProcessorCore /// /// Alters the brightness component of the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The new brightness of the image. Must be between -100 and 100. /// /// The structure that specifies the portion of the image object to alter. /// /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Brightness(this Image source, int amount, Rectangle rectangle, ProgressEventHandler progressHandler = null) + /// The . + public static Image Brightness(this Image source, int amount, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - BrightnessProcessor processor = new BrightnessProcessor(amount); + BrightnessProcessor processor = new BrightnessProcessor(amount); processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Filters/ColorBlindness.cs b/src/ImageProcessorCore/Filters/ColorBlindness.cs index 78267b05e3..6ab553ba2d 100644 --- a/src/ImageProcessorCore/Filters/ColorBlindness.cs +++ b/src/ImageProcessorCore/Filters/ColorBlindness.cs @@ -1,25 +1,29 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Applies the given colorblindness simulator to the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The type of color blindness simulator to apply. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image ColorBlindness(this Image source, ColorBlindness colorBlindness, ProgressEventHandler progressHandler = null) + /// The . + public static Image ColorBlindness(this Image source, ColorBlindness colorBlindness, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { return ColorBlindness(source, colorBlindness, source.Bounds, progressHandler); } @@ -27,49 +31,53 @@ namespace ImageProcessorCore /// /// Applies the given colorblindness simulator to the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The type of color blindness simulator to apply. /// /// The structure that specifies the portion of the image object to alter. /// /// A delegate which is called as progress is made processing the image. - /// The . - public static Image ColorBlindness(this Image source, ColorBlindness colorBlindness, Rectangle rectangle, ProgressEventHandler progressHandler = null) + /// The . + public static Image ColorBlindness(this Image source, ColorBlindness colorBlindness, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - IImageProcessor processor; + IImageProcessor processor; switch (colorBlindness) { case ImageProcessorCore.ColorBlindness.Achromatomaly: - processor = new AchromatomalyProcessor(); + processor = new AchromatomalyProcessor(); break; case ImageProcessorCore.ColorBlindness.Achromatopsia: - processor = new AchromatopsiaProcessor(); + processor = new AchromatopsiaProcessor(); break; case ImageProcessorCore.ColorBlindness.Deuteranomaly: - processor = new DeuteranomalyProcessor(); + processor = new DeuteranomalyProcessor(); break; case ImageProcessorCore.ColorBlindness.Deuteranopia: - processor = new DeuteranopiaProcessor(); + processor = new DeuteranopiaProcessor(); break; case ImageProcessorCore.ColorBlindness.Protanomaly: - processor = new ProtanomalyProcessor(); + processor = new ProtanomalyProcessor(); break; case ImageProcessorCore.ColorBlindness.Protanopia: - processor = new ProtanopiaProcessor(); + processor = new ProtanopiaProcessor(); break; case ImageProcessorCore.ColorBlindness.Tritanomaly: - processor = new TritanomalyProcessor(); + processor = new TritanomalyProcessor(); break; default: - processor = new TritanopiaProcessor(); + processor = new TritanopiaProcessor(); break; } diff --git a/src/ImageProcessorCore/Filters/Contrast.cs b/src/ImageProcessorCore/Filters/Contrast.cs index cab12ca4e5..5025daf641 100644 --- a/src/ImageProcessorCore/Filters/Contrast.cs +++ b/src/ImageProcessorCore/Filters/Contrast.cs @@ -1,25 +1,29 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Alters the contrast component of the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The new contrast of the image. Must be between -100 and 100. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Contrast(this Image source, int amount, ProgressEventHandler progressHandler = null) + /// The . + public static Image Contrast(this Image source, int amount, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { return Contrast(source, amount, source.Bounds, progressHandler); } @@ -27,16 +31,20 @@ namespace ImageProcessorCore /// /// Alters the contrast component of the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The new contrast of the image. Must be between -100 and 100. /// /// The structure that specifies the portion of the image object to alter. /// /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Contrast(this Image source, int amount, Rectangle rectangle, ProgressEventHandler progressHandler = null) + /// The . + public static Image Contrast(this Image source, int amount, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - ContrastProcessor processor = new ContrastProcessor(amount); + ContrastProcessor processor = new ContrastProcessor(amount); processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Filters/DetectEdges.cs b/src/ImageProcessorCore/Filters/DetectEdges.cs index ac3a0282d5..33fa00d30d 100644 --- a/src/ImageProcessorCore/Filters/DetectEdges.cs +++ b/src/ImageProcessorCore/Filters/DetectEdges.cs @@ -1,37 +1,103 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Detects any edges within the image. Uses the filter - /// operating in greyscale mode. + /// operating in Grayscale mode. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image DetectEdges(this Image source, ProgressEventHandler progressHandler = null) + /// The . + public static Image DetectEdges(this Image source, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - return DetectEdges(source, source.Bounds, new SobelProcessor { Greyscale = true }, progressHandler); + return DetectEdges(source, source.Bounds, new SobelProcessor { Grayscale = true }, progressHandler); } /// /// Detects any edges within the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The filter for detecting edges. + /// Whether to convert the image to Grayscale first. Defaults to true. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image DetectEdges(this Image source, IEdgeDetectorFilter filter, ProgressEventHandler progressHandler = null) + /// The . + public static Image DetectEdges(this Image source, EdgeDetection filter, bool Grayscale = true, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + IEdgeDetectorFilter processor; + + switch (filter) + { + case EdgeDetection.Kayyali: + processor = new KayyaliProcessor { Grayscale = Grayscale }; + break; + + case EdgeDetection.Kirsch: + processor = new KirschProcessor { Grayscale = Grayscale }; + break; + + case EdgeDetection.Lapacian3X3: + processor = new Laplacian3X3Processor { Grayscale = Grayscale }; + break; + + case EdgeDetection.Lapacian5X5: + processor = new Laplacian5X5Processor { Grayscale = Grayscale }; + break; + + case EdgeDetection.LaplacianOfGaussian: + processor = new LaplacianOfGaussianProcessor { Grayscale = Grayscale }; + break; + + case EdgeDetection.Prewitt: + processor = new PrewittProcessor { Grayscale = Grayscale }; + break; + + case EdgeDetection.RobertsCross: + processor = new RobertsCrossProcessor { Grayscale = Grayscale }; + break; + + case EdgeDetection.Scharr: + processor = new ScharrProcessor { Grayscale = Grayscale }; + break; + + default: + processor = new ScharrProcessor { Grayscale = Grayscale }; + break; + } + + return DetectEdges(source, source.Bounds, processor, progressHandler); + } + + /// + /// Detects any edges within the image. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image this method extends. + /// The filter for detecting edges. + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image DetectEdges(this Image source, IEdgeDetectorFilter filter, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { return DetectEdges(source, source.Bounds, filter, progressHandler); } @@ -45,8 +111,10 @@ namespace ImageProcessorCore /// /// The filter for detecting edges. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image DetectEdges(this Image source, Rectangle rectangle, IEdgeDetectorFilter filter, ProgressEventHandler progressHandler = null) + /// The . + public static Image DetectEdges(this Image source, Rectangle rectangle, IEdgeDetectorFilter filter, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { filter.OnProgress += progressHandler; diff --git a/src/ImageProcessorCore/Filters/Greyscale.cs b/src/ImageProcessorCore/Filters/Grayscale.cs similarity index 50% rename from src/ImageProcessorCore/Filters/Greyscale.cs rename to src/ImageProcessorCore/Filters/Grayscale.cs index 786a935766..46ef445266 100644 --- a/src/ImageProcessorCore/Filters/Greyscale.cs +++ b/src/ImageProcessorCore/Filters/Grayscale.cs @@ -1,44 +1,52 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// - /// Applies greyscale toning to the image. + /// Applies Grayscale toning to the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The formula to apply to perform the operation. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Greyscale(this Image source, GreyscaleMode mode = GreyscaleMode.Bt709, ProgressEventHandler progressHandler = null) + /// The . + public static Image Grayscale(this Image source, GrayscaleMode mode = GrayscaleMode.Bt709, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - return Greyscale(source, source.Bounds, mode, progressHandler); + return Grayscale(source, source.Bounds, mode, progressHandler); } /// - /// Applies greyscale toning to the image. + /// Applies Grayscale toning to the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// /// The structure that specifies the portion of the image object to alter. /// /// The formula to apply to perform the operation. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Greyscale(this Image source, Rectangle rectangle, GreyscaleMode mode = GreyscaleMode.Bt709, ProgressEventHandler progressHandler = null) + /// The . + public static Image Grayscale(this Image source, Rectangle rectangle, GrayscaleMode mode = GrayscaleMode.Bt709, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - IImageProcessor processor = mode == GreyscaleMode.Bt709 - ? (IImageProcessor)new GreyscaleBt709Processor() - : new GreyscaleBt601Processor(); + IImageProcessor processor = mode == GrayscaleMode.Bt709 + ? (IImageProcessor)new GrayscaleBt709Processor() + : new GrayscaleBt601Processor(); processor.OnProgress += progressHandler; diff --git a/src/ImageProcessorCore/Filters/GuassianBlur.cs b/src/ImageProcessorCore/Filters/GuassianBlur.cs index 646c6bdc08..a57f43b2c2 100644 --- a/src/ImageProcessorCore/Filters/GuassianBlur.cs +++ b/src/ImageProcessorCore/Filters/GuassianBlur.cs @@ -1,25 +1,29 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Applies a Guassian blur to the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The 'sigma' value representing the weight of the blur. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image GuassianBlur(this Image source, float sigma = 3f, ProgressEventHandler progressHandler = null) + /// The . + public static Image GuassianBlur(this Image source, float sigma = 3f, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { return GuassianBlur(source, sigma, source.Bounds, progressHandler); } @@ -27,16 +31,20 @@ namespace ImageProcessorCore /// /// Applies a Guassian blur to the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The 'sigma' value representing the weight of the blur. /// /// The structure that specifies the portion of the image object to alter. /// /// A delegate which is called as progress is made processing the image. - /// The . - public static Image GuassianBlur(this Image source, float sigma, Rectangle rectangle, ProgressEventHandler progressHandler = null) + /// The . + public static Image GuassianBlur(this Image source, float sigma, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - GuassianBlurProcessor processor = new GuassianBlurProcessor(sigma); + GuassianBlurProcessor processor = new GuassianBlurProcessor(sigma); processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Filters/GuassianSharpen.cs b/src/ImageProcessorCore/Filters/GuassianSharpen.cs index 06993070d8..6fb888cdfc 100644 --- a/src/ImageProcessorCore/Filters/GuassianSharpen.cs +++ b/src/ImageProcessorCore/Filters/GuassianSharpen.cs @@ -1,25 +1,29 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Applies a Guassian sharpening filter to the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The 'sigma' value representing the weight of the blur. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image GuassianSharpen(this Image source, float sigma = 3f, ProgressEventHandler progressHandler = null) + /// The . + public static Image GuassianSharpen(this Image source, float sigma = 3f, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { return GuassianSharpen(source, sigma, source.Bounds, progressHandler); } @@ -27,16 +31,20 @@ namespace ImageProcessorCore /// /// Applies a Guassian sharpening filter to the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The 'sigma' value representing the weight of the blur. /// /// The structure that specifies the portion of the image object to alter. /// /// A delegate which is called as progress is made processing the image. - /// The . - public static Image GuassianSharpen(this Image source, float sigma, Rectangle rectangle, ProgressEventHandler progressHandler = null) + /// The . + public static Image GuassianSharpen(this Image source, float sigma, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - GuassianSharpenProcessor processor = new GuassianSharpenProcessor(sigma); + GuassianSharpenProcessor processor = new GuassianSharpenProcessor(sigma); processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Filters/Hue.cs b/src/ImageProcessorCore/Filters/Hue.cs index 45a995c301..a4de7c6102 100644 --- a/src/ImageProcessorCore/Filters/Hue.cs +++ b/src/ImageProcessorCore/Filters/Hue.cs @@ -1,25 +1,29 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Alters the hue component of the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The angle in degrees to adjust the image. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Hue(this Image source, float degrees, ProgressEventHandler progressHandler = null) + /// The . + public static Image Hue(this Image source, float degrees, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { return Hue(source, degrees, source.Bounds, progressHandler); } @@ -27,16 +31,20 @@ namespace ImageProcessorCore /// /// Alters the hue component of the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The angle in degrees to adjust the image. /// /// The structure that specifies the portion of the image object to alter. /// /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Hue(this Image source, float degrees, Rectangle rectangle, ProgressEventHandler progressHandler = null) + /// The . + public static Image Hue(this Image source, float degrees, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - HueProcessor processor = new HueProcessor(degrees); + HueProcessor processor = new HueProcessor(degrees); processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Filters/Invert.cs b/src/ImageProcessorCore/Filters/Invert.cs index eaeddfc62b..d7888be591 100644 --- a/src/ImageProcessorCore/Filters/Invert.cs +++ b/src/ImageProcessorCore/Filters/Invert.cs @@ -18,7 +18,9 @@ namespace ImageProcessorCore /// The image this method extends. /// A delegate which is called as progress is made processing the image. /// The . - public static Image Invert(this Image source, ProgressEventHandler progressHandler = null) + public static Image Invert(this Image source, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { return Invert(source, source.Bounds, progressHandler); } @@ -32,9 +34,11 @@ namespace ImageProcessorCore /// /// A delegate which is called as progress is made processing the image. /// The . - public static Image Invert(this Image source, Rectangle rectangle, ProgressEventHandler progressHandler = null) + public static Image Invert(this Image source, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - InvertProcessor processor = new InvertProcessor(); + InvertProcessor processor = new InvertProcessor(); processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Filters/Kodachrome.cs b/src/ImageProcessorCore/Filters/Kodachrome.cs index 3b4e3f1c66..0f4bf2ae83 100644 --- a/src/ImageProcessorCore/Filters/Kodachrome.cs +++ b/src/ImageProcessorCore/Filters/Kodachrome.cs @@ -1,24 +1,28 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Alters the colors of the image recreating an old Kodachrome camera effect. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Kodachrome(this Image source, ProgressEventHandler progressHandler = null) + /// The . + public static Image Kodachrome(this Image source, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { return Kodachrome(source, source.Bounds, progressHandler); } @@ -26,15 +30,19 @@ namespace ImageProcessorCore /// /// Alters the colors of the image recreating an old Kodachrome camera effect. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// /// The structure that specifies the portion of the image object to alter. /// /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Kodachrome(this Image source, Rectangle rectangle, ProgressEventHandler progressHandler = null) + /// The . + public static Image Kodachrome(this Image source, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - KodachromeProcessor processor = new KodachromeProcessor(); + KodachromeProcessor processor = new KodachromeProcessor(); processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Filters/Lomograph.cs b/src/ImageProcessorCore/Filters/Lomograph.cs index 5ecf0bcf4a..5a484bcc87 100644 --- a/src/ImageProcessorCore/Filters/Lomograph.cs +++ b/src/ImageProcessorCore/Filters/Lomograph.cs @@ -1,24 +1,28 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Alters the colors of the image recreating an old Lomograph camera effect. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Lomograph(this Image source, ProgressEventHandler progressHandler = null) + /// The . + public static Image Lomograph(this Image source, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { return Lomograph(source, source.Bounds, progressHandler); } @@ -26,15 +30,19 @@ namespace ImageProcessorCore /// /// Alters the colors of the image recreating an old Lomograph camera effect. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// /// The structure that specifies the portion of the image object to alter. /// /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Lomograph(this Image source, Rectangle rectangle, ProgressEventHandler progressHandler = null) + /// The . + public static Image Lomograph(this Image source, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - LomographProcessor processor = new LomographProcessor(); + LomographProcessor processor = new LomographProcessor(); processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Filters/Options/ColorBlindness.cs b/src/ImageProcessorCore/Filters/Options/ColorBlindness.cs index 6d7fe849bc..e128044cd8 100644 --- a/src/ImageProcessorCore/Filters/Options/ColorBlindness.cs +++ b/src/ImageProcessorCore/Filters/Options/ColorBlindness.cs @@ -6,7 +6,7 @@ namespace ImageProcessorCore { /// - /// Enumerates the various types of color blindness. + /// Enumerates the various types of defined color blindness filters. /// public enum ColorBlindness { diff --git a/src/ImageProcessorCore/Filters/Options/EdgeDetection.cs b/src/ImageProcessorCore/Filters/Options/EdgeDetection.cs new file mode 100644 index 0000000000..f637e4573e --- /dev/null +++ b/src/ImageProcessorCore/Filters/Options/EdgeDetection.cs @@ -0,0 +1,58 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// Enumerates the various types of defined edge detection filters. + /// + public enum EdgeDetection + { + /// + /// The Kayyali operator filter. + /// + Kayyali, + + /// + /// The Kirsch operator filter. + /// + Kirsch, + + /// + /// The Lapacian3X3 operator filter. + /// + Lapacian3X3, + + /// + /// The Lapacian5X5 operator filter. + /// + Lapacian5X5, + + /// + /// The LaplacianOfGaussian operator filter. + /// + LaplacianOfGaussian, + + /// + /// The Prewitt operator filter. + /// + Prewitt, + + /// + /// The RobertsCross operator filter. + /// + RobertsCross, + + /// + /// The Scharr operator filter. + /// + Scharr, + + /// + /// The Sobel operator filter. + /// + Sobel + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleMode.cs b/src/ImageProcessorCore/Filters/Options/GrayscaleMode.cs similarity index 64% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleMode.cs rename to src/ImageProcessorCore/Filters/Options/GrayscaleMode.cs index 269c1179ef..9c1b9df5d4 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleMode.cs +++ b/src/ImageProcessorCore/Filters/Options/GrayscaleMode.cs @@ -1,14 +1,14 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // -namespace ImageProcessorCore.Processors +namespace ImageProcessorCore { /// - /// Provides enumeration over the various greyscale methods available. + /// Enumerates the various types of defined Grayscale filters. /// - public enum GreyscaleMode + public enum GrayscaleMode { /// /// ITU-R Recommendation BT.709 diff --git a/src/ImageProcessorCore/Filters/Pixelate.cs b/src/ImageProcessorCore/Filters/Pixelate.cs index 6f86848d6a..8bfb7cd2d5 100644 --- a/src/ImageProcessorCore/Filters/Pixelate.cs +++ b/src/ImageProcessorCore/Filters/Pixelate.cs @@ -1,15 +1,18 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { using Processors; + using System; /// - /// Extension methods for the type. + /// Extension methods for the type. /// + /// The pixel format. + /// The packed format. long, float. public static partial class ImageExtensions { /// @@ -18,8 +21,10 @@ namespace ImageProcessorCore /// The image this method extends. /// The size of the pixels. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Pixelate(this Image source, int size = 4, ProgressEventHandler progressHandler = null) + /// The . + public static Image Pixelate(this Image source, int size = 4, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { return Pixelate(source, size, source.Bounds, progressHandler); } @@ -33,10 +38,17 @@ namespace ImageProcessorCore /// The structure that specifies the portion of the image object to alter. /// /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Pixelate(this Image source, int size, Rectangle rectangle, ProgressEventHandler progressHandler = null) + /// The . + public static Image Pixelate(this Image source, int size, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - PixelateProcessor processor = new PixelateProcessor(size); + if (size <= 0 || size > source.Height || size > source.Width) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + PixelateProcessor processor = new PixelateProcessor(size); processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Filters/Polaroid.cs b/src/ImageProcessorCore/Filters/Polaroid.cs index 165f1d59a5..e56496322c 100644 --- a/src/ImageProcessorCore/Filters/Polaroid.cs +++ b/src/ImageProcessorCore/Filters/Polaroid.cs @@ -1,24 +1,28 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Alters the colors of the image recreating an old Polaroid camera effect. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Polaroid(this Image source, ProgressEventHandler progressHandler = null) + /// The . + public static Image Polaroid(this Image source, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { return Polaroid(source, source.Bounds, progressHandler); } @@ -26,15 +30,19 @@ namespace ImageProcessorCore /// /// Alters the colors of the image recreating an old Polaroid camera effect. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// /// The structure that specifies the portion of the image object to alter. /// /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Polaroid(this Image source, Rectangle rectangle, ProgressEventHandler progressHandler = null) + /// The . + public static Image Polaroid(this Image source, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - PolaroidProcessor processor = new PolaroidProcessor(); + PolaroidProcessor processor = new PolaroidProcessor(); processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs b/src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs index 8ed703e034..b2a2520c3b 100644 --- a/src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs @@ -5,17 +5,20 @@ namespace ImageProcessorCore.Processors { - using System; using System.Numerics; using System.Threading.Tasks; /// - /// An to change the Alpha of an . + /// An to change the Alpha of an . /// - public class AlphaProcessor : ImageProcessor + /// The pixel format. + /// The packed format. long, float. + public class AlphaProcessor : ImageProcessor + where T : IPackedVector + where TP : struct { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The percentage to adjust the opacity of the image. Must be between 0 and 100. /// @@ -33,7 +36,7 @@ namespace ImageProcessorCore.Processors public int Value { get; } /// - 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) { float alpha = this.Value / 100f; int sourceY = sourceRectangle.Y; @@ -42,21 +45,25 @@ namespace ImageProcessorCore.Processors int endX = sourceRectangle.Right; Vector4 alphaVector = new Vector4(1, 1, 1, alpha); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { if (y >= sourceY && y < sourceBottom) { for (int x = startX; x < endX; x++) { - Vector4 color = Color.ToNonPremultiplied(sourcePixels[x, y]).ToVector4(); + Vector4 color = sourcePixels[x, y].ToVector4(); color *= alphaVector; - targetPixels[x, y] = Color.FromNonPremultiplied(new Color(color)); + + T packed = default(T); + packed.PackVector(color); + targetPixels[x, y] = packed; } this.OnRowProcessed(); diff --git a/src/ImageProcessorCore/Filters/Processors/BackgroundColorProcessor.cs b/src/ImageProcessorCore/Filters/Processors/BackgroundColorProcessor.cs index e78987d23a..cc59d2d4fc 100644 --- a/src/ImageProcessorCore/Filters/Processors/BackgroundColorProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/BackgroundColorProcessor.cs @@ -6,12 +6,15 @@ namespace ImageProcessorCore.Processors { using System; + using System.Numerics; using System.Threading.Tasks; /// /// Sets the background color of the image. /// - public class BackgroundColorProcessor : ImageProcessor + public class BackgroundColorProcessor : ImageProcessor + where T : IPackedVector + where TP : struct { /// /// The epsilon for comparing floating point numbers. @@ -19,46 +22,47 @@ namespace ImageProcessorCore.Processors private const float Epsilon = 0.001f; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The to set the background color to. - public BackgroundColorProcessor(Color color) + /// The to set the background color to. + public BackgroundColorProcessor(T color) { - this.Value = Color.FromNonPremultiplied(color); + this.Value = color; } /// /// Gets the background color value. /// - public Color Value { get; } + public T Value { get; } /// - protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { int sourceY = sourceRectangle.Y; int sourceBottom = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; - Color backgroundColor = this.Value; + Vector4 backgroundColor = this.Value.ToVector4(); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { if (y >= sourceY && y < sourceBottom) { for (int x = startX; x < endX; x++) { - Color color = sourcePixels[x, y]; - float a = color.A; + Vector4 color = sourcePixels[x, y].ToVector4(); + float a = color.W; if (a < 1 && a > 0) { - color = Color.Lerp(color, backgroundColor, .5f); + color = Vector4.Lerp(color, backgroundColor, .5f); } if (Math.Abs(a) < Epsilon) @@ -66,7 +70,9 @@ namespace ImageProcessorCore.Processors color = backgroundColor; } - targetPixels[x, y] = color; + T packed = default(T); + packed.PackVector(color); + targetPixels[x, y] = packed; } this.OnRowProcessed(); diff --git a/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Binarization/BinaryThresholdProcessor.cs similarity index 51% rename from src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs rename to src/ImageProcessorCore/Filters/Processors/Binarization/BinaryThresholdProcessor.cs index 60b49d0eec..ffe5c4452a 100644 --- a/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/Binarization/BinaryThresholdProcessor.cs @@ -1,19 +1,22 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // namespace ImageProcessorCore.Processors { - using System; using System.Threading.Tasks; /// - /// An to perform binary threshold filtering against an - /// . The image will be converted to greyscale before thresholding + /// An to perform binary threshold filtering against an + /// . The image will be converted to Grayscale before thresholding /// occurs. /// - public class ThresholdProcessor : ImageProcessor + /// The pixel format. + /// The packed format. long, float. + public class BinaryThresholdProcessor : ImageProcessor + where T : IPackedVector + where TP : struct { /// /// Initializes a new instance of the class. @@ -22,10 +25,19 @@ namespace ImageProcessorCore.Processors /// /// is less than 0 or is greater than 1. /// - public ThresholdProcessor(float threshold) + public BinaryThresholdProcessor(float threshold) { + // TODO: Check limit. Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); this.Value = threshold; + + T upper = default(T); + upper.PackVector(Color.White.ToVector4()); + this.UpperColor = upper; + + T lower = default(T); + lower.PackVector(Color.Black.ToVector4()); + this.LowerColor = lower; } /// @@ -36,46 +48,50 @@ namespace ImageProcessorCore.Processors /// /// The color to use for pixels that are above the threshold. /// - public Color UpperColor => Color.White; + public T UpperColor { get; set; } /// /// The color to use for pixels that fall below the threshold. /// - public Color LowerColor => Color.Black; + public T LowerColor { 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) { - new GreyscaleBt709Processor().Apply(source, source, sourceRectangle); + new GrayscaleBt709Processor().Apply(source, source, sourceRectangle); } /// - 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) { + // target.SetPixels(source.Width, source.Height, source.Pixels); + + float threshold = this.Value; - Color upper = this.UpperColor; - Color lower = this.LowerColor; + T upper = this.UpperColor; + T lower = this.LowerColor; int sourceY = sourceRectangle.Y; int sourceBottom = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { if (y >= sourceY && y < sourceBottom) { for (int x = startX; x < endX; x++) { - Color color = sourcePixels[x, y]; + T color = sourcePixels[x, y]; - // Any channel will do since it's greyscale. - targetPixels[x, y] = color.B >= threshold ? upper : lower; + // Any channel will do since it's Grayscale. + targetPixels[x, y] = color.ToVector4().X >= threshold ? upper : lower; } this.OnRowProcessed(); } diff --git a/src/ImageProcessorCore/Filters/Processors/BlendProcessor.cs b/src/ImageProcessorCore/Filters/Processors/BlendProcessor.cs index 5de0741fae..56c754729a 100644 --- a/src/ImageProcessorCore/Filters/Processors/BlendProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/BlendProcessor.cs @@ -5,17 +5,22 @@ namespace ImageProcessorCore.Processors { + using System.Numerics; using System.Threading.Tasks; /// /// Combines two images together by blending the pixels. /// - public class BlendProcessor : ImageProcessor + /// The pixel format. + /// The packed format. long, float. + public class BlendProcessor : ImageProcessor + where T : IPackedVector + where TP : struct { /// /// The image to blend. /// - private readonly ImageBase blend; + private readonly ImageBase blend; /// /// Initializes a new instance of the class. @@ -25,7 +30,7 @@ namespace ImageProcessorCore.Processors /// Disposal of this image is the responsibility of the developer. /// /// The opacity of the image to blend. Between 0 and 100. - public BlendProcessor(ImageBase image, int alpha = 100) + public BlendProcessor(ImageBase image, int alpha = 100) { Guard.MustBeBetweenOrEqualTo(alpha, 0, 100, nameof(alpha)); this.blend = image; @@ -38,7 +43,7 @@ namespace ImageProcessorCore.Processors public int Value { get; } /// - protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { int sourceY = sourceRectangle.Y; int sourceBottom = sourceRectangle.Bottom; @@ -47,9 +52,9 @@ namespace ImageProcessorCore.Processors Rectangle bounds = this.blend.Bounds; float alpha = this.Value / 100f; - using (PixelAccessor toBlendPixels = this.blend.Lock()) - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor toBlendPixels = this.blend.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, @@ -60,21 +65,23 @@ namespace ImageProcessorCore.Processors { for (int x = startX; x < endX; x++) { - Color color = sourcePixels[x, y]; + Vector4 color = sourcePixels[x, y].ToVector4(); if (bounds.Contains(x, y)) { - Color blendedColor = toBlendPixels[x, y]; + Vector4 blendedColor = toBlendPixels[x, y].ToVector4(); - if (blendedColor.A > 0) + if (blendedColor.W > 0) { // Lerping colors is dependent on the alpha of the blended color - float alphaFactor = alpha > 0 ? alpha : blendedColor.A; - color = Color.Lerp(color, blendedColor, alphaFactor); + float alphaFactor = alpha > 0 ? alpha : blendedColor.W; + color = Vector4.Lerp(color, blendedColor, alphaFactor); } } - targetPixels[x, y] = color; + T packed = default(T); + packed.PackVector(color); + targetPixels[x, y] = packed; } this.OnRowProcessed(); diff --git a/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs b/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs index 6710ff5938..bedbd6b71d 100644 --- a/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs @@ -5,14 +5,17 @@ namespace ImageProcessorCore.Processors { - using System; using System.Numerics; using System.Threading.Tasks; /// - /// An to change the brightness of an . + /// An to change the brightness of an . /// - public class BrightnessProcessor : ImageProcessor + /// The pixel format. + /// The packed format. long, float. + public class BrightnessProcessor : ImageProcessor + where T : IPackedVector + where TP : struct { /// /// Initializes a new instance of the class. @@ -33,7 +36,7 @@ namespace ImageProcessorCore.Processors public int Value { get; } /// - 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) { float brightness = this.Value / 100f; int sourceY = sourceRectangle.Y; @@ -41,25 +44,31 @@ namespace ImageProcessorCore.Processors int startX = sourceRectangle.X; int endX = sourceRectangle.Right; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { if (y >= sourceY && y < sourceBottom) { for (int x = startX; x < endX; x++) { - Color color = Color.Expand(sourcePixels[x, y]); + // TODO: Check this with other formats. + Vector4 vector = sourcePixels[x, y].ToVector4().Expand(); + Vector3 transformed = new Vector3(vector.X, vector.Y, vector.Z); + transformed += new Vector3(brightness); + vector = new Vector4(transformed, vector.W); - Vector3 vector3 = color.ToVector3(); - vector3 += new Vector3(brightness); + T packed = default(T); + packed.PackVector(vector.Compress()); - targetPixels[x, y] = Color.Compress(new Color(vector3, color.A)); + targetPixels[x, y] = packed; } + this.OnRowProcessed(); } }); diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs index 7fc3240d00..6893d2a8a0 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs @@ -10,7 +10,11 @@ namespace ImageProcessorCore.Processors /// /// Converts the colors of the image to their black and white equivalent. /// - public class BlackWhiteProcessor : ColorMatrixFilter + /// The pixel format. + /// The packed format. long, float. + public class BlackWhiteProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs index d5740ec284..e8dd077ebc 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs @@ -10,7 +10,11 @@ namespace ImageProcessorCore.Processors /// /// Converts the colors of the image recreating Achromatomaly (Color desensitivity) color blindness. /// - public class AchromatomalyProcessor : ColorMatrixFilter + /// The pixel format. + /// The packed format. long, float. + public class AchromatomalyProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs index 6f2f7c2693..0e7f69a131 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs @@ -10,7 +10,11 @@ namespace ImageProcessorCore.Processors /// /// Converts the colors of the image recreating Achromatopsia (Monochrome) color blindness. /// - public class AchromatopsiaProcessor : ColorMatrixFilter + /// The pixel format. + /// The packed format. long, float. + public class AchromatopsiaProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs index fed09991f6..b7f48ea1dc 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs @@ -10,7 +10,11 @@ namespace ImageProcessorCore.Processors /// /// Converts the colors of the image recreating Deuteranomaly (Green-Weak) color blindness. /// - public class DeuteranomalyProcessor : ColorMatrixFilter + /// The pixel format. + /// The packed format. long, float. + public class DeuteranomalyProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs index 0ef190861b..82f50d07fb 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs @@ -10,7 +10,11 @@ namespace ImageProcessorCore.Processors /// /// Converts the colors of the image recreating Deuteranopia (Green-Blind) color blindness. /// - public class DeuteranopiaProcessor : ColorMatrixFilter + /// The pixel format. + /// The packed format. long, float. + public class DeuteranopiaProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs index b7152a68e4..32380cba13 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs @@ -10,7 +10,11 @@ namespace ImageProcessorCore.Processors /// /// Converts the colors of the image recreating Protanopia (Red-Weak) color blindness. /// - public class ProtanomalyProcessor : ColorMatrixFilter + /// The pixel format. + /// The packed format. long, float. + public class ProtanomalyProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs index 7984be139b..891b6e06ad 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs @@ -10,7 +10,11 @@ namespace ImageProcessorCore.Processors /// /// Converts the colors of the image recreating Protanopia (Red-Blind) color blindness. /// - public class ProtanopiaProcessor : ColorMatrixFilter + /// The pixel format. + /// The packed format. long, float. + public class ProtanopiaProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs index 618da36bb9..88e5fd5dcb 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs @@ -10,7 +10,11 @@ namespace ImageProcessorCore.Processors /// /// Converts the colors of the image recreating Tritanomaly (Blue-Weak) color blindness. /// - public class TritanomalyProcessor : ColorMatrixFilter + /// The pixel format. + /// The packed format. long, float. + public class TritanomalyProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs index e53de7a69a..b708927a24 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs @@ -10,7 +10,11 @@ namespace ImageProcessorCore.Processors /// /// Converts the colors of the image recreating Tritanopia (Blue-Blind) color blindness. /// - public class TritanopiaProcessor : ColorMatrixFilter + /// The pixel format. + /// The packed format. long, float. + public class TritanopiaProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs index d27ea5c6d5..dddb1b2b34 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs @@ -9,34 +9,40 @@ namespace ImageProcessorCore.Processors using System.Threading.Tasks; /// - /// The color matrix filter. + /// The color matrix filter. Inherit from this class to perform operation involving color matrices. /// - public abstract class ColorMatrixFilter : ImageProcessor, IColorMatrixFilter + /// The pixel format. + /// The packed format. long, float. + public abstract class ColorMatrixFilter : ImageProcessor, IColorMatrixFilter + where T : IPackedVector + where TP : struct { /// public abstract Matrix4x4 Matrix { get; } /// - public virtual bool Compand => true; + public override bool Compand { get; set; } = true; /// - protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { int startX = sourceRectangle.X; int endX = sourceRectangle.Right; Matrix4x4 matrix = this.Matrix; + bool compand = this.Compand; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { for (int x = startX; x < endX; x++) { - targetPixels[x, y] = this.ApplyMatrix(sourcePixels[x, y], matrix); + targetPixels[x, y] = this.ApplyMatrix(sourcePixels[x, y], matrix, compand); } this.OnRowProcessed(); @@ -49,20 +55,24 @@ namespace ImageProcessorCore.Processors /// /// The source color. /// The matrix. + /// Whether to compand the color during processing. /// /// The . /// - private Color ApplyMatrix(Color color, Matrix4x4 matrix) + private T ApplyMatrix(T color, Matrix4x4 matrix, bool compand) { - bool compand = this.Compand; + Vector4 vector = color.ToVector4(); if (compand) { - color = Color.Expand(color); + vector = vector.Expand(); } - Vector3 transformed = Vector3.Transform(color.ToVector3(), matrix); - return compand ? Color.Compress(new Color(transformed, color.A)) : new Color(transformed, color.A); + Vector3 transformed = Vector3.Transform(new Vector3(vector.X, vector.Y, vector.Z), matrix); + vector = new Vector4(transformed, vector.W); + T packed = default(T); + packed.PackVector(compand ? vector.Compress() : vector); + return packed; } } } diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GrayscaleBt601Processor.cs similarity index 64% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs rename to src/ImageProcessorCore/Filters/Processors/ColorMatrix/GrayscaleBt601Processor.cs index b9a6daa6e7..b650f0cfc0 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GrayscaleBt601Processor.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -8,10 +8,14 @@ namespace ImageProcessorCore.Processors using System.Numerics; /// - /// Converts the colors of the image to greyscale applying the formula as specified by + /// Converts the colors of the image to Grayscale applying the formula as specified by /// ITU-R Recommendation BT.601 . /// - public class GreyscaleBt601Processor : ColorMatrixFilter + /// The pixel format. + /// The packed format. long, float. + public class GrayscaleBt601Processor : ColorMatrixFilter + where T : IPackedVector + where TP : struct { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GrayscaleBt709Processor.cs similarity index 73% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs rename to src/ImageProcessorCore/Filters/Processors/ColorMatrix/GrayscaleBt709Processor.cs index 949e51e6a9..435f00d198 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GrayscaleBt709Processor.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -8,10 +8,12 @@ namespace ImageProcessorCore.Processors using System.Numerics; /// - /// Converts the colors of the image to greyscale applying the formula as specified by + /// Converts the colors of the image to Grayscale applying the formula as specified by /// ITU-R Recommendation BT.709 . /// - public class GreyscaleBt709Processor : ColorMatrixFilter + public class GrayscaleBt709Processor : ColorMatrixFilter + where T : IPackedVector + where TP : struct { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs index da7d4631c5..ab68741696 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs @@ -8,7 +8,14 @@ namespace ImageProcessorCore.Processors using System; using System.Numerics; - public class HueProcessor : ColorMatrixFilter + /// + /// An to change the hue of an . + /// + /// The pixel format. + /// The packed format. long, float. + public class HueProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct { /// /// The used to alter the image. @@ -31,23 +38,8 @@ namespace ImageProcessorCore.Processors } this.Angle = angle; - } - /// - /// Gets the rotation value. - /// - public float Angle { get; } - - /// - public override Matrix4x4 Matrix => this.matrix; - - /// - public override bool Compand => false; - - /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - float radians = (float)ImageMaths.DegreesToRadians(this.Angle); + float radians = ImageMaths.DegreesToRadians(angle); double cosradians = Math.Cos(radians); double sinradians = Math.Sin(radians); @@ -77,5 +69,16 @@ namespace ImageProcessorCore.Processors this.matrix = matrix4X4; } + + /// + /// Gets the rotation value. + /// + public float Angle { get; } + + /// + public override Matrix4x4 Matrix => this.matrix; + + /// + public override bool Compand => false; } } diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs index 5a0c766848..a3fc7d65c5 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs @@ -11,17 +11,13 @@ namespace ImageProcessorCore.Processors /// Encapsulates properties and methods for creating processors that utilize a matrix to /// alter the image pixels. /// - public interface IColorMatrixFilter : IImageProcessor + public interface IColorMatrixFilter : IImageProcessor + where T : IPackedVector + where TP : struct { /// /// Gets the used to alter the image. /// Matrix4x4 Matrix { get; } - - /// - /// Gets a value indicating whether to compress - /// or expand individual pixel colors the value on processing. - /// - bool Compand { get; } } } diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/KodachromeProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/KodachromeProcessor.cs index 91c436460e..f7a84fc082 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/KodachromeProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/KodachromeProcessor.cs @@ -10,7 +10,11 @@ namespace ImageProcessorCore.Processors /// /// Converts the colors of the image recreating an old Kodachrome camera effect. /// - public class KodachromeProcessor : ColorMatrixFilter + /// The pixel format. + /// The packed format. long, float. + public class KodachromeProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/LomographProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/LomographProcessor.cs index 2422994337..cfdc628935 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/LomographProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/LomographProcessor.cs @@ -10,7 +10,11 @@ namespace ImageProcessorCore.Processors /// /// Converts the colors of the image recreating an old Lomograph effect. /// - public class LomographProcessor : ColorMatrixFilter + /// The pixel format. + /// The packed format. long, float. + public class LomographProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct { /// public override Matrix4x4 Matrix => new Matrix4x4() @@ -24,9 +28,11 @@ 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) { - new VignetteProcessor { Color = new Color(0, 10 / 255f, 0) }.Apply(target, target, targetRectangle); + T packed = default(T); + packed.PackBytes(0, 10, 0, 255); + new VignetteProcessor { VignetteColor = packed }.Apply(target, target, targetRectangle); } } } diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/PolaroidProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/PolaroidProcessor.cs index ea6f85a390..7a0263bd05 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/PolaroidProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/PolaroidProcessor.cs @@ -10,7 +10,11 @@ namespace ImageProcessorCore.Processors /// /// Converts the colors of the image recreating an old Polaroid effect. /// - public class PolaroidProcessor : ColorMatrixFilter + /// The pixel format. + /// The packed format. long, float. + public class PolaroidProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct { /// public override Matrix4x4 Matrix => new Matrix4x4() @@ -30,12 +34,17 @@ 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) { - new VignetteProcessor { Color = new Color(102 / 255f, 34 / 255f, 0) }.Apply(target, target, targetRectangle); - new GlowProcessor + T packedV = default(T); + packedV.PackBytes(102, 34, 0, 255); + new VignetteProcessor { VignetteColor = packedV }.Apply(target, target, targetRectangle); + + T packedG = default(T); + packedG.PackBytes(255, 153, 102, 178); + new GlowProcessor { - Color = new Color(1, 153 / 255f, 102 / 255f, .7f), + GlowColor = packedG, RadiusX = target.Width / 4f, RadiusY = target.Width / 4f } diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs index abc00bfb61..1afd3db5ff 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs @@ -5,13 +5,16 @@ namespace ImageProcessorCore.Processors { - using System; using System.Numerics; /// - /// An to change the saturation of an . + /// An to change the saturation of an . /// - public class SaturationProcessor : ColorMatrixFilter + /// The pixel format. + /// The packed format. long, float. + public class SaturationProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct { /// /// The saturation to be applied to the image. @@ -34,14 +37,7 @@ namespace ImageProcessorCore.Processors { Guard.MustBeBetweenOrEqualTo(saturation, -100, 100, nameof(saturation)); this.saturation = saturation; - } - /// - public override Matrix4x4 Matrix => this.matrix; - - /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { float saturationFactor = this.saturation / 100f; // Stop at -1 to prevent inversion. @@ -71,5 +67,8 @@ namespace ImageProcessorCore.Processors this.matrix = matrix4X4; } + + /// + public override Matrix4x4 Matrix => this.matrix; } } diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SepiaProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SepiaProcessor.cs index da68013110..60ba27c0e6 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SepiaProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SepiaProcessor.cs @@ -11,7 +11,11 @@ namespace ImageProcessorCore.Processors /// Converts the colors of the image to their sepia equivalent. /// The formula used matches the svg specification. /// - public class SepiaProcessor : ColorMatrixFilter + /// The pixel format. + /// The packed format. long, float. + public class SepiaProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct { /// public override Matrix4x4 Matrix => new Matrix4x4() diff --git a/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs index 19f564be4d..1415027209 100644 --- a/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs @@ -5,14 +5,17 @@ namespace ImageProcessorCore.Processors { - using System; using System.Numerics; using System.Threading.Tasks; /// - /// An to change the contrast of an . + /// An to change the contrast of an . /// - public class ContrastProcessor : ImageProcessor + /// The pixel format. + /// The packed format. long, float. + public class ContrastProcessor : ImageProcessor + where T : IPackedVector + where TP : struct { /// /// Initializes a new instance of the class. @@ -33,7 +36,7 @@ namespace ImageProcessorCore.Processors public int Value { get; } /// - 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) { float contrast = (100f + this.Value) / 100f; int sourceY = sourceRectangle.Y; @@ -43,23 +46,26 @@ namespace ImageProcessorCore.Processors Vector4 contrastVector = new Vector4(contrast, contrast, contrast, 1); Vector4 shiftVector = new Vector4(.5f, .5f, .5f, 1); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { if (y >= sourceY && y < sourceBottom) { for (int x = startX; x < endX; x++) { - Vector4 color = Color.Expand(sourcePixels[x, y]).ToVector4(); - color -= shiftVector; - color *= contrastVector; - color += shiftVector; - targetPixels[x, y] = Color.Compress(new Color(color)); + Vector4 vector = (sourcePixels[x, y]).ToVector4().Expand(); + vector -= shiftVector; + vector *= contrastVector; + vector += shiftVector; + T packed = default(T); + packed.PackVector(vector.Compress()); + targetPixels[x, y] = packed; } this.OnRowProcessed(); } diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/BoxBlurProcessor.cs index 042288ffdc..a4fe4da0f1 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/BoxBlurProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/BoxBlurProcessor.cs @@ -8,7 +8,11 @@ namespace ImageProcessorCore.Processors /// /// Applies a Box blur filter to the image. /// - public class BoxBlurProcessor : Convolution2PassFilter + /// The pixel format. + /// The packed format. long, float. + public class BoxBlurProcessor : Convolution2PassFilter + where T : IPackedVector + where TP : struct { /// /// The maximum size of the kernal in either direction. @@ -43,7 +47,7 @@ namespace ImageProcessorCore.Processors public override float[,] KernelY => this.kernelY; /// - 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.kernelY == null) { diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs index b16a528db4..1113d0640a 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs @@ -6,12 +6,17 @@ namespace ImageProcessorCore.Processors { using System; + using System.Numerics; using System.Threading.Tasks; /// /// Defines a filter that uses two one-dimensional matrices to perform convolution against an image. /// - public abstract class Convolution2DFilter : ImageProcessor + /// The pixel format. + /// The packed format. long, float. + public abstract class Convolution2DFilter : ImageProcessor + where T : IPackedVector + where TP : struct { /// /// Gets the horizontal gradient operator. @@ -24,7 +29,7 @@ namespace ImageProcessorCore.Processors public abstract float[,] KernelY { get; } /// - 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) { float[,] kernelX = this.KernelX; float[,] kernelY = this.KernelY; @@ -42,12 +47,13 @@ namespace ImageProcessorCore.Processors int maxY = sourceBottom - 1; int maxX = endX - 1; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { if (y >= sourceY && y < sourceBottom) @@ -76,10 +82,10 @@ namespace ImageProcessorCore.Processors offsetX = offsetX.Clamp(0, maxX); - Color currentColor = sourcePixels[offsetX, offsetY]; - float r = currentColor.R; - float g = currentColor.G; - float b = currentColor.B; + Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + float r = currentColor.X; + float g = currentColor.Y; + float b = currentColor.Z; if (fy < kernelXHeight) { @@ -101,8 +107,10 @@ namespace ImageProcessorCore.Processors float green = (float)Math.Sqrt((gX * gX) + (gY * gY)); float blue = (float)Math.Sqrt((bX * bX) + (bY * bY)); - Color targetColor = targetPixels[x, y]; - targetPixels[x, y] = new Color(red, green, blue, targetColor.A); + Vector4 targetColor = targetPixels[x, y].ToVector4(); + T packed = default(T); + packed.PackVector(new Vector4(red, green, blue, targetColor.Z)); + targetPixels[x, y] = packed; } this.OnRowProcessed(); } diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2PassFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2PassFilter.cs index bfdf184443..6e1fb78419 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2PassFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2PassFilter.cs @@ -5,12 +5,17 @@ namespace ImageProcessorCore.Processors { + using System.Numerics; using System.Threading.Tasks; /// /// Defines a filter that uses two one-dimensional matrices to perform two-pass convolution against an image. /// - public abstract class Convolution2PassFilter : ImageProcessor + /// The pixel format. + /// The packed format. long, float. + public abstract class Convolution2PassFilter : ImageProcessor + where T : IPackedVector + where TP : struct { /// /// Gets the horizontal gradient operator. @@ -23,24 +28,18 @@ namespace ImageProcessorCore.Processors public abstract float[,] KernelY { get; } /// - 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) { float[,] kernelX = this.KernelX; float[,] kernelY = this.KernelY; - ImageBase firstPass = new Image(source.Width, source.Height); + ImageBase firstPass = new Image(source.Width, source.Height); this.ApplyConvolution(firstPass, source, sourceRectangle, startY, endY, kernelX); this.ApplyConvolution(target, firstPass, sourceRectangle, startY, endY, kernelY); } /// - /// Applies the process to the specified portion of the specified at the specified location + /// Applies the process to the specified portion of the specified at the specified location /// and with the specified size. /// /// Target image to apply the process to. @@ -51,13 +50,7 @@ namespace ImageProcessorCore.Processors /// 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 kernel operator. - private void ApplyConvolution( - ImageBase target, - ImageBase source, - Rectangle sourceRectangle, - int startY, - int endY, - float[,] kernel) + private void ApplyConvolution(ImageBase target, ImageBase source, Rectangle sourceRectangle, int startY, int endY, float[,] kernel) { int kernelHeight = kernel.GetLength(0); int kernelWidth = kernel.GetLength(1); @@ -70,17 +63,18 @@ namespace ImageProcessorCore.Processors int maxY = sourceBottom - 1; int maxX = endX - 1; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { for (int x = startX; x < endX; x++) { - Color destination = Color.Empty; + Vector4 destination = new Vector4(); // Apply each matrix multiplier to the color components for each pixel. for (int fy = 0; fy < kernelHeight; fy++) @@ -97,12 +91,14 @@ namespace ImageProcessorCore.Processors offsetX = offsetX.Clamp(0, maxX); - Color currentColor = sourcePixels[offsetX, offsetY]; + Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); destination += kernel[fy, fx] * currentColor; } } - targetPixels[x, y] = destination; + T packed = default(T); + packed.PackVector(destination); + targetPixels[x, y] = packed; } this.OnRowProcessed(); diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/ConvolutionFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/ConvolutionFilter.cs index 8ddcab4a5d..eb0c454b44 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/ConvolutionFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/ConvolutionFilter.cs @@ -5,12 +5,15 @@ namespace ImageProcessorCore.Processors { + using System.Numerics; using System.Threading.Tasks; /// /// Defines a filter that uses a 2 dimensional matrix to perform convolution against an image. /// - public abstract class ConvolutionFilter : ImageProcessor + public abstract class ConvolutionFilter : ImageProcessor + where T : IPackedVector + where TP : struct { /// /// Gets the 2d gradient operator. @@ -18,7 +21,7 @@ namespace ImageProcessorCore.Processors public abstract float[,] KernelXY { get; } /// - 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) { float[,] kernelX = this.KernelXY; int kernelLength = kernelX.GetLength(0); @@ -31,12 +34,13 @@ namespace ImageProcessorCore.Processors int maxY = sourceBottom - 1; int maxX = endX - 1; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { if (y >= sourceY && y < sourceBottom) @@ -62,10 +66,10 @@ namespace ImageProcessorCore.Processors offsetX = offsetX.Clamp(0, maxX); - Color currentColor = sourcePixels[offsetX, offsetY]; - float r = currentColor.R; - float g = currentColor.G; - float b = currentColor.B; + Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + float r = currentColor.X; + float g = currentColor.Y; + float b = currentColor.Z; rX += kernelX[fy, fx] * r; gX += kernelX[fy, fx] * g; @@ -77,7 +81,11 @@ namespace ImageProcessorCore.Processors float green = gX; float blue = bX; - targetPixels[x, y] = new Color(red, green, blue); + Vector4 targetColor = targetPixels[x, y].ToVector4(); + T packed = default(T); + packed.PackVector(new Vector4(red, green, blue, targetColor.Z)); + targetPixels[x, y] = packed; + } this.OnRowProcessed(); } diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs index ce4aa0b2aa..4effae8b0e 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs @@ -9,17 +9,19 @@ namespace ImageProcessorCore.Processors /// Defines a filter that detects edges within an image using two /// one-dimensional matrices. /// - public abstract class EdgeDetector2DFilter : Convolution2DFilter, IEdgeDetectorFilter + public abstract class EdgeDetector2DFilter : Convolution2DFilter, IEdgeDetectorFilter + where T : IPackedVector + where TP : struct { /// - public bool Greyscale { get; set; } + public bool Grayscale { 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.Greyscale) + if (this.Grayscale) { - new GreyscaleBt709Processor().Apply(source, source, sourceRectangle); + new GrayscaleBt709Processor().Apply(source, source, sourceRectangle); } } } diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs index 7a18bb96b3..a82f0d4caf 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs @@ -9,17 +9,19 @@ namespace ImageProcessorCore.Processors /// Defines a filter that detects edges within an image using a single /// two dimensional matrix. /// - public abstract class EdgeDetectorFilter : ConvolutionFilter, IEdgeDetectorFilter + public abstract class EdgeDetectorFilter : ConvolutionFilter, IEdgeDetectorFilter + where T : IPackedVector + where TP : struct { /// - public bool Greyscale { get; set; } + public bool Grayscale { 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.Greyscale) + if (this.Grayscale) { - new GreyscaleBt709Processor().Apply(source, source, sourceRectangle); + new GrayscaleBt709Processor().Apply(source, source, sourceRectangle); } } } diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs index b90f0d2ea2..9d9485875b 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs @@ -8,12 +8,21 @@ namespace ImageProcessorCore.Processors /// /// Provides properties and methods allowing the detection of edges within an image. /// - public interface IEdgeDetectorFilter : IImageProcessor + public interface IEdgeDetectorFilter : IImageProcessor, IEdgeDetectorFilter + where T : IPackedVector + where TP : struct + { + } + + /// + /// Provides properties and methods allowing the detection of edges within an image. + /// + public interface IEdgeDetectorFilter { /// /// Gets or sets a value indicating whether to convert the - /// image to greyscale before performing edge detection. + /// image to Grayscale before performing edge detection. /// - bool Greyscale { get; set; } + bool Grayscale { get; set; } } } diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs index 5af8182412..59327467ee 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs @@ -9,7 +9,11 @@ namespace ImageProcessorCore.Processors /// The Kayyali operator filter. /// /// - public class KayyaliProcessor : EdgeDetector2DFilter + /// The pixel format. + /// The packed format. long, float. + public class KayyaliProcessor : EdgeDetector2DFilter + where T : IPackedVector + where TP : struct { /// public override float[,] KernelX => new float[,] diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KirschProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KirschProcessor.cs index 24a561572e..aba7e26d25 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KirschProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KirschProcessor.cs @@ -9,7 +9,11 @@ namespace ImageProcessorCore.Processors /// The Kirsch operator filter. /// /// - public class KirschProcessor : EdgeDetector2DFilter + /// The pixel format. + /// The packed format. long, float. + public class KirschProcessor : EdgeDetector2DFilter + where T : IPackedVector + where TP : struct { /// public override float[,] KernelX => new float[,] diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs index a66d692537..31829a4220 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs @@ -9,7 +9,11 @@ namespace ImageProcessorCore.Processors /// The Laplacian 3 x 3 operator filter. /// /// - public class Laplacian3X3Processor : EdgeDetectorFilter + /// The pixel format. + /// The packed format. long, float. + public class Laplacian3X3Processor : EdgeDetectorFilter + where T : IPackedVector + where TP : struct { /// public override float[,] KernelXY => new float[,] diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs index f2d3d885ee..d43876b56f 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs @@ -9,7 +9,11 @@ namespace ImageProcessorCore.Processors /// The Laplacian 5 x 5 operator filter. /// /// - public class Laplacian5X5Processor : EdgeDetectorFilter + /// The pixel format. + /// The packed format. long, float. + public class Laplacian5X5Processor : EdgeDetectorFilter + where T : IPackedVector + where TP : struct { /// public override float[,] KernelXY => new float[,] diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs index 0aae3020a9..e470136100 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs @@ -9,7 +9,11 @@ namespace ImageProcessorCore.Processors /// The Laplacian of Gaussian operator filter. /// /// - public class LaplacianOfGaussianProcessor : EdgeDetectorFilter + /// The pixel format. + /// The packed format. long, float. + public class LaplacianOfGaussianProcessor : EdgeDetectorFilter + where T : IPackedVector + where TP : struct { /// public override float[,] KernelXY => new float[,] diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/PrewittProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/PrewittProcessor.cs index 3a5bbe9860..4ce4c7675c 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/PrewittProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/PrewittProcessor.cs @@ -9,7 +9,11 @@ namespace ImageProcessorCore.Processors /// The Prewitt operator filter. /// /// - public class PrewittProcessor : EdgeDetector2DFilter + /// The pixel format. + /// The packed format. long, float. + public class PrewittProcessor : EdgeDetector2DFilter + where T : IPackedVector + where TP : struct { /// public override float[,] KernelX => new float[,] diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs index 0a61664184..b933280c60 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs @@ -9,7 +9,11 @@ namespace ImageProcessorCore.Processors /// The Roberts Cross operator filter. /// /// - public class RobertsCrossProcessor : EdgeDetector2DFilter + /// The pixel format. + /// The packed format. long, float. + public class RobertsCrossProcessor : EdgeDetector2DFilter + where T : IPackedVector + where TP : struct { /// public override float[,] KernelX => new float[,] diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/ScharrProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/ScharrProcessor.cs index 80308a1409..1dd4408a01 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/ScharrProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/ScharrProcessor.cs @@ -9,7 +9,11 @@ namespace ImageProcessorCore.Processors /// The Scharr operator filter. /// /// - public class ScharrProcessor : EdgeDetector2DFilter + /// The pixel format. + /// The packed format. long, float. + public class ScharrProcessor : EdgeDetector2DFilter + where T : IPackedVector + where TP : struct { /// public override float[,] KernelX => new float[,] diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs index 39f7d350d2..9459e2ed8d 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs @@ -9,7 +9,11 @@ namespace ImageProcessorCore.Processors /// The Sobel operator filter. /// /// - public class SobelProcessor : EdgeDetector2DFilter + /// The pixel format. + /// The packed format. long, float. + public class SobelProcessor : EdgeDetector2DFilter + where T : IPackedVector + where TP : struct { /// public override float[,] KernelX => new float[,] diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianBlurProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianBlurProcessor.cs index 6288ea963b..23d6d94378 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianBlurProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianBlurProcessor.cs @@ -10,7 +10,11 @@ namespace ImageProcessorCore.Processors /// /// Applies a Gaussian blur filter to the image. /// - public class GuassianBlurProcessor : Convolution2PassFilter + /// The pixel format. + /// The packed format. long, float. + public class GuassianBlurProcessor : Convolution2PassFilter + where T : IPackedVector + where TP : struct { /// /// The maximum size of the kernal in either direction. @@ -77,7 +81,7 @@ namespace ImageProcessorCore.Processors public override float[,] KernelY => this.kernelY; /// - 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.kernelY == null) { diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianSharpenProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianSharpenProcessor.cs index 9d70732d20..edfe594513 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianSharpenProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianSharpenProcessor.cs @@ -10,7 +10,11 @@ namespace ImageProcessorCore.Processors /// /// Applies a Gaussian sharpening filter to the image. /// - public class GuassianSharpenProcessor : Convolution2PassFilter + /// The pixel format. + /// The packed format. long, float. + public class GuassianSharpenProcessor : Convolution2PassFilter + where T : IPackedVector + where TP : struct { /// /// The maximum size of the kernal in either direction. @@ -79,7 +83,7 @@ namespace ImageProcessorCore.Processors public override float[,] KernelY => this.kernelY; /// - 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.kernelY == null) { diff --git a/src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs b/src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs index e046be6a9d..6215393810 100644 --- a/src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs @@ -12,12 +12,24 @@ namespace ImageProcessorCore.Processors /// /// Creates a glow effect on the image /// - public class GlowProcessor : ImageProcessor + /// The pixel format. + /// The packed format. long, float. + public class GlowProcessor : ImageProcessor + where T : IPackedVector + where TP : struct { + /// + /// Initializes a new instance of the class. + /// + public GlowProcessor() + { + this.GlowColor.PackVector(Color.White.ToVector4()); + } + /// /// Gets or sets the glow color to apply. /// - public Color Color { get; set; } = Color.White; + public T GlowColor { get; set; } /// /// Gets or sets the the x-radius. @@ -30,29 +42,34 @@ namespace ImageProcessorCore.Processors public float RadiusY { get; set; } /// - protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { int startX = sourceRectangle.X; int endX = sourceRectangle.Right; - Color glowColor = this.Color; + T glowColor = this.GlowColor; Vector2 centre = Rectangle.Center(targetRectangle).ToVector2(); float rX = this.RadiusX > 0 ? this.RadiusX : targetRectangle.Width / 2f; float rY = this.RadiusY > 0 ? this.RadiusY : targetRectangle.Height / 2f; float maxDistance = (float)Math.Sqrt(rX * rX + rY * rY); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { for (int x = startX; x < endX; x++) { + // TODO: Premultiply? float distance = Vector2.Distance(centre, new Vector2(x, y)); - Color sourceColor = sourcePixels[x, y]; - targetPixels[x, y] = Color.Lerp(glowColor, sourceColor, .5f * (distance / maxDistance)); + Vector4 sourceColor = sourcePixels[x, y].ToVector4(); + Vector4 result = Vector4.Lerp(glowColor.ToVector4(), sourceColor, .5f * (distance / maxDistance)); + T packed = default(T); + packed.PackVector(result); + targetPixels[x, y] = packed; } this.OnRowProcessed(); diff --git a/src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs b/src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs index 1f319b92b5..3d8465fa4c 100644 --- a/src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs @@ -9,12 +9,14 @@ namespace ImageProcessorCore.Processors using System.Threading.Tasks; /// - /// An to invert the colors of an . + /// An to invert the colors of an . /// - public class InvertProcessor : ImageProcessor + public class InvertProcessor : ImageProcessor + where T : IPackedVector + where TP : struct { /// - protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { int sourceY = sourceRectangle.Y; int sourceBottom = sourceRectangle.Bottom; @@ -22,21 +24,25 @@ namespace ImageProcessorCore.Processors int endX = sourceRectangle.Right; Vector3 inverseVector = Vector3.One; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { if (y >= sourceY && y < sourceBottom) { for (int x = startX; x < endX; x++) { - Color color = sourcePixels[x, y]; - Vector3 vector = inverseVector - color.ToVector3(); - targetPixels[x, y] = new Color(vector, color.A); + Vector4 color = sourcePixels[x, y].ToVector4(); + Vector3 vector = inverseVector - new Vector3(color.X, color.Y, color.Z); + + T packed = default(T); + packed.PackVector(new Vector4(vector, color.W)); + targetPixels[x, y] = packed; } this.OnRowProcessed(); @@ -46,3 +52,4 @@ namespace ImageProcessorCore.Processors } } } + diff --git a/src/ImageProcessorCore/Filters/Processors/PixelateProcessor.cs b/src/ImageProcessorCore/Filters/Processors/PixelateProcessor.cs index 4769799b5d..816ce2f42b 100644 --- a/src/ImageProcessorCore/Filters/Processors/PixelateProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/PixelateProcessor.cs @@ -5,20 +5,23 @@ namespace ImageProcessorCore.Processors { - using System; using System.Collections.Generic; using System.Threading.Tasks; /// - /// An to invert the colors of an . + /// An to invert the colors of an . /// - public class PixelateProcessor : ImageProcessor + /// The pixel format. + /// The packed format. long, float. + public class PixelateProcessor : ImageProcessor + where T : IPackedVector + where TP : struct { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The size of the pixels. Must be greater than 0. - /// + /// /// is less than 0 or equal to 0. /// public PixelateProcessor(int size) @@ -33,7 +36,7 @@ namespace ImageProcessorCore.Processors public int Value { get; } /// - protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { int sourceY = sourceRectangle.Y; int sourceBottom = sourceRectangle.Bottom; @@ -45,8 +48,8 @@ namespace ImageProcessorCore.Processors // Get the range on the y-plane to choose from. IEnumerable range = EnumerableExtensions.SteppedRange(startY, i => i < endY, size); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.ForEach( range, @@ -73,7 +76,7 @@ namespace ImageProcessorCore.Processors // Get the pixel color in the centre of the soon to be pixelated area. // ReSharper disable AccessToDisposedClosure - Color pixel = sourcePixels[x + offsetX, y + offsetY]; + T pixel = sourcePixels[x + offsetX, y + offsetY]; // For each pixel in the pixelate size, set it to the centre color. for (int l = y; l < y + size && l < sourceBottom; l++) diff --git a/src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs b/src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs index 1fd4630ed3..7e7f02317e 100644 --- a/src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs @@ -12,12 +12,24 @@ namespace ImageProcessorCore.Processors /// /// Creates a vignette effect on the image /// - public class VignetteProcessor : ImageProcessor + /// The pixel format. + /// The packed format. long, float. + public class VignetteProcessor : ImageProcessor + where T : IPackedVector + where TP : struct { + /// + /// Initializes a new instance of the class. + /// + public VignetteProcessor() + { + this.VignetteColor.PackVector(Color.Black.ToVector4()); + } + /// /// Gets or sets the vignette color to apply. /// - public Color Color { get; set; } = Color.Black; + public T VignetteColor { get; set; } /// /// Gets or sets the the x-radius. @@ -30,29 +42,34 @@ namespace ImageProcessorCore.Processors public float RadiusY { get; set; } /// - protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { int startX = sourceRectangle.X; int endX = sourceRectangle.Right; - Color vignetteColor = this.Color; + T vignetteColor = this.VignetteColor; Vector2 centre = Rectangle.Center(targetRectangle).ToVector2(); float rX = this.RadiusX > 0 ? this.RadiusX : targetRectangle.Width / 2f; float rY = this.RadiusY > 0 ? this.RadiusY : targetRectangle.Height / 2f; float maxDistance = (float)Math.Sqrt(rX * rX + rY * rY); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { for (int x = startX; x < endX; x++) { float distance = Vector2.Distance(centre, new Vector2(x, y)); - Color sourceColor = sourcePixels[x, y]; - targetPixels[x, y] = Color.Lerp(vignetteColor, sourceColor, 1 - .9f * distance / maxDistance); + Vector4 sourceColor = sourcePixels[x, y].ToVector4(); + Vector4 result = Vector4.Lerp(vignetteColor.ToVector4(), sourceColor, 1 - .9f * (distance / maxDistance)); + T packed = default(T); + packed.PackVector(result); + targetPixels[x, y] = packed; + } this.OnRowProcessed(); }); diff --git a/src/ImageProcessorCore/Filters/Saturation.cs b/src/ImageProcessorCore/Filters/Saturation.cs index 93c69fe3e7..cdd2a1bc03 100644 --- a/src/ImageProcessorCore/Filters/Saturation.cs +++ b/src/ImageProcessorCore/Filters/Saturation.cs @@ -1,25 +1,29 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Alters the saturation component of the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The new saturation of the image. Must be between -100 and 100. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Saturation(this Image source, int amount, ProgressEventHandler progressHandler = null) + /// The . + public static Image Saturation(this Image source, int amount, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { return Saturation(source, amount, source.Bounds, progressHandler); } @@ -27,16 +31,20 @@ namespace ImageProcessorCore /// /// Alters the saturation component of the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The new saturation of the image. Must be between -100 and 100. /// /// The structure that specifies the portion of the image object to alter. /// /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Saturation(this Image source, int amount, Rectangle rectangle, ProgressEventHandler progressHandler = null) + /// The . + public static Image Saturation(this Image source, int amount, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - SaturationProcessor processor = new SaturationProcessor(amount); + SaturationProcessor processor = new SaturationProcessor(amount); processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Filters/Sepia.cs b/src/ImageProcessorCore/Filters/Sepia.cs index 27d19b3194..6a007d3b3b 100644 --- a/src/ImageProcessorCore/Filters/Sepia.cs +++ b/src/ImageProcessorCore/Filters/Sepia.cs @@ -1,24 +1,28 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Applies sepia toning to the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// A delegate which is called as progress is made processing the image. /// The . - public static Image Sepia(this Image source, ProgressEventHandler progressHandler = null) + public static Image Sepia(this Image source, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { return Sepia(source, source.Bounds, progressHandler); } @@ -26,15 +30,19 @@ namespace ImageProcessorCore /// /// Applies sepia toning to the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// /// The structure that specifies the portion of the image object to alter. /// /// A delegate which is called as progress is made processing the image. /// The . - public static Image Sepia(this Image source, Rectangle rectangle, ProgressEventHandler progressHandler = null) + public static Image Sepia(this Image source, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - SepiaProcessor processor = new SepiaProcessor(); + SepiaProcessor processor = new SepiaProcessor(); processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs index 03d45f1122..bd05cc7e86 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs @@ -69,13 +69,14 @@ namespace ImageProcessorCore.Formats 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) + /// + public void Decode(Image image, Stream stream) + where T : IPackedVector + where TP : struct { + Guard.NotNull(image, "image"); + Guard.NotNull(stream, "stream"); + new BmpDecoderCore().Decode(image, stream); } } diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs index da92e6a63c..c6704bdb07 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs @@ -49,16 +49,20 @@ namespace ImageProcessorCore.Formats /// Decodes the image from the specified this._stream and sets /// the data to image. /// + /// The pixel format. + /// The packed format. long, float. /// The image, where the data should be set to. /// Cannot be null (Nothing in Visual Basic). - /// The this._stream, where the image should be + /// The 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) + public void Decode(Image image, Stream stream) + where T : IPackedVector + where TP : struct { this.currentStream = stream; @@ -110,14 +114,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]; + T[] imageData = new T[this.infoHeader.Width * this.infoHeader.Height]; switch (this.infoHeader.Compression) { @@ -130,25 +134,19 @@ namespace ImageProcessorCore.Formats if (this.infoHeader.BitsPerPixel == 32) { - this.ReadRgb32(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted); + 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); + 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); + 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); + this.ReadRgbPalette(imageData, palette, this.infoHeader.Width, this.infoHeader.Height, this.infoHeader.BitsPerPixel, inverted); } break; @@ -169,6 +167,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 +188,17 @@ namespace ImageProcessorCore.Formats /// /// Reads the color palette from the stream. /// - /// The image data to assign the palette to. + /// The pixel format. + /// The packed format. long, float. + /// 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(T[] imageData, byte[] colors, int width, int height, int bits, bool inverted) + where T : IPackedVector + where TP : struct { // Pixels per byte (bits per pixel) int ppb = 8 / bits; @@ -218,6 +222,7 @@ namespace ImageProcessorCore.Formats Parallel.For( 0, height, + Bootstrapper.Instance.ParallelOptions, y => { int rowOffset = y * (arrayWidth + alignment); @@ -234,14 +239,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 order. + T packed = default(T); + packed.PackBytes(colors[colorIndex + 2], colors[colorIndex + 1], colors[colorIndex], 255); + imageData[arrayOffset] = packed; } } }); @@ -250,14 +253,19 @@ namespace ImageProcessorCore.Formats /// /// Reads the 16 bit color palette from the stream /// - /// The image data to assign the palette to. + /// The pixel format. + /// The packed format. long, float. + /// 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(T[] imageData, int width, int height, bool inverted) + where T : IPackedVector + where TP : struct { // 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); @@ -265,6 +273,7 @@ namespace ImageProcessorCore.Formats Parallel.For( 0, height, + Bootstrapper.Instance.ParallelOptions, y => { int rowOffset = y * ((width * 2) + alignment); @@ -278,17 +287,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 order. + T packed = default(T); + packed.PackBytes(r, g, b, 255); + imageData[arrayOffset] = packed; } }); } @@ -296,10 +304,15 @@ namespace ImageProcessorCore.Formats /// /// Reads the 24 bit color palette from the stream /// - /// The image data to assign the palette to. + /// The pixel format. + /// The packed format. long, float. + /// 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(T[] imageData, int width, int height, bool inverted) + where T : IPackedVector + where TP : struct { int alignment; byte[] data = this.GetImageArray(width, height, 3, out alignment); @@ -307,6 +320,7 @@ namespace ImageProcessorCore.Formats Parallel.For( 0, height, + Bootstrapper.Instance.ParallelOptions, y => { int rowOffset = y * ((width * 3) + alignment); @@ -317,14 +331,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. + T packed = default(T); + packed.PackBytes(data[offset + 2], data[offset + 1], data[offset], 255); + imageData[arrayOffset] = packed; } }); } @@ -332,10 +345,15 @@ namespace ImageProcessorCore.Formats /// /// Reads the 32 bit color palette from the stream /// - /// The image data to assign the palette to. + /// The pixel format. + /// The packed format. long, float. + /// 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(T[] imageData, int width, int height, bool inverted) + where T : IPackedVector + where TP : struct { int alignment; byte[] data = this.GetImageArray(width, height, 4, out alignment); @@ -343,6 +361,7 @@ namespace ImageProcessorCore.Formats Parallel.For( 0, height, + Bootstrapper.Instance.ParallelOptions, y => { int rowOffset = y * ((width * 4) + alignment); @@ -353,14 +372,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. + T packed = default(T); + packed.PackBytes(data[offset + 2], data[offset + 1], data[offset], data[offset + 3]); + imageData[arrayOffset] = packed; } }); } diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs index 944154df21..08f73b181b 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs @@ -43,7 +43,9 @@ namespace ImageProcessorCore.Formats } /// - public void Encode(ImageBase image, Stream stream) + public void Encode(ImageBase image, Stream stream) + where T : IPackedVector + where TP : struct { 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..f193aa598c 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs @@ -8,12 +8,11 @@ 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. /// - /// The encoder can currently only write 24-bit rgb images to streams. internal sealed class BmpEncoderCore { /// @@ -22,12 +21,16 @@ 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 pixel format. + /// The packed format. long, float. + /// 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 T : IPackedVector + where TP : struct { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -36,6 +39,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 +121,16 @@ namespace ImageProcessorCore.Formats /// /// Writes the pixel data to the binary stream. /// - /// + /// The pixel format. + /// The packed format. long, float./// /// 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 T : IPackedVector + where TP : struct { // TODO: Add more compression formats. int amount = (image.Width * (int)this.bmpBitsPerPixel) % 4; @@ -132,7 +139,7 @@ namespace ImageProcessorCore.Formats amount = 4 - amount; } - using (PixelAccessor pixels = image.Lock()) + using (IPixelAccessor pixels = image.Lock()) { switch (this.bmpBitsPerPixel) { @@ -150,22 +157,22 @@ namespace ImageProcessorCore.Formats /// /// Writes the 32bit color palette to the stream. /// + /// The pixel format. + /// The packed format. long, float. /// 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) + where T : IPackedVector + where TP : struct { 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(new[] { bytes[2], bytes[1], bytes[0], bytes[3] }); } // Pad @@ -179,22 +186,21 @@ namespace ImageProcessorCore.Formats /// /// Writes the 24bit color palette to the stream. /// - /// The containing the stream to write to. - /// The containing pixel data. + /// The pixel format. + /// The packed format. long, float./// The containing the stream to write to. + /// 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) + where T : IPackedVector + where TP : struct { 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 }); + // Convert back to b-> g-> r order. + byte[] bytes = pixels[x, y].ToBytes(); + writer.Write(new[] { bytes[2], bytes[1], bytes[0] }); } // Pad diff --git a/src/ImageProcessorCore/Formats/Gif/GifDecoder.cs b/src/ImageProcessorCore/Formats/Gif/GifDecoder.cs index 1abbcb2fb7..3f73510ca7 100644 --- a/src/ImageProcessorCore/Formats/Gif/GifDecoder.cs +++ b/src/ImageProcessorCore/Formats/Gif/GifDecoder.cs @@ -54,14 +54,12 @@ namespace ImageProcessorCore.Formats header[5] == 0x61; // a } - /// - /// Decodes the image from the specified stream to the . - /// - /// The to decode to. - /// The containing image data. - public void Decode(Image image, Stream stream) + /// + public void Decode(Image image, Stream stream) + where T : IPackedVector + where TP : struct { - new GifDecoderCore().Decode(image, stream); + new GifDecoderCore().Decode(image, stream); } } } diff --git a/src/ImageProcessorCore/Formats/Gif/GifDecoderCore.cs b/src/ImageProcessorCore/Formats/Gif/GifDecoderCore.cs index 693f974c7a..f431d3a0a3 100644 --- a/src/ImageProcessorCore/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageProcessorCore/Formats/Gif/GifDecoderCore.cs @@ -11,12 +11,16 @@ namespace ImageProcessorCore.Formats /// /// Performs the gif decoding operation. /// - internal class GifDecoderCore + /// The pixel format. + /// The packed format. long, float. + internal class GifDecoderCore + where T : IPackedVector + where TP : struct { /// /// The image to decode the information to. /// - private Image decodedImage; + private Image decodedImage; /// /// The currently loaded stream. @@ -31,7 +35,7 @@ namespace ImageProcessorCore.Formats /// /// The current frame. /// - private float[] currentFrame; + private T[] currentFrame; /// /// The logical screen descriptor. @@ -48,7 +52,7 @@ namespace ImageProcessorCore.Formats /// /// The image to decode to. /// The stream containing image data. - public void Decode(Image image, Stream stream) + public void Decode(Image image, Stream stream) { this.decodedImage = image; @@ -175,10 +179,10 @@ namespace ImageProcessorCore.Formats $"Invalid gif colormap size '{this.logicalScreenDescriptor.GlobalColorTableSize}'"); } - if (this.logicalScreenDescriptor.Width > ImageBase.MaxWidth || this.logicalScreenDescriptor.Height > ImageBase.MaxHeight) + if (this.logicalScreenDescriptor.Width > this.decodedImage.MaxWidth || this.logicalScreenDescriptor.Height > this.decodedImage.MaxHeight) { throw new ArgumentOutOfRangeException( - $"The input gif '{this.logicalScreenDescriptor.Width}x{this.logicalScreenDescriptor.Height}' is bigger then the max allowed size '{ImageBase.MaxWidth}x{ImageBase.MaxHeight}'"); + $"The input gif '{this.logicalScreenDescriptor.Width}x{this.logicalScreenDescriptor.Height}' is bigger then the max allowed size '{this.decodedImage.MaxWidth}x{this.decodedImage.MaxHeight}'"); } } @@ -288,15 +292,15 @@ namespace ImageProcessorCore.Formats if (this.currentFrame == null) { - this.currentFrame = new float[imageWidth * imageHeight * 4]; + this.currentFrame = new T[imageWidth * imageHeight]; } - float[] lastFrame = null; + T[] lastFrame = null; if (this.graphicsControlExtension != null && this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious) { - lastFrame = new float[imageWidth * imageHeight * 4]; + lastFrame = new T[imageWidth * imageHeight]; Array.Copy(this.currentFrame, lastFrame, lastFrame.Length); } @@ -345,33 +349,30 @@ namespace ImageProcessorCore.Formats for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++) { - offset = ((writeY * imageWidth) + x) * 4; + offset = (writeY * imageWidth) + x; int index = indices[i]; if (this.graphicsControlExtension == null || this.graphicsControlExtension.TransparencyFlag == false || this.graphicsControlExtension.TransparencyIndex != index) { - // We divide by 255 as we will store the colors in our floating point format. // Stored in r-> g-> b-> a order. - // Gifs don't store alpha transparency so we don't need to convert to - // premultiplied. int indexOffset = index * 3; - this.currentFrame[offset + 0] = colorTable[indexOffset] / 255f; // r - this.currentFrame[offset + 1] = colorTable[indexOffset + 1] / 255f; // g - this.currentFrame[offset + 2] = colorTable[indexOffset + 2] / 255f; // b - this.currentFrame[offset + 3] = 1; // a + + T pixel = default(T); + pixel.PackBytes(colorTable[indexOffset], colorTable[indexOffset + 1], colorTable[indexOffset + 2], 255); + this.currentFrame[offset] = pixel; } i++; } } - float[] pixels = new float[imageWidth * imageHeight * 4]; + T[] pixels = new T[imageWidth * imageHeight]; Array.Copy(this.currentFrame, pixels, pixels.Length); - ImageBase currentImage; + ImageBase currentImage; if (this.decodedImage.Pixels == null) { @@ -386,7 +387,7 @@ namespace ImageProcessorCore.Formats } else { - ImageFrame frame = new ImageFrame(); + ImageFrame frame = new ImageFrame(); currentImage = frame; currentImage.SetPixels(imageWidth, imageHeight, pixels); @@ -408,13 +409,10 @@ namespace ImageProcessorCore.Formats { for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++) { - offset = ((y * imageWidth) + x) * 4; + offset = (y * imageWidth) + x; // Stored in r-> g-> b-> a order. - this.currentFrame[offset] = 0; - this.currentFrame[offset + 1] = 0; - this.currentFrame[offset + 2] = 0; - this.currentFrame[offset + 3] = 0; + this.currentFrame[offset] = default(T); } } } diff --git a/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs b/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs index 7a1c81c2b7..4ece1c769c 100644 --- a/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs +++ b/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs @@ -47,7 +47,9 @@ namespace ImageProcessorCore.Formats } /// - public void Encode(ImageBase image, Stream stream) + public void Encode(ImageBase image, Stream stream) + where T : IPackedVector + where TP : struct { GifEncoderCore encoder = new GifEncoderCore { diff --git a/src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs b/src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs index c4846c44f5..9ac7c2d584 100644 --- a/src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs @@ -10,8 +10,8 @@ namespace ImageProcessorCore.Formats using System.Linq; using System.Threading.Tasks; - using ImageProcessorCore.IO; - using ImageProcessorCore.Quantizers; + using IO; + using Quantizers; /// /// Performs the gif encoding operation. @@ -40,20 +40,24 @@ namespace ImageProcessorCore.Formats public IQuantizer Quantizer { get; set; } /// - /// Encodes the image to the specified stream from the . + /// Encodes the image to the specified stream from the . /// - /// The to encode from. + /// The pixel format. + /// The packed format. long, float. + /// The to encode from. /// The to encode the image data to. - public void Encode(ImageBase imageBase, Stream stream) + public void Encode(ImageBase imageBase, Stream stream) + where T : IPackedVector + where TP : struct { Guard.NotNull(imageBase, nameof(imageBase)); Guard.NotNull(stream, nameof(stream)); - Image image = (Image)imageBase; + Image image = (Image)imageBase; if (this.Quantizer == null) { - this.Quantizer = new OctreeQuantizer { Threshold = this.Threshold }; + this.Quantizer = new OctreeQuantizer { Threshold = this.Threshold }; } // Do not use IDisposable pattern here as we want to preserve the stream. @@ -67,7 +71,7 @@ namespace ImageProcessorCore.Formats this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(this.Quality); // Quantize the image returning a palette. - QuantizedImage quantized = this.Quantizer.Quantize(image, this.Quality); + QuantizedImage quantized = ((IQuantizer)this.Quantizer).Quantize(image, this.Quality); // Write the header. this.WriteHeader(writer); @@ -85,9 +89,9 @@ namespace ImageProcessorCore.Formats if (image.Frames.Any()) { this.WriteApplicationExtension(writer, image.RepeatCount, image.Frames.Count); - foreach (ImageFrame frame in image.Frames) + foreach (ImageFrame frame in image.Frames) { - QuantizedImage quantizedFrame = this.Quantizer.Quantize(frame, this.Quality); + QuantizedImage quantizedFrame = ((IQuantizer)this.Quantizer).Quantize(frame, this.Quality); this.WriteGraphicalControlExtension(frame, writer, quantizedFrame.TransparentIndex); this.WriteImageDescriptor(frame, writer); this.WriteColorTable(quantizedFrame, writer); @@ -111,10 +115,14 @@ namespace ImageProcessorCore.Formats /// /// Writes the logical screen descriptor to the stream. /// + /// The pixel format. + /// The packed format. long, float. /// The image to encode. /// The writer to write to the stream with. /// The transparency index to set the default backgound index to. - private void WriteLogicalScreenDescriptor(Image image, EndianBinaryWriter writer, int tranparencyIndex) + private void WriteLogicalScreenDescriptor(Image image, EndianBinaryWriter writer, int tranparencyIndex) + where T : IPackedVector + where TP : struct { GifLogicalScreenDescriptor descriptor = new GifLogicalScreenDescriptor { @@ -180,10 +188,14 @@ namespace ImageProcessorCore.Formats /// /// Writes the graphics control extension to the stream. /// - /// The to encode. + /// The pixel format. + /// The packed format. long, float. + /// The to encode. /// The stream to write to. /// The index of the color in the color palette to make transparent. - private void WriteGraphicalControlExtension(ImageBase image, EndianBinaryWriter writer, int transparencyIndex) + private void WriteGraphicalControlExtension(ImageBase image, EndianBinaryWriter writer, int transparencyIndex) + where T : IPackedVector + where TP : struct { // TODO: Check transparency logic. bool hasTransparent = transparencyIndex > -1; @@ -224,9 +236,13 @@ namespace ImageProcessorCore.Formats /// /// Writes the image descriptor to the stream. /// - /// The to be encoded. + /// The pixel format. + /// The packed format. long, float. + /// The to be encoded. /// The stream to write to. - private void WriteImageDescriptor(ImageBase image, EndianBinaryWriter writer) + private void WriteImageDescriptor(ImageBase image, EndianBinaryWriter writer) + where T : IPackedVector + where TP : struct { writer.Write(GifConstants.ImageDescriptorLabel); // 2c // TODO: Can we capture this? @@ -247,12 +263,16 @@ namespace ImageProcessorCore.Formats /// /// Writes the color table to the stream. /// - /// The to encode. + /// The pixel format. + /// The packed format. long, float. + /// The to encode. /// The writer to write to the stream with. - private void WriteColorTable(QuantizedImage image, EndianBinaryWriter writer) + private void WriteColorTable(QuantizedImage image, EndianBinaryWriter writer) + where T : IPackedVector + where TP : struct { // Grab the palette and write it to the stream. - Bgra32[] palette = image.Palette; + T[] palette = image.Palette; int pixelCount = palette.Length; // Get max colors for bit depth. @@ -263,11 +283,11 @@ namespace ImageProcessorCore.Formats i => { int offset = i * 3; - Bgra32 color = palette[i]; + byte[] color = palette[i].ToBytes(); - colorTable[offset] = color.R; - colorTable[offset + 1] = color.G; - colorTable[offset + 2] = color.B; + colorTable[offset] = color[0]; + colorTable[offset + 1] = color[1]; + colorTable[offset + 2] = color[2]; }); writer.Write(colorTable, 0, colorTableLength); @@ -276,9 +296,13 @@ namespace ImageProcessorCore.Formats /// /// Writes the image pixel data to the stream. /// - /// The containing indexed pixels. + /// The pixel format. + /// The packed format. long, float. + /// The containing indexed pixels. /// The stream to write to. - private void WriteImageData(QuantizedImage image, EndianBinaryWriter writer) + private void WriteImageData(QuantizedImage image, EndianBinaryWriter writer) + where T : IPackedVector + where TP : struct { byte[] indexedPixels = image.Pixels; diff --git a/src/ImageProcessorCore/Formats/IImageDecoder.cs b/src/ImageProcessorCore/Formats/IImageDecoder.cs index 761f3a68a7..8f9050905b 100644 --- a/src/ImageProcessorCore/Formats/IImageDecoder.cs +++ b/src/ImageProcessorCore/Formats/IImageDecoder.cs @@ -39,10 +39,14 @@ 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 pixel format. + /// The packed format. long, float. + /// The to decode to. /// The containing image data. - void Decode(Image image, Stream stream); + void Decode(Image image, Stream stream) + where T : IPackedVector + where TP : struct; } } diff --git a/src/ImageProcessorCore/Formats/IImageEncoder.cs b/src/ImageProcessorCore/Formats/IImageEncoder.cs index 4acceff996..2cdd78792c 100644 --- a/src/ImageProcessorCore/Formats/IImageEncoder.cs +++ b/src/ImageProcessorCore/Formats/IImageEncoder.cs @@ -1,12 +1,7 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. +// +// 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 { @@ -43,10 +38,14 @@ 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 pixel format. + /// The packed format. long, float. + /// The to encode from. /// The to encode the image data to. - void Encode(ImageBase image, Stream stream); + void Encode(ImageBase image, Stream stream) + where T : IPackedVector + where TP : struct; } } diff --git a/src/ImageProcessorCore/Formats/Jpg/Block.cs b/src/ImageProcessorCore/Formats/Jpg/Block.cs index 655471a07d..35aa10f181 100644 --- a/src/ImageProcessorCore/Formats/Jpg/Block.cs +++ b/src/ImageProcessorCore/Formats/Jpg/Block.cs @@ -1,19 +1,44 @@ -namespace ImageProcessorCore.Formats +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats { + /// + /// Represents an 8x8 block of coefficients to transform and encode. + /// internal class Block { - public const int blockSize = 64; - private int[] _data; + /// + /// Gets the size of the block. + /// + public const int BlockSize = 64; + + /// + /// The array of block data. + /// + private readonly int[] data; + /// + /// Initializes a new instance of the class. + /// public Block() { - _data = new int[blockSize]; + this.data = new int[BlockSize]; } - public int this[int idx] + /// + /// Gets the pixel data at the given block index. + /// + /// The index of the data to return. + /// + /// The . + /// + public int this[int index] { - get { return _data[idx]; } - set { _data[idx] = value; } + get { return this.data[index]; } + set { this.data[index] = value; } } } } diff --git a/src/ImageProcessorCore/Formats/Jpg/FDCT.cs b/src/ImageProcessorCore/Formats/Jpg/FDCT.cs index a6073f9c79..e51ea64151 100644 --- a/src/ImageProcessorCore/Formats/Jpg/FDCT.cs +++ b/src/ImageProcessorCore/Formats/Jpg/FDCT.cs @@ -5,9 +5,14 @@ namespace ImageProcessorCore.Formats { + /// + /// Performs a fast, forward descrete cosine transform against the given block + /// decomposing it into 64 orthogonal basis signals. + /// internal class FDCT { // Trigonometric constants in 13-bit fixed point format. + // TODO: Rename and describe these. private const int fix_0_298631336 = 2446; private const int fix_0_390180644 = 3196; private const int fix_0_541196100 = 4433; @@ -20,27 +25,42 @@ namespace ImageProcessorCore.Formats private const int fix_2_053119869 = 16819; private const int fix_2_562915447 = 20995; private const int fix_3_072711026 = 25172; - private const int constBits = 13; - private const int pass1Bits = 2; - private const int centerJSample = 128; - // fdct performs a forward DCT on an 8x8 block of coefficients, including a - // level shift. - public static void Transform(Block b) + /// + /// The number of bits + /// + private const int Bits = 13; + + /// + /// The number of bits to shift by on the first pass. + /// + private const int Pass1Bits = 2; + + /// + /// The value to shift by + /// + private const int CenterJSample = 128; + + /// + /// Performs a forward DCT on an 8x8 block of coefficients, including a + /// level shift. + /// + /// The block. + public static void Transform(Block block) { // Pass 1: process rows. for (int y = 0; y < 8; y++) { int y8 = y * 8; - int x0 = b[y8 + 0]; - int x1 = b[y8 + 1]; - int x2 = b[y8 + 2]; - int x3 = b[y8 + 3]; - int x4 = b[y8 + 4]; - int x5 = b[y8 + 5]; - int x6 = b[y8 + 6]; - int x7 = b[y8 + 7]; + int x0 = block[y8]; + int x1 = block[y8 + 1]; + int x2 = block[y8 + 2]; + int x3 = block[y8 + 3]; + int x4 = block[y8 + 4]; + int x5 = block[y8 + 5]; + int x6 = block[y8 + 6]; + int x7 = block[y8 + 7]; int tmp0 = x0 + x7; int tmp1 = x1 + x6; @@ -57,19 +77,19 @@ namespace ImageProcessorCore.Formats tmp2 = x2 - x5; tmp3 = x3 - x4; - b[y8] = (tmp10 + tmp11 - 8 * centerJSample) << pass1Bits; - b[y8 + 4] = (tmp10 - tmp11) << pass1Bits; + block[y8] = (tmp10 + tmp11 - (8 * CenterJSample)) << Pass1Bits; + block[y8 + 4] = (tmp10 - tmp11) << Pass1Bits; int z1 = (tmp12 + tmp13) * fix_0_541196100; - z1 += 1 << (constBits - pass1Bits - 1); - b[y8 + 2] = (z1 + tmp12 * fix_0_765366865) >> (constBits - pass1Bits); - b[y8 + 6] = (z1 - tmp13 * fix_1_847759065) >> (constBits - pass1Bits); + z1 += 1 << (Bits - Pass1Bits - 1); + block[y8 + 2] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits - Pass1Bits); + block[y8 + 6] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits - Pass1Bits); tmp10 = tmp0 + tmp3; tmp11 = tmp1 + tmp2; tmp12 = tmp0 + tmp2; tmp13 = tmp1 + tmp3; z1 = (tmp12 + tmp13) * fix_1_175875602; - z1 += 1 << (constBits - pass1Bits - 1); + z1 += 1 << (Bits - Pass1Bits - 1); tmp0 = tmp0 * fix_1_501321110; tmp1 = tmp1 * fix_3_072711026; tmp2 = tmp2 * fix_2_053119869; @@ -81,45 +101,45 @@ namespace ImageProcessorCore.Formats tmp12 += z1; tmp13 += z1; - b[y8 + 1] = (tmp0 + tmp10 + tmp12) >> (constBits - pass1Bits); - b[y8 + 3] = (tmp1 + tmp11 + tmp13) >> (constBits - pass1Bits); - b[y8 + 5] = (tmp2 + tmp11 + tmp12) >> (constBits - pass1Bits); - b[y8 + 7] = (tmp3 + tmp10 + tmp13) >> (constBits - pass1Bits); + block[y8 + 1] = (tmp0 + tmp10 + tmp12) >> (Bits - Pass1Bits); + block[y8 + 3] = (tmp1 + tmp11 + tmp13) >> (Bits - Pass1Bits); + block[y8 + 5] = (tmp2 + tmp11 + tmp12) >> (Bits - Pass1Bits); + block[y8 + 7] = (tmp3 + tmp10 + tmp13) >> (Bits - Pass1Bits); } // Pass 2: process columns. // We remove pass1Bits scaling, but leave results scaled up by an overall factor of 8. for (int x = 0; x < 8; x++) { - int tmp0 = b[x] + b[56 + x]; - int tmp1 = b[8 + x] + b[48 + x]; - int tmp2 = b[16 + x] + b[40 + x]; - int tmp3 = b[24 + x] + b[32 + x]; + int tmp0 = block[x] + block[56 + x]; + int tmp1 = block[8 + x] + block[48 + x]; + int tmp2 = block[16 + x] + block[40 + x]; + int tmp3 = block[24 + x] + block[32 + x]; - int tmp10 = tmp0 + tmp3 + (1 << (pass1Bits - 1)); + int tmp10 = tmp0 + tmp3 + (1 << (Pass1Bits - 1)); int tmp12 = tmp0 - tmp3; int tmp11 = tmp1 + tmp2; int tmp13 = tmp1 - tmp2; - tmp0 = b[x] - b[56 + x]; - tmp1 = b[8 + x] - b[48 + x]; - tmp2 = b[16 + x] - b[40 + x]; - tmp3 = b[24 + x] - b[32 + x]; + tmp0 = block[x] - block[56 + x]; + tmp1 = block[8 + x] - block[48 + x]; + tmp2 = block[16 + x] - block[40 + x]; + tmp3 = block[24 + x] - block[32 + x]; - b[x] = (tmp10 + tmp11) >> pass1Bits; - b[32 + x] = (tmp10 - tmp11) >> pass1Bits; + block[x] = (tmp10 + tmp11) >> Pass1Bits; + block[32 + x] = (tmp10 - tmp11) >> Pass1Bits; int z1 = (tmp12 + tmp13) * fix_0_541196100; - z1 += 1 << (constBits + pass1Bits - 1); - b[16 + x] = (z1 + tmp12 * fix_0_765366865) >> (constBits + pass1Bits); - b[48 + x] = (z1 - tmp13 * fix_1_847759065) >> (constBits + pass1Bits); + z1 += 1 << (Bits + Pass1Bits - 1); + block[16 + x] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits + Pass1Bits); + block[48 + x] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits + Pass1Bits); tmp10 = tmp0 + tmp3; tmp11 = tmp1 + tmp2; tmp12 = tmp0 + tmp2; tmp13 = tmp1 + tmp3; z1 = (tmp12 + tmp13) * fix_1_175875602; - z1 += 1 << (constBits + pass1Bits - 1); + z1 += 1 << (Bits + Pass1Bits - 1); tmp0 = tmp0 * fix_1_501321110; tmp1 = tmp1 * fix_3_072711026; tmp2 = tmp2 * fix_2_053119869; @@ -131,10 +151,10 @@ namespace ImageProcessorCore.Formats tmp12 += z1; tmp13 += z1; - b[8 + x] = (tmp0 + tmp10 + tmp12) >> (constBits + pass1Bits); - b[24 + x] = (tmp1 + tmp11 + tmp13) >> (constBits + pass1Bits); - b[40 + x] = (tmp2 + tmp11 + tmp12) >> (constBits + pass1Bits); - b[56 + x] = (tmp3 + tmp10 + tmp13) >> (constBits + pass1Bits); + block[8 + x] = (tmp0 + tmp10 + tmp12) >> (Bits + Pass1Bits); + block[24 + x] = (tmp1 + tmp11 + tmp13) >> (Bits + Pass1Bits); + block[40 + x] = (tmp2 + tmp11 + tmp12) >> (Bits + Pass1Bits); + block[56 + x] = (tmp3 + tmp10 + tmp13) >> (Bits + Pass1Bits); } } } diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs b/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs new file mode 100644 index 0000000000..4df086f292 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs @@ -0,0 +1,209 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Defines jpeg constants defined in the specification. + /// + internal static class JpegConstants + { + /// + /// The maximum allowable length in each dimension of a jpeg image. + /// + public const ushort MaxLength = 65535; + + /// + /// Represents high detail chroma horizontal subsampling. + /// + public static readonly byte[] ChromaFourFourFourHorizontal = { 0x11, 0x11, 0x11 }; + + /// + /// Represents high detail chroma vertical subsampling. + /// + public static readonly byte[] ChromaFourFourFourVertical = { 0x11, 0x11, 0x11 }; + + /// + /// Represents medium detail chroma horizontal subsampling. + /// + public static readonly byte[] ChromaFourTwoTwoHorizontal = { 0x22, 0x11, 0x11 }; + + /// + /// Represents medium detail chroma vertical subsampling. + /// + public static readonly byte[] ChromaFourTwoTwoVertical = { 0x11, 0x11, 0x11 }; + + /// + /// Represents low detail chroma horizontal subsampling. + /// + public static readonly byte[] ChromaFourTwoZeroHorizontal = { 0x22, 0x11, 0x11 }; + + /// + /// Represents low detail chroma vertical subsampling. + /// + public static readonly byte[] ChromaFourTwoZeroVertical = { 0x22, 0x11, 0x11 }; + + /// + /// Describes component ids for start of frame components. + /// + internal static class Components + { + /// + /// The YCbCr luminance component id. + /// + public const byte Y = 1; + + /// + /// The YCbCr chroma component id. + /// + public const byte Cb = 2; + + /// + /// The YCbCr chroma component id. + /// + public const byte Cr = 3; + + /// + /// The YIQ x coordinate component id. + /// + public const byte I = 4; + + /// + /// The YIQ y coordinate component id. + /// + public const byte Q = 5; + } + + /// + /// Describes common Jpeg markers + /// + internal static class Markers + { + /// + /// Marker prefix. Next byte is a marker. + /// + public const byte XFF = 0xff; + + /// + /// Start of Image + /// + public const byte SOI = 0xd8; + + /// + /// Start of Frame (baseline DCT) + /// + /// Indicates that this is a baseline DCT-based JPEG, and specifies the width, height, number of components, + /// and component subsampling (e.g., 4:2:0). + /// + /// + public const byte SOF0 = 0xc0; + + /// + /// Start Of Frame (Extended Sequential DCT) + /// + /// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components, + /// and component subsampling (e.g., 4:2:0). + /// + /// + public const byte SOF1 = 0xc1; + + /// + /// Start Of Frame (progressive DCT) + /// + /// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components, + /// and component subsampling (e.g., 4:2:0). + /// + /// + public const byte SOF2 = 0xc2; + + /// + /// Define Huffman Table(s) + /// + /// Specifies one or more Huffman tables. + /// + /// + public const byte DHT = 0xc4; + + /// + /// Define Quantization Table(s) + /// + /// Specifies one or more quantization tables. + /// + /// + public const byte DQT = 0xdb; + + /// + /// Define Restart Interval + /// + /// Specifies the interval between RSTn markers, in macroblocks. This marker is followed by two bytes + /// indicating the fixed size so it can be treated like any other variable size segment. + /// + /// + public const byte DRI = 0xdd; + + /// + /// Define First Restart + /// + /// Inserted every r macroblocks, where r is the restart interval set by a DRI marker. + /// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7. + /// + /// + public const byte RST0 = 0xd0; + + /// + /// Define Eigth Restart + /// + /// Inserted every r macroblocks, where r is the restart interval set by a DRI marker. + /// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7. + /// + /// + public const byte RST7 = 0xd7; + + /// + /// Start of Scan + /// + /// Begins a top-to-bottom scan of the image. In baseline DCT JPEG images, there is generally a single scan. + /// Progressive DCT JPEG images usually contain multiple scans. This marker specifies which slice of data it + /// will contain, and is immediately followed by entropy-coded data. + /// + /// + public const byte SOS = 0xda; + + /// + /// Comment + /// + /// Contains a text comment. + /// + /// + public const byte COM = 0xfe; + + /// + /// End of Image + /// + public const byte EOI = 0xd9; + + /// + /// Application specific marker for marking the jpeg format. + /// + /// + public const byte APP0 = 0xe0; + + /// + /// Application specific marker for marking where to store metadata. + /// + public const byte APP1 = 0xe1; + + /// + /// Application specific marker used by Adobe for storing encoding information for DCT filters. + /// + public const byte APP14 = 0xee; + + /// + /// Application specific marker used by GraphicConverter to store JPEG quality. + /// + public const byte APP15 = 0xef; + } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegDecoder.cs b/src/ImageProcessorCore/Formats/Jpg/JpegDecoder.cs index 34e0932dcb..84e46d5a4c 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegDecoder.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegDecoder.cs @@ -7,7 +7,6 @@ namespace ImageProcessorCore.Formats { using System; using System.IO; - using System.Threading.Tasks; /// /// Image decoder for generating an image out of a jpg stream. @@ -76,59 +75,16 @@ namespace ImageProcessorCore.Formats return isSupported; } - /// - /// Decodes the image from the specified stream and sets - /// the data to image. - /// - /// The image, where the data should be set to. - /// Cannot be null (Nothing in Visual Basic). - /// The stream, where the image should be - /// decoded from. Cannot be null (Nothing in Visual Basic). - /// - /// is null (Nothing in Visual Basic). - /// - or - - /// is null (Nothing in Visual Basic). - /// - public void Decode(Image image, Stream stream) + /// + public void Decode(Image image, Stream stream) + where T : IPackedVector + where TP : struct { Guard.NotNull(image, "image"); Guard.NotNull(stream, "stream"); JpegDecoderCore decoder = new JpegDecoderCore(); - decoder.Decode(stream, image, false); - - // TODO: When nComp is 3 we set the ImageBase pixels internally, Eventually we should - // do the same here - if (decoder.nComp == 1) - { - int pixelWidth = decoder.width; - int pixelHeight = decoder.height; - - float[] pixels = new float[pixelWidth * pixelHeight * 4]; - - Parallel.For( - 0, - pixelHeight, - y => - { - var yoff = decoder.img1.get_row_offset(y); - for (int x = 0; x < pixelWidth; x++) - { - int offset = ((y * pixelWidth) + x) * 4; - - pixels[offset + 0] = decoder.img1.pixels[yoff + x] / 255f; - pixels[offset + 1] = decoder.img1.pixels[yoff + x] / 255f; - pixels[offset + 2] = decoder.img1.pixels[yoff + x] / 255f; - pixels[offset + 3] = 1; - } - }); - - image.SetPixels(pixelWidth, pixelHeight, pixels); - } - else if (decoder.nComp != 3) - { - throw new NotSupportedException("JpegDecoder only supports RGB and Grayscale color spaces."); - } + decoder.Decode(image, stream, false); } /// diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id index 6cf3cee55e..de448dbb1c 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id +++ b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id @@ -1 +1 @@ -07d53b1f9ee1e867cfd8bb664c2cf3f31c0e8289 \ No newline at end of file +09cbf92c0d7d3e4659626b60663612836e1e90e7 \ No newline at end of file diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoder.cs b/src/ImageProcessorCore/Formats/Jpg/JpegEncoder.cs index 32c4d5f288..9a267f79ba 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegEncoder.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegEncoder.cs @@ -78,19 +78,18 @@ namespace ImageProcessorCore.Formats } /// - public void Encode(ImageBase image, Stream stream) + public void Encode(ImageBase image, Stream stream) + where T : IPackedVector + where TP : struct { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); - JpegEncoderCore encode = new JpegEncoderCore(); if (this.subsampleSet) { - encode.Encode(stream, image, this.Quality, this.Subsample); + encode.Encode(image, stream, this.Quality, this.Subsample); } else { - encode.Encode(stream, image, this.Quality, this.Quality >= 80 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); + encode.Encode(image, stream, this.Quality, this.Quality >= 80 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); } } } diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs index a1f49a0c83..041fcf9406 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs @@ -2,7 +2,6 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // - namespace ImageProcessorCore.Formats { using System; @@ -10,208 +9,171 @@ namespace ImageProcessorCore.Formats internal class JpegEncoderCore { - private const int sof0Marker = 0xc0; // Start Of Frame (Baseline). - private const int sof1Marker = 0xc1; // Start Of Frame (Extended Sequential). - private const int sof2Marker = 0xc2; // Start Of Frame (Progressive). - private const int dhtMarker = 0xc4; // Define Huffman Table. - private const int rst0Marker = 0xd0; // ReSTart (0). - private const int rst7Marker = 0xd7; // ReSTart (7). - private const int soiMarker = 0xd8; // Start Of Image. - private const int eoiMarker = 0xd9; // End Of Image. - private const int sosMarker = 0xda; // Start Of Scan. - private const int dqtMarker = 0xdb; // Define Quantization Table. - private const int driMarker = 0xdd; // Define Restart Interval. - private const int comMarker = 0xfe; // COMment. - // "APPlication specific" markers aren't part of the JPEG spec per se, - // but in practice, their use is described at - // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html - private const int app0Marker = 0xe0; - private const int app14Marker = 0xee; - private const int app15Marker = 0xef; - - // bitCount counts the number of bits needed to hold an integer. - private readonly byte[] bitCount = { - 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, }; - - // unzig maps from the zig-zag ordering to the natural ordering. For example, - // unzig[3] is the column and row of the fourth element in zig-zag order. The - // value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). - private static readonly int[] unzig = new int[] { - 0, 1, 8, 16, 9, 2, 3, 10, - 17, 24, 32, 25, 18, 11, 4, 5, - 12, 19, 26, 33, 40, 48, 41, 34, - 27, 20, 13, 6, 7, 14, 21, 28, - 35, 42, 49, 56, 57, 50, 43, 36, - 29, 22, 15, 23, 30, 37, 44, 51, - 58, 59, 52, 45, 38, 31, 39, 46, - 53, 60, 61, 54, 47, 55, 62, 63, + /// + /// Maps from the zig-zag ordering to the natural ordering. For example, + /// unzig[3] is the column and row of the fourth element in zig-zag order. The + /// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). + /// + private static readonly int[] Unzig = + { + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, + 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, + 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, + 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, }; - private const int nQuantIndex = 2; - private const int nHuffIndex = 4; - - private enum quantIndex - { - quantIndexLuminance = 0, - quantIndexChrominance = 1, - } + private const int NQuantIndex = 2; - private enum huffIndex + /// + /// Counts the number of bits needed to hold an integer. + /// + private readonly byte[] bitCount = { - huffIndexLuminanceDC = 0, - huffIndexLuminanceAC = 1, - huffIndexChrominanceDC = 2, - huffIndexChrominanceAC = 3, - } + 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, + }; - // unscaledQuant are the unscaled quantization tables in zig-zag order. Each - // encoder copies and scales the tables according to its quality parameter. - // The values are derived from section K.1 after converting from natural to - // zig-zag order. - private byte[,] unscaledQuant = new byte[,] { - // Luminance. + /// + /// The unscaled quantization tables in zig-zag order. Each + /// encoder copies and scales the tables according to its quality parameter. + /// The values are derived from section K.1 after converting from natural to + /// zig-zag order. + /// + private readonly byte[,] unscaledQuant = { { - 16, 11, 12, 14, 12, 10, 16, 14, - 13, 14, 18, 17, 16, 19, 24, 40, - 26, 24, 22, 22, 24, 49, 35, 37, - 29, 40, 58, 51, 61, 60, 57, 51, - 56, 55, 64, 72, 92, 78, 64, 68, - 87, 69, 55, 56, 80, 109, 81, 87, - 95, 98, 103, 104, 103, 62, 77, 113, - 121, 112, 100, 120, 92, 101, 103, 99, + // Luminance. + 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, 40, + 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, 57, 51, + 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, 109, 81, + 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, 100, 120, 92, + 101, 103, 99, }, - // Chrominance. { - 17, 18, 18, 24, 21, 24, 47, 26, - 26, 47, 99, 66, 56, 66, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - }, + // Chrominance. + 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + } }; - private class huffmanSpec - { - public huffmanSpec(byte[] c, byte[] v) { count = c; values = v; } - public byte[] count; - public byte[] values; - } - - // theHuffmanSpec is the Huffman encoding specifications. - // This encoder uses the same Huffman encoding for all images. - private huffmanSpec[] theHuffmanSpec = new huffmanSpec[] { + /// + /// The Huffman encoding specifications. + /// This encoder uses the same Huffman encoding for all images. + /// + private readonly HuffmanSpec[] theHuffmanSpec = { // Luminance DC. - new huffmanSpec( - new byte[] { 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }), - new huffmanSpec( - new byte[] { 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125 }, + new HuffmanSpec( new byte[] - { - 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, - 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, - 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, - 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, - 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, - 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, - 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, - 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, - 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, - 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, - 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, - 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, - 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, - 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, - 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, - 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, - 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, - 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, - 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, - 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, - 0xf9, 0xfa}), - new huffmanSpec( - new byte[] { 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 }, + { + 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 + }, new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }), + new HuffmanSpec( + new byte[] + { + 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125 + }, + new byte[] + { + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, + 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, + 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, + 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, + 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, + 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, + 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, + 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa + }), + new HuffmanSpec( + new byte[] + { + 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 + }, + new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }), // Chrominance AC. - new huffmanSpec( - new byte[] { 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119 }, + new HuffmanSpec( new byte[] - { - 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, - 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, - 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, - 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, - 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, - 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, - 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, - 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, - 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, - 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, - 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, - 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, - 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, - 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, - 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, - 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, - 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, - 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, - 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, - 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, - 0xf9, 0xfa, + { + 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119 + }, + new byte[] + { + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, + 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, + 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, + 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, + 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, + 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, + 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, + 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, + 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, + 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, + 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, + 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, }) }; - // huffmanLUT is a compiled look-up table representation of a huffmanSpec. - // Each value maps to a uint32 of which the 8 most significant bits hold the - // codeword size in bits and the 24 least significant bits hold the codeword. - // The maximum codeword size is 16 bits. - private class huffmanLUT + /// + /// A compiled look-up table representation of a huffmanSpec. + /// Each value maps to a uint32 of which the 8 most significant bits hold the + /// codeword size in bits and the 24 least significant bits hold the codeword. + /// The maximum codeword size is 16 bits. + /// + private class HuffmanLut { - public uint[] values; + public readonly uint[] Values; - public huffmanLUT(huffmanSpec s) + public HuffmanLut(HuffmanSpec s) { int maxValue = 0; - foreach (var v in s.values) + foreach (var v in s.Values) { - if (v > maxValue) - maxValue = v; + if (v > maxValue) maxValue = v; } - values = new uint[maxValue + 1]; + this.Values = new uint[maxValue + 1]; int code = 0; int k = 0; - for (int i = 0; i < s.count.Length; i++) + for (int i = 0; i < s.Count.Length; i++) { int nBits = (i + 1) << 24; - for (int j = 0; j < s.count[i]; j++) + for (int j = 0; j < s.Count[i]; j++) { - values[s.values[k]] = (uint)(nBits | code); + this.Values[s.Values[k]] = (uint)(nBits | code); code++; k++; } + code <<= 1; } } @@ -220,26 +182,53 @@ namespace ImageProcessorCore.Formats // w is the writer to write to. err is the first error encountered during // writing. All attempted writes after the first error become no-ops. private Stream outputStream; - // buf is a scratch buffer. - private byte[] buf = new byte[16]; - // bits and nBits are accumulated bits to write to w. - private uint bits, nBits; - // quant is the scaled quantization tables, in zig-zag order. - private byte[][] quant = new byte[nQuantIndex][];//[Block.blockSize]; - // theHuffmanLUT are compiled representations of theHuffmanSpec. - private huffmanLUT[] theHuffmanLUT = new huffmanLUT[4]; + + /// + /// A scratch buffer to reduce allocations. + /// + private readonly byte[] buffer = new byte[16]; + + /// + /// The accumulated bits to write to the stream. + /// + private uint bits; + + /// + /// The accumulated bits to write to the stream. + /// + private uint nBits; + + /// + /// The scaled quantization tables, in zig-zag order. + /// + private readonly byte[][] quant = new byte[NQuantIndex][]; // [Block.blockSize]; + + // The compiled representations of theHuffmanSpec. + private readonly HuffmanLut[] theHuffmanLUT = new HuffmanLut[4]; + + /// + /// The subsampling method to use. + /// private JpegSubsample subsample; - private void writeByte(byte b) + /// + /// Writes the given byte to the stream. + /// + /// + private void WriteByte(byte b) { var data = new byte[1]; data[0] = b; - outputStream.Write(data, 0, 1); + this.outputStream.Write(data, 0, 1); } - // emit emits the least significant nBits bits of bits to the bit-stream. - // The precondition is bits < 1< + /// Emits the least significant nBits bits of bits to the bit-stream. + /// The precondition is bits < 1<<nBits && nBits <= 16. + /// + /// + /// + private void Emit(uint bits, uint nBits) { nBits += this.nBits; bits <<= (int)(32 - nBits); @@ -247,158 +236,85 @@ namespace ImageProcessorCore.Formats while (nBits >= 8) { byte b = (byte)(bits >> 24); - writeByte(b); - if (b == 0xff) - writeByte(0x00); + this.WriteByte(b); + if (b == 0xff) this.WriteByte(0x00); bits <<= 8; nBits -= 8; } + this.bits = bits; this.nBits = nBits; } - // emitHuff emits the given value with the given Huffman encoder. - private void emitHuff(huffIndex h, int v) + /// + /// Emits the given value with the given Huffman encoder. + /// + /// The index of the Huffman encoder + /// The value to encode. + private void EmitHuff(HuffIndex index, int value) { - uint x = theHuffmanLUT[(int)h].values[v]; - emit(x & ((1 << 24) - 1), x >> 24); + uint x = this.theHuffmanLUT[(int)index].Values[value]; + this.Emit(x & ((1 << 24) - 1), x >> 24); } - // emitHuffRLE emits a run of runLength copies of value encoded with the given - // Huffman encoder. - private void emitHuffRLE(huffIndex h, int runLength, int v) + /// + /// Emits a run of runLength copies of value encoded with the given Huffman encoder. + /// + /// The index of the Huffman encoder + /// The number of copies to encode. + /// The value to encode. + private void EmitHuffRLE(HuffIndex index, int runLength, int value) { - int a = v; - int b = v; + int a = value; + int b = value; if (a < 0) { - a = -v; - b = v - 1; - } - uint nBits = 0; - if (a < 0x100) - nBits = bitCount[a]; - else - nBits = 8 + (uint)bitCount[a >> 8]; - - emitHuff(h, (int)((uint)(runLength << 4) | nBits)); - if (nBits > 0) emit((uint)b & (uint)((1 << ((int)nBits)) - 1), nBits); - } - - // writeMarkerHeader writes the header for a marker with the given length. - private void writeMarkerHeader(byte marker, int markerlen) - { - buf[0] = 0xff; - buf[1] = marker; - buf[2] = (byte)(markerlen >> 8); - buf[3] = (byte)(markerlen & 0xff); - outputStream.Write(buf, 0, 4); - } - - // writeDQT writes the Define Quantization Table marker. - private void writeDQT() - { - int markerlen = 2 + nQuantIndex * (1 + Block.blockSize); - writeMarkerHeader(dqtMarker, markerlen); - for (int i = 0; i < nQuantIndex; i++) - { - writeByte((byte)i); - outputStream.Write(quant[i], 0, quant[i].Length); + a = -value; + b = value - 1; } - } - - // writeSOF0 writes the Start Of Frame (Baseline) marker. - private void writeSOF0(int wid, int hei, int nComponent) - { - //"default" to 4:2:0 - byte[] subsamples = new byte[] { 0x22, 0x11, 0x11 }; - byte[] chroma = new byte[] { 0x00, 0x01, 0x01 }; - switch (subsample) - { - case JpegSubsample.Ratio444: - subsamples = new byte[] { 0x11, 0x11, 0x11 }; - break; - case JpegSubsample.Ratio420: - subsamples = new byte[] { 0x22, 0x11, 0x11 }; - break; - } - - int markerlen = 8 + 3 * nComponent; - writeMarkerHeader(sof0Marker, markerlen); - buf[0] = 8; // 8-bit color. - buf[1] = (byte)(hei >> 8); - buf[2] = (byte)(hei & 0xff); - buf[3] = (byte)(wid >> 8); - buf[4] = (byte)(wid & 0xff); - buf[5] = (byte)(nComponent); - if (nComponent == 1) + uint bt; + if (a < 0x100) { - buf[6] = 1; - // No subsampling for grayscale image. - buf[7] = 0x11; - buf[8] = 0x00; + bt = this.bitCount[a]; } else { - for (int i = 0; i < nComponent; i++) - { - buf[3 * i + 6] = (byte)(i + 1); - // We use 4:2:0 chroma subsampling. - buf[3 * i + 7] = subsamples[i]; - buf[3 * i + 8] = chroma[i]; - } - } - outputStream.Write(buf, 0, 3 * (nComponent - 1) + 9); - } - - // writeDHT writes the Define Huffman Table marker. - private void writeDHT(int nComponent) - { - byte[] headers = new byte[] { 0x00, 0x10, 0x01, 0x11 }; - int markerlen = 2; - huffmanSpec[] specs = theHuffmanSpec; - - if (nComponent == 1) - { - // Drop the Chrominance tables. - specs = new huffmanSpec[] { theHuffmanSpec[0], theHuffmanSpec[1] }; - } - - foreach (var s in specs) - { - markerlen += 1 + 16 + s.values.Length; + bt = 8 + (uint)this.bitCount[a >> 8]; } - writeMarkerHeader(dhtMarker, markerlen); - for (int i = 0; i < specs.Length; i++) + this.EmitHuff(index, (int)((uint)(runLength << 4) | bt)); + if (bt > 0) { - var s = specs[i]; - - writeByte(headers[i]); - outputStream.Write(s.count, 0, s.count.Length); - outputStream.Write(s.values, 0, s.values.Length); + this.Emit((uint)b & (uint)((1 << ((int)bt)) - 1), bt); } } - // writeBlock writes a block of pixel data using the given quantization table, - // returning the post-quantized DC value of the DCT-transformed block. b is in - // natural (not zig-zag) order. - private int writeBlock(Block b, quantIndex q, int prevDC) + + /// + /// Writes a block of pixel data using the given quantization table, + /// returning the post-quantized DC value of the DCT-transformed block. + /// The block is in natural (not zig-zag) order. + /// + /// The block to write. + /// The quantization table index. + /// The previous DC value. + /// + private int WriteBlock(Block block, QuantIndex index, int prevDC) { - FDCT.Transform(b); + FDCT.Transform(block); // Emit the DC delta. - int dc = div(b[0], 8 * quant[(int)q][0]); - emitHuffRLE((huffIndex)(2 * (int)q + 0), 0, dc - prevDC); + int dc = Round(block[0], 8 * this.quant[(int)index][0]); + this.EmitHuffRLE((HuffIndex)(2 * (int)index + 0), 0, dc - prevDC); // Emit the AC components. - var h = (huffIndex)(2 * (int)q + 1); + var h = (HuffIndex)(2 * (int)index + 1); int runLength = 0; - for (int zig = 1; zig < Block.blockSize; zig++) + for (int zig = 1; zig < Block.BlockSize; zig++) { - int ac = div(b[unzig[zig]], 8 * quant[(int)q][zig]); + int ac = Round(block[Unzig[zig]], 8 * this.quant[(int)index][zig]); if (ac == 0) { @@ -408,22 +324,24 @@ namespace ImageProcessorCore.Formats { while (runLength > 15) { - emitHuff(h, 0xf0); + this.EmitHuff(h, 0xf0); runLength -= 16; } - emitHuffRLE(h, runLength, ac); + this.EmitHuffRLE(h, runLength, ac); runLength = 0; } } - if (runLength > 0) - emitHuff(h, 0x00); + + if (runLength > 0) this.EmitHuff(h, 0x00); return dc; } // toYCbCr converts the 8x8 region of m whose top-left corner is p to its // YCbCr values. - private void toYCbCr(PixelAccessor pixels, int x, int y, Block yBlock, Block cbBlock, Block crBlock) + private void ToYCbCr(IPixelAccessor pixels, int x, int y, Block yBlock, Block cbBlock, Block crBlock) + where T : IPackedVector + where TP : struct { int xmax = pixels.Width - 1; int ymax = pixels.Height - 1; @@ -431,7 +349,10 @@ namespace ImageProcessorCore.Formats { for (int i = 0; i < 8; i++) { - YCbCr color = pixels[Math.Min(x + i, xmax), Math.Min(y + j, ymax)]; + // Bytes are expected in r->g->b->a oder. + byte[] pixel = pixels[Math.Min(x + i, xmax), Math.Min(y + j, ymax)].ToBytes(); + + YCbCr color = new Color(pixel[0], pixel[1], pixel[2], pixel[3]); int index = (8 * j) + i; yBlock[index] = (int)color.Y; cbBlock[index] = (int)color.Cb; @@ -440,9 +361,13 @@ namespace ImageProcessorCore.Formats } } - // scale scales the 16x16 region represented by the 4 src blocks to the 8x8 - // dst block. - private void scale_16x16_8x8(Block dst, Block[] src) + /// + /// Scales the 16x16 region represented by the 4 src blocks to the 8x8 + /// dst block. + /// + /// The destination block array + /// The source block array. + private void Scale16X16_8X8(Block destination, Block[] source) { for (int i = 0; i < 4; i++) { @@ -452,59 +377,334 @@ namespace ImageProcessorCore.Formats for (int x = 0; x < 4; x++) { int j = 16 * y + 2 * x; - int sum = src[i][j] + src[i][j + 1] + src[i][j + 8] + src[i][j + 9]; - dst[8 * y + x + dstOff] = (sum + 2) / 4; + int sum = source[i][j] + source[i][j + 1] + source[i][j + 8] + source[i][j + 9]; + destination[8 * y + x + dstOff] = (sum + 2) / 4; } } } } - // sosHeaderY is the SOS marker "\xff\xda" followed by 8 bytes: - // - the marker length "\x00\x08", - // - the number of components "\x01", - // - component 1 uses DC table 0 and AC table 0 "\x01\x00", - // - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for - // sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) - // should be 0x00, 0x3f, 0x00<<4 | 0x00. - private readonly byte[] sosHeaderY = new byte[] { - 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, + // The SOS marker "\xff\xda" followed by 8 bytes: + // - the marker length "\x00\x08", + // - the number of components "\x01", + // - component 1 uses DC table 0 and AC table 0 "\x01\x00", + // - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for + // sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) + // should be 0x00, 0x3f, 0x00<<4 | 0x00. + private readonly byte[] SOSHeaderY = + { + JpegConstants.Markers.XFF, JpegConstants.Markers.SOS, + 0x00, 0x08, // Length (high byte, low byte), must be 6 + 2 * (number of components in scan) + 0x01, // Number of components in a scan, 1 + 0x01, // Component Id Y + 0x00, // DC/AC Huffman table + 0x00, // Ss - Start of spectral selection. + 0x3f, // Se - End of spectral selection. + 0x00 // Ah + Ah (Successive approximation bit position high + low) }; - // sosHeaderYCbCr is the SOS marker "\xff\xda" followed by 12 bytes: - // - the marker length "\x00\x0c", - // - the number of components "\x03", - // - component 1 uses DC table 0 and AC table 0 "\x01\x00", - // - component 2 uses DC table 1 and AC table 1 "\x02\x11", - // - component 3 uses DC table 1 and AC table 1 "\x03\x11", - // - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for - // sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) - // should be 0x00, 0x3f, 0x00<<4 | 0x00. - private readonly byte[] sosHeaderYCbCr = new byte[] { - 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, - 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, + // The SOS marker "\xff\xda" followed by 12 bytes: + // - the marker length "\x00\x0c", + // - the number of components "\x03", + // - component 1 uses DC table 0 and AC table 0 "\x01\x00", + // - component 2 uses DC table 1 and AC table 1 "\x02\x11", + // - component 3 uses DC table 1 and AC table 1 "\x03\x11", + // - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for + // sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) + // should be 0x00, 0x3f, 0x00<<4 | 0x00. + private readonly byte[] SOSHeaderYCbCr = + { + JpegConstants.Markers.XFF, JpegConstants.Markers.SOS, + 0x00, 0x0c, // Length (high byte, low byte), must be 6 + 2 * (number of components in scan) + 0x03, // Number of components in a scan, 3 + 0x01, // Component Id Y + 0x00, // DC/AC Huffman table + 0x02, // Component Id Cb + 0x11, // DC/AC Huffman table + 0x03, // Component Id Cr + 0x11, // DC/AC Huffman table + 0x00, // Ss - Start of spectral selection. + 0x3f, // Se - End of spectral selection. + 0x00 // Ah + Ah (Successive approximation bit position high + low) }; + // Encode writes the Image m to w in JPEG 4:2:0 baseline format with the given + // options. Default parameters are used if a nil *Options is passed. + public void Encode(ImageBase image, Stream stream, int quality, JpegSubsample sample) + where T : IPackedVector + where TP : struct + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + ushort max = JpegConstants.MaxLength; + if (image.Width >= max || image.Height >= max) + { + throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}."); + } + + this.outputStream = stream; + this.subsample = sample; + + // TODO: This should be static should it not? + for (int i = 0; i < this.theHuffmanSpec.Length; i++) + { + this.theHuffmanLUT[i] = new HuffmanLut(this.theHuffmanSpec[i]); + } + + for (int i = 0; i < NQuantIndex; i++) + { + this.quant[i] = new byte[Block.BlockSize]; + } + + if (quality < 1) quality = 1; + if (quality > 100) quality = 100; + + // Convert from a quality rating to a scaling factor. + int scale; + if (quality < 50) + { + scale = 5000 / quality; + } + else + { + scale = 200 - quality * 2; + } + + // Initialize the quantization tables. + for (int i = 0; i < NQuantIndex; i++) + { + for (int j = 0; j < Block.BlockSize; j++) + { + int x = this.unscaledQuant[i, j]; + x = (x * scale + 50) / 100; + if (x < 1) x = 1; + if (x > 255) x = 255; + this.quant[i][j] = (byte)x; + } + } + + // Compute number of components based on input image type. + int componentCount = 3; + + // Write the Start Of Image marker. + double densityX = ((Image)image).HorizontalResolution; + double densityY = ((Image)image).VerticalResolution; + + WriteApplicationHeader((short)densityX, (short)densityY); + + // Write the quantization tables. + this.WriteDQT(); + + // Write the image dimensions. + this.WriteSOF0(image.Width, image.Height, componentCount); + + // Write the Huffman tables. + this.WriteDHT(componentCount); + + // Write the image data. + using (IPixelAccessor pixels = image.Lock()) + { + this.WriteSOS(pixels); + } + + // Write the End Of Image marker. + this.buffer[0] = 0xff; + this.buffer[1] = 0xd9; + stream.Write(this.buffer, 0, 2); + stream.Flush(); + } + + /// + /// Gets the quotient of the two numbers rounded to the nearest integer, instead of rounded to zero. + /// + /// The value to divide. + /// The value to divide by. + /// The + private static int Round(int dividend, int divisor) + { + if (dividend >= 0) + { + return (dividend + (divisor >> 1)) / divisor; + } + + return -((-dividend + (divisor >> 1)) / divisor); + } + + /// + /// Writes the application header containing the Jfif identifier plus extra data. + /// + /// The resolution of the image in the x- direction. + /// The resolution of the image in the y- direction. + private void WriteApplicationHeader(short horizontalResolution, short verticalResolution) + { + // Write the start of image marker. Markers are always prefixed with with 0xff. + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = JpegConstants.Markers.SOI; + + // Write the JFIF headers + this.buffer[2] = JpegConstants.Markers.XFF; + this.buffer[3] = JpegConstants.Markers.APP0; // Application Marker + this.buffer[4] = 0x00; + this.buffer[5] = 0x10; + this.buffer[6] = 0x4a; // J + this.buffer[7] = 0x46; // F + this.buffer[8] = 0x49; // I + this.buffer[9] = 0x46; // F + this.buffer[10] = 0x00; // = "JFIF",'\0' + this.buffer[11] = 0x01; // versionhi + this.buffer[12] = 0x01; // versionlo + this.buffer[13] = 0x01; // xyunits as dpi + + // No thumbnail + this.buffer[14] = 0x00; // Thumbnail width + this.buffer[15] = 0x00; // Thumbnail height + + this.outputStream.Write(this.buffer, 0, 16); + + // Resolution. Big Endian + this.buffer[0] = (byte)(horizontalResolution >> 8); + this.buffer[1] = (byte)horizontalResolution; + this.buffer[2] = (byte)(verticalResolution >> 8); + this.buffer[3] = (byte)verticalResolution; + + this.outputStream.Write(this.buffer, 0, 4); + } + + /// + /// Writes the Define Quantization Marker and tables. + /// + private void WriteDQT() + { + int markerlen = 2 + NQuantIndex * (1 + Block.BlockSize); + this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen); + for (int i = 0; i < NQuantIndex; i++) + { + this.WriteByte((byte)i); + this.outputStream.Write(this.quant[i], 0, this.quant[i].Length); + } + } + + /// + /// Writes the Start Of Frame (Baseline) marker + /// + /// The width of the image + /// The height of the image + /// + private void WriteSOF0(int width, int height, int componentCount) + { + // "default" to 4:2:0 + byte[] subsamples = { 0x22, 0x11, 0x11 }; + byte[] chroma = { 0x00, 0x01, 0x01 }; + + switch (this.subsample) + { + case JpegSubsample.Ratio444: + subsamples = new byte[] { 0x11, 0x11, 0x11 }; + break; + case JpegSubsample.Ratio420: + subsamples = new byte[] { 0x22, 0x11, 0x11 }; + break; + } + + // Length (high byte, low byte), 8 + components * 3. + int markerlen = 8 + 3 * componentCount; + this.WriteMarkerHeader(JpegConstants.Markers.SOF0, markerlen); + this.buffer[0] = 8; // Data Precision. 8 for now, 12 and 16 bit jpegs not supported + this.buffer[1] = (byte)(height >> 8); + this.buffer[2] = (byte)(height & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported + this.buffer[3] = (byte)(width >> 8); + this.buffer[4] = (byte)(width & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported + this.buffer[5] = (byte)componentCount; // Number of components (1 byte), usually 1 = Gray scaled, 3 = color YCbCr or YIQ, 4 = color CMYK) + if (componentCount == 1) + { + this.buffer[6] = 1; + + // No subsampling for grayscale images. + this.buffer[7] = 0x11; + this.buffer[8] = 0x00; + } + else + { + for (int i = 0; i < componentCount; i++) + { + this.buffer[3 * i + 6] = (byte)(i + 1); + + // We use 4:2:0 chroma subsampling by default. + this.buffer[3 * i + 7] = subsamples[i]; + this.buffer[3 * i + 8] = chroma[i]; + } + } + + this.outputStream.Write(this.buffer, 0, 3 * (componentCount - 1) + 9); + } + + /// + /// Writes the Define Huffman Table marker and tables. + /// + /// The number of components to write. + private void WriteDHT(int nComponent) + { + byte[] headers = { 0x00, 0x10, 0x01, 0x11 }; + int markerlen = 2; + HuffmanSpec[] specs = this.theHuffmanSpec; + + if (nComponent == 1) + { + // Drop the Chrominance tables. + specs = new[] { this.theHuffmanSpec[0], this.theHuffmanSpec[1] }; + } + + foreach (var s in specs) + { + markerlen += 1 + 16 + s.Values.Length; + } - // writeSOS writes the StartOfScan marker. - private void writeSOS(PixelAccessor pixels) + this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen); + for (int i = 0; i < specs.Length; i++) + { + HuffmanSpec spec = specs[i]; + + this.WriteByte(headers[i]); + this.outputStream.Write(spec.Count, 0, spec.Count.Length); + this.outputStream.Write(spec.Values, 0, spec.Values.Length); + } + } + + /// + /// Writes the StartOfScan marker. + /// + /// The pixel accessor providing acces to the image pixels. + private void WriteSOS(IPixelAccessor pixels) + where T : IPackedVector + where TP : struct { - outputStream.Write(sosHeaderYCbCr, 0, sosHeaderYCbCr.Length); + // TODO: We should allow grayscale writing. + this.outputStream.Write(this.SOSHeaderYCbCr, 0, this.SOSHeaderYCbCr.Length); - switch (subsample) + switch (this.subsample) { case JpegSubsample.Ratio444: - encode444(pixels); + this.Encode444(pixels); break; case JpegSubsample.Ratio420: - encode420(pixels); + this.Encode420(pixels); break; } // Pad the last byte with 1's. - emit(0x7f, 7); + this.Emit(0x7f, 7); } - private void encode444(PixelAccessor pixels) + + + /// + /// Encodes the image with no subsampling. + /// + /// The pixel accessor providing acces to the image pixels. + private void Encode444(IPixelAccessor pixels) + where T : IPackedVector + where TP : struct { Block b = new Block(); Block cb = new Block(); @@ -515,15 +715,22 @@ namespace ImageProcessorCore.Formats { for (int x = 0; x < pixels.Width; x += 8) { - toYCbCr(pixels, x, y, b, cb, cr); - prevDCY = writeBlock(b, (quantIndex)0, prevDCY); - prevDCCb = writeBlock(cb, (quantIndex)1, prevDCCb); - prevDCCr = writeBlock(cr, (quantIndex)1, prevDCCr); + this.ToYCbCr(pixels, x, y, b, cb, cr); + prevDCY = this.WriteBlock(b, QuantIndex.Luminance, prevDCY); + prevDCCb = this.WriteBlock(cb, QuantIndex.Chrominance, prevDCCb); + prevDCCr = this.WriteBlock(cr, QuantIndex.Chrominance, prevDCCr); } } } - private void encode420(PixelAccessor pixels) + /// + /// Encodes the image with subsampling. The Cb and Cr components are each subsampled + /// at a factor of 2 both horizontally and vertically. + /// + /// The pixel accessor providing acces to the image pixels. + private void Encode420(IPixelAccessor pixels) + where T : IPackedVector + where TP : struct { Block b = new Block(); Block[] cb = new Block[4]; @@ -542,103 +749,88 @@ namespace ImageProcessorCore.Formats int xOff = (i & 1) * 8; int yOff = (i & 2) * 4; - toYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]); - prevDCY = writeBlock(b, (quantIndex)0, prevDCY); + this.ToYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]); + prevDCY = this.WriteBlock(b, QuantIndex.Luminance, prevDCY); } - scale_16x16_8x8(b, cb); - prevDCCb = writeBlock(b, (quantIndex)1, prevDCCb); - scale_16x16_8x8(b, cr); - prevDCCr = writeBlock(b, (quantIndex)1, prevDCCr); + + this.Scale16X16_8X8(b, cb); + prevDCCb = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCb); + this.Scale16X16_8X8(b, cr); + prevDCCr = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCr); } } } - // Encode writes the Image m to w in JPEG 4:2:0 baseline format with the given - // options. Default parameters are used if a nil *Options is passed. - public void Encode(Stream stream, ImageBase image, int quality, JpegSubsample subsample) + /// + /// Writes the header for a marker with the given length. + /// + /// The marker to write. + /// The marker length. + private void WriteMarkerHeader(byte marker, int length) { - this.outputStream = stream; - this.subsample = subsample; - - for (int i = 0; i < theHuffmanSpec.Length; i++) - { - theHuffmanLUT[i] = new huffmanLUT(theHuffmanSpec[i]); - } - - for (int i = 0; i < nQuantIndex; i++) - { - quant[i] = new byte[Block.blockSize]; - } - - if (image.Width >= (1 << 16) || image.Height >= (1 << 16)) - { - throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}."); - } - - if (quality < 1) quality = 1; - if (quality > 100) quality = 100; - - // Convert from a quality rating to a scaling factor. - int scale; - if (quality < 50) - { - scale = 5000 / quality; - } - else - { - scale = 200 - quality * 2; - } - - // Initialize the quantization tables. - for (int i = 0; i < nQuantIndex; i++) - { - for (int j = 0; j < Block.blockSize; j++) - { - int x = unscaledQuant[i, j]; - x = (x * scale + 50) / 100; - if (x < 1) x = 1; - if (x > 255) x = 255; - quant[i][j] = (byte)x; - } - } + // Markers are always prefixed with with 0xff. + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = marker; + this.buffer[2] = (byte)(length >> 8); + this.buffer[3] = (byte)(length & 0xff); + this.outputStream.Write(this.buffer, 0, 4); + } - // Compute number of components based on input image type. - int nComponent = 3; + /// + /// Enumerates the Huffman tables + /// + private enum HuffIndex + { + LuminanceDC = 0, - // Write the Start Of Image marker. - buf[0] = 0xff; - buf[1] = 0xd8; - stream.Write(buf, 0, 2); + LuminanceAC = 1, - // Write the quantization tables. - writeDQT(); + ChrominanceDC = 2, - // Write the image dimensions. - writeSOF0(image.Width, image.Height, nComponent); + ChrominanceAC = 3, + } - // Write the Huffman tables. - writeDHT(nComponent); + /// + /// Enumerates the quantization tables + /// + private enum QuantIndex + { + /// + /// Luminance + /// + Luminance = 0, + + /// + /// Chrominance + /// + Chrominance = 1, + } - // Write the image data. - using (PixelAccessor pixels = image.Lock()) + /// + /// The Huffman encoding specifications. + /// + private struct HuffmanSpec + { + /// + /// Initializes a n ew instance of the struct. + /// + /// The number of codes. + /// The decoded values. + public HuffmanSpec(byte[] count, byte[] values) { - writeSOS(pixels); + this.Count = count; + this.Values = values; } - // Write the End Of Image marker. - buf[0] = 0xff; - buf[1] = 0xd9; - stream.Write(buf, 0, 2); - stream.Flush(); - } + /// + /// Gets count[i] - The number of codes of length i bits. + /// + public readonly byte[] Count; - // div returns a/b rounded to the nearest integer, instead of rounded to zero. - private static int div(int a, int b) - { - if (a >= 0) - return (a + (b >> 1)) / b; - else - return -((-a + (b >> 1)) / b); + /// + /// Gets value[i] - The decoded value of the i'th codeword. + /// + public readonly byte[] Values; } } } diff --git a/src/ImageProcessorCore/Formats/Png/GrayscaleReader.cs b/src/ImageProcessorCore/Formats/Png/GrayscaleReader.cs index 87f074e0bd..780f5f6241 100644 --- a/src/ImageProcessorCore/Formats/Png/GrayscaleReader.cs +++ b/src/ImageProcessorCore/Formats/Png/GrayscaleReader.cs @@ -33,44 +33,40 @@ namespace ImageProcessorCore.Formats } /// - public void ReadScanline(byte[] scanline, float[] pixels, PngHeader header) + public void ReadScanline(byte[] scanline, T[] pixels, PngHeader header) + where T : IPackedVector + where TP : struct { int offset; byte[] newScanline = scanline.ToArrayByBitsLength(header.BitDepth); - // We divide by 255 as we will store the colors in our floating point format. // Stored in r-> g-> b-> a order. if (this.useAlpha) { for (int x = 0; x < header.Width / 2; x++) { - offset = ((this.row * header.Width) + x) * 4; + offset = (this.row * header.Width) + x; - // We want to convert to premultiplied alpha here. - float r = newScanline[x * 2] / 255f; - float g = newScanline[x * 2] / 255f; - float b = newScanline[x * 2] / 255f; - float a = newScanline[(x * 2) + 1] / 255f; + byte rgb = newScanline[x * 2]; + byte a = newScanline[(x * 2) + 1]; - Color premultiplied = Color.FromNonPremultiplied(new Color(r, g, b, a)); - - pixels[offset] = premultiplied.R; - pixels[offset + 1] = premultiplied.G; - pixels[offset + 2] = premultiplied.B; - pixels[offset + 3] = premultiplied.A; + T color = default(T); + color.PackBytes(rgb, rgb, rgb, a); + pixels[offset] = color; } } else { for (int x = 0; x < header.Width; x++) { - offset = ((this.row * header.Width) + x) * 4; + offset = (this.row * header.Width) + x; + byte rgb = newScanline[x]; + + T color = default(T); + color.PackBytes(rgb, rgb, rgb, 255); - pixels[offset] = newScanline[x] / 255f; - pixels[offset + 1] = newScanline[x] / 255f; - pixels[offset + 2] = newScanline[x] / 255f; - pixels[offset + 3] = 1; + pixels[offset] = color; } } diff --git a/src/ImageProcessorCore/Formats/Png/IColorReader.cs b/src/ImageProcessorCore/Formats/Png/IColorReader.cs index 68183fac72..88ce0d4d26 100644 --- a/src/ImageProcessorCore/Formats/Png/IColorReader.cs +++ b/src/ImageProcessorCore/Formats/Png/IColorReader.cs @@ -14,12 +14,16 @@ namespace ImageProcessorCore.Formats /// /// Reads the specified scanline. /// + /// The pixel format. + /// The packed format. long, float. /// The scanline. - /// The pixels, where the colors should be stored in RGBA format. + /// The pixels to read the image row to. /// /// The header, which contains information about the png file, like /// the width of the image and the height. /// - void ReadScanline(byte[] scanline, float[] pixels, PngHeader header); + void ReadScanline(byte[] scanline, T[] pixels, PngHeader header) + where T : IPackedVector + where TP : struct; } } diff --git a/src/ImageProcessorCore/Formats/Png/PaletteIndexReader.cs b/src/ImageProcessorCore/Formats/Png/PaletteIndexReader.cs index 40de59dbb8..9fba4f0e21 100644 --- a/src/ImageProcessorCore/Formats/Png/PaletteIndexReader.cs +++ b/src/ImageProcessorCore/Formats/Png/PaletteIndexReader.cs @@ -39,7 +39,9 @@ namespace ImageProcessorCore.Formats } /// - public void ReadScanline(byte[] scanline, float[] pixels, PngHeader header) + public void ReadScanline(byte[] scanline, T[] pixels, PngHeader header) + where T : IPackedVector + where TP : struct { byte[] newScanline = scanline.ToArrayByBitsLength(header.BitDepth); int offset, index; @@ -53,28 +55,19 @@ namespace ImageProcessorCore.Formats { index = newScanline[i]; - offset = ((this.row * header.Width) + i) * 4; + offset = (this.row * header.Width) + i; int pixelOffset = index * 3; - - // BUGFIX changed newScanline[] to this.palette[] , 99% sure it was a typo and not intent - float r = this.palette[pixelOffset] / 255f; - float g = this.palette[pixelOffset + 1] / 255f; - float b = this.palette[pixelOffset + 2] / 255f; - float a = this.paletteAlpha.Length > index - ? this.paletteAlpha[index] / 255f - : 1; - Color color = new Color(r, g, b, a); - if (color.A < 1) - { - // We want to convert to premultiplied alpha here. - color = Color.FromNonPremultiplied(color); - } - - pixels[offset] = color.R; - pixels[offset + 1] = color.G; - pixels[offset + 2] = color.B; - pixels[offset + 3] = color.A; + byte r = this.palette[pixelOffset]; + byte g = this.palette[pixelOffset + 1]; + byte b = this.palette[pixelOffset + 2]; + byte a = this.paletteAlpha.Length > index + ? this.paletteAlpha[index] + : (byte)255; + + T color = default(T); + color.PackBytes(r, g, b, a); + pixels[offset] = color; } } else @@ -83,13 +76,16 @@ namespace ImageProcessorCore.Formats { index = newScanline[i]; - offset = ((this.row * header.Width) + i) * 4; + offset = (this.row * header.Width) + i; int pixelOffset = index * 3; - pixels[offset] = this.palette[pixelOffset] / 255f; - pixels[offset + 1] = this.palette[pixelOffset + 1] / 255f; - pixels[offset + 2] = this.palette[pixelOffset + 2] / 255f; - pixels[offset + 3] = 1; + byte r = this.palette[pixelOffset]; + byte g = this.palette[pixelOffset + 1]; + byte b = this.palette[pixelOffset + 2]; + + T color = default(T); + color.PackBytes(r, g, b, 255); + pixels[offset] = color; } } diff --git a/src/ImageProcessorCore/Formats/Png/PngDecoder.cs b/src/ImageProcessorCore/Formats/Png/PngDecoder.cs index 7e92f26145..b2630fb36d 100644 --- a/src/ImageProcessorCore/Formats/Png/PngDecoder.cs +++ b/src/ImageProcessorCore/Formats/Png/PngDecoder.cs @@ -21,8 +21,8 @@ namespace ImageProcessorCore.Formats /// /// RGBA (True color) with alpha (8 bit). /// RGB (True color) without alpha (8 bit). - /// Greyscale with alpha (8 bit). - /// Greyscale without alpha (8 bit). + /// Grayscale with alpha (8 bit). + /// Grayscale without alpha (8 bit). /// Palette Index with alpha (8 bit). /// Palette Index without alpha (8 bit). /// @@ -75,11 +75,13 @@ 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 T : IPackedVector + where TP : struct { new PngDecoderCore().Decode(image, stream); } diff --git a/src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs b/src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs index a8e3596b05..777a8e7664 100644 --- a/src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs +++ b/src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs @@ -25,7 +25,7 @@ namespace ImageProcessorCore.Formats /// /// The image to decode. /// - private Image currentImage; + //private IImage currentImage; /// /// The stream to decode from. @@ -65,6 +65,8 @@ namespace ImageProcessorCore.Formats /// /// Decodes the stream to the image. /// + /// The pixel format. + /// The packed format. long, float. /// The image to decode to. /// The stream containing image data. /// @@ -73,9 +75,11 @@ namespace ImageProcessorCore.Formats /// /// Thrown if the image is larger than the maximum allowable size. /// - public void Decode(Image image, Stream stream) + public void Decode(Image image, Stream stream) + where T : IPackedVector + where TP : struct { - this.currentImage = image; + Image currentImage = image; this.currentStream = stream; this.currentStream.Seek(8, SeekOrigin.Current); @@ -101,7 +105,7 @@ namespace ImageProcessorCore.Formats } else if (currentChunk.Type == PngChunkTypes.Physical) { - this.ReadPhysicalChunk(currentChunk.Data); + this.ReadPhysicalChunk(currentImage, currentChunk.Data); } else if (currentChunk.Type == PngChunkTypes.Data) { @@ -117,7 +121,7 @@ namespace ImageProcessorCore.Formats } else if (currentChunk.Type == PngChunkTypes.Text) { - this.ReadTextChunk(currentChunk.Data); + this.ReadTextChunk(currentImage, currentChunk.Data); } else if (currentChunk.Type == PngChunkTypes.End) { @@ -125,14 +129,14 @@ namespace ImageProcessorCore.Formats } } - if (this.header.Width > ImageBase.MaxWidth || this.header.Height > ImageBase.MaxHeight) + if (this.header.Width > image.MaxWidth || this.header.Height > image.MaxHeight) { throw new ArgumentOutOfRangeException( $"The input png '{this.header.Width}x{this.header.Height}' is bigger than the " - + $"max allowed size '{ImageBase.MaxWidth}x{ImageBase.MaxHeight}'"); + + $"max allowed size '{image.MaxWidth}x{image.MaxHeight}'"); } - float[] pixels = new float[this.header.Width * this.header.Height * 4]; + T[] pixels = new T[this.header.Width * this.header.Height]; PngColorTypeInformation colorTypeInformation = ColorTypes[this.header.ColorType]; @@ -140,7 +144,7 @@ namespace ImageProcessorCore.Formats { IColorReader colorReader = colorTypeInformation.CreateColorReader(palette, paletteAlpha); - this.ReadScanlines(dataStream, pixels, colorReader, colorTypeInformation); + this.ReadScanlines(dataStream, pixels, colorReader, colorTypeInformation); } image.SetPixels(this.header.Width, this.header.Height, pixels); @@ -185,15 +189,20 @@ namespace ImageProcessorCore.Formats /// /// Reads the data chunk containing physical dimension data. /// + /// The pixel format. + /// The packed format. long, float. + /// The image to read to. /// The data containing physical data. - private void ReadPhysicalChunk(byte[] data) + private void ReadPhysicalChunk(Image image, byte[] data) + where T : IPackedVector + where TP : struct { Array.Reverse(data, 0, 4); Array.Reverse(data, 4, 4); // 39.3700787 = inches in a meter. - this.currentImage.HorizontalResolution = BitConverter.ToInt32(data, 0) / 39.3700787d; - this.currentImage.VerticalResolution = BitConverter.ToInt32(data, 4) / 39.3700787d; + image.HorizontalResolution = BitConverter.ToInt32(data, 0) / 39.3700787d; + image.VerticalResolution = BitConverter.ToInt32(data, 4) / 39.3700787d; } /// @@ -239,7 +248,9 @@ namespace ImageProcessorCore.Formats /// The containing pixel data. /// The color reader. /// The color type information. - private void ReadScanlines(MemoryStream dataStream, float[] pixels, IColorReader colorReader, PngColorTypeInformation colorTypeInformation) + private void ReadScanlines(MemoryStream dataStream, T[] pixels, IColorReader colorReader, PngColorTypeInformation colorTypeInformation) + where T : IPackedVector + where TP : struct { dataStream.Position = 0; @@ -303,7 +314,7 @@ namespace ImageProcessorCore.Formats if (column == scanlineLength) { - colorReader.ReadScanline(currentScanline, pixels, this.header); + colorReader.ReadScanline(currentScanline, pixels, this.header); column = -1; this.Swap(ref currentScanline, ref lastScanline); @@ -316,8 +327,13 @@ namespace ImageProcessorCore.Formats /// /// Reads a text chunk containing image properties from the data. /// + /// The pixel format. + /// The packed format. long, float. + /// The image to decode to. /// The containing data. - private void ReadTextChunk(byte[] data) + private void ReadTextChunk(Image image, byte[] data) + where T : IPackedVector + where TP : struct { int zeroIndex = 0; @@ -333,7 +349,7 @@ namespace ImageProcessorCore.Formats string name = Encoding.Unicode.GetString(data, 0, zeroIndex); string value = Encoding.Unicode.GetString(data, zeroIndex + 1, data.Length - zeroIndex - 1); - this.currentImage.Properties.Add(new ImageProperty(name, value)); + image.Properties.Add(new ImageProperty(name, value)); } /// diff --git a/src/ImageProcessorCore/Formats/Png/PngEncoder.cs b/src/ImageProcessorCore/Formats/Png/PngEncoder.cs index 18deb10466..43b7aa5545 100644 --- a/src/ImageProcessorCore/Formats/Png/PngEncoder.cs +++ b/src/ImageProcessorCore/Formats/Png/PngEncoder.cs @@ -67,7 +67,9 @@ namespace ImageProcessorCore.Formats } /// - public void Encode(ImageBase image, Stream stream) + public void Encode(ImageBase image, Stream stream) + where T : IPackedVector + where TP : struct { PngEncoderCore encoder = new PngEncoderCore { diff --git a/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs b/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs index 7e61f736ef..eeb7c651f4 100644 --- a/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs @@ -9,7 +9,7 @@ namespace ImageProcessorCore.Formats using System.IO; using System.Threading.Tasks; - using ImageProcessorCore.Quantizers; + using Quantizers; /// /// Performs the png encoding operation. @@ -27,11 +27,6 @@ namespace ImageProcessorCore.Formats /// private byte bitDepth; - /// - /// The quantized image result. - /// - private QuantizedImage quantized; - /// /// Gets or sets the quality of output for images. /// @@ -67,8 +62,16 @@ namespace ImageProcessorCore.Formats /// public byte Threshold { get; set; } = 128; - /// - public void Encode(ImageBase image, Stream stream) + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The packed format. long, float. + /// The to encode from. + /// The to encode the image data to. + public void Encode(ImageBase image, Stream stream) + where T : IPackedVector + where TP : struct { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -120,13 +123,13 @@ namespace ImageProcessorCore.Formats }; this.WriteHeaderChunk(stream, header); - this.WritePaletteChunk(stream, header, image); + QuantizedImage quantized = this.WritePaletteChunk(stream, header, image); this.WritePhysicalChunk(stream, image); this.WriteGammaChunk(stream); - using (PixelAccessor pixels = image.Lock()) + using (IPixelAccessor pixels = image.Lock()) { - this.WriteDataChunks(stream, pixels); + this.WriteDataChunks(stream, pixels, quantized); } this.WriteEndChunk(stream); @@ -199,65 +202,79 @@ namespace ImageProcessorCore.Formats /// /// Writes the palette chunk to the stream. /// + /// The pixel format. + /// The packed format. long, float. /// The containing image data. /// The . /// The image to encode. - private void WritePaletteChunk(Stream stream, PngHeader header, ImageBase image) + private QuantizedImage WritePaletteChunk(Stream stream, PngHeader header, ImageBase image) + where T : IPackedVector + where TP : struct { if (this.Quality > 256) { - return; + return null; } if (this.Quantizer == null) { - this.Quantizer = new WuQuantizer { Threshold = this.Threshold }; + this.Quantizer = new WuQuantizer { Threshold = this.Threshold }; } - // Quantize the image returning a palette. - this.quantized = this.Quantizer.Quantize(image, this.Quality); + // Quantize the image returning a palette. This boxing is icky. + QuantizedImage quantized = ((IQuantizer)this.Quantizer).Quantize(image, this.Quality); // Grab the palette and write it to the stream. - Bgra32[] palette = this.quantized.Palette; + T[] palette = quantized.Palette; int pixelCount = palette.Length; // Get max colors for bit depth. int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3; byte[] colorTable = new byte[colorTableLength]; - Parallel.For(0, pixelCount, + Parallel.For( + 0, + pixelCount, + Bootstrapper.Instance.ParallelOptions, i => { int offset = i * 3; - Bgra32 color = palette[i]; + byte[] color = palette[i].ToBytes(); - colorTable[offset] = color.R; - colorTable[offset + 1] = color.G; - colorTable[offset + 2] = color.B; + // Expected format r->g->b + colorTable[offset] = color[0]; + colorTable[offset + 1] = color[1]; + colorTable[offset + 2] = color[2]; }); this.WriteChunk(stream, PngChunkTypes.Palette, colorTable); // Write the transparency data - if (this.quantized.TransparentIndex > -1) + if (quantized.TransparentIndex > -1) { - this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, new[] { (byte)this.quantized.TransparentIndex }); + this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, new[] { (byte)quantized.TransparentIndex }); } + + return quantized; } /// /// Writes the physical dimension information to the stream. /// + /// The pixel format. + /// The packed format. long, float. /// The containing image data. /// The image base. - private void WritePhysicalChunk(Stream stream, ImageBase imageBase) + private void WritePhysicalChunk(Stream stream, ImageBase imageBase) + where T : IPackedVector + where TP : struct { - Image image = imageBase as Image; + Image image = imageBase as Image; if (image != null && image.HorizontalResolution > 0 && image.VerticalResolution > 0) { // 39.3700787 = inches in a meter. - int dpmX = (int)Math.Round(image.HorizontalResolution * 39.3700787d); - int dpmY = (int)Math.Round(image.VerticalResolution * 39.3700787d); + int dpmX = (int)Math.Round(image.HorizontalResolution * 39.3700787D); + int dpmY = (int)Math.Round(image.VerticalResolution * 39.3700787D); byte[] chunkData = new byte[9]; @@ -296,9 +313,14 @@ namespace ImageProcessorCore.Formats /// /// Writes the pixel information to the stream. /// + /// The pixel format. + /// The packed format. long, float. /// The containing image data. /// The image pixels. - private void WriteDataChunks(Stream stream, PixelAccessor pixels) + /// The quantized image. + private void WriteDataChunks(Stream stream, IPixelAccessor pixels, QuantizedImage quantized) + where T : IPackedVector + where TP : struct { byte[] data; int imageWidth = pixels.Width; @@ -310,7 +332,11 @@ namespace ImageProcessorCore.Formats int rowLength = imageWidth + 1; data = new byte[rowLength * imageHeight]; - Parallel.For(0, imageHeight, y => + Parallel.For( + 0, + imageHeight, + Bootstrapper.Instance.ParallelOptions, + y => { int dataOffset = (y * rowLength); byte compression = 0; @@ -321,10 +347,10 @@ namespace ImageProcessorCore.Formats data[dataOffset++] = compression; for (int x = 0; x < imageWidth; x++) { - data[dataOffset++] = this.quantized.Pixels[(y * imageWidth) + x]; + data[dataOffset++] = quantized.Pixels[(y * imageWidth) + x]; if (y > 0) { - data[dataOffset - 1] -= this.quantized.Pixels[((y - 1) * imageWidth) + x]; + data[dataOffset - 1] -= quantized.Pixels[((y - 1) * imageWidth) + x]; } } }); @@ -336,7 +362,11 @@ namespace ImageProcessorCore.Formats int rowLength = (imageWidth * 4) + 1; - Parallel.For(0, imageHeight, y => + Parallel.For( + 0, + imageHeight, + Bootstrapper.Instance.ParallelOptions, + y => { byte compression = 0; if (y > 0) @@ -348,23 +378,25 @@ namespace ImageProcessorCore.Formats for (int x = 0; x < imageWidth; x++) { - Bgra32 color = Color.ToNonPremultiplied(pixels[x, y]); + byte[] color = pixels[x, y].ToBytes(); // Calculate the offset for the new array. int dataOffset = (y * rowLength) + (x * 4) + 1; - data[dataOffset] = color.R; - data[dataOffset + 1] = color.G; - data[dataOffset + 2] = color.B; - data[dataOffset + 3] = color.A; + + // Expected format + data[dataOffset] = color[0]; + data[dataOffset + 1] = color[1]; + data[dataOffset + 2] = color[2]; + data[dataOffset + 3] = color[3]; if (y > 0) { - color = Color.ToNonPremultiplied(pixels[x, y - 1]); + color = pixels[x, y - 1].ToBytes(); - data[dataOffset] -= color.R; - data[dataOffset + 1] -= color.G; - data[dataOffset + 2] -= color.B; - data[dataOffset + 3] -= color.A; + data[dataOffset] -= color[0]; + data[dataOffset + 1] -= color[1]; + data[dataOffset + 2] -= color[2]; + data[dataOffset + 3] -= color[3]; } } }); diff --git a/src/ImageProcessorCore/Formats/Png/TrueColorReader.cs b/src/ImageProcessorCore/Formats/Png/TrueColorReader.cs index 47c39b029f..07ed958554 100644 --- a/src/ImageProcessorCore/Formats/Png/TrueColorReader.cs +++ b/src/ImageProcessorCore/Formats/Png/TrueColorReader.cs @@ -32,7 +32,9 @@ namespace ImageProcessorCore.Formats } /// - public void ReadScanline(byte[] scanline, float[] pixels, PngHeader header) + public void ReadScanline(byte[] scanline, T[] pixels, PngHeader header) + where T : IPackedVector + where TP : struct { int offset; @@ -42,33 +44,34 @@ namespace ImageProcessorCore.Formats { for (int x = 0; x < newScanline.Length; x += 4) { - offset = ((this.row * header.Width) + (x >> 2)) * 4; + offset = (this.row * header.Width) + (x >> 2); // We want to convert to premultiplied alpha here. - float r = newScanline[x] / 255f; - float g = newScanline[x + 1] / 255f; - float b = newScanline[x + 2] / 255f; - float a = newScanline[x + 3] / 255f; + byte r = newScanline[x]; + byte g = newScanline[x + 1]; + byte b = newScanline[x + 2]; + byte a = newScanline[x + 3]; - Color premultiplied = Color.FromNonPremultiplied(new Color(r, g, b, a)); + T color = default(T); + color.PackBytes(r, g, b, a); - pixels[offset] = premultiplied.R; - pixels[offset + 1] = premultiplied.G; - pixels[offset + 2] = premultiplied.B; - pixels[offset + 3] = premultiplied.A; + pixels[offset] = color; } } else { for (int x = 0; x < newScanline.Length / 3; x++) { - offset = ((this.row * header.Width) + x) * 4; + offset = (this.row * header.Width) + x; int pixelOffset = x * 3; - pixels[offset] = newScanline[pixelOffset] / 255f; - pixels[offset + 1] = newScanline[pixelOffset + 1] / 255f; - pixels[offset + 2] = newScanline[pixelOffset + 2] / 255f; - pixels[offset + 3] = 1; + byte r = newScanline[pixelOffset]; + byte g = newScanline[pixelOffset + 1]; + byte b = newScanline[pixelOffset + 2]; + + T color = default(T); + color.PackBytes(r, g, b, 255); + pixels[offset] = color; } } diff --git a/src/ImageProcessorCore/Image.cs b/src/ImageProcessorCore/Image.cs index 3cde695432..e4e2186b97 100644 --- a/src/ImageProcessorCore/Image.cs +++ b/src/ImageProcessorCore/Image.cs @@ -5,43 +5,22 @@ namespace ImageProcessorCore { - using System; - using System.Collections.Generic; using System.Diagnostics; using System.IO; - using System.Linq; - using System.Text; - - using Formats; /// - /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. + /// Represents an image. Each pixel is a made up four 8-bit components red, green, blue, and alpha + /// packed into a single unsigned integer value. /// - /// - /// The image data is always stored in RGBA format, where the red, green, blue, and - /// alpha values are floating point numbers. - /// [DebuggerDisplay("Image: {Width}x{Height}")] - public class Image : ImageBase + public class Image : Image { /// - /// 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. + /// Initializes a new instance of the class + /// with the height and the width of the image. /// public Image() { - this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat)); } /// @@ -51,32 +30,8 @@ namespace ImageProcessorCore /// 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 - /// by making a copy from another image. - /// - /// The other image, where the clone should be made from. - /// is null. - public Image(Image other) - : base(other) + : base(width, height) { - 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; } /// @@ -87,201 +42,19 @@ namespace ImageProcessorCore /// /// Thrown if the is null. public Image(Stream stream) + : base(stream) { - Guard.NotNull(stream, nameof(stream)); - this.Load(stream); - } - - /// - /// 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; } - - /// - /// 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. + /// Initializes a new instance of the class + /// by making a copy from another image. /// - /// The stream containing image information. - /// - /// Thrown if the stream is not readable nor seekable. - /// - private void Load(Stream stream) + /// The other image, where the clone should be made from. + /// is null. + public Image(Image other) + : base(other) { - 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/Image/IImageBase.cs b/src/ImageProcessorCore/Image/IImageBase.cs new file mode 100644 index 0000000000..3a4444b5e9 --- /dev/null +++ b/src/ImageProcessorCore/Image/IImageBase.cs @@ -0,0 +1,111 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + + /// + /// Encapsulates the basic properties and methods required to manipulate images in varying formats. + /// + /// The pixel format. + /// The packed format. long, float. + public interface IImageBase : IImageBase + where T : IPackedVector + where TP : struct + { + /// + /// Gets the pixels as an array of the given packed pixel format. + /// + T[] Pixels { get; } + + /// + /// 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 pixels. 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. + /// + void SetPixels(int width, int height, T[] 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 pixels. 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. + /// + void ClonePixels(int width, int height, T[] pixels); + + /// + /// Locks the image providing access to the pixels. + /// + /// It is imperative that the accessor is correctly disposed off after use. + /// + /// + /// The + IPixelAccessor Lock(); + } + + /// + /// Encapsulates the basic properties and methods required to manipulate images. + /// + public interface IImageBase + { + /// + /// Gets the representing the bounds of the image. + /// + Rectangle Bounds { get; } + + /// + /// Gets or sets the quality of the image. This affects the output quality of lossy image formats. + /// + 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. + /// + int FrameDelay { 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; } + + /// + /// Gets the width in pixels. + /// + int Width { get; } + + /// + /// Gets the height in pixels. + /// + int Height { get; } + + /// + /// Gets the pixel ratio made up of the width and height. + /// + double PixelRatio { get; } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore/Image/IImageFrame.cs b/src/ImageProcessorCore/Image/IImageFrame.cs new file mode 100644 index 0000000000..ad5e346943 --- /dev/null +++ b/src/ImageProcessorCore/Image/IImageFrame.cs @@ -0,0 +1,18 @@ +// +// 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 pixel format. + /// The packed format. long, float. + public interface IImageFrame : IImageBase + where T : IPackedVector + where TP : struct + { + } +} diff --git a/src/ImageProcessorCore/IImageProcessor.cs b/src/ImageProcessorCore/Image/IImageProcessor.cs similarity index 75% rename from src/ImageProcessorCore/IImageProcessor.cs rename to src/ImageProcessorCore/Image/IImageProcessor.cs index e71762b107..fb325e0013 100644 --- a/src/ImageProcessorCore/IImageProcessor.cs +++ b/src/ImageProcessorCore/Image/IImageProcessor.cs @@ -3,6 +3,8 @@ // Licensed under the Apache License, Version 2.0. // +using System.Threading.Tasks; + namespace ImageProcessorCore.Processors { /// @@ -15,7 +17,11 @@ namespace ImageProcessorCore.Processors /// /// Encapsulates methods to alter the pixels of an image. /// - public interface IImageProcessor + /// The pixel format. + /// The packed format. long, float. + public interface IImageProcessor + where T : IPackedVector + where TP : struct { /// /// Event fires when each row of the source image has been processed. @@ -27,7 +33,18 @@ namespace ImageProcessorCore.Processors event ProgressEventHandler OnProgress; /// - /// Applies the process to the specified portion of the specified . + /// Gets or sets the parallel options for processing tasks in parallel. + /// + ParallelOptions ParallelOptions { get; set; } + + /// + /// Gets or sets a value indicating whether to compress + /// or expand individual pixel colors the value on processing. + /// + bool Compand { get; set; } + + /// + /// Applies the process to the specified portion of the specified . /// /// Target image to apply the process to. /// The source image. Cannot be null. @@ -44,10 +61,10 @@ namespace ImageProcessorCore.Processors /// /// doesnt fit the dimension of the image. /// - void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle); + void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle); /// - /// Applies the process to the specified portion of the specified at the specified + /// Applies the process to the specified portion of the specified at the specified /// location and with the specified size. /// /// Target image to apply the process to. @@ -65,6 +82,6 @@ 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); + void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle); } } diff --git a/src/ImageProcessorCore/Image/Image.cs b/src/ImageProcessorCore/Image/Image.cs new file mode 100644 index 0000000000..a53c87c5f7 --- /dev/null +++ b/src/ImageProcessorCore/Image/Image.cs @@ -0,0 +1,294 @@ +// +// 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; + using System.Diagnostics; + + /// + /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. + /// + /// The pixel format. + /// The packed format. long, float. + [DebuggerDisplay("Image: {Width}x{Height}")] + public class Image : ImageBase + where T : IPackedVector + where TP : struct + { + /// + /// 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) + : base(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/Image/ImageBase.cs b/src/ImageProcessorCore/Image/ImageBase.cs new file mode 100644 index 0000000000..adbe70d1b1 --- /dev/null +++ b/src/ImageProcessorCore/Image/ImageBase.cs @@ -0,0 +1,149 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.Diagnostics; + + /// + /// The base class of all images. Encapsulates the basic properties and methods required to manipulate + /// images in different pixel formats. + /// + /// The pixel format. + /// The packed format. long, float. + [DebuggerDisplay("Image: {Width}x{Height}")] + public abstract class ImageBase : IImageBase + where T : IPackedVector + where TP : struct + { + /// + /// 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 T[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 T[this.Width * this.Height]; + Array.Copy(other.Pixels, this.Pixels, other.Pixels.Length); + } + + /// + public int MaxWidth { get; set; } = int.MaxValue; + + /// + public int MaxHeight { get; set; } = int.MaxValue; + + /// + public T[] Pixels { get; private set; } + + /// + public int Width { get; private set; } + + /// + public int Height { get; private set; } + + /// + public double PixelRatio => (double)this.Width / this.Height; + + /// + public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height); + + /// + public int Quality { get; set; } + + /// + public int FrameDelay { get; set; } + + /// + public void SetPixels(int width, int height, T[] 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; + } + + /// + public void ClonePixels(int width, int height, T[] 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 T[pixels.Length]; + Array.Copy(pixels, this.Pixels, pixels.Length); + } + + /// + public abstract IPixelAccessor Lock(); + } +} diff --git a/src/ImageProcessorCore/ImageExtensions.cs b/src/ImageProcessorCore/Image/ImageExtensions.cs similarity index 57% rename from src/ImageProcessorCore/ImageExtensions.cs rename to src/ImageProcessorCore/Image/ImageExtensions.cs index e7b261d608..0b1bac55e6 100644 --- a/src/ImageProcessorCore/ImageExtensions.cs +++ b/src/ImageProcessorCore/Image/ImageExtensions.cs @@ -12,55 +12,80 @@ 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 pixel format. + /// The packed format. long, float. /// 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); + public static void SaveAsBmp(this ImageBase source, Stream stream) + where T : IPackedVector + where TP : struct + => new BmpEncoder().Encode(source, stream); + /// /// Saves the image to the given stream with the png format. /// + /// The pixel format. + /// The packed format. long, float. /// 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); + public static void SaveAsPng(this ImageBase source, Stream stream, int quality = int.MaxValue) + where T : IPackedVector + where TP : struct + => new PngEncoder { Quality = quality }.Encode(source, stream); /// /// Saves the image to the given stream with the jpeg format. /// + /// The pixel format. + /// The packed format. long, float. /// 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); + public static void SaveAsJpeg(this ImageBase source, Stream stream, int quality = 75) + where T : IPackedVector + where TP : struct + => new JpegEncoder { Quality = quality }.Encode(source, stream); /// /// Saves the image to the given stream with the gif format. /// + /// The pixel format. + /// The packed format. long, float. /// 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); + internal static void SaveAsGif(this ImageBase source, Stream stream, int quality = 256) + where T : IPackedVector + where TP : struct + => new GifEncoder { Quality = quality }.Encode(source, stream); /// /// Applies the collection of processors to the image. /// This method does not resize the target image. /// + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The processor to apply to the image. - /// The . - public static Image Process(this Image source, IImageProcessor processor) + /// The . + internal static Image Process(this Image source, IImageProcessor processor) + where T : IPackedVector + where TP : struct { return Process(source, source.Bounds, processor); } @@ -69,13 +94,17 @@ namespace ImageProcessorCore /// Applies the collection of processors to the image. /// This method does not resize the target image. /// + /// The pixel format. + /// The packed format. long, float. /// 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 . + internal static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor) + where T : IPackedVector + where TP : struct { return PerformAction(source, true, (sourceImage, targetImage) => processor.Apply(targetImage, sourceImage, sourceRectangle)); } @@ -86,12 +115,16 @@ namespace ImageProcessorCore /// This method is not chainable. /// /// + /// The pixel format. + /// The packed format. long, float. /// 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 . + internal static Image Process(this Image source, int width, int height, IImageSampler sampler) + where T : IPackedVector + where TP : struct { return Process(source, width, height, source.Bounds, default(Rectangle), sampler); } @@ -102,6 +135,8 @@ namespace ImageProcessorCore /// This method does will resize the target image if the source and target rectangles are different. /// /// + /// The pixel format. + /// The packed format. long, float. /// The source image. Cannot be null. /// The target image width. /// The target image height. @@ -113,28 +148,34 @@ 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 . + internal static Image Process(this Image source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler) + where T : IPackedVector + where TP : struct { - 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 pixel format. + /// The packed format. long, float. /// 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 T : IPackedVector + where TP : struct { - 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? + FrameDelay = source.FrameDelay, + Quality = source.Quality, HorizontalResolution = source.HorizontalResolution, VerticalResolution = source.VerticalResolution, CurrentImageFormat = source.CurrentImageFormat, @@ -145,8 +186,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/Image/ImageFrame.cs b/src/ImageProcessorCore/Image/ImageFrame.cs new file mode 100644 index 0000000000..9b83029d28 --- /dev/null +++ b/src/ImageProcessorCore/Image/ImageFrame.cs @@ -0,0 +1,41 @@ +// +// 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 pixel format. + /// The packed format. long, float. + public class ImageFrame : ImageBase + where T : IPackedVector + where TP : struct + { + /// + /// Initializes a new instance of the class. + /// + public ImageFrame() + { + } + + /// + /// 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/ImageProperty.cs b/src/ImageProcessorCore/Image/ImageProperty.cs similarity index 86% rename from src/ImageProcessorCore/ImageProperty.cs rename to src/ImageProcessorCore/Image/ImageProperty.cs index 1d8f5bb8ef..ef432ada24 100644 --- a/src/ImageProcessorCore/ImageProperty.cs +++ b/src/ImageProcessorCore/Image/ImageProperty.cs @@ -1,14 +1,7 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. +// +// 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 { @@ -24,12 +17,8 @@ namespace ImageProcessorCore /// /// Initializes a new instance of the struct. /// - /// - /// The name of the property. - /// - /// - /// The value of the property. - /// + /// The name of the property. + /// The value of the property. public ImageProperty(string name, string value) { this.Name = name; diff --git a/src/ImageProcessorCore/ImageBase.cs b/src/ImageProcessorCore/ImageBase.cs deleted file mode 100644 index 5b7ab65f83..0000000000 --- a/src/ImageProcessorCore/ImageBase.cs +++ /dev/null @@ -1,212 +0,0 @@ -// -// 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. - /// - public abstract class ImageBase - { - /// - /// The array of pixels. - /// - private float[] pixelsArray; - - /// - /// 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; - - // Assign the pointer and pixels. - this.pixelsArray = new float[width * height * 4]; - } - - /// - /// 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.pixelsArray = new float[this.Width * this.Height * 4]; - Array.Copy(other.pixelsArray, this.pixelsArray, other.pixelsArray.Length); - } - - /// - /// Gets or sets the maximum allowable width in pixels. - /// - public static int MaxWidth { get; set; } = int.MaxValue; - - /// - /// Gets or sets the maximum allowable height in pixels. - /// - public static int MaxHeight { get; set; } = int.MaxValue; - - /// - /// Gets the image pixels as byte array. - /// - /// - /// The returned array has a length of Width * Height * 4 bytes - /// and stores the red, the green, the blue, and the alpha value for - /// each pixel in this order. - /// - public float[] Pixels => this.pixelsArray; - - /// - /// 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 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 * 4. - /// - public void SetPixels(int width, int height, float[] 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 * 4) - { - throw new ArgumentException("Pixel array must have the length of Width * Height * 4."); - } - - this.Width = width; - this.Height = height; - this.pixelsArray = 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 * 4. - /// - public void ClonePixels(int width, int height, float[] 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 * 4) - { - throw new ArgumentException("Pixel array must have the length of Width * Height * 4."); - } - - this.Width = width; - this.Height = height; - - // Copy the pixels. - this.pixelsArray = new float[pixels.Length]; - Array.Copy(pixels, this.pixelsArray, pixels.Length); - } - - /// - /// Locks the image providing access to the pixels. - /// - /// It is imperative that the accessor is correctly disposed off after use. - /// - /// - /// The - public PixelAccessor Lock() - { - return new PixelAccessor(this); - } - } -} diff --git a/src/ImageProcessorCore/ImageFrame.cs b/src/ImageProcessorCore/ImageFrame.cs deleted file mode 100644 index e9ff7214bc..0000000000 --- a/src/ImageProcessorCore/ImageFrame.cs +++ /dev/null @@ -1,36 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - - /// - /// Represents a single frame in a animation. - /// - public class ImageFrame : ImageBase - { - /// - /// Initializes a new instance of the class. - /// - public ImageFrame() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The other to create this instance from. - /// - /// - /// Thrown if the given is null. - /// - public ImageFrame(ImageFrame other) - : base(other) - { - } - } -} diff --git a/src/ImageProcessorCore/ImageProcessor.cs b/src/ImageProcessorCore/ImageProcessor.cs index ea76ebbf86..6d6796746d 100644 --- a/src/ImageProcessorCore/ImageProcessor.cs +++ b/src/ImageProcessorCore/ImageProcessor.cs @@ -7,11 +7,14 @@ namespace ImageProcessorCore.Processors { using System; using System.Threading; + using System.Threading.Tasks; /// /// Allows the application of processors to images. /// - public abstract class ImageProcessor : IImageProcessor + public abstract class ImageProcessor : IImageProcessor + where T : IPackedVector + where TP : struct { /// public event ProgressEventHandler OnProgress; @@ -27,7 +30,13 @@ namespace ImageProcessorCore.Processors private int totalRows; /// - public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) + public virtual ParallelOptions ParallelOptions { get; set; } = Bootstrapper.Instance.ParallelOptions; + + /// + public virtual bool Compand { get; set; } = false; + + /// + public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) { try { @@ -48,11 +57,11 @@ namespace ImageProcessorCore.Processors } /// - public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) + public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) { try { - float[] pixels = new float[width * height * 4]; + T[] pixels = new T[width * height]; target.SetPixels(width, height, pixels); // Ensure we always have bounds. @@ -84,6 +93,8 @@ namespace ImageProcessorCore.Processors /// /// This method is called before the process is applied to prepare the processor. /// + /// The pixel format. + /// The packed format. long, float. /// Target image to apply the process to. /// The source image. Cannot be null. /// @@ -93,12 +104,12 @@ 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) + protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { } /// - /// Applies the process to the specified portion of the specified at the specified location + /// Applies the process to the specified portion of the specified at the specified location /// and with the specified size. /// /// Target image to apply the process to. @@ -116,7 +127,7 @@ 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); + protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY); /// /// This method is called after the process is applied to prepare the processor. @@ -130,7 +141,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) + protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { } diff --git a/src/ImageProcessorCore/PixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor/ColorPixelAccessor.cs similarity index 85% rename from src/ImageProcessorCore/PixelAccessor.cs rename to src/ImageProcessorCore/PixelAccessor/ColorPixelAccessor.cs index 2553b2536d..6d0812a2a4 100644 --- a/src/ImageProcessorCore/PixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor/ColorPixelAccessor.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -11,7 +11,11 @@ namespace ImageProcessorCore /// /// Provides per-pixel access to an images pixels. /// - public sealed unsafe class PixelAccessor : IDisposable + /// + /// The image data is always stored in format, where the red, green, blue, and + /// alpha values are 8 bit unsigned bytes. + /// + public sealed unsafe class ColorPixelAccessor : IPixelAccessor { /// /// The position of the first pixel in the bitmap. @@ -37,12 +41,12 @@ namespace ImageProcessorCore private bool isDisposed; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The image to provide pixel access for. /// - public PixelAccessor(ImageBase image) + public ColorPixelAccessor(IImageBase image) { Guard.NotNull(image, nameof(image)); Guard.MustBeGreaterThan(image.Width, 0, "image width"); @@ -51,15 +55,14 @@ namespace ImageProcessorCore this.Width = image.Width; this.Height = image.Height; - // Assign the pointer. - this.pixelsHandle = GCHandle.Alloc(image.Pixels, GCHandleType.Pinned); + this.pixelsHandle = GCHandle.Alloc(((ImageBase)image).Pixels, GCHandleType.Pinned); this.pixelsBase = (Color*)this.pixelsHandle.AddrOfPinnedObject().ToPointer(); } /// - /// Finalizes an instance of the class. + /// Finalizes an instance of the class. /// - ~PixelAccessor() + ~ColorPixelAccessor() { this.Dispose(); } @@ -85,7 +88,7 @@ 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. + /// The at the specified position. public Color this[int x, int y] { get @@ -149,4 +152,4 @@ namespace ImageProcessorCore GC.SuppressFinalize(this); } } -} \ No newline at end of file +} diff --git a/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs new file mode 100644 index 0000000000..dcc1b5b681 --- /dev/null +++ b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs @@ -0,0 +1,53 @@ +// +// 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. + /// + /// The pixel format. + /// The packed format. long, float. + public interface IPixelAccessor : IPixelAccessor + where T : IPackedVector + where TP : struct + { + /// + /// 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. + T this[int x, int y] + { + get; + set; + } + } + + /// + /// 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; } + } +} diff --git a/src/ImageProcessorCore/Quantizers/IQuantizer.cs b/src/ImageProcessorCore/Quantizers/IQuantizer.cs index 3196ebf9f1..f6169b4299 100644 --- a/src/ImageProcessorCore/Quantizers/IQuantizer.cs +++ b/src/ImageProcessorCore/Quantizers/IQuantizer.cs @@ -8,13 +8,12 @@ namespace ImageProcessorCore.Quantizers /// /// Provides methods for allowing quantization of images pixels. /// - public interface IQuantizer + /// The pixel format. + /// The packed format. long, float. + public interface IQuantizer : IQuantizer + where T : IPackedVector + where TP : struct { - /// - /// Gets or sets the transparency threshold. - /// - byte Threshold { get; set; } - /// /// Quantize an image and return the resulting output pixels. /// @@ -23,6 +22,17 @@ namespace ImageProcessorCore.Quantizers /// /// A representing a quantized version of the image pixels. /// - QuantizedImage Quantize(ImageBase image, int maxColors); + QuantizedImage Quantize(ImageBase image, int maxColors); + } + + /// + /// Provides methods for allowing quantization of images pixels. + /// + public interface IQuantizer + { + /// + /// Gets or sets the transparency threshold. + /// + byte Threshold { get; set; } } } diff --git a/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs index c0c900145b..344a1607e5 100644 --- a/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs +++ b/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs @@ -7,12 +7,17 @@ namespace ImageProcessorCore.Quantizers { using System; using System.Collections.Generic; + using System.Linq; /// /// Encapsulates methods to calculate the colour palette if an image using an Octree pattern. /// /// - public sealed class OctreeQuantizer : Quantizer + /// The pixel format. + /// The packed format. long, float. + public sealed class OctreeQuantizer : Quantizer + where T : IPackedVector + where TP : struct { /// /// Stores the tree @@ -25,7 +30,7 @@ namespace ImageProcessorCore.Quantizers private int colors; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree, @@ -37,7 +42,7 @@ namespace ImageProcessorCore.Quantizers } /// - public override QuantizedImage Quantize(ImageBase image, int maxColors) + public override QuantizedImage Quantize(ImageBase image, int maxColors) { this.colors = maxColors.Clamp(1, 255); @@ -60,7 +65,7 @@ namespace ImageProcessorCore.Quantizers /// This function need only be overridden if your quantize algorithm needs two passes, /// such as an Octree quantizer. /// - protected override void InitialQuantizePixel(Bgra32 pixel) + protected override void InitialQuantizePixel(T pixel) { // Add the color to the Octree this.octree.AddColor(pixel); @@ -75,13 +80,13 @@ namespace ImageProcessorCore.Quantizers /// /// The quantized value /// - protected override byte QuantizePixel(Bgra32 pixel) + protected override byte QuantizePixel(T pixel) { // The color at [maxColors] is set to transparent byte paletteIndex = (byte)this.colors; // Get the palette index if it's transparency meets criterea. - if (pixel.A > this.Threshold) + if (pixel.ToBytes()[3] > this.Threshold) { paletteIndex = (byte)this.octree.GetPaletteIndex(pixel); } @@ -95,12 +100,16 @@ namespace ImageProcessorCore.Quantizers /// /// The new color palette /// - protected override List GetPalette() + protected override List GetPalette() { // First off convert the Octree to maxColors colors - List palette = this.octree.Palletize(Math.Max(this.colors, 1)); + List palette = this.octree.Palletize(Math.Max(this.colors, 1)); - palette.Add(Bgra32.Empty); + int diff = this.colors - palette.Count; + if (diff > 0) + { + palette.AddRange(Enumerable.Repeat(default(T), diff)); + } this.TransparentIndex = this.colors; return palette; @@ -152,7 +161,7 @@ namespace ImageProcessorCore.Quantizers /// /// Cache the previous color quantized /// - private int previousColor; + private TP previousColor; /// /// Initializes a new instance of the class. @@ -166,7 +175,7 @@ namespace ImageProcessorCore.Quantizers this.Leaves = 0; this.reducibleNodes = new OctreeNode[9]; this.root = new OctreeNode(0, this.maxColorBits, this); - this.previousColor = 0; + this.previousColor = default(TP); this.previousNode = null; } @@ -184,18 +193,19 @@ namespace ImageProcessorCore.Quantizers /// Add a given color value to the Octree /// /// - /// The containing color information to add. + /// The containing color information to add. /// - public void AddColor(Bgra32 pixel) + public void AddColor(T pixel) { + TP packed = pixel.PackedValue(); // Check if this request is for the same color as the last - if (this.previousColor == pixel.Bgra) + if (this.previousColor.Equals(packed)) { // If so, check if I have a previous node setup. This will only occur if the first color in the image // happens to be black, with an alpha component of zero. if (this.previousNode == null) { - this.previousColor = pixel.Bgra; + this.previousColor = packed; this.root.AddColor(pixel, this.maxColorBits, 0, this); } else @@ -206,7 +216,7 @@ namespace ImageProcessorCore.Quantizers } else { - this.previousColor = pixel.Bgra; + this.previousColor = packed; this.root.AddColor(pixel, this.maxColorBits, 0, this); } } @@ -218,9 +228,9 @@ namespace ImageProcessorCore.Quantizers /// The maximum number of colors /// /// - /// An with the palletized colors + /// An with the palletized colors /// - public List Palletize(int colorCount) + public List Palletize(int colorCount) { while (this.Leaves > colorCount) { @@ -228,7 +238,7 @@ namespace ImageProcessorCore.Quantizers } // Now palletize the nodes - List palette = new List(this.Leaves); + List palette = new List(this.Leaves); int paletteIndex = 0; this.root.ConstructPalette(palette, ref paletteIndex); @@ -240,12 +250,12 @@ namespace ImageProcessorCore.Quantizers /// Get the palette index for the passed color /// /// - /// The containing the pixel data. + /// The containing the pixel data. /// /// /// The index of the given structure. /// - public int GetPaletteIndex(Bgra32 pixel) + public int GetPaletteIndex(T pixel) { return this.root.GetPaletteIndex(pixel, 0); } @@ -369,19 +379,11 @@ namespace ImageProcessorCore.Quantizers /// /// Add a color into the tree /// - /// - /// The color - /// - /// - /// The number of significant color bits - /// - /// - /// The level in the tree - /// - /// - /// The tree to which this node belongs - /// - public void AddColor(Bgra32 pixel, int colorBits, int level, Octree octree) + /// The color + /// The number of significant color bits + /// The level in the tree + /// The tree to which this node belongs + public void AddColor(T pixel, int colorBits, int level, Octree octree) { // Update the color information if this is a leaf if (this.leaf) @@ -395,9 +397,10 @@ namespace ImageProcessorCore.Quantizers { // Go to the next level down in the tree int shift = 7 - level; - int index = ((pixel.R & Mask[level]) >> (shift - 2)) | - ((pixel.G & Mask[level]) >> (shift - 1)) | - ((pixel.B & Mask[level]) >> shift); + byte[] components = pixel.ToBytes(); + int index = ((components[2] & Mask[level]) >> (shift - 2)) | + ((components[1] & Mask[level]) >> (shift - 1)) | + ((components[0] & Mask[level]) >> shift); OctreeNode child = this.children[index]; @@ -452,7 +455,7 @@ namespace ImageProcessorCore.Quantizers /// /// The current palette index /// - public void ConstructPalette(List palette, ref int index) + public void ConstructPalette(List palette, ref int index) { if (this.leaf) { @@ -464,7 +467,9 @@ namespace ImageProcessorCore.Quantizers byte b = (this.blue / this.pixelCount).ToByte(); // And set the color of the palette entry - palette.Add(new Bgra32(b, g, r)); + T pixel = default(T); + pixel.PackBytes(r, g, b, 255); + palette.Add(pixel); } else { @@ -483,7 +488,7 @@ namespace ImageProcessorCore.Quantizers /// Return the palette index for the passed color /// /// - /// The representing the pixel. + /// The representing the pixel. /// /// /// The level. @@ -491,16 +496,17 @@ namespace ImageProcessorCore.Quantizers /// /// The representing the index of the pixel in the palette. /// - public int GetPaletteIndex(Bgra32 pixel, int level) + public int GetPaletteIndex(T pixel, int level) { int index = this.paletteIndex; if (!this.leaf) { int shift = 7 - level; - int pixelIndex = ((pixel.R & Mask[level]) >> (shift - 2)) | - ((pixel.G & Mask[level]) >> (shift - 1)) | - ((pixel.B & Mask[level]) >> shift); + byte[] components = pixel.ToBytes(); + int pixelIndex = ((components[2] & Mask[level]) >> (shift - 2)) | + ((components[1] & Mask[level]) >> (shift - 1)) | + ((components[0] & Mask[level]) >> shift); if (this.children[pixelIndex] != null) { @@ -508,7 +514,7 @@ namespace ImageProcessorCore.Quantizers } else { - throw new Exception("Didn't expect this!"); + throw new Exception($"Cannot retrive a pixel at the given index {pixelIndex}."); } } @@ -521,12 +527,13 @@ namespace ImageProcessorCore.Quantizers /// /// The pixel to add. /// - public void Increment(Bgra32 pixel) + public void Increment(T pixel) { this.pixelCount++; - this.red += pixel.R; - this.green += pixel.G; - this.blue += pixel.B; + byte[] components = pixel.ToBytes(); + this.red += components[0]; + this.green += components[1]; + this.blue += components[2]; } } } diff --git a/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs b/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs index 40d281015c..57ea9203ae 100644 --- a/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs +++ b/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs @@ -11,7 +11,9 @@ namespace ImageProcessorCore.Quantizers /// /// Encapsulates methods to calculate the color palette of an image. /// - public abstract class Quantizer : IQuantizer + public abstract class Quantizer : IQuantizer + where T : IPackedVector + where TP : struct { /// /// Flag used to indicate whether a single pass or two passes are needed for quantization. @@ -19,7 +21,7 @@ namespace ImageProcessorCore.Quantizers private readonly bool singlePass; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// If true, the quantization only needs to loop through the source pixels once @@ -43,7 +45,7 @@ namespace ImageProcessorCore.Quantizers public byte Threshold { get; set; } /// - public virtual QuantizedImage Quantize(ImageBase image, int maxColors) + public virtual QuantizedImage Quantize(ImageBase image, int maxColors) { Guard.NotNull(image, nameof(image)); @@ -51,9 +53,9 @@ namespace ImageProcessorCore.Quantizers int height = image.Height; int width = image.Width; byte[] quantizedPixels = new byte[width * height]; - List palette; + List palette; - using (PixelAccessor pixels = image.Lock()) + using (IPixelAccessor pixels = image.Lock()) { // Call the FirstPass function if not a single pass algorithm. // For something like an Octree quantizer, this will run through @@ -62,14 +64,14 @@ namespace ImageProcessorCore.Quantizers { this.FirstPass(pixels, width, height); } - + // Get the palette palette = this.GetPalette(); this.SecondPass(pixels, quantizedPixels, width, height); } - return new QuantizedImage(width, height, palette.ToArray(), quantizedPixels, this.TransparentIndex); + return new QuantizedImage(width, height, palette.ToArray(), quantizedPixels, this.TransparentIndex); } /// @@ -78,7 +80,7 @@ namespace ImageProcessorCore.Quantizers /// The source data /// The width in pixels of the image. /// The height in pixels of the image. - protected virtual void FirstPass(PixelAccessor source, int width, int height) + protected virtual void FirstPass(IPixelAccessor source, int width, int height) { // Loop through each row for (int y = 0; y < height; y++) @@ -99,16 +101,17 @@ namespace ImageProcessorCore.Quantizers /// The output pixel array /// The width in pixels of the image /// The height in pixels of the image - protected virtual void SecondPass(PixelAccessor source, byte[] output, int width, int height) + protected virtual void SecondPass(IPixelAccessor source, byte[] output, int width, int height) { Parallel.For( 0, source.Height, + Bootstrapper.Instance.ParallelOptions, y => { for (int x = 0; x < source.Width; x++) { - Bgra32 sourcePixel = source[x, y]; + T sourcePixel = source[x, y]; output[(y * source.Width) + x] = this.QuantizePixel(sourcePixel); } }); @@ -122,7 +125,7 @@ namespace ImageProcessorCore.Quantizers /// This function need only be overridden if your quantize algorithm needs two passes, /// such as an Octree quantizer. /// - protected virtual void InitialQuantizePixel(Bgra32 pixel) + protected virtual void InitialQuantizePixel(T pixel) { } @@ -133,7 +136,7 @@ namespace ImageProcessorCore.Quantizers /// /// The quantized value /// - protected abstract byte QuantizePixel(Bgra32 pixel); + protected abstract byte QuantizePixel(T pixel); /// /// Retrieve the palette for the quantized image @@ -141,6 +144,6 @@ namespace ImageProcessorCore.Quantizers /// /// The new color palette /// - protected abstract List GetPalette(); + protected abstract List GetPalette(); } } \ No newline at end of file diff --git a/src/ImageProcessorCore/Quantizers/Options/Quantization.cs b/src/ImageProcessorCore/Quantizers/Options/Quantization.cs new file mode 100644 index 0000000000..3dd0c87997 --- /dev/null +++ b/src/ImageProcessorCore/Quantizers/Options/Quantization.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 an image should be quantized. + /// + public enum Quantization + { + /// + /// An adaptive Octree quantizer. Fast with good quality. + /// + Octree, + + /// + /// Xiaolin Wu's Color Quantizer which generates high quality output. + /// + Wu, + + /// + /// Palette based, Uses the collection of web-safe colors by default. + /// + Palette + } +} diff --git a/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs b/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs index db2a4c59cb..9de4a4dc4a 100644 --- a/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs +++ b/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs @@ -14,7 +14,11 @@ namespace ImageProcessorCore.Quantizers /// Encapsulates methods to create a quantized image based upon the given palette. /// /// - public class PaletteQuantizer : Quantizer + /// The pixel format. + /// The packed format. long, float. + public class PaletteQuantizer : Quantizer + where T : IPackedVector + where TP : struct { /// /// A lookup table for colors @@ -24,39 +28,46 @@ namespace ImageProcessorCore.Quantizers /// /// List of all colors in the palette /// - private Bgra32[] colors; + private T[] colors; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The color palette. If none is given this will default to the web safe colors defined /// in the CSS Color Module Level 4. /// - public PaletteQuantizer(Color[] palette = null) + public PaletteQuantizer(T[] palette = null) : base(true) { if (palette == null) { - List safe = ColorConstants.WebSafeColors.Select(c => (Bgra32)c).ToList(); - safe.Insert(0, Bgra32.Empty); + Color[] constants = ColorConstants.WebSafeColors; + List safe = new List { default(T) }; + foreach (Color c in constants) + { + T packed = default(T); + packed.PackVector(c.ToVector4()); + safe.Add(packed); + } + this.colors = safe.ToArray(); } else { - this.colors = palette.Select(c => (Bgra32)c).ToArray(); + this.colors = palette; } } /// - public override QuantizedImage Quantize(ImageBase image, int maxColors) + public override QuantizedImage Quantize(ImageBase image, int maxColors) { Array.Resize(ref this.colors, maxColors.Clamp(1, 256)); return base.Quantize(image, maxColors); } /// - protected override byte QuantizePixel(Bgra32 pixel) + protected override byte QuantizePixel(T pixel) { byte colorIndex = 0; string colorHash = pixel.ToString(); @@ -70,12 +81,13 @@ namespace ImageProcessorCore.Quantizers { // Not found - loop through the palette and find the nearest match. // Firstly check the alpha value - if less than the threshold, lookup the transparent color - if (!(pixel.A > this.Threshold)) + byte[] bytes = pixel.ToBytes(); + if (!(bytes[3] > this.Threshold)) { // Transparent. Lookup the first color with an alpha value of 0 for (int index = 0; index < this.colors.Length; index++) { - if (this.colors[index].A == 0) + if (this.colors[index].ToBytes()[3] == 0) { colorIndex = (byte)index; this.TransparentIndex = colorIndex; @@ -87,18 +99,17 @@ namespace ImageProcessorCore.Quantizers { // Not transparent... int leastDistance = int.MaxValue; - int red = pixel.R; - int green = pixel.G; - int blue = pixel.B; + int red = bytes[0]; + int green = bytes[1]; + int blue = bytes[2]; // Loop through the entire palette, looking for the closest color match for (int index = 0; index < this.colors.Length; index++) { - Bgra32 paletteColor = this.colors[index]; - - int redDistance = paletteColor.R - red; - int greenDistance = paletteColor.G - green; - int blueDistance = paletteColor.B - blue; + byte[] paletteColor = this.colors[index].ToBytes(); + int redDistance = paletteColor[0] - red; + int greenDistance = paletteColor[1] - green; + int blueDistance = paletteColor[2] - blue; int distance = (redDistance * redDistance) + (greenDistance * greenDistance) + @@ -126,7 +137,7 @@ namespace ImageProcessorCore.Quantizers } /// - protected override List GetPalette() + protected override List GetPalette() { return this.colors.ToList(); } diff --git a/src/ImageProcessorCore/Quantizers/Quantize.cs b/src/ImageProcessorCore/Quantizers/Quantize.cs new file mode 100644 index 0000000000..2db63a76b8 --- /dev/null +++ b/src/ImageProcessorCore/Quantizers/Quantize.cs @@ -0,0 +1,65 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +using ImageProcessorCore.Quantizers; + +namespace ImageProcessorCore +{ + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Applies quantization to the image. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image this method extends. + /// The quantization mode to apply to perform the operation. + /// The maximum number of colors to return. Defaults to 256. + /// The . + public static Image Quantize(this Image source, Quantization mode = Quantization.Octree, int maxColors = 256) + where T : IPackedVector + where TP : struct + { + IQuantizer quantizer; + switch (mode) + { + case Quantization.Wu: + quantizer = new WuQuantizer(); + break; + + case Quantization.Palette: + quantizer = new PaletteQuantizer(); + break; + + default: + quantizer = new OctreeQuantizer(); + break; + } + + return Quantize(source, quantizer, maxColors); + } + + /// + /// Applies quantization to the image. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image this method extends. + /// The quantizer to apply to perform the operation. + /// The maximum number of colors to return. + /// The . + public static Image Quantize(this Image source, IQuantizer quantizer, int maxColors) + where T : IPackedVector + where TP : struct + { + QuantizedImage quantizedImage = quantizer.Quantize(source, maxColors); + source.SetPixels(source.Width, source.Height, quantizedImage.ToImage().Pixels); + return source; + } + } +} diff --git a/src/ImageProcessorCore/Quantizers/QuantizedImage.cs b/src/ImageProcessorCore/Quantizers/QuantizedImage.cs index fdf93abd33..680f8b7ab0 100644 --- a/src/ImageProcessorCore/Quantizers/QuantizedImage.cs +++ b/src/ImageProcessorCore/Quantizers/QuantizedImage.cs @@ -11,17 +11,21 @@ namespace ImageProcessorCore.Quantizers /// /// Represents a quantized image where the pixels indexed by a color palette. /// - public class QuantizedImage + /// The pixel format. + /// The packed format. long, float. + public class QuantizedImage + where T : IPackedVector + where TP : struct { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The image width. /// The image height. /// The color palette. /// The quantized pixels. /// The transparency index. - public QuantizedImage(int width, int height, Bgra32[] palette, byte[] pixels, int transparentIndex = -1) + public QuantizedImage(int width, int height, T[] palette, byte[] pixels, int transparentIndex = -1) { Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); @@ -54,7 +58,7 @@ namespace ImageProcessorCore.Quantizers /// /// Gets the color palette of this . /// - public Bgra32[] Palette { get; } + public T[] Palette { get; } /// /// Gets the pixels of this . @@ -72,26 +76,25 @@ namespace ImageProcessorCore.Quantizers /// /// The /// - public Image ToImage() + public Image ToImage() { - Image image = new Image(); + Image image = new Image(); int pixelCount = this.Pixels.Length; int palletCount = this.Palette.Length - 1; - float[] bgraPixels = new float[pixelCount * 4]; + T[] pixels = new T[pixelCount]; - Parallel.For(0, pixelCount, + Parallel.For( + 0, + pixelCount, + Bootstrapper.Instance.ParallelOptions, i => { - int offset = i * 4; - Color color = this.Palette[Math.Min(palletCount, this.Pixels[i])]; - bgraPixels[offset] = color.R; - bgraPixels[offset + 1] = color.G; - bgraPixels[offset + 2] = color.B; - bgraPixels[offset + 3] = color.A; + T color = this.Palette[Math.Min(palletCount, this.Pixels[i])]; + pixels[i] = color; }); - image.SetPixels(this.Width, this.Height, bgraPixels); + image.SetPixels(this.Width, this.Height, pixels); return image; } } diff --git a/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs b/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs index c06262503d..8b6e72e22a 100644 --- a/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs +++ b/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs @@ -1,5 +1,5 @@ // -// Copyright (c) James Jackson-South and contributors. +// Copyright © James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -30,7 +30,11 @@ namespace ImageProcessorCore.Quantizers /// but more expensive versions. /// /// - public sealed class WuQuantizer : IQuantizer + /// The pixel format. + /// The packed format. long, float. + public sealed class WuQuantizer : IQuantizer + where T : IPackedVector + where TP : struct { /// /// The epsilon for comparing floating point numbers. @@ -98,7 +102,7 @@ namespace ImageProcessorCore.Quantizers private readonly byte[] tag; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public WuQuantizer() { @@ -115,7 +119,7 @@ namespace ImageProcessorCore.Quantizers public byte Threshold { get; set; } /// - public QuantizedImage Quantize(ImageBase image, int maxColors) + public QuantizedImage Quantize(ImageBase image, int maxColors) { Guard.NotNull(image, nameof(image)); @@ -123,7 +127,7 @@ namespace ImageProcessorCore.Quantizers this.Clear(); - using (PixelAccessor imagePixels = image.Lock()) + using (IPixelAccessor imagePixels = image.Lock()) { this.Build3DHistogram(imagePixels); this.Get3DMoments(); @@ -320,19 +324,20 @@ namespace ImageProcessorCore.Quantizers /// /// Builds a 3-D color histogram of counts, r/g/b, c^2. /// - /// The image. - private void Build3DHistogram(PixelAccessor image) + /// The pixel accessor. + private void Build3DHistogram(IPixelAccessor pixels) { - for (int y = 0; y < image.Height; y++) + for (int y = 0; y < pixels.Height; y++) { - for (int x = 0; x < image.Width; x++) + for (int x = 0; x < pixels.Width; x++) { - Bgra32 color = image[x, y]; + // Colors are expected in r->g->b->a format + byte[] color = pixels[x, y].ToBytes(); - byte r = color.R; - byte g = color.G; - byte b = color.B; - byte a = color.A; + byte r = color[0]; + byte g = color[1]; + byte b = color[2]; + byte a = color[3]; int inr = r >> (8 - IndexBits); int ing = g >> (8 - IndexBits); @@ -718,9 +723,9 @@ namespace ImageProcessorCore.Quantizers /// The color count. /// The cube. /// The result. - private QuantizedImage GenerateResult(PixelAccessor imagePixels, int colorCount, Box[] cube) + private QuantizedImage GenerateResult(IPixelAccessor imagePixels, int colorCount, Box[] cube) { - List pallette = new List(); + List pallette = new List(); byte[] pixels = new byte[imagePixels.Width * imagePixels.Height]; int transparentIndex = -1; int width = imagePixels.Width; @@ -739,9 +744,10 @@ namespace ImageProcessorCore.Quantizers byte b = (byte)(Volume(cube[k], this.vmb) / weight); byte a = (byte)(Volume(cube[k], this.vma) / weight); - Bgra32 color = new Bgra32(b, g, r, a); + T color = default(T); + color.PackBytes(r, g, b, a); - if (color == Bgra32.Empty) + if (color.Equals(default(T))) { transparentIndex = k; } @@ -750,7 +756,7 @@ namespace ImageProcessorCore.Quantizers } else { - pallette.Add(Bgra32.Empty); + pallette.Add(default(T)); transparentIndex = k; } } @@ -758,17 +764,19 @@ namespace ImageProcessorCore.Quantizers Parallel.For( 0, height, + Bootstrapper.Instance.ParallelOptions, y => { for (int x = 0; x < width; x++) { - Bgra32 color = imagePixels[x, y]; - int a = color.A >> (8 - IndexAlphaBits); - int r = color.R >> (8 - IndexBits); - int g = color.G >> (8 - IndexBits); - int b = color.B >> (8 - IndexBits); - - if (transparentIndex > -1 && color.A <= this.Threshold) + // Expected order r->g->b->a + byte[] color = imagePixels[x, y].ToBytes(); + int r = color[0] >> (8 - IndexBits); + int g = color[1] >> (8 - IndexBits); + int b = color[2] >> (8 - IndexBits); + int a = color[3] >> (8 - IndexAlphaBits); + + if (transparentIndex > -1 && color[3] <= this.Threshold) { pixels[(y * width) + x] = (byte)transparentIndex; continue; @@ -779,8 +787,7 @@ namespace ImageProcessorCore.Quantizers } }); - - return new QuantizedImage(width, height, pallette.ToArray(), pixels, transparentIndex); + return new QuantizedImage(width, height, pallette.ToArray(), pixels, transparentIndex); } } } \ No newline at end of file diff --git a/src/ImageProcessorCore/Samplers/Crop.cs b/src/ImageProcessorCore/Samplers/Crop.cs index 239fd63f14..84d343d6d6 100644 --- a/src/ImageProcessorCore/Samplers/Crop.cs +++ b/src/ImageProcessorCore/Samplers/Crop.cs @@ -1,26 +1,30 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Crops an image to the given width and height. /// + /// The pixel format. + /// The packed format. long, float. /// 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 - public static Image Crop(this Image source, int width, int height, ProgressEventHandler progressHandler = null) + /// The + public static Image Crop(this Image source, int width, int height, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { return Crop(source, width, height, source.Bounds, progressHandler); } @@ -32,6 +36,8 @@ namespace ImageProcessorCore /// area within the source is resized performing a zoomed crop. /// /// + /// The pixel format. + /// The packed format. long, float. /// The image to crop. /// The target image width. /// The target image height. @@ -40,7 +46,9 @@ namespace ImageProcessorCore /// /// A delegate which is called as progress is made processing the image. /// The - public static Image Crop(this Image source, int width, int height, Rectangle sourceRectangle, ProgressEventHandler progressHandler = null) + public static Image Crop(this Image source, int width, int height, Rectangle sourceRectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); @@ -52,7 +60,7 @@ namespace ImageProcessorCore source = source.Resize(sourceRectangle.Width, sourceRectangle.Height); } - CropProcessor processor = new CropProcessor(); + CropProcessor processor = new CropProcessor(); processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Samplers/EntropyCrop.cs b/src/ImageProcessorCore/Samplers/EntropyCrop.cs index 55284668ca..cbc9eedee8 100644 --- a/src/ImageProcessorCore/Samplers/EntropyCrop.cs +++ b/src/ImageProcessorCore/Samplers/EntropyCrop.cs @@ -1,27 +1,31 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Crops an image to the area of greatest entropy. /// + /// The pixel format. + /// The packed format. long, float. /// The image to crop. /// The threshold for entropic density. /// A delegate which is called as progress is made processing the image. /// The - public static Image EntropyCrop(this Image source, float threshold = .5f, ProgressEventHandler progressHandler = null) + public static Image EntropyCrop(this Image source, float threshold = .5f, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - EntropyCropProcessor processor = new EntropyCropProcessor(threshold); + EntropyCropProcessor processor = new EntropyCropProcessor(threshold); processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs b/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs index a80ea47778..96065f7e58 100644 --- a/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs +++ b/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs @@ -17,12 +17,15 @@ 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 T : IPackedVector + where TP : struct { switch (options.Mode) { @@ -39,19 +42,22 @@ 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 T : IPackedVector + where TP : struct { int width = options.Size.Width; int height = options.Size.Height; @@ -163,12 +169,15 @@ 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 T : IPackedVector + where TP : struct { int width = options.Size.Width; int height = options.Size.Height; @@ -242,12 +251,15 @@ 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 T : IPackedVector + where TP : struct { int width = options.Size.Width; int height = options.Size.Height; @@ -327,12 +339,15 @@ 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 T : IPackedVector + where TP : struct { int width = options.Size.Width; int height = options.Size.Height; @@ -366,12 +381,15 @@ 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 T : IPackedVector + where TP : struct { int width = options.Size.Width; int height = options.Size.Height; @@ -413,18 +431,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/Pad.cs b/src/ImageProcessorCore/Samplers/Pad.cs index de973d3454..6be9f654c4 100644 --- a/src/ImageProcessorCore/Samplers/Pad.cs +++ b/src/ImageProcessorCore/Samplers/Pad.cs @@ -1,26 +1,30 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Evenly pads an image to fit the new dimensions. /// + /// The pixel format. + /// The packed format. long, float. /// The source image to pad. /// The new width. /// The new height. /// A delegate which is called as progress is made processing the image. - /// The . - public static Image Pad(this Image source, int width, int height, ProgressEventHandler progressHandler = null) + /// The . + public static Image Pad(this Image source, int width, int height, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { ResizeOptions options = new ResizeOptions { diff --git a/src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs index adcd3c180c..0990711122 100644 --- a/src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs @@ -10,18 +10,20 @@ namespace ImageProcessorCore.Processors /// /// Provides methods to allow the cropping of an image. /// - public class CropProcessor : ImageSampler + public class CropProcessor : ImageSampler + where T : IPackedVector + where TP : struct { /// - protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { int startX = targetRectangle.X; int endX = targetRectangle.Right; int sourceX = sourceRectangle.X; int sourceY = sourceRectangle.Y; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, diff --git a/src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs index f9f8601f52..6cd7726c92 100644 --- a/src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs @@ -12,7 +12,9 @@ namespace ImageProcessorCore.Processors /// Provides methods to allow the cropping of an image to preserve areas of highest /// entropy. /// - public class EntropyCropProcessor : ImageSampler + public class EntropyCropProcessor : ImageSampler + where T : IPackedVector + where TP : struct { /// /// The rectangle for cropping @@ -38,26 +40,26 @@ namespace ImageProcessorCore.Processors public float Value { get; } /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { - ImageBase temp = new Image(source.Width, source.Height); + ImageBase temp = new Image(source.Width, source.Height); // Detect the edges. - new SobelProcessor().Apply(temp, source, sourceRectangle); + new SobelProcessor().Apply(temp, source, sourceRectangle); // Apply threshold binarization filter. - new ThresholdProcessor(.5f).Apply(temp, temp, sourceRectangle); + new BinaryThresholdProcessor(.5f).Apply(temp, temp, sourceRectangle); // Search for the first white pixels Rectangle rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0); // Reset the target pixel to the correct size. - target.SetPixels(rectangle.Width, rectangle.Height, new float[rectangle.Width * rectangle.Height * 4]); + target.SetPixels(rectangle.Width, rectangle.Height, new T[rectangle.Width * rectangle.Height]); this.cropRectangle = rectangle; } /// - 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) @@ -70,8 +72,8 @@ namespace ImageProcessorCore.Processors int startX = this.cropRectangle.X; int endX = this.cropRectangle.Right; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, @@ -92,7 +94,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) diff --git a/src/ImageProcessorCore/Samplers/Processors/IImageSampler.cs b/src/ImageProcessorCore/Samplers/Processors/IImageSampler.cs index 76a2c5a4d4..36fe5810fb 100644 --- a/src/ImageProcessorCore/Samplers/Processors/IImageSampler.cs +++ b/src/ImageProcessorCore/Samplers/Processors/IImageSampler.cs @@ -8,12 +8,9 @@ namespace ImageProcessorCore.Processors /// /// Acts as a marker for generic parameters that require an image sampler. /// - public interface IImageSampler : IImageProcessor + public interface IImageSampler : IImageProcessor + where T : IPackedVector + where TP : struct { - /// - /// 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/Samplers/Processors/ImageSampler.cs b/src/ImageProcessorCore/Samplers/Processors/ImageSampler.cs index adfe77432f..d016f5628a 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ImageSampler.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ImageSampler.cs @@ -9,9 +9,9 @@ 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 abstract class ImageSampler : ImageProcessor, IImageSampler + where T : IPackedVector + where TP : struct { - /// - public virtual bool Compand { get; set; } = false; } } diff --git a/src/ImageProcessorCore/Samplers/Processors/Matrix3x2Processor.cs b/src/ImageProcessorCore/Samplers/Processors/Matrix3x2Processor.cs index e9b0441e3d..1d791f909c 100644 --- a/src/ImageProcessorCore/Samplers/Processors/Matrix3x2Processor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/Matrix3x2Processor.cs @@ -10,34 +10,40 @@ namespace ImageProcessorCore.Processors /// /// Provides methods to transform an image using a . /// - public abstract class Matrix3x2Processor : ImageSampler + public abstract class Matrix3x2Processor : ImageSampler + where T : IPackedVector + where TP : struct { /// /// Creates a new target to contain the results of the matrix transform. /// + /// The pixel format. + /// The packed format. long, float. /// Target image to apply the process to. /// The source rectangle. /// The processing matrix. - protected static void CreateNewTarget(ImageBase target, Rectangle sourceRectangle, Matrix3x2 processMatrix) + protected static void CreateNewTarget(ImageBase target, Rectangle sourceRectangle, Matrix3x2 processMatrix) { Matrix3x2 sizeMatrix; if (Matrix3x2.Invert(processMatrix, out sizeMatrix)) { Rectangle rectangle = ImageMaths.GetBoundingRectangle(sourceRectangle, sizeMatrix); - target.SetPixels(rectangle.Width, rectangle.Height, new float[rectangle.Width * rectangle.Height * 4]); + target.SetPixels(rectangle.Width, rectangle.Height, new T[rectangle.Width * rectangle.Height]); } } /// /// Gets a transform matrix adjusted to center upon the target image bounds. /// + /// The pixel format. + /// The packed format. long, float. /// Target image to apply the process to. /// The source image. /// The transform matrix. /// /// The . /// - protected static Matrix3x2 GetCenteredMatrix(ImageBase target, ImageBase source, Matrix3x2 matrix) + protected static Matrix3x2 GetCenteredMatrix(ImageBase target, ImageBase source, Matrix3x2 matrix) { Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(-target.Width / 2f, -target.Height / 2f); Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width / 2f, source.Height / 2f); diff --git a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs index 05d9d5962f..6175f81390 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs @@ -6,12 +6,15 @@ namespace ImageProcessorCore.Processors { using System; + using System.Numerics; using System.Threading.Tasks; /// /// Provides methods that allow the resizing of images using various algorithms. /// - public class ResizeProcessor : ImageSampler + public class ResizeProcessor : ImageSampler + where T : IPackedVector + where TP : struct { /// /// Initializes a new instance of the class. @@ -42,7 +45,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)) { @@ -52,7 +55,7 @@ namespace ImageProcessorCore.Processors } /// - 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) @@ -77,33 +80,33 @@ namespace ImageProcessorCore.Processors 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, endY, + this.ParallelOptions, y => + { + if (targetY <= y && y < targetBottom) { - if (targetY <= y && y < targetBottom) - { - // Y coordinates of source points - int originY = (int)((y - startY) * heightFactor); + // Y coordinates of source points + int originY = (int)((y - startY) * heightFactor); - for (int x = startX; x < endX; x++) + for (int x = startX; x < endX; x++) + { + if (targetX <= x && x < targetRight) { - if (targetX <= x && x < targetRight) - { - // X coordinates of source points - int originX = (int)((x - startX) * widthFactor); - - targetPixels[x, y] = sourcePixels[originX, originY]; - } + // X coordinates of source points + int originX = (int)((x - startX) * widthFactor); + targetPixels[x, y] = sourcePixels[originX, originY]; } - - this.OnRowProcessed(); } - }); + + this.OnRowProcessed(); + } + }); } // Break out now. @@ -114,95 +117,107 @@ 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. - Image firstPass = new Image(target.Width, source.Height); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor firstPassPixels = firstPass.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + Image firstPass = new Image(target.Width, source.Height); + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor firstPassPixels = firstPass.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( 0, sourceHeight, + this.ParallelOptions, y => + { + for (int x = startX; x < endX; x++) { - for (int x = startX; x < endX; x++) + if (x >= 0 && x < width) { - 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 = Color.Empty; + // Ensure offsets are normalised for cropping and padding. + int offsetX = x - startX; + double sum = this.HorizontalWeights[offsetX].Sum; + Weight[] horizontalValues = this.HorizontalWeights[offsetX].Values; - 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 color components + Vector4 destination = Vector4.Zero; - destination += sourceColor * xw.Value; - } + for (int i = 0; i < sum; i++) + { + Weight xw = horizontalValues[i]; + int originX = xw.Index; + Vector4 sourceColor = sourcePixels[originX, y].ToVector4(); if (compand) { - destination = Color.Compress(destination); + sourceColor = sourceColor.Expand(); } - firstPassPixels[x, y] = destination; + destination += sourceColor * xw.Value; + } + + if (compand) + { + destination = destination.Compress(); } + + T d = default(T); + d.PackVector(destination); + firstPassPixels[x, y] = d; } - }); + } + }); // Now process the rows. Parallel.For( startY, endY, + this.ParallelOptions, y => + { + if (y >= 0 && y < height) { - if (y >= 0 && y < height) + // Ensure offsets are normalised for cropping and padding. + int offsetY = y - startY; + double sum = this.VerticalWeights[offsetY].Sum; + Weight[] verticalValues = this.VerticalWeights[offsetY].Values; + + for (int x = 0; x < width; x++) { - // Ensure offsets are normalised for cropping and padding. - int offsetY = y - startY; - float sum = this.VerticalWeights[offsetY].Sum; - Weight[] verticalValues = this.VerticalWeights[offsetY].Values; + // Destination color components + Vector4 destination = Vector4.Zero; - for (int x = 0; x < width; x++) + for (int i = 0; i < sum; i++) { - // Destination color components - Color destination = Color.Empty; - - 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; - } + Weight yw = verticalValues[i]; + int originY = yw.Index; + Vector4 sourceColor = firstPassPixels[x, originY].ToVector4(); if (compand) { - destination = Color.Compress(destination); + sourceColor = sourceColor.Expand(); } - targetPixels[x, y] = destination; + destination += sourceColor * yw.Value; + } + + if (compand) + { + destination = destination.Compress(); } + + T d = default(T); + d.PackVector(destination); + targetPixels[x, y] = d; } + } - this.OnRowProcessed(); - }); + this.OnRowProcessed(); + }); } } /// - 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/Processors/RotateFlipProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs index 6ef8866ba1..7a6259f79b 100644 --- a/src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs @@ -11,7 +11,11 @@ namespace ImageProcessorCore.Processors /// /// Provides methods that allow the rotation and flipping of an image around its center point. /// - public class RotateFlipProcessor : ImageSampler + /// The pixel format. + /// The packed format. long, float. + public class RotateFlipProcessor : ImageSampler + where T : IPackedVector + where TP : struct { /// /// Initializes a new instance of the class. @@ -35,7 +39,7 @@ namespace ImageProcessorCore.Processors public RotateType RotateType { get; } /// - 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) { switch (this.RotateType) { @@ -68,20 +72,23 @@ namespace ImageProcessorCore.Processors /// /// Rotates the image 270 degrees clockwise at the centre point. /// + /// The pixel format. + /// The packed format. long, float. /// The target image. /// The source image. - private void Rotate270(ImageBase target, ImageBase source) + private void Rotate270(ImageBase target, ImageBase source) { int width = source.Width; int height = source.Height; - Image temp = new Image(height, width); + Image temp = new Image(height, width); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor tempPixels = temp.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor tempPixels = temp.Lock()) { Parallel.For( 0, height, + Bootstrapper.Instance.ParallelOptions, y => { for (int x = 0; x < width; x++) @@ -103,19 +110,22 @@ namespace ImageProcessorCore.Processors /// /// Rotates the image 180 degrees clockwise at the centre point. /// + /// The pixel format. + /// The packed format. long, float. /// The target image. /// The source image. - private void Rotate180(ImageBase target, ImageBase source) + private void Rotate180(ImageBase target, ImageBase source) { int width = source.Width; int height = source.Height; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( 0, height, + Bootstrapper.Instance.ParallelOptions, y => { for (int x = 0; x < width; x++) @@ -133,20 +143,23 @@ namespace ImageProcessorCore.Processors /// /// Rotates the image 90 degrees clockwise at the centre point. /// + /// The pixel format. + /// The packed format. long, float. /// The target image. /// The source image. - private void Rotate90(ImageBase target, ImageBase source) + private void Rotate90(ImageBase target, ImageBase source) { int width = source.Width; int height = source.Height; - Image temp = new Image(height, width); + Image temp = new Image(height, width); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor tempPixels = temp.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor tempPixels = temp.Lock()) { Parallel.For( 0, height, + Bootstrapper.Instance.ParallelOptions, y => { for (int x = 0; x < width; x++) @@ -166,21 +179,24 @@ namespace ImageProcessorCore.Processors /// Swaps the image at the X-axis, which goes horizontally through the middle /// at half the height of the image. /// + /// The pixel format. + /// The packed format. long, float. /// Target image to apply the process to. - private void FlipX(ImageBase target) + private void FlipX(ImageBase target) { int width = target.Width; int height = target.Height; int halfHeight = (int)Math.Ceiling(target.Height * .5); - ImageBase temp = new Image(width, height); + Image temp = new Image(width, height); temp.ClonePixels(width, height, target.Pixels); - using (PixelAccessor targetPixels = target.Lock()) - using (PixelAccessor tempPixels = temp.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor tempPixels = temp.Lock()) { Parallel.For( 0, halfHeight, + Bootstrapper.Instance.ParallelOptions, y => { for (int x = 0; x < width; x++) @@ -199,21 +215,24 @@ namespace ImageProcessorCore.Processors /// Swaps the image at the Y-axis, which goes vertically through the middle /// at half of the width of the image. /// + /// The pixel format. + /// The packed format. long, float. /// Target image to apply the process to. - private void FlipY(ImageBase target) + private void FlipY(ImageBase target) { int width = target.Width; int height = target.Height; int halfWidth = (int)Math.Ceiling(width / 2d); - ImageBase temp = new Image(width, height); + Image temp = new Image(width, height); temp.ClonePixels(width, height, target.Pixels); - using (PixelAccessor targetPixels = target.Lock()) - using (PixelAccessor tempPixels = temp.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor tempPixels = temp.Lock()) { Parallel.For( 0, height, + Bootstrapper.Instance.ParallelOptions, y => { for (int x = 0; x < halfWidth; x++) diff --git a/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs index 7aafe0e082..71ae1aad2f 100644 --- a/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs @@ -11,7 +11,9 @@ namespace ImageProcessorCore.Processors /// /// Provides methods that allow the rotating of images. /// - public class RotateProcessor : Matrix3x2Processor + public class RotateProcessor : Matrix3x2Processor + where T : IPackedVector + where TP : struct { /// /// The tranform matrix to apply. @@ -29,7 +31,7 @@ namespace ImageProcessorCore.Processors public bool Expand { get; set; } = true; /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { processMatrix = Point.CreateRotation(new Point(0, 0), -this.Angle); if (this.Expand) @@ -39,12 +41,12 @@ namespace ImageProcessorCore.Processors } /// - 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) { Matrix3x2 matrix = GetCenteredMatrix(target, source, this.processMatrix); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( 0, diff --git a/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs index 02d5dd9e44..a595d3ee69 100644 --- a/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs @@ -11,7 +11,9 @@ namespace ImageProcessorCore.Processors /// /// Provides methods that allow the skewing of images. /// - public class SkewProcessor : Matrix3x2Processor + public class SkewProcessor : Matrix3x2Processor + where T : IPackedVector + where TP : struct { /// /// The tranform matrix to apply. @@ -34,7 +36,7 @@ namespace ImageProcessorCore.Processors public bool Expand { get; set; } = true; /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { this.processMatrix = Point.CreateSkew(new Point(0, 0), -this.AngleX, -this.AngleY); if (this.Expand) @@ -44,12 +46,12 @@ namespace ImageProcessorCore.Processors } /// - 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) { Matrix3x2 matrix = GetCenteredMatrix(target, source, this.processMatrix); - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( 0, diff --git a/src/ImageProcessorCore/Samplers/Resamplers/BicubicResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/BicubicResampler.cs index 8aecac7a60..752c12d6d6 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/BicubicResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/BicubicResampler.cs @@ -19,22 +19,24 @@ namespace ImageProcessorCore public float GetValue(float x) { // The coefficient. - float a = -0.5f; + float a = -0.5F; - if (x < 0) + if (x < 0F) { x = -x; } float result = 0; - if (x <= 1) + if (x <= 1F) { - result = (((1.5f * x) - 2.5f) * x * x) + 1; + // Below simplified result = ((a + 2F) * (x * x * x)) - ((a + 3F) * (x * x)) + 1; + result = (((1.5F * x) - 2.5F) * x * x) + 1; } - else if (x < 2) + else if (x < 2F) { - result = (((((a * x) + 2.5f) * x) - 4) * x) + 2; + // Below simplified result = (a * (x * x * x)) - ((5F * a) * (x * x)) + ((8F * a) * x) - (4F * a); + result = (((((a * x) + 2.5F) * x) - 4) * x) + 2; } return result; diff --git a/src/ImageProcessorCore/Samplers/Resamplers/BoxResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/BoxResampler.cs index b1234e415d..49a3ad4678 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/BoxResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/BoxResampler.cs @@ -17,7 +17,7 @@ namespace ImageProcessorCore /// public float GetValue(float x) { - if (x > -0.5 && x <= 0.5) + if (x > -0.5F && x <= 0.5F) { return 1; } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/CatmullRomResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/CatmullRomResampler.cs index 0b5031df88..281d0190ac 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/CatmullRomResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/CatmullRomResampler.cs @@ -20,7 +20,7 @@ namespace ImageProcessorCore public float GetValue(float x) { const float B = 0; - const float C = 1 / 2f; + const float C = 0.5F; return ImageMaths.GetBcValue(x, B, C); } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/HermiteResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/HermiteResampler.cs index 49193a3de3..43d05fc880 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/HermiteResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/HermiteResampler.cs @@ -18,8 +18,8 @@ namespace ImageProcessorCore.Processors /// public float GetValue(float x) { - const float B = 0; - const float C = 0; + const float B = 0F; + const float C = 0F; return ImageMaths.GetBcValue(x, B, C); } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos2Resampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos2Resampler.cs new file mode 100644 index 0000000000..ced92365b8 --- /dev/null +++ b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos2Resampler.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 2 pixels. + /// + public class Lanczos2Resampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + if (x < 0F) + { + x = -x; + } + + if (x < 2F) + { + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 2F); + } + + return 0F; + } + } +} diff --git a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos3Resampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos3Resampler.cs index a78b6c066a..6ed82afc09 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos3Resampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos3Resampler.cs @@ -18,17 +18,17 @@ namespace ImageProcessorCore /// public float GetValue(float x) { - if (x < 0) + if (x < 0F) { x = -x; } - if (x < 3) + if (x < 3F) { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 3f); + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 3F); } - return 0; + return 0F; } } } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos5Resampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos5Resampler.cs index 05af2dd7f2..c0d1d2fdd7 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos5Resampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos5Resampler.cs @@ -18,17 +18,17 @@ namespace ImageProcessorCore /// public float GetValue(float x) { - if (x < 0) + if (x < 0F) { x = -x; } - if (x < 5) + if (x < 5F) { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 5f); + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 5F); } - return 0; + return 0F; } } } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos8Resampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos8Resampler.cs index 8c9a9237d9..e0f2917c27 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos8Resampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos8Resampler.cs @@ -18,17 +18,17 @@ namespace ImageProcessorCore /// public float GetValue(float x) { - if (x < 0) + if (x < 0F) { x = -x; } - if (x < 8) + if (x < 8F) { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 8f); + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 8F); } - return 0; + return 0F; } } } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/MitchellNetravaliResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/MitchellNetravaliResampler.cs index f609f26450..cacd35f0a1 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/MitchellNetravaliResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/MitchellNetravaliResampler.cs @@ -17,8 +17,8 @@ namespace ImageProcessorCore /// public float GetValue(float x) { - const float B = 1 / 3f; - const float C = 1 / 3f; + const float B = 0.3333333F; + const float C = 0.3333333F; return ImageMaths.GetBcValue(x, B, C); } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/RobidouxResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/RobidouxResampler.cs index caead12d5d..85f68c531e 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/RobidouxResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/RobidouxResampler.cs @@ -17,8 +17,8 @@ namespace ImageProcessorCore /// public float GetValue(float x) { - const float B = 0.3782158F; - const float C = 0.3108921F; + const float B = 0.37821575509399867F; + const float C = 0.31089212245300067F; return ImageMaths.GetBcValue(x, B, C); } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/RobidouxSharpResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/RobidouxSharpResampler.cs index 633503cd16..eb8e07ade5 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/RobidouxSharpResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/RobidouxSharpResampler.cs @@ -17,8 +17,8 @@ namespace ImageProcessorCore /// public float GetValue(float x) { - const float B = 0.26201451F; - const float C = 0.36899274F; + const float B = 0.2620145123990142F; + const float C = 0.3689927438004929F; return ImageMaths.GetBcValue(x, B, C); } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/RobidouxSoftResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/RobidouxSoftResampler.cs deleted file mode 100644 index 8706f492bb..0000000000 --- a/src/ImageProcessorCore/Samplers/Resamplers/RobidouxSoftResampler.cs +++ /dev/null @@ -1,26 +0,0 @@ -// -// 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/Samplers/Resamplers/SplineResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/SplineResampler.cs index 55ef5656a9..f88f9abef3 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/SplineResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/SplineResampler.cs @@ -17,8 +17,8 @@ namespace ImageProcessorCore /// public float GetValue(float x) { - const float B = 1; - const float C = 0; + const float B = 1F; + const float C = 0F; return ImageMaths.GetBcValue(x, B, C); } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/TriangleResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/TriangleResampler.cs index cb404b7369..2269bb251d 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/TriangleResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/TriangleResampler.cs @@ -18,17 +18,17 @@ namespace ImageProcessorCore /// public float GetValue(float x) { - if (x < 0) + if (x < 0F) { x = -x; } - if (x < 1) + if (x < 1F) { - return 1 - x; + return 1F - x; } - return 0; + return 0F; } } } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/WelchResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/WelchResampler.cs index 3ecaa6a747..ef0ab9cea1 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/WelchResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/WelchResampler.cs @@ -17,17 +17,17 @@ namespace ImageProcessorCore /// public float GetValue(float x) { - if (x < 0) + if (x < 0F) { x = -x; } - if (x < 3) + if (x < 3F) { - return ImageMaths.SinC(x) * (1.0f - (x * x / 9.0f)); + return ImageMaths.SinC(x) * (1F - (x * x / 9.0F)); } - return 0; + return 0F; } } } diff --git a/src/ImageProcessorCore/Samplers/Resize.cs b/src/ImageProcessorCore/Samplers/Resize.cs index 2eadd7a11c..40b07469ec 100644 --- a/src/ImageProcessorCore/Samplers/Resize.cs +++ b/src/ImageProcessorCore/Samplers/Resize.cs @@ -1,26 +1,30 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// 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 pixel format. + /// The packed format. long, float. /// 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 T : IPackedVector + where TP : struct { // Ensure size is populated across both dimensions. if (options.Size.Width == 0 && options.Size.Height > 0) @@ -41,13 +45,17 @@ namespace ImageProcessorCore /// /// Resizes an image to the given width and height. /// + /// The pixel format. + /// The packed format. long, float. /// 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 T : IPackedVector + where TP : struct { return Resize(source, width, height, new BicubicResampler(), false, progressHandler); } @@ -55,14 +63,18 @@ namespace ImageProcessorCore /// /// Resizes an image to the given width and height. /// + /// The pixel format. + /// The packed format. long, float. /// 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 T : IPackedVector + where TP : struct { return Resize(source, width, height, new BicubicResampler(), compand, progressHandler); } @@ -70,15 +82,19 @@ namespace ImageProcessorCore /// /// Resizes an image to the given width and height with the given sampler. /// + /// The pixel format. + /// The packed format. long, float. /// 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 T : IPackedVector + where TP : struct { return Resize(source, width, height, sampler, source.Bounds, new Rectangle(0, 0, width, height), compand, progressHandler); } @@ -87,6 +103,8 @@ namespace ImageProcessorCore /// Resizes an image to the given width and height with the given sampler and /// source rectangle. /// + /// The pixel format. + /// The packed format. long, float. /// The image to resize. /// The target image width. /// The target image height. @@ -99,9 +117,11 @@ 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 T : IPackedVector + where TP : struct { if (width == 0 && height > 0) { @@ -118,7 +138,7 @@ namespace ImageProcessorCore Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - ResizeProcessor processor = new ResizeProcessor(sampler) { Compand = compand }; + ResizeProcessor processor = new ResizeProcessor(sampler) { Compand = compand }; processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Samplers/Rotate.cs b/src/ImageProcessorCore/Samplers/Rotate.cs index fa30d9d342..ad00518f14 100644 --- a/src/ImageProcessorCore/Samplers/Rotate.cs +++ b/src/ImageProcessorCore/Samplers/Rotate.cs @@ -8,18 +8,22 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Rotates an image by the given angle in degrees, expanding the image to fit the rotated result. /// + /// The pixel format. + /// The packed format. long, float. /// The image to rotate. /// The angle in degrees to perform the rotation. /// A delegate which is called as progress is made processing the image. /// The - public static Image Rotate(this Image source, float degrees, ProgressEventHandler progressHandler = null) + public static Image Rotate(this Image source, float degrees, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { return Rotate(source, degrees, true, progressHandler); } @@ -27,14 +31,18 @@ namespace ImageProcessorCore /// /// Rotates an image by the given angle in degrees. /// + /// The pixel format. + /// The packed format. long, float. /// The image to rotate. /// The angle in degrees to perform the rotation. /// Whether to expand the image to fit the rotated result. /// A delegate which is called as progress is made processing the image. /// The - public static Image Rotate(this Image source, float degrees, bool expand, ProgressEventHandler progressHandler = null) + public static Image Rotate(this Image source, float degrees, bool expand, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - RotateProcessor processor = new RotateProcessor { Angle = degrees, Expand = expand }; + RotateProcessor processor = new RotateProcessor { Angle = degrees, Expand = expand }; processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Samplers/RotateFlip.cs b/src/ImageProcessorCore/Samplers/RotateFlip.cs index 93449bcd06..093af8503f 100644 --- a/src/ImageProcessorCore/Samplers/RotateFlip.cs +++ b/src/ImageProcessorCore/Samplers/RotateFlip.cs @@ -8,21 +8,25 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Rotates and flips an image by the given instructions. /// + /// The pixel format. + /// The packed format. long, float. /// The image to rotate, flip, or both. /// The to perform the rotation. /// The to perform the flip. /// A delegate which is called as progress is made processing the image. /// The - public static Image RotateFlip(this Image source, RotateType rotateType, FlipType flipType, ProgressEventHandler progressHandler = null) + public static Image RotateFlip(this Image source, RotateType rotateType, FlipType flipType, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - RotateFlipProcessor processor = new RotateFlipProcessor(rotateType, flipType); + RotateFlipProcessor processor = new RotateFlipProcessor(rotateType, flipType); processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/Samplers/Skew.cs b/src/ImageProcessorCore/Samplers/Skew.cs index 904f1d89df..ab188ceeee 100644 --- a/src/ImageProcessorCore/Samplers/Skew.cs +++ b/src/ImageProcessorCore/Samplers/Skew.cs @@ -8,19 +8,23 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Skews an image by the given angles in degrees, expanding the image to fit the skewed result. /// + /// The pixel format. + /// The packed format. long, float. /// The image to skew. /// The angle in degrees to perform the rotation along the x-axis. /// The angle in degrees to perform the rotation along the y-axis. /// A delegate which is called as progress is made processing the image. /// The - public static Image Skew(this Image source, float degreesX, float degreesY, ProgressEventHandler progressHandler = null) + public static Image Skew(this Image source, float degreesX, float degreesY, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { return Skew(source, degreesX, degreesY, true, progressHandler); } @@ -28,15 +32,19 @@ namespace ImageProcessorCore /// /// Skews an image by the given angles in degrees. /// + /// The pixel format. + /// The packed format. long, float. /// The image to skew. /// The angle in degrees to perform the rotation along the x-axis. /// The angle in degrees to perform the rotation along the y-axis. /// Whether to expand the image to fit the skewed result. /// A delegate which is called as progress is made processing the image. /// The - public static Image Skew(this Image source, float degreesX, float degreesY, bool expand, ProgressEventHandler progressHandler = null) + public static Image Skew(this Image source, float degreesX, float degreesY, bool expand, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct { - SkewProcessor processor = new SkewProcessor { AngleX = degreesX, AngleY = degreesY, Expand = expand }; + SkewProcessor processor = new SkewProcessor { AngleX = degreesX, AngleY = degreesY, Expand = expand }; processor.OnProgress += progressHandler; try diff --git a/src/ImageProcessorCore/project.json b/src/ImageProcessorCore/project.json index e3800d495e..85d2f9723a 100644 --- a/src/ImageProcessorCore/project.json +++ b/src/ImageProcessorCore/project.json @@ -24,6 +24,7 @@ "System.IO.Compression": "4.1.0", "System.Linq": "4.1.0", "System.Numerics.Vectors": "4.1.1", + "System.ObjectModel": "4.0.12", "System.Resources.ResourceManager": "4.0.1", "System.Runtime.Extensions": "4.1.0", "System.Runtime.InteropServices": "4.1.0", diff --git a/tests/ImageProcessorCore.Benchmarks/Color/Clamp.cs b/tests/ImageProcessorCore.Benchmarks/Color/Clamp.cs new file mode 100644 index 0000000000..7af7010ab8 --- /dev/null +++ b/tests/ImageProcessorCore.Benchmarks/Color/Clamp.cs @@ -0,0 +1,43 @@ +using System; + +namespace ImageProcessorCore.Benchmarks.Color +{ + using BenchmarkDotNet.Attributes; + + public class Clamp + { + [Benchmark(Baseline = true, Description = "Maths Clamp")] + public byte ClampMaths() + { + double value = 256; + return (byte)Math.Min(Math.Max(0, value), 255); + } + + [Benchmark(Description = "No Maths Clamp")] + public byte ClampNoMaths() + { + double value = 256; + value = (value > 255) ? 255 : value; + value = (value < 0) ? 0 : value; + return (byte)value; + } + + [Benchmark(Description = "No Maths Clamp No Ternary")] + public byte ClampNoMathsNoTernary() + { + double value = 256; + + if(value > 255) + { + return 255; + } + + if (value < 0) + { + return 0; + } + + return (byte)value; + } + } +} diff --git a/tests/ImageProcessorCore.Benchmarks/Color/ColorEquality.cs b/tests/ImageProcessorCore.Benchmarks/Color/ColorEquality.cs index e9ef2c1835..1221989549 100644 --- a/tests/ImageProcessorCore.Benchmarks/Color/ColorEquality.cs +++ b/tests/ImageProcessorCore.Benchmarks/Color/ColorEquality.cs @@ -16,7 +16,7 @@ [Benchmark(Description = "ImageProcessorCore Color Equals")] public bool ColorEqual() { - return new CoreColor(.5f, .5f, .5f, .5f).Equals(new CoreColor(.5f, .5f, .5f, .5f)); + return new CoreColor(128, 128, 128, 128).Equals(new CoreColor(128, 128, 128, 128)); } } } diff --git a/tests/ImageProcessorCore.Benchmarks/Config.cs b/tests/ImageProcessorCore.Benchmarks/Config.cs new file mode 100644 index 0000000000..126ecdaed4 --- /dev/null +++ b/tests/ImageProcessorCore.Benchmarks/Config.cs @@ -0,0 +1,15 @@ +using BenchmarkDotNet.Configs; + +namespace ImageProcessorCore.Benchmarks +{ + public class Config : ManualConfig + { + public Config() + { + // Uncomment if you want to use any of the diagnoser + this.Add(new BenchmarkDotNet.Diagnostics.Windows.MemoryDiagnoser()); + // System.Drawing doesn't like this. + // this.Add(new BenchmarkDotNet.Diagnostics.Windows.InliningDiagnoser()); + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs b/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs index 64d7896362..c13b84064a 100644 --- a/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs +++ b/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs @@ -24,7 +24,7 @@ public CoreColor ResizeCore() { CoreImage image = new CoreImage(400, 400); - using (PixelAccessor imagePixels = image.Lock()) + using (IPixelAccessor imagePixels = image.Lock()) { imagePixels[200, 200] = CoreColor.White; return imagePixels[200, 200]; diff --git a/tests/ImageProcessorCore.Benchmarks/Program.cs b/tests/ImageProcessorCore.Benchmarks/Program.cs index 9e743dd023..ddbb4cd817 100644 --- a/tests/ImageProcessorCore.Benchmarks/Program.cs +++ b/tests/ImageProcessorCore.Benchmarks/Program.cs @@ -1,11 +1,5 @@ namespace ImageProcessorCore.Benchmarks { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; - - using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; public class Program @@ -18,20 +12,7 @@ /// public static void Main(string[] args) { - // Use reflection for a more maintainable way of creating the benchmark switcher, - Type[] benchmarks = typeof(Program).Assembly.GetTypes() - .Where(t => t.GetMethods(BindingFlags.Instance | BindingFlags.Public) - .Any(m => m.GetCustomAttributes(typeof(BenchmarkAttribute), false).Any())) - .OrderBy(t => t.Namespace) - .ThenBy(t => t.Name) - .ToArray(); - - // TODO: This throws an exception. - // List x = new List(args) { "diagnosers=MemoryDiagnoser,InliningDiagnoser" }; - BenchmarkSwitcher benchmarkSwitcher = new BenchmarkSwitcher(benchmarks); - - // benchmarkSwitcher.Run(x.ToArray()); - benchmarkSwitcher.Run(args); + new BenchmarkSwitcher(typeof(Program).Assembly).Run(args); } } } diff --git a/tests/ImageProcessorCore.Benchmarks/Properties/AssemblyInfo.cs b/tests/ImageProcessorCore.Benchmarks/Properties/AssemblyInfo.cs index 08636c3903..695e5b7407 100644 --- a/tests/ImageProcessorCore.Benchmarks/Properties/AssemblyInfo.cs +++ b/tests/ImageProcessorCore.Benchmarks/Properties/AssemblyInfo.cs @@ -1,6 +1,8 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using BenchmarkDotNet.Attributes; +using ImageProcessorCore.Benchmarks; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information @@ -21,3 +23,5 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("299d8e18-102c-42de-adbf-79098ee706a8")] + +[assembly: Config(typeof(Config))] \ No newline at end of file diff --git a/tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs b/tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs index 4ffce9d48d..4eaeea6ecc 100644 --- a/tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs +++ b/tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs @@ -4,8 +4,8 @@ using System.Drawing.Drawing2D; using BenchmarkDotNet.Attributes; - using CoreImage = ImageProcessorCore.Image; using CoreSize = ImageProcessorCore.Size; + using CoreImage = ImageProcessorCore.Image; public class Crop { diff --git a/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs b/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs index bee3fe3cb8..e5eb412f29 100644 --- a/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs +++ b/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs @@ -4,8 +4,8 @@ using System.Drawing.Drawing2D; using BenchmarkDotNet.Attributes; - using CoreImage = ImageProcessorCore.Image; using CoreSize = ImageProcessorCore.Size; + using CoreImage = ImageProcessorCore.Image; public class Resize { @@ -21,7 +21,7 @@ graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.CompositingQuality = CompositingQuality.HighQuality; - graphics.DrawImage(source, 0, 0, 100, 100); + graphics.DrawImage(source, 0, 0, 400, 400); } return destination.Size; diff --git a/tests/ImageProcessorCore.Benchmarks/project.json b/tests/ImageProcessorCore.Benchmarks/project.json index ebeb7dc648..6aabff3df9 100644 --- a/tests/ImageProcessorCore.Benchmarks/project.json +++ b/tests/ImageProcessorCore.Benchmarks/project.json @@ -13,8 +13,7 @@ "emitEntryPoint": true }, "dependencies": { - "BenchmarkDotNet": "0.9.7", - "BenchmarkDotNet.Diagnostics.Windows": "0.9.7", + "BenchmarkDotNet.Diagnostics.Windows": "0.9.8.78", "ImageProcessorCore": "1.0.0-*" }, "commands": { @@ -24,12 +23,8 @@ "net451": { "dependencies": { }, - "imports": [ - "dnx451" - ], "frameworkAssemblies": { - "System.Drawing": "", - "System.Runtime": "" + "System.Drawing": "" } } } diff --git a/tests/ImageProcessorCore.Tests/Colors/ColorTests.cs b/tests/ImageProcessorCore.Tests/Colors/Class.cs similarity index 59% rename from tests/ImageProcessorCore.Tests/Colors/ColorTests.cs rename to tests/ImageProcessorCore.Tests/Colors/Class.cs index e7d86012d0..a93aac5f9b 100644 --- a/tests/ImageProcessorCore.Tests/Colors/ColorTests.cs +++ b/tests/ImageProcessorCore.Tests/Colors/Class.cs @@ -7,6 +7,7 @@ using System.Numerics; namespace ImageProcessorCore.Tests { + using System; using Xunit; /// @@ -21,7 +22,7 @@ namespace ImageProcessorCore.Tests public void AreEqual() { Color color1 = new Color(0, 0, 0); - Color color2 = new Color(0, 0, 0, 1); + Color color2 = new Color(0, 0, 0, 1F); Color color3 = new Color("#000"); Color color4 = new Color("#000000"); Color color5 = new Color("#FF000000"); @@ -57,40 +58,40 @@ namespace ImageProcessorCore.Tests 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); + Assert.Equal(255, color1.R); + Assert.Equal((byte)Math.Round(.1f * 255), color1.G); + Assert.Equal((byte)Math.Round(.133f * 255), color1.B); + Assert.Equal((byte)Math.Round(.864f * 255), color1.A); 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); + Assert.Equal(255, color2.R); + Assert.Equal(Math.Round(.1f * 255), color2.G); + Assert.Equal(Math.Round(.133f * 255), color2.B); + Assert.Equal(255, color2.A); 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); + Assert.Equal(255, color3.R); + Assert.Equal(0, color3.G); + Assert.Equal(0, color3.B); + Assert.Equal(255, color3.A); - 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 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 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); + Assert.Equal(255, color6.R); + Assert.Equal(Math.Round(.1f * 255), color6.G); + Assert.Equal(Math.Round(.133f * 255), color6.B); + Assert.Equal(Math.Round(.5f * 255), color6.A); } /// @@ -100,9 +101,9 @@ namespace ImageProcessorCore.Tests public void ConvertHex() { const string First = "FF000000"; - Bgra32 bgra = new Color(0, 0, 0, 1); - string second = bgra.Bgra.ToString("X"); + Color color = Color.Black; + string second = color.PackedValue().ToString("X"); Assert.Equal(First, second); } } -} +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Colors/ColorConversionTests.cs b/tests/ImageProcessorCore.Tests/Colors/ColorConversionTests.cs index c7a252b03e..091ce2abf5 100644 --- a/tests/ImageProcessorCore.Tests/Colors/ColorConversionTests.cs +++ b/tests/ImageProcessorCore.Tests/Colors/ColorConversionTests.cs @@ -28,7 +28,7 @@ namespace ImageProcessorCore.Tests public void ColorToYCbCr() { // White - Color color = new Color(1, 1, 1); + Color color = Color.White; YCbCr yCbCr = color; Assert.Equal(255, yCbCr.Y, 0); @@ -36,14 +36,14 @@ namespace ImageProcessorCore.Tests Assert.Equal(128, yCbCr.Cr, 0); // Black - Color color2 = new Color(0, 0, 0); + Color color2 = Color.Black; 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); + // Gray + Color color3 = Color.Gray; YCbCr yCbCr3 = color3; Assert.Equal(128, yCbCr3.Y, 0); Assert.Equal(128, yCbCr3.Cb, 0); @@ -62,10 +62,10 @@ namespace ImageProcessorCore.Tests 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); + Assert.Equal(255, color.R); + Assert.Equal(255, color.G); + Assert.Equal(255, color.B); + Assert.Equal(255, color.A); // Black YCbCr yCbCr2 = new YCbCr(0, 128, 128); @@ -74,16 +74,16 @@ namespace ImageProcessorCore.Tests Assert.Equal(0, color2.R); Assert.Equal(0, color2.G); Assert.Equal(0, color2.B); - Assert.Equal(1, color2.A); + Assert.Equal(255, color2.A); - // Grey + // Gray 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); + Assert.Equal(128, color3.R); + Assert.Equal(128, color3.G); + Assert.Equal(128, color3.B); + Assert.Equal(255, color3.A); } /// @@ -95,7 +95,7 @@ namespace ImageProcessorCore.Tests public void ColorToCieXyz() { // White - Color color = new Color(1, 1, 1); + Color color = Color.White; CieXyz ciexyz = color; Assert.Equal(95.05f, ciexyz.X, 3); @@ -103,21 +103,21 @@ namespace ImageProcessorCore.Tests Assert.Equal(108.900f, ciexyz.Z, 3); // Black - Color color2 = new Color(0, 0, 0); + Color color2 = Color.Black; 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); + // Gray + Color color3 = Color.Gray; 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); + // Cyan + Color color4 = Color.Cyan; CieXyz ciexyz4 = color4; Assert.Equal(53.810f, ciexyz4.X, 3); Assert.Equal(78.740f, ciexyz4.Y, 3); @@ -136,25 +136,25 @@ namespace ImageProcessorCore.Tests 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); + Assert.Equal(128, color.R); + Assert.Equal(64, color.G); + Assert.Equal(106, color.B); // 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); + Assert.Equal(204, color2.R); + Assert.Equal(119, color2.G); + Assert.Equal(34, color2.B); - //// White + // Black 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); + Assert.Equal(0, color3.R); + Assert.Equal(0, color3.G); + Assert.Equal(0, color3.B); //// Check others. //Random random = new Random(0); @@ -175,7 +175,7 @@ namespace ImageProcessorCore.Tests public void ColorToHsv() { // Black - Color b = new Color(0, 0, 0); + Color b = Color.Black; Hsv h = b; Assert.Equal(0, h.H, 1); @@ -183,7 +183,7 @@ namespace ImageProcessorCore.Tests Assert.Equal(0, h.V, 1); // White - Color color = new Color(1, 1, 1); + Color color = Color.White; Hsv hsv = color; Assert.Equal(0f, hsv.H, 1); @@ -191,7 +191,7 @@ namespace ImageProcessorCore.Tests Assert.Equal(1f, hsv.V, 1); // Dark moderate pink. - Color color2 = new Color(128 / 255f, 64 / 255f, 106 / 255f); + Color color2 = new Color(128, 64, 106); Hsv hsv2 = color2; Assert.Equal(320.6f, hsv2.H, 1); @@ -199,7 +199,7 @@ namespace ImageProcessorCore.Tests Assert.Equal(0.502f, hsv2.V, 2); // Ochre. - Color color3 = new Color(204 / 255f, 119 / 255f, 34 / 255f); + Color color3 = new Color(204, 119, 34); Hsv hsv3 = color3; Assert.Equal(30f, hsv3.H, 1); @@ -217,25 +217,25 @@ namespace ImageProcessorCore.Tests 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); + Assert.Equal(color.R, 128); + Assert.Equal(color.G, 64); + Assert.Equal(color.B, 106); // 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); + Assert.Equal(color2.R, 204); + Assert.Equal(color2.G, 119); + Assert.Equal(color2.B, 34); // 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); + Assert.Equal(color3.B, 255); + Assert.Equal(color3.G, 255); + Assert.Equal(color3.R, 255); // Check others. //Random random = new Random(0); @@ -256,7 +256,7 @@ namespace ImageProcessorCore.Tests public void ColorToHsl() { // Black - Color b = new Color(0, 0, 0); + Color b = Color.Black; Hsl h = b; Assert.Equal(0, h.H, 1); @@ -264,7 +264,7 @@ namespace ImageProcessorCore.Tests Assert.Equal(0, h.L, 1); // White - Color color = new Color(1, 1, 1); + Color color = Color.White; Hsl hsl = color; Assert.Equal(0f, hsl.H, 1); @@ -272,7 +272,7 @@ namespace ImageProcessorCore.Tests Assert.Equal(1f, hsl.L, 1); // Dark moderate pink. - Color color2 = new Color(128 / 255f, 64 / 255f, 106 / 255f); + Color color2 = new Color(128, 64, 106); Hsl hsl2 = color2; Assert.Equal(320.6f, hsl2.H, 1); @@ -280,7 +280,7 @@ namespace ImageProcessorCore.Tests Assert.Equal(0.376f, hsl2.L, 2); // Ochre. - Color color3 = new Color(204 / 255f, 119 / 255f, 34 / 255f); + Color color3 = new Color(204, 119, 34); Hsl hsl3 = color3; Assert.Equal(30f, hsl3.H, 1); @@ -298,25 +298,25 @@ namespace ImageProcessorCore.Tests 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); + Assert.Equal(color.R, 128); + Assert.Equal(color.G, 64); + Assert.Equal(color.B, 106); // 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); + Assert.Equal(color2.R, 204); + Assert.Equal(color2.G, 119); + Assert.Equal(color2.B, 34); // 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); + Assert.Equal(color3.R, 255); + Assert.Equal(color3.G, 255); + Assert.Equal(color3.B, 255); // Check others. //Random random = new Random(0); @@ -337,7 +337,7 @@ namespace ImageProcessorCore.Tests public void ColorToCmyk() { // White - Color color = new Color(1, 1, 1); + Color color = Color.White; Cmyk cmyk = color; Assert.Equal(0, cmyk.C, 1); @@ -346,15 +346,15 @@ namespace ImageProcessorCore.Tests Assert.Equal(0, cmyk.K, 1); // Black - Color color2 = new Color(0, 0, 0); + Color color2 = Color.Black; 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); + // Gray + Color color3 = Color.Gray; Cmyk cmyk3 = color3; Assert.Equal(0f, cmyk3.C, 1); Assert.Equal(0f, cmyk3.M, 1); @@ -362,7 +362,7 @@ namespace ImageProcessorCore.Tests Assert.Equal(0.498, cmyk3.K, 2); // Checked with other online converters. // Cyan - Color color4 = new Color(0, 1, 1); + Color color4 = Color.Cyan; Cmyk cmyk4 = color4; Assert.Equal(1, cmyk4.C, 1); Assert.Equal(0f, cmyk4.M, 1); @@ -380,25 +380,25 @@ namespace ImageProcessorCore.Tests 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); + Assert.Equal(color.R, 128); + Assert.Equal(color.G, 64); + Assert.Equal(color.B, 106); // 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); + Assert.Equal(color2.R, 204); + Assert.Equal(color2.G, 119); + Assert.Equal(color2.B, 34); // 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); + Assert.Equal(color3.R, 255); + Assert.Equal(color3.G, 255); + Assert.Equal(color3.B, 255); // Check others. //Random random = new Random(0); @@ -419,7 +419,7 @@ namespace ImageProcessorCore.Tests public void ColorToCieLab() { // White - Color color = new Color(1, 1, 1); + Color color = Color.White; CieLab cielab = color; Assert.Equal(100, cielab.L, 3); @@ -427,21 +427,21 @@ namespace ImageProcessorCore.Tests Assert.Equal(-0.010, cielab.B, 3); // Black - Color color2 = new Color(0, 0, 0); + Color color2 = Color.Black; 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); + // Gray + Color color3 = Color.Gray; 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); + // Cyan + Color color4 = Color.Cyan; CieLab cielab4 = color4; Assert.Equal(91.117, cielab4.L, 3); Assert.Equal(-48.080, cielab4.A, 3); @@ -460,25 +460,25 @@ namespace ImageProcessorCore.Tests 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); + Assert.Equal(color.R, 128); + Assert.Equal(color.G, 64); + Assert.Equal(color.B, 106); // 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); + Assert.Equal(color2.R, 204); + Assert.Equal(color2.G, 119); + Assert.Equal(color2.B, 34); - // White + // Black 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); + Assert.Equal(color3.R, 0); + Assert.Equal(color3.G, 0); + Assert.Equal(color3.B, 0); // Check others. //Random random = new Random(0); @@ -490,4 +490,4 @@ namespace ImageProcessorCore.Tests //} } } -} +} \ No newline at end of file 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/FileTestBase.cs b/tests/ImageProcessorCore.Tests/FileTestBase.cs index 100e74d0ff..53a7620ab7 100644 --- a/tests/ImageProcessorCore.Tests/FileTestBase.cs +++ b/tests/ImageProcessorCore.Tests/FileTestBase.cs @@ -21,6 +21,7 @@ namespace ImageProcessorCore.Tests { //"TestImages/Formats/Jpg/Floorplan.jpeg", // Perf: Enable for local testing only "TestImages/Formats/Jpg/Calliphora.jpg", + "TestImages/Formats/Jpg/turtle.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 @@ -32,7 +33,7 @@ namespace ImageProcessorCore.Tests "TestImages/Formats/Gif/rings.gif", //"TestImages/Formats/Gif/giphy.gif" // Perf: Enable for local testing only }; - + protected void ProgressUpdate(object sender, ProgressEventArgs e) { Assert.InRange(e.RowsProcessed, 1, e.TotalRows); diff --git a/tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs b/tests/ImageProcessorCore.Tests/Formats/Bmp/BitmapTests.cs similarity index 73% rename from tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs rename to tests/ImageProcessorCore.Tests/Formats/Bmp/BitmapTests.cs index 6ff3a0f713..23884e9ed6 100644 --- a/tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs +++ b/tests/ImageProcessorCore.Tests/Formats/Bmp/BitmapTests.cs @@ -5,7 +5,6 @@ namespace ImageProcessorCore.Tests { - using System.Diagnostics; using System.IO; using Formats; @@ -17,9 +16,10 @@ namespace ImageProcessorCore.Tests [Fact] public void BitmapCanEncodeDifferentBitRates() { - if (!Directory.Exists("TestOutput/Encode/Bitmap")) + const string path = "TestOutput/Bmp"; + if (!Directory.Exists(path)) { - Directory.CreateDirectory("TestOutput/Encode/Bitmap"); + Directory.CreateDirectory(path); } foreach (string file in Files) @@ -27,14 +27,14 @@ namespace ImageProcessorCore.Tests using (FileStream stream = File.OpenRead(file)) { Image image = new Image(stream); - string encodeFilename = "TestOutput/Encode/Bitmap/" + "24-" + Path.GetFileNameWithoutExtension(file) + ".bmp"; + string encodeFilename = path + "/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"; + encodeFilename = path + "/32-" + Path.GetFileNameWithoutExtension(file) + ".bmp"; using (FileStream output = File.OpenWrite(encodeFilename)) { diff --git a/tests/ImageProcessorCore.Tests/Formats/EncoderDecoderTests.cs b/tests/ImageProcessorCore.Tests/Formats/GeneralFormatTests.cs similarity index 55% rename from tests/ImageProcessorCore.Tests/Formats/EncoderDecoderTests.cs rename to tests/ImageProcessorCore.Tests/Formats/GeneralFormatTests.cs index b26ef68093..462a39cebc 100644 --- a/tests/ImageProcessorCore.Tests/Formats/EncoderDecoderTests.cs +++ b/tests/ImageProcessorCore.Tests/Formats/GeneralFormatTests.cs @@ -1,28 +1,50 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // namespace ImageProcessorCore.Tests { - using System.Diagnostics; + using System; using System.IO; - using Formats; - using Xunit; - using System.Linq; - - using ImageProcessorCore.Quantizers; - public class EncoderDecoderTests : FileTestBase + public class GeneralFormatTests : FileTestBase { + [Fact] + public void ResolutionShouldChange() + { + const string path = "TestOutput/Resolution"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + 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($"{path}/{filename}")) + { + image.VerticalResolution = 150; + image.HorizontalResolution = 150; + image.Save(output); + } + } + } + } + [Fact] public void ImageCanEncodeToString() { - if (!Directory.Exists("TestOutput/ToString")) + const string path = "TestOutput/ToString"; + if (!Directory.Exists(path)) { - Directory.CreateDirectory("TestOutput/ToString"); + Directory.CreateDirectory(path); } foreach (string file in Files) @@ -30,7 +52,7 @@ namespace ImageProcessorCore.Tests using (FileStream stream = File.OpenRead(file)) { Image image = new Image(stream); - string filename = "TestOutput/ToString/" + Path.GetFileNameWithoutExtension(file) + ".txt"; + string filename = path + "/" + Path.GetFileNameWithoutExtension(file) + ".txt"; File.WriteAllText(filename, image.ToString()); } } @@ -39,9 +61,10 @@ namespace ImageProcessorCore.Tests [Fact] public void DecodeThenEncodeImageFromStreamShouldSucceed() { - if (!Directory.Exists("TestOutput/Encode")) + const string path = "TestOutput/Encode"; + if (!Directory.Exists(path)) { - Directory.CreateDirectory("TestOutput/Encode"); + Directory.CreateDirectory(path); } foreach (string file in Files) @@ -49,7 +72,7 @@ namespace ImageProcessorCore.Tests using (FileStream stream = File.OpenRead(file)) { Image image = new Image(stream); - string encodeFilename = "TestOutput/Encode/" + Path.GetFileName(file); + string encodeFilename = path + "/" + Path.GetFileName(file); using (FileStream output = File.OpenWrite(encodeFilename)) { @@ -62,9 +85,10 @@ namespace ImageProcessorCore.Tests [Fact] public void QuantizeImageShouldPreserveMaximumColorPrecision() { - if (!Directory.Exists("TestOutput/Quantize")) + const string path = "TestOutput/Quantize"; + if (!Directory.Exists(path)) { - Directory.CreateDirectory("TestOutput/Quantize"); + Directory.CreateDirectory(path); } foreach (string file in Files) @@ -72,31 +96,30 @@ namespace ImageProcessorCore.Tests 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)}")) + // Copy the original pixels to save decoding time. + Color[] pixels = new Color[image.Width * image.Height]; + Array.Copy(image.Pixels, pixels, image.Pixels.Length); + + using (FileStream output = File.OpenWrite($"{path}/Octree-{Path.GetFileName(file)}")) { - Image qi = quantizedImage.ToImage(); - qi.Save(output, image.CurrentImageFormat); + image.Quantize(Quantization.Octree) + .Save(output, image.CurrentImageFormat); } - quantizer = new WuQuantizer(); - quantizedImage = quantizer.Quantize(image, 256); - - using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Wu-{Path.GetFileName(file)}")) + image.SetPixels(image.Width, image.Height, pixels); + using (FileStream output = File.OpenWrite($"{path}/Wu-{Path.GetFileName(file)}")) { - quantizedImage.ToImage().Save(output, image.CurrentImageFormat); + image.Quantize(Quantization.Wu) + .Save(output, image.CurrentImageFormat); } - quantizer = new PaletteQuantizer(); - quantizedImage = quantizer.Quantize(image, 256); - - using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Palette-{Path.GetFileName(file)}")) + image.SetPixels(image.Width, image.Height, pixels); + using (FileStream output = File.OpenWrite($"{path}/Palette-{Path.GetFileName(file)}")) { - Image qi = quantizedImage.ToImage(); - qi.Save(output, image.CurrentImageFormat); + image.Quantize(Quantization.Palette) + .Save(output, image.CurrentImageFormat); } } } @@ -105,9 +128,10 @@ namespace ImageProcessorCore.Tests [Fact] public void ImageCanConvertFormat() { - if (!Directory.Exists("TestOutput/Format")) + const string path = "TestOutput/Format"; + if (!Directory.Exists(path)) { - Directory.CreateDirectory("TestOutput/Format"); + Directory.CreateDirectory(path); } foreach (string file in Files) @@ -115,22 +139,22 @@ namespace ImageProcessorCore.Tests using (FileStream stream = File.OpenRead(file)) { Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.gif")) + using (FileStream output = File.OpenWrite($"{path}/{Path.GetFileNameWithoutExtension(file)}.gif")) { image.SaveAsGif(output); } - using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.bmp")) + using (FileStream output = File.OpenWrite($"{path}/{Path.GetFileNameWithoutExtension(file)}.bmp")) { image.SaveAsBmp(output); } - using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.jpg")) + using (FileStream output = File.OpenWrite($"{path}/{Path.GetFileNameWithoutExtension(file)}.jpg")) { image.SaveAsJpeg(output); } - using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.png")) + using (FileStream output = File.OpenWrite($"{path}/{Path.GetFileNameWithoutExtension(file)}.png")) { image.SaveAsPng(output); } @@ -141,9 +165,10 @@ namespace ImageProcessorCore.Tests [Fact] public void ImageShouldPreservePixelByteOrderWhenSerialized() { - if (!Directory.Exists("TestOutput/Serialized")) + const string path = "TestOutput/Serialized"; + if (!Directory.Exists(path)) { - Directory.CreateDirectory("TestOutput/Serialized"); + Directory.CreateDirectory(path); } foreach (string file in Files) @@ -162,7 +187,7 @@ namespace ImageProcessorCore.Tests using (MemoryStream memoryStream = new MemoryStream(serialized)) { Image image2 = new Image(memoryStream); - using (FileStream output = File.OpenWrite($"TestOutput/Serialized/{Path.GetFileName(file)}")) + using (FileStream output = File.OpenWrite($"{path}/{Path.GetFileName(file)}")) { image2.Save(output); } diff --git a/tests/ImageProcessorCore.Tests/Formats/PngTests.cs b/tests/ImageProcessorCore.Tests/Formats/Png/PngTests.cs similarity index 81% rename from tests/ImageProcessorCore.Tests/Formats/PngTests.cs rename to tests/ImageProcessorCore.Tests/Formats/Png/PngTests.cs index e824a62f93..c86238cbac 100644 --- a/tests/ImageProcessorCore.Tests/Formats/PngTests.cs +++ b/tests/ImageProcessorCore.Tests/Formats/Png/PngTests.cs @@ -16,9 +16,10 @@ namespace ImageProcessorCore.Tests [Fact] public void ImageCanSaveIndexedPng() { - if (!Directory.Exists("TestOutput/Encode/Png")) + const string path = "TestOutput/Png"; + if (!Directory.Exists(path)) { - Directory.CreateDirectory("TestOutput/Encode/Png"); + Directory.CreateDirectory(path); } foreach (string file in Files) @@ -26,7 +27,7 @@ namespace ImageProcessorCore.Tests using (FileStream stream = File.OpenRead(file)) { Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/Encode/Png/{Path.GetFileNameWithoutExtension(file)}.png")) + using (FileStream output = File.OpenWrite($"{path}/{Path.GetFileNameWithoutExtension(file)}.png")) { image.Quality = 256; image.Save(output, new PngFormat()); diff --git a/tests/ImageProcessorCore.Tests/Helpers/GuardTests.cs b/tests/ImageProcessorCore.Tests/Helpers/GuardTests.cs index bb93f928ac..14b81708ff 100644 --- a/tests/ImageProcessorCore.Tests/Helpers/GuardTests.cs +++ b/tests/ImageProcessorCore.Tests/Helpers/GuardTests.cs @@ -1,12 +1,7 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright © James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. // -// -// Tests the helper. -// -// -------------------------------------------------------------------------------------------------------------------- namespace ImageProcessorCore.Tests.Helpers { @@ -206,4 +201,4 @@ namespace ImageProcessorCore.Tests.Helpers Assert.Null(ex); } } -} +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Numerics/PointTests.cs b/tests/ImageProcessorCore.Tests/Numerics/PointTests.cs index 0108fa5905..e45a4fb2de 100644 --- a/tests/ImageProcessorCore.Tests/Numerics/PointTests.cs +++ b/tests/ImageProcessorCore.Tests/Numerics/PointTests.cs @@ -1,12 +1,7 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright © James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. // -// -// Tests the struct. -// -// -------------------------------------------------------------------------------------------------------------------- namespace ImageProcessorCore.Tests { @@ -52,4 +47,4 @@ namespace ImageProcessorCore.Tests Assert.Equal(5, first.Y); } } -} +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Numerics/RectangleTests.cs b/tests/ImageProcessorCore.Tests/Numerics/RectangleTests.cs index 3b76bfa51b..c67e9a7048 100644 --- a/tests/ImageProcessorCore.Tests/Numerics/RectangleTests.cs +++ b/tests/ImageProcessorCore.Tests/Numerics/RectangleTests.cs @@ -1,12 +1,7 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright © James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. // -// -// Tests the struct. -// -// -------------------------------------------------------------------------------------------------------------------- namespace ImageProcessorCore.Tests { @@ -68,4 +63,4 @@ namespace ImageProcessorCore.Tests Assert.Equal(1, second.Left); } } -} +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Numerics/SizeTests.cs b/tests/ImageProcessorCore.Tests/Numerics/SizeTests.cs index 3d90b2bceb..56b2ffa209 100644 --- a/tests/ImageProcessorCore.Tests/Numerics/SizeTests.cs +++ b/tests/ImageProcessorCore.Tests/Numerics/SizeTests.cs @@ -1,12 +1,7 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright © James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. // -// -// Tests the struct. -// -// -------------------------------------------------------------------------------------------------------------------- namespace ImageProcessorCore.Tests { @@ -52,4 +47,4 @@ namespace ImageProcessorCore.Tests Assert.Equal(5, first.Height); } } -} +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/AlphaTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/AlphaTest.cs new file mode 100644 index 0000000000..6176cbb716 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/AlphaTest.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class AlphaTest : FileTestBase + { + public static readonly TheoryData AlphaValues + = new TheoryData + { + 20 , + 80 , + }; + + [Theory] + [MemberData("AlphaValues")] + public void ImageShouldApplyAlphaFilter(int value) + { + const string path = "TestOutput/Alpha"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + Path.GetExtension(file); + + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Alpha(value) + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/BackgroundColorTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/BackgroundColorTest.cs new file mode 100644 index 0000000000..220d0f3840 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/BackgroundColorTest.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class BackgroundColorTest : FileTestBase + { + [Fact] + public void ImageShouldApplyBackgroundColorFilter() + { + const string path = "TestOutput/BackgroundColor"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + 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($"{path}/{filename}")) + { + image.BackgroundColor(Color.HotPink) + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/BinaryThreshold.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/BinaryThreshold.cs new file mode 100644 index 0000000000..1dcc28d70c --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/BinaryThreshold.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class BinaryThresholdTest : FileTestBase + { + public static readonly TheoryData BinaryThresholdValues + = new TheoryData + { + .25f , + .75f , + }; + + [Theory] + [MemberData("BinaryThresholdValues")] + public void ImageShouldApplyBinaryThresholdFilter(float value) + { + const string path = "TestOutput/BinaryThreshold"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + Path.GetExtension(file); + + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.BinaryThreshold(value) + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/BlackWhiteTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/BlackWhiteTest.cs new file mode 100644 index 0000000000..c40224ef76 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/BlackWhiteTest.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class BlackWhiteTest : FileTestBase + { + [Fact] + public void ImageShouldApplyBlackWhiteFilter() + { + const string path = "TestOutput/BlackWhite"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + 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($"{path}/{filename}")) + { + image.BlackWhite() + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/BlendTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/BlendTest.cs new file mode 100644 index 0000000000..2aa6ce0444 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/BlendTest.cs @@ -0,0 +1,44 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class BlendTest : FileTestBase + { + [Fact] + public void ImageShouldApplyBlendFilter() + { + const string path = "TestOutput/Blend"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + Image blend; + using (FileStream stream = File.OpenRead("TestImages/Formats/Bmp/Car.bmp")) + { + blend = new Image(stream); + } + + 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($"{path}/{filename}")) + { + image.Blend(blend) + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/BoxBlurTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/BoxBlurTest.cs new file mode 100644 index 0000000000..9c48752af0 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/BoxBlurTest.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class BoxBlurTest : FileTestBase + { + public static readonly TheoryData BoxBlurValues + = new TheoryData + { + 3 , + 5 , + }; + + [Theory] + [MemberData("BoxBlurValues")] + public void ImageShouldApplyBoxBlurFilter(int value) + { + const string path = "TestOutput/BoxBlur"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + Path.GetExtension(file); + + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.BoxBlur(value) + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/BrightnessTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/BrightnessTest.cs new file mode 100644 index 0000000000..9c0d840df7 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/BrightnessTest.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class BrightnessTest : FileTestBase + { + public static readonly TheoryData BrightnessValues + = new TheoryData + { + 50 , + -50 , + }; + + [Theory] + [MemberData("BrightnessValues")] + public void ImageShouldApplyBrightnessFilter(int value) + { + const string path = "TestOutput/Brightness"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + Path.GetExtension(file); + + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Brightness(value) + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/ColorBlindnessTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/ColorBlindnessTest.cs new file mode 100644 index 0000000000..9e6c9c1cb7 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/ColorBlindnessTest.cs @@ -0,0 +1,52 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class ColorBlindnessTest : FileTestBase + { + public static readonly TheoryData ColorBlindnessFilters + = new TheoryData + { + ColorBlindness.Achromatomaly, + ColorBlindness.Achromatopsia, + ColorBlindness.Deuteranomaly, + ColorBlindness.Deuteranopia, + ColorBlindness.Protanomaly, + ColorBlindness.Protanopia, + ColorBlindness.Tritanomaly, + ColorBlindness.Tritanopia + }; + + [Theory] + [MemberData("ColorBlindnessFilters")] + public void ImageShouldApplyColorBlindnessFilter(ColorBlindness colorBlindness) + { + const string path = "TestOutput/ColorBlindness"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-" + colorBlindness + Path.GetExtension(file); + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.ColorBlindness(colorBlindness) + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/ContrastTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/ContrastTest.cs new file mode 100644 index 0000000000..589fa8a6eb --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/ContrastTest.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class ContrastTest : FileTestBase + { + public static readonly TheoryData ContrastValues + = new TheoryData + { + 50 , + -50 , + }; + + [Theory] + [MemberData("ContrastValues")] + public void ImageShouldApplyContrastFilter(int value) + { + const string path = "TestOutput/Contrast"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + Path.GetExtension(file); + + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Contrast(value) + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/DetectEdgesTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/DetectEdgesTest.cs new file mode 100644 index 0000000000..9444130076 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/DetectEdgesTest.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class DetectEdgesTest : FileTestBase + { + public static readonly TheoryData DetectEdgesFilters + = new TheoryData + { + EdgeDetection.Kayyali, + EdgeDetection.Kirsch, + EdgeDetection.Lapacian3X3, + EdgeDetection.Lapacian5X5, + EdgeDetection.LaplacianOfGaussian, + EdgeDetection.Prewitt, + EdgeDetection.RobertsCross, + EdgeDetection.Scharr, + EdgeDetection.Sobel, + }; + + [Theory] + [MemberData("DetectEdgesFilters")] + public void ImageShouldApplyDetectEdgesFilter(EdgeDetection detector) + { + const string path = "TestOutput/DetectEdges"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-" + detector + Path.GetExtension(file); + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.DetectEdges(detector) + .Save(output); + } + } + } + } + } +} \ No newline at end of file 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/Filters/GrayscaleTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/GrayscaleTest.cs new file mode 100644 index 0000000000..da60c06d39 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/GrayscaleTest.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using Processors; + using System.IO; + + using Xunit; + + public class GrayscaleTest : FileTestBase + { + public static readonly TheoryData GrayscaleValues + = new TheoryData + { + GrayscaleMode.Bt709 , + GrayscaleMode.Bt601 , + }; + + [Theory] + [MemberData("GrayscaleValues")] + public void ImageShouldApplyGrayscaleFilter(GrayscaleMode value) + { + const string path = "TestOutput/Grayscale"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + Path.GetExtension(file); + + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Grayscale(value) + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/GuassianBlurTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/GuassianBlurTest.cs new file mode 100644 index 0000000000..667fac55c5 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/GuassianBlurTest.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class GuassianBlurTest : FileTestBase + { + public static readonly TheoryData GuassianBlurValues + = new TheoryData + { + 3 , + 5 , + }; + + [Theory] + [MemberData("GuassianBlurValues")] + public void ImageShouldApplyGuassianBlurFilter(int value) + { + const string path = "TestOutput/GuassianBlur"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + Path.GetExtension(file); + + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.GuassianBlur(value) + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/GuassianSharpenTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/GuassianSharpenTest.cs new file mode 100644 index 0000000000..47316e59e9 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/GuassianSharpenTest.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class GuassianSharpenTest : FileTestBase + { + public static readonly TheoryData GuassianSharpenValues + = new TheoryData + { + 3 , + 5 , + }; + + [Theory] + [MemberData("GuassianSharpenValues")] + public void ImageShouldApplyGuassianSharpenFilter(int value) + { + const string path = "TestOutput/GuassianSharpen"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + Path.GetExtension(file); + + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.GuassianSharpen(value) + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/HueTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/HueTest.cs new file mode 100644 index 0000000000..7ece88159b --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/HueTest.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class HueTest : FileTestBase + { + public static readonly TheoryData HueValues + = new TheoryData + { + 180 , + -180 , + }; + + [Theory] + [MemberData("HueValues")] + public void ImageShouldApplyHueFilter(int value) + { + const string path = "TestOutput/Hue"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + Path.GetExtension(file); + + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Hue(value) + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/InvertTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/InvertTest.cs new file mode 100644 index 0000000000..14b589b3f6 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/InvertTest.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class InvertTest : FileTestBase + { + [Fact] + public void ImageShouldApplyInvertFilter() + { + const string path = "TestOutput/Invert"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + 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($"{path}/{filename}")) + { + image.Invert() + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/KodachromeTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/KodachromeTest.cs new file mode 100644 index 0000000000..fa53bf5f35 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/KodachromeTest.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class KodachromeTest : FileTestBase + { + [Fact] + public void ImageShouldApplyKodachromeFilter() + { + const string path = "TestOutput/Kodachrome"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + 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($"{path}/{filename}")) + { + image.Kodachrome() + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/LomographTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/LomographTest.cs new file mode 100644 index 0000000000..e125bd8efd --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/LomographTest.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class LomographTest : FileTestBase + { + [Fact] + public void ImageShouldApplyLomographFilter() + { + const string path = "TestOutput/Lomograph"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + 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($"{path}/{filename}")) + { + image.Lomograph() + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/PixelateTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/PixelateTest.cs new file mode 100644 index 0000000000..b2ef2fb386 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/PixelateTest.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class PixelateTest : FileTestBase + { + public static readonly TheoryData PixelateValues + = new TheoryData + { + 4 , + 8 , + }; + + [Theory] + [MemberData("PixelateValues")] + public void ImageShouldApplyPixelateFilter(int value) + { + const string path = "TestOutput/Pixelate"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + Path.GetExtension(file); + + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Pixelate(value) + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/PolaroidTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/PolaroidTest.cs new file mode 100644 index 0000000000..b44cbd6d5d --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/PolaroidTest.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class PolaroidTest : FileTestBase + { + [Fact] + public void ImageShouldApplyPolaroidFilter() + { + const string path = "TestOutput/Polaroid"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + 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($"{path}/{filename}")) + { + image.Polaroid() + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/SaturationTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/SaturationTest.cs new file mode 100644 index 0000000000..2dbfadb686 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/SaturationTest.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class SaturationTest : FileTestBase + { + public static readonly TheoryData SaturationValues + = new TheoryData + { + 50 , + -50 , + }; + + [Theory] + [MemberData("SaturationValues")] + public void ImageShouldApplySaturationFilter(int value) + { + const string path = "TestOutput/Saturation"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + Path.GetExtension(file); + + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Saturation(value) + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/SepiaTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/SepiaTest.cs new file mode 100644 index 0000000000..f8017d4b4f --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/SepiaTest.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class SepiaTest : FileTestBase + { + [Fact] + public void ImageShouldApplySepiaFilter() + { + const string path = "TestOutput/Sepia"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + 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($"{path}/{filename}")) + { + image.Sepia() + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/CropTest.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/CropTest.cs new file mode 100644 index 0000000000..a92d0feb02 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/CropTest.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class CropTest : FileTestBase + { + [Fact] + public void ImageShouldApplyCropSampler() + { + const string path = "TestOutput/Crop"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + 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($"{path}/{filename}")) + { + image.Crop(image.Width / 2, image.Height / 2, this.ProgressUpdate) + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/EntropyCropTest.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/EntropyCropTest.cs new file mode 100644 index 0000000000..a1de089906 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/EntropyCropTest.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class EntropyCropTest : FileTestBase + { + public static readonly TheoryData EntropyCropValues + = new TheoryData + { + .25f , + .75f , + }; + + [Theory] + [MemberData("EntropyCropValues")] + public void ImageShouldApplyEntropyCropSampler(float value) + { + const string path = "TestOutput/EntropyCrop"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + Path.GetExtension(file); + + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.EntropyCrop(value) + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/PadTest.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/PadTest.cs new file mode 100644 index 0000000000..8a70b0887a --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/PadTest.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class PadTest : FileTestBase + { + [Fact] + public void ImageShouldApplyPadSampler() + { + const string path = "TestOutput/Pad"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + 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($"{path}/{filename}")) + { + image.Pad(image.Width + 50, image.Height + 50, this.ProgressUpdate) + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/ResizeTests.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/ResizeTests.cs new file mode 100644 index 0000000000..dbd36bee7a --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/ResizeTests.cs @@ -0,0 +1,319 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class ResizeTests : FileTestBase + { + private const string path = "TestOutput/Resize"; + + public static readonly TheoryData ReSamplers = + new TheoryData + { + { "Bicubic", new BicubicResampler() }, + { "Triangle", new TriangleResampler() }, + { "NearestNeighbor", new NearestNeighborResampler() }, + // Perf: Enable for local testing only + //{ "Box", new BoxResampler() }, + //{ "Lanczos3", new Lanczos3Resampler() }, + //{ "Lanczos5", new Lanczos5Resampler() }, + //{ "Lanczos8", new Lanczos8Resampler() }, + //{ "MitchellNetravali", new MitchellNetravaliResampler() }, + //{ "Hermite", new HermiteResampler() }, + //{ "Spline", new SplineResampler() }, + //{ "Robidoux", new RobidouxResampler() }, + //{ "RobidouxSharp", new RobidouxSharpResampler() }, + //{ "RobidouxSoft", new RobidouxSoftResampler() }, + //{ "Welch", new WelchResampler() } + }; + + [Theory] + [MemberData("ReSamplers")] + public void ImageShouldResize(string name, IResampler sampler) + { + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + 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($"{path}/{filename}")) + { + image.Resize(image.Width / 2, image.Height / 2, sampler, false, this.ProgressUpdate) + .Save(output); + } + } + } + } + + [Fact] + public void ImageShouldResizeWidthAndKeepAspect() + { + const string name = "FixedWidth"; + + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + 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($"{path}/{filename}")) + { + image.Resize(image.Width / 3, 0, new TriangleResampler(), false, this.ProgressUpdate) + .Save(output); + } + } + } + } + + [Fact] + public void ImageShouldResizeHeightAndKeepAspect() + { + const string name = "FixedHeight"; + + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + 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($"{path}/{filename}")) + { + image.Resize(0, image.Height / 3, new TriangleResampler(), false, this.ProgressUpdate) + .Save(output); + } + } + } + } + + [Fact] + public void ImageShouldResizeWithCropMode() + { + const string name = "Crop"; + + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + 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($"{path}/{filename}")) + { + ResizeOptions options = new ResizeOptions() + { + Size = new Size(image.Width / 2, image.Height) + }; + + image.Resize(options, this.ProgressUpdate) + .Save(output); + } + } + } + } + + [Fact] + public void ImageShouldResizeWithPadMode() + { + const string name = "Pad"; + + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + 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($"{path}/{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() + { + const string name = "BoxPad"; + + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + 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($"{path}/{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() + { + const string name = "Max"; + + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + 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($"{path}/{filename}")) + { + ResizeOptions options = new ResizeOptions() + { + Size = new Size(300, 300), + Mode = ResizeMode.Max + }; + + image.Resize(options, this.ProgressUpdate) + .Save(output); + } + } + } + } + + [Fact] + public void ImageShouldResizeWithMinMode() + { + const string name = "Min"; + + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + 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($"{path}/{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() + { + const string name = "Stretch"; + + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + 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($"{path}/{filename}")) + { + ResizeOptions options = new ResizeOptions() + { + Size = new Size(image.Width - 200, image.Height), + Mode = ResizeMode.Stretch + }; + + image.Resize(options, 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); + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/RotateFlipTest.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/RotateFlipTest.cs new file mode 100644 index 0000000000..72fcfb7b9d --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/RotateFlipTest.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class RotateFlipTest : FileTestBase + { + public static readonly TheoryData RotateFlipValues + = new TheoryData + { + { RotateType.None, FlipType.Vertical }, + { RotateType.None, FlipType.Horizontal }, + { RotateType.Rotate90, FlipType.None }, + { RotateType.Rotate180, FlipType.None }, + { RotateType.Rotate270, FlipType.None }, + }; + + [Theory] + [MemberData("RotateFlipValues")] + public void ImageShouldRotateFlip(RotateType rotateType, FlipType flipType) + { + const string path = "TestOutput/RotateFlip"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + 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($"{path}/{filename}")) + { + image.RotateFlip(rotateType, flipType, this.ProgressUpdate) + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/RotateTest.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/RotateTest.cs new file mode 100644 index 0000000000..7b38656b14 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/RotateTest.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class RotateTest : FileTestBase + { + public static readonly TheoryData RotateValues + = new TheoryData + { + 170 , + -170 , + }; + + [Theory] + [MemberData("RotateValues")] + public void ImageShouldApplyRotateSampler(float value) + { + const string path = "TestOutput/Rotate"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + Path.GetExtension(file); + + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Rotate(value) + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs deleted file mode 100644 index e81607b585..0000000000 --- a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs +++ /dev/null @@ -1,526 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Tests -{ - using System.IO; - - using Processors; - - using Xunit; - - public class SamplerTests : FileTestBase - { - public static readonly TheoryData ReSamplers = - new TheoryData - { - { "Bicubic", new BicubicResampler() }, - { "Triangle", new TriangleResampler() }, - { "NearestNeighbor", new NearestNeighborResampler() }, - // Perf: Enable for local testing only - //{ "Box", new BoxResampler() }, - //{ "Lanczos3", new Lanczos3Resampler() }, - //{ "Lanczos5", new Lanczos5Resampler() }, - //{ "Lanczos8", new Lanczos8Resampler() }, - //{ "MitchellNetravali", new MitchellNetravaliResampler() }, - //{ "Hermite", new HermiteResampler() }, - //{ "Spline", new SplineResampler() }, - //{ "Robidoux", new RobidouxResampler() }, - //{ "RobidouxSharp", new RobidouxSharpResampler() }, - //{ "RobidouxSoft", new RobidouxSoftResampler() }, - //{ "Welch", new WelchResampler() } - }; - - public static readonly TheoryData Samplers = new TheoryData - { - { "Resize", new ResizeProcessor(new BicubicResampler()) }, - { "Crop", new CropProcessor() } - }; - - public static readonly TheoryData RotateFlips = new TheoryData - { - { RotateType.None, FlipType.Vertical }, - { RotateType.None, FlipType.Horizontal }, - { RotateType.Rotate90, FlipType.None }, - { RotateType.Rotate180, FlipType.None }, - { RotateType.Rotate270, FlipType.None }, - }; - - [Theory] - [MemberData("Samplers")] - public void 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")] - public void ImageShouldResize(string name, IResampler sampler) - { - if (!Directory.Exists("TestOutput/Resize")) - { - Directory.CreateDirectory("TestOutput/Resize"); - } - - 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 / 2, image.Height / 2, sampler, false, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [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); - } - } -} diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/SkewTest.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/SkewTest.cs new file mode 100644 index 0000000000..4a0c77cfe0 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/SkewTest.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class SkewTest : FileTestBase + { + public static readonly TheoryData SkewValues + = new TheoryData + { + { 20, 10 }, + { -20, -10 } + }; + + [Theory] + [MemberData("SkewValues")] + public void ImageShouldApplySkewSampler(float x, float y) + { + const string path = "TestOutput/Skew"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + // 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.GetFileNameWithoutExtension(file) + "-" + x + "-" + y + Path.GetExtension(file); + + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Skew(x, y) + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/TestImages/Formats/Jpg/turtle.jpg b/tests/ImageProcessorCore.Tests/TestImages/Formats/Jpg/turtle.jpg new file mode 100644 index 0000000000..0e93194956 Binary files /dev/null and b/tests/ImageProcessorCore.Tests/TestImages/Formats/Jpg/turtle.jpg differ