From b5947b07b3b28fdf847bdf2ac391d4c04340ae5a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 30 Jun 2016 13:32:49 +1000 Subject: [PATCH 01/91] Experiments with a generic image Former-commit-id: 14be25789f2d8cbfc10b39557e63b60819d393ef Former-commit-id: 8445c082984b42411d1de44c0c36008bd77d036b Former-commit-id: a6d97b49cc8a1bc003b205b062e37abf45183f4e --- GenericImage/Common/Helpers/ImageMaths.cs | 40 +++++ GenericImage/GenericImage.xproj | 21 +++ GenericImage/IImageBase.cs | 14 ++ GenericImage/IPixelAccessor.cs | 15 ++ GenericImage/ImageRgba64.cs | 23 +++ GenericImage/PackedVectors/IPackedVector.cs | 38 +++++ GenericImage/PackedVectors/Rgba64.cs | 142 +++++++++++++++++ GenericImage/PixelAccessorRgba64.cs | 161 ++++++++++++++++++++ GenericImage/Properties/AssemblyInfo.cs | 19 +++ GenericImage/project.json | 27 ++++ ImageProcessorCore.sln | 9 +- 11 files changed, 508 insertions(+), 1 deletion(-) create mode 100644 GenericImage/Common/Helpers/ImageMaths.cs create mode 100644 GenericImage/GenericImage.xproj create mode 100644 GenericImage/IImageBase.cs create mode 100644 GenericImage/IPixelAccessor.cs create mode 100644 GenericImage/ImageRgba64.cs create mode 100644 GenericImage/PackedVectors/IPackedVector.cs create mode 100644 GenericImage/PackedVectors/Rgba64.cs create mode 100644 GenericImage/PixelAccessorRgba64.cs create mode 100644 GenericImage/Properties/AssemblyInfo.cs create mode 100644 GenericImage/project.json diff --git a/GenericImage/Common/Helpers/ImageMaths.cs b/GenericImage/Common/Helpers/ImageMaths.cs new file mode 100644 index 0000000000..2c5e4f884a --- /dev/null +++ b/GenericImage/Common/Helpers/ImageMaths.cs @@ -0,0 +1,40 @@ +namespace GenericImage +{ + public static class ImageMaths + { + /// + /// Restricts a value to be within a specified range. + /// + /// The value to clamp. + /// The minimum value. If value is less than min, min will be returned. + /// The maximum value. If value is greater than max, max will be returned. + /// The clamped value. + public static float Clamp(float value, float min, float max) + { + // This compare order is very important!!! + // We must follow HLSL behavior in the case user specified min value is bigger than max value. + // First we check to see if we're greater than the max + value = (value > max) ? max : value; + + // Then we check to see if we're less than the min. + value = (value < min) ? min : value; + + // There's no check to see if min > max. + return value; + } + + /// + /// Restricts a value to be within a specified range. + /// + /// The value to clamp. + /// The minimum value. If value is less than min, min will be returned. + /// The maximum value. If value is greater than max, max will be returned. + /// The clamped value. + public static int Clamp(int value, int min, int max) + { + value = (value > max) ? max : value; + value = (value < min) ? min : value; + return value; + } + } +} diff --git a/GenericImage/GenericImage.xproj b/GenericImage/GenericImage.xproj new file mode 100644 index 0000000000..9ffcf72232 --- /dev/null +++ b/GenericImage/GenericImage.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 45d91211-9d4a-4382-a114-8786859e302a + GenericImage + .\obj + .\bin\ + v4.5.2 + + + + 2.0 + + + diff --git a/GenericImage/IImageBase.cs b/GenericImage/IImageBase.cs new file mode 100644 index 0000000000..4de7d39685 --- /dev/null +++ b/GenericImage/IImageBase.cs @@ -0,0 +1,14 @@ +namespace GenericImage +{ + public interface IImageBase + where T : struct + { + T[] Pixels { get; } + + int Width { get; } + + int Height { get; } + + IPixelAccessor Lock(); + } +} diff --git a/GenericImage/IPixelAccessor.cs b/GenericImage/IPixelAccessor.cs new file mode 100644 index 0000000000..7eb9b445b6 --- /dev/null +++ b/GenericImage/IPixelAccessor.cs @@ -0,0 +1,15 @@ +namespace GenericImage +{ + using System; + + using GenericImage.PackedVectors; + + public interface IPixelAccessor : IDisposable + { + IPackedVector this[int x, int y] + { + get; + set; + } + } +} diff --git a/GenericImage/ImageRgba64.cs b/GenericImage/ImageRgba64.cs new file mode 100644 index 0000000000..1b673079d0 --- /dev/null +++ b/GenericImage/ImageRgba64.cs @@ -0,0 +1,23 @@ +namespace GenericImage +{ + public class ImageRgba64 : IImageBase + { + public ImageRgba64(int width, int height) + { + this.Width = width; + this.Height = height; + this.Pixels = new ulong[width * height * 4]; + } + + public ulong[] Pixels { get; } + + public int Width { get; } + + public int Height { get; } + + public IPixelAccessor Lock() + { + return new PixelAccessorRgba64(this); + } + } +} diff --git a/GenericImage/PackedVectors/IPackedVector.cs b/GenericImage/PackedVectors/IPackedVector.cs new file mode 100644 index 0000000000..be87f6d83e --- /dev/null +++ b/GenericImage/PackedVectors/IPackedVector.cs @@ -0,0 +1,38 @@ +namespace GenericImage.PackedVectors +{ + using System.Numerics; + + /// + /// An interface that converts packed vector types to and from values, + /// allowing multiple encodings to be manipulated in a generic way. + /// + /// + /// The type of object representing the packed value. + /// + public interface IPackedVector : IPackedVector + where TPacked : struct + { + /// + /// Gets or sets the packed representation of the value. + /// + TPacked PackedValue { get; set; } + } + + /// + /// An interface that converts packed vector types to and from values. + /// + public interface IPackedVector + { + /// + /// Sets the packed representation from a . + /// + /// The vector to pack. + void PackVector(Vector4 vector); + + /// + /// Expands the packed representation into a . + /// + /// The . + Vector4 ToVector4(); + } +} diff --git a/GenericImage/PackedVectors/Rgba64.cs b/GenericImage/PackedVectors/Rgba64.cs new file mode 100644 index 0000000000..832366ba17 --- /dev/null +++ b/GenericImage/PackedVectors/Rgba64.cs @@ -0,0 +1,142 @@ +namespace GenericImage.PackedVectors +{ + using System; + using System.Numerics; + + /// + /// Packed vector type containing four 16-bit unsigned normalized values ranging from 0 to 1. + /// + public struct Rgba64 : IPackedVector, IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + public Rgba64(float r, float g, float b, float a) + { + this.PackedValue = Pack(r, g, b, a); + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// Vector containing the components for the packed vector. + /// + public Rgba64(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + } + + /// + public ulong PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Rgba64 left, Rgba64 right) + { + return left.PackedValue == right.PackedValue; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator !=(Rgba64 left, Rgba64 right) + { + return left.PackedValue != right.PackedValue; + } + + /// + public void PackVector(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + } + + /// + public Vector4 ToVector4() + { + return new Vector4( + (this.PackedValue & 0xFFFF) / 65535f, + ((this.PackedValue >> 16) & 0xFFFF) / 65535f, + ((this.PackedValue >> 32) & 0xFFFF) / 65535f, + ((this.PackedValue >> 48) & 0xFFFF) / 65535f); + } + + /// + public override bool Equals(object obj) + { + return (obj is Rgba64) && this.Equals((Rgba64)obj); + } + + /// + public bool Equals(Rgba64 other) + { + return this.PackedValue == other.PackedValue; + } + + /// + /// Gets a string representation of the packed vector. + /// + /// A string representation of the packed vector. + public override string ToString() + { + return this.ToVector4().ToString(); + } + + /// + public override int GetHashCode() + { + return this.GetHashCode(this); + } + + /// + /// Sets the packed representation from the given component values. + /// + /// The x component. + /// The y component. + /// The z component. + /// The w component. + /// + /// The . + /// + private static ulong Pack(float x, float y, float z, float w) + { + return (ulong)Math.Round(ImageMaths.Clamp(x, 0, 1) * 65535f) | + ((ulong)Math.Round(ImageMaths.Clamp(y, 0, 1) * 65535f) << 16) | + ((ulong)Math.Round(ImageMaths.Clamp(z, 0, 1) * 65535f) << 32) | + ((ulong)Math.Round(ImageMaths.Clamp(w, 0, 1) * 65535f) << 48); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private int GetHashCode(Rgba64 packed) + { + return packed.PackedValue.GetHashCode(); + } + } +} diff --git a/GenericImage/PixelAccessorRgba64.cs b/GenericImage/PixelAccessorRgba64.cs new file mode 100644 index 0000000000..46dbd8deaa --- /dev/null +++ b/GenericImage/PixelAccessorRgba64.cs @@ -0,0 +1,161 @@ +namespace GenericImage +{ + using System; + using System.Runtime.InteropServices; + + using GenericImage.PackedVectors; + + /// + /// Provides per-pixel access to an images pixels. + /// + public sealed unsafe class PixelAccessorRgba64 : IPixelAccessor + { + /// + /// The position of the first pixel in the bitmap. + /// + private ulong* pixelsBase; + + /// + /// Provides a way to access the pixels from unmanaged memory. + /// + private GCHandle pixelsHandle; + + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// If the entity is disposed, it must not be disposed a second + /// time. The isDisposed field is set the first time the entity + /// is disposed. If the isDisposed field is true, then the Dispose() + /// method will not dispose again. This help not to prolong the entity's + /// life in the Garbage Collector. + /// + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The image to provide pixel access for. + /// + public PixelAccessorRgba64(IImageBase image) + { + //Guard.NotNull(image, nameof(image)); + //Guard.MustBeGreaterThan(image.Width, 0, "image width"); + //Guard.MustBeGreaterThan(image.Height, 0, "image height"); + + int size = image.Pixels.Length; + this.Width = image.Width; + this.Height = image.Height; + + // Assign the pointer. + // If buffer is allocated on Large Object Heap i.e > 85Kb, then we are going to pin it instead of making a copy. + if (size > 87040) + { + this.pixelsHandle = GCHandle.Alloc(image.Pixels, GCHandleType.Pinned); + this.pixelsBase = (ulong*)this.pixelsHandle.AddrOfPinnedObject().ToPointer(); + } + else + { + fixed (ulong* pbuffer = image.Pixels) + { + this.pixelsBase = pbuffer; + } + } + } + + /// + /// Finalizes an instance of the class. + /// + ~PixelAccessorRgba64() + { + this.Dispose(); + } + + /// + /// Gets the width of the image. + /// + public int Width { get; } + + /// + /// Gets the height of the image. + /// + public int Height { get; } + + /// + /// Gets or sets the color of a pixel at the specified position. + /// + /// + /// The x-coordinate of the pixel. Must be greater + /// than zero and smaller than the width of the pixel. + /// + /// + /// The y-coordinate of the pixel. Must be greater + /// than zero and smaller than the width of the pixel. + /// + /// The at the specified position. + public IPackedVector this[int x, int y] + { + get + { +#if DEBUG + if ((x < 0) || (x >= this.Width)) + { + throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width."); + } + + if ((y < 0) || (y >= this.Height)) + { + throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); + } +#endif + return *((Rgba64*)(this.pixelsBase + (((y * this.Width) + x) * 4))); + } + + set + { +#if DEBUG + if ((x < 0) || (x >= this.Width)) + { + throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width."); + } + + if ((y < 0) || (y >= this.Height)) + { + throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); + } +#endif + *(Rgba64*)(this.pixelsBase + (((y * this.Width) + x) * 4)) = (Rgba64)value; + } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + if (this.pixelsHandle.IsAllocated) + { + this.pixelsHandle.Free(); + } + + this.pixelsBase = null; + + // Note disposing is done. + this.isDisposed = true; + + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SuppressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + } +} diff --git a/GenericImage/Properties/AssemblyInfo.cs b/GenericImage/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..8dd0359516 --- /dev/null +++ b/GenericImage/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GenericImage")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("45d91211-9d4a-4382-a114-8786859e302a")] diff --git a/GenericImage/project.json b/GenericImage/project.json new file mode 100644 index 0000000000..617c22489d --- /dev/null +++ b/GenericImage/project.json @@ -0,0 +1,27 @@ +{ + "version": "1.0.0-*", + + "buildOptions": { + "allowUnsafe": true, + "debugType": "portable" + }, + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Diagnostics.Tools": "4.0.1", + "System.IO": "4.1.0", + "System.IO.Compression": "4.1.0", + "System.Linq": "4.1.0", + "System.Numerics.Vectors": "4.1.1", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.InteropServices": "4.1.0", + "System.Text.Encoding.Extensions": "4.0.11", + "System.Threading": "4.0.11", + "System.Threading.Tasks": "4.0.11", + "System.Threading.Tasks.Parallel": "4.0.1" + }, + "frameworks": { + "netstandard1.1": { } + } +} diff --git a/ImageProcessorCore.sln b/ImageProcessorCore.sln index 9d828a74a0..6e8766591b 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 @@ -21,6 +21,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{56801022 EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ImageProcessorCore.Benchmarks", "tests\ImageProcessorCore.Benchmarks\ImageProcessorCore.Benchmarks.xproj", "{299D8E18-102C-42DE-ADBF-79098EE706A8}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "GenericImage", "GenericImage\GenericImage.xproj", "{45D91211-9D4A-4382-A114-8786859E302A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -39,6 +41,10 @@ Global {299D8E18-102C-42DE-ADBF-79098EE706A8}.Debug|Any CPU.Build.0 = Debug|Any CPU {299D8E18-102C-42DE-ADBF-79098EE706A8}.Release|Any CPU.ActiveCfg = Release|Any CPU {299D8E18-102C-42DE-ADBF-79098EE706A8}.Release|Any CPU.Build.0 = Release|Any CPU + {45D91211-9D4A-4382-A114-8786859E302A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {45D91211-9D4A-4382-A114-8786859E302A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {45D91211-9D4A-4382-A114-8786859E302A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {45D91211-9D4A-4382-A114-8786859E302A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -47,5 +53,6 @@ Global {2AA31A1F-142C-43F4-8687-09ABCA4B3A26} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} {F836E8E6-B4D9-4208-8346-140C74678B91} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {299D8E18-102C-42DE-ADBF-79098EE706A8} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} + {45D91211-9D4A-4382-A114-8786859E302A} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} EndGlobalSection EndGlobal From 5433d52d41606160e1e161265f2ad3a1ebb30de6 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 6 Jul 2016 17:59:53 +1000 Subject: [PATCH 02/91] We might be getting somewhere. Fix ToBytes ulong Former-commit-id: 26d591aac6b99819a036f9667bf28685dad8ef4b Former-commit-id: 5ecd391866013328fab1bb81ff0e7c7b73fa8054 Former-commit-id: 1127739e8a52cf1d1f5a5826c483511029785791 --- GenericImage/IImageBase.cs | 8 +- GenericImage/ImageRgba64.cs | 8 +- GenericImage/PackedVectors/IPackedVector.cs | 6 + GenericImage/PackedVectors/Rgba32.cs | 155 ++++++++++++++++++++ GenericImage/PackedVectors/Rgba64.cs | 6 + GenericImage/PixelAccessorRgba64.cs | 25 +--- 6 files changed, 183 insertions(+), 25 deletions(-) create mode 100644 GenericImage/PackedVectors/Rgba32.cs diff --git a/GenericImage/IImageBase.cs b/GenericImage/IImageBase.cs index 4de7d39685..4e614a628d 100644 --- a/GenericImage/IImageBase.cs +++ b/GenericImage/IImageBase.cs @@ -1,9 +1,11 @@ namespace GenericImage { - public interface IImageBase - where T : struct + using GenericImage.PackedVectors; + + public interface IImageBase + where TPacked : IPackedVector { - T[] Pixels { get; } + TPacked[] Pixels { get; } int Width { get; } diff --git a/GenericImage/ImageRgba64.cs b/GenericImage/ImageRgba64.cs index 1b673079d0..9e58bf7819 100644 --- a/GenericImage/ImageRgba64.cs +++ b/GenericImage/ImageRgba64.cs @@ -1,15 +1,17 @@ namespace GenericImage { - public class ImageRgba64 : IImageBase + using GenericImage.PackedVectors; + + public class ImageRgba64 : IImageBase { public ImageRgba64(int width, int height) { this.Width = width; this.Height = height; - this.Pixels = new ulong[width * height * 4]; + this.Pixels = new Rgba64[width * height]; } - public ulong[] Pixels { get; } + public Rgba64[] Pixels { get; } public int Width { get; } diff --git a/GenericImage/PackedVectors/IPackedVector.cs b/GenericImage/PackedVectors/IPackedVector.cs index be87f6d83e..7bd809094c 100644 --- a/GenericImage/PackedVectors/IPackedVector.cs +++ b/GenericImage/PackedVectors/IPackedVector.cs @@ -34,5 +34,11 @@ /// /// The . Vector4 ToVector4(); + + /// + /// Expands the packed representation into a . + /// + /// The . + byte[] ToBytes(); } } diff --git a/GenericImage/PackedVectors/Rgba32.cs b/GenericImage/PackedVectors/Rgba32.cs new file mode 100644 index 0000000000..40bf1a8948 --- /dev/null +++ b/GenericImage/PackedVectors/Rgba32.cs @@ -0,0 +1,155 @@ +namespace GenericImage.PackedVectors +{ + using System; + using System.Numerics; + + /// + /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 255. + /// + public struct Rgba32 : IPackedVector, IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + public Rgba32(float r, float g, float b, float a) + { + this.PackedValue = Pack(r, g, b, a); + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// Vector containing the components for the packed vector. + /// + public Rgba32(Vector4 vector) + { + this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + } + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Rgba32 left, Rgba32 right) + { + return left.PackedValue == right.PackedValue; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator !=(Rgba32 left, Rgba32 right) + { + return left.PackedValue != right.PackedValue; + } + + /// + public void PackVector(Vector4 vector) + { + Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); + this.PackedValue = Pack(clamped.X, clamped.Y, clamped.Z, clamped.W); + } + + /// + public Vector4 ToVector4() + { + return new Vector4( + ((this.PackedValue >> 16) & 0xFF) / 255f, + ((this.PackedValue >> 8) & 0xFF) / 255f, + (this.PackedValue & 0xFF) / 255f, + ((this.PackedValue >> 24) & 0xFF) / 255f); + } + + /// + public byte[] ToBytes() + { + return new[] + { + (byte)((this.PackedValue >> 16) & 0xFF), + (byte)((this.PackedValue >> 8) & 0xFF), + (byte)(this.PackedValue & 0xFF), + (byte)((this.PackedValue >> 24) & 0xFF) + }; + } + + /// + public override bool Equals(object obj) + { + return (obj is Rgba32) && this.Equals((Rgba32)obj); + } + + /// + public bool Equals(Rgba32 other) + { + return this.PackedValue == other.PackedValue; + } + + /// + /// Gets a string representation of the packed vector. + /// + /// A string representation of the packed vector. + public override string ToString() + { + return this.ToVector4().ToString(); + } + + /// + public override int GetHashCode() + { + return this.GetHashCode(this); + } + + /// + /// Sets the packed representation from the given component values. + /// + /// The x component. + /// The y component. + /// The z component. + /// The w component. + /// + /// The . + /// + private static uint Pack(float x, float y, float z, float w) + { + return ((uint)Math.Round(x * 255f) << 16) | + ((uint)Math.Round(y * 255f) << 8) | + (uint)Math.Round(z * 255f) | + ((uint)Math.Round(w * 255f) << 24); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private int GetHashCode(Rgba32 packed) + { + return packed.PackedValue.GetHashCode(); + } + } +} diff --git a/GenericImage/PackedVectors/Rgba64.cs b/GenericImage/PackedVectors/Rgba64.cs index 832366ba17..ff529d15e2 100644 --- a/GenericImage/PackedVectors/Rgba64.cs +++ b/GenericImage/PackedVectors/Rgba64.cs @@ -80,6 +80,12 @@ ((this.PackedValue >> 48) & 0xFFFF) / 65535f); } + /// + public byte[] ToBytes() + { + throw new NotImplementedException(); + } + /// public override bool Equals(object obj) { diff --git a/GenericImage/PixelAccessorRgba64.cs b/GenericImage/PixelAccessorRgba64.cs index 46dbd8deaa..2dae8a0f16 100644 --- a/GenericImage/PixelAccessorRgba64.cs +++ b/GenericImage/PixelAccessorRgba64.cs @@ -13,7 +13,7 @@ /// /// The position of the first pixel in the bitmap. /// - private ulong* pixelsBase; + private Rgba64* pixelsBase; /// /// Provides a way to access the pixels from unmanaged memory. @@ -39,30 +39,17 @@ /// /// The image to provide pixel access for. /// - public PixelAccessorRgba64(IImageBase image) + public PixelAccessorRgba64(IImageBase image) { //Guard.NotNull(image, nameof(image)); //Guard.MustBeGreaterThan(image.Width, 0, "image width"); //Guard.MustBeGreaterThan(image.Height, 0, "image height"); - int size = image.Pixels.Length; this.Width = image.Width; this.Height = image.Height; - // Assign the pointer. - // If buffer is allocated on Large Object Heap i.e > 85Kb, then we are going to pin it instead of making a copy. - if (size > 87040) - { - this.pixelsHandle = GCHandle.Alloc(image.Pixels, GCHandleType.Pinned); - this.pixelsBase = (ulong*)this.pixelsHandle.AddrOfPinnedObject().ToPointer(); - } - else - { - fixed (ulong* pbuffer = image.Pixels) - { - this.pixelsBase = pbuffer; - } - } + this.pixelsHandle = GCHandle.Alloc(image.Pixels, GCHandleType.Pinned); + this.pixelsBase = (Rgba64*)this.pixelsHandle.AddrOfPinnedObject().ToPointer(); } /// @@ -110,7 +97,7 @@ throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); } #endif - return *((Rgba64*)(this.pixelsBase + (((y * this.Width) + x) * 4))); + return *(this.pixelsBase + ((y * this.Width) + x)); } set @@ -126,7 +113,7 @@ throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); } #endif - *(Rgba64*)(this.pixelsBase + (((y * this.Width) + x) * 4)) = (Rgba64)value; + *(this.pixelsBase + ((y * this.Width) + x)) = (Rgba64)value; } } From d54170028bc3d84a992834e84b2dcfdcea476579 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 6 Jul 2016 19:45:07 +1000 Subject: [PATCH 03/91] Packed vectors now convert correctly. Former-commit-id: e5599a44bb59ca107bc45f9e27c15d3488825a41 Former-commit-id: 77e6dea53e44f60b93a2067ef9fbf0de0ca63751 Former-commit-id: 89f8d00fcd9c1a2f56d9b892cb7f3406085ae727 --- GenericImage/PackedVectors/IPackedVector.cs | 9 ++++ GenericImage/PackedVectors/Rgba32.cs | 46 +++++++++++-------- GenericImage/PackedVectors/Rgba64.cs | 51 +++++++++++++-------- 3 files changed, 69 insertions(+), 37 deletions(-) diff --git a/GenericImage/PackedVectors/IPackedVector.cs b/GenericImage/PackedVectors/IPackedVector.cs index 7bd809094c..860409ba21 100644 --- a/GenericImage/PackedVectors/IPackedVector.cs +++ b/GenericImage/PackedVectors/IPackedVector.cs @@ -29,6 +29,15 @@ /// 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 . /// diff --git a/GenericImage/PackedVectors/Rgba32.cs b/GenericImage/PackedVectors/Rgba32.cs index 40bf1a8948..1786b36814 100644 --- a/GenericImage/PackedVectors/Rgba32.cs +++ b/GenericImage/PackedVectors/Rgba32.cs @@ -4,7 +4,7 @@ using System.Numerics; /// - /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 255. + /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 1. /// public struct Rgba32 : IPackedVector, IEquatable { @@ -17,18 +17,20 @@ /// The alpha component. public Rgba32(float r, float g, float b, float a) { - this.PackedValue = Pack(r, g, b, a); + Vector4 clamped = Vector4.Clamp(new Vector4(r, g, b, a), Vector4.Zero, Vector4.One) * 255f; + this.PackedValue = Pack(ref clamped); } /// /// Initializes a new instance of the struct. /// /// - /// Vector containing the components for the packed vector. + /// The vector containing the components for the packed vector. /// public Rgba32(Vector4 vector) { - this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; + this.PackedValue = Pack(ref clamped); } /// @@ -67,18 +69,25 @@ /// public void PackVector(Vector4 vector) { - Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); - this.PackedValue = Pack(clamped.X, clamped.Y, clamped.Z, clamped.W); + Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; + this.PackedValue = Pack(ref clamped); + } + + /// + public void PackBytes(byte x, byte y, byte z, byte w) + { + Vector4 vector = new Vector4(x, y, z, w); + this.PackedValue = Pack(ref vector); } /// public Vector4 ToVector4() { return new Vector4( - ((this.PackedValue >> 16) & 0xFF) / 255f, - ((this.PackedValue >> 8) & 0xFF) / 255f, - (this.PackedValue & 0xFF) / 255f, - ((this.PackedValue >> 24) & 0xFF) / 255f); + (this.PackedValue >> 16) & 0xFF, + (this.PackedValue >> 8) & 0xFF, + this.PackedValue & 0xFF, + (this.PackedValue >> 24) & 0xFF) / 255f; } /// @@ -123,19 +132,18 @@ /// /// Sets the packed representation from the given component values. /// - /// The x component. - /// The y component. - /// The z component. - /// The w component. + /// + /// The vector containing the components for the packed vector. + /// /// /// The . /// - private static uint Pack(float x, float y, float z, float w) + private static uint Pack(ref Vector4 vector) { - return ((uint)Math.Round(x * 255f) << 16) | - ((uint)Math.Round(y * 255f) << 8) | - (uint)Math.Round(z * 255f) | - ((uint)Math.Round(w * 255f) << 24); + return ((uint)Math.Round(vector.X) << 16) | + ((uint)Math.Round(vector.Y) << 8) | + (uint)Math.Round(vector.Z) | + ((uint)Math.Round(vector.W) << 24); } /// diff --git a/GenericImage/PackedVectors/Rgba64.cs b/GenericImage/PackedVectors/Rgba64.cs index ff529d15e2..06821f63ed 100644 --- a/GenericImage/PackedVectors/Rgba64.cs +++ b/GenericImage/PackedVectors/Rgba64.cs @@ -17,18 +17,20 @@ /// The alpha component. public Rgba64(float r, float g, float b, float a) { - this.PackedValue = Pack(r, g, b, a); + Vector4 clamped = Vector4.Clamp(new Vector4(r, g, b, a), Vector4.Zero, Vector4.One) * 65535f; + this.PackedValue = Pack(ref clamped); } /// /// Initializes a new instance of the struct. /// /// - /// Vector containing the components for the packed vector. + /// The vector containing the components for the packed vector. /// public Rgba64(Vector4 vector) { - this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 65535f; + this.PackedValue = Pack(ref clamped); } /// @@ -67,23 +69,37 @@ /// public void PackVector(Vector4 vector) { - this.PackedValue = Pack(vector.X, vector.Y, vector.Z, vector.W); + Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 65535f; + this.PackedValue = Pack(ref clamped); + } + + /// + public void PackBytes(byte x, byte y, byte z, byte w) + { + Vector4 vector = (new Vector4(x, y, z, w) / 255f) * 65535f; + this.PackedValue = Pack(ref vector); } /// public Vector4 ToVector4() { return new Vector4( - (this.PackedValue & 0xFFFF) / 65535f, - ((this.PackedValue >> 16) & 0xFFFF) / 65535f, - ((this.PackedValue >> 32) & 0xFFFF) / 65535f, - ((this.PackedValue >> 48) & 0xFFFF) / 65535f); + this.PackedValue & 0xFFFF, + (this.PackedValue >> 16) & 0xFFFF, + (this.PackedValue >> 32) & 0xFFFF, + (this.PackedValue >> 48) & 0xFFFF) / 65535f; } /// public byte[] ToBytes() { - throw new NotImplementedException(); + return new[] + { + (byte)((this.PackedValue >> 40) & 255), + (byte)((this.PackedValue >> 24) & 255), + (byte)((this.PackedValue >> 8) & 255), + (byte)((this.PackedValue >> 56) & 255) + }; } /// @@ -116,19 +132,18 @@ /// /// Sets the packed representation from the given component values. /// - /// The x component. - /// The y component. - /// The z component. - /// The w component. + /// + /// The vector containing the components for the packed vector. + /// /// /// The . /// - private static ulong Pack(float x, float y, float z, float w) + private static ulong Pack(ref Vector4 vector) { - return (ulong)Math.Round(ImageMaths.Clamp(x, 0, 1) * 65535f) | - ((ulong)Math.Round(ImageMaths.Clamp(y, 0, 1) * 65535f) << 16) | - ((ulong)Math.Round(ImageMaths.Clamp(z, 0, 1) * 65535f) << 32) | - ((ulong)Math.Round(ImageMaths.Clamp(w, 0, 1) * 65535f) << 48); + return (ulong)Math.Round(vector.X) | + ((ulong)Math.Round(vector.Y) << 16) | + ((ulong)Math.Round(vector.Z) << 32) | + ((ulong)Math.Round(vector.W) << 48); } /// From dacf62cab15761f84ec7df2ac52241ca3055fb17 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 6 Jul 2016 20:57:35 +1000 Subject: [PATCH 04/91] ImageRgba32 Former-commit-id: e8d174508130e99cb9152e3c6dad690232425075 Former-commit-id: 4191408f0e2678dfb6937918c46a0d588da073d7 Former-commit-id: 3dc34dfac9b7e02f3dca4e80b1d8cf41fe40ee35 --- GenericImage/ImageRgba32.cs | 25 +++++ GenericImage/PackedVectors/Rgba64.cs | 8 +- GenericImage/PixelAccessorRgba32.cs | 148 +++++++++++++++++++++++++++ 3 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 GenericImage/ImageRgba32.cs create mode 100644 GenericImage/PixelAccessorRgba32.cs diff --git a/GenericImage/ImageRgba32.cs b/GenericImage/ImageRgba32.cs new file mode 100644 index 0000000000..8ec301c2ef --- /dev/null +++ b/GenericImage/ImageRgba32.cs @@ -0,0 +1,25 @@ +namespace GenericImage +{ + using GenericImage.PackedVectors; + + public class ImageRgba32 : IImageBase + { + public ImageRgba32(int width, int height) + { + this.Width = width; + this.Height = height; + this.Pixels = new Rgba32[width * height]; + } + + public Rgba32[] Pixels { get; } + + public int Width { get; } + + public int Height { get; } + + public IPixelAccessor Lock() + { + return new PixelAccessorRgba32(this); + } + } +} diff --git a/GenericImage/PackedVectors/Rgba64.cs b/GenericImage/PackedVectors/Rgba64.cs index 06821f63ed..53b01d1c33 100644 --- a/GenericImage/PackedVectors/Rgba64.cs +++ b/GenericImage/PackedVectors/Rgba64.cs @@ -84,9 +84,9 @@ public Vector4 ToVector4() { return new Vector4( - this.PackedValue & 0xFFFF, - (this.PackedValue >> 16) & 0xFFFF, (this.PackedValue >> 32) & 0xFFFF, + (this.PackedValue >> 16) & 0xFFFF, + this.PackedValue & 0xFFFF, (this.PackedValue >> 48) & 0xFFFF) / 65535f; } @@ -140,9 +140,9 @@ /// private static ulong Pack(ref Vector4 vector) { - return (ulong)Math.Round(vector.X) | + return ((ulong)Math.Round(vector.Z) << 32) | ((ulong)Math.Round(vector.Y) << 16) | - ((ulong)Math.Round(vector.Z) << 32) | + (ulong)Math.Round(vector.X) | ((ulong)Math.Round(vector.W) << 48); } diff --git a/GenericImage/PixelAccessorRgba32.cs b/GenericImage/PixelAccessorRgba32.cs new file mode 100644 index 0000000000..3af32aae22 --- /dev/null +++ b/GenericImage/PixelAccessorRgba32.cs @@ -0,0 +1,148 @@ +namespace GenericImage +{ + using System; + using System.Runtime.InteropServices; + + using GenericImage.PackedVectors; + + /// + /// Provides per-pixel access to an images pixels. + /// + public sealed unsafe class PixelAccessorRgba32 : IPixelAccessor + { + /// + /// The position of the first pixel in the bitmap. + /// + private Rgba32* pixelsBase; + + /// + /// Provides a way to access the pixels from unmanaged memory. + /// + private GCHandle pixelsHandle; + + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// If the entity is disposed, it must not be disposed a second + /// time. The isDisposed field is set the first time the entity + /// is disposed. If the isDisposed field is true, then the Dispose() + /// method will not dispose again. This help not to prolong the entity's + /// life in the Garbage Collector. + /// + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The image to provide pixel access for. + /// + public PixelAccessorRgba32(IImageBase image) + { + //Guard.NotNull(image, nameof(image)); + //Guard.MustBeGreaterThan(image.Width, 0, "image width"); + //Guard.MustBeGreaterThan(image.Height, 0, "image height"); + + this.Width = image.Width; + this.Height = image.Height; + + this.pixelsHandle = GCHandle.Alloc(image.Pixels, GCHandleType.Pinned); + this.pixelsBase = (Rgba32*)this.pixelsHandle.AddrOfPinnedObject().ToPointer(); + } + + /// + /// Finalizes an instance of the class. + /// + ~PixelAccessorRgba32() + { + this.Dispose(); + } + + /// + /// Gets the width of the image. + /// + public int Width { get; } + + /// + /// Gets the height of the image. + /// + public int Height { get; } + + /// + /// Gets or sets the color of a pixel at the specified position. + /// + /// + /// The x-coordinate of the pixel. Must be greater + /// than zero and smaller than the width of the pixel. + /// + /// + /// The y-coordinate of the pixel. Must be greater + /// than zero and smaller than the width of the pixel. + /// + /// The at the specified position. + public IPackedVector this[int x, int y] + { + get + { +#if DEBUG + if ((x < 0) || (x >= this.Width)) + { + throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width."); + } + + if ((y < 0) || (y >= this.Height)) + { + throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); + } +#endif + return *(this.pixelsBase + ((y * this.Width) + x)); + } + + set + { +#if DEBUG + if ((x < 0) || (x >= this.Width)) + { + throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width."); + } + + if ((y < 0) || (y >= this.Height)) + { + throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); + } +#endif + *(this.pixelsBase + ((y * this.Width) + x)) = (Rgba32)value; + } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + if (this.pixelsHandle.IsAllocated) + { + this.pixelsHandle.Free(); + } + + this.pixelsBase = null; + + // Note disposing is done. + this.isDisposed = true; + + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SuppressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + } +} From ffaddb984c8c2a046195559982df87f9814ff298 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 7 Jul 2016 00:42:41 +1000 Subject: [PATCH 05/91] Begin switching to generics Former-commit-id: 88d16159717d23208e4e151e5434aca3a27ff3b2 Former-commit-id: 2a7d97bb9c9a42b3e6ce720bd0da36d0b3fea5d8 Former-commit-id: d6db3166d72eb48c18e2fc0c093ba42462e2a208 --- GenericImage/ImageBase.cs | 188 ++++++++++ GenericImage/ImageRgba32.cs | 18 +- GenericImage/PackedVectors/Rgba64.cs | 4 +- GenericImage/PixelAccessorRgba32.cs | 2 +- .../Colors/Colorspaces/Bgra32.cs | 336 +++++++++--------- src/ImageProcessorCore/GenericImageFrame.cs | 40 +++ src/ImageProcessorCore/IImageBase.cs | 18 + src/ImageProcessorCore/IImageFrame.cs | 7 + src/ImageProcessorCore/IPixelAccessor.cs | 33 ++ src/ImageProcessorCore/Image.cs | 259 +------------- src/ImageProcessorCore/ImageBase.cs | 87 ++--- src/ImageProcessorCore/ImageFrame.cs | 34 +- src/ImageProcessorCore/PackedVector/Bgra32.cs | 168 +++++++++ .../PackedVector/IPackedVector.cs | 61 ++++ src/ImageProcessorCore/PixelAccessor.cs | 19 +- 15 files changed, 747 insertions(+), 527 deletions(-) create mode 100644 GenericImage/ImageBase.cs create mode 100644 src/ImageProcessorCore/GenericImageFrame.cs create mode 100644 src/ImageProcessorCore/IImageBase.cs create mode 100644 src/ImageProcessorCore/IImageFrame.cs create mode 100644 src/ImageProcessorCore/IPixelAccessor.cs create mode 100644 src/ImageProcessorCore/PackedVector/Bgra32.cs create mode 100644 src/ImageProcessorCore/PackedVector/IPackedVector.cs diff --git a/GenericImage/ImageBase.cs b/GenericImage/ImageBase.cs new file mode 100644 index 0000000000..9eed0dcccb --- /dev/null +++ b/GenericImage/ImageBase.cs @@ -0,0 +1,188 @@ +namespace GenericImage +{ + using System; + + using GenericImage.PackedVectors; + + /// + /// Encapsulates the basic properties and methods required to manipulate images + /// in different pixel formats. + /// + /// + /// The packed vector pixels format. + /// + public abstract class ImageBase + where TPacked : IPackedVector + { + /// + /// Initializes a new instance of the class. + /// + protected ImageBase() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The width of the image in pixels. + /// The height of the image in pixels. + /// + /// Thrown if either or are less than or equal to 0. + /// + protected ImageBase(int width, int height) + { + // Guard.MustBeGreaterThan(width, 0, nameof(width)); + // Guard.MustBeGreaterThan(height, 0, nameof(height)); + + this.Width = width; + this.Height = height; + this.Pixels = new TPacked[width * height]; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The other to create this instance from. + /// + /// + /// Thrown if the given is null. + /// + protected ImageBase(ImageBase other) + { + // Guard.NotNull(other, nameof(other), "Other image cannot be null."); + + this.Width = other.Width; + this.Height = other.Height; + this.Quality = other.Quality; + this.FrameDelay = other.FrameDelay; + + // Copy the pixels. + this.Pixels = new TPacked[this.Width * this.Height]; + Array.Copy(other.Pixels, this.Pixels, other.Pixels.Length); + } + + /// + /// Gets the pixels as an array of the given packed pixel format. + /// + public TPacked[] Pixels { get; private set; } + + /// + /// Gets the width in pixels. + /// + public int Width { get; private set; } + + /// + /// Gets the height in pixels. + /// + public int Height { get; private set; } + + /// + /// Gets the pixel ratio made up of the width and height. + /// + public double PixelRatio => (double)this.Width / this.Height; + + /// + /// Gets the representing the bounds of the image. + /// + // public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height); + + /// + /// Gets or sets th quality of the image. This affects the output quality of lossy image formats. + /// + public int Quality { get; set; } + + /// + /// Gets or sets the frame delay for animated images. + /// If not 0, this field specifies the number of hundredths (1/100) of a second to + /// wait before continuing with the processing of the Data Stream. + /// The clock starts ticking immediately after the graphic is rendered. + /// + public int FrameDelay { get; set; } + + /// + /// Sets the pixel array of the image to the given value. + /// + /// The new width of the image. Must be greater than zero. + /// The new height of the image. Must be greater than zero. + /// + /// The array with colors. Must be a multiple of the width and height. + /// + /// + /// Thrown if either or are less than or equal to 0. + /// + /// + /// Thrown if the length is not equal to Width * Height. + /// + public void SetPixels(int width, int height, TPacked[] pixels) + { + if (width <= 0) + { + throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than or equals than zero."); + } + + if (height <= 0) + { + throw new ArgumentOutOfRangeException(nameof(height), "Height must be greater than or equal than zero."); + } + + if (pixels.Length != width * height) + { + throw new ArgumentException("Pixel array must have the length of Width * Height."); + } + + this.Width = width; + this.Height = height; + this.Pixels = pixels; + } + + /// + /// Sets the pixel array of the image to the given value, creating a copy of + /// the original pixels. + /// + /// The new width of the image. Must be greater than zero. + /// The new height of the image. Must be greater than zero. + /// + /// The array with colors. Must be a multiple of four times the width and height. + /// + /// + /// Thrown if either or are less than or equal to 0. + /// + /// + /// Thrown if the length is not equal to Width * Height. + /// + public void ClonePixels(int width, int height, TPacked[] pixels) + { + if (width <= 0) + { + throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than or equals than zero."); + } + + if (height <= 0) + { + throw new ArgumentOutOfRangeException(nameof(height), "Height must be greater than or equal than zero."); + } + + if (pixels.Length != width * height) + { + throw new ArgumentException("Pixel array must have the length of Width * Height."); + } + + this.Width = width; + this.Height = height; + + // Copy the pixels. + this.Pixels = new TPacked[pixels.Length]; + Array.Copy(pixels, this.Pixels, pixels.Length); + } + + /// + /// Locks the image providing access to the pixels. + /// + /// It is imperative that the accessor is correctly disposed off after use. + /// + /// + /// The + public abstract IPixelAccessor Lock(); + } +} diff --git a/GenericImage/ImageRgba32.cs b/GenericImage/ImageRgba32.cs index 8ec301c2ef..c5a48d0f93 100644 --- a/GenericImage/ImageRgba32.cs +++ b/GenericImage/ImageRgba32.cs @@ -2,22 +2,10 @@ { using GenericImage.PackedVectors; - public class ImageRgba32 : IImageBase + public class ImageRgba32 : ImageBase { - public ImageRgba32(int width, int height) - { - this.Width = width; - this.Height = height; - this.Pixels = new Rgba32[width * height]; - } - - public Rgba32[] Pixels { get; } - - public int Width { get; } - - public int Height { get; } - - public IPixelAccessor Lock() + /// + public override IPixelAccessor Lock() { return new PixelAccessorRgba32(this); } diff --git a/GenericImage/PackedVectors/Rgba64.cs b/GenericImage/PackedVectors/Rgba64.cs index 53b01d1c33..f62eeb4de8 100644 --- a/GenericImage/PackedVectors/Rgba64.cs +++ b/GenericImage/PackedVectors/Rgba64.cs @@ -140,9 +140,9 @@ /// private static ulong Pack(ref Vector4 vector) { - return ((ulong)Math.Round(vector.Z) << 32) | + return ((ulong)Math.Round(vector.X) << 32) | ((ulong)Math.Round(vector.Y) << 16) | - (ulong)Math.Round(vector.X) | + (ulong)Math.Round(vector.Z) | ((ulong)Math.Round(vector.W) << 48); } diff --git a/GenericImage/PixelAccessorRgba32.cs b/GenericImage/PixelAccessorRgba32.cs index 3af32aae22..25b49ea210 100644 --- a/GenericImage/PixelAccessorRgba32.cs +++ b/GenericImage/PixelAccessorRgba32.cs @@ -39,7 +39,7 @@ /// /// The image to provide pixel access for. /// - public PixelAccessorRgba32(IImageBase image) + public PixelAccessorRgba32(ImageBase image) { //Guard.NotNull(image, nameof(image)); //Guard.MustBeGreaterThan(image.Width, 0, "image width"); diff --git a/src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs b/src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs index e31d693bb8..cc4fad541e 100644 --- a/src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs +++ b/src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs @@ -1,168 +1,168 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - using System.ComponentModel; - using System.Numerics; - - /// - /// Represents an BGRA (blue, green, red, alpha) color. - /// - public struct Bgra32 : IEquatable - { - /// - /// Represents a 32 bit that has B, G, R, and A values set to zero. - /// - public static readonly Bgra32 Empty = default(Bgra32); - - /// - /// The backing vector for SIMD support. - /// - private Vector4 backingVector; - - /// - /// Initializes a new instance of the struct. - /// - /// The blue component of this . - /// The green component of this . - /// The red component of this . - /// The alpha component of this . - public Bgra32(byte b, byte g, byte r, byte a = 255) - : this() - { - this.backingVector = Vector4.Clamp(new Vector4(b, g, r, a), Vector4.Zero, new Vector4(255)); - } - - /// - /// Gets the blue component of the color - /// - public byte B => (byte)this.backingVector.X; - - /// - /// Gets the green component of the color - /// - public byte G => (byte)this.backingVector.Y; - - /// - /// Gets the red component of the color - /// - public byte R => (byte)this.backingVector.Z; - - /// - /// Gets the alpha component of the color - /// - public byte A => (byte)this.backingVector.W; - - /// - /// Gets the integer representation of the color. - /// - public int Bgra => (this.R << 16) | (this.G << 8) | (this.B << 0) | (this.A << 24); - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// - /// The instance of to convert. - /// - /// - /// An instance of . - /// - 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); - } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(Bgra32 left, Bgra32 right) - { - return left.Equals(right); - } - - /// - /// Compares two objects for inequality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(Bgra32 left, Bgra32 right) - { - return !left.Equals(right); - } - - /// - public override bool Equals(object obj) - { - if (obj is Bgra32) - { - Bgra32 color = (Bgra32)obj; - - return this.backingVector == color.backingVector; - } - - return false; - } - - /// - public override int GetHashCode() - { - return GetHashCode(this); - } - - /// - public override string ToString() - { - if (this.IsEmpty) - { - return "Bgra32 [ Empty ]"; - } - - return $"Bgra32 [ B={this.B}, G={this.G}, R={this.R}, A={this.A} ]"; - } - - /// - public bool Equals(Bgra32 other) - { - return this.backingVector.Equals(other.backingVector); - } - - /// - /// Returns the hash code for this instance. - /// - /// - /// The instance of to return the hash code for. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// - private static int GetHashCode(Bgra32 color) => color.backingVector.GetHashCode(); - } -} +//// +//// Copyright (c) James Jackson-South and contributors. +//// Licensed under the Apache License, Version 2.0. +//// + +//namespace ImageProcessorCore +//{ +// using System; +// using System.ComponentModel; +// using System.Numerics; + +// /// +// /// Represents an BGRA (blue, green, red, alpha) color. +// /// +// public struct Bgra32 : IEquatable +// { +// /// +// /// Represents a 32 bit that has B, G, R, and A values set to zero. +// /// +// public static readonly Bgra32 Empty = default(Bgra32); + +// /// +// /// The backing vector for SIMD support. +// /// +// private Vector4 backingVector; + +// /// +// /// Initializes a new instance of the struct. +// /// +// /// The blue component of this . +// /// The green component of this . +// /// The red component of this . +// /// The alpha component of this . +// public Bgra32(byte b, byte g, byte r, byte a = 255) +// : this() +// { +// this.backingVector = Vector4.Clamp(new Vector4(b, g, r, a), Vector4.Zero, new Vector4(255)); +// } + +// /// +// /// Gets the blue component of the color +// /// +// public byte B => (byte)this.backingVector.X; + +// /// +// /// Gets the green component of the color +// /// +// public byte G => (byte)this.backingVector.Y; + +// /// +// /// Gets the red component of the color +// /// +// public byte R => (byte)this.backingVector.Z; + +// /// +// /// Gets the alpha component of the color +// /// +// public byte A => (byte)this.backingVector.W; + +// /// +// /// Gets the integer representation of the color. +// /// +// public int Bgra => (this.R << 16) | (this.G << 8) | (this.B << 0) | (this.A << 24); + +// /// +// /// Gets a value indicating whether this is empty. +// /// +// [EditorBrowsable(EditorBrowsableState.Never)] +// public bool IsEmpty => this.Equals(Empty); + +// /// +// /// Allows the implicit conversion of an instance of to a +// /// . +// /// +// /// +// /// The instance of to convert. +// /// +// /// +// /// An instance of . +// /// +// 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); +// } + +// /// +// /// Compares two objects for equality. +// /// +// /// +// /// The on the left side of the operand. +// /// +// /// +// /// The on the right side of the operand. +// /// +// /// +// /// True if the current left is equal to the parameter; otherwise, false. +// /// +// public static bool operator ==(Bgra32 left, Bgra32 right) +// { +// return left.Equals(right); +// } + +// /// +// /// Compares two objects for inequality. +// /// +// /// +// /// The on the left side of the operand. +// /// +// /// +// /// The on the right side of the operand. +// /// +// /// +// /// True if the current left is unequal to the parameter; otherwise, false. +// /// +// public static bool operator !=(Bgra32 left, Bgra32 right) +// { +// return !left.Equals(right); +// } + +// /// +// public override bool Equals(object obj) +// { +// if (obj is Bgra32) +// { +// Bgra32 color = (Bgra32)obj; + +// return this.backingVector == color.backingVector; +// } + +// return false; +// } + +// /// +// public override int GetHashCode() +// { +// return GetHashCode(this); +// } + +// /// +// public override string ToString() +// { +// if (this.IsEmpty) +// { +// return "Bgra32 [ Empty ]"; +// } + +// return $"Bgra32 [ B={this.B}, G={this.G}, R={this.R}, A={this.A} ]"; +// } + +// /// +// public bool Equals(Bgra32 other) +// { +// return this.backingVector.Equals(other.backingVector); +// } + +// /// +// /// Returns the hash code for this instance. +// /// +// /// +// /// The instance of to return the hash code for. +// /// +// /// +// /// A 32-bit signed integer that is the hash code for this instance. +// /// +// private static int GetHashCode(Bgra32 color) => color.backingVector.GetHashCode(); +// } +//} diff --git a/src/ImageProcessorCore/GenericImageFrame.cs b/src/ImageProcessorCore/GenericImageFrame.cs new file mode 100644 index 0000000000..b0dbe341cd --- /dev/null +++ b/src/ImageProcessorCore/GenericImageFrame.cs @@ -0,0 +1,40 @@ +// +// 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. + /// + /// + /// The packed vector pixels format. + /// + public abstract class GenericImageFrame : ImageBase, IImageFrame + where TPacked : IPackedVector + { + /// + /// Initializes a new instance of the class. + /// + protected GenericImageFrame() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The other to create this instance from. + /// + /// + /// Thrown if the given is null. + /// + protected GenericImageFrame(GenericImageFrame other) + : base(other) + { + } + } +} diff --git a/src/ImageProcessorCore/IImageBase.cs b/src/ImageProcessorCore/IImageBase.cs new file mode 100644 index 0000000000..85ffa0086b --- /dev/null +++ b/src/ImageProcessorCore/IImageBase.cs @@ -0,0 +1,18 @@ +namespace ImageProcessorCore +{ + public interface IImageBase + where TPacked : IPackedVector + { + Rectangle Bounds { get; } + int FrameDelay { get; set; } + int Height { get; } + double PixelRatio { get; } + TPacked[] Pixels { get; } + int Quality { get; set; } + int Width { get; } + + void ClonePixels(int width, int height, TPacked[] pixels); + IPixelAccessor Lock(); + void SetPixels(int width, int height, TPacked[] pixels); + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore/IImageFrame.cs b/src/ImageProcessorCore/IImageFrame.cs new file mode 100644 index 0000000000..1565879a76 --- /dev/null +++ b/src/ImageProcessorCore/IImageFrame.cs @@ -0,0 +1,7 @@ +namespace ImageProcessorCore +{ + public interface IImageFrame : IImageBase + where TPacked : IPackedVector + { + } +} diff --git a/src/ImageProcessorCore/IPixelAccessor.cs b/src/ImageProcessorCore/IPixelAccessor.cs new file mode 100644 index 0000000000..10838726ca --- /dev/null +++ b/src/ImageProcessorCore/IPixelAccessor.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + + /// + /// Encapsulates properties to provides per-pixel access to an images pixels. + /// + public interface IPixelAccessor : IDisposable + { + /// + /// Gets or sets the pixel at the specified position. + /// + /// + /// The x-coordinate of the pixel. Must be greater + /// than zero and smaller than the width of the pixel. + /// + /// + /// The y-coordinate of the pixel. Must be greater + /// than zero and smaller than the width of the pixel. + /// + /// The at the specified position. + IPackedVector this[int x, int y] + { + get; + set; + } + } +} diff --git a/src/ImageProcessorCore/Image.cs b/src/ImageProcessorCore/Image.cs index 3cde695432..88d4d1b6c0 100644 --- a/src/ImageProcessorCore/Image.cs +++ b/src/ImageProcessorCore/Image.cs @@ -1,61 +1,19 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore +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. /// /// - /// The image data is always stored in RGBA format, where the red, green, blue, and - /// alpha values are floating point numbers. + /// The image data is always stored in format, where the blue, green, red, and + /// alpha values are 8 bit unsigned bytes. /// [DebuggerDisplay("Image: {Width}x{Height}")] - public class Image : ImageBase + public class Image : GenericImage { - /// - /// 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 /// by making a copy from another image. @@ -63,9 +21,9 @@ namespace ImageProcessorCore /// The other image, where the clone should be made from. /// is null. public Image(Image other) - : base(other) { - foreach (ImageFrame frame in other.Frames) + // TODO: Check this. Not sure why I was getting a cast warning. + foreach (ImageFrame frame in other.Frames.Cast()) { if (frame != null) { @@ -79,209 +37,10 @@ namespace ImageProcessorCore this.CurrentImageFormat = other.CurrentImageFormat; } - /// - /// 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); - } - - /// - /// 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. - /// - /// The stream containing image information. - /// - /// Thrown if the stream is not readable nor seekable. - /// - private void Load(Stream stream) + /// + public override IPixelAccessor Lock() { - 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()); + return new PixelAccessor(this); } } } diff --git a/src/ImageProcessorCore/ImageBase.cs b/src/ImageProcessorCore/ImageBase.cs index 5b7ab65f83..4dcb73174c 100644 --- a/src/ImageProcessorCore/ImageBase.cs +++ b/src/ImageProcessorCore/ImageBase.cs @@ -8,25 +8,24 @@ namespace ImageProcessorCore using System; /// - /// The base class of all images. Encapsulates the basic properties and methods - /// required to manipulate images. + /// The base class of all images. Encapsulates the basic properties and methods required to manipulate images + /// in different pixel formats. /// - public abstract class ImageBase + /// + /// The packed vector pixels format. + /// + public abstract class ImageBase : IImageBase + where TPacked : IPackedVector { /// - /// The array of pixels. - /// - private float[] pixelsArray; - - /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// protected ImageBase() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The width of the image in pixels. /// The height of the image in pixels. @@ -40,21 +39,19 @@ namespace ImageProcessorCore this.Width = width; this.Height = height; - - // Assign the pointer and pixels. - this.pixelsArray = new float[width * height * 4]; + this.Pixels = new TPacked[width * height]; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - /// The other to create this instance from. + /// The other to create this instance from. /// /// - /// Thrown if the given is null. + /// Thrown if the given is null. /// - protected ImageBase(ImageBase other) + protected ImageBase(ImageBase other) { Guard.NotNull(other, nameof(other), "Other image cannot be null."); @@ -64,29 +61,14 @@ namespace ImageProcessorCore 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); + this.Pixels = new TPacked[this.Width * this.Height]; + Array.Copy(other.Pixels, this.Pixels, other.Pixels.Length); } /// - /// Gets or sets the maximum allowable width in pixels. - /// - public 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. + /// Gets the pixels as an array of the given packed pixel format. /// - /// - /// 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; + public TPacked[] Pixels { get; private set; } /// /// Gets the width in pixels. @@ -127,15 +109,15 @@ namespace ImageProcessorCore /// 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. + /// The array with colors. Must be a multiple of the width and height. /// /// /// Thrown if either or are less than or equal to 0. /// /// - /// Thrown if the length is not equal to Width * Height * 4. + /// Thrown if the length is not equal to Width * Height. /// - public void SetPixels(int width, int height, float[] pixels) + public void SetPixels(int width, int height, TPacked[] pixels) { if (width <= 0) { @@ -147,14 +129,14 @@ namespace ImageProcessorCore throw new ArgumentOutOfRangeException(nameof(height), "Height must be greater than or equal than zero."); } - if (pixels.Length != width * height * 4) + if (pixels.Length != width * height) { - throw new ArgumentException("Pixel array must have the length of Width * Height * 4."); + throw new ArgumentException("Pixel array must have the length of Width * Height."); } this.Width = width; this.Height = height; - this.pixelsArray = pixels; + this.Pixels = pixels; } /// @@ -170,11 +152,11 @@ namespace ImageProcessorCore /// Thrown if either or are less than or equal to 0. /// /// - /// Thrown if the length is not equal to Width * Height * 4. + /// Thrown if the length is not equal to Width * Height. /// - public void ClonePixels(int width, int height, float[] pixels) + public void ClonePixels(int width, int height, TPacked[] pixels) { - if (width <= 0) + if (width <= 0) { throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than or equals than zero."); } @@ -184,17 +166,17 @@ namespace ImageProcessorCore throw new ArgumentOutOfRangeException(nameof(height), "Height must be greater than or equal than zero."); } - if (pixels.Length != width * height * 4) + if (pixels.Length != width * height) { - throw new ArgumentException("Pixel array must have the length of Width * Height * 4."); + throw new ArgumentException("Pixel array must have the length of Width * Height."); } this.Width = width; this.Height = height; // Copy the pixels. - this.pixelsArray = new float[pixels.Length]; - Array.Copy(pixels, this.pixelsArray, pixels.Length); + this.Pixels = new TPacked[pixels.Length]; + Array.Copy(pixels, this.Pixels, pixels.Length); } /// @@ -203,10 +185,7 @@ namespace ImageProcessorCore /// It is imperative that the accessor is correctly disposed off after use. /// /// - /// The - public PixelAccessor Lock() - { - return new PixelAccessor(this); - } + /// The + public abstract IPixelAccessor Lock(); } } diff --git a/src/ImageProcessorCore/ImageFrame.cs b/src/ImageProcessorCore/ImageFrame.cs index e9ff7214bc..42cf08da48 100644 --- a/src/ImageProcessorCore/ImageFrame.cs +++ b/src/ImageProcessorCore/ImageFrame.cs @@ -1,36 +1,16 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore +namespace ImageProcessorCore { - using System; - - /// - /// Represents a single frame in a animation. - /// - public class ImageFrame : ImageBase + public class ImageFrame : GenericImageFrame { - /// - /// Initializes a new instance of the class. - /// - public ImageFrame() + public ImageFrame(ImageFrame frame) + : base(frame) { } - /// - /// 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) + /// + public override IPixelAccessor Lock() { + return new PixelAccessor(this); } } } diff --git a/src/ImageProcessorCore/PackedVector/Bgra32.cs b/src/ImageProcessorCore/PackedVector/Bgra32.cs new file mode 100644 index 0000000000..fc6a788b08 --- /dev/null +++ b/src/ImageProcessorCore/PackedVector/Bgra32.cs @@ -0,0 +1,168 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.Numerics; + + /// + /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 1. + /// + public struct Bgra32 : IPackedVector, IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// The blue component. + /// The green component. + /// The red component. + /// The alpha component. + public Bgra32(float b, float g, float r, float a) + { + Vector4 clamped = Vector4.Clamp(new Vector4(b, g, r, a), Vector4.Zero, Vector4.One) * 255f; + this.PackedValue = Pack(ref clamped); + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + public Bgra32(Vector4 vector) + { + Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; + this.PackedValue = Pack(ref clamped); + } + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Bgra32 left, Bgra32 right) + { + return left.PackedValue == right.PackedValue; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator !=(Bgra32 left, Bgra32 right) + { + return left.PackedValue != right.PackedValue; + } + + /// + public void PackVector(Vector4 vector) + { + Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; + this.PackedValue = Pack(ref clamped); + } + + /// + public void PackBytes(byte x, byte y, byte z, byte w) + { + Vector4 vector = new Vector4(x, y, z, w); + this.PackedValue = Pack(ref vector); + } + + /// + public Vector4 ToVector4() + { + return new Vector4( + this.PackedValue & 0xFF, + (this.PackedValue >> 8) & 0xFF, + (this.PackedValue >> 16) & 0xFF, + (this.PackedValue >> 24) & 0xFF) / 255f; + } + + /// + public byte[] ToBytes() + { + return new[] + { + (byte)(this.PackedValue & 0xFF), + (byte)((this.PackedValue >> 8) & 0xFF), + (byte)((this.PackedValue >> 16) & 0xFF), + (byte)((this.PackedValue >> 24) & 0xFF) + }; + } + + /// + public override bool Equals(object obj) + { + return (obj is Bgra32) && this.Equals((Bgra32)obj); + } + + /// + public bool Equals(Bgra32 other) + { + return this.PackedValue == other.PackedValue; + } + + /// + /// Gets a string representation of the packed vector. + /// + /// A string representation of the packed vector. + public override string ToString() + { + return this.ToVector4().ToString(); + } + + /// + public override int GetHashCode() + { + return this.GetHashCode(this); + } + + /// + /// Sets the packed representation from the given component values. + /// + /// + /// The vector containing the components for the packed vector. + /// + /// + /// The . + /// + private static uint Pack(ref Vector4 vector) + { + return (uint)Math.Round(vector.X) | + ((uint)Math.Round(vector.Y) << 8) | + ((uint)Math.Round(vector.Z) << 16) | + ((uint)Math.Round(vector.W) << 24); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private int GetHashCode(Bgra32 packed) + { + return packed.PackedValue.GetHashCode(); + } + } +} diff --git a/src/ImageProcessorCore/PackedVector/IPackedVector.cs b/src/ImageProcessorCore/PackedVector/IPackedVector.cs new file mode 100644 index 0000000000..02e10cc3a5 --- /dev/null +++ b/src/ImageProcessorCore/PackedVector/IPackedVector.cs @@ -0,0 +1,61 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System.Numerics; + + /// + /// An interface that converts packed vector types to and from values, + /// allowing multiple encodings to be manipulated in a generic way. + /// + /// + /// The type of object representing the packed value. + /// + public interface IPackedVector : IPackedVector + where TPacked : struct + { + /// + /// Gets or sets the packed representation of the value. + /// Typically packed in least to greatest significance order. + /// + TPacked PackedValue { get; set; } + } + + /// + /// An interface that converts packed vector types to and from values. + /// + public interface IPackedVector + { + /// + /// Sets the packed representation from a . + /// + /// The vector to pack. + void PackVector(Vector4 vector); + + /// + /// Sets the packed representation from a . + /// + /// The x-component. + /// The y-component. + /// The z-component. + /// The w-component. + void PackBytes(byte x, byte y, byte z, byte w); + + /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + Vector4 ToVector4(); + + /// + /// Expands the packed representation into a . + /// The bytes are typically expanded in least to greatest significance order. + /// + /// The . + byte[] ToBytes(); + } +} diff --git a/src/ImageProcessorCore/PixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor.cs index 2553b2536d..bf01cb8563 100644 --- a/src/ImageProcessorCore/PixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor.cs @@ -9,14 +9,14 @@ namespace ImageProcessorCore using System.Runtime.InteropServices; /// - /// Provides per-pixel access to an images pixels. + /// Provides per-pixel access to an images pixels as 8 bit unsigned components. /// - public sealed unsafe class PixelAccessor : IDisposable + public sealed unsafe class PixelAccessor : IPixelAccessor { /// /// The position of the first pixel in the bitmap. /// - private Color* pixelsBase; + private Bgra32* pixelsBase; /// /// Provides a way to access the pixels from unmanaged memory. @@ -42,7 +42,7 @@ namespace ImageProcessorCore /// /// The image to provide pixel access for. /// - public PixelAccessor(ImageBase image) + public PixelAccessor(ImageBase image) { Guard.NotNull(image, nameof(image)); Guard.MustBeGreaterThan(image.Width, 0, "image width"); @@ -51,9 +51,8 @@ namespace ImageProcessorCore this.Width = image.Width; this.Height = image.Height; - // Assign the pointer. this.pixelsHandle = GCHandle.Alloc(image.Pixels, GCHandleType.Pinned); - this.pixelsBase = (Color*)this.pixelsHandle.AddrOfPinnedObject().ToPointer(); + this.pixelsBase = (Bgra32*)this.pixelsHandle.AddrOfPinnedObject().ToPointer(); } /// @@ -85,8 +84,8 @@ namespace ImageProcessorCore /// The y-coordinate of the pixel. Must be greater /// than zero and smaller than the width of the pixel. /// - /// The at the specified position. - public Color this[int x, int y] + /// The at the specified position. + public IPackedVector this[int x, int y] { get { @@ -117,7 +116,7 @@ namespace ImageProcessorCore throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); } #endif - *(this.pixelsBase + ((y * this.Width) + x)) = value; + *(this.pixelsBase + ((y * this.Width) + x)) = (Bgra32)value; } } @@ -149,4 +148,4 @@ namespace ImageProcessorCore GC.SuppressFinalize(this); } } -} \ No newline at end of file +} From 611d0adda7159fd05b2e19bfb024d70e0632a8c5 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 7 Jul 2016 01:02:45 +1000 Subject: [PATCH 06/91] Bring back generic image Former-commit-id: dd3f92b3358e43e8c26e6ad974b8fd1432d3a36d Former-commit-id: e0ce0080191426e2bf6bc73afc0da56bb2545a3c Former-commit-id: ba0189d24efa44c744044a88d05204bc7afecf6e --- src/ImageProcessorCore/GenericImage.cs | 261 ++++++++++++++++++++++++ src/ImageProcessorCore/ImageFrame.cs | 17 +- src/ImageProcessorCore/PixelAccessor.cs | 6 +- 3 files changed, 281 insertions(+), 3 deletions(-) create mode 100644 src/ImageProcessorCore/GenericImage.cs diff --git a/src/ImageProcessorCore/GenericImage.cs b/src/ImageProcessorCore/GenericImage.cs new file mode 100644 index 0000000000..b0408513ac --- /dev/null +++ b/src/ImageProcessorCore/GenericImage.cs @@ -0,0 +1,261 @@ + + +namespace ImageProcessorCore +{ + using System.IO; + using System.Text; + + using System; + using System.Collections.Generic; + using System.Linq; + + using ImageProcessorCore.Formats; + + /// + /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. + /// + /// + /// The image data is always stored in RGBA format, where the red, green, blue, and + /// alpha values are floating point numbers. + /// + public abstract class GenericImage : ImageBase + where TPacked : IPackedVector + { + /// + /// The default horizontal resolution value (dots per inch) in x direction. + /// The default value is 96 dots per inch. + /// + public const double DefaultHorizontalResolution = 96; + + /// + /// The default vertical resolution value (dots per inch) in y direction. + /// The default value is 96 dots per inch. + /// + public const double DefaultVerticalResolution = 96; + + /// + /// Initializes a new instance of the class. + /// + protected GenericImage() + { + 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. + protected GenericImage(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. + protected GenericImage(Stream 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. + /// + /// 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/ImageFrame.cs b/src/ImageProcessorCore/ImageFrame.cs index 42cf08da48..2f1fe518c8 100644 --- a/src/ImageProcessorCore/ImageFrame.cs +++ b/src/ImageProcessorCore/ImageFrame.cs @@ -1,13 +1,26 @@ namespace ImageProcessorCore { - public class ImageFrame : GenericImageFrame + /// + /// Represents a single frame in a animation. + /// + /// + /// The image data is always stored in format, where the blue, green, red, and + /// alpha values are 8 bit unsigned bytes. + /// + public class ImageFrame : ImageBase, IImageFrame { + /// + /// 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 new PixelAccessor(this); diff --git a/src/ImageProcessorCore/PixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor.cs index bf01cb8563..3606491321 100644 --- a/src/ImageProcessorCore/PixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor.cs @@ -9,8 +9,12 @@ namespace ImageProcessorCore using System.Runtime.InteropServices; /// - /// Provides per-pixel access to an images pixels as 8 bit unsigned components. + /// Provides per-pixel access to an images pixels. /// + /// + /// The image data is always stored in format, where the blue, green, red, and + /// alpha values are 8 bit unsigned bytes. + /// public sealed unsafe class PixelAccessor : IPixelAccessor { /// From 7aa7c40d3893ae960ee126e004b08bf71ac706ac Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 7 Jul 2016 16:48:39 +1000 Subject: [PATCH 07/91] True Generic Image? Former-commit-id: 5b08411e203ba38ad40a1892f599cf67af7c513f Former-commit-id: 64f7bf7a94fc878e8fb8b4a19f049b55bca5dea6 Former-commit-id: d183d15ce5ebefc2f8b9dc86ac8a9d7bb5c27714 --- src/ImageProcessorCore/Bootstrapper.cs | 66 ++++- src/ImageProcessorCore/GenericImage.cs | 261 ----------------- src/ImageProcessorCore/GenericImageFrame.cs | 40 --- src/ImageProcessorCore/IImageBase.cs | 10 +- src/ImageProcessorCore/IImageProcessor.cs | 10 +- src/ImageProcessorCore/Image.cs | 273 +++++++++++++++++- src/ImageProcessorCore/ImageFrame.cs | 23 +- src/ImageProcessorCore/ImageProcessor.cs | 20 +- .../Bgra32PixelAccessor.cs} | 12 +- .../{ => PixelAccessor}/IPixelAccessor.cs | 0 10 files changed, 367 insertions(+), 348 deletions(-) delete mode 100644 src/ImageProcessorCore/GenericImage.cs delete mode 100644 src/ImageProcessorCore/GenericImageFrame.cs rename src/ImageProcessorCore/{PixelAccessor.cs => PixelAccessor/Bgra32PixelAccessor.cs} (92%) rename src/ImageProcessorCore/{ => PixelAccessor}/IPixelAccessor.cs (100%) diff --git a/src/ImageProcessorCore/Bootstrapper.cs b/src/ImageProcessorCore/Bootstrapper.cs index a9c7dc8318..5f2978534c 100644 --- a/src/ImageProcessorCore/Bootstrapper.cs +++ b/src/ImageProcessorCore/Bootstrapper.cs @@ -8,6 +8,8 @@ namespace ImageProcessorCore using System; using System.Collections.Generic; using System.Collections.ObjectModel; + using System.Text; + using ImageProcessorCore.Formats; /// @@ -26,18 +28,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(Bgra32), typeof(Bgra32PixelAccessor) } + }; } /// @@ -58,5 +67,58 @@ namespace ImageProcessorCore { this.imageFormats.Add(format); } + + /// + /// Gets an instance of the correct for the packed vector. + /// + /// The type of pixel data. + /// The image + /// The + public IPixelAccessor GetPixelAccessor(Image image) + where TPackedVector : IPackedVector + { + Type packed = typeof(TPackedVector); + if (!this.pixelAccessors.ContainsKey(packed)) + { + // TODO: Double check this. It should work... + return (IPixelAccessor)Activator.CreateInstance(this.pixelAccessors[packed], image); + } + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("PixelAccessor cannot be loaded. Available accessors:"); + + foreach (Type value in this.pixelAccessors.Values) + { + stringBuilder.AppendLine("-" + value.Name); + } + + throw new NotSupportedException(stringBuilder.ToString()); + } + + /// + /// Gets an instance of the correct for the packed vector. + /// + /// The type of pixel data. + /// The image + /// The + public IPixelAccessor GetPixelAccessor(ImageFrame image) + where TPackedVector : IPackedVector + { + Type packed = typeof(TPackedVector); + if (!this.pixelAccessors.ContainsKey(packed)) + { + return (IPixelAccessor)Activator.CreateInstance(this.pixelAccessors[packed], image); + } + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("PixelAccessor cannot be loaded. Available accessors:"); + + foreach (Type value in this.pixelAccessors.Values) + { + stringBuilder.AppendLine("-" + value.Name); + } + + throw new NotSupportedException(stringBuilder.ToString()); + } } } diff --git a/src/ImageProcessorCore/GenericImage.cs b/src/ImageProcessorCore/GenericImage.cs deleted file mode 100644 index b0408513ac..0000000000 --- a/src/ImageProcessorCore/GenericImage.cs +++ /dev/null @@ -1,261 +0,0 @@ - - -namespace ImageProcessorCore -{ - using System.IO; - using System.Text; - - using System; - using System.Collections.Generic; - using System.Linq; - - using ImageProcessorCore.Formats; - - /// - /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. - /// - /// - /// The image data is always stored in RGBA format, where the red, green, blue, and - /// alpha values are floating point numbers. - /// - public abstract class GenericImage : ImageBase - where TPacked : IPackedVector - { - /// - /// The default horizontal resolution value (dots per inch) in x direction. - /// The default value is 96 dots per inch. - /// - public const double DefaultHorizontalResolution = 96; - - /// - /// The default vertical resolution value (dots per inch) in y direction. - /// The default value is 96 dots per inch. - /// - public const double DefaultVerticalResolution = 96; - - /// - /// Initializes a new instance of the class. - /// - protected GenericImage() - { - 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. - protected GenericImage(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. - protected GenericImage(Stream 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. - /// - /// 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/GenericImageFrame.cs b/src/ImageProcessorCore/GenericImageFrame.cs deleted file mode 100644 index b0dbe341cd..0000000000 --- a/src/ImageProcessorCore/GenericImageFrame.cs +++ /dev/null @@ -1,40 +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. - /// - /// - /// The packed vector pixels format. - /// - public abstract class GenericImageFrame : ImageBase, IImageFrame - where TPacked : IPackedVector - { - /// - /// Initializes a new instance of the class. - /// - protected GenericImageFrame() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The other to create this instance from. - /// - /// - /// Thrown if the given is null. - /// - protected GenericImageFrame(GenericImageFrame other) - : base(other) - { - } - } -} diff --git a/src/ImageProcessorCore/IImageBase.cs b/src/ImageProcessorCore/IImageBase.cs index 85ffa0086b..b01ee7c9c4 100644 --- a/src/ImageProcessorCore/IImageBase.cs +++ b/src/ImageProcessorCore/IImageBase.cs @@ -1,18 +1,18 @@ namespace ImageProcessorCore { - public interface IImageBase - where TPacked : IPackedVector + public interface IImageBase + where TPackedVector : IPackedVector { Rectangle Bounds { get; } int FrameDelay { get; set; } int Height { get; } double PixelRatio { get; } - TPacked[] Pixels { get; } + TPackedVector[] Pixels { get; } int Quality { get; set; } int Width { get; } - void ClonePixels(int width, int height, TPacked[] pixels); + void ClonePixels(int width, int height, TPackedVector[] pixels); IPixelAccessor Lock(); - void SetPixels(int width, int height, TPacked[] pixels); + void SetPixels(int width, int height, TPackedVector[] pixels); } } \ No newline at end of file diff --git a/src/ImageProcessorCore/IImageProcessor.cs b/src/ImageProcessorCore/IImageProcessor.cs index e71762b107..ad6e4ac761 100644 --- a/src/ImageProcessorCore/IImageProcessor.cs +++ b/src/ImageProcessorCore/IImageProcessor.cs @@ -27,8 +27,9 @@ namespace ImageProcessorCore.Processors event ProgressEventHandler OnProgress; /// - /// Applies the process to the specified portion of the specified . + /// Applies the process to the specified portion of the specified . /// + /// The type of pixels contained within the image. /// Target image to apply the process to. /// The source image. Cannot be null. /// @@ -44,12 +45,13 @@ 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) where TPackedVector : IPackedVector; /// - /// 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. /// + /// The type of pixels contained within the image. /// Target image to apply the process to. /// The source image. Cannot be null. /// The target width. @@ -65,6 +67,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) where TPackedVector : IPackedVector; } } diff --git a/src/ImageProcessorCore/Image.cs b/src/ImageProcessorCore/Image.cs index 88d4d1b6c0..c8dc861d6a 100644 --- a/src/ImageProcessorCore/Image.cs +++ b/src/ImageProcessorCore/Image.cs @@ -1,33 +1,86 @@ -namespace ImageProcessorCore +// +// 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.Diagnostics; + using System.Collections.Generic; using System.Linq; + using Formats; + /// /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. /// - /// - /// The image data is always stored in format, where the blue, green, red, and - /// alpha values are 8 bit unsigned bytes. - /// - [DebuggerDisplay("Image: {Width}x{Height}")] - public class Image : GenericImage + /// + /// The packed vector containing pixel information. + /// + public class Image : ImageBase + where TPackedVector : IPackedVector { /// - /// Initializes a new instance of the class + /// 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) + public Image(Image other) { - // TODO: Check this. Not sure why I was getting a cast warning. - foreach (ImageFrame frame in other.Frames.Cast()) + foreach (ImageFrame frame in other.Frames) { if (frame != null) { - this.Frames.Add(new ImageFrame(frame)); + this.Frames.Add(new ImageFrame(frame)); } } @@ -37,10 +90,202 @@ 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 new PixelAccessor(this); + 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/ImageFrame.cs b/src/ImageProcessorCore/ImageFrame.cs index 2f1fe518c8..479d9dd9b0 100644 --- a/src/ImageProcessorCore/ImageFrame.cs +++ b/src/ImageProcessorCore/ImageFrame.cs @@ -1,21 +1,26 @@ -namespace ImageProcessorCore +// +// 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 image data is always stored in format, where the blue, green, red, and - /// alpha values are 8 bit unsigned bytes. - /// - public class ImageFrame : ImageBase, IImageFrame + /// + /// The packed vector containing pixel information. + /// + public class ImageFrame : ImageBase + where TPackedVector : IPackedVector { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The frame to create the frame from. /// - public ImageFrame(ImageFrame frame) + public ImageFrame(ImageFrame frame) : base(frame) { } @@ -23,7 +28,7 @@ /// public override IPixelAccessor Lock() { - return new PixelAccessor(this); + return Bootstrapper.Instance.GetPixelAccessor(this); } } } diff --git a/src/ImageProcessorCore/ImageProcessor.cs b/src/ImageProcessorCore/ImageProcessor.cs index ea76ebbf86..e7057a3f9c 100644 --- a/src/ImageProcessorCore/ImageProcessor.cs +++ b/src/ImageProcessorCore/ImageProcessor.cs @@ -27,7 +27,8 @@ namespace ImageProcessorCore.Processors private int totalRows; /// - public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) + public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) + where TPackedVector : IPackedVector { try { @@ -48,11 +49,12 @@ 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)) + where TPackedVector : IPackedVector { try { - float[] pixels = new float[width * height * 4]; + TPackedVector[] pixels = new TPackedVector[width * height]; target.SetPixels(width, height, pixels); // Ensure we always have bounds. @@ -93,14 +95,16 @@ 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) + where TPackedVector : IPackedVector { } /// - /// 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. /// + /// The type of pixels contained within the image. /// Target image to apply the process to. /// The source image. Cannot be null. /// @@ -116,11 +120,12 @@ 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) where TPackedVector : IPackedVector; /// /// This method is called after the process is applied to prepare the processor. /// + /// The type of pixels contained within the image. /// Target image to apply the process to. /// The source image. Cannot be null. /// @@ -130,7 +135,8 @@ 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) + where TPackedVector : IPackedVector { } diff --git a/src/ImageProcessorCore/PixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs similarity index 92% rename from src/ImageProcessorCore/PixelAccessor.cs rename to src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs index 3606491321..cecc148b93 100644 --- a/src/ImageProcessorCore/PixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -15,7 +15,7 @@ namespace ImageProcessorCore /// The image data is always stored in format, where the blue, green, red, and /// alpha values are 8 bit unsigned bytes. /// - public sealed unsafe class PixelAccessor : IPixelAccessor + public sealed unsafe class Bgra32PixelAccessor : IPixelAccessor { /// /// The position of the first pixel in the bitmap. @@ -41,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 Bgra32PixelAccessor(IImageBase image) { Guard.NotNull(image, nameof(image)); Guard.MustBeGreaterThan(image.Width, 0, "image width"); @@ -60,9 +60,9 @@ namespace ImageProcessorCore } /// - /// Finalizes an instance of the class. + /// Finalizes an instance of the class. /// - ~PixelAccessor() + ~Bgra32PixelAccessor() { this.Dispose(); } diff --git a/src/ImageProcessorCore/IPixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs similarity index 100% rename from src/ImageProcessorCore/IPixelAccessor.cs rename to src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs From c1cea3b06eeed1ac3c9105af32c8fc8528b4884f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 8 Jul 2016 01:18:05 +1000 Subject: [PATCH 08/91] Trim back to basics Former-commit-id: 3cca13c49abcf0e9a5a394794153406084d2e19e Former-commit-id: 767a3db69cf4adecac58c58a84891b736ada79eb Former-commit-id: db1a5b695cc73951becdff9b31535b8f7b03e75b --- src/ImageProcessorCore - Copy/Bootstrapper.cs | 124 +++ .../Colors/Color.cs | 0 .../Colors/ColorConstants.cs | 0 .../Colors/ColorDefinitions.cs | 0 .../Colors/ColorTransforms.cs | 0 .../Colors/ColorspaceTransforms.cs | 0 .../Colors/Colorspaces/Bgra32.cs | 0 .../Colors/Colorspaces/CieLab.cs | 0 .../Colors/Colorspaces/CieXyz.cs | 0 .../Colors/Colorspaces/Cmyk.cs | 0 .../Colors/Colorspaces/Hsl.cs | 0 .../Colors/Colorspaces/Hsv.cs | 0 .../Colors/Colorspaces/YCbCr.cs | 0 .../Colors/IAlmostEquatable.cs | 0 .../Colors/RgbaComponent.cs | 0 .../Common/Exceptions/ImageFormatException.cs | 45 + .../Exceptions/ImageProcessingException.cs | 44 + .../Common/Extensions/ByteExtensions.cs | 60 ++ .../Common/Extensions/ComparableExtensions.cs | 145 +++ .../Common/Extensions/EnumerableExtensions.cs | 88 ++ .../Common/Helpers/Guard.cs | 192 ++++ .../Common/Helpers/ImageMaths.cs | 289 ++++++ .../Filters/Alpha.cs | 0 .../Filters/BackgroundColor.cs | 0 .../Filters/BlackWhite.cs | 0 .../Filters/Blend.cs | 0 .../Filters/BoxBlur.cs | 0 .../Filters/Brightness.cs | 0 .../Filters/ColorBlindness.cs | 0 .../Filters/Contrast.cs | 0 .../Filters/DetectEdges.cs | 0 .../Filters/Greyscale.cs | 0 .../Filters/GuassianBlur.cs | 0 .../Filters/GuassianSharpen.cs | 0 .../Filters/Hue.cs | 0 .../Filters/Invert.cs | 0 .../Filters/Kodachrome.cs | 0 .../Filters/Lomograph.cs | 0 .../Filters/Options/ColorBlindness.cs | 0 .../Filters/Pixelate.cs | 0 .../Filters/Polaroid.cs | 0 .../Filters/Processors/AlphaProcessor.cs | 0 .../Processors/BackgroundColorProcessor.cs | 0 .../Binarization/ThresholdProcessor.cs | 0 .../Filters/Processors/BlendProcessor.cs | 0 .../Filters/Processors/BrightnessProcessor.cs | 0 .../ColorMatrix/BlackWhiteProcessor.cs | 0 .../ColorBlindness/AchromatomalyProcessor.cs | 0 .../ColorBlindness/AchromatopsiaProcessor.cs | 0 .../ColorBlindness/DeuteranomalyProcessor.cs | 0 .../ColorBlindness/DeuteranopiaProcessor.cs | 0 .../ColorBlindness/ProtanomalyProcessor.cs | 0 .../ColorBlindness/ProtanopiaProcessor.cs | 0 .../ColorMatrix/ColorBlindness/README.md | 0 .../ColorBlindness/TritanomalyProcessor.cs | 0 .../ColorBlindness/TritanopiaProcessor.cs | 0 .../ColorMatrix/ColorMatrixFilter.cs | 0 .../ColorMatrix/GreyscaleBt601Processor.cs | 0 .../ColorMatrix/GreyscaleBt709Processor.cs | 0 .../Processors/ColorMatrix/GreyscaleMode.cs | 0 .../Processors/ColorMatrix/HueProcessor.cs | 0 .../ColorMatrix/IColorMatrixFilter.cs | 0 .../ColorMatrix/KodachromeProcessor.cs | 0 .../ColorMatrix/LomographProcessor.cs | 0 .../ColorMatrix/PolaroidProcessor.cs | 0 .../ColorMatrix/SaturationProcessor.cs | 0 .../Processors/ColorMatrix/SepiaProcessor.cs | 0 .../Filters/Processors/ContrastProcessor.cs | 0 .../Convolution/BoxBlurProcessor.cs | 0 .../Convolution/Convolution2DFilter.cs | 0 .../Convolution/Convolution2PassFilter.cs | 0 .../Convolution/ConvolutionFilter.cs | 0 .../EdgeDetection/EdgeDetector2DFilter.cs | 0 .../EdgeDetection/EdgeDetectorFilter.cs | 0 .../EdgeDetection/IEdgeDetectorFilter.cs | 0 .../EdgeDetection/KayyaliProcessor.cs | 0 .../EdgeDetection/KirschProcessor.cs | 0 .../EdgeDetection/Laplacian3X3Processor.cs | 0 .../EdgeDetection/Laplacian5X5Processor.cs | 0 .../LaplacianOfGaussianProcessor.cs | 0 .../EdgeDetection/PrewittProcessor.cs | 0 .../EdgeDetection/RobertsCrossProcessor.cs | 0 .../EdgeDetection/ScharrProcessor.cs | 0 .../EdgeDetection/SobelProcessor.cs | 0 .../Convolution/GuassianBlurProcessor.cs | 0 .../Convolution/GuassianSharpenProcessor.cs | 0 .../Filters/Processors/GlowProcessor.cs | 0 .../Filters/Processors/InvertProcessor.cs | 0 .../Filters/Processors/PixelateProcessor.cs | 0 .../Filters/Processors/VignetteProcessor.cs | 0 .../Filters/Saturation.cs | 0 .../Filters/Sepia.cs | 0 .../Formats/Bmp/BmpBitsPerPixel.cs | 23 + .../Formats/Bmp/BmpCompression.cs | 63 ++ .../Formats/Bmp/BmpDecoder.cs | 82 ++ .../Formats/Bmp/BmpDecoderCore.cs | 445 +++++++++ .../Formats/Bmp/BmpEncoder.cs | 53 ++ .../Formats/Bmp/BmpEncoderCore.cs | 205 ++++ .../Formats/Bmp/BmpFileHeader.cs | 49 + .../Formats/Bmp/BmpFormat.cs | 19 + .../Formats/Bmp/BmpInfoHeader.cs | 82 ++ .../Formats/Bmp/README.md | 8 + .../Formats/Gif/BitEncoder.cs | 0 .../Formats/Gif/DisposalMethod.cs | 0 .../Formats/Gif/GifConstants.cs | 0 .../Formats/Gif/GifDecoder.cs | 0 .../Formats/Gif/GifDecoderCore.cs | 0 .../Formats/Gif/GifEncoder.cs | 0 .../Formats/Gif/GifEncoderCore.cs | 0 .../Formats/Gif/GifFormat.cs | 0 .../Formats/Gif/LzwDecoder.cs | 0 .../Formats/Gif/LzwEncoder.cs | 0 .../Formats/Gif/PackedField.cs | 0 .../Formats/Gif/README.md | 0 .../Sections/GifGraphicsControlExtension.cs | 0 .../Gif/Sections/GifImageDescriptor.cs | 0 .../Sections/GifLogicalScreenDescriptor.cs | 0 .../Formats/IImageDecoder.cs | 49 + .../Formats/IImageEncoder.cs | 53 ++ .../Formats/IImageFormat.cs | 23 + .../Formats/Jpg/Block.cs | 0 .../Formats/Jpg/FDCT.cs | 0 .../Formats/Jpg/IDCT.cs | 0 .../Formats/Jpg/JpegDecoder.cs | 0 .../Jpg/JpegDecoderCore.cs.REMOVED.git-id | 0 .../Formats/Jpg/JpegEncoder.cs | 0 .../Formats/Jpg/JpegEncoderCore.cs | 0 .../Formats/Jpg/JpegFormat.cs | 0 .../Formats/Jpg/JpegSubsample.cs | 0 .../Formats/Jpg/README.md | 0 .../Formats/Png/GrayscaleReader.cs | 0 .../Formats/Png/IColorReader.cs | 0 .../Formats/Png/PaletteIndexReader.cs | 0 .../Formats/Png/PngChunk.cs | 0 .../Formats/Png/PngChunkTypes.cs | 0 .../Formats/Png/PngColorTypeInformation.cs | 0 .../Formats/Png/PngDecoder.cs | 0 .../Formats/Png/PngDecoderCore.cs | 0 .../Formats/Png/PngEncoder.cs | 0 .../Formats/Png/PngEncoderCore.cs | 0 .../Formats/Png/PngFormat.cs | 0 .../Formats/Png/PngHeader.cs | 0 .../Formats/Png/README.md | 0 .../Formats/Png/TrueColorReader.cs | 0 .../Formats/Png/Zlib/Adler32.cs | 0 .../Formats/Png/Zlib/Crc32.cs | 0 .../Formats/Png/Zlib/IChecksum.cs | 0 .../Formats/Png/Zlib/README.md | 0 .../Formats/Png/Zlib/ZlibDeflateStream.cs | 0 .../Formats/Png/Zlib/ZlibInflateStream.cs | 0 src/ImageProcessorCore - Copy/IImageBase.cs | 29 + src/ImageProcessorCore - Copy/IImageFrame.cs | 7 + .../IImageProcessor.cs | 72 ++ .../IO/BigEndianBitConverter.cs | 48 + .../IO/EndianBinaryReader.cs | 616 ++++++++++++ .../IO/EndianBinaryWriter.cs | 385 ++++++++ .../IO/EndianBitConverter.cs | 724 ++++++++++++++ .../IO/Endianness.cs | 23 + .../IO/LittleEndianBitConverter.cs | 47 + src/ImageProcessorCore - Copy/Image.cs | 291 ++++++ src/ImageProcessorCore - Copy/ImageBase.cs | 201 ++++ .../ImageExtensions.cs | 166 ++++ src/ImageProcessorCore - Copy/ImageFrame.cs | 34 + .../ImageProcessor.cs | 163 ++++ .../ImageProcessorCore.xproj | 22 + .../ImageProperty.cs | 153 +++ .../Numerics/Ellipse.cs | 174 ++++ .../Numerics/Point.cs | 281 ++++++ .../Numerics/Rectangle.cs | 291 ++++++ .../Numerics/Size.cs | 208 ++++ .../PackedVector/Bgra32.cs | 168 ++++ .../PackedVector/IPackedVector.cs | 61 ++ .../PixelAccessor/Bgra32PixelAccessor.cs | 155 +++ .../PixelAccessor/IPixelAccessor.cs | 43 + .../ProgressEventArgs.cs | 23 + .../Properties/AssemblyInfo.cs | 34 + .../Quantizers/IQuantizer.cs | 0 .../Quantizers/Octree/OctreeQuantizer.cs | 0 .../Quantizers/Octree/Quantizer.cs | 0 .../Quantizers/Palette/PaletteQuantizer.cs | 0 .../Quantizers/QuantizedImage.cs | 0 .../Quantizers/Wu/Box.cs | 0 .../Quantizers/Wu/WuQuantizer.cs | 0 .../Samplers/Crop.cs | 0 .../Samplers/EntropyCrop.cs | 0 .../Samplers/Options/AnchorPosition.cs | 58 ++ .../Samplers/Options/FlipType.cs | 28 + .../Samplers/Options/ResizeHelper.cs | 430 +++++++++ .../Samplers/Options/ResizeMode.cs | 49 + .../Samplers/Options/ResizeOptions.cs | 47 + .../Samplers/Options/RotateType.cs | 33 + .../Samplers/Pad.cs | 0 .../Samplers/Processors/CropProcessor.cs | 0 .../Processors/EntropyCropProcessor.cs | 0 .../Samplers/Processors/IImageSampler.cs | 19 + .../Samplers/Processors/ImageSampler.cs | 17 + .../Samplers/Processors/Matrix3x2Processor.cs | 0 .../Samplers/Processors/ResizeProcessor.cs | 362 +++++++ .../Processors/RotateFlipProcessor.cs | 0 .../Samplers/Processors/RotateProcessor.cs | 0 .../Samplers/Processors/SkewProcessor.cs | 0 .../Samplers/Resamplers/BicubicResampler.cs | 43 + .../Samplers/Resamplers/BoxResampler.cs | 28 + .../Resamplers/CatmullRomResampler.cs | 28 + .../Samplers/Resamplers/HermiteResampler.cs | 27 + .../Samplers/Resamplers/IResampler.cs | 27 + .../Samplers/Resamplers/Lanczos3Resampler.cs | 34 + .../Samplers/Resamplers/Lanczos5Resampler.cs | 34 + .../Samplers/Resamplers/Lanczos8Resampler.cs | 34 + .../Resamplers/MitchellNetravaliResampler.cs | 26 + .../Resamplers/NearestNeighborResampler.cs | 23 + .../Samplers/Resamplers/RobidouxResampler.cs | 26 + .../Resamplers/RobidouxSharpResampler.cs | 26 + .../Resamplers/RobidouxSoftResampler.cs | 26 + .../Samplers/Resamplers/SplineResampler.cs | 26 + .../Samplers/Resamplers/TriangleResampler.cs | 34 + .../Samplers/Resamplers/WelchResampler.cs | 33 + .../Samplers/Resize.cs | 134 +++ .../Samplers/Rotate.cs | 0 .../Samplers/RotateFlip.cs | 0 .../Samplers/Skew.cs | 0 src/ImageProcessorCore - Copy/project.json | 38 + src/ImageProcessorCore/Bootstrapper.cs | 16 +- .../Common/Helpers/ImageMaths.cs | 208 ++-- .../Formats/Bmp/BmpDecoder.cs | 7 +- .../Formats/Bmp/BmpDecoderCore.cs | 97 +- .../Formats/Bmp/BmpEncoder.cs | 3 +- .../Formats/Bmp/BmpEncoderCore.cs | 43 +- .../Formats/IImageDecoder.cs | 7 +- .../Formats/IImageEncoder.cs | 7 +- src/ImageProcessorCore/IImageBase.cs | 33 +- src/ImageProcessorCore/IImageFrame.cs | 2 +- src/ImageProcessorCore/IImageProcessor.cs | 6 +- src/ImageProcessorCore/Image.cs | 9 +- src/ImageProcessorCore/ImageBase.cs | 32 +- src/ImageProcessorCore/ImageExtensions.cs | 114 ++- src/ImageProcessorCore/ImageFrame.cs | 11 +- src/ImageProcessorCore/ImageProcessor.cs | 11 +- .../PixelAccessor/Bgra32PixelAccessor.cs | 11 +- .../PixelAccessor/IPixelAccessor.cs | 16 +- .../Samplers/Options/ResizeHelper.cs | 39 +- .../Samplers/Processors/ResizeProcessor.cs | 69 +- src/ImageProcessorCore/Samplers/Resize.cs | 32 +- .../Color/ColorEquality.cs | 38 +- .../Image/GetSetPixel.cs | 14 +- .../Samplers/Crop.cs | 72 +- .../Samplers/Resize.cs | 3 +- .../Colors/ColorConversionTests.cs | 493 ---------- .../Colors/ColorSpacialTransformTests.cs | 58 -- .../Colors/ColorTests.cs | 108 --- .../ImageProcessorCore.Tests/FileTestBase.cs | 6 +- .../Formats/BitmapTests.cs | 47 - .../Formats/EncoderDecoderTests.cs | 174 ---- .../Formats/PngTests.cs | 38 - .../Helpers/GuardTests.cs | 209 ---- .../Numerics/PointTests.cs | 55 -- .../Numerics/RectangleTests.cs | 71 -- .../Numerics/SizeTests.cs | 55 -- .../Processors/Filters/FilterTests.cs | 89 -- .../Processors/Samplers/SamplerTests.cs | 900 +++++++++--------- 260 files changed, 9198 insertions(+), 2255 deletions(-) create mode 100644 src/ImageProcessorCore - Copy/Bootstrapper.cs rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Colors/Color.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Colors/ColorConstants.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Colors/ColorDefinitions.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Colors/ColorTransforms.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Colors/ColorspaceTransforms.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Colors/Colorspaces/Bgra32.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Colors/Colorspaces/CieLab.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Colors/Colorspaces/CieXyz.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Colors/Colorspaces/Cmyk.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Colors/Colorspaces/Hsl.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Colors/Colorspaces/Hsv.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Colors/Colorspaces/YCbCr.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Colors/IAlmostEquatable.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Colors/RgbaComponent.cs (100%) create mode 100644 src/ImageProcessorCore - Copy/Common/Exceptions/ImageFormatException.cs create mode 100644 src/ImageProcessorCore - Copy/Common/Exceptions/ImageProcessingException.cs create mode 100644 src/ImageProcessorCore - Copy/Common/Extensions/ByteExtensions.cs create mode 100644 src/ImageProcessorCore - Copy/Common/Extensions/ComparableExtensions.cs create mode 100644 src/ImageProcessorCore - Copy/Common/Extensions/EnumerableExtensions.cs create mode 100644 src/ImageProcessorCore - Copy/Common/Helpers/Guard.cs create mode 100644 src/ImageProcessorCore - Copy/Common/Helpers/ImageMaths.cs rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Alpha.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/BackgroundColor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/BlackWhite.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Blend.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/BoxBlur.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Brightness.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/ColorBlindness.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Contrast.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/DetectEdges.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Greyscale.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/GuassianBlur.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/GuassianSharpen.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Hue.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Invert.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Kodachrome.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Lomograph.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Options/ColorBlindness.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Pixelate.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Polaroid.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/AlphaProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/BackgroundColorProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/Binarization/ThresholdProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/BlendProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/BrightnessProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ColorMatrix/ColorBlindness/README.md (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ColorMatrix/GreyscaleMode.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ColorMatrix/HueProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ColorMatrix/KodachromeProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ColorMatrix/LomographProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ColorMatrix/PolaroidProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ColorMatrix/SaturationProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ColorMatrix/SepiaProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/ContrastProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/Convolution/BoxBlurProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/Convolution/Convolution2DFilter.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/Convolution/Convolution2PassFilter.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/Convolution/ConvolutionFilter.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/Convolution/EdgeDetection/KirschProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/Convolution/EdgeDetection/PrewittProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/Convolution/EdgeDetection/ScharrProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/Convolution/GuassianBlurProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/Convolution/GuassianSharpenProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/GlowProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/InvertProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/PixelateProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Processors/VignetteProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Saturation.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Filters/Sepia.cs (100%) create mode 100644 src/ImageProcessorCore - Copy/Formats/Bmp/BmpBitsPerPixel.cs create mode 100644 src/ImageProcessorCore - Copy/Formats/Bmp/BmpCompression.cs create mode 100644 src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoder.cs create mode 100644 src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoderCore.cs create mode 100644 src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoder.cs create mode 100644 src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoderCore.cs create mode 100644 src/ImageProcessorCore - Copy/Formats/Bmp/BmpFileHeader.cs create mode 100644 src/ImageProcessorCore - Copy/Formats/Bmp/BmpFormat.cs create mode 100644 src/ImageProcessorCore - Copy/Formats/Bmp/BmpInfoHeader.cs create mode 100644 src/ImageProcessorCore - Copy/Formats/Bmp/README.md rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Gif/BitEncoder.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Gif/DisposalMethod.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Gif/GifConstants.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Gif/GifDecoder.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Gif/GifDecoderCore.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Gif/GifEncoder.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Gif/GifEncoderCore.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Gif/GifFormat.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Gif/LzwDecoder.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Gif/LzwEncoder.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Gif/PackedField.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Gif/README.md (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Gif/Sections/GifGraphicsControlExtension.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Gif/Sections/GifImageDescriptor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs (100%) create mode 100644 src/ImageProcessorCore - Copy/Formats/IImageDecoder.cs create mode 100644 src/ImageProcessorCore - Copy/Formats/IImageEncoder.cs create mode 100644 src/ImageProcessorCore - Copy/Formats/IImageFormat.cs rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Jpg/Block.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Jpg/FDCT.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Jpg/IDCT.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Jpg/JpegDecoder.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Jpg/JpegEncoder.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Jpg/JpegEncoderCore.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Jpg/JpegFormat.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Jpg/JpegSubsample.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Jpg/README.md (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Png/GrayscaleReader.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Png/IColorReader.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Png/PaletteIndexReader.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Png/PngChunk.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Png/PngChunkTypes.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Png/PngColorTypeInformation.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Png/PngDecoder.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Png/PngDecoderCore.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Png/PngEncoder.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Png/PngEncoderCore.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Png/PngFormat.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Png/PngHeader.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Png/README.md (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Png/TrueColorReader.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Png/Zlib/Adler32.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Png/Zlib/Crc32.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Png/Zlib/IChecksum.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Png/Zlib/README.md (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Png/Zlib/ZlibDeflateStream.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Formats/Png/Zlib/ZlibInflateStream.cs (100%) create mode 100644 src/ImageProcessorCore - Copy/IImageBase.cs create mode 100644 src/ImageProcessorCore - Copy/IImageFrame.cs create mode 100644 src/ImageProcessorCore - Copy/IImageProcessor.cs create mode 100644 src/ImageProcessorCore - Copy/IO/BigEndianBitConverter.cs create mode 100644 src/ImageProcessorCore - Copy/IO/EndianBinaryReader.cs create mode 100644 src/ImageProcessorCore - Copy/IO/EndianBinaryWriter.cs create mode 100644 src/ImageProcessorCore - Copy/IO/EndianBitConverter.cs create mode 100644 src/ImageProcessorCore - Copy/IO/Endianness.cs create mode 100644 src/ImageProcessorCore - Copy/IO/LittleEndianBitConverter.cs create mode 100644 src/ImageProcessorCore - Copy/Image.cs create mode 100644 src/ImageProcessorCore - Copy/ImageBase.cs create mode 100644 src/ImageProcessorCore - Copy/ImageExtensions.cs create mode 100644 src/ImageProcessorCore - Copy/ImageFrame.cs create mode 100644 src/ImageProcessorCore - Copy/ImageProcessor.cs create mode 100644 src/ImageProcessorCore - Copy/ImageProcessorCore.xproj create mode 100644 src/ImageProcessorCore - Copy/ImageProperty.cs create mode 100644 src/ImageProcessorCore - Copy/Numerics/Ellipse.cs create mode 100644 src/ImageProcessorCore - Copy/Numerics/Point.cs create mode 100644 src/ImageProcessorCore - Copy/Numerics/Rectangle.cs create mode 100644 src/ImageProcessorCore - Copy/Numerics/Size.cs create mode 100644 src/ImageProcessorCore - Copy/PackedVector/Bgra32.cs create mode 100644 src/ImageProcessorCore - Copy/PackedVector/IPackedVector.cs create mode 100644 src/ImageProcessorCore - Copy/PixelAccessor/Bgra32PixelAccessor.cs create mode 100644 src/ImageProcessorCore - Copy/PixelAccessor/IPixelAccessor.cs create mode 100644 src/ImageProcessorCore - Copy/ProgressEventArgs.cs create mode 100644 src/ImageProcessorCore - Copy/Properties/AssemblyInfo.cs rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Quantizers/IQuantizer.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Quantizers/Octree/OctreeQuantizer.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Quantizers/Octree/Quantizer.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Quantizers/Palette/PaletteQuantizer.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Quantizers/QuantizedImage.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Quantizers/Wu/Box.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Quantizers/Wu/WuQuantizer.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Samplers/Crop.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Samplers/EntropyCrop.cs (100%) create mode 100644 src/ImageProcessorCore - Copy/Samplers/Options/AnchorPosition.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Options/FlipType.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Options/ResizeHelper.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Options/ResizeMode.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Options/ResizeOptions.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Options/RotateType.cs rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Samplers/Pad.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Samplers/Processors/CropProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Samplers/Processors/EntropyCropProcessor.cs (100%) create mode 100644 src/ImageProcessorCore - Copy/Samplers/Processors/IImageSampler.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Processors/ImageSampler.cs rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Samplers/Processors/Matrix3x2Processor.cs (100%) create mode 100644 src/ImageProcessorCore - Copy/Samplers/Processors/ResizeProcessor.cs rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Samplers/Processors/RotateFlipProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Samplers/Processors/RotateProcessor.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Samplers/Processors/SkewProcessor.cs (100%) create mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/BicubicResampler.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/BoxResampler.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/CatmullRomResampler.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/HermiteResampler.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/IResampler.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos3Resampler.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos5Resampler.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos8Resampler.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/MitchellNetravaliResampler.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/NearestNeighborResampler.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxResampler.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxSharpResampler.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxSoftResampler.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/SplineResampler.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/TriangleResampler.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/WelchResampler.cs create mode 100644 src/ImageProcessorCore - Copy/Samplers/Resize.cs rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Samplers/Rotate.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Samplers/RotateFlip.cs (100%) rename src/{ImageProcessorCore => ImageProcessorCore - Copy}/Samplers/Skew.cs (100%) create mode 100644 src/ImageProcessorCore - Copy/project.json delete mode 100644 tests/ImageProcessorCore.Tests/Colors/ColorConversionTests.cs delete mode 100644 tests/ImageProcessorCore.Tests/Colors/ColorSpacialTransformTests.cs delete mode 100644 tests/ImageProcessorCore.Tests/Colors/ColorTests.cs delete mode 100644 tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs delete mode 100644 tests/ImageProcessorCore.Tests/Formats/EncoderDecoderTests.cs delete mode 100644 tests/ImageProcessorCore.Tests/Formats/PngTests.cs delete mode 100644 tests/ImageProcessorCore.Tests/Helpers/GuardTests.cs delete mode 100644 tests/ImageProcessorCore.Tests/Numerics/PointTests.cs delete mode 100644 tests/ImageProcessorCore.Tests/Numerics/RectangleTests.cs delete mode 100644 tests/ImageProcessorCore.Tests/Numerics/SizeTests.cs delete mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/FilterTests.cs diff --git a/src/ImageProcessorCore - Copy/Bootstrapper.cs b/src/ImageProcessorCore - Copy/Bootstrapper.cs new file mode 100644 index 0000000000..5f2978534c --- /dev/null +++ b/src/ImageProcessorCore - Copy/Bootstrapper.cs @@ -0,0 +1,124 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Text; + + using ImageProcessorCore.Formats; + + /// + /// Provides initialization code which allows extending the library. + /// + public class Bootstrapper + { + /// + /// A new instance Initializes a new instance of the class. + /// with lazy initialization. + /// + private static readonly Lazy Lazy = new Lazy(() => new Bootstrapper()); + + /// + /// The default list of supported + /// + private readonly List imageFormats; + + private readonly Dictionary pixelAccessors; + + /// + /// Prevents a default instance of the class from being created. + /// + private Bootstrapper() + { + this.imageFormats = new List + { + new BmpFormat(), + new JpegFormat(), + new PngFormat(), + new GifFormat() + }; + + this.pixelAccessors = new Dictionary + { + { typeof(Bgra32), typeof(Bgra32PixelAccessor) } + }; + } + + /// + /// Gets the current bootstrapper instance. + /// + public static Bootstrapper Instance = Lazy.Value; + + /// + /// Gets the list of supported + /// + public IReadOnlyCollection ImageFormats => new ReadOnlyCollection(this.imageFormats); + + /// + /// Adds a new to the collection of supported image formats. + /// + /// The new format to add. + public void AddImageFormat(IImageFormat format) + { + this.imageFormats.Add(format); + } + + /// + /// Gets an instance of the correct for the packed vector. + /// + /// The type of pixel data. + /// The image + /// The + public IPixelAccessor GetPixelAccessor(Image image) + where TPackedVector : IPackedVector + { + Type packed = typeof(TPackedVector); + if (!this.pixelAccessors.ContainsKey(packed)) + { + // TODO: Double check this. It should work... + return (IPixelAccessor)Activator.CreateInstance(this.pixelAccessors[packed], image); + } + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("PixelAccessor cannot be loaded. Available accessors:"); + + foreach (Type value in this.pixelAccessors.Values) + { + stringBuilder.AppendLine("-" + value.Name); + } + + throw new NotSupportedException(stringBuilder.ToString()); + } + + /// + /// Gets an instance of the correct for the packed vector. + /// + /// The type of pixel data. + /// The image + /// The + public IPixelAccessor GetPixelAccessor(ImageFrame image) + where TPackedVector : IPackedVector + { + Type packed = typeof(TPackedVector); + if (!this.pixelAccessors.ContainsKey(packed)) + { + return (IPixelAccessor)Activator.CreateInstance(this.pixelAccessors[packed], image); + } + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("PixelAccessor cannot be loaded. Available accessors:"); + + foreach (Type value in this.pixelAccessors.Values) + { + stringBuilder.AppendLine("-" + value.Name); + } + + throw new NotSupportedException(stringBuilder.ToString()); + } + } +} diff --git a/src/ImageProcessorCore/Colors/Color.cs b/src/ImageProcessorCore - Copy/Colors/Color.cs similarity index 100% rename from src/ImageProcessorCore/Colors/Color.cs rename to src/ImageProcessorCore - Copy/Colors/Color.cs diff --git a/src/ImageProcessorCore/Colors/ColorConstants.cs b/src/ImageProcessorCore - Copy/Colors/ColorConstants.cs similarity index 100% rename from src/ImageProcessorCore/Colors/ColorConstants.cs rename to src/ImageProcessorCore - Copy/Colors/ColorConstants.cs diff --git a/src/ImageProcessorCore/Colors/ColorDefinitions.cs b/src/ImageProcessorCore - Copy/Colors/ColorDefinitions.cs similarity index 100% rename from src/ImageProcessorCore/Colors/ColorDefinitions.cs rename to src/ImageProcessorCore - Copy/Colors/ColorDefinitions.cs diff --git a/src/ImageProcessorCore/Colors/ColorTransforms.cs b/src/ImageProcessorCore - Copy/Colors/ColorTransforms.cs similarity index 100% rename from src/ImageProcessorCore/Colors/ColorTransforms.cs rename to src/ImageProcessorCore - Copy/Colors/ColorTransforms.cs diff --git a/src/ImageProcessorCore/Colors/ColorspaceTransforms.cs b/src/ImageProcessorCore - Copy/Colors/ColorspaceTransforms.cs similarity index 100% rename from src/ImageProcessorCore/Colors/ColorspaceTransforms.cs rename to src/ImageProcessorCore - Copy/Colors/ColorspaceTransforms.cs diff --git a/src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs b/src/ImageProcessorCore - Copy/Colors/Colorspaces/Bgra32.cs similarity index 100% rename from src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs rename to src/ImageProcessorCore - Copy/Colors/Colorspaces/Bgra32.cs diff --git a/src/ImageProcessorCore/Colors/Colorspaces/CieLab.cs b/src/ImageProcessorCore - Copy/Colors/Colorspaces/CieLab.cs similarity index 100% rename from src/ImageProcessorCore/Colors/Colorspaces/CieLab.cs rename to src/ImageProcessorCore - Copy/Colors/Colorspaces/CieLab.cs diff --git a/src/ImageProcessorCore/Colors/Colorspaces/CieXyz.cs b/src/ImageProcessorCore - Copy/Colors/Colorspaces/CieXyz.cs similarity index 100% rename from src/ImageProcessorCore/Colors/Colorspaces/CieXyz.cs rename to src/ImageProcessorCore - Copy/Colors/Colorspaces/CieXyz.cs diff --git a/src/ImageProcessorCore/Colors/Colorspaces/Cmyk.cs b/src/ImageProcessorCore - Copy/Colors/Colorspaces/Cmyk.cs similarity index 100% rename from src/ImageProcessorCore/Colors/Colorspaces/Cmyk.cs rename to src/ImageProcessorCore - Copy/Colors/Colorspaces/Cmyk.cs diff --git a/src/ImageProcessorCore/Colors/Colorspaces/Hsl.cs b/src/ImageProcessorCore - Copy/Colors/Colorspaces/Hsl.cs similarity index 100% rename from src/ImageProcessorCore/Colors/Colorspaces/Hsl.cs rename to src/ImageProcessorCore - Copy/Colors/Colorspaces/Hsl.cs diff --git a/src/ImageProcessorCore/Colors/Colorspaces/Hsv.cs b/src/ImageProcessorCore - Copy/Colors/Colorspaces/Hsv.cs similarity index 100% rename from src/ImageProcessorCore/Colors/Colorspaces/Hsv.cs rename to src/ImageProcessorCore - Copy/Colors/Colorspaces/Hsv.cs diff --git a/src/ImageProcessorCore/Colors/Colorspaces/YCbCr.cs b/src/ImageProcessorCore - Copy/Colors/Colorspaces/YCbCr.cs similarity index 100% rename from src/ImageProcessorCore/Colors/Colorspaces/YCbCr.cs rename to src/ImageProcessorCore - Copy/Colors/Colorspaces/YCbCr.cs diff --git a/src/ImageProcessorCore/Colors/IAlmostEquatable.cs b/src/ImageProcessorCore - Copy/Colors/IAlmostEquatable.cs similarity index 100% rename from src/ImageProcessorCore/Colors/IAlmostEquatable.cs rename to src/ImageProcessorCore - Copy/Colors/IAlmostEquatable.cs diff --git a/src/ImageProcessorCore/Colors/RgbaComponent.cs b/src/ImageProcessorCore - Copy/Colors/RgbaComponent.cs similarity index 100% rename from src/ImageProcessorCore/Colors/RgbaComponent.cs rename to src/ImageProcessorCore - Copy/Colors/RgbaComponent.cs diff --git a/src/ImageProcessorCore - Copy/Common/Exceptions/ImageFormatException.cs b/src/ImageProcessorCore - Copy/Common/Exceptions/ImageFormatException.cs new file mode 100644 index 0000000000..87fb381597 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Common/Exceptions/ImageFormatException.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + + /// + /// The exception that is thrown when the library tries to load + /// an image, which has an invalid format. + /// + public class ImageFormatException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public ImageFormatException() + { + } + + /// + /// Initializes a new instance of the class with the name of the + /// parameter that causes this exception. + /// + /// The error message that explains the reason for this exception. + public ImageFormatException(string errorMessage) + : base(errorMessage) + { + } + + /// + /// Initializes a new instance of the class with a specified + /// error message and the exception that is the cause of this exception. + /// + /// The error message that explains the reason for this exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) + /// if no inner exception is specified. + public ImageFormatException(string errorMessage, Exception innerException) + : base(errorMessage, innerException) + { + } + } +} diff --git a/src/ImageProcessorCore - Copy/Common/Exceptions/ImageProcessingException.cs b/src/ImageProcessorCore - Copy/Common/Exceptions/ImageProcessingException.cs new file mode 100644 index 0000000000..7899dcf44c --- /dev/null +++ b/src/ImageProcessorCore - Copy/Common/Exceptions/ImageProcessingException.cs @@ -0,0 +1,44 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + + /// + /// The exception that is thrown when an error occurs when applying a process to an image. + /// + public class ImageProcessingException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public ImageProcessingException() + { + } + + /// + /// Initializes a new instance of the class with the name of the + /// parameter that causes this exception. + /// + /// The error message that explains the reason for this exception. + public ImageProcessingException(string errorMessage) + : base(errorMessage) + { + } + + /// + /// Initializes a new instance of the class with a specified + /// error message and the exception that is the cause of this exception. + /// + /// The error message that explains the reason for this exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) + /// if no inner exception is specified. + public ImageProcessingException(string errorMessage, Exception innerException) + : base(errorMessage, innerException) + { + } + } +} diff --git a/src/ImageProcessorCore - Copy/Common/Extensions/ByteExtensions.cs b/src/ImageProcessorCore - Copy/Common/Extensions/ByteExtensions.cs new file mode 100644 index 0000000000..05b71bb2f6 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Common/Extensions/ByteExtensions.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + + /// + /// Extension methods for the struct. + /// + internal static class ByteExtensions + { + /// + /// Converts a byte array to a new array where each value in the original array is represented + /// by a the specified number of bits. + /// + /// The bytes to convert from. Cannot be null. + /// The number of bits per value. + /// The resulting array. Is never null. + /// is null. + /// is less than or equals than zero. + public static byte[] ToArrayByBitsLength(this byte[] bytes, int bits) + { + Guard.NotNull(bytes, "bytes"); + Guard.MustBeGreaterThan(bits, 0, "bits"); + + byte[] result; + + if (bits < 8) + { + result = new byte[bytes.Length * 8 / bits]; + + // BUGFIX I dont think it should be there, but I am not sure if it breaks something else + // int factor = (int)Math.Pow(2, bits) - 1; + int mask = 0xFF >> (8 - bits); + int resultOffset = 0; + + foreach (byte b in bytes) + { + for (int shift = 0; shift < 8; shift += bits) + { + int colorIndex = (b >> (8 - bits - shift)) & mask; // * (255 / factor); + + result[resultOffset] = (byte)colorIndex; + + resultOffset++; + } + } + } + else + { + result = bytes; + } + + return result; + } + } +} diff --git a/src/ImageProcessorCore - Copy/Common/Extensions/ComparableExtensions.cs b/src/ImageProcessorCore - Copy/Common/Extensions/ComparableExtensions.cs new file mode 100644 index 0000000000..cb0288fb7b --- /dev/null +++ b/src/ImageProcessorCore - Copy/Common/Extensions/ComparableExtensions.cs @@ -0,0 +1,145 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + + /// + /// Extension methods for classes that implement . + /// + internal static class ComparableExtensions + { + /// + /// Restricts a to be within a specified range. + /// + /// The The value to clamp. + /// The minimum value. If value is less than min, min will be returned. + /// The maximum value. If value is greater than max, max will be returned. + /// + /// The representing the clamped value. + /// + public static byte Clamp(this byte value, byte min, byte max) + { + // Order is important here as someone might set min to higher than max. + if (value > max) + { + return max; + } + + if (value < min) + { + return min; + } + + return value; + } + + /// + /// Restricts a to be within a specified range. + /// + /// The The value to clamp. + /// The minimum value. If value is less than min, min will be returned. + /// The maximum value. If value is greater than max, max will be returned. + /// + /// The representing the clamped value. + /// + public static int Clamp(this int value, int min, int max) + { + if (value > max) + { + return max; + } + + if (value < min) + { + return min; + } + + return value; + } + + /// + /// Restricts a to be within a specified range. + /// + /// The The value to clamp. + /// The minimum value. If value is less than min, min will be returned. + /// The maximum value. If value is greater than max, max will be returned. + /// + /// The representing the clamped value. + /// + public static float Clamp(this float value, float min, float max) + { + if (value > max) + { + return max; + } + + if (value < min) + { + return min; + } + + return value; + } + + /// + /// Restricts a to be within a specified range. + /// + /// The The value to clamp. + /// The minimum value. If value is less than min, min will be returned. + /// The maximum value. If value is greater than max, max will be returned. + /// + /// The representing the clamped value. + /// + public static double Clamp(this double value, double min, double max) + { + if (value > max) + { + return max; + } + + if (value < min) + { + return min; + } + + return value; + } + + /// + /// Converts an to a first restricting the value between the + /// minimum and maximum allowable ranges. + /// + /// The this method extends. + /// The + public static byte ToByte(this int value) + { + return (byte)value.Clamp(0, 255); + } + + /// + /// Converts an to a first restricting the value between the + /// minimum and maximum allowable ranges. + /// + /// The this method extends. + /// The + public static byte ToByte(this float value) + { + return (byte)value.Clamp(0, 255); + } + + /// + /// Converts an to a first restricting the value between the + /// minimum and maximum allowable ranges. + /// + /// The this method extends. + /// The + public static byte ToByte(this double value) + { + return (byte)value.Clamp(0, 255); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Common/Extensions/EnumerableExtensions.cs b/src/ImageProcessorCore - Copy/Common/Extensions/EnumerableExtensions.cs new file mode 100644 index 0000000000..107320412e --- /dev/null +++ b/src/ImageProcessorCore - Copy/Common/Extensions/EnumerableExtensions.cs @@ -0,0 +1,88 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.Collections.Generic; + + /// + /// Encapsulates a series of time saving extension methods to the interface. + /// + public static class EnumerableExtensions + { + /// + /// Generates a sequence of integral numbers within a specified range. + /// + /// + /// The start index, inclusive. + /// + /// + /// The end index, exclusive. + /// + /// + /// The incremental step. + /// + /// + /// The that contains a range of sequential integral numbers. + /// + public static IEnumerable SteppedRange(int fromInclusive, int toExclusive, int step) + { + // Borrowed from Enumerable.Range + long num = (fromInclusive + toExclusive) - 1L; + if ((toExclusive < 0) || (num > 0x7fffffffL)) + { + throw new ArgumentOutOfRangeException(nameof(toExclusive)); + } + + return RangeIterator(fromInclusive, i => i < toExclusive, step); + } + + /// + /// Generates a sequence of integral numbers within a specified range. + /// + /// + /// The start index, inclusive. + /// + /// + /// A method that has one parameter and returns a calculating the end index + /// + /// + /// The incremental step. + /// + /// + /// The that contains a range of sequential integral numbers. + /// + public static IEnumerable SteppedRange(int fromInclusive, Func toDelegate, int step) + { + return RangeIterator(fromInclusive, toDelegate, step); + } + + /// + /// Generates a sequence of integral numbers within a specified range. + /// + /// + /// The start index, inclusive. + /// + /// + /// A method that has one parameter and returns a calculating the end index + /// + /// + /// The incremental step. + /// + /// + /// The that contains a range of sequential integral numbers. + /// + private static IEnumerable RangeIterator(int fromInclusive, Func toDelegate, int step) + { + int i = fromInclusive; + while (toDelegate(i)) + { + yield return i; + i += step; + } + } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/Common/Helpers/Guard.cs b/src/ImageProcessorCore - Copy/Common/Helpers/Guard.cs new file mode 100644 index 0000000000..96c7023d43 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Common/Helpers/Guard.cs @@ -0,0 +1,192 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +// +// Provides methods to protect against invalid parameters. +// +// -------------------------------------------------------------------------------------------------------------------- + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("ImageProcessorCore.Tests")] +namespace ImageProcessorCore +{ + using System; + using System.Diagnostics; + + /// + /// Provides methods to protect against invalid parameters. + /// + [DebuggerStepThrough] + internal static class Guard + { + /// + /// Verifies, that the method parameter with specified object value is not null + /// and throws an exception if it is found to be so. + /// + /// + /// The target object, which cannot be null. + /// + /// + /// The name of the parameter that is to be checked. + /// + /// + /// The error message, if any to add to the exception. + /// + /// + /// is null + /// + public static void NotNull(object target, string parameterName, string message = "") + { + if (target == null) + { + if (string.IsNullOrWhiteSpace(message)) + { + throw new ArgumentNullException(parameterName, message); + } + + throw new ArgumentNullException(parameterName); + } + } + + /// + /// Verifies, that the string method parameter with specified object value and message + /// is not null, not empty and does not contain only blanks and throws an exception + /// if the object is null. + /// + /// The target string, which should be checked against being null or empty. + /// Name of the parameter. + /// + /// is null. + /// + /// + /// is + /// empty or contains only blanks. + /// + public static void NotNullOrEmpty(string target, string parameterName) + { + if (target == null) + { + throw new ArgumentNullException(parameterName); + } + + if (string.IsNullOrWhiteSpace(target)) + { + throw new ArgumentException("Value cannot be null or empty and cannot contain only blanks.", parameterName); + } + } + + /// + /// Verifies that the specified value is less than a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is greater than the maximum value. + /// + public static void MustBeLessThan(TValue value, TValue max, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(max) >= 0) + { + throw new ArgumentOutOfRangeException( + parameterName, + $"Value must be less than {max}."); + } + } + + /// + /// Verifies that the specified value is less than or equal to a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is greater than the maximum value. + /// + public static void MustBeLessThanOrEqualTo(TValue value, TValue max, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(max) > 0) + { + throw new ArgumentOutOfRangeException( + parameterName, + $"Value must be less than or equal to {max}."); + } + } + + /// + /// Verifies that the specified value is greater than a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is less than the minimum value. + /// + public static void MustBeGreaterThan(TValue value, TValue min, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(min) <= 0) + { + throw new ArgumentOutOfRangeException( + parameterName, + $"Value must be greater than {min}."); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is less than the minimum value. + /// + public static void MustBeGreaterThanOrEqualTo(TValue value, TValue min, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(min) < 0) + { + throw new ArgumentOutOfRangeException( + parameterName, + $"Value must be greater than or equal to {min}."); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value and less than + /// or equal to a maximum value and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is less than the minimum value of greater than the maximum value. + /// + public static void MustBeBetweenOrEqualTo(TValue value, TValue min, TValue max, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(min) < 0 || value.CompareTo(max) > 0) + { + throw new ArgumentOutOfRangeException( + parameterName, + $"Value must be greater than or equal to {min} and less than or equal to {max}."); + } + } + } +} diff --git a/src/ImageProcessorCore - Copy/Common/Helpers/ImageMaths.cs b/src/ImageProcessorCore - Copy/Common/Helpers/ImageMaths.cs new file mode 100644 index 0000000000..051b75f70f --- /dev/null +++ b/src/ImageProcessorCore - Copy/Common/Helpers/ImageMaths.cs @@ -0,0 +1,289 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.Linq; + using System.Numerics; + + /// + /// Provides common mathematical methods. + /// + internal static class ImageMaths + { + /// + /// Returns how many bits are required to store the specified number of colors. + /// Performs a Log2() on the value. + /// + /// The number of colors. + /// + /// The + /// + public static int GetBitsNeededForColorDepth(int colors) + { + return (int)Math.Ceiling(Math.Log(colors, 2)); + } + + /// + /// Implementation of 1D Gaussian G(x) function + /// + /// The x provided to G(x). + /// The spread of the blur. + /// The Gaussian G(x) + public static float Gaussian(float x, float sigma) + { + const float Numerator = 1.0f; + float denominator = (float)(Math.Sqrt(2 * Math.PI) * sigma); + + float exponentNumerator = -x * x; + float exponentDenominator = (float)(2 * Math.Pow(sigma, 2)); + + float left = Numerator / denominator; + float right = (float)Math.Exp(exponentNumerator / exponentDenominator); + + return left * right; + } + + /// + /// Returns the result of a B-C filter against the given value. + /// + /// + /// The value to process. + /// The B-Spline curve variable. + /// The Cardinal curve variable. + /// + /// The . + /// + public static float GetBcValue(float x, float b, float c) + { + float temp; + + if (x < 0) + { + x = -x; + } + + temp = x * x; + if (x < 1) + { + x = ((12 - (9 * b) - (6 * c)) * (x * temp)) + ((-18 + (12 * b) + (6 * c)) * temp) + (6 - (2 * b)); + return x / 6; + } + + if (x < 2) + { + x = ((-b - (6 * c)) * (x * temp)) + (((6 * b) + (30 * c)) * temp) + (((-12 * b) - (48 * c)) * x) + ((8 * b) + (24 * c)); + return x / 6; + } + + return 0; + } + + /// + /// Gets the result of a sine cardinal function for the given value. + /// + /// The value to calculate the result for. + /// + /// The . + /// + public static float SinC(float x) + { + const float Epsilon = .00001f; + + if (Math.Abs(x) > Epsilon) + { + x *= (float)Math.PI; + return Clean((float)Math.Sin(x) / x); + } + + return 1.0f; + } + + /// + /// Returns the given degrees converted to radians. + /// + /// The angle in degrees. + /// + /// The representing the degree as radians. + /// + public static float DegreesToRadians(float degrees) + { + return degrees * (float)(Math.PI / 180); + } + + /// + /// Gets the bounding from the given points. + /// + /// + /// The designating the top left position. + /// + /// + /// The designating the bottom right position. + /// + /// + /// The bounding . + /// + public static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) + { + return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y); + } + + /// + /// Gets the bounding from the given matrix. + /// + /// The source rectangle. + /// The transformation matrix. + /// + /// The . + /// + public static Rectangle GetBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix) + { + Vector2 leftTop = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix); + Vector2 rightTop = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix); + Vector2 leftBottom = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix); + Vector2 rightBottom = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix); + + Vector2[] allCorners = { leftTop, rightTop, leftBottom, rightBottom }; + float extentX = allCorners.Select(v => v.X).Max() - allCorners.Select(v => v.X).Min(); + float extentY = allCorners.Select(v => v.Y).Max() - allCorners.Select(v => v.Y).Min(); + return new Rectangle(0, 0, (int)extentX, (int)extentY); + } + + /// + /// Finds the bounding rectangle based on the first instance of any color component other + /// than the given one. + /// + /// The to search within. + /// The color component value to remove. + /// The channel to test against. + /// + /// The . + /// + public static Rectangle GetFilteredBoundingRectangle(ImageBase bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B) + { + const float Epsilon = .00001f; + int width = bitmap.Width; + int height = bitmap.Height; + Point topLeft = new Point(); + Point bottomRight = new Point(); + + Func delegateFunc; + + // Determine which channel to check against + switch (channel) + { + case RgbaComponent.R: + delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].R - b) > Epsilon; + break; + + case RgbaComponent.G: + delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].G - b) > Epsilon; + break; + + case RgbaComponent.A: + delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].A - b) > Epsilon; + break; + + default: + delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].B - b) > Epsilon; + break; + } + + Func getMinY = pixels => + { + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + if (delegateFunc(pixels, x, y, componentValue)) + { + return y; + } + } + } + + return 0; + }; + + Func getMaxY = pixels => + { + for (int y = height - 1; y > -1; y--) + { + for (int x = 0; x < width; x++) + { + if (delegateFunc(pixels, x, y, componentValue)) + { + return y; + } + } + } + + return height; + }; + + Func getMinX = pixels => + { + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + if (delegateFunc(pixels, x, y, componentValue)) + { + return x; + } + } + } + + return 0; + }; + + Func getMaxX = pixels => + { + for (int x = width - 1; x > -1; x--) + { + for (int y = 0; y < height; y++) + { + if (delegateFunc(pixels, x, y, componentValue)) + { + return x; + } + } + } + + return height; + }; + + using (PixelAccessor bitmapPixels = bitmap.Lock()) + { + topLeft.Y = getMinY(bitmapPixels); + topLeft.X = getMinX(bitmapPixels); + bottomRight.Y = (getMaxY(bitmapPixels) + 1).Clamp(0, height); + bottomRight.X = (getMaxX(bitmapPixels) + 1).Clamp(0, width); + } + + return GetBoundingRectangle(topLeft, bottomRight); + } + + /// + /// Ensures that any passed double is correctly rounded to zero + /// + /// The value to clean. + /// + /// The + /// . + private static float Clean(float x) + { + const float Epsilon = .00001f; + + if (Math.Abs(x) < Epsilon) + { + return 0f; + } + + return x; + } + } +} diff --git a/src/ImageProcessorCore/Filters/Alpha.cs b/src/ImageProcessorCore - Copy/Filters/Alpha.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Alpha.cs rename to src/ImageProcessorCore - Copy/Filters/Alpha.cs diff --git a/src/ImageProcessorCore/Filters/BackgroundColor.cs b/src/ImageProcessorCore - Copy/Filters/BackgroundColor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/BackgroundColor.cs rename to src/ImageProcessorCore - Copy/Filters/BackgroundColor.cs diff --git a/src/ImageProcessorCore/Filters/BlackWhite.cs b/src/ImageProcessorCore - Copy/Filters/BlackWhite.cs similarity index 100% rename from src/ImageProcessorCore/Filters/BlackWhite.cs rename to src/ImageProcessorCore - Copy/Filters/BlackWhite.cs diff --git a/src/ImageProcessorCore/Filters/Blend.cs b/src/ImageProcessorCore - Copy/Filters/Blend.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Blend.cs rename to src/ImageProcessorCore - Copy/Filters/Blend.cs diff --git a/src/ImageProcessorCore/Filters/BoxBlur.cs b/src/ImageProcessorCore - Copy/Filters/BoxBlur.cs similarity index 100% rename from src/ImageProcessorCore/Filters/BoxBlur.cs rename to src/ImageProcessorCore - Copy/Filters/BoxBlur.cs diff --git a/src/ImageProcessorCore/Filters/Brightness.cs b/src/ImageProcessorCore - Copy/Filters/Brightness.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Brightness.cs rename to src/ImageProcessorCore - Copy/Filters/Brightness.cs diff --git a/src/ImageProcessorCore/Filters/ColorBlindness.cs b/src/ImageProcessorCore - Copy/Filters/ColorBlindness.cs similarity index 100% rename from src/ImageProcessorCore/Filters/ColorBlindness.cs rename to src/ImageProcessorCore - Copy/Filters/ColorBlindness.cs diff --git a/src/ImageProcessorCore/Filters/Contrast.cs b/src/ImageProcessorCore - Copy/Filters/Contrast.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Contrast.cs rename to src/ImageProcessorCore - Copy/Filters/Contrast.cs diff --git a/src/ImageProcessorCore/Filters/DetectEdges.cs b/src/ImageProcessorCore - Copy/Filters/DetectEdges.cs similarity index 100% rename from src/ImageProcessorCore/Filters/DetectEdges.cs rename to src/ImageProcessorCore - Copy/Filters/DetectEdges.cs diff --git a/src/ImageProcessorCore/Filters/Greyscale.cs b/src/ImageProcessorCore - Copy/Filters/Greyscale.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Greyscale.cs rename to src/ImageProcessorCore - Copy/Filters/Greyscale.cs diff --git a/src/ImageProcessorCore/Filters/GuassianBlur.cs b/src/ImageProcessorCore - Copy/Filters/GuassianBlur.cs similarity index 100% rename from src/ImageProcessorCore/Filters/GuassianBlur.cs rename to src/ImageProcessorCore - Copy/Filters/GuassianBlur.cs diff --git a/src/ImageProcessorCore/Filters/GuassianSharpen.cs b/src/ImageProcessorCore - Copy/Filters/GuassianSharpen.cs similarity index 100% rename from src/ImageProcessorCore/Filters/GuassianSharpen.cs rename to src/ImageProcessorCore - Copy/Filters/GuassianSharpen.cs diff --git a/src/ImageProcessorCore/Filters/Hue.cs b/src/ImageProcessorCore - Copy/Filters/Hue.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Hue.cs rename to src/ImageProcessorCore - Copy/Filters/Hue.cs diff --git a/src/ImageProcessorCore/Filters/Invert.cs b/src/ImageProcessorCore - Copy/Filters/Invert.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Invert.cs rename to src/ImageProcessorCore - Copy/Filters/Invert.cs diff --git a/src/ImageProcessorCore/Filters/Kodachrome.cs b/src/ImageProcessorCore - Copy/Filters/Kodachrome.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Kodachrome.cs rename to src/ImageProcessorCore - Copy/Filters/Kodachrome.cs diff --git a/src/ImageProcessorCore/Filters/Lomograph.cs b/src/ImageProcessorCore - Copy/Filters/Lomograph.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Lomograph.cs rename to src/ImageProcessorCore - Copy/Filters/Lomograph.cs diff --git a/src/ImageProcessorCore/Filters/Options/ColorBlindness.cs b/src/ImageProcessorCore - Copy/Filters/Options/ColorBlindness.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Options/ColorBlindness.cs rename to src/ImageProcessorCore - Copy/Filters/Options/ColorBlindness.cs diff --git a/src/ImageProcessorCore/Filters/Pixelate.cs b/src/ImageProcessorCore - Copy/Filters/Pixelate.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Pixelate.cs rename to src/ImageProcessorCore - Copy/Filters/Pixelate.cs diff --git a/src/ImageProcessorCore/Filters/Polaroid.cs b/src/ImageProcessorCore - Copy/Filters/Polaroid.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Polaroid.cs rename to src/ImageProcessorCore - Copy/Filters/Polaroid.cs diff --git a/src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/AlphaProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/AlphaProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/BackgroundColorProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/BackgroundColorProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/BackgroundColorProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/BackgroundColorProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Binarization/ThresholdProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Binarization/ThresholdProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/BlendProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/BlendProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/BlendProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/BlendProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/BrightnessProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/BrightnessProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/README.md b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/README.md similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/README.md rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/README.md diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleMode.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleMode.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleMode.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleMode.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/HueProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/HueProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/KodachromeProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/KodachromeProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/KodachromeProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/KodachromeProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/LomographProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/LomographProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/LomographProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/LomographProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/PolaroidProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/PolaroidProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/PolaroidProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/PolaroidProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/SaturationProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/SaturationProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SepiaProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/SepiaProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/SepiaProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/SepiaProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ContrastProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/ContrastProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/BoxBlurProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/BoxBlurProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/BoxBlurProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/Convolution2DFilter.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/Convolution2DFilter.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2PassFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/Convolution2PassFilter.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2PassFilter.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/Convolution2PassFilter.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/ConvolutionFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/ConvolutionFilter.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/ConvolutionFilter.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/ConvolutionFilter.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KirschProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/KirschProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KirschProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/KirschProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/PrewittProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/PrewittProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/PrewittProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/PrewittProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/ScharrProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/ScharrProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/ScharrProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/ScharrProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianBlurProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/GuassianBlurProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/GuassianBlurProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/GuassianBlurProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianSharpenProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/GuassianSharpenProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/Convolution/GuassianSharpenProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/Convolution/GuassianSharpenProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/GlowProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/GlowProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/InvertProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/InvertProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/PixelateProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/PixelateProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/PixelateProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/PixelateProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/VignetteProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs rename to src/ImageProcessorCore - Copy/Filters/Processors/VignetteProcessor.cs diff --git a/src/ImageProcessorCore/Filters/Saturation.cs b/src/ImageProcessorCore - Copy/Filters/Saturation.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Saturation.cs rename to src/ImageProcessorCore - Copy/Filters/Saturation.cs diff --git a/src/ImageProcessorCore/Filters/Sepia.cs b/src/ImageProcessorCore - Copy/Filters/Sepia.cs similarity index 100% rename from src/ImageProcessorCore/Filters/Sepia.cs rename to src/ImageProcessorCore - Copy/Filters/Sepia.cs diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpBitsPerPixel.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpBitsPerPixel.cs new file mode 100644 index 0000000000..e7de3bc295 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpBitsPerPixel.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Enumerates the available bits per pixel for bitmap. + /// + public enum BmpBitsPerPixel + { + /// + /// 24 bits per pixel. Each pixel consists of 3 bytes. + /// + Pixel24 = 3, + + /// + /// 32 bits per pixel. Each pixel consists of 4 bytes. + /// + Pixel32 = 4, + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpCompression.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpCompression.cs new file mode 100644 index 0000000000..de3c66495d --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpCompression.cs @@ -0,0 +1,63 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Defines how the compression type of the image data + /// in the bitmap file. + /// + internal enum BmpCompression + { + /// + /// Each image row has a multiple of four elements. If the + /// row has less elements, zeros will be added at the right side. + /// The format depends on the number of bits, stored in the info header. + /// If the number of bits are one, four or eight each pixel data is + /// a index to the palette. If the number of bits are sixteen, + /// twenty-four or thirty-two each pixel contains a color. + /// + RGB = 0, + + /// + /// Two bytes are one data record. If the first byte is not zero, the + /// next two half bytes will be repeated as much as the value of the first byte. + /// If the first byte is zero, the record has different meanings, depending + /// on the second byte. If the second byte is zero, it is the end of the row, + /// if it is one, it is the end of the image. + /// Not supported at the moment. + /// + RLE8 = 1, + + /// + /// Two bytes are one data record. If the first byte is not zero, the + /// next byte will be repeated as much as the value of the first byte. + /// If the first byte is zero, the record has different meanings, depending + /// on the second byte. If the second byte is zero, it is the end of the row, + /// if it is one, it is the end of the image. + /// Not supported at the moment. + /// + RLE4 = 2, + + /// + /// Each image row has a multiple of four elements. If the + /// row has less elements, zeros will be added at the right side. + /// Not supported at the moment. + /// + BitFields = 3, + + /// + /// The bitmap contains a JPG image. + /// Not supported at the moment. + /// + JPEG = 4, + + /// + /// The bitmap contains a PNG image. + /// Not supported at the moment. + /// + PNG = 5 + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoder.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoder.cs new file mode 100644 index 0000000000..03d45f1122 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoder.cs @@ -0,0 +1,82 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + + /// + /// Image decoder for generating an image out of a Windows bitmap stream. + /// + /// + /// Does not support the following formats at the moment: + /// + /// JPG + /// PNG + /// RLE4 + /// RLE8 + /// BitFields + /// + /// Formats will be supported in a later releases. We advise always + /// to use only 24 Bit Windows bitmaps. + /// + public class BmpDecoder : IImageDecoder + { + /// + /// Gets the size of the header for this image type. + /// + /// The size of the header. + public int HeaderSize => 2; + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file extension. + /// + /// True if the decoder supports the file extension; otherwise, false. + /// + public bool IsSupportedFileExtension(string extension) + { + Guard.NotNullOrEmpty(extension, "extension"); + + extension = extension.StartsWith(".") ? extension.Substring(1) : extension; + + return extension.Equals("BMP", StringComparison.OrdinalIgnoreCase) + || extension.Equals("DIP", StringComparison.OrdinalIgnoreCase); + } + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file header. + /// + /// True if the decoder supports the file header; otherwise, false. + /// + public bool IsSupportedFileFormat(byte[] header) + { + bool isBmp = false; + if (header.Length >= 2) + { + isBmp = header[0] == 0x42 && // B + header[1] == 0x4D; // M + } + + return isBmp; + } + + /// + /// Decodes the image from the specified stream to the . + /// + /// The to decode to. + /// The containing image data. + public void Decode(Image image, Stream stream) + { + new BmpDecoderCore().Decode(image, stream); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoderCore.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoderCore.cs new file mode 100644 index 0000000000..9c2bc45b9a --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoderCore.cs @@ -0,0 +1,445 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + using System.Threading.Tasks; + + /// + /// Performs the bmp decoding operation. + /// + internal sealed class BmpDecoderCore + { + /// + /// The mask for the red part of the color for 16 bit rgb bitmaps. + /// + private const int Rgb16RMask = 0x00007C00; + + /// + /// The mask for the green part of the color for 16 bit rgb bitmaps. + /// + private const int Rgb16GMask = 0x000003E0; + + /// + /// The mask for the blue part of the color for 16 bit rgb bitmaps. + /// + private const int Rgb16BMask = 0x0000001F; + + /// + /// The stream to decode from. + /// + private Stream currentStream; + + /// + /// The file header containing general information. + /// TODO: Why is this not used? We advance the stream but do not use the values parsed. + /// + private BmpFileHeader fileHeader; + + /// + /// The info header containing detailed information about the bitmap. + /// + private BmpInfoHeader infoHeader; + + /// + /// Decodes the image from the specified this._stream and sets + /// the data to image. + /// + /// The type of pixels contained within the image. + /// The image, where the data should be set to. + /// Cannot be null (Nothing in Visual Basic). + /// The this._stream, where the image should be + /// decoded from. Cannot be null (Nothing in Visual Basic). + /// + /// is null. + /// - or - + /// is null. + /// + public void Decode(Image image, Stream stream) + where TPackedVector : IPackedVector, new() + { + this.currentStream = stream; + + try + { + this.ReadFileHeader(); + this.ReadInfoHeader(); + + // see http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517 + // If the height is negative, then this is a Windows bitmap whose origin + // is the upper-left corner and not the lower-left.The inverted flag + // indicates a lower-left origin.Our code will be outputting an + // upper-left origin pixel array. + bool inverted = false; + if (this.infoHeader.Height < 0) + { + inverted = true; + this.infoHeader.Height = -this.infoHeader.Height; + } + + int colorMapSize = -1; + + if (this.infoHeader.ClrUsed == 0) + { + if (this.infoHeader.BitsPerPixel == 1 || + this.infoHeader.BitsPerPixel == 4 || + this.infoHeader.BitsPerPixel == 8) + { + colorMapSize = (int)Math.Pow(2, this.infoHeader.BitsPerPixel) * 4; + } + } + else + { + colorMapSize = this.infoHeader.ClrUsed * 4; + } + + byte[] palette = null; + + if (colorMapSize > 0) + { + // 255 * 4 + if (colorMapSize > 1020) + { + throw new ImageFormatException($"Invalid bmp colormap size '{colorMapSize}'"); + } + + palette = new byte[colorMapSize]; + + this.currentStream.Read(palette, 0, colorMapSize); + } + + if (this.infoHeader.Width > image.MaxWidth || this.infoHeader.Height > image.MaxHeight) + { + throw new ArgumentOutOfRangeException( + $"The input bitmap '{this.infoHeader.Width}x{this.infoHeader.Height}' is " + + $"bigger then the max allowed size '{image.MaxWidth}x{image.MaxHeight}'"); + } + + TPackedVector[] imageData = new TPackedVector[this.infoHeader.Width * this.infoHeader.Height]; + + switch (this.infoHeader.Compression) + { + case BmpCompression.RGB: + if (this.infoHeader.HeaderSize != 40) + { + throw new ImageFormatException( + $"Header Size value '{this.infoHeader.HeaderSize}' is not valid."); + } + + if (this.infoHeader.BitsPerPixel == 32) + { + this.ReadRgb32(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted); + } + else if (this.infoHeader.BitsPerPixel == 24) + { + this.ReadRgb24(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted); + } + else if (this.infoHeader.BitsPerPixel == 16) + { + this.ReadRgb16(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted); + } + else if (this.infoHeader.BitsPerPixel <= 8) + { + this.ReadRgbPalette( + imageData, + palette, + this.infoHeader.Width, + this.infoHeader.Height, + this.infoHeader.BitsPerPixel, + inverted); + } + + break; + default: + throw new NotSupportedException("Does not support this kind of bitmap files."); + } + + image.SetPixels(this.infoHeader.Width, this.infoHeader.Height, imageData); + } + catch (IndexOutOfRangeException e) + { + throw new ImageFormatException("Bitmap does not have a valid format.", e); + } + } + + /// + /// Returns the y- value based on the given height. + /// + /// The y- value representing the current row. + /// The height of the bitmap. + /// The representing the inverted value. + private static int Invert(int y, int height, bool inverted) + { + int row; + + if (!inverted) + { + row = height - y - 1; + } + else + { + row = y; + } + + return row; + } + + /// + /// Reads the color palette from the stream. + /// + /// The image data to assign the palette to. + /// The containing the colors. + /// The width of the bitmap. + /// The height of the bitmap. + /// The number of bits per pixel. + private void ReadRgbPalette(float[] imageData, byte[] colors, int width, int height, int bits, bool inverted) + { + // Pixels per byte (bits per pixel) + int ppb = 8 / bits; + + int arrayWidth = (width + ppb - 1) / ppb; + + // Bit mask + int mask = 0xFF >> (8 - bits); + + byte[] data = new byte[arrayWidth * height]; + + this.currentStream.Read(data, 0, data.Length); + + // Rows are aligned on 4 byte boundaries + int alignment = arrayWidth % 4; + if (alignment != 0) + { + alignment = 4 - alignment; + } + + Parallel.For( + 0, + height, + y => + { + int rowOffset = y * (arrayWidth + alignment); + + for (int x = 0; x < arrayWidth; x++) + { + int offset = rowOffset + x; + + // Revert the y value, because bitmaps are saved from down to top + int row = Invert(y, height, inverted); + + int colOffset = x * ppb; + + for (int shift = 0; shift < ppb && (colOffset + shift) < width; shift++) + { + int colorIndex = ((data[offset] >> (8 - bits - (shift * bits))) & mask) * 4; + int arrayOffset = ((row * width) + (colOffset + shift)) * 4; + + // We divide by 255 as we will store the colors in our floating point format. + // Stored in r-> g-> b-> a order. + imageData[arrayOffset] = colors[colorIndex + 2] / 255f; // r + imageData[arrayOffset + 1] = colors[colorIndex + 1] / 255f; // g + imageData[arrayOffset + 2] = colors[colorIndex] / 255f; // b + imageData[arrayOffset + 3] = 1; // a + } + } + }); + } + + /// + /// Reads the 16 bit color palette from the stream + /// + /// The type of pixels contained within the image. + /// The image data to assign the palette to. + /// The width of the bitmap. + /// The height of the bitmap. + private void ReadRgb16(TPackedVector[] imageData, int width, int height, bool inverted) + where TPackedVector : IPackedVector, new() + { + // We divide here as we will store the colors in our floating point format. + const int ScaleR = 8; // 256/32 + const int ScaleG = 4; // 256/64 + + int alignment; + byte[] data = this.GetImageArray(width, height, 2, out alignment); + + Parallel.For( + 0, + height, + y => + { + int rowOffset = y * ((width * 2) + alignment); + + // Revert the y value, because bitmaps are saved from down to top + int row = Invert(y, height, inverted); + + for (int x = 0; x < width; x++) + { + int offset = rowOffset + (x * 2); + + short temp = BitConverter.ToInt16(data, offset); + + byte r = (byte)(((temp & Rgb16RMask) >> 11) * ScaleR); + byte g = (byte)(((temp & Rgb16GMask) >> 5) * ScaleG); + byte b = (byte)((temp & Rgb16BMask) * ScaleR); + + int arrayOffset = ((row * width) + x); + + // Stored in b-> g-> r-> a order. + TPackedVector packed = new TPackedVector(); + packed.PackBytes(b, g, r, 255); + imageData[arrayOffset] = packed; + } + }); + } + + /// + /// Reads the 24 bit color palette from the stream + /// + /// The type of pixels contained within the image. + /// The image data to assign the palette to. + /// The width of the bitmap. + /// The height of the bitmap. + private void ReadRgb24(TPackedVector[] imageData, int width, int height, bool inverted) + where TPackedVector : IPackedVector, new() + { + int alignment; + byte[] data = this.GetImageArray(width, height, 3, out alignment); + + Parallel.For( + 0, + height, + y => + { + int rowOffset = y * ((width * 3) + alignment); + + // Revert the y value, because bitmaps are saved from down to top + int row = Invert(y, height, inverted); + + for (int x = 0; x < width; x++) + { + int offset = rowOffset + (x * 3); + int arrayOffset = ((row * width) + x); + + // We divide by 255 as we will store the colors in our floating point format. + // Stored in b-> g-> r-> a order. + TPackedVector packed = new TPackedVector(); + packed.PackBytes(data[offset], data[offset + 1], data[offset + 2], 255); + imageData[arrayOffset] = packed; + } + }); + } + + /// + /// Reads the 32 bit color palette from the stream + /// + /// The type of pixels contained within the image. + /// The image data to assign the palette to. + /// The width of the bitmap. + /// The height of the bitmap. + private void ReadRgb32(TPackedVector[] imageData, int width, int height, bool inverted) + where TPackedVector : IPackedVector, new() + { + int alignment; + byte[] data = this.GetImageArray(width, height, 4, out alignment); + + Parallel.For( + 0, + height, + y => + { + int rowOffset = y * ((width * 4) + alignment); + + // Revert the y value, because bitmaps are saved from down to top + int row = Invert(y, height, inverted); + + for (int x = 0; x < width; x++) + { + int offset = rowOffset + (x * 4); + int arrayOffset = ((row * width) + x); + + // Stored in b-> g-> r-> a order. + TPackedVector packed = new TPackedVector(); + packed.PackBytes(data[offset], data[offset + 1], data[offset + 2], data[offset + 3]); + imageData[arrayOffset] = packed; + } + }); + } + + /// + /// Returns a containing the pixels for the current bitmap. + /// + /// The width of the bitmap. + /// The height. + /// The number of bytes per pixel. + /// The alignment of the pixels. + /// + /// The containing the pixels. + /// + private byte[] GetImageArray(int width, int height, int bytes, out int alignment) + { + int dataWidth = width; + + alignment = (width * bytes) % 4; + + if (alignment != 0) + { + alignment = 4 - alignment; + } + + int size = ((dataWidth * bytes) + alignment) * height; + + byte[] data = new byte[size]; + + this.currentStream.Read(data, 0, size); + + return data; + } + + /// + /// Reads the from the stream. + /// + private void ReadInfoHeader() + { + byte[] data = new byte[BmpInfoHeader.Size]; + + this.currentStream.Read(data, 0, BmpInfoHeader.Size); + + this.infoHeader = new BmpInfoHeader + { + HeaderSize = BitConverter.ToInt32(data, 0), + Width = BitConverter.ToInt32(data, 4), + Height = BitConverter.ToInt32(data, 8), + Planes = BitConverter.ToInt16(data, 12), + BitsPerPixel = BitConverter.ToInt16(data, 14), + ImageSize = BitConverter.ToInt32(data, 20), + XPelsPerMeter = BitConverter.ToInt32(data, 24), + YPelsPerMeter = BitConverter.ToInt32(data, 28), + ClrUsed = BitConverter.ToInt32(data, 32), + ClrImportant = BitConverter.ToInt32(data, 36), + Compression = (BmpCompression)BitConverter.ToInt32(data, 16) + }; + } + + /// + /// Reads the from the stream. + /// + private void ReadFileHeader() + { + byte[] data = new byte[BmpFileHeader.Size]; + + this.currentStream.Read(data, 0, BmpFileHeader.Size); + + this.fileHeader = new BmpFileHeader + { + Type = BitConverter.ToInt16(data, 0), + FileSize = BitConverter.ToInt32(data, 2), + Reserved = BitConverter.ToInt32(data, 6), + Offset = BitConverter.ToInt32(data, 10) + }; + } + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoder.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoder.cs new file mode 100644 index 0000000000..4b212d4ea0 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoder.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + + /// + /// Image encoder for writing an image to a stream as a Windows bitmap. + /// + /// The encoder can currently only write 24-bit rgb images to streams. + public class BmpEncoder : IImageEncoder + { + /// + /// Gets or sets the quality of output for images. + /// + /// Bitmap is a lossless format so this is not used in this encoder. + public int Quality { get; set; } + + /// + public string MimeType => "image/bmp"; + + /// + public string Extension => "bmp"; + + /// + /// Gets or sets the number of bits per pixel. + /// + public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; + + /// + public bool IsSupportedFileExtension(string extension) + { + Guard.NotNullOrEmpty(extension, nameof(extension)); + + extension = extension.StartsWith(".") ? extension.Substring(1) : extension; + + return extension.Equals(this.Extension, StringComparison.OrdinalIgnoreCase) + || extension.Equals("dip", StringComparison.OrdinalIgnoreCase); + } + + /// + public void Encode(ImageBase image, Stream stream) + where TPackedVector: IPackedVector + { + BmpEncoderCore encoder = new BmpEncoderCore(); + encoder.Encode(image, stream, this.BitsPerPixel); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoderCore.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoderCore.cs new file mode 100644 index 0000000000..2ad8e24e6e --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoderCore.cs @@ -0,0 +1,205 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + + using IO; + + /// + /// Image encoder for writing an image to a stream as a Windows bitmap. + /// + /// The encoder can currently only write 24-bit rgb images to streams. + internal sealed class BmpEncoderCore + { + /// + /// The number of bits per pixel. + /// + private BmpBitsPerPixel bmpBitsPerPixel; + + /// + /// Encodes the image to the specified stream from the . + /// + /// The type of pixels contained within the image. + /// The to encode from. + /// The to encode the image data to. + /// The + public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) + where TPackedVector : IPackedVector + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + this.bmpBitsPerPixel = bitsPerPixel; + + int rowWidth = image.Width; + + // TODO: Check this for varying file formats. + int amount = (image.Width * (int)this.bmpBitsPerPixel) % 4; + if (amount != 0) + { + rowWidth += 4 - amount; + } + + // Do not use IDisposable pattern here as we want to preserve the stream. + EndianBinaryWriter writer = new EndianBinaryWriter(EndianBitConverter.Little, stream); + + int bpp = (int)this.bmpBitsPerPixel; + + BmpFileHeader fileHeader = new BmpFileHeader + { + Type = 19778, // BM + Offset = 54, + FileSize = 54 + (image.Height * rowWidth * bpp) + }; + + BmpInfoHeader infoHeader = new BmpInfoHeader + { + HeaderSize = 40, + Height = image.Height, + Width = image.Width, + BitsPerPixel = (short)(8 * bpp), + Planes = 1, + ImageSize = image.Height * rowWidth * bpp, + ClrUsed = 0, + ClrImportant = 0 + }; + + WriteHeader(writer, fileHeader); + this.WriteInfo(writer, infoHeader); + this.WriteImage(writer, image); + + writer.Flush(); + } + + /// + /// Writes the bitmap header data to the binary stream. + /// + /// + /// The containing the stream to write to. + /// + /// + /// The containing the header data. + /// + private static void WriteHeader(EndianBinaryWriter writer, BmpFileHeader fileHeader) + { + writer.Write(fileHeader.Type); + writer.Write(fileHeader.FileSize); + writer.Write(fileHeader.Reserved); + writer.Write(fileHeader.Offset); + } + + /// + /// Writes the bitmap information to the binary stream. + /// + /// + /// The containing the stream to write to. + /// + /// + /// The containing the detailed information about the image. + /// + private void WriteInfo(EndianBinaryWriter writer, BmpInfoHeader infoHeader) + { + writer.Write(infoHeader.HeaderSize); + writer.Write(infoHeader.Width); + writer.Write(infoHeader.Height); + writer.Write(infoHeader.Planes); + writer.Write(infoHeader.BitsPerPixel); + writer.Write((int)infoHeader.Compression); + writer.Write(infoHeader.ImageSize); + writer.Write(infoHeader.XPelsPerMeter); + writer.Write(infoHeader.YPelsPerMeter); + writer.Write(infoHeader.ClrUsed); + writer.Write(infoHeader.ClrImportant); + } + + /// + /// Writes the pixel data to the binary stream. + /// + /// The type of pixels contained within the image. + /// + /// The containing the stream to write to. + /// + /// + /// The containing pixel data. + /// + private void WriteImage(EndianBinaryWriter writer, ImageBase image) + where TPackedVector : IPackedVector + { + // TODO: Add more compression formats. + int amount = (image.Width * (int)this.bmpBitsPerPixel) % 4; + if (amount != 0) + { + amount = 4 - amount; + } + + using (IPixelAccessor pixels = image.Lock()) + { + switch (this.bmpBitsPerPixel) + { + case BmpBitsPerPixel.Pixel32: + this.Write32bit(writer, pixels, amount); + break; + + case BmpBitsPerPixel.Pixel24: + this.Write24bit(writer, pixels, amount); + break; + } + } + } + + /// + /// Writes the 32bit color palette to the stream. + /// + /// The containing the stream to write to. + /// The containing pixel data. + /// The amount to pad each row by. + private void Write32bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) + { + for (int y = pixels.Height - 1; y >= 0; y--) + { + for (int x = 0; x < pixels.Width; x++) + { + // Convert back to b-> g-> r-> a order. + byte[] bytes = pixels[x, y].ToBytes(); + writer.Write(bytes); + } + + // Pad + for (int i = 0; i < amount; i++) + { + writer.Write((byte)0); + } + } + } + + /// + /// Writes the 24bit color palette to the stream. + /// + /// The containing the stream to write to. + /// The containing pixel data. + /// The amount to pad each row by. + private void Write24bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) + { + for (int y = pixels.Height - 1; y >= 0; y--) + { + for (int x = 0; x < pixels.Width; x++) + { + // Convert back to b-> g-> r-> a order. + byte[] bytes = pixels[x, y].ToBytes(); + writer.Write(new[] { bytes[0], bytes[1], bytes[2] }); + } + + // Pad + for (int i = 0; i < amount; i++) + { + writer.Write((byte)0); + } + } + } + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpFileHeader.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpFileHeader.cs new file mode 100644 index 0000000000..6f626ee703 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpFileHeader.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Stores general information about the Bitmap file. + /// + /// + /// + /// The first two bytes of the Bitmap file format + /// (thus the Bitmap header) are stored in big-endian order. + /// All of the other integer values are stored in little-endian format + /// (i.e. least-significant byte first). + /// + internal class BmpFileHeader + { + /// + /// Defines of the data structure in the bitmap file. + /// + public const int Size = 14; + + /// + /// Gets or sets the Bitmap identifier. + /// The field used to identify the bitmap file: 0x42 0x4D + /// (Hex code points for B and M) + /// + public short Type { get; set; } + + /// + /// Gets or sets the size of the bitmap file in bytes. + /// + public int FileSize { get; set; } + + /// + /// Gets or sets any reserved data; actual value depends on the application + /// that creates the image. + /// + public int Reserved { get; set; } + + /// + /// Gets or sets the offset, i.e. starting address, of the byte where + /// the bitmap data can be found. + /// + public int Offset { get; set; } + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpFormat.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpFormat.cs new file mode 100644 index 0000000000..6f640c4b9e --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpFormat.cs @@ -0,0 +1,19 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Encapsulates the means to encode and decode bitmap images. + /// + public class BmpFormat : IImageFormat + { + /// + public IImageDecoder Decoder => new BmpDecoder(); + + /// + public IImageEncoder Encoder => new BmpEncoder(); + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpInfoHeader.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpInfoHeader.cs new file mode 100644 index 0000000000..c21a52d21b --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpInfoHeader.cs @@ -0,0 +1,82 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageProcessorCore.Formats +{ + /// + /// This block of bytes tells the application detailed information + /// about the image, which will be used to display the image on + /// the screen. + /// + /// + internal class BmpInfoHeader + { + /// + /// Defines of the data structure in the bitmap file. + /// + public const int Size = 40; + + /// + /// Gets or sets the size of this header (40 bytes) + /// + public int HeaderSize { get; set; } + + /// + /// Gets or sets the bitmap width in pixels (signed integer). + /// + public int Width { get; set; } + + /// + /// Gets or sets the bitmap height in pixels (signed integer). + /// + public int Height { get; set; } + + /// + /// Gets or sets the number of color planes being used. Must be set to 1. + /// + public short Planes { get; set; } + + /// + /// Gets or sets the number of bits per pixel, which is the color depth of the image. + /// Typical values are 1, 4, 8, 16, 24 and 32. + /// + public short BitsPerPixel { get; set; } + + /// + /// Gets or sets the compression method being used. + /// See the next table for a list of possible values. + /// + public BmpCompression Compression { get; set; } + + /// + /// Gets or sets the image size. This is the size of the raw bitmap data (see below), + /// and should not be confused with the file size. + /// + public int ImageSize { get; set; } + + /// + /// Gets or sets the horizontal resolution of the image. + /// (pixel per meter, signed integer) + /// + public int XPelsPerMeter { get; set; } + + /// + /// Gets or sets the vertical resolution of the image. + /// (pixel per meter, signed integer) + /// + public int YPelsPerMeter { get; set; } + + /// + /// Gets or sets the number of colors in the color palette, + /// or 0 to default to 2^n. + /// + public int ClrUsed { get; set; } + + /// + /// Gets or sets the number of important colors used, + /// or 0 when every color is important{ get; set; } generally ignored. + /// + public int ClrImportant { get; set; } + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/README.md b/src/ImageProcessorCore - Copy/Formats/Bmp/README.md new file mode 100644 index 0000000000..d072838438 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/Bmp/README.md @@ -0,0 +1,8 @@ +Encoder/Decoder adapted from: + +https://github.com/yufeih/Nine.Imaging/ +https://imagetools.codeplex.com/ + +TODO: + +- Add support for all bitmap formats. diff --git a/src/ImageProcessorCore/Formats/Gif/BitEncoder.cs b/src/ImageProcessorCore - Copy/Formats/Gif/BitEncoder.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/BitEncoder.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/BitEncoder.cs diff --git a/src/ImageProcessorCore/Formats/Gif/DisposalMethod.cs b/src/ImageProcessorCore - Copy/Formats/Gif/DisposalMethod.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/DisposalMethod.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/DisposalMethod.cs diff --git a/src/ImageProcessorCore/Formats/Gif/GifConstants.cs b/src/ImageProcessorCore - Copy/Formats/Gif/GifConstants.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/GifConstants.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/GifConstants.cs diff --git a/src/ImageProcessorCore/Formats/Gif/GifDecoder.cs b/src/ImageProcessorCore - Copy/Formats/Gif/GifDecoder.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/GifDecoder.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/GifDecoder.cs diff --git a/src/ImageProcessorCore/Formats/Gif/GifDecoderCore.cs b/src/ImageProcessorCore - Copy/Formats/Gif/GifDecoderCore.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/GifDecoderCore.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/GifDecoderCore.cs diff --git a/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs b/src/ImageProcessorCore - Copy/Formats/Gif/GifEncoder.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/GifEncoder.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/GifEncoder.cs diff --git a/src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs b/src/ImageProcessorCore - Copy/Formats/Gif/GifEncoderCore.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/GifEncoderCore.cs diff --git a/src/ImageProcessorCore/Formats/Gif/GifFormat.cs b/src/ImageProcessorCore - Copy/Formats/Gif/GifFormat.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/GifFormat.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/GifFormat.cs diff --git a/src/ImageProcessorCore/Formats/Gif/LzwDecoder.cs b/src/ImageProcessorCore - Copy/Formats/Gif/LzwDecoder.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/LzwDecoder.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/LzwDecoder.cs diff --git a/src/ImageProcessorCore/Formats/Gif/LzwEncoder.cs b/src/ImageProcessorCore - Copy/Formats/Gif/LzwEncoder.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/LzwEncoder.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/LzwEncoder.cs diff --git a/src/ImageProcessorCore/Formats/Gif/PackedField.cs b/src/ImageProcessorCore - Copy/Formats/Gif/PackedField.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/PackedField.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/PackedField.cs diff --git a/src/ImageProcessorCore/Formats/Gif/README.md b/src/ImageProcessorCore - Copy/Formats/Gif/README.md similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/README.md rename to src/ImageProcessorCore - Copy/Formats/Gif/README.md diff --git a/src/ImageProcessorCore/Formats/Gif/Sections/GifGraphicsControlExtension.cs b/src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifGraphicsControlExtension.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/Sections/GifGraphicsControlExtension.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifGraphicsControlExtension.cs diff --git a/src/ImageProcessorCore/Formats/Gif/Sections/GifImageDescriptor.cs b/src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifImageDescriptor.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/Sections/GifImageDescriptor.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifImageDescriptor.cs diff --git a/src/ImageProcessorCore/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs b/src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs rename to src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs diff --git a/src/ImageProcessorCore - Copy/Formats/IImageDecoder.cs b/src/ImageProcessorCore - Copy/Formats/IImageDecoder.cs new file mode 100644 index 0000000000..0f3a8504c9 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/IImageDecoder.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System.IO; + + /// + /// Encapsulates properties and methods required for decoding an image from a stream. + /// + public interface IImageDecoder + { + /// + /// Gets the size of the header for this image type. + /// + /// The size of the header. + int HeaderSize { get; } + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file extension. + /// + /// True if the decoder supports the file extension; otherwise, false. + /// + bool IsSupportedFileExtension(string extension); + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file header. + /// + /// True if the decoder supports the file header; otherwise, false. + /// + bool IsSupportedFileFormat(byte[] header); + + /// + /// Decodes the image from the specified stream to the . + /// + /// The type of pixels contained within the image. + /// The to decode to. + /// The containing image data. + void Decode(Image image, Stream stream) where TPackedVector : IPackedVector; + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/IImageEncoder.cs b/src/ImageProcessorCore - Copy/Formats/IImageEncoder.cs new file mode 100644 index 0000000000..df7234aad0 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/IImageEncoder.cs @@ -0,0 +1,53 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +// +// Encapsulates properties and methods required for decoding an image to a stream. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessorCore.Formats +{ + using System.IO; + + /// + /// Encapsulates properties and methods required for encoding an image to a stream. + /// + public interface IImageEncoder + { + /// + /// Gets or sets the quality of output for images. + /// + int Quality { get; set; } + + /// + /// Gets the standard identifier used on the Internet to indicate the type of data that a file contains. + /// + string MimeType { get; } + + /// + /// Gets the default file extension for this encoder. + /// + string Extension { get; } + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file extension. + /// + /// True if the decoder supports the file extension; otherwise, false. + /// + bool IsSupportedFileExtension(string extension); + + /// + /// Encodes the image to the specified stream from the . + /// + /// The type of pixels contained within the image. + /// The to encode from. + /// The to encode the image data to. + void Encode(ImageBase image, Stream stream) where TPackedVector : IPackedVector; + } +} diff --git a/src/ImageProcessorCore - Copy/Formats/IImageFormat.cs b/src/ImageProcessorCore - Copy/Formats/IImageFormat.cs new file mode 100644 index 0000000000..62b4b78916 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Formats/IImageFormat.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Encapsulates a supported image format, providing means to encode and decode an image. + /// + public interface IImageFormat + { + /// + /// Gets the image encoder for encoding an image from a stream. + /// + IImageEncoder Encoder { get; } + + /// + /// Gets the image decoder for decoding an image from a stream. + /// + IImageDecoder Decoder { get; } + } +} diff --git a/src/ImageProcessorCore/Formats/Jpg/Block.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/Block.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Jpg/Block.cs rename to src/ImageProcessorCore - Copy/Formats/Jpg/Block.cs diff --git a/src/ImageProcessorCore/Formats/Jpg/FDCT.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/FDCT.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Jpg/FDCT.cs rename to src/ImageProcessorCore - Copy/Formats/Jpg/FDCT.cs diff --git a/src/ImageProcessorCore/Formats/Jpg/IDCT.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/IDCT.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Jpg/IDCT.cs rename to src/ImageProcessorCore - Copy/Formats/Jpg/IDCT.cs diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegDecoder.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/JpegDecoder.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Jpg/JpegDecoder.cs rename to src/ImageProcessorCore - Copy/Formats/Jpg/JpegDecoder.cs diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id b/src/ImageProcessorCore - Copy/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id similarity index 100% rename from src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id rename to src/ImageProcessorCore - Copy/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoder.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/JpegEncoder.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Jpg/JpegEncoder.cs rename to src/ImageProcessorCore - Copy/Formats/Jpg/JpegEncoder.cs diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/JpegEncoderCore.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs rename to src/ImageProcessorCore - Copy/Formats/Jpg/JpegEncoderCore.cs diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegFormat.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/JpegFormat.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Jpg/JpegFormat.cs rename to src/ImageProcessorCore - Copy/Formats/Jpg/JpegFormat.cs diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegSubsample.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/JpegSubsample.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Jpg/JpegSubsample.cs rename to src/ImageProcessorCore - Copy/Formats/Jpg/JpegSubsample.cs diff --git a/src/ImageProcessorCore/Formats/Jpg/README.md b/src/ImageProcessorCore - Copy/Formats/Jpg/README.md similarity index 100% rename from src/ImageProcessorCore/Formats/Jpg/README.md rename to src/ImageProcessorCore - Copy/Formats/Jpg/README.md diff --git a/src/ImageProcessorCore/Formats/Png/GrayscaleReader.cs b/src/ImageProcessorCore - Copy/Formats/Png/GrayscaleReader.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/GrayscaleReader.cs rename to src/ImageProcessorCore - Copy/Formats/Png/GrayscaleReader.cs diff --git a/src/ImageProcessorCore/Formats/Png/IColorReader.cs b/src/ImageProcessorCore - Copy/Formats/Png/IColorReader.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/IColorReader.cs rename to src/ImageProcessorCore - Copy/Formats/Png/IColorReader.cs diff --git a/src/ImageProcessorCore/Formats/Png/PaletteIndexReader.cs b/src/ImageProcessorCore - Copy/Formats/Png/PaletteIndexReader.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/PaletteIndexReader.cs rename to src/ImageProcessorCore - Copy/Formats/Png/PaletteIndexReader.cs diff --git a/src/ImageProcessorCore/Formats/Png/PngChunk.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngChunk.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/PngChunk.cs rename to src/ImageProcessorCore - Copy/Formats/Png/PngChunk.cs diff --git a/src/ImageProcessorCore/Formats/Png/PngChunkTypes.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngChunkTypes.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/PngChunkTypes.cs rename to src/ImageProcessorCore - Copy/Formats/Png/PngChunkTypes.cs diff --git a/src/ImageProcessorCore/Formats/Png/PngColorTypeInformation.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngColorTypeInformation.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/PngColorTypeInformation.cs rename to src/ImageProcessorCore - Copy/Formats/Png/PngColorTypeInformation.cs diff --git a/src/ImageProcessorCore/Formats/Png/PngDecoder.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngDecoder.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/PngDecoder.cs rename to src/ImageProcessorCore - Copy/Formats/Png/PngDecoder.cs diff --git a/src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngDecoderCore.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs rename to src/ImageProcessorCore - Copy/Formats/Png/PngDecoderCore.cs diff --git a/src/ImageProcessorCore/Formats/Png/PngEncoder.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngEncoder.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/PngEncoder.cs rename to src/ImageProcessorCore - Copy/Formats/Png/PngEncoder.cs diff --git a/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngEncoderCore.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs rename to src/ImageProcessorCore - Copy/Formats/Png/PngEncoderCore.cs diff --git a/src/ImageProcessorCore/Formats/Png/PngFormat.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngFormat.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/PngFormat.cs rename to src/ImageProcessorCore - Copy/Formats/Png/PngFormat.cs diff --git a/src/ImageProcessorCore/Formats/Png/PngHeader.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngHeader.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/PngHeader.cs rename to src/ImageProcessorCore - Copy/Formats/Png/PngHeader.cs diff --git a/src/ImageProcessorCore/Formats/Png/README.md b/src/ImageProcessorCore - Copy/Formats/Png/README.md similarity index 100% rename from src/ImageProcessorCore/Formats/Png/README.md rename to src/ImageProcessorCore - Copy/Formats/Png/README.md diff --git a/src/ImageProcessorCore/Formats/Png/TrueColorReader.cs b/src/ImageProcessorCore - Copy/Formats/Png/TrueColorReader.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/TrueColorReader.cs rename to src/ImageProcessorCore - Copy/Formats/Png/TrueColorReader.cs diff --git a/src/ImageProcessorCore/Formats/Png/Zlib/Adler32.cs b/src/ImageProcessorCore - Copy/Formats/Png/Zlib/Adler32.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/Zlib/Adler32.cs rename to src/ImageProcessorCore - Copy/Formats/Png/Zlib/Adler32.cs diff --git a/src/ImageProcessorCore/Formats/Png/Zlib/Crc32.cs b/src/ImageProcessorCore - Copy/Formats/Png/Zlib/Crc32.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/Zlib/Crc32.cs rename to src/ImageProcessorCore - Copy/Formats/Png/Zlib/Crc32.cs diff --git a/src/ImageProcessorCore/Formats/Png/Zlib/IChecksum.cs b/src/ImageProcessorCore - Copy/Formats/Png/Zlib/IChecksum.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/Zlib/IChecksum.cs rename to src/ImageProcessorCore - Copy/Formats/Png/Zlib/IChecksum.cs diff --git a/src/ImageProcessorCore/Formats/Png/Zlib/README.md b/src/ImageProcessorCore - Copy/Formats/Png/Zlib/README.md similarity index 100% rename from src/ImageProcessorCore/Formats/Png/Zlib/README.md rename to src/ImageProcessorCore - Copy/Formats/Png/Zlib/README.md diff --git a/src/ImageProcessorCore/Formats/Png/Zlib/ZlibDeflateStream.cs b/src/ImageProcessorCore - Copy/Formats/Png/Zlib/ZlibDeflateStream.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/Zlib/ZlibDeflateStream.cs rename to src/ImageProcessorCore - Copy/Formats/Png/Zlib/ZlibDeflateStream.cs diff --git a/src/ImageProcessorCore/Formats/Png/Zlib/ZlibInflateStream.cs b/src/ImageProcessorCore - Copy/Formats/Png/Zlib/ZlibInflateStream.cs similarity index 100% rename from src/ImageProcessorCore/Formats/Png/Zlib/ZlibInflateStream.cs rename to src/ImageProcessorCore - Copy/Formats/Png/Zlib/ZlibInflateStream.cs diff --git a/src/ImageProcessorCore - Copy/IImageBase.cs b/src/ImageProcessorCore - Copy/IImageBase.cs new file mode 100644 index 0000000000..a721033770 --- /dev/null +++ b/src/ImageProcessorCore - Copy/IImageBase.cs @@ -0,0 +1,29 @@ +namespace ImageProcessorCore +{ + public interface IImageBase + where TPackedVector : IPackedVector + { + Rectangle Bounds { get; } + int FrameDelay { get; set; } + int Height { get; } + double PixelRatio { get; } + TPackedVector[] Pixels { get; } + int Quality { get; set; } + + /// + /// Gets or sets the maximum allowable width in pixels. + /// + int MaxWidth { get; set; } + + /// + /// Gets or sets the maximum allowable height in pixels. + /// + int MaxHeight { get; set; } + + int Width { get; } + + void ClonePixels(int width, int height, TPackedVector[] pixels); + IPixelAccessor Lock(); + void SetPixels(int width, int height, TPackedVector[] pixels); + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/IImageFrame.cs b/src/ImageProcessorCore - Copy/IImageFrame.cs new file mode 100644 index 0000000000..1565879a76 --- /dev/null +++ b/src/ImageProcessorCore - Copy/IImageFrame.cs @@ -0,0 +1,7 @@ +namespace ImageProcessorCore +{ + public interface IImageFrame : IImageBase + where TPacked : IPackedVector + { + } +} diff --git a/src/ImageProcessorCore - Copy/IImageProcessor.cs b/src/ImageProcessorCore - Copy/IImageProcessor.cs new file mode 100644 index 0000000000..ad6e4ac761 --- /dev/null +++ b/src/ImageProcessorCore - Copy/IImageProcessor.cs @@ -0,0 +1,72 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// A delegate which is called as progress is made processing an image. + /// + /// The source of the event. + /// An object that contains the event data. + public delegate void ProgressEventHandler(object sender, ProgressEventArgs e); + + /// + /// Encapsulates methods to alter the pixels of an image. + /// + public interface IImageProcessor + { + /// + /// Event fires when each row of the source image has been processed. + /// + /// + /// This event may be called from threads other than the client thread, and from multiple threads simultaneously. + /// Individual row notifications may arrived out of order. + /// + event ProgressEventHandler OnProgress; + + /// + /// Applies the process to the specified portion of the specified . + /// + /// The type of pixels contained within the image. + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// The method keeps the source image unchanged and returns the + /// the result of image processing filter as new image. + /// + /// + /// is null or is null. + /// + /// + /// doesnt fit the dimension of the image. + /// + void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) where TPackedVector : IPackedVector; + + /// + /// Applies the process to the specified portion of the specified at the specified + /// location and with the specified size. + /// + /// The type of pixels contained within the image. + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// The target width. + /// The target height. + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. + /// + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// The method keeps the source image unchanged and returns the + /// the result of image process as new image. + /// + void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle) where TPackedVector : IPackedVector; + } +} diff --git a/src/ImageProcessorCore - Copy/IO/BigEndianBitConverter.cs b/src/ImageProcessorCore - Copy/IO/BigEndianBitConverter.cs new file mode 100644 index 0000000000..6c624c6a6b --- /dev/null +++ b/src/ImageProcessorCore - Copy/IO/BigEndianBitConverter.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.IO +{ + /// + /// Implementation of EndianBitConverter which converts to/from big-endian + /// byte arrays. + /// + /// Adapted from Miscellaneous Utility Library + /// This product includes software developed by Jon Skeet and Marc Gravell. Contact , or see + /// . + /// + /// + internal sealed class BigEndianBitConverter : EndianBitConverter + { + /// + public override Endianness Endianness => Endianness.BigEndian; + + /// + public override bool IsLittleEndian() => false; + + /// + protected internal override void CopyBytesImpl(long value, int bytes, byte[] buffer, int index) + { + int endOffset = index + bytes - 1; + for (int i = 0; i < bytes; i++) + { + buffer[endOffset - i] = unchecked((byte)(value & 0xff)); + value = value >> 8; + } + } + + /// + protected internal override long FromBytes(byte[] buffer, int startIndex, int bytesToConvert) + { + long ret = 0; + for (int i = 0; i < bytesToConvert; i++) + { + ret = unchecked((ret << 8) | buffer[startIndex + i]); + } + + return ret; + } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/IO/EndianBinaryReader.cs b/src/ImageProcessorCore - Copy/IO/EndianBinaryReader.cs new file mode 100644 index 0000000000..e2fc14a050 --- /dev/null +++ b/src/ImageProcessorCore - Copy/IO/EndianBinaryReader.cs @@ -0,0 +1,616 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.IO +{ + using System; + using System.IO; + using System.Text; + + /// + /// Equivalent of , but with either endianness, depending on + /// the EndianBitConverter it is constructed with. No data is buffered in the + /// reader; the client may seek within the stream at will. + /// + internal class EndianBinaryReader : IDisposable + { + /// + /// Decoder to use for string conversions. + /// + private readonly Decoder decoder; + + /// + /// Buffer used for temporary storage before conversion into primitives + /// + private readonly byte[] buffer = new byte[16]; + + /// + /// Buffer used for temporary storage when reading a single character + /// + private readonly char[] charBuffer = new char[1]; + + /// + /// Minimum number of bytes used to encode a character + /// + private readonly int minBytesPerChar; + + /// + /// Whether or not this reader has been disposed yet. + /// + private bool disposed; + + /// + /// Equivalent of System.IO.BinaryWriter, but with either endianness, depending on + /// the EndianBitConverter it is constructed with. + /// + /// Converter to use when reading data + /// Stream to read data from + public EndianBinaryReader(EndianBitConverter bitConverter, Stream stream) + : this(bitConverter, stream, Encoding.UTF8) + { + } + + /// + /// Constructs a new binary reader with the given bit converter, reading + /// to the given stream, using the given encoding. + /// + /// Converter to use when reading data + /// Stream to read data from + /// Encoding to use when reading character data + public EndianBinaryReader(EndianBitConverter bitConverter, Stream stream, Encoding encoding) + { + // TODO: Use Guard + if (bitConverter == null) + { + throw new ArgumentNullException("bitConverter"); + } + + if (stream == null) + { + throw new ArgumentNullException("stream"); + } + + if (encoding == null) + { + throw new ArgumentNullException("encoding"); + } + + if (!stream.CanRead) + { + throw new ArgumentException("Stream isn't writable", "stream"); + } + + this.BaseStream = stream; + this.BitConverter = bitConverter; + this.Encoding = encoding; + this.decoder = encoding.GetDecoder(); + this.minBytesPerChar = 1; + + if (encoding is UnicodeEncoding) + { + this.minBytesPerChar = 2; + } + } + + /// + /// Gets the bit converter used to read values from the stream. + /// + public EndianBitConverter BitConverter { get; } + + /// + /// Gets the encoding used to read strings + /// + public Encoding Encoding { get; } + + /// + /// Gets the underlying stream of the EndianBinaryReader. + /// + public Stream BaseStream { get; } + + /// + /// Closes the reader, including the underlying stream. + /// + public void Close() + { + this.Dispose(); + } + + /// + /// Seeks within the stream. + /// + /// Offset to seek to. + /// Origin of seek operation. + public void Seek(int offset, SeekOrigin origin) + { + this.CheckDisposed(); + this.BaseStream.Seek(offset, origin); + } + + /// + /// Reads a single byte from the stream. + /// + /// The byte read + public byte ReadByte() + { + this.ReadInternal(this.buffer, 1); + return this.buffer[0]; + } + + /// + /// Reads a single signed byte from the stream. + /// + /// The byte read + public sbyte ReadSByte() + { + this.ReadInternal(this.buffer, 1); + return unchecked((sbyte)this.buffer[0]); + } + + /// + /// Reads a boolean from the stream. 1 byte is read. + /// + /// The boolean read + public bool ReadBoolean() + { + this.ReadInternal(this.buffer, 1); + return this.BitConverter.ToBoolean(this.buffer, 0); + } + + /// + /// Reads a 16-bit signed integer from the stream, using the bit converter + /// for this reader. 2 bytes are read. + /// + /// The 16-bit integer read + public short ReadInt16() + { + this.ReadInternal(this.buffer, 2); + return this.BitConverter.ToInt16(this.buffer, 0); + } + + /// + /// Reads a 32-bit signed integer from the stream, using the bit converter + /// for this reader. 4 bytes are read. + /// + /// The 32-bit integer read + public int ReadInt32() + { + this.ReadInternal(this.buffer, 4); + return this.BitConverter.ToInt32(this.buffer, 0); + } + + /// + /// Reads a 64-bit signed integer from the stream, using the bit converter + /// for this reader. 8 bytes are read. + /// + /// The 64-bit integer read + public long ReadInt64() + { + this.ReadInternal(this.buffer, 8); + return this.BitConverter.ToInt64(this.buffer, 0); + } + + /// + /// Reads a 16-bit unsigned integer from the stream, using the bit converter + /// for this reader. 2 bytes are read. + /// + /// The 16-bit unsigned integer read + public ushort ReadUInt16() + { + this.ReadInternal(this.buffer, 2); + return this.BitConverter.ToUInt16(this.buffer, 0); + } + + /// + /// Reads a 32-bit unsigned integer from the stream, using the bit converter + /// for this reader. 4 bytes are read. + /// + /// The 32-bit unsigned integer read + public uint ReadUInt32() + { + this.ReadInternal(this.buffer, 4); + return this.BitConverter.ToUInt32(this.buffer, 0); + } + + /// + /// Reads a 64-bit unsigned integer from the stream, using the bit converter + /// for this reader. 8 bytes are read. + /// + /// The 64-bit unsigned integer read + public ulong ReadUInt64() + { + this.ReadInternal(this.buffer, 8); + return this.BitConverter.ToUInt64(this.buffer, 0); + } + + /// + /// Reads a single-precision floating-point value from the stream, using the bit converter + /// for this reader. 4 bytes are read. + /// + /// The floating point value read + public float ReadSingle() + { + this.ReadInternal(this.buffer, 4); + return this.BitConverter.ToSingle(this.buffer, 0); + } + + /// + /// Reads a double-precision floating-point value from the stream, using the bit converter + /// for this reader. 8 bytes are read. + /// + /// The floating point value read + public double ReadDouble() + { + this.ReadInternal(this.buffer, 8); + return this.BitConverter.ToDouble(this.buffer, 0); + } + + /// + /// Reads a decimal value from the stream, using the bit converter + /// for this reader. 16 bytes are read. + /// + /// The decimal value read + public decimal ReadDecimal() + { + this.ReadInternal(this.buffer, 16); + return this.BitConverter.ToDecimal(this.buffer, 0); + } + + /// + /// Reads a single character from the stream, using the character encoding for + /// this reader. If no characters have been fully read by the time the stream ends, + /// -1 is returned. + /// + /// The character read, or -1 for end of stream. + public int Read() + { + int charsRead = this.Read(this.charBuffer, 0, 1); + if (charsRead == 0) + { + return -1; + } + else + { + return this.charBuffer[0]; + } + } + + /// + /// Reads the specified number of characters into the given buffer, starting at + /// the given index. + /// + /// The buffer to copy data into + /// The first index to copy data into + /// The number of characters to read + /// The number of characters actually read. This will only be less than + /// the requested number of characters if the end of the stream is reached. + /// + public int Read(char[] data, int index, int count) + { + this.CheckDisposed(); + + // TODO: Use Guard + if (this.buffer == null) + { + throw new ArgumentNullException("buffer"); + } + + if (index < 0) + { + throw new ArgumentOutOfRangeException("index"); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException("index"); + } + + if (count + index > data.Length) + { + throw new ArgumentException("Not enough space in buffer for specified number of characters starting at specified index"); + } + + int read = 0; + bool firstTime = true; + + // Use the normal buffer if we're only reading a small amount, otherwise + // use at most 4K at a time. + byte[] byteBuffer = this.buffer; + + if (byteBuffer.Length < count * this.minBytesPerChar) + { + byteBuffer = new byte[4096]; + } + + while (read < count) + { + int amountToRead; + + // First time through we know we haven't previously read any data + if (firstTime) + { + amountToRead = count * this.minBytesPerChar; + firstTime = false; + } + + // After that we can only assume we need to fully read 'chars left -1' characters + // and a single byte of the character we may be in the middle of + else + { + amountToRead = ((count - read - 1) * this.minBytesPerChar) + 1; + } + + if (amountToRead > byteBuffer.Length) + { + amountToRead = byteBuffer.Length; + } + + int bytesRead = this.TryReadInternal(byteBuffer, amountToRead); + if (bytesRead == 0) + { + return read; + } + + int decoded = this.decoder.GetChars(byteBuffer, 0, bytesRead, data, index); + read += decoded; + index += decoded; + } + + return read; + } + + /// + /// Reads the specified number of bytes into the given buffer, starting at + /// the given index. + /// + /// The buffer to copy data into + /// The first index to copy data into + /// The number of bytes to read + /// The number of bytes actually read. This will only be less than + /// the requested number of bytes if the end of the stream is reached. + /// + public int Read(byte[] buffer, int index, int count) + { + this.CheckDisposed(); + if (buffer == null) + { + throw new ArgumentNullException("buffer"); + } + + if (index < 0) + { + throw new ArgumentOutOfRangeException("index"); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException("index"); + } + + if (count + index > buffer.Length) + { + throw new ArgumentException("Not enough space in buffer for specified number of bytes starting at specified index"); + } + + int read = 0; + while (count > 0) + { + int block = this.BaseStream.Read(buffer, index, count); + if (block == 0) + { + return read; + } + + index += block; + read += block; + count -= block; + } + + return read; + } + + /// + /// Reads the specified number of bytes, returning them in a new byte array. + /// If not enough bytes are available before the end of the stream, this + /// method will return what is available. + /// + /// The number of bytes to read + /// The bytes read + public byte[] ReadBytes(int count) + { + this.CheckDisposed(); + if (count < 0) + { + throw new ArgumentOutOfRangeException("count"); + } + + byte[] ret = new byte[count]; + int index = 0; + while (index < count) + { + int read = this.BaseStream.Read(ret, index, count - index); + + // Stream has finished half way through. That's fine, return what we've got. + if (read == 0) + { + byte[] copy = new byte[index]; + Buffer.BlockCopy(ret, 0, copy, 0, index); + return copy; + } + + index += read; + } + + return ret; + } + + /// + /// Reads the specified number of bytes, returning them in a new byte array. + /// If not enough bytes are available before the end of the stream, this + /// method will throw an IOException. + /// + /// The number of bytes to read + /// The bytes read + public byte[] ReadBytesOrThrow(int count) + { + byte[] ret = new byte[count]; + this.ReadInternal(ret, count); + return ret; + } + + /// + /// Reads a 7-bit encoded integer from the stream. This is stored with the least significant + /// information first, with 7 bits of information per byte of value, and the top + /// bit as a continuation flag. This method is not affected by the endianness + /// of the bit converter. + /// + /// The 7-bit encoded integer read from the stream. + public int Read7BitEncodedInt() + { + this.CheckDisposed(); + + int ret = 0; + for (int shift = 0; shift < 35; shift += 7) + { + int b = this.BaseStream.ReadByte(); + if (b == -1) + { + throw new EndOfStreamException(); + } + + ret = ret | ((b & 0x7f) << shift); + if ((b & 0x80) == 0) + { + return ret; + } + } + + // Still haven't seen a byte with the high bit unset? Dodgy data. + throw new IOException("Invalid 7-bit encoded integer in stream."); + } + + /// + /// Reads a 7-bit encoded integer from the stream. This is stored with the most significant + /// information first, with 7 bits of information per byte of value, and the top + /// bit as a continuation flag. This method is not affected by the endianness + /// of the bit converter. + /// + /// The 7-bit encoded integer read from the stream. + public int ReadBigEndian7BitEncodedInt() + { + this.CheckDisposed(); + + int ret = 0; + for (int i = 0; i < 5; i++) + { + int b = this.BaseStream.ReadByte(); + if (b == -1) + { + throw new EndOfStreamException(); + } + + ret = (ret << 7) | (b & 0x7f); + if ((b & 0x80) == 0) + { + return ret; + } + } + + // Still haven't seen a byte with the high bit unset? Dodgy data. + throw new IOException("Invalid 7-bit encoded integer in stream."); + } + + /// + /// Reads a length-prefixed string from the stream, using the encoding for this reader. + /// A 7-bit encoded integer is first read, which specifies the number of bytes + /// to read from the stream. These bytes are then converted into a string with + /// the encoding for this reader. + /// + /// The string read from the stream. + public string ReadString() + { + int bytesToRead = this.Read7BitEncodedInt(); + + byte[] data = new byte[bytesToRead]; + this.ReadInternal(data, bytesToRead); + return this.Encoding.GetString(data, 0, data.Length); + } + + /// + /// Disposes of the underlying stream. + /// + public void Dispose() + { + if (!this.disposed) + { + this.disposed = true; + ((IDisposable)this.BaseStream).Dispose(); + } + } + + /// + /// Checks whether or not the reader has been disposed, throwing an exception if so. + /// + private void CheckDisposed() + { + if (this.disposed) + { + throw new ObjectDisposedException("EndianBinaryReader"); + } + } + + /// + /// Reads the given number of bytes from the stream, throwing an exception + /// if they can't all be read. + /// + /// Buffer to read into + /// Number of bytes to read + private void ReadInternal(byte[] data, int size) + { + this.CheckDisposed(); + int index = 0; + while (index < size) + { + int read = this.BaseStream.Read(data, index, size - index); + if (read == 0) + { + throw new EndOfStreamException + ( + string.Format( + "End of stream reached with {0} byte{1} left to read.", + size - index, + size - index == 1 ? "s" : string.Empty)); + } + + index += read; + } + } + + /// + /// Reads the given number of bytes from the stream if possible, returning + /// the number of bytes actually read, which may be less than requested if + /// (and only if) the end of the stream is reached. + /// + /// Buffer to read into + /// Number of bytes to read + /// Number of bytes actually read + private int TryReadInternal(byte[] data, int size) + { + this.CheckDisposed(); + int index = 0; + while (index < size) + { + int read = this.BaseStream.Read(data, index, size - index); + if (read == 0) + { + return index; + } + + index += read; + } + + return index; + } + } +} diff --git a/src/ImageProcessorCore - Copy/IO/EndianBinaryWriter.cs b/src/ImageProcessorCore - Copy/IO/EndianBinaryWriter.cs new file mode 100644 index 0000000000..0f37b9a13d --- /dev/null +++ b/src/ImageProcessorCore - Copy/IO/EndianBinaryWriter.cs @@ -0,0 +1,385 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.IO +{ + using System; + using System.IO; + using System.Text; + + /// + /// Equivalent of , but with either endianness, depending on + /// the it is constructed with. + /// + internal class EndianBinaryWriter : IDisposable + { + /// + /// Buffer used for temporary storage during conversion from primitives + /// + private readonly byte[] buffer = new byte[16]; + + /// + /// Buffer used for Write(char) + /// + private readonly char[] charBuffer = new char[1]; + + /// + /// Whether or not this writer has been disposed yet. + /// + private bool disposed; + + /// + /// Initializes a new instance of the class + /// with the given bit converter, writing to the given stream, using UTF-8 encoding. + /// + /// Converter to use when writing data + /// Stream to write data to + public EndianBinaryWriter(EndianBitConverter bitConverter, Stream stream) + : this(bitConverter, stream, Encoding.UTF8) + { + } + + /// + /// Initializes a new instance of the class + /// with the given bit converter, writing to the given stream, using the given encoding. + /// + /// Converter to use when writing data + /// Stream to write data to + /// + /// Encoding to use when writing character data + /// + public EndianBinaryWriter(EndianBitConverter bitConverter, Stream stream, Encoding encoding) + { + // TODO: Use Guard + if (bitConverter == null) + { + throw new ArgumentNullException("bitConverter"); + } + + if (stream == null) + { + throw new ArgumentNullException("stream"); + } + + if (encoding == null) + { + throw new ArgumentNullException("encoding"); + } + + if (!stream.CanWrite) + { + throw new ArgumentException("Stream isn't writable", "stream"); + } + + this.BaseStream = stream; + this.BitConverter = bitConverter; + this.Encoding = encoding; + } + + /// + /// Gets the bit converter used to write values to the stream + /// + public EndianBitConverter BitConverter { get; } + + /// + /// Gets the encoding used to write strings + /// + public Encoding Encoding { get; } + + /// + /// Gets the underlying stream of the EndianBinaryWriter. + /// + public Stream BaseStream { get; } + + /// + /// Closes the writer, including the underlying stream. + /// + public void Close() + { + this.Dispose(); + } + + /// + /// Flushes the underlying stream. + /// + public void Flush() + { + this.CheckDisposed(); + this.BaseStream.Flush(); + } + + /// + /// Seeks within the stream. + /// + /// Offset to seek to. + /// Origin of seek operation. + public void Seek(int offset, SeekOrigin origin) + { + this.CheckDisposed(); + this.BaseStream.Seek(offset, origin); + } + + /// + /// Writes a boolean value to the stream. 1 byte is written. + /// + /// The value to write + public void Write(bool value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 1); + } + + /// + /// Writes a 16-bit signed integer to the stream, using the bit converter + /// for this writer. 2 bytes are written. + /// + /// The value to write + public void Write(short value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 2); + } + + /// + /// Writes a 32-bit signed integer to the stream, using the bit converter + /// for this writer. 4 bytes are written. + /// + /// The value to write + public void Write(int value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 4); + } + + /// + /// Writes a 64-bit signed integer to the stream, using the bit converter + /// for this writer. 8 bytes are written. + /// + /// The value to write + public void Write(long value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 8); + } + + /// + /// Writes a 16-bit unsigned integer to the stream, using the bit converter + /// for this writer. 2 bytes are written. + /// + /// The value to write + public void Write(ushort value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 2); + } + + /// + /// Writes a 32-bit unsigned integer to the stream, using the bit converter + /// for this writer. 4 bytes are written. + /// + /// The value to write + public void Write(uint value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 4); + } + + /// + /// Writes a 64-bit unsigned integer to the stream, using the bit converter + /// for this writer. 8 bytes are written. + /// + /// The value to write + public void Write(ulong value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 8); + } + + /// + /// Writes a single-precision floating-point value to the stream, using the bit converter + /// for this writer. 4 bytes are written. + /// + /// The value to write + public void Write(float value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 4); + } + + /// + /// Writes a double-precision floating-point value to the stream, using the bit converter + /// for this writer. 8 bytes are written. + /// + /// The value to write + public void Write(double value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 8); + } + + /// + /// Writes a decimal value to the stream, using the bit converter for this writer. + /// 16 bytes are written. + /// + /// The value to write + public void Write(decimal value) + { + this.BitConverter.CopyBytes(value, this.buffer, 0); + this.WriteInternal(this.buffer, 16); + } + + /// + /// Writes a signed byte to the stream. + /// + /// The value to write + public void Write(byte value) + { + this.buffer[0] = value; + this.WriteInternal(this.buffer, 1); + } + + /// + /// Writes an unsigned byte to the stream. + /// + /// The value to write + public void Write(sbyte value) + { + this.buffer[0] = unchecked((byte)value); + this.WriteInternal(this.buffer, 1); + } + + /// + /// Writes an array of bytes to the stream. + /// + /// The values to write + public void Write(byte[] value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + this.WriteInternal(value, value.Length); + } + + /// + /// Writes a portion of an array of bytes to the stream. + /// + /// An array containing the bytes to write + /// The index of the first byte to write within the array + /// The number of bytes to write + public void Write(byte[] value, int offset, int count) + { + this.CheckDisposed(); + this.BaseStream.Write(value, offset, count); + } + + /// + /// Writes a single character to the stream, using the encoding for this writer. + /// + /// The value to write + public void Write(char value) + { + this.charBuffer[0] = value; + this.Write(this.charBuffer); + } + + /// + /// Writes an array of characters to the stream, using the encoding for this writer. + /// + /// An array containing the characters to write + public void Write(char[] value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + this.CheckDisposed(); + byte[] data = this.Encoding.GetBytes(value, 0, value.Length); + this.WriteInternal(data, data.Length); + } + + /// + /// Writes a string to the stream, using the encoding for this writer. + /// + /// The value to write. Must not be null. + /// value is null + public void Write(string value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + this.CheckDisposed(); + byte[] data = this.Encoding.GetBytes(value); + this.Write7BitEncodedInt(data.Length); + this.WriteInternal(data, data.Length); + } + + /// + /// Writes a 7-bit encoded integer from the stream. This is stored with the least significant + /// information first, with 7 bits of information per byte of value, and the top + /// bit as a continuation flag. + /// + /// The 7-bit encoded integer to write to the stream + public void Write7BitEncodedInt(int value) + { + this.CheckDisposed(); + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "Value must be greater than or equal to 0."); + } + + int index = 0; + while (value >= 128) + { + this.buffer[index++] = (byte)((value & 0x7f) | 0x80); + value = value >> 7; + index++; + } + + this.buffer[index++] = (byte)value; + this.BaseStream.Write(this.buffer, 0, index); + } + + /// + /// Checks whether or not the writer has been disposed, throwing an exception if so. + /// + private void CheckDisposed() + { + if (this.disposed) + { + throw new ObjectDisposedException("EndianBinaryWriter"); + } + } + + /// + /// Writes the specified number of bytes from the start of the given byte array, + /// after checking whether or not the writer has been disposed. + /// + /// The array of bytes to write from + /// The number of bytes to write + private void WriteInternal(byte[] bytes, int length) + { + this.CheckDisposed(); + this.BaseStream.Write(bytes, 0, length); + } + + /// + /// Disposes of the underlying stream. + /// + public void Dispose() + { + if (!this.disposed) + { + this.Flush(); + this.disposed = true; + ((IDisposable)this.BaseStream).Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/IO/EndianBitConverter.cs b/src/ImageProcessorCore - Copy/IO/EndianBitConverter.cs new file mode 100644 index 0000000000..d95527002b --- /dev/null +++ b/src/ImageProcessorCore - Copy/IO/EndianBitConverter.cs @@ -0,0 +1,724 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.IO +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.Runtime.InteropServices; + + /// + /// Equivalent of , but with either endianness. + /// + /// Adapted from Miscellaneous Utility Library + /// This product includes software developed by Jon Skeet and Marc Gravell. Contact , or see + /// . + /// + /// + [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:ElementsMustAppearInTheCorrectOrder", Justification = "Reviewed. Suppression is OK here. Better readability.")] + [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:ElementsMustBeOrderedByAccess", Justification = "Reviewed. Suppression is OK here. Better readability.")] + [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204:StaticElementsMustAppearBeforeInstanceElements", Justification = "Reviewed. Suppression is OK here. Better readability.")] + internal abstract class EndianBitConverter + { + #region Endianness of this converter + /// + /// Indicates the byte order ("endianness") in which data is converted using this class. + /// + /// + /// Different computer architectures store data using different byte orders. "Big-endian" + /// means the most significant byte is on the left end of a word. "Little-endian" means the + /// most significant byte is on the right end of a word. + /// + /// true if this converter is little-endian, false otherwise. + public abstract bool IsLittleEndian(); + + /// + /// Gets the byte order ("endianness") in which data is converted using this class. + /// + public abstract Endianness Endianness { get; } + #endregion + + #region Factory properties + /// + /// The little-endian bit converter. + /// + private static readonly LittleEndianBitConverter LittleConverter = new LittleEndianBitConverter(); + + /// + /// Gets a little-endian bit converter instance. The same instance is + /// always returned. + /// + public static LittleEndianBitConverter Little => LittleConverter; + + /// + /// The big-endian bit converter. + /// + private static readonly BigEndianBitConverter BigConverter = new BigEndianBitConverter(); + + /// + /// Gets a big-endian bit converter instance. The same instance is + /// always returned. + /// + public static BigEndianBitConverter Big => BigConverter; + #endregion + + #region Double/primitive conversions + /// + /// Converts the specified double-precision floating point number to a + /// 64-bit signed integer. Note: the endianness of this converter does not + /// affect the returned value. + /// + /// The number to convert. + /// A 64-bit signed integer whose value is equivalent to value. + public long DoubleToInt64Bits(double value) + { + return BitConverter.DoubleToInt64Bits(value); + } + + /// + /// Converts the specified 64-bit signed integer to a double-precision + /// floating point number. Note: the endianness of this converter does not + /// affect the returned value. + /// + /// The number to convert. + /// A double-precision floating point number whose value is equivalent to value. + public double Int64BitsToDouble(long value) + { + return BitConverter.Int64BitsToDouble(value); + } + + /// + /// Converts the specified single-precision floating point number to a + /// 32-bit signed integer. Note: the endianness of this converter does not + /// affect the returned value. + /// + /// The number to convert. + /// A 32-bit signed integer whose value is equivalent to value. + public int SingleToInt32Bits(float value) + { + return new Int32SingleUnion(value).AsInt32; + } + + /// + /// Converts the specified 32-bit signed integer to a single-precision floating point + /// number. Note: the endianness of this converter does not + /// affect the returned value. + /// + /// The number to convert. + /// A single-precision floating point number whose value is equivalent to value. + public float Int32BitsToSingle(int value) + { + return new Int32SingleUnion(value).AsSingle; + } + #endregion + + #region To(PrimitiveType) conversions + /// + /// Returns a Boolean value converted from one byte at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// true if the byte at startIndex in value is nonzero; otherwise, false. + public bool ToBoolean(byte[] value, int startIndex) + { + CheckByteArgument(value, startIndex, 1); + return BitConverter.ToBoolean(value, startIndex); + } + + /// + /// Returns a Unicode character converted from two bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A character formed by two bytes beginning at startIndex. + public char ToChar(byte[] value, int startIndex) + { + return unchecked((char)this.CheckedFromBytes(value, startIndex, 2)); + } + + /// + /// Returns a double-precision floating point number converted from eight bytes + /// at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A double precision floating point number formed by eight bytes beginning at startIndex. + public double ToDouble(byte[] value, int startIndex) + { + return this.Int64BitsToDouble(this.ToInt64(value, startIndex)); + } + + /// + /// Returns a single-precision floating point number converted from four bytes + /// at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A single precision floating point number formed by four bytes beginning at startIndex. + public float ToSingle(byte[] value, int startIndex) + { + return this.Int32BitsToSingle(this.ToInt32(value, startIndex)); + } + + /// + /// Returns a 16-bit signed integer converted from two bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 16-bit signed integer formed by two bytes beginning at startIndex. + public short ToInt16(byte[] value, int startIndex) + { + return unchecked((short)this.CheckedFromBytes(value, startIndex, 2)); + } + + /// + /// Returns a 32-bit signed integer converted from four bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 32-bit signed integer formed by four bytes beginning at startIndex. + public int ToInt32(byte[] value, int startIndex) + { + return unchecked((int)this.CheckedFromBytes(value, startIndex, 4)); + } + + /// + /// Returns a 64-bit signed integer converted from eight bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 64-bit signed integer formed by eight bytes beginning at startIndex. + public long ToInt64(byte[] value, int startIndex) + { + return this.CheckedFromBytes(value, startIndex, 8); + } + + /// + /// Returns a 16-bit unsigned integer converted from two bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 16-bit unsigned integer formed by two bytes beginning at startIndex. + public ushort ToUInt16(byte[] value, int startIndex) + { + return unchecked((ushort)this.CheckedFromBytes(value, startIndex, 2)); + } + + /// + /// Returns a 32-bit unsigned integer converted from four bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 32-bit unsigned integer formed by four bytes beginning at startIndex. + public uint ToUInt32(byte[] value, int startIndex) + { + return unchecked((uint)this.CheckedFromBytes(value, startIndex, 4)); + } + + /// + /// Returns a 64-bit unsigned integer converted from eight bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 64-bit unsigned integer formed by eight bytes beginning at startIndex. + public ulong ToUInt64(byte[] value, int startIndex) + { + return unchecked((ulong)this.CheckedFromBytes(value, startIndex, 8)); + } + + /// + /// Convert the given number of bytes from the given array, from the given start + /// position, into a long, using the bytes as the least significant part of the long. + /// By the time this is called, the arguments have been checked for validity. + /// + /// The bytes to convert + /// The index of the first byte to convert + /// The number of bytes to use in the conversion + /// The converted number + protected internal abstract long FromBytes(byte[] value, int startIndex, int bytesToConvert); + + /// + /// Checks the given argument for validity. + /// + /// The byte array passed in + /// The start index passed in + /// The number of bytes required + /// value is a null reference + /// + /// startIndex is less than zero or greater than the length of value minus bytesRequired. + /// + [SuppressMessage("ReSharper", "UnusedParameter.Local", Justification = "Keeps code DRY")] + private static void CheckByteArgument(byte[] value, int startIndex, int bytesRequired) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (startIndex < 0 || startIndex > value.Length - bytesRequired) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + } + + /// + /// Checks the arguments for validity before calling FromBytes + /// (which can therefore assume the arguments are valid). + /// + /// The bytes to convert after checking + /// The index of the first byte to convert + /// The number of bytes to convert + /// The + private long CheckedFromBytes(byte[] value, int startIndex, int bytesToConvert) + { + CheckByteArgument(value, startIndex, bytesToConvert); + return this.FromBytes(value, startIndex, bytesToConvert); + } + #endregion + + #region ToString conversions + /// + /// Returns a String converted from the elements of a byte array. + /// + /// An array of bytes. + /// All the elements of value are converted. + /// + /// A String of hexadecimal pairs separated by hyphens, where each pair + /// represents the corresponding element in value; for example, "7F-2C-4A". + /// + public static string ToString(byte[] value) + { + return BitConverter.ToString(value); + } + + /// + /// Returns a String converted from the elements of a byte array starting at a specified array position. + /// + /// An array of bytes. + /// The starting position within value. + /// The elements from array position startIndex to the end of the array are converted. + /// + /// A String of hexadecimal pairs separated by hyphens, where each pair + /// represents the corresponding element in value; for example, "7F-2C-4A". + /// + public static string ToString(byte[] value, int startIndex) + { + return BitConverter.ToString(value, startIndex); + } + + /// + /// Returns a String converted from a specified number of bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// The number of bytes to convert. + /// The length elements from array position startIndex are converted. + /// + /// A String of hexadecimal pairs separated by hyphens, where each pair + /// represents the corresponding element in value; for example, "7F-2C-4A". + /// + public static string ToString(byte[] value, int startIndex, int length) + { + return BitConverter.ToString(value, startIndex, length); + } + #endregion + + #region Decimal conversions + /// + /// Returns a decimal value converted from sixteen bytes + /// at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A decimal formed by sixteen bytes beginning at startIndex. + public decimal ToDecimal(byte[] value, int startIndex) + { + // HACK: This always assumes four parts, each in their own endianness, + // starting with the first part at the start of the byte array. + // On the other hand, there's no real format specified... + int[] parts = new int[4]; + for (int i = 0; i < 4; i++) + { + parts[i] = this.ToInt32(value, startIndex + (i * 4)); + } + + return new decimal(parts); + } + + /// + /// Returns the specified decimal value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 16. + public byte[] GetBytes(decimal value) + { + byte[] bytes = new byte[16]; + int[] parts = decimal.GetBits(value); + for (int i = 0; i < 4; i++) + { + this.CopyBytesImpl(parts[i], 4, bytes, i * 4); + } + + return bytes; + } + + /// + /// Copies the specified decimal value into the specified byte array, + /// beginning at the specified index. + /// + /// A character to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(decimal value, byte[] buffer, int index) + { + int[] parts = decimal.GetBits(value); + for (int i = 0; i < 4; i++) + { + this.CopyBytesImpl(parts[i], 4, buffer, (i * 4) + index); + } + } + #endregion + + #region GetBytes conversions + /// + /// Returns an array with the given number of bytes formed + /// from the least significant bytes of the specified value. + /// This is used to implement the other GetBytes methods. + /// + /// The value to get bytes for + /// The number of significant bytes to return + /// + /// The . + /// + private byte[] GetBytes(long value, int bytes) + { + byte[] buffer = new byte[bytes]; + this.CopyBytes(value, bytes, buffer, 0); + return buffer; + } + + /// + /// Returns the specified Boolean value as an array of bytes. + /// + /// A Boolean value. + /// An array of bytes with length 1. + /// + /// The . + /// + public byte[] GetBytes(bool value) + { + return BitConverter.GetBytes(value); + } + + /// + /// Returns the specified Unicode character value as an array of bytes. + /// + /// A character to convert. + /// An array of bytes with length 2. + /// + /// The . + /// + public byte[] GetBytes(char value) + { + return this.GetBytes(value, 2); + } + + /// + /// Returns the specified double-precision floating point value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 8. + public byte[] GetBytes(double value) + { + return this.GetBytes(this.DoubleToInt64Bits(value), 8); + } + + /// + /// Returns the specified 16-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 2. + public byte[] GetBytes(short value) + { + return this.GetBytes(value, 2); + } + + /// + /// Returns the specified 32-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 4. + public byte[] GetBytes(int value) + { + return this.GetBytes(value, 4); + } + + /// + /// Returns the specified 64-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 8. + public byte[] GetBytes(long value) + { + return this.GetBytes(value, 8); + } + + /// + /// Returns the specified single-precision floating point value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 4. + public byte[] GetBytes(float value) + { + return this.GetBytes(this.SingleToInt32Bits(value), 4); + } + + /// + /// Returns the specified 16-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 2. + public byte[] GetBytes(ushort value) + { + return this.GetBytes(value, 2); + } + + /// + /// Returns the specified 32-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 4. + public byte[] GetBytes(uint value) + { + return this.GetBytes(value, 4); + } + + /// + /// Returns the specified 64-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 8. + public byte[] GetBytes(ulong value) + { + return this.GetBytes(unchecked((long)value), 8); + } + + #endregion + + #region CopyBytes conversions + /// + /// Copies the given number of bytes from the least-specific + /// end of the specified value into the specified byte array, beginning + /// at the specified index. + /// This is used to implement the other CopyBytes methods. + /// + /// The value to copy bytes for + /// The number of significant bytes to copy + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + private void CopyBytes(long value, int bytes, byte[] buffer, int index) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer), "Byte array must not be null"); + } + + if (buffer.Length < index + bytes) + { + throw new ArgumentOutOfRangeException(nameof(buffer), "Buffer not big enough for value"); + } + + this.CopyBytesImpl(value, bytes, buffer, index); + } + + /// + /// Copies the given number of bytes from the least-specific + /// end of the specified value into the specified byte array, beginning + /// at the specified index. + /// This must be implemented in concrete derived classes, but the implementation + /// may assume that the value will fit into the buffer. + /// + /// The value to copy bytes for + /// The number of significant bytes to copy + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + protected internal abstract void CopyBytesImpl(long value, int bytes, byte[] buffer, int index); + + /// + /// Copies the specified Boolean value into the specified byte array, + /// beginning at the specified index. + /// + /// A Boolean value. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(bool value, byte[] buffer, int index) + { + this.CopyBytes(value ? 1 : 0, 1, buffer, index); + } + + /// + /// Copies the specified Unicode character value into the specified byte array, + /// beginning at the specified index. + /// + /// A character to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(char value, byte[] buffer, int index) + { + this.CopyBytes(value, 2, buffer, index); + } + + /// + /// Copies the specified double-precision floating point value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(double value, byte[] buffer, int index) + { + this.CopyBytes(this.DoubleToInt64Bits(value), 8, buffer, index); + } + + /// + /// Copies the specified 16-bit signed integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(short value, byte[] buffer, int index) + { + this.CopyBytes(value, 2, buffer, index); + } + + /// + /// Copies the specified 32-bit signed integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(int value, byte[] buffer, int index) + { + this.CopyBytes(value, 4, buffer, index); + } + + /// + /// Copies the specified 64-bit signed integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(long value, byte[] buffer, int index) + { + this.CopyBytes(value, 8, buffer, index); + } + + /// + /// Copies the specified single-precision floating point value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(float value, byte[] buffer, int index) + { + this.CopyBytes(this.SingleToInt32Bits(value), 4, buffer, index); + } + + /// + /// Copies the specified 16-bit unsigned integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(ushort value, byte[] buffer, int index) + { + this.CopyBytes(value, 2, buffer, index); + } + + /// + /// Copies the specified 32-bit unsigned integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(uint value, byte[] buffer, int index) + { + this.CopyBytes(value, 4, buffer, index); + } + + /// + /// Copies the specified 64-bit unsigned integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(ulong value, byte[] buffer, int index) + { + this.CopyBytes(unchecked((long)value), 8, buffer, index); + } + + #endregion + + #region Private struct used for Single/Int32 conversions + /// + /// Union used solely for the equivalent of DoubleToInt64Bits and vice versa. + /// + [StructLayout(LayoutKind.Explicit)] + private struct Int32SingleUnion + { + /// + /// Int32 version of the value. + /// + [FieldOffset(0)] + private readonly int i; + + /// + /// Single version of the value. + /// + [FieldOffset(0)] + private readonly float f; + + /// + /// Initializes a new instance of the struct. + /// + /// The integer value of the new instance. + internal Int32SingleUnion(int i) + { + this.f = 0; // Just to keep the compiler happy + this.i = i; + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The floating point value of the new instance. + /// + internal Int32SingleUnion(float f) + { + this.i = 0; // Just to keep the compiler happy + this.f = f; + } + + /// + /// Gets the value of the instance as an integer. + /// + internal int AsInt32 => this.i; + + /// + /// Gets the value of the instance as a floating point number. + /// + internal float AsSingle => this.f; + } + #endregion + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/IO/Endianness.cs b/src/ImageProcessorCore - Copy/IO/Endianness.cs new file mode 100644 index 0000000000..e8e4ef4c2d --- /dev/null +++ b/src/ImageProcessorCore - Copy/IO/Endianness.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.IO +{ + /// + /// Endianness of a converter + /// + internal enum Endianness + { + /// + /// Little endian - least significant byte first + /// + LittleEndian, + + /// + /// Big endian - most significant byte first + /// + BigEndian + } +} diff --git a/src/ImageProcessorCore - Copy/IO/LittleEndianBitConverter.cs b/src/ImageProcessorCore - Copy/IO/LittleEndianBitConverter.cs new file mode 100644 index 0000000000..70e65d9091 --- /dev/null +++ b/src/ImageProcessorCore - Copy/IO/LittleEndianBitConverter.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.IO +{ + /// + /// Implementation of EndianBitConverter which converts to/from little-endian + /// byte arrays. + /// + /// Adapted from Miscellaneous Utility Library + /// This product includes software developed by Jon Skeet and Marc Gravell. Contact , or see + /// . + /// + /// + internal sealed class LittleEndianBitConverter : EndianBitConverter + { + /// + public override Endianness Endianness => Endianness.LittleEndian; + + /// + public override bool IsLittleEndian() => true; + + /// + protected internal override void CopyBytesImpl(long value, int bytes, byte[] buffer, int index) + { + for (int i = 0; i < bytes; i++) + { + buffer[i + index] = unchecked((byte)(value & 0xff)); + value = value >> 8; + } + } + + /// + protected internal override long FromBytes(byte[] buffer, int startIndex, int bytesToConvert) + { + long ret = 0; + for (int i = 0; i < bytesToConvert; i++) + { + ret = unchecked((ret << 8) | buffer[startIndex + bytesToConvert - 1 - i]); + } + + return ret; + } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/Image.cs b/src/ImageProcessorCore - Copy/Image.cs new file mode 100644 index 0000000000..c8dc861d6a --- /dev/null +++ b/src/ImageProcessorCore - Copy/Image.cs @@ -0,0 +1,291 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System.IO; + using System.Text; + + using System; + using System.Collections.Generic; + using System.Linq; + + using Formats; + + /// + /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. + /// + /// + /// The packed vector containing pixel information. + /// + public class Image : ImageBase + where TPackedVector : IPackedVector + { + /// + /// The default horizontal resolution value (dots per inch) in x direction. + /// The default value is 96 dots per inch. + /// + public const double DefaultHorizontalResolution = 96; + + /// + /// The default vertical resolution value (dots per inch) in y direction. + /// The default value is 96 dots per inch. + /// + public const double DefaultVerticalResolution = 96; + + /// + /// Initializes a new instance of the class. + /// + public Image() + { + this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat)); + } + + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + /// The width of the image in pixels. + /// The height of the image in pixels. + public Image(int width, int height) + : base(width, height) + { + this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat)); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// Thrown if the is null. + public Image(Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + this.Load(stream); + } + + /// + /// Initializes a new instance of the class + /// by making a copy from another image. + /// + /// The other image, where the clone should be made from. + /// is null. + public Image(Image other) + { + foreach (ImageFrame frame in other.Frames) + { + if (frame != null) + { + this.Frames.Add(new ImageFrame(frame)); + } + } + + this.RepeatCount = other.RepeatCount; + this.HorizontalResolution = other.HorizontalResolution; + this.VerticalResolution = other.VerticalResolution; + this.CurrentImageFormat = other.CurrentImageFormat; + } + + /// + /// Gets a list of supported image formats. + /// + public IReadOnlyCollection Formats { get; } = Bootstrapper.Instance.ImageFormats; + + /// + /// Gets or sets the resolution of the image in x- direction. It is defined as + /// number of dots per inch and should be an positive value. + /// + /// The density of the image in x- direction. + public double HorizontalResolution { get; set; } = DefaultHorizontalResolution; + + /// + /// Gets or sets the resolution of the image in y- direction. It is defined as + /// number of dots per inch and should be an positive value. + /// + /// The density of the image in y- direction. + public double VerticalResolution { get; set; } = DefaultVerticalResolution; + + /// + /// Gets the width of the image in inches. It is calculated as the width of the image + /// in pixels multiplied with the density. When the density is equals or less than zero + /// the default value is used. + /// + /// The width of the image in inches. + public double InchWidth + { + get + { + double resolution = this.HorizontalResolution; + + if (resolution <= 0) + { + resolution = DefaultHorizontalResolution; + } + + return this.Width / resolution; + } + } + + /// + /// Gets the height of the image in inches. It is calculated as the height of the image + /// in pixels multiplied with the density. When the density is equals or less than zero + /// the default value is used. + /// + /// The height of the image in inches. + public double InchHeight + { + get + { + double resolution = this.VerticalResolution; + + if (resolution <= 0) + { + resolution = DefaultVerticalResolution; + } + + return this.Height / resolution; + } + } + + /// + /// Gets a value indicating whether this image is animated. + /// + /// + /// True if this image is animated; otherwise, false. + /// + public bool IsAnimated => this.Frames.Count > 0; + + /// + /// Gets or sets the number of times any animation is repeated. + /// 0 means to repeat indefinitely. + /// + public ushort RepeatCount { get; set; } + + /// + /// Gets the other frames for the animation. + /// + /// The list of frame images. + public IList> Frames { get; } = new List>(); + + /// + /// Gets the list of properties for storing meta information about this image. + /// + /// A list of image properties. + public IList Properties { get; } = new List(); + + /// + /// Gets the currently loaded image format. + /// + public IImageFormat CurrentImageFormat { get; internal set; } + + /// + public override IPixelAccessor Lock() + { + return Bootstrapper.Instance.GetPixelAccessor(this); + } + + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The stream to save the image to. + /// Thrown if the stream is null. + public void Save(Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + this.CurrentImageFormat.Encoder.Encode(this, stream); + } + + /// + /// Saves the image to the given stream using the given image format. + /// + /// The stream to save the image to. + /// The format to save the image as. + /// Thrown if the stream is null. + public void Save(Stream stream, IImageFormat format) + { + Guard.NotNull(stream, nameof(stream)); + format.Encoder.Encode(this, stream); + } + + /// + /// Saves the image to the given stream using the given image encoder. + /// + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + public void Save(Stream stream, IImageEncoder encoder) + { + Guard.NotNull(stream, nameof(stream)); + encoder.Encode(this, stream); + } + + /// + /// Returns a Base64 encoded string from the given image. + /// + /// data:image/gif;base64,R0lGODlhAQABAIABAEdJRgAAACwAAAAAAQABAAACAkQBAA== + /// The + public override string ToString() + { + using (MemoryStream stream = new MemoryStream()) + { + this.Save(stream); + stream.Flush(); + return $"data:{this.CurrentImageFormat.Encoder.MimeType};base64,{Convert.ToBase64String(stream.ToArray())}"; + } + } + + /// + /// Loads the image from the given stream. + /// + /// The stream containing image information. + /// + /// Thrown if the stream is not readable nor seekable. + /// + private void Load(Stream stream) + { + if (!this.Formats.Any()) { return; } + + if (!stream.CanRead) + { + throw new NotSupportedException("Cannot read from the stream."); + } + + if (!stream.CanSeek) + { + throw new NotSupportedException("The stream does not support seeking."); + } + + int maxHeaderSize = this.Formats.Max(x => x.Decoder.HeaderSize); + if (maxHeaderSize > 0) + { + byte[] header = new byte[maxHeaderSize]; + + stream.Position = 0; + stream.Read(header, 0, maxHeaderSize); + stream.Position = 0; + + IImageFormat format = this.Formats.FirstOrDefault(x => x.Decoder.IsSupportedFileFormat(header)); + if (format != null) + { + format.Decoder.Decode(this, stream); + this.CurrentImageFormat = format; + return; + } + } + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("Image cannot be loaded. Available formats:"); + + foreach (IImageFormat format in this.Formats) + { + stringBuilder.AppendLine("-" + format); + } + + throw new NotSupportedException(stringBuilder.ToString()); + } + } +} diff --git a/src/ImageProcessorCore - Copy/ImageBase.cs b/src/ImageProcessorCore - Copy/ImageBase.cs new file mode 100644 index 0000000000..5fa83df4c7 --- /dev/null +++ b/src/ImageProcessorCore - Copy/ImageBase.cs @@ -0,0 +1,201 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + + /// + /// The base class of all images. Encapsulates the basic properties and methods required to manipulate images + /// in different pixel formats. + /// + /// + /// The packed vector pixels format. + /// + public abstract class ImageBase : IImageBase + where TPacked : IPackedVector + { + /// + /// Initializes a new instance of the class. + /// + protected ImageBase() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The width of the image in pixels. + /// The height of the image in pixels. + /// + /// Thrown if either or are less than or equal to 0. + /// + protected ImageBase(int width, int height) + { + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + this.Width = width; + this.Height = height; + this.Pixels = new TPacked[width * height]; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The other to create this instance from. + /// + /// + /// Thrown if the given is null. + /// + protected ImageBase(ImageBase other) + { + Guard.NotNull(other, nameof(other), "Other image cannot be null."); + + this.Width = other.Width; + this.Height = other.Height; + this.Quality = other.Quality; + this.FrameDelay = other.FrameDelay; + + // Copy the pixels. + this.Pixels = new TPacked[this.Width * this.Height]; + Array.Copy(other.Pixels, this.Pixels, other.Pixels.Length); + } + + /// + /// Gets or sets the maximum allowable width in pixels. + /// + public int MaxWidth { get; set; } = int.MaxValue; + + /// + /// Gets or sets the maximum allowable height in pixels. + /// + public int MaxHeight { get; set; } = int.MaxValue; + + /// + /// Gets the pixels as an array of the given packed pixel format. + /// + public TPacked[] Pixels { get; private set; } + + /// + /// Gets the width in pixels. + /// + public int Width { get; private set; } + + /// + /// Gets the height in pixels. + /// + public int Height { get; private set; } + + /// + /// Gets the pixel ratio made up of the width and height. + /// + public double PixelRatio => (double)this.Width / this.Height; + + /// + /// Gets the representing the bounds of the image. + /// + public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height); + + /// + /// Gets or sets th quality of the image. This affects the output quality of lossy image formats. + /// + public int Quality { get; set; } + + /// + /// Gets or sets the frame delay for animated images. + /// If not 0, this field specifies the number of hundredths (1/100) of a second to + /// wait before continuing with the processing of the Data Stream. + /// The clock starts ticking immediately after the graphic is rendered. + /// + public int FrameDelay { get; set; } + + /// + /// Sets the pixel array of the image to the given value. + /// + /// The new width of the image. Must be greater than zero. + /// The new height of the image. Must be greater than zero. + /// + /// The array with colors. Must be a multiple of the width and height. + /// + /// + /// Thrown if either or are less than or equal to 0. + /// + /// + /// Thrown if the length is not equal to Width * Height. + /// + public void SetPixels(int width, int height, TPacked[] pixels) + { + if (width <= 0) + { + throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than or equals than zero."); + } + + if (height <= 0) + { + throw new ArgumentOutOfRangeException(nameof(height), "Height must be greater than or equal than zero."); + } + + if (pixels.Length != width * height) + { + throw new ArgumentException("Pixel array must have the length of Width * Height."); + } + + this.Width = width; + this.Height = height; + this.Pixels = pixels; + } + + /// + /// Sets the pixel array of the image to the given value, creating a copy of + /// the original pixels. + /// + /// The new width of the image. Must be greater than zero. + /// The new height of the image. Must be greater than zero. + /// + /// The array with colors. Must be a multiple of four times the width and height. + /// + /// + /// Thrown if either or are less than or equal to 0. + /// + /// + /// Thrown if the length is not equal to Width * Height. + /// + public void ClonePixels(int width, int height, TPacked[] pixels) + { + if (width <= 0) + { + throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than or equals than zero."); + } + + if (height <= 0) + { + throw new ArgumentOutOfRangeException(nameof(height), "Height must be greater than or equal than zero."); + } + + if (pixels.Length != width * height) + { + throw new ArgumentException("Pixel array must have the length of Width * Height."); + } + + this.Width = width; + this.Height = height; + + // Copy the pixels. + this.Pixels = new TPacked[pixels.Length]; + Array.Copy(pixels, this.Pixels, pixels.Length); + } + + /// + /// Locks the image providing access to the pixels. + /// + /// It is imperative that the accessor is correctly disposed off after use. + /// + /// + /// The + public abstract IPixelAccessor Lock(); + } +} diff --git a/src/ImageProcessorCore - Copy/ImageExtensions.cs b/src/ImageProcessorCore - Copy/ImageExtensions.cs new file mode 100644 index 0000000000..e7b261d608 --- /dev/null +++ b/src/ImageProcessorCore - Copy/ImageExtensions.cs @@ -0,0 +1,166 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.IO; + + using Formats; + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Saves the image to the given stream with the bmp format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsBmp(this ImageBase source, Stream stream) => new BmpEncoder().Encode(source, stream); + + /// + /// Saves the image to the given stream with the png format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The quality to save the image to representing the number of colors. + /// Anything equal to 256 and below will cause the encoder to save the image in an indexed format. + /// + /// Thrown if the stream is null. + public static void SaveAsPng(this ImageBase source, Stream stream, int quality = Int32.MaxValue) => new PngEncoder { Quality = quality }.Encode(source, stream); + + /// + /// Saves the image to the given stream with the jpeg format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The quality to save the image to. Between 1 and 100. + /// Thrown if the stream is null. + public static void SaveAsJpeg(this ImageBase source, Stream stream, int quality = 75) => new JpegEncoder { Quality = quality }.Encode(source, stream); + + /// + /// Saves the image to the given stream with the gif format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The quality to save the image to representing the number of colors. Between 1 and 256. + /// Thrown if the stream is null. + public static void SaveAsGif(this ImageBase source, Stream stream, int quality = 256) => new GifEncoder { Quality = quality }.Encode(source, stream); + + /// + /// Applies the collection of processors to the image. + /// This method does not resize the target image. + /// + /// The image this method extends. + /// The processor to apply to the image. + /// The . + public static Image Process(this Image source, IImageProcessor processor) + { + return Process(source, source.Bounds, processor); + } + + /// + /// Applies the collection of processors to the image. + /// This method does not resize the target image. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The processors to apply to the image. + /// The . + public static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor) + { + return PerformAction(source, true, (sourceImage, targetImage) => processor.Apply(targetImage, sourceImage, sourceRectangle)); + } + + /// + /// Applies the collection of processors to the image. + /// + /// This method is not chainable. + /// + /// + /// The source image. Cannot be null. + /// The target image width. + /// The target image height. + /// The processor to apply to the image. + /// The . + public static Image Process(this Image source, int width, int height, IImageSampler sampler) + { + return Process(source, width, height, source.Bounds, default(Rectangle), sampler); + } + + /// + /// Applies the collection of processors to the image. + /// + /// This method does will resize the target image if the source and target rectangles are different. + /// + /// + /// The source image. Cannot be null. + /// The target image width. + /// The target image height. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. + /// + /// The processor to apply to the image. + /// The . + public static Image Process(this Image source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler) + { + return PerformAction(source, false, (sourceImage, targetImage) => sampler.Apply(targetImage, sourceImage, width, height, targetRectangle, sourceRectangle)); + } + + /// + /// Performs the given action on the source image. + /// + /// The image to perform the action against. + /// Whether to clone the image. + /// The to perform against the image. + /// The . + /// Thrown if the has been disposed. + private static Image PerformAction(Image source, bool clone, Action action) + { + Image transformedImage = clone + ? new Image(source) + : new Image + { + // Several properties require copying + // TODO: Check why we need to set these? + HorizontalResolution = source.HorizontalResolution, + VerticalResolution = source.VerticalResolution, + CurrentImageFormat = source.CurrentImageFormat, + RepeatCount = source.RepeatCount + }; + + action(source, transformedImage); + + for (int i = 0; i < source.Frames.Count; i++) + { + ImageFrame sourceFrame = source.Frames[i]; + ImageFrame tranformedFrame = clone ? new ImageFrame(sourceFrame) : new ImageFrame { FrameDelay = sourceFrame.FrameDelay }; + action(sourceFrame, tranformedFrame); + + if (!clone) + { + transformedImage.Frames.Add(tranformedFrame); + } + else + { + transformedImage.Frames[i] = tranformedFrame; + } + } + + source = transformedImage; + return source; + } + } +} diff --git a/src/ImageProcessorCore - Copy/ImageFrame.cs b/src/ImageProcessorCore - Copy/ImageFrame.cs new file mode 100644 index 0000000000..479d9dd9b0 --- /dev/null +++ b/src/ImageProcessorCore - Copy/ImageFrame.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// Represents a single frame in a animation. + /// + /// + /// The packed vector containing pixel information. + /// + public class ImageFrame : ImageBase + where TPackedVector : IPackedVector + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The frame to create the frame from. + /// + public ImageFrame(ImageFrame frame) + : base(frame) + { + } + + /// + public override IPixelAccessor Lock() + { + return Bootstrapper.Instance.GetPixelAccessor(this); + } + } +} diff --git a/src/ImageProcessorCore - Copy/ImageProcessor.cs b/src/ImageProcessorCore - Copy/ImageProcessor.cs new file mode 100644 index 0000000000..e7057a3f9c --- /dev/null +++ b/src/ImageProcessorCore - Copy/ImageProcessor.cs @@ -0,0 +1,163 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System; + using System.Threading; + + /// + /// Allows the application of processors to images. + /// + public abstract class ImageProcessor : IImageProcessor + { + /// + public event ProgressEventHandler OnProgress; + + /// + /// The number of rows processed by a derived class. + /// + private int numRowsProcessed; + + /// + /// The total number of rows that will be processed by a derived class. + /// + private int totalRows; + + /// + public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) + where TPackedVector : IPackedVector + { + try + { + this.OnApply(target, source, target.Bounds, sourceRectangle); + + this.numRowsProcessed = 0; + this.totalRows = sourceRectangle.Height; + + this.Apply(target, source, target.Bounds, sourceRectangle, sourceRectangle.Y, sourceRectangle.Bottom); + + this.AfterApply(target, source, target.Bounds, sourceRectangle); + } + catch (Exception ex) + { + + throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); + } + } + + /// + public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) + where TPackedVector : IPackedVector + { + try + { + TPackedVector[] pixels = new TPackedVector[width * height]; + target.SetPixels(width, height, pixels); + + // Ensure we always have bounds. + if (sourceRectangle == Rectangle.Empty) + { + sourceRectangle = source.Bounds; + } + + if (targetRectangle == Rectangle.Empty) + { + targetRectangle = target.Bounds; + } + + this.OnApply(target, source, targetRectangle, sourceRectangle); + + this.numRowsProcessed = 0; + this.totalRows = targetRectangle.Height; + + this.Apply(target, source, targetRectangle, sourceRectangle, targetRectangle.Y, targetRectangle.Bottom); + + this.AfterApply(target, source, target.Bounds, sourceRectangle); + } + catch (Exception ex) + { + throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); + } + } + + /// + /// This method is called before the process is applied to prepare the processor. + /// + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. + /// + /// + /// The structure that specifies the portion of the image object to draw. + /// + protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + where TPackedVector : IPackedVector + { + } + + /// + /// Applies the process to the specified portion of the specified at the specified location + /// and with the specified size. + /// + /// The type of pixels contained within the image. + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. + /// + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The index of the row within the source image to start processing. + /// The index of the row within the source image to end processing. + /// + /// The method keeps the source image unchanged and returns the + /// the result of image process as new image. + /// + protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) where TPackedVector : IPackedVector; + + /// + /// This method is called after the process is applied to prepare the processor. + /// + /// The type of pixels contained within the image. + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. + /// + /// + /// The structure that specifies the portion of the image object to draw. + /// + protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + where TPackedVector : IPackedVector + { + } + + /// + /// Must be called by derived classes after processing a single row. + /// + protected void OnRowProcessed() + { + if (this.OnProgress != null) + { + int currThreadNumRows = Interlocked.Add(ref this.numRowsProcessed, 1); + + // Multi-pass filters process multiple times more rows than totalRows, so update totalRows on the fly + if (currThreadNumRows > this.totalRows) + { + this.totalRows = currThreadNumRows; + } + + // Report progress. This may be on the client's thread, or on a Task library thread. + this.OnProgress(this, new ProgressEventArgs { RowsProcessed = currThreadNumRows, TotalRows = this.totalRows }); + } + } + } +} diff --git a/src/ImageProcessorCore - Copy/ImageProcessorCore.xproj b/src/ImageProcessorCore - Copy/ImageProcessorCore.xproj new file mode 100644 index 0000000000..ffe5b1cea0 --- /dev/null +++ b/src/ImageProcessorCore - Copy/ImageProcessorCore.xproj @@ -0,0 +1,22 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 2aa31a1f-142c-43f4-8687-09abca4b3a26 + ImageProcessorCore + .\obj + .\bin\ + v4.5.1 + + + 2.0 + + + True + + + \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/ImageProperty.cs b/src/ImageProcessorCore - Copy/ImageProperty.cs new file mode 100644 index 0000000000..1d8f5bb8ef --- /dev/null +++ b/src/ImageProcessorCore - Copy/ImageProperty.cs @@ -0,0 +1,153 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +// +// Stores meta information about a image, like the name of the author, +// the copyright information, the date, where the image was created +// or some other information. +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessorCore +{ + using System; + + /// + /// Stores meta information about a image, like the name of the author, + /// the copyright information, the date, where the image was created + /// or some other information. + /// + public struct ImageProperty : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// + /// The name of the property. + /// + /// + /// The value of the property. + /// + public ImageProperty(string name, string value) + { + this.Name = name; + this.Value = value; + } + + /// + /// Gets the name of this indicating which kind of + /// information this property stores. + /// + /// + /// Typical properties are the author, copyright + /// information or other meta information. + /// + public string Name { get; } + + /// + /// The value of this . + /// + public string Value { get; } + + /// + /// Compares two objects. The result specifies whether the values + /// of the or properties of the two + /// objects are equal. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(ImageProperty left, ImageProperty right) + { + return left.Equals(right); + } + + /// + /// Compares two objects. The result specifies whether the values + /// of the or properties of the two + /// objects are unequal. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(ImageProperty left, ImageProperty right) + { + return !left.Equals(right); + } + + /// + /// Indicates whether this instance and a specified object are equal. + /// + /// + /// The object to compare with the current instance. + /// + /// + /// true if and this instance are the same type and represent the + /// same value; otherwise, false. + /// + public override bool Equals(object obj) + { + if (!(obj is ImageProperty)) + { + return false; + } + + ImageProperty other = (ImageProperty)obj; + + return other.Name == this.Name && other.Value == this.Value; + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = this.Name.GetHashCode(); + hashCode = (hashCode * 397) ^ this.Value.GetHashCode(); + return hashCode; + } + } + + /// + /// Returns the fully qualified type name of this instance. + /// + /// + /// A containing a fully qualified type name. + /// + public override string ToString() + { + return $"ImageProperty [ Name={this.Name}, Value={this.Value} ]"; + } + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// + /// True if the current object is equal to the parameter; otherwise, false. + /// + /// An object to compare with this object. + public bool Equals(ImageProperty other) + { + return this.Name.Equals(other.Name) && this.Value.Equals(other.Value); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Numerics/Ellipse.cs b/src/ImageProcessorCore - Copy/Numerics/Ellipse.cs new file mode 100644 index 0000000000..5d87d1247b --- /dev/null +++ b/src/ImageProcessorCore - Copy/Numerics/Ellipse.cs @@ -0,0 +1,174 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.ComponentModel; + using System.Numerics; + + public struct Ellipse : IEquatable + { + /// + /// The center point. + /// + private Point center; + + /// + /// Represents a that has X and Y values set to zero. + /// + public static readonly Ellipse Empty = default(Ellipse); + + public Ellipse(Point center, float radiusX, float radiusY) + { + this.center = center; + this.RadiusX = radiusX; + this.RadiusY = radiusY; + } + + /// + /// Gets the x-radius of this . + /// + public float RadiusX { get; } + + /// + /// Gets the y-radius of this . + /// + public float RadiusY { get; } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Ellipse left, Ellipse right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(Ellipse left, Ellipse right) + { + return !left.Equals(right); + } + + /// + /// Returns the center point of the given + /// + /// The ellipse + /// + public static Vector2 Center(Ellipse ellipse) + { + return new Vector2(ellipse.center.X, ellipse.center.Y); + } + + /// + /// Determines if the specfied point is contained within the rectangular region defined by + /// this . + /// + /// The x-coordinate of the given point. + /// The y-coordinate of the given point. + /// The + public bool Contains(int x, int y) + { + if (this.RadiusX <= 0 || this.RadiusY <= 0) + { + return false; + } + + // TODO: SIMD? + // This is a more general form of the circle equation + // X^2/a^2 + Y^2/b^2 <= 1 + Point normalized = new Point(x - this.center.X, y - this.center.Y); + int nX = normalized.X; + int nY = normalized.Y; + + return (double)(nX * nX) / (this.RadiusX * this.RadiusX) + + (double)(nY * nY) / (this.RadiusY * this.RadiusY) + <= 1.0; + } + + /// + public override int GetHashCode() + { + return this.GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Ellipse [ Empty ]"; + } + + return + $"Ellipse [ RadiusX={this.RadiusX}, RadiusY={this.RadiusX}, Centre={this.center.X},{this.center.Y} ]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is Ellipse) + { + return this.Equals((Ellipse)obj); + } + + return false; + } + + /// + public bool Equals(Ellipse other) + { + return this.center.Equals(other.center) + && this.RadiusX.Equals(other.RadiusX) + && this.RadiusY.Equals(other.RadiusY); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private int GetHashCode(Ellipse ellipse) + { + unchecked + { + int hashCode = ellipse.center.GetHashCode(); + hashCode = (hashCode * 397) ^ ellipse.RadiusX.GetHashCode(); + hashCode = (hashCode * 397) ^ ellipse.RadiusY.GetHashCode(); + return hashCode; + } + } + } +} diff --git a/src/ImageProcessorCore - Copy/Numerics/Point.cs b/src/ImageProcessorCore - Copy/Numerics/Point.cs new file mode 100644 index 0000000000..818002f9ef --- /dev/null +++ b/src/ImageProcessorCore - Copy/Numerics/Point.cs @@ -0,0 +1,281 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageProcessorCore +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Represents an ordered pair of integer x- and y-coordinates that defines a point in + /// a two-dimensional plane. + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + public struct Point : IEquatable + { + /// + /// Represents a that has X and Y values set to zero. + /// + public static readonly Point Empty = default(Point); + + /// + /// The backing vector for SIMD support. + /// + private Vector2 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The horizontal position of the point. + /// The vertical position of the point. + public Point(int x, int y) + : this() + { + this.backingVector = new Vector2(x, y); + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector representing the width and height. + /// + public Point(Vector2 vector) + { + this.backingVector = new Vector2(vector.X, vector.Y); + } + + /// + /// The x-coordinate of this . + /// + public int X + { + get + { + return (int)this.backingVector.X; + } + + set + { + this.backingVector.X = value; + } + } + + /// + /// The y-coordinate of this . + /// + public int Y + { + get + { + return (int)this.backingVector.Y; + } + + set + { + this.backingVector.Y = value; + } + } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Computes the sum of adding two points. + /// + /// The point on the left hand of the operand. + /// The point on the right hand of the operand. + /// + /// The + /// + public static Point operator +(Point left, Point right) + { + return new Point(left.backingVector + right.backingVector); + } + + /// + /// Computes the difference left by subtracting one point from another. + /// + /// The point on the left hand of the operand. + /// The point on the right hand of the operand. + /// + /// The + /// + public static Point operator -(Point left, Point right) + { + return new Point(left.backingVector - right.backingVector); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Point left, Point right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(Point left, Point right) + { + return !left.Equals(right); + } + + /// + /// Gets a representation for this . + /// + /// A representation for this object. + public Vector2 ToVector2() + { + return new Vector2(this.X, this.Y); + } + + /// + /// Creates a rotation matrix for the given point and angle. + /// + /// The origin point to rotate around + /// Rotation in degrees + /// The rotation + public static Matrix3x2 CreateRotation(Point origin, float degrees) + { + float radians = ImageMaths.DegreesToRadians(degrees); + return Matrix3x2.CreateRotation(radians, origin.backingVector); + } + + /// + /// Rotates a point around a given a rotation matrix. + /// + /// The point to rotate + /// Rotation matrix used + /// The rotated + public static Point Rotate(Point point, Matrix3x2 rotation) + { + return new Point(Vector2.Transform(point.backingVector, rotation)); + } + + /// + /// Rotates a point around a given origin by the specified angle in degrees. + /// + /// The point to rotate + /// The center point to rotate around. + /// The angle in degrees. + /// The rotated + public static Point Rotate(Point point, Point origin, float degrees) + { + return new Point(Vector2.Transform(point.backingVector, CreateRotation(origin, degrees))); + } + + /// + /// Creates a skew matrix for the given point and angle. + /// + /// The origin point to rotate around + /// The x-angle in degrees. + /// The y-angle in degrees. + /// The rotation + public static Matrix3x2 CreateSkew(Point origin, float degreesX, float degreesY) + { + float radiansX = ImageMaths.DegreesToRadians(degreesX); + float radiansY = ImageMaths.DegreesToRadians(degreesY); + return Matrix3x2.CreateSkew(radiansX, radiansY, origin.backingVector); + } + + /// + /// Skews a point using a given a skew matrix. + /// + /// The point to rotate + /// Rotation matrix used + /// The rotated + public static Point Skew(Point point, Matrix3x2 skew) + { + return new Point(Vector2.Transform(point.backingVector, skew)); + } + + /// + /// Skews a point around a given origin by the specified angles in degrees. + /// + /// The point to skew. + /// The center point to rotate around. + /// The x-angle in degrees. + /// The y-angle in degrees. + /// The skewed + public static Point Skew(Point point, Point origin, float degreesX, float degreesY) + { + return new Point(Vector2.Transform(point.backingVector, CreateSkew(origin, degreesX, degreesY))); + } + + /// + public override int GetHashCode() + { + return this.GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Point [ Empty ]"; + } + + return $"Point [ X={this.X}, Y={this.Y} ]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is Point) + { + return this.Equals((Point)obj); + } + + return false; + } + + /// + public bool Equals(Point other) + { + return this.backingVector.Equals(other.backingVector); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private int GetHashCode(Point point) + { + return point.backingVector.GetHashCode(); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Numerics/Rectangle.cs b/src/ImageProcessorCore - Copy/Numerics/Rectangle.cs new file mode 100644 index 0000000000..6fe2c3a497 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Numerics/Rectangle.cs @@ -0,0 +1,291 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Stores a set of four integers that represent the location and size of a rectangle. + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + public struct Rectangle : IEquatable + { + /// + /// Represents a that has X, Y, Width, and Height values set to zero. + /// + public static readonly Rectangle Empty = default(Rectangle); + + /// + /// The backing vector for SIMD support. + /// + private Vector4 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The horizontal position of the rectangle. + /// The vertical position of the rectangle. + /// The width of the rectangle. + /// The height of the rectangle. + public Rectangle(int x, int y, int width, int height) + { + this.backingVector = new Vector4(x, y, width, height); + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The which specifies the rectangles point in a two-dimensional plane. + /// + /// + /// The which specifies the rectangles height and width. + /// + public Rectangle(Point point, Size size) + { + this.backingVector = new Vector4(point.X, point.Y, size.Width, size.Height); + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector. + public Rectangle(Vector4 vector) + { + this.backingVector = vector; + } + + /// + /// The x-coordinate of this . + /// + public int X + { + get + { + return (int)this.backingVector.X; + } + + set + { + this.backingVector.X = value; + } + } + + /// + /// The y-coordinate of this . + /// + public int Y + { + get + { + return (int)this.backingVector.Y; + } + + set + { + this.backingVector.Y = value; + } + } + + /// + /// The width of this . + /// + public int Width + { + get + { + return (int)this.backingVector.Z; + } + + set + { + this.backingVector.Z = value; + } + } + + /// + /// The height of this . + /// + public int Height + { + get + { + return (int)this.backingVector.W; + } + + set + { + this.backingVector.W = value; + } + } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Gets the y-coordinate of the top edge of this . + /// + public int Top => this.Y; + + /// + /// Gets the x-coordinate of the right edge of this . + /// + public int Right => this.X + this.Width; + + /// + /// Gets the y-coordinate of the bottom edge of this . + /// + public int Bottom => this.Y + this.Height; + + /// + /// Gets the x-coordinate of the left edge of this . + /// + public int Left => this.X; + + /// + /// Computes the sum of adding two rectangles. + /// + /// The rectangle on the left hand of the operand. + /// The rectangle on the right hand of the operand. + /// + /// The + /// + public static Rectangle operator +(Rectangle left, Rectangle right) + { + return new Rectangle(left.backingVector + right.backingVector); + } + + /// + /// Computes the difference left by subtracting one rectangle from another. + /// + /// The rectangle on the left hand of the operand. + /// The rectangle on the right hand of the operand. + /// + /// The + /// + public static Rectangle operator -(Rectangle left, Rectangle right) + { + return new Rectangle(left.backingVector - right.backingVector); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Rectangle left, Rectangle right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(Rectangle left, Rectangle right) + { + return !left.Equals(right); + } + + /// + /// Determines if the specfied point is contained within the rectangular region defined by + /// this . + /// + /// The x-coordinate of the given point. + /// The y-coordinate of the given point. + /// The + public bool Contains(int x, int y) + { + // TODO: SIMD? + return this.X <= x + && x < this.Right + && this.Y <= y + && y < this.Bottom; + } + + /// + /// Returns the center point of the given + /// + /// The rectangle + /// + public static Point Center(Rectangle rectangle) + { + return new Point(rectangle.Left + rectangle.Width / 2, rectangle.Top + rectangle.Height / 2); + } + + /// + public override int GetHashCode() + { + return this.GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Rectangle [ Empty ]"; + } + + return + $"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is Rectangle) + { + return this.Equals((Rectangle)obj); + } + + return false; + } + + /// + public bool Equals(Rectangle other) + { + return this.backingVector.Equals(other.backingVector); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private int GetHashCode(Rectangle rectangle) + { + return rectangle.backingVector.GetHashCode(); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Numerics/Size.cs b/src/ImageProcessorCore - Copy/Numerics/Size.cs new file mode 100644 index 0000000000..4b416b2182 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Numerics/Size.cs @@ -0,0 +1,208 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Stores an ordered pair of integers, which specify a height and width. + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + public struct Size : IEquatable + { + /// + /// Represents a that has Width and Height values set to zero. + /// + public static readonly Size Empty = default(Size); + + /// + /// The backing vector for SIMD support. + /// + private Vector2 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The width of the size. + /// The height of the size. + public Size(int width, int height) + { + this.backingVector = new Vector2(width, height); + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector representing the width and height. + /// + public Size(Vector2 vector) + { + this.backingVector = new Vector2(vector.X, vector.Y); + } + + /// + /// The width of this . + /// + public int Width + { + get + { + return (int)this.backingVector.X; + } + + set + { + this.backingVector.X = value; + } + } + + /// + /// The height of this . + /// + public int Height + { + get + { + return (int)this.backingVector.Y; + } + + set + { + this.backingVector.Y = value; + } + } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Computes the sum of adding two sizes. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The + /// + public static Size operator +(Size left, Size right) + { + return new Size(left.backingVector + right.backingVector); + } + + /// + /// Computes the difference left by subtracting one size from another. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The + /// + public static Size operator -(Size left, Size right) + { + return new Size(left.backingVector - right.backingVector); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Size left, Size right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(Size left, Size right) + { + return !left.Equals(right); + } + + /// + /// Gets a representation for this . + /// + /// A representation for this object. + public Vector2 ToVector2() + { + return new Vector2(this.Width, this.Height); + } + + /// + public override int GetHashCode() + { + return this.GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Size [ Empty ]"; + } + + return + $"Size [ Width={this.Width}, Height={this.Height} ]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is Size) + { + return this.Equals((Size)obj); + } + + return false; + } + + /// + public bool Equals(Size other) + { + return this.backingVector.Equals(other.backingVector); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private int GetHashCode(Size size) + { + return size.backingVector.GetHashCode(); + } + } +} diff --git a/src/ImageProcessorCore - Copy/PackedVector/Bgra32.cs b/src/ImageProcessorCore - Copy/PackedVector/Bgra32.cs new file mode 100644 index 0000000000..fc6a788b08 --- /dev/null +++ b/src/ImageProcessorCore - Copy/PackedVector/Bgra32.cs @@ -0,0 +1,168 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.Numerics; + + /// + /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 1. + /// + public struct Bgra32 : IPackedVector, IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// The blue component. + /// The green component. + /// The red component. + /// The alpha component. + public Bgra32(float b, float g, float r, float a) + { + Vector4 clamped = Vector4.Clamp(new Vector4(b, g, r, a), Vector4.Zero, Vector4.One) * 255f; + this.PackedValue = Pack(ref clamped); + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + public Bgra32(Vector4 vector) + { + Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; + this.PackedValue = Pack(ref clamped); + } + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Bgra32 left, Bgra32 right) + { + return left.PackedValue == right.PackedValue; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator !=(Bgra32 left, Bgra32 right) + { + return left.PackedValue != right.PackedValue; + } + + /// + public void PackVector(Vector4 vector) + { + Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; + this.PackedValue = Pack(ref clamped); + } + + /// + public void PackBytes(byte x, byte y, byte z, byte w) + { + Vector4 vector = new Vector4(x, y, z, w); + this.PackedValue = Pack(ref vector); + } + + /// + public Vector4 ToVector4() + { + return new Vector4( + this.PackedValue & 0xFF, + (this.PackedValue >> 8) & 0xFF, + (this.PackedValue >> 16) & 0xFF, + (this.PackedValue >> 24) & 0xFF) / 255f; + } + + /// + public byte[] ToBytes() + { + return new[] + { + (byte)(this.PackedValue & 0xFF), + (byte)((this.PackedValue >> 8) & 0xFF), + (byte)((this.PackedValue >> 16) & 0xFF), + (byte)((this.PackedValue >> 24) & 0xFF) + }; + } + + /// + public override bool Equals(object obj) + { + return (obj is Bgra32) && this.Equals((Bgra32)obj); + } + + /// + public bool Equals(Bgra32 other) + { + return this.PackedValue == other.PackedValue; + } + + /// + /// Gets a string representation of the packed vector. + /// + /// A string representation of the packed vector. + public override string ToString() + { + return this.ToVector4().ToString(); + } + + /// + public override int GetHashCode() + { + return this.GetHashCode(this); + } + + /// + /// Sets the packed representation from the given component values. + /// + /// + /// The vector containing the components for the packed vector. + /// + /// + /// The . + /// + private static uint Pack(ref Vector4 vector) + { + return (uint)Math.Round(vector.X) | + ((uint)Math.Round(vector.Y) << 8) | + ((uint)Math.Round(vector.Z) << 16) | + ((uint)Math.Round(vector.W) << 24); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private int GetHashCode(Bgra32 packed) + { + return packed.PackedValue.GetHashCode(); + } + } +} diff --git a/src/ImageProcessorCore - Copy/PackedVector/IPackedVector.cs b/src/ImageProcessorCore - Copy/PackedVector/IPackedVector.cs new file mode 100644 index 0000000000..02e10cc3a5 --- /dev/null +++ b/src/ImageProcessorCore - Copy/PackedVector/IPackedVector.cs @@ -0,0 +1,61 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System.Numerics; + + /// + /// An interface that converts packed vector types to and from values, + /// allowing multiple encodings to be manipulated in a generic way. + /// + /// + /// The type of object representing the packed value. + /// + public interface IPackedVector : IPackedVector + where TPacked : struct + { + /// + /// Gets or sets the packed representation of the value. + /// Typically packed in least to greatest significance order. + /// + TPacked PackedValue { get; set; } + } + + /// + /// An interface that converts packed vector types to and from values. + /// + public interface IPackedVector + { + /// + /// Sets the packed representation from a . + /// + /// The vector to pack. + void PackVector(Vector4 vector); + + /// + /// Sets the packed representation from a . + /// + /// The x-component. + /// The y-component. + /// The z-component. + /// The w-component. + void PackBytes(byte x, byte y, byte z, byte w); + + /// + /// Expands the packed representation into a . + /// The vector components are typically expanded in least to greatest significance order. + /// + /// The . + Vector4 ToVector4(); + + /// + /// Expands the packed representation into a . + /// The bytes are typically expanded in least to greatest significance order. + /// + /// The . + byte[] ToBytes(); + } +} diff --git a/src/ImageProcessorCore - Copy/PixelAccessor/Bgra32PixelAccessor.cs b/src/ImageProcessorCore - Copy/PixelAccessor/Bgra32PixelAccessor.cs new file mode 100644 index 0000000000..cecc148b93 --- /dev/null +++ b/src/ImageProcessorCore - Copy/PixelAccessor/Bgra32PixelAccessor.cs @@ -0,0 +1,155 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.Runtime.InteropServices; + + /// + /// Provides per-pixel access to an images pixels. + /// + /// + /// The image data is always stored in format, where the blue, green, red, and + /// alpha values are 8 bit unsigned bytes. + /// + public sealed unsafe class Bgra32PixelAccessor : IPixelAccessor + { + /// + /// The position of the first pixel in the bitmap. + /// + private Bgra32* pixelsBase; + + /// + /// Provides a way to access the pixels from unmanaged memory. + /// + private GCHandle pixelsHandle; + + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// If the entity is disposed, it must not be disposed a second + /// time. The isDisposed field is set the first time the entity + /// is disposed. If the isDisposed field is true, then the Dispose() + /// method will not dispose again. This help not to prolong the entity's + /// life in the Garbage Collector. + /// + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The image to provide pixel access for. + /// + public Bgra32PixelAccessor(IImageBase image) + { + Guard.NotNull(image, nameof(image)); + Guard.MustBeGreaterThan(image.Width, 0, "image width"); + Guard.MustBeGreaterThan(image.Height, 0, "image height"); + + this.Width = image.Width; + this.Height = image.Height; + + this.pixelsHandle = GCHandle.Alloc(image.Pixels, GCHandleType.Pinned); + this.pixelsBase = (Bgra32*)this.pixelsHandle.AddrOfPinnedObject().ToPointer(); + } + + /// + /// Finalizes an instance of the class. + /// + ~Bgra32PixelAccessor() + { + this.Dispose(); + } + + /// + /// Gets the width of the image. + /// + public int Width { get; } + + /// + /// Gets the height of the image. + /// + public int Height { get; } + + /// + /// Gets or sets the color of a pixel at the specified position. + /// + /// + /// The x-coordinate of the pixel. Must be greater + /// than zero and smaller than the width of the pixel. + /// + /// + /// The y-coordinate of the pixel. Must be greater + /// than zero and smaller than the width of the pixel. + /// + /// The at the specified position. + public IPackedVector this[int x, int y] + { + get + { +#if DEBUG + if ((x < 0) || (x >= this.Width)) + { + throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width."); + } + + if ((y < 0) || (y >= this.Height)) + { + throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); + } +#endif + return *(this.pixelsBase + ((y * this.Width) + x)); + } + + set + { +#if DEBUG + if ((x < 0) || (x >= this.Width)) + { + throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width."); + } + + if ((y < 0) || (y >= this.Height)) + { + throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); + } +#endif + *(this.pixelsBase + ((y * this.Width) + x)) = (Bgra32)value; + } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + if (this.pixelsHandle.IsAllocated) + { + this.pixelsHandle.Free(); + } + + this.pixelsBase = null; + + // Note disposing is done. + this.isDisposed = true; + + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SuppressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + } +} diff --git a/src/ImageProcessorCore - Copy/PixelAccessor/IPixelAccessor.cs b/src/ImageProcessorCore - Copy/PixelAccessor/IPixelAccessor.cs new file mode 100644 index 0000000000..bf3a4067c4 --- /dev/null +++ b/src/ImageProcessorCore - Copy/PixelAccessor/IPixelAccessor.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + + /// + /// Encapsulates properties to provides per-pixel access to an images pixels. + /// + public interface IPixelAccessor : IDisposable + { + /// + /// Gets the width of the image in pixels. + /// + int Width { get; } + + /// + /// Gets the height of the image in pixels. + /// + int Height { get; } + + /// + /// Gets or sets the pixel at the specified position. + /// + /// + /// The x-coordinate of the pixel. Must be greater + /// than zero and smaller than the width of the pixel. + /// + /// + /// The y-coordinate of the pixel. Must be greater + /// than zero and smaller than the width of the pixel. + /// + /// The at the specified position. + IPackedVector this[int x, int y] + { + get; + set; + } + } +} diff --git a/src/ImageProcessorCore - Copy/ProgressEventArgs.cs b/src/ImageProcessorCore - Copy/ProgressEventArgs.cs new file mode 100644 index 0000000000..0f4c027f5e --- /dev/null +++ b/src/ImageProcessorCore - Copy/ProgressEventArgs.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// Contains event data related to the progress made processing an image. + /// + public class ProgressEventArgs : System.EventArgs + { + /// + /// Gets or sets the number of rows processed. + /// + public int RowsProcessed { get; set; } + + /// + /// Gets or sets the total number of rows. + /// + public int TotalRows { get; set; } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/Properties/AssemblyInfo.cs b/src/ImageProcessorCore - Copy/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..ea81ff60c6 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Properties/AssemblyInfo.cs @@ -0,0 +1,34 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ImageProcessorCore")] +[assembly: AssemblyDescription("A cross-platform library for processing of image files written in C#")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("James Jackson-South")] +[assembly: AssemblyProduct("ImageProcessorCore")] +[assembly: AssemblyCopyright("Copyright (c) James Jackson-South and contributors.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +// Ensure the internals can be tested. +[assembly: InternalsVisibleTo("ImageProcessorCore.Benchmarks")] +[assembly: InternalsVisibleTo("ImageProcessorCore.Tests")] diff --git a/src/ImageProcessorCore/Quantizers/IQuantizer.cs b/src/ImageProcessorCore - Copy/Quantizers/IQuantizer.cs similarity index 100% rename from src/ImageProcessorCore/Quantizers/IQuantizer.cs rename to src/ImageProcessorCore - Copy/Quantizers/IQuantizer.cs diff --git a/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageProcessorCore - Copy/Quantizers/Octree/OctreeQuantizer.cs similarity index 100% rename from src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs rename to src/ImageProcessorCore - Copy/Quantizers/Octree/OctreeQuantizer.cs diff --git a/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs b/src/ImageProcessorCore - Copy/Quantizers/Octree/Quantizer.cs similarity index 100% rename from src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs rename to src/ImageProcessorCore - Copy/Quantizers/Octree/Quantizer.cs diff --git a/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs b/src/ImageProcessorCore - Copy/Quantizers/Palette/PaletteQuantizer.cs similarity index 100% rename from src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs rename to src/ImageProcessorCore - Copy/Quantizers/Palette/PaletteQuantizer.cs diff --git a/src/ImageProcessorCore/Quantizers/QuantizedImage.cs b/src/ImageProcessorCore - Copy/Quantizers/QuantizedImage.cs similarity index 100% rename from src/ImageProcessorCore/Quantizers/QuantizedImage.cs rename to src/ImageProcessorCore - Copy/Quantizers/QuantizedImage.cs diff --git a/src/ImageProcessorCore/Quantizers/Wu/Box.cs b/src/ImageProcessorCore - Copy/Quantizers/Wu/Box.cs similarity index 100% rename from src/ImageProcessorCore/Quantizers/Wu/Box.cs rename to src/ImageProcessorCore - Copy/Quantizers/Wu/Box.cs diff --git a/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs b/src/ImageProcessorCore - Copy/Quantizers/Wu/WuQuantizer.cs similarity index 100% rename from src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs rename to src/ImageProcessorCore - Copy/Quantizers/Wu/WuQuantizer.cs diff --git a/src/ImageProcessorCore/Samplers/Crop.cs b/src/ImageProcessorCore - Copy/Samplers/Crop.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/Crop.cs rename to src/ImageProcessorCore - Copy/Samplers/Crop.cs diff --git a/src/ImageProcessorCore/Samplers/EntropyCrop.cs b/src/ImageProcessorCore - Copy/Samplers/EntropyCrop.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/EntropyCrop.cs rename to src/ImageProcessorCore - Copy/Samplers/EntropyCrop.cs diff --git a/src/ImageProcessorCore - Copy/Samplers/Options/AnchorPosition.cs b/src/ImageProcessorCore - Copy/Samplers/Options/AnchorPosition.cs new file mode 100644 index 0000000000..af840b292a --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Options/AnchorPosition.cs @@ -0,0 +1,58 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// Enumerated anchor positions to apply to resized images. + /// + public enum AnchorPosition + { + /// + /// Anchors the position of the image to the center of it's bounding container. + /// + Center, + + /// + /// Anchors the position of the image to the top of it's bounding container. + /// + Top, + + /// + /// Anchors the position of the image to the bottom of it's bounding container. + /// + Bottom, + + /// + /// Anchors the position of the image to the left of it's bounding container. + /// + Left, + + /// + /// Anchors the position of the image to the right of it's bounding container. + /// + Right, + + /// + /// Anchors the position of the image to the top left side of it's bounding container. + /// + TopLeft, + + /// + /// Anchors the position of the image to the top right side of it's bounding container. + /// + TopRight, + + /// + /// Anchors the position of the image to the bottom right side of it's bounding container. + /// + BottomRight, + + /// + /// Anchors the position of the image to the bottom left side of it's bounding container. + /// + BottomLeft + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Options/FlipType.cs b/src/ImageProcessorCore - Copy/Samplers/Options/FlipType.cs new file mode 100644 index 0000000000..c9b21a37e5 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Options/FlipType.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// Provides enumeration over how a image should be flipped. + /// + public enum FlipType + { + /// + /// Don't flip the image. + /// + None, + + /// + /// Flip the image horizontally. + /// + Horizontal, + + /// + /// Flip the image vertically. + /// + Vertical, + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Options/ResizeHelper.cs b/src/ImageProcessorCore - Copy/Samplers/Options/ResizeHelper.cs new file mode 100644 index 0000000000..a80ea47778 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Options/ResizeHelper.cs @@ -0,0 +1,430 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.Linq; + + /// + /// Provides methods to help calculate the target rectangle when resizing using the + /// enumeration. + /// + internal static class ResizeHelper + { + /// + /// Calculates the target location and bounds to perform the resize operation against. + /// + /// The source image. + /// The resize options. + /// + /// The . + /// + public static Rectangle CalculateTargetLocationAndBounds(ImageBase source, ResizeOptions options) + { + switch (options.Mode) + { + case ResizeMode.Crop: + return CalculateCropRectangle(source, options); + case ResizeMode.Pad: + return CalculatePadRectangle(source, options); + case ResizeMode.BoxPad: + return CalculateBoxPadRectangle(source, options); + case ResizeMode.Max: + return CalculateMaxRectangle(source, options); + case ResizeMode.Min: + return CalculateMinRectangle(source, options); + + // Last case ResizeMode.Stretch: + default: + return CalculateStretchRectangle(source, options); + } + } + + /// + /// Calculates the target rectangle for crop mode. + /// + /// The source image. + /// The resize options. + /// + /// The . + /// + private static Rectangle CalculateCropRectangle(ImageBase source, ResizeOptions options) + { + int width = options.Size.Width; + int height = options.Size.Height; + + if (width <= 0 || height <= 0) + { + return new Rectangle(0, 0, source.Width, source.Height); + } + + double ratio; + int sourceWidth = source.Width; + int sourceHeight = source.Height; + + int destinationX = 0; + int destinationY = 0; + int destinationWidth = width; + int destinationHeight = height; + + // Fractional variants for preserving aspect ratio. + double percentHeight = Math.Abs(height / (double)sourceHeight); + double percentWidth = Math.Abs(width / (double)sourceWidth); + + if (percentHeight < percentWidth) + { + ratio = percentWidth; + + if (options.CenterCoordinates.Any()) + { + double center = -(ratio * sourceHeight) * options.CenterCoordinates.First(); + destinationY = (int)center + (height / 2); + + if (destinationY > 0) + { + destinationY = 0; + } + + if (destinationY < (int)(height - (sourceHeight * ratio))) + { + destinationY = (int)(height - (sourceHeight * ratio)); + } + } + else + { + switch (options.Position) + { + case AnchorPosition.Top: + case AnchorPosition.TopLeft: + case AnchorPosition.TopRight: + destinationY = 0; + break; + case AnchorPosition.Bottom: + case AnchorPosition.BottomLeft: + case AnchorPosition.BottomRight: + destinationY = (int)(height - (sourceHeight * ratio)); + break; + default: + destinationY = (int)((height - (sourceHeight * ratio)) / 2); + break; + } + } + + destinationHeight = (int)Math.Ceiling(sourceHeight * percentWidth); + } + else + { + ratio = percentHeight; + + if (options.CenterCoordinates.Any()) + { + double center = -(ratio * sourceWidth) * options.CenterCoordinates.ToArray()[1]; + destinationX = (int)center + (width / 2); + + if (destinationX > 0) + { + destinationX = 0; + } + + if (destinationX < (int)(width - (sourceWidth * ratio))) + { + destinationX = (int)(width - (sourceWidth * ratio)); + } + } + else + { + switch (options.Position) + { + case AnchorPosition.Left: + case AnchorPosition.TopLeft: + case AnchorPosition.BottomLeft: + destinationX = 0; + break; + case AnchorPosition.Right: + case AnchorPosition.TopRight: + case AnchorPosition.BottomRight: + destinationX = (int)(width - (sourceWidth * ratio)); + break; + default: + destinationX = (int)((width - (sourceWidth * ratio)) / 2); + break; + } + } + + destinationWidth = (int)Math.Ceiling(sourceWidth * percentHeight); + } + + return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); + } + + /// + /// Calculates the target rectangle for pad mode. + /// + /// The source image. + /// The resize options. + /// + /// The . + /// + private static Rectangle CalculatePadRectangle(ImageBase source, ResizeOptions options) + { + int width = options.Size.Width; + int height = options.Size.Height; + + if (width <= 0 || height <= 0) + { + return new Rectangle(0, 0, source.Width, source.Height); + } + + double ratio; + int sourceWidth = source.Width; + int sourceHeight = source.Height; + + int destinationX = 0; + int destinationY = 0; + int destinationWidth = width; + int destinationHeight = height; + + // Fractional variants for preserving aspect ratio. + double percentHeight = Math.Abs(height / (double)sourceHeight); + double percentWidth = Math.Abs(width / (double)sourceWidth); + + if (percentHeight < percentWidth) + { + ratio = percentHeight; + destinationWidth = Convert.ToInt32(sourceWidth * percentHeight); + + switch (options.Position) + { + case AnchorPosition.Left: + case AnchorPosition.TopLeft: + case AnchorPosition.BottomLeft: + destinationX = 0; + break; + case AnchorPosition.Right: + case AnchorPosition.TopRight: + case AnchorPosition.BottomRight: + destinationX = (int)(width - (sourceWidth * ratio)); + break; + default: + destinationX = Convert.ToInt32((width - (sourceWidth * ratio)) / 2); + break; + } + } + else + { + ratio = percentWidth; + destinationHeight = Convert.ToInt32(sourceHeight * percentWidth); + + switch (options.Position) + { + case AnchorPosition.Top: + case AnchorPosition.TopLeft: + case AnchorPosition.TopRight: + destinationY = 0; + break; + case AnchorPosition.Bottom: + case AnchorPosition.BottomLeft: + case AnchorPosition.BottomRight: + destinationY = (int)(height - (sourceHeight * ratio)); + break; + default: + destinationY = (int)((height - (sourceHeight * ratio)) / 2); + break; + } + } + + return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); + } + + /// + /// Calculates the target rectangle for box pad mode. + /// + /// The source image. + /// The resize options. + /// + /// The . + /// + private static Rectangle CalculateBoxPadRectangle(ImageBase source, ResizeOptions options) + { + int width = options.Size.Width; + int height = options.Size.Height; + + if (width <= 0 || height <= 0) + { + return new Rectangle(0, 0, source.Width, source.Height); + } + + int sourceWidth = source.Width; + int sourceHeight = source.Height; + + // Fractional variants for preserving aspect ratio. + double percentHeight = Math.Abs(height / (double)sourceHeight); + double percentWidth = Math.Abs(width / (double)sourceWidth); + + int boxPadHeight = height > 0 ? height : Convert.ToInt32(sourceHeight * percentWidth); + int boxPadWidth = width > 0 ? width : Convert.ToInt32(sourceWidth * percentHeight); + + // Only calculate if upscaling. + if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight) + { + int destinationX; + int destinationY; + int destinationWidth = sourceWidth; + int destinationHeight = sourceHeight; + width = boxPadWidth; + height = boxPadHeight; + + switch (options.Position) + { + case AnchorPosition.Left: + destinationY = (height - sourceHeight) / 2; + destinationX = 0; + break; + case AnchorPosition.Right: + destinationY = (height - sourceHeight) / 2; + destinationX = width - sourceWidth; + break; + case AnchorPosition.TopRight: + destinationY = 0; + destinationX = width - sourceWidth; + break; + case AnchorPosition.Top: + destinationY = 0; + destinationX = (width - sourceWidth) / 2; + break; + case AnchorPosition.TopLeft: + destinationY = 0; + destinationX = 0; + break; + case AnchorPosition.BottomRight: + destinationY = height - sourceHeight; + destinationX = width - sourceWidth; + break; + case AnchorPosition.Bottom: + destinationY = height - sourceHeight; + destinationX = (width - sourceWidth) / 2; + break; + case AnchorPosition.BottomLeft: + destinationY = height - sourceHeight; + destinationX = 0; + break; + default: + destinationY = (height - sourceHeight) / 2; + destinationX = (width - sourceWidth) / 2; + break; + } + + return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); + } + + // Switch to pad mode to downscale and calculate from there. + return CalculatePadRectangle(source, options); + } + + /// + /// Calculates the target rectangle for max mode. + /// + /// The source image. + /// The resize options. + /// + /// The . + /// + private static Rectangle CalculateMaxRectangle(ImageBase source, ResizeOptions options) + { + int width = options.Size.Width; + int height = options.Size.Height; + int destinationWidth = width; + int destinationHeight = height; + + // Fractional variants for preserving aspect ratio. + double percentHeight = Math.Abs(height / (double)source.Height); + double percentWidth = Math.Abs(width / (double)source.Width); + + // Integers must be cast to doubles to get needed precision + double ratio = (double)options.Size.Height / options.Size.Width; + double sourceRatio = (double)source.Height / source.Width; + + if (sourceRatio < ratio) + { + destinationHeight = Convert.ToInt32(source.Height * percentWidth); + height = destinationHeight; + } + else + { + destinationWidth = Convert.ToInt32(source.Width * percentHeight); + width = destinationWidth; + } + + // Replace the size to match the rectangle. + options.Size = new Size(width, height); + return new Rectangle(0, 0, destinationWidth, destinationHeight); + } + + /// + /// Calculates the target rectangle for min mode. + /// + /// The source image. + /// The resize options. + /// + /// The . + /// + private static Rectangle CalculateMinRectangle(ImageBase source, ResizeOptions options) + { + int width = options.Size.Width; + int height = options.Size.Height; + int destinationWidth; + int destinationHeight; + + // Don't upscale + if (width > source.Width || height > source.Height) + { + options.Size = new Size(source.Width, source.Height); + return new Rectangle(0, 0, source.Width, source.Height); + } + + double sourceRatio = (double)source.Height / source.Width; + + // Find the shortest distance to go. + int widthDiff = source.Width - width; + int heightDiff = source.Height - height; + + if (widthDiff < heightDiff) + { + destinationHeight = Convert.ToInt32(width * sourceRatio); + height = destinationHeight; + destinationWidth = width; + } + else if (widthDiff > heightDiff) + { + destinationWidth = Convert.ToInt32(height / sourceRatio); + destinationHeight = height; + width = destinationWidth; + } + else + { + destinationWidth = width; + destinationHeight = height; + } + + // Replace the size to match the rectangle. + options.Size = new Size(width, height); + return new Rectangle(0, 0, destinationWidth, destinationHeight); + } + + /// + /// Calculates the target rectangle for stretch mode. + /// + /// The source image. + /// The resize options. + /// + /// The . + /// + private static Rectangle CalculateStretchRectangle(ImageBase source, ResizeOptions options) + { + return new Rectangle(0, 0, options.Size.Width, options.Size.Height); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Options/ResizeMode.cs b/src/ImageProcessorCore - Copy/Samplers/Options/ResizeMode.cs new file mode 100644 index 0000000000..a0ce943417 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Options/ResizeMode.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// Enumerated resize modes to apply to resized images. + /// + public enum ResizeMode + { + /// + /// Crops the resized image to fit the bounds of its container. + /// + Crop, + + /// + /// Pads the resized image to fit the bounds of its container. + /// If only one dimension is passed, will maintain the original aspect ratio. + /// + Pad, + + /// + /// Pads the image to fit the bound of the container without resizing the + /// original source. + /// When downscaling, performs the same functionality as + /// + BoxPad, + + /// + /// Constrains the resized image to fit the bounds of its container maintaining + /// the original aspect ratio. + /// + Max, + + /// + /// Resizes the image until the shortest side reaches the set given dimension. + /// Upscaling is disabled in this mode and the original image will be returned + /// if attempted. + /// + Min, + + /// + /// Stretches the resized image to fit the bounds of its container. + /// + Stretch + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Options/ResizeOptions.cs b/src/ImageProcessorCore - Copy/Samplers/Options/ResizeOptions.cs new file mode 100644 index 0000000000..82db8ed860 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Options/ResizeOptions.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System.Collections.Generic; + using System.Linq; + + /// + /// The resize options for resizing images against certain modes. + /// + public class ResizeOptions + { + /// + /// Gets or sets the resize mode. + /// + public ResizeMode Mode { get; set; } = ResizeMode.Crop; + + /// + /// Gets or sets the anchor position. + /// + public AnchorPosition Position { get; set; } = AnchorPosition.Center; + + /// + /// Gets or sets the center coordinates. + /// + public IEnumerable CenterCoordinates { get; set; } = Enumerable.Empty(); + + /// + /// Gets or sets the target size. + /// + public Size Size { get; set; } + + /// + /// Gets or sets the sampler to perform the resize operation. + /// + public IResampler Sampler { get; set; } = new BicubicResampler(); + + /// + /// Gets or sets a value indicating whether to compress + /// or expand individual pixel colors the value on processing. + /// + public bool Compand { get; set; } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Options/RotateType.cs b/src/ImageProcessorCore - Copy/Samplers/Options/RotateType.cs new file mode 100644 index 0000000000..43644de858 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Options/RotateType.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// Provides enumeration over how the image should be rotated. + /// + public enum RotateType + { + /// + /// Do not rotate the image. + /// + None, + + /// + /// Rotate the image by 90 degrees clockwise. + /// + Rotate90, + + /// + /// Rotate the image by 180 degrees clockwise. + /// + Rotate180, + + /// + /// Rotate the image by 270 degrees clockwise. + /// + Rotate270 + } +} diff --git a/src/ImageProcessorCore/Samplers/Pad.cs b/src/ImageProcessorCore - Copy/Samplers/Pad.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/Pad.cs rename to src/ImageProcessorCore - Copy/Samplers/Pad.cs diff --git a/src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/CropProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs rename to src/ImageProcessorCore - Copy/Samplers/Processors/CropProcessor.cs diff --git a/src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/EntropyCropProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs rename to src/ImageProcessorCore - Copy/Samplers/Processors/EntropyCropProcessor.cs diff --git a/src/ImageProcessorCore - Copy/Samplers/Processors/IImageSampler.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/IImageSampler.cs new file mode 100644 index 0000000000..76a2c5a4d4 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Processors/IImageSampler.cs @@ -0,0 +1,19 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// Acts as a marker for generic parameters that require an image sampler. + /// + public interface IImageSampler : IImageProcessor + { + /// + /// Gets or sets a value indicating whether to compress + /// or expand individual pixel colors the value on processing. + /// + bool Compand { get; set; } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Processors/ImageSampler.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/ImageSampler.cs new file mode 100644 index 0000000000..adfe77432f --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Processors/ImageSampler.cs @@ -0,0 +1,17 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// Applies sampling methods to an image. + /// All processors requiring resampling or resizing should inherit from this. + /// + public abstract class ImageSampler : ImageProcessor, IImageSampler + { + /// + public virtual bool Compand { get; set; } = false; + } +} diff --git a/src/ImageProcessorCore/Samplers/Processors/Matrix3x2Processor.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/Matrix3x2Processor.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/Processors/Matrix3x2Processor.cs rename to src/ImageProcessorCore - Copy/Samplers/Processors/Matrix3x2Processor.cs diff --git a/src/ImageProcessorCore - Copy/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/ResizeProcessor.cs new file mode 100644 index 0000000000..95ead2bf04 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Processors/ResizeProcessor.cs @@ -0,0 +1,362 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System; + using System.Threading.Tasks; + + /// + /// Provides methods that allow the resizing of images using various algorithms. + /// + public class ResizeProcessor : ImageSampler + { + /// + /// The image used for storing the first pass pixels. + /// + private Image firstPass; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The sampler to perform the resize operation. + /// + public ResizeProcessor(IResampler sampler) + { + Guard.NotNull(sampler, nameof(sampler)); + + this.Sampler = sampler; + } + + /// + /// Gets the sampler to perform the resize operation. + /// + public IResampler Sampler { get; } + + /// + /// Gets or sets the horizontal weights. + /// + protected Weights[] HorizontalWeights { get; set; } + + /// + /// Gets or sets the vertical weights. + /// + protected Weights[] VerticalWeights { get; set; } + + /// + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + if (!(this.Sampler is NearestNeighborResampler)) + { + this.HorizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width); + this.VerticalWeights = this.PrecomputeWeights(targetRectangle.Height, sourceRectangle.Height); + } + + this.firstPass = new Image(target.Width, source.Height); + } + + /// + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + // Jump out, we'll deal with that later. + if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) + { + return; + } + + int width = target.Width; + int height = target.Height; + int sourceHeight = sourceRectangle.Height; + int targetX = target.Bounds.X; + int targetY = target.Bounds.Y; + int targetRight = target.Bounds.Right; + int targetBottom = target.Bounds.Bottom; + int startX = targetRectangle.X; + int endX = targetRectangle.Right; + bool compand = this.Compand; + + if (this.Sampler is NearestNeighborResampler) + { + // Scaling factors + float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width; + float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height; + + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => + { + if (targetY <= y && y < targetBottom) + { + // Y coordinates of source points + int originY = (int)((y - startY) * heightFactor); + + for (int x = startX; x < endX; x++) + { + if (targetX <= x && x < targetRight) + { + // X coordinates of source points + int originX = (int)((x - startX) * widthFactor); + + targetPixels[x, y] = sourcePixels[originX, originY]; + } + } + + this.OnRowProcessed(); + } + }); + } + + // Break out now. + return; + } + + // Interpolate the image using the calculated weights. + // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm + // First process the columns. Since we are not using multiple threads startY and endY + // are the upper and lower bounds of the source rectangle. + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor firstPassPixels = this.firstPass.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + 0, + sourceHeight, + y => + { + for (int x = startX; x < endX; x++) + { + if (x >= 0 && x < width) + { + // Ensure offsets are normalised for cropping and padding. + int offsetX = x - startX; + float sum = this.HorizontalWeights[offsetX].Sum; + Weight[] horizontalValues = this.HorizontalWeights[offsetX].Values; + + // Destination color components + Color destination = new Color(); + + for (int i = 0; i < sum; i++) + { + Weight xw = horizontalValues[i]; + int originX = xw.Index; + Color sourceColor = compand + ? Color.Expand(sourcePixels[originX, y]) + : sourcePixels[originX, y]; + + destination += sourceColor * xw.Value; + } + + if (compand) + { + destination = Color.Compress(destination); + } + + firstPassPixels[x, y] = destination; + } + } + }); + + // Now process the rows. + Parallel.For( + startY, + endY, + y => + { + if (y >= 0 && y < height) + { + // Ensure offsets are normalised for cropping and padding. + int offsetY = y - startY; + float sum = this.VerticalWeights[offsetY].Sum; + Weight[] verticalValues = this.VerticalWeights[offsetY].Values; + + for (int x = 0; x < width; x++) + { + // Destination color components + Color destination = new Color(); + + for (int i = 0; i < sum; i++) + { + Weight yw = verticalValues[i]; + int originY = yw.Index; + Color sourceColor = compand + ? Color.Expand(firstPassPixels[x, originY]) + : firstPassPixels[x, originY]; + + destination += sourceColor * yw.Value; + } + + if (compand) + { + destination = Color.Compress(destination); + } + + targetPixels[x, y] = destination; + } + } + + this.OnRowProcessed(); + }); + + } + } + + /// + protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + // Copy the pixels over. + if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) + { + target.ClonePixels(target.Width, target.Height, source.Pixels); + } + } + + /// + /// Computes the weights to apply at each pixel when resizing. + /// + /// The destination section size. + /// The source section size. + /// + /// The . + /// + protected Weights[] PrecomputeWeights(int destinationSize, int sourceSize) + { + float scale = (float)destinationSize / sourceSize; + IResampler sampler = this.Sampler; + float radius = sampler.Radius; + double left; + double right; + float weight; + int index; + int sum; + + Weights[] result = new Weights[destinationSize]; + + // When shrinking, broaden the effective kernel support so that we still + // visit every source pixel. + if (scale < 1) + { + float width = radius / scale; + float filterScale = 1 / scale; + + // Make the weights slices, one source for each column or row. + for (int i = 0; i < destinationSize; i++) + { + float centre = i / scale; + left = Math.Ceiling(centre - width); + right = Math.Floor(centre + width); + + result[i] = new Weights + { + Values = new Weight[(int)(right - left + 1)] + }; + + for (double j = left; j <= right; j++) + { + weight = sampler.GetValue((float)((centre - j) / filterScale)) / filterScale; + if (j < 0) + { + index = (int)-j; + } + else if (j >= sourceSize) + { + index = (int)((sourceSize - j) + sourceSize - 1); + } + else + { + index = (int)j; + } + + sum = (int)result[i].Sum++; + result[i].Values[sum] = new Weight(index, weight); + } + } + } + else + { + // Make the weights slices, one source for each column or row. + for (int i = 0; i < destinationSize; i++) + { + float centre = i / scale; + left = Math.Ceiling(centre - radius); + right = Math.Floor(centre + radius); + result[i] = new Weights + { + Values = new Weight[(int)(right - left + 1)] + }; + + for (double j = left; j <= right; j++) + { + weight = sampler.GetValue((float)(centre - j)); + if (j < 0) + { + index = (int)-j; + } + else if (j >= sourceSize) + { + index = (int)((sourceSize - j) + sourceSize - 1); + } + else + { + index = (int)j; + } + + sum = (int)result[i].Sum++; + result[i].Values[sum] = new Weight(index, weight); + } + } + } + + return result; + } + + /// + /// Represents the weight to be added to a scaled pixel. + /// + protected struct Weight + { + /// + /// Initializes a new instance of the struct. + /// + /// The index. + /// The value. + public Weight(int index, float value) + { + this.Index = index; + this.Value = value; + } + + /// + /// Gets the pixel index. + /// + public int Index { get; } + + /// + /// Gets the result of the interpolation algorithm. + /// + public float Value { get; } + } + + /// + /// Represents a collection of weights and their sum. + /// + protected class Weights + { + /// + /// Gets or sets the values. + /// + public Weight[] Values { get; set; } + + /// + /// Gets or sets the sum. + /// + public float Sum { get; set; } + } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/RotateFlipProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs rename to src/ImageProcessorCore - Copy/Samplers/Processors/RotateFlipProcessor.cs diff --git a/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/RotateProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs rename to src/ImageProcessorCore - Copy/Samplers/Processors/RotateProcessor.cs diff --git a/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/SkewProcessor.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs rename to src/ImageProcessorCore - Copy/Samplers/Processors/SkewProcessor.cs diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/BicubicResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/BicubicResampler.cs new file mode 100644 index 0000000000..8aecac7a60 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/BicubicResampler.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the bicubic kernel algorithm W(x) as described on + /// Wikipedia + /// A commonly used algorithm within imageprocessing that preserves sharpness better than triangle interpolation. + /// + public class BicubicResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + // The coefficient. + float a = -0.5f; + + if (x < 0) + { + x = -x; + } + + float result = 0; + + if (x <= 1) + { + result = (((1.5f * x) - 2.5f) * x * x) + 1; + } + else if (x < 2) + { + result = (((((a * x) + 2.5f) * x) - 4) * x) + 2; + } + + return result; + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/BoxResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/BoxResampler.cs new file mode 100644 index 0000000000..b1234e415d --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/BoxResampler.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the box algorithm. Similar to nearest neighbour when upscaling. + /// When downscaling the pixels will average, merging together. + /// + public class BoxResampler : IResampler + { + /// + public float Radius => 0.5F; + + /// + public float GetValue(float x) + { + if (x > -0.5 && x <= 0.5) + { + return 1; + } + + return 0; + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/CatmullRomResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/CatmullRomResampler.cs new file mode 100644 index 0000000000..0b5031df88 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/CatmullRomResampler.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The Catmull-Rom filter is a well known standard Cubic Filter often used as a interpolation function. + /// This filter produces a reasonably sharp edge, but without a the pronounced gradient change on large + /// scale image enlargements that a 'Lagrange' filter can produce. + /// + /// + public class CatmullRomResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 0; + const float C = 1 / 2f; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/HermiteResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/HermiteResampler.cs new file mode 100644 index 0000000000..49193a3de3 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/HermiteResampler.cs @@ -0,0 +1,27 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// The Hermite filter is type of smoothed triangular interpolation Filter, + /// This filter rounds off strong edges while preserving flat 'color levels' in the original image. + /// + /// + public class HermiteResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 0; + const float C = 0; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/IResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/IResampler.cs new file mode 100644 index 0000000000..0dea58440c --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/IResampler.cs @@ -0,0 +1,27 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// Encapsulates an interpolation algorithm for resampling images. + /// + public interface IResampler + { + /// + /// Gets the radius in which to sample pixels. + /// + float Radius { get; } + + /// + /// Gets the result of the interpolation algorithm. + /// + /// The value to process. + /// + /// The + /// + float GetValue(float x); + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos3Resampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos3Resampler.cs new file mode 100644 index 0000000000..a78b6c066a --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos3Resampler.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the Lanczos kernel algorithm as described on + /// Wikipedia + /// with a radius of 3 pixels. + /// + public class Lanczos3Resampler : IResampler + { + /// + public float Radius => 3; + + /// + public float GetValue(float x) + { + if (x < 0) + { + x = -x; + } + + if (x < 3) + { + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 3f); + } + + return 0; + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos5Resampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos5Resampler.cs new file mode 100644 index 0000000000..05af2dd7f2 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos5Resampler.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the Lanczos kernel algorithm as described on + /// Wikipedia + /// with a radius of 5 pixels. + /// + public class Lanczos5Resampler : IResampler + { + /// + public float Radius => 5; + + /// + public float GetValue(float x) + { + if (x < 0) + { + x = -x; + } + + if (x < 5) + { + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 5f); + } + + return 0; + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos8Resampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos8Resampler.cs new file mode 100644 index 0000000000..8c9a9237d9 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos8Resampler.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the Lanczos kernel algorithm as described on + /// Wikipedia + /// with a radius of 8 pixels. + /// + public class Lanczos8Resampler : IResampler + { + /// + public float Radius => 8; + + /// + public float GetValue(float x) + { + if (x < 0) + { + x = -x; + } + + if (x < 8) + { + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 8f); + } + + return 0; + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/MitchellNetravaliResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/MitchellNetravaliResampler.cs new file mode 100644 index 0000000000..f609f26450 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/MitchellNetravaliResampler.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the mitchell algorithm as described on + /// Wikipedia + /// + public class MitchellNetravaliResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 1 / 3f; + const float C = 1 / 3f; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/NearestNeighborResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/NearestNeighborResampler.cs new file mode 100644 index 0000000000..58b6a9d584 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/NearestNeighborResampler.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the nearest neighbour algorithm. This uses an unscaled filter + /// which will select the closest pixel to the new pixels position. + /// + public class NearestNeighborResampler : IResampler + { + /// + public float Radius => 1; + + /// + public float GetValue(float x) + { + return x; + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxResampler.cs new file mode 100644 index 0000000000..caead12d5d --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxResampler.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the Robidoux algorithm. + /// + /// + public class RobidouxResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 0.3782158F; + const float C = 0.3108921F; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxSharpResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxSharpResampler.cs new file mode 100644 index 0000000000..633503cd16 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxSharpResampler.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the Robidoux Sharp algorithm. + /// + /// + public class RobidouxSharpResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 0.26201451F; + const float C = 0.36899274F; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxSoftResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxSoftResampler.cs new file mode 100644 index 0000000000..8706f492bb --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxSoftResampler.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the Robidoux Soft algorithm. + /// + /// + public class RobidouxSoftResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 0.6796f; + const float C = 0.1602f; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/SplineResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/SplineResampler.cs new file mode 100644 index 0000000000..55ef5656a9 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/SplineResampler.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the spline algorithm. + /// + /// + public class SplineResampler : IResampler + { + /// + public float Radius => 2; + + /// + public float GetValue(float x) + { + const float B = 1; + const float C = 0; + + return ImageMaths.GetBcValue(x, B, C); + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/TriangleResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/TriangleResampler.cs new file mode 100644 index 0000000000..cb404b7369 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/TriangleResampler.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the triangle (bilinear) algorithm. + /// Bilinear interpolation can be used where perfect image transformation with pixel matching is impossible, + /// so that one can calculate and assign appropriate intensity values to pixels. + /// + public class TriangleResampler : IResampler + { + /// + public float Radius => 1; + + /// + public float GetValue(float x) + { + if (x < 0) + { + x = -x; + } + + if (x < 1) + { + return 1 - x; + } + + return 0; + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/WelchResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/WelchResampler.cs new file mode 100644 index 0000000000..3ecaa6a747 --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resamplers/WelchResampler.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// The function implements the welch algorithm. + /// + /// + public class WelchResampler : IResampler + { + /// + public float Radius => 3; + + /// + public float GetValue(float x) + { + if (x < 0) + { + x = -x; + } + + if (x < 3) + { + return ImageMaths.SinC(x) * (1.0f - (x * x / 9.0f)); + } + + return 0; + } + } +} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resize.cs b/src/ImageProcessorCore - Copy/Samplers/Resize.cs new file mode 100644 index 0000000000..2eadd7a11c --- /dev/null +++ b/src/ImageProcessorCore - Copy/Samplers/Resize.cs @@ -0,0 +1,134 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// ------------------------------------------------------------------------------------------------------------------- + +namespace ImageProcessorCore +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Resizes an image in accordance with the given . + /// + /// The image to resize. + /// The resize options. + /// A delegate which is called as progress is made processing the image. + /// The + /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image + public static Image Resize(this Image source, ResizeOptions options, ProgressEventHandler progressHandler = null) + { + // Ensure size is populated across both dimensions. + if (options.Size.Width == 0 && options.Size.Height > 0) + { + options.Size = new Size(source.Width * options.Size.Height / source.Height, options.Size.Height); + } + + if (options.Size.Height == 0 && options.Size.Width > 0) + { + options.Size = new Size(options.Size.Width, source.Height * options.Size.Width / source.Width); + } + + Rectangle targetRectangle = ResizeHelper.CalculateTargetLocationAndBounds(source, options); + + return Resize(source, options.Size.Width, options.Size.Height, options.Sampler, source.Bounds, targetRectangle, options.Compand, progressHandler); + } + + /// + /// Resizes an image to the given width and height. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// A delegate which is called as progress is made processing the image. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + public static Image Resize(this Image source, int width, int height, ProgressEventHandler progressHandler = null) + { + return Resize(source, width, height, new BicubicResampler(), false, progressHandler); + } + + /// + /// Resizes an image to the given width and height. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// A delegate which is called as progress is made processing the image. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + public static Image Resize(this Image source, int width, int height, bool compand, ProgressEventHandler progressHandler = null) + { + return Resize(source, width, height, new BicubicResampler(), compand, progressHandler); + } + + /// + /// Resizes an image to the given width and height with the given sampler. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to perform the resampling. + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// A delegate which is called as progress is made processing the image. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + public static Image Resize(this Image source, int width, int height, IResampler sampler, bool compand, ProgressEventHandler progressHandler = null) + { + return Resize(source, width, height, sampler, source.Bounds, new Rectangle(0, 0, width, height), compand, progressHandler); + } + + /// + /// Resizes an image to the given width and height with the given sampler and + /// source rectangle. + /// + /// The image to resize. + /// The target image width. + /// The target image height. + /// The to perform the resampling. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// The structure that specifies the portion of the target image object to draw to. + /// + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// A delegate which is called as progress is made processing the image. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand = false, ProgressEventHandler progressHandler = null) + { + if (width == 0 && height > 0) + { + width = source.Width * height / source.Height; + targetRectangle.Width = width; + } + + if (height == 0 && width > 0) + { + height = source.Height * width / source.Width; + targetRectangle.Height = height; + } + + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + ResizeProcessor processor = new ResizeProcessor(sampler) { Compand = compand }; + processor.OnProgress += progressHandler; + + try + { + return source.Process(width, height, sourceRectangle, targetRectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Samplers/Rotate.cs b/src/ImageProcessorCore - Copy/Samplers/Rotate.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/Rotate.cs rename to src/ImageProcessorCore - Copy/Samplers/Rotate.cs diff --git a/src/ImageProcessorCore/Samplers/RotateFlip.cs b/src/ImageProcessorCore - Copy/Samplers/RotateFlip.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/RotateFlip.cs rename to src/ImageProcessorCore - Copy/Samplers/RotateFlip.cs diff --git a/src/ImageProcessorCore/Samplers/Skew.cs b/src/ImageProcessorCore - Copy/Samplers/Skew.cs similarity index 100% rename from src/ImageProcessorCore/Samplers/Skew.cs rename to src/ImageProcessorCore - Copy/Samplers/Skew.cs diff --git a/src/ImageProcessorCore - Copy/project.json b/src/ImageProcessorCore - Copy/project.json new file mode 100644 index 0000000000..e3800d495e --- /dev/null +++ b/src/ImageProcessorCore - Copy/project.json @@ -0,0 +1,38 @@ +{ + "version": "1.0.0-*", + "title": "ImageProcessorCore", + "description": "A cross-platform library for processing of image files written in C#", + "authors": [ + "James Jackson-South and contributors" + ], + "packOptions": { + "projectUrl": "https://github.com/JimBobSquarePants/ImageProcessor", + "licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0", + "tags": [ + "Image Resize Crop Quality Gif Jpg Jpeg Bitmap Png Fluent Animated" + ] + }, + "buildOptions": { + "allowUnsafe": true, + "debugType": "portable" + }, + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Diagnostics.Tools": "4.0.1", + "System.IO": "4.1.0", + "System.IO.Compression": "4.1.0", + "System.Linq": "4.1.0", + "System.Numerics.Vectors": "4.1.1", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.InteropServices": "4.1.0", + "System.Text.Encoding.Extensions": "4.0.11", + "System.Threading": "4.0.11", + "System.Threading.Tasks": "4.0.11", + "System.Threading.Tasks.Parallel": "4.0.1" + }, + "frameworks": { + "netstandard1.1": {} + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore/Bootstrapper.cs b/src/ImageProcessorCore/Bootstrapper.cs index 5f2978534c..08698af320 100644 --- a/src/ImageProcessorCore/Bootstrapper.cs +++ b/src/ImageProcessorCore/Bootstrapper.cs @@ -38,9 +38,9 @@ namespace ImageProcessorCore this.imageFormats = new List { new BmpFormat(), - new JpegFormat(), - new PngFormat(), - new GifFormat() + //new JpegFormat(), + //new PngFormat(), + //new GifFormat() }; this.pixelAccessors = new Dictionary @@ -74,14 +74,16 @@ namespace ImageProcessorCore /// The type of pixel data. /// The image /// The - public IPixelAccessor GetPixelAccessor(Image image) - where TPackedVector : IPackedVector + public IPixelAccessor GetPixelAccessor(IImageBase image) + where TPackedVector : IPackedVector, new() { Type packed = typeof(TPackedVector); - if (!this.pixelAccessors.ContainsKey(packed)) + if (this.pixelAccessors.ContainsKey(packed)) { // TODO: Double check this. It should work... - return (IPixelAccessor)Activator.CreateInstance(this.pixelAccessors[packed], image); + + return (IPixelAccessor)new Bgra32PixelAccessor(image); + //return (IPixelAccessor)Activator.CreateInstance(this.pixelAccessors[packed], image); } StringBuilder stringBuilder = new StringBuilder(); diff --git a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs index 051b75f70f..2a55286a31 100644 --- a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs +++ b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs @@ -162,110 +162,110 @@ namespace ImageProcessorCore /// /// The . /// - public static Rectangle GetFilteredBoundingRectangle(ImageBase bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B) - { - const float Epsilon = .00001f; - int width = bitmap.Width; - int height = bitmap.Height; - Point topLeft = new Point(); - Point bottomRight = new Point(); - - Func delegateFunc; - - // Determine which channel to check against - switch (channel) - { - case RgbaComponent.R: - delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].R - b) > Epsilon; - break; - - case RgbaComponent.G: - delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].G - b) > Epsilon; - break; - - case RgbaComponent.A: - delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].A - b) > Epsilon; - break; - - default: - delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].B - b) > Epsilon; - break; - } - - Func getMinY = pixels => - { - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - if (delegateFunc(pixels, x, y, componentValue)) - { - return y; - } - } - } - - return 0; - }; - - Func getMaxY = pixels => - { - for (int y = height - 1; y > -1; y--) - { - for (int x = 0; x < width; x++) - { - if (delegateFunc(pixels, x, y, componentValue)) - { - return y; - } - } - } - - return height; - }; - - Func getMinX = pixels => - { - for (int x = 0; x < width; x++) - { - for (int y = 0; y < height; y++) - { - if (delegateFunc(pixels, x, y, componentValue)) - { - return x; - } - } - } - - return 0; - }; - - Func getMaxX = pixels => - { - for (int x = width - 1; x > -1; x--) - { - for (int y = 0; y < height; y++) - { - if (delegateFunc(pixels, x, y, componentValue)) - { - return x; - } - } - } - - return height; - }; - - using (PixelAccessor bitmapPixels = bitmap.Lock()) - { - topLeft.Y = getMinY(bitmapPixels); - topLeft.X = getMinX(bitmapPixels); - bottomRight.Y = (getMaxY(bitmapPixels) + 1).Clamp(0, height); - bottomRight.X = (getMaxX(bitmapPixels) + 1).Clamp(0, width); - } - - return GetBoundingRectangle(topLeft, bottomRight); - } + //public static Rectangle GetFilteredBoundingRectangle(ImageBase bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B) + //{ + // const float Epsilon = .00001f; + // int width = bitmap.Width; + // int height = bitmap.Height; + // Point topLeft = new Point(); + // Point bottomRight = new Point(); + + // Func delegateFunc; + + // // Determine which channel to check against + // switch (channel) + // { + // case RgbaComponent.R: + // delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].R - b) > Epsilon; + // break; + + // case RgbaComponent.G: + // delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].G - b) > Epsilon; + // break; + + // case RgbaComponent.A: + // delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].A - b) > Epsilon; + // break; + + // default: + // delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].B - b) > Epsilon; + // break; + // } + + // Func getMinY = pixels => + // { + // for (int y = 0; y < height; y++) + // { + // for (int x = 0; x < width; x++) + // { + // if (delegateFunc(pixels, x, y, componentValue)) + // { + // return y; + // } + // } + // } + + // return 0; + // }; + + // Func getMaxY = pixels => + // { + // for (int y = height - 1; y > -1; y--) + // { + // for (int x = 0; x < width; x++) + // { + // if (delegateFunc(pixels, x, y, componentValue)) + // { + // return y; + // } + // } + // } + + // return height; + // }; + + // Func getMinX = pixels => + // { + // for (int x = 0; x < width; x++) + // { + // for (int y = 0; y < height; y++) + // { + // if (delegateFunc(pixels, x, y, componentValue)) + // { + // return x; + // } + // } + // } + + // return 0; + // }; + + // Func getMaxX = pixels => + // { + // for (int x = width - 1; x > -1; x--) + // { + // for (int y = 0; y < height; y++) + // { + // if (delegateFunc(pixels, x, y, componentValue)) + // { + // return x; + // } + // } + // } + + // return height; + // }; + + // using (PixelAccessor bitmapPixels = bitmap.Lock()) + // { + // topLeft.Y = getMinY(bitmapPixels); + // topLeft.X = getMinX(bitmapPixels); + // bottomRight.Y = (getMaxY(bitmapPixels) + 1).Clamp(0, height); + // bottomRight.X = (getMaxX(bitmapPixels) + 1).Clamp(0, width); + // } + + // return GetBoundingRectangle(topLeft, bottomRight); + //} /// /// Ensures that any passed double is correctly rounded to zero diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs index 03d45f1122..4872ba89dc 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs @@ -70,11 +70,12 @@ namespace ImageProcessorCore.Formats } /// - /// Decodes the image from the specified stream to the . + /// Decodes the image from the specified stream to the . /// - /// The to decode to. + /// The to decode to. /// The containing image data. - public void Decode(Image image, Stream stream) + public void Decode(Image image, Stream stream) + where TPackedVector : IPackedVector, new() { new BmpDecoderCore().Decode(image, stream); } diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs index da92e6a63c..4a36a07e31 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs @@ -49,6 +49,7 @@ namespace ImageProcessorCore.Formats /// Decodes the image from the specified this._stream and sets /// the data to image. /// + /// The type of pixels contained within the image. /// The image, where the data should be set to. /// Cannot be null (Nothing in Visual Basic). /// The this._stream, where the image should be @@ -58,7 +59,8 @@ namespace ImageProcessorCore.Formats /// - or - /// is null. /// - public void Decode(Image image, Stream stream) + public void Decode(Image image, Stream stream) + where TPackedVector : IPackedVector, new() { this.currentStream = stream; @@ -110,14 +112,14 @@ namespace ImageProcessorCore.Formats this.currentStream.Read(palette, 0, colorMapSize); } - if (this.infoHeader.Width > ImageBase.MaxWidth || this.infoHeader.Height > ImageBase.MaxHeight) + if (this.infoHeader.Width > image.MaxWidth || this.infoHeader.Height > image.MaxHeight) { throw new ArgumentOutOfRangeException( $"The input bitmap '{this.infoHeader.Width}x{this.infoHeader.Height}' is " - + $"bigger then the max allowed size '{ImageBase.MaxWidth}x{ImageBase.MaxHeight}'"); + + $"bigger then the max allowed size '{image.MaxWidth}x{image.MaxHeight}'"); } - float[] imageData = new float[this.infoHeader.Width * this.infoHeader.Height * 4]; + TPackedVector[] imageData = new TPackedVector[this.infoHeader.Width * this.infoHeader.Height]; switch (this.infoHeader.Compression) { @@ -169,6 +171,7 @@ namespace ImageProcessorCore.Formats /// /// The y- value representing the current row. /// The height of the bitmap. + /// Whether the bitmap is inverted. /// The representing the inverted value. private static int Invert(int y, int height, bool inverted) { @@ -189,12 +192,15 @@ namespace ImageProcessorCore.Formats /// /// Reads the color palette from the stream. /// - /// The image data to assign the palette to. + /// The type of pixels contained within the image. + /// The image data to assign the palette to. /// The containing the colors. /// The width of the bitmap. /// The height of the bitmap. /// The number of bits per pixel. - private void ReadRgbPalette(float[] imageData, byte[] colors, int width, int height, int bits, bool inverted) + /// Whether the bitmap is inverted. + private void ReadRgbPalette(TPackedVector[] imageData, byte[] colors, int width, int height, int bits, bool inverted) + where TPackedVector : IPackedVector, new() { // Pixels per byte (bits per pixel) int ppb = 8 / bits; @@ -234,14 +240,12 @@ namespace ImageProcessorCore.Formats for (int shift = 0; shift < ppb && (colOffset + shift) < width; shift++) { int colorIndex = ((data[offset] >> (8 - bits - (shift * bits))) & mask) * 4; - int arrayOffset = ((row * width) + (colOffset + shift)) * 4; - - // We divide by 255 as we will store the colors in our floating point format. - // Stored in r-> g-> b-> a order. - imageData[arrayOffset] = colors[colorIndex + 2] / 255f; // r - imageData[arrayOffset + 1] = colors[colorIndex + 1] / 255f; // g - imageData[arrayOffset + 2] = colors[colorIndex] / 255f; // b - imageData[arrayOffset + 3] = 1; // a + int arrayOffset = (row * width) + (colOffset + shift); + + // Stored in b-> g-> r-> a order. + TPackedVector packed = new TPackedVector(); + packed.PackBytes(colors[colorIndex], colors[colorIndex + 1], colors[colorIndex + 2], 255); + imageData[arrayOffset] = packed; } } }); @@ -250,14 +254,17 @@ namespace ImageProcessorCore.Formats /// /// Reads the 16 bit color palette from the stream /// - /// The image data to assign the palette to. + /// The type of pixels contained within the image. + /// The image data to assign the palette to. /// The width of the bitmap. /// The height of the bitmap. - private void ReadRgb16(float[] imageData, int width, int height, bool inverted) + /// Whether the bitmap is inverted. + private void ReadRgb16(TPackedVector[] imageData, int width, int height, bool inverted) + where TPackedVector : IPackedVector, new() { // We divide here as we will store the colors in our floating point format. - const float ScaleR = 0.25F; // (256 / 32) / 32 - const float ScaleG = 0.0625F; // (256 / 64) / 64 + const int ScaleR = 8; // 256/32 + const int ScaleG = 4; // 256/64 int alignment; byte[] data = this.GetImageArray(width, height, 2, out alignment); @@ -278,17 +285,16 @@ namespace ImageProcessorCore.Formats short temp = BitConverter.ToInt16(data, offset); - float r = ((temp & Rgb16RMask) >> 11) * ScaleR; - float g = ((temp & Rgb16GMask) >> 5) * ScaleG; - float b = (temp & Rgb16BMask) * ScaleR; + byte r = (byte)(((temp & Rgb16RMask) >> 11) * ScaleR); + byte g = (byte)(((temp & Rgb16GMask) >> 5) * ScaleG); + byte b = (byte)((temp & Rgb16BMask) * ScaleR); - int arrayOffset = ((row * width) + x) * 4; + int arrayOffset = ((row * width) + x); - // Stored in r-> g-> b-> a order. - imageData[arrayOffset] = r; - imageData[arrayOffset + 1] = g; - imageData[arrayOffset + 2] = b; - imageData[arrayOffset + 3] = 1; + // Stored in b-> g-> r-> a order. + TPackedVector packed = new TPackedVector(); + packed.PackBytes(b, g, r, 255); + imageData[arrayOffset] = packed; } }); } @@ -296,10 +302,13 @@ namespace ImageProcessorCore.Formats /// /// Reads the 24 bit color palette from the stream /// - /// The image data to assign the palette to. + /// The type of pixels contained within the image. + /// The image data to assign the palette to. /// The width of the bitmap. /// The height of the bitmap. - private void ReadRgb24(float[] imageData, int width, int height, bool inverted) + /// Whether the bitmap is inverted. + private void ReadRgb24(TPackedVector[] imageData, int width, int height, bool inverted) + where TPackedVector : IPackedVector, new() { int alignment; byte[] data = this.GetImageArray(width, height, 3, out alignment); @@ -317,14 +326,13 @@ namespace ImageProcessorCore.Formats for (int x = 0; x < width; x++) { int offset = rowOffset + (x * 3); - int arrayOffset = ((row * width) + x) * 4; + int arrayOffset = ((row * width) + x); // We divide by 255 as we will store the colors in our floating point format. - // Stored in r-> g-> b-> a order. - imageData[arrayOffset] = data[offset + 2] / 255f; - imageData[arrayOffset + 1] = data[offset + 1] / 255f; - imageData[arrayOffset + 2] = data[offset] / 255f; - imageData[arrayOffset + 3] = 1; + // Stored in b-> g-> r-> a order. + TPackedVector packed = new TPackedVector(); + packed.PackBytes(data[offset], data[offset + 1], data[offset + 2], 255); + imageData[arrayOffset] = packed; } }); } @@ -332,10 +340,13 @@ namespace ImageProcessorCore.Formats /// /// Reads the 32 bit color palette from the stream /// - /// The image data to assign the palette to. + /// The type of pixels contained within the image. + /// The image data to assign the palette to. /// The width of the bitmap. /// The height of the bitmap. - private void ReadRgb32(float[] imageData, int width, int height, bool inverted) + /// Whether the bitmap is inverted. + private void ReadRgb32(TPackedVector[] imageData, int width, int height, bool inverted) + where TPackedVector : IPackedVector, new() { int alignment; byte[] data = this.GetImageArray(width, height, 4, out alignment); @@ -353,14 +364,12 @@ namespace ImageProcessorCore.Formats for (int x = 0; x < width; x++) { int offset = rowOffset + (x * 4); - int arrayOffset = ((row * width) + x) * 4; + int arrayOffset = ((row * width) + x); - // We divide by 255 as we will store the colors in our floating point format. - // Stored in r-> g-> b-> a order. - imageData[arrayOffset] = data[offset + 2] / 255f; - imageData[arrayOffset + 1] = data[offset + 1] / 255f; - imageData[arrayOffset + 2] = data[offset] / 255f; - imageData[arrayOffset + 3] = 1; // TODO: Can we use our real alpha here? + // Stored in b-> g-> r-> a order. + TPackedVector packed = new TPackedVector(); + packed.PackBytes(data[offset], data[offset + 1], data[offset + 2], data[offset + 3]); + imageData[arrayOffset] = packed; } }); } diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs index 944154df21..4b212d4ea0 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs @@ -43,7 +43,8 @@ namespace ImageProcessorCore.Formats } /// - public void Encode(ImageBase image, Stream stream) + public void Encode(ImageBase image, Stream stream) + where TPackedVector: IPackedVector { BmpEncoderCore encoder = new BmpEncoderCore(); encoder.Encode(image, stream, this.BitsPerPixel); diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs index 8c16b4e40c..2ad8e24e6e 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs @@ -8,7 +8,7 @@ namespace ImageProcessorCore.Formats using System; using System.IO; - using ImageProcessorCore.IO; + using IO; /// /// Image encoder for writing an image to a stream as a Windows bitmap. @@ -22,12 +22,14 @@ namespace ImageProcessorCore.Formats private BmpBitsPerPixel bmpBitsPerPixel; /// - /// Encodes the image to the specified stream from the . + /// Encodes the image to the specified stream from the . /// - /// The to encode from. + /// The type of pixels contained within the image. + /// The to encode from. /// The to encode the image data to. /// The - public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) + public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) + where TPackedVector : IPackedVector { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -36,6 +38,7 @@ namespace ImageProcessorCore.Formats int rowWidth = image.Width; + // TODO: Check this for varying file formats. int amount = (image.Width * (int)this.bmpBitsPerPixel) % 4; if (amount != 0) { @@ -117,13 +120,15 @@ namespace ImageProcessorCore.Formats /// /// Writes the pixel data to the binary stream. /// + /// The type of pixels contained within the image. /// /// The containing the stream to write to. /// /// - /// The containing pixel data. + /// The containing pixel data. /// - private void WriteImage(EndianBinaryWriter writer, ImageBase image) + private void WriteImage(EndianBinaryWriter writer, ImageBase image) + where TPackedVector : IPackedVector { // TODO: Add more compression formats. int amount = (image.Width * (int)this.bmpBitsPerPixel) % 4; @@ -132,7 +137,7 @@ namespace ImageProcessorCore.Formats amount = 4 - amount; } - using (PixelAccessor pixels = image.Lock()) + using (IPixelAccessor pixels = image.Lock()) { switch (this.bmpBitsPerPixel) { @@ -151,21 +156,17 @@ namespace ImageProcessorCore.Formats /// Writes the 32bit color palette to the stream. /// /// The containing the stream to write to. - /// The containing pixel data. + /// The containing pixel data. /// The amount to pad each row by. - private void Write32bit(EndianBinaryWriter writer, PixelAccessor pixels, int amount) + private void Write32bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) { for (int y = pixels.Height - 1; y >= 0; y--) { for (int x = 0; x < pixels.Width; x++) { - // Limit the output range and multiply out from our floating point. // Convert back to b-> g-> r-> a order. - // Convert to non-premultiplied color. - Bgra32 color = Color.ToNonPremultiplied(pixels[x, y]); - - // We can take advantage of BGRA here - writer.Write(color.Bgra); + byte[] bytes = pixels[x, y].ToBytes(); + writer.Write(bytes); } // Pad @@ -180,21 +181,17 @@ namespace ImageProcessorCore.Formats /// Writes the 24bit color palette to the stream. /// /// The containing the stream to write to. - /// The containing pixel data. + /// The containing pixel data. /// The amount to pad each row by. - private void Write24bit(EndianBinaryWriter writer, PixelAccessor pixels, int amount) + private void Write24bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) { for (int y = pixels.Height - 1; y >= 0; y--) { for (int x = 0; x < pixels.Width; x++) { - // Limit the output range and multiply out from our floating point. // Convert back to b-> g-> r-> a order. - // Convert to non-premultiplied color. - Bgra32 color = Color.ToNonPremultiplied(pixels[x, y]); - - // Allocate 1 array instead of allocating 3. - writer.Write(new[] { color.B, color.G, color.R }); + byte[] bytes = pixels[x, y].ToBytes(); + writer.Write(new[] { bytes[0], bytes[1], bytes[2] }); } // Pad diff --git a/src/ImageProcessorCore/Formats/IImageDecoder.cs b/src/ImageProcessorCore/Formats/IImageDecoder.cs index 761f3a68a7..f394843caa 100644 --- a/src/ImageProcessorCore/Formats/IImageDecoder.cs +++ b/src/ImageProcessorCore/Formats/IImageDecoder.cs @@ -39,10 +39,11 @@ namespace ImageProcessorCore.Formats bool IsSupportedFileFormat(byte[] header); /// - /// Decodes the image from the specified stream to the . + /// Decodes the image from the specified stream to the . /// - /// The to decode to. + /// The type of pixels contained within the image. + /// The to decode to. /// The containing image data. - void Decode(Image image, Stream stream); + void Decode(Image image, Stream stream) where TPackedVector : IPackedVector, new(); } } diff --git a/src/ImageProcessorCore/Formats/IImageEncoder.cs b/src/ImageProcessorCore/Formats/IImageEncoder.cs index 4acceff996..df7234aad0 100644 --- a/src/ImageProcessorCore/Formats/IImageEncoder.cs +++ b/src/ImageProcessorCore/Formats/IImageEncoder.cs @@ -43,10 +43,11 @@ namespace ImageProcessorCore.Formats bool IsSupportedFileExtension(string extension); /// - /// Encodes the image to the specified stream from the . + /// Encodes the image to the specified stream from the . /// - /// The to encode from. + /// The type of pixels contained within the image. + /// The to encode from. /// The to encode the image data to. - void Encode(ImageBase image, Stream stream); + void Encode(ImageBase image, Stream stream) where TPackedVector : IPackedVector; } } diff --git a/src/ImageProcessorCore/IImageBase.cs b/src/ImageProcessorCore/IImageBase.cs index b01ee7c9c4..c8b762382c 100644 --- a/src/ImageProcessorCore/IImageBase.cs +++ b/src/ImageProcessorCore/IImageBase.cs @@ -1,18 +1,35 @@ -namespace ImageProcessorCore +using System.Collections.Generic; + +namespace ImageProcessorCore { - public interface IImageBase - where TPackedVector : IPackedVector + public interface IImageBase : IImageBase + where TPackedVector : IPackedVector, new() + { + TPackedVector[] Pixels { get; } + void ClonePixels(int width, int height, IEnumerable pixels); + IPixelAccessor Lock(); + void SetPixels(int width, int height, TPackedVector[] pixels); + } + + public interface IImageBase { Rectangle Bounds { get; } int FrameDelay { get; set; } int Height { get; } double PixelRatio { get; } - TPackedVector[] Pixels { get; } + int Quality { get; set; } - int Width { get; } - void ClonePixels(int width, int height, TPackedVector[] pixels); - IPixelAccessor Lock(); - void SetPixels(int width, int height, TPackedVector[] pixels); + /// + /// Gets or sets the maximum allowable width in pixels. + /// + int MaxWidth { get; set; } + + /// + /// Gets or sets the maximum allowable height in pixels. + /// + int MaxHeight { get; set; } + + int Width { get; } } } \ No newline at end of file diff --git a/src/ImageProcessorCore/IImageFrame.cs b/src/ImageProcessorCore/IImageFrame.cs index 1565879a76..a3c82d9325 100644 --- a/src/ImageProcessorCore/IImageFrame.cs +++ b/src/ImageProcessorCore/IImageFrame.cs @@ -1,7 +1,7 @@ namespace ImageProcessorCore { public interface IImageFrame : IImageBase - where TPacked : IPackedVector + where TPacked : IPackedVector, new() { } } diff --git a/src/ImageProcessorCore/IImageProcessor.cs b/src/ImageProcessorCore/IImageProcessor.cs index ad6e4ac761..153dd0a84a 100644 --- a/src/ImageProcessorCore/IImageProcessor.cs +++ b/src/ImageProcessorCore/IImageProcessor.cs @@ -45,7 +45,8 @@ namespace ImageProcessorCore.Processors /// /// doesnt fit the dimension of the image. /// - void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) where TPackedVector : IPackedVector; + void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) + where TPackedVector : IPackedVector, new(); /// /// Applies the process to the specified portion of the specified at the specified @@ -67,6 +68,7 @@ namespace ImageProcessorCore.Processors /// The method keeps the source image unchanged and returns the /// the result of image process as new image. /// - void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle) where TPackedVector : IPackedVector; + void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle) + where TPackedVector : IPackedVector, new(); } } diff --git a/src/ImageProcessorCore/Image.cs b/src/ImageProcessorCore/Image.cs index c8dc861d6a..acd49d5514 100644 --- a/src/ImageProcessorCore/Image.cs +++ b/src/ImageProcessorCore/Image.cs @@ -21,7 +21,7 @@ namespace ImageProcessorCore /// The packed vector containing pixel information. /// public class Image : ImageBase - where TPackedVector : IPackedVector + where TPackedVector : IPackedVector, new() { /// /// The default horizontal resolution value (dots per inch) in x direction. @@ -40,7 +40,7 @@ namespace ImageProcessorCore /// public Image() { - this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat)); + this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(BmpFormat)); } /// @@ -52,7 +52,8 @@ namespace ImageProcessorCore public Image(int width, int height) : base(width, height) { - this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat)); + // TODO: Change to PNG + this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(BmpFormat)); } /// @@ -183,7 +184,7 @@ namespace ImageProcessorCore public IImageFormat CurrentImageFormat { get; internal set; } /// - public override IPixelAccessor Lock() + public override IPixelAccessor Lock() { return Bootstrapper.Instance.GetPixelAccessor(this); } diff --git a/src/ImageProcessorCore/ImageBase.cs b/src/ImageProcessorCore/ImageBase.cs index 4dcb73174c..0c0bb6fac6 100644 --- a/src/ImageProcessorCore/ImageBase.cs +++ b/src/ImageProcessorCore/ImageBase.cs @@ -11,11 +11,11 @@ namespace ImageProcessorCore /// The base class of all images. Encapsulates the basic properties and methods required to manipulate images /// in different pixel formats. /// - /// + /// /// The packed vector pixels format. /// - public abstract class ImageBase : IImageBase - where TPacked : IPackedVector + public abstract class ImageBase : IImageBase + where TPackedVector : IPackedVector, new() { /// /// Initializes a new instance of the class. @@ -39,7 +39,7 @@ namespace ImageProcessorCore this.Width = width; this.Height = height; - this.Pixels = new TPacked[width * height]; + this.Pixels = new TPackedVector[width * height]; } /// @@ -51,7 +51,7 @@ namespace ImageProcessorCore /// /// Thrown if the given is null. /// - protected ImageBase(ImageBase other) + protected ImageBase(ImageBase other) { Guard.NotNull(other, nameof(other), "Other image cannot be null."); @@ -61,14 +61,24 @@ namespace ImageProcessorCore this.FrameDelay = other.FrameDelay; // Copy the pixels. - this.Pixels = new TPacked[this.Width * this.Height]; + this.Pixels = new TPackedVector[this.Width * this.Height]; Array.Copy(other.Pixels, this.Pixels, other.Pixels.Length); } + /// + /// Gets or sets the maximum allowable width in pixels. + /// + public int MaxWidth { get; set; } = int.MaxValue; + + /// + /// Gets or sets the maximum allowable height in pixels. + /// + public int MaxHeight { get; set; } = int.MaxValue; + /// /// Gets the pixels as an array of the given packed pixel format. /// - public TPacked[] Pixels { get; private set; } + public TPackedVector[] Pixels { get; private set; } /// /// Gets the width in pixels. @@ -117,7 +127,7 @@ namespace ImageProcessorCore /// /// Thrown if the length is not equal to Width * Height. /// - public void SetPixels(int width, int height, TPacked[] pixels) + public void SetPixels(int width, int height, TPackedVector[] pixels) { if (width <= 0) { @@ -154,7 +164,7 @@ namespace ImageProcessorCore /// /// Thrown if the length is not equal to Width * Height. /// - public void ClonePixels(int width, int height, TPacked[] pixels) + public void ClonePixels(int width, int height, TPackedVector[] pixels) { if (width <= 0) { @@ -175,7 +185,7 @@ namespace ImageProcessorCore this.Height = height; // Copy the pixels. - this.Pixels = new TPacked[pixels.Length]; + this.Pixels = new TPackedVector[pixels.Length]; Array.Copy(pixels, this.Pixels, pixels.Length); } @@ -186,6 +196,6 @@ namespace ImageProcessorCore /// /// /// The - public abstract IPixelAccessor Lock(); + public abstract IPixelAccessor Lock(); } } diff --git a/src/ImageProcessorCore/ImageExtensions.cs b/src/ImageProcessorCore/ImageExtensions.cs index e7b261d608..526ec8d554 100644 --- a/src/ImageProcessorCore/ImageExtensions.cs +++ b/src/ImageProcessorCore/ImageExtensions.cs @@ -12,55 +12,57 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { - /// - /// Saves the image to the given stream with the bmp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsBmp(this ImageBase source, Stream stream) => new BmpEncoder().Encode(source, stream); + ///// + ///// Saves the image to the given stream with the bmp format. + ///// + ///// The image this method extends. + ///// The stream to save the image to. + ///// Thrown if the stream is null. + //public static void SaveAsBmp(this ImageBase source, Stream stream) => new BmpEncoder().Encode(source, stream); - /// - /// Saves the image to the given stream with the png format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The quality to save the image to representing the number of colors. - /// Anything equal to 256 and below will cause the encoder to save the image in an indexed format. - /// - /// Thrown if the stream is null. - public static void SaveAsPng(this ImageBase source, Stream stream, int quality = Int32.MaxValue) => new PngEncoder { Quality = quality }.Encode(source, stream); + ///// + ///// Saves the image to the given stream with the png format. + ///// + ///// The image this method extends. + ///// The stream to save the image to. + ///// The quality to save the image to representing the number of colors. + ///// Anything equal to 256 and below will cause the encoder to save the image in an indexed format. + ///// + ///// Thrown if the stream is null. + //public static void SaveAsPng(this ImageBase source, Stream stream, int quality = Int32.MaxValue) => new PngEncoder { Quality = quality }.Encode(source, stream); - /// - /// Saves the image to the given stream with the jpeg format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The quality to save the image to. Between 1 and 100. - /// Thrown if the stream is null. - public static void SaveAsJpeg(this ImageBase source, Stream stream, int quality = 75) => new JpegEncoder { Quality = quality }.Encode(source, stream); + ///// + ///// Saves the image to the given stream with the jpeg format. + ///// + ///// The image this method extends. + ///// The stream to save the image to. + ///// The quality to save the image to. Between 1 and 100. + ///// Thrown if the stream is null. + //public static void SaveAsJpeg(this ImageBase source, Stream stream, int quality = 75) => new JpegEncoder { Quality = quality }.Encode(source, stream); - /// - /// Saves the image to the given stream with the gif format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The quality to save the image to representing the number of colors. Between 1 and 256. - /// Thrown if the stream is null. - public static void SaveAsGif(this ImageBase source, Stream stream, int quality = 256) => new GifEncoder { Quality = quality }.Encode(source, stream); + ///// + ///// Saves the image to the given stream with the gif format. + ///// + ///// The image this method extends. + ///// The stream to save the image to. + ///// The quality to save the image to representing the number of colors. Between 1 and 256. + ///// Thrown if the stream is null. + //public static void SaveAsGif(this ImageBase source, Stream stream, int quality = 256) => new GifEncoder { Quality = quality }.Encode(source, stream); /// /// Applies the collection of processors to the image. /// This method does not resize the target image. /// + /// The type of pixels contained within the image. /// The image this method extends. /// The processor to apply to the image. - /// The . - public static Image Process(this Image source, IImageProcessor processor) + /// The . + public static Image Process(this Image source, IImageProcessor processor) + where TPackedVector : IPackedVector, new() { return Process(source, source.Bounds, processor); } @@ -69,13 +71,15 @@ namespace ImageProcessorCore /// Applies the collection of processors to the image. /// This method does not resize the target image. /// + /// The type of pixels contained within the image. /// The image this method extends. /// /// The structure that specifies the portion of the image object to draw. /// /// The processors to apply to the image. - /// The . - public static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor) + /// The . + public static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor) + where TPackedVector : IPackedVector, new() { return PerformAction(source, true, (sourceImage, targetImage) => processor.Apply(targetImage, sourceImage, sourceRectangle)); } @@ -86,12 +90,14 @@ namespace ImageProcessorCore /// This method is not chainable. /// /// + /// The type of pixels contained within the image. /// The source image. Cannot be null. /// The target image width. /// The target image height. /// The processor to apply to the image. - /// The . - public static Image Process(this Image source, int width, int height, IImageSampler sampler) + /// The . + public static Image Process(this Image source, int width, int height, IImageSampler sampler) + where TPackedVector : IPackedVector, new() { return Process(source, width, height, source.Bounds, default(Rectangle), sampler); } @@ -102,6 +108,7 @@ namespace ImageProcessorCore /// This method does will resize the target image if the source and target rectangles are different. /// /// + /// The type of pixels contained within the image. /// The source image. Cannot be null. /// The target image width. /// The target image height. @@ -113,25 +120,27 @@ namespace ImageProcessorCore /// The image is scaled to fit the rectangle. /// /// The processor to apply to the image. - /// The . - public static Image Process(this Image source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler) + /// The . + public static Image Process(this Image source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler) + where TPackedVector : IPackedVector, new() { - return PerformAction(source, false, (sourceImage, targetImage) => sampler.Apply(targetImage, sourceImage, width, height, targetRectangle, sourceRectangle)); + return PerformAction(source, false, (sourceImage, targetImage) => sampler.Apply(targetImage, sourceImage, width, height, targetRectangle, sourceRectangle)); } /// /// Performs the given action on the source image. /// + /// The type of pixels contained within the image. /// The image to perform the action against. /// Whether to clone the image. /// The to perform against the image. - /// The . - /// Thrown if the has been disposed. - private static Image PerformAction(Image source, bool clone, Action action) + /// The . + private static Image PerformAction(Image source, bool clone, Action, ImageBase> action) + where TPackedVector : IPackedVector, new() { - Image transformedImage = clone - ? new Image(source) - : new Image + Image transformedImage = clone + ? new Image(source) + : new Image { // Several properties require copying // TODO: Check why we need to set these? @@ -145,8 +154,11 @@ namespace ImageProcessorCore for (int i = 0; i < source.Frames.Count; i++) { - ImageFrame sourceFrame = source.Frames[i]; - ImageFrame tranformedFrame = clone ? new ImageFrame(sourceFrame) : new ImageFrame { FrameDelay = sourceFrame.FrameDelay }; + ImageFrame sourceFrame = source.Frames[i]; + ImageFrame tranformedFrame = clone + ? new ImageFrame(sourceFrame) + : new ImageFrame { FrameDelay = sourceFrame.FrameDelay }; + action(sourceFrame, tranformedFrame); if (!clone) diff --git a/src/ImageProcessorCore/ImageFrame.cs b/src/ImageProcessorCore/ImageFrame.cs index 479d9dd9b0..cc65739c13 100644 --- a/src/ImageProcessorCore/ImageFrame.cs +++ b/src/ImageProcessorCore/ImageFrame.cs @@ -12,8 +12,15 @@ namespace ImageProcessorCore /// The packed vector containing pixel information. /// public class ImageFrame : ImageBase - where TPackedVector : IPackedVector + where TPackedVector : IPackedVector, new() { + /// + /// Initializes a new instance of the class. + /// + public ImageFrame() + { + } + /// /// Initializes a new instance of the class. /// @@ -26,7 +33,7 @@ namespace ImageProcessorCore } /// - public override IPixelAccessor Lock() + public override IPixelAccessor Lock() { return Bootstrapper.Instance.GetPixelAccessor(this); } diff --git a/src/ImageProcessorCore/ImageProcessor.cs b/src/ImageProcessorCore/ImageProcessor.cs index e7057a3f9c..0206e667ee 100644 --- a/src/ImageProcessorCore/ImageProcessor.cs +++ b/src/ImageProcessorCore/ImageProcessor.cs @@ -28,7 +28,7 @@ namespace ImageProcessorCore.Processors /// public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) - where TPackedVector : IPackedVector + where TPackedVector : IPackedVector, new() { try { @@ -50,7 +50,7 @@ namespace ImageProcessorCore.Processors /// public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) - where TPackedVector : IPackedVector + where TPackedVector : IPackedVector, new() { try { @@ -96,7 +96,7 @@ namespace ImageProcessorCore.Processors /// The structure that specifies the portion of the image object to draw. /// protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - where TPackedVector : IPackedVector + where TPackedVector : IPackedVector, new() { } @@ -120,7 +120,8 @@ namespace ImageProcessorCore.Processors /// The method keeps the source image unchanged and returns the /// the result of image process as new image. /// - protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) where TPackedVector : IPackedVector; + protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + where TPackedVector : IPackedVector, new(); /// /// This method is called after the process is applied to prepare the processor. @@ -136,7 +137,7 @@ namespace ImageProcessorCore.Processors /// The structure that specifies the portion of the image object to draw. /// protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - where TPackedVector : IPackedVector + where TPackedVector : IPackedVector, new() { } diff --git a/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs index cecc148b93..0645ff429f 100644 --- a/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs @@ -15,7 +15,8 @@ namespace ImageProcessorCore /// The image data is always stored in format, where the blue, green, red, and /// alpha values are 8 bit unsigned bytes. /// - public sealed unsafe class Bgra32PixelAccessor : IPixelAccessor + public sealed unsafe class Bgra32PixelAccessor : IPixelAccessor + { /// /// The position of the first pixel in the bitmap. @@ -46,7 +47,7 @@ namespace ImageProcessorCore /// /// The image to provide pixel access for. /// - public Bgra32PixelAccessor(IImageBase image) + public Bgra32PixelAccessor(IImageBase image) { Guard.NotNull(image, nameof(image)); Guard.MustBeGreaterThan(image.Width, 0, "image width"); @@ -55,7 +56,7 @@ namespace ImageProcessorCore this.Width = image.Width; this.Height = image.Height; - this.pixelsHandle = GCHandle.Alloc(image.Pixels, GCHandleType.Pinned); + this.pixelsHandle = GCHandle.Alloc(((ImageBase)image).Pixels, GCHandleType.Pinned); this.pixelsBase = (Bgra32*)this.pixelsHandle.AddrOfPinnedObject().ToPointer(); } @@ -89,7 +90,7 @@ namespace ImageProcessorCore /// than zero and smaller than the width of the pixel. /// /// The at the specified position. - public IPackedVector this[int x, int y] + public Bgra32 this[int x, int y] { get { @@ -120,7 +121,7 @@ namespace ImageProcessorCore throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); } #endif - *(this.pixelsBase + ((y * this.Width) + x)) = (Bgra32)value; + *(this.pixelsBase + ((y * this.Width) + x)) = value; } } diff --git a/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs index 10838726ca..f64fcf231d 100644 --- a/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs @@ -10,8 +10,18 @@ namespace ImageProcessorCore /// /// Encapsulates properties to provides per-pixel access to an images pixels. /// - public interface IPixelAccessor : IDisposable + public interface IPixelAccessor : IDisposable where TPackedVector : IPackedVector, new() { + /// + /// Gets the width of the image in pixels. + /// + int Width { get; } + + /// + /// Gets the height of the image in pixels. + /// + int Height { get; } + /// /// Gets or sets the pixel at the specified position. /// @@ -23,8 +33,8 @@ namespace ImageProcessorCore /// The y-coordinate of the pixel. Must be greater /// than zero and smaller than the width of the pixel. /// - /// The at the specified position. - IPackedVector this[int x, int y] + /// The at the specified position. + TPackedVector this[int x, int y] { get; set; diff --git a/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs b/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs index a80ea47778..2c3221ec6c 100644 --- a/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs +++ b/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs @@ -17,12 +17,14 @@ namespace ImageProcessorCore /// /// Calculates the target location and bounds to perform the resize operation against. /// + /// The type of pixels contained within the image. /// The source image. /// The resize options. /// /// The . /// - public static Rectangle CalculateTargetLocationAndBounds(ImageBase source, ResizeOptions options) + public static Rectangle CalculateTargetLocationAndBounds(ImageBase source, ResizeOptions options) + where TPackedVector : IPackedVector, new() { switch (options.Mode) { @@ -39,19 +41,21 @@ namespace ImageProcessorCore // Last case ResizeMode.Stretch: default: - return CalculateStretchRectangle(source, options); + return new Rectangle(0, 0, options.Size.Width, options.Size.Height); } } /// /// Calculates the target rectangle for crop mode. /// + /// The type of pixels contained within the image. /// The source image. /// The resize options. /// /// The . /// - private static Rectangle CalculateCropRectangle(ImageBase source, ResizeOptions options) + private static Rectangle CalculateCropRectangle(ImageBase source, ResizeOptions options) + where TPackedVector : IPackedVector, new() { int width = options.Size.Width; int height = options.Size.Height; @@ -163,12 +167,14 @@ namespace ImageProcessorCore /// /// Calculates the target rectangle for pad mode. /// + /// The type of pixels contained within the image. /// The source image. /// The resize options. /// /// The . /// - private static Rectangle CalculatePadRectangle(ImageBase source, ResizeOptions options) + private static Rectangle CalculatePadRectangle(ImageBase source, ResizeOptions options) + where TPackedVector : IPackedVector, new() { int width = options.Size.Width; int height = options.Size.Height; @@ -242,12 +248,14 @@ namespace ImageProcessorCore /// /// Calculates the target rectangle for box pad mode. /// + /// The type of pixels contained within the image. /// The source image. /// The resize options. /// /// The . /// - private static Rectangle CalculateBoxPadRectangle(ImageBase source, ResizeOptions options) + private static Rectangle CalculateBoxPadRectangle(ImageBase source, ResizeOptions options) + where TPackedVector : IPackedVector, new() { int width = options.Size.Width; int height = options.Size.Height; @@ -327,12 +335,14 @@ namespace ImageProcessorCore /// /// Calculates the target rectangle for max mode. /// + /// The type of pixels contained within the image. /// The source image. /// The resize options. /// /// The . /// - private static Rectangle CalculateMaxRectangle(ImageBase source, ResizeOptions options) + private static Rectangle CalculateMaxRectangle(ImageBase source, ResizeOptions options) + where TPackedVector : IPackedVector, new() { int width = options.Size.Width; int height = options.Size.Height; @@ -366,12 +376,14 @@ namespace ImageProcessorCore /// /// Calculates the target rectangle for min mode. /// + /// The type of pixels contained within the image. /// The source image. /// The resize options. /// /// The . /// - private static Rectangle CalculateMinRectangle(ImageBase source, ResizeOptions options) + private static Rectangle CalculateMinRectangle(ImageBase source, ResizeOptions options) + where TPackedVector : IPackedVector, new() { int width = options.Size.Width; int height = options.Size.Height; @@ -413,18 +425,5 @@ namespace ImageProcessorCore options.Size = new Size(width, height); return new Rectangle(0, 0, destinationWidth, destinationHeight); } - - /// - /// Calculates the target rectangle for stretch mode. - /// - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculateStretchRectangle(ImageBase source, ResizeOptions options) - { - return new Rectangle(0, 0, options.Size.Width, options.Size.Height); - } } } diff --git a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs index 95ead2bf04..a6e787714d 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs @@ -6,6 +6,7 @@ namespace ImageProcessorCore.Processors { using System; + using System.Numerics; using System.Threading.Tasks; /// @@ -16,7 +17,7 @@ namespace ImageProcessorCore.Processors /// /// The image used for storing the first pass pixels. /// - private Image firstPass; + private object firstPass; /// /// Initializes a new instance of the class. @@ -47,7 +48,7 @@ namespace ImageProcessorCore.Processors protected Weights[] VerticalWeights { get; set; } /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { if (!(this.Sampler is NearestNeighborResampler)) { @@ -55,11 +56,11 @@ namespace ImageProcessorCore.Processors this.VerticalWeights = this.PrecomputeWeights(targetRectangle.Height, sourceRectangle.Height); } - this.firstPass = new Image(target.Width, source.Height); + this.firstPass = new Image(target.Width, source.Height); } /// - protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { // Jump out, we'll deal with that later. if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) @@ -78,14 +79,17 @@ namespace ImageProcessorCore.Processors int endX = targetRectangle.Right; bool compand = this.Compand; + // TODO: Yuck! Fix this boxing nonsense + Image fp = (Image)this.firstPass; + if (this.Sampler is NearestNeighborResampler) { // Scaling factors float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width; float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height; - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, @@ -103,7 +107,6 @@ namespace ImageProcessorCore.Processors { // X coordinates of source points int originX = (int)((x - startX) * widthFactor); - targetPixels[x, y] = sourcePixels[originX, originY]; } } @@ -121,9 +124,9 @@ namespace ImageProcessorCore.Processors // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm // First process the columns. Since we are not using multiple threads startY and endY // are the upper and lower bounds of the source rectangle. - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor firstPassPixels = this.firstPass.Lock()) - using (PixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor firstPassPixels = fp.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( 0, @@ -140,25 +143,27 @@ namespace ImageProcessorCore.Processors Weight[] horizontalValues = this.HorizontalWeights[offsetX].Values; // Destination color components - Color destination = new Color(); + Vector4 destination = new Vector4(); for (int i = 0; i < sum; i++) { Weight xw = horizontalValues[i]; int originX = xw.Index; - Color sourceColor = compand - ? Color.Expand(sourcePixels[originX, y]) - : sourcePixels[originX, y]; + Vector4 sourceColor = sourcePixels[originX, y].ToVector4(); + //Color sourceColor = compand + // ? Color.Expand(sourcePixels[originX, y]) + // : sourcePixels[originX, y]; destination += sourceColor * xw.Value; } - if (compand) - { - destination = Color.Compress(destination); - } - - firstPassPixels[x, y] = destination; + //if (compand) + //{ + // destination = Color.Compress(destination); + //} + TPackedVector packed = new TPackedVector(); + packed.PackVector(destination); + firstPassPixels[x, y] = packed; } } }); @@ -179,25 +184,29 @@ namespace ImageProcessorCore.Processors for (int x = 0; x < width; x++) { // Destination color components - Color destination = new Color(); + Vector4 destination = new Vector4(); for (int i = 0; i < sum; i++) { Weight yw = verticalValues[i]; int originY = yw.Index; - Color sourceColor = compand - ? Color.Expand(firstPassPixels[x, originY]) - : firstPassPixels[x, originY]; + + Vector4 sourceColor = sourcePixels[x, originY].ToVector4(); + //Color sourceColor = compand + // ? Color.Expand(firstPassPixels[x, originY]) + // : firstPassPixels[x, originY]; destination += sourceColor * yw.Value; } - if (compand) - { - destination = Color.Compress(destination); - } + //if (compand) + //{ + // destination = Color.Compress(destination); + //} - targetPixels[x, y] = destination; + TPackedVector packed = new TPackedVector(); + packed.PackVector(destination); + targetPixels[x, y] = packed; } } @@ -208,7 +217,7 @@ namespace ImageProcessorCore.Processors } /// - protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { // Copy the pixels over. if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) diff --git a/src/ImageProcessorCore/Samplers/Resize.cs b/src/ImageProcessorCore/Samplers/Resize.cs index 2eadd7a11c..3d81f41da8 100644 --- a/src/ImageProcessorCore/Samplers/Resize.cs +++ b/src/ImageProcessorCore/Samplers/Resize.cs @@ -8,19 +8,21 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Resizes an image in accordance with the given . /// + /// The type of pixels contained within the image. /// The image to resize. /// The resize options. /// A delegate which is called as progress is made processing the image. - /// The + /// The /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, ResizeOptions options, ProgressEventHandler progressHandler = null) + public static Image Resize(this Image source, ResizeOptions options, ProgressEventHandler progressHandler = null) + where TPackedVector : IPackedVector, new() { // Ensure size is populated across both dimensions. if (options.Size.Width == 0 && options.Size.Height > 0) @@ -41,13 +43,15 @@ namespace ImageProcessorCore /// /// Resizes an image to the given width and height. /// + /// The type of pixels contained within the image. /// The image to resize. /// The target image width. /// The target image height. /// A delegate which is called as progress is made processing the image. - /// The + /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, ProgressEventHandler progressHandler = null) + public static Image Resize(this Image source, int width, int height, ProgressEventHandler progressHandler = null) + where TPackedVector : IPackedVector, new() { return Resize(source, width, height, new BicubicResampler(), false, progressHandler); } @@ -55,14 +59,16 @@ namespace ImageProcessorCore /// /// Resizes an image to the given width and height. /// + /// The type of pixels contained within the image. /// The image to resize. /// The target image width. /// The target image height. /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// A delegate which is called as progress is made processing the image. - /// The + /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, bool compand, ProgressEventHandler progressHandler = null) + public static Image Resize(this Image source, int width, int height, bool compand, ProgressEventHandler progressHandler = null) + where TPackedVector : IPackedVector, new() { return Resize(source, width, height, new BicubicResampler(), compand, progressHandler); } @@ -70,15 +76,17 @@ namespace ImageProcessorCore /// /// Resizes an image to the given width and height with the given sampler. /// + /// The type of pixels contained within the image. /// The image to resize. /// The target image width. /// The target image height. /// The to perform the resampling. /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// A delegate which is called as progress is made processing the image. - /// The + /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, IResampler sampler, bool compand, ProgressEventHandler progressHandler = null) + public static Image Resize(this Image source, int width, int height, IResampler sampler, bool compand, ProgressEventHandler progressHandler = null) + where TPackedVector : IPackedVector, new() { return Resize(source, width, height, sampler, source.Bounds, new Rectangle(0, 0, width, height), compand, progressHandler); } @@ -87,6 +95,7 @@ namespace ImageProcessorCore /// Resizes an image to the given width and height with the given sampler and /// source rectangle. /// + /// The type of pixels contained within the image. /// The image to resize. /// The target image width. /// The target image height. @@ -99,9 +108,10 @@ namespace ImageProcessorCore /// /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// A delegate which is called as progress is made processing the image. - /// The + /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand = false, ProgressEventHandler progressHandler = null) + public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand = false, ProgressEventHandler progressHandler = null) + where TPackedVector : IPackedVector, new() { if (width == 0 && height > 0) { diff --git a/tests/ImageProcessorCore.Benchmarks/Color/ColorEquality.cs b/tests/ImageProcessorCore.Benchmarks/Color/ColorEquality.cs index e9ef2c1835..1ab1d2c80c 100644 --- a/tests/ImageProcessorCore.Benchmarks/Color/ColorEquality.cs +++ b/tests/ImageProcessorCore.Benchmarks/Color/ColorEquality.cs @@ -1,22 +1,22 @@ -namespace ImageProcessorCore.Benchmarks -{ - using BenchmarkDotNet.Attributes; +//namespace ImageProcessorCore.Benchmarks +//{ +// using BenchmarkDotNet.Attributes; - using CoreColor = ImageProcessorCore.Color; - using SystemColor = System.Drawing.Color; +// using CoreColor = ImageProcessorCore.Color; +// using SystemColor = System.Drawing.Color; - public class ColorEquality - { - [Benchmark(Baseline = true, Description = "System.Drawing Color Equals")] - public bool SystemDrawingColorEqual() - { - return SystemColor.FromArgb(128, 128, 128, 128).Equals(SystemColor.FromArgb(128, 128, 128, 128)); - } +// public class ColorEquality +// { +// [Benchmark(Baseline = true, Description = "System.Drawing Color Equals")] +// public bool SystemDrawingColorEqual() +// { +// return SystemColor.FromArgb(128, 128, 128, 128).Equals(SystemColor.FromArgb(128, 128, 128, 128)); +// } - [Benchmark(Description = "ImageProcessorCore Color Equals")] - public bool ColorEqual() - { - return new CoreColor(.5f, .5f, .5f, .5f).Equals(new CoreColor(.5f, .5f, .5f, .5f)); - } - } -} +// [Benchmark(Description = "ImageProcessorCore Color Equals")] +// public bool ColorEqual() +// { +// return new CoreColor(.5f, .5f, .5f, .5f).Equals(new CoreColor(.5f, .5f, .5f, .5f)); +// } +// } +//} diff --git a/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs b/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs index 64d7896362..98bcf80493 100644 --- a/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs +++ b/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs @@ -4,8 +4,8 @@ using BenchmarkDotNet.Attributes; - using CoreColor = ImageProcessorCore.Color; - using CoreImage = ImageProcessorCore.Image; + //using CoreColor = ImageProcessorCore.Color; + //using CoreImage = ImageProcessorCore.Image; using SystemColor = System.Drawing.Color; public class GetSetPixel @@ -21,13 +21,13 @@ } [Benchmark(Description = "ImageProcessorCore GetSet Pixel")] - public CoreColor ResizeCore() + public Bgra32 ResizeCore() { - CoreImage image = new CoreImage(400, 400); - using (PixelAccessor imagePixels = image.Lock()) + Image image = new Image(400, 400); + using (IPixelAccessor imagePixels = image.Lock()) { - imagePixels[200, 200] = CoreColor.White; - return imagePixels[200, 200]; + imagePixels[200, 200] = new Bgra32(1, 1, 1, 1); + return (Bgra32)imagePixels[200, 200]; } } } diff --git a/tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs b/tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs index 4ffce9d48d..5eae5cdefb 100644 --- a/tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs +++ b/tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs @@ -1,40 +1,40 @@ -namespace ImageProcessorCore.Benchmarks -{ - using System.Drawing; - using System.Drawing.Drawing2D; +//namespace ImageProcessorCore.Benchmarks +//{ +// using System.Drawing; +// using System.Drawing.Drawing2D; - using BenchmarkDotNet.Attributes; - using CoreImage = ImageProcessorCore.Image; - using CoreSize = ImageProcessorCore.Size; +// using BenchmarkDotNet.Attributes; +// using CoreImage = ImageProcessorCore.Image; +// using CoreSize = ImageProcessorCore.Size; - public class Crop - { - [Benchmark(Baseline = true, Description = "System.Drawing Crop")] - public Size CropSystemDrawing() - { - using (Bitmap source = new Bitmap(400, 400)) - { - using (Bitmap destination = new Bitmap(100, 100)) - { - using (Graphics graphics = Graphics.FromImage(destination)) - { - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - graphics.CompositingQuality = CompositingQuality.HighQuality; - graphics.DrawImage(source, new Rectangle(0, 0, 100, 100), 0, 0, 100, 100, GraphicsUnit.Pixel); - } +// public class Crop +// { +// [Benchmark(Baseline = true, Description = "System.Drawing Crop")] +// public Size CropSystemDrawing() +// { +// using (Bitmap source = new Bitmap(400, 400)) +// { +// using (Bitmap destination = new Bitmap(100, 100)) +// { +// using (Graphics graphics = Graphics.FromImage(destination)) +// { +// graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; +// graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; +// graphics.CompositingQuality = CompositingQuality.HighQuality; +// graphics.DrawImage(source, new Rectangle(0, 0, 100, 100), 0, 0, 100, 100, GraphicsUnit.Pixel); +// } - return destination.Size; - } - } - } +// return destination.Size; +// } +// } +// } - [Benchmark(Description = "ImageProcessorCore Crop")] - public CoreSize CropResizeCore() - { - CoreImage image = new CoreImage(400, 400); - image.Crop(100, 100); - return new CoreSize(image.Width, image.Height); - } - } -} +// [Benchmark(Description = "ImageProcessorCore Crop")] +// public CoreSize CropResizeCore() +// { +// CoreImage image = new CoreImage(400, 400); +// image.Crop(100, 100); +// return new CoreSize(image.Width, image.Height); +// } +// } +//} diff --git a/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs b/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs index 479392cc8e..21a9033e0d 100644 --- a/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs +++ b/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs @@ -4,7 +4,6 @@ using System.Drawing.Drawing2D; using BenchmarkDotNet.Attributes; - using CoreImage = ImageProcessorCore.Image; using CoreSize = ImageProcessorCore.Size; public class Resize @@ -32,7 +31,7 @@ [Benchmark(Description = "ImageProcessorCore Resize")] public CoreSize ResizeCore() { - CoreImage image = new CoreImage(400, 400); + Image image = new Image(400, 400); image.Resize(100, 100); return new CoreSize(image.Width, image.Height); } diff --git a/tests/ImageProcessorCore.Tests/Colors/ColorConversionTests.cs b/tests/ImageProcessorCore.Tests/Colors/ColorConversionTests.cs deleted file mode 100644 index c7a252b03e..0000000000 --- a/tests/ImageProcessorCore.Tests/Colors/ColorConversionTests.cs +++ /dev/null @@ -1,493 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Tests -{ - using System; - using System.Diagnostics.CodeAnalysis; - - using Xunit; - - /// - /// Test conversion between the various color structs. - /// - /// - /// Output values have been compared with - /// and for accuracy. - /// - public class ColorConversionTests - { - /// - /// Tests the implicit conversion from to . - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", - Justification = "Reviewed. Suppression is OK here.")] - public void ColorToYCbCr() - { - // White - Color color = new Color(1, 1, 1); - YCbCr yCbCr = color; - - Assert.Equal(255, yCbCr.Y, 0); - Assert.Equal(128, yCbCr.Cb, 0); - Assert.Equal(128, yCbCr.Cr, 0); - - // Black - Color color2 = new Color(0, 0, 0); - YCbCr yCbCr2 = color2; - Assert.Equal(0, yCbCr2.Y, 0); - Assert.Equal(128, yCbCr2.Cb, 0); - Assert.Equal(128, yCbCr2.Cr, 0); - - // Grey - Color color3 = new Color(.5f, .5f, .5f); - YCbCr yCbCr3 = color3; - Assert.Equal(128, yCbCr3.Y, 0); - Assert.Equal(128, yCbCr3.Cb, 0); - Assert.Equal(128, yCbCr3.Cr, 0); - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", - Justification = "Reviewed. Suppression is OK here.")] - public void YCbCrToColor() - { - // White - YCbCr yCbCr = new YCbCr(255, 128, 128); - Color color = yCbCr; - - Assert.Equal(1f, color.R, 1); - Assert.Equal(1f, color.G, 1); - Assert.Equal(1f, color.B, 1); - Assert.Equal(1f, color.A, 1); - - // Black - YCbCr yCbCr2 = new YCbCr(0, 128, 128); - Color color2 = yCbCr2; - - Assert.Equal(0, color2.R); - Assert.Equal(0, color2.G); - Assert.Equal(0, color2.B); - Assert.Equal(1, color2.A); - - // Grey - YCbCr yCbCr3 = new YCbCr(128, 128, 128); - Color color3 = yCbCr3; - - Assert.Equal(.5f, color3.R, 1); - Assert.Equal(.5f, color3.G, 1); - Assert.Equal(.5f, color3.B, 1); - Assert.Equal(1f, color3.A, 1); - } - - /// - /// Tests the implicit conversion from to . - /// Comparison values obtained from - /// http://colormine.org/convert/rgb-to-xyz - /// - [Fact] - public void ColorToCieXyz() - { - // White - Color color = new Color(1, 1, 1); - CieXyz ciexyz = color; - - Assert.Equal(95.05f, ciexyz.X, 3); - Assert.Equal(100.0f, ciexyz.Y, 3); - Assert.Equal(108.900f, ciexyz.Z, 3); - - // Black - Color color2 = new Color(0, 0, 0); - CieXyz ciexyz2 = color2; - Assert.Equal(0, ciexyz2.X, 3); - Assert.Equal(0, ciexyz2.Y, 3); - Assert.Equal(0, ciexyz2.Z, 3); - - //// Grey - Color color3 = new Color(128 / 255f, 128 / 255f, 128 / 255f); - CieXyz ciexyz3 = color3; - Assert.Equal(20.518, ciexyz3.X, 3); - Assert.Equal(21.586, ciexyz3.Y, 3); - Assert.Equal(23.507, ciexyz3.Z, 3); - - //// Cyan - Color color4 = new Color(0, 1, 1); - CieXyz ciexyz4 = color4; - Assert.Equal(53.810f, ciexyz4.X, 3); - Assert.Equal(78.740f, ciexyz4.Y, 3); - Assert.Equal(106.970f, ciexyz4.Z, 3); - } - - /// - /// Tests the implicit conversion from to . - /// Comparison values obtained from - /// http://colormine.org/convert/rgb-to-xyz - /// - [Fact] - public void CieXyzToColor() - { - // Dark moderate pink. - CieXyz ciexyz = new CieXyz(13.337f, 9.297f, 14.727f); - Color color = ciexyz; - - Assert.Equal(128 / 255f, color.R, 3); - Assert.Equal(64 / 255f, color.G, 3); - Assert.Equal(106 / 255f, color.B, 3); - - // Ochre - CieXyz ciexyz2 = new CieXyz(31.787f, 26.147f, 4.885f); - Color color2 = ciexyz2; - - Assert.Equal(204 / 255f, color2.R, 3); - Assert.Equal(119 / 255f, color2.G, 3); - Assert.Equal(34 / 255f, color2.B, 3); - - //// White - CieXyz ciexyz3 = new CieXyz(0, 0, 0); - Color color3 = ciexyz3; - - Assert.Equal(0f, color3.R, 3); - Assert.Equal(0f, color3.G, 3); - Assert.Equal(0f, color3.B, 3); - - //// Check others. - //Random random = new Random(0); - //for (int i = 0; i < 1000; i++) - //{ - // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); - // CieXyz ciexyz4 = color4; - // Assert.Equal(color4, (Color)ciexyz4); - //} - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", - Justification = "Reviewed. Suppression is OK here.")] - public void ColorToHsv() - { - // Black - Color b = new Color(0, 0, 0); - Hsv h = b; - - Assert.Equal(0, h.H, 1); - Assert.Equal(0, h.S, 1); - Assert.Equal(0, h.V, 1); - - // White - Color color = new Color(1, 1, 1); - Hsv hsv = color; - - Assert.Equal(0f, hsv.H, 1); - Assert.Equal(0f, hsv.S, 1); - Assert.Equal(1f, hsv.V, 1); - - // Dark moderate pink. - Color color2 = new Color(128 / 255f, 64 / 255f, 106 / 255f); - Hsv hsv2 = color2; - - Assert.Equal(320.6f, hsv2.H, 1); - Assert.Equal(0.5f, hsv2.S, 1); - Assert.Equal(0.502f, hsv2.V, 2); - - // Ochre. - Color color3 = new Color(204 / 255f, 119 / 255f, 34 / 255f); - Hsv hsv3 = color3; - - Assert.Equal(30f, hsv3.H, 1); - Assert.Equal(0.833f, hsv3.S, 3); - Assert.Equal(0.8f, hsv3.V, 1); - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - public void HsvToColor() - { - // Dark moderate pink. - Hsv hsv = new Hsv(320.6f, 0.5f, 0.502f); - Color color = hsv; - - Assert.Equal(color.B, 106 / 255f, 1); - Assert.Equal(color.G, 64 / 255f, 1); - Assert.Equal(color.R, 128 / 255f, 1); - - // Ochre - Hsv hsv2 = new Hsv(30, 0.833f, 0.8f); - Color color2 = hsv2; - - Assert.Equal(color2.B, 34 / 255f, 1); - Assert.Equal(color2.G, 119 / 255f, 1); - Assert.Equal(color2.R, 204 / 255f, 1); - - // White - Hsv hsv3 = new Hsv(0, 0, 1); - Color color3 = hsv3; - - Assert.Equal(color3.B, 1, 1); - Assert.Equal(color3.G, 1, 1); - Assert.Equal(color3.R, 1, 1); - - // Check others. - //Random random = new Random(0); - //for (int i = 0; i < 1000; i++) - //{ - // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); - // Hsv hsv4 = color4; - // Assert.Equal(color4, (Color)hsv4); - //} - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", - Justification = "Reviewed. Suppression is OK here.")] - public void ColorToHsl() - { - // Black - Color b = new Color(0, 0, 0); - Hsl h = b; - - Assert.Equal(0, h.H, 1); - Assert.Equal(0, h.S, 1); - Assert.Equal(0, h.L, 1); - - // White - Color color = new Color(1, 1, 1); - Hsl hsl = color; - - Assert.Equal(0f, hsl.H, 1); - Assert.Equal(0f, hsl.S, 1); - Assert.Equal(1f, hsl.L, 1); - - // Dark moderate pink. - Color color2 = new Color(128 / 255f, 64 / 255f, 106 / 255f); - Hsl hsl2 = color2; - - Assert.Equal(320.6f, hsl2.H, 1); - Assert.Equal(0.33f, hsl2.S, 1); - Assert.Equal(0.376f, hsl2.L, 2); - - // Ochre. - Color color3 = new Color(204 / 255f, 119 / 255f, 34 / 255f); - Hsl hsl3 = color3; - - Assert.Equal(30f, hsl3.H, 1); - Assert.Equal(0.714f, hsl3.S, 3); - Assert.Equal(0.467f, hsl3.L, 3); - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - public void HslToColor() - { - // Dark moderate pink. - Hsl hsl = new Hsl(320.6f, 0.33f, 0.376f); - Color color = hsl; - - Assert.Equal(color.B, 106 / 255f, 1); - Assert.Equal(color.G, 64 / 255f, 1); - Assert.Equal(color.R, 128 / 255f, 1); - - // Ochre - Hsl hsl2 = new Hsl(30, 0.714f, 0.467f); - Color color2 = hsl2; - - Assert.Equal(color2.B, 34 / 255f, 1); - Assert.Equal(color2.G, 119 / 255f, 1); - Assert.Equal(color2.R, 204 / 255f, 1); - - // White - Hsl hsl3 = new Hsl(0, 0, 1); - Color color3 = hsl3; - - Assert.Equal(color3.B, 1, 1); - Assert.Equal(color3.G, 1, 1); - Assert.Equal(color3.R, 1, 1); - - // Check others. - //Random random = new Random(0); - //for (int i = 0; i < 1000; i++) - //{ - // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); - // Hsl hsl4 = color4; - // Assert.Equal(color4, (Color)hsl4); - //} - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", - Justification = "Reviewed. Suppression is OK here.")] - public void ColorToCmyk() - { - // White - Color color = new Color(1, 1, 1); - Cmyk cmyk = color; - - Assert.Equal(0, cmyk.C, 1); - Assert.Equal(0, cmyk.M, 1); - Assert.Equal(0, cmyk.Y, 1); - Assert.Equal(0, cmyk.K, 1); - - // Black - Color color2 = new Color(0, 0, 0); - Cmyk cmyk2 = color2; - Assert.Equal(0, cmyk2.C, 1); - Assert.Equal(0, cmyk2.M, 1); - Assert.Equal(0, cmyk2.Y, 1); - Assert.Equal(1, cmyk2.K, 1); - - // Grey - Color color3 = new Color(128 / 255f, 128 / 255f, 128 / 255f); - Cmyk cmyk3 = color3; - Assert.Equal(0f, cmyk3.C, 1); - Assert.Equal(0f, cmyk3.M, 1); - Assert.Equal(0f, cmyk3.Y, 1); - Assert.Equal(0.498, cmyk3.K, 2); // Checked with other online converters. - - // Cyan - Color color4 = new Color(0, 1, 1); - Cmyk cmyk4 = color4; - Assert.Equal(1, cmyk4.C, 1); - Assert.Equal(0f, cmyk4.M, 1); - Assert.Equal(0f, cmyk4.Y, 1); - Assert.Equal(0f, cmyk4.K, 1); - } - - /// - /// Tests the implicit conversion from to . - /// - [Fact] - public void CmykToColor() - { - // Dark moderate pink. - Cmyk cmyk = new Cmyk(0f, .5f, .171f, .498f); - Color color = cmyk; - - Assert.Equal(color.R, 128 / 255f, 1); - Assert.Equal(color.G, 64 / 255f, 1); - Assert.Equal(color.B, 106 / 255f, 1); - - // Ochre - Cmyk cmyk2 = new Cmyk(0, .416f, .833f, .199f); - Color color2 = cmyk2; - - Assert.Equal(color2.R, 204 / 255f, 1); - Assert.Equal(color2.G, 119 / 255f, 1); - Assert.Equal(color2.B, 34 / 255f, 1); - - // White - Cmyk cmyk3 = new Cmyk(0, 0, 0, 0); - Color color3 = cmyk3; - - Assert.Equal(color3.R, 1f, 1); - Assert.Equal(color3.G, 1f, 1); - Assert.Equal(color3.B, 1f, 1); - - // Check others. - //Random random = new Random(0); - //for (int i = 0; i < 1000; i++) - //{ - // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); - // Cmyk cmyk4 = color4; - // Assert.Equal(color4, (Color)cmyk4); - //} - } - - /// - /// Tests the implicit conversion from to . - /// Comparison values obtained from - /// http://colormine.org/convert/rgb-to-lab - /// - [Fact] - public void ColorToCieLab() - { - // White - Color color = new Color(1, 1, 1); - CieLab cielab = color; - - Assert.Equal(100, cielab.L, 3); - Assert.Equal(0.005, cielab.A, 3); - Assert.Equal(-0.010, cielab.B, 3); - - // Black - Color color2 = new Color(0, 0, 0); - CieLab cielab2 = color2; - Assert.Equal(0, cielab2.L, 3); - Assert.Equal(0, cielab2.A, 3); - Assert.Equal(0, cielab2.B, 3); - - //// Grey - Color color3 = new Color(128 / 255f, 128 / 255f, 128 / 255f); - CieLab cielab3 = color3; - Assert.Equal(53.585, cielab3.L, 3); - Assert.Equal(0.003, cielab3.A, 3); - Assert.Equal(-0.006, cielab3.B, 3); - - //// Cyan - Color color4 = new Color(0, 1, 1); - CieLab cielab4 = color4; - Assert.Equal(91.117, cielab4.L, 3); - Assert.Equal(-48.080, cielab4.A, 3); - Assert.Equal(-14.138, cielab4.B, 3); - } - - /// - /// Tests the implicit conversion from to . - /// - /// Comparison values obtained from - /// http://colormine.org/convert/rgb-to-lab - [Fact] - public void CieLabToColor() - { - // Dark moderate pink. - CieLab cielab = new CieLab(36.5492f, 33.3173f, -12.0615f); - Color color = cielab; - - Assert.Equal(color.R, 128 / 255f, 3); - Assert.Equal(color.G, 64 / 255f, 3); - Assert.Equal(color.B, 106 / 255f, 3); - - // Ochre - CieLab cielab2 = new CieLab(58.1758f, 27.3399f, 56.8240f); - Color color2 = cielab2; - - Assert.Equal(color2.R, 204 / 255f, 3); - Assert.Equal(color2.G, 119 / 255f, 3); - Assert.Equal(color2.B, 34 / 255f, 3); - - // White - CieLab cielab3 = new CieLab(0, 0, 0); - Color color3 = cielab3; - - Assert.Equal(color3.R, 0f, 3); - Assert.Equal(color3.G, 0f, 3); - Assert.Equal(color3.B, 0f, 3); - - // Check others. - //Random random = new Random(0); - //for (int i = 0; i < 1000; i++) - //{ - // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); - // CieLab cielab4 = color4; - // Assert.Equal(color4, (Color)cielab4); - //} - } - } -} diff --git a/tests/ImageProcessorCore.Tests/Colors/ColorSpacialTransformTests.cs b/tests/ImageProcessorCore.Tests/Colors/ColorSpacialTransformTests.cs deleted file mode 100644 index a4371cf54c..0000000000 --- a/tests/ImageProcessorCore.Tests/Colors/ColorSpacialTransformTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Tests -{ - using Xunit; - - public class ColorSpacialTransformTests - { - public class MultiplyTests - { - [Fact] - public void MultiplyBlendConvertsRedBackdropAndGreenOverlayToBlack() - { - var backdrop = Color.Red; - var overlay = Color.Green; - - var result = Color.Multiply(backdrop, overlay); - - Assert.Equal(Color.Black, result); - } - [Fact] - public void MultiplyBlendConvertsBlueBackdropAndWhiteOverlayToBlue() - { - var backdrop = Color.Blue; - var overlay = Color.White; - - var result = Color.Multiply(backdrop, overlay); - - Assert.Equal(Color.Blue, result); - } - [Fact] - public void MultiplyBlendConvertsBlueBackdropAndBlackOverlayToBlack() - { - var backdrop = Color.Blue; - var overlay = Color.Black; - - var result = Color.Multiply(backdrop, overlay); - - Assert.Equal(Color.Black, result); - } - [Fact] - public void MultiplyBlendConvertsBlueBackdropAndGrayOverlayToBlueBlack() - { - var backdrop = Color.Blue; - var overlay = Color.Gray; - - var result = Color.Multiply(backdrop, overlay); - - var expected = new Color(0, 0, 0.5f, 1); - - Assert.True(expected.AlmostEquals(result,.01f)); - } - } - } -} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Colors/ColorTests.cs b/tests/ImageProcessorCore.Tests/Colors/ColorTests.cs deleted file mode 100644 index e7d86012d0..0000000000 --- a/tests/ImageProcessorCore.Tests/Colors/ColorTests.cs +++ /dev/null @@ -1,108 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -using System.Numerics; - -namespace ImageProcessorCore.Tests -{ - using Xunit; - - /// - /// Tests the struct. - /// - public class ColorTests - { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Color color1 = new Color(0, 0, 0); - Color color2 = new Color(0, 0, 0, 1); - Color color3 = new Color("#000"); - Color color4 = new Color("#000000"); - Color color5 = new Color("#FF000000"); - - Assert.Equal(color1, color2); - Assert.Equal(color1, color3); - Assert.Equal(color1, color4); - Assert.Equal(color1, color5); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Color color1 = new Color(255, 0, 0, 255); - Color color2 = new Color(0, 0, 0, 255); - Color color3 = new Color("#000"); - Color color4 = new Color("#000000"); - Color color5 = new Color("#FF000000"); - - Assert.NotEqual(color1, color2); - Assert.NotEqual(color1, color3); - Assert.NotEqual(color1, color4); - Assert.NotEqual(color1, color5); - } - - /// - /// Tests whether the color constructor correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - Color color1 = new Color(1, .1f, .133f, .864f); - Assert.Equal(1, color1.R, 1); - Assert.Equal(.1f, color1.G, 1); - Assert.Equal(.133f, color1.B, 3); - Assert.Equal(.864f, color1.A, 3); - - Color color2 = new Color(1, .1f, .133f); - Assert.Equal(1, color2.R, 1); - Assert.Equal(.1f, color2.G, 1); - Assert.Equal(.133f, color2.B, 3); - Assert.Equal(1, color2.A, 1); - - Color color3 = new Color("#FF0000"); - Assert.Equal(1, color3.R, 1); - Assert.Equal(0, color3.G, 1); - Assert.Equal(0, color3.B, 3); - Assert.Equal(1, color3.A, 1); - - Color color4 = new Color(new Vector3(1, .1f, .133f)); - Assert.Equal(1, color4.R, 1); - Assert.Equal(.1f, color4.G, 1); - Assert.Equal(.133f, color4.B, 3); - Assert.Equal(1, color4.A, 1); - - Color color5 = new Color(new Vector3(1, .1f, .133f), .5f); - Assert.Equal(1, color5.R, 1); - Assert.Equal(.1f, color5.G, 1); - Assert.Equal(.133f, color5.B, 3); - Assert.Equal(.5f, color5.A, 1); - - Color color6 = new Color(new Vector4(1, .1f, .133f, .5f)); - Assert.Equal(1, color5.R, 1); - Assert.Equal(.1f, color6.G, 1); - Assert.Equal(.133f, color6.B, 3); - Assert.Equal(.5f, color6.A, 1); - } - - /// - /// Tests to see that in the input hex matches that of the output. - /// - [Fact] - public void ConvertHex() - { - const string First = "FF000000"; - Bgra32 bgra = new Color(0, 0, 0, 1); - string second = bgra.Bgra.ToString("X"); - Assert.Equal(First, second); - } - } -} diff --git a/tests/ImageProcessorCore.Tests/FileTestBase.cs b/tests/ImageProcessorCore.Tests/FileTestBase.cs index 100e74d0ff..05136a1c87 100644 --- a/tests/ImageProcessorCore.Tests/FileTestBase.cs +++ b/tests/ImageProcessorCore.Tests/FileTestBase.cs @@ -20,7 +20,7 @@ namespace ImageProcessorCore.Tests protected static readonly List Files = new List { //"TestImages/Formats/Jpg/Floorplan.jpeg", // Perf: Enable for local testing only - "TestImages/Formats/Jpg/Calliphora.jpg", + //"TestImages/Formats/Jpg/Calliphora.jpg", //"TestImages/Formats/Jpg/fb.jpg", // Perf: Enable for local testing only //"TestImages/Formats/Jpg/progress.jpg", // Perf: Enable for local testing only //"TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg", // Perf: Enable for local testing only @@ -28,8 +28,8 @@ namespace ImageProcessorCore.Tests // "TestImages/Formats/Bmp/neg_height.bmp", // Perf: Enable for local testing only //"TestImages/Formats/Png/blur.png", // Perf: Enable for local testing only //"TestImages/Formats/Png/indexed.png", // Perf: Enable for local testing only - "TestImages/Formats/Png/splash.png", - "TestImages/Formats/Gif/rings.gif", + //"TestImages/Formats/Png/splash.png", + //"TestImages/Formats/Gif/rings.gif", //"TestImages/Formats/Gif/giphy.gif" // Perf: Enable for local testing only }; diff --git a/tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs b/tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs deleted file mode 100644 index 6ff3a0f713..0000000000 --- a/tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Tests -{ - using System.Diagnostics; - using System.IO; - - using Formats; - - using Xunit; - - public class BitmapTests : FileTestBase - { - [Fact] - public void BitmapCanEncodeDifferentBitRates() - { - if (!Directory.Exists("TestOutput/Encode/Bitmap")) - { - Directory.CreateDirectory("TestOutput/Encode/Bitmap"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - Image image = new Image(stream); - string encodeFilename = "TestOutput/Encode/Bitmap/" + "24-" + Path.GetFileNameWithoutExtension(file) + ".bmp"; - - using (FileStream output = File.OpenWrite(encodeFilename)) - { - image.Save(output, new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel24 }); - } - - encodeFilename = "TestOutput/Encode/Bitmap/" + "32-" + Path.GetFileNameWithoutExtension(file) + ".bmp"; - - using (FileStream output = File.OpenWrite(encodeFilename)) - { - image.Save(output, new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel32 }); - } - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Formats/EncoderDecoderTests.cs b/tests/ImageProcessorCore.Tests/Formats/EncoderDecoderTests.cs deleted file mode 100644 index b26ef68093..0000000000 --- a/tests/ImageProcessorCore.Tests/Formats/EncoderDecoderTests.cs +++ /dev/null @@ -1,174 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Tests -{ - using System.Diagnostics; - using System.IO; - - using Formats; - - using Xunit; - using System.Linq; - - using ImageProcessorCore.Quantizers; - - public class EncoderDecoderTests : FileTestBase - { - [Fact] - public void ImageCanEncodeToString() - { - if (!Directory.Exists("TestOutput/ToString")) - { - Directory.CreateDirectory("TestOutput/ToString"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - Image image = new Image(stream); - string filename = "TestOutput/ToString/" + Path.GetFileNameWithoutExtension(file) + ".txt"; - File.WriteAllText(filename, image.ToString()); - } - } - } - - [Fact] - public void DecodeThenEncodeImageFromStreamShouldSucceed() - { - if (!Directory.Exists("TestOutput/Encode")) - { - Directory.CreateDirectory("TestOutput/Encode"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - Image image = new Image(stream); - string encodeFilename = "TestOutput/Encode/" + Path.GetFileName(file); - - using (FileStream output = File.OpenWrite(encodeFilename)) - { - image.Save(output); - } - } - } - } - - [Fact] - public void QuantizeImageShouldPreserveMaximumColorPrecision() - { - if (!Directory.Exists("TestOutput/Quantize")) - { - Directory.CreateDirectory("TestOutput/Quantize"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - Image image = new Image(stream); - IQuantizer quantizer = new OctreeQuantizer(); - QuantizedImage quantizedImage = quantizer.Quantize(image, 256); - - using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Octree-{Path.GetFileName(file)}")) - { - Image qi = quantizedImage.ToImage(); - qi.Save(output, image.CurrentImageFormat); - - } - - quantizer = new WuQuantizer(); - quantizedImage = quantizer.Quantize(image, 256); - - using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Wu-{Path.GetFileName(file)}")) - { - quantizedImage.ToImage().Save(output, image.CurrentImageFormat); - } - - quantizer = new PaletteQuantizer(); - quantizedImage = quantizer.Quantize(image, 256); - - using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Palette-{Path.GetFileName(file)}")) - { - Image qi = quantizedImage.ToImage(); - qi.Save(output, image.CurrentImageFormat); - } - } - } - } - - [Fact] - public void ImageCanConvertFormat() - { - if (!Directory.Exists("TestOutput/Format")) - { - Directory.CreateDirectory("TestOutput/Format"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.gif")) - { - image.SaveAsGif(output); - } - - using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.bmp")) - { - image.SaveAsBmp(output); - } - - using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.jpg")) - { - image.SaveAsJpeg(output); - } - - using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.png")) - { - image.SaveAsPng(output); - } - } - } - } - - [Fact] - public void ImageShouldPreservePixelByteOrderWhenSerialized() - { - if (!Directory.Exists("TestOutput/Serialized")) - { - Directory.CreateDirectory("TestOutput/Serialized"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - Image image = new Image(stream); - byte[] serialized; - using (MemoryStream memoryStream = new MemoryStream()) - { - image.Save(memoryStream); - memoryStream.Flush(); - serialized = memoryStream.ToArray(); - } - - using (MemoryStream memoryStream = new MemoryStream(serialized)) - { - Image image2 = new Image(memoryStream); - using (FileStream output = File.OpenWrite($"TestOutput/Serialized/{Path.GetFileName(file)}")) - { - image2.Save(output); - } - } - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Formats/PngTests.cs b/tests/ImageProcessorCore.Tests/Formats/PngTests.cs deleted file mode 100644 index e824a62f93..0000000000 --- a/tests/ImageProcessorCore.Tests/Formats/PngTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Tests -{ - using System.IO; - - using Formats; - - using Xunit; - - public class PngTests : FileTestBase - { - [Fact] - public void ImageCanSaveIndexedPng() - { - if (!Directory.Exists("TestOutput/Encode/Png")) - { - Directory.CreateDirectory("TestOutput/Encode/Png"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/Encode/Png/{Path.GetFileNameWithoutExtension(file)}.png")) - { - image.Quality = 256; - image.Save(output, new PngFormat()); - } - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Helpers/GuardTests.cs b/tests/ImageProcessorCore.Tests/Helpers/GuardTests.cs deleted file mode 100644 index bb93f928ac..0000000000 --- a/tests/ImageProcessorCore.Tests/Helpers/GuardTests.cs +++ /dev/null @@ -1,209 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright © James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// -// -// Tests the helper. -// -// -------------------------------------------------------------------------------------------------------------------- - -namespace ImageProcessorCore.Tests.Helpers -{ - using System; - using System.Diagnostics.CodeAnalysis; - - using Xunit; - - /// - /// Tests the helper. - /// - public class GuardTests - { - /// - /// Tests that the method throws when the argument is null. - /// - [Fact] - public void NotNullThrowsWhenArgIsNull() - { - Assert.Throws(() => Guard.NotNull(null, "foo")); - } - - /// - /// Tests that the method throws when the argument name is empty. - /// - [Fact] - public void NotNullThrowsWhenArgNameEmpty() - { - Assert.Throws(() => Guard.NotNull(null, string.Empty)); - } - - /// - /// Tests that the method throws when the argument is empty. - /// - [Fact] - [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1122:UseStringEmptyForEmptyStrings", Justification = "Reviewed. Suppression is OK here.")] - public void NotEmptyThrowsWhenEmpty() - { - Assert.Throws(() => Guard.NotNullOrEmpty("", string.Empty)); - } - - /// - /// Tests that the method throws when the argument is whitespace. - /// - [Fact] - public void NotEmptyThrowsWhenWhitespace() - { - Assert.Throws(() => Guard.NotNullOrEmpty(" ", string.Empty)); - } - - /// - /// Tests that the method throws when the argument name is null. - /// - [Fact] - public void NotEmptyThrowsWhenParameterNameNull() - { - Assert.Throws(() => Guard.NotNullOrEmpty(null, null)); - } - - /// - /// Tests that the method throws when the argument is greater. - /// - [Fact] - public void LessThanThrowsWhenArgIsGreater() - { - Assert.Throws(() => Guard.MustBeLessThan(1, 0, "foo")); - } - - /// - /// Tests that the method throws when the argument is equal. - /// - [Fact] - public void LessThanThrowsWhenArgIsEqual() - { - Assert.Throws(() => Guard.MustBeLessThan(1, 1, "foo")); - } - - /// - /// Tests that the method throws when the argument is greater. - /// - [Fact] - public void LessThanOrEqualToThrowsWhenArgIsGreater() - { - Assert.Throws(() => Guard.MustBeLessThanOrEqualTo(1, 0, "foo")); - } - - /// - /// Tests that the method does not throw when the argument - /// is less. - /// - [Fact] - public void LessThanOrEqualToDoesNotThrowWhenArgIsLess() - { - Exception ex = Record.Exception(() => Guard.MustBeLessThanOrEqualTo(0, 1, "foo")); - Assert.Null(ex); - } - - /// - /// Tests that the method does not throw when the argument - /// is equal. - /// - [Fact] - public void LessThanOrEqualToDoesNotThrowWhenArgIsEqual() - { - Exception ex = Record.Exception(() => Guard.MustBeLessThanOrEqualTo(1, 1, "foo")); - Assert.Equal(1, 1); - Assert.Null(ex); - } - - /// - /// Tests that the method throws when the argument is greater. - /// - [Fact] - public void GreaterThanThrowsWhenArgIsLess() - { - Assert.Throws(() => Guard.MustBeGreaterThan(0, 1, "foo")); - } - - /// - /// Tests that the method throws when the argument is greater. - /// - [Fact] - public void GreaterThanThrowsWhenArgIsEqual() - { - Assert.Throws(() => Guard.MustBeGreaterThan(1, 1, "foo")); - } - - /// - /// Tests that the method throws when the argument name is greater. - /// - [Fact] - public void GreaterThanOrEqualToThrowsWhenArgIsLess() - { - Assert.Throws(() => Guard.MustBeGreaterThanOrEqualTo(0, 1, "foo")); - } - - /// - /// Tests that the method does not throw when the argument - /// is less. - /// - [Fact] - public void GreaterThanOrEqualToDoesNotThrowWhenArgIsGreater() - { - Exception ex = Record.Exception(() => Guard.MustBeGreaterThanOrEqualTo(1, 0, "foo")); - Assert.Null(ex); - } - - /// - /// Tests that the method does not throw when the argument - /// is equal. - /// - [Fact] - public void GreaterThanOrEqualToDoesNotThrowWhenArgIsEqual() - { - Exception ex = Record.Exception(() => Guard.MustBeGreaterThanOrEqualTo(1, 1, "foo")); - Assert.Equal(1, 1); - Assert.Null(ex); - } - - /// - /// Tests that the method throws when the argument is less. - /// - [Fact] - public void BetweenOrEqualToThrowsWhenArgIsLess() - { - Assert.Throws(() => Guard.MustBeBetweenOrEqualTo(-2, -1, 1, "foo")); - } - - /// - /// Tests that the method throws when the argument is greater. - /// - [Fact] - public void BetweenOrEqualToThrowsWhenArgIsGreater() - { - Assert.Throws(() => Guard.MustBeBetweenOrEqualTo(2, -1, 1, "foo")); - } - - /// - /// Tests that the method does not throw when the argument - /// is equal. - /// - [Fact] - public void BetweenOrEqualToDoesNotThrowWhenArgIsEqual() - { - Exception ex = Record.Exception(() => Guard.MustBeBetweenOrEqualTo(1, 1, 1, "foo")); - Assert.Null(ex); - } - - /// - /// Tests that the method does not throw when the argument - /// is equal. - /// - [Fact] - public void BetweenOrEqualToDoesNotThrowWhenArgIsBetween() - { - Exception ex = Record.Exception(() => Guard.MustBeBetweenOrEqualTo(0, -1, 1, "foo")); - Assert.Null(ex); - } - } -} diff --git a/tests/ImageProcessorCore.Tests/Numerics/PointTests.cs b/tests/ImageProcessorCore.Tests/Numerics/PointTests.cs deleted file mode 100644 index 0108fa5905..0000000000 --- a/tests/ImageProcessorCore.Tests/Numerics/PointTests.cs +++ /dev/null @@ -1,55 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright © James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// -// -// Tests the struct. -// -// -------------------------------------------------------------------------------------------------------------------- - -namespace ImageProcessorCore.Tests -{ - using Xunit; - - /// - /// Tests the struct. - /// - public class PointTests - { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Point first = new Point(100, 100); - Point second = new Point(100, 100); - - Assert.Equal(first, second); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Point first = new Point(0, 100); - Point second = new Point(100, 100); - - Assert.NotEqual(first, second); - } - - /// - /// Tests whether the point constructor correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - Point first = new Point(4, 5); - Assert.Equal(4, first.X); - Assert.Equal(5, first.Y); - } - } -} diff --git a/tests/ImageProcessorCore.Tests/Numerics/RectangleTests.cs b/tests/ImageProcessorCore.Tests/Numerics/RectangleTests.cs deleted file mode 100644 index 3b76bfa51b..0000000000 --- a/tests/ImageProcessorCore.Tests/Numerics/RectangleTests.cs +++ /dev/null @@ -1,71 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright © James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// -// -// Tests the struct. -// -// -------------------------------------------------------------------------------------------------------------------- - -namespace ImageProcessorCore.Tests -{ - using Xunit; - - /// - /// Tests the struct. - /// - public class RectangleTests - { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Rectangle first = new Rectangle(1, 1, 100, 100); - Rectangle second = new Rectangle(1, 1, 100, 100); - - Assert.Equal(first, second); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Rectangle first = new Rectangle(1, 1, 0, 100); - Rectangle second = new Rectangle(1, 1, 100, 100); - - Assert.NotEqual(first, second); - } - - /// - /// Tests whether the rectangle constructors correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - Rectangle first = new Rectangle(1, 1, 50, 100); - Assert.Equal(1, first.X); - Assert.Equal(1, first.Y); - Assert.Equal(50, first.Width); - Assert.Equal(100, first.Height); - Assert.Equal(1, first.Top); - Assert.Equal(51, first.Right); - Assert.Equal(101, first.Bottom); - Assert.Equal(1, first.Left); - - Rectangle second = new Rectangle(new Point(1, 1), new Size(50, 100)); - Assert.Equal(1, second.X); - Assert.Equal(1, second.Y); - Assert.Equal(50, second.Width); - Assert.Equal(100, second.Height); - Assert.Equal(1, second.Top); - Assert.Equal(51, second.Right); - Assert.Equal(101, second.Bottom); - Assert.Equal(1, second.Left); - } - } -} diff --git a/tests/ImageProcessorCore.Tests/Numerics/SizeTests.cs b/tests/ImageProcessorCore.Tests/Numerics/SizeTests.cs deleted file mode 100644 index 3d90b2bceb..0000000000 --- a/tests/ImageProcessorCore.Tests/Numerics/SizeTests.cs +++ /dev/null @@ -1,55 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright © James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// -// -// Tests the struct. -// -// -------------------------------------------------------------------------------------------------------------------- - -namespace ImageProcessorCore.Tests -{ - using Xunit; - - /// - /// Tests the struct. - /// - public class SizeTests - { - /// - /// Tests the equality operators for equality. - /// - [Fact] - public void AreEqual() - { - Size first = new Size(100, 100); - Size second = new Size(100, 100); - - Assert.Equal(first, second); - } - - /// - /// Tests the equality operators for inequality. - /// - [Fact] - public void AreNotEqual() - { - Size first = new Size(0, 100); - Size second = new Size(100, 100); - - Assert.NotEqual(first, second); - } - - /// - /// Tests whether the size constructor correctly assign properties. - /// - [Fact] - public void ConstructorAssignsProperties() - { - Size first = new Size(4, 5); - Assert.Equal(4, first.Width); - Assert.Equal(5, first.Height); - } - } -} diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/FilterTests.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/FilterTests.cs deleted file mode 100644 index e9414393f3..0000000000 --- a/tests/ImageProcessorCore.Tests/Processors/Filters/FilterTests.cs +++ /dev/null @@ -1,89 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Tests -{ - using System.Diagnostics; - using System.IO; - - using Processors; - - using Xunit; - public class FilterTests : FileTestBase - { - public static readonly TheoryData Filters = new TheoryData - { - { "Brightness-50", new BrightnessProcessor(50) }, - { "Brightness--50", new BrightnessProcessor(-50) }, - { "Contrast-50", new ContrastProcessor(50) }, - { "Contrast--50", new ContrastProcessor(-50) }, - { "BackgroundColor", new BackgroundColorProcessor(new Color(243 / 255f, 87 / 255f, 161 / 255f,.5f))}, - { "Blend", new BlendProcessor(new Image(File.OpenRead("TestImages/Formats/Bmp/Car.bmp")),50)}, - { "Saturation-50", new SaturationProcessor(50) }, - { "Saturation--50", new SaturationProcessor(-50) }, - { "Alpha--50", new AlphaProcessor(50) }, - { "Invert", new InvertProcessor() }, - { "Sepia", new SepiaProcessor() }, - { "BlackWhite", new BlackWhiteProcessor() }, - { "Lomograph", new LomographProcessor() }, - { "Polaroid", new PolaroidProcessor() }, - { "Kodachrome", new KodachromeProcessor() }, - { "GreyscaleBt709", new GreyscaleBt709Processor() }, - { "GreyscaleBt601", new GreyscaleBt601Processor() }, - { "Kayyali", new KayyaliProcessor() }, - { "Kirsch", new KirschProcessor() }, - { "Laplacian3X3", new Laplacian3X3Processor() }, - { "Laplacian5X5", new Laplacian5X5Processor() }, - { "LaplacianOfGaussian", new LaplacianOfGaussianProcessor() }, - { "Prewitt", new PrewittProcessor() }, - { "RobertsCross", new RobertsCrossProcessor() }, - { "Scharr", new ScharrProcessor() }, - { "Sobel", new SobelProcessor {Greyscale = true} }, - { "Pixelate", new PixelateProcessor(8) }, - { "GuassianBlur", new GuassianBlurProcessor(10) }, - { "GuassianSharpen", new GuassianSharpenProcessor(10) }, - { "Hue-180", new HueProcessor(180) }, - { "Hue--180", new HueProcessor(-180) }, - { "BoxBlur", new BoxBlurProcessor(10) }, - { "Vignette", new VignetteProcessor() }, - { "Protanopia", new ProtanopiaProcessor() }, - { "Protanomaly", new ProtanomalyProcessor() }, - { "Deuteranopia", new DeuteranopiaProcessor() }, - { "Deuteranomaly", new DeuteranomalyProcessor() }, - { "Tritanopia", new TritanopiaProcessor() }, - { "Tritanomaly", new TritanomalyProcessor() }, - { "Achromatopsia", new AchromatopsiaProcessor() }, - { "Achromatomaly", new AchromatomalyProcessor() } - - }; - - [Theory] - [MemberData("Filters")] - public void FilterImage(string name, IImageProcessor processor) - { - if (!Directory.Exists("TestOutput/Filter")) - { - Directory.CreateDirectory("TestOutput/Filter"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - Stopwatch watch = Stopwatch.StartNew(); - - Image image = new Image(stream); - string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); - using (FileStream output = File.OpenWrite($"TestOutput/Filter/{Path.GetFileName(filename)}")) - { - processor.OnProgress += this.ProgressUpdate; - image.Process(processor).Save(output); - processor.OnProgress -= this.ProgressUpdate; - } - } - } - } - } -} diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs index e81607b585..96f6da0daf 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs @@ -36,7 +36,7 @@ namespace ImageProcessorCore.Tests public static readonly TheoryData Samplers = new TheoryData { { "Resize", new ResizeProcessor(new BicubicResampler()) }, - { "Crop", new CropProcessor() } + //{ "Crop", new CropProcessor() } }; public static readonly TheoryData RotateFlips = new TheoryData @@ -48,56 +48,56 @@ namespace ImageProcessorCore.Tests { RotateType.Rotate270, FlipType.None }, }; - [Theory] - [MemberData("Samplers")] - public void SampleImage(string name, IImageSampler processor) - { - if (!Directory.Exists("TestOutput/Sample")) - { - Directory.CreateDirectory("TestOutput/Sample"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - Image image = new Image(stream); - string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); - - using (FileStream output = File.OpenWrite($"TestOutput/Sample/{ Path.GetFileName(filename) }")) - { - processor.OnProgress += this.ProgressUpdate; - image = image.Process(image.Width / 2, image.Height / 2, processor); - image.Save(output); - processor.OnProgress -= this.ProgressUpdate; - } - } - } - } - - [Fact] - public void ImageShouldPad() - { - if (!Directory.Exists("TestOutput/Pad")) - { - Directory.CreateDirectory("TestOutput/Pad"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileName(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/Pad/{filename}")) - { - image.Pad(image.Width + 50, image.Height + 50, this.ProgressUpdate) - .Save(output); - } - } - } - } + //[Theory] + //[MemberData("Samplers")] + //public void SampleImage(string name, IImageSampler processor) + //{ + // if (!Directory.Exists("TestOutput/Sample")) + // { + // Directory.CreateDirectory("TestOutput/Sample"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // Image image = new Image(stream); + // string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); + + // using (FileStream output = File.OpenWrite($"TestOutput/Sample/{ Path.GetFileName(filename) }")) + // { + // processor.OnProgress += this.ProgressUpdate; + // image = image.Process(image.Width / 2, image.Height / 2, processor); + // image.Save(output); + // processor.OnProgress -= this.ProgressUpdate; + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldPad() + //{ + // if (!Directory.Exists("TestOutput/Pad")) + // { + // Directory.CreateDirectory("TestOutput/Pad"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileName(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/Pad/{filename}")) + // { + // image.Pad(image.Width + 50, image.Height + 50, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} [Theory] [MemberData("ReSamplers")] @@ -114,7 +114,7 @@ namespace ImageProcessorCore.Tests { string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); - Image image = new Image(stream); + Image image = new Image(stream); using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) { image.Resize(image.Width / 2, image.Height / 2, sampler, false, this.ProgressUpdate) @@ -124,403 +124,403 @@ namespace ImageProcessorCore.Tests } } - [Fact] - public void ImageShouldResizeWidthAndKeepAspect() - { - if (!Directory.Exists("TestOutput/Resize")) - { - Directory.CreateDirectory("TestOutput/Resize"); - } - - var name = "FixedWidth"; - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) - { - image.Resize(image.Width / 3, 0, new TriangleResampler(), false, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldResizeHeightAndKeepAspect() - { - if (!Directory.Exists("TestOutput/Resize")) - { - Directory.CreateDirectory("TestOutput/Resize"); - } - - string name = "FixedHeight"; - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) - { - image.Resize(0, image.Height / 3, new TriangleResampler(), false, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldResizeWithCropMode() - { - if (!Directory.Exists("TestOutput/ResizeCrop")) - { - Directory.CreateDirectory("TestOutput/ResizeCrop"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileName(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/ResizeCrop/{filename}")) - { - ResizeOptions options = new ResizeOptions() - { - Size = new Size(image.Width / 2, image.Height) - }; - - image.Resize(options, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldResizeWithPadMode() - { - if (!Directory.Exists("TestOutput/ResizePad")) - { - Directory.CreateDirectory("TestOutput/ResizePad"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileName(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/ResizePad/{filename}")) - { - ResizeOptions options = new ResizeOptions() - { - Size = new Size(image.Width + 200, image.Height), - Mode = ResizeMode.Pad - }; - - image.Resize(options, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldResizeWithBoxPadMode() - { - if (!Directory.Exists("TestOutput/ResizeBoxPad")) - { - Directory.CreateDirectory("TestOutput/ResizeBoxPad"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileName(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/ResizeBoxPad/{filename}")) - { - ResizeOptions options = new ResizeOptions() - { - Size = new Size(image.Width + 200, image.Height + 200), - Mode = ResizeMode.BoxPad - }; - - image.Resize(options, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldResizeWithMaxMode() - { - if (!Directory.Exists("TestOutput/ResizeMax")) - { - Directory.CreateDirectory("TestOutput/ResizeMax"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileName(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/ResizeMax/{filename}")) - { - ResizeOptions options = new ResizeOptions() - { - Size = new Size(300, 300), - Mode = ResizeMode.Max, - //Sampler = new NearestNeighborResampler() - }; - - image.Resize(options, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldResizeWithMinMode() - { - if (!Directory.Exists("TestOutput/ResizeMin")) - { - Directory.CreateDirectory("TestOutput/ResizeMin"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileName(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/ResizeMin/{filename}")) - { - ResizeOptions options = new ResizeOptions() - { - Size = new Size(image.Width - 50, image.Height - 25), - Mode = ResizeMode.Min - }; - - image.Resize(options, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldResizeWithStretchMode() - { - if (!Directory.Exists("TestOutput/ResizeStretch")) - { - Directory.CreateDirectory("TestOutput/ResizeStretch"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileName(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/ResizeStretch/{filename}")) - { - ResizeOptions options = new ResizeOptions() - { - Size = new Size(image.Width - 200, image.Height), - Mode = ResizeMode.Stretch - }; - - image.Resize(options, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldNotDispose() - { - if (!Directory.Exists("TestOutput/Dispose")) - { - Directory.CreateDirectory("TestOutput/Dispose"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileName(file); - - Image image = new Image(stream); - image = image.BackgroundColor(Color.RebeccaPurple); - using (FileStream output = File.OpenWrite($"TestOutput/Dispose/{filename}")) - { - ResizeOptions options = new ResizeOptions() - { - Size = new Size(image.Width - 10, image.Height), - Mode = ResizeMode.Stretch - }; - - image.Resize(options, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Theory] - [MemberData("RotateFlips")] - public void ImageShouldRotateFlip(RotateType rotateType, FlipType flipType) - { - if (!Directory.Exists("TestOutput/RotateFlip")) - { - Directory.CreateDirectory("TestOutput/RotateFlip"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileNameWithoutExtension(file) + "-" + rotateType + flipType + Path.GetExtension(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/RotateFlip/{filename}")) - { - image.RotateFlip(rotateType, flipType, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldRotate() - { - if (!Directory.Exists("TestOutput/Rotate")) - { - Directory.CreateDirectory("TestOutput/Rotate"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileName(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}")) - { - image.Rotate(-170, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldSkew() - { - if (!Directory.Exists("TestOutput/Skew")) - { - Directory.CreateDirectory("TestOutput/Skew"); - } - - // Matches live example http://www.w3schools.com/css/tryit.asp?filename=trycss3_transform_skew - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileName(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/Skew/{filename}")) - { - image.Skew(-20, -10, this.ProgressUpdate) - .Save(output); - } - } - } - } - - [Fact] - public void ImageShouldEntropyCrop() - { - if (!Directory.Exists("TestOutput/EntropyCrop")) - { - Directory.CreateDirectory("TestOutput/EntropyCrop"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - string filename = Path.GetFileNameWithoutExtension(file) + "-EntropyCrop" + Path.GetExtension(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/EntropyCrop/{filename}")) - { - image.EntropyCrop(.5f, this.ProgressUpdate).Save(output); - } - } - } - } - - [Fact] - public void ImageShouldCrop() - { - if (!Directory.Exists("TestOutput/Crop")) - { - Directory.CreateDirectory("TestOutput/Crop"); - } - - foreach (string file in Files) - { - using (FileStream stream = File.OpenRead(file)) - { - - string filename = Path.GetFileNameWithoutExtension(file) + "-Crop" + Path.GetExtension(file); - - Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/Crop/{filename}")) - { - image.Crop(image.Width / 2, image.Height / 2, this.ProgressUpdate).Save(output); - } - } - } - } - - [Theory] - [InlineData(-2, 0)] - [InlineData(-1, 0)] - [InlineData(0, 1)] - [InlineData(1, 0)] - [InlineData(2, 0)] - [InlineData(2, 0)] - public static void Lanczos3WindowOscillatesCorrectly(float x, float expected) - { - Lanczos3Resampler sampler = new Lanczos3Resampler(); - float result = sampler.GetValue(x); - - Assert.Equal(result, expected); - } + //[Fact] + //public void ImageShouldResizeWidthAndKeepAspect() + //{ + // if (!Directory.Exists("TestOutput/Resize")) + // { + // Directory.CreateDirectory("TestOutput/Resize"); + // } + + // var name = "FixedWidth"; + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) + // { + // image.Resize(image.Width / 3, 0, new TriangleResampler(), false, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldResizeHeightAndKeepAspect() + //{ + // if (!Directory.Exists("TestOutput/Resize")) + // { + // Directory.CreateDirectory("TestOutput/Resize"); + // } + + // string name = "FixedHeight"; + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) + // { + // image.Resize(0, image.Height / 3, new TriangleResampler(), false, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldResizeWithCropMode() + //{ + // if (!Directory.Exists("TestOutput/ResizeCrop")) + // { + // Directory.CreateDirectory("TestOutput/ResizeCrop"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileName(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/ResizeCrop/{filename}")) + // { + // ResizeOptions options = new ResizeOptions() + // { + // Size = new Size(image.Width / 2, image.Height) + // }; + + // image.Resize(options, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldResizeWithPadMode() + //{ + // if (!Directory.Exists("TestOutput/ResizePad")) + // { + // Directory.CreateDirectory("TestOutput/ResizePad"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileName(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/ResizePad/{filename}")) + // { + // ResizeOptions options = new ResizeOptions() + // { + // Size = new Size(image.Width + 200, image.Height), + // Mode = ResizeMode.Pad + // }; + + // image.Resize(options, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldResizeWithBoxPadMode() + //{ + // if (!Directory.Exists("TestOutput/ResizeBoxPad")) + // { + // Directory.CreateDirectory("TestOutput/ResizeBoxPad"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileName(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/ResizeBoxPad/{filename}")) + // { + // ResizeOptions options = new ResizeOptions() + // { + // Size = new Size(image.Width + 200, image.Height + 200), + // Mode = ResizeMode.BoxPad + // }; + + // image.Resize(options, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldResizeWithMaxMode() + //{ + // if (!Directory.Exists("TestOutput/ResizeMax")) + // { + // Directory.CreateDirectory("TestOutput/ResizeMax"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileName(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/ResizeMax/{filename}")) + // { + // ResizeOptions options = new ResizeOptions() + // { + // Size = new Size(300, 300), + // Mode = ResizeMode.Max, + // //Sampler = new NearestNeighborResampler() + // }; + + // image.Resize(options, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldResizeWithMinMode() + //{ + // if (!Directory.Exists("TestOutput/ResizeMin")) + // { + // Directory.CreateDirectory("TestOutput/ResizeMin"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileName(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/ResizeMin/{filename}")) + // { + // ResizeOptions options = new ResizeOptions() + // { + // Size = new Size(image.Width - 50, image.Height - 25), + // Mode = ResizeMode.Min + // }; + + // image.Resize(options, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldResizeWithStretchMode() + //{ + // if (!Directory.Exists("TestOutput/ResizeStretch")) + // { + // Directory.CreateDirectory("TestOutput/ResizeStretch"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileName(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/ResizeStretch/{filename}")) + // { + // ResizeOptions options = new ResizeOptions() + // { + // Size = new Size(image.Width - 200, image.Height), + // Mode = ResizeMode.Stretch + // }; + + // image.Resize(options, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldNotDispose() + //{ + // if (!Directory.Exists("TestOutput/Dispose")) + // { + // Directory.CreateDirectory("TestOutput/Dispose"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileName(file); + + // Image image = new Image(stream); + // image = image.BackgroundColor(Color.RebeccaPurple); + // using (FileStream output = File.OpenWrite($"TestOutput/Dispose/{filename}")) + // { + // ResizeOptions options = new ResizeOptions() + // { + // Size = new Size(image.Width - 10, image.Height), + // Mode = ResizeMode.Stretch + // }; + + // image.Resize(options, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Theory] + //[MemberData("RotateFlips")] + //public void ImageShouldRotateFlip(RotateType rotateType, FlipType flipType) + //{ + // if (!Directory.Exists("TestOutput/RotateFlip")) + // { + // Directory.CreateDirectory("TestOutput/RotateFlip"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileNameWithoutExtension(file) + "-" + rotateType + flipType + Path.GetExtension(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/RotateFlip/{filename}")) + // { + // image.RotateFlip(rotateType, flipType, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldRotate() + //{ + // if (!Directory.Exists("TestOutput/Rotate")) + // { + // Directory.CreateDirectory("TestOutput/Rotate"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileName(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}")) + // { + // image.Rotate(-170, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldSkew() + //{ + // if (!Directory.Exists("TestOutput/Skew")) + // { + // Directory.CreateDirectory("TestOutput/Skew"); + // } + + // // Matches live example http://www.w3schools.com/css/tryit.asp?filename=trycss3_transform_skew + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileName(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/Skew/{filename}")) + // { + // image.Skew(-20, -10, this.ProgressUpdate) + // .Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldEntropyCrop() + //{ + // if (!Directory.Exists("TestOutput/EntropyCrop")) + // { + // Directory.CreateDirectory("TestOutput/EntropyCrop"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + // string filename = Path.GetFileNameWithoutExtension(file) + "-EntropyCrop" + Path.GetExtension(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/EntropyCrop/{filename}")) + // { + // image.EntropyCrop(.5f, this.ProgressUpdate).Save(output); + // } + // } + // } + //} + + //[Fact] + //public void ImageShouldCrop() + //{ + // if (!Directory.Exists("TestOutput/Crop")) + // { + // Directory.CreateDirectory("TestOutput/Crop"); + // } + + // foreach (string file in Files) + // { + // using (FileStream stream = File.OpenRead(file)) + // { + + // string filename = Path.GetFileNameWithoutExtension(file) + "-Crop" + Path.GetExtension(file); + + // Image image = new Image(stream); + // using (FileStream output = File.OpenWrite($"TestOutput/Crop/{filename}")) + // { + // image.Crop(image.Width / 2, image.Height / 2, this.ProgressUpdate).Save(output); + // } + // } + // } + //} + + //[Theory] + //[InlineData(-2, 0)] + //[InlineData(-1, 0)] + //[InlineData(0, 1)] + //[InlineData(1, 0)] + //[InlineData(2, 0)] + //[InlineData(2, 0)] + //public static void Lanczos3WindowOscillatesCorrectly(float x, float expected) + //{ + // Lanczos3Resampler sampler = new Lanczos3Resampler(); + // float result = sampler.GetValue(x); + + // Assert.Equal(result, expected); + //} } } From 4608c5c87d5a1acacd69dbca4d6999d64e1e019a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 8 Jul 2016 01:54:37 +1000 Subject: [PATCH 09/91] F**ck me it works! Former-commit-id: 5349fc67232b2863c8e5d6fa880ffd1bd415efa3 Former-commit-id: 5e92562e0c7131b65d41ff81ba48bbc0f83b60d8 Former-commit-id: 79570635457be4888019984092c73105a159848d --- src/ImageProcessorCore/Bootstrapper.cs | 56 +++---- .../Formats/Bmp/BmpEncoder.cs | 2 +- .../Formats/Bmp/BmpEncoderCore.cs | 4 +- .../Formats/IImageDecoder.cs | 3 +- .../Formats/IImageEncoder.cs | 3 +- src/ImageProcessorCore/IImageBase.cs | 4 +- src/ImageProcessorCore/Image.cs | 4 +- src/ImageProcessorCore/ImageBase.cs | 2 +- src/ImageProcessorCore/ImageFrame.cs | 4 +- .../PixelAccessor/Bgra32PixelAccessor.cs | 6 +- .../PixelAccessor/IPixelAccessor.cs | 4 +- .../Samplers/Processors/ResizeProcessor.cs | 156 ++++++++++-------- 12 files changed, 130 insertions(+), 118 deletions(-) diff --git a/src/ImageProcessorCore/Bootstrapper.cs b/src/ImageProcessorCore/Bootstrapper.cs index 08698af320..7a7d1892f0 100644 --- a/src/ImageProcessorCore/Bootstrapper.cs +++ b/src/ImageProcessorCore/Bootstrapper.cs @@ -74,7 +74,7 @@ namespace ImageProcessorCore /// The type of pixel data. /// The image /// The - public IPixelAccessor GetPixelAccessor(IImageBase image) + public IPixelAccessor GetPixelAccessor(IImageBase image) where TPackedVector : IPackedVector, new() { Type packed = typeof(TPackedVector); @@ -82,33 +82,7 @@ namespace ImageProcessorCore { // TODO: Double check this. It should work... - return (IPixelAccessor)new Bgra32PixelAccessor(image); - //return (IPixelAccessor)Activator.CreateInstance(this.pixelAccessors[packed], image); - } - - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.AppendLine("PixelAccessor cannot be loaded. Available accessors:"); - - foreach (Type value in this.pixelAccessors.Values) - { - stringBuilder.AppendLine("-" + value.Name); - } - - throw new NotSupportedException(stringBuilder.ToString()); - } - - /// - /// Gets an instance of the correct for the packed vector. - /// - /// The type of pixel data. - /// The image - /// The - public IPixelAccessor GetPixelAccessor(ImageFrame image) - where TPackedVector : IPackedVector - { - Type packed = typeof(TPackedVector); - if (!this.pixelAccessors.ContainsKey(packed)) - { + //return new Bgra32PixelAccessor(image); return (IPixelAccessor)Activator.CreateInstance(this.pixelAccessors[packed], image); } @@ -122,5 +96,31 @@ namespace ImageProcessorCore throw new NotSupportedException(stringBuilder.ToString()); } + + ///// + ///// Gets an instance of the correct for the packed vector. + ///// + ///// The type of pixel data. + ///// The image + ///// The + //public IPixelAccessor GetPixelAccessor(ImageFrame image) + // where TPackedVector : IPackedVector, new() + //{ + // Type packed = typeof(TPackedVector); + // if (!this.pixelAccessors.ContainsKey(packed)) + // { + // return (IPixelAccessor)Activator.CreateInstance(this.pixelAccessors[packed], image); + // } + + // StringBuilder stringBuilder = new StringBuilder(); + // stringBuilder.AppendLine("PixelAccessor cannot be loaded. Available accessors:"); + + // foreach (Type value in this.pixelAccessors.Values) + // { + // stringBuilder.AppendLine("-" + value.Name); + // } + + // throw new NotSupportedException(stringBuilder.ToString()); + //} } } diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs index 4b212d4ea0..bb413cc383 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs @@ -44,7 +44,7 @@ namespace ImageProcessorCore.Formats /// public void Encode(ImageBase image, Stream stream) - where TPackedVector: IPackedVector + where TPackedVector : IPackedVector, new() { 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 2ad8e24e6e..a0d321ecb9 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs @@ -29,7 +29,7 @@ namespace ImageProcessorCore.Formats /// The to encode the image data to. /// The public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) - where TPackedVector : IPackedVector + where TPackedVector : IPackedVector, new() { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -128,7 +128,7 @@ namespace ImageProcessorCore.Formats /// The containing pixel data. /// private void WriteImage(EndianBinaryWriter writer, ImageBase image) - where TPackedVector : IPackedVector + where TPackedVector : IPackedVector, new() { // TODO: Add more compression formats. int amount = (image.Width * (int)this.bmpBitsPerPixel) % 4; diff --git a/src/ImageProcessorCore/Formats/IImageDecoder.cs b/src/ImageProcessorCore/Formats/IImageDecoder.cs index f394843caa..59b4ddb2d4 100644 --- a/src/ImageProcessorCore/Formats/IImageDecoder.cs +++ b/src/ImageProcessorCore/Formats/IImageDecoder.cs @@ -44,6 +44,7 @@ namespace ImageProcessorCore.Formats /// The type of pixels contained within the image. /// The to decode to. /// The containing image data. - void Decode(Image image, Stream stream) where TPackedVector : IPackedVector, new(); + void Decode(Image image, Stream stream) + where TPackedVector : IPackedVector, new(); } } diff --git a/src/ImageProcessorCore/Formats/IImageEncoder.cs b/src/ImageProcessorCore/Formats/IImageEncoder.cs index df7234aad0..32b6f99018 100644 --- a/src/ImageProcessorCore/Formats/IImageEncoder.cs +++ b/src/ImageProcessorCore/Formats/IImageEncoder.cs @@ -48,6 +48,7 @@ namespace ImageProcessorCore.Formats /// The type of pixels contained within the image. /// The to encode from. /// The to encode the image data to. - void Encode(ImageBase image, Stream stream) where TPackedVector : IPackedVector; + void Encode(ImageBase image, Stream stream) + where TPackedVector : IPackedVector, new(); } } diff --git a/src/ImageProcessorCore/IImageBase.cs b/src/ImageProcessorCore/IImageBase.cs index c8b762382c..cc383cb258 100644 --- a/src/ImageProcessorCore/IImageBase.cs +++ b/src/ImageProcessorCore/IImageBase.cs @@ -6,8 +6,8 @@ namespace ImageProcessorCore where TPackedVector : IPackedVector, new() { TPackedVector[] Pixels { get; } - void ClonePixels(int width, int height, IEnumerable pixels); - IPixelAccessor Lock(); + void ClonePixels(int width, int height, TPackedVector[] pixels); + IPixelAccessor Lock(); void SetPixels(int width, int height, TPackedVector[] pixels); } diff --git a/src/ImageProcessorCore/Image.cs b/src/ImageProcessorCore/Image.cs index acd49d5514..caa333e9c0 100644 --- a/src/ImageProcessorCore/Image.cs +++ b/src/ImageProcessorCore/Image.cs @@ -184,9 +184,9 @@ namespace ImageProcessorCore public IImageFormat CurrentImageFormat { get; internal set; } /// - public override IPixelAccessor Lock() + public override IPixelAccessor Lock() { - return Bootstrapper.Instance.GetPixelAccessor(this); + return Bootstrapper.Instance.GetPixelAccessor(this); } /// diff --git a/src/ImageProcessorCore/ImageBase.cs b/src/ImageProcessorCore/ImageBase.cs index 0c0bb6fac6..15fd9b5d3f 100644 --- a/src/ImageProcessorCore/ImageBase.cs +++ b/src/ImageProcessorCore/ImageBase.cs @@ -196,6 +196,6 @@ namespace ImageProcessorCore /// /// /// The - public abstract IPixelAccessor Lock(); + public abstract IPixelAccessor Lock(); } } diff --git a/src/ImageProcessorCore/ImageFrame.cs b/src/ImageProcessorCore/ImageFrame.cs index cc65739c13..d170778e41 100644 --- a/src/ImageProcessorCore/ImageFrame.cs +++ b/src/ImageProcessorCore/ImageFrame.cs @@ -33,9 +33,9 @@ namespace ImageProcessorCore } /// - public override IPixelAccessor Lock() + public override IPixelAccessor Lock() { - return Bootstrapper.Instance.GetPixelAccessor(this); + return Bootstrapper.Instance.GetPixelAccessor(this); } } } diff --git a/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs index 0645ff429f..e5f090e417 100644 --- a/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs @@ -15,7 +15,7 @@ namespace ImageProcessorCore /// The image data is always stored in format, where the blue, green, red, and /// alpha values are 8 bit unsigned bytes. /// - public sealed unsafe class Bgra32PixelAccessor : IPixelAccessor + public sealed unsafe class Bgra32PixelAccessor : IPixelAccessor { /// @@ -90,7 +90,7 @@ namespace ImageProcessorCore /// than zero and smaller than the width of the pixel. /// /// The at the specified position. - public Bgra32 this[int x, int y] + public IPackedVector this[int x, int y] { get { @@ -121,7 +121,7 @@ namespace ImageProcessorCore throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); } #endif - *(this.pixelsBase + ((y * this.Width) + x)) = value; + *(this.pixelsBase + ((y * this.Width) + x)) = (Bgra32)value; } } diff --git a/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs index f64fcf231d..cf3c2cf539 100644 --- a/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs @@ -10,7 +10,7 @@ namespace ImageProcessorCore /// /// Encapsulates properties to provides per-pixel access to an images pixels. /// - public interface IPixelAccessor : IDisposable where TPackedVector : IPackedVector, new() + public interface IPixelAccessor : IDisposable { /// /// Gets the width of the image in pixels. @@ -34,7 +34,7 @@ namespace ImageProcessorCore /// than zero and smaller than the width of the pixel. /// /// The at the specified position. - TPackedVector this[int x, int y] + IPackedVector this[int x, int y] { get; set; diff --git a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs index a6e787714d..ce713797b5 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs @@ -14,11 +14,6 @@ namespace ImageProcessorCore.Processors /// public class ResizeProcessor : ImageSampler { - /// - /// The image used for storing the first pass pixels. - /// - private object firstPass; - /// /// Initializes a new instance of the class. /// @@ -55,8 +50,6 @@ namespace ImageProcessorCore.Processors this.HorizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width); this.VerticalWeights = this.PrecomputeWeights(targetRectangle.Height, sourceRectangle.Height); } - - this.firstPass = new Image(target.Width, source.Height); } /// @@ -79,17 +72,14 @@ namespace ImageProcessorCore.Processors int endX = targetRectangle.Right; bool compand = this.Compand; - // TODO: Yuck! Fix this boxing nonsense - Image fp = (Image)this.firstPass; - if (this.Sampler is NearestNeighborResampler) { // Scaling factors float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width; float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height; - using (IPixelAccessor sourcePixels = source.Lock()) - using (IPixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, @@ -124,94 +114,114 @@ namespace ImageProcessorCore.Processors // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm // First process the columns. Since we are not using multiple threads startY and endY // are the upper and lower bounds of the source rectangle. - using (IPixelAccessor sourcePixels = source.Lock()) - using (IPixelAccessor firstPassPixels = fp.Lock()) - using (IPixelAccessor 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, 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; + // 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 - Vector4 destination = new Vector4(); + // Destination color components + //Color destination = new Color(); - for (int i = 0; i < sum; i++) - { - Weight xw = horizontalValues[i]; - int originX = xw.Index; - Vector4 sourceColor = sourcePixels[originX, y].ToVector4(); - //Color sourceColor = compand - // ? Color.Expand(sourcePixels[originX, y]) - // : sourcePixels[originX, y]; - - destination += sourceColor * xw.Value; - } + //for (int i = 0; i < sum; i++) + //{ + // Weight xw = horizontalValues[i]; + // int originX = xw.Index; + // Color sourceColor = compand + // ? Color.Expand(sourcePixels[originX, y]) + // : sourcePixels[originX, y]; + + // destination += sourceColor * xw.Value; + //} - //if (compand) - //{ - // destination = Color.Compress(destination); - //} - TPackedVector packed = new TPackedVector(); - packed.PackVector(destination); - firstPassPixels[x, y] = packed; + //if (compand) + //{ + // destination = Color.Compress(destination); + //} + + //firstPassPixels[x, y] = destination; + Vector4 destination = new Vector4(); + + for (int i = 0; i < sum; i++) + { + Weight xw = horizontalValues[i]; + int originX = xw.Index; + Vector4 sourceColor = sourcePixels[originX, y].ToVector4(); + //Color sourceColor = compand + // ? Color.Expand(sourcePixels[originX, y]) + // : sourcePixels[originX, y]; + + destination += sourceColor * xw.Value; } + + //if (compand) + //{ + // destination = Color.Compress(destination); + //} + TPackedVector packed = new TPackedVector(); + packed.PackVector(destination); + + firstPassPixels[x, y] = packed; } - }); + } + }); // Now process the rows. Parallel.For( startY, endY, y => + { + if (y >= 0 && y < height) { - if (y >= 0 && y < height) + // Ensure offsets are normalised for cropping and padding. + int offsetY = y - startY; + float sum = this.VerticalWeights[offsetY].Sum; + Weight[] verticalValues = this.VerticalWeights[offsetY].Values; + + for (int x = 0; x < width; x++) { - // 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 = new Vector4(); - for (int x = 0; x < width; x++) + for (int i = 0; i < sum; i++) { - // Destination color components - Vector4 destination = new Vector4(); - - for (int i = 0; i < sum; i++) - { - Weight yw = verticalValues[i]; - int originY = yw.Index; - - Vector4 sourceColor = sourcePixels[x, originY].ToVector4(); - //Color sourceColor = compand - // ? Color.Expand(firstPassPixels[x, originY]) - // : firstPassPixels[x, originY]; + Weight yw = verticalValues[i]; + int originY = yw.Index; + //Color sourceColor = compand + // ? Color.Expand(firstPassPixels[x, originY]) + // : firstPassPixels[x, originY]; + Vector4 sourceColor = firstPassPixels[x, originY].ToVector4(); + destination += sourceColor * yw.Value; + } - destination += sourceColor * yw.Value; - } + //if (compand) + //{ + // destination = Color.Compress(destination); + //} - //if (compand) - //{ - // destination = Color.Compress(destination); - //} + TPackedVector packed = new TPackedVector(); + packed.PackVector(destination); - TPackedVector packed = new TPackedVector(); - packed.PackVector(destination); - targetPixels[x, y] = packed; - } + targetPixels[x, y] = packed; } + } - this.OnRowProcessed(); - }); + this.OnRowProcessed(); + }); } } From aaaacf12ec153b8725b87b8cdd1a1995d20ee169 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 8 Jul 2016 10:25:28 +1000 Subject: [PATCH 10/91] Avoid activator Former-commit-id: 369a59330765de72829d5b2e6eec83cdb18a3f03 Former-commit-id: 8611d5b3aebe7f155a80598eeff86c707c614e64 Former-commit-id: 862e506ccc0485d075b4afa2c514c6619823f1f5 --- src/ImageProcessorCore/Bootstrapper.cs | 48 ++----------------- .../Samplers/Resize.cs | 8 ++-- 2 files changed, 9 insertions(+), 47 deletions(-) diff --git a/src/ImageProcessorCore/Bootstrapper.cs b/src/ImageProcessorCore/Bootstrapper.cs index 7a7d1892f0..1601f8d892 100644 --- a/src/ImageProcessorCore/Bootstrapper.cs +++ b/src/ImageProcessorCore/Bootstrapper.cs @@ -8,7 +8,6 @@ namespace ImageProcessorCore using System; using System.Collections.Generic; using System.Collections.ObjectModel; - using System.Text; using ImageProcessorCore.Formats; @@ -28,7 +27,7 @@ namespace ImageProcessorCore /// private readonly List imageFormats; - private readonly Dictionary pixelAccessors; + private readonly Dictionary> pixelAccessors; /// /// Prevents a default instance of the class from being created. @@ -43,9 +42,9 @@ namespace ImageProcessorCore //new GifFormat() }; - this.pixelAccessors = new Dictionary + this.pixelAccessors = new Dictionary> { - { typeof(Bgra32), typeof(Bgra32PixelAccessor) } + { typeof(Bgra32), i=> new Bgra32PixelAccessor(i) } }; } @@ -80,47 +79,10 @@ namespace ImageProcessorCore Type packed = typeof(TPackedVector); if (this.pixelAccessors.ContainsKey(packed)) { - // TODO: Double check this. It should work... - - //return new Bgra32PixelAccessor(image); - return (IPixelAccessor)Activator.CreateInstance(this.pixelAccessors[packed], image); - } - - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.AppendLine("PixelAccessor cannot be loaded. Available accessors:"); - - foreach (Type value in this.pixelAccessors.Values) - { - stringBuilder.AppendLine("-" + value.Name); + return this.pixelAccessors[packed].Invoke(image); } - throw new NotSupportedException(stringBuilder.ToString()); + throw new NotSupportedException($"PixelAccessor cannot be loaded for {packed}:"); } - - ///// - ///// Gets an instance of the correct for the packed vector. - ///// - ///// The type of pixel data. - ///// The image - ///// The - //public IPixelAccessor GetPixelAccessor(ImageFrame image) - // where TPackedVector : IPackedVector, new() - //{ - // Type packed = typeof(TPackedVector); - // if (!this.pixelAccessors.ContainsKey(packed)) - // { - // return (IPixelAccessor)Activator.CreateInstance(this.pixelAccessors[packed], image); - // } - - // StringBuilder stringBuilder = new StringBuilder(); - // stringBuilder.AppendLine("PixelAccessor cannot be loaded. Available accessors:"); - - // foreach (Type value in this.pixelAccessors.Values) - // { - // stringBuilder.AppendLine("-" + value.Name); - // } - - // throw new NotSupportedException(stringBuilder.ToString()); - //} } } diff --git a/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs b/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs index 21a9033e0d..cbc90c7265 100644 --- a/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs +++ b/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs @@ -11,9 +11,9 @@ [Benchmark(Baseline = true, Description = "System.Drawing Resize")] public Size ResizeSystemDrawing() { - using (Bitmap source = new Bitmap(400, 400)) + using (Bitmap source = new Bitmap(2000, 2000)) { - using (Bitmap destination = new Bitmap(100, 100)) + using (Bitmap destination = new Bitmap(400, 400)) { using (Graphics graphics = Graphics.FromImage(destination)) { @@ -31,8 +31,8 @@ [Benchmark(Description = "ImageProcessorCore Resize")] public CoreSize ResizeCore() { - Image image = new Image(400, 400); - image.Resize(100, 100); + Image image = new Image(2000, 2000); + image.Resize(400, 400); return new CoreSize(image.Width, image.Height); } } From b04b6298e028bb8caddea92fca8acdb5f3471912 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 8 Jul 2016 19:49:39 +1000 Subject: [PATCH 11/91] IPixelAcessor Former-commit-id: 5b04e85fc18ebe98bd122766c58379a1666d0cbb Former-commit-id: 6e626d6a6e55aa75922a3c1aeeb4094390b4e185 Former-commit-id: f5293704eec3ece4aceaebff57218bce383d02e8 --- src/ImageProcessorCore/Bootstrapper.cs | 4 +-- .../Formats/Bmp/BmpEncoderCore.cs | 9 ++++-- src/ImageProcessorCore/IImageBase.cs | 2 +- src/ImageProcessorCore/Image.cs | 2 +- src/ImageProcessorCore/ImageBase.cs | 2 +- src/ImageProcessorCore/ImageFrame.cs | 2 +- .../PixelAccessor/Bgra32PixelAccessor.cs | 6 ++-- .../PixelAccessor/IPixelAccessor.cs | 28 +++++++++++-------- .../Samplers/Processors/ResizeProcessor.cs | 10 +++---- .../Image/GetSetPixel.cs | 2 +- 10 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/ImageProcessorCore/Bootstrapper.cs b/src/ImageProcessorCore/Bootstrapper.cs index 1601f8d892..36e53ce51b 100644 --- a/src/ImageProcessorCore/Bootstrapper.cs +++ b/src/ImageProcessorCore/Bootstrapper.cs @@ -73,13 +73,13 @@ namespace ImageProcessorCore /// The type of pixel data. /// The image /// The - public IPixelAccessor GetPixelAccessor(IImageBase image) + public IPixelAccessor GetPixelAccessor(IImageBase image) where TPackedVector : IPackedVector, new() { Type packed = typeof(TPackedVector); if (this.pixelAccessors.ContainsKey(packed)) { - return this.pixelAccessors[packed].Invoke(image); + return (IPixelAccessor)this.pixelAccessors[packed].Invoke(image); } throw new NotSupportedException($"PixelAccessor cannot be loaded for {packed}:"); diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs index a0d321ecb9..fae92c0f12 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs @@ -137,7 +137,7 @@ namespace ImageProcessorCore.Formats amount = 4 - amount; } - using (IPixelAccessor pixels = image.Lock()) + using (IPixelAccessor pixels = image.Lock()) { switch (this.bmpBitsPerPixel) { @@ -155,10 +155,12 @@ namespace ImageProcessorCore.Formats /// /// Writes the 32bit color palette to the stream. /// + /// The type of pixels contained within the image. /// The containing the stream to write to. /// The containing pixel data. /// The amount to pad each row by. - private void Write32bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) + private void Write32bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) + where TPackedVector : IPackedVector, new() { for (int y = pixels.Height - 1; y >= 0; y--) { @@ -183,7 +185,8 @@ namespace ImageProcessorCore.Formats /// The containing the stream to write to. /// The containing pixel data. /// The amount to pad each row by. - private void Write24bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) + private void Write24bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) + where TPackedVector : IPackedVector, new() { for (int y = pixels.Height - 1; y >= 0; y--) { diff --git a/src/ImageProcessorCore/IImageBase.cs b/src/ImageProcessorCore/IImageBase.cs index cc383cb258..5a61f76701 100644 --- a/src/ImageProcessorCore/IImageBase.cs +++ b/src/ImageProcessorCore/IImageBase.cs @@ -7,7 +7,7 @@ namespace ImageProcessorCore { TPackedVector[] Pixels { get; } void ClonePixels(int width, int height, TPackedVector[] pixels); - IPixelAccessor Lock(); + IPixelAccessor Lock(); void SetPixels(int width, int height, TPackedVector[] pixels); } diff --git a/src/ImageProcessorCore/Image.cs b/src/ImageProcessorCore/Image.cs index caa333e9c0..a672f6a250 100644 --- a/src/ImageProcessorCore/Image.cs +++ b/src/ImageProcessorCore/Image.cs @@ -184,7 +184,7 @@ namespace ImageProcessorCore public IImageFormat CurrentImageFormat { get; internal set; } /// - public override IPixelAccessor Lock() + public override IPixelAccessor Lock() { return Bootstrapper.Instance.GetPixelAccessor(this); } diff --git a/src/ImageProcessorCore/ImageBase.cs b/src/ImageProcessorCore/ImageBase.cs index 15fd9b5d3f..0c0bb6fac6 100644 --- a/src/ImageProcessorCore/ImageBase.cs +++ b/src/ImageProcessorCore/ImageBase.cs @@ -196,6 +196,6 @@ namespace ImageProcessorCore /// /// /// The - public abstract IPixelAccessor Lock(); + public abstract IPixelAccessor Lock(); } } diff --git a/src/ImageProcessorCore/ImageFrame.cs b/src/ImageProcessorCore/ImageFrame.cs index d170778e41..75d755f5fc 100644 --- a/src/ImageProcessorCore/ImageFrame.cs +++ b/src/ImageProcessorCore/ImageFrame.cs @@ -33,7 +33,7 @@ namespace ImageProcessorCore } /// - public override IPixelAccessor Lock() + public override IPixelAccessor Lock() { return Bootstrapper.Instance.GetPixelAccessor(this); } diff --git a/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs index e5f090e417..f9fa7005bc 100644 --- a/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs @@ -15,7 +15,7 @@ namespace ImageProcessorCore /// The image data is always stored in format, where the blue, green, red, and /// alpha values are 8 bit unsigned bytes. /// - public sealed unsafe class Bgra32PixelAccessor : IPixelAccessor + public sealed unsafe class Bgra32PixelAccessor : IPixelAccessor { /// @@ -90,7 +90,7 @@ namespace ImageProcessorCore /// than zero and smaller than the width of the pixel. /// /// The at the specified position. - public IPackedVector this[int x, int y] + public Bgra32 this[int x, int y] { get { @@ -121,7 +121,7 @@ namespace ImageProcessorCore throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); } #endif - *(this.pixelsBase + ((y * this.Width) + x)) = (Bgra32)value; + *(this.pixelsBase + ((y * this.Width) + x)) = value; } } diff --git a/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs index cf3c2cf539..7eac9fd030 100644 --- a/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs @@ -10,18 +10,9 @@ namespace ImageProcessorCore /// /// Encapsulates properties to provides per-pixel access to an images pixels. /// - public interface IPixelAccessor : IDisposable + public interface IPixelAccessor : IPixelAccessor + where TPackedVector : IPackedVector, new() { - /// - /// Gets the width of the image in pixels. - /// - int Width { get; } - - /// - /// Gets the height of the image in pixels. - /// - int Height { get; } - /// /// Gets or sets the pixel at the specified position. /// @@ -34,10 +25,23 @@ namespace ImageProcessorCore /// than zero and smaller than the width of the pixel. /// /// The at the specified position. - IPackedVector this[int x, int y] + TPackedVector this[int x, int y] { get; set; } } + + 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/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs index ce713797b5..11b397d745 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs @@ -78,8 +78,8 @@ namespace ImageProcessorCore.Processors float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width; float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height; - using (IPixelAccessor sourcePixels = source.Lock()) - using (IPixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, @@ -115,9 +115,9 @@ namespace ImageProcessorCore.Processors // 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 (IPixelAccessor sourcePixels = source.Lock()) - using (IPixelAccessor firstPassPixels = firstPass.Lock()) - using (IPixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor firstPassPixels = firstPass.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( 0, diff --git a/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs b/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs index 98bcf80493..d900377cda 100644 --- a/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs +++ b/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs @@ -24,7 +24,7 @@ public Bgra32 ResizeCore() { Image image = new Image(400, 400); - using (IPixelAccessor imagePixels = image.Lock()) + using (IPixelAccessor imagePixels = image.Lock()) { imagePixels[200, 200] = new Bgra32(1, 1, 1, 1); return (Bgra32)imagePixels[200, 200]; From 9f25784c041c06515e031032a1a257e70c369e3c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 9 Jul 2016 00:04:09 +1000 Subject: [PATCH 12/91] Moar T Former-commit-id: 53f37fb68f78c602df15d87f6c9086f75eed1cfc Former-commit-id: 79ee79b98b686595c4e5d6054365884a7dbf5b7a Former-commit-id: afc91142ccd716afb271b29bdbf736d779ef4d84 --- src/ImageProcessorCore/Bootstrapper.cs | 10 ++-- .../Formats/Bmp/BmpDecoder.cs | 8 +-- .../Formats/Bmp/BmpDecoderCore.cs | 48 ++++++++-------- .../Formats/Bmp/BmpEncoder.cs | 4 +- .../Formats/Bmp/BmpEncoderCore.cs | 30 +++++----- .../Formats/IImageDecoder.cs | 10 ++-- .../Formats/IImageEncoder.cs | 10 ++-- src/ImageProcessorCore/IImageBase.cs | 12 ++-- src/ImageProcessorCore/IImageFrame.cs | 4 +- src/ImageProcessorCore/IImageProcessor.cs | 16 +++--- src/ImageProcessorCore/Image.cs | 26 ++++----- src/ImageProcessorCore/ImageBase.cs | 32 +++++------ src/ImageProcessorCore/ImageExtensions.cs | 56 +++++++++---------- src/ImageProcessorCore/ImageFrame.cs | 16 +++--- src/ImageProcessorCore/ImageProcessor.cs | 28 +++++----- .../PackedVector/IPackedVector.cs | 8 +-- .../PixelAccessor/IPixelAccessor.cs | 8 +-- .../Samplers/Options/ResizeHelper.cs | 36 ++++++------ .../Samplers/Processors/ResizeProcessor.cs | 23 ++++---- src/ImageProcessorCore/Samplers/Resize.cs | 40 ++++++------- 20 files changed, 212 insertions(+), 213 deletions(-) diff --git a/src/ImageProcessorCore/Bootstrapper.cs b/src/ImageProcessorCore/Bootstrapper.cs index 36e53ce51b..2076fab19a 100644 --- a/src/ImageProcessorCore/Bootstrapper.cs +++ b/src/ImageProcessorCore/Bootstrapper.cs @@ -70,16 +70,16 @@ namespace ImageProcessorCore /// /// Gets an instance of the correct for the packed vector. /// - /// The type of pixel data. + /// The type of pixel data. /// The image /// The - public IPixelAccessor GetPixelAccessor(IImageBase image) - where TPackedVector : IPackedVector, new() + public IPixelAccessor GetPixelAccessor(IImageBase image) + where T : IPackedVector, new() { - Type packed = typeof(TPackedVector); + Type packed = typeof(T); if (this.pixelAccessors.ContainsKey(packed)) { - return (IPixelAccessor)this.pixelAccessors[packed].Invoke(image); + return (IPixelAccessor)this.pixelAccessors[packed].Invoke(image); } throw new NotSupportedException($"PixelAccessor cannot be loaded for {packed}:"); diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs index 4872ba89dc..5565fd690b 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs @@ -70,12 +70,12 @@ namespace ImageProcessorCore.Formats } /// - /// Decodes the image from the specified stream to the . + /// Decodes the image from the specified stream to the . /// - /// The to decode to. + /// The to decode to. /// The containing image data. - public void Decode(Image image, Stream stream) - where TPackedVector : IPackedVector, new() + public void Decode(Image image, Stream stream) + where T : IPackedVector, new() { new BmpDecoderCore().Decode(image, stream); } diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs index 4a36a07e31..56e2d2e2a5 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs @@ -49,7 +49,7 @@ namespace ImageProcessorCore.Formats /// Decodes the image from the specified this._stream and sets /// the data to image. /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// The image, where the data should be set to. /// Cannot be null (Nothing in Visual Basic). /// The this._stream, where the image should be @@ -59,8 +59,8 @@ namespace ImageProcessorCore.Formats /// - or - /// is null. /// - public void Decode(Image image, Stream stream) - where TPackedVector : IPackedVector, new() + public void Decode(Image image, Stream stream) + where T : IPackedVector, new() { this.currentStream = stream; @@ -119,7 +119,7 @@ namespace ImageProcessorCore.Formats + $"bigger then the max allowed size '{image.MaxWidth}x{image.MaxHeight}'"); } - TPackedVector[] imageData = new TPackedVector[this.infoHeader.Width * this.infoHeader.Height]; + T[] imageData = new T[this.infoHeader.Width * this.infoHeader.Height]; switch (this.infoHeader.Compression) { @@ -192,15 +192,15 @@ namespace ImageProcessorCore.Formats /// /// Reads the color palette from the stream. /// - /// The type of pixels contained within the image. - /// The image data to assign the palette to. + /// The type of pixels contained within the image. + /// The image data to assign the palette to. /// The containing the colors. /// The width of the bitmap. /// The height of the bitmap. /// The number of bits per pixel. /// Whether the bitmap is inverted. - private void ReadRgbPalette(TPackedVector[] imageData, byte[] colors, int width, int height, int bits, bool inverted) - where TPackedVector : IPackedVector, new() + private void ReadRgbPalette(T[] imageData, byte[] colors, int width, int height, int bits, bool inverted) + where T : IPackedVector, new() { // Pixels per byte (bits per pixel) int ppb = 8 / bits; @@ -243,7 +243,7 @@ namespace ImageProcessorCore.Formats int arrayOffset = (row * width) + (colOffset + shift); // Stored in b-> g-> r-> a order. - TPackedVector packed = new TPackedVector(); + T packed = new T(); packed.PackBytes(colors[colorIndex], colors[colorIndex + 1], colors[colorIndex + 2], 255); imageData[arrayOffset] = packed; } @@ -254,13 +254,13 @@ namespace ImageProcessorCore.Formats /// /// Reads the 16 bit color palette from the stream /// - /// The type of pixels contained within the image. - /// The image data to assign the palette to. + /// The type of pixels contained within the image. + /// The image data to assign the palette to. /// The width of the bitmap. /// The height of the bitmap. /// Whether the bitmap is inverted. - private void ReadRgb16(TPackedVector[] imageData, int width, int height, bool inverted) - where TPackedVector : IPackedVector, new() + private void ReadRgb16(T[] imageData, int width, int height, bool inverted) + where T : IPackedVector, new() { // We divide here as we will store the colors in our floating point format. const int ScaleR = 8; // 256/32 @@ -292,7 +292,7 @@ namespace ImageProcessorCore.Formats int arrayOffset = ((row * width) + x); // Stored in b-> g-> r-> a order. - TPackedVector packed = new TPackedVector(); + T packed = new T(); packed.PackBytes(b, g, r, 255); imageData[arrayOffset] = packed; } @@ -302,13 +302,13 @@ namespace ImageProcessorCore.Formats /// /// Reads the 24 bit color palette from the stream /// - /// The type of pixels contained within the image. - /// The image data to assign the palette to. + /// The type of pixels contained within the image. + /// The image data to assign the palette to. /// The width of the bitmap. /// The height of the bitmap. /// Whether the bitmap is inverted. - private void ReadRgb24(TPackedVector[] imageData, int width, int height, bool inverted) - where TPackedVector : IPackedVector, new() + private void ReadRgb24(T[] imageData, int width, int height, bool inverted) + where T : IPackedVector, new() { int alignment; byte[] data = this.GetImageArray(width, height, 3, out alignment); @@ -330,7 +330,7 @@ namespace ImageProcessorCore.Formats // We divide by 255 as we will store the colors in our floating point format. // Stored in b-> g-> r-> a order. - TPackedVector packed = new TPackedVector(); + T packed = new T(); packed.PackBytes(data[offset], data[offset + 1], data[offset + 2], 255); imageData[arrayOffset] = packed; } @@ -340,13 +340,13 @@ namespace ImageProcessorCore.Formats /// /// Reads the 32 bit color palette from the stream /// - /// The type of pixels contained within the image. - /// The image data to assign the palette to. + /// The type of pixels contained within the image. + /// The image data to assign the palette to. /// The width of the bitmap. /// The height of the bitmap. /// Whether the bitmap is inverted. - private void ReadRgb32(TPackedVector[] imageData, int width, int height, bool inverted) - where TPackedVector : IPackedVector, new() + private void ReadRgb32(T[] imageData, int width, int height, bool inverted) + where T : IPackedVector, new() { int alignment; byte[] data = this.GetImageArray(width, height, 4, out alignment); @@ -367,7 +367,7 @@ namespace ImageProcessorCore.Formats int arrayOffset = ((row * width) + x); // Stored in b-> g-> r-> a order. - TPackedVector packed = new TPackedVector(); + T packed = new T(); packed.PackBytes(data[offset], data[offset + 1], data[offset + 2], data[offset + 3]); imageData[arrayOffset] = packed; } diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs index bb413cc383..f58f6b85f4 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs @@ -43,8 +43,8 @@ namespace ImageProcessorCore.Formats } /// - public void Encode(ImageBase image, Stream stream) - where TPackedVector : IPackedVector, new() + public void Encode(ImageBase image, Stream stream) + where T : IPackedVector, new() { 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 fae92c0f12..1df95592a2 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs @@ -22,14 +22,14 @@ namespace ImageProcessorCore.Formats private BmpBitsPerPixel bmpBitsPerPixel; /// - /// Encodes the image to the specified stream from the . + /// Encodes the image to the specified stream from the . /// - /// The type of pixels contained within the image. - /// The to encode from. + /// The type of pixels contained within the image. + /// The to encode from. /// The to encode the image data to. /// The - public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) - where TPackedVector : IPackedVector, new() + public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) + where T : IPackedVector, new() { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -120,15 +120,15 @@ namespace ImageProcessorCore.Formats /// /// Writes the pixel data to the binary stream. /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// /// The containing the stream to write to. /// /// - /// The containing pixel data. + /// The containing pixel data. /// - private void WriteImage(EndianBinaryWriter writer, ImageBase image) - where TPackedVector : IPackedVector, new() + private void WriteImage(EndianBinaryWriter writer, ImageBase image) + where T : IPackedVector, new() { // TODO: Add more compression formats. int amount = (image.Width * (int)this.bmpBitsPerPixel) % 4; @@ -137,7 +137,7 @@ namespace ImageProcessorCore.Formats amount = 4 - amount; } - using (IPixelAccessor pixels = image.Lock()) + using (IPixelAccessor pixels = image.Lock()) { switch (this.bmpBitsPerPixel) { @@ -155,12 +155,12 @@ namespace ImageProcessorCore.Formats /// /// Writes the 32bit color palette to the stream. /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// The containing the stream to write to. /// The containing pixel data. /// The amount to pad each row by. - private void Write32bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) - where TPackedVector : IPackedVector, new() + private void Write32bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) + where T : IPackedVector, new() { for (int y = pixels.Height - 1; y >= 0; y--) { @@ -185,8 +185,8 @@ namespace ImageProcessorCore.Formats /// The containing the stream to write to. /// The containing pixel data. /// The amount to pad each row by. - private void Write24bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) - where TPackedVector : IPackedVector, new() + private void Write24bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) + where T : IPackedVector, new() { for (int y = pixels.Height - 1; y >= 0; y--) { diff --git a/src/ImageProcessorCore/Formats/IImageDecoder.cs b/src/ImageProcessorCore/Formats/IImageDecoder.cs index 59b4ddb2d4..3c5676bac4 100644 --- a/src/ImageProcessorCore/Formats/IImageDecoder.cs +++ b/src/ImageProcessorCore/Formats/IImageDecoder.cs @@ -39,12 +39,12 @@ 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 type of pixels contained within the image. - /// The to decode to. + /// The type of pixels contained within the image. + /// The to decode to. /// The containing image data. - void Decode(Image image, Stream stream) - where TPackedVector : IPackedVector, new(); + void Decode(Image image, Stream stream) + where T : IPackedVector, new(); } } diff --git a/src/ImageProcessorCore/Formats/IImageEncoder.cs b/src/ImageProcessorCore/Formats/IImageEncoder.cs index 32b6f99018..6e83c11ae9 100644 --- a/src/ImageProcessorCore/Formats/IImageEncoder.cs +++ b/src/ImageProcessorCore/Formats/IImageEncoder.cs @@ -43,12 +43,12 @@ 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 type of pixels contained within the image. - /// The to encode from. + /// The type of pixels contained within the image. + /// The to encode from. /// The to encode the image data to. - void Encode(ImageBase image, Stream stream) - where TPackedVector : IPackedVector, new(); + void Encode(ImageBase image, Stream stream) + where T : IPackedVector, new(); } } diff --git a/src/ImageProcessorCore/IImageBase.cs b/src/ImageProcessorCore/IImageBase.cs index 5a61f76701..4b78df66c0 100644 --- a/src/ImageProcessorCore/IImageBase.cs +++ b/src/ImageProcessorCore/IImageBase.cs @@ -2,13 +2,13 @@ namespace ImageProcessorCore { - public interface IImageBase : IImageBase - where TPackedVector : IPackedVector, new() + public interface IImageBase : IImageBase + where T : IPackedVector, new() { - TPackedVector[] Pixels { get; } - void ClonePixels(int width, int height, TPackedVector[] pixels); - IPixelAccessor Lock(); - void SetPixels(int width, int height, TPackedVector[] pixels); + T[] Pixels { get; } + void ClonePixels(int width, int height, T[] pixels); + IPixelAccessor Lock(); + void SetPixels(int width, int height, T[] pixels); } public interface IImageBase diff --git a/src/ImageProcessorCore/IImageFrame.cs b/src/ImageProcessorCore/IImageFrame.cs index a3c82d9325..fc92b9306e 100644 --- a/src/ImageProcessorCore/IImageFrame.cs +++ b/src/ImageProcessorCore/IImageFrame.cs @@ -1,7 +1,7 @@ namespace ImageProcessorCore { - public interface IImageFrame : IImageBase - where TPacked : IPackedVector, new() + public interface IImageFrame : IImageBase + where T : IPackedVector, new() { } } diff --git a/src/ImageProcessorCore/IImageProcessor.cs b/src/ImageProcessorCore/IImageProcessor.cs index 153dd0a84a..76bc9851c6 100644 --- a/src/ImageProcessorCore/IImageProcessor.cs +++ b/src/ImageProcessorCore/IImageProcessor.cs @@ -27,9 +27,9 @@ namespace ImageProcessorCore.Processors event ProgressEventHandler OnProgress; /// - /// Applies the process to the specified portion of the specified . + /// Applies the process to the specified portion of the specified . /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// Target image to apply the process to. /// The source image. Cannot be null. /// @@ -45,14 +45,14 @@ namespace ImageProcessorCore.Processors /// /// doesnt fit the dimension of the image. /// - void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) - where TPackedVector : IPackedVector, new(); + void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) + where T : IPackedVector, new(); /// - /// 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. /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// Target image to apply the process to. /// The source image. Cannot be null. /// The target width. @@ -68,7 +68,7 @@ namespace ImageProcessorCore.Processors /// The method keeps the source image unchanged and returns the /// the result of image process as new image. /// - void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle) - where TPackedVector : IPackedVector, new(); + void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle) + where T : IPackedVector, new(); } } diff --git a/src/ImageProcessorCore/Image.cs b/src/ImageProcessorCore/Image.cs index a672f6a250..35eaff2434 100644 --- a/src/ImageProcessorCore/Image.cs +++ b/src/ImageProcessorCore/Image.cs @@ -17,11 +17,11 @@ namespace ImageProcessorCore /// /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. /// - /// + /// /// The packed vector containing pixel information. /// - public class Image : ImageBase - where TPackedVector : IPackedVector, new() + public class Image : ImageBase + where T : IPackedVector, new() { /// /// The default horizontal resolution value (dots per inch) in x direction. @@ -36,7 +36,7 @@ namespace ImageProcessorCore public const double DefaultVerticalResolution = 96; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public Image() { @@ -44,7 +44,7 @@ namespace ImageProcessorCore } /// - /// Initializes a new instance of the class + /// Initializes a new instance of the class /// with the height and the width of the image. /// /// The width of the image in pixels. @@ -57,7 +57,7 @@ namespace ImageProcessorCore } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The stream containing image information. @@ -70,18 +70,18 @@ namespace ImageProcessorCore } /// - /// Initializes a new instance of the class + /// 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) + public Image(Image other) { - foreach (ImageFrame frame in other.Frames) + foreach (ImageFrame frame in other.Frames) { if (frame != null) { - this.Frames.Add(new ImageFrame(frame)); + this.Frames.Add(new ImageFrame(frame)); } } @@ -170,7 +170,7 @@ namespace ImageProcessorCore /// Gets the other frames for the animation. /// /// The list of frame images. - public IList> Frames { get; } = new List>(); + public IList> Frames { get; } = new List>(); /// /// Gets the list of properties for storing meta information about this image. @@ -184,9 +184,9 @@ namespace ImageProcessorCore public IImageFormat CurrentImageFormat { get; internal set; } /// - public override IPixelAccessor Lock() + public override IPixelAccessor Lock() { - return Bootstrapper.Instance.GetPixelAccessor(this); + return Bootstrapper.Instance.GetPixelAccessor(this); } /// diff --git a/src/ImageProcessorCore/ImageBase.cs b/src/ImageProcessorCore/ImageBase.cs index 0c0bb6fac6..357f490010 100644 --- a/src/ImageProcessorCore/ImageBase.cs +++ b/src/ImageProcessorCore/ImageBase.cs @@ -11,21 +11,21 @@ namespace ImageProcessorCore /// The base class of all images. Encapsulates the basic properties and methods required to manipulate images /// in different pixel formats. /// - /// + /// /// The packed vector pixels format. /// - public abstract class ImageBase : IImageBase - where TPackedVector : IPackedVector, new() + public abstract class ImageBase : IImageBase + where T : IPackedVector, new() { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// protected ImageBase() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The width of the image in pixels. /// The height of the image in pixels. @@ -39,19 +39,19 @@ namespace ImageProcessorCore this.Width = width; this.Height = height; - this.Pixels = new TPackedVector[width * height]; + this.Pixels = new T[width * height]; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - /// The other to create this instance from. + /// The other to create this instance from. /// /// - /// Thrown if the given is null. + /// Thrown if the given is null. /// - protected ImageBase(ImageBase other) + protected ImageBase(ImageBase other) { Guard.NotNull(other, nameof(other), "Other image cannot be null."); @@ -61,7 +61,7 @@ namespace ImageProcessorCore this.FrameDelay = other.FrameDelay; // Copy the pixels. - this.Pixels = new TPackedVector[this.Width * this.Height]; + this.Pixels = new T[this.Width * this.Height]; Array.Copy(other.Pixels, this.Pixels, other.Pixels.Length); } @@ -78,7 +78,7 @@ namespace ImageProcessorCore /// /// Gets the pixels as an array of the given packed pixel format. /// - public TPackedVector[] Pixels { get; private set; } + public T[] Pixels { get; private set; } /// /// Gets the width in pixels. @@ -127,7 +127,7 @@ namespace ImageProcessorCore /// /// Thrown if the length is not equal to Width * Height. /// - public void SetPixels(int width, int height, TPackedVector[] pixels) + public void SetPixels(int width, int height, T[] pixels) { if (width <= 0) { @@ -164,7 +164,7 @@ namespace ImageProcessorCore /// /// Thrown if the length is not equal to Width * Height. /// - public void ClonePixels(int width, int height, TPackedVector[] pixels) + public void ClonePixels(int width, int height, T[] pixels) { if (width <= 0) { @@ -185,7 +185,7 @@ namespace ImageProcessorCore this.Height = height; // Copy the pixels. - this.Pixels = new TPackedVector[pixels.Length]; + this.Pixels = new T[pixels.Length]; Array.Copy(pixels, this.Pixels, pixels.Length); } @@ -196,6 +196,6 @@ namespace ImageProcessorCore /// /// /// The - public abstract IPixelAccessor Lock(); + public abstract IPixelAccessor Lock(); } } diff --git a/src/ImageProcessorCore/ImageExtensions.cs b/src/ImageProcessorCore/ImageExtensions.cs index 526ec8d554..c30607ce94 100644 --- a/src/ImageProcessorCore/ImageExtensions.cs +++ b/src/ImageProcessorCore/ImageExtensions.cs @@ -12,7 +12,7 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { @@ -57,12 +57,12 @@ namespace ImageProcessorCore /// Applies the collection of processors to the image. /// This method does not resize the target image. /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// The image this method extends. /// The processor to apply to the image. - /// The . - public static Image Process(this Image source, IImageProcessor processor) - where TPackedVector : IPackedVector, new() + /// The . + public static Image Process(this Image source, IImageProcessor processor) + where T : IPackedVector, new() { return Process(source, source.Bounds, processor); } @@ -71,15 +71,15 @@ namespace ImageProcessorCore /// Applies the collection of processors to the image. /// This method does not resize the target image. /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// The image this method extends. /// /// The structure that specifies the portion of the image object to draw. /// /// The processors to apply to the image. - /// The . - public static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor) - where TPackedVector : IPackedVector, new() + /// The . + public static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor) + where T : IPackedVector, new() { return PerformAction(source, true, (sourceImage, targetImage) => processor.Apply(targetImage, sourceImage, sourceRectangle)); } @@ -90,14 +90,14 @@ namespace ImageProcessorCore /// This method is not chainable. /// /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// The source image. Cannot be null. /// The target image width. /// The target image height. /// The processor to apply to the image. - /// The . - public static Image Process(this Image source, int width, int height, IImageSampler sampler) - where TPackedVector : IPackedVector, new() + /// The . + public static Image Process(this Image source, int width, int height, IImageSampler sampler) + where T : IPackedVector, new() { return Process(source, width, height, source.Bounds, default(Rectangle), sampler); } @@ -108,7 +108,7 @@ namespace ImageProcessorCore /// This method does will resize the target image if the source and target rectangles are different. /// /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// The source image. Cannot be null. /// The target image width. /// The target image height. @@ -120,9 +120,9 @@ 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) - where TPackedVector : IPackedVector, new() + /// The . + public static Image Process(this Image source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler) + where T : IPackedVector, new() { return PerformAction(source, false, (sourceImage, targetImage) => sampler.Apply(targetImage, sourceImage, width, height, targetRectangle, sourceRectangle)); } @@ -130,17 +130,17 @@ namespace ImageProcessorCore /// /// Performs the given action on the source image. /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// The image to perform the action against. /// Whether to clone the image. /// The to perform against the image. - /// The . - private static Image PerformAction(Image source, bool clone, Action, ImageBase> action) - where TPackedVector : IPackedVector, new() + /// The . + private static Image PerformAction(Image source, bool clone, Action, ImageBase> action) + where T : IPackedVector, new() { - Image transformedImage = clone - ? new Image(source) - : new Image + Image transformedImage = clone + ? new Image(source) + : new Image { // Several properties require copying // TODO: Check why we need to set these? @@ -154,10 +154,10 @@ 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); diff --git a/src/ImageProcessorCore/ImageFrame.cs b/src/ImageProcessorCore/ImageFrame.cs index 75d755f5fc..790591b182 100644 --- a/src/ImageProcessorCore/ImageFrame.cs +++ b/src/ImageProcessorCore/ImageFrame.cs @@ -8,34 +8,34 @@ namespace ImageProcessorCore /// /// Represents a single frame in a animation. /// - /// + /// /// The packed vector containing pixel information. /// - public class ImageFrame : ImageBase - where TPackedVector : IPackedVector, new() + public class ImageFrame : ImageBase + where T : IPackedVector, new() { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public ImageFrame() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The frame to create the frame from. /// - public ImageFrame(ImageFrame frame) + public ImageFrame(ImageFrame frame) : base(frame) { } /// - public override IPixelAccessor Lock() + public override IPixelAccessor Lock() { - return Bootstrapper.Instance.GetPixelAccessor(this); + return Bootstrapper.Instance.GetPixelAccessor(this); } } } diff --git a/src/ImageProcessorCore/ImageProcessor.cs b/src/ImageProcessorCore/ImageProcessor.cs index 0206e667ee..380e5c6340 100644 --- a/src/ImageProcessorCore/ImageProcessor.cs +++ b/src/ImageProcessorCore/ImageProcessor.cs @@ -27,8 +27,8 @@ namespace ImageProcessorCore.Processors private int totalRows; /// - public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) - where TPackedVector : IPackedVector, new() + public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) + where T : IPackedVector, new() { try { @@ -49,12 +49,12 @@ namespace ImageProcessorCore.Processors } /// - public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) - where TPackedVector : IPackedVector, new() + public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) + where T : IPackedVector, new() { try { - TPackedVector[] pixels = new TPackedVector[width * height]; + T[] pixels = new T[width * height]; target.SetPixels(width, height, pixels); // Ensure we always have bounds. @@ -95,16 +95,16 @@ namespace ImageProcessorCore.Processors /// /// The structure that specifies the portion of the image object to draw. /// - protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - where TPackedVector : IPackedVector, new() + protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + where T : IPackedVector, new() { } /// - /// 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. /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// Target image to apply the process to. /// The source image. Cannot be null. /// @@ -120,13 +120,13 @@ namespace ImageProcessorCore.Processors /// The method keeps the source image unchanged and returns the /// the result of image process as new image. /// - protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - where TPackedVector : IPackedVector, new(); + protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + where T : IPackedVector, new(); /// /// This method is called after the process is applied to prepare the processor. /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// Target image to apply the process to. /// The source image. Cannot be null. /// @@ -136,8 +136,8 @@ namespace ImageProcessorCore.Processors /// /// The structure that specifies the portion of the image object to draw. /// - protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - where TPackedVector : IPackedVector, new() + protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + where T : IPackedVector, new() { } diff --git a/src/ImageProcessorCore/PackedVector/IPackedVector.cs b/src/ImageProcessorCore/PackedVector/IPackedVector.cs index 02e10cc3a5..7022e4d9d5 100644 --- a/src/ImageProcessorCore/PackedVector/IPackedVector.cs +++ b/src/ImageProcessorCore/PackedVector/IPackedVector.cs @@ -11,17 +11,17 @@ namespace ImageProcessorCore /// An interface that converts packed vector types to and from values, /// allowing multiple encodings to be manipulated in a generic way. /// - /// + /// /// The type of object representing the packed value. /// - public interface IPackedVector : IPackedVector - where TPacked : struct + public interface IPackedVector : IPackedVector + where T : struct { /// /// Gets or sets the packed representation of the value. /// Typically packed in least to greatest significance order. /// - TPacked PackedValue { get; set; } + T PackedValue { get; set; } } /// diff --git a/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs index 7eac9fd030..f726163975 100644 --- a/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs @@ -10,8 +10,8 @@ namespace ImageProcessorCore /// /// Encapsulates properties to provides per-pixel access to an images pixels. /// - public interface IPixelAccessor : IPixelAccessor - where TPackedVector : IPackedVector, new() + public interface IPixelAccessor : IPixelAccessor + where T : IPackedVector, new() { /// /// Gets or sets the pixel at the specified position. @@ -24,8 +24,8 @@ namespace ImageProcessorCore /// The y-coordinate of the pixel. Must be greater /// than zero and smaller than the width of the pixel. /// - /// The at the specified position. - TPackedVector this[int x, int y] + /// The at the specified position. + T this[int x, int y] { get; set; diff --git a/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs b/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs index 2c3221ec6c..0c806d5e86 100644 --- a/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs +++ b/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs @@ -17,14 +17,14 @@ namespace ImageProcessorCore /// /// Calculates the target location and bounds to perform the resize operation against. /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// The source image. /// The resize options. /// /// The . /// - public static Rectangle CalculateTargetLocationAndBounds(ImageBase source, ResizeOptions options) - where TPackedVector : IPackedVector, new() + public static Rectangle CalculateTargetLocationAndBounds(ImageBase source, ResizeOptions options) + where T : IPackedVector, new() { switch (options.Mode) { @@ -48,14 +48,14 @@ namespace ImageProcessorCore /// /// Calculates the target rectangle for crop mode. /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// The source image. /// The resize options. /// /// The . /// - private static Rectangle CalculateCropRectangle(ImageBase source, ResizeOptions options) - where TPackedVector : IPackedVector, new() + private static Rectangle CalculateCropRectangle(ImageBase source, ResizeOptions options) + where T : IPackedVector, new() { int width = options.Size.Width; int height = options.Size.Height; @@ -167,14 +167,14 @@ namespace ImageProcessorCore /// /// Calculates the target rectangle for pad mode. /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// The source image. /// The resize options. /// /// The . /// - private static Rectangle CalculatePadRectangle(ImageBase source, ResizeOptions options) - where TPackedVector : IPackedVector, new() + private static Rectangle CalculatePadRectangle(ImageBase source, ResizeOptions options) + where T : IPackedVector, new() { int width = options.Size.Width; int height = options.Size.Height; @@ -248,14 +248,14 @@ namespace ImageProcessorCore /// /// Calculates the target rectangle for box pad mode. /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// The source image. /// The resize options. /// /// The . /// - private static Rectangle CalculateBoxPadRectangle(ImageBase source, ResizeOptions options) - where TPackedVector : IPackedVector, new() + private static Rectangle CalculateBoxPadRectangle(ImageBase source, ResizeOptions options) + where T : IPackedVector, new() { int width = options.Size.Width; int height = options.Size.Height; @@ -335,14 +335,14 @@ namespace ImageProcessorCore /// /// Calculates the target rectangle for max mode. /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// The source image. /// The resize options. /// /// The . /// - private static Rectangle CalculateMaxRectangle(ImageBase source, ResizeOptions options) - where TPackedVector : IPackedVector, new() + private static Rectangle CalculateMaxRectangle(ImageBase source, ResizeOptions options) + where T : IPackedVector, new() { int width = options.Size.Width; int height = options.Size.Height; @@ -376,14 +376,14 @@ namespace ImageProcessorCore /// /// Calculates the target rectangle for min mode. /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// The source image. /// The resize options. /// /// The . /// - private static Rectangle CalculateMinRectangle(ImageBase source, ResizeOptions options) - where TPackedVector : IPackedVector, new() + private static Rectangle CalculateMinRectangle(ImageBase source, ResizeOptions options) + where T : IPackedVector, new() { int width = options.Size.Width; int height = options.Size.Height; diff --git a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs index 11b397d745..67d20a5648 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs @@ -43,7 +43,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)) { @@ -53,7 +53,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) @@ -78,8 +78,8 @@ namespace ImageProcessorCore.Processors float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width; float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height; - using (IPixelAccessor sourcePixels = source.Lock()) - using (IPixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, @@ -114,10 +114,10 @@ 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 (IPixelAccessor sourcePixels = source.Lock()) - using (IPixelAccessor firstPassPixels = firstPass.Lock()) - using (IPixelAccessor 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, @@ -163,7 +163,6 @@ namespace ImageProcessorCore.Processors //Color sourceColor = compand // ? Color.Expand(sourcePixels[originX, y]) // : sourcePixels[originX, y]; - destination += sourceColor * xw.Value; } @@ -171,7 +170,7 @@ namespace ImageProcessorCore.Processors //{ // destination = Color.Compress(destination); //} - TPackedVector packed = new TPackedVector(); + T packed = new T(); packed.PackVector(destination); firstPassPixels[x, y] = packed; @@ -213,7 +212,7 @@ namespace ImageProcessorCore.Processors // destination = Color.Compress(destination); //} - TPackedVector packed = new TPackedVector(); + T packed = new T(); packed.PackVector(destination); targetPixels[x, y] = packed; @@ -227,7 +226,7 @@ namespace ImageProcessorCore.Processors } /// - protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { // Copy the pixels over. if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) diff --git a/src/ImageProcessorCore/Samplers/Resize.cs b/src/ImageProcessorCore/Samplers/Resize.cs index 3d81f41da8..3dd0a9c966 100644 --- a/src/ImageProcessorCore/Samplers/Resize.cs +++ b/src/ImageProcessorCore/Samplers/Resize.cs @@ -15,14 +15,14 @@ namespace ImageProcessorCore /// /// Resizes an image in accordance with the given . /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// The image to resize. /// The resize options. /// A delegate which is called as progress is made processing the image. - /// The + /// The /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, ResizeOptions options, ProgressEventHandler progressHandler = null) - where TPackedVector : IPackedVector, new() + public static Image Resize(this Image source, ResizeOptions options, ProgressEventHandler progressHandler = null) + where T : IPackedVector, new() { // Ensure size is populated across both dimensions. if (options.Size.Width == 0 && options.Size.Height > 0) @@ -43,15 +43,15 @@ namespace ImageProcessorCore /// /// Resizes an image to the given width and height. /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// The image to resize. /// The target image width. /// The target image height. /// A delegate which is called as progress is made processing the image. - /// The + /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, ProgressEventHandler progressHandler = null) - where TPackedVector : IPackedVector, new() + public static Image Resize(this Image source, int width, int height, ProgressEventHandler progressHandler = null) + where T : IPackedVector, new() { return Resize(source, width, height, new BicubicResampler(), false, progressHandler); } @@ -59,16 +59,16 @@ namespace ImageProcessorCore /// /// Resizes an image to the given width and height. /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// The image to resize. /// The target image width. /// The target image height. /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// A delegate which is called as progress is made processing the image. - /// The + /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, bool compand, ProgressEventHandler progressHandler = null) - where TPackedVector : IPackedVector, new() + public static Image Resize(this Image source, int width, int height, bool compand, ProgressEventHandler progressHandler = null) + where T : IPackedVector, new() { return Resize(source, width, height, new BicubicResampler(), compand, progressHandler); } @@ -76,17 +76,17 @@ namespace ImageProcessorCore /// /// Resizes an image to the given width and height with the given sampler. /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// The image to resize. /// The target image width. /// The target image height. /// The to perform the resampling. /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// A delegate which is called as progress is made processing the image. - /// The + /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, IResampler sampler, bool compand, ProgressEventHandler progressHandler = null) - where TPackedVector : IPackedVector, new() + public static Image Resize(this Image source, int width, int height, IResampler sampler, bool compand, ProgressEventHandler progressHandler = null) + where T : IPackedVector, new() { return Resize(source, width, height, sampler, source.Bounds, new Rectangle(0, 0, width, height), compand, progressHandler); } @@ -95,7 +95,7 @@ namespace ImageProcessorCore /// Resizes an image to the given width and height with the given sampler and /// source rectangle. /// - /// The type of pixels contained within the image. + /// The type of pixels contained within the image. /// The image to resize. /// The target image width. /// The target image height. @@ -108,10 +108,10 @@ namespace ImageProcessorCore /// /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// A delegate which is called as progress is made processing the image. - /// The + /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand = false, ProgressEventHandler progressHandler = null) - where TPackedVector : IPackedVector, new() + 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, new() { if (width == 0 && height > 0) { From 19380cf264fcf08216c06bcf2f8c871a76f48290 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 9 Jul 2016 20:06:02 +1000 Subject: [PATCH 13/91] Trim back to test cause of slowness. Former-commit-id: 467d987c47c6f4a97448a478aedba2b35d74b527 Former-commit-id: 38c2fb2764fc522bf2850bc8018f7303f51f56ed Former-commit-id: caa43702a4f1aa90c87daf68a236d48a6ffcfe44 --- src/ImageProcessorCore/PackedVector/Bgra32.cs | 34 ++++++++++++++++++- .../PackedVector/IPackedVector.cs | 12 +++++++ .../Samplers/Processors/ResizeProcessor.cs | 29 +++++++++------- 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/src/ImageProcessorCore/PackedVector/Bgra32.cs b/src/ImageProcessorCore/PackedVector/Bgra32.cs index fc6a788b08..5fdf24e17f 100644 --- a/src/ImageProcessorCore/PackedVector/Bgra32.cs +++ b/src/ImageProcessorCore/PackedVector/Bgra32.cs @@ -11,7 +11,7 @@ namespace ImageProcessorCore /// /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 1. /// - public struct Bgra32 : IPackedVector, IEquatable + public struct Bgra32 : IPackedVector, IEquatable { /// /// Initializes a new instance of the struct. @@ -41,6 +41,38 @@ namespace ImageProcessorCore /// public uint PackedValue { get; set; } + // The maths are wrong here I just wanted to test the performance to see if the + // issues are caused by the Vector transform or something else. + public void Add(IPackedVector value) + { + + } + + public void Subtract(IPackedVector value) + { + + } + + public void Multiply(IPackedVector value) + { + + } + + public void Multiply(float value) + { + + } + + public void Divide(IPackedVector value) + { + + } + + public void Divide(float value) + { + + } + /// /// Compares two objects for equality. /// diff --git a/src/ImageProcessorCore/PackedVector/IPackedVector.cs b/src/ImageProcessorCore/PackedVector/IPackedVector.cs index 7022e4d9d5..e7533a0dd6 100644 --- a/src/ImageProcessorCore/PackedVector/IPackedVector.cs +++ b/src/ImageProcessorCore/PackedVector/IPackedVector.cs @@ -29,6 +29,18 @@ namespace ImageProcessorCore /// public interface IPackedVector { + void Add(IPackedVector value); + + void Subtract(IPackedVector value); + + void Multiply(IPackedVector value); + + void Multiply(float value); + + void Divide(IPackedVector value); + + void Divide(float value); + /// /// Sets the packed representation from a . /// diff --git a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs index 67d20a5648..15d202ef4c 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs @@ -153,27 +153,29 @@ namespace ImageProcessorCore.Processors //} //firstPassPixels[x, y] = destination; - Vector4 destination = new Vector4(); + T destination = new T(); for (int i = 0; i < sum; i++) { Weight xw = horizontalValues[i]; int originX = xw.Index; - Vector4 sourceColor = sourcePixels[originX, y].ToVector4(); + T sourceColor = sourcePixels[originX, y]; //Color sourceColor = compand // ? Color.Expand(sourcePixels[originX, y]) // : sourcePixels[originX, y]; - destination += sourceColor * xw.Value; + //sourceColor.Multiply(xw.Value); + //destination.Add(sourceColor); + //destination += sourceColor * xw.Value; } //if (compand) //{ // destination = Color.Compress(destination); //} - T packed = new T(); - packed.PackVector(destination); + //T packed = new T(); + //packed.PackVector(destination); - firstPassPixels[x, y] = packed; + firstPassPixels[x, y] = destination; } } }); @@ -194,7 +196,7 @@ namespace ImageProcessorCore.Processors for (int x = 0; x < width; x++) { // Destination color components - Vector4 destination = new Vector4(); + T destination = new T(); for (int i = 0; i < sum; i++) { @@ -203,8 +205,11 @@ namespace ImageProcessorCore.Processors //Color sourceColor = compand // ? Color.Expand(firstPassPixels[x, originY]) // : firstPassPixels[x, originY]; - Vector4 sourceColor = firstPassPixels[x, originY].ToVector4(); - destination += sourceColor * yw.Value; + //Vector4 sourceColor = firstPassPixels[x, originY].ToVector4(); + //destination += sourceColor * yw.Value; + T sourceColor = firstPassPixels[x, originY]; + //sourceColor.Multiply(yw.Value); + //destination.Add(sourceColor); } //if (compand) @@ -212,10 +217,10 @@ namespace ImageProcessorCore.Processors // destination = Color.Compress(destination); //} - T packed = new T(); - packed.PackVector(destination); + //T packed = new T(); + //packed.PackVector(destination); - targetPixels[x, y] = packed; + targetPixels[x, y] = destination; } } From d3f8a2f3b0647f6b2eae7b50ff5d8b815c548406 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 9 Jul 2016 21:42:50 +1000 Subject: [PATCH 14/91] Move allocation outside the loop. Former-commit-id: 924260a626f911229b51c5758718261bd4b891cc Former-commit-id: b525508bf841c68404316c328e510a89fbe00a40 Former-commit-id: 67ebc6a448994abb588f5308f03e7cf8be70f9c7 --- .../Samplers/Processors/ResizeProcessor.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs index 15d202ef4c..3269b00ccd 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs @@ -153,26 +153,30 @@ namespace ImageProcessorCore.Processors //} //firstPassPixels[x, y] = destination; - T destination = new T(); + T sourceColor; + T destination = default(T); for (int i = 0; i < sum; i++) { Weight xw = horizontalValues[i]; int originX = xw.Index; - T sourceColor = sourcePixels[originX, y]; + sourceColor = sourcePixels[originX, y]; //Color sourceColor = compand // ? Color.Expand(sourcePixels[originX, y]) // : sourcePixels[originX, y]; //sourceColor.Multiply(xw.Value); //destination.Add(sourceColor); //destination += sourceColor * xw.Value; + + sourceColor.Multiply(xw.Value); + destination.Add(sourceColor); } //if (compand) //{ // destination = Color.Compress(destination); //} - //T packed = new T(); + //T packed = default(T); //packed.PackVector(destination); firstPassPixels[x, y] = destination; @@ -196,20 +200,22 @@ namespace ImageProcessorCore.Processors for (int x = 0; x < width; x++) { // Destination color components - T destination = new T(); + T sourceColor; + T destination = default(T); for (int i = 0; i < sum; i++) { Weight yw = verticalValues[i]; int originY = yw.Index; + sourceColor = firstPassPixels[x, originY]; //Color sourceColor = compand // ? Color.Expand(firstPassPixels[x, originY]) // : firstPassPixels[x, originY]; //Vector4 sourceColor = firstPassPixels[x, originY].ToVector4(); //destination += sourceColor * yw.Value; - T sourceColor = firstPassPixels[x, originY]; - //sourceColor.Multiply(yw.Value); - //destination.Add(sourceColor); + + sourceColor.Multiply(yw.Value); + destination.Add(sourceColor); } //if (compand) @@ -217,7 +223,7 @@ namespace ImageProcessorCore.Processors // destination = Color.Compress(destination); //} - //T packed = new T(); + //T packed = default(T); //packed.PackVector(destination); targetPixels[x, y] = destination; From ccb982033741ee7794ef57c3a4f6f833789b7358 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 12 Jul 2016 16:52:06 +1000 Subject: [PATCH 15/91] Experiment with multipliers. Former-commit-id: 084a071bbe2f57ed8eaa29fc48e77ebf24eb204b Former-commit-id: cf8ca002917043c93bd18c10e21c0005999ef972 Former-commit-id: a7f231eb49f32fe8b3bc9bff312fd9995e2dabbc --- GenericImage/IImageBase.cs | 9 +- GenericImage/IImageProcessor.cs | 76 ++++ GenericImage/IPixelAccessor.cs | 16 +- GenericImage/ImageBase.cs | 40 +- GenericImage/ImageProcessor.cs | 165 ++++++++ GenericImage/PackedVectors/Bgra.cs | 52 +++ GenericImage/PackedVectors/IColor.cs | 37 ++ GenericImage/ProgressEventArgs.cs | 23 + GenericImage/ResizeProcessor.cs | 393 ++++++++++++++++++ src/ImageProcessorCore/PackedVector/Bgra32.cs | 4 +- .../PackedVector/IPackedVector.cs | 22 +- 11 files changed, 804 insertions(+), 33 deletions(-) create mode 100644 GenericImage/IImageProcessor.cs create mode 100644 GenericImage/ImageProcessor.cs create mode 100644 GenericImage/PackedVectors/Bgra.cs create mode 100644 GenericImage/PackedVectors/IColor.cs create mode 100644 GenericImage/ProgressEventArgs.cs create mode 100644 GenericImage/ResizeProcessor.cs diff --git a/GenericImage/IImageBase.cs b/GenericImage/IImageBase.cs index 4e614a628d..d5dac55168 100644 --- a/GenericImage/IImageBase.cs +++ b/GenericImage/IImageBase.cs @@ -2,15 +2,16 @@ { using GenericImage.PackedVectors; - public interface IImageBase - where TPacked : IPackedVector + public interface IImageBase + where TColor : IColor + where TDepth : struct { - TPacked[] Pixels { get; } + TColor[] Pixels { get; } int Width { get; } int Height { get; } - IPixelAccessor Lock(); + IPixelAccessor Lock(); } } diff --git a/GenericImage/IImageProcessor.cs b/GenericImage/IImageProcessor.cs new file mode 100644 index 0000000000..68aecb3be8 --- /dev/null +++ b/GenericImage/IImageProcessor.cs @@ -0,0 +1,76 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace GenericImage +{ + using PackedVectors; + + /// + /// A delegate which is called as progress is made processing an image. + /// + /// The source of the event. + /// An object that contains the event data. + public delegate void ProgressEventHandler(object sender, ProgressEventArgs e); + + /// + /// Encapsulates methods to alter the pixels of an image. + /// + public interface IImageProcessor + { + /// + /// Event fires when each row of the source image has been processed. + /// + /// + /// This event may be called from threads other than the client thread, and from multiple threads simultaneously. + /// Individual row notifications may arrived out of order. + /// + event ProgressEventHandler OnProgress; + + /// + /// Applies the process to the specified portion of the specified . + /// + /// The type of pixels contained within the image. + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// The method keeps the source image unchanged and returns the + /// the result of image processing filter as new image. + /// + /// + /// is null or is null. + /// + /// + /// doesnt fit the dimension of the image. + /// + void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) + where TColor : IColor, new() where TDepth : struct; + + /// + /// Applies the process to the specified portion of the specified at the specified + /// location and with the specified size. + /// + /// The type of pixels contained within the image. + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// The target width. + /// The target height. + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. + /// + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// + /// The method keeps the source image unchanged and returns the + /// the result of image process as new image. + /// + void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle) + where TColor : IColor, new() where TDepth : struct; + } +} diff --git a/GenericImage/IPixelAccessor.cs b/GenericImage/IPixelAccessor.cs index 7eb9b445b6..3ef5887916 100644 --- a/GenericImage/IPixelAccessor.cs +++ b/GenericImage/IPixelAccessor.cs @@ -2,14 +2,22 @@ { using System; - using GenericImage.PackedVectors; - - public interface IPixelAccessor : IDisposable + public interface IPixelAccessor : IDisposable { - IPackedVector this[int x, int y] + TColor this[int x, int y] { get; set; } + + /// + /// Gets the width. + /// + int Width { get; } + + /// + /// Gets the height. + /// + int Height { get; } } } diff --git a/GenericImage/ImageBase.cs b/GenericImage/ImageBase.cs index 9eed0dcccb..e8052d36ca 100644 --- a/GenericImage/ImageBase.cs +++ b/GenericImage/ImageBase.cs @@ -8,21 +8,21 @@ /// Encapsulates the basic properties and methods required to manipulate images /// in different pixel formats. /// - /// - /// The packed vector pixels format. - /// - public abstract class ImageBase - where TPacked : IPackedVector + /// The packed vector pixels format. + /// The bit depth of the image. byte, float, etc. + public abstract class ImageBase : IImageBase + where TColor : IColor + where TDepth : struct { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// protected ImageBase() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The width of the image in pixels. /// The height of the image in pixels. @@ -36,19 +36,19 @@ this.Width = width; this.Height = height; - this.Pixels = new TPacked[width * height]; + this.Pixels = new TColor[width * height]; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - /// The other to create this instance from. + /// The other to create this instance from. /// /// - /// Thrown if the given is null. + /// Thrown if the given is null. /// - protected ImageBase(ImageBase other) + protected ImageBase(ImageBase other) { // Guard.NotNull(other, nameof(other), "Other image cannot be null."); @@ -58,14 +58,14 @@ this.FrameDelay = other.FrameDelay; // Copy the pixels. - this.Pixels = new TPacked[this.Width * this.Height]; + this.Pixels = new TColor[this.Width * this.Height]; Array.Copy(other.Pixels, this.Pixels, other.Pixels.Length); } /// /// Gets the pixels as an array of the given packed pixel format. /// - public TPacked[] Pixels { get; private set; } + public TColor[] Pixels { get; private set; } /// /// Gets the width in pixels. @@ -100,6 +100,8 @@ /// public int FrameDelay { get; set; } + + /// /// Sets the pixel array of the image to the given value. /// @@ -114,7 +116,7 @@ /// /// Thrown if the length is not equal to Width * Height. /// - public void SetPixels(int width, int height, TPacked[] pixels) + public void SetPixels(int width, int height, TColor[] pixels) { if (width <= 0) { @@ -151,7 +153,7 @@ /// /// Thrown if the length is not equal to Width * Height. /// - public void ClonePixels(int width, int height, TPacked[] pixels) + public void ClonePixels(int width, int height, TColor[] pixels) { if (width <= 0) { @@ -172,7 +174,7 @@ this.Height = height; // Copy the pixels. - this.Pixels = new TPacked[pixels.Length]; + this.Pixels = new TColor[pixels.Length]; Array.Copy(pixels, this.Pixels, pixels.Length); } @@ -182,7 +184,7 @@ /// It is imperative that the accessor is correctly disposed off after use. /// /// - /// The - public abstract IPixelAccessor Lock(); + /// The + public abstract IPixelAccessor Lock(); } } diff --git a/GenericImage/ImageProcessor.cs b/GenericImage/ImageProcessor.cs new file mode 100644 index 0000000000..18f4966206 --- /dev/null +++ b/GenericImage/ImageProcessor.cs @@ -0,0 +1,165 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace GenericImage +{ + using System; + using System.Threading; + + using GenericImage.PackedVectors; + + /// + /// Allows the application of processors to images. + /// + public abstract class ImageProcessor : IImageProcessor + { + /// + public event ProgressEventHandler OnProgress; + + /// + /// The number of rows processed by a derived class. + /// + private int numRowsProcessed; + + /// + /// The total number of rows that will be processed by a derived class. + /// + private int totalRows; + + /// + public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) + where TColor : IColor, new() where TDepth : struct + { + try + { + this.OnApply(target, source, target.Bounds, sourceRectangle); + + this.numRowsProcessed = 0; + this.totalRows = sourceRectangle.Height; + + this.Apply(target, source, target.Bounds, sourceRectangle, sourceRectangle.Y, sourceRectangle.Bottom); + + this.AfterApply(target, source, target.Bounds, sourceRectangle); + } + catch (Exception ex) + { + + throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); + } + } + + /// + public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle) where TColor : IColor, new() where TDepth : struct + { + try + { + TColor[] pixels = new TColor[width * height]; + target.SetPixels(width, height, pixels); + + // Ensure we always have bounds. + if (sourceRectangle == Rectangle.Empty) + { + sourceRectangle = source.Bounds; + } + + if (targetRectangle == Rectangle.Empty) + { + targetRectangle = target.Bounds; + } + + this.OnApply(target, source, targetRectangle, sourceRectangle); + + this.numRowsProcessed = 0; + this.totalRows = targetRectangle.Height; + + this.Apply(target, source, targetRectangle, sourceRectangle, targetRectangle.Y, targetRectangle.Bottom); + + this.AfterApply(target, source, target.Bounds, sourceRectangle); + } + catch (Exception ex) + { + throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); + } + } + + /// + /// This method is called before the process is applied to prepare the processor. + /// + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. + /// + /// + /// The structure that specifies the portion of the image object to draw. + /// + protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + where TColor : IColor, new() where TDepth : struct + { + } + + /// + /// Applies the process to the specified portion of the specified at the specified location + /// and with the specified size. + /// + /// The type of pixels contained within the image. + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. + /// + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The index of the row within the source image to start processing. + /// The index of the row within the source image to end processing. + /// + /// The method keeps the source image unchanged and returns the + /// the result of image process as new image. + /// + protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + where TColor : IColor, new() where TDepth : struct; + + /// + /// This method is called after the process is applied to prepare the processor. + /// + /// The type of pixels contained within the image. + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// + /// The structure that specifies the location and size of the drawn image. + /// The image is scaled to fit the rectangle. + /// + /// + /// The structure that specifies the portion of the image object to draw. + /// + protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + where TColor : IColor, new() where TDepth : struct + { + } + + /// + /// Must be called by derived classes after processing a single row. + /// + protected void OnRowProcessed() + { + if (this.OnProgress != null) + { + int currThreadNumRows = Interlocked.Add(ref this.numRowsProcessed, 1); + + // Multi-pass filters process multiple times more rows than totalRows, so update totalRows on the fly + if (currThreadNumRows > this.totalRows) + { + this.totalRows = currThreadNumRows; + } + + // Report progress. This may be on the client's thread, or on a Task library thread. + this.OnProgress(this, new ProgressEventArgs { RowsProcessed = currThreadNumRows, TotalRows = this.totalRows }); + } + } + } +} diff --git a/GenericImage/PackedVectors/Bgra.cs b/GenericImage/PackedVectors/Bgra.cs new file mode 100644 index 0000000000..7b82c0683a --- /dev/null +++ b/GenericImage/PackedVectors/Bgra.cs @@ -0,0 +1,52 @@ +using System; +using System.Numerics; + +namespace GenericImage.PackedVectors +{ + public struct Bgra : IColor4 + where TDepth : struct + { + public TDepth X { get; set; } + + public TDepth Y { get; set; } + + public TDepth Z { get; set; } + + public TDepth W { get; set; } + + public void Add(TColor value) where TColor : IColor + { + throw new NotImplementedException(); + } + + public void Multiply(TColor value) where TColor : IColor + { + throw new NotImplementedException(); + } + + public void Multiply(float value) where TColor : IColor + { + throw new NotImplementedException(); + } + + public void Divide(TColor value) where TColor : IColor + { + throw new NotImplementedException(); + } + + public void PackVector(Vector4 vector) + { + throw new NotImplementedException(); + } + + public Vector4 ToVector() + { + throw new NotImplementedException(); + } + + public byte[] ToBytes() + { + throw new System.NotImplementedException(); + } + } +} diff --git a/GenericImage/PackedVectors/IColor.cs b/GenericImage/PackedVectors/IColor.cs new file mode 100644 index 0000000000..52825d63a6 --- /dev/null +++ b/GenericImage/PackedVectors/IColor.cs @@ -0,0 +1,37 @@ +namespace GenericImage.PackedVectors +{ + using System.Numerics; + + public interface IColor4 : IColor + where T : struct + { + T X { get; set; } + + T Y { get; set; } + + T Z { get; set; } + + T W { get; set; } + } + + public interface IColor : IColor + where TDepth : struct + { + void Add(TColor value) where TColor : IColor; + + void Multiply(TColor value) where TColor : IColor; + + void Multiply(float value) where TColor : IColor; + + void Divide(TColor value) where TColor : IColor; + } + + public interface IColor + { + void PackVector(Vector4 vector); + + Vector4 ToVector(); + + byte[] ToBytes(); + } +} diff --git a/GenericImage/ProgressEventArgs.cs b/GenericImage/ProgressEventArgs.cs new file mode 100644 index 0000000000..5e9a1e9598 --- /dev/null +++ b/GenericImage/ProgressEventArgs.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace GenericImage +{ + /// + /// Contains event data related to the progress made processing an image. + /// + public class ProgressEventArgs : System.EventArgs + { + /// + /// Gets or sets the number of rows processed. + /// + public int RowsProcessed { get; set; } + + /// + /// Gets or sets the total number of rows. + /// + public int TotalRows { get; set; } + } +} \ No newline at end of file diff --git a/GenericImage/ResizeProcessor.cs b/GenericImage/ResizeProcessor.cs new file mode 100644 index 0000000000..9752e2960d --- /dev/null +++ b/GenericImage/ResizeProcessor.cs @@ -0,0 +1,393 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + using GenericImage; + + /// + /// Provides methods that allow the resizing of images using various algorithms. + /// + public class ResizeProcessor : ImageProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The sampler to perform the resize operation. + /// + public ResizeProcessor(IResampler sampler) + { + Guard.NotNull(sampler, nameof(sampler)); + + this.Sampler = sampler; + } + + /// + /// Gets the sampler to perform the resize operation. + /// + public IResampler Sampler { get; } + + /// + /// Gets or sets the horizontal weights. + /// + protected Weights[] HorizontalWeights { get; set; } + + /// + /// Gets or sets the vertical weights. + /// + protected Weights[] VerticalWeights { get; set; } + + /// + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + if (!(this.Sampler is NearestNeighborResampler)) + { + this.HorizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width); + this.VerticalWeights = this.PrecomputeWeights(targetRectangle.Height, sourceRectangle.Height); + } + } + + /// + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + // Jump out, we'll deal with that later. + if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) + { + return; + } + + int width = target.Width; + int height = target.Height; + int sourceHeight = sourceRectangle.Height; + int targetX = target.Bounds.X; + int targetY = target.Bounds.Y; + int targetRight = target.Bounds.Right; + int targetBottom = target.Bounds.Bottom; + int startX = targetRectangle.X; + int endX = targetRectangle.Right; + bool compand = this.Compand; + + if (this.Sampler is NearestNeighborResampler) + { + // Scaling factors + float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width; + float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height; + + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => + { + if (targetY <= y && y < targetBottom) + { + // Y coordinates of source points + int originY = (int)((y - startY) * heightFactor); + + for (int x = startX; x < endX; x++) + { + if (targetX <= x && x < targetRight) + { + // X coordinates of source points + int originX = (int)((x - startX) * widthFactor); + targetPixels[x, y] = sourcePixels[originX, originY]; + } + } + + this.OnRowProcessed(); + } + }); + } + + // Break out now. + return; + } + + // Interpolate the image using the calculated weights. + // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm + // First process the columns. Since we are not using multiple threads startY and endY + // are the upper and lower bounds of the source rectangle. + 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, + y => + { + for (int x = startX; x < endX; x++) + { + if (x >= 0 && x < width) + { + // Ensure offsets are normalised for cropping and padding. + int offsetX = x - startX; + float sum = this.HorizontalWeights[offsetX].Sum; + Weight[] horizontalValues = this.HorizontalWeights[offsetX].Values; + + // Destination color components + //Color destination = new Color(); + + //for (int i = 0; i < sum; i++) + //{ + // Weight xw = horizontalValues[i]; + // int originX = xw.Index; + // Color sourceColor = compand + // ? Color.Expand(sourcePixels[originX, y]) + // : sourcePixels[originX, y]; + + // destination += sourceColor * xw.Value; + //} + + //if (compand) + //{ + // destination = Color.Compress(destination); + //} + + //firstPassPixels[x, y] = destination; + TColor sourceColor; + TColor destination = default(TColor); + + for (int i = 0; i < sum; i++) + { + Weight xw = horizontalValues[i]; + int originX = xw.Index; + sourceColor = sourcePixels[originX, y]; + //Color sourceColor = compand + // ? Color.Expand(sourcePixels[originX, y]) + // : sourcePixels[originX, y]; + //sourceColor.Multiply(xw.Value); + //destination.Add(sourceColor); + //destination += sourceColor * xw.Value; + + sourceColor.Multiply(xw.Value); + destination.Add(sourceColor); + } + + //if (compand) + //{ + // destination = Color.Compress(destination); + //} + //T packed = default(T); + //packed.PackVector(destination); + + firstPassPixels[x, y] = destination; + } + } + }); + + // Now process the rows. + Parallel.For( + startY, + endY, + y => + { + if (y >= 0 && y < height) + { + // Ensure offsets are normalised for cropping and padding. + int offsetY = y - startY; + float sum = this.VerticalWeights[offsetY].Sum; + Weight[] verticalValues = this.VerticalWeights[offsetY].Values; + + for (int x = 0; x < width; x++) + { + // Destination color components + TColor sourceColor; + TColor destination = default(TColor); + + for (int i = 0; i < sum; i++) + { + Weight yw = verticalValues[i]; + int originY = yw.Index; + sourceColor = firstPassPixels[x, originY]; + //Color sourceColor = compand + // ? Color.Expand(firstPassPixels[x, originY]) + // : firstPassPixels[x, originY]; + //Vector4 sourceColor = firstPassPixels[x, originY].ToVector4(); + //destination += sourceColor * yw.Value; + + sourceColor.Multiply(yw.Value); + destination.Add(sourceColor); + } + + //if (compand) + //{ + // destination = Color.Compress(destination); + //} + + //T packed = default(T); + //packed.PackVector(destination); + + targetPixels[x, y] = destination; + } + } + + this.OnRowProcessed(); + }); + + } + } + + /// + protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + // Copy the pixels over. + if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) + { + target.ClonePixels(target.Width, target.Height, source.Pixels); + } + } + + /// + /// Computes the weights to apply at each pixel when resizing. + /// + /// The destination section size. + /// The source section size. + /// + /// The . + /// + protected Weights[] PrecomputeWeights(int destinationSize, int sourceSize) + { + float scale = (float)destinationSize / sourceSize; + IResampler sampler = this.Sampler; + float radius = sampler.Radius; + double left; + double right; + float weight; + int index; + int sum; + + Weights[] result = new Weights[destinationSize]; + + // When shrinking, broaden the effective kernel support so that we still + // visit every source pixel. + if (scale < 1) + { + float width = radius / scale; + float filterScale = 1 / scale; + + // Make the weights slices, one source for each column or row. + for (int i = 0; i < destinationSize; i++) + { + float centre = i / scale; + left = Math.Ceiling(centre - width); + right = Math.Floor(centre + width); + + result[i] = new Weights + { + Values = new Weight[(int)(right - left + 1)] + }; + + for (double j = left; j <= right; j++) + { + weight = sampler.GetValue((float)((centre - j) / filterScale)) / filterScale; + if (j < 0) + { + index = (int)-j; + } + else if (j >= sourceSize) + { + index = (int)((sourceSize - j) + sourceSize - 1); + } + else + { + index = (int)j; + } + + sum = (int)result[i].Sum++; + result[i].Values[sum] = new Weight(index, weight); + } + } + } + else + { + // Make the weights slices, one source for each column or row. + for (int i = 0; i < destinationSize; i++) + { + float centre = i / scale; + left = Math.Ceiling(centre - radius); + right = Math.Floor(centre + radius); + result[i] = new Weights + { + Values = new Weight[(int)(right - left + 1)] + }; + + for (double j = left; j <= right; j++) + { + weight = sampler.GetValue((float)(centre - j)); + if (j < 0) + { + index = (int)-j; + } + else if (j >= sourceSize) + { + index = (int)((sourceSize - j) + sourceSize - 1); + } + else + { + index = (int)j; + } + + sum = (int)result[i].Sum++; + result[i].Values[sum] = new Weight(index, weight); + } + } + } + + return result; + } + + /// + /// Represents the weight to be added to a scaled pixel. + /// + protected struct Weight + { + /// + /// Initializes a new instance of the struct. + /// + /// The index. + /// The value. + public Weight(int index, float value) + { + this.Index = index; + this.Value = value; + } + + /// + /// Gets the pixel index. + /// + public int Index { get; } + + /// + /// Gets the result of the interpolation algorithm. + /// + public float Value { get; } + } + + /// + /// Represents a collection of weights and their sum. + /// + protected class Weights + { + /// + /// Gets or sets the values. + /// + public Weight[] Values { get; set; } + + /// + /// Gets or sets the sum. + /// + public float Sum { get; set; } + } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore/PackedVector/Bgra32.cs b/src/ImageProcessorCore/PackedVector/Bgra32.cs index 5fdf24e17f..c1119d9c45 100644 --- a/src/ImageProcessorCore/PackedVector/Bgra32.cs +++ b/src/ImageProcessorCore/PackedVector/Bgra32.cs @@ -11,7 +11,7 @@ namespace ImageProcessorCore /// /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 1. /// - public struct Bgra32 : IPackedVector, IEquatable + public struct Bgra32 : IPackedVector, IEquatable { /// /// Initializes a new instance of the struct. @@ -43,7 +43,7 @@ namespace ImageProcessorCore // The maths are wrong here I just wanted to test the performance to see if the // issues are caused by the Vector transform or something else. - public void Add(IPackedVector value) + public void Add(IPackedVector value) { } diff --git a/src/ImageProcessorCore/PackedVector/IPackedVector.cs b/src/ImageProcessorCore/PackedVector/IPackedVector.cs index e7533a0dd6..bf4556309b 100644 --- a/src/ImageProcessorCore/PackedVector/IPackedVector.cs +++ b/src/ImageProcessorCore/PackedVector/IPackedVector.cs @@ -22,6 +22,8 @@ namespace ImageProcessorCore /// Typically packed in least to greatest significance order. /// T PackedValue { get; set; } + + } /// @@ -29,18 +31,30 @@ namespace ImageProcessorCore /// public interface IPackedVector { - void Add(IPackedVector value); + void Add(U value); - void Subtract(IPackedVector value); + void Subtract(U value); - void Multiply(IPackedVector value); + void Multiply(U value); void Multiply(float value); - void Divide(IPackedVector value); + void Divide(U value); void Divide(float value); + //void Add(IPackedVector value); + + //void Subtract(IPackedVector value); + + //void Multiply(IPackedVector value); + + //void Multiply(float value); + + //void Divide(IPackedVector value); + + //void Divide(float value); + /// /// Sets the packed representation from a . /// From cfda01fc444b685a9df17a779b6ce3bbf757b17d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 13 Jul 2016 00:48:46 +1000 Subject: [PATCH 16/91] Use Operator Former-commit-id: 1bc110efc169542f998698c406473969ff03432a Former-commit-id: 0a96341575e95754884d20aff07d2f31f4265576 Former-commit-id: 404c8605fcc6a522e96b5812d647a89337ce65cc --- GenericImage/Common/Helpers/Class.cs | 58 +++ GenericImage/Common/Helpers/ExpressionUtil.cs | 100 ++++ GenericImage/Common/Helpers/Operator.cs | 479 ++++++++++++++++++ GenericImage/PackedVectors/Bgra.cs | 67 ++- GenericImage/PackedVectors/IColor.cs | 11 +- GenericImage/project.json | 1 + .../Common/Helpers/Class.cs | 58 +++ .../Common/Helpers/ExpressionUtil.cs | 100 ++++ .../Common/Helpers/Operator.cs | 464 +++++++++++++++++ src/ImageProcessorCore/PackedVector/Bgra32.cs | 26 +- .../PackedVector/IPackedVector.cs | 24 +- src/ImageProcessorCore/project.json | 1 + 12 files changed, 1344 insertions(+), 45 deletions(-) create mode 100644 GenericImage/Common/Helpers/Class.cs create mode 100644 GenericImage/Common/Helpers/ExpressionUtil.cs create mode 100644 GenericImage/Common/Helpers/Operator.cs create mode 100644 src/ImageProcessorCore/Common/Helpers/Class.cs create mode 100644 src/ImageProcessorCore/Common/Helpers/ExpressionUtil.cs create mode 100644 src/ImageProcessorCore/Common/Helpers/Operator.cs diff --git a/GenericImage/Common/Helpers/Class.cs b/GenericImage/Common/Helpers/Class.cs new file mode 100644 index 0000000000..ad446739ac --- /dev/null +++ b/GenericImage/Common/Helpers/Class.cs @@ -0,0 +1,58 @@ +namespace GenericImage.Helpers +{ + interface INullOp + { + bool HasValue(T value); + bool AddIfNotNull(ref T accumulator, T value); + } + sealed class StructNullOp + : INullOp, INullOp + where T : struct + { + public bool HasValue(T value) + { + return true; + } + public bool AddIfNotNull(ref T accumulator, T value) + { + accumulator = Operator.Add(accumulator, value); + return true; + } + public bool HasValue(T? value) + { + return value.HasValue; + } + public bool AddIfNotNull(ref T? accumulator, T? value) + { + if (value.HasValue) + { + accumulator = accumulator.HasValue ? + Operator.Add( + accumulator.GetValueOrDefault(), + value.GetValueOrDefault()) + : value; + return true; + } + return false; + } + } + sealed class ClassNullOp + : INullOp + where T : class + { + public bool HasValue(T value) + { + return value != null; + } + public bool AddIfNotNull(ref T accumulator, T value) + { + if (value != null) + { + accumulator = accumulator == null ? + value : Operator.Add(accumulator, value); + return true; + } + return false; + } + } +} diff --git a/GenericImage/Common/Helpers/ExpressionUtil.cs b/GenericImage/Common/Helpers/ExpressionUtil.cs new file mode 100644 index 0000000000..fc66742a01 --- /dev/null +++ b/GenericImage/Common/Helpers/ExpressionUtil.cs @@ -0,0 +1,100 @@ +namespace GenericImage.Helpers +{ + using System; + using System.Linq.Expressions; + + /// + /// General purpose Expression utilities + /// + public static class ExpressionUtil + { + /// + /// Create a function delegate representing a unary operation + /// + /// The parameter type + /// The return type + /// Body factory + /// Compiled function delegate + public static Func CreateExpression( + Func body) + { + ParameterExpression inp = Expression.Parameter(typeof(TArg1), "inp"); + try + { + return Expression.Lambda>(body(inp), inp).Compile(); + } + catch (Exception ex) + { + string msg = ex.Message; // avoid capture of ex itself + return delegate { throw new InvalidOperationException(msg); }; + } + } + + /// + /// Create a function delegate representing a binary operation + /// + /// The first parameter type + /// The second parameter type + /// The return type + /// Body factory + /// Compiled function delegate + public static Func CreateExpression( + Func body) + { + return CreateExpression(body, false); + } + + /// + /// Create a function delegate representing a binary operation + /// + /// + /// If no matching operation is possible, attempt to convert + /// TArg1 and TArg2 to TResult for a match? For example, there is no + /// "decimal operator /(decimal, int)", but by converting TArg2 (int) to + /// TResult (decimal) a match is found. + /// + /// The first parameter type + /// The second parameter type + /// The return type + /// Body factory + /// Compiled function delegate + public static Func CreateExpression( + Func body, bool castArgsToResultOnFailure) + { + ParameterExpression lhs = Expression.Parameter(typeof(TArg1), "lhs"); + ParameterExpression rhs = Expression.Parameter(typeof(TArg2), "rhs"); + try + { + try + { + return Expression.Lambda>(body(lhs, rhs), lhs, rhs).Compile(); + } + catch (InvalidOperationException) + { + // If we show retry and the args aren't already "TValue, TValue, TValue"... + // convert both lhs and rhs to TResult (as appropriate) + if (castArgsToResultOnFailure && !(typeof(TArg1) == typeof(TResult) && typeof(TArg2) == typeof(TResult))) + { + Expression castLhs = typeof(TArg1) == typeof(TResult) + ? lhs + : (Expression)Expression.Convert(lhs, typeof(TResult)); + + Expression castRhs = typeof(TArg2) == typeof(TResult) + ? rhs + : (Expression)Expression.Convert(rhs, typeof(TResult)); + + return Expression.Lambda>( + body(castLhs, castRhs), lhs, rhs).Compile(); + } + + throw; + } + } + catch (Exception ex) + { + string msg = ex.Message; // avoid capture of ex itself + return delegate { throw new InvalidOperationException(msg); }; + } + } + } +} diff --git a/GenericImage/Common/Helpers/Operator.cs b/GenericImage/Common/Helpers/Operator.cs new file mode 100644 index 0000000000..872eb3abe9 --- /dev/null +++ b/GenericImage/Common/Helpers/Operator.cs @@ -0,0 +1,479 @@ +namespace GenericImage.Helpers +{ + using System; + using System.Linq.Expressions; + using System.Reflection; + + /// + /// The Operator class provides easy access to the standard operators + /// (addition, etc) for generic types, using type inference to simplify + /// usage. + /// + public static class Operator + { + + /// + /// Indicates if the supplied value is non-null, + /// for reference-types or Nullable<T> + /// + /// True for non-null values, else false + public static bool HasValue(T value) + { + + return Operator.NullOp.HasValue(value); + + } + + /// + /// Increments the accumulator only + /// if the value is non-null. If the accumulator + /// is null, then the accumulator is given the new + /// value; otherwise the accumulator and value + /// are added. + /// + /// The current total to be incremented (can be null) + /// The value to be tested and added to the accumulator + /// True if the value is non-null, else false - i.e. + /// "has the accumulator been updated?" + public static bool AddIfNotNull(ref T accumulator, T value) + { + return Operator.NullOp.AddIfNotNull(ref accumulator, value); + } + + /// + /// Evaluates unary negation (-) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static T Negate(T value) + { + return Operator.Negate(value); + } + /// + /// Evaluates bitwise not (~) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static T Not(T value) + { + return Operator.Not(value); + } + /// + /// Evaluates bitwise or (|) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static T Or(T value1, T value2) + { + return Operator.Or(value1, value2); + } + /// + /// Evaluates bitwise and (&) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static T And(T value1, T value2) + { + return Operator.And(value1, value2); + } + /// + /// Evaluates bitwise xor (^) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static T Xor(T value1, T value2) + { + return Operator.Xor(value1, value2); + } + /// + /// Performs a conversion between the given types; this will throw + /// an InvalidOperationException if the type T does not provide a suitable cast, or for + /// Nullable<TInner> if TInner does not provide this cast. + /// + public static TTo Convert(TFrom value) + { + return Operator.Convert(value); + } + /// + /// Evaluates binary addition (+) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static T Add(T value1, T value2) + { + return Operator.Add(value1, value2); + } + /// + /// Evaluates binary addition (+) for the given type(s); this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static TArg1 AddAlternative(TArg1 value1, TArg2 value2) + { + return Operator.Add(value1, value2); + } + /// + /// Evaluates binary subtraction (-) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static T Subtract(T value1, T value2) + { + return Operator.Subtract(value1, value2); + } + /// + /// Evaluates binary subtraction(-) for the given type(s); this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static TArg1 SubtractAlternative(TArg1 value1, TArg2 value2) + { + return Operator.Subtract(value1, value2); + } + /// + /// Evaluates binary multiplication (*) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static T Multiply(T value1, T value2) + { + return Operator.Multiply(value1, value2); + } + /// + /// Evaluates binary multiplication (*) for the given type(s); this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static TArg1 MultiplyAlternative(TArg1 value1, TArg2 value2) + { + return Operator.Multiply(value1, value2); + } + /// + /// Evaluates binary division (/) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static T Divide(T value1, T value2) + { + return Operator.Divide(value1, value2); + } + /// + /// Evaluates binary division (/) for the given type(s); this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static TArg1 DivideAlternative(TArg1 value1, TArg2 value2) + { + return Operator.Divide(value1, value2); + } + /// + /// Evaluates binary equality (==) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static bool Equal(T value1, T value2) + { + return Operator.Equal(value1, value2); + } + /// + /// Evaluates binary inequality (!=) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static bool NotEqual(T value1, T value2) + { + return Operator.NotEqual(value1, value2); + } + /// + /// Evaluates binary greater-than (>) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static bool GreaterThan(T value1, T value2) + { + return Operator.GreaterThan(value1, value2); + } + /// + /// Evaluates binary less-than (<) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static bool LessThan(T value1, T value2) + { + return Operator.LessThan(value1, value2); + } + /// + /// Evaluates binary greater-than-on-eqauls (>=) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static bool GreaterThanOrEqual(T value1, T value2) + { + return Operator.GreaterThanOrEqual(value1, value2); + } + /// + /// Evaluates binary less-than-or-equal (<=) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static bool LessThanOrEqual(T value1, T value2) + { + return Operator.LessThanOrEqual(value1, value2); + } + /// + /// Evaluates integer division (/) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + /// This operation is particularly useful for computing averages and + /// similar aggregates. + /// + public static T DivideInt32(T value, int divisor) + { + return Operator.Divide(value, divisor); + } + } + /// + /// Provides standard operators (such as addition) that operate over operands of + /// different types. For operators, the return type is assumed to match the first + /// operand. + /// + /// + /// + public static class Operator + { + private static readonly Func convert; + /// + /// Returns a delegate to convert a value between two types; this delegate will throw + /// an InvalidOperationException if the type T does not provide a suitable cast, or for + /// Nullable<TInner> if TInner does not provide this cast. + /// + public static Func Convert => convert; + + static Operator() + { + convert = ExpressionUtil.CreateExpression(body => Expression.Convert(body, typeof(TResult))); + add = ExpressionUtil.CreateExpression(Expression.Add, true); + subtract = ExpressionUtil.CreateExpression(Expression.Subtract, true); + multiply = ExpressionUtil.CreateExpression(Expression.Multiply, true); + divide = ExpressionUtil.CreateExpression(Expression.Divide, true); + } + + private static readonly Func add, subtract, multiply, divide; + + private static readonly Func multiplyF, divideF; + + /// + /// Returns a delegate to evaluate binary addition (+) for the given types; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Add => add; + + /// + /// Returns a delegate to evaluate binary subtraction (-) for the given types; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Subtract => subtract; + + /// + /// Returns a delegate to evaluate binary multiplication (*) for the given types; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Multiply => multiply; + + /// + /// Returns a delegate to evaluate binary division (/) for the given types; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Divide => divide; + + public static Func MultiplyF => multiplyF; + + public static Func DivideF => divideF; + } + + /// + /// Provides standard operators (such as addition) over a single type + /// + /// + /// + public static class Operator + { + static readonly INullOp nullOp; + internal static INullOp NullOp => nullOp; + + static readonly T zero; + /// + /// Returns the zero value for value-types (even full Nullable<TInner>) - or null for reference types + /// + public static T Zero => zero; + + static readonly Func negate, not; + static readonly Func or, and, xor; + /// + /// Returns a delegate to evaluate unary negation (-) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Negate => negate; + + /// + /// Returns a delegate to evaluate bitwise not (~) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Not => not; + + /// + /// Returns a delegate to evaluate bitwise or (|) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Or => or; + + /// + /// Returns a delegate to evaluate bitwise and (&) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func And => and; + + /// + /// Returns a delegate to evaluate bitwise xor (^) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Xor => xor; + + static readonly Func add, subtract, multiply, divide; + + static readonly Func multiplyF, divideF; + + /// + /// Returns a delegate to evaluate binary addition (+) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Add => add; + + /// + /// Returns a delegate to evaluate binary subtraction (-) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Subtract => subtract; + + /// + /// Returns a delegate to evaluate binary multiplication (*) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Multiply => multiply; + + /// + /// Returns a delegate to evaluate binary division (/) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Divide => divide; + + public static Func MultiplyF => multiplyF; + + public static Func DivideF => divideF; + + static readonly Func equal, notEqual, greaterThan, lessThan, greaterThanOrEqual, lessThanOrEqual; + + /// + /// Returns a delegate to evaluate binary equality (==) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Equal => equal; + + /// + /// Returns a delegate to evaluate binary inequality (!=) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func NotEqual => notEqual; + + /// + /// Returns a delegate to evaluate binary greater-then (>) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func GreaterThan => greaterThan; + + /// + /// Returns a delegate to evaluate binary less-than (<) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func LessThan => lessThan; + + /// + /// Returns a delegate to evaluate binary greater-than-or-equal (>=) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func GreaterThanOrEqual => greaterThanOrEqual; + + /// + /// Returns a delegate to evaluate binary less-than-or-equal (<=) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func LessThanOrEqual => lessThanOrEqual; + + static Operator() + { + add = ExpressionUtil.CreateExpression(Expression.Add); + subtract = ExpressionUtil.CreateExpression(Expression.Subtract); + divide = ExpressionUtil.CreateExpression(Expression.Divide); + multiply = ExpressionUtil.CreateExpression(Expression.Multiply); + multiplyF = ExpressionUtil.CreateExpression(Expression.Multiply); + divideF = ExpressionUtil.CreateExpression(Expression.Multiply); + + greaterThan = ExpressionUtil.CreateExpression(Expression.GreaterThan); + greaterThanOrEqual = ExpressionUtil.CreateExpression(Expression.GreaterThanOrEqual); + lessThan = ExpressionUtil.CreateExpression(Expression.LessThan); + lessThanOrEqual = ExpressionUtil.CreateExpression(Expression.LessThanOrEqual); + equal = ExpressionUtil.CreateExpression(Expression.Equal); + notEqual = ExpressionUtil.CreateExpression(Expression.NotEqual); + + negate = ExpressionUtil.CreateExpression(Expression.Negate); + and = ExpressionUtil.CreateExpression(Expression.And); + or = ExpressionUtil.CreateExpression(Expression.Or); + not = ExpressionUtil.CreateExpression(Expression.Not); + xor = ExpressionUtil.CreateExpression(Expression.ExclusiveOr); + + Type typeT = typeof(T); + if (typeT.GetTypeInfo().IsValueType && typeT.GetTypeInfo().IsGenericType && (typeT.GetGenericTypeDefinition() == typeof(Nullable<>))) + { + // get the *inner* zero (not a null Nullable, but default(TValue)) + Type nullType = typeT.GetTypeInfo().GenericTypeArguments[0]; + zero = (T)Activator.CreateInstance(nullType); + nullOp = (INullOp)Activator.CreateInstance( + typeof(StructNullOp<>).MakeGenericType(nullType)); + } + else + { + zero = default(T); + if (typeT.GetTypeInfo().IsValueType) + { + nullOp = (INullOp)Activator.CreateInstance( + typeof(StructNullOp<>).MakeGenericType(typeT)); + } + else + { + nullOp = (INullOp)Activator.CreateInstance( + typeof(ClassNullOp<>).MakeGenericType(typeT)); + } + } + } + } +} diff --git a/GenericImage/PackedVectors/Bgra.cs b/GenericImage/PackedVectors/Bgra.cs index 7b82c0683a..dadc844c0e 100644 --- a/GenericImage/PackedVectors/Bgra.cs +++ b/GenericImage/PackedVectors/Bgra.cs @@ -1,52 +1,85 @@ using System; using System.Numerics; +using GenericImage.Helpers; +using GenericImage.PackedVectors; + namespace GenericImage.PackedVectors { - public struct Bgra : IColor4 + public struct Bgra : IColor where TDepth : struct { - public TDepth X { get; set; } - - public TDepth Y { get; set; } + private static readonly TDepth[] Components = new TDepth[4]; - public TDepth Z { get; set; } - - public TDepth W { get; set; } + public TDepth[] Values => Components; public void Add(TColor value) where TColor : IColor { - throw new NotImplementedException(); + for (int i = 0; i < value.Values.Length; i++) + { + this.Values[i] = Operator.Add(this.Values[i], value.Values[i]); + } } public void Multiply(TColor value) where TColor : IColor { - throw new NotImplementedException(); + for (int i = 0; i < value.Values.Length; i++) + { + this.Values[i] = Operator.Multiply(this.Values[i], value.Values[i]); + } } public void Multiply(float value) where TColor : IColor { - throw new NotImplementedException(); + for (int i = 0; i < this.Values.Length; i++) + { + this.Values[i] = Operator.MultiplyF(this.Values[i], value); + } } public void Divide(TColor value) where TColor : IColor { - throw new NotImplementedException(); + for (int i = 0; i < value.Values.Length; i++) + { + this.Values[i] = Operator.Divide(this.Values[i], value.Values[i]); + } } - public void PackVector(Vector4 vector) + public void Divide(float value) where TColor : IColor { - throw new NotImplementedException(); + for (int i = 0; i < this.Values.Length; i++) + { + this.Values[i] = Operator.DivideF(this.Values[i], value); + } } - public Vector4 ToVector() + public byte[] ToBytes() { - throw new NotImplementedException(); + if (typeof(TDepth) == typeof(byte)) + { + return new[] + { + (byte)(object)this.Values[0], + (byte)(object)this.Values[1], + (byte)(object)this.Values[2], + (byte)(object)this.Values[3] + }; + } + + return null; } - public byte[] ToBytes() + public void FromBytes(byte[] bytes) { - throw new System.NotImplementedException(); + if (bytes.Length != 4) + { + throw new ArgumentOutOfRangeException(nameof(bytes)); + } + + for (int i = 0; i < bytes.Length; i++) + { + this.Values[i] = (TDepth)(object)bytes[i]; + } } } } diff --git a/GenericImage/PackedVectors/IColor.cs b/GenericImage/PackedVectors/IColor.cs index 52825d63a6..69924e47f9 100644 --- a/GenericImage/PackedVectors/IColor.cs +++ b/GenericImage/PackedVectors/IColor.cs @@ -17,6 +17,8 @@ public interface IColor : IColor where TDepth : struct { + TDepth[] Values { get; } + void Add(TColor value) where TColor : IColor; void Multiply(TColor value) where TColor : IColor; @@ -24,14 +26,17 @@ void Multiply(float value) where TColor : IColor; void Divide(TColor value) where TColor : IColor; + + void Divide(float value) where TColor : IColor; + + void FromBytes(byte[] bytes); + + byte[] ToBytes(); } public interface IColor { - void PackVector(Vector4 vector); - Vector4 ToVector(); - byte[] ToBytes(); } } diff --git a/GenericImage/project.json b/GenericImage/project.json index 617c22489d..bdcdc4ff70 100644 --- a/GenericImage/project.json +++ b/GenericImage/project.json @@ -12,6 +12,7 @@ "System.IO": "4.1.0", "System.IO.Compression": "4.1.0", "System.Linq": "4.1.0", + "System.Linq.Expressions": "4.1.0", "System.Numerics.Vectors": "4.1.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime.Extensions": "4.1.0", diff --git a/src/ImageProcessorCore/Common/Helpers/Class.cs b/src/ImageProcessorCore/Common/Helpers/Class.cs new file mode 100644 index 0000000000..cf60c9f34a --- /dev/null +++ b/src/ImageProcessorCore/Common/Helpers/Class.cs @@ -0,0 +1,58 @@ +namespace ImageProcessorCore.Helpers +{ + interface INullOp + { + bool HasValue(T value); + bool AddIfNotNull(ref T accumulator, T value); + } + sealed class StructNullOp + : INullOp, INullOp + where T : struct + { + public bool HasValue(T value) + { + return true; + } + public bool AddIfNotNull(ref T accumulator, T value) + { + accumulator = Operator.Add(accumulator, value); + return true; + } + public bool HasValue(T? value) + { + return value.HasValue; + } + public bool AddIfNotNull(ref T? accumulator, T? value) + { + if (value.HasValue) + { + accumulator = accumulator.HasValue ? + Operator.Add( + accumulator.GetValueOrDefault(), + value.GetValueOrDefault()) + : value; + return true; + } + return false; + } + } + sealed class ClassNullOp + : INullOp + where T : class + { + public bool HasValue(T value) + { + return value != null; + } + public bool AddIfNotNull(ref T accumulator, T value) + { + if (value != null) + { + accumulator = accumulator == null ? + value : Operator.Add(accumulator, value); + return true; + } + return false; + } + } +} diff --git a/src/ImageProcessorCore/Common/Helpers/ExpressionUtil.cs b/src/ImageProcessorCore/Common/Helpers/ExpressionUtil.cs new file mode 100644 index 0000000000..b3c7dbe80e --- /dev/null +++ b/src/ImageProcessorCore/Common/Helpers/ExpressionUtil.cs @@ -0,0 +1,100 @@ +namespace ImageProcessorCore.Helpers +{ + using System; + using System.Linq.Expressions; + + /// + /// General purpose Expression utilities + /// + public static class ExpressionUtil + { + /// + /// Create a function delegate representing a unary operation + /// + /// The parameter type + /// The return type + /// Body factory + /// Compiled function delegate + public static Func CreateExpression( + Func body) + { + ParameterExpression inp = Expression.Parameter(typeof(TArg1), "inp"); + try + { + return Expression.Lambda>(body(inp), inp).Compile(); + } + catch (Exception ex) + { + string msg = ex.Message; // avoid capture of ex itself + return delegate { throw new InvalidOperationException(msg); }; + } + } + + /// + /// Create a function delegate representing a binary operation + /// + /// The first parameter type + /// The second parameter type + /// The return type + /// Body factory + /// Compiled function delegate + public static Func CreateExpression( + Func body) + { + return CreateExpression(body, false); + } + + /// + /// Create a function delegate representing a binary operation + /// + /// + /// If no matching operation is possible, attempt to convert + /// TArg1 and TArg2 to TResult for a match? For example, there is no + /// "decimal operator /(decimal, int)", but by converting TArg2 (int) to + /// TResult (decimal) a match is found. + /// + /// The first parameter type + /// The second parameter type + /// The return type + /// Body factory + /// Compiled function delegate + public static Func CreateExpression( + Func body, bool castArgsToResultOnFailure) + { + ParameterExpression lhs = Expression.Parameter(typeof(TArg1), "lhs"); + ParameterExpression rhs = Expression.Parameter(typeof(TArg2), "rhs"); + try + { + try + { + return Expression.Lambda>(body(lhs, rhs), lhs, rhs).Compile(); + } + catch (InvalidOperationException) + { + // If we show retry and the args aren't already "TValue, TValue, TValue"... + // convert both lhs and rhs to TResult (as appropriate) + if (castArgsToResultOnFailure && !(typeof(TArg1) == typeof(TResult) && typeof(TArg2) == typeof(TResult))) + { + Expression castLhs = typeof(TArg1) == typeof(TResult) + ? lhs + : (Expression)Expression.Convert(lhs, typeof(TResult)); + + Expression castRhs = typeof(TArg2) == typeof(TResult) + ? rhs + : (Expression)Expression.Convert(rhs, typeof(TResult)); + + return Expression.Lambda>( + body(castLhs, castRhs), lhs, rhs).Compile(); + } + + throw; + } + } + catch (Exception ex) + { + string msg = ex.Message; // avoid capture of ex itself + return delegate { throw new InvalidOperationException(msg); }; + } + } + } +} diff --git a/src/ImageProcessorCore/Common/Helpers/Operator.cs b/src/ImageProcessorCore/Common/Helpers/Operator.cs new file mode 100644 index 0000000000..fc09a0c7be --- /dev/null +++ b/src/ImageProcessorCore/Common/Helpers/Operator.cs @@ -0,0 +1,464 @@ +namespace ImageProcessorCore.Helpers +{ + using System; + using System.Linq.Expressions; + using System.Reflection; + + /// + /// The Operator class provides easy access to the standard operators + /// (addition, etc) for generic types, using type inference to simplify + /// usage. + /// + public static class Operator + { + + /// + /// Indicates if the supplied value is non-null, + /// for reference-types or Nullable<T> + /// + /// True for non-null values, else false + public static bool HasValue(T value) + { + + return Operator.NullOp.HasValue(value); + + } + + /// + /// Increments the accumulator only + /// if the value is non-null. If the accumulator + /// is null, then the accumulator is given the new + /// value; otherwise the accumulator and value + /// are added. + /// + /// The current total to be incremented (can be null) + /// The value to be tested and added to the accumulator + /// True if the value is non-null, else false - i.e. + /// "has the accumulator been updated?" + public static bool AddIfNotNull(ref T accumulator, T value) + { + return Operator.NullOp.AddIfNotNull(ref accumulator, value); + } + + /// + /// Evaluates unary negation (-) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static T Negate(T value) + { + return Operator.Negate(value); + } + /// + /// Evaluates bitwise not (~) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static T Not(T value) + { + return Operator.Not(value); + } + /// + /// Evaluates bitwise or (|) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static T Or(T value1, T value2) + { + return Operator.Or(value1, value2); + } + /// + /// Evaluates bitwise and (&) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static T And(T value1, T value2) + { + return Operator.And(value1, value2); + } + /// + /// Evaluates bitwise xor (^) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static T Xor(T value1, T value2) + { + return Operator.Xor(value1, value2); + } + /// + /// Performs a conversion between the given types; this will throw + /// an InvalidOperationException if the type T does not provide a suitable cast, or for + /// Nullable<TInner> if TInner does not provide this cast. + /// + public static TTo Convert(TFrom value) + { + return Operator.Convert(value); + } + /// + /// Evaluates binary addition (+) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static T Add(T value1, T value2) + { + return Operator.Add(value1, value2); + } + /// + /// Evaluates binary addition (+) for the given type(s); this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static TArg1 AddAlternative(TArg1 value1, TArg2 value2) + { + return Operator.Add(value1, value2); + } + /// + /// Evaluates binary subtraction (-) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static T Subtract(T value1, T value2) + { + return Operator.Subtract(value1, value2); + } + /// + /// Evaluates binary subtraction(-) for the given type(s); this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static TArg1 SubtractAlternative(TArg1 value1, TArg2 value2) + { + return Operator.Subtract(value1, value2); + } + /// + /// Evaluates binary multiplication (*) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static T Multiply(T value1, T value2) + { + return Operator.Multiply(value1, value2); + } + /// + /// Evaluates binary multiplication (*) for the given type(s); this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static TArg1 MultiplyAlternative(TArg1 value1, TArg2 value2) + { + return Operator.Multiply(value1, value2); + } + /// + /// Evaluates binary division (/) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static T Divide(T value1, T value2) + { + return Operator.Divide(value1, value2); + } + /// + /// Evaluates binary division (/) for the given type(s); this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static TArg1 DivideAlternative(TArg1 value1, TArg2 value2) + { + return Operator.Divide(value1, value2); + } + /// + /// Evaluates binary equality (==) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static bool Equal(T value1, T value2) + { + return Operator.Equal(value1, value2); + } + /// + /// Evaluates binary inequality (!=) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static bool NotEqual(T value1, T value2) + { + return Operator.NotEqual(value1, value2); + } + /// + /// Evaluates binary greater-than (>) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static bool GreaterThan(T value1, T value2) + { + return Operator.GreaterThan(value1, value2); + } + /// + /// Evaluates binary less-than (<) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static bool LessThan(T value1, T value2) + { + return Operator.LessThan(value1, value2); + } + /// + /// Evaluates binary greater-than-on-eqauls (>=) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static bool GreaterThanOrEqual(T value1, T value2) + { + return Operator.GreaterThanOrEqual(value1, value2); + } + /// + /// Evaluates binary less-than-or-equal (<=) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static bool LessThanOrEqual(T value1, T value2) + { + return Operator.LessThanOrEqual(value1, value2); + } + /// + /// Evaluates integer division (/) for the given type; this will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + /// This operation is particularly useful for computing averages and + /// similar aggregates. + /// + public static T DivideInt32(T value, int divisor) + { + return Operator.Divide(value, divisor); + } + } + /// + /// Provides standard operators (such as addition) that operate over operands of + /// different types. For operators, the return type is assumed to match the first + /// operand. + /// + /// + /// + public static class Operator + { + private static readonly Func convert; + /// + /// Returns a delegate to convert a value between two types; this delegate will throw + /// an InvalidOperationException if the type T does not provide a suitable cast, or for + /// Nullable<TInner> if TInner does not provide this cast. + /// + public static Func Convert => convert; + + static Operator() + { + convert = ExpressionUtil.CreateExpression(body => Expression.Convert(body, typeof(TResult))); + add = ExpressionUtil.CreateExpression(Expression.Add, true); + subtract = ExpressionUtil.CreateExpression(Expression.Subtract, true); + multiply = ExpressionUtil.CreateExpression(Expression.Multiply, true); + divide = ExpressionUtil.CreateExpression(Expression.Divide, true); + } + + private static readonly Func add, subtract, multiply, divide; + /// + /// Returns a delegate to evaluate binary addition (+) for the given types; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Add => add; + + /// + /// Returns a delegate to evaluate binary subtraction (-) for the given types; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Subtract => subtract; + + /// + /// Returns a delegate to evaluate binary multiplication (*) for the given types; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Multiply => multiply; + + /// + /// Returns a delegate to evaluate binary division (/) for the given types; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Divide => divide; + } + + /// + /// Provides standard operators (such as addition) over a single type + /// + /// + /// + public static class Operator + { + static readonly INullOp nullOp; + internal static INullOp NullOp => nullOp; + + static readonly T zero; + /// + /// Returns the zero value for value-types (even full Nullable<TInner>) - or null for reference types + /// + public static T Zero => zero; + + static readonly Func negate, not; + static readonly Func or, and, xor; + /// + /// Returns a delegate to evaluate unary negation (-) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Negate => negate; + + /// + /// Returns a delegate to evaluate bitwise not (~) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Not => not; + + /// + /// Returns a delegate to evaluate bitwise or (|) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Or => or; + + /// + /// Returns a delegate to evaluate bitwise and (&) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func And => and; + + /// + /// Returns a delegate to evaluate bitwise xor (^) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Xor => xor; + + static readonly Func add, subtract, multiply, divide; + + /// + /// Returns a delegate to evaluate binary addition (+) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Add => add; + + /// + /// Returns a delegate to evaluate binary subtraction (-) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Subtract => subtract; + + /// + /// Returns a delegate to evaluate binary multiplication (*) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Multiply => multiply; + + /// + /// Returns a delegate to evaluate binary division (/) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Divide => divide; + + static readonly Func equal, notEqual, greaterThan, lessThan, greaterThanOrEqual, lessThanOrEqual; + + /// + /// Returns a delegate to evaluate binary equality (==) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func Equal => equal; + + /// + /// Returns a delegate to evaluate binary inequality (!=) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func NotEqual => notEqual; + + /// + /// Returns a delegate to evaluate binary greater-then (>) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func GreaterThan => greaterThan; + + /// + /// Returns a delegate to evaluate binary less-than (<) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func LessThan => lessThan; + + /// + /// Returns a delegate to evaluate binary greater-than-or-equal (>=) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func GreaterThanOrEqual => greaterThanOrEqual; + + /// + /// Returns a delegate to evaluate binary less-than-or-equal (<=) for the given type; this delegate will throw + /// an InvalidOperationException if the type T does not provide this operator, or for + /// Nullable<TInner> if TInner does not provide this operator. + /// + public static Func LessThanOrEqual => lessThanOrEqual; + + static Operator() + { + add = ExpressionUtil.CreateExpression(Expression.Add); + subtract = ExpressionUtil.CreateExpression(Expression.Subtract); + divide = ExpressionUtil.CreateExpression(Expression.Divide); + multiply = ExpressionUtil.CreateExpression(Expression.Multiply); + + greaterThan = ExpressionUtil.CreateExpression(Expression.GreaterThan); + greaterThanOrEqual = ExpressionUtil.CreateExpression(Expression.GreaterThanOrEqual); + lessThan = ExpressionUtil.CreateExpression(Expression.LessThan); + lessThanOrEqual = ExpressionUtil.CreateExpression(Expression.LessThanOrEqual); + equal = ExpressionUtil.CreateExpression(Expression.Equal); + notEqual = ExpressionUtil.CreateExpression(Expression.NotEqual); + + negate = ExpressionUtil.CreateExpression(Expression.Negate); + and = ExpressionUtil.CreateExpression(Expression.And); + or = ExpressionUtil.CreateExpression(Expression.Or); + not = ExpressionUtil.CreateExpression(Expression.Not); + xor = ExpressionUtil.CreateExpression(Expression.ExclusiveOr); + + Type typeT = typeof(T); + if (typeT.GetTypeInfo().IsValueType && typeT.GetTypeInfo().IsGenericType && (typeT.GetGenericTypeDefinition() == typeof(Nullable<>))) + { + // get the *inner* zero (not a null Nullable, but default(TValue)) + Type nullType = typeT.GetTypeInfo().GenericTypeArguments[0]; + zero = (T)Activator.CreateInstance(nullType); + nullOp = (INullOp)Activator.CreateInstance( + typeof(StructNullOp<>).MakeGenericType(nullType)); + } + else + { + zero = default(T); + if (typeT.GetTypeInfo().IsValueType) + { + nullOp = (INullOp)Activator.CreateInstance( + typeof(StructNullOp<>).MakeGenericType(typeT)); + } + else + { + nullOp = (INullOp)Activator.CreateInstance( + typeof(ClassNullOp<>).MakeGenericType(typeT)); + } + } + } + } +} diff --git a/src/ImageProcessorCore/PackedVector/Bgra32.cs b/src/ImageProcessorCore/PackedVector/Bgra32.cs index c1119d9c45..d1a692e5d5 100644 --- a/src/ImageProcessorCore/PackedVector/Bgra32.cs +++ b/src/ImageProcessorCore/PackedVector/Bgra32.cs @@ -43,14 +43,14 @@ namespace ImageProcessorCore // The maths are wrong here I just wanted to test the performance to see if the // issues are caused by the Vector transform or something else. - public void Add(IPackedVector value) + public void Add(IPackedVector value) { - + } public void Subtract(IPackedVector value) { - + } public void Multiply(IPackedVector value) @@ -65,12 +65,12 @@ namespace ImageProcessorCore public void Divide(IPackedVector value) { - + } public void Divide(float value) { - + } /// @@ -132,11 +132,11 @@ namespace ImageProcessorCore { return new[] { - (byte)(this.PackedValue & 0xFF), - (byte)((this.PackedValue >> 8) & 0xFF), - (byte)((this.PackedValue >> 16) & 0xFF), - (byte)((this.PackedValue >> 24) & 0xFF) - }; + (byte)(this.PackedValue & 0xFF), + (byte)((this.PackedValue >> 8) & 0xFF), + (byte)((this.PackedValue >> 16) & 0xFF), + (byte)((this.PackedValue >> 24) & 0xFF) + }; } /// @@ -178,9 +178,9 @@ namespace ImageProcessorCore private static uint Pack(ref Vector4 vector) { return (uint)Math.Round(vector.X) | - ((uint)Math.Round(vector.Y) << 8) | - ((uint)Math.Round(vector.Z) << 16) | - ((uint)Math.Round(vector.W) << 24); + ((uint)Math.Round(vector.Y) << 8) | + ((uint)Math.Round(vector.Z) << 16) | + ((uint)Math.Round(vector.W) << 24); } /// diff --git a/src/ImageProcessorCore/PackedVector/IPackedVector.cs b/src/ImageProcessorCore/PackedVector/IPackedVector.cs index bf4556309b..90d52b27f5 100644 --- a/src/ImageProcessorCore/PackedVector/IPackedVector.cs +++ b/src/ImageProcessorCore/PackedVector/IPackedVector.cs @@ -31,29 +31,29 @@ namespace ImageProcessorCore /// public interface IPackedVector { - void Add(U value); + //void Add(U value); - void Subtract(U value); + //void Subtract(U value); - void Multiply(U value); + //void Multiply(U value); - void Multiply(float value); + //void Multiply(float value); - void Divide(U value); + //void Divide(U value); - void Divide(float value); + //void Divide(float value); - //void Add(IPackedVector value); + void Add(IPackedVector value); - //void Subtract(IPackedVector value); + void Subtract(IPackedVector value); - //void Multiply(IPackedVector value); + void Multiply(IPackedVector value); - //void Multiply(float value); + void Multiply(float value); - //void Divide(IPackedVector value); + void Divide(IPackedVector value); - //void Divide(float value); + void Divide(float value); /// /// Sets the packed representation from a . diff --git a/src/ImageProcessorCore/project.json b/src/ImageProcessorCore/project.json index e3800d495e..9469f297c1 100644 --- a/src/ImageProcessorCore/project.json +++ b/src/ImageProcessorCore/project.json @@ -23,6 +23,7 @@ "System.IO": "4.1.0", "System.IO.Compression": "4.1.0", "System.Linq": "4.1.0", + "System.Linq.Expressions": "4.1.0", "System.Numerics.Vectors": "4.1.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime.Extensions": "4.1.0", From db714b9767bc396d6b0df34c0985e66825f7946a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 13 Jul 2016 12:12:15 +1000 Subject: [PATCH 17/91] Getting there.... Former-commit-id: 52d18a5b7db2755316ced3502b012186b6eebf96 Former-commit-id: c03f4bd0c6cf3701e9cd9244b43425c1d5e7c32b Former-commit-id: 794deb40b750df545be1aacff66677ea4443190b --- src/ImageProcessorCore/Bootstrapper.cs | 7 +- .../Common/Helpers/Operator.cs | 17 +- .../Formats/Bmp/BmpDecoder.cs | 5 +- .../Formats/Bmp/BmpDecoderCore.cs | 39 ++- .../Formats/Bmp/BmpEncoder.cs | 5 +- .../Formats/Bmp/BmpEncoderCore.cs | 22 +- .../Formats/IImageDecoder.cs | 5 +- .../Formats/IImageEncoder.cs | 5 +- src/ImageProcessorCore/IImageFrame.cs | 7 - src/ImageProcessorCore/Image.cs | 284 +---------------- .../{ => Image}/IImageBase.cs | 10 +- src/ImageProcessorCore/Image/IImageFrame.cs | 8 + .../{ => Image}/IImageProcessor.cs | 10 +- src/ImageProcessorCore/Image/Image.cs | 293 ++++++++++++++++++ .../{ => Image}/ImageBase.cs | 19 +- .../{ => Image}/ImageExtensions.cs | 39 ++- .../{ => Image}/ImageFrame.cs | 11 +- .../{ => Image}/ImageProperty.cs | 0 src/ImageProcessorCore/ImageProcessor.cs | 25 +- src/ImageProcessorCore/PackedVector/Bgra32.cs | 227 ++++++++++++-- .../PackedVector/IPackedVector.cs | 38 +-- .../PixelAccessor/Bgra32PixelAccessor.cs | 5 +- .../PixelAccessor/IPixelAccessor.cs | 5 +- .../Samplers/Options/ResizeHelper.cs | 30 +- .../Samplers/Processors/ResizeProcessor.cs | 36 ++- src/ImageProcessorCore/Samplers/Resize.cs | 25 +- .../Image/GetSetPixel.cs | 6 +- .../Samplers/Resize.cs | 3 +- 28 files changed, 707 insertions(+), 479 deletions(-) delete mode 100644 src/ImageProcessorCore/IImageFrame.cs rename src/ImageProcessorCore/{ => Image}/IImageBase.cs (82%) create mode 100644 src/ImageProcessorCore/Image/IImageFrame.cs rename src/ImageProcessorCore/{ => Image}/IImageProcessor.cs (89%) create mode 100644 src/ImageProcessorCore/Image/Image.cs rename src/ImageProcessorCore/{ => Image}/ImageBase.cs (94%) rename src/ImageProcessorCore/{ => Image}/ImageExtensions.cs (84%) rename src/ImageProcessorCore/{ => Image}/ImageFrame.cs (75%) rename src/ImageProcessorCore/{ => Image}/ImageProperty.cs (100%) diff --git a/src/ImageProcessorCore/Bootstrapper.cs b/src/ImageProcessorCore/Bootstrapper.cs index 2076fab19a..a611119921 100644 --- a/src/ImageProcessorCore/Bootstrapper.cs +++ b/src/ImageProcessorCore/Bootstrapper.cs @@ -73,13 +73,14 @@ namespace ImageProcessorCore /// The type of pixel data. /// The image /// The - public IPixelAccessor GetPixelAccessor(IImageBase image) - where T : IPackedVector, new() + public IPixelAccessor GetPixelAccessor(IImageBase image) + where T : IPackedVector, new() + where TP : struct { Type packed = typeof(T); if (this.pixelAccessors.ContainsKey(packed)) { - return (IPixelAccessor)this.pixelAccessors[packed].Invoke(image); + return (IPixelAccessor)this.pixelAccessors[packed].Invoke(image); } throw new NotSupportedException($"PixelAccessor cannot be loaded for {packed}:"); diff --git a/src/ImageProcessorCore/Common/Helpers/Operator.cs b/src/ImageProcessorCore/Common/Helpers/Operator.cs index fc09a0c7be..8d0d03caae 100644 --- a/src/ImageProcessorCore/Common/Helpers/Operator.cs +++ b/src/ImageProcessorCore/Common/Helpers/Operator.cs @@ -260,6 +260,9 @@ } private static readonly Func add, subtract, multiply, divide; + + private static readonly Func multiplyF, divideF; + /// /// Returns a delegate to evaluate binary addition (+) for the given types; this delegate will throw /// an InvalidOperationException if the type T does not provide this operator, or for @@ -287,6 +290,10 @@ /// Nullable<TInner> if TInner does not provide this operator. /// public static Func Divide => divide; + + public static Func MultiplyF => multiplyF; + + public static Func DivideF => divideF; } /// @@ -344,6 +351,8 @@ static readonly Func add, subtract, multiply, divide; + static readonly Func multiplyF, divideF; + /// /// Returns a delegate to evaluate binary addition (+) for the given type; this delegate will throw /// an InvalidOperationException if the type T does not provide this operator, or for @@ -372,8 +381,12 @@ /// public static Func Divide => divide; + public static Func MultiplyF => multiplyF; + + public static Func DivideF => divideF; + static readonly Func equal, notEqual, greaterThan, lessThan, greaterThanOrEqual, lessThanOrEqual; - + /// /// Returns a delegate to evaluate binary equality (==) for the given type; this delegate will throw /// an InvalidOperationException if the type T does not provide this operator, or for @@ -422,6 +435,8 @@ subtract = ExpressionUtil.CreateExpression(Expression.Subtract); divide = ExpressionUtil.CreateExpression(Expression.Divide); multiply = ExpressionUtil.CreateExpression(Expression.Multiply); + multiplyF = ExpressionUtil.CreateExpression(Expression.Multiply); + divideF = ExpressionUtil.CreateExpression(Expression.Multiply); greaterThan = ExpressionUtil.CreateExpression(Expression.GreaterThan); greaterThanOrEqual = ExpressionUtil.CreateExpression(Expression.GreaterThanOrEqual); diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs index 5565fd690b..916bddce7b 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs @@ -74,8 +74,9 @@ namespace ImageProcessorCore.Formats /// /// The to decode to. /// The containing image data. - public void Decode(Image image, Stream stream) - where T : IPackedVector, new() + public void Decode(Image image, Stream stream) + where T : IPackedVector, new() + where TP : struct { new BmpDecoderCore().Decode(image, stream); } diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs index 56e2d2e2a5..faa0113697 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs @@ -59,8 +59,9 @@ namespace ImageProcessorCore.Formats /// - or - /// is null. /// - public void Decode(Image image, Stream stream) - where T : IPackedVector, new() + public void Decode(Image image, Stream stream) + where T : IPackedVector, new() + where TP : struct { this.currentStream = stream; @@ -132,25 +133,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; @@ -199,8 +194,9 @@ namespace ImageProcessorCore.Formats /// The height of the bitmap. /// The number of bits per pixel. /// Whether the bitmap is inverted. - private void ReadRgbPalette(T[] imageData, byte[] colors, int width, int height, int bits, bool inverted) - where T : IPackedVector, new() + private void ReadRgbPalette(T[] imageData, byte[] colors, int width, int height, int bits, bool inverted) + where T : IPackedVector, new() + where TP : struct { // Pixels per byte (bits per pixel) int ppb = 8 / bits; @@ -259,8 +255,9 @@ namespace ImageProcessorCore.Formats /// The width of the bitmap. /// The height of the bitmap. /// Whether the bitmap is inverted. - private void ReadRgb16(T[] imageData, int width, int height, bool inverted) - where T : IPackedVector, new() + private void ReadRgb16(T[] imageData, int width, int height, bool inverted) + where T : IPackedVector, new() + where TP : struct { // We divide here as we will store the colors in our floating point format. const int ScaleR = 8; // 256/32 @@ -307,8 +304,9 @@ namespace ImageProcessorCore.Formats /// The width of the bitmap. /// The height of the bitmap. /// Whether the bitmap is inverted. - private void ReadRgb24(T[] imageData, int width, int height, bool inverted) - where T : IPackedVector, new() + private void ReadRgb24(T[] imageData, int width, int height, bool inverted) + where T : IPackedVector, new() + where TP : struct { int alignment; byte[] data = this.GetImageArray(width, height, 3, out alignment); @@ -345,8 +343,9 @@ namespace ImageProcessorCore.Formats /// The width of the bitmap. /// The height of the bitmap. /// Whether the bitmap is inverted. - private void ReadRgb32(T[] imageData, int width, int height, bool inverted) - where T : IPackedVector, new() + private void ReadRgb32(T[] imageData, int width, int height, bool inverted) + where T : IPackedVector, new() + where TP : struct { int alignment; byte[] data = this.GetImageArray(width, height, 4, out alignment); diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs index f58f6b85f4..07ada9c05a 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs @@ -43,8 +43,9 @@ namespace ImageProcessorCore.Formats } /// - public void Encode(ImageBase image, Stream stream) - where T : IPackedVector, new() + public void Encode(ImageBase image, Stream stream) + where T : IPackedVector, new() + 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 1df95592a2..cc26ea700c 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs @@ -28,8 +28,9 @@ namespace ImageProcessorCore.Formats /// The to encode from. /// The to encode the image data to. /// The - public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) - where T : IPackedVector, new() + public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) + where T : IPackedVector, new() + where TP : struct { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -127,8 +128,9 @@ namespace ImageProcessorCore.Formats /// /// The containing pixel data. /// - private void WriteImage(EndianBinaryWriter writer, ImageBase image) - where T : IPackedVector, new() + private void WriteImage(EndianBinaryWriter writer, ImageBase image) + where T : IPackedVector, new() + where TP : struct { // TODO: Add more compression formats. int amount = (image.Width * (int)this.bmpBitsPerPixel) % 4; @@ -137,7 +139,7 @@ namespace ImageProcessorCore.Formats amount = 4 - amount; } - using (IPixelAccessor pixels = image.Lock()) + using (IPixelAccessor pixels = image.Lock()) { switch (this.bmpBitsPerPixel) { @@ -159,8 +161,9 @@ namespace ImageProcessorCore.Formats /// The containing the stream to write to. /// The containing pixel data. /// The amount to pad each row by. - private void Write32bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) - where T : IPackedVector, new() + private void Write32bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) + where T : IPackedVector, new() + where TP : struct { for (int y = pixels.Height - 1; y >= 0; y--) { @@ -185,8 +188,9 @@ namespace ImageProcessorCore.Formats /// The containing the stream to write to. /// The containing pixel data. /// The amount to pad each row by. - private void Write24bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) - where T : IPackedVector, new() + private void Write24bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) + where T : IPackedVector, new() + where TP : struct { for (int y = pixels.Height - 1; y >= 0; y--) { diff --git a/src/ImageProcessorCore/Formats/IImageDecoder.cs b/src/ImageProcessorCore/Formats/IImageDecoder.cs index 3c5676bac4..09dbcdb1e7 100644 --- a/src/ImageProcessorCore/Formats/IImageDecoder.cs +++ b/src/ImageProcessorCore/Formats/IImageDecoder.cs @@ -44,7 +44,8 @@ namespace ImageProcessorCore.Formats /// The type of pixels contained within the image. /// The to decode to. /// The containing image data. - void Decode(Image image, Stream stream) - where T : IPackedVector, new(); + void Decode(Image image, Stream stream) + where T : IPackedVector, new() + where TP : struct; } } diff --git a/src/ImageProcessorCore/Formats/IImageEncoder.cs b/src/ImageProcessorCore/Formats/IImageEncoder.cs index 6e83c11ae9..7738968631 100644 --- a/src/ImageProcessorCore/Formats/IImageEncoder.cs +++ b/src/ImageProcessorCore/Formats/IImageEncoder.cs @@ -48,7 +48,8 @@ namespace ImageProcessorCore.Formats /// The type of pixels contained within the image. /// The to encode from. /// The to encode the image data to. - void Encode(ImageBase image, Stream stream) - where T : IPackedVector, new(); + void Encode(ImageBase image, Stream stream) + where T : IPackedVector, + new() where TP : struct; } } diff --git a/src/ImageProcessorCore/IImageFrame.cs b/src/ImageProcessorCore/IImageFrame.cs deleted file mode 100644 index fc92b9306e..0000000000 --- a/src/ImageProcessorCore/IImageFrame.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace ImageProcessorCore -{ - public interface IImageFrame : IImageBase - where T : IPackedVector, new() - { - } -} diff --git a/src/ImageProcessorCore/Image.cs b/src/ImageProcessorCore/Image.cs index 35eaff2434..3cb8866bbb 100644 --- a/src/ImageProcessorCore/Image.cs +++ b/src/ImageProcessorCore/Image.cs @@ -1,292 +1,22 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// +using System.IO; namespace ImageProcessorCore { - using System.IO; - using System.Text; - - using System; - using System.Collections.Generic; - using System.Linq; - - using Formats; - - /// - /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. - /// - /// - /// The packed vector containing pixel information. - /// - public class Image : ImageBase - where T : IPackedVector, new() + 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. - /// - public Image() - { - this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(BmpFormat)); - } - - /// - /// 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. + // TODO Constructors. public Image(int width, int height) - : base(width, height) + : base(width, height) { - // TODO: Change to PNG - this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(BmpFormat)); } - /// - /// Initializes a new instance of the class. - /// - /// - /// The stream containing image information. - /// - /// Thrown if the is null. public Image(Stream stream) + : base(stream) { - Guard.NotNull(stream, nameof(stream)); - this.Load(stream); - } - - /// - /// Initializes a new instance of the class - /// by making a copy from another image. - /// - /// The other image, where the clone should be made from. - /// is null. - public Image(Image other) - { - foreach (ImageFrame frame in other.Frames) - { - if (frame != null) - { - this.Frames.Add(new ImageFrame(frame)); - } - } - - this.RepeatCount = other.RepeatCount; - this.HorizontalResolution = other.HorizontalResolution; - this.VerticalResolution = other.VerticalResolution; - this.CurrentImageFormat = other.CurrentImageFormat; - } - - /// - /// Gets a list of supported image formats. - /// - public IReadOnlyCollection Formats { get; } = Bootstrapper.Instance.ImageFormats; - - /// - /// Gets or sets the resolution of the image in x- direction. It is defined as - /// number of dots per inch and should be an positive value. - /// - /// The density of the image in x- direction. - public double HorizontalResolution { get; set; } = DefaultHorizontalResolution; - - /// - /// Gets or sets the resolution of the image in y- direction. It is defined as - /// number of dots per inch and should be an positive value. - /// - /// The density of the image in y- direction. - public double VerticalResolution { get; set; } = DefaultVerticalResolution; - - /// - /// Gets the width of the image in inches. It is calculated as the width of the image - /// in pixels multiplied with the density. When the density is equals or less than zero - /// the default value is used. - /// - /// The width of the image in inches. - public double InchWidth - { - get - { - double resolution = this.HorizontalResolution; - - if (resolution <= 0) - { - resolution = DefaultHorizontalResolution; - } - - return this.Width / resolution; - } - } - - /// - /// Gets the height of the image in inches. It is calculated as the height of the image - /// in pixels multiplied with the density. When the density is equals or less than zero - /// the default value is used. - /// - /// The height of the image in inches. - public double InchHeight - { - get - { - double resolution = this.VerticalResolution; - - if (resolution <= 0) - { - resolution = DefaultVerticalResolution; - } - - return this.Height / resolution; - } - } - - /// - /// Gets a value indicating whether this image is animated. - /// - /// - /// True if this image is animated; otherwise, false. - /// - public bool IsAnimated => this.Frames.Count > 0; - - /// - /// Gets or sets the number of times any animation is repeated. - /// 0 means to repeat indefinitely. - /// - public ushort RepeatCount { get; set; } - - /// - /// Gets the other frames for the animation. - /// - /// The list of frame images. - public IList> Frames { get; } = new List>(); - - /// - /// Gets the list of properties for storing meta information about this image. - /// - /// A list of image properties. - public IList Properties { get; } = new List(); - - /// - /// Gets the currently loaded image format. - /// - public IImageFormat CurrentImageFormat { get; internal set; } - - /// - public override IPixelAccessor Lock() - { - return Bootstrapper.Instance.GetPixelAccessor(this); - } - - /// - /// Saves the image to the given stream using the currently loaded image format. - /// - /// The stream to save the image to. - /// Thrown if the stream is null. - public void Save(Stream stream) - { - Guard.NotNull(stream, nameof(stream)); - this.CurrentImageFormat.Encoder.Encode(this, stream); } - - /// - /// Saves the image to the given stream using the given image format. - /// - /// The stream to save the image to. - /// The format to save the image as. - /// Thrown if the stream is null. - public void Save(Stream stream, IImageFormat format) + public Image(Image other) + : base(other) { - 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/IImageBase.cs b/src/ImageProcessorCore/Image/IImageBase.cs similarity index 82% rename from src/ImageProcessorCore/IImageBase.cs rename to src/ImageProcessorCore/Image/IImageBase.cs index 4b78df66c0..256a238f3b 100644 --- a/src/ImageProcessorCore/IImageBase.cs +++ b/src/ImageProcessorCore/Image/IImageBase.cs @@ -2,12 +2,16 @@ namespace ImageProcessorCore { - public interface IImageBase : IImageBase - where T : IPackedVector, new() + public interface IImageBase : IImageBase + where T : IPackedVector, new() + where TP : struct { T[] Pixels { get; } + void ClonePixels(int width, int height, T[] pixels); - IPixelAccessor Lock(); + + IPixelAccessor Lock(); + void SetPixels(int width, int height, T[] pixels); } diff --git a/src/ImageProcessorCore/Image/IImageFrame.cs b/src/ImageProcessorCore/Image/IImageFrame.cs new file mode 100644 index 0000000000..744716fafe --- /dev/null +++ b/src/ImageProcessorCore/Image/IImageFrame.cs @@ -0,0 +1,8 @@ +namespace ImageProcessorCore +{ + public interface IImageFrame : IImageBase + where T : IPackedVector, new() + where TP : struct + { + } +} diff --git a/src/ImageProcessorCore/IImageProcessor.cs b/src/ImageProcessorCore/Image/IImageProcessor.cs similarity index 89% rename from src/ImageProcessorCore/IImageProcessor.cs rename to src/ImageProcessorCore/Image/IImageProcessor.cs index 76bc9851c6..6e92c7c8cf 100644 --- a/src/ImageProcessorCore/IImageProcessor.cs +++ b/src/ImageProcessorCore/Image/IImageProcessor.cs @@ -45,8 +45,9 @@ namespace ImageProcessorCore.Processors /// /// doesnt fit the dimension of the image. /// - void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) - where T : IPackedVector, new(); + void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) + where T : IPackedVector, + new() where TP : struct; /// /// Applies the process to the specified portion of the specified at the specified @@ -68,7 +69,8 @@ namespace ImageProcessorCore.Processors /// The method keeps the source image unchanged and returns the /// the result of image process as new image. /// - void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle) - where T : IPackedVector, new(); + void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle) + where T : IPackedVector, new() + where TP : struct; } } diff --git a/src/ImageProcessorCore/Image/Image.cs b/src/ImageProcessorCore/Image/Image.cs new file mode 100644 index 0000000000..890feb9f89 --- /dev/null +++ b/src/ImageProcessorCore/Image/Image.cs @@ -0,0 +1,293 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System.IO; + using System.Text; + + using System; + using System.Collections.Generic; + using System.Linq; + + using Formats; + + /// + /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. + /// + /// + /// The packed vector containing pixel information. + /// + public class Image : ImageBase + where T : IPackedVector, new() + 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(BmpFormat)); + } + + /// + /// 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) + { + // TODO: Change to PNG + this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(BmpFormat)); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// Thrown if the is null. + public Image(Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + this.Load(stream); + } + + /// + /// Initializes a new instance of the class + /// by making a copy from another image. + /// + /// The other image, where the clone should be made from. + /// is null. + public Image(Image other) + { + foreach (ImageFrame frame in other.Frames) + { + if (frame != null) + { + this.Frames.Add(new ImageFrame(frame)); + } + } + + this.RepeatCount = other.RepeatCount; + this.HorizontalResolution = other.HorizontalResolution; + this.VerticalResolution = other.VerticalResolution; + this.CurrentImageFormat = other.CurrentImageFormat; + } + + /// + /// Gets a list of supported image formats. + /// + public IReadOnlyCollection Formats { get; } = Bootstrapper.Instance.ImageFormats; + + /// + /// Gets or sets the resolution of the image in x- direction. It is defined as + /// number of dots per inch and should be an positive value. + /// + /// The density of the image in x- direction. + public double HorizontalResolution { get; set; } = DefaultHorizontalResolution; + + /// + /// Gets or sets the resolution of the image in y- direction. It is defined as + /// number of dots per inch and should be an positive value. + /// + /// The density of the image in y- direction. + public double VerticalResolution { get; set; } = DefaultVerticalResolution; + + /// + /// Gets the width of the image in inches. It is calculated as the width of the image + /// in pixels multiplied with the density. When the density is equals or less than zero + /// the default value is used. + /// + /// The width of the image in inches. + public double InchWidth + { + get + { + double resolution = this.HorizontalResolution; + + if (resolution <= 0) + { + resolution = DefaultHorizontalResolution; + } + + return this.Width / resolution; + } + } + + /// + /// Gets the height of the image in inches. It is calculated as the height of the image + /// in pixels multiplied with the density. When the density is equals or less than zero + /// the default value is used. + /// + /// The height of the image in inches. + public double InchHeight + { + get + { + double resolution = this.VerticalResolution; + + if (resolution <= 0) + { + resolution = DefaultVerticalResolution; + } + + return this.Height / resolution; + } + } + + /// + /// Gets a value indicating whether this image is animated. + /// + /// + /// True if this image is animated; otherwise, false. + /// + public bool IsAnimated => this.Frames.Count > 0; + + /// + /// Gets or sets the number of times any animation is repeated. + /// 0 means to repeat indefinitely. + /// + public ushort RepeatCount { get; set; } + + /// + /// Gets the other frames for the animation. + /// + /// The list of frame images. + public IList> Frames { get; } = new List>(); + + /// + /// Gets the list of properties for storing meta information about this image. + /// + /// A list of image properties. + public IList Properties { get; } = new List(); + + /// + /// Gets the currently loaded image format. + /// + public IImageFormat CurrentImageFormat { get; internal set; } + + /// + public override IPixelAccessor Lock() + { + return Bootstrapper.Instance.GetPixelAccessor(this); + } + + /// + /// Saves the image to the given stream using the currently loaded image format. + /// + /// The stream to save the image to. + /// Thrown if the stream is null. + public void Save(Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + this.CurrentImageFormat.Encoder.Encode(this, stream); + } + + /// + /// Saves the image to the given stream using the given image format. + /// + /// The stream to save the image to. + /// The format to save the image as. + /// Thrown if the stream is null. + public void Save(Stream stream, IImageFormat format) + { + Guard.NotNull(stream, nameof(stream)); + format.Encoder.Encode(this, stream); + } + + /// + /// Saves the image to the given stream using the given image encoder. + /// + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + public void Save(Stream stream, IImageEncoder encoder) + { + Guard.NotNull(stream, nameof(stream)); + encoder.Encode(this, stream); + } + + /// + /// Returns a Base64 encoded string from the given image. + /// + /// data:image/gif;base64,R0lGODlhAQABAIABAEdJRgAAACwAAAAAAQABAAACAkQBAA== + /// The + public override string ToString() + { + using (MemoryStream stream = new MemoryStream()) + { + this.Save(stream); + stream.Flush(); + return $"data:{this.CurrentImageFormat.Encoder.MimeType};base64,{Convert.ToBase64String(stream.ToArray())}"; + } + } + + /// + /// Loads the image from the given stream. + /// + /// The stream containing image information. + /// + /// Thrown if the stream is not readable nor seekable. + /// + private void Load(Stream stream) + { + if (!this.Formats.Any()) { return; } + + if (!stream.CanRead) + { + throw new NotSupportedException("Cannot read from the stream."); + } + + if (!stream.CanSeek) + { + throw new NotSupportedException("The stream does not support seeking."); + } + + int maxHeaderSize = this.Formats.Max(x => x.Decoder.HeaderSize); + if (maxHeaderSize > 0) + { + byte[] header = new byte[maxHeaderSize]; + + stream.Position = 0; + stream.Read(header, 0, maxHeaderSize); + stream.Position = 0; + + IImageFormat format = this.Formats.FirstOrDefault(x => x.Decoder.IsSupportedFileFormat(header)); + if (format != null) + { + format.Decoder.Decode(this, stream); + this.CurrentImageFormat = format; + return; + } + } + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("Image cannot be loaded. Available formats:"); + + foreach (IImageFormat format in this.Formats) + { + stringBuilder.AppendLine("-" + format); + } + + throw new NotSupportedException(stringBuilder.ToString()); + } + } +} diff --git a/src/ImageProcessorCore/ImageBase.cs b/src/ImageProcessorCore/Image/ImageBase.cs similarity index 94% rename from src/ImageProcessorCore/ImageBase.cs rename to src/ImageProcessorCore/Image/ImageBase.cs index 357f490010..ec455f271d 100644 --- a/src/ImageProcessorCore/ImageBase.cs +++ b/src/ImageProcessorCore/Image/ImageBase.cs @@ -14,18 +14,19 @@ namespace ImageProcessorCore /// /// The packed vector pixels format. /// - public abstract class ImageBase : IImageBase - where T : IPackedVector, new() + public abstract class ImageBase : IImageBase + where T : IPackedVector, new() + where TP : struct { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// protected ImageBase() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The width of the image in pixels. /// The height of the image in pixels. @@ -43,15 +44,15 @@ namespace ImageProcessorCore } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - /// The other to create this instance from. + /// The other to create this instance from. /// /// - /// Thrown if the given is null. + /// Thrown if the given is null. /// - protected ImageBase(ImageBase other) + protected ImageBase(ImageBase other) { Guard.NotNull(other, nameof(other), "Other image cannot be null."); @@ -196,6 +197,6 @@ namespace ImageProcessorCore /// /// /// The - public abstract IPixelAccessor Lock(); + public abstract IPixelAccessor Lock(); } } diff --git a/src/ImageProcessorCore/ImageExtensions.cs b/src/ImageProcessorCore/Image/ImageExtensions.cs similarity index 84% rename from src/ImageProcessorCore/ImageExtensions.cs rename to src/ImageProcessorCore/Image/ImageExtensions.cs index c30607ce94..c38f951a8d 100644 --- a/src/ImageProcessorCore/ImageExtensions.cs +++ b/src/ImageProcessorCore/Image/ImageExtensions.cs @@ -61,8 +61,9 @@ namespace ImageProcessorCore /// The image this method extends. /// The processor to apply to the image. /// The . - public static Image Process(this Image source, IImageProcessor processor) - where T : IPackedVector, new() + public static Image Process(this Image source, IImageProcessor processor) + where T : IPackedVector, new() + where TP : struct { return Process(source, source.Bounds, processor); } @@ -78,8 +79,9 @@ namespace ImageProcessorCore /// /// The processors to apply to the image. /// The . - public static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor) - where T : IPackedVector, new() + public static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor) + where T : IPackedVector, new() + where TP : struct { return PerformAction(source, true, (sourceImage, targetImage) => processor.Apply(targetImage, sourceImage, sourceRectangle)); } @@ -96,8 +98,9 @@ namespace ImageProcessorCore /// 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) - where T : IPackedVector, new() + public static Image Process(this Image source, int width, int height, IImageSampler sampler) + where T : IPackedVector, new() + where TP : struct { return Process(source, width, height, source.Bounds, default(Rectangle), sampler); } @@ -121,8 +124,9 @@ namespace ImageProcessorCore /// /// 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) - where T : IPackedVector, new() + public static Image Process(this Image source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler) + where T : IPackedVector, new() + where TP : struct { return PerformAction(source, false, (sourceImage, targetImage) => sampler.Apply(targetImage, sourceImage, width, height, targetRectangle, sourceRectangle)); } @@ -135,12 +139,13 @@ namespace ImageProcessorCore /// Whether to clone the image. /// The to perform against the image. /// The . - private static Image PerformAction(Image source, bool clone, Action, ImageBase> action) - where T : IPackedVector, new() + private static Image PerformAction(Image source, bool clone, Action, ImageBase> action) + where T : IPackedVector, new() + 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? @@ -154,10 +159,10 @@ 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); diff --git a/src/ImageProcessorCore/ImageFrame.cs b/src/ImageProcessorCore/Image/ImageFrame.cs similarity index 75% rename from src/ImageProcessorCore/ImageFrame.cs rename to src/ImageProcessorCore/Image/ImageFrame.cs index 790591b182..0846dc7783 100644 --- a/src/ImageProcessorCore/ImageFrame.cs +++ b/src/ImageProcessorCore/Image/ImageFrame.cs @@ -11,8 +11,9 @@ namespace ImageProcessorCore /// /// The packed vector containing pixel information. /// - public class ImageFrame : ImageBase - where T : IPackedVector, new() + public class ImageFrame : ImageBase + where T : IPackedVector, new() + where TP : struct { /// /// Initializes a new instance of the class. @@ -27,15 +28,15 @@ namespace ImageProcessorCore /// /// The frame to create the frame from. /// - public ImageFrame(ImageFrame frame) + public ImageFrame(ImageFrame frame) : base(frame) { } /// - public override IPixelAccessor Lock() + public override IPixelAccessor Lock() { - return Bootstrapper.Instance.GetPixelAccessor(this); + return Bootstrapper.Instance.GetPixelAccessor(this); } } } diff --git a/src/ImageProcessorCore/ImageProperty.cs b/src/ImageProcessorCore/Image/ImageProperty.cs similarity index 100% rename from src/ImageProcessorCore/ImageProperty.cs rename to src/ImageProcessorCore/Image/ImageProperty.cs diff --git a/src/ImageProcessorCore/ImageProcessor.cs b/src/ImageProcessorCore/ImageProcessor.cs index 380e5c6340..bfa8eacdfc 100644 --- a/src/ImageProcessorCore/ImageProcessor.cs +++ b/src/ImageProcessorCore/ImageProcessor.cs @@ -27,8 +27,9 @@ namespace ImageProcessorCore.Processors private int totalRows; /// - public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) - where T : IPackedVector, new() + public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) + where T : IPackedVector, new() + where TP : struct { try { @@ -49,8 +50,9 @@ namespace ImageProcessorCore.Processors } /// - public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) - where T : IPackedVector, new() + public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) + where T : IPackedVector, new() + where TP : struct { try { @@ -95,8 +97,9 @@ namespace ImageProcessorCore.Processors /// /// The structure that specifies the portion of the image object to draw. /// - protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - where T : IPackedVector, new() + protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + where T : IPackedVector, new() + where TP : struct { } @@ -120,8 +123,9 @@ namespace ImageProcessorCore.Processors /// The method keeps the source image unchanged and returns the /// the result of image process as new image. /// - protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - where T : IPackedVector, new(); + protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + where T : IPackedVector, new() + where TP : struct; /// /// This method is called after the process is applied to prepare the processor. @@ -136,8 +140,9 @@ namespace ImageProcessorCore.Processors /// /// The structure that specifies the portion of the image object to draw. /// - protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - where T : IPackedVector, new() + protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + where T : IPackedVector, new() + where TP : struct { } diff --git a/src/ImageProcessorCore/PackedVector/Bgra32.cs b/src/ImageProcessorCore/PackedVector/Bgra32.cs index d1a692e5d5..9c1848aab4 100644 --- a/src/ImageProcessorCore/PackedVector/Bgra32.cs +++ b/src/ImageProcessorCore/PackedVector/Bgra32.cs @@ -2,7 +2,6 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // - namespace ImageProcessorCore { using System; @@ -11,8 +10,19 @@ namespace ImageProcessorCore /// /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 1. /// - public struct Bgra32 : IPackedVector, IEquatable + public unsafe struct Bgra32 : IPackedVector, IEquatable { + const uint B_MASK = 0x000000FF; + const uint G_MASK = 0x0000FF00; + const uint R_MASK = 0x00FF0000; + const uint A_MASK = 0xFF000000; + const int B_SHIFT = 0; + const int G_SHIFT = 8; + const int R_SHIFT = 16; + const int A_SHIFT = 24; + + private uint packedValue; + /// /// Initializes a new instance of the struct. /// @@ -23,7 +33,7 @@ namespace ImageProcessorCore public Bgra32(float b, float g, float r, float a) { Vector4 clamped = Vector4.Clamp(new Vector4(b, g, r, a), Vector4.Zero, Vector4.One) * 255f; - this.PackedValue = Pack(ref clamped); + this.packedValue = Pack(ref clamped); } /// @@ -35,42 +45,191 @@ namespace ImageProcessorCore public Bgra32(Vector4 vector) { Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; - this.PackedValue = Pack(ref clamped); + this.packedValue = Pack(ref clamped); + } + + public byte B + { + get + { + return (byte)(this.packedValue & B_MASK); + } + + set + { + this.packedValue = (this.packedValue & ~B_MASK) | value; + } + } + + public byte G + { + get + { + return (byte)((this.packedValue & G_MASK) >> G_SHIFT); + } + + set + { + this.packedValue = (this.packedValue & ~G_MASK) | (((uint)value) << G_SHIFT); + } + } + + public byte R + { + get + { + return (byte)((this.packedValue & R_MASK) >> R_SHIFT); + } + + set + { + this.packedValue = (this.packedValue & ~R_MASK) | (((uint)value) << R_SHIFT); + } + } + + public byte A + { + get + { + return (byte)((this.packedValue & A_MASK) >> A_SHIFT); + } + + set + { + this.packedValue = (this.packedValue & ~A_MASK) | (((uint)value) << A_SHIFT); + } } /// - public uint PackedValue { get; set; } + public uint PackedValue() + { + return this.packedValue; + } + + public void Add(TP value) where TP : IPackedVector + { + // this.PackVector(this.ToVector4() + value.ToVector4()); + } + + public void Subtract(TP value) where TP : IPackedVector + { + // this.PackVector(this.ToVector4() - value.ToVector4()); + } - // The maths are wrong here I just wanted to test the performance to see if the - // issues are caused by the Vector transform or something else. - public void Add(IPackedVector value) + public void Multiply(TP value) where TP : IPackedVector { + // this.PackVector(this.ToVector4() * value.ToVector4()); + } + public void Multiply(float value) where TP : IPackedVector + { + this.B = (byte)(this.B * value); + this.G = (byte)(this.G * value); + this.R = (byte)(this.R * value); + this.A = (byte)(this.A * value); } - public void Subtract(IPackedVector value) + public void Divide(TP value) where TP : IPackedVector { + // this.PackVector(this.ToVector4() / value.ToVector4()); + } + public void Divide(float value) where TP : IPackedVector + { + this.B = (byte)(this.B / value); + this.G = (byte)(this.G / value); + this.R = (byte)(this.R / value); + this.A = (byte)(this.A / value); } - public void Multiply(IPackedVector value) + /// + /// Computes the product of multiplying a Bgra32 by a given factor. + /// + /// The Bgra32. + /// The multiplication factor. + /// + /// The + /// + public static Bgra32 operator *(Bgra32 value, float factor) { + byte b = (byte)(value.B * factor); + byte g = (byte)(value.G * factor); + byte r = (byte)(value.R * factor); + byte a = (byte)(value.A * factor); + return new Bgra32(b, g, r, a); } - public void Multiply(float value) + /// + /// Computes the product of multiplying a Bgra32 by a given factor. + /// + /// The multiplication factor. + /// The Bgra32. + /// + /// The + /// + public static Bgra32 operator *(float factor, Bgra32 value) { + byte b = (byte)(value.B * factor); + byte g = (byte)(value.G * factor); + byte r = (byte)(value.R * factor); + byte a = (byte)(value.A * factor); + return new Bgra32(b, g, r, a); } - public void Divide(IPackedVector value) + /// + /// Computes the product of multiplying two Bgra32s. + /// + /// The Bgra32 on the left hand of the operand. + /// The Bgra32 on the right hand of the operand. + /// + /// The + /// + public static Bgra32 operator *(Bgra32 left, Bgra32 right) + { + byte b = (byte)(left.B * right.B); + byte g = (byte)(left.G * right.G); + byte r = (byte)(left.R * right.R); + byte a = (byte)(left.A * right.A); + + return new Bgra32(b, g, r, a); + } + + /// + /// Computes the sum of adding two Bgra32s. + /// + /// The Bgra32 on the left hand of the operand. + /// The Bgra32 on the right hand of the operand. + /// + /// The + /// + public static Bgra32 operator +(Bgra32 left, Bgra32 right) { + byte b = (byte)(left.B + right.B); + byte g = (byte)(left.G + right.G); + byte r = (byte)(left.R + right.R); + byte a = (byte)(left.A + right.A); + return new Bgra32(b, g, r, a); } - public void Divide(float value) + /// + /// Computes the difference left by subtracting one Bgra32 from another. + /// + /// The Bgra32 on the left hand of the operand. + /// The Bgra32 on the right hand of the operand. + /// + /// The + /// + public static Bgra32 operator -(Bgra32 left, Bgra32 right) { + byte b = (byte)(left.B - right.B); + byte g = (byte)(left.G - right.G); + byte r = (byte)(left.R - right.R); + byte a = (byte)(left.A - right.A); + return new Bgra32(b, g, r, a); } /// @@ -87,7 +246,7 @@ namespace ImageProcessorCore /// public static bool operator ==(Bgra32 left, Bgra32 right) { - return left.PackedValue == right.PackedValue; + return left.packedValue == right.packedValue; } /// @@ -100,31 +259,30 @@ namespace ImageProcessorCore /// public static bool operator !=(Bgra32 left, Bgra32 right) { - return left.PackedValue != right.PackedValue; + return left.packedValue != right.packedValue; } /// public void PackVector(Vector4 vector) { Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; - this.PackedValue = Pack(ref clamped); + this.packedValue = Pack(ref clamped); } /// public void PackBytes(byte x, byte y, byte z, byte w) { - Vector4 vector = new Vector4(x, y, z, w); - this.PackedValue = Pack(ref vector); + this.packedValue = Pack(ref x, ref y, ref z, ref w); } /// public Vector4 ToVector4() { return new Vector4( - this.PackedValue & 0xFF, - (this.PackedValue >> 8) & 0xFF, - (this.PackedValue >> 16) & 0xFF, - (this.PackedValue >> 24) & 0xFF) / 255f; + this.packedValue & 0xFF, + (this.packedValue >> 8) & 0xFF, + (this.packedValue >> 16) & 0xFF, + (this.packedValue >> 24) & 0xFF) / 255f; } /// @@ -132,11 +290,11 @@ namespace ImageProcessorCore { return new[] { - (byte)(this.PackedValue & 0xFF), - (byte)((this.PackedValue >> 8) & 0xFF), - (byte)((this.PackedValue >> 16) & 0xFF), - (byte)((this.PackedValue >> 24) & 0xFF) - }; + (byte)(this.packedValue & 0xFF), + (byte)((this.packedValue >> 8) & 0xFF), + (byte)((this.packedValue >> 16) & 0xFF), + (byte)((this.packedValue >> 24) & 0xFF) + }; } /// @@ -148,7 +306,7 @@ namespace ImageProcessorCore /// public bool Equals(Bgra32 other) { - return this.PackedValue == other.PackedValue; + return this.packedValue == other.packedValue; } /// @@ -178,9 +336,14 @@ namespace ImageProcessorCore private static uint Pack(ref Vector4 vector) { return (uint)Math.Round(vector.X) | - ((uint)Math.Round(vector.Y) << 8) | - ((uint)Math.Round(vector.Z) << 16) | - ((uint)Math.Round(vector.W) << 24); + ((uint)Math.Round(vector.Y) << 8) | + ((uint)Math.Round(vector.Z) << 16) | + ((uint)Math.Round(vector.W) << 24); + } + + private static uint Pack(ref byte x, ref byte y, ref byte z, ref byte w) + { + return x | ((uint)y << 8) | ((uint)z << 16) | ((uint)w << 24); } /// @@ -194,7 +357,7 @@ namespace ImageProcessorCore /// private int GetHashCode(Bgra32 packed) { - return packed.PackedValue.GetHashCode(); + return packed.packedValue.GetHashCode(); } } } diff --git a/src/ImageProcessorCore/PackedVector/IPackedVector.cs b/src/ImageProcessorCore/PackedVector/IPackedVector.cs index 90d52b27f5..2beeb2c809 100644 --- a/src/ImageProcessorCore/PackedVector/IPackedVector.cs +++ b/src/ImageProcessorCore/PackedVector/IPackedVector.cs @@ -18,12 +18,22 @@ namespace ImageProcessorCore where T : struct { /// - /// Gets or sets the packed representation of the value. + /// Gets the packed representation of the value. /// Typically packed in least to greatest significance order. /// - T PackedValue { get; set; } + T PackedValue(); + void Add(TP value) where TP : IPackedVector; + void Subtract(TP value) where TP : IPackedVector; + + void Multiply(TP value) where TP : IPackedVector; + + void Multiply(float value) where TP : IPackedVector; + + void Divide(TP value) where TP : IPackedVector; + + void Divide(float value) where TP : IPackedVector; } /// @@ -31,30 +41,6 @@ namespace ImageProcessorCore /// public interface IPackedVector { - //void Add(U value); - - //void Subtract(U value); - - //void Multiply(U value); - - //void Multiply(float value); - - //void Divide(U value); - - //void Divide(float value); - - void Add(IPackedVector value); - - void Subtract(IPackedVector value); - - void Multiply(IPackedVector value); - - void Multiply(float value); - - void Divide(IPackedVector value); - - void Divide(float value); - /// /// Sets the packed representation from a . /// diff --git a/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs index f9fa7005bc..c48090d7b2 100644 --- a/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs @@ -15,8 +15,7 @@ namespace ImageProcessorCore /// The image data is always stored in format, where the blue, green, red, and /// alpha values are 8 bit unsigned bytes. /// - public sealed unsafe class Bgra32PixelAccessor : IPixelAccessor - + public sealed unsafe class Bgra32PixelAccessor : IPixelAccessor { /// /// The position of the first pixel in the bitmap. @@ -56,7 +55,7 @@ namespace ImageProcessorCore this.Width = image.Width; this.Height = image.Height; - this.pixelsHandle = GCHandle.Alloc(((ImageBase)image).Pixels, GCHandleType.Pinned); + this.pixelsHandle = GCHandle.Alloc(((ImageBase)image).Pixels, GCHandleType.Pinned); this.pixelsBase = (Bgra32*)this.pixelsHandle.AddrOfPinnedObject().ToPointer(); } diff --git a/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs index f726163975..e41e7d0d9b 100644 --- a/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs @@ -10,8 +10,9 @@ namespace ImageProcessorCore /// /// Encapsulates properties to provides per-pixel access to an images pixels. /// - public interface IPixelAccessor : IPixelAccessor - where T : IPackedVector, new() + public interface IPixelAccessor : IPixelAccessor + where T : IPackedVector, new() + where TP : struct { /// /// Gets or sets the pixel at the specified position. diff --git a/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs b/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs index 0c806d5e86..e99bfbbf0a 100644 --- a/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs +++ b/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs @@ -23,8 +23,9 @@ namespace ImageProcessorCore /// /// The . /// - public static Rectangle CalculateTargetLocationAndBounds(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + public static Rectangle CalculateTargetLocationAndBounds(ImageBase source, ResizeOptions options) + where T : IPackedVector, new() + where TP : struct { switch (options.Mode) { @@ -54,8 +55,9 @@ namespace ImageProcessorCore /// /// The . /// - private static Rectangle CalculateCropRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + private static Rectangle CalculateCropRectangle(ImageBase source, ResizeOptions options) + where T : IPackedVector, new() + where TP : struct { int width = options.Size.Width; int height = options.Size.Height; @@ -173,8 +175,9 @@ namespace ImageProcessorCore /// /// The . /// - private static Rectangle CalculatePadRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + private static Rectangle CalculatePadRectangle(ImageBase source, ResizeOptions options) + where T : IPackedVector, new() + where TP : struct { int width = options.Size.Width; int height = options.Size.Height; @@ -254,8 +257,9 @@ namespace ImageProcessorCore /// /// The . /// - private static Rectangle CalculateBoxPadRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + private static Rectangle CalculateBoxPadRectangle(ImageBase source, ResizeOptions options) + where T : IPackedVector, new() + where TP : struct { int width = options.Size.Width; int height = options.Size.Height; @@ -341,8 +345,9 @@ namespace ImageProcessorCore /// /// The . /// - private static Rectangle CalculateMaxRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + private static Rectangle CalculateMaxRectangle(ImageBase source, ResizeOptions options) + where T : IPackedVector, new() + where TP : struct { int width = options.Size.Width; int height = options.Size.Height; @@ -382,8 +387,9 @@ namespace ImageProcessorCore /// /// The . /// - private static Rectangle CalculateMinRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + private static Rectangle CalculateMinRectangle(ImageBase source, ResizeOptions options) + where T : IPackedVector, new() + where TP : struct { int width = options.Size.Width; int height = options.Size.Height; diff --git a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs index 3269b00ccd..135d0d62f7 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs @@ -9,6 +9,8 @@ namespace ImageProcessorCore.Processors using System.Numerics; using System.Threading.Tasks; + using ImageProcessorCore.Helpers; + /// /// Provides methods that allow the resizing of images using various algorithms. /// @@ -43,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)) { @@ -53,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) @@ -78,8 +80,8 @@ namespace ImageProcessorCore.Processors float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width; float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height; - using (IPixelAccessor sourcePixels = source.Lock()) - using (IPixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( startY, @@ -114,10 +116,10 @@ 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 (IPixelAccessor sourcePixels = source.Lock()) - using (IPixelAccessor firstPassPixels = firstPass.Lock()) - using (IPixelAccessor 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, @@ -153,14 +155,13 @@ namespace ImageProcessorCore.Processors //} //firstPassPixels[x, y] = destination; - T sourceColor; T destination = default(T); for (int i = 0; i < sum; i++) { Weight xw = horizontalValues[i]; int originX = xw.Index; - sourceColor = sourcePixels[originX, y]; + T sourceColor = sourcePixels[originX, y]; //Color sourceColor = compand // ? Color.Expand(sourcePixels[originX, y]) // : sourcePixels[originX, y]; @@ -168,8 +169,8 @@ namespace ImageProcessorCore.Processors //destination.Add(sourceColor); //destination += sourceColor * xw.Value; - sourceColor.Multiply(xw.Value); - destination.Add(sourceColor); + //sourceColor.Multiply(xw.Value); + destination.Add(Operator.Add(destination, Operator.MultiplyF(sourceColor, xw.Value))); } //if (compand) @@ -200,22 +201,23 @@ namespace ImageProcessorCore.Processors for (int x = 0; x < width; x++) { // Destination color components - T sourceColor; T destination = default(T); for (int i = 0; i < sum; i++) { Weight yw = verticalValues[i]; int originY = yw.Index; - sourceColor = firstPassPixels[x, originY]; + T sourceColor = firstPassPixels[x, originY]; //Color sourceColor = compand // ? Color.Expand(firstPassPixels[x, originY]) // : firstPassPixels[x, originY]; //Vector4 sourceColor = firstPassPixels[x, originY].ToVector4(); //destination += sourceColor * yw.Value; - sourceColor.Multiply(yw.Value); - destination.Add(sourceColor); + //sourceColor.Multiply(yw.Value); + //destination.Add(sourceColor); + destination.Add(Operator.Add(destination, Operator.MultiplyF(sourceColor, yw.Value))); + } //if (compand) @@ -237,7 +239,7 @@ namespace ImageProcessorCore.Processors } /// - protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { // Copy the pixels over. if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) diff --git a/src/ImageProcessorCore/Samplers/Resize.cs b/src/ImageProcessorCore/Samplers/Resize.cs index 3dd0a9c966..7a08e95bf4 100644 --- a/src/ImageProcessorCore/Samplers/Resize.cs +++ b/src/ImageProcessorCore/Samplers/Resize.cs @@ -21,8 +21,9 @@ namespace ImageProcessorCore /// A delegate which is called as progress is made processing the image. /// The /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, ResizeOptions options, ProgressEventHandler progressHandler = null) - where T : IPackedVector, new() + public static Image Resize(this Image source, ResizeOptions options, ProgressEventHandler progressHandler = null) + where T : IPackedVector, new() + where TP : struct { // Ensure size is populated across both dimensions. if (options.Size.Width == 0 && options.Size.Height > 0) @@ -50,8 +51,9 @@ namespace ImageProcessorCore /// A delegate which is called as progress is made processing the image. /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, ProgressEventHandler progressHandler = null) - where T : IPackedVector, new() + public static Image Resize(this Image source, int width, int height, ProgressEventHandler progressHandler = null) + where T : IPackedVector, new() + where TP : struct { return Resize(source, width, height, new BicubicResampler(), false, progressHandler); } @@ -67,8 +69,9 @@ namespace ImageProcessorCore /// A delegate which is called as progress is made processing the image. /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, bool compand, ProgressEventHandler progressHandler = null) - where T : IPackedVector, new() + public static Image Resize(this Image source, int width, int height, bool compand, ProgressEventHandler progressHandler = null) + where T : IPackedVector, new() + where TP : struct { return Resize(source, width, height, new BicubicResampler(), compand, progressHandler); } @@ -85,8 +88,9 @@ namespace ImageProcessorCore /// A delegate which is called as progress is made processing the image. /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, IResampler sampler, bool compand, ProgressEventHandler progressHandler = null) - where T : IPackedVector, new() + public static Image Resize(this Image source, int width, int height, IResampler sampler, bool compand, ProgressEventHandler progressHandler = null) + where T : IPackedVector, new() + where TP : struct { return Resize(source, width, height, sampler, source.Bounds, new Rectangle(0, 0, width, height), compand, progressHandler); } @@ -110,8 +114,9 @@ namespace ImageProcessorCore /// A delegate which is called as progress is made processing the image. /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand = false, ProgressEventHandler progressHandler = null) - where T : IPackedVector, new() + 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, new() + where TP : struct { if (width == 0 && height > 0) { diff --git a/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs b/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs index d900377cda..238a5ccad4 100644 --- a/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs +++ b/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs @@ -23,11 +23,11 @@ [Benchmark(Description = "ImageProcessorCore GetSet Pixel")] public Bgra32 ResizeCore() { - Image image = new Image(400, 400); - using (IPixelAccessor imagePixels = image.Lock()) + Image image = new Image(400, 400); + using (IPixelAccessor imagePixels = image.Lock()) { imagePixels[200, 200] = new Bgra32(1, 1, 1, 1); - return (Bgra32)imagePixels[200, 200]; + return imagePixels[200, 200]; } } } diff --git a/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs b/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs index cbc90c7265..65ed841f00 100644 --- a/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs +++ b/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs @@ -5,6 +5,7 @@ using BenchmarkDotNet.Attributes; using CoreSize = ImageProcessorCore.Size; + using CoreImage = ImageProcessorCore.Image; public class Resize { @@ -31,7 +32,7 @@ [Benchmark(Description = "ImageProcessorCore Resize")] public CoreSize ResizeCore() { - Image image = new Image(2000, 2000); + CoreImage image = new CoreImage(2000, 2000); image.Resize(400, 400); return new CoreSize(image.Width, image.Height); } From dc80f36e766a1eccb45373a96dea77154c20dc84 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 13 Jul 2016 14:59:20 +1000 Subject: [PATCH 18/91] Faster... Much Faster... Former-commit-id: 72030d1552aad7da681b03900ffe614bb8fee3d2 Former-commit-id: 13219f66ec6a6f6e3d61eddd8102810ab4b948f7 Former-commit-id: 356cddebc1dd634474e0b5ddc450318d5b21bff3 --- GenericImage/Common/Helpers/Class.cs | 58 --- GenericImage/Common/Helpers/ExpressionUtil.cs | 100 ---- GenericImage/Common/Helpers/ImageMaths.cs | 40 -- GenericImage/Common/Helpers/Operator.cs | 479 ------------------ GenericImage/GenericImage.xproj | 21 - GenericImage/IImageBase.cs | 17 - GenericImage/IImageProcessor.cs | 76 --- GenericImage/IPixelAccessor.cs | 23 - GenericImage/ImageBase.cs | 190 ------- GenericImage/ImageProcessor.cs | 165 ------ GenericImage/ImageRgba32.cs | 13 - GenericImage/ImageRgba64.cs | 25 - GenericImage/PackedVectors/Bgra.cs | 85 ---- GenericImage/PackedVectors/IColor.cs | 42 -- GenericImage/PackedVectors/IPackedVector.cs | 53 -- GenericImage/PackedVectors/Rgba32.cs | 163 ------ GenericImage/PackedVectors/Rgba64.cs | 163 ------ GenericImage/PixelAccessorRgba32.cs | 148 ------ GenericImage/PixelAccessorRgba64.cs | 148 ------ GenericImage/ProgressEventArgs.cs | 23 - GenericImage/Properties/AssemblyInfo.cs | 19 - GenericImage/ResizeProcessor.cs | 393 -------------- GenericImage/project.json | 28 - ImageProcessorCore.sln | 7 - src/ImageProcessorCore/Bootstrapper.cs | 2 +- .../Common/Helpers/Class.cs | 58 --- .../Common/Helpers/ExpressionUtil.cs | 100 ---- .../Common/Helpers/Operator.cs | 479 ------------------ .../Formats/Bmp/BmpDecoder.cs | 2 +- .../Formats/Bmp/BmpDecoderCore.cs | 10 +- .../Formats/Bmp/BmpEncoder.cs | 2 +- .../Formats/Bmp/BmpEncoderCore.cs | 8 +- .../Formats/IImageDecoder.cs | 2 +- .../Formats/IImageEncoder.cs | 2 +- src/ImageProcessorCore/Image/IImageBase.cs | 2 +- src/ImageProcessorCore/Image/IImageFrame.cs | 2 +- .../Image/IImageProcessor.cs | 4 +- src/ImageProcessorCore/Image/Image.cs | 2 +- src/ImageProcessorCore/Image/ImageBase.cs | 2 +- .../Image/ImageExtensions.cs | 10 +- src/ImageProcessorCore/Image/ImageFrame.cs | 2 +- src/ImageProcessorCore/ImageProcessor.cs | 10 +- src/ImageProcessorCore/PackedVector/Bgra32.cs | 285 ++++------- .../PackedVector/IPackedVector.cs | 18 +- .../PixelAccessor/IPixelAccessor.cs | 2 +- .../Samplers/Options/ResizeHelper.cs | 12 +- .../Samplers/Processors/ResizeProcessor.cs | 41 +- src/ImageProcessorCore/Samplers/Resize.cs | 10 +- src/ImageProcessorCore/project.json | 1 - .../Processors/Samplers/SamplerTests.cs | 2 +- 50 files changed, 159 insertions(+), 3390 deletions(-) delete mode 100644 GenericImage/Common/Helpers/Class.cs delete mode 100644 GenericImage/Common/Helpers/ExpressionUtil.cs delete mode 100644 GenericImage/Common/Helpers/ImageMaths.cs delete mode 100644 GenericImage/Common/Helpers/Operator.cs delete mode 100644 GenericImage/GenericImage.xproj delete mode 100644 GenericImage/IImageBase.cs delete mode 100644 GenericImage/IImageProcessor.cs delete mode 100644 GenericImage/IPixelAccessor.cs delete mode 100644 GenericImage/ImageBase.cs delete mode 100644 GenericImage/ImageProcessor.cs delete mode 100644 GenericImage/ImageRgba32.cs delete mode 100644 GenericImage/ImageRgba64.cs delete mode 100644 GenericImage/PackedVectors/Bgra.cs delete mode 100644 GenericImage/PackedVectors/IColor.cs delete mode 100644 GenericImage/PackedVectors/IPackedVector.cs delete mode 100644 GenericImage/PackedVectors/Rgba32.cs delete mode 100644 GenericImage/PackedVectors/Rgba64.cs delete mode 100644 GenericImage/PixelAccessorRgba32.cs delete mode 100644 GenericImage/PixelAccessorRgba64.cs delete mode 100644 GenericImage/ProgressEventArgs.cs delete mode 100644 GenericImage/Properties/AssemblyInfo.cs delete mode 100644 GenericImage/ResizeProcessor.cs delete mode 100644 GenericImage/project.json delete mode 100644 src/ImageProcessorCore/Common/Helpers/Class.cs delete mode 100644 src/ImageProcessorCore/Common/Helpers/ExpressionUtil.cs delete mode 100644 src/ImageProcessorCore/Common/Helpers/Operator.cs diff --git a/GenericImage/Common/Helpers/Class.cs b/GenericImage/Common/Helpers/Class.cs deleted file mode 100644 index ad446739ac..0000000000 --- a/GenericImage/Common/Helpers/Class.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace GenericImage.Helpers -{ - interface INullOp - { - bool HasValue(T value); - bool AddIfNotNull(ref T accumulator, T value); - } - sealed class StructNullOp - : INullOp, INullOp - where T : struct - { - public bool HasValue(T value) - { - return true; - } - public bool AddIfNotNull(ref T accumulator, T value) - { - accumulator = Operator.Add(accumulator, value); - return true; - } - public bool HasValue(T? value) - { - return value.HasValue; - } - public bool AddIfNotNull(ref T? accumulator, T? value) - { - if (value.HasValue) - { - accumulator = accumulator.HasValue ? - Operator.Add( - accumulator.GetValueOrDefault(), - value.GetValueOrDefault()) - : value; - return true; - } - return false; - } - } - sealed class ClassNullOp - : INullOp - where T : class - { - public bool HasValue(T value) - { - return value != null; - } - public bool AddIfNotNull(ref T accumulator, T value) - { - if (value != null) - { - accumulator = accumulator == null ? - value : Operator.Add(accumulator, value); - return true; - } - return false; - } - } -} diff --git a/GenericImage/Common/Helpers/ExpressionUtil.cs b/GenericImage/Common/Helpers/ExpressionUtil.cs deleted file mode 100644 index fc66742a01..0000000000 --- a/GenericImage/Common/Helpers/ExpressionUtil.cs +++ /dev/null @@ -1,100 +0,0 @@ -namespace GenericImage.Helpers -{ - using System; - using System.Linq.Expressions; - - /// - /// General purpose Expression utilities - /// - public static class ExpressionUtil - { - /// - /// Create a function delegate representing a unary operation - /// - /// The parameter type - /// The return type - /// Body factory - /// Compiled function delegate - public static Func CreateExpression( - Func body) - { - ParameterExpression inp = Expression.Parameter(typeof(TArg1), "inp"); - try - { - return Expression.Lambda>(body(inp), inp).Compile(); - } - catch (Exception ex) - { - string msg = ex.Message; // avoid capture of ex itself - return delegate { throw new InvalidOperationException(msg); }; - } - } - - /// - /// Create a function delegate representing a binary operation - /// - /// The first parameter type - /// The second parameter type - /// The return type - /// Body factory - /// Compiled function delegate - public static Func CreateExpression( - Func body) - { - return CreateExpression(body, false); - } - - /// - /// Create a function delegate representing a binary operation - /// - /// - /// If no matching operation is possible, attempt to convert - /// TArg1 and TArg2 to TResult for a match? For example, there is no - /// "decimal operator /(decimal, int)", but by converting TArg2 (int) to - /// TResult (decimal) a match is found. - /// - /// The first parameter type - /// The second parameter type - /// The return type - /// Body factory - /// Compiled function delegate - public static Func CreateExpression( - Func body, bool castArgsToResultOnFailure) - { - ParameterExpression lhs = Expression.Parameter(typeof(TArg1), "lhs"); - ParameterExpression rhs = Expression.Parameter(typeof(TArg2), "rhs"); - try - { - try - { - return Expression.Lambda>(body(lhs, rhs), lhs, rhs).Compile(); - } - catch (InvalidOperationException) - { - // If we show retry and the args aren't already "TValue, TValue, TValue"... - // convert both lhs and rhs to TResult (as appropriate) - if (castArgsToResultOnFailure && !(typeof(TArg1) == typeof(TResult) && typeof(TArg2) == typeof(TResult))) - { - Expression castLhs = typeof(TArg1) == typeof(TResult) - ? lhs - : (Expression)Expression.Convert(lhs, typeof(TResult)); - - Expression castRhs = typeof(TArg2) == typeof(TResult) - ? rhs - : (Expression)Expression.Convert(rhs, typeof(TResult)); - - return Expression.Lambda>( - body(castLhs, castRhs), lhs, rhs).Compile(); - } - - throw; - } - } - catch (Exception ex) - { - string msg = ex.Message; // avoid capture of ex itself - return delegate { throw new InvalidOperationException(msg); }; - } - } - } -} diff --git a/GenericImage/Common/Helpers/ImageMaths.cs b/GenericImage/Common/Helpers/ImageMaths.cs deleted file mode 100644 index 2c5e4f884a..0000000000 --- a/GenericImage/Common/Helpers/ImageMaths.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace GenericImage -{ - public static class ImageMaths - { - /// - /// Restricts a value to be within a specified range. - /// - /// The value to clamp. - /// The minimum value. If value is less than min, min will be returned. - /// The maximum value. If value is greater than max, max will be returned. - /// The clamped value. - public static float Clamp(float value, float min, float max) - { - // This compare order is very important!!! - // We must follow HLSL behavior in the case user specified min value is bigger than max value. - // First we check to see if we're greater than the max - value = (value > max) ? max : value; - - // Then we check to see if we're less than the min. - value = (value < min) ? min : value; - - // There's no check to see if min > max. - return value; - } - - /// - /// Restricts a value to be within a specified range. - /// - /// The value to clamp. - /// The minimum value. If value is less than min, min will be returned. - /// The maximum value. If value is greater than max, max will be returned. - /// The clamped value. - public static int Clamp(int value, int min, int max) - { - value = (value > max) ? max : value; - value = (value < min) ? min : value; - return value; - } - } -} diff --git a/GenericImage/Common/Helpers/Operator.cs b/GenericImage/Common/Helpers/Operator.cs deleted file mode 100644 index 872eb3abe9..0000000000 --- a/GenericImage/Common/Helpers/Operator.cs +++ /dev/null @@ -1,479 +0,0 @@ -namespace GenericImage.Helpers -{ - using System; - using System.Linq.Expressions; - using System.Reflection; - - /// - /// The Operator class provides easy access to the standard operators - /// (addition, etc) for generic types, using type inference to simplify - /// usage. - /// - public static class Operator - { - - /// - /// Indicates if the supplied value is non-null, - /// for reference-types or Nullable<T> - /// - /// True for non-null values, else false - public static bool HasValue(T value) - { - - return Operator.NullOp.HasValue(value); - - } - - /// - /// Increments the accumulator only - /// if the value is non-null. If the accumulator - /// is null, then the accumulator is given the new - /// value; otherwise the accumulator and value - /// are added. - /// - /// The current total to be incremented (can be null) - /// The value to be tested and added to the accumulator - /// True if the value is non-null, else false - i.e. - /// "has the accumulator been updated?" - public static bool AddIfNotNull(ref T accumulator, T value) - { - return Operator.NullOp.AddIfNotNull(ref accumulator, value); - } - - /// - /// Evaluates unary negation (-) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static T Negate(T value) - { - return Operator.Negate(value); - } - /// - /// Evaluates bitwise not (~) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static T Not(T value) - { - return Operator.Not(value); - } - /// - /// Evaluates bitwise or (|) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static T Or(T value1, T value2) - { - return Operator.Or(value1, value2); - } - /// - /// Evaluates bitwise and (&) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static T And(T value1, T value2) - { - return Operator.And(value1, value2); - } - /// - /// Evaluates bitwise xor (^) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static T Xor(T value1, T value2) - { - return Operator.Xor(value1, value2); - } - /// - /// Performs a conversion between the given types; this will throw - /// an InvalidOperationException if the type T does not provide a suitable cast, or for - /// Nullable<TInner> if TInner does not provide this cast. - /// - public static TTo Convert(TFrom value) - { - return Operator.Convert(value); - } - /// - /// Evaluates binary addition (+) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static T Add(T value1, T value2) - { - return Operator.Add(value1, value2); - } - /// - /// Evaluates binary addition (+) for the given type(s); this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static TArg1 AddAlternative(TArg1 value1, TArg2 value2) - { - return Operator.Add(value1, value2); - } - /// - /// Evaluates binary subtraction (-) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static T Subtract(T value1, T value2) - { - return Operator.Subtract(value1, value2); - } - /// - /// Evaluates binary subtraction(-) for the given type(s); this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static TArg1 SubtractAlternative(TArg1 value1, TArg2 value2) - { - return Operator.Subtract(value1, value2); - } - /// - /// Evaluates binary multiplication (*) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static T Multiply(T value1, T value2) - { - return Operator.Multiply(value1, value2); - } - /// - /// Evaluates binary multiplication (*) for the given type(s); this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static TArg1 MultiplyAlternative(TArg1 value1, TArg2 value2) - { - return Operator.Multiply(value1, value2); - } - /// - /// Evaluates binary division (/) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static T Divide(T value1, T value2) - { - return Operator.Divide(value1, value2); - } - /// - /// Evaluates binary division (/) for the given type(s); this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static TArg1 DivideAlternative(TArg1 value1, TArg2 value2) - { - return Operator.Divide(value1, value2); - } - /// - /// Evaluates binary equality (==) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static bool Equal(T value1, T value2) - { - return Operator.Equal(value1, value2); - } - /// - /// Evaluates binary inequality (!=) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static bool NotEqual(T value1, T value2) - { - return Operator.NotEqual(value1, value2); - } - /// - /// Evaluates binary greater-than (>) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static bool GreaterThan(T value1, T value2) - { - return Operator.GreaterThan(value1, value2); - } - /// - /// Evaluates binary less-than (<) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static bool LessThan(T value1, T value2) - { - return Operator.LessThan(value1, value2); - } - /// - /// Evaluates binary greater-than-on-eqauls (>=) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static bool GreaterThanOrEqual(T value1, T value2) - { - return Operator.GreaterThanOrEqual(value1, value2); - } - /// - /// Evaluates binary less-than-or-equal (<=) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static bool LessThanOrEqual(T value1, T value2) - { - return Operator.LessThanOrEqual(value1, value2); - } - /// - /// Evaluates integer division (/) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - /// This operation is particularly useful for computing averages and - /// similar aggregates. - /// - public static T DivideInt32(T value, int divisor) - { - return Operator.Divide(value, divisor); - } - } - /// - /// Provides standard operators (such as addition) that operate over operands of - /// different types. For operators, the return type is assumed to match the first - /// operand. - /// - /// - /// - public static class Operator - { - private static readonly Func convert; - /// - /// Returns a delegate to convert a value between two types; this delegate will throw - /// an InvalidOperationException if the type T does not provide a suitable cast, or for - /// Nullable<TInner> if TInner does not provide this cast. - /// - public static Func Convert => convert; - - static Operator() - { - convert = ExpressionUtil.CreateExpression(body => Expression.Convert(body, typeof(TResult))); - add = ExpressionUtil.CreateExpression(Expression.Add, true); - subtract = ExpressionUtil.CreateExpression(Expression.Subtract, true); - multiply = ExpressionUtil.CreateExpression(Expression.Multiply, true); - divide = ExpressionUtil.CreateExpression(Expression.Divide, true); - } - - private static readonly Func add, subtract, multiply, divide; - - private static readonly Func multiplyF, divideF; - - /// - /// Returns a delegate to evaluate binary addition (+) for the given types; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Add => add; - - /// - /// Returns a delegate to evaluate binary subtraction (-) for the given types; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Subtract => subtract; - - /// - /// Returns a delegate to evaluate binary multiplication (*) for the given types; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Multiply => multiply; - - /// - /// Returns a delegate to evaluate binary division (/) for the given types; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Divide => divide; - - public static Func MultiplyF => multiplyF; - - public static Func DivideF => divideF; - } - - /// - /// Provides standard operators (such as addition) over a single type - /// - /// - /// - public static class Operator - { - static readonly INullOp nullOp; - internal static INullOp NullOp => nullOp; - - static readonly T zero; - /// - /// Returns the zero value for value-types (even full Nullable<TInner>) - or null for reference types - /// - public static T Zero => zero; - - static readonly Func negate, not; - static readonly Func or, and, xor; - /// - /// Returns a delegate to evaluate unary negation (-) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Negate => negate; - - /// - /// Returns a delegate to evaluate bitwise not (~) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Not => not; - - /// - /// Returns a delegate to evaluate bitwise or (|) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Or => or; - - /// - /// Returns a delegate to evaluate bitwise and (&) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func And => and; - - /// - /// Returns a delegate to evaluate bitwise xor (^) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Xor => xor; - - static readonly Func add, subtract, multiply, divide; - - static readonly Func multiplyF, divideF; - - /// - /// Returns a delegate to evaluate binary addition (+) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Add => add; - - /// - /// Returns a delegate to evaluate binary subtraction (-) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Subtract => subtract; - - /// - /// Returns a delegate to evaluate binary multiplication (*) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Multiply => multiply; - - /// - /// Returns a delegate to evaluate binary division (/) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Divide => divide; - - public static Func MultiplyF => multiplyF; - - public static Func DivideF => divideF; - - static readonly Func equal, notEqual, greaterThan, lessThan, greaterThanOrEqual, lessThanOrEqual; - - /// - /// Returns a delegate to evaluate binary equality (==) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Equal => equal; - - /// - /// Returns a delegate to evaluate binary inequality (!=) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func NotEqual => notEqual; - - /// - /// Returns a delegate to evaluate binary greater-then (>) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func GreaterThan => greaterThan; - - /// - /// Returns a delegate to evaluate binary less-than (<) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func LessThan => lessThan; - - /// - /// Returns a delegate to evaluate binary greater-than-or-equal (>=) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func GreaterThanOrEqual => greaterThanOrEqual; - - /// - /// Returns a delegate to evaluate binary less-than-or-equal (<=) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func LessThanOrEqual => lessThanOrEqual; - - static Operator() - { - add = ExpressionUtil.CreateExpression(Expression.Add); - subtract = ExpressionUtil.CreateExpression(Expression.Subtract); - divide = ExpressionUtil.CreateExpression(Expression.Divide); - multiply = ExpressionUtil.CreateExpression(Expression.Multiply); - multiplyF = ExpressionUtil.CreateExpression(Expression.Multiply); - divideF = ExpressionUtil.CreateExpression(Expression.Multiply); - - greaterThan = ExpressionUtil.CreateExpression(Expression.GreaterThan); - greaterThanOrEqual = ExpressionUtil.CreateExpression(Expression.GreaterThanOrEqual); - lessThan = ExpressionUtil.CreateExpression(Expression.LessThan); - lessThanOrEqual = ExpressionUtil.CreateExpression(Expression.LessThanOrEqual); - equal = ExpressionUtil.CreateExpression(Expression.Equal); - notEqual = ExpressionUtil.CreateExpression(Expression.NotEqual); - - negate = ExpressionUtil.CreateExpression(Expression.Negate); - and = ExpressionUtil.CreateExpression(Expression.And); - or = ExpressionUtil.CreateExpression(Expression.Or); - not = ExpressionUtil.CreateExpression(Expression.Not); - xor = ExpressionUtil.CreateExpression(Expression.ExclusiveOr); - - Type typeT = typeof(T); - if (typeT.GetTypeInfo().IsValueType && typeT.GetTypeInfo().IsGenericType && (typeT.GetGenericTypeDefinition() == typeof(Nullable<>))) - { - // get the *inner* zero (not a null Nullable, but default(TValue)) - Type nullType = typeT.GetTypeInfo().GenericTypeArguments[0]; - zero = (T)Activator.CreateInstance(nullType); - nullOp = (INullOp)Activator.CreateInstance( - typeof(StructNullOp<>).MakeGenericType(nullType)); - } - else - { - zero = default(T); - if (typeT.GetTypeInfo().IsValueType) - { - nullOp = (INullOp)Activator.CreateInstance( - typeof(StructNullOp<>).MakeGenericType(typeT)); - } - else - { - nullOp = (INullOp)Activator.CreateInstance( - typeof(ClassNullOp<>).MakeGenericType(typeT)); - } - } - } - } -} diff --git a/GenericImage/GenericImage.xproj b/GenericImage/GenericImage.xproj deleted file mode 100644 index 9ffcf72232..0000000000 --- a/GenericImage/GenericImage.xproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - 45d91211-9d4a-4382-a114-8786859e302a - GenericImage - .\obj - .\bin\ - v4.5.2 - - - - 2.0 - - - diff --git a/GenericImage/IImageBase.cs b/GenericImage/IImageBase.cs deleted file mode 100644 index d5dac55168..0000000000 --- a/GenericImage/IImageBase.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace GenericImage -{ - using GenericImage.PackedVectors; - - public interface IImageBase - where TColor : IColor - where TDepth : struct - { - TColor[] Pixels { get; } - - int Width { get; } - - int Height { get; } - - IPixelAccessor Lock(); - } -} diff --git a/GenericImage/IImageProcessor.cs b/GenericImage/IImageProcessor.cs deleted file mode 100644 index 68aecb3be8..0000000000 --- a/GenericImage/IImageProcessor.cs +++ /dev/null @@ -1,76 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace GenericImage -{ - using PackedVectors; - - /// - /// A delegate which is called as progress is made processing an image. - /// - /// The source of the event. - /// An object that contains the event data. - public delegate void ProgressEventHandler(object sender, ProgressEventArgs e); - - /// - /// Encapsulates methods to alter the pixels of an image. - /// - public interface IImageProcessor - { - /// - /// Event fires when each row of the source image has been processed. - /// - /// - /// This event may be called from threads other than the client thread, and from multiple threads simultaneously. - /// Individual row notifications may arrived out of order. - /// - event ProgressEventHandler OnProgress; - - /// - /// Applies the process to the specified portion of the specified . - /// - /// The type of pixels contained within the image. - /// Target image to apply the process to. - /// The source image. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// - /// The method keeps the source image unchanged and returns the - /// the result of image processing filter as new image. - /// - /// - /// is null or is null. - /// - /// - /// doesnt fit the dimension of the image. - /// - void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) - where TColor : IColor, new() where TDepth : struct; - - /// - /// Applies the process to the specified portion of the specified at the specified - /// location and with the specified size. - /// - /// The type of pixels contained within the image. - /// Target image to apply the process to. - /// The source image. Cannot be null. - /// The target width. - /// The target height. - /// - /// The structure that specifies the location and size of the drawn image. - /// The image is scaled to fit the rectangle. - /// - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// - /// The method keeps the source image unchanged and returns the - /// the result of image process as new image. - /// - void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle) - where TColor : IColor, new() where TDepth : struct; - } -} diff --git a/GenericImage/IPixelAccessor.cs b/GenericImage/IPixelAccessor.cs deleted file mode 100644 index 3ef5887916..0000000000 --- a/GenericImage/IPixelAccessor.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace GenericImage -{ - using System; - - public interface IPixelAccessor : IDisposable - { - TColor this[int x, int y] - { - get; - set; - } - - /// - /// Gets the width. - /// - int Width { get; } - - /// - /// Gets the height. - /// - int Height { get; } - } -} diff --git a/GenericImage/ImageBase.cs b/GenericImage/ImageBase.cs deleted file mode 100644 index e8052d36ca..0000000000 --- a/GenericImage/ImageBase.cs +++ /dev/null @@ -1,190 +0,0 @@ -namespace GenericImage -{ - using System; - - using GenericImage.PackedVectors; - - /// - /// Encapsulates the basic properties and methods required to manipulate images - /// in different pixel formats. - /// - /// The packed vector pixels format. - /// The bit depth of the image. byte, float, etc. - public abstract class ImageBase : IImageBase - where TColor : IColor - where TDepth : 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 TColor[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 TColor[this.Width * this.Height]; - Array.Copy(other.Pixels, this.Pixels, other.Pixels.Length); - } - - /// - /// Gets the pixels as an array of the given packed pixel format. - /// - public TColor[] Pixels { get; private set; } - - /// - /// Gets the width in pixels. - /// - public int Width { get; private set; } - - /// - /// Gets the height in pixels. - /// - public int Height { get; private set; } - - /// - /// Gets the pixel ratio made up of the width and height. - /// - public double PixelRatio => (double)this.Width / this.Height; - - /// - /// Gets the representing the bounds of the image. - /// - // public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height); - - /// - /// Gets or sets th quality of the image. This affects the output quality of lossy image formats. - /// - public int Quality { get; set; } - - /// - /// Gets or sets the frame delay for animated images. - /// If not 0, this field specifies the number of hundredths (1/100) of a second to - /// wait before continuing with the processing of the Data Stream. - /// The clock starts ticking immediately after the graphic is rendered. - /// - public int FrameDelay { get; set; } - - - - /// - /// Sets the pixel array of the image to the given value. - /// - /// The new width of the image. Must be greater than zero. - /// The new height of the image. Must be greater than zero. - /// - /// The array with colors. Must be a multiple of the width and height. - /// - /// - /// Thrown if either or are less than or equal to 0. - /// - /// - /// Thrown if the length is not equal to Width * Height. - /// - public void SetPixels(int width, int height, TColor[] pixels) - { - if (width <= 0) - { - throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than or equals than zero."); - } - - if (height <= 0) - { - throw new ArgumentOutOfRangeException(nameof(height), "Height must be greater than or equal than zero."); - } - - if (pixels.Length != width * height) - { - throw new ArgumentException("Pixel array must have the length of Width * Height."); - } - - this.Width = width; - this.Height = height; - this.Pixels = pixels; - } - - /// - /// Sets the pixel array of the image to the given value, creating a copy of - /// the original pixels. - /// - /// The new width of the image. Must be greater than zero. - /// The new height of the image. Must be greater than zero. - /// - /// The array with colors. Must be a multiple of four times the width and height. - /// - /// - /// Thrown if either or are less than or equal to 0. - /// - /// - /// Thrown if the length is not equal to Width * Height. - /// - public void ClonePixels(int width, int height, TColor[] 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 TColor[pixels.Length]; - Array.Copy(pixels, this.Pixels, pixels.Length); - } - - /// - /// Locks the image providing access to the pixels. - /// - /// It is imperative that the accessor is correctly disposed off after use. - /// - /// - /// The - public abstract IPixelAccessor Lock(); - } -} diff --git a/GenericImage/ImageProcessor.cs b/GenericImage/ImageProcessor.cs deleted file mode 100644 index 18f4966206..0000000000 --- a/GenericImage/ImageProcessor.cs +++ /dev/null @@ -1,165 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace GenericImage -{ - using System; - using System.Threading; - - using GenericImage.PackedVectors; - - /// - /// Allows the application of processors to images. - /// - public abstract class ImageProcessor : IImageProcessor - { - /// - public event ProgressEventHandler OnProgress; - - /// - /// The number of rows processed by a derived class. - /// - private int numRowsProcessed; - - /// - /// The total number of rows that will be processed by a derived class. - /// - private int totalRows; - - /// - public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) - where TColor : IColor, new() where TDepth : struct - { - try - { - this.OnApply(target, source, target.Bounds, sourceRectangle); - - this.numRowsProcessed = 0; - this.totalRows = sourceRectangle.Height; - - this.Apply(target, source, target.Bounds, sourceRectangle, sourceRectangle.Y, sourceRectangle.Bottom); - - this.AfterApply(target, source, target.Bounds, sourceRectangle); - } - catch (Exception ex) - { - - throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); - } - } - - /// - public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle) where TColor : IColor, new() where TDepth : struct - { - try - { - TColor[] pixels = new TColor[width * height]; - target.SetPixels(width, height, pixels); - - // Ensure we always have bounds. - if (sourceRectangle == Rectangle.Empty) - { - sourceRectangle = source.Bounds; - } - - if (targetRectangle == Rectangle.Empty) - { - targetRectangle = target.Bounds; - } - - this.OnApply(target, source, targetRectangle, sourceRectangle); - - this.numRowsProcessed = 0; - this.totalRows = targetRectangle.Height; - - this.Apply(target, source, targetRectangle, sourceRectangle, targetRectangle.Y, targetRectangle.Bottom); - - this.AfterApply(target, source, target.Bounds, sourceRectangle); - } - catch (Exception ex) - { - throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); - } - } - - /// - /// This method is called before the process is applied to prepare the processor. - /// - /// Target image to apply the process to. - /// The source image. Cannot be null. - /// - /// The structure that specifies the location and size of the drawn image. - /// The image is scaled to fit the rectangle. - /// - /// - /// The structure that specifies the portion of the image object to draw. - /// - protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - where TColor : IColor, new() where TDepth : struct - { - } - - /// - /// Applies the process to the specified portion of the specified at the specified location - /// and with the specified size. - /// - /// The type of pixels contained within the image. - /// Target image to apply the process to. - /// The source image. Cannot be null. - /// - /// The structure that specifies the location and size of the drawn image. - /// The image is scaled to fit the rectangle. - /// - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The index of the row within the source image to start processing. - /// The index of the row within the source image to end processing. - /// - /// The method keeps the source image unchanged and returns the - /// the result of image process as new image. - /// - protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - where TColor : IColor, new() where TDepth : struct; - - /// - /// This method is called after the process is applied to prepare the processor. - /// - /// The type of pixels contained within the image. - /// Target image to apply the process to. - /// The source image. Cannot be null. - /// - /// The structure that specifies the location and size of the drawn image. - /// The image is scaled to fit the rectangle. - /// - /// - /// The structure that specifies the portion of the image object to draw. - /// - protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - where TColor : IColor, new() where TDepth : struct - { - } - - /// - /// Must be called by derived classes after processing a single row. - /// - protected void OnRowProcessed() - { - if (this.OnProgress != null) - { - int currThreadNumRows = Interlocked.Add(ref this.numRowsProcessed, 1); - - // Multi-pass filters process multiple times more rows than totalRows, so update totalRows on the fly - if (currThreadNumRows > this.totalRows) - { - this.totalRows = currThreadNumRows; - } - - // Report progress. This may be on the client's thread, or on a Task library thread. - this.OnProgress(this, new ProgressEventArgs { RowsProcessed = currThreadNumRows, TotalRows = this.totalRows }); - } - } - } -} diff --git a/GenericImage/ImageRgba32.cs b/GenericImage/ImageRgba32.cs deleted file mode 100644 index c5a48d0f93..0000000000 --- a/GenericImage/ImageRgba32.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace GenericImage -{ - using GenericImage.PackedVectors; - - public class ImageRgba32 : ImageBase - { - /// - public override IPixelAccessor Lock() - { - return new PixelAccessorRgba32(this); - } - } -} diff --git a/GenericImage/ImageRgba64.cs b/GenericImage/ImageRgba64.cs deleted file mode 100644 index 9e58bf7819..0000000000 --- a/GenericImage/ImageRgba64.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace GenericImage -{ - using GenericImage.PackedVectors; - - public class ImageRgba64 : IImageBase - { - public ImageRgba64(int width, int height) - { - this.Width = width; - this.Height = height; - this.Pixels = new Rgba64[width * height]; - } - - public Rgba64[] Pixels { get; } - - public int Width { get; } - - public int Height { get; } - - public IPixelAccessor Lock() - { - return new PixelAccessorRgba64(this); - } - } -} diff --git a/GenericImage/PackedVectors/Bgra.cs b/GenericImage/PackedVectors/Bgra.cs deleted file mode 100644 index dadc844c0e..0000000000 --- a/GenericImage/PackedVectors/Bgra.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Numerics; - -using GenericImage.Helpers; -using GenericImage.PackedVectors; - -namespace GenericImage.PackedVectors -{ - public struct Bgra : IColor - where TDepth : struct - { - private static readonly TDepth[] Components = new TDepth[4]; - - public TDepth[] Values => Components; - - public void Add(TColor value) where TColor : IColor - { - for (int i = 0; i < value.Values.Length; i++) - { - this.Values[i] = Operator.Add(this.Values[i], value.Values[i]); - } - } - - public void Multiply(TColor value) where TColor : IColor - { - for (int i = 0; i < value.Values.Length; i++) - { - this.Values[i] = Operator.Multiply(this.Values[i], value.Values[i]); - } - } - - public void Multiply(float value) where TColor : IColor - { - for (int i = 0; i < this.Values.Length; i++) - { - this.Values[i] = Operator.MultiplyF(this.Values[i], value); - } - } - - public void Divide(TColor value) where TColor : IColor - { - for (int i = 0; i < value.Values.Length; i++) - { - this.Values[i] = Operator.Divide(this.Values[i], value.Values[i]); - } - } - - public void Divide(float value) where TColor : IColor - { - for (int i = 0; i < this.Values.Length; i++) - { - this.Values[i] = Operator.DivideF(this.Values[i], value); - } - } - - public byte[] ToBytes() - { - if (typeof(TDepth) == typeof(byte)) - { - return new[] - { - (byte)(object)this.Values[0], - (byte)(object)this.Values[1], - (byte)(object)this.Values[2], - (byte)(object)this.Values[3] - }; - } - - return null; - } - - public void FromBytes(byte[] bytes) - { - if (bytes.Length != 4) - { - throw new ArgumentOutOfRangeException(nameof(bytes)); - } - - for (int i = 0; i < bytes.Length; i++) - { - this.Values[i] = (TDepth)(object)bytes[i]; - } - } - } -} diff --git a/GenericImage/PackedVectors/IColor.cs b/GenericImage/PackedVectors/IColor.cs deleted file mode 100644 index 69924e47f9..0000000000 --- a/GenericImage/PackedVectors/IColor.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace GenericImage.PackedVectors -{ - using System.Numerics; - - public interface IColor4 : IColor - where T : struct - { - T X { get; set; } - - T Y { get; set; } - - T Z { get; set; } - - T W { get; set; } - } - - public interface IColor : IColor - where TDepth : struct - { - TDepth[] Values { get; } - - void Add(TColor value) where TColor : IColor; - - void Multiply(TColor value) where TColor : IColor; - - void Multiply(float value) where TColor : IColor; - - void Divide(TColor value) where TColor : IColor; - - void Divide(float value) where TColor : IColor; - - void FromBytes(byte[] bytes); - - byte[] ToBytes(); - } - - public interface IColor - { - - - } -} diff --git a/GenericImage/PackedVectors/IPackedVector.cs b/GenericImage/PackedVectors/IPackedVector.cs deleted file mode 100644 index 860409ba21..0000000000 --- a/GenericImage/PackedVectors/IPackedVector.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace GenericImage.PackedVectors -{ - using System.Numerics; - - /// - /// An interface that converts packed vector types to and from values, - /// allowing multiple encodings to be manipulated in a generic way. - /// - /// - /// The type of object representing the packed value. - /// - public interface IPackedVector : IPackedVector - where TPacked : struct - { - /// - /// Gets or sets the packed representation of the value. - /// - TPacked PackedValue { get; set; } - } - - /// - /// An interface that converts packed vector types to and from values. - /// - public interface IPackedVector - { - /// - /// Sets the packed representation from a . - /// - /// The vector to pack. - void PackVector(Vector4 vector); - - /// - /// Sets the packed representation from a . - /// - /// The x-component. - /// The y-component. - /// The z-component. - /// The w-component. - void PackBytes(byte x, byte y, byte z, byte w); - - /// - /// Expands the packed representation into a . - /// - /// The . - Vector4 ToVector4(); - - /// - /// Expands the packed representation into a . - /// - /// The . - byte[] ToBytes(); - } -} diff --git a/GenericImage/PackedVectors/Rgba32.cs b/GenericImage/PackedVectors/Rgba32.cs deleted file mode 100644 index 1786b36814..0000000000 --- a/GenericImage/PackedVectors/Rgba32.cs +++ /dev/null @@ -1,163 +0,0 @@ -namespace GenericImage.PackedVectors -{ - using System; - using System.Numerics; - - /// - /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 1. - /// - public struct Rgba32 : IPackedVector, IEquatable - { - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - public Rgba32(float r, float g, float b, float a) - { - Vector4 clamped = Vector4.Clamp(new Vector4(r, g, b, a), Vector4.Zero, Vector4.One) * 255f; - this.PackedValue = Pack(ref clamped); - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - public Rgba32(Vector4 vector) - { - Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; - this.PackedValue = Pack(ref clamped); - } - - /// - public uint PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(Rgba32 left, Rgba32 right) - { - return left.PackedValue == right.PackedValue; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator !=(Rgba32 left, Rgba32 right) - { - return left.PackedValue != right.PackedValue; - } - - /// - public void PackVector(Vector4 vector) - { - Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; - this.PackedValue = Pack(ref clamped); - } - - /// - public void PackBytes(byte x, byte y, byte z, byte w) - { - Vector4 vector = new Vector4(x, y, z, w); - this.PackedValue = Pack(ref vector); - } - - /// - public Vector4 ToVector4() - { - return new Vector4( - (this.PackedValue >> 16) & 0xFF, - (this.PackedValue >> 8) & 0xFF, - this.PackedValue & 0xFF, - (this.PackedValue >> 24) & 0xFF) / 255f; - } - - /// - public byte[] ToBytes() - { - return new[] - { - (byte)((this.PackedValue >> 16) & 0xFF), - (byte)((this.PackedValue >> 8) & 0xFF), - (byte)(this.PackedValue & 0xFF), - (byte)((this.PackedValue >> 24) & 0xFF) - }; - } - - /// - public override bool Equals(object obj) - { - return (obj is Rgba32) && this.Equals((Rgba32)obj); - } - - /// - public bool Equals(Rgba32 other) - { - return this.PackedValue == other.PackedValue; - } - - /// - /// Gets a string representation of the packed vector. - /// - /// A string representation of the packed vector. - public override string ToString() - { - return this.ToVector4().ToString(); - } - - /// - public override int GetHashCode() - { - return this.GetHashCode(this); - } - - /// - /// Sets the packed representation from the given component values. - /// - /// - /// The vector containing the components for the packed vector. - /// - /// - /// The . - /// - private static uint Pack(ref Vector4 vector) - { - return ((uint)Math.Round(vector.X) << 16) | - ((uint)Math.Round(vector.Y) << 8) | - (uint)Math.Round(vector.Z) | - ((uint)Math.Round(vector.W) << 24); - } - - /// - /// Returns the hash code for this instance. - /// - /// - /// The instance of to return the hash code for. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// - private int GetHashCode(Rgba32 packed) - { - return packed.PackedValue.GetHashCode(); - } - } -} diff --git a/GenericImage/PackedVectors/Rgba64.cs b/GenericImage/PackedVectors/Rgba64.cs deleted file mode 100644 index f62eeb4de8..0000000000 --- a/GenericImage/PackedVectors/Rgba64.cs +++ /dev/null @@ -1,163 +0,0 @@ -namespace GenericImage.PackedVectors -{ - using System; - using System.Numerics; - - /// - /// Packed vector type containing four 16-bit unsigned normalized values ranging from 0 to 1. - /// - public struct Rgba64 : IPackedVector, IEquatable - { - /// - /// Initializes a new instance of the struct. - /// - /// The red component. - /// The green component. - /// The blue component. - /// The alpha component. - public Rgba64(float r, float g, float b, float a) - { - Vector4 clamped = Vector4.Clamp(new Vector4(r, g, b, a), Vector4.Zero, Vector4.One) * 65535f; - this.PackedValue = Pack(ref clamped); - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - public Rgba64(Vector4 vector) - { - Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 65535f; - this.PackedValue = Pack(ref clamped); - } - - /// - public ulong PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(Rgba64 left, Rgba64 right) - { - return left.PackedValue == right.PackedValue; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator !=(Rgba64 left, Rgba64 right) - { - return left.PackedValue != right.PackedValue; - } - - /// - public void PackVector(Vector4 vector) - { - Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 65535f; - this.PackedValue = Pack(ref clamped); - } - - /// - public void PackBytes(byte x, byte y, byte z, byte w) - { - Vector4 vector = (new Vector4(x, y, z, w) / 255f) * 65535f; - this.PackedValue = Pack(ref vector); - } - - /// - public Vector4 ToVector4() - { - return new Vector4( - (this.PackedValue >> 32) & 0xFFFF, - (this.PackedValue >> 16) & 0xFFFF, - this.PackedValue & 0xFFFF, - (this.PackedValue >> 48) & 0xFFFF) / 65535f; - } - - /// - public byte[] ToBytes() - { - return new[] - { - (byte)((this.PackedValue >> 40) & 255), - (byte)((this.PackedValue >> 24) & 255), - (byte)((this.PackedValue >> 8) & 255), - (byte)((this.PackedValue >> 56) & 255) - }; - } - - /// - public override bool Equals(object obj) - { - return (obj is Rgba64) && this.Equals((Rgba64)obj); - } - - /// - public bool Equals(Rgba64 other) - { - return this.PackedValue == other.PackedValue; - } - - /// - /// Gets a string representation of the packed vector. - /// - /// A string representation of the packed vector. - public override string ToString() - { - return this.ToVector4().ToString(); - } - - /// - public override int GetHashCode() - { - return this.GetHashCode(this); - } - - /// - /// Sets the packed representation from the given component values. - /// - /// - /// The vector containing the components for the packed vector. - /// - /// - /// The . - /// - private static ulong Pack(ref Vector4 vector) - { - return ((ulong)Math.Round(vector.X) << 32) | - ((ulong)Math.Round(vector.Y) << 16) | - (ulong)Math.Round(vector.Z) | - ((ulong)Math.Round(vector.W) << 48); - } - - /// - /// Returns the hash code for this instance. - /// - /// - /// The instance of to return the hash code for. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// - private int GetHashCode(Rgba64 packed) - { - return packed.PackedValue.GetHashCode(); - } - } -} diff --git a/GenericImage/PixelAccessorRgba32.cs b/GenericImage/PixelAccessorRgba32.cs deleted file mode 100644 index 25b49ea210..0000000000 --- a/GenericImage/PixelAccessorRgba32.cs +++ /dev/null @@ -1,148 +0,0 @@ -namespace GenericImage -{ - using System; - using System.Runtime.InteropServices; - - using GenericImage.PackedVectors; - - /// - /// Provides per-pixel access to an images pixels. - /// - public sealed unsafe class PixelAccessorRgba32 : IPixelAccessor - { - /// - /// The position of the first pixel in the bitmap. - /// - private Rgba32* pixelsBase; - - /// - /// Provides a way to access the pixels from unmanaged memory. - /// - private GCHandle pixelsHandle; - - /// - /// A value indicating whether this instance of the given entity has been disposed. - /// - /// if this instance has been disposed; otherwise, . - /// - /// If the entity is disposed, it must not be disposed a second - /// time. The isDisposed field is set the first time the entity - /// is disposed. If the isDisposed field is true, then the Dispose() - /// method will not dispose again. This help not to prolong the entity's - /// life in the Garbage Collector. - /// - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The image to provide pixel access for. - /// - public PixelAccessorRgba32(ImageBase image) - { - //Guard.NotNull(image, nameof(image)); - //Guard.MustBeGreaterThan(image.Width, 0, "image width"); - //Guard.MustBeGreaterThan(image.Height, 0, "image height"); - - this.Width = image.Width; - this.Height = image.Height; - - this.pixelsHandle = GCHandle.Alloc(image.Pixels, GCHandleType.Pinned); - this.pixelsBase = (Rgba32*)this.pixelsHandle.AddrOfPinnedObject().ToPointer(); - } - - /// - /// Finalizes an instance of the class. - /// - ~PixelAccessorRgba32() - { - this.Dispose(); - } - - /// - /// Gets the width of the image. - /// - public int Width { get; } - - /// - /// Gets the height of the image. - /// - public int Height { get; } - - /// - /// Gets or sets the color of a pixel at the specified position. - /// - /// - /// The x-coordinate of the pixel. Must be greater - /// than zero and smaller than the width of the pixel. - /// - /// - /// The y-coordinate of the pixel. Must be greater - /// than zero and smaller than the width of the pixel. - /// - /// The at the specified position. - public IPackedVector this[int x, int y] - { - get - { -#if DEBUG - if ((x < 0) || (x >= this.Width)) - { - throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width."); - } - - if ((y < 0) || (y >= this.Height)) - { - throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); - } -#endif - return *(this.pixelsBase + ((y * this.Width) + x)); - } - - set - { -#if DEBUG - if ((x < 0) || (x >= this.Width)) - { - throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width."); - } - - if ((y < 0) || (y >= this.Height)) - { - throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); - } -#endif - *(this.pixelsBase + ((y * this.Width) + x)) = (Rgba32)value; - } - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - if (this.isDisposed) - { - return; - } - - if (this.pixelsHandle.IsAllocated) - { - this.pixelsHandle.Free(); - } - - this.pixelsBase = null; - - // Note disposing is done. - this.isDisposed = true; - - // This object will be cleaned up by the Dispose method. - // Therefore, you should call GC.SuppressFinalize to - // take this object off the finalization queue - // and prevent finalization code for this object - // from executing a second time. - GC.SuppressFinalize(this); - } - } -} diff --git a/GenericImage/PixelAccessorRgba64.cs b/GenericImage/PixelAccessorRgba64.cs deleted file mode 100644 index 2dae8a0f16..0000000000 --- a/GenericImage/PixelAccessorRgba64.cs +++ /dev/null @@ -1,148 +0,0 @@ -namespace GenericImage -{ - using System; - using System.Runtime.InteropServices; - - using GenericImage.PackedVectors; - - /// - /// Provides per-pixel access to an images pixels. - /// - public sealed unsafe class PixelAccessorRgba64 : IPixelAccessor - { - /// - /// The position of the first pixel in the bitmap. - /// - private Rgba64* pixelsBase; - - /// - /// Provides a way to access the pixels from unmanaged memory. - /// - private GCHandle pixelsHandle; - - /// - /// A value indicating whether this instance of the given entity has been disposed. - /// - /// if this instance has been disposed; otherwise, . - /// - /// If the entity is disposed, it must not be disposed a second - /// time. The isDisposed field is set the first time the entity - /// is disposed. If the isDisposed field is true, then the Dispose() - /// method will not dispose again. This help not to prolong the entity's - /// life in the Garbage Collector. - /// - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The image to provide pixel access for. - /// - public PixelAccessorRgba64(IImageBase image) - { - //Guard.NotNull(image, nameof(image)); - //Guard.MustBeGreaterThan(image.Width, 0, "image width"); - //Guard.MustBeGreaterThan(image.Height, 0, "image height"); - - this.Width = image.Width; - this.Height = image.Height; - - this.pixelsHandle = GCHandle.Alloc(image.Pixels, GCHandleType.Pinned); - this.pixelsBase = (Rgba64*)this.pixelsHandle.AddrOfPinnedObject().ToPointer(); - } - - /// - /// Finalizes an instance of the class. - /// - ~PixelAccessorRgba64() - { - this.Dispose(); - } - - /// - /// Gets the width of the image. - /// - public int Width { get; } - - /// - /// Gets the height of the image. - /// - public int Height { get; } - - /// - /// Gets or sets the color of a pixel at the specified position. - /// - /// - /// The x-coordinate of the pixel. Must be greater - /// than zero and smaller than the width of the pixel. - /// - /// - /// The y-coordinate of the pixel. Must be greater - /// than zero and smaller than the width of the pixel. - /// - /// The at the specified position. - public IPackedVector this[int x, int y] - { - get - { -#if DEBUG - if ((x < 0) || (x >= this.Width)) - { - throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width."); - } - - if ((y < 0) || (y >= this.Height)) - { - throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); - } -#endif - return *(this.pixelsBase + ((y * this.Width) + x)); - } - - set - { -#if DEBUG - if ((x < 0) || (x >= this.Width)) - { - throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width."); - } - - if ((y < 0) || (y >= this.Height)) - { - throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); - } -#endif - *(this.pixelsBase + ((y * this.Width) + x)) = (Rgba64)value; - } - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - if (this.isDisposed) - { - return; - } - - if (this.pixelsHandle.IsAllocated) - { - this.pixelsHandle.Free(); - } - - this.pixelsBase = null; - - // Note disposing is done. - this.isDisposed = true; - - // This object will be cleaned up by the Dispose method. - // Therefore, you should call GC.SuppressFinalize to - // take this object off the finalization queue - // and prevent finalization code for this object - // from executing a second time. - GC.SuppressFinalize(this); - } - } -} diff --git a/GenericImage/ProgressEventArgs.cs b/GenericImage/ProgressEventArgs.cs deleted file mode 100644 index 5e9a1e9598..0000000000 --- a/GenericImage/ProgressEventArgs.cs +++ /dev/null @@ -1,23 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace GenericImage -{ - /// - /// Contains event data related to the progress made processing an image. - /// - public class ProgressEventArgs : System.EventArgs - { - /// - /// Gets or sets the number of rows processed. - /// - public int RowsProcessed { get; set; } - - /// - /// Gets or sets the total number of rows. - /// - public int TotalRows { get; set; } - } -} \ No newline at end of file diff --git a/GenericImage/Properties/AssemblyInfo.cs b/GenericImage/Properties/AssemblyInfo.cs deleted file mode 100644 index 8dd0359516..0000000000 --- a/GenericImage/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("GenericImage")] -[assembly: AssemblyTrademark("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("45d91211-9d4a-4382-a114-8786859e302a")] diff --git a/GenericImage/ResizeProcessor.cs b/GenericImage/ResizeProcessor.cs deleted file mode 100644 index 9752e2960d..0000000000 --- a/GenericImage/ResizeProcessor.cs +++ /dev/null @@ -1,393 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System; - using System.Numerics; - using System.Threading.Tasks; - - using GenericImage; - - /// - /// Provides methods that allow the resizing of images using various algorithms. - /// - public class ResizeProcessor : ImageProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// - /// The sampler to perform the resize operation. - /// - public ResizeProcessor(IResampler sampler) - { - Guard.NotNull(sampler, nameof(sampler)); - - this.Sampler = sampler; - } - - /// - /// Gets the sampler to perform the resize operation. - /// - public IResampler Sampler { get; } - - /// - /// Gets or sets the horizontal weights. - /// - protected Weights[] HorizontalWeights { get; set; } - - /// - /// Gets or sets the vertical weights. - /// - protected Weights[] VerticalWeights { get; set; } - - /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - if (!(this.Sampler is NearestNeighborResampler)) - { - this.HorizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width); - this.VerticalWeights = this.PrecomputeWeights(targetRectangle.Height, sourceRectangle.Height); - } - } - - /// - protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - // Jump out, we'll deal with that later. - if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) - { - return; - } - - int width = target.Width; - int height = target.Height; - int sourceHeight = sourceRectangle.Height; - int targetX = target.Bounds.X; - int targetY = target.Bounds.Y; - int targetRight = target.Bounds.Right; - int targetBottom = target.Bounds.Bottom; - int startX = targetRectangle.X; - int endX = targetRectangle.Right; - bool compand = this.Compand; - - if (this.Sampler is NearestNeighborResampler) - { - // Scaling factors - float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width; - float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height; - - using (IPixelAccessor sourcePixels = source.Lock()) - using (IPixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - startY, - endY, - y => - { - if (targetY <= y && y < targetBottom) - { - // Y coordinates of source points - int originY = (int)((y - startY) * heightFactor); - - for (int x = startX; x < endX; x++) - { - if (targetX <= x && x < targetRight) - { - // X coordinates of source points - int originX = (int)((x - startX) * widthFactor); - targetPixels[x, y] = sourcePixels[originX, originY]; - } - } - - this.OnRowProcessed(); - } - }); - } - - // Break out now. - return; - } - - // Interpolate the image using the calculated weights. - // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm - // First process the columns. Since we are not using multiple threads startY and endY - // are the upper and lower bounds of the source rectangle. - 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, - y => - { - for (int x = startX; x < endX; x++) - { - if (x >= 0 && x < width) - { - // Ensure offsets are normalised for cropping and padding. - int offsetX = x - startX; - float sum = this.HorizontalWeights[offsetX].Sum; - Weight[] horizontalValues = this.HorizontalWeights[offsetX].Values; - - // Destination color components - //Color destination = new Color(); - - //for (int i = 0; i < sum; i++) - //{ - // Weight xw = horizontalValues[i]; - // int originX = xw.Index; - // Color sourceColor = compand - // ? Color.Expand(sourcePixels[originX, y]) - // : sourcePixels[originX, y]; - - // destination += sourceColor * xw.Value; - //} - - //if (compand) - //{ - // destination = Color.Compress(destination); - //} - - //firstPassPixels[x, y] = destination; - TColor sourceColor; - TColor destination = default(TColor); - - for (int i = 0; i < sum; i++) - { - Weight xw = horizontalValues[i]; - int originX = xw.Index; - sourceColor = sourcePixels[originX, y]; - //Color sourceColor = compand - // ? Color.Expand(sourcePixels[originX, y]) - // : sourcePixels[originX, y]; - //sourceColor.Multiply(xw.Value); - //destination.Add(sourceColor); - //destination += sourceColor * xw.Value; - - sourceColor.Multiply(xw.Value); - destination.Add(sourceColor); - } - - //if (compand) - //{ - // destination = Color.Compress(destination); - //} - //T packed = default(T); - //packed.PackVector(destination); - - firstPassPixels[x, y] = destination; - } - } - }); - - // Now process the rows. - Parallel.For( - startY, - endY, - y => - { - if (y >= 0 && y < height) - { - // Ensure offsets are normalised for cropping and padding. - int offsetY = y - startY; - float sum = this.VerticalWeights[offsetY].Sum; - Weight[] verticalValues = this.VerticalWeights[offsetY].Values; - - for (int x = 0; x < width; x++) - { - // Destination color components - TColor sourceColor; - TColor destination = default(TColor); - - for (int i = 0; i < sum; i++) - { - Weight yw = verticalValues[i]; - int originY = yw.Index; - sourceColor = firstPassPixels[x, originY]; - //Color sourceColor = compand - // ? Color.Expand(firstPassPixels[x, originY]) - // : firstPassPixels[x, originY]; - //Vector4 sourceColor = firstPassPixels[x, originY].ToVector4(); - //destination += sourceColor * yw.Value; - - sourceColor.Multiply(yw.Value); - destination.Add(sourceColor); - } - - //if (compand) - //{ - // destination = Color.Compress(destination); - //} - - //T packed = default(T); - //packed.PackVector(destination); - - targetPixels[x, y] = destination; - } - } - - this.OnRowProcessed(); - }); - - } - } - - /// - protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - // Copy the pixels over. - if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) - { - target.ClonePixels(target.Width, target.Height, source.Pixels); - } - } - - /// - /// Computes the weights to apply at each pixel when resizing. - /// - /// The destination section size. - /// The source section size. - /// - /// The . - /// - protected Weights[] PrecomputeWeights(int destinationSize, int sourceSize) - { - float scale = (float)destinationSize / sourceSize; - IResampler sampler = this.Sampler; - float radius = sampler.Radius; - double left; - double right; - float weight; - int index; - int sum; - - Weights[] result = new Weights[destinationSize]; - - // When shrinking, broaden the effective kernel support so that we still - // visit every source pixel. - if (scale < 1) - { - float width = radius / scale; - float filterScale = 1 / scale; - - // Make the weights slices, one source for each column or row. - for (int i = 0; i < destinationSize; i++) - { - float centre = i / scale; - left = Math.Ceiling(centre - width); - right = Math.Floor(centre + width); - - result[i] = new Weights - { - Values = new Weight[(int)(right - left + 1)] - }; - - for (double j = left; j <= right; j++) - { - weight = sampler.GetValue((float)((centre - j) / filterScale)) / filterScale; - if (j < 0) - { - index = (int)-j; - } - else if (j >= sourceSize) - { - index = (int)((sourceSize - j) + sourceSize - 1); - } - else - { - index = (int)j; - } - - sum = (int)result[i].Sum++; - result[i].Values[sum] = new Weight(index, weight); - } - } - } - else - { - // Make the weights slices, one source for each column or row. - for (int i = 0; i < destinationSize; i++) - { - float centre = i / scale; - left = Math.Ceiling(centre - radius); - right = Math.Floor(centre + radius); - result[i] = new Weights - { - Values = new Weight[(int)(right - left + 1)] - }; - - for (double j = left; j <= right; j++) - { - weight = sampler.GetValue((float)(centre - j)); - if (j < 0) - { - index = (int)-j; - } - else if (j >= sourceSize) - { - index = (int)((sourceSize - j) + sourceSize - 1); - } - else - { - index = (int)j; - } - - sum = (int)result[i].Sum++; - result[i].Values[sum] = new Weight(index, weight); - } - } - } - - return result; - } - - /// - /// Represents the weight to be added to a scaled pixel. - /// - protected struct Weight - { - /// - /// Initializes a new instance of the struct. - /// - /// The index. - /// The value. - public Weight(int index, float value) - { - this.Index = index; - this.Value = value; - } - - /// - /// Gets the pixel index. - /// - public int Index { get; } - - /// - /// Gets the result of the interpolation algorithm. - /// - public float Value { get; } - } - - /// - /// Represents a collection of weights and their sum. - /// - protected class Weights - { - /// - /// Gets or sets the values. - /// - public Weight[] Values { get; set; } - - /// - /// Gets or sets the sum. - /// - public float Sum { get; set; } - } - } -} \ No newline at end of file diff --git a/GenericImage/project.json b/GenericImage/project.json deleted file mode 100644 index bdcdc4ff70..0000000000 --- a/GenericImage/project.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "version": "1.0.0-*", - - "buildOptions": { - "allowUnsafe": true, - "debugType": "portable" - }, - "dependencies": { - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Diagnostics.Tools": "4.0.1", - "System.IO": "4.1.0", - "System.IO.Compression": "4.1.0", - "System.Linq": "4.1.0", - "System.Linq.Expressions": "4.1.0", - "System.Numerics.Vectors": "4.1.1", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime.Extensions": "4.1.0", - "System.Runtime.InteropServices": "4.1.0", - "System.Text.Encoding.Extensions": "4.0.11", - "System.Threading": "4.0.11", - "System.Threading.Tasks": "4.0.11", - "System.Threading.Tasks.Parallel": "4.0.1" - }, - "frameworks": { - "netstandard1.1": { } - } -} diff --git a/ImageProcessorCore.sln b/ImageProcessorCore.sln index 6e8766591b..28b7b107c3 100644 --- a/ImageProcessorCore.sln +++ b/ImageProcessorCore.sln @@ -21,8 +21,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{56801022 EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ImageProcessorCore.Benchmarks", "tests\ImageProcessorCore.Benchmarks\ImageProcessorCore.Benchmarks.xproj", "{299D8E18-102C-42DE-ADBF-79098EE706A8}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "GenericImage", "GenericImage\GenericImage.xproj", "{45D91211-9D4A-4382-A114-8786859E302A}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -41,10 +39,6 @@ Global {299D8E18-102C-42DE-ADBF-79098EE706A8}.Debug|Any CPU.Build.0 = Debug|Any CPU {299D8E18-102C-42DE-ADBF-79098EE706A8}.Release|Any CPU.ActiveCfg = Release|Any CPU {299D8E18-102C-42DE-ADBF-79098EE706A8}.Release|Any CPU.Build.0 = Release|Any CPU - {45D91211-9D4A-4382-A114-8786859E302A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {45D91211-9D4A-4382-A114-8786859E302A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {45D91211-9D4A-4382-A114-8786859E302A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {45D91211-9D4A-4382-A114-8786859E302A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -53,6 +47,5 @@ Global {2AA31A1F-142C-43F4-8687-09ABCA4B3A26} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} {F836E8E6-B4D9-4208-8346-140C74678B91} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {299D8E18-102C-42DE-ADBF-79098EE706A8} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} - {45D91211-9D4A-4382-A114-8786859E302A} = {815C0625-CD3D-440F-9F80-2D83856AB7AE} EndGlobalSection EndGlobal diff --git a/src/ImageProcessorCore/Bootstrapper.cs b/src/ImageProcessorCore/Bootstrapper.cs index a611119921..98537c46a8 100644 --- a/src/ImageProcessorCore/Bootstrapper.cs +++ b/src/ImageProcessorCore/Bootstrapper.cs @@ -74,7 +74,7 @@ namespace ImageProcessorCore /// The image /// The public IPixelAccessor GetPixelAccessor(IImageBase image) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { Type packed = typeof(T); diff --git a/src/ImageProcessorCore/Common/Helpers/Class.cs b/src/ImageProcessorCore/Common/Helpers/Class.cs deleted file mode 100644 index cf60c9f34a..0000000000 --- a/src/ImageProcessorCore/Common/Helpers/Class.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace ImageProcessorCore.Helpers -{ - interface INullOp - { - bool HasValue(T value); - bool AddIfNotNull(ref T accumulator, T value); - } - sealed class StructNullOp - : INullOp, INullOp - where T : struct - { - public bool HasValue(T value) - { - return true; - } - public bool AddIfNotNull(ref T accumulator, T value) - { - accumulator = Operator.Add(accumulator, value); - return true; - } - public bool HasValue(T? value) - { - return value.HasValue; - } - public bool AddIfNotNull(ref T? accumulator, T? value) - { - if (value.HasValue) - { - accumulator = accumulator.HasValue ? - Operator.Add( - accumulator.GetValueOrDefault(), - value.GetValueOrDefault()) - : value; - return true; - } - return false; - } - } - sealed class ClassNullOp - : INullOp - where T : class - { - public bool HasValue(T value) - { - return value != null; - } - public bool AddIfNotNull(ref T accumulator, T value) - { - if (value != null) - { - accumulator = accumulator == null ? - value : Operator.Add(accumulator, value); - return true; - } - return false; - } - } -} diff --git a/src/ImageProcessorCore/Common/Helpers/ExpressionUtil.cs b/src/ImageProcessorCore/Common/Helpers/ExpressionUtil.cs deleted file mode 100644 index b3c7dbe80e..0000000000 --- a/src/ImageProcessorCore/Common/Helpers/ExpressionUtil.cs +++ /dev/null @@ -1,100 +0,0 @@ -namespace ImageProcessorCore.Helpers -{ - using System; - using System.Linq.Expressions; - - /// - /// General purpose Expression utilities - /// - public static class ExpressionUtil - { - /// - /// Create a function delegate representing a unary operation - /// - /// The parameter type - /// The return type - /// Body factory - /// Compiled function delegate - public static Func CreateExpression( - Func body) - { - ParameterExpression inp = Expression.Parameter(typeof(TArg1), "inp"); - try - { - return Expression.Lambda>(body(inp), inp).Compile(); - } - catch (Exception ex) - { - string msg = ex.Message; // avoid capture of ex itself - return delegate { throw new InvalidOperationException(msg); }; - } - } - - /// - /// Create a function delegate representing a binary operation - /// - /// The first parameter type - /// The second parameter type - /// The return type - /// Body factory - /// Compiled function delegate - public static Func CreateExpression( - Func body) - { - return CreateExpression(body, false); - } - - /// - /// Create a function delegate representing a binary operation - /// - /// - /// If no matching operation is possible, attempt to convert - /// TArg1 and TArg2 to TResult for a match? For example, there is no - /// "decimal operator /(decimal, int)", but by converting TArg2 (int) to - /// TResult (decimal) a match is found. - /// - /// The first parameter type - /// The second parameter type - /// The return type - /// Body factory - /// Compiled function delegate - public static Func CreateExpression( - Func body, bool castArgsToResultOnFailure) - { - ParameterExpression lhs = Expression.Parameter(typeof(TArg1), "lhs"); - ParameterExpression rhs = Expression.Parameter(typeof(TArg2), "rhs"); - try - { - try - { - return Expression.Lambda>(body(lhs, rhs), lhs, rhs).Compile(); - } - catch (InvalidOperationException) - { - // If we show retry and the args aren't already "TValue, TValue, TValue"... - // convert both lhs and rhs to TResult (as appropriate) - if (castArgsToResultOnFailure && !(typeof(TArg1) == typeof(TResult) && typeof(TArg2) == typeof(TResult))) - { - Expression castLhs = typeof(TArg1) == typeof(TResult) - ? lhs - : (Expression)Expression.Convert(lhs, typeof(TResult)); - - Expression castRhs = typeof(TArg2) == typeof(TResult) - ? rhs - : (Expression)Expression.Convert(rhs, typeof(TResult)); - - return Expression.Lambda>( - body(castLhs, castRhs), lhs, rhs).Compile(); - } - - throw; - } - } - catch (Exception ex) - { - string msg = ex.Message; // avoid capture of ex itself - return delegate { throw new InvalidOperationException(msg); }; - } - } - } -} diff --git a/src/ImageProcessorCore/Common/Helpers/Operator.cs b/src/ImageProcessorCore/Common/Helpers/Operator.cs deleted file mode 100644 index 8d0d03caae..0000000000 --- a/src/ImageProcessorCore/Common/Helpers/Operator.cs +++ /dev/null @@ -1,479 +0,0 @@ -namespace ImageProcessorCore.Helpers -{ - using System; - using System.Linq.Expressions; - using System.Reflection; - - /// - /// The Operator class provides easy access to the standard operators - /// (addition, etc) for generic types, using type inference to simplify - /// usage. - /// - public static class Operator - { - - /// - /// Indicates if the supplied value is non-null, - /// for reference-types or Nullable<T> - /// - /// True for non-null values, else false - public static bool HasValue(T value) - { - - return Operator.NullOp.HasValue(value); - - } - - /// - /// Increments the accumulator only - /// if the value is non-null. If the accumulator - /// is null, then the accumulator is given the new - /// value; otherwise the accumulator and value - /// are added. - /// - /// The current total to be incremented (can be null) - /// The value to be tested and added to the accumulator - /// True if the value is non-null, else false - i.e. - /// "has the accumulator been updated?" - public static bool AddIfNotNull(ref T accumulator, T value) - { - return Operator.NullOp.AddIfNotNull(ref accumulator, value); - } - - /// - /// Evaluates unary negation (-) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static T Negate(T value) - { - return Operator.Negate(value); - } - /// - /// Evaluates bitwise not (~) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static T Not(T value) - { - return Operator.Not(value); - } - /// - /// Evaluates bitwise or (|) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static T Or(T value1, T value2) - { - return Operator.Or(value1, value2); - } - /// - /// Evaluates bitwise and (&) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static T And(T value1, T value2) - { - return Operator.And(value1, value2); - } - /// - /// Evaluates bitwise xor (^) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static T Xor(T value1, T value2) - { - return Operator.Xor(value1, value2); - } - /// - /// Performs a conversion between the given types; this will throw - /// an InvalidOperationException if the type T does not provide a suitable cast, or for - /// Nullable<TInner> if TInner does not provide this cast. - /// - public static TTo Convert(TFrom value) - { - return Operator.Convert(value); - } - /// - /// Evaluates binary addition (+) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static T Add(T value1, T value2) - { - return Operator.Add(value1, value2); - } - /// - /// Evaluates binary addition (+) for the given type(s); this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static TArg1 AddAlternative(TArg1 value1, TArg2 value2) - { - return Operator.Add(value1, value2); - } - /// - /// Evaluates binary subtraction (-) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static T Subtract(T value1, T value2) - { - return Operator.Subtract(value1, value2); - } - /// - /// Evaluates binary subtraction(-) for the given type(s); this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static TArg1 SubtractAlternative(TArg1 value1, TArg2 value2) - { - return Operator.Subtract(value1, value2); - } - /// - /// Evaluates binary multiplication (*) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static T Multiply(T value1, T value2) - { - return Operator.Multiply(value1, value2); - } - /// - /// Evaluates binary multiplication (*) for the given type(s); this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static TArg1 MultiplyAlternative(TArg1 value1, TArg2 value2) - { - return Operator.Multiply(value1, value2); - } - /// - /// Evaluates binary division (/) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static T Divide(T value1, T value2) - { - return Operator.Divide(value1, value2); - } - /// - /// Evaluates binary division (/) for the given type(s); this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static TArg1 DivideAlternative(TArg1 value1, TArg2 value2) - { - return Operator.Divide(value1, value2); - } - /// - /// Evaluates binary equality (==) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static bool Equal(T value1, T value2) - { - return Operator.Equal(value1, value2); - } - /// - /// Evaluates binary inequality (!=) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static bool NotEqual(T value1, T value2) - { - return Operator.NotEqual(value1, value2); - } - /// - /// Evaluates binary greater-than (>) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static bool GreaterThan(T value1, T value2) - { - return Operator.GreaterThan(value1, value2); - } - /// - /// Evaluates binary less-than (<) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static bool LessThan(T value1, T value2) - { - return Operator.LessThan(value1, value2); - } - /// - /// Evaluates binary greater-than-on-eqauls (>=) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static bool GreaterThanOrEqual(T value1, T value2) - { - return Operator.GreaterThanOrEqual(value1, value2); - } - /// - /// Evaluates binary less-than-or-equal (<=) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static bool LessThanOrEqual(T value1, T value2) - { - return Operator.LessThanOrEqual(value1, value2); - } - /// - /// Evaluates integer division (/) for the given type; this will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - /// This operation is particularly useful for computing averages and - /// similar aggregates. - /// - public static T DivideInt32(T value, int divisor) - { - return Operator.Divide(value, divisor); - } - } - /// - /// Provides standard operators (such as addition) that operate over operands of - /// different types. For operators, the return type is assumed to match the first - /// operand. - /// - /// - /// - public static class Operator - { - private static readonly Func convert; - /// - /// Returns a delegate to convert a value between two types; this delegate will throw - /// an InvalidOperationException if the type T does not provide a suitable cast, or for - /// Nullable<TInner> if TInner does not provide this cast. - /// - public static Func Convert => convert; - - static Operator() - { - convert = ExpressionUtil.CreateExpression(body => Expression.Convert(body, typeof(TResult))); - add = ExpressionUtil.CreateExpression(Expression.Add, true); - subtract = ExpressionUtil.CreateExpression(Expression.Subtract, true); - multiply = ExpressionUtil.CreateExpression(Expression.Multiply, true); - divide = ExpressionUtil.CreateExpression(Expression.Divide, true); - } - - private static readonly Func add, subtract, multiply, divide; - - private static readonly Func multiplyF, divideF; - - /// - /// Returns a delegate to evaluate binary addition (+) for the given types; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Add => add; - - /// - /// Returns a delegate to evaluate binary subtraction (-) for the given types; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Subtract => subtract; - - /// - /// Returns a delegate to evaluate binary multiplication (*) for the given types; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Multiply => multiply; - - /// - /// Returns a delegate to evaluate binary division (/) for the given types; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Divide => divide; - - public static Func MultiplyF => multiplyF; - - public static Func DivideF => divideF; - } - - /// - /// Provides standard operators (such as addition) over a single type - /// - /// - /// - public static class Operator - { - static readonly INullOp nullOp; - internal static INullOp NullOp => nullOp; - - static readonly T zero; - /// - /// Returns the zero value for value-types (even full Nullable<TInner>) - or null for reference types - /// - public static T Zero => zero; - - static readonly Func negate, not; - static readonly Func or, and, xor; - /// - /// Returns a delegate to evaluate unary negation (-) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Negate => negate; - - /// - /// Returns a delegate to evaluate bitwise not (~) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Not => not; - - /// - /// Returns a delegate to evaluate bitwise or (|) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Or => or; - - /// - /// Returns a delegate to evaluate bitwise and (&) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func And => and; - - /// - /// Returns a delegate to evaluate bitwise xor (^) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Xor => xor; - - static readonly Func add, subtract, multiply, divide; - - static readonly Func multiplyF, divideF; - - /// - /// Returns a delegate to evaluate binary addition (+) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Add => add; - - /// - /// Returns a delegate to evaluate binary subtraction (-) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Subtract => subtract; - - /// - /// Returns a delegate to evaluate binary multiplication (*) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Multiply => multiply; - - /// - /// Returns a delegate to evaluate binary division (/) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Divide => divide; - - public static Func MultiplyF => multiplyF; - - public static Func DivideF => divideF; - - static readonly Func equal, notEqual, greaterThan, lessThan, greaterThanOrEqual, lessThanOrEqual; - - /// - /// Returns a delegate to evaluate binary equality (==) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func Equal => equal; - - /// - /// Returns a delegate to evaluate binary inequality (!=) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func NotEqual => notEqual; - - /// - /// Returns a delegate to evaluate binary greater-then (>) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func GreaterThan => greaterThan; - - /// - /// Returns a delegate to evaluate binary less-than (<) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func LessThan => lessThan; - - /// - /// Returns a delegate to evaluate binary greater-than-or-equal (>=) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func GreaterThanOrEqual => greaterThanOrEqual; - - /// - /// Returns a delegate to evaluate binary less-than-or-equal (<=) for the given type; this delegate will throw - /// an InvalidOperationException if the type T does not provide this operator, or for - /// Nullable<TInner> if TInner does not provide this operator. - /// - public static Func LessThanOrEqual => lessThanOrEqual; - - static Operator() - { - add = ExpressionUtil.CreateExpression(Expression.Add); - subtract = ExpressionUtil.CreateExpression(Expression.Subtract); - divide = ExpressionUtil.CreateExpression(Expression.Divide); - multiply = ExpressionUtil.CreateExpression(Expression.Multiply); - multiplyF = ExpressionUtil.CreateExpression(Expression.Multiply); - divideF = ExpressionUtil.CreateExpression(Expression.Multiply); - - greaterThan = ExpressionUtil.CreateExpression(Expression.GreaterThan); - greaterThanOrEqual = ExpressionUtil.CreateExpression(Expression.GreaterThanOrEqual); - lessThan = ExpressionUtil.CreateExpression(Expression.LessThan); - lessThanOrEqual = ExpressionUtil.CreateExpression(Expression.LessThanOrEqual); - equal = ExpressionUtil.CreateExpression(Expression.Equal); - notEqual = ExpressionUtil.CreateExpression(Expression.NotEqual); - - negate = ExpressionUtil.CreateExpression(Expression.Negate); - and = ExpressionUtil.CreateExpression(Expression.And); - or = ExpressionUtil.CreateExpression(Expression.Or); - not = ExpressionUtil.CreateExpression(Expression.Not); - xor = ExpressionUtil.CreateExpression(Expression.ExclusiveOr); - - Type typeT = typeof(T); - if (typeT.GetTypeInfo().IsValueType && typeT.GetTypeInfo().IsGenericType && (typeT.GetGenericTypeDefinition() == typeof(Nullable<>))) - { - // get the *inner* zero (not a null Nullable, but default(TValue)) - Type nullType = typeT.GetTypeInfo().GenericTypeArguments[0]; - zero = (T)Activator.CreateInstance(nullType); - nullOp = (INullOp)Activator.CreateInstance( - typeof(StructNullOp<>).MakeGenericType(nullType)); - } - else - { - zero = default(T); - if (typeT.GetTypeInfo().IsValueType) - { - nullOp = (INullOp)Activator.CreateInstance( - typeof(StructNullOp<>).MakeGenericType(typeT)); - } - else - { - nullOp = (INullOp)Activator.CreateInstance( - typeof(ClassNullOp<>).MakeGenericType(typeT)); - } - } - } - } -} diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs index 916bddce7b..f55fa28edd 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs @@ -75,7 +75,7 @@ namespace ImageProcessorCore.Formats /// The to decode to. /// The containing image data. public void Decode(Image image, Stream stream) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { new BmpDecoderCore().Decode(image, stream); diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs index faa0113697..391654f219 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs @@ -60,7 +60,7 @@ namespace ImageProcessorCore.Formats /// is null. /// public void Decode(Image image, Stream stream) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { this.currentStream = stream; @@ -195,7 +195,7 @@ namespace ImageProcessorCore.Formats /// The number of bits per pixel. /// Whether the bitmap is inverted. private void ReadRgbPalette(T[] imageData, byte[] colors, int width, int height, int bits, bool inverted) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { // Pixels per byte (bits per pixel) @@ -256,7 +256,7 @@ namespace ImageProcessorCore.Formats /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRgb16(T[] imageData, int width, int height, bool inverted) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { // We divide here as we will store the colors in our floating point format. @@ -305,7 +305,7 @@ namespace ImageProcessorCore.Formats /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRgb24(T[] imageData, int width, int height, bool inverted) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { int alignment; @@ -344,7 +344,7 @@ namespace ImageProcessorCore.Formats /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRgb32(T[] imageData, int width, int height, bool inverted) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { int alignment; diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs index 07ada9c05a..b7fbdd12ac 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs @@ -44,7 +44,7 @@ namespace ImageProcessorCore.Formats /// public void Encode(ImageBase image, Stream stream) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { BmpEncoderCore encoder = new BmpEncoderCore(); diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs index cc26ea700c..de67b9fe43 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs @@ -29,7 +29,7 @@ namespace ImageProcessorCore.Formats /// The to encode the image data to. /// The public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { Guard.NotNull(image, nameof(image)); @@ -129,7 +129,7 @@ namespace ImageProcessorCore.Formats /// The containing pixel data. /// private void WriteImage(EndianBinaryWriter writer, ImageBase image) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { // TODO: Add more compression formats. @@ -162,7 +162,7 @@ namespace ImageProcessorCore.Formats /// The containing pixel data. /// The amount to pad each row by. private void Write32bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { for (int y = pixels.Height - 1; y >= 0; y--) @@ -189,7 +189,7 @@ namespace ImageProcessorCore.Formats /// The containing pixel data. /// The amount to pad each row by. private void Write24bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { for (int y = pixels.Height - 1; y >= 0; y--) diff --git a/src/ImageProcessorCore/Formats/IImageDecoder.cs b/src/ImageProcessorCore/Formats/IImageDecoder.cs index 09dbcdb1e7..e2e89f2df4 100644 --- a/src/ImageProcessorCore/Formats/IImageDecoder.cs +++ b/src/ImageProcessorCore/Formats/IImageDecoder.cs @@ -45,7 +45,7 @@ namespace ImageProcessorCore.Formats /// The to decode to. /// The containing image data. void Decode(Image image, Stream stream) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct; } } diff --git a/src/ImageProcessorCore/Formats/IImageEncoder.cs b/src/ImageProcessorCore/Formats/IImageEncoder.cs index 7738968631..f040619e55 100644 --- a/src/ImageProcessorCore/Formats/IImageEncoder.cs +++ b/src/ImageProcessorCore/Formats/IImageEncoder.cs @@ -49,7 +49,7 @@ namespace ImageProcessorCore.Formats /// The to encode from. /// The to encode the image data to. void Encode(ImageBase image, Stream stream) - where T : IPackedVector, + where T : IPackedVector, new() where TP : struct; } } diff --git a/src/ImageProcessorCore/Image/IImageBase.cs b/src/ImageProcessorCore/Image/IImageBase.cs index 256a238f3b..db78d127b9 100644 --- a/src/ImageProcessorCore/Image/IImageBase.cs +++ b/src/ImageProcessorCore/Image/IImageBase.cs @@ -3,7 +3,7 @@ namespace ImageProcessorCore { public interface IImageBase : IImageBase - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { T[] Pixels { get; } diff --git a/src/ImageProcessorCore/Image/IImageFrame.cs b/src/ImageProcessorCore/Image/IImageFrame.cs index 744716fafe..dadc5dd1c6 100644 --- a/src/ImageProcessorCore/Image/IImageFrame.cs +++ b/src/ImageProcessorCore/Image/IImageFrame.cs @@ -1,7 +1,7 @@ namespace ImageProcessorCore { public interface IImageFrame : IImageBase - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { } diff --git a/src/ImageProcessorCore/Image/IImageProcessor.cs b/src/ImageProcessorCore/Image/IImageProcessor.cs index 6e92c7c8cf..617dc76cdc 100644 --- a/src/ImageProcessorCore/Image/IImageProcessor.cs +++ b/src/ImageProcessorCore/Image/IImageProcessor.cs @@ -46,7 +46,7 @@ namespace ImageProcessorCore.Processors /// doesnt fit the dimension of the image. /// void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) - where T : IPackedVector, + where T : IPackedVector, new() where TP : struct; /// @@ -70,7 +70,7 @@ namespace ImageProcessorCore.Processors /// the result of image process as new image. /// void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct; } } diff --git a/src/ImageProcessorCore/Image/Image.cs b/src/ImageProcessorCore/Image/Image.cs index 890feb9f89..67d0b53e40 100644 --- a/src/ImageProcessorCore/Image/Image.cs +++ b/src/ImageProcessorCore/Image/Image.cs @@ -21,7 +21,7 @@ namespace ImageProcessorCore /// The packed vector containing pixel information. /// public class Image : ImageBase - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { /// diff --git a/src/ImageProcessorCore/Image/ImageBase.cs b/src/ImageProcessorCore/Image/ImageBase.cs index ec455f271d..691f7450dd 100644 --- a/src/ImageProcessorCore/Image/ImageBase.cs +++ b/src/ImageProcessorCore/Image/ImageBase.cs @@ -15,7 +15,7 @@ namespace ImageProcessorCore /// The packed vector pixels format. /// public abstract class ImageBase : IImageBase - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { /// diff --git a/src/ImageProcessorCore/Image/ImageExtensions.cs b/src/ImageProcessorCore/Image/ImageExtensions.cs index c38f951a8d..b65bdc760a 100644 --- a/src/ImageProcessorCore/Image/ImageExtensions.cs +++ b/src/ImageProcessorCore/Image/ImageExtensions.cs @@ -62,7 +62,7 @@ namespace ImageProcessorCore /// The processor to apply to the image. /// The . public static Image Process(this Image source, IImageProcessor processor) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { return Process(source, source.Bounds, processor); @@ -80,7 +80,7 @@ namespace ImageProcessorCore /// The processors to apply to the image. /// The . public static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { return PerformAction(source, true, (sourceImage, targetImage) => processor.Apply(targetImage, sourceImage, sourceRectangle)); @@ -99,7 +99,7 @@ namespace ImageProcessorCore /// The processor to apply to the image. /// The . public static Image Process(this Image source, int width, int height, IImageSampler sampler) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { return Process(source, width, height, source.Bounds, default(Rectangle), sampler); @@ -125,7 +125,7 @@ namespace ImageProcessorCore /// 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) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { return PerformAction(source, false, (sourceImage, targetImage) => sampler.Apply(targetImage, sourceImage, width, height, targetRectangle, sourceRectangle)); @@ -140,7 +140,7 @@ namespace ImageProcessorCore /// The to perform against the image. /// The . private static Image PerformAction(Image source, bool clone, Action, ImageBase> action) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { Image transformedImage = clone diff --git a/src/ImageProcessorCore/Image/ImageFrame.cs b/src/ImageProcessorCore/Image/ImageFrame.cs index 0846dc7783..84ea69fa09 100644 --- a/src/ImageProcessorCore/Image/ImageFrame.cs +++ b/src/ImageProcessorCore/Image/ImageFrame.cs @@ -12,7 +12,7 @@ namespace ImageProcessorCore /// The packed vector containing pixel information. /// public class ImageFrame : ImageBase - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { /// diff --git a/src/ImageProcessorCore/ImageProcessor.cs b/src/ImageProcessorCore/ImageProcessor.cs index bfa8eacdfc..7569435c0b 100644 --- a/src/ImageProcessorCore/ImageProcessor.cs +++ b/src/ImageProcessorCore/ImageProcessor.cs @@ -28,7 +28,7 @@ namespace ImageProcessorCore.Processors /// public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { try @@ -51,7 +51,7 @@ namespace ImageProcessorCore.Processors /// public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { try @@ -98,7 +98,7 @@ namespace ImageProcessorCore.Processors /// The structure that specifies the portion of the image object to draw. /// protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { } @@ -124,7 +124,7 @@ namespace ImageProcessorCore.Processors /// the result of image process as new image. /// protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct; /// @@ -141,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) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { } diff --git a/src/ImageProcessorCore/PackedVector/Bgra32.cs b/src/ImageProcessorCore/PackedVector/Bgra32.cs index 9c1848aab4..7020d43b76 100644 --- a/src/ImageProcessorCore/PackedVector/Bgra32.cs +++ b/src/ImageProcessorCore/PackedVector/Bgra32.cs @@ -2,6 +2,7 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // + namespace ImageProcessorCore { using System; @@ -10,17 +11,11 @@ namespace ImageProcessorCore /// /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 1. /// - public unsafe struct Bgra32 : IPackedVector, IEquatable + public struct Bgra32 : IPackedVector, IEquatable { - const uint B_MASK = 0x000000FF; - const uint G_MASK = 0x0000FF00; - const uint R_MASK = 0x00FF0000; - const uint A_MASK = 0xFF000000; - const int B_SHIFT = 0; - const int G_SHIFT = 8; - const int R_SHIFT = 16; - const int A_SHIFT = 24; - + /// + /// The packed value. + /// private uint packedValue; /// @@ -31,11 +26,29 @@ namespace ImageProcessorCore /// The red component. /// The alpha component. public Bgra32(float b, float g, float r, float a) + : this() { Vector4 clamped = Vector4.Clamp(new Vector4(b, g, r, a), Vector4.Zero, Vector4.One) * 255f; this.packedValue = Pack(ref clamped); } + /// + /// Initializes a new instance of the struct. + /// + /// The blue component. + /// The green component. + /// The red component. + /// The alpha component. + public Bgra32(byte b, byte g, byte r, byte a) + : this() + { + this.B = b; + this.G = g; + this.R = r; + this.A = a; + this.packedValue = this.Pack(); + } + /// /// Initializes a new instance of the struct. /// @@ -43,194 +56,31 @@ namespace ImageProcessorCore /// The vector containing the components for the packed vector. /// public Bgra32(Vector4 vector) + : this() { Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; this.packedValue = Pack(ref clamped); } - public byte B - { - get - { - return (byte)(this.packedValue & B_MASK); - } - - set - { - this.packedValue = (this.packedValue & ~B_MASK) | value; - } - } - - public byte G - { - get - { - return (byte)((this.packedValue & G_MASK) >> G_SHIFT); - } - - set - { - this.packedValue = (this.packedValue & ~G_MASK) | (((uint)value) << G_SHIFT); - } - } - - public byte R - { - get - { - return (byte)((this.packedValue & R_MASK) >> R_SHIFT); - } - - set - { - this.packedValue = (this.packedValue & ~R_MASK) | (((uint)value) << R_SHIFT); - } - } - - public byte A - { - get - { - return (byte)((this.packedValue & A_MASK) >> A_SHIFT); - } - - set - { - this.packedValue = (this.packedValue & ~A_MASK) | (((uint)value) << A_SHIFT); - } - } - - /// - public uint PackedValue() - { - return this.packedValue; - } - - public void Add(TP value) where TP : IPackedVector - { - // this.PackVector(this.ToVector4() + value.ToVector4()); - } - - public void Subtract(TP value) where TP : IPackedVector - { - // this.PackVector(this.ToVector4() - value.ToVector4()); - } - - public void Multiply(TP value) where TP : IPackedVector - { - // this.PackVector(this.ToVector4() * value.ToVector4()); - } - - public void Multiply(float value) where TP : IPackedVector - { - this.B = (byte)(this.B * value); - this.G = (byte)(this.G * value); - this.R = (byte)(this.R * value); - this.A = (byte)(this.A * value); - } - - public void Divide(TP value) where TP : IPackedVector - { - // this.PackVector(this.ToVector4() / value.ToVector4()); - } - - public void Divide(float value) where TP : IPackedVector - { - this.B = (byte)(this.B / value); - this.G = (byte)(this.G / value); - this.R = (byte)(this.R / value); - this.A = (byte)(this.A / value); - } - /// - /// Computes the product of multiplying a Bgra32 by a given factor. + /// Gets or sets the blue component. /// - /// The Bgra32. - /// The multiplication factor. - /// - /// The - /// - public static Bgra32 operator *(Bgra32 value, float factor) - { - byte b = (byte)(value.B * factor); - byte g = (byte)(value.G * factor); - byte r = (byte)(value.R * factor); - byte a = (byte)(value.A * factor); - - return new Bgra32(b, g, r, a); - } + public byte B { get; set; } /// - /// Computes the product of multiplying a Bgra32 by a given factor. + /// Gets or sets the green component. /// - /// The multiplication factor. - /// The Bgra32. - /// - /// The - /// - public static Bgra32 operator *(float factor, Bgra32 value) - { - byte b = (byte)(value.B * factor); - byte g = (byte)(value.G * factor); - byte r = (byte)(value.R * factor); - byte a = (byte)(value.A * factor); - - return new Bgra32(b, g, r, a); - } - - /// - /// Computes the product of multiplying two Bgra32s. - /// - /// The Bgra32 on the left hand of the operand. - /// The Bgra32 on the right hand of the operand. - /// - /// The - /// - public static Bgra32 operator *(Bgra32 left, Bgra32 right) - { - byte b = (byte)(left.B * right.B); - byte g = (byte)(left.G * right.G); - byte r = (byte)(left.R * right.R); - byte a = (byte)(left.A * right.A); - - return new Bgra32(b, g, r, a); - } + public byte G { get; set; } /// - /// Computes the sum of adding two Bgra32s. + /// Gets or sets the red component. /// - /// The Bgra32 on the left hand of the operand. - /// The Bgra32 on the right hand of the operand. - /// - /// The - /// - public static Bgra32 operator +(Bgra32 left, Bgra32 right) - { - byte b = (byte)(left.B + right.B); - byte g = (byte)(left.G + right.G); - byte r = (byte)(left.R + right.R); - byte a = (byte)(left.A + right.A); - - return new Bgra32(b, g, r, a); - } + public byte R { get; set; } /// - /// Computes the difference left by subtracting one Bgra32 from another. + /// Gets or sets the alpha component. /// - /// The Bgra32 on the left hand of the operand. - /// The Bgra32 on the right hand of the operand. - /// - /// The - /// - public static Bgra32 operator -(Bgra32 left, Bgra32 right) - { - byte b = (byte)(left.B - right.B); - byte g = (byte)(left.G - right.G); - byte r = (byte)(left.R - right.R); - byte a = (byte)(left.A - right.A); - - return new Bgra32(b, g, r, a); - } + public byte A { get; set; } /// /// Compares two objects for equality. @@ -262,6 +112,61 @@ namespace ImageProcessorCore return left.packedValue != right.packedValue; } + /// + public uint PackedValue() + { + this.packedValue = this.Pack(); + return this.packedValue; + } + + public void Add(Bgra32 value) + { + this.B = (byte)(this.B + value.B); + this.G = (byte)(this.G + value.G); + this.R = (byte)(this.R + value.R); + this.A = (byte)(this.A + value.A); + } + + public void Subtract(Bgra32 value) + { + this.B = (byte)(this.B - value.B); + this.G = (byte)(this.G - value.G); + this.R = (byte)(this.R - value.R); + this.A = (byte)(this.A - value.A); + } + + public void Multiply(Bgra32 value) + { + this.B = (byte)(this.B * value.B); + this.G = (byte)(this.G * value.G); + this.R = (byte)(this.R * value.R); + this.A = (byte)(this.A * value.A); + } + + public void Multiply(float value) + { + this.B = (byte)(this.B * value); + this.G = (byte)(this.G * value); + this.R = (byte)(this.R * value); + this.A = (byte)(this.A * value); + } + + public void Divide(Bgra32 value) + { + this.B = (byte)(this.B / value.B); + this.G = (byte)(this.G / value.G); + this.R = (byte)(this.R / value.R); + this.A = (byte)(this.A / value.A); + } + + public void Divide(float value) + { + this.B = (byte)(this.B / value); + this.G = (byte)(this.G / value); + this.R = (byte)(this.R / value); + this.A = (byte)(this.A / value); + } + /// public void PackVector(Vector4 vector) { @@ -272,7 +177,11 @@ namespace ImageProcessorCore /// public void PackBytes(byte x, byte y, byte z, byte w) { - this.packedValue = Pack(ref x, ref y, ref z, ref w); + this.B = x; + this.G = y; + this.R = z; + this.A = w; + this.packedValue = this.Pack(); } /// @@ -341,9 +250,15 @@ namespace ImageProcessorCore ((uint)Math.Round(vector.W) << 24); } - private static uint Pack(ref byte x, ref byte y, ref byte z, ref byte w) + /// + /// Sets the packed representation from the given component values. + /// + /// + /// The . + /// + private uint Pack() { - return x | ((uint)y << 8) | ((uint)z << 16) | ((uint)w << 24); + return this.B | ((uint)this.G << 8) | ((uint)this.R << 16) | ((uint)this.A << 24); } /// diff --git a/src/ImageProcessorCore/PackedVector/IPackedVector.cs b/src/ImageProcessorCore/PackedVector/IPackedVector.cs index 2beeb2c809..5f8a369a4a 100644 --- a/src/ImageProcessorCore/PackedVector/IPackedVector.cs +++ b/src/ImageProcessorCore/PackedVector/IPackedVector.cs @@ -14,26 +14,26 @@ namespace ImageProcessorCore /// /// The type of object representing the packed value. /// - public interface IPackedVector : IPackedVector - where T : struct + public interface IPackedVector : IPackedVector + where TP : struct { /// /// Gets the packed representation of the value. /// Typically packed in least to greatest significance order. /// - T PackedValue(); + TP PackedValue(); - void Add(TP value) where TP : IPackedVector; + void Add(T value); - void Subtract(TP value) where TP : IPackedVector; + void Subtract(T value); - void Multiply(TP value) where TP : IPackedVector; + void Multiply(T value); - void Multiply(float value) where TP : IPackedVector; + void Multiply(float value); - void Divide(TP value) where TP : IPackedVector; + void Divide(T value); - void Divide(float value) where TP : IPackedVector; + void Divide(float value); } /// diff --git a/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs index e41e7d0d9b..20830e4a8f 100644 --- a/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs @@ -11,7 +11,7 @@ namespace ImageProcessorCore /// Encapsulates properties to provides per-pixel access to an images pixels. /// public interface IPixelAccessor : IPixelAccessor - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { /// diff --git a/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs b/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs index e99bfbbf0a..181b8b5e16 100644 --- a/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs +++ b/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs @@ -24,7 +24,7 @@ namespace ImageProcessorCore /// The . /// public static Rectangle CalculateTargetLocationAndBounds(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { switch (options.Mode) @@ -56,7 +56,7 @@ namespace ImageProcessorCore /// The . /// private static Rectangle CalculateCropRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { int width = options.Size.Width; @@ -176,7 +176,7 @@ namespace ImageProcessorCore /// The . /// private static Rectangle CalculatePadRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { int width = options.Size.Width; @@ -258,7 +258,7 @@ namespace ImageProcessorCore /// The . /// private static Rectangle CalculateBoxPadRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { int width = options.Size.Width; @@ -346,7 +346,7 @@ namespace ImageProcessorCore /// The . /// private static Rectangle CalculateMaxRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { int width = options.Size.Width; @@ -388,7 +388,7 @@ namespace ImageProcessorCore /// The . /// private static Rectangle CalculateMinRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { int width = options.Size.Width; diff --git a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs index 135d0d62f7..6dcec5c572 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs @@ -9,8 +9,6 @@ namespace ImageProcessorCore.Processors using System.Numerics; using System.Threading.Tasks; - using ImageProcessorCore.Helpers; - /// /// Provides methods that allow the resizing of images using various algorithms. /// @@ -136,25 +134,6 @@ namespace ImageProcessorCore.Processors Weight[] horizontalValues = this.HorizontalWeights[offsetX].Values; // Destination color components - //Color destination = new Color(); - - //for (int i = 0; i < sum; i++) - //{ - // Weight xw = horizontalValues[i]; - // int originX = xw.Index; - // Color sourceColor = compand - // ? Color.Expand(sourcePixels[originX, y]) - // : sourcePixels[originX, y]; - - // destination += sourceColor * xw.Value; - //} - - //if (compand) - //{ - // destination = Color.Compress(destination); - //} - - //firstPassPixels[x, y] = destination; T destination = default(T); for (int i = 0; i < sum; i++) @@ -162,23 +141,20 @@ namespace ImageProcessorCore.Processors Weight xw = horizontalValues[i]; int originX = xw.Index; T sourceColor = sourcePixels[originX, y]; + //Color sourceColor = compand // ? Color.Expand(sourcePixels[originX, y]) // : sourcePixels[originX, y]; - //sourceColor.Multiply(xw.Value); - //destination.Add(sourceColor); //destination += sourceColor * xw.Value; - //sourceColor.Multiply(xw.Value); - destination.Add(Operator.Add(destination, Operator.MultiplyF(sourceColor, xw.Value))); + sourceColor.Multiply(xw.Value); + destination.Add(sourceColor); } //if (compand) //{ // destination = Color.Compress(destination); //} - //T packed = default(T); - //packed.PackVector(destination); firstPassPixels[x, y] = destination; } @@ -208,16 +184,14 @@ namespace ImageProcessorCore.Processors Weight yw = verticalValues[i]; int originY = yw.Index; T sourceColor = firstPassPixels[x, originY]; + //Color sourceColor = compand // ? Color.Expand(firstPassPixels[x, originY]) // : firstPassPixels[x, originY]; - //Vector4 sourceColor = firstPassPixels[x, originY].ToVector4(); //destination += sourceColor * yw.Value; - //sourceColor.Multiply(yw.Value); - //destination.Add(sourceColor); - destination.Add(Operator.Add(destination, Operator.MultiplyF(sourceColor, yw.Value))); - + sourceColor.Multiply(yw.Value); + destination.Add(sourceColor); } //if (compand) @@ -225,9 +199,6 @@ namespace ImageProcessorCore.Processors // destination = Color.Compress(destination); //} - //T packed = default(T); - //packed.PackVector(destination); - targetPixels[x, y] = destination; } } diff --git a/src/ImageProcessorCore/Samplers/Resize.cs b/src/ImageProcessorCore/Samplers/Resize.cs index 7a08e95bf4..6eec422dba 100644 --- a/src/ImageProcessorCore/Samplers/Resize.cs +++ b/src/ImageProcessorCore/Samplers/Resize.cs @@ -22,7 +22,7 @@ namespace ImageProcessorCore /// 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) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { // Ensure size is populated across both dimensions. @@ -52,7 +52,7 @@ namespace ImageProcessorCore /// 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) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { return Resize(source, width, height, new BicubicResampler(), false, progressHandler); @@ -70,7 +70,7 @@ namespace ImageProcessorCore /// 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) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { return Resize(source, width, height, new BicubicResampler(), compand, progressHandler); @@ -89,7 +89,7 @@ namespace ImageProcessorCore /// 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) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { return Resize(source, width, height, sampler, source.Bounds, new Rectangle(0, 0, width, height), compand, progressHandler); @@ -115,7 +115,7 @@ namespace ImageProcessorCore /// 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) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { if (width == 0 && height > 0) diff --git a/src/ImageProcessorCore/project.json b/src/ImageProcessorCore/project.json index 9469f297c1..e3800d495e 100644 --- a/src/ImageProcessorCore/project.json +++ b/src/ImageProcessorCore/project.json @@ -23,7 +23,6 @@ "System.IO": "4.1.0", "System.IO.Compression": "4.1.0", "System.Linq": "4.1.0", - "System.Linq.Expressions": "4.1.0", "System.Numerics.Vectors": "4.1.1", "System.Resources.ResourceManager": "4.0.1", "System.Runtime.Extensions": "4.1.0", diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs index 96f6da0daf..878a9f1ce5 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs @@ -114,7 +114,7 @@ namespace ImageProcessorCore.Tests { string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); - Image image = new Image(stream); + Image image = new Image(stream); using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) { image.Resize(image.Width / 2, image.Height / 2, sampler, false, this.ProgressUpdate) From 15711f6a119409180b7277557453e71d7038d5b4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 13 Jul 2016 20:02:07 +1000 Subject: [PATCH 19/91] Use blitting, cleanup code. Former-commit-id: a52a42b2bfb7b00e838d5c5108c7eaec8bf1da95 Former-commit-id: 495ad3d53a5d04e510dffb7b1f2f71e3fc7b28d1 Former-commit-id: df993deb570146848acce1064c2a6a960ec7d6f2 --- src/ImageProcessorCore/Image.cs | 32 ++++- src/ImageProcessorCore/Image/IImageBase.cs | 84 +++++++++++- src/ImageProcessorCore/Image/IImageFrame.cs | 14 +- .../Image/IImageProcessor.cs | 10 +- src/ImageProcessorCore/Image/Image.cs | 13 +- src/ImageProcessorCore/Image/ImageBase.cs | 87 +++--------- .../Image/ImageExtensions.cs | 27 ++-- src/ImageProcessorCore/Image/ImageFrame.cs | 9 +- src/ImageProcessorCore/Image/ImageProperty.cs | 21 +-- src/ImageProcessorCore/PackedVector/Bgra32.cs | 125 ++++++++---------- .../PackedVector/IPackedVector.cs | 32 ++++- .../Processors/Samplers/SamplerTests.cs | 2 +- 12 files changed, 259 insertions(+), 197 deletions(-) diff --git a/src/ImageProcessorCore/Image.cs b/src/ImageProcessorCore/Image.cs index 3cb8866bbb..22c171f17b 100644 --- a/src/ImageProcessorCore/Image.cs +++ b/src/ImageProcessorCore/Image.cs @@ -1,19 +1,47 @@ -using System.IO; +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// namespace ImageProcessorCore { + using System; + using System.IO; + + /// + /// Represents an image. Each pixel is a made up four 8-bit components blue, green, red, and alpha. + /// public class Image : Image { - // TODO Constructors. + /// + /// 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) { } + /// + /// Initializes a new instance of the class. + /// + /// + /// The stream containing image information. + /// + /// Thrown if the is null. public Image(Stream stream) : base(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) { diff --git a/src/ImageProcessorCore/Image/IImageBase.cs b/src/ImageProcessorCore/Image/IImageBase.cs index db78d127b9..cdee55cd67 100644 --- a/src/ImageProcessorCore/Image/IImageBase.cs +++ b/src/ImageProcessorCore/Image/IImageBase.cs @@ -1,29 +1,88 @@ -using System.Collections.Generic; +// +// 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, new() 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(); - - void SetPixels(int width, int height, T[] pixels); } + /// + /// Encapsulates the basic properties and methods required to manipulate images. + /// public interface IImageBase { + /// + /// Gets the representing the bounds of the image. + /// Rectangle Bounds { get; } - int FrameDelay { get; set; } - int Height { get; } - double PixelRatio { 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. /// @@ -34,6 +93,19 @@ namespace ImageProcessorCore /// 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 index dadc5dd1c6..4907019e01 100644 --- a/src/ImageProcessorCore/Image/IImageFrame.cs +++ b/src/ImageProcessorCore/Image/IImageFrame.cs @@ -1,6 +1,16 @@ -namespace ImageProcessorCore +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore { - public interface IImageFrame : IImageBase + /// + /// Represents a single frame in a animation. + /// + /// The pixel format. + /// The packed format. long, float. + public interface IImageFrame : IImageBase where T : IPackedVector, new() where TP : struct { diff --git a/src/ImageProcessorCore/Image/IImageProcessor.cs b/src/ImageProcessorCore/Image/IImageProcessor.cs index 617dc76cdc..b817be7245 100644 --- a/src/ImageProcessorCore/Image/IImageProcessor.cs +++ b/src/ImageProcessorCore/Image/IImageProcessor.cs @@ -27,9 +27,10 @@ namespace ImageProcessorCore.Processors event ProgressEventHandler OnProgress; /// - /// Applies the process to the specified portion of the specified . + /// Applies the process to the specified portion of the specified . /// - /// The type of pixels contained within the image. + /// The pixel format. + /// The packed format. long, float. /// Target image to apply the process to. /// The source image. Cannot be null. /// @@ -50,10 +51,11 @@ namespace ImageProcessorCore.Processors new() where TP : struct; /// - /// 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. /// - /// The type of pixels contained within the image. + /// The pixel format. + /// The packed format. long, float. /// Target image to apply the process to. /// The source image. Cannot be null. /// The target width. diff --git a/src/ImageProcessorCore/Image/Image.cs b/src/ImageProcessorCore/Image/Image.cs index 67d0b53e40..8156b17e6e 100644 --- a/src/ImageProcessorCore/Image/Image.cs +++ b/src/ImageProcessorCore/Image/Image.cs @@ -17,9 +17,8 @@ namespace ImageProcessorCore /// /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. /// - /// - /// The packed vector containing pixel information. - /// + /// The pixel format. + /// The packed format. long, float. public class Image : ImageBase where T : IPackedVector, new() where TP : struct @@ -37,7 +36,7 @@ namespace ImageProcessorCore public const double DefaultVerticalResolution = 96; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public Image() { @@ -45,7 +44,7 @@ namespace ImageProcessorCore } /// - /// Initializes a new instance of the class + /// Initializes a new instance of the class /// with the height and the width of the image. /// /// The width of the image in pixels. @@ -58,7 +57,7 @@ namespace ImageProcessorCore } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The stream containing image information. @@ -71,7 +70,7 @@ namespace ImageProcessorCore } /// - /// Initializes a new instance of the class + /// Initializes a new instance of the class /// by making a copy from another image. /// /// The other image, where the clone should be made from. diff --git a/src/ImageProcessorCore/Image/ImageBase.cs b/src/ImageProcessorCore/Image/ImageBase.cs index 691f7450dd..6cd0ca6151 100644 --- a/src/ImageProcessorCore/Image/ImageBase.cs +++ b/src/ImageProcessorCore/Image/ImageBase.cs @@ -8,12 +8,11 @@ namespace ImageProcessorCore using System; /// - /// The base class of all images. Encapsulates the basic properties and methods required to manipulate images - /// in different pixel formats. + /// The base class of all images. Encapsulates the basic properties and methods required to manipulate + /// images in different pixel formats. /// - /// - /// The packed vector pixels format. - /// + /// The pixel format. + /// The packed format. long, float. public abstract class ImageBase : IImageBase where T : IPackedVector, new() where TP : struct @@ -66,68 +65,34 @@ namespace ImageProcessorCore Array.Copy(other.Pixels, this.Pixels, other.Pixels.Length); } - /// - /// Gets or sets the maximum allowable width in pixels. - /// + /// public int MaxWidth { get; set; } = int.MaxValue; - /// - /// Gets or sets the maximum allowable height in pixels. - /// + /// public int MaxHeight { get; set; } = int.MaxValue; - /// - /// Gets the pixels as an array of the given packed pixel format. - /// + /// public T[] Pixels { get; private set; } - /// - /// Gets the width in pixels. - /// + /// public int Width { get; private set; } - /// - /// Gets the height in pixels. - /// + /// public int Height { get; private set; } - /// - /// Gets the pixel ratio made up of the width and height. - /// + /// public double PixelRatio => (double)this.Width / this.Height; - /// - /// Gets the representing the bounds of the image. - /// + /// public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height); - /// - /// Gets or sets th quality of the image. This affects the output quality of lossy image formats. - /// + /// public int Quality { get; set; } - /// - /// Gets or sets the frame delay for animated images. - /// If not 0, this field specifies the number of hundredths (1/100) of a second to - /// wait before continuing with the processing of the Data Stream. - /// The clock starts ticking immediately after the graphic is rendered. - /// + /// public int FrameDelay { get; set; } - /// - /// Sets the pixel array of the image to the given value. - /// - /// The new width of the image. Must be greater than zero. - /// The new height of the image. Must be greater than zero. - /// - /// The array with colors. Must be a multiple of the width and height. - /// - /// - /// Thrown if either or are less than or equal to 0. - /// - /// - /// Thrown if the length is not equal to Width * Height. - /// + /// public void SetPixels(int width, int height, T[] pixels) { if (width <= 0) @@ -150,21 +115,7 @@ namespace ImageProcessorCore this.Pixels = pixels; } - /// - /// Sets the pixel array of the image to the given value, creating a copy of - /// the original pixels. - /// - /// The new width of the image. Must be greater than zero. - /// The new height of the image. Must be greater than zero. - /// - /// The array with colors. Must be a multiple of four times the width and height. - /// - /// - /// Thrown if either or are less than or equal to 0. - /// - /// - /// Thrown if the length is not equal to Width * Height. - /// + /// public void ClonePixels(int width, int height, T[] pixels) { if (width <= 0) @@ -190,13 +141,7 @@ namespace ImageProcessorCore Array.Copy(pixels, this.Pixels, pixels.Length); } - /// - /// Locks the image providing access to the pixels. - /// - /// It is imperative that the accessor is correctly disposed off after use. - /// - /// - /// The + /// public abstract IPixelAccessor Lock(); } } diff --git a/src/ImageProcessorCore/Image/ImageExtensions.cs b/src/ImageProcessorCore/Image/ImageExtensions.cs index b65bdc760a..087edd6556 100644 --- a/src/ImageProcessorCore/Image/ImageExtensions.cs +++ b/src/ImageProcessorCore/Image/ImageExtensions.cs @@ -12,7 +12,7 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { @@ -57,10 +57,11 @@ namespace ImageProcessorCore /// Applies the collection of processors to the image. /// This method does not resize the target image. /// - /// The type of pixels contained within the image. + /// The pixel format. + /// The packed format. long, float. /// The image this method extends. /// The processor to apply to the image. - /// The . + /// The . public static Image Process(this Image source, IImageProcessor processor) where T : IPackedVector, new() where TP : struct @@ -72,13 +73,14 @@ namespace ImageProcessorCore /// Applies the collection of processors to the image. /// This method does not resize the target image. /// - /// The type of pixels contained within the image. + /// The 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 . + /// The . public static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor) where T : IPackedVector, new() where TP : struct @@ -92,12 +94,13 @@ namespace ImageProcessorCore /// This method is not chainable. /// /// - /// The type of pixels contained within the image. + /// 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 . + /// The . public static Image Process(this Image source, int width, int height, IImageSampler sampler) where T : IPackedVector, new() where TP : struct @@ -111,7 +114,8 @@ namespace ImageProcessorCore /// This method does will resize the target image if the source and target rectangles are different. /// /// - /// The type of pixels contained within the image. + /// The pixel format. + /// The packed format. long, float. /// The source image. Cannot be null. /// The target image width. /// The target image height. @@ -123,7 +127,7 @@ namespace ImageProcessorCore /// The image is scaled to fit the rectangle. /// /// The processor to apply to the image. - /// The . + /// The . public static Image Process(this Image source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler) where T : IPackedVector, new() where TP : struct @@ -134,11 +138,12 @@ namespace ImageProcessorCore /// /// Performs the given action on the source image. /// - /// The type of pixels contained within the 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 . + /// The . private static Image PerformAction(Image source, bool clone, Action, ImageBase> action) where T : IPackedVector, new() where TP : struct diff --git a/src/ImageProcessorCore/Image/ImageFrame.cs b/src/ImageProcessorCore/Image/ImageFrame.cs index 84ea69fa09..0cf3ef1e00 100644 --- a/src/ImageProcessorCore/Image/ImageFrame.cs +++ b/src/ImageProcessorCore/Image/ImageFrame.cs @@ -8,22 +8,21 @@ namespace ImageProcessorCore /// /// Represents a single frame in a animation. /// - /// - /// The packed vector containing pixel information. - /// + /// The pixel format. + /// The packed format. long, float. public class ImageFrame : ImageBase where T : IPackedVector, new() where TP : struct { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public ImageFrame() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The frame to create the frame from. diff --git a/src/ImageProcessorCore/Image/ImageProperty.cs b/src/ImageProcessorCore/Image/ImageProperty.cs index 1d8f5bb8ef..ef432ada24 100644 --- a/src/ImageProcessorCore/Image/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/PackedVector/Bgra32.cs b/src/ImageProcessorCore/PackedVector/Bgra32.cs index 7020d43b76..7c0f241429 100644 --- a/src/ImageProcessorCore/PackedVector/Bgra32.cs +++ b/src/ImageProcessorCore/PackedVector/Bgra32.cs @@ -7,16 +7,43 @@ namespace ImageProcessorCore { using System; using System.Numerics; + using System.Runtime.InteropServices; /// /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 1. /// + [StructLayout(LayoutKind.Explicit)] public struct Bgra32 : IPackedVector, IEquatable { + /// + /// Gets or sets the blue component. + /// + [FieldOffset(0)] + public byte B; + + /// + /// Gets or sets the green component. + /// + [FieldOffset(1)] + public byte G; + + /// + /// Gets or sets the red component. + /// + [FieldOffset(2)] + public byte R; + + /// + /// Gets or sets the alpha component. + /// + [FieldOffset(3)] + public byte A; + /// /// The packed value. /// - private uint packedValue; + [FieldOffset(0)] + private readonly uint packedValue; /// /// Initializes a new instance of the struct. @@ -29,7 +56,10 @@ namespace ImageProcessorCore : this() { Vector4 clamped = Vector4.Clamp(new Vector4(b, g, r, a), Vector4.Zero, Vector4.One) * 255f; - this.packedValue = Pack(ref clamped); + this.B = (byte)Math.Round(clamped.X); + this.G = (byte)Math.Round(clamped.Y); + this.R = (byte)Math.Round(clamped.Z); + this.A = (byte)Math.Round(clamped.W); } /// @@ -46,7 +76,6 @@ namespace ImageProcessorCore this.G = g; this.R = r; this.A = a; - this.packedValue = this.Pack(); } /// @@ -59,29 +88,12 @@ namespace ImageProcessorCore : this() { Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; - this.packedValue = Pack(ref clamped); + this.B = (byte)Math.Round(clamped.X); + this.G = (byte)Math.Round(clamped.Y); + this.R = (byte)Math.Round(clamped.Z); + this.A = (byte)Math.Round(clamped.W); } - /// - /// Gets or sets the blue component. - /// - public byte B { get; set; } - - /// - /// Gets or sets the green component. - /// - public byte G { get; set; } - - /// - /// Gets or sets the red component. - /// - public byte R { get; set; } - - /// - /// Gets or sets the alpha component. - /// - public byte A { get; set; } - /// /// Compares two objects for equality. /// @@ -113,12 +125,6 @@ namespace ImageProcessorCore } /// - public uint PackedValue() - { - this.packedValue = this.Pack(); - return this.packedValue; - } - public void Add(Bgra32 value) { this.B = (byte)(this.B + value.B); @@ -127,6 +133,7 @@ namespace ImageProcessorCore this.A = (byte)(this.A + value.A); } + /// public void Subtract(Bgra32 value) { this.B = (byte)(this.B - value.B); @@ -135,6 +142,7 @@ namespace ImageProcessorCore this.A = (byte)(this.A - value.A); } + /// public void Multiply(Bgra32 value) { this.B = (byte)(this.B * value.B); @@ -143,6 +151,7 @@ namespace ImageProcessorCore this.A = (byte)(this.A * value.A); } + /// public void Multiply(float value) { this.B = (byte)(this.B * value); @@ -151,6 +160,7 @@ namespace ImageProcessorCore this.A = (byte)(this.A * value); } + /// public void Divide(Bgra32 value) { this.B = (byte)(this.B / value.B); @@ -159,6 +169,7 @@ namespace ImageProcessorCore this.A = (byte)(this.A / value.A); } + /// public void Divide(float value) { this.B = (byte)(this.B / value); @@ -167,11 +178,20 @@ namespace ImageProcessorCore this.A = (byte)(this.A / value); } + /// + public uint PackedValue() + { + return this.packedValue; + } + /// public void PackVector(Vector4 vector) { Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; - this.packedValue = Pack(ref clamped); + this.B = (byte)Math.Round(clamped.X); + this.G = (byte)Math.Round(clamped.Y); + this.R = (byte)Math.Round(clamped.Z); + this.A = (byte)Math.Round(clamped.W); } /// @@ -181,17 +201,12 @@ namespace ImageProcessorCore this.G = y; this.R = z; this.A = w; - this.packedValue = this.Pack(); } /// public Vector4 ToVector4() { - return new Vector4( - this.packedValue & 0xFF, - (this.packedValue >> 8) & 0xFF, - (this.packedValue >> 16) & 0xFF, - (this.packedValue >> 24) & 0xFF) / 255f; + return new Vector4(this.B, this.G, this.R, this.A) / 255f; } /// @@ -199,10 +214,10 @@ namespace ImageProcessorCore { return new[] { - (byte)(this.packedValue & 0xFF), - (byte)((this.packedValue >> 8) & 0xFF), - (byte)((this.packedValue >> 16) & 0xFF), - (byte)((this.packedValue >> 24) & 0xFF) + this.B, + this.G, + this.R, + this.A }; } @@ -233,34 +248,6 @@ namespace ImageProcessorCore return this.GetHashCode(this); } - /// - /// Sets the packed representation from the given component values. - /// - /// - /// The vector containing the components for the packed vector. - /// - /// - /// The . - /// - private static uint Pack(ref Vector4 vector) - { - return (uint)Math.Round(vector.X) | - ((uint)Math.Round(vector.Y) << 8) | - ((uint)Math.Round(vector.Z) << 16) | - ((uint)Math.Round(vector.W) << 24); - } - - /// - /// Sets the packed representation from the given component values. - /// - /// - /// The . - /// - private uint Pack() - { - return this.B | ((uint)this.G << 8) | ((uint)this.R << 16) | ((uint)this.A << 24); - } - /// /// Returns the hash code for this instance. /// diff --git a/src/ImageProcessorCore/PackedVector/IPackedVector.cs b/src/ImageProcessorCore/PackedVector/IPackedVector.cs index 5f8a369a4a..329cc1703e 100644 --- a/src/ImageProcessorCore/PackedVector/IPackedVector.cs +++ b/src/ImageProcessorCore/PackedVector/IPackedVector.cs @@ -11,9 +11,8 @@ namespace ImageProcessorCore /// An interface that converts packed vector types to and from values, /// allowing multiple encodings to be manipulated in a generic way. /// - /// - /// The type of object representing the packed value. - /// + /// The pixel format. + /// The packed format. long, float. public interface IPackedVector : IPackedVector where TP : struct { @@ -21,18 +20,45 @@ namespace ImageProcessorCore /// Gets the packed representation of the value. /// Typically packed in least to greatest significance order. /// + /// + /// The . + /// TP PackedValue(); + /// + /// Adds the given to the current instance. + /// + /// The packed vector to add. void Add(T value); + /// + /// Subtracts the given from the current instance. + /// + /// The packed vector to subtract. void Subtract(T value); + /// + /// Multiplies the given current instance by given the . + /// + /// The packed vector to multiply by. void Multiply(T value); + /// + /// Multiplies the given current instance by given the value. + /// + /// The value to multiply by. void Multiply(float value); + /// + /// Divides the given current instance by given the . + /// + /// The packed vector to divide by. void Divide(T value); + /// + /// Divides the given current instance by given the value. + /// + /// The value to divide by. void Divide(float value); } diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs index 878a9f1ce5..ba12cdf107 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs @@ -24,7 +24,7 @@ namespace ImageProcessorCore.Tests //{ "Lanczos3", new Lanczos3Resampler() }, //{ "Lanczos5", new Lanczos5Resampler() }, //{ "Lanczos8", new Lanczos8Resampler() }, - //{ "MitchellNetravali", new MitchellNetravaliResampler() }, + { "MitchellNetravali", new MitchellNetravaliResampler() }, //{ "Hermite", new HermiteResampler() }, //{ "Spline", new SplineResampler() }, //{ "Robidoux", new RobidouxResampler() }, From 80fecd1f58e59dd2f5ac774f19c71c6a0e231d4b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 13 Jul 2016 21:41:39 +1000 Subject: [PATCH 20/91] Use default(T) Former-commit-id: b6028bf2565e2e25395e0658332e351df870277b Former-commit-id: cfdbc86934bc83757056a87600580cbd81743b53 Former-commit-id: ac78e9ca21eccb3830d5ba12ca573a9f54aae995 --- src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs | 8 ++++---- src/ImageProcessorCore/ImageProcessor.cs | 10 +++++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs index 391654f219..03bbe6396d 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs @@ -239,7 +239,7 @@ namespace ImageProcessorCore.Formats int arrayOffset = (row * width) + (colOffset + shift); // Stored in b-> g-> r-> a order. - T packed = new T(); + T packed = default(T); packed.PackBytes(colors[colorIndex], colors[colorIndex + 1], colors[colorIndex + 2], 255); imageData[arrayOffset] = packed; } @@ -289,7 +289,7 @@ namespace ImageProcessorCore.Formats int arrayOffset = ((row * width) + x); // Stored in b-> g-> r-> a order. - T packed = new T(); + T packed = default(T); packed.PackBytes(b, g, r, 255); imageData[arrayOffset] = packed; } @@ -328,7 +328,7 @@ namespace ImageProcessorCore.Formats // We divide by 255 as we will store the colors in our floating point format. // Stored in b-> g-> r-> a order. - T packed = new T(); + T packed = default(T); packed.PackBytes(data[offset], data[offset + 1], data[offset + 2], 255); imageData[arrayOffset] = packed; } @@ -366,7 +366,7 @@ namespace ImageProcessorCore.Formats int arrayOffset = ((row * width) + x); // Stored in b-> g-> r-> a order. - T packed = new T(); + T packed = default(T); packed.PackBytes(data[offset], data[offset + 1], data[offset + 2], data[offset + 3]); imageData[arrayOffset] = packed; } diff --git a/src/ImageProcessorCore/ImageProcessor.cs b/src/ImageProcessorCore/ImageProcessor.cs index 7569435c0b..84c5647c4c 100644 --- a/src/ImageProcessorCore/ImageProcessor.cs +++ b/src/ImageProcessorCore/ImageProcessor.cs @@ -88,6 +88,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. /// @@ -104,10 +106,11 @@ namespace ImageProcessorCore.Processors } /// - /// 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. /// - /// The type of pixels contained within the image. + /// The pixel format. + /// The packed format. long, float. /// Target image to apply the process to. /// The source image. Cannot be null. /// @@ -130,7 +133,8 @@ namespace ImageProcessorCore.Processors /// /// This method is called after the process is applied to prepare the processor. /// - /// The type of pixels contained within the image. + /// The pixel format. + /// The packed format. long, float. /// Target image to apply the process to. /// The source image. Cannot be null. /// From 4b8237320c6d80c0f5b676f47685c93b31ada426 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 13 Jul 2016 22:58:28 +1000 Subject: [PATCH 21/91] Use double for resamplers Former-commit-id: 6c024e150c3521c9a8f6ac12da96abfd7b4d1be7 Former-commit-id: 598e6e70606a95d16c6205d4eb04a960842246aa Former-commit-id: 2c3e00cf2db86aaf7eedcbca3b7eef382d689885 --- .../Common/Helpers/ImageMaths.cs | 22 +++++++-------- src/ImageProcessorCore/PackedVector/Bgra32.cs | 18 ++++++++++++ .../PackedVector/IPackedVector.cs | 12 ++++++++ .../Samplers/Processors/ResizeProcessor.cs | 28 +++++++++---------- .../Samplers/Resamplers/BicubicResampler.cs | 12 ++++---- .../Samplers/Resamplers/BoxResampler.cs | 4 +-- .../Resamplers/CatmullRomResampler.cs | 8 +++--- .../Samplers/Resamplers/HermiteResampler.cs | 8 +++--- .../Samplers/Resamplers/IResampler.cs | 6 ++-- .../Samplers/Resamplers/Lanczos3Resampler.cs | 6 ++-- .../Samplers/Resamplers/Lanczos5Resampler.cs | 6 ++-- .../Samplers/Resamplers/Lanczos8Resampler.cs | 6 ++-- .../Resamplers/MitchellNetravaliResampler.cs | 8 +++--- .../Resamplers/NearestNeighborResampler.cs | 4 +-- .../Samplers/Resamplers/RobidouxResampler.cs | 8 +++--- .../Resamplers/RobidouxSharpResampler.cs | 8 +++--- .../Resamplers/RobidouxSoftResampler.cs | 26 ----------------- .../Samplers/Resamplers/SplineResampler.cs | 8 +++--- .../Samplers/Resamplers/TriangleResampler.cs | 4 +-- .../Samplers/Resamplers/WelchResampler.cs | 6 ++-- 20 files changed, 106 insertions(+), 102 deletions(-) delete mode 100644 src/ImageProcessorCore/Samplers/Resamplers/RobidouxSoftResampler.cs diff --git a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs index 2a55286a31..384d3973a8 100644 --- a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs +++ b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs @@ -55,11 +55,11 @@ namespace ImageProcessorCore /// The B-Spline curve variable. /// The Cardinal curve variable. /// - /// The . + /// The . /// - public static float GetBcValue(float x, float b, float c) + public static double GetBcValue(double x, double b, double c) { - float temp; + double temp; if (x < 0) { @@ -87,16 +87,16 @@ namespace ImageProcessorCore /// /// The value to calculate the result for. /// - /// The . + /// The . /// - public static float SinC(float x) + public static double SinC(double x) { - const float Epsilon = .00001f; + const double Epsilon = .00001d; if (Math.Abs(x) > Epsilon) { - x *= (float)Math.PI; - return Clean((float)Math.Sin(x) / x); + x *= (double)Math.PI; + return Clean((double)Math.Sin(x) / x); } return 1.0f; @@ -272,11 +272,11 @@ namespace ImageProcessorCore /// /// The value to clean. /// - /// The + /// The /// . - private static float Clean(float x) + private static double Clean(double x) { - const float Epsilon = .00001f; + const double Epsilon = .00001d; if (Math.Abs(x) < Epsilon) { diff --git a/src/ImageProcessorCore/PackedVector/Bgra32.cs b/src/ImageProcessorCore/PackedVector/Bgra32.cs index 7c0f241429..46c96bf2f2 100644 --- a/src/ImageProcessorCore/PackedVector/Bgra32.cs +++ b/src/ImageProcessorCore/PackedVector/Bgra32.cs @@ -160,6 +160,15 @@ namespace ImageProcessorCore this.A = (byte)(this.A * value); } + /// + public void Multiply(double value) + { + this.B = (byte)(this.B * value); + this.G = (byte)(this.G * value); + this.R = (byte)(this.R * value); + this.A = (byte)(this.A * value); + } + /// public void Divide(Bgra32 value) { @@ -178,6 +187,15 @@ namespace ImageProcessorCore this.A = (byte)(this.A / value); } + /// + public void Divide(double value) + { + this.B = (byte)(this.B / value); + this.G = (byte)(this.G / value); + this.R = (byte)(this.R / value); + this.A = (byte)(this.A / value); + } + /// public uint PackedValue() { diff --git a/src/ImageProcessorCore/PackedVector/IPackedVector.cs b/src/ImageProcessorCore/PackedVector/IPackedVector.cs index 329cc1703e..f130bd5159 100644 --- a/src/ImageProcessorCore/PackedVector/IPackedVector.cs +++ b/src/ImageProcessorCore/PackedVector/IPackedVector.cs @@ -49,6 +49,12 @@ namespace ImageProcessorCore /// The value to multiply by. void Multiply(float value); + /// + /// Multiplies the given current instance by given the value. + /// + /// The value to multiply by. + void Multiply(double value); + /// /// Divides the given current instance by given the . /// @@ -60,6 +66,12 @@ namespace ImageProcessorCore /// /// The value to divide by. void Divide(float value); + + /// + /// Divides the given current instance by given the value. + /// + /// The value to divide by. + void Divide(double value); } /// diff --git a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs index 6dcec5c572..0fd63e4b3f 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs @@ -130,7 +130,7 @@ namespace ImageProcessorCore.Processors { // Ensure offsets are normalised for cropping and padding. int offsetX = x - startX; - float sum = this.HorizontalWeights[offsetX].Sum; + double sum = this.HorizontalWeights[offsetX].Sum; Weight[] horizontalValues = this.HorizontalWeights[offsetX].Values; // Destination color components @@ -171,7 +171,7 @@ namespace ImageProcessorCore.Processors { // Ensure offsets are normalised for cropping and padding. int offsetY = y - startY; - float sum = this.VerticalWeights[offsetY].Sum; + double sum = this.VerticalWeights[offsetY].Sum; Weight[] verticalValues = this.VerticalWeights[offsetY].Values; for (int x = 0; x < width; x++) @@ -229,12 +229,12 @@ namespace ImageProcessorCore.Processors /// protected Weights[] PrecomputeWeights(int destinationSize, int sourceSize) { - float scale = (float)destinationSize / sourceSize; + double scale = (double)destinationSize / sourceSize; IResampler sampler = this.Sampler; - float radius = sampler.Radius; + double radius = sampler.Radius; double left; double right; - float weight; + double weight; int index; int sum; @@ -244,13 +244,13 @@ namespace ImageProcessorCore.Processors // visit every source pixel. if (scale < 1) { - float width = radius / scale; - float filterScale = 1 / scale; + double width = radius / scale; + double filterScale = 1 / scale; // Make the weights slices, one source for each column or row. for (int i = 0; i < destinationSize; i++) { - float centre = i / scale; + double centre = i / scale; left = Math.Ceiling(centre - width); right = Math.Floor(centre + width); @@ -261,7 +261,7 @@ namespace ImageProcessorCore.Processors for (double j = left; j <= right; j++) { - weight = sampler.GetValue((float)((centre - j) / filterScale)) / filterScale; + weight = sampler.GetValue((centre - j) / filterScale) / filterScale; if (j < 0) { index = (int)-j; @@ -285,7 +285,7 @@ namespace ImageProcessorCore.Processors // Make the weights slices, one source for each column or row. for (int i = 0; i < destinationSize; i++) { - float centre = i / scale; + double centre = i / scale; left = Math.Ceiling(centre - radius); right = Math.Floor(centre + radius); result[i] = new Weights @@ -295,7 +295,7 @@ namespace ImageProcessorCore.Processors for (double j = left; j <= right; j++) { - weight = sampler.GetValue((float)(centre - j)); + weight = sampler.GetValue(centre - j); if (j < 0) { index = (int)-j; @@ -328,7 +328,7 @@ namespace ImageProcessorCore.Processors /// /// The index. /// The value. - public Weight(int index, float value) + public Weight(int index, double value) { this.Index = index; this.Value = value; @@ -342,7 +342,7 @@ namespace ImageProcessorCore.Processors /// /// Gets the result of the interpolation algorithm. /// - public float Value { get; } + public double Value { get; } } /// @@ -358,7 +358,7 @@ namespace ImageProcessorCore.Processors /// /// Gets or sets the sum. /// - public float Sum { get; set; } + public double Sum { get; set; } } } } \ No newline at end of file diff --git a/src/ImageProcessorCore/Samplers/Resamplers/BicubicResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/BicubicResampler.cs index 8aecac7a60..041434cdc2 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/BicubicResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/BicubicResampler.cs @@ -13,28 +13,28 @@ namespace ImageProcessorCore public class BicubicResampler : IResampler { /// - public float Radius => 2; + public double Radius => 2; /// - public float GetValue(float x) + public double GetValue(double x) { // The coefficient. - float a = -0.5f; + double a = -0.5d; if (x < 0) { x = -x; } - float result = 0; + double result = 0; if (x <= 1) { - result = (((1.5f * x) - 2.5f) * x * x) + 1; + result = (((1.5d * x) - 2.5d) * x * x) + 1; } else if (x < 2) { - result = (((((a * x) + 2.5f) * x) - 4) * x) + 2; + result = (((((a * x) + 2.5d) * x) - 4) * x) + 2; } return result; diff --git a/src/ImageProcessorCore/Samplers/Resamplers/BoxResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/BoxResampler.cs index b1234e415d..89a6d8acd0 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/BoxResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/BoxResampler.cs @@ -12,10 +12,10 @@ namespace ImageProcessorCore public class BoxResampler : IResampler { /// - public float Radius => 0.5F; + public double Radius => 0.5d; /// - public float GetValue(float x) + public double GetValue(double x) { if (x > -0.5 && x <= 0.5) { diff --git a/src/ImageProcessorCore/Samplers/Resamplers/CatmullRomResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/CatmullRomResampler.cs index 0b5031df88..39dd56129c 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/CatmullRomResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/CatmullRomResampler.cs @@ -14,13 +14,13 @@ namespace ImageProcessorCore public class CatmullRomResampler : IResampler { /// - public float Radius => 2; + public double Radius => 2; /// - public float GetValue(float x) + public double GetValue(double x) { - const float B = 0; - const float C = 1 / 2f; + const double B = 0; + const double C = 0.5d; return ImageMaths.GetBcValue(x, B, C); } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/HermiteResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/HermiteResampler.cs index 49193a3de3..3714471171 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/HermiteResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/HermiteResampler.cs @@ -13,13 +13,13 @@ namespace ImageProcessorCore.Processors public class HermiteResampler : IResampler { /// - public float Radius => 2; + public double Radius => 2; /// - public float GetValue(float x) + public double GetValue(double x) { - const float B = 0; - const float C = 0; + const double B = 0; + const double C = 0; return ImageMaths.GetBcValue(x, B, C); } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/IResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/IResampler.cs index 0dea58440c..d72e448390 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/IResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/IResampler.cs @@ -13,15 +13,15 @@ namespace ImageProcessorCore /// /// Gets the radius in which to sample pixels. /// - float Radius { get; } + double Radius { get; } /// /// Gets the result of the interpolation algorithm. /// /// The value to process. /// - /// The + /// The /// - float GetValue(float x); + double GetValue(double x); } } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos3Resampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos3Resampler.cs index a78b6c066a..6240226f6f 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos3Resampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos3Resampler.cs @@ -13,10 +13,10 @@ namespace ImageProcessorCore public class Lanczos3Resampler : IResampler { /// - public float Radius => 3; + public double Radius => 3; /// - public float GetValue(float x) + public double GetValue(double x) { if (x < 0) { @@ -25,7 +25,7 @@ namespace ImageProcessorCore if (x < 3) { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 3f); + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 3d); } return 0; diff --git a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos5Resampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos5Resampler.cs index 05af2dd7f2..4cd75066ee 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos5Resampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos5Resampler.cs @@ -13,10 +13,10 @@ namespace ImageProcessorCore public class Lanczos5Resampler : IResampler { /// - public float Radius => 5; + public double Radius => 5; /// - public float GetValue(float x) + public double GetValue(double x) { if (x < 0) { @@ -25,7 +25,7 @@ namespace ImageProcessorCore if (x < 5) { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 5f); + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 5d); } return 0; diff --git a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos8Resampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos8Resampler.cs index 8c9a9237d9..3db31b1fe3 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos8Resampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos8Resampler.cs @@ -13,10 +13,10 @@ namespace ImageProcessorCore public class Lanczos8Resampler : IResampler { /// - public float Radius => 8; + public double Radius => 8; /// - public float GetValue(float x) + public double GetValue(double x) { if (x < 0) { @@ -25,7 +25,7 @@ namespace ImageProcessorCore if (x < 8) { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 8f); + return ImageMaths.SinC(x) * ImageMaths.SinC(x / 8d); } return 0; diff --git a/src/ImageProcessorCore/Samplers/Resamplers/MitchellNetravaliResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/MitchellNetravaliResampler.cs index f609f26450..c3f98a9fff 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/MitchellNetravaliResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/MitchellNetravaliResampler.cs @@ -12,13 +12,13 @@ namespace ImageProcessorCore public class MitchellNetravaliResampler : IResampler { /// - public float Radius => 2; + public double Radius => 2; /// - public float GetValue(float x) + public double GetValue(double x) { - const float B = 1 / 3f; - const float C = 1 / 3f; + const double B = 0.333333333333333D; + const double C = 0.333333333333333D; return ImageMaths.GetBcValue(x, B, C); } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/NearestNeighborResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/NearestNeighborResampler.cs index 58b6a9d584..6cd5c53cff 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/NearestNeighborResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/NearestNeighborResampler.cs @@ -12,10 +12,10 @@ namespace ImageProcessorCore public class NearestNeighborResampler : IResampler { /// - public float Radius => 1; + public double Radius => 1; /// - public float GetValue(float x) + public double GetValue(double x) { return x; } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/RobidouxResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/RobidouxResampler.cs index caead12d5d..200c727341 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/RobidouxResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/RobidouxResampler.cs @@ -12,13 +12,13 @@ namespace ImageProcessorCore public class RobidouxResampler : IResampler { /// - public float Radius => 2; + public double Radius => 2; /// - public float GetValue(float x) + public double GetValue(double x) { - const float B = 0.3782158F; - const float C = 0.3108921F; + const double B = 0.37821575509399867D; + const double C = 0.31089212245300067D; return ImageMaths.GetBcValue(x, B, C); } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/RobidouxSharpResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/RobidouxSharpResampler.cs index 633503cd16..3612d5b1d5 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/RobidouxSharpResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/RobidouxSharpResampler.cs @@ -12,13 +12,13 @@ namespace ImageProcessorCore public class RobidouxSharpResampler : IResampler { /// - public float Radius => 2; + public double Radius => 2; /// - public float GetValue(float x) + public double GetValue(double x) { - const float B = 0.26201451F; - const float C = 0.36899274F; + const double B = 0.2620145123990142D; + const double C = 0.3689927438004929D; 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..d4b4982949 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/SplineResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/SplineResampler.cs @@ -12,13 +12,13 @@ namespace ImageProcessorCore public class SplineResampler : IResampler { /// - public float Radius => 2; + public double Radius => 2; /// - public float GetValue(float x) + public double GetValue(double x) { - const float B = 1; - const float C = 0; + const double B = 1; + const double C = 0; return ImageMaths.GetBcValue(x, B, C); } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/TriangleResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/TriangleResampler.cs index cb404b7369..9a7bb1bff9 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/TriangleResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/TriangleResampler.cs @@ -13,10 +13,10 @@ namespace ImageProcessorCore public class TriangleResampler : IResampler { /// - public float Radius => 1; + public double Radius => 1; /// - public float GetValue(float x) + public double GetValue(double x) { if (x < 0) { diff --git a/src/ImageProcessorCore/Samplers/Resamplers/WelchResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/WelchResampler.cs index 3ecaa6a747..3bd0d36cff 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/WelchResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/WelchResampler.cs @@ -12,10 +12,10 @@ namespace ImageProcessorCore public class WelchResampler : IResampler { /// - public float Radius => 3; + public double Radius => 3; /// - public float GetValue(float x) + public double GetValue(double x) { if (x < 0) { @@ -24,7 +24,7 @@ namespace ImageProcessorCore if (x < 3) { - return ImageMaths.SinC(x) * (1.0f - (x * x / 9.0f)); + return ImageMaths.SinC(x) * (1.0d - (x * x / 9.0d)); } return 0; From db0dd3efacb1b06e4d2a4bb63125723f13f6284c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 13 Jul 2016 23:46:31 +1000 Subject: [PATCH 22/91] Clamp values! Former-commit-id: 83933734dde4c1545394533a62388835dfae0996 Former-commit-id: 63b7ec79f61945702174a51807695c71d3354f21 Former-commit-id: 43b34db2532e71a7f98913ecfe8c2ada1c43ee49 --- src/ImageProcessorCore/PackedVector/Bgra32.cs | 103 +++++++++++------- 1 file changed, 64 insertions(+), 39 deletions(-) diff --git a/src/ImageProcessorCore/PackedVector/Bgra32.cs b/src/ImageProcessorCore/PackedVector/Bgra32.cs index 46c96bf2f2..626031d0e8 100644 --- a/src/ImageProcessorCore/PackedVector/Bgra32.cs +++ b/src/ImageProcessorCore/PackedVector/Bgra32.cs @@ -7,6 +7,7 @@ namespace ImageProcessorCore { using System; using System.Numerics; + using System.Runtime.CompilerServices; using System.Runtime.InteropServices; /// @@ -127,73 +128,73 @@ namespace ImageProcessorCore /// public void Add(Bgra32 value) { - this.B = (byte)(this.B + value.B); - this.G = (byte)(this.G + value.G); - this.R = (byte)(this.R + value.R); - this.A = (byte)(this.A + value.A); + this.B = (byte)Clamp(this.B + value.B); + this.G = (byte)Clamp(this.G + value.G); + this.R = (byte)Clamp(this.R + value.R); + this.A = (byte)Clamp(this.A + value.A); } /// public void Subtract(Bgra32 value) { - this.B = (byte)(this.B - value.B); - this.G = (byte)(this.G - value.G); - this.R = (byte)(this.R - value.R); - this.A = (byte)(this.A - value.A); + this.B = (byte)Clamp(this.B - value.B); + this.G = (byte)Clamp(this.G - value.G); + this.R = (byte)Clamp(this.R - value.R); + this.A = (byte)Clamp(this.A - value.A); } /// public void Multiply(Bgra32 value) { - this.B = (byte)(this.B * value.B); - this.G = (byte)(this.G * value.G); - this.R = (byte)(this.R * value.R); - this.A = (byte)(this.A * value.A); + this.B = (byte)Clamp(this.B * value.B); + this.G = (byte)Clamp(this.G * value.G); + this.R = (byte)Clamp(this.R * value.R); + this.A = (byte)Clamp(this.A * value.A); } /// public void Multiply(float value) { - this.B = (byte)(this.B * value); - this.G = (byte)(this.G * value); - this.R = (byte)(this.R * value); - this.A = (byte)(this.A * value); + this.B = (byte)Clamp(this.B * value); + this.G = (byte)Clamp(this.G * value); + this.R = (byte)Clamp(this.R * value); + this.A = (byte)Clamp(this.A * value); } /// public void Multiply(double value) { - this.B = (byte)(this.B * value); - this.G = (byte)(this.G * value); - this.R = (byte)(this.R * value); - this.A = (byte)(this.A * value); + this.B = (byte)Clamp(this.B * value); + this.G = (byte)Clamp(this.G * value); + this.R = (byte)Clamp(this.R * value); + this.A = (byte)Clamp(this.A * value); } /// public void Divide(Bgra32 value) { - this.B = (byte)(this.B / value.B); - this.G = (byte)(this.G / value.G); - this.R = (byte)(this.R / value.R); - this.A = (byte)(this.A / value.A); + this.B = (byte)Clamp((float)this.B / value.B); + this.G = (byte)Clamp((float)this.G / value.G); + this.R = (byte)Clamp((float)this.R / value.R); + this.A = (byte)Clamp((float)this.A / value.A); } /// public void Divide(float value) { - this.B = (byte)(this.B / value); - this.G = (byte)(this.G / value); - this.R = (byte)(this.R / value); - this.A = (byte)(this.A / value); + this.B = (byte)Clamp(this.B / value); + this.G = (byte)Clamp(this.G / value); + this.R = (byte)Clamp(this.R / value); + this.A = (byte)Clamp(this.A / value); } /// public void Divide(double value) { - this.B = (byte)(this.B / value); - this.G = (byte)(this.G / value); - this.R = (byte)(this.R / value); - this.A = (byte)(this.A / value); + this.B = (byte)Clamp(this.B / value); + this.G = (byte)Clamp(this.G / value); + this.R = (byte)Clamp(this.R / value); + this.A = (byte)Clamp(this.A / value); } /// @@ -230,13 +231,7 @@ namespace ImageProcessorCore /// public byte[] ToBytes() { - return new[] - { - this.B, - this.G, - this.R, - this.A - }; + return new[] { this.B, this.G, this.R, this.A }; } /// @@ -266,6 +261,36 @@ namespace ImageProcessorCore return this.GetHashCode(this); } + /// + /// Clamps the value to the acceptable byte range. + /// + /// The value. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float Clamp(float value) + { + value = (value > 255) ? 255 : value; + value = (value < 0) ? 0 : value; + return value; + } + + /// + /// Clamps the value to the acceptable byte range. + /// + /// The value. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static double Clamp(double value) + { + value = (value > 255) ? 255 : value; + value = (value < 0) ? 0 : value; + return value; + } + /// /// Returns the hash code for this instance. /// From a16a10a6398e2775d817eb4541402dfdd60fe8df Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 14 Jul 2016 00:55:06 +1000 Subject: [PATCH 23/91] Benchmark clamp Former-commit-id: ecfd869a3c6f282f03f38239b0dc0a204d3bf565 Former-commit-id: 4ad46c7929a15caebfe440eb7610617e97872a0b Former-commit-id: dfdcfa618c48e66921be1ebb395183de6785323c --- src/ImageProcessorCore/PackedVector/Bgra32.cs | 74 +++++++++---------- .../Color/Clamp.cs | 26 +++++++ 2 files changed, 63 insertions(+), 37 deletions(-) create mode 100644 tests/ImageProcessorCore.Benchmarks/Color/Clamp.cs diff --git a/src/ImageProcessorCore/PackedVector/Bgra32.cs b/src/ImageProcessorCore/PackedVector/Bgra32.cs index 626031d0e8..eed4d5c02f 100644 --- a/src/ImageProcessorCore/PackedVector/Bgra32.cs +++ b/src/ImageProcessorCore/PackedVector/Bgra32.cs @@ -2,7 +2,6 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // - namespace ImageProcessorCore { using System; @@ -128,73 +127,73 @@ namespace ImageProcessorCore /// public void Add(Bgra32 value) { - this.B = (byte)Clamp(this.B + value.B); - this.G = (byte)Clamp(this.G + value.G); - this.R = (byte)Clamp(this.R + value.R); - this.A = (byte)Clamp(this.A + value.A); + this.B = Clamp(this.B + value.B); + this.G = Clamp(this.G + value.G); + this.R = Clamp(this.R + value.R); + this.A = Clamp(this.A + value.A); } /// public void Subtract(Bgra32 value) { - this.B = (byte)Clamp(this.B - value.B); - this.G = (byte)Clamp(this.G - value.G); - this.R = (byte)Clamp(this.R - value.R); - this.A = (byte)Clamp(this.A - value.A); + this.B = Clamp(this.B - value.B); + this.G = Clamp(this.G - value.G); + this.R = Clamp(this.R - value.R); + this.A = Clamp(this.A - value.A); } /// public void Multiply(Bgra32 value) { - this.B = (byte)Clamp(this.B * value.B); - this.G = (byte)Clamp(this.G * value.G); - this.R = (byte)Clamp(this.R * value.R); - this.A = (byte)Clamp(this.A * value.A); + this.B = Clamp(this.B * value.B); + this.G = Clamp(this.G * value.G); + this.R = Clamp(this.R * value.R); + this.A = Clamp(this.A * value.A); } /// public void Multiply(float value) { - this.B = (byte)Clamp(this.B * value); - this.G = (byte)Clamp(this.G * value); - this.R = (byte)Clamp(this.R * value); - this.A = (byte)Clamp(this.A * value); + this.B = Clamp(this.B * value); + this.G = Clamp(this.G * value); + this.R = Clamp(this.R * value); + this.A = Clamp(this.A * value); } /// public void Multiply(double value) { - this.B = (byte)Clamp(this.B * value); - this.G = (byte)Clamp(this.G * value); - this.R = (byte)Clamp(this.R * value); - this.A = (byte)Clamp(this.A * value); + this.B = Clamp(this.B * value); + this.G = Clamp(this.G * value); + this.R = Clamp(this.R * value); + this.A = Clamp(this.A * value); } /// public void Divide(Bgra32 value) { - this.B = (byte)Clamp((float)this.B / value.B); - this.G = (byte)Clamp((float)this.G / value.G); - this.R = (byte)Clamp((float)this.R / value.R); - this.A = (byte)Clamp((float)this.A / value.A); + this.B = Clamp((float)this.B / value.B); + this.G = Clamp((float)this.G / value.G); + this.R = Clamp((float)this.R / value.R); + this.A = Clamp((float)this.A / value.A); } /// public void Divide(float value) { - this.B = (byte)Clamp(this.B / value); - this.G = (byte)Clamp(this.G / value); - this.R = (byte)Clamp(this.R / value); - this.A = (byte)Clamp(this.A / value); + this.B = Clamp(this.B / value); + this.G = Clamp(this.G / value); + this.R = Clamp(this.R / value); + this.A = Clamp(this.A / value); } /// public void Divide(double value) { - this.B = (byte)Clamp(this.B / value); - this.G = (byte)Clamp(this.G / value); - this.R = (byte)Clamp(this.R / value); - this.A = (byte)Clamp(this.A / value); + this.B = Clamp(this.B / value); + this.G = Clamp(this.G / value); + this.R = Clamp(this.R / value); + this.A = Clamp(this.A / value); } /// @@ -269,11 +268,11 @@ namespace ImageProcessorCore /// The . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float Clamp(float value) + private static byte Clamp(float value) { value = (value > 255) ? 255 : value; value = (value < 0) ? 0 : value; - return value; + return (byte)value; } /// @@ -284,11 +283,11 @@ namespace ImageProcessorCore /// The . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static double Clamp(double value) + private static byte Clamp(double value) { value = (value > 255) ? 255 : value; value = (value < 0) ? 0 : value; - return value; + return (byte)value; } /// @@ -300,6 +299,7 @@ namespace ImageProcessorCore /// /// A 32-bit signed integer that is the hash code for this instance. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private int GetHashCode(Bgra32 packed) { return packed.packedValue.GetHashCode(); diff --git a/tests/ImageProcessorCore.Benchmarks/Color/Clamp.cs b/tests/ImageProcessorCore.Benchmarks/Color/Clamp.cs new file mode 100644 index 0000000000..93b6cd94e6 --- /dev/null +++ b/tests/ImageProcessorCore.Benchmarks/Color/Clamp.cs @@ -0,0 +1,26 @@ +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; + } + + } +} From f5e73368a104211d6216c95047d48d8c2ffc337f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 14 Jul 2016 14:18:22 +1000 Subject: [PATCH 24/91] Faster clamp 30% performance increase removing ternary operator. Former-commit-id: 9664ddb7da1c63be4034d0466ac10560db42edf0 Former-commit-id: 019ec4638e02acdf8bafc1f251859a25f75f07d2 Former-commit-id: 9b608a6ba964226e0f45d023d466f207aa24c796 --- src/ImageProcessorCore/PackedVector/Bgra32.cs | 24 +++++++++++++++---- .../Color/Clamp.cs | 17 +++++++++++++ .../Samplers/Resize.cs | 2 +- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/ImageProcessorCore/PackedVector/Bgra32.cs b/src/ImageProcessorCore/PackedVector/Bgra32.cs index eed4d5c02f..3372cb2833 100644 --- a/src/ImageProcessorCore/PackedVector/Bgra32.cs +++ b/src/ImageProcessorCore/PackedVector/Bgra32.cs @@ -270,8 +270,16 @@ namespace ImageProcessorCore [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte Clamp(float value) { - value = (value > 255) ? 255 : value; - value = (value < 0) ? 0 : value; + if (value > 255) + { + return 255; + } + + if (value < 0) + { + return 0; + } + return (byte)value; } @@ -285,8 +293,16 @@ namespace ImageProcessorCore [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte Clamp(double value) { - value = (value > 255) ? 255 : value; - value = (value < 0) ? 0 : value; + if (value > 255) + { + return 255; + } + + if (value < 0) + { + return 0; + } + return (byte)value; } diff --git a/tests/ImageProcessorCore.Benchmarks/Color/Clamp.cs b/tests/ImageProcessorCore.Benchmarks/Color/Clamp.cs index 93b6cd94e6..7af7010ab8 100644 --- a/tests/ImageProcessorCore.Benchmarks/Color/Clamp.cs +++ b/tests/ImageProcessorCore.Benchmarks/Color/Clamp.cs @@ -22,5 +22,22 @@ namespace ImageProcessorCore.Benchmarks.Color 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/Samplers/Resize.cs b/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs index 65ed841f00..e5eb412f29 100644 --- a/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs +++ b/tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs @@ -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; From 91d0b3ec8ec697ede179d94137fe0c5120537b19 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 15 Jul 2016 11:52:36 +1000 Subject: [PATCH 25/91] Now faster than System.Drawing. Former-commit-id: 14ac44a018e4525cf65c19e3864840b4d6934981 Former-commit-id: b6d60bd3b96b352577dfd53a6cdd8f0d237d6a91 Former-commit-id: a0c275d2ed132c6d87f01c2469fd1b7d69e9dc62 --- src/ImageProcessorCore/PackedVector/Bgra32.cs | 122 +----------------- .../PackedVector/IPackedVector.cs | 48 ------- .../Samplers/Processors/ResizeProcessor.cs | 34 ++--- 3 files changed, 14 insertions(+), 190 deletions(-) diff --git a/src/ImageProcessorCore/PackedVector/Bgra32.cs b/src/ImageProcessorCore/PackedVector/Bgra32.cs index 3372cb2833..e519a87424 100644 --- a/src/ImageProcessorCore/PackedVector/Bgra32.cs +++ b/src/ImageProcessorCore/PackedVector/Bgra32.cs @@ -10,7 +10,7 @@ namespace ImageProcessorCore using System.Runtime.InteropServices; /// - /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 1. + /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 255. /// [StructLayout(LayoutKind.Explicit)] public struct Bgra32 : IPackedVector, IEquatable @@ -124,78 +124,6 @@ namespace ImageProcessorCore return left.packedValue != right.packedValue; } - /// - public void Add(Bgra32 value) - { - this.B = Clamp(this.B + value.B); - this.G = Clamp(this.G + value.G); - this.R = Clamp(this.R + value.R); - this.A = Clamp(this.A + value.A); - } - - /// - public void Subtract(Bgra32 value) - { - this.B = Clamp(this.B - value.B); - this.G = Clamp(this.G - value.G); - this.R = Clamp(this.R - value.R); - this.A = Clamp(this.A - value.A); - } - - /// - public void Multiply(Bgra32 value) - { - this.B = Clamp(this.B * value.B); - this.G = Clamp(this.G * value.G); - this.R = Clamp(this.R * value.R); - this.A = Clamp(this.A * value.A); - } - - /// - public void Multiply(float value) - { - this.B = Clamp(this.B * value); - this.G = Clamp(this.G * value); - this.R = Clamp(this.R * value); - this.A = Clamp(this.A * value); - } - - /// - public void Multiply(double value) - { - this.B = Clamp(this.B * value); - this.G = Clamp(this.G * value); - this.R = Clamp(this.R * value); - this.A = Clamp(this.A * value); - } - - /// - public void Divide(Bgra32 value) - { - this.B = Clamp((float)this.B / value.B); - this.G = Clamp((float)this.G / value.G); - this.R = Clamp((float)this.R / value.R); - this.A = Clamp((float)this.A / value.A); - } - - /// - public void Divide(float value) - { - this.B = Clamp(this.B / value); - this.G = Clamp(this.G / value); - this.R = Clamp(this.R / value); - this.A = Clamp(this.A / value); - } - - /// - public void Divide(double value) - { - this.B = Clamp(this.B / value); - this.G = Clamp(this.G / value); - this.R = Clamp(this.R / value); - this.A = Clamp(this.A / value); - } - /// public uint PackedValue() { @@ -260,52 +188,6 @@ namespace ImageProcessorCore return this.GetHashCode(this); } - /// - /// Clamps the value to the acceptable byte range. - /// - /// The value. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte Clamp(float value) - { - if (value > 255) - { - return 255; - } - - if (value < 0) - { - return 0; - } - - return (byte)value; - } - - /// - /// Clamps the value to the acceptable byte range. - /// - /// The value. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte Clamp(double value) - { - if (value > 255) - { - return 255; - } - - if (value < 0) - { - return 0; - } - - return (byte)value; - } - /// /// Returns the hash code for this instance. /// @@ -321,4 +203,4 @@ namespace ImageProcessorCore return packed.packedValue.GetHashCode(); } } -} +} \ No newline at end of file diff --git a/src/ImageProcessorCore/PackedVector/IPackedVector.cs b/src/ImageProcessorCore/PackedVector/IPackedVector.cs index f130bd5159..a528559efe 100644 --- a/src/ImageProcessorCore/PackedVector/IPackedVector.cs +++ b/src/ImageProcessorCore/PackedVector/IPackedVector.cs @@ -24,54 +24,6 @@ namespace ImageProcessorCore /// The . /// TP PackedValue(); - - /// - /// Adds the given to the current instance. - /// - /// The packed vector to add. - void Add(T value); - - /// - /// Subtracts the given from the current instance. - /// - /// The packed vector to subtract. - void Subtract(T value); - - /// - /// Multiplies the given current instance by given the . - /// - /// The packed vector to multiply by. - void Multiply(T value); - - /// - /// Multiplies the given current instance by given the value. - /// - /// The value to multiply by. - void Multiply(float value); - - /// - /// Multiplies the given current instance by given the value. - /// - /// The value to multiply by. - void Multiply(double value); - - /// - /// Divides the given current instance by given the . - /// - /// The packed vector to divide by. - void Divide(T value); - - /// - /// Divides the given current instance by given the value. - /// - /// The value to divide by. - void Divide(float value); - - /// - /// Divides the given current instance by given the value. - /// - /// The value to divide by. - void Divide(double value); } /// diff --git a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs index 0fd63e4b3f..0dcafd7b0b 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs @@ -134,21 +134,14 @@ namespace ImageProcessorCore.Processors Weight[] horizontalValues = this.HorizontalWeights[offsetX].Values; // Destination color components - T destination = default(T); + Vector4 destination = Vector4.Zero; for (int i = 0; i < sum; i++) { Weight xw = horizontalValues[i]; int originX = xw.Index; - T sourceColor = sourcePixels[originX, y]; - - //Color sourceColor = compand - // ? Color.Expand(sourcePixels[originX, y]) - // : sourcePixels[originX, y]; - //destination += sourceColor * xw.Value; - - sourceColor.Multiply(xw.Value); - destination.Add(sourceColor); + Vector4 sourceColor = sourcePixels[originX, y].ToVector4(); + destination += sourceColor * (float)xw.Value; } //if (compand) @@ -156,7 +149,9 @@ namespace ImageProcessorCore.Processors // destination = Color.Compress(destination); //} - firstPassPixels[x, y] = destination; + T d = default(T); + d.PackVector(destination); + firstPassPixels[x, y] = d; } } }); @@ -177,21 +172,14 @@ namespace ImageProcessorCore.Processors for (int x = 0; x < width; x++) { // Destination color components - T destination = default(T); + Vector4 destination = Vector4.Zero; for (int i = 0; i < sum; i++) { Weight yw = verticalValues[i]; int originY = yw.Index; - T sourceColor = firstPassPixels[x, originY]; - - //Color sourceColor = compand - // ? Color.Expand(firstPassPixels[x, originY]) - // : firstPassPixels[x, originY]; - //destination += sourceColor * yw.Value; - - sourceColor.Multiply(yw.Value); - destination.Add(sourceColor); + Vector4 sourceColor = firstPassPixels[x, originY].ToVector4(); + destination += sourceColor * (float)yw.Value; } //if (compand) @@ -199,7 +187,9 @@ namespace ImageProcessorCore.Processors // destination = Color.Compress(destination); //} - targetPixels[x, y] = destination; + T d = default(T); + d.PackVector(destination); + targetPixels[x, y] = d; } } From 2922f76ec3355bbea5d7b814d4034ca63c7d6b9e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 15 Jul 2016 12:10:07 +1000 Subject: [PATCH 26/91] Use float to prevent casting. Former-commit-id: 4d0f0912927e86fb3dda26bc8131c2b61e4913d6 Former-commit-id: 87fc5322e14aa23b36a5edc91e2a539be87e6568 Former-commit-id: ba498a73a37b2550eded45e46a37515e5182d157 --- .../Common/Helpers/ImageMaths.cs | 36 +++++++++---------- .../Samplers/Processors/ResizeProcessor.cs | 28 +++++++-------- .../Samplers/Resamplers/BicubicResampler.cs | 18 +++++----- .../Samplers/Resamplers/BoxResampler.cs | 6 ++-- .../Resamplers/CatmullRomResampler.cs | 8 ++--- .../Samplers/Resamplers/HermiteResampler.cs | 8 ++--- .../Samplers/Resamplers/IResampler.cs | 6 ++-- .../Samplers/Resamplers/Lanczos3Resampler.cs | 12 +++---- .../Samplers/Resamplers/Lanczos5Resampler.cs | 12 +++---- .../Samplers/Resamplers/Lanczos8Resampler.cs | 12 +++---- .../Resamplers/MitchellNetravaliResampler.cs | 8 ++--- .../Resamplers/NearestNeighborResampler.cs | 4 +-- .../Samplers/Resamplers/RobidouxResampler.cs | 8 ++--- .../Resamplers/RobidouxSharpResampler.cs | 8 ++--- .../Samplers/Resamplers/SplineResampler.cs | 8 ++--- .../Samplers/Resamplers/TriangleResampler.cs | 12 +++---- .../Samplers/Resamplers/WelchResampler.cs | 12 +++---- 17 files changed, 103 insertions(+), 103 deletions(-) diff --git a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs index 384d3973a8..6ec45549bc 100644 --- a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs +++ b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs @@ -55,31 +55,31 @@ namespace ImageProcessorCore /// The B-Spline curve variable. /// The Cardinal curve variable. /// - /// The . + /// The . /// - public static double GetBcValue(double x, double b, double c) + public static float GetBcValue(float x, float b, float c) { - double temp; + 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; } /// @@ -87,16 +87,16 @@ namespace ImageProcessorCore /// /// The value to calculate the result for. /// - /// The . + /// The . /// - public static double SinC(double x) + public static float SinC(float x) { - const double Epsilon = .00001d; + const float Epsilon = .00001F; if (Math.Abs(x) > Epsilon) { - x *= (double)Math.PI; - return Clean((double)Math.Sin(x) / x); + x *= (float)Math.PI; + return Clean((float)Math.Sin(x) / x); } return 1.0f; @@ -272,15 +272,15 @@ namespace ImageProcessorCore /// /// The value to clean. /// - /// The + /// The /// . - private static double Clean(double x) + private static float Clean(float x) { - const double Epsilon = .00001d; + const float Epsilon = .00001F; if (Math.Abs(x) < Epsilon) { - return 0f; + return 0F; } return x; diff --git a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs index 0dcafd7b0b..a01fd92402 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs @@ -141,7 +141,7 @@ namespace ImageProcessorCore.Processors Weight xw = horizontalValues[i]; int originX = xw.Index; Vector4 sourceColor = sourcePixels[originX, y].ToVector4(); - destination += sourceColor * (float)xw.Value; + destination += sourceColor * xw.Value; } //if (compand) @@ -179,7 +179,7 @@ namespace ImageProcessorCore.Processors Weight yw = verticalValues[i]; int originY = yw.Index; Vector4 sourceColor = firstPassPixels[x, originY].ToVector4(); - destination += sourceColor * (float)yw.Value; + destination += sourceColor * yw.Value; } //if (compand) @@ -219,12 +219,12 @@ namespace ImageProcessorCore.Processors /// protected Weights[] PrecomputeWeights(int destinationSize, int sourceSize) { - double scale = (double)destinationSize / sourceSize; + float scale = (float)destinationSize / sourceSize; IResampler sampler = this.Sampler; - double radius = sampler.Radius; + float radius = sampler.Radius; double left; double right; - double weight; + float weight; int index; int sum; @@ -234,13 +234,13 @@ namespace ImageProcessorCore.Processors // visit every source pixel. if (scale < 1) { - double width = radius / scale; - double filterScale = 1 / scale; + float width = radius / scale; + float filterScale = 1 / scale; // Make the weights slices, one source for each column or row. for (int i = 0; i < destinationSize; i++) { - double centre = i / scale; + float centre = i / scale; left = Math.Ceiling(centre - width); right = Math.Floor(centre + width); @@ -251,7 +251,7 @@ namespace ImageProcessorCore.Processors for (double j = left; j <= right; j++) { - weight = sampler.GetValue((centre - j) / filterScale) / filterScale; + weight = sampler.GetValue((float)((centre - j) / filterScale)) / filterScale; if (j < 0) { index = (int)-j; @@ -275,7 +275,7 @@ namespace ImageProcessorCore.Processors // Make the weights slices, one source for each column or row. for (int i = 0; i < destinationSize; i++) { - double centre = i / scale; + float centre = i / scale; left = Math.Ceiling(centre - radius); right = Math.Floor(centre + radius); result[i] = new Weights @@ -285,7 +285,7 @@ namespace ImageProcessorCore.Processors for (double j = left; j <= right; j++) { - weight = sampler.GetValue(centre - j); + weight = sampler.GetValue((float)(centre - j)); if (j < 0) { index = (int)-j; @@ -318,7 +318,7 @@ namespace ImageProcessorCore.Processors /// /// The index. /// The value. - public Weight(int index, double value) + public Weight(int index, float value) { this.Index = index; this.Value = value; @@ -332,7 +332,7 @@ namespace ImageProcessorCore.Processors /// /// Gets the result of the interpolation algorithm. /// - public double Value { get; } + public float Value { get; } } /// @@ -348,7 +348,7 @@ namespace ImageProcessorCore.Processors /// /// Gets or sets the sum. /// - public double Sum { get; set; } + public float Sum { get; set; } } } } \ No newline at end of file diff --git a/src/ImageProcessorCore/Samplers/Resamplers/BicubicResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/BicubicResampler.cs index 041434cdc2..33120b0663 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/BicubicResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/BicubicResampler.cs @@ -13,28 +13,28 @@ namespace ImageProcessorCore public class BicubicResampler : IResampler { /// - public double Radius => 2; + public float Radius => 2; /// - public double GetValue(double x) + public float GetValue(float x) { // The coefficient. - double a = -0.5d; + float a = -0.5F; - if (x < 0) + if (x < 0F) { x = -x; } - double result = 0; + float result = 0; - if (x <= 1) + if (x <= 1F) { - result = (((1.5d * x) - 2.5d) * x * x) + 1; + result = (((1.5F * x) - 2.5F) * x * x) + 1; } - else if (x < 2) + else if (x < 2F) { - result = (((((a * x) + 2.5d) * x) - 4) * x) + 2; + 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 89a6d8acd0..49a3ad4678 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/BoxResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/BoxResampler.cs @@ -12,12 +12,12 @@ namespace ImageProcessorCore public class BoxResampler : IResampler { /// - public double Radius => 0.5d; + public float Radius => 0.5F; /// - public double GetValue(double x) + 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 39dd56129c..281d0190ac 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/CatmullRomResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/CatmullRomResampler.cs @@ -14,13 +14,13 @@ namespace ImageProcessorCore public class CatmullRomResampler : IResampler { /// - public double Radius => 2; + public float Radius => 2; /// - public double GetValue(double x) + public float GetValue(float x) { - const double B = 0; - const double C = 0.5d; + const float B = 0; + 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 3714471171..43d05fc880 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/HermiteResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/HermiteResampler.cs @@ -13,13 +13,13 @@ namespace ImageProcessorCore.Processors public class HermiteResampler : IResampler { /// - public double Radius => 2; + public float Radius => 2; /// - public double GetValue(double x) + public float GetValue(float x) { - const double B = 0; - const double C = 0; + const float B = 0F; + const float C = 0F; return ImageMaths.GetBcValue(x, B, C); } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/IResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/IResampler.cs index d72e448390..0dea58440c 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/IResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/IResampler.cs @@ -13,15 +13,15 @@ namespace ImageProcessorCore /// /// Gets the radius in which to sample pixels. /// - double Radius { get; } + float Radius { get; } /// /// Gets the result of the interpolation algorithm. /// /// The value to process. /// - /// The + /// The /// - double GetValue(double x); + float GetValue(float x); } } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos3Resampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos3Resampler.cs index 6240226f6f..6ed82afc09 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos3Resampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos3Resampler.cs @@ -13,22 +13,22 @@ namespace ImageProcessorCore public class Lanczos3Resampler : IResampler { /// - public double Radius => 3; + public float Radius => 3; /// - public double GetValue(double x) + 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 / 3d); + 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 4cd75066ee..c0d1d2fdd7 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos5Resampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos5Resampler.cs @@ -13,22 +13,22 @@ namespace ImageProcessorCore public class Lanczos5Resampler : IResampler { /// - public double Radius => 5; + public float Radius => 5; /// - public double GetValue(double x) + 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 / 5d); + 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 3db31b1fe3..e0f2917c27 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/Lanczos8Resampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/Lanczos8Resampler.cs @@ -13,22 +13,22 @@ namespace ImageProcessorCore public class Lanczos8Resampler : IResampler { /// - public double Radius => 8; + public float Radius => 8; /// - public double GetValue(double x) + 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 / 8d); + 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 c3f98a9fff..cacd35f0a1 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/MitchellNetravaliResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/MitchellNetravaliResampler.cs @@ -12,13 +12,13 @@ namespace ImageProcessorCore public class MitchellNetravaliResampler : IResampler { /// - public double Radius => 2; + public float Radius => 2; /// - public double GetValue(double x) + public float GetValue(float x) { - const double B = 0.333333333333333D; - const double C = 0.333333333333333D; + const float B = 0.3333333F; + const float C = 0.3333333F; return ImageMaths.GetBcValue(x, B, C); } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/NearestNeighborResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/NearestNeighborResampler.cs index 6cd5c53cff..58b6a9d584 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/NearestNeighborResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/NearestNeighborResampler.cs @@ -12,10 +12,10 @@ namespace ImageProcessorCore public class NearestNeighborResampler : IResampler { /// - public double Radius => 1; + public float Radius => 1; /// - public double GetValue(double x) + public float GetValue(float x) { return x; } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/RobidouxResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/RobidouxResampler.cs index 200c727341..85f68c531e 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/RobidouxResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/RobidouxResampler.cs @@ -12,13 +12,13 @@ namespace ImageProcessorCore public class RobidouxResampler : IResampler { /// - public double Radius => 2; + public float Radius => 2; /// - public double GetValue(double x) + public float GetValue(float x) { - const double B = 0.37821575509399867D; - const double C = 0.31089212245300067D; + 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 3612d5b1d5..eb8e07ade5 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/RobidouxSharpResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/RobidouxSharpResampler.cs @@ -12,13 +12,13 @@ namespace ImageProcessorCore public class RobidouxSharpResampler : IResampler { /// - public double Radius => 2; + public float Radius => 2; /// - public double GetValue(double x) + public float GetValue(float x) { - const double B = 0.2620145123990142D; - const double C = 0.3689927438004929D; + const float B = 0.2620145123990142F; + const float C = 0.3689927438004929F; return ImageMaths.GetBcValue(x, B, C); } diff --git a/src/ImageProcessorCore/Samplers/Resamplers/SplineResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/SplineResampler.cs index d4b4982949..f88f9abef3 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/SplineResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/SplineResampler.cs @@ -12,13 +12,13 @@ namespace ImageProcessorCore public class SplineResampler : IResampler { /// - public double Radius => 2; + public float Radius => 2; /// - public double GetValue(double x) + public float GetValue(float x) { - const double B = 1; - const double 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 9a7bb1bff9..2269bb251d 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/TriangleResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/TriangleResampler.cs @@ -13,22 +13,22 @@ namespace ImageProcessorCore public class TriangleResampler : IResampler { /// - public double Radius => 1; + public float Radius => 1; /// - public double GetValue(double x) + 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 3bd0d36cff..ef0ab9cea1 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/WelchResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/WelchResampler.cs @@ -12,22 +12,22 @@ namespace ImageProcessorCore public class WelchResampler : IResampler { /// - public double Radius => 3; + public float Radius => 3; /// - public double GetValue(double x) + public float GetValue(float x) { - if (x < 0) + if (x < 0F) { x = -x; } - if (x < 3) + if (x < 3F) { - return ImageMaths.SinC(x) * (1.0d - (x * x / 9.0d)); + return ImageMaths.SinC(x) * (1F - (x * x / 9.0F)); } - return 0; + return 0F; } } } From 33497d525085d08e50490e377341cad645a09857 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 15 Jul 2016 19:30:39 +1000 Subject: [PATCH 27/91] No need for second type param Former-commit-id: 19180d2962371ebdbe77b1e08a1867b8630abf43 Former-commit-id: 84da094533206cb0c1a859a438fe6a937af9fa77 Former-commit-id: 3094f56fbf68569748a6b31f3874cf716ed673b6 --- src/ImageProcessorCore/Bootstrapper.cs | 2 +- src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs | 2 +- src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs | 10 +++++----- src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs | 2 +- src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs | 8 ++++---- src/ImageProcessorCore/Formats/IImageDecoder.cs | 2 +- src/ImageProcessorCore/Formats/IImageEncoder.cs | 2 +- src/ImageProcessorCore/Image/IImageBase.cs | 2 +- src/ImageProcessorCore/Image/IImageFrame.cs | 2 +- src/ImageProcessorCore/Image/IImageProcessor.cs | 4 ++-- src/ImageProcessorCore/Image/Image.cs | 2 +- src/ImageProcessorCore/Image/ImageBase.cs | 2 +- src/ImageProcessorCore/Image/ImageExtensions.cs | 10 +++++----- src/ImageProcessorCore/Image/ImageFrame.cs | 2 +- src/ImageProcessorCore/ImageProcessor.cs | 10 +++++----- src/ImageProcessorCore/PackedVector/Bgra32.cs | 2 +- src/ImageProcessorCore/PackedVector/IPackedVector.cs | 3 +-- .../PixelAccessor/IPixelAccessor.cs | 9 +++++++-- .../Samplers/Options/ResizeHelper.cs | 12 ++++++------ src/ImageProcessorCore/Samplers/Resize.cs | 10 +++++----- 20 files changed, 51 insertions(+), 47 deletions(-) diff --git a/src/ImageProcessorCore/Bootstrapper.cs b/src/ImageProcessorCore/Bootstrapper.cs index 98537c46a8..a611119921 100644 --- a/src/ImageProcessorCore/Bootstrapper.cs +++ b/src/ImageProcessorCore/Bootstrapper.cs @@ -74,7 +74,7 @@ namespace ImageProcessorCore /// The image /// The public IPixelAccessor GetPixelAccessor(IImageBase image) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { Type packed = typeof(T); diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs index f55fa28edd..916bddce7b 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs @@ -75,7 +75,7 @@ namespace ImageProcessorCore.Formats /// The to decode to. /// The containing image data. public void Decode(Image image, Stream stream) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { new BmpDecoderCore().Decode(image, stream); diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs index 03bbe6396d..ee52c4ebfc 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs @@ -60,7 +60,7 @@ namespace ImageProcessorCore.Formats /// is null. /// public void Decode(Image image, Stream stream) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { this.currentStream = stream; @@ -195,7 +195,7 @@ namespace ImageProcessorCore.Formats /// The number of bits per pixel. /// Whether the bitmap is inverted. private void ReadRgbPalette(T[] imageData, byte[] colors, int width, int height, int bits, bool inverted) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { // Pixels per byte (bits per pixel) @@ -256,7 +256,7 @@ namespace ImageProcessorCore.Formats /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRgb16(T[] imageData, int width, int height, bool inverted) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { // We divide here as we will store the colors in our floating point format. @@ -305,7 +305,7 @@ namespace ImageProcessorCore.Formats /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRgb24(T[] imageData, int width, int height, bool inverted) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { int alignment; @@ -344,7 +344,7 @@ namespace ImageProcessorCore.Formats /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRgb32(T[] imageData, int width, int height, bool inverted) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { int alignment; diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs index b7fbdd12ac..07ada9c05a 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs @@ -44,7 +44,7 @@ namespace ImageProcessorCore.Formats /// public void Encode(ImageBase image, Stream stream) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { BmpEncoderCore encoder = new BmpEncoderCore(); diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs index de67b9fe43..cc26ea700c 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs @@ -29,7 +29,7 @@ namespace ImageProcessorCore.Formats /// The to encode the image data to. /// The public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { Guard.NotNull(image, nameof(image)); @@ -129,7 +129,7 @@ namespace ImageProcessorCore.Formats /// The containing pixel data. /// private void WriteImage(EndianBinaryWriter writer, ImageBase image) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { // TODO: Add more compression formats. @@ -162,7 +162,7 @@ namespace ImageProcessorCore.Formats /// The containing pixel data. /// The amount to pad each row by. private void Write32bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { for (int y = pixels.Height - 1; y >= 0; y--) @@ -189,7 +189,7 @@ namespace ImageProcessorCore.Formats /// The containing pixel data. /// The amount to pad each row by. private void Write24bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { for (int y = pixels.Height - 1; y >= 0; y--) diff --git a/src/ImageProcessorCore/Formats/IImageDecoder.cs b/src/ImageProcessorCore/Formats/IImageDecoder.cs index e2e89f2df4..09dbcdb1e7 100644 --- a/src/ImageProcessorCore/Formats/IImageDecoder.cs +++ b/src/ImageProcessorCore/Formats/IImageDecoder.cs @@ -45,7 +45,7 @@ namespace ImageProcessorCore.Formats /// The to decode to. /// The containing image data. void Decode(Image image, Stream stream) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct; } } diff --git a/src/ImageProcessorCore/Formats/IImageEncoder.cs b/src/ImageProcessorCore/Formats/IImageEncoder.cs index f040619e55..7738968631 100644 --- a/src/ImageProcessorCore/Formats/IImageEncoder.cs +++ b/src/ImageProcessorCore/Formats/IImageEncoder.cs @@ -49,7 +49,7 @@ namespace ImageProcessorCore.Formats /// The to encode from. /// The to encode the image data to. void Encode(ImageBase image, Stream stream) - where T : IPackedVector, + where T : IPackedVector, new() where TP : struct; } } diff --git a/src/ImageProcessorCore/Image/IImageBase.cs b/src/ImageProcessorCore/Image/IImageBase.cs index cdee55cd67..a168f91cc6 100644 --- a/src/ImageProcessorCore/Image/IImageBase.cs +++ b/src/ImageProcessorCore/Image/IImageBase.cs @@ -13,7 +13,7 @@ namespace ImageProcessorCore /// The pixel format. /// The packed format. long, float. public interface IImageBase : IImageBase - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { /// diff --git a/src/ImageProcessorCore/Image/IImageFrame.cs b/src/ImageProcessorCore/Image/IImageFrame.cs index 4907019e01..6e51a83003 100644 --- a/src/ImageProcessorCore/Image/IImageFrame.cs +++ b/src/ImageProcessorCore/Image/IImageFrame.cs @@ -11,7 +11,7 @@ namespace ImageProcessorCore /// The pixel format. /// The packed format. long, float. public interface IImageFrame : IImageBase - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { } diff --git a/src/ImageProcessorCore/Image/IImageProcessor.cs b/src/ImageProcessorCore/Image/IImageProcessor.cs index b817be7245..4241b88c75 100644 --- a/src/ImageProcessorCore/Image/IImageProcessor.cs +++ b/src/ImageProcessorCore/Image/IImageProcessor.cs @@ -47,7 +47,7 @@ namespace ImageProcessorCore.Processors /// doesnt fit the dimension of the image. /// void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) - where T : IPackedVector, + where T : IPackedVector, new() where TP : struct; /// @@ -72,7 +72,7 @@ namespace ImageProcessorCore.Processors /// the result of image process as new image. /// void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct; } } diff --git a/src/ImageProcessorCore/Image/Image.cs b/src/ImageProcessorCore/Image/Image.cs index 8156b17e6e..2c7ceeab15 100644 --- a/src/ImageProcessorCore/Image/Image.cs +++ b/src/ImageProcessorCore/Image/Image.cs @@ -20,7 +20,7 @@ namespace ImageProcessorCore /// The pixel format. /// The packed format. long, float. public class Image : ImageBase - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { /// diff --git a/src/ImageProcessorCore/Image/ImageBase.cs b/src/ImageProcessorCore/Image/ImageBase.cs index 6cd0ca6151..2ec1538b74 100644 --- a/src/ImageProcessorCore/Image/ImageBase.cs +++ b/src/ImageProcessorCore/Image/ImageBase.cs @@ -14,7 +14,7 @@ namespace ImageProcessorCore /// The pixel format. /// The packed format. long, float. public abstract class ImageBase : IImageBase - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { /// diff --git a/src/ImageProcessorCore/Image/ImageExtensions.cs b/src/ImageProcessorCore/Image/ImageExtensions.cs index 087edd6556..67b1263189 100644 --- a/src/ImageProcessorCore/Image/ImageExtensions.cs +++ b/src/ImageProcessorCore/Image/ImageExtensions.cs @@ -63,7 +63,7 @@ namespace ImageProcessorCore /// The processor to apply to the image. /// The . public static Image Process(this Image source, IImageProcessor processor) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { return Process(source, source.Bounds, processor); @@ -82,7 +82,7 @@ namespace ImageProcessorCore /// The processors to apply to the image. /// The . public static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { return PerformAction(source, true, (sourceImage, targetImage) => processor.Apply(targetImage, sourceImage, sourceRectangle)); @@ -102,7 +102,7 @@ namespace ImageProcessorCore /// The processor to apply to the image. /// The . public static Image Process(this Image source, int width, int height, IImageSampler sampler) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { return Process(source, width, height, source.Bounds, default(Rectangle), sampler); @@ -129,7 +129,7 @@ namespace ImageProcessorCore /// 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) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { return PerformAction(source, false, (sourceImage, targetImage) => sampler.Apply(targetImage, sourceImage, width, height, targetRectangle, sourceRectangle)); @@ -145,7 +145,7 @@ namespace ImageProcessorCore /// The to perform against the image. /// The . private static Image PerformAction(Image source, bool clone, Action, ImageBase> action) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { Image transformedImage = clone diff --git a/src/ImageProcessorCore/Image/ImageFrame.cs b/src/ImageProcessorCore/Image/ImageFrame.cs index 0cf3ef1e00..4afb12a947 100644 --- a/src/ImageProcessorCore/Image/ImageFrame.cs +++ b/src/ImageProcessorCore/Image/ImageFrame.cs @@ -11,7 +11,7 @@ namespace ImageProcessorCore /// The pixel format. /// The packed format. long, float. public class ImageFrame : ImageBase - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { /// diff --git a/src/ImageProcessorCore/ImageProcessor.cs b/src/ImageProcessorCore/ImageProcessor.cs index 84c5647c4c..c7c0f4e1b7 100644 --- a/src/ImageProcessorCore/ImageProcessor.cs +++ b/src/ImageProcessorCore/ImageProcessor.cs @@ -28,7 +28,7 @@ namespace ImageProcessorCore.Processors /// public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { try @@ -51,7 +51,7 @@ namespace ImageProcessorCore.Processors /// public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { try @@ -100,7 +100,7 @@ namespace ImageProcessorCore.Processors /// The structure that specifies the portion of the image object to draw. /// protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { } @@ -127,7 +127,7 @@ namespace ImageProcessorCore.Processors /// the result of image process as new image. /// protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct; /// @@ -145,7 +145,7 @@ namespace ImageProcessorCore.Processors /// The structure that specifies the portion of the image object to draw. /// protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { } diff --git a/src/ImageProcessorCore/PackedVector/Bgra32.cs b/src/ImageProcessorCore/PackedVector/Bgra32.cs index e519a87424..73ff87d9fe 100644 --- a/src/ImageProcessorCore/PackedVector/Bgra32.cs +++ b/src/ImageProcessorCore/PackedVector/Bgra32.cs @@ -13,7 +13,7 @@ namespace ImageProcessorCore /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 255. /// [StructLayout(LayoutKind.Explicit)] - public struct Bgra32 : IPackedVector, IEquatable + public struct Bgra32 : IPackedVector, IEquatable { /// /// Gets or sets the blue component. diff --git a/src/ImageProcessorCore/PackedVector/IPackedVector.cs b/src/ImageProcessorCore/PackedVector/IPackedVector.cs index a528559efe..a32870b2e0 100644 --- a/src/ImageProcessorCore/PackedVector/IPackedVector.cs +++ b/src/ImageProcessorCore/PackedVector/IPackedVector.cs @@ -11,9 +11,8 @@ namespace ImageProcessorCore /// An interface that converts packed vector types to and from values, /// allowing multiple encodings to be manipulated in a generic way. /// - /// The pixel format. /// The packed format. long, float. - public interface IPackedVector : IPackedVector + public interface IPackedVector : IPackedVector where TP : struct { /// diff --git a/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs index 20830e4a8f..15cd3268bd 100644 --- a/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs @@ -6,12 +6,14 @@ 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, new() + where T : IPackedVector, new() where TP : struct { /// @@ -33,6 +35,9 @@ namespace ImageProcessorCore } } + /// + /// Encapsulates properties to provides per-pixel access to an images pixels. + /// public interface IPixelAccessor : IDisposable { /// diff --git a/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs b/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs index 181b8b5e16..e99bfbbf0a 100644 --- a/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs +++ b/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs @@ -24,7 +24,7 @@ namespace ImageProcessorCore /// The . /// public static Rectangle CalculateTargetLocationAndBounds(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { switch (options.Mode) @@ -56,7 +56,7 @@ namespace ImageProcessorCore /// The . /// private static Rectangle CalculateCropRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { int width = options.Size.Width; @@ -176,7 +176,7 @@ namespace ImageProcessorCore /// The . /// private static Rectangle CalculatePadRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { int width = options.Size.Width; @@ -258,7 +258,7 @@ namespace ImageProcessorCore /// The . /// private static Rectangle CalculateBoxPadRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { int width = options.Size.Width; @@ -346,7 +346,7 @@ namespace ImageProcessorCore /// The . /// private static Rectangle CalculateMaxRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { int width = options.Size.Width; @@ -388,7 +388,7 @@ namespace ImageProcessorCore /// The . /// private static Rectangle CalculateMinRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { int width = options.Size.Width; diff --git a/src/ImageProcessorCore/Samplers/Resize.cs b/src/ImageProcessorCore/Samplers/Resize.cs index 6eec422dba..7a08e95bf4 100644 --- a/src/ImageProcessorCore/Samplers/Resize.cs +++ b/src/ImageProcessorCore/Samplers/Resize.cs @@ -22,7 +22,7 @@ namespace ImageProcessorCore /// 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) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { // Ensure size is populated across both dimensions. @@ -52,7 +52,7 @@ namespace ImageProcessorCore /// 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) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { return Resize(source, width, height, new BicubicResampler(), false, progressHandler); @@ -70,7 +70,7 @@ namespace ImageProcessorCore /// 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) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { return Resize(source, width, height, new BicubicResampler(), compand, progressHandler); @@ -89,7 +89,7 @@ namespace ImageProcessorCore /// 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) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { return Resize(source, width, height, sampler, source.Bounds, new Rectangle(0, 0, width, height), compand, progressHandler); @@ -115,7 +115,7 @@ namespace ImageProcessorCore /// 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) - where T : IPackedVector, new() + where T : IPackedVector, new() where TP : struct { if (width == 0 && height > 0) From 20c65011ac8b0f22fc6f93b8a8666382944f7d74 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 15 Jul 2016 20:05:27 +1000 Subject: [PATCH 28/91] Now Color Former-commit-id: df0c84ebe32444c8e2bf964f839af8fd6b80d196 Former-commit-id: af528ad7c73c8c20f1f1fb5f0e4864bc00dfc4ee Former-commit-id: e62c836d6621e86a03386c83867d4dbcb668dc5e --- src/ImageProcessorCore/Bootstrapper.cs | 2 +- .../Formats/Bmp/BmpDecoderCore.cs | 27 ++++++----- .../Formats/Bmp/BmpEncoderCore.cs | 36 ++++++++------- src/ImageProcessorCore/Image.cs | 4 +- .../PackedVector/{Bgra32.cs => Color.cs} | 45 ++++++++++--------- .../PackedVector/IPackedVector.cs | 1 + ...PixelAccessor.cs => ColorPixelAccessor.cs} | 20 ++++----- .../Image/GetSetPixel.cs | 12 ++--- 8 files changed, 78 insertions(+), 69 deletions(-) rename src/ImageProcessorCore/PackedVector/{Bgra32.cs => Color.cs} (80%) rename src/ImageProcessorCore/PixelAccessor/{Bgra32PixelAccessor.cs => ColorPixelAccessor.cs} (87%) diff --git a/src/ImageProcessorCore/Bootstrapper.cs b/src/ImageProcessorCore/Bootstrapper.cs index a611119921..39e97e1100 100644 --- a/src/ImageProcessorCore/Bootstrapper.cs +++ b/src/ImageProcessorCore/Bootstrapper.cs @@ -44,7 +44,7 @@ namespace ImageProcessorCore this.pixelAccessors = new Dictionary> { - { typeof(Bgra32), i=> new Bgra32PixelAccessor(i) } + { typeof(Color), i=> new ColorPixelAccessor(i) } }; } diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs index ee52c4ebfc..c964dec48a 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs @@ -49,7 +49,8 @@ namespace ImageProcessorCore.Formats /// Decodes the image from the specified this._stream and sets /// the data to image. /// - /// The type of pixels contained within the image. + /// The 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 @@ -187,7 +188,8 @@ namespace ImageProcessorCore.Formats /// /// Reads the color palette from the stream. /// - /// The type of pixels contained within the image. + /// 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. @@ -238,9 +240,9 @@ namespace ImageProcessorCore.Formats int colorIndex = ((data[offset] >> (8 - bits - (shift * bits))) & mask) * 4; int arrayOffset = (row * width) + (colOffset + shift); - // Stored in b-> g-> r-> a order. + // Stored in b-> g-> r order. T packed = default(T); - packed.PackBytes(colors[colorIndex], colors[colorIndex + 1], colors[colorIndex + 2], 255); + packed.PackBytes(colors[colorIndex + 2], colors[colorIndex + 1], colors[colorIndex], 255); imageData[arrayOffset] = packed; } } @@ -250,7 +252,8 @@ namespace ImageProcessorCore.Formats /// /// Reads the 16 bit color palette from the stream /// - /// The type of pixels contained within the image. + /// 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. @@ -288,9 +291,9 @@ namespace ImageProcessorCore.Formats int arrayOffset = ((row * width) + x); - // Stored in b-> g-> r-> a order. + // Stored in b-> g-> r order. T packed = default(T); - packed.PackBytes(b, g, r, 255); + packed.PackBytes(r, g, b, 255); imageData[arrayOffset] = packed; } }); @@ -299,7 +302,8 @@ namespace ImageProcessorCore.Formats /// /// Reads the 24 bit color palette from the stream /// - /// The type of pixels contained within the image. + /// 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. @@ -329,7 +333,7 @@ namespace ImageProcessorCore.Formats // We divide by 255 as we will store the colors in our floating point format. // Stored in b-> g-> r-> a order. T packed = default(T); - packed.PackBytes(data[offset], data[offset + 1], data[offset + 2], 255); + packed.PackBytes(data[offset + 2], data[offset + 1], data[offset], 255); imageData[arrayOffset] = packed; } }); @@ -338,7 +342,8 @@ namespace ImageProcessorCore.Formats /// /// Reads the 32 bit color palette from the stream /// - /// The type of pixels contained within the image. + /// 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. @@ -367,7 +372,7 @@ namespace ImageProcessorCore.Formats // Stored in b-> g-> r-> a order. T packed = default(T); - packed.PackBytes(data[offset], data[offset + 1], data[offset + 2], data[offset + 3]); + packed.PackBytes(data[offset + 2], data[offset + 1], data[offset], data[offset + 3]); imageData[arrayOffset] = packed; } }); diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs index cc26ea700c..497afc70c4 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs @@ -13,7 +13,6 @@ namespace ImageProcessorCore.Formats /// /// 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,13 +21,14 @@ namespace ImageProcessorCore.Formats private BmpBitsPerPixel bmpBitsPerPixel; /// - /// Encodes the image to the specified stream from the . + /// Encodes the image to the specified stream from the . /// - /// The type of pixels contained within the image. - /// 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, new() where TP : struct { @@ -121,14 +121,14 @@ namespace ImageProcessorCore.Formats /// /// Writes the pixel data to the binary stream. /// - /// The type of pixels contained within the image. - /// + /// 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, new() where TP : struct { @@ -139,7 +139,7 @@ namespace ImageProcessorCore.Formats amount = 4 - amount; } - using (IPixelAccessor pixels = image.Lock()) + using (IPixelAccessor pixels = image.Lock()) { switch (this.bmpBitsPerPixel) { @@ -157,11 +157,12 @@ namespace ImageProcessorCore.Formats /// /// Writes the 32bit color palette to the stream. /// - /// The type of pixels contained within the image. + /// 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 Write32bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) + private void Write32bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) where T : IPackedVector, new() where TP : struct { @@ -171,7 +172,7 @@ namespace ImageProcessorCore.Formats { // Convert back to b-> g-> r-> a order. byte[] bytes = pixels[x, y].ToBytes(); - writer.Write(bytes); + writer.Write(new[] { bytes[2], bytes[1], bytes[0], bytes[3] }); } // Pad @@ -185,10 +186,11 @@ namespace ImageProcessorCore.Formats /// /// Writes the 24bit color palette to the stream. /// - /// The containing the stream to write to. + /// 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, IPixelAccessor pixels, int amount) + private void Write24bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) where T : IPackedVector, new() where TP : struct { @@ -196,9 +198,9 @@ namespace ImageProcessorCore.Formats { for (int x = 0; x < pixels.Width; x++) { - // Convert back to b-> g-> r-> a order. + // Convert back to b-> g-> r order. byte[] bytes = pixels[x, y].ToBytes(); - writer.Write(new[] { bytes[0], bytes[1], bytes[2] }); + writer.Write(new[] { bytes[2], bytes[1], bytes[0] }); } // Pad diff --git a/src/ImageProcessorCore/Image.cs b/src/ImageProcessorCore/Image.cs index 22c171f17b..e57129d29e 100644 --- a/src/ImageProcessorCore/Image.cs +++ b/src/ImageProcessorCore/Image.cs @@ -9,9 +9,9 @@ namespace ImageProcessorCore using System.IO; /// - /// Represents an image. Each pixel is a made up four 8-bit components blue, green, red, and alpha. + /// Represents an image. Each pixel is a made up four 8-bit components red, green, blue, and alpha. /// - public class Image : Image + public class Image : Image { /// /// Initializes a new instance of the class diff --git a/src/ImageProcessorCore/PackedVector/Bgra32.cs b/src/ImageProcessorCore/PackedVector/Color.cs similarity index 80% rename from src/ImageProcessorCore/PackedVector/Bgra32.cs rename to src/ImageProcessorCore/PackedVector/Color.cs index 73ff87d9fe..5f1ace7b0c 100644 --- a/src/ImageProcessorCore/PackedVector/Bgra32.cs +++ b/src/ImageProcessorCore/PackedVector/Color.cs @@ -1,7 +1,8 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // + namespace ImageProcessorCore { using System; @@ -13,13 +14,13 @@ namespace ImageProcessorCore /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 255. /// [StructLayout(LayoutKind.Explicit)] - public struct Bgra32 : IPackedVector, IEquatable + public struct Color : IPackedVector, IEquatable { /// /// Gets or sets the blue component. /// [FieldOffset(0)] - public byte B; + public byte R; /// /// Gets or sets the green component. @@ -31,7 +32,7 @@ namespace ImageProcessorCore /// Gets or sets the red component. /// [FieldOffset(2)] - public byte R; + public byte B; /// /// Gets or sets the alpha component. @@ -46,13 +47,13 @@ namespace ImageProcessorCore private readonly uint packedValue; /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// /// The blue component. /// The green component. /// The red component. /// The alpha component. - public Bgra32(float b, float g, float r, float a) + public Color(float b, float g, float r, float a) : this() { Vector4 clamped = Vector4.Clamp(new Vector4(b, g, r, a), Vector4.Zero, Vector4.One) * 255f; @@ -63,13 +64,13 @@ namespace ImageProcessorCore } /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// /// The blue component. /// The green component. /// The red component. /// The alpha component. - public Bgra32(byte b, byte g, byte r, byte a) + public Color(byte b, byte g, byte r, byte a) : this() { this.B = b; @@ -79,12 +80,12 @@ namespace ImageProcessorCore } /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// /// /// The vector containing the components for the packed vector. /// - public Bgra32(Vector4 vector) + public Color(Vector4 vector) : this() { Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; @@ -95,31 +96,31 @@ namespace ImageProcessorCore } /// - /// Compares two objects for equality. + /// Compares two objects for equality. /// /// - /// The on the left side of the operand. + /// The on the left side of the operand. /// /// - /// The on the right side of the operand. + /// The on the right side of the operand. /// /// /// True if the current left is equal to the parameter; otherwise, false. /// - public static bool operator ==(Bgra32 left, Bgra32 right) + public static bool operator ==(Color left, Color right) { return left.packedValue == right.packedValue; } /// - /// Compares two objects for equality. + /// 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 equal to the parameter; otherwise, false. /// - public static bool operator !=(Bgra32 left, Bgra32 right) + public static bool operator !=(Color left, Color right) { return left.packedValue != right.packedValue; } @@ -164,11 +165,11 @@ namespace ImageProcessorCore /// public override bool Equals(object obj) { - return (obj is Bgra32) && this.Equals((Bgra32)obj); + return (obj is Color) && this.Equals((Color)obj); } /// - public bool Equals(Bgra32 other) + public bool Equals(Color other) { return this.packedValue == other.packedValue; } @@ -192,13 +193,13 @@ 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. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int GetHashCode(Bgra32 packed) + private int GetHashCode(Color packed) { return packed.packedValue.GetHashCode(); } diff --git a/src/ImageProcessorCore/PackedVector/IPackedVector.cs b/src/ImageProcessorCore/PackedVector/IPackedVector.cs index a32870b2e0..99cd70d748 100644 --- a/src/ImageProcessorCore/PackedVector/IPackedVector.cs +++ b/src/ImageProcessorCore/PackedVector/IPackedVector.cs @@ -55,6 +55,7 @@ namespace ImageProcessorCore /// /// 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/PixelAccessor/Bgra32PixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor/ColorPixelAccessor.cs similarity index 87% rename from src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs rename to src/ImageProcessorCore/PixelAccessor/ColorPixelAccessor.cs index c48090d7b2..6d0812a2a4 100644 --- a/src/ImageProcessorCore/PixelAccessor/Bgra32PixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor/ColorPixelAccessor.cs @@ -12,15 +12,15 @@ namespace ImageProcessorCore /// Provides per-pixel access to an images pixels. /// /// - /// The image data is always stored in format, where the blue, green, red, and + /// 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 Bgra32PixelAccessor : IPixelAccessor + public sealed unsafe class ColorPixelAccessor : IPixelAccessor { /// /// The position of the first pixel in the bitmap. /// - private Bgra32* pixelsBase; + private Color* pixelsBase; /// /// Provides a way to access the pixels from unmanaged memory. @@ -41,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 Bgra32PixelAccessor(IImageBase image) + public ColorPixelAccessor(IImageBase image) { Guard.NotNull(image, nameof(image)); Guard.MustBeGreaterThan(image.Width, 0, "image width"); @@ -55,14 +55,14 @@ namespace ImageProcessorCore this.Width = image.Width; this.Height = image.Height; - this.pixelsHandle = GCHandle.Alloc(((ImageBase)image).Pixels, GCHandleType.Pinned); - this.pixelsBase = (Bgra32*)this.pixelsHandle.AddrOfPinnedObject().ToPointer(); + 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. /// - ~Bgra32PixelAccessor() + ~ColorPixelAccessor() { this.Dispose(); } @@ -89,7 +89,7 @@ namespace ImageProcessorCore /// than zero and smaller than the width of the pixel. /// /// The at the specified position. - public Bgra32 this[int x, int y] + public Color this[int x, int y] { get { diff --git a/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs b/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs index 238a5ccad4..0af79c78ed 100644 --- a/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs +++ b/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs @@ -4,8 +4,8 @@ using BenchmarkDotNet.Attributes; - //using CoreColor = ImageProcessorCore.Color; - //using CoreImage = ImageProcessorCore.Image; + using CoreColor = ImageProcessorCore.Color; + using CoreImage = ImageProcessorCore.Image; using SystemColor = System.Drawing.Color; public class GetSetPixel @@ -21,12 +21,12 @@ } [Benchmark(Description = "ImageProcessorCore GetSet Pixel")] - public Bgra32 ResizeCore() + public CoreColor ResizeCore() { - Image image = new Image(400, 400); - using (IPixelAccessor imagePixels = image.Lock()) + CoreImage image = new CoreImage(400, 400); + using (IPixelAccessor imagePixels = image.Lock()) { - imagePixels[200, 200] = new Bgra32(1, 1, 1, 1); + imagePixels[200, 200] = new CoreColor(255, 255, 255, 255); return imagePixels[200, 200]; } } From 8756f2f11d6b18ea9fae700bd23080ace087b9b9 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Fri, 15 Jul 2016 17:16:52 +0200 Subject: [PATCH 29/91] update to latest #BenchmarkDotNet version Former-commit-id: 6ca587c2d3f773d2f430030e2ca0d8d4bd7fe11b Former-commit-id: 01b7b1e4c8fb0146d5efa0d755d0d0f248df5eeb Former-commit-id: 20c535434e174f9adb525313989423a986df908e --- NuGet.config | 3 ++- tests/ImageProcessorCore.Benchmarks/Config.cs | 14 +++++++++++++ .../ImageProcessorCore.Benchmarks/Program.cs | 21 +------------------ .../Properties/AssemblyInfo.cs | 4 ++++ .../project.json | 10 +++------ 5 files changed, 24 insertions(+), 28 deletions(-) create mode 100644 tests/ImageProcessorCore.Benchmarks/Config.cs 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/tests/ImageProcessorCore.Benchmarks/Config.cs b/tests/ImageProcessorCore.Benchmarks/Config.cs new file mode 100644 index 0000000000..10f377bb53 --- /dev/null +++ b/tests/ImageProcessorCore.Benchmarks/Config.cs @@ -0,0 +1,14 @@ +using BenchmarkDotNet.Configs; + +namespace ImageProcessorCore.Benchmarks +{ + public class Config : ManualConfig + { + public Config() + { + // uncomment if you want to use any of the diagnoser + //Add(new BenchmarkDotNet.Diagnostics.MemoryDiagnoser()); + //Add(new BenchmarkDotNet.Diagnostics.InliningDiagnoser()); + } + } +} \ No newline at end of file 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/project.json b/tests/ImageProcessorCore.Benchmarks/project.json index ebeb7dc648..6d70e44cb5 100644 --- a/tests/ImageProcessorCore.Benchmarks/project.json +++ b/tests/ImageProcessorCore.Benchmarks/project.json @@ -13,8 +13,8 @@ "emitEntryPoint": true }, "dependencies": { - "BenchmarkDotNet": "0.9.7", - "BenchmarkDotNet.Diagnostics.Windows": "0.9.7", + "BenchmarkDotNet": "0.9.8-develop", + "BenchmarkDotNet.Diagnostics.Windows": "0.9.8-develop", "ImageProcessorCore": "1.0.0-*" }, "commands": { @@ -24,12 +24,8 @@ "net451": { "dependencies": { }, - "imports": [ - "dnx451" - ], "frameworkAssemblies": { - "System.Drawing": "", - "System.Runtime": "" + "System.Drawing": "" } } } From 954dea7995e01434760af9f0684d3dda48206308 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 16 Jul 2016 11:31:20 +1000 Subject: [PATCH 30/91] Add Color definitions. Former-commit-id: fbc52195022a0ffe0ba9310ce0dd86294a7e9eb3 Former-commit-id: de0582afc92bffb9d338ffc316f2bebce342b2f6 Former-commit-id: 47c00e0a74886ebeab0d9d03c24dd633088c8259 --- src/ImageProcessorCore/PackedVector/Color.cs | 15 +- .../PackedVector/ColorDefinitions.cs | 729 ++++++++++++++++++ 2 files changed, 739 insertions(+), 5 deletions(-) create mode 100644 src/ImageProcessorCore/PackedVector/ColorDefinitions.cs diff --git a/src/ImageProcessorCore/PackedVector/Color.cs b/src/ImageProcessorCore/PackedVector/Color.cs index 5f1ace7b0c..cc6664c0d0 100644 --- a/src/ImageProcessorCore/PackedVector/Color.cs +++ b/src/ImageProcessorCore/PackedVector/Color.cs @@ -12,9 +12,14 @@ namespace ImageProcessorCore /// /// 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. + /// [StructLayout(LayoutKind.Explicit)] - public struct Color : IPackedVector, IEquatable + public partial struct Color : IPackedVector, IEquatable { /// /// Gets or sets the blue component. @@ -56,7 +61,7 @@ namespace ImageProcessorCore public Color(float b, float g, float r, float a) : this() { - Vector4 clamped = Vector4.Clamp(new Vector4(b, g, r, a), Vector4.Zero, Vector4.One) * 255f; + Vector4 clamped = Vector4.Clamp(new Vector4(b, g, r, a), Vector4.Zero, Vector4.One) * 255F; this.B = (byte)Math.Round(clamped.X); this.G = (byte)Math.Round(clamped.Y); this.R = (byte)Math.Round(clamped.Z); @@ -88,7 +93,7 @@ namespace ImageProcessorCore public Color(Vector4 vector) : this() { - Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; + Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255F; this.B = (byte)Math.Round(clamped.X); this.G = (byte)Math.Round(clamped.Y); this.R = (byte)Math.Round(clamped.Z); @@ -134,7 +139,7 @@ namespace ImageProcessorCore /// public void PackVector(Vector4 vector) { - Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; + Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255F; this.B = (byte)Math.Round(clamped.X); this.G = (byte)Math.Round(clamped.Y); this.R = (byte)Math.Round(clamped.Z); @@ -153,7 +158,7 @@ namespace ImageProcessorCore /// public Vector4 ToVector4() { - return new Vector4(this.B, this.G, this.R, this.A) / 255f; + return new Vector4(this.B, this.G, this.R, this.A) / 255F; } /// diff --git a/src/ImageProcessorCore/PackedVector/ColorDefinitions.cs b/src/ImageProcessorCore/PackedVector/ColorDefinitions.cs new file mode 100644 index 0000000000..1c853949f6 --- /dev/null +++ b/src/ImageProcessorCore/PackedVector/ColorDefinitions.cs @@ -0,0 +1,729 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// 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 + { + /// + /// Represents a matching the W3C definition that has an hex value of #F0F8FF. + /// + public static readonly Color AliceBlue = new Color(240, 248, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAEBD7. + /// + public static readonly Color AntiqueWhite = new Color(250, 235, 215, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FFFF. + /// + public static readonly Color Aqua = new Color(0, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7FFFD4. + /// + public static readonly Color Aquamarine = new Color(127, 255, 212, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0FFFF. + /// + public static readonly Color Azure = new Color(240, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5F5DC. + /// + public static readonly Color Beige = new Color(245, 245, 220, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4C4. + /// + public static readonly Color Bisque = new Color(255, 228, 196, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #000000. + /// + public static readonly Color Black = new Color(0, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFEBCD. + /// + public static readonly Color BlanchedAlmond = new Color(255, 235, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #0000FF. + /// + public static readonly Color Blue = new Color(0, 0, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8A2BE2. + /// + public static readonly Color BlueViolet = new Color(138, 43, 226, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #A52A2A. + /// + public static readonly Color Brown = new Color(165, 42, 42, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DEB887. + /// + public static readonly Color BurlyWood = new Color(222, 184, 135, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #5F9EA0. + /// + public static readonly Color CadetBlue = new Color(95, 158, 160, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7FFF00. + /// + public static readonly Color Chartreuse = new Color(127, 255, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D2691E. + /// + public static readonly Color Chocolate = new Color(210, 105, 30, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF7F50. + /// + public static readonly Color Coral = new Color(255, 127, 80, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #6495ED. + /// + public static readonly Color CornflowerBlue = new Color(100, 149, 237, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF8DC. + /// + public static readonly Color Cornsilk = new Color(255, 248, 220, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DC143C. + /// + public static readonly Color Crimson = new Color(220, 20, 60, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FFFF. + /// + public static readonly Color Cyan = new Color(0, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00008B. + /// + public static readonly Color DarkBlue = new Color(0, 0, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #008B8B. + /// + public static readonly Color DarkCyan = new Color(0, 139, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B8860B. + /// + public static readonly Color DarkGoldenrod = new Color(184, 134, 11, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #A9A9A9. + /// + public static readonly Color DarkGray = new Color(169, 169, 169, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #006400. + /// + public static readonly Color DarkGreen = new Color(0, 100, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #BDB76B. + /// + public static readonly Color DarkKhaki = new Color(189, 183, 107, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8B008B. + /// + public static readonly Color DarkMagenta = new Color(139, 0, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #556B2F. + /// + public static readonly Color DarkOliveGreen = new Color(85, 107, 47, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF8C00. + /// + public static readonly Color DarkOrange = new Color(255, 140, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9932CC. + /// + public static readonly Color DarkOrchid = new Color(153, 50, 204, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8B0000. + /// + public static readonly Color DarkRed = new Color(139, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #E9967A. + /// + public static readonly Color DarkSalmon = new Color(233, 150, 122, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8FBC8B. + /// + public static readonly Color DarkSeaGreen = new Color(143, 188, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #483D8B. + /// + public static readonly Color DarkSlateBlue = new Color(72, 61, 139, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #2F4F4F. + /// + public static readonly Color DarkSlateGray = new Color(47, 79, 79, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00CED1. + /// + public static readonly Color DarkTurquoise = new Color(0, 206, 209, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9400D3. + /// + public static readonly Color DarkViolet = new Color(148, 0, 211, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF1493. + /// + public static readonly Color DeepPink = new Color(255, 20, 147, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00BFFF. + /// + public static readonly Color DeepSkyBlue = new Color(0, 191, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #696969. + /// + public static readonly Color DimGray = new Color(105, 105, 105, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #1E90FF. + /// + public static readonly Color DodgerBlue = new Color(30, 144, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B22222. + /// + public static readonly Color Firebrick = new Color(178, 34, 34, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFAF0. + /// + public static readonly Color FloralWhite = new Color(255, 250, 240, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #228B22. + /// + public static readonly Color ForestGreen = new Color(34, 139, 34, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF00FF. + /// + public static readonly Color Fuchsia = new Color(255, 0, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DCDCDC. + /// + public static readonly Color Gainsboro = new Color(220, 220, 220, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F8F8FF. + /// + public static readonly Color GhostWhite = new Color(248, 248, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFD700. + /// + public static readonly Color Gold = new Color(255, 215, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DAA520. + /// + public static readonly Color Goldenrod = new Color(218, 165, 32, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #808080. + /// + public static readonly Color Gray = new Color(128, 128, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #008000. + /// + public static readonly Color Green = new Color(0, 128, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #ADFF2F. + /// + public static readonly Color GreenYellow = new Color(173, 255, 47, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0FFF0. + /// + public static readonly Color Honeydew = new Color(240, 255, 240, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF69B4. + /// + public static readonly Color HotPink = new Color(255, 105, 180, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #CD5C5C. + /// + public static readonly Color IndianRed = new Color(205, 92, 92, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #4B0082. + /// + public static readonly Color Indigo = new Color(75, 0, 130, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFF0. + /// + public static readonly Color Ivory = new Color(255, 255, 240, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F0E68C. + /// + public static readonly Color Khaki = new Color(240, 230, 140, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #E6E6FA. + /// + public static readonly Color Lavender = new Color(230, 230, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF0F5. + /// + public static readonly Color LavenderBlush = new Color(255, 240, 245, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7CFC00. + /// + public static readonly Color LawnGreen = new Color(124, 252, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFACD. + /// + public static readonly Color LemonChiffon = new Color(255, 250, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #ADD8E6. + /// + public static readonly Color LightBlue = new Color(173, 216, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F08080. + /// + public static readonly Color LightCoral = new Color(240, 128, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #E0FFFF. + /// + public static readonly Color LightCyan = new Color(224, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAFAD2. + /// + public static readonly Color LightGoldenrodYellow = new Color(250, 250, 210, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D3D3D3. + /// + public static readonly Color LightGray = new Color(211, 211, 211, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #90EE90. + /// + public static readonly Color LightGreen = new Color(144, 238, 144, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFB6C1. + /// + public static readonly Color LightPink = new Color(255, 182, 193, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFA07A. + /// + public static readonly Color LightSalmon = new Color(255, 160, 122, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #20B2AA. + /// + public static readonly Color LightSeaGreen = new Color(32, 178, 170, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #87CEFA. + /// + public static readonly Color LightSkyBlue = new Color(135, 206, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #778899. + /// + public static readonly Color LightSlateGray = new Color(119, 136, 153, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B0C4DE. + /// + public static readonly Color LightSteelBlue = new Color(176, 196, 222, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFE0. + /// + public static readonly Color LightYellow = new Color(255, 255, 224, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FF00. + /// + public static readonly Color Lime = new Color(0, 255, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #32CD32. + /// + public static readonly Color LimeGreen = new Color(50, 205, 50, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FAF0E6. + /// + public static readonly Color Linen = new Color(250, 240, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF00FF. + /// + public static readonly Color Magenta = new Color(255, 0, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #800000. + /// + public static readonly Color Maroon = new Color(128, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #66CDAA. + /// + public static readonly Color MediumAquamarine = new Color(102, 205, 170, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #0000CD. + /// + public static readonly Color MediumBlue = new Color(0, 0, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #BA55D3. + /// + public static readonly Color MediumOrchid = new Color(186, 85, 211, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9370DB. + /// + public static readonly Color MediumPurple = new Color(147, 112, 219, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #3CB371. + /// + public static readonly Color MediumSeaGreen = new Color(60, 179, 113, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #7B68EE. + /// + public static readonly Color MediumSlateBlue = new Color(123, 104, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FA9A. + /// + public static readonly Color MediumSpringGreen = new Color(0, 250, 154, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #48D1CC. + /// + public static readonly Color MediumTurquoise = new Color(72, 209, 204, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #C71585. + /// + public static readonly Color MediumVioletRed = new Color(199, 21, 133, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #191970. + /// + public static readonly Color MidnightBlue = new Color(25, 25, 112, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5FFFA. + /// + public static readonly Color MintCream = new Color(245, 255, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4E1. + /// + public static readonly Color MistyRose = new Color(255, 228, 225, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFE4B5. + /// + public static readonly Color Moccasin = new Color(255, 228, 181, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFDEAD. + /// + public static readonly Color NavajoWhite = new Color(255, 222, 173, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #000080. + /// + public static readonly Color Navy = new Color(0, 0, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FDF5E6. + /// + public static readonly Color OldLace = new Color(253, 245, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #808000. + /// + public static readonly Color Olive = new Color(128, 128, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #6B8E23. + /// + public static readonly Color OliveDrab = new Color(107, 142, 35, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFA500. + /// + public static readonly Color Orange = new Color(255, 165, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF4500. + /// + public static readonly Color OrangeRed = new Color(255, 69, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DA70D6. + /// + public static readonly Color Orchid = new Color(218, 112, 214, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #EEE8AA. + /// + public static readonly Color PaleGoldenrod = new Color(238, 232, 170, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #98FB98. + /// + public static readonly Color PaleGreen = new Color(152, 251, 152, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #AFEEEE. + /// + public static readonly Color PaleTurquoise = new Color(175, 238, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DB7093. + /// + public static readonly Color PaleVioletRed = new Color(219, 112, 147, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFEFD5. + /// + public static readonly Color PapayaWhip = new Color(255, 239, 213, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFDAB9. + /// + public static readonly Color PeachPuff = new Color(255, 218, 185, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #CD853F. + /// + public static readonly Color Peru = new Color(205, 133, 63, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFC0CB. + /// + public static readonly Color Pink = new Color(255, 192, 203, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #DDA0DD. + /// + public static readonly Color Plum = new Color(221, 160, 221, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #B0E0E6. + /// + public static readonly Color PowderBlue = new Color(176, 224, 230, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #800080. + /// + public static readonly Color Purple = new Color(128, 0, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #0. + /// + public static readonly Color RebeccaPurple = new Color(102, 51, 153, 255); + + + /// + /// Represents a matching the W3C definition that has an hex value of #FF0000. + /// + public static readonly Color Red = new Color(255, 0, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #BC8F8F. + /// + public static readonly Color RosyBrown = new Color(188, 143, 143, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #4169E1. + /// + public static readonly Color RoyalBlue = new Color(65, 105, 225, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #8B4513. + /// + public static readonly Color SaddleBrown = new Color(139, 69, 19, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FA8072. + /// + public static readonly Color Salmon = new Color(250, 128, 114, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F4A460. + /// + public static readonly Color SandyBrown = new Color(244, 164, 96, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #2E8B57. + /// + public static readonly Color SeaGreen = new Color(46, 139, 87, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFF5EE. + /// + public static readonly Color SeaShell = new Color(255, 245, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #A0522D. + /// + public static readonly Color Sienna = new Color(160, 82, 45, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #C0C0C0. + /// + public static readonly Color Silver = new Color(192, 192, 192, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #87CEEB. + /// + public static readonly Color SkyBlue = new Color(135, 206, 235, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #6A5ACD. + /// + public static readonly Color SlateBlue = new Color(106, 90, 205, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #708090. + /// + public static readonly Color SlateGray = new Color(112, 128, 144, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFAFA. + /// + public static readonly Color Snow = new Color(255, 250, 250, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #00FF7F. + /// + public static readonly Color SpringGreen = new Color(0, 255, 127, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #4682B4. + /// + public static readonly Color SteelBlue = new Color(70, 130, 180, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D2B48C. + /// + public static readonly Color Tan = new Color(210, 180, 140, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #008080. + /// + public static readonly Color Teal = new Color(0, 128, 128, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #D8BFD8. + /// + public static readonly Color Thistle = new Color(216, 191, 216, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FF6347. + /// + public static readonly Color Tomato = new Color(255, 99, 71, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFFF. + /// + public static readonly Color Transparent = new Color(255, 255, 255, 0); + + /// + /// Represents a matching the W3C definition that has an hex value of #40E0D0. + /// + public static readonly Color Turquoise = new Color(64, 224, 208, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #EE82EE. + /// + public static readonly Color Violet = new Color(238, 130, 238, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5DEB3. + /// + public static readonly Color Wheat = new Color(245, 222, 179, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFFFF. + /// + public static readonly Color White = new Color(255, 255, 255, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #F5F5F5. + /// + public static readonly Color WhiteSmoke = new Color(245, 245, 245, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #FFFF00. + /// + public static readonly Color Yellow = new Color(255, 255, 0, 255); + + /// + /// Represents a matching the W3C definition that has an hex value of #9ACD32. + /// + public static readonly Color YellowGreen = new Color(154, 205, 50, 255); + } +} \ No newline at end of file From c8aeebeb1f9a3897212416c6d064c42493281990 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 16 Jul 2016 12:46:36 +1000 Subject: [PATCH 31/91] Add parallel options Former-commit-id: a81f9393544d2c2fac2a4ebe247dc83cc27a3934 Former-commit-id: 098166601a68b3b8d34f5e8206aea51ed02f82aa Former-commit-id: c5b423b967d17e571d9bc6b1918f7414eddde44e --- src/ImageProcessorCore/Bootstrapper.cs | 6 ++++++ src/ImageProcessorCore/Image/IImageProcessor.cs | 7 +++++++ src/ImageProcessorCore/ImageProcessor.cs | 4 ++++ .../Samplers/Processors/ResizeProcessor.cs | 3 +++ 4 files changed, 20 insertions(+) diff --git a/src/ImageProcessorCore/Bootstrapper.cs b/src/ImageProcessorCore/Bootstrapper.cs index 39e97e1100..0f47526b3b 100644 --- a/src/ImageProcessorCore/Bootstrapper.cs +++ b/src/ImageProcessorCore/Bootstrapper.cs @@ -10,6 +10,7 @@ namespace ImageProcessorCore using System.Collections.ObjectModel; using ImageProcessorCore.Formats; + using System.Threading.Tasks; /// /// Provides initialization code which allows extending the library. @@ -58,6 +59,11 @@ namespace ImageProcessorCore /// public IReadOnlyCollection ImageFormats => new ReadOnlyCollection(this.imageFormats); + /// + /// Gets or sets the global parallel options for processing tasks in parallel. + /// + public ParallelOptions ParallelOptions { get; set; } = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; + /// /// Adds a new to the collection of supported image formats. /// diff --git a/src/ImageProcessorCore/Image/IImageProcessor.cs b/src/ImageProcessorCore/Image/IImageProcessor.cs index 4241b88c75..2c3dd50204 100644 --- a/src/ImageProcessorCore/Image/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 { /// @@ -26,6 +28,11 @@ namespace ImageProcessorCore.Processors /// event ProgressEventHandler OnProgress; + /// + /// Gets or sets the global parallel options for processing tasks in parallel. + /// + ParallelOptions ParallelOptions { get; set; } + /// /// Applies the process to the specified portion of the specified . /// diff --git a/src/ImageProcessorCore/ImageProcessor.cs b/src/ImageProcessorCore/ImageProcessor.cs index c7c0f4e1b7..211bbd5038 100644 --- a/src/ImageProcessorCore/ImageProcessor.cs +++ b/src/ImageProcessorCore/ImageProcessor.cs @@ -7,6 +7,7 @@ namespace ImageProcessorCore.Processors { using System; using System.Threading; + using System.Threading.Tasks; /// /// Allows the application of processors to images. @@ -26,6 +27,9 @@ namespace ImageProcessorCore.Processors /// private int totalRows; + /// + public virtual ParallelOptions ParallelOptions { get; set; } = Bootstrapper.Instance.ParallelOptions; + /// public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) where T : IPackedVector, new() diff --git a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs index a01fd92402..3f78ed0bef 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs @@ -84,6 +84,7 @@ namespace ImageProcessorCore.Processors Parallel.For( startY, endY, + this.ParallelOptions, y => { if (targetY <= y && y < targetBottom) @@ -122,6 +123,7 @@ namespace ImageProcessorCore.Processors Parallel.For( 0, sourceHeight, + this.ParallelOptions, y => { for (int x = startX; x < endX; x++) @@ -160,6 +162,7 @@ namespace ImageProcessorCore.Processors Parallel.For( startY, endY, + this.ParallelOptions, y => { if (y >= 0 && y < height) From eff93ab55899ab594f74b0416041134fa4c19b6c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 16 Jul 2016 13:47:44 +1000 Subject: [PATCH 32/91] Fix Color equality test Former-commit-id: 0c56d5ebf2db246bc725cac0a7dd74b82e48d579 Former-commit-id: 1041547e752303bdac6677d4a482e349784fa8aa Former-commit-id: daef0c97bd5bd5eca5bd3398cb66b719e8f7b6ef --- .../Image/IImageProcessor.cs | 2 +- .../Color/ColorEquality.cs | 38 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/ImageProcessorCore/Image/IImageProcessor.cs b/src/ImageProcessorCore/Image/IImageProcessor.cs index 2c3dd50204..0b07dca046 100644 --- a/src/ImageProcessorCore/Image/IImageProcessor.cs +++ b/src/ImageProcessorCore/Image/IImageProcessor.cs @@ -29,7 +29,7 @@ namespace ImageProcessorCore.Processors event ProgressEventHandler OnProgress; /// - /// Gets or sets the global parallel options for processing tasks in parallel. + /// Gets or sets the parallel options for processing tasks in parallel. /// ParallelOptions ParallelOptions { get; set; } diff --git a/tests/ImageProcessorCore.Benchmarks/Color/ColorEquality.cs b/tests/ImageProcessorCore.Benchmarks/Color/ColorEquality.cs index 1ab1d2c80c..1221989549 100644 --- a/tests/ImageProcessorCore.Benchmarks/Color/ColorEquality.cs +++ b/tests/ImageProcessorCore.Benchmarks/Color/ColorEquality.cs @@ -1,22 +1,22 @@ -//namespace ImageProcessorCore.Benchmarks -//{ -// using BenchmarkDotNet.Attributes; +namespace ImageProcessorCore.Benchmarks +{ + using BenchmarkDotNet.Attributes; -// using CoreColor = ImageProcessorCore.Color; -// using SystemColor = System.Drawing.Color; + using CoreColor = ImageProcessorCore.Color; + using SystemColor = System.Drawing.Color; -// public class ColorEquality -// { -// [Benchmark(Baseline = true, Description = "System.Drawing Color Equals")] -// public bool SystemDrawingColorEqual() -// { -// return SystemColor.FromArgb(128, 128, 128, 128).Equals(SystemColor.FromArgb(128, 128, 128, 128)); -// } + public class ColorEquality + { + [Benchmark(Baseline = true, Description = "System.Drawing Color Equals")] + public bool SystemDrawingColorEqual() + { + return SystemColor.FromArgb(128, 128, 128, 128).Equals(SystemColor.FromArgb(128, 128, 128, 128)); + } -// [Benchmark(Description = "ImageProcessorCore Color Equals")] -// public bool ColorEqual() -// { -// return new CoreColor(.5f, .5f, .5f, .5f).Equals(new CoreColor(.5f, .5f, .5f, .5f)); -// } -// } -//} + [Benchmark(Description = "ImageProcessorCore Color Equals")] + public bool ColorEqual() + { + return new CoreColor(128, 128, 128, 128).Equals(new CoreColor(128, 128, 128, 128)); + } + } +} From d2af8123b4d8138037ca3408bd7cc4b7cdc3de8b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 16 Jul 2016 13:55:45 +1000 Subject: [PATCH 33/91] Fix GETSET benchmark Former-commit-id: 59173a2ea1f93e0b84a7ca026d572a721a166b5f Former-commit-id: 2174f77edb68a8c348b20b34e9e0046f3a9eae69 Former-commit-id: 238d14a0644251f40dc75f8cd10f98e4d5ed94cc --- tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs b/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs index 0af79c78ed..c13b84064a 100644 --- a/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs +++ b/tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs @@ -26,7 +26,7 @@ CoreImage image = new CoreImage(400, 400); using (IPixelAccessor imagePixels = image.Lock()) { - imagePixels[200, 200] = new CoreColor(255, 255, 255, 255); + imagePixels[200, 200] = CoreColor.White; return imagePixels[200, 200]; } } From ef65706d96fe4d15342f427c89cbe8e007a51317 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 16 Jul 2016 20:47:59 +1000 Subject: [PATCH 34/91] Add png encoder Former-commit-id: b222f96370adf0a3f87d3e2d762e270fac829a5a Former-commit-id: eecf02f023c9d43ff3e54dd4fcfbfbfdde22f543 Former-commit-id: 03bf7ed3356ee33b96e8dcff9b7a53b26e8f919f --- src/ImageProcessorCore/Bootstrapper.cs | 2 +- .../Formats/Bmp/BmpDecoder.cs | 6 +- .../Formats/Png/GrayscaleReader.cs | 76 ++ .../Formats/Png/IColorReader.cs | 29 + .../Formats/Png/PaletteIndexReader.cs | 95 +++ .../Formats/Png/PngChunk.cs | 39 + .../Formats/Png/PngChunkTypes.cs | 62 ++ .../Formats/Png/PngColorTypeInformation.cs | 61 ++ .../Formats/Png/PngDecoder.cs | 89 ++ .../Formats/Png/PngDecoderCore.cs | 543 ++++++++++++ .../Formats/Png/PngEncoder.cs | 87 ++ .../Formats/Png/PngEncoderCore.cs | 504 +++++++++++ .../Formats/Png/PngFormat.cs | 19 + .../Formats/Png/PngHeader.cs | 62 ++ src/ImageProcessorCore/Formats/Png/README.md | 6 + .../Formats/Png/TrueColorReader.cs | 81 ++ .../Formats/Png/Zlib/Adler32.cs | 174 ++++ .../Formats/Png/Zlib/Crc32.cs | 180 ++++ .../Formats/Png/Zlib/IChecksum.cs | 60 ++ .../Formats/Png/Zlib/README.md | 2 + .../Formats/Png/Zlib/ZlibDeflateStream.cs | 210 +++++ .../Formats/Png/Zlib/ZlibInflateStream.cs | 205 +++++ src/ImageProcessorCore/Image.cs | 8 + .../Quantizers/IQuantizer.cs | 32 + .../Quantizers/QuantizedImage.cs | 102 +++ src/ImageProcessorCore/Quantizers/Wu/Box.cs | 58 ++ .../Quantizers/Wu/WuQuantizer.cs | 800 ++++++++++++++++++ .../ImageProcessorCore.Tests/FileTestBase.cs | 2 +- 28 files changed, 3589 insertions(+), 5 deletions(-) create mode 100644 src/ImageProcessorCore/Formats/Png/GrayscaleReader.cs create mode 100644 src/ImageProcessorCore/Formats/Png/IColorReader.cs create mode 100644 src/ImageProcessorCore/Formats/Png/PaletteIndexReader.cs create mode 100644 src/ImageProcessorCore/Formats/Png/PngChunk.cs create mode 100644 src/ImageProcessorCore/Formats/Png/PngChunkTypes.cs create mode 100644 src/ImageProcessorCore/Formats/Png/PngColorTypeInformation.cs create mode 100644 src/ImageProcessorCore/Formats/Png/PngDecoder.cs create mode 100644 src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs create mode 100644 src/ImageProcessorCore/Formats/Png/PngEncoder.cs create mode 100644 src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs create mode 100644 src/ImageProcessorCore/Formats/Png/PngFormat.cs create mode 100644 src/ImageProcessorCore/Formats/Png/PngHeader.cs create mode 100644 src/ImageProcessorCore/Formats/Png/README.md create mode 100644 src/ImageProcessorCore/Formats/Png/TrueColorReader.cs create mode 100644 src/ImageProcessorCore/Formats/Png/Zlib/Adler32.cs create mode 100644 src/ImageProcessorCore/Formats/Png/Zlib/Crc32.cs create mode 100644 src/ImageProcessorCore/Formats/Png/Zlib/IChecksum.cs create mode 100644 src/ImageProcessorCore/Formats/Png/Zlib/README.md create mode 100644 src/ImageProcessorCore/Formats/Png/Zlib/ZlibDeflateStream.cs create mode 100644 src/ImageProcessorCore/Formats/Png/Zlib/ZlibInflateStream.cs create mode 100644 src/ImageProcessorCore/Quantizers/IQuantizer.cs create mode 100644 src/ImageProcessorCore/Quantizers/QuantizedImage.cs create mode 100644 src/ImageProcessorCore/Quantizers/Wu/Box.cs create mode 100644 src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs diff --git a/src/ImageProcessorCore/Bootstrapper.cs b/src/ImageProcessorCore/Bootstrapper.cs index 0f47526b3b..a1e91e386f 100644 --- a/src/ImageProcessorCore/Bootstrapper.cs +++ b/src/ImageProcessorCore/Bootstrapper.cs @@ -39,7 +39,7 @@ namespace ImageProcessorCore { new BmpFormat(), //new JpegFormat(), - //new PngFormat(), + new PngFormat(), //new GifFormat() }; diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs index 916bddce7b..48ee205f95 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs @@ -70,11 +70,11 @@ 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, new() where TP : struct { diff --git a/src/ImageProcessorCore/Formats/Png/GrayscaleReader.cs b/src/ImageProcessorCore/Formats/Png/GrayscaleReader.cs new file mode 100644 index 0000000000..f8884ae439 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Png/GrayscaleReader.cs @@ -0,0 +1,76 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Color reader for reading grayscale colors from a png file. + /// + internal sealed class GrayscaleReader : IColorReader + { + /// + /// Whether t also read the alpha channel. + /// + private readonly bool useAlpha; + + /// + /// The current row. + /// + private int row; + + /// + /// Initializes a new instance of the class. + /// + /// + /// If set to true the color reader will also read the + /// alpha channel from the scanline. + /// + public GrayscaleReader(bool useAlpha) + { + this.useAlpha = useAlpha; + } + + /// + public void ReadScanline(byte[] scanline, T[] pixels, PngHeader header) + where T : IPackedVector, new() + where TP : struct + { + int offset; + + byte[] newScanline = scanline.ToArrayByBitsLength(header.BitDepth); + + // 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; + + byte rgb = newScanline[x * 2]; + byte a = newScanline[(x * 2) + 1]; + + 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; + byte rgb = newScanline[x]; + + T color = default(T); + color.PackBytes(rgb, rgb, rgb, 255); + + pixels[offset] = color; + } + } + + this.row++; + } + } +} diff --git a/src/ImageProcessorCore/Formats/Png/IColorReader.cs b/src/ImageProcessorCore/Formats/Png/IColorReader.cs new file mode 100644 index 0000000000..c28dd3c055 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Png/IColorReader.cs @@ -0,0 +1,29 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Encapsulates methods for color readers, which are responsible for reading + /// different color formats from a png file. + /// + public interface IColorReader + { + /// + /// Reads the specified scanline. + /// + /// The pixel format. + /// The packed format. long, float. + /// The scanline. + /// 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, T[] pixels, PngHeader header) + where T : IPackedVector, new() + where TP : struct; + } +} diff --git a/src/ImageProcessorCore/Formats/Png/PaletteIndexReader.cs b/src/ImageProcessorCore/Formats/Png/PaletteIndexReader.cs new file mode 100644 index 0000000000..b7e1f2cfb2 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Png/PaletteIndexReader.cs @@ -0,0 +1,95 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// A color reader for reading palette indices from the png file. + /// + internal sealed class PaletteIndexReader : IColorReader + { + /// + /// The palette. + /// + private readonly byte[] palette; + + /// + /// The alpha palette. + /// + private readonly byte[] paletteAlpha; + + /// + /// The current row. + /// + private int row; + + /// + /// Initializes a new instance of the class. + /// + /// The palette as simple byte array. It will contains 3 values for each + /// color, which represents the red-, the green- and the blue channel. + /// The alpha palette. Can be null, if the image does not have an + /// alpha channel and can contain less entries than the number of colors in the palette. + public PaletteIndexReader(byte[] palette, byte[] paletteAlpha) + { + this.palette = palette; + this.paletteAlpha = paletteAlpha; + } + + /// + public void ReadScanline(byte[] scanline, T[] pixels, PngHeader header) + where T : IPackedVector, new() + where TP : struct + { + byte[] newScanline = scanline.ToArrayByBitsLength(header.BitDepth); + int offset, index; + + if (this.paletteAlpha != null && this.paletteAlpha.Length > 0) + { + // If the alpha palette is not null and does one or + // more entries, this means, that the image contains and alpha + // channel and we should try to read it. + for (int i = 0; i < header.Width; i++) + { + index = newScanline[i]; + + offset = (this.row * header.Width) + i; + int pixelOffset = index * 3; + + 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 + { + for (int i = 0; i < header.Width; i++) + { + index = newScanline[i]; + + offset = (this.row * header.Width) + i; + int pixelOffset = index * 3; + + 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; + } + } + + this.row++; + } + } +} diff --git a/src/ImageProcessorCore/Formats/Png/PngChunk.cs b/src/ImageProcessorCore/Formats/Png/PngChunk.cs new file mode 100644 index 0000000000..31ea703a65 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Png/PngChunk.cs @@ -0,0 +1,39 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Stores header information about a chunk. + /// + internal sealed class PngChunk + { + /// + /// Gets or sets the length. + /// An unsigned integer giving the number of bytes in the chunk's + /// data field. The length counts only the data field, not itself, + /// the chunk type code, or the CRC. Zero is a valid length + /// + public int Length { get; set; } + + /// + /// Gets or sets the chunk type as string with 4 chars. + /// + public string Type { get; set; } + + /// + /// Gets or sets the data bytes appropriate to the chunk type, if any. + /// This field can be of zero length. + /// + public byte[] Data { get; set; } + + /// + /// Gets or sets a CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk, + /// including the chunk type code and chunk data fields, but not including the length field. + /// The CRC is always present, even for chunks containing no data + /// + public uint Crc { get; set; } + } +} diff --git a/src/ImageProcessorCore/Formats/Png/PngChunkTypes.cs b/src/ImageProcessorCore/Formats/Png/PngChunkTypes.cs new file mode 100644 index 0000000000..5c35b3d4d2 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Png/PngChunkTypes.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Contains a list of possible chunk type identifiers. + /// + internal static class PngChunkTypes + { + /// + /// The first chunk in a png file. Can only exists once. Contains + /// common information like the width and the height of the image or + /// the used compression method. + /// + public const string Header = "IHDR"; + + /// + /// The PLTE chunk contains from 1 to 256 palette entries, each a three byte + /// series in the RGB format. + /// + public const string Palette = "PLTE"; + + /// + /// The IDAT chunk contains the actual image data. The image can contains more + /// than one chunk of this type. All chunks together are the whole image. + /// + public const string Data = "IDAT"; + + /// + /// This chunk must appear last. It marks the end of the PNG data stream. + /// The chunk's data field is empty. + /// + public const string End = "IEND"; + + /// + /// This chunk specifies that the image uses simple transparency: + /// either alpha values associated with palette entries (for indexed-color images) + /// or a single transparent color (for grayscale and true color images). + /// + public const string PaletteAlpha = "tRNS"; + + /// + /// Textual information that the encoder wishes to record with the image can be stored in + /// tEXt chunks. Each tEXt chunk contains a keyword and a text string. + /// + public const string Text = "tEXt"; + + /// + /// This chunk specifies the relationship between the image samples and the desired + /// display output intensity. + /// + public const string Gamma = "gAMA"; + + /// + /// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. + /// + public const string Physical = "pHYs"; + } +} diff --git a/src/ImageProcessorCore/Formats/Png/PngColorTypeInformation.cs b/src/ImageProcessorCore/Formats/Png/PngColorTypeInformation.cs new file mode 100644 index 0000000000..9909cf47cc --- /dev/null +++ b/src/ImageProcessorCore/Formats/Png/PngColorTypeInformation.cs @@ -0,0 +1,61 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + + /// + /// Contains information that are required when loading a png with a specific color type. + /// + internal sealed class PngColorTypeInformation + { + /// + /// Initializes a new instance of the class with + /// the scanline factory, the function to create the color reader and the supported bit depths. + /// + /// The scanline factor. + /// The supported bit depths. + /// The factory to create the color reader. + public PngColorTypeInformation(int scanlineFactor, int[] supportedBitDepths, Func scanlineReaderFactory) + { + this.ChannelsPerColor = scanlineFactor; + this.ScanlineReaderFactory = scanlineReaderFactory; + this.SupportedBitDepths = supportedBitDepths; + } + + /// + /// Gets an array with the bit depths that are supported for the color type + /// where this object is created for. + /// + /// The supported bit depths that can be used in combination with this color type. + public int[] SupportedBitDepths { get; private set; } + + /// + /// Gets a function that is used the create the color reader for the color type where + /// this object is created for. + /// + /// The factory function to create the color type. + public Func ScanlineReaderFactory { get; private set; } + + /// + /// Gets a factor that is used when iterating through the scan lines. + /// + /// The scanline factor. + public int ChannelsPerColor { get; private set; } + + /// + /// Creates the color reader for the color type where this object is create for. + /// + /// The palette of the image. Can be null when no palette is used. + /// The alpha palette of the image. Can be null when + /// no palette is used for the image or when the image has no alpha. + /// The color reader for the image. + public IColorReader CreateColorReader(byte[] palette, byte[] paletteAlpha) + { + return this.ScanlineReaderFactory(palette, paletteAlpha); + } + } +} diff --git a/src/ImageProcessorCore/Formats/Png/PngDecoder.cs b/src/ImageProcessorCore/Formats/Png/PngDecoder.cs new file mode 100644 index 0000000000..d77e46e1e8 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Png/PngDecoder.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + + /// + /// Encoder for generating an image out of a png encoded stream. + /// + /// + /// At the moment the following features are supported: + /// + /// Filters: all filters are supported. + /// + /// + /// Pixel 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). + /// Palette Index with alpha (8 bit). + /// Palette Index without alpha (8 bit). + /// + /// + /// + public class PngDecoder : IImageDecoder + { + /// + /// Gets the size of the header for this image type. + /// + /// The size of the header. + public int HeaderSize => 8; + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file extension. + /// + /// True if the decoder supports the file extension; otherwise, false. + /// + public bool IsSupportedFileExtension(string extension) + { + Guard.NotNullOrEmpty(extension, "extension"); + + extension = extension.StartsWith(".") ? extension.Substring(1) : extension; + + return extension.Equals("PNG", StringComparison.OrdinalIgnoreCase); + } + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file header. + /// + /// True if the decoder supports the file header; otherwise, false. + /// + public bool IsSupportedFileFormat(byte[] header) + { + return header.Length >= 8 && + header[0] == 0x89 && + header[1] == 0x50 && // P + header[2] == 0x4E && // N + header[3] == 0x47 && // G + header[4] == 0x0D && // CR + header[5] == 0x0A && // LF + header[6] == 0x1A && // EOF + header[7] == 0x0A; // LF + } + + /// + /// Decodes the image from the specified stream to the . + /// + /// The to decode to. + /// The containing image data. + public void Decode(Image image, Stream stream) + where T : IPackedVector, new() + where TP : struct + { + new PngDecoderCore().Decode(image, stream); + } + } +} diff --git a/src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs b/src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs new file mode 100644 index 0000000000..3cf7fabf48 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs @@ -0,0 +1,543 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + + /// + /// Performs the png decoding operation. + /// + internal class PngDecoderCore + { + /// + /// The dictionary of available color types. + /// + private static readonly Dictionary ColorTypes + = new Dictionary(); + + /// + /// The image to decode. + /// + //private IImage currentImage; + + /// + /// The stream to decode from. + /// + private Stream currentStream; + + /// + /// The png header. + /// + private PngHeader header; + + /// + /// Initializes static members of the class. + /// + static PngDecoderCore() + { + ColorTypes.Add( + 0, + new PngColorTypeInformation(1, new[] { 1, 2, 4, 8 }, (p, a) => new GrayscaleReader(false))); + + ColorTypes.Add( + 2, + new PngColorTypeInformation(3, new[] { 8 }, (p, a) => new TrueColorReader(false))); + + ColorTypes.Add( + 3, + new PngColorTypeInformation(1, new[] { 1, 2, 4, 8 }, (p, a) => new PaletteIndexReader(p, a))); + + ColorTypes.Add( + 4, + new PngColorTypeInformation(2, new[] { 8 }, (p, a) => new GrayscaleReader(true))); + + ColorTypes.Add(6, + new PngColorTypeInformation(4, new[] { 8 }, (p, a) => new TrueColorReader(true))); + } + + /// + /// Decodes the stream to the image. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image to decode to. + /// The stream containing image data. + /// + /// Thrown if the stream does not contain and end chunk. + /// + /// + /// Thrown if the image is larger than the maximum allowable size. + /// + public void Decode(Image image, Stream stream) + where T : IPackedVector, new() + where TP : struct + { + Image currentImage = image; + this.currentStream = stream; + this.currentStream.Seek(8, SeekOrigin.Current); + + bool isEndChunkReached = false; + + byte[] palette = null; + byte[] paletteAlpha = null; + + using (MemoryStream dataStream = new MemoryStream()) + { + PngChunk currentChunk; + while ((currentChunk = this.ReadChunk()) != null) + { + if (isEndChunkReached) + { + throw new ImageFormatException("Image does not end with end chunk."); + } + + if (currentChunk.Type == PngChunkTypes.Header) + { + this.ReadHeaderChunk(currentChunk.Data); + this.ValidateHeader(); + } + else if (currentChunk.Type == PngChunkTypes.Physical) + { + this.ReadPhysicalChunk(currentImage, currentChunk.Data); + } + else if (currentChunk.Type == PngChunkTypes.Data) + { + dataStream.Write(currentChunk.Data, 0, currentChunk.Data.Length); + } + else if (currentChunk.Type == PngChunkTypes.Palette) + { + palette = currentChunk.Data; + } + else if (currentChunk.Type == PngChunkTypes.PaletteAlpha) + { + paletteAlpha = currentChunk.Data; + } + else if (currentChunk.Type == PngChunkTypes.Text) + { + this.ReadTextChunk(currentImage, currentChunk.Data); + } + else if (currentChunk.Type == PngChunkTypes.End) + { + isEndChunkReached = true; + } + } + + 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 '{image.MaxWidth}x{image.MaxHeight}'"); + } + + T[] pixels = new T[this.header.Width * this.header.Height]; + + PngColorTypeInformation colorTypeInformation = ColorTypes[this.header.ColorType]; + + if (colorTypeInformation != null) + { + IColorReader colorReader = colorTypeInformation.CreateColorReader(palette, paletteAlpha); + + this.ReadScanlines(dataStream, pixels, colorReader, colorTypeInformation); + } + + image.SetPixels(this.header.Width, this.header.Height, pixels); + } + } + + /// + /// Computes a simple linear function of the three neighboring pixels (left, above, upper left), then chooses + /// as predictor the neighboring pixel closest to the computed value. + /// + /// The left neighbour pixel. + /// The above neighbour pixel. + /// The upper left neighbour pixel. + /// + /// The . + /// + private static byte PaethPredicator(byte left, byte above, byte upperLeft) + { + byte predicator; + + int p = left + above - upperLeft; + int pa = Math.Abs(p - left); + int pb = Math.Abs(p - above); + int pc = Math.Abs(p - upperLeft); + + if (pa <= pb && pa <= pc) + { + predicator = left; + } + else if (pb <= pc) + { + predicator = above; + } + else + { + predicator = upperLeft; + } + + return predicator; + } + + /// + /// 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(Image image, byte[] data) + where T : IPackedVector, new() + where TP : struct + { + Array.Reverse(data, 0, 4); + Array.Reverse(data, 4, 4); + + // 39.3700787 = inches in a meter. + image.HorizontalResolution = BitConverter.ToInt32(data, 0) / 39.3700787d; + image.VerticalResolution = BitConverter.ToInt32(data, 4) / 39.3700787d; + } + + /// + /// Calculates the scanline length. + /// + /// The color type information. + /// The representing the length. + private int CalculateScanlineLength(PngColorTypeInformation colorTypeInformation) + { + int scanlineLength = this.header.Width * this.header.BitDepth * colorTypeInformation.ChannelsPerColor; + + int amount = scanlineLength % 8; + if (amount != 0) + { + scanlineLength += 8 - amount; + } + + return scanlineLength / 8; + } + + /// + /// Calculates a scanline step. + /// + /// The color type information. + /// The representing the length of each step. + private int CalculateScanlineStep(PngColorTypeInformation colorTypeInformation) + { + int scanlineStep = 1; + + if (this.header.BitDepth >= 8) + { + scanlineStep = (colorTypeInformation.ChannelsPerColor * this.header.BitDepth) / 8; + } + + return scanlineStep; + } + + /// + /// Reads the scanlines within the image. + /// + /// The containing data. + /// + /// The containing pixel data. + /// The color reader. + /// The color type information. + private void ReadScanlines(MemoryStream dataStream, T[] pixels, IColorReader colorReader, PngColorTypeInformation colorTypeInformation) + where T : IPackedVector, new() + where TP : struct + { + dataStream.Position = 0; + + int scanlineLength = this.CalculateScanlineLength(colorTypeInformation); + int scanlineStep = this.CalculateScanlineStep(colorTypeInformation); + + byte[] lastScanline = new byte[scanlineLength]; + byte[] currentScanline = new byte[scanlineLength]; + int filter = 0, column = -1; + + using (ZlibInflateStream compressedStream = new ZlibInflateStream(dataStream)) + { + int readByte; + while ((readByte = compressedStream.ReadByte()) >= 0) + { + if (column == -1) + { + filter = readByte; + + column++; + } + else + { + currentScanline[column] = (byte)readByte; + + byte a; + byte b; + byte c; + + if (column >= scanlineStep) + { + a = currentScanline[column - scanlineStep]; + c = lastScanline[column - scanlineStep]; + } + else + { + a = 0; + c = 0; + } + + b = lastScanline[column]; + + if (filter == 1) + { + currentScanline[column] = (byte)(currentScanline[column] + a); + } + else if (filter == 2) + { + currentScanline[column] = (byte)(currentScanline[column] + b); + } + else if (filter == 3) + { + currentScanline[column] = (byte)(currentScanline[column] + (byte)((a + b) / 2)); + } + else if (filter == 4) + { + currentScanline[column] = (byte)(currentScanline[column] + PaethPredicator(a, b, c)); + } + + column++; + + if (column == scanlineLength) + { + colorReader.ReadScanline(currentScanline, pixels, this.header); + column = -1; + + this.Swap(ref currentScanline, ref lastScanline); + } + } + } + } + } + + /// + /// 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(Image image, byte[] data) + where T : IPackedVector, new() + where TP : struct + { + int zeroIndex = 0; + + for (int i = 0; i < data.Length; i++) + { + if (data[i] == 0) + { + zeroIndex = i; + break; + } + } + + string name = Encoding.Unicode.GetString(data, 0, zeroIndex); + string value = Encoding.Unicode.GetString(data, zeroIndex + 1, data.Length - zeroIndex - 1); + + image.Properties.Add(new ImageProperty(name, value)); + } + + /// + /// Reads a header chunk from the data. + /// + /// The containing data. + private void ReadHeaderChunk(byte[] data) + { + this.header = new PngHeader(); + + Array.Reverse(data, 0, 4); + Array.Reverse(data, 4, 4); + + this.header.Width = BitConverter.ToInt32(data, 0); + this.header.Height = BitConverter.ToInt32(data, 4); + + this.header.BitDepth = data[8]; + this.header.ColorType = data[9]; + this.header.FilterMethod = data[11]; + this.header.InterlaceMethod = data[12]; + this.header.CompressionMethod = data[10]; + } + + /// + /// Validates the png header. + /// + /// + /// Thrown if the image does pass validation. + /// + private void ValidateHeader() + { + if (!ColorTypes.ContainsKey(this.header.ColorType)) + { + throw new ImageFormatException("Color type is not supported or not valid."); + } + + if (!ColorTypes[this.header.ColorType].SupportedBitDepths.Contains(this.header.BitDepth)) + { + throw new ImageFormatException("Bit depth is not supported or not valid."); + } + + if (this.header.FilterMethod != 0) + { + throw new ImageFormatException("The png specification only defines 0 as filter method."); + } + + if (this.header.InterlaceMethod != 0) + { + throw new ImageFormatException("Interlacing is not supported."); + } + } + + /// + /// Reads a chunk from the stream. + /// + /// + /// The . + /// + private PngChunk ReadChunk() + { + PngChunk chunk = new PngChunk(); + + if (this.ReadChunkLength(chunk) == 0) + { + return null; + } + + byte[] typeBuffer = this.ReadChunkType(chunk); + + this.ReadChunkData(chunk); + this.ReadChunkCrc(chunk, typeBuffer); + + return chunk; + } + + /// + /// Reads the cycle redundancy chunk from the data. + /// + /// The chunk. + /// The type buffer. + /// + /// Thrown if the input stream is not valid or corrupt. + /// + private void ReadChunkCrc(PngChunk chunk, byte[] typeBuffer) + { + byte[] crcBuffer = new byte[4]; + + int numBytes = this.currentStream.Read(crcBuffer, 0, 4); + if (numBytes >= 1 && numBytes <= 3) + { + throw new ImageFormatException("Image stream is not valid!"); + } + + Array.Reverse(crcBuffer); + + chunk.Crc = BitConverter.ToUInt32(crcBuffer, 0); + + Crc32 crc = new Crc32(); + crc.Update(typeBuffer); + crc.Update(chunk.Data); + + if (crc.Value != chunk.Crc) + { + throw new ImageFormatException("CRC Error. PNG Image chunk is corrupt!"); + } + } + + /// + /// Reads the chunk data from the stream. + /// + /// The chunk. + private void ReadChunkData(PngChunk chunk) + { + chunk.Data = new byte[chunk.Length]; + this.currentStream.Read(chunk.Data, 0, chunk.Length); + } + + /// + /// Identifies the chunk type from the chunk. + /// + /// The chunk. + /// + /// The containing identifying information. + /// + /// + /// Thrown if the input stream is not valid. + /// + private byte[] ReadChunkType(PngChunk chunk) + { + byte[] typeBuffer = new byte[4]; + + int numBytes = this.currentStream.Read(typeBuffer, 0, 4); + if (numBytes >= 1 && numBytes <= 3) + { + throw new ImageFormatException("Image stream is not valid!"); + } + + char[] chars = new char[4]; + chars[0] = (char)typeBuffer[0]; + chars[1] = (char)typeBuffer[1]; + chars[2] = (char)typeBuffer[2]; + chars[3] = (char)typeBuffer[3]; + + chunk.Type = new string(chars); + + return typeBuffer; + } + + /// + /// Calculates the length of the given chunk. + /// + /// he chunk. + /// + /// The representing the chunk length. + /// + /// + /// Thrown if the input stream is not valid. + /// + private int ReadChunkLength(PngChunk chunk) + { + byte[] lengthBuffer = new byte[4]; + + int numBytes = this.currentStream.Read(lengthBuffer, 0, 4); + if (numBytes >= 1 && numBytes <= 3) + { + throw new ImageFormatException("Image stream is not valid!"); + } + + Array.Reverse(lengthBuffer); + + chunk.Length = BitConverter.ToInt32(lengthBuffer, 0); + + return numBytes; + } + + /// + /// Swaps two references. + /// + /// The type of the references to swap. + /// The first reference. + /// The second reference. + private void Swap(ref TRef lhs, ref TRef rhs) + where TRef : class + { + TRef tmp = lhs; + + lhs = rhs; + rhs = tmp; + } + } +} diff --git a/src/ImageProcessorCore/Formats/Png/PngEncoder.cs b/src/ImageProcessorCore/Formats/Png/PngEncoder.cs new file mode 100644 index 0000000000..5e34bfa881 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Png/PngEncoder.cs @@ -0,0 +1,87 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + + using ImageProcessorCore.Quantizers; + + /// + /// Image encoder for writing image data to a stream in png format. + /// + public class PngEncoder : IImageEncoder + { + /// + /// Gets or sets the quality of output for images. + /// + public int Quality { get; set; } + + /// + public string MimeType => "image/png"; + + /// + public string Extension => "png"; + + /// + /// The compression level 1-9. + /// Defaults to 6. + /// + public int CompressionLevel { get; set; } = 6; + + /// + /// Gets or sets the gamma value, that will be written + /// the the stream, when the property + /// is set to true. The default value is 2.2F. + /// + /// The gamma value of the image. + public float Gamma { get; set; } = 2.2F; + + /// + /// The quantizer for reducing the color count. + /// + public IQuantizer Quantizer { get; set; } + + /// + /// Gets or sets the transparency threshold. + /// + public byte Threshold { get; set; } = 128; + + /// + /// Gets or sets a value indicating whether this instance should write + /// gamma information to the stream. The default value is false. + /// + public bool WriteGamma { get; set; } + + /// + public bool IsSupportedFileExtension(string extension) + { + Guard.NotNullOrEmpty(extension, nameof(extension)); + + extension = extension.StartsWith(".") ? extension.Substring(1) : extension; + + return extension.Equals(this.Extension, StringComparison.OrdinalIgnoreCase); + } + + /// + public void Encode(ImageBase image, Stream stream) + where T : IPackedVector, new() + where TP : struct + { + PngEncoderCore encoder = new PngEncoderCore + { + CompressionLevel = this.CompressionLevel, + Gamma = this.Gamma, + Quality = this.Quality, + Quantizer = this.Quantizer, + WriteGamma = this.WriteGamma, + Threshold = this.Threshold + }; + + encoder.Encode(image, stream); + } + } +} diff --git a/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs b/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs new file mode 100644 index 0000000000..15ed7793a2 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs @@ -0,0 +1,504 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + using System.Threading.Tasks; + + using ImageProcessorCore.Quantizers; + + /// + /// Performs the png encoding operation. + /// TODO: Perf. There's lots of array parsing going on here. This should be unmanaged. + /// + internal sealed class PngEncoderCore + { + /// + /// The maximum block size, defaults at 64k for uncompressed blocks. + /// + private const int MaxBlockSize = 65535; + + /// + /// The number of bits required to encode the colors in the png. + /// + private byte bitDepth; + + /// + /// The quantized image result. + /// + //private QuantizedImage quantized; + + /// + /// Gets or sets the quality of output for images. + /// + public int Quality { get; set; } + + /// + /// The compression level 1-9. + /// Defaults to 6. + /// + public int CompressionLevel { get; set; } = 6; + + /// + /// Gets or sets a value indicating whether this instance should write + /// gamma information to the stream. The default value is false. + /// + public bool WriteGamma { get; set; } + + /// + /// Gets or sets the gamma value, that will be written + /// the the stream, when the property + /// is set to true. The default value is 2.2F. + /// + /// The gamma value of the image. + public float Gamma { get; set; } = 2.2F; + + /// + /// The quantizer for reducing the color count. + /// + public IQuantizer Quantizer { get; set; } + + /// + /// Gets or sets the transparency threshold. + /// + public byte Threshold { get; set; } = 128; + + /// + /// 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, new() + where TP : struct + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + // Write the png header. + stream.Write( + new byte[] + { + 0x89, // Set the high bit. + 0x50, // P + 0x4E, // N + 0x47, // G + 0x0D, // Line ending CRLF + 0x0A, // Line ending CRLF + 0x1A, // EOF + 0x0A // LF + }, + 0, + 8); + + // Ensure that quality can be set but has a fallback. + int quality = this.Quality > 0 ? this.Quality : image.Quality; + this.Quality = quality > 0 ? quality.Clamp(1, int.MaxValue) : int.MaxValue; + + this.bitDepth = this.Quality <= 256 + ? (byte)(ImageMaths.GetBitsNeededForColorDepth(this.Quality).Clamp(1, 8)) + : (byte)8; + + // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk + if (this.bitDepth == 3) + { + this.bitDepth = 4; + } + else if (this.bitDepth >= 5 || this.bitDepth <= 7) + { + this.bitDepth = 8; + } + + // TODO: Add more color options here. + PngHeader header = new PngHeader + { + Width = image.Width, + Height = image.Height, + ColorType = (byte)(this.Quality <= 256 ? 3 : 6), // 3 = indexed, 6= Each pixel is an R,G,B triple, followed by an alpha sample. + BitDepth = this.bitDepth, + FilterMethod = 0, // None + CompressionMethod = 0, + InterlaceMethod = 0 + }; + + this.WriteHeaderChunk(stream, header); + QuantizedImage quantized = this.WritePaletteChunk(stream, header, image); + this.WritePhysicalChunk(stream, image); + this.WriteGammaChunk(stream); + + using (IPixelAccessor pixels = image.Lock()) + { + this.WriteDataChunks(stream, pixels, quantized); + } + + this.WriteEndChunk(stream); + stream.Flush(); + } + + /// + /// Writes an integer to the byte array. + /// + /// The containing image data. + /// The amount to offset by. + /// The value to write. + private static void WriteInteger(byte[] data, int offset, int value) + { + byte[] buffer = BitConverter.GetBytes(value); + + Array.Reverse(buffer); + Array.Copy(buffer, 0, data, offset, 4); + } + + /// + /// Writes an integer to the stream. + /// + /// The containing image data. + /// The value to write. + private static void WriteInteger(Stream stream, int value) + { + byte[] buffer = BitConverter.GetBytes(value); + + Array.Reverse(buffer); + + stream.Write(buffer, 0, 4); + } + + /// + /// Writes an unsigned integer to the stream. + /// + /// The containing image data. + /// The value to write. + private static void WriteInteger(Stream stream, uint value) + { + byte[] buffer = BitConverter.GetBytes(value); + + Array.Reverse(buffer); + + stream.Write(buffer, 0, 4); + } + + /// + /// Writes the header chunk to the stream. + /// + /// The containing image data. + /// The . + private void WriteHeaderChunk(Stream stream, PngHeader header) + { + byte[] chunkData = new byte[13]; + + WriteInteger(chunkData, 0, header.Width); + WriteInteger(chunkData, 4, header.Height); + + chunkData[8] = header.BitDepth; + chunkData[9] = header.ColorType; + chunkData[10] = header.CompressionMethod; + chunkData[11] = header.FilterMethod; + chunkData[12] = header.InterlaceMethod; + + this.WriteChunk(stream, PngChunkTypes.Header, chunkData); + } + + /// + /// 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 QuantizedImage WritePaletteChunk(Stream stream, PngHeader header, ImageBase image) + where T : IPackedVector, new() + where TP : struct + { + if (this.Quality > 256) + { + return null; + } + + if (this.Quantizer == null) + { + this.Quantizer = new WuQuantizer { Threshold = this.Threshold }; + } + + // Quantize the image returning a palette. + QuantizedImage quantized = Quantizer.Quantize(image, this.Quality); + + // Grab the palette and write it to the stream. + 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, + i => + { + int offset = i * 3; + byte[] color = palette[i].ToBytes(); + + // 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 (quantized.TransparentIndex > -1) + { + 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) + where T : IPackedVector, new() + where TP : struct + { + 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); + + byte[] chunkData = new byte[9]; + + WriteInteger(chunkData, 0, dpmX); + WriteInteger(chunkData, 4, dpmY); + + chunkData[8] = 1; + + this.WriteChunk(stream, PngChunkTypes.Physical, chunkData); + } + } + + /// + /// Writes the gamma information to the stream. + /// + /// The containing image data. + private void WriteGammaChunk(Stream stream) + { + if (this.WriteGamma) + { + int gammaValue = (int)(this.Gamma * 100000f); + + byte[] fourByteData = new byte[4]; + + byte[] size = BitConverter.GetBytes(gammaValue); + + fourByteData[0] = size[3]; + fourByteData[1] = size[2]; + fourByteData[2] = size[1]; + fourByteData[3] = size[0]; + + this.WriteChunk(stream, PngChunkTypes.Gamma, fourByteData); + } + } + + /// + /// Writes the pixel information to the stream. + /// + /// The pixel format. + /// The packed format. long, float. + /// The containing image data. + /// The image pixels. + /// The quantized image. + private void WriteDataChunks(Stream stream, IPixelAccessor pixels, QuantizedImage quantized) + where T : IPackedVector, new() + where TP : struct + { + byte[] data; + int imageWidth = pixels.Width; + int imageHeight = pixels.Height; + + // Indexed image. + if (this.Quality <= 256) + { + int rowLength = imageWidth + 1; + data = new byte[rowLength * imageHeight]; + + Parallel.For( + 0, + imageHeight, + //Bootstrapper.Instance.ParallelOptions, + y => + { + int dataOffset = (y * rowLength); + byte compression = 0; + if (y > 0) + { + compression = 2; + } + data[dataOffset++] = compression; + for (int x = 0; x < imageWidth; x++) + { + data[dataOffset++] = quantized.Pixels[(y * imageWidth) + x]; + if (y > 0) + { + data[dataOffset - 1] -= quantized.Pixels[((y - 1) * imageWidth) + x]; + } + } + }); + } + else + { + // TrueColor image. + data = new byte[(imageWidth * imageHeight * 4) + pixels.Height]; + + int rowLength = (imageWidth * 4) + 1; + + Parallel.For( + 0, + imageHeight, + Bootstrapper.Instance.ParallelOptions, + y => + { + byte compression = 0; + if (y > 0) + { + compression = 2; + } + + data[y * rowLength] = compression; + + for (int x = 0; x < imageWidth; x++) + { + byte[] color = pixels[x, y].ToBytes(); + + // Calculate the offset for the new array. + int dataOffset = (y * rowLength) + (x * 4) + 1; + + // 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 = pixels[x, y - 1].ToBytes(); + + data[dataOffset] -= color[0]; + data[dataOffset + 1] -= color[1]; + data[dataOffset + 2] -= color[2]; + data[dataOffset + 3] -= color[3]; + } + } + }); + } + + byte[] buffer; + int bufferLength; + + MemoryStream memoryStream = null; + try + { + memoryStream = new MemoryStream(); + + using (ZlibDeflateStream deflateStream = new ZlibDeflateStream(memoryStream, this.CompressionLevel)) + { + deflateStream.Write(data, 0, data.Length); + } + + bufferLength = (int)memoryStream.Length; + buffer = memoryStream.ToArray(); + } + finally + { + memoryStream?.Dispose(); + } + + int numChunks = bufferLength / MaxBlockSize; + + if (bufferLength % MaxBlockSize != 0) + { + numChunks++; + } + + for (int i = 0; i < numChunks; i++) + { + int length = bufferLength - (i * MaxBlockSize); + + if (length > MaxBlockSize) + { + length = MaxBlockSize; + } + + this.WriteChunk(stream, PngChunkTypes.Data, buffer, i * MaxBlockSize, length); + } + } + + /// + /// Writes the chunk end to the stream. + /// + /// The containing image data. + private void WriteEndChunk(Stream stream) + { + this.WriteChunk(stream, PngChunkTypes.End, null); + } + + /// + /// Writes a chunk to the stream. + /// + /// The to write to. + /// The type of chunk to write. + /// The containing data. + private void WriteChunk(Stream stream, string type, byte[] data) + { + this.WriteChunk(stream, type, data, 0, data?.Length ?? 0); + } + + /// + /// Writes a chunk of a specified length to the stream at the given offset. + /// + /// The to write to. + /// The type of chunk to write. + /// The containing data. + /// The position to offset the data at. + /// The of the data to write. + private void WriteChunk(Stream stream, string type, byte[] data, int offset, int length) + { + WriteInteger(stream, length); + + byte[] typeArray = new byte[4]; + typeArray[0] = (byte)type[0]; + typeArray[1] = (byte)type[1]; + typeArray[2] = (byte)type[2]; + typeArray[3] = (byte)type[3]; + + stream.Write(typeArray, 0, 4); + + if (data != null) + { + stream.Write(data, offset, length); + } + + Crc32 crc32 = new Crc32(); + crc32.Update(typeArray); + + if (data != null) + { + crc32.Update(data, offset, length); + } + + WriteInteger(stream, (uint)crc32.Value); + } + } +} diff --git a/src/ImageProcessorCore/Formats/Png/PngFormat.cs b/src/ImageProcessorCore/Formats/Png/PngFormat.cs new file mode 100644 index 0000000000..38a0a7c384 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Png/PngFormat.cs @@ -0,0 +1,19 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Encapsulates the means to encode and decode png images. + /// + public class PngFormat : IImageFormat + { + /// + public IImageDecoder Decoder => new PngDecoder(); + + /// + public IImageEncoder Encoder => new PngEncoder(); + } +} diff --git a/src/ImageProcessorCore/Formats/Png/PngHeader.cs b/src/ImageProcessorCore/Formats/Png/PngHeader.cs new file mode 100644 index 0000000000..dfa30794a8 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Png/PngHeader.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Represents the png header chunk. + /// + public sealed class PngHeader + { + /// + /// Gets or sets the dimension in x-direction of the image in pixels. + /// + public int Width { get; set; } + + /// + /// Gets or sets the dimension in y-direction of the image in pixels. + /// + public int Height { get; set; } + + /// + /// Gets or sets the bit depth. + /// Bit depth is a single-byte integer giving the number of bits per sample + /// or per palette index (not per pixel). Valid values are 1, 2, 4, 8, and 16, + /// although not all values are allowed for all color types. + /// + public byte BitDepth { get; set; } + + /// + /// Gets or sets the color type. + /// Color type is a integer that describes the interpretation of the + /// image data. Color type codes represent sums of the following values: + /// 1 (palette used), 2 (color used), and 4 (alpha channel used). + /// + public byte ColorType { get; set; } + + /// + /// Gets or sets the compression method. + /// Indicates the method used to compress the image data. At present, + /// only compression method 0 (deflate/inflate compression with a sliding + /// window of at most 32768 bytes) is defined. + /// + public byte CompressionMethod { get; set; } + + /// + /// Gets or sets the preprocessing method. + /// Indicates the preprocessing method applied to the image + /// data before compression. At present, only filter method 0 + /// (adaptive filtering with five basic filter types) is defined. + /// + public byte FilterMethod { get; set; } + + /// + /// Gets or sets the transmission order. + /// Indicates the transmission order of the image data. + /// Two values are currently defined: 0 (no interlace) or 1 (Adam7 interlace). + /// + public byte InterlaceMethod { get; set; } + } +} diff --git a/src/ImageProcessorCore/Formats/Png/README.md b/src/ImageProcessorCore/Formats/Png/README.md new file mode 100644 index 0000000000..8ade379560 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Png/README.md @@ -0,0 +1,6 @@ +Encoder/Decoder adapted from: + +https://github.com/yufeih/Nine.Imaging/ +https://imagetools.codeplex.com/ +https://github.com/leonbloy/pngcs + diff --git a/src/ImageProcessorCore/Formats/Png/TrueColorReader.cs b/src/ImageProcessorCore/Formats/Png/TrueColorReader.cs new file mode 100644 index 0000000000..0fd135c4aa --- /dev/null +++ b/src/ImageProcessorCore/Formats/Png/TrueColorReader.cs @@ -0,0 +1,81 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Color reader for reading true colors from a png file. Only colors + /// with 24 or 32 bit (3 or 4 bytes) per pixel are supported at the moment. + /// + internal sealed class TrueColorReader : IColorReader + { + /// + /// Whether t also read the alpha channel. + /// + private readonly bool useAlpha; + + /// + /// The current row. + /// + private int row; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true the color reader will also read the + /// alpha channel from the scanline. + public TrueColorReader(bool useAlpha) + { + this.useAlpha = useAlpha; + } + + /// + public void ReadScanline(byte[] scanline, T[] pixels, PngHeader header) + where T : IPackedVector, new() + where TP : struct + { + int offset; + + byte[] newScanline = scanline.ToArrayByBitsLength(header.BitDepth); + + if (this.useAlpha) + { + for (int x = 0; x < newScanline.Length; x += 4) + { + offset = (this.row * header.Width) + (x >> 2); + + // We want to convert to premultiplied alpha here. + byte r = newScanline[x]; + byte g = newScanline[x + 1]; + byte b = newScanline[x + 2]; + byte a = newScanline[x + 3]; + + T color = default(T); + color.PackBytes(r, g, b, a); + + pixels[offset] = color; + } + } + else + { + for (int x = 0; x < newScanline.Length / 3; x++) + { + offset = (this.row * header.Width) + x; + int pixelOffset = x * 3; + + 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; + } + } + + this.row++; + } + } +} diff --git a/src/ImageProcessorCore/Formats/Png/Zlib/Adler32.cs b/src/ImageProcessorCore/Formats/Png/Zlib/Adler32.cs new file mode 100644 index 0000000000..f58ec34c2d --- /dev/null +++ b/src/ImageProcessorCore/Formats/Png/Zlib/Adler32.cs @@ -0,0 +1,174 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + + /// + /// Computes Adler32 checksum for a stream of data. An Adler32 + /// checksum is not as reliable as a CRC32 checksum, but a lot faster to + /// compute. + /// + /// + /// The specification for Adler32 may be found in RFC 1950. + /// ZLIB Compressed Data Format Specification version 3.3) + /// + /// + /// From that document: + /// + /// "ADLER32 (Adler-32 checksum) + /// This contains a checksum value of the uncompressed data + /// (excluding any dictionary data) computed according to Adler-32 + /// algorithm. This algorithm is a 32-bit extension and improvement + /// of the Fletcher algorithm, used in the ITU-T X.224 / ISO 8073 + /// standard. + /// + /// Adler-32 is composed of two sums accumulated per byte: s1 is + /// the sum of all bytes, s2 is the sum of all s1 values. Both sums + /// are done modulo 65521. s1 is initialized to 1, s2 to zero. The + /// Adler-32 checksum is stored as s2*65536 + s1 in most- + /// significant-byte first (network) order." + /// + /// "8.2. The Adler-32 algorithm + /// + /// The Adler-32 algorithm is much faster than the CRC32 algorithm yet + /// still provides an extremely low probability of undetected errors. + /// + /// The modulo on unsigned long accumulators can be delayed for 5552 + /// bytes, so the modulo operation time is negligible. If the bytes + /// are a, b, c, the second sum is 3a + 2b + c + 3, and so is position + /// and order sensitive, unlike the first sum, which is just a + /// checksum. That 65521 is prime is important to avoid a possible + /// large class of two-byte errors that leave the check unchanged. + /// (The Fletcher checksum uses 255, which is not prime and which also + /// makes the Fletcher check insensitive to single byte changes 0 - + /// 255.) + /// + /// The sum s1 is initialized to 1 instead of zero to make the length + /// of the sequence part of s2, so that the length does not have to be + /// checked separately. (Any sequence of zeroes has a Fletcher + /// checksum of zero.)" + /// + /// + /// + internal sealed class Adler32 : IChecksum + { + /// + /// largest prime smaller than 65536 + /// + private const uint Base = 65521; + + /// + /// The checksum calculated to far. + /// + private uint checksum; + + /// + /// Initializes a new instance of the class. + /// The checksum starts off with a value of 1. + /// + public Adler32() + { + this.Reset(); + } + + /// + public long Value => this.checksum; + + /// + public void Reset() + { + this.checksum = 1; + } + + /// + /// Updates the checksum with a byte value. + /// + /// + /// The data value to add. The high byte of the int is ignored. + /// + public void Update(int value) + { + // We could make a length 1 byte array and call update again, but I + // would rather not have that overhead + uint s1 = this.checksum & 0xFFFF; + uint s2 = this.checksum >> 16; + + s1 = (s1 + ((uint)value & 0xFF)) % Base; + s2 = (s1 + s2) % Base; + + this.checksum = (s2 << 16) + s1; + } + + /// + public void Update(byte[] buffer) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + this.Update(buffer, 0, buffer.Length); + } + + /// + public void Update(byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset), "cannot be negative"); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "cannot be negative"); + } + + if (offset >= buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(offset), "not a valid index into buffer"); + } + + if (offset + count > buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(count), "exceeds buffer size"); + } + + // (By Per Bothner) + uint s1 = this.checksum & 0xFFFF; + uint s2 = this.checksum >> 16; + + while (count > 0) + { + // We can defer the modulo operation: + // s1 maximally grows from 65521 to 65521 + 255 * 3800 + // s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31 + int n = 3800; + if (n > count) + { + n = count; + } + + count -= n; + while (--n >= 0) + { + s1 = s1 + (uint)(buffer[offset++] & 0xff); + s2 = s2 + s1; + } + + s1 %= Base; + s2 %= Base; + } + + this.checksum = (s2 << 16) | s1; + } + } +} diff --git a/src/ImageProcessorCore/Formats/Png/Zlib/Crc32.cs b/src/ImageProcessorCore/Formats/Png/Zlib/Crc32.cs new file mode 100644 index 0000000000..da42e8dae4 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Png/Zlib/Crc32.cs @@ -0,0 +1,180 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + + /// + /// Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: + /// x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. + /// + /// + /// + /// Polynomials over GF(2) are represented in binary, one bit per coefficient, + /// with the lowest powers in the most significant bit. Then adding polynomials + /// is just exclusive-or, and multiplying a polynomial by x is a right shift by + /// one. If we call the above polynomial p, and represent a byte as the + /// polynomial q, also with the lowest power in the most significant bit (so the + /// byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, + /// where a mod b means the remainder after dividing a by b. + /// + /// + /// This calculation is done using the shift-register method of multiplying and + /// taking the remainder. The register is initialized to zero, and for each + /// incoming bit, x^32 is added mod p to the register if the bit is a one (where + /// x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by + /// x (which is shifting right by one and adding x^32 mod p if the bit shifted + /// out is a one). We start with the highest power (least significant bit) of + /// q and repeat for all eight bits of q. + /// + /// + /// The table is simply the CRC of all possible eight bit values. This is all + /// the information needed to generate CRC's on data a byte at a time for all + /// combinations of CRC register values and incoming bytes. + /// + /// + internal sealed class Crc32 : IChecksum + { + /// + /// The cycle redundancy check seed + /// + private const uint CrcSeed = 0xFFFFFFFF; + + /// + /// The table of all possible eight bit values for fast lookup. + /// + private static readonly uint[] CrcTable = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, + 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, + 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, + 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, + 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, + 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, + 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, + 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, + 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, + 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, + 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, + 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, + 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, + 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, + 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, + 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, + 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, + 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, + 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, + 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, + 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, + 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, + 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, + 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, + 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, + 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, + 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, + 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, + 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, + 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, + 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, + 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, + 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, + 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, + 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, + 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, + 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, + 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, + 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, + 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, + 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, + 0x2D02EF8D + }; + + /// + /// The data checksum so far. + /// + private uint crc; + + /// + public long Value + { + get + { + return this.crc; + } + + set + { + this.crc = (uint)value; + } + } + + /// + public void Reset() + { + this.crc = 0; + } + + /// + /// Updates the checksum with the given value. + /// + /// The byte is taken as the lower 8 bits of value. + public void Update(int value) + { + this.crc ^= CrcSeed; + this.crc = CrcTable[(this.crc ^ value) & 0xFF] ^ (this.crc >> 8); + this.crc ^= CrcSeed; + } + + /// + public void Update(byte[] buffer) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + this.Update(buffer, 0, buffer.Length); + } + + /// + public void Update(byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), "Count cannot be less than zero"); + } + + if (offset < 0 || offset + count > buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + this.crc ^= CrcSeed; + + while (--count >= 0) + { + this.crc = CrcTable[(this.crc ^ buffer[offset++]) & 0xFF] ^ (this.crc >> 8); + } + + this.crc ^= CrcSeed; + } + } +} diff --git a/src/ImageProcessorCore/Formats/Png/Zlib/IChecksum.cs b/src/ImageProcessorCore/Formats/Png/Zlib/IChecksum.cs new file mode 100644 index 0000000000..077a5ad2a6 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Png/Zlib/IChecksum.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Interface to compute a data checksum used by checked input/output streams. + /// A data checksum can be updated by one byte or with a byte array. After each + /// update the value of the current checksum can be returned by calling + /// Value. The complete checksum object can also be reset + /// so it can be used again with new data. + /// + public interface IChecksum + { + /// + /// Gets the data checksum computed so far. + /// + long Value + { + get; + } + + /// + /// Resets the data checksum as if no update was ever called. + /// + void Reset(); + + /// + /// Adds one byte to the data checksum. + /// + /// + /// The data value to add. The high byte of the integer is ignored. + /// + void Update(int value); + + /// + /// Updates the data checksum with the bytes taken from the array. + /// + /// + /// buffer an array of bytes + /// + void Update(byte[] buffer); + + /// + /// Adds the byte array to the data checksum. + /// + /// + /// The buffer which contains the data + /// + /// + /// The offset in the buffer where the data starts + /// + /// + /// the number of data bytes to add. + /// + void Update(byte[] buffer, int offset, int count); + } +} diff --git a/src/ImageProcessorCore/Formats/Png/Zlib/README.md b/src/ImageProcessorCore/Formats/Png/Zlib/README.md new file mode 100644 index 0000000000..c297a91d5e --- /dev/null +++ b/src/ImageProcessorCore/Formats/Png/Zlib/README.md @@ -0,0 +1,2 @@ +Adler32.cs and Crc32.cs have been copied from +https://github.com/ygrenier/SharpZipLib.Portable diff --git a/src/ImageProcessorCore/Formats/Png/Zlib/ZlibDeflateStream.cs b/src/ImageProcessorCore/Formats/Png/Zlib/ZlibDeflateStream.cs new file mode 100644 index 0000000000..a2c0ca202f --- /dev/null +++ b/src/ImageProcessorCore/Formats/Png/Zlib/ZlibDeflateStream.cs @@ -0,0 +1,210 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + using System.IO.Compression; + + /// + /// Provides methods and properties for compressing streams by using the Zlib Deflate algorithm. + /// + internal sealed class ZlibDeflateStream : Stream + { + /// + /// The raw stream containing the uncompressed image data. + /// + private readonly Stream rawStream; + + /// + /// Computes the checksum for the data stream. + /// + private readonly Adler32 adler32 = new Adler32(); + + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// If the entity is disposed, it must not be disposed a second + /// time. The isDisposed field is set the first time the entity + /// is disposed. If the isDisposed field is true, then the Dispose() + /// method will not dispose again. This help not to prolong the entity's + /// life in the Garbage Collector. + /// + private bool isDisposed; + + // The stream responsible for decompressing the input stream. + private DeflateStream deflateStream; + + /// + /// Initializes a new instance of + /// + /// The stream to compress. + /// The compression level. + public ZlibDeflateStream(Stream stream, int compressionLevel) + { + this.rawStream = stream; + + // Write the zlib header : http://tools.ietf.org/html/rfc1950 + // CMF(Compression Method and flags) + // This byte is divided into a 4 - bit compression method and a + // 4-bit information field depending on the compression method. + // bits 0 to 3 CM Compression method + // bits 4 to 7 CINFO Compression info + // + // 0 1 + // +---+---+ + // |CMF|FLG| + // +---+---+ + int cmf = 0x78; + int flg = 218; + + // http://stackoverflow.com/a/2331025/277304 + if (compressionLevel >= 5 && compressionLevel <= 6) + { + flg = 156; + } + else if (compressionLevel >= 3 && compressionLevel <= 4) + { + flg = 94; + } + + else if (compressionLevel <= 2) + { + flg = 1; + } + + // Just in case + flg -= (cmf * 256 + flg) % 31; + + if (flg < 0) + { + flg += 31; + } + + this.rawStream.WriteByte((byte)cmf); + this.rawStream.WriteByte((byte)flg); + + // Initialize the deflate Stream. + CompressionLevel level = CompressionLevel.Optimal; + + if (compressionLevel >= 1 && compressionLevel <= 5) + { + level = CompressionLevel.Fastest; + } + + else if (compressionLevel == 0) + { + level = CompressionLevel.NoCompression; + } + + this.deflateStream = new DeflateStream(this.rawStream, level, true); + } + + /// + public override bool CanRead => false; + + /// + public override bool CanSeek => false; + + /// + public override bool CanWrite => true; + + /// + public override long Length + { + get + { + throw new NotSupportedException(); + } + } + + /// + public override long Position + { + get + { + throw new NotSupportedException(); + } + + set + { + throw new NotSupportedException(); + } + } + + /// + public override void Flush() + { + this.deflateStream?.Flush(); + } + + /// + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + /// + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + /// + public override void Write(byte[] buffer, int offset, int count) + { + this.deflateStream.Write(buffer, offset, count); + this.adler32.Update(buffer, offset, count); + } + + /// + protected override void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (disposing) + { + // dispose managed resources + if (this.deflateStream != null) + { + this.deflateStream.Dispose(); + this.deflateStream = null; + } + else { + + // Hack: empty input? + this.rawStream.WriteByte(3); + this.rawStream.WriteByte(0); + } + + // Add the crc + uint crc = (uint)this.adler32.Value; + this.rawStream.WriteByte((byte)((crc >> 24) & 0xFF)); + this.rawStream.WriteByte((byte)((crc >> 16) & 0xFF)); + this.rawStream.WriteByte((byte)((crc >> 8) & 0xFF)); + this.rawStream.WriteByte((byte)((crc) & 0xFF)); + } + + base.Dispose(disposing); + + // Call the appropriate methods to clean up + // unmanaged resources here. + // Note disposing is done. + this.isDisposed = true; + } + } +} diff --git a/src/ImageProcessorCore/Formats/Png/Zlib/ZlibInflateStream.cs b/src/ImageProcessorCore/Formats/Png/Zlib/ZlibInflateStream.cs new file mode 100644 index 0000000000..4373b5fd17 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Png/Zlib/ZlibInflateStream.cs @@ -0,0 +1,205 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + using System.IO.Compression; + + /// + /// Provides methods and properties for decompressing streams by using the Zlib Deflate algorithm. + /// + internal sealed class ZlibInflateStream : Stream + { + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// If the entity is disposed, it must not be disposed a second + /// time. The isDisposed field is set the first time the entity + /// is disposed. If the isDisposed field is true, then the Dispose() + /// method will not dispose again. This help not to prolong the entity's + /// life in the Garbage Collector. + /// + private bool isDisposed; + + /// + /// The raw stream containing the uncompressed image data. + /// + private readonly Stream rawStream; + + /// + /// The read crc data. + /// + private byte[] crcread; + + // The stream responsible for decompressing the input stream. + private DeflateStream deflateStream; + + public ZlibInflateStream(Stream stream) + { + // The DICT dictionary identifier identifying the used dictionary. + + // The preset dictionary. + bool fdict; + this.rawStream = stream; + + // Read the zlib header : http://tools.ietf.org/html/rfc1950 + // CMF(Compression Method and flags) + // This byte is divided into a 4 - bit compression method and a + // 4-bit information field depending on the compression method. + // bits 0 to 3 CM Compression method + // bits 4 to 7 CINFO Compression info + // + // 0 1 + // +---+---+ + // |CMF|FLG| + // +---+---+ + int cmf = this.rawStream.ReadByte(); + int flag = this.rawStream.ReadByte(); + if (cmf == -1 || flag == -1) + { + return; + } + + if ((cmf & 0x0f) != 8) + { + throw new Exception($"Bad compression method for ZLIB header: cmf={cmf}"); + } + + // CINFO is the base-2 logarithm of the LZ77 window size, minus eight. + // int cinfo = ((cmf & (0xf0)) >> 8); + fdict = (flag & 32) != 0; + + if (fdict) + { + // The DICT dictionary identifier identifying the used dictionary. + byte[] dictId = new byte[4]; + + for (int i = 0; i < 4; i++) + { + // We consume but don't use this. + dictId[i] = (byte)this.rawStream.ReadByte(); + } + } + + // Initialize the deflate Stream. + this.deflateStream = new DeflateStream(this.rawStream, CompressionMode.Decompress, true); + } + + /// + public override bool CanRead => true; + + /// + public override bool CanSeek => false; + + /// + public override bool CanWrite => false; + + /// + public override long Length + { + get + { + throw new NotSupportedException(); + } + } + + /// + public override long Position + { + get + { + throw new NotSupportedException(); + } + + set + { + throw new NotSupportedException(); + } + } + + /// + public override void Flush() + { + this.deflateStream?.Flush(); + } + + /// + public override int Read(byte[] buffer, int offset, int count) + { + // We dont't check CRC on reading + int read = this.deflateStream.Read(buffer, offset, count); + if (read < 1 && this.crcread == null) + { + // The deflater has ended. We try to read the next 4 bytes from raw stream (crc) + this.crcread = new byte[4]; + for (int i = 0; i < 4; i++) + { + // we dont really check/use this + this.crcread[i] = (byte)this.rawStream.ReadByte(); + } + } + + return read; + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + /// + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + /// + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + /// + protected override void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (disposing) + { + // dispose managed resources + if (this.deflateStream != null) + { + this.deflateStream.Dispose(); + this.deflateStream = null; + + if (this.crcread == null) + { + // Consume the trailing 4 bytes + this.crcread = new byte[4]; + for (int i = 0; i < 4; i++) + { + this.crcread[i] = (byte)this.rawStream.ReadByte(); + } + } + } + } + + base.Dispose(disposing); + + // Call the appropriate methods to clean up + // unmanaged resources here. + // Note disposing is done. + this.isDisposed = true; + } + } +} diff --git a/src/ImageProcessorCore/Image.cs b/src/ImageProcessorCore/Image.cs index e57129d29e..9ab3e73ba2 100644 --- a/src/ImageProcessorCore/Image.cs +++ b/src/ImageProcessorCore/Image.cs @@ -13,6 +13,14 @@ namespace ImageProcessorCore /// public class Image : Image { + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + public Image() + { + } + /// /// Initializes a new instance of the class /// with the height and the width of the image. diff --git a/src/ImageProcessorCore/Quantizers/IQuantizer.cs b/src/ImageProcessorCore/Quantizers/IQuantizer.cs new file mode 100644 index 0000000000..3f555236a0 --- /dev/null +++ b/src/ImageProcessorCore/Quantizers/IQuantizer.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Quantizers +{ + /// + /// Provides methods for allowing quantization of images pixels. + /// + public interface IQuantizer + { + /// + /// Gets or sets the transparency threshold. + /// + byte Threshold { get; set; } + + /// + /// Quantize an image and return the resulting output pixels. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image to quantize. + /// The maximum number of colors to return. + /// + /// A representing a quantized version of the image pixels. + /// + QuantizedImage Quantize(ImageBase image, int maxColors) + where T : IPackedVector, new() + where TP : struct; + } +} diff --git a/src/ImageProcessorCore/Quantizers/QuantizedImage.cs b/src/ImageProcessorCore/Quantizers/QuantizedImage.cs new file mode 100644 index 0000000000..ddadc099ec --- /dev/null +++ b/src/ImageProcessorCore/Quantizers/QuantizedImage.cs @@ -0,0 +1,102 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Quantizers +{ + using System; + using System.Threading.Tasks; + + /// + /// Represents a quantized image where the pixels indexed by a color palette. + /// + /// The pixel format. + /// The packed format. long, float. + public class QuantizedImage + where T : IPackedVector, new() + where TP : struct + { + /// + /// 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, T[] palette, byte[] pixels, int transparentIndex = -1) + { + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + Guard.NotNull(palette, nameof(palette)); + Guard.NotNull(pixels, nameof(pixels)); + + if (pixels.Length != width * height) + { + throw new ArgumentException( + $"Pixel array size must be {nameof(width)} * {nameof(height)}", nameof(pixels)); + } + + this.Width = width; + this.Height = height; + this.Palette = palette; + this.Pixels = pixels; + this.TransparentIndex = transparentIndex; + } + + /// + /// Gets the width of this . + /// + public int Width { get; } + + /// + /// Gets the height of this . + /// + public int Height { get; } + + /// + /// Gets the color palette of this . + /// + public T[] Palette { get; } + + /// + /// Gets the pixels of this . + /// + public byte[] Pixels { get; } + + /// + /// Gets the transparent index + /// + public int TransparentIndex { get; } + + /// + /// Converts this quantized image to a normal image. + /// + /// + /// The + /// + public Image ToImage() + { + Image image = new Image(); + + int pixelCount = this.Pixels.Length; + int palletCount = this.Palette.Length - 1; + T[] pixels = new T[pixelCount]; + + Parallel.For( + 0, + pixelCount, + Bootstrapper.Instance.ParallelOptions, + i => + { + int offset = i * 4; + T color = this.Palette[Math.Min(palletCount, this.Pixels[i])]; + pixels[offset] = color; + }); + + image.SetPixels(this.Width, this.Height, pixels); + return image; + } + } +} diff --git a/src/ImageProcessorCore/Quantizers/Wu/Box.cs b/src/ImageProcessorCore/Quantizers/Wu/Box.cs new file mode 100644 index 0000000000..b9300b0870 --- /dev/null +++ b/src/ImageProcessorCore/Quantizers/Wu/Box.cs @@ -0,0 +1,58 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Quantizers +{ + /// + /// Represents a box color cube. + /// + internal sealed class Box + { + /// + /// Gets or sets the min red value, exclusive. + /// + public int R0 { get; set; } + + /// + /// Gets or sets the max red value, inclusive. + /// + public int R1 { get; set; } + + /// + /// Gets or sets the min green value, exclusive. + /// + public int G0 { get; set; } + + /// + /// Gets or sets the max green value, inclusive. + /// + public int G1 { get; set; } + + /// + /// Gets or sets the min blue value, exclusive. + /// + public int B0 { get; set; } + + /// + /// Gets or sets the max blue value, inclusive. + /// + public int B1 { get; set; } + + /// + /// Gets or sets the min alpha value, exclusive. + /// + public int A0 { get; set; } + + /// + /// Gets or sets the max alpha value, inclusive. + /// + public int A1 { get; set; } + + /// + /// Gets or sets the volume. + /// + public int Volume { get; set; } + } +} diff --git a/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs b/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs new file mode 100644 index 0000000000..95e0b07728 --- /dev/null +++ b/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs @@ -0,0 +1,800 @@ +// +// Copyright © James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Quantizers +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + + /// + /// An implementation of Wu's color quantizer with alpha channel. + /// + /// + /// + /// Based on C Implementation of Xiaolin Wu's Color Quantizer (v. 2) + /// (see Graphics Gems volume II, pages 126-133) + /// (). + /// + /// + /// This adaptation is based on the excellent JeremyAnsel.ColorQuant by Jérémy Ansel + /// + /// + /// + /// Algorithm: Greedy orthogonal bipartition of RGB space for variance + /// minimization aided by inclusion-exclusion tricks. + /// For speed no nearest neighbor search is done. Slightly + /// better performance can be expected by more sophisticated + /// but more expensive versions. + /// + /// + public sealed class WuQuantizer : IQuantizer + { + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.001f; + + /// + /// The index bits. + /// + private const int IndexBits = 6; + + /// + /// The index alpha bits. + /// + private const int IndexAlphaBits = 3; + + /// + /// The index count. + /// + private const int IndexCount = (1 << IndexBits) + 1; + + /// + /// The index alpha count. + /// + private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1; + + /// + /// The table length. + /// + private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; + + /// + /// Moment of P(c). + /// + private readonly long[] vwt; + + /// + /// Moment of r*P(c). + /// + private readonly long[] vmr; + + /// + /// Moment of g*P(c). + /// + private readonly long[] vmg; + + /// + /// Moment of b*P(c). + /// + private readonly long[] vmb; + + /// + /// Moment of a*P(c). + /// + private readonly long[] vma; + + /// + /// Moment of c^2*P(c). + /// + private readonly double[] m2; + + /// + /// Color space tag. + /// + private readonly byte[] tag; + + /// + /// Initializes a new instance of the class. + /// + public WuQuantizer() + { + this.vwt = new long[TableLength]; + this.vmr = new long[TableLength]; + this.vmg = new long[TableLength]; + this.vmb = new long[TableLength]; + this.vma = new long[TableLength]; + this.m2 = new double[TableLength]; + this.tag = new byte[TableLength]; + } + + /// + public byte Threshold { get; set; } + + /// + public QuantizedImage Quantize(ImageBase image, int maxColors) + where T : IPackedVector, new() + where TP : struct + { + Guard.NotNull(image, nameof(image)); + + int colorCount = maxColors.Clamp(1, 256); + + this.Clear(); + + using (IPixelAccessor imagePixels = image.Lock()) + { + this.Build3DHistogram(imagePixels); + this.Get3DMoments(); + + Box[] cube; + this.BuildCube(out cube, ref colorCount); + + return this.GenerateResult(imagePixels, colorCount, cube); + } + } + + /// + /// Gets an index. + /// + /// The red value. + /// The green value. + /// The blue value. + /// The alpha value. + /// The index. + private static int GetPaletteIndex(int r, int g, int b, int a) + { + return (r << ((IndexBits * 2) + IndexAlphaBits)) + + (r << (IndexBits + IndexAlphaBits + 1)) + + (g << (IndexBits + IndexAlphaBits)) + + (r << (IndexBits * 2)) + + (r << (IndexBits + 1)) + + (g << IndexBits) + + ((r + g + b) << IndexAlphaBits) + + r + g + b + a; + } + + /// + /// Computes sum over a box of any given statistic. + /// + /// The cube. + /// The moment. + /// The result. + private static double Volume(Box cube, long[] moment) + { + return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] + - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] + - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] + + moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] + - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] + + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] + + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] + - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] + - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)] + + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] + + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] + - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] + + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] + - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] + - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] + + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; + } + + /// + /// Computes part of Volume(cube, moment) that doesn't depend on r1, g1, or b1 (depending on direction). + /// + /// The cube. + /// The direction. + /// The moment. + /// The result. + private static long Bottom(Box cube, int direction, long[] moment) + { + switch (direction) + { + // Red + case 0: + return -moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)] + + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] + + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] + - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] + + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] + - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] + - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] + + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; + + // Green + case 1: + return -moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] + + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] + + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] + - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] + + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] + - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] + - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] + + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; + + // Blue + case 2: + return -moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] + + moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] + + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] + - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] + + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] + - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] + - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] + + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; + + // Alpha + case 3: + return -moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] + + moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] + + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] + - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] + + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] + - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] + - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] + + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; + + default: + throw new ArgumentOutOfRangeException(nameof(direction)); + } + } + + /// + /// Computes remainder of Volume(cube, moment), substituting position for r1, g1, or b1 (depending on direction). + /// + /// The cube. + /// The direction. + /// The position. + /// The moment. + /// The result. + private static long Top(Box cube, int direction, int position, long[] moment) + { + switch (direction) + { + // Red + case 0: + return moment[GetPaletteIndex(position, cube.G1, cube.B1, cube.A1)] + - moment[GetPaletteIndex(position, cube.G1, cube.B1, cube.A0)] + - moment[GetPaletteIndex(position, cube.G1, cube.B0, cube.A1)] + + moment[GetPaletteIndex(position, cube.G1, cube.B0, cube.A0)] + - moment[GetPaletteIndex(position, cube.G0, cube.B1, cube.A1)] + + moment[GetPaletteIndex(position, cube.G0, cube.B1, cube.A0)] + + moment[GetPaletteIndex(position, cube.G0, cube.B0, cube.A1)] + - moment[GetPaletteIndex(position, cube.G0, cube.B0, cube.A0)]; + + // Green + case 1: + return moment[GetPaletteIndex(cube.R1, position, cube.B1, cube.A1)] + - moment[GetPaletteIndex(cube.R1, position, cube.B1, cube.A0)] + - moment[GetPaletteIndex(cube.R1, position, cube.B0, cube.A1)] + + moment[GetPaletteIndex(cube.R1, position, cube.B0, cube.A0)] + - moment[GetPaletteIndex(cube.R0, position, cube.B1, cube.A1)] + + moment[GetPaletteIndex(cube.R0, position, cube.B1, cube.A0)] + + moment[GetPaletteIndex(cube.R0, position, cube.B0, cube.A1)] + - moment[GetPaletteIndex(cube.R0, position, cube.B0, cube.A0)]; + + // Blue + case 2: + return moment[GetPaletteIndex(cube.R1, cube.G1, position, cube.A1)] + - moment[GetPaletteIndex(cube.R1, cube.G1, position, cube.A0)] + - moment[GetPaletteIndex(cube.R1, cube.G0, position, cube.A1)] + + moment[GetPaletteIndex(cube.R1, cube.G0, position, cube.A0)] + - moment[GetPaletteIndex(cube.R0, cube.G1, position, cube.A1)] + + moment[GetPaletteIndex(cube.R0, cube.G1, position, cube.A0)] + + moment[GetPaletteIndex(cube.R0, cube.G0, position, cube.A1)] + - moment[GetPaletteIndex(cube.R0, cube.G0, position, cube.A0)]; + + // Alpha + case 3: + return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, position)] + - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, position)] + - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, position)] + + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, position)] + - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, position)] + + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, position)] + + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, position)] + - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, position)]; + + default: + throw new ArgumentOutOfRangeException(nameof(direction)); + } + } + + /// + /// Clears the tables. + /// + private void Clear() + { + Array.Clear(this.vwt, 0, TableLength); + Array.Clear(this.vmr, 0, TableLength); + Array.Clear(this.vmg, 0, TableLength); + Array.Clear(this.vmb, 0, TableLength); + Array.Clear(this.vma, 0, TableLength); + Array.Clear(this.m2, 0, TableLength); + + Array.Clear(this.tag, 0, TableLength); + } + + /// + /// Builds a 3-D color histogram of counts, r/g/b, c^2. + /// + /// The pixel format. + /// The packed format. long, float. + /// The pixel accessor. + private void Build3DHistogram(IPixelAccessor pixels) + where T : IPackedVector, new() + where TP : struct + { + for (int y = 0; y < pixels.Height; y++) + { + for (int x = 0; x < pixels.Width; x++) + { + // Colors are expected in r->g->b->a format + byte[] color = pixels[x, y].ToBytes(); + + 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); + int inb = b >> (8 - IndexBits); + int ina = a >> (8 - IndexAlphaBits); + + int ind = GetPaletteIndex(inr + 1, ing + 1, inb + 1, ina + 1); + + this.vwt[ind]++; + this.vmr[ind] += r; + this.vmg[ind] += g; + this.vmb[ind] += b; + this.vma[ind] += a; + this.m2[ind] += (r * r) + (g * g) + (b * b) + (a * a); + } + } + } + + /// + /// Converts the histogram into moments so that we can rapidly calculate + /// the sums of the above quantities over any desired box. + /// + private void Get3DMoments() + { + long[] volume = new long[IndexCount * IndexAlphaCount]; + long[] volumeR = new long[IndexCount * IndexAlphaCount]; + long[] volumeG = new long[IndexCount * IndexAlphaCount]; + long[] volumeB = new long[IndexCount * IndexAlphaCount]; + long[] volumeA = new long[IndexCount * IndexAlphaCount]; + double[] volume2 = new double[IndexCount * IndexAlphaCount]; + + long[] area = new long[IndexAlphaCount]; + long[] areaR = new long[IndexAlphaCount]; + long[] areaG = new long[IndexAlphaCount]; + long[] areaB = new long[IndexAlphaCount]; + long[] areaA = new long[IndexAlphaCount]; + double[] area2 = new double[IndexAlphaCount]; + + for (int r = 1; r < IndexCount; r++) + { + Array.Clear(volume, 0, IndexCount * IndexAlphaCount); + Array.Clear(volumeR, 0, IndexCount * IndexAlphaCount); + Array.Clear(volumeG, 0, IndexCount * IndexAlphaCount); + Array.Clear(volumeB, 0, IndexCount * IndexAlphaCount); + Array.Clear(volumeA, 0, IndexCount * IndexAlphaCount); + Array.Clear(volume2, 0, IndexCount * IndexAlphaCount); + + for (int g = 1; g < IndexCount; g++) + { + Array.Clear(area, 0, IndexAlphaCount); + Array.Clear(areaR, 0, IndexAlphaCount); + Array.Clear(areaG, 0, IndexAlphaCount); + Array.Clear(areaB, 0, IndexAlphaCount); + Array.Clear(areaA, 0, IndexAlphaCount); + Array.Clear(area2, 0, IndexAlphaCount); + + for (int b = 1; b < IndexCount; b++) + { + long line = 0; + long lineR = 0; + long lineG = 0; + long lineB = 0; + long lineA = 0; + double line2 = 0; + + for (int a = 1; a < IndexAlphaCount; a++) + { + int ind1 = GetPaletteIndex(r, g, b, a); + + line += this.vwt[ind1]; + lineR += this.vmr[ind1]; + lineG += this.vmg[ind1]; + lineB += this.vmb[ind1]; + lineA += this.vma[ind1]; + line2 += this.m2[ind1]; + + area[a] += line; + areaR[a] += lineR; + areaG[a] += lineG; + areaB[a] += lineB; + areaA[a] += lineA; + area2[a] += line2; + + int inv = (b * IndexAlphaCount) + a; + + volume[inv] += area[a]; + volumeR[inv] += areaR[a]; + volumeG[inv] += areaG[a]; + volumeB[inv] += areaB[a]; + volumeA[inv] += areaA[a]; + volume2[inv] += area2[a]; + + int ind2 = ind1 - GetPaletteIndex(1, 0, 0, 0); + + this.vwt[ind1] = this.vwt[ind2] + volume[inv]; + this.vmr[ind1] = this.vmr[ind2] + volumeR[inv]; + this.vmg[ind1] = this.vmg[ind2] + volumeG[inv]; + this.vmb[ind1] = this.vmb[ind2] + volumeB[inv]; + this.vma[ind1] = this.vma[ind2] + volumeA[inv]; + this.m2[ind1] = this.m2[ind2] + volume2[inv]; + } + } + } + } + } + + /// + /// Computes the weighted variance of a box cube. + /// + /// The cube. + /// The . + private double Variance(Box cube) + { + double dr = Volume(cube, this.vmr); + double dg = Volume(cube, this.vmg); + double db = Volume(cube, this.vmb); + double da = Volume(cube, this.vma); + + double xx = + this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] + - this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] + - this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] + + this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] + - this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] + + this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] + + this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] + - this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] + - this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)] + + this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] + + this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] + - this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] + + this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] + - this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] + - this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] + + this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; + + return xx - (((dr * dr) + (dg * dg) + (db * db) + (da * da)) / Volume(cube, this.vwt)); + } + + /// + /// We want to minimize the sum of the variances of two sub-boxes. + /// The sum(c^2) terms can be ignored since their sum over both sub-boxes + /// is the same (the sum for the whole box) no matter where we split. + /// The remaining terms have a minus sign in the variance formula, + /// so we drop the minus sign and maximize the sum of the two terms. + /// + /// The cube. + /// The direction. + /// The first position. + /// The last position. + /// The cutting point. + /// The whole red. + /// The whole green. + /// The whole blue. + /// The whole alpha. + /// The whole weight. + /// The . + private double Maximize(Box cube, int direction, int first, int last, out int cut, double wholeR, double wholeG, double wholeB, double wholeA, double wholeW) + { + long baseR = Bottom(cube, direction, this.vmr); + long baseG = Bottom(cube, direction, this.vmg); + long baseB = Bottom(cube, direction, this.vmb); + long baseA = Bottom(cube, direction, this.vma); + long baseW = Bottom(cube, direction, this.vwt); + + double max = 0.0; + cut = -1; + + for (int i = first; i < last; i++) + { + double halfR = baseR + Top(cube, direction, i, this.vmr); + double halfG = baseG + Top(cube, direction, i, this.vmg); + double halfB = baseB + Top(cube, direction, i, this.vmb); + double halfA = baseA + Top(cube, direction, i, this.vma); + double halfW = baseW + Top(cube, direction, i, this.vwt); + + double temp; + + if (Math.Abs(halfW) < Epsilon) + { + continue; + } + + temp = ((halfR * halfR) + (halfG * halfG) + (halfB * halfB) + (halfA * halfA)) / halfW; + + halfR = wholeR - halfR; + halfG = wholeG - halfG; + halfB = wholeB - halfB; + halfA = wholeA - halfA; + halfW = wholeW - halfW; + + if (Math.Abs(halfW) < Epsilon) + { + continue; + } + + temp += ((halfR * halfR) + (halfG * halfG) + (halfB * halfB) + (halfA * halfA)) / halfW; + + if (temp > max) + { + max = temp; + cut = i; + } + } + + return max; + } + + /// + /// Cuts a box. + /// + /// The first set. + /// The second set. + /// Returns a value indicating whether the box has been split. + private bool Cut(Box set1, Box set2) + { + double wholeR = Volume(set1, this.vmr); + double wholeG = Volume(set1, this.vmg); + double wholeB = Volume(set1, this.vmb); + double wholeA = Volume(set1, this.vma); + double wholeW = Volume(set1, this.vwt); + + int cutr; + int cutg; + int cutb; + int cuta; + + double maxr = this.Maximize(set1, 0, set1.R0 + 1, set1.R1, out cutr, wholeR, wholeG, wholeB, wholeA, wholeW); + double maxg = this.Maximize(set1, 1, set1.G0 + 1, set1.G1, out cutg, wholeR, wholeG, wholeB, wholeA, wholeW); + double maxb = this.Maximize(set1, 2, set1.B0 + 1, set1.B1, out cutb, wholeR, wholeG, wholeB, wholeA, wholeW); + double maxa = this.Maximize(set1, 3, set1.A0 + 1, set1.A1, out cuta, wholeR, wholeG, wholeB, wholeA, wholeW); + + int dir; + + if ((maxr >= maxg) && (maxr >= maxb) && (maxr >= maxa)) + { + dir = 0; + + if (cutr < 0) + { + return false; + } + } + else if ((maxg >= maxr) && (maxg >= maxb) && (maxg >= maxa)) + { + dir = 1; + } + else if ((maxb >= maxr) && (maxb >= maxg) && (maxb >= maxa)) + { + dir = 2; + } + else + { + dir = 3; + } + + set2.R1 = set1.R1; + set2.G1 = set1.G1; + set2.B1 = set1.B1; + set2.A1 = set1.A1; + + switch (dir) + { + // Red + case 0: + set2.R0 = set1.R1 = cutr; + set2.G0 = set1.G0; + set2.B0 = set1.B0; + set2.A0 = set1.A0; + break; + + // Green + case 1: + set2.G0 = set1.G1 = cutg; + set2.R0 = set1.R0; + set2.B0 = set1.B0; + set2.A0 = set1.A0; + break; + + // Blue + case 2: + set2.B0 = set1.B1 = cutb; + set2.R0 = set1.R0; + set2.G0 = set1.G0; + set2.A0 = set1.A0; + break; + + // Alpha + case 3: + set2.A0 = set1.A1 = cuta; + set2.R0 = set1.R0; + set2.G0 = set1.G0; + set2.B0 = set1.B0; + break; + } + + set1.Volume = (set1.R1 - set1.R0) * (set1.G1 - set1.G0) * (set1.B1 - set1.B0) * (set1.A1 - set1.A0); + set2.Volume = (set2.R1 - set2.R0) * (set2.G1 - set2.G0) * (set2.B1 - set2.B0) * (set2.A1 - set2.A0); + + return true; + } + + /// + /// Marks a color space tag. + /// + /// The cube. + /// A label. + private void Mark(Box cube, byte label) + { + for (int r = cube.R0 + 1; r <= cube.R1; r++) + { + for (int g = cube.G0 + 1; g <= cube.G1; g++) + { + for (int b = cube.B0 + 1; b <= cube.B1; b++) + { + for (int a = cube.A0 + 1; a <= cube.A1; a++) + { + this.tag[GetPaletteIndex(r, g, b, a)] = label; + } + } + } + } + } + + /// + /// Builds the cube. + /// + /// The cube. + /// The color count. + private void BuildCube(out Box[] cube, ref int colorCount) + { + cube = new Box[colorCount]; + double[] vv = new double[colorCount]; + + for (int i = 0; i < colorCount; i++) + { + cube[i] = new Box(); + } + + cube[0].R0 = cube[0].G0 = cube[0].B0 = cube[0].A0 = 0; + cube[0].R1 = cube[0].G1 = cube[0].B1 = IndexCount - 1; + cube[0].A1 = IndexAlphaCount - 1; + + int next = 0; + + for (int i = 1; i < colorCount; i++) + { + if (this.Cut(cube[next], cube[i])) + { + vv[next] = cube[next].Volume > 1 ? this.Variance(cube[next]) : 0.0; + vv[i] = cube[i].Volume > 1 ? this.Variance(cube[i]) : 0.0; + } + else + { + vv[next] = 0.0; + i--; + } + + next = 0; + + double temp = vv[0]; + for (int k = 1; k <= i; k++) + { + if (vv[k] > temp) + { + temp = vv[k]; + next = k; + } + } + + if (temp <= 0.0) + { + colorCount = i + 1; + break; + } + } + } + + /// + /// Generates the quantized result. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image pixels. + /// The color count. + /// The cube. + /// The result. + private QuantizedImage GenerateResult(IPixelAccessor imagePixels, int colorCount, Box[] cube) + where T : IPackedVector, new() + where TP : struct + { + List pallette = new List(); + byte[] pixels = new byte[imagePixels.Width * imagePixels.Height]; + int transparentIndex = -1; + int width = imagePixels.Width; + int height = imagePixels.Height; + + for (int k = 0; k < colorCount; k++) + { + this.Mark(cube[k], (byte)k); + + double weight = Volume(cube[k], this.vwt); + + if (Math.Abs(weight) > Epsilon) + { + byte r = (byte)(Volume(cube[k], this.vmr) / weight); + byte g = (byte)(Volume(cube[k], this.vmg) / weight); + byte b = (byte)(Volume(cube[k], this.vmb) / weight); + byte a = (byte)(Volume(cube[k], this.vma) / weight); + + T color = default(T); + color.PackBytes(r, g, b, a); + + if (color.Equals(default(T))) + { + transparentIndex = k; + } + + pallette.Add(color); + } + else + { + pallette.Add(default(T)); + transparentIndex = k; + } + } + + Parallel.For( + 0, + height, + Bootstrapper.Instance.ParallelOptions, + y => + { + for (int x = 0; x < width; x++) + { + // 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; + } + + int ind = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); + pixels[(y * width) + x] = this.tag[ind]; + } + }); + + + return new QuantizedImage(width, height, pallette.ToArray(), pixels, transparentIndex); + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/FileTestBase.cs b/tests/ImageProcessorCore.Tests/FileTestBase.cs index 05136a1c87..280e0e5120 100644 --- a/tests/ImageProcessorCore.Tests/FileTestBase.cs +++ b/tests/ImageProcessorCore.Tests/FileTestBase.cs @@ -28,7 +28,7 @@ namespace ImageProcessorCore.Tests // "TestImages/Formats/Bmp/neg_height.bmp", // Perf: Enable for local testing only //"TestImages/Formats/Png/blur.png", // Perf: Enable for local testing only //"TestImages/Formats/Png/indexed.png", // Perf: Enable for local testing only - //"TestImages/Formats/Png/splash.png", + "TestImages/Formats/Png/splash.png", //"TestImages/Formats/Gif/rings.gif", //"TestImages/Formats/Gif/giphy.gif" // Perf: Enable for local testing only }; From 7b9fa020eb08f66defa90aedd60c184fad40d335 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 17 Jul 2016 10:32:00 +1000 Subject: [PATCH 35/91] Ensure quality is carried across. Former-commit-id: 6069f3358b046e27a7aa3e56ac8a11cb6684b7e7 Former-commit-id: 34bf7b98f19e05db77daa5b7fc57020c3e563b08 Former-commit-id: 0b28b45eaa0450d775062c0be210856c57d96e7f --- src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs | 12 +++++------- src/ImageProcessorCore/Image/Image.cs | 1 + src/ImageProcessorCore/Image/ImageExtensions.cs | 1 + 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs b/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs index 15ed7793a2..ab9057760f 100644 --- a/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs @@ -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. /// @@ -237,7 +232,10 @@ namespace ImageProcessorCore.Formats 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; @@ -337,7 +335,7 @@ namespace ImageProcessorCore.Formats Parallel.For( 0, imageHeight, - //Bootstrapper.Instance.ParallelOptions, + Bootstrapper.Instance.ParallelOptions, y => { int dataOffset = (y * rowLength); diff --git a/src/ImageProcessorCore/Image/Image.cs b/src/ImageProcessorCore/Image/Image.cs index 2c7ceeab15..f4c3ac6c6a 100644 --- a/src/ImageProcessorCore/Image/Image.cs +++ b/src/ImageProcessorCore/Image/Image.cs @@ -85,6 +85,7 @@ namespace ImageProcessorCore } } + this.Quality = other.Quality; this.RepeatCount = other.RepeatCount; this.HorizontalResolution = other.HorizontalResolution; this.VerticalResolution = other.VerticalResolution; diff --git a/src/ImageProcessorCore/Image/ImageExtensions.cs b/src/ImageProcessorCore/Image/ImageExtensions.cs index 67b1263189..6049086033 100644 --- a/src/ImageProcessorCore/Image/ImageExtensions.cs +++ b/src/ImageProcessorCore/Image/ImageExtensions.cs @@ -154,6 +154,7 @@ namespace ImageProcessorCore { // Several properties require copying // TODO: Check why we need to set these? + Quality = source.Quality, HorizontalResolution = source.HorizontalResolution, VerticalResolution = source.VerticalResolution, CurrentImageFormat = source.CurrentImageFormat, From 08d0581bc8e903dd66e6ed96f8d8a82a64112c8e Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Mon, 18 Jul 2016 02:43:47 +0200 Subject: [PATCH 36/91] use the right nuget package that contains the fix + namespace fix Former-commit-id: 3203aa870f85a72005d09c814151abdb760fca8c Former-commit-id: 38a7036b1a7aecd46879ac2d64b1cd099a6e34db Former-commit-id: 23721bcd71dc78358aa04c10525a8f1af2a6af65 --- tests/ImageProcessorCore.Benchmarks/Config.cs | 4 ++-- tests/ImageProcessorCore.Benchmarks/project.json | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/ImageProcessorCore.Benchmarks/Config.cs b/tests/ImageProcessorCore.Benchmarks/Config.cs index 10f377bb53..1b88533008 100644 --- a/tests/ImageProcessorCore.Benchmarks/Config.cs +++ b/tests/ImageProcessorCore.Benchmarks/Config.cs @@ -7,8 +7,8 @@ namespace ImageProcessorCore.Benchmarks public Config() { // uncomment if you want to use any of the diagnoser - //Add(new BenchmarkDotNet.Diagnostics.MemoryDiagnoser()); - //Add(new BenchmarkDotNet.Diagnostics.InliningDiagnoser()); + //Add(new BenchmarkDotNet.Diagnostics.Windows.MemoryDiagnoser()); + //Add(new BenchmarkDotNet.Diagnostics.Windows.InliningDiagnoser()); } } } \ No newline at end of file diff --git a/tests/ImageProcessorCore.Benchmarks/project.json b/tests/ImageProcessorCore.Benchmarks/project.json index 6d70e44cb5..6aabff3df9 100644 --- a/tests/ImageProcessorCore.Benchmarks/project.json +++ b/tests/ImageProcessorCore.Benchmarks/project.json @@ -13,8 +13,7 @@ "emitEntryPoint": true }, "dependencies": { - "BenchmarkDotNet": "0.9.8-develop", - "BenchmarkDotNet.Diagnostics.Windows": "0.9.8-develop", + "BenchmarkDotNet.Diagnostics.Windows": "0.9.8.78", "ImageProcessorCore": "1.0.0-*" }, "commands": { From e69bb004e4e9cd78b5f8a54e195060f73d271d81 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 18 Jul 2016 11:16:56 +1000 Subject: [PATCH 37/91] Use ParallelOptions everywhere. Former-commit-id: 8bba0d7876f26688f1b7107107f3d5423f590cb5 Former-commit-id: cd6dc1e39f39c584ef1e75cc30f64f714a30b5ef Former-commit-id: 82b0d1b2dce9d947518ea330cc0957aa9b0f084a --- src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs index c964dec48a..ebf56ef859 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs @@ -222,6 +222,7 @@ namespace ImageProcessorCore.Formats Parallel.For( 0, height, + Bootstrapper.Instance.ParallelOptions, y => { int rowOffset = y * (arrayWidth + alignment); @@ -272,6 +273,7 @@ namespace ImageProcessorCore.Formats Parallel.For( 0, height, + Bootstrapper.Instance.ParallelOptions, y => { int rowOffset = y * ((width * 2) + alignment); @@ -318,6 +320,7 @@ namespace ImageProcessorCore.Formats Parallel.For( 0, height, + Bootstrapper.Instance.ParallelOptions, y => { int rowOffset = y * ((width * 3) + alignment); @@ -358,6 +361,7 @@ namespace ImageProcessorCore.Formats Parallel.For( 0, height, + Bootstrapper.Instance.ParallelOptions, y => { int rowOffset = y * ((width * 4) + alignment); From 8033627644b4e91b5584644b4961b8fb5b3d3f7d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 18 Jul 2016 11:17:12 +1000 Subject: [PATCH 38/91] Bicubic comment Former-commit-id: ec9b6c9a7f64ff089245421b95215ea3eec621be Former-commit-id: 9c0bf865af4b584eb7386ae85a75e7937efbd43d Former-commit-id: 69ca3cdf66e446ee9d72766f910eddda4fd1387d --- src/ImageProcessorCore/Samplers/Resamplers/BicubicResampler.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ImageProcessorCore/Samplers/Resamplers/BicubicResampler.cs b/src/ImageProcessorCore/Samplers/Resamplers/BicubicResampler.cs index 33120b0663..752c12d6d6 100644 --- a/src/ImageProcessorCore/Samplers/Resamplers/BicubicResampler.cs +++ b/src/ImageProcessorCore/Samplers/Resamplers/BicubicResampler.cs @@ -30,10 +30,12 @@ namespace ImageProcessorCore if (x <= 1F) { + // 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 < 2F) { + // 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; } From 87018d0c9947c376ffdd2679909a57a70731adf8 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 18 Jul 2016 11:23:10 +1000 Subject: [PATCH 39/91] Remove new() type constraint Former-commit-id: 60b0e004f0517fe6ffd7b0f2aef1d196ed3ef9fd Former-commit-id: e42b121f44034c0d82ffdeac627cd0d214eb6780 Former-commit-id: 792ae7427d0028d2a012b100259e7ee72e1fc224 --- src/ImageProcessorCore/Bootstrapper.cs | 2 +- .../Formats/Bmp/BmpDecoder.cs | 2 +- .../Formats/Bmp/BmpDecoderCore.cs | 10 ++++----- .../Formats/Bmp/BmpEncoder.cs | 2 +- .../Formats/Bmp/BmpEncoderCore.cs | 8 +++---- .../Formats/IImageDecoder.cs | 2 +- .../Formats/IImageEncoder.cs | 22 ++++++++----------- .../Formats/Png/GrayscaleReader.cs | 2 +- .../Formats/Png/IColorReader.cs | 2 +- .../Formats/Png/PaletteIndexReader.cs | 2 +- .../Formats/Png/PngDecoder.cs | 2 +- .../Formats/Png/PngDecoderCore.cs | 8 +++---- .../Formats/Png/PngEncoder.cs | 2 +- .../Formats/Png/PngEncoderCore.cs | 8 +++---- .../Formats/Png/TrueColorReader.cs | 2 +- src/ImageProcessorCore/Image/IImageBase.cs | 2 +- src/ImageProcessorCore/Image/IImageFrame.cs | 2 +- .../Image/IImageProcessor.cs | 6 ++--- src/ImageProcessorCore/Image/Image.cs | 2 +- src/ImageProcessorCore/Image/ImageBase.cs | 2 +- .../Image/ImageExtensions.cs | 10 ++++----- src/ImageProcessorCore/Image/ImageFrame.cs | 2 +- src/ImageProcessorCore/ImageProcessor.cs | 10 ++++----- .../PixelAccessor/IPixelAccessor.cs | 2 +- .../Quantizers/IQuantizer.cs | 2 +- .../Quantizers/QuantizedImage.cs | 2 +- .../Quantizers/Wu/WuQuantizer.cs | 6 ++--- .../Samplers/Options/ResizeHelper.cs | 12 +++++----- src/ImageProcessorCore/Samplers/Resize.cs | 10 ++++----- 29 files changed, 71 insertions(+), 75 deletions(-) diff --git a/src/ImageProcessorCore/Bootstrapper.cs b/src/ImageProcessorCore/Bootstrapper.cs index a1e91e386f..56eee634e3 100644 --- a/src/ImageProcessorCore/Bootstrapper.cs +++ b/src/ImageProcessorCore/Bootstrapper.cs @@ -80,7 +80,7 @@ namespace ImageProcessorCore /// The image /// The public IPixelAccessor GetPixelAccessor(IImageBase image) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { Type packed = typeof(T); diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs index 48ee205f95..e61f049fc3 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs @@ -75,7 +75,7 @@ namespace ImageProcessorCore.Formats /// The to decode to. /// The containing image data. public void Decode(Image image, Stream stream) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { new BmpDecoderCore().Decode(image, stream); diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs index ebf56ef859..645ddbac68 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs @@ -61,7 +61,7 @@ namespace ImageProcessorCore.Formats /// is null. /// public void Decode(Image image, Stream stream) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { this.currentStream = stream; @@ -197,7 +197,7 @@ namespace ImageProcessorCore.Formats /// The number of bits per pixel. /// Whether the bitmap is inverted. private void ReadRgbPalette(T[] imageData, byte[] colors, int width, int height, int bits, bool inverted) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { // Pixels per byte (bits per pixel) @@ -260,7 +260,7 @@ namespace ImageProcessorCore.Formats /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRgb16(T[] imageData, int width, int height, bool inverted) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { // We divide here as we will store the colors in our floating point format. @@ -311,7 +311,7 @@ namespace ImageProcessorCore.Formats /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRgb24(T[] imageData, int width, int height, bool inverted) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { int alignment; @@ -352,7 +352,7 @@ namespace ImageProcessorCore.Formats /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRgb32(T[] imageData, int width, int height, bool inverted) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { int alignment; diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs index 07ada9c05a..0d95591435 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs @@ -44,7 +44,7 @@ namespace ImageProcessorCore.Formats /// public void Encode(ImageBase image, Stream stream) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { BmpEncoderCore encoder = new BmpEncoderCore(); diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs index 497afc70c4..f193aa598c 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs @@ -29,7 +29,7 @@ namespace ImageProcessorCore.Formats /// The to encode the image data to. /// The public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { Guard.NotNull(image, nameof(image)); @@ -129,7 +129,7 @@ namespace ImageProcessorCore.Formats /// The containing pixel data. /// private void WriteImage(EndianBinaryWriter writer, ImageBase image) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { // TODO: Add more compression formats. @@ -163,7 +163,7 @@ namespace ImageProcessorCore.Formats /// The containing pixel data. /// The amount to pad each row by. private void Write32bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { for (int y = pixels.Height - 1; y >= 0; y--) @@ -191,7 +191,7 @@ namespace ImageProcessorCore.Formats /// The containing pixel data. /// The amount to pad each row by. private void Write24bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { for (int y = pixels.Height - 1; y >= 0; y--) diff --git a/src/ImageProcessorCore/Formats/IImageDecoder.cs b/src/ImageProcessorCore/Formats/IImageDecoder.cs index 09dbcdb1e7..5b270cb6d2 100644 --- a/src/ImageProcessorCore/Formats/IImageDecoder.cs +++ b/src/ImageProcessorCore/Formats/IImageDecoder.cs @@ -45,7 +45,7 @@ namespace ImageProcessorCore.Formats /// The to decode to. /// The containing image data. void Decode(Image image, Stream stream) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct; } } diff --git a/src/ImageProcessorCore/Formats/IImageEncoder.cs b/src/ImageProcessorCore/Formats/IImageEncoder.cs index 7738968631..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,13 +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 type of pixels contained within the image. - /// 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) - where T : IPackedVector, - new() where TP : struct; + where T : IPackedVector + where TP : struct; } } diff --git a/src/ImageProcessorCore/Formats/Png/GrayscaleReader.cs b/src/ImageProcessorCore/Formats/Png/GrayscaleReader.cs index f8884ae439..780f5f6241 100644 --- a/src/ImageProcessorCore/Formats/Png/GrayscaleReader.cs +++ b/src/ImageProcessorCore/Formats/Png/GrayscaleReader.cs @@ -34,7 +34,7 @@ namespace ImageProcessorCore.Formats /// public void ReadScanline(byte[] scanline, T[] pixels, PngHeader header) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { int offset; diff --git a/src/ImageProcessorCore/Formats/Png/IColorReader.cs b/src/ImageProcessorCore/Formats/Png/IColorReader.cs index c28dd3c055..88ce0d4d26 100644 --- a/src/ImageProcessorCore/Formats/Png/IColorReader.cs +++ b/src/ImageProcessorCore/Formats/Png/IColorReader.cs @@ -23,7 +23,7 @@ namespace ImageProcessorCore.Formats /// the width of the image and the height. /// void ReadScanline(byte[] scanline, T[] pixels, PngHeader header) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct; } } diff --git a/src/ImageProcessorCore/Formats/Png/PaletteIndexReader.cs b/src/ImageProcessorCore/Formats/Png/PaletteIndexReader.cs index b7e1f2cfb2..9fba4f0e21 100644 --- a/src/ImageProcessorCore/Formats/Png/PaletteIndexReader.cs +++ b/src/ImageProcessorCore/Formats/Png/PaletteIndexReader.cs @@ -40,7 +40,7 @@ namespace ImageProcessorCore.Formats /// public void ReadScanline(byte[] scanline, T[] pixels, PngHeader header) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { byte[] newScanline = scanline.ToArrayByBitsLength(header.BitDepth); diff --git a/src/ImageProcessorCore/Formats/Png/PngDecoder.cs b/src/ImageProcessorCore/Formats/Png/PngDecoder.cs index d77e46e1e8..0ce7c3c887 100644 --- a/src/ImageProcessorCore/Formats/Png/PngDecoder.cs +++ b/src/ImageProcessorCore/Formats/Png/PngDecoder.cs @@ -80,7 +80,7 @@ namespace ImageProcessorCore.Formats /// The to decode to. /// The containing image data. public void Decode(Image image, Stream stream) - where T : IPackedVector, new() + 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 3cf7fabf48..777a8e7664 100644 --- a/src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs +++ b/src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs @@ -76,7 +76,7 @@ namespace ImageProcessorCore.Formats /// Thrown if the image is larger than the maximum allowable size. /// public void Decode(Image image, Stream stream) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { Image currentImage = image; @@ -194,7 +194,7 @@ namespace ImageProcessorCore.Formats /// The image to read to. /// The data containing physical data. private void ReadPhysicalChunk(Image image, byte[] data) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { Array.Reverse(data, 0, 4); @@ -249,7 +249,7 @@ namespace ImageProcessorCore.Formats /// The color reader. /// The color type information. private void ReadScanlines(MemoryStream dataStream, T[] pixels, IColorReader colorReader, PngColorTypeInformation colorTypeInformation) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { dataStream.Position = 0; @@ -332,7 +332,7 @@ namespace ImageProcessorCore.Formats /// The image to decode to. /// The containing data. private void ReadTextChunk(Image image, byte[] data) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { int zeroIndex = 0; diff --git a/src/ImageProcessorCore/Formats/Png/PngEncoder.cs b/src/ImageProcessorCore/Formats/Png/PngEncoder.cs index 5e34bfa881..43b7aa5545 100644 --- a/src/ImageProcessorCore/Formats/Png/PngEncoder.cs +++ b/src/ImageProcessorCore/Formats/Png/PngEncoder.cs @@ -68,7 +68,7 @@ namespace ImageProcessorCore.Formats /// public void Encode(ImageBase image, Stream stream) - where T : IPackedVector, new() + 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 ab9057760f..a3bf2cafb1 100644 --- a/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs @@ -70,7 +70,7 @@ namespace ImageProcessorCore.Formats /// The to encode from. /// The to encode the image data to. public void Encode(ImageBase image, Stream stream) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { Guard.NotNull(image, nameof(image)); @@ -208,7 +208,7 @@ namespace ImageProcessorCore.Formats /// The . /// The image to encode. private QuantizedImage WritePaletteChunk(Stream stream, PngHeader header, ImageBase image) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { if (this.Quality > 256) @@ -266,7 +266,7 @@ namespace ImageProcessorCore.Formats /// The containing image data. /// The image base. private void WritePhysicalChunk(Stream stream, ImageBase imageBase) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { Image image = imageBase as Image; @@ -319,7 +319,7 @@ namespace ImageProcessorCore.Formats /// The image pixels. /// The quantized image. private void WriteDataChunks(Stream stream, IPixelAccessor pixels, QuantizedImage quantized) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { byte[] data; diff --git a/src/ImageProcessorCore/Formats/Png/TrueColorReader.cs b/src/ImageProcessorCore/Formats/Png/TrueColorReader.cs index 0fd135c4aa..07ed958554 100644 --- a/src/ImageProcessorCore/Formats/Png/TrueColorReader.cs +++ b/src/ImageProcessorCore/Formats/Png/TrueColorReader.cs @@ -33,7 +33,7 @@ namespace ImageProcessorCore.Formats /// public void ReadScanline(byte[] scanline, T[] pixels, PngHeader header) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { int offset; diff --git a/src/ImageProcessorCore/Image/IImageBase.cs b/src/ImageProcessorCore/Image/IImageBase.cs index a168f91cc6..3a4444b5e9 100644 --- a/src/ImageProcessorCore/Image/IImageBase.cs +++ b/src/ImageProcessorCore/Image/IImageBase.cs @@ -13,7 +13,7 @@ namespace ImageProcessorCore /// The pixel format. /// The packed format. long, float. public interface IImageBase : IImageBase - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { /// diff --git a/src/ImageProcessorCore/Image/IImageFrame.cs b/src/ImageProcessorCore/Image/IImageFrame.cs index 6e51a83003..ad5e346943 100644 --- a/src/ImageProcessorCore/Image/IImageFrame.cs +++ b/src/ImageProcessorCore/Image/IImageFrame.cs @@ -11,7 +11,7 @@ namespace ImageProcessorCore /// The pixel format. /// The packed format. long, float. public interface IImageFrame : IImageBase - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { } diff --git a/src/ImageProcessorCore/Image/IImageProcessor.cs b/src/ImageProcessorCore/Image/IImageProcessor.cs index 0b07dca046..0164077c5a 100644 --- a/src/ImageProcessorCore/Image/IImageProcessor.cs +++ b/src/ImageProcessorCore/Image/IImageProcessor.cs @@ -54,8 +54,8 @@ namespace ImageProcessorCore.Processors /// doesnt fit the dimension of the image. /// void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) - where T : IPackedVector, - new() where TP : struct; + where T : IPackedVector + where TP : struct; /// /// Applies the process to the specified portion of the specified at the specified @@ -79,7 +79,7 @@ namespace ImageProcessorCore.Processors /// the result of image process as new image. /// void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct; } } diff --git a/src/ImageProcessorCore/Image/Image.cs b/src/ImageProcessorCore/Image/Image.cs index f4c3ac6c6a..748fada558 100644 --- a/src/ImageProcessorCore/Image/Image.cs +++ b/src/ImageProcessorCore/Image/Image.cs @@ -20,7 +20,7 @@ namespace ImageProcessorCore /// The pixel format. /// The packed format. long, float. public class Image : ImageBase - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { /// diff --git a/src/ImageProcessorCore/Image/ImageBase.cs b/src/ImageProcessorCore/Image/ImageBase.cs index 2ec1538b74..3a7a8f99a9 100644 --- a/src/ImageProcessorCore/Image/ImageBase.cs +++ b/src/ImageProcessorCore/Image/ImageBase.cs @@ -14,7 +14,7 @@ namespace ImageProcessorCore /// The pixel format. /// The packed format. long, float. public abstract class ImageBase : IImageBase - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { /// diff --git a/src/ImageProcessorCore/Image/ImageExtensions.cs b/src/ImageProcessorCore/Image/ImageExtensions.cs index 6049086033..544c346596 100644 --- a/src/ImageProcessorCore/Image/ImageExtensions.cs +++ b/src/ImageProcessorCore/Image/ImageExtensions.cs @@ -63,7 +63,7 @@ namespace ImageProcessorCore /// The processor to apply to the image. /// The . public static Image Process(this Image source, IImageProcessor processor) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { return Process(source, source.Bounds, processor); @@ -82,7 +82,7 @@ namespace ImageProcessorCore /// The processors to apply to the image. /// The . public static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { return PerformAction(source, true, (sourceImage, targetImage) => processor.Apply(targetImage, sourceImage, sourceRectangle)); @@ -102,7 +102,7 @@ namespace ImageProcessorCore /// The processor to apply to the image. /// The . public static Image Process(this Image source, int width, int height, IImageSampler sampler) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { return Process(source, width, height, source.Bounds, default(Rectangle), sampler); @@ -129,7 +129,7 @@ namespace ImageProcessorCore /// 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) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { return PerformAction(source, false, (sourceImage, targetImage) => sampler.Apply(targetImage, sourceImage, width, height, targetRectangle, sourceRectangle)); @@ -145,7 +145,7 @@ namespace ImageProcessorCore /// The to perform against the image. /// The . private static Image PerformAction(Image source, bool clone, Action, ImageBase> action) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { Image transformedImage = clone diff --git a/src/ImageProcessorCore/Image/ImageFrame.cs b/src/ImageProcessorCore/Image/ImageFrame.cs index 4afb12a947..9b83029d28 100644 --- a/src/ImageProcessorCore/Image/ImageFrame.cs +++ b/src/ImageProcessorCore/Image/ImageFrame.cs @@ -11,7 +11,7 @@ namespace ImageProcessorCore /// The pixel format. /// The packed format. long, float. public class ImageFrame : ImageBase - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { /// diff --git a/src/ImageProcessorCore/ImageProcessor.cs b/src/ImageProcessorCore/ImageProcessor.cs index 211bbd5038..ae0b239873 100644 --- a/src/ImageProcessorCore/ImageProcessor.cs +++ b/src/ImageProcessorCore/ImageProcessor.cs @@ -32,7 +32,7 @@ namespace ImageProcessorCore.Processors /// public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { try @@ -55,7 +55,7 @@ namespace ImageProcessorCore.Processors /// public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { try @@ -104,7 +104,7 @@ namespace ImageProcessorCore.Processors /// The structure that specifies the portion of the image object to draw. /// protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { } @@ -131,7 +131,7 @@ namespace ImageProcessorCore.Processors /// the result of image process as new image. /// protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct; /// @@ -149,7 +149,7 @@ namespace ImageProcessorCore.Processors /// The structure that specifies the portion of the image object to draw. /// protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { } diff --git a/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs index 15cd3268bd..dcc1b5b681 100644 --- a/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs +++ b/src/ImageProcessorCore/PixelAccessor/IPixelAccessor.cs @@ -13,7 +13,7 @@ namespace ImageProcessorCore /// The pixel format. /// The packed format. long, float. public interface IPixelAccessor : IPixelAccessor - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { /// diff --git a/src/ImageProcessorCore/Quantizers/IQuantizer.cs b/src/ImageProcessorCore/Quantizers/IQuantizer.cs index 3f555236a0..336582d5a3 100644 --- a/src/ImageProcessorCore/Quantizers/IQuantizer.cs +++ b/src/ImageProcessorCore/Quantizers/IQuantizer.cs @@ -26,7 +26,7 @@ namespace ImageProcessorCore.Quantizers /// A representing a quantized version of the image pixels. /// QuantizedImage Quantize(ImageBase image, int maxColors) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct; } } diff --git a/src/ImageProcessorCore/Quantizers/QuantizedImage.cs b/src/ImageProcessorCore/Quantizers/QuantizedImage.cs index ddadc099ec..123efe6533 100644 --- a/src/ImageProcessorCore/Quantizers/QuantizedImage.cs +++ b/src/ImageProcessorCore/Quantizers/QuantizedImage.cs @@ -14,7 +14,7 @@ namespace ImageProcessorCore.Quantizers /// The pixel format. /// The packed format. long, float. public class QuantizedImage - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { /// diff --git a/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs b/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs index 95e0b07728..31225f6301 100644 --- a/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs +++ b/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs @@ -116,7 +116,7 @@ namespace ImageProcessorCore.Quantizers /// public QuantizedImage Quantize(ImageBase image, int maxColors) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { Guard.NotNull(image, nameof(image)); @@ -326,7 +326,7 @@ namespace ImageProcessorCore.Quantizers /// The packed format. long, float. /// The pixel accessor. private void Build3DHistogram(IPixelAccessor pixels) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { for (int y = 0; y < pixels.Height; y++) @@ -728,7 +728,7 @@ namespace ImageProcessorCore.Quantizers /// The cube. /// The result. private QuantizedImage GenerateResult(IPixelAccessor imagePixels, int colorCount, Box[] cube) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { List pallette = new List(); diff --git a/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs b/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs index e99bfbbf0a..96065f7e58 100644 --- a/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs +++ b/src/ImageProcessorCore/Samplers/Options/ResizeHelper.cs @@ -24,7 +24,7 @@ namespace ImageProcessorCore /// The . /// public static Rectangle CalculateTargetLocationAndBounds(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { switch (options.Mode) @@ -56,7 +56,7 @@ namespace ImageProcessorCore /// The . /// private static Rectangle CalculateCropRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { int width = options.Size.Width; @@ -176,7 +176,7 @@ namespace ImageProcessorCore /// The . /// private static Rectangle CalculatePadRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { int width = options.Size.Width; @@ -258,7 +258,7 @@ namespace ImageProcessorCore /// The . /// private static Rectangle CalculateBoxPadRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { int width = options.Size.Width; @@ -346,7 +346,7 @@ namespace ImageProcessorCore /// The . /// private static Rectangle CalculateMaxRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { int width = options.Size.Width; @@ -388,7 +388,7 @@ namespace ImageProcessorCore /// The . /// private static Rectangle CalculateMinRectangle(ImageBase source, ResizeOptions options) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { int width = options.Size.Width; diff --git a/src/ImageProcessorCore/Samplers/Resize.cs b/src/ImageProcessorCore/Samplers/Resize.cs index 7a08e95bf4..8e5405870d 100644 --- a/src/ImageProcessorCore/Samplers/Resize.cs +++ b/src/ImageProcessorCore/Samplers/Resize.cs @@ -22,7 +22,7 @@ namespace ImageProcessorCore /// 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) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { // Ensure size is populated across both dimensions. @@ -52,7 +52,7 @@ namespace ImageProcessorCore /// 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) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { return Resize(source, width, height, new BicubicResampler(), false, progressHandler); @@ -70,7 +70,7 @@ namespace ImageProcessorCore /// 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) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { return Resize(source, width, height, new BicubicResampler(), compand, progressHandler); @@ -89,7 +89,7 @@ namespace ImageProcessorCore /// 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) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { return Resize(source, width, height, sampler, source.Bounds, new Rectangle(0, 0, width, height), compand, progressHandler); @@ -115,7 +115,7 @@ namespace ImageProcessorCore /// 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) - where T : IPackedVector, new() + where T : IPackedVector where TP : struct { if (width == 0 && height > 0) From f46c6ace7cc293cfe8d1f777aa5d5fcd0b46444b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 18 Jul 2016 14:41:56 +1000 Subject: [PATCH 40/91] Enable analyser [skip ci] Former-commit-id: 9bb39e691cd827ac0970beeb5c82cf78ff1654c0 Former-commit-id: cba638c99eae3467387661ba330c6a6593f93f84 Former-commit-id: 52aaec2abd2aeaaf822cb90c604b982bd43df07b --- tests/ImageProcessorCore.Benchmarks/Config.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ImageProcessorCore.Benchmarks/Config.cs b/tests/ImageProcessorCore.Benchmarks/Config.cs index 1b88533008..e2eb1f0ff9 100644 --- a/tests/ImageProcessorCore.Benchmarks/Config.cs +++ b/tests/ImageProcessorCore.Benchmarks/Config.cs @@ -6,9 +6,9 @@ namespace ImageProcessorCore.Benchmarks { public Config() { - // uncomment if you want to use any of the diagnoser - //Add(new BenchmarkDotNet.Diagnostics.Windows.MemoryDiagnoser()); - //Add(new BenchmarkDotNet.Diagnostics.Windows.InliningDiagnoser()); + // Uncomment if you want to use any of the diagnoser + this.Add(new BenchmarkDotNet.Diagnostics.Windows.MemoryDiagnoser()); + this.Add(new BenchmarkDotNet.Diagnostics.Windows.InliningDiagnoser()); } } } \ No newline at end of file From e957a690c395c9b0c7dadd4135350bc1cecdbe49 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 19 Jul 2016 00:36:55 +1000 Subject: [PATCH 41/91] Add Octree Quantizer Former-commit-id: 672dd8bce7ac2208ff9038a9b14253dd17f3a509 Former-commit-id: 3be5c82117eb583244897b2581a628e4499ee470 Former-commit-id: 51eb9045c7bb7a3e677fa35463f4ad2c64ea7f11 --- .../Formats/Png/PngEncoderCore.cs | 9 +- .../Quantizers/IQuantizer.cs | 28 +- .../Quantizers/Octree/OctreeQuantizer.cs | 536 ++++++++++++++++++ .../Quantizers/Octree/Quantizer.cs | 149 +++++ .../Quantizers/Wu/WuQuantizer.cs | 25 +- 5 files changed, 716 insertions(+), 31 deletions(-) create mode 100644 src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs create mode 100644 src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs diff --git a/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs b/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs index ab9057760f..20647f0651 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. @@ -218,11 +218,12 @@ namespace ImageProcessorCore.Formats if (this.Quantizer == null) { - this.Quantizer = new WuQuantizer { Threshold = this.Threshold }; + this.Quantizer = new WuQuantizer { Threshold = this.Threshold }; } // Quantize the image returning a palette. - QuantizedImage quantized = Quantizer.Quantize(image, this.Quality); + // TODO: This is icky. + QuantizedImage quantized = ((WuQuantizer)Quantizer).Quantize(image, this.Quality); // Grab the palette and write it to the stream. T[] palette = quantized.Palette; @@ -233,7 +234,7 @@ namespace ImageProcessorCore.Formats byte[] colorTable = new byte[colorTableLength]; Parallel.For( - 0, + 0, pixelCount, Bootstrapper.Instance.ParallelOptions, i => diff --git a/src/ImageProcessorCore/Quantizers/IQuantizer.cs b/src/ImageProcessorCore/Quantizers/IQuantizer.cs index 3f555236a0..6a8ad2cfa9 100644 --- a/src/ImageProcessorCore/Quantizers/IQuantizer.cs +++ b/src/ImageProcessorCore/Quantizers/IQuantizer.cs @@ -8,25 +8,31 @@ 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, new() + where TP : struct { - /// - /// Gets or sets the transparency threshold. - /// - byte Threshold { get; set; } - /// /// Quantize an image and return the resulting output pixels. /// - /// The pixel format. - /// The packed format. long, float. /// The image to quantize. /// The maximum number of colors to return. /// /// A representing a quantized version of the image pixels. /// - QuantizedImage Quantize(ImageBase image, int maxColors) - where T : IPackedVector, new() - where TP : struct; + 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 new file mode 100644 index 0000000000..030425c96b --- /dev/null +++ b/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs @@ -0,0 +1,536 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Quantizers +{ + using System; + using System.Collections.Generic; + + /// + /// Encapsulates methods to calculate the colour palette if an image using an Octree pattern. + /// + /// + /// The pixel format. + /// The packed format. long, float. + public sealed class OctreeQuantizer : Quantizer + where T : IPackedVector, new() + where TP : struct + { + /// + /// Stores the tree + /// + private Octree octree; + + /// + /// Maximum allowed color depth + /// + private int colors; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree, + /// the second pass quantizes a color based on the nodes in the tree + /// + public OctreeQuantizer() + : base(false) + { + } + + /// + public override QuantizedImage Quantize(ImageBase image, int maxColors) + { + this.colors = maxColors.Clamp(1, 255); + + if (this.octree == null) + { + // Construct the Octree + this.octree = new Octree(this.GetBitsNeededForColorDepth(maxColors)); + } + + return base.Quantize(image, maxColors); + } + + /// + /// Process the pixel in the first pass of the algorithm + /// + /// + /// The pixel to quantize + /// + /// + /// This function need only be overridden if your quantize algorithm needs two passes, + /// such as an Octree quantizer. + /// + protected override void InitialQuantizePixel(T pixel) + { + // Add the color to the Octree + this.octree.AddColor(pixel); + } + + /// + /// Override this to process the pixel in the second pass of the algorithm + /// + /// + /// The pixel to quantize + /// + /// + /// The quantized value + /// + 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.ToBytes()[4] > this.Threshold) + { + paletteIndex = (byte)this.octree.GetPaletteIndex(pixel); + } + + return paletteIndex; + } + + /// + /// Retrieve the palette for the quantized image. + /// + /// + /// The new color palette + /// + protected override List GetPalette() + { + // First off convert the Octree to maxColors colors + List palette = this.octree.Palletize(Math.Max(this.colors, 1)); + + palette.Add(default(T)); + this.TransparentIndex = this.colors; + + return palette; + } + + /// + /// Returns how many bits are required to store the specified number of colors. + /// Performs a Log2() on the value. + /// + /// The number of colors. + /// + /// The + /// + private int GetBitsNeededForColorDepth(int colorCount) + { + return (int)Math.Ceiling(Math.Log(colorCount, 2)); + } + + /// + /// Class which does the actual quantization + /// + private class Octree + { + /// + /// Mask used when getting the appropriate pixels for a given node + /// + private static readonly int[] Mask = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; + + /// + /// The root of the Octree + /// + private readonly OctreeNode root; + + /// + /// Array of reducible nodes + /// + private readonly OctreeNode[] reducibleNodes; + + /// + /// Maximum number of significant bits in the image + /// + private readonly int maxColorBits; + + /// + /// Store the last node quantized + /// + private OctreeNode previousNode; + + /// + /// Cache the previous color quantized + /// + private TP previousColor; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The maximum number of significant bits in the image + /// + public Octree(int maxColorBits) + { + this.maxColorBits = maxColorBits; + this.Leaves = 0; + this.reducibleNodes = new OctreeNode[9]; + this.root = new OctreeNode(0, this.maxColorBits, this); + this.previousColor = default(TP); + this.previousNode = null; + } + + /// + /// Gets or sets the number of leaves in the tree + /// + private int Leaves { get; set; } + + /// + /// Gets the array of reducible nodes + /// + private OctreeNode[] ReducibleNodes => this.reducibleNodes; + + /// + /// Add a given color value to the Octree + /// + /// + /// The containing color information to add. + /// + public void AddColor(T pixel) + { + TP packed = pixel.PackedValue(); + // Check if this request is for the same color as the last + 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 = packed; + this.root.AddColor(pixel, this.maxColorBits, 0, this); + } + else + { + // Just update the previous node + this.previousNode.Increment(pixel); + } + } + else + { + this.previousColor = packed; + this.root.AddColor(pixel, this.maxColorBits, 0, this); + } + } + + /// + /// Convert the nodes in the Octree to a palette with a maximum of colorCount colors + /// + /// + /// The maximum number of colors + /// + /// + /// An with the palletized colors + /// + public List Palletize(int colorCount) + { + while (this.Leaves > colorCount) + { + this.Reduce(); + } + + // Now palletize the nodes + List palette = new List(this.Leaves); + int paletteIndex = 0; + this.root.ConstructPalette(palette, ref paletteIndex); + + // And return the palette + return palette; + } + + /// + /// Get the palette index for the passed color + /// + /// + /// The containing the pixel data. + /// + /// + /// The index of the given structure. + /// + public int GetPaletteIndex(T pixel) + { + return this.root.GetPaletteIndex(pixel, 0); + } + + /// + /// Keep track of the previous node that was quantized + /// + /// + /// The node last quantized + /// + protected void TrackPrevious(OctreeNode node) + { + this.previousNode = node; + } + + /// + /// Reduce the depth of the tree + /// + private void Reduce() + { + // Find the deepest level containing at least one reducible node + int index = this.maxColorBits - 1; + while ((index > 0) && (this.reducibleNodes[index] == null)) + { + index--; + } + + // Reduce the node most recently added to the list at level 'index' + OctreeNode node = this.reducibleNodes[index]; + this.reducibleNodes[index] = node.NextReducible; + + // Decrement the leaf count after reducing the node + this.Leaves -= node.Reduce(); + + // And just in case I've reduced the last color to be added, and the next color to + // be added is the same, invalidate the previousNode... + this.previousNode = null; + } + + /// + /// Class which encapsulates each node in the tree + /// + protected class OctreeNode + { + /// + /// Pointers to any child nodes + /// + private readonly OctreeNode[] children; + + /// + /// Flag indicating that this is a leaf node + /// + private bool leaf; + + /// + /// Number of pixels in this node + /// + private int pixelCount; + + /// + /// Red component + /// + private int red; + + /// + /// Green Component + /// + private int green; + + /// + /// Blue component + /// + private int blue; + + /// + /// The index of this node in the palette + /// + private int paletteIndex; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The level in the tree = 0 - 7 + /// + /// + /// The number of significant color bits in the image + /// + /// + /// The tree to which this node belongs + /// + public OctreeNode(int level, int colorBits, Octree octree) + { + // Construct the new node + this.leaf = level == colorBits; + + this.red = this.green = this.blue = 0; + this.pixelCount = 0; + + // If a leaf, increment the leaf count + if (this.leaf) + { + octree.Leaves++; + this.NextReducible = null; + this.children = null; + } + else + { + // Otherwise add this to the reducible nodes + this.NextReducible = octree.ReducibleNodes[level]; + octree.ReducibleNodes[level] = this; + this.children = new OctreeNode[8]; + } + } + + /// + /// Gets the next reducible node + /// + public OctreeNode NextReducible { get; } + + /// + /// 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(T pixel, int colorBits, int level, Octree octree) + { + // Update the color information if this is a leaf + if (this.leaf) + { + this.Increment(pixel); + + // Setup the previous node + octree.TrackPrevious(this); + } + else + { + // Go to the next level down in the tree + int shift = 7 - level; + 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]; + + if (child == null) + { + // Create a new child node and store it in the array + child = new OctreeNode(level + 1, colorBits, octree); + this.children[index] = child; + } + + // Add the color to the child node + child.AddColor(pixel, colorBits, level + 1, octree); + } + } + + /// + /// Reduce this node by removing all of its children + /// + /// The number of leaves removed + public int Reduce() + { + this.red = this.green = this.blue = 0; + int childNodes = 0; + + // Loop through all children and add their information to this node + for (int index = 0; index < 8; index++) + { + if (this.children[index] != null) + { + this.red += this.children[index].red; + this.green += this.children[index].green; + this.blue += this.children[index].blue; + this.pixelCount += this.children[index].pixelCount; + ++childNodes; + this.children[index] = null; + } + } + + // Now change this to a leaf node + this.leaf = true; + + // Return the number of nodes to decrement the leaf count by + return childNodes - 1; + } + + /// + /// Traverse the tree, building up the color palette + /// + /// + /// The palette + /// + /// + /// The current palette index + /// + public void ConstructPalette(List palette, ref int index) + { + if (this.leaf) + { + // Consume the next palette index + this.paletteIndex = index++; + + byte r = (this.red / this.pixelCount).ToByte(); + byte g = (this.green / this.pixelCount).ToByte(); + byte b = (this.blue / this.pixelCount).ToByte(); + + // And set the color of the palette entry + T pixel = default(T); + pixel.PackBytes(r, g, b, 255); + palette.Add(pixel); + } + else + { + // Loop through children looking for leaves + for (int i = 0; i < 8; i++) + { + if (this.children[i] != null) + { + this.children[i].ConstructPalette(palette, ref index); + } + } + } + } + + /// + /// Return the palette index for the passed color + /// + /// + /// The representing the pixel. + /// + /// + /// The level. + /// + /// + /// The representing the index of the pixel in the palette. + /// + public int GetPaletteIndex(T pixel, int level) + { + int index = this.paletteIndex; + + if (!this.leaf) + { + int shift = 7 - level; + 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) + { + index = this.children[pixelIndex].GetPaletteIndex(pixel, level + 1); + } + else + { + throw new Exception($"Cannot retrive a pixel at the given index {pixelIndex}."); + } + } + + return index; + } + + /// + /// Increment the pixel count and add to the color information + /// + /// + /// The pixel to add. + /// + public void Increment(T pixel) + { + this.pixelCount++; + 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 new file mode 100644 index 0000000000..abacd900ef --- /dev/null +++ b/src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs @@ -0,0 +1,149 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Quantizers +{ + using System.Collections.Generic; + using System.Threading.Tasks; + + /// + /// Encapsulates methods to calculate the color palette of an image. + /// + public abstract class Quantizer : IQuantizer + where T : IPackedVector, new() + where TP : struct + { + /// + /// Flag used to indicate whether a single pass or two passes are needed for quantization. + /// + private readonly bool singlePass; + + /// + /// Initializes a new instance of the class. + /// + /// + /// If true, the quantization only needs to loop through the source pixels once + /// + /// + /// If you construct this class with a true value for singlePass, then the code will, when quantizing your image, + /// only call the 'QuantizeImage' function. If two passes are required, the code will call 'InitialQuantizeImage' + /// and then 'QuantizeImage'. + /// + protected Quantizer(bool singlePass) + { + this.singlePass = singlePass; + } + + /// + /// Gets or sets the transparency index. + /// + public int TransparentIndex { get; protected set; } = -1; + + /// + public byte Threshold { get; set; } + + /// + public virtual QuantizedImage Quantize(ImageBase image, int maxColors) + { + Guard.NotNull(image, nameof(image)); + + // Get the size of the source image + int height = image.Height; + int width = image.Width; + byte[] quantizedPixels = new byte[width * height]; + List palette; + + 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 + // all image pixels, build a data structure, and create a palette. + if (!this.singlePass) + { + 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); + } + + /// + /// Execute the first pass through the pixels in the image + /// + /// The source data + /// The width in pixels of the image. + /// The height in pixels of the image. + protected virtual void FirstPass(IPixelAccessor source, int width, int height) + { + // Loop through each row + for (int y = 0; y < height; y++) + { + // And loop through each column + for (int x = 0; x < width; x++) + { + // Now I have the pixel, call the FirstPassQuantize function... + this.InitialQuantizePixel(source[x, y]); + } + } + } + + /// + /// Execute a second pass through the bitmap + /// + /// The source image. + /// The output pixel array + /// The width in pixels of the image + /// The height in pixels of the image + 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++) + { + T sourcePixel = source[x, y]; + output[(y * source.Width) + x] = this.QuantizePixel(sourcePixel); + } + }); + } + + /// + /// Override this to process the pixel in the first pass of the algorithm + /// + /// The pixel to quantize + /// + /// This function need only be overridden if your quantize algorithm needs two passes, + /// such as an Octree quantizer. + /// + protected virtual void InitialQuantizePixel(T pixel) + { + } + + /// + /// Override this to process the pixel in the second pass of the algorithm + /// + /// The pixel to quantize + /// + /// The quantized value + /// + protected abstract byte QuantizePixel(T pixel); + + /// + /// Retrieve the palette for the quantized image + /// + /// + /// The new color palette + /// + protected abstract List GetPalette(); + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs b/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs index 95e0b07728..e9d6680f1a 100644 --- a/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs +++ b/src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs @@ -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, new() + 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,9 +119,7 @@ namespace ImageProcessorCore.Quantizers public byte Threshold { get; set; } /// - public QuantizedImage Quantize(ImageBase image, int maxColors) - where T : IPackedVector, new() - where TP : struct + public QuantizedImage Quantize(ImageBase image, int maxColors) { Guard.NotNull(image, nameof(image)); @@ -322,12 +324,8 @@ namespace ImageProcessorCore.Quantizers /// /// Builds a 3-D color histogram of counts, r/g/b, c^2. /// - /// The pixel format. - /// The packed format. long, float. /// The pixel accessor. - private void Build3DHistogram(IPixelAccessor pixels) - where T : IPackedVector, new() - where TP : struct + private void Build3DHistogram(IPixelAccessor pixels) { for (int y = 0; y < pixels.Height; y++) { @@ -721,15 +719,11 @@ namespace ImageProcessorCore.Quantizers /// /// Generates the quantized result. /// - /// The pixel format. - /// The packed format. long, float. /// The image pixels. /// The color count. /// The cube. /// The result. - private QuantizedImage GenerateResult(IPixelAccessor imagePixels, int colorCount, Box[] cube) - where T : IPackedVector, new() - where TP : struct + private QuantizedImage GenerateResult(IPixelAccessor imagePixels, int colorCount, Box[] cube) { List pallette = new List(); byte[] pixels = new byte[imagePixels.Width * imagePixels.Height]; @@ -793,7 +787,6 @@ namespace ImageProcessorCore.Quantizers } }); - return new QuantizedImage(width, height, pallette.ToArray(), pixels, transparentIndex); } } From 0aea02d86e5eeed679e671b23676d73ddc5e3a1a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 19 Jul 2016 13:02:42 +1000 Subject: [PATCH 42/91] Quality should be copied. Former-commit-id: e30a7b839d512f4a5faa85d16b23d421f2325e4c Former-commit-id: a49e33c994d79bf4092788ef3027c1f47c2f66c4 Former-commit-id: 15bc1d0c90ebca357322e1ba10c21c3830a8e4ae --- src/ImageProcessorCore/Image.cs | 1 + src/ImageProcessorCore/ImageExtensions.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ImageProcessorCore/Image.cs b/src/ImageProcessorCore/Image.cs index 3cde695432..ec27e457bd 100644 --- a/src/ImageProcessorCore/Image.cs +++ b/src/ImageProcessorCore/Image.cs @@ -73,6 +73,7 @@ namespace ImageProcessorCore } } + this.Quality = other.Quality; this.RepeatCount = other.RepeatCount; this.HorizontalResolution = other.HorizontalResolution; this.VerticalResolution = other.VerticalResolution; diff --git a/src/ImageProcessorCore/ImageExtensions.cs b/src/ImageProcessorCore/ImageExtensions.cs index e7b261d608..ea320aad4d 100644 --- a/src/ImageProcessorCore/ImageExtensions.cs +++ b/src/ImageProcessorCore/ImageExtensions.cs @@ -116,7 +116,7 @@ namespace ImageProcessorCore /// The . public static Image Process(this Image source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler) { - return PerformAction(source, false, (sourceImage, targetImage) => sampler.Apply(targetImage, sourceImage, width, height, targetRectangle, sourceRectangle)); + return PerformAction(source, false, (sourceImage, targetImage) => sampler.Apply(targetImage, sourceImage, width, height, targetRectangle, sourceRectangle)); } /// @@ -135,6 +135,7 @@ namespace ImageProcessorCore { // Several properties require copying // TODO: Check why we need to set these? + Quality = source.Quality, HorizontalResolution = source.HorizontalResolution, VerticalResolution = source.VerticalResolution, CurrentImageFormat = source.CurrentImageFormat, From 5ef0e7f43c2f59c561c2b9c2cc883e5052602c4a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 19 Jul 2016 13:58:35 +1000 Subject: [PATCH 43/91] Convert gif format. Former-commit-id: 085c4bc99388de35847b2b8dd26b20d518e272de Former-commit-id: e8b3af949ca0f63f001483188dbab20cd263c10f Former-commit-id: 8660e1a08e5da2bfebcc269d75ee20488542d4f6 --- src/ImageProcessorCore/Bootstrapper.cs | 2 +- .../Formats/Gif/BitEncoder.cs | 132 ++++++ .../Formats/Gif/DisposalMethod.cs | 37 ++ .../Formats/Gif/GifConstants.cs | 83 ++++ .../Formats/Gif/GifDecoder.cs | 65 +++ .../Formats/Gif/GifDecoderCore.cs | 426 ++++++++++++++++++ .../Formats/Gif/GifEncoder.cs | 64 +++ .../Formats/Gif/GifEncoderCore.cs | 313 +++++++++++++ .../Formats/Gif/GifFormat.cs | 19 + .../Formats/Gif/LzwDecoder.cs | 231 ++++++++++ .../Formats/Gif/LzwEncoder.cs | 385 ++++++++++++++++ .../Formats/Gif/PackedField.cs | 194 ++++++++ src/ImageProcessorCore/Formats/Gif/README.md | 4 + .../Sections/GifGraphicsControlExtension.cs | 42 ++ .../Gif/Sections/GifImageDescriptor.cs | 59 +++ .../Sections/GifLogicalScreenDescriptor.cs | 55 +++ .../Formats/Png/PngEncoderCore.cs | 3 +- .../Image/ImageExtensions.cs | 66 ++- .../Quantizers/Octree/OctreeQuantizer.cs | 5 +- .../ImageProcessorCore.Tests/FileTestBase.cs | 6 +- .../Processors/Samplers/SamplerTests.cs | 2 +- 21 files changed, 2160 insertions(+), 33 deletions(-) create mode 100644 src/ImageProcessorCore/Formats/Gif/BitEncoder.cs create mode 100644 src/ImageProcessorCore/Formats/Gif/DisposalMethod.cs create mode 100644 src/ImageProcessorCore/Formats/Gif/GifConstants.cs create mode 100644 src/ImageProcessorCore/Formats/Gif/GifDecoder.cs create mode 100644 src/ImageProcessorCore/Formats/Gif/GifDecoderCore.cs create mode 100644 src/ImageProcessorCore/Formats/Gif/GifEncoder.cs create mode 100644 src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs create mode 100644 src/ImageProcessorCore/Formats/Gif/GifFormat.cs create mode 100644 src/ImageProcessorCore/Formats/Gif/LzwDecoder.cs create mode 100644 src/ImageProcessorCore/Formats/Gif/LzwEncoder.cs create mode 100644 src/ImageProcessorCore/Formats/Gif/PackedField.cs create mode 100644 src/ImageProcessorCore/Formats/Gif/README.md create mode 100644 src/ImageProcessorCore/Formats/Gif/Sections/GifGraphicsControlExtension.cs create mode 100644 src/ImageProcessorCore/Formats/Gif/Sections/GifImageDescriptor.cs create mode 100644 src/ImageProcessorCore/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs diff --git a/src/ImageProcessorCore/Bootstrapper.cs b/src/ImageProcessorCore/Bootstrapper.cs index 56eee634e3..3294086fe4 100644 --- a/src/ImageProcessorCore/Bootstrapper.cs +++ b/src/ImageProcessorCore/Bootstrapper.cs @@ -40,7 +40,7 @@ namespace ImageProcessorCore new BmpFormat(), //new JpegFormat(), new PngFormat(), - //new GifFormat() + new GifFormat() }; this.pixelAccessors = new Dictionary> diff --git a/src/ImageProcessorCore/Formats/Gif/BitEncoder.cs b/src/ImageProcessorCore/Formats/Gif/BitEncoder.cs new file mode 100644 index 0000000000..a0c633a194 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Gif/BitEncoder.cs @@ -0,0 +1,132 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System.Collections.Generic; + + /// + /// Handles the encoding of bits for compression. + /// + internal class BitEncoder + { + /// + /// The inner list for collecting the bits. + /// + private readonly List list = new List(); + + /// + /// The current working bit. + /// + private int currentBit; + + /// + /// The current value. + /// + private int currentValue; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The initial bits. + /// + public BitEncoder(int initial) + { + this.IntitialBit = initial; + } + + /// + /// Gets or sets the intitial bit. + /// + public int IntitialBit { get; set; } + + /// + /// The number of bytes in the encoder. + /// + public int Length => this.list.Count; + + /// + /// Adds the current byte to the end of the encoder. + /// + /// + /// The byte to add. + /// + public void Add(int item) + { + this.currentValue |= item << this.currentBit; + + this.currentBit += this.IntitialBit; + + while (this.currentBit >= 8) + { + byte value = (byte)(this.currentValue & 0XFF); + this.currentValue = this.currentValue >> 8; + this.currentBit -= 8; + this.list.Add(value); + } + } + + /// + /// Adds the collection of bytes to the end of the encoder. + /// + /// + /// The collection of bytes to add. + /// The collection itself cannot be null but can contain elements that are null. + public void AddRange(byte[] collection) + { + this.list.AddRange(collection); + } + + /// + /// Copies a range of elements from the encoder to a compatible one-dimensional array, + /// starting at the specified index of the target array. + /// + /// + /// The zero-based index in the source at which copying begins. + /// + /// + /// The one-dimensional Array that is the destination of the elements copied + /// from . The Array must have zero-based indexing + /// + /// The zero-based index in array at which copying begins. + /// The number of bytes to copy. + public void CopyTo(int index, byte[] array, int arrayIndex, int count) + { + this.list.CopyTo(index, array, arrayIndex, count); + } + + /// + /// Removes all the bytes from the encoder. + /// + public void Clear() + { + this.list.Clear(); + } + + /// + /// Copies the bytes into a new array. + /// + /// + public byte[] ToArray() + { + return this.list.ToArray(); + } + + /// + /// The end. + /// + internal void End() + { + while (this.currentBit > 0) + { + byte value = (byte)(this.currentValue & 0XFF); + this.currentValue = this.currentValue >> 8; + this.currentBit -= 8; + this.list.Add(value); + } + } + } +} diff --git a/src/ImageProcessorCore/Formats/Gif/DisposalMethod.cs b/src/ImageProcessorCore/Formats/Gif/DisposalMethod.cs new file mode 100644 index 0000000000..4b0a019734 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Gif/DisposalMethod.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Provides enumeration for instructing the decoder what to do with the last image + /// in an animation sequence. + /// section 23 + /// + public enum DisposalMethod + { + /// + /// No disposal specified. The decoder is not required to take any action. + /// + Unspecified = 0, + + /// + /// Do not dispose. The graphic is to be left in place. + /// + NotDispose = 1, + + /// + /// Restore to background color. The area used by the graphic must be restored to + /// the background color. + /// + RestoreToBackground = 2, + + /// + /// Restore to previous. The decoder is required to restore the area overwritten by the + /// graphic with what was there prior to rendering the graphic. + /// + RestoreToPrevious = 3 + } +} diff --git a/src/ImageProcessorCore/Formats/Gif/GifConstants.cs b/src/ImageProcessorCore/Formats/Gif/GifConstants.cs new file mode 100644 index 0000000000..42949cf168 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Gif/GifConstants.cs @@ -0,0 +1,83 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Constants that define specific points within a gif. + /// + internal sealed class GifConstants + { + /// + /// The file type. + /// + public const string FileType = "GIF"; + + /// + /// The file version. + /// + public const string FileVersion = "89a"; + + /// + /// The extension block introducer !. + /// + public const byte ExtensionIntroducer = 0x21; + + /// + /// The graphic control label. + /// + public const byte GraphicControlLabel = 0xF9; + + /// + /// The application extension label. + /// + public const byte ApplicationExtensionLabel = 0xFF; + + /// + /// The application identification. + /// + public const string ApplicationIdentification = "NETSCAPE2.0"; + + /// + /// The application block size. + /// + public const byte ApplicationBlockSize = 0x0b; + + /// + /// The comment label. + /// + public const byte CommentLabel = 0xFE; + + /// + /// The maximum comment length. + /// + public const int MaxCommentLength = 1024 * 8; + + /// + /// The image descriptor label ,. + /// + public const byte ImageDescriptorLabel = 0x2C; + + /// + /// The plain text label. + /// + public const byte PlainTextLabel = 0x01; + + /// + /// The image label introducer ,. + /// + public const byte ImageLabel = 0x2C; + + /// + /// The terminator. + /// + public const byte Terminator = 0; + + /// + /// The end introducer trailer ;. + /// + public const byte EndIntroducer = 0x3B; + } +} diff --git a/src/ImageProcessorCore/Formats/Gif/GifDecoder.cs b/src/ImageProcessorCore/Formats/Gif/GifDecoder.cs new file mode 100644 index 0000000000..3f73510ca7 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Gif/GifDecoder.cs @@ -0,0 +1,65 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + + /// + /// Decoder for generating an image out of a gif encoded stream. + /// + public class GifDecoder : IImageDecoder + { + /// + /// Gets the size of the header for this image type. + /// + /// The size of the header. + public int HeaderSize => 6; + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file extension. + /// + /// True if the decoder supports the file extension; otherwise, false. + /// + public bool IsSupportedFileExtension(string extension) + { + Guard.NotNullOrEmpty(extension, nameof(extension)); + + extension = extension.StartsWith(".") ? extension.Substring(1) : extension; + return extension.Equals("GIF", StringComparison.OrdinalIgnoreCase); + } + + /// + /// Returns a value indicating whether the supports the specified + /// file header. + /// + /// The containing the file header. + /// + /// True if the decoder supports the file header; otherwise, false. + /// + public bool IsSupportedFileFormat(byte[] header) + { + return header.Length >= 6 && + header[0] == 0x47 && // G + header[1] == 0x49 && // I + header[2] == 0x46 && // F + header[3] == 0x38 && // 8 + (header[4] == 0x39 || header[4] == 0x37) && // 9 or 7 + header[5] == 0x61; // a + } + + /// + public void Decode(Image image, Stream stream) + where T : IPackedVector + where TP : struct + { + new GifDecoderCore().Decode(image, stream); + } + } +} diff --git a/src/ImageProcessorCore/Formats/Gif/GifDecoderCore.cs b/src/ImageProcessorCore/Formats/Gif/GifDecoderCore.cs new file mode 100644 index 0000000000..f431d3a0a3 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Gif/GifDecoderCore.cs @@ -0,0 +1,426 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + + /// + /// Performs the gif decoding operation. + /// + /// 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; + + /// + /// The currently loaded stream. + /// + private Stream currentStream; + + /// + /// The global color table. + /// + private byte[] globalColorTable; + + /// + /// The current frame. + /// + private T[] currentFrame; + + /// + /// The logical screen descriptor. + /// + private GifLogicalScreenDescriptor logicalScreenDescriptor; + + /// + /// The graphics control extension. + /// + private GifGraphicsControlExtension graphicsControlExtension; + + /// + /// Decodes the stream to the image. + /// + /// The image to decode to. + /// The stream containing image data. + public void Decode(Image image, Stream stream) + { + this.decodedImage = image; + + this.currentStream = stream; + + // Skip the identifier + this.currentStream.Seek(6, SeekOrigin.Current); + this.ReadLogicalScreenDescriptor(); + + if (this.logicalScreenDescriptor.GlobalColorTableFlag) + { + this.globalColorTable = new byte[this.logicalScreenDescriptor.GlobalColorTableSize * 3]; + + // Read the global color table from the stream + stream.Read(this.globalColorTable, 0, this.globalColorTable.Length); + } + + // Loop though the respective gif parts and read the data. + int nextFlag = stream.ReadByte(); + while (nextFlag != GifConstants.Terminator) + { + if (nextFlag == GifConstants.ImageLabel) + { + this.ReadFrame(); + } + else if (nextFlag == GifConstants.ExtensionIntroducer) + { + int label = stream.ReadByte(); + switch (label) + { + case GifConstants.GraphicControlLabel: + this.ReadGraphicalControlExtension(); + break; + case GifConstants.CommentLabel: + this.ReadComments(); + break; + case GifConstants.ApplicationExtensionLabel: + this.Skip(12); // No need to read. + break; + case GifConstants.PlainTextLabel: + this.Skip(13); // Not supported by any known decoder. + break; + } + } + else if (nextFlag == GifConstants.EndIntroducer) + { + break; + } + + nextFlag = stream.ReadByte(); + } + } + + /// + /// Reads the graphic control extension. + /// + private void ReadGraphicalControlExtension() + { + byte[] buffer = new byte[6]; + + this.currentStream.Read(buffer, 0, buffer.Length); + + byte packed = buffer[1]; + + this.graphicsControlExtension = new GifGraphicsControlExtension + { + DelayTime = BitConverter.ToInt16(buffer, 2), + TransparencyIndex = buffer[4], + TransparencyFlag = (packed & 0x01) == 1, + DisposalMethod = (DisposalMethod)((packed & 0x1C) >> 2) + }; + } + + /// + /// Reads the image descriptor + /// + /// + private GifImageDescriptor ReadImageDescriptor() + { + byte[] buffer = new byte[9]; + + this.currentStream.Read(buffer, 0, buffer.Length); + + byte packed = buffer[8]; + + GifImageDescriptor imageDescriptor = new GifImageDescriptor + { + Left = BitConverter.ToInt16(buffer, 0), + Top = BitConverter.ToInt16(buffer, 2), + Width = BitConverter.ToInt16(buffer, 4), + Height = BitConverter.ToInt16(buffer, 6), + LocalColorTableFlag = ((packed & 0x80) >> 7) == 1, + LocalColorTableSize = 2 << (packed & 0x07), + InterlaceFlag = ((packed & 0x40) >> 6) == 1 + }; + + return imageDescriptor; + } + + /// + /// Reads the logical screen descriptor. + /// + private void ReadLogicalScreenDescriptor() + { + byte[] buffer = new byte[7]; + + this.currentStream.Read(buffer, 0, buffer.Length); + + byte packed = buffer[4]; + + this.logicalScreenDescriptor = new GifLogicalScreenDescriptor + { + Width = BitConverter.ToInt16(buffer, 0), + Height = BitConverter.ToInt16(buffer, 2), + BackgroundColorIndex = buffer[5], + PixelAspectRatio = buffer[6], + GlobalColorTableFlag = ((packed & 0x80) >> 7) == 1, + GlobalColorTableSize = 2 << (packed & 0x07) + }; + + if (this.logicalScreenDescriptor.GlobalColorTableSize > 255 * 4) + { + throw new ImageFormatException( + $"Invalid gif colormap size '{this.logicalScreenDescriptor.GlobalColorTableSize}'"); + } + + 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 '{this.decodedImage.MaxWidth}x{this.decodedImage.MaxHeight}'"); + } + } + + /// + /// Skips the designated number of bytes in the stream. + /// + /// The number of bytes to skip. + private void Skip(int length) + { + this.currentStream.Seek(length, SeekOrigin.Current); + + int flag; + + while ((flag = this.currentStream.ReadByte()) != 0) + { + this.currentStream.Seek(flag, SeekOrigin.Current); + } + } + + /// + /// Reads the gif comments. + /// + private void ReadComments() + { + int flag; + + while ((flag = this.currentStream.ReadByte()) != 0) + { + if (flag > GifConstants.MaxCommentLength) + { + throw new ImageFormatException($"Gif comment length '{flag}' exceeds max '{GifConstants.MaxCommentLength}'"); + } + + byte[] buffer = new byte[flag]; + + this.currentStream.Read(buffer, 0, flag); + + this.decodedImage.Properties.Add(new ImageProperty("Comments", BitConverter.ToString(buffer))); + } + } + + /// + /// Reads an individual gif frame. + /// + private void ReadFrame() + { + GifImageDescriptor imageDescriptor = this.ReadImageDescriptor(); + + byte[] localColorTable = this.ReadFrameLocalColorTable(imageDescriptor); + + byte[] indices = this.ReadFrameIndices(imageDescriptor); + + // Determine the color table for this frame. If there is a local one, use it + // otherwise use the global color table. + byte[] colorTable = localColorTable ?? this.globalColorTable; + + this.ReadFrameColors(indices, colorTable, imageDescriptor); + + // Skip any remaining blocks + this.Skip(0); + } + + /// + /// Reads the frame indices marking the color to use for each pixel. + /// + /// The . + /// The + private byte[] ReadFrameIndices(GifImageDescriptor imageDescriptor) + { + int dataSize = this.currentStream.ReadByte(); + LzwDecoder lzwDecoder = new LzwDecoder(this.currentStream); + + byte[] indices = lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize); + + return indices; + } + + /// + /// Reads the local color table from the current frame. + /// + /// The . + /// The + private byte[] ReadFrameLocalColorTable(GifImageDescriptor imageDescriptor) + { + byte[] localColorTable = null; + + if (imageDescriptor.LocalColorTableFlag) + { + localColorTable = new byte[imageDescriptor.LocalColorTableSize * 3]; + + this.currentStream.Read(localColorTable, 0, localColorTable.Length); + } + + return localColorTable; + } + + /// + /// Reads the frames colors, mapping indices to colors. + /// + /// The indexed pixels. + /// The color table containing the available colors. + /// The + private void ReadFrameColors(byte[] indices, byte[] colorTable, GifImageDescriptor descriptor) + { + int imageWidth = this.logicalScreenDescriptor.Width; + int imageHeight = this.logicalScreenDescriptor.Height; + + if (this.currentFrame == null) + { + this.currentFrame = new T[imageWidth * imageHeight]; + } + + T[] lastFrame = null; + + if (this.graphicsControlExtension != null && + this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious) + { + lastFrame = new T[imageWidth * imageHeight]; + + Array.Copy(this.currentFrame, lastFrame, lastFrame.Length); + } + + int offset, i = 0; + int interlacePass = 0; // The interlace pass + int interlaceIncrement = 8; // The interlacing line increment + int interlaceY = 0; // The current interlaced line + + for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) + { + // Check if this image is interlaced. + int writeY; // the target y offset to write to + if (descriptor.InterlaceFlag) + { + // If so then we read lines at predetermined offsets. + // When an entire image height worth of offset lines has been read we consider this a pass. + // With each pass the number of offset lines changes and the starting line changes. + if (interlaceY >= descriptor.Height) + { + interlacePass++; + switch (interlacePass) + { + case 1: + interlaceY = 4; + break; + case 2: + interlaceY = 2; + interlaceIncrement = 4; + break; + case 3: + interlaceY = 1; + interlaceIncrement = 2; + break; + } + } + + writeY = interlaceY + descriptor.Top; + + interlaceY += interlaceIncrement; + } + else + { + writeY = y; + } + + for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++) + { + offset = (writeY * imageWidth) + x; + int index = indices[i]; + + if (this.graphicsControlExtension == null || + this.graphicsControlExtension.TransparencyFlag == false || + this.graphicsControlExtension.TransparencyIndex != index) + { + // Stored in r-> g-> b-> a order. + int indexOffset = index * 3; + + T pixel = default(T); + pixel.PackBytes(colorTable[indexOffset], colorTable[indexOffset + 1], colorTable[indexOffset + 2], 255); + this.currentFrame[offset] = pixel; + } + + i++; + } + } + + T[] pixels = new T[imageWidth * imageHeight]; + + Array.Copy(this.currentFrame, pixels, pixels.Length); + + ImageBase currentImage; + + if (this.decodedImage.Pixels == null) + { + currentImage = this.decodedImage; + currentImage.SetPixels(imageWidth, imageHeight, pixels); + currentImage.Quality = colorTable.Length / 3; + + if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0) + { + this.decodedImage.FrameDelay = this.graphicsControlExtension.DelayTime; + } + } + else + { + ImageFrame frame = new ImageFrame(); + + currentImage = frame; + currentImage.SetPixels(imageWidth, imageHeight, pixels); + currentImage.Quality = colorTable.Length / 3; + + if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0) + { + currentImage.FrameDelay = this.graphicsControlExtension.DelayTime; + } + + this.decodedImage.Frames.Add(frame); + } + + if (this.graphicsControlExtension != null) + { + if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground) + { + for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) + { + for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++) + { + offset = (y * imageWidth) + x; + + // Stored in r-> g-> b-> a order. + this.currentFrame[offset] = default(T); + } + } + } + else if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious) + { + this.currentFrame = lastFrame; + } + } + } + } +} diff --git a/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs b/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs new file mode 100644 index 0000000000..4ece1c769c --- /dev/null +++ b/src/ImageProcessorCore/Formats/Gif/GifEncoder.cs @@ -0,0 +1,64 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + + using ImageProcessorCore.Quantizers; + + /// + /// Image encoder for writing image data to a stream in gif format. + /// + public class GifEncoder : IImageEncoder + { + /// + /// Gets or sets the quality of output for images. + /// + /// For gifs the value ranges from 1 to 256. + public int Quality { get; set; } + + /// + /// Gets or sets the transparency threshold. + /// + public byte Threshold { get; set; } = 128; + + /// + /// The quantizer for reducing the color count. + /// + public IQuantizer Quantizer { get; set; } + + /// + public string Extension => "gif"; + + /// + public string MimeType => "image/gif"; + + /// + public bool IsSupportedFileExtension(string extension) + { + Guard.NotNullOrEmpty(extension, nameof(extension)); + + extension = extension.StartsWith(".") ? extension.Substring(1) : extension; + return extension.Equals(this.Extension, StringComparison.OrdinalIgnoreCase); + } + + /// + public void Encode(ImageBase image, Stream stream) + where T : IPackedVector + where TP : struct + { + GifEncoderCore encoder = new GifEncoderCore + { + Quality = this.Quality, + Quantizer = this.Quantizer, + Threshold = this.Threshold + }; + + encoder.Encode(image, stream); + } + } +} diff --git a/src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs b/src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs new file mode 100644 index 0000000000..9ac7c2d584 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Gif/GifEncoderCore.cs @@ -0,0 +1,313 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + using System.Linq; + using System.Threading.Tasks; + + using IO; + using Quantizers; + + /// + /// Performs the gif encoding operation. + /// + internal sealed class GifEncoderCore + { + /// + /// The number of bits requires to store the image palette. + /// + private int bitDepth; + + /// + /// Gets or sets the quality of output for images. + /// + /// For gifs the value ranges from 1 to 256. + public int Quality { get; set; } + + /// + /// Gets or sets the transparency threshold. + /// + public byte Threshold { get; set; } = 128; + + /// + /// The quantizer for reducing the color count. + /// + public IQuantizer Quantizer { get; set; } + + /// + /// 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 imageBase, Stream stream) + where T : IPackedVector + where TP : struct + { + Guard.NotNull(imageBase, nameof(imageBase)); + Guard.NotNull(stream, nameof(stream)); + + Image image = (Image)imageBase; + + if (this.Quantizer == null) + { + this.Quantizer = new OctreeQuantizer { Threshold = this.Threshold }; + } + + // Do not use IDisposable pattern here as we want to preserve the stream. + EndianBinaryWriter writer = new EndianBinaryWriter(EndianBitConverter.Little, stream); + + // Ensure that quality can be set but has a fallback. + int quality = this.Quality > 0 ? this.Quality : imageBase.Quality; + this.Quality = quality > 0 ? quality.Clamp(1, 256) : 256; + + // Get the number of bits. + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(this.Quality); + + // Quantize the image returning a palette. + QuantizedImage quantized = ((IQuantizer)this.Quantizer).Quantize(image, this.Quality); + + // Write the header. + this.WriteHeader(writer); + + // Write the LSD. We'll use local color tables for now. + this.WriteLogicalScreenDescriptor(image, writer, quantized.TransparentIndex); + + // Write the first frame. + this.WriteGraphicalControlExtension(imageBase, writer, quantized.TransparentIndex); + this.WriteImageDescriptor(image, writer); + this.WriteColorTable(quantized, writer); + this.WriteImageData(quantized, writer); + + // Write additional frames. + if (image.Frames.Any()) + { + this.WriteApplicationExtension(writer, image.RepeatCount, image.Frames.Count); + foreach (ImageFrame frame in image.Frames) + { + QuantizedImage quantizedFrame = ((IQuantizer)this.Quantizer).Quantize(frame, this.Quality); + this.WriteGraphicalControlExtension(frame, writer, quantizedFrame.TransparentIndex); + this.WriteImageDescriptor(frame, writer); + this.WriteColorTable(quantizedFrame, writer); + this.WriteImageData(quantizedFrame, writer); + } + } + + // TODO: Write Comments extension etc + writer.Write(GifConstants.EndIntroducer); + } + + /// + /// Writes the file header signature and version to the stream. + /// + /// The writer to write to the stream with. + private void WriteHeader(EndianBinaryWriter writer) + { + writer.Write((GifConstants.FileType + GifConstants.FileVersion).ToCharArray()); + } + + /// + /// 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) + where T : IPackedVector + where TP : struct + { + GifLogicalScreenDescriptor descriptor = new GifLogicalScreenDescriptor + { + Width = (short)image.Width, + Height = (short)image.Height, + GlobalColorTableFlag = false, // Always false for now. + GlobalColorTableSize = this.bitDepth - 1, + BackgroundColorIndex = (byte)(tranparencyIndex > -1 ? tranparencyIndex : 255) + }; + + writer.Write((ushort)descriptor.Width); + writer.Write((ushort)descriptor.Height); + + PackedField field = new PackedField(); + field.SetBit(0, descriptor.GlobalColorTableFlag); // 1 : Global color table flag = 1 || 0 (GCT used/ not used) + field.SetBits(1, 3, descriptor.GlobalColorTableSize); // 2-4 : color resolution + field.SetBit(4, false); // 5 : GCT sort flag = 0 + field.SetBits(5, 3, descriptor.GlobalColorTableSize); // 6-8 : GCT size. 2^(N+1) + + // Reduce the number of writes + byte[] arr = { + field.Byte, + descriptor.BackgroundColorIndex, // Background Color Index + descriptor.PixelAspectRatio // Pixel aspect ratio. Assume 1:1 + }; + + writer.Write(arr); + } + + /// + /// Writes the application exstension to the stream. + /// + /// The writer to write to the stream with. + /// The animated image repeat count. + /// Th number of image frames. + private void WriteApplicationExtension(EndianBinaryWriter writer, ushort repeatCount, int frames) + { + // Application Extension Header + if (repeatCount != 1 && frames > 0) + { + byte[] ext = + { + GifConstants.ExtensionIntroducer, + GifConstants.ApplicationExtensionLabel, + GifConstants.ApplicationBlockSize + }; + + writer.Write(ext); + + writer.Write(GifConstants.ApplicationIdentification.ToCharArray()); // NETSCAPE2.0 + writer.Write((byte)3); // Application block length + writer.Write((byte)1); // Data sub-block index (always 1) + + // 0 means loop indefinitely. Count is set as play n + 1 times. + repeatCount = (ushort)(Math.Max((ushort)0, repeatCount) - 1); + + writer.Write(repeatCount); // Repeat count for images. + + writer.Write(GifConstants.Terminator); // Terminator + } + } + + /// + /// Writes the graphics control extension to the stream. + /// + /// 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) + where T : IPackedVector + where TP : struct + { + // TODO: Check transparency logic. + bool hasTransparent = transparencyIndex > -1; + DisposalMethod disposalMethod = hasTransparent + ? DisposalMethod.RestoreToBackground + : DisposalMethod.Unspecified; + + GifGraphicsControlExtension extension = new GifGraphicsControlExtension() + { + DisposalMethod = disposalMethod, + TransparencyFlag = hasTransparent, + TransparencyIndex = transparencyIndex, + DelayTime = image.FrameDelay + }; + + // Reduce the number of writes. + byte[] intro = { + GifConstants.ExtensionIntroducer, + GifConstants.GraphicControlLabel, + 4 // Size + }; + + writer.Write(intro); + + PackedField field = new PackedField(); + field.SetBits(3, 3, (int)extension.DisposalMethod); // 1-3 : Reserved, 4-6 : Disposal + + // TODO: Allow this as an option. + field.SetBit(6, false); // 7 : User input - 0 = none + field.SetBit(7, extension.TransparencyFlag); // 8: Has transparent. + + writer.Write(field.Byte); + writer.Write((ushort)extension.DelayTime); + writer.Write((byte)(extension.TransparencyIndex == -1 ? 255 : extension.TransparencyIndex)); + writer.Write(GifConstants.Terminator); + } + + /// + /// Writes the image descriptor to the stream. + /// + /// The pixel format. + /// The packed format. long, float. + /// The to be encoded. + /// The stream to write to. + private void WriteImageDescriptor(ImageBase image, EndianBinaryWriter writer) + where T : IPackedVector + where TP : struct + { + writer.Write(GifConstants.ImageDescriptorLabel); // 2c + // TODO: Can we capture this? + writer.Write((ushort)0); // Left position + writer.Write((ushort)0); // Top position + writer.Write((ushort)image.Width); + writer.Write((ushort)image.Height); + + PackedField field = new PackedField(); + field.SetBit(0, true); // 1: Local color table flag = 1 (LCT used) + field.SetBit(1, false); // 2: Interlace flag 0 + field.SetBit(2, false); // 3: Sort flag 0 + field.SetBits(5, 3, this.bitDepth - 1); // 4-5: Reserved, 6-8 : LCT size. 2^(N+1) + + writer.Write(field.Byte); + } + + /// + /// Writes the color table to the stream. + /// + /// 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) + where T : IPackedVector + where TP : struct + { + // Grab the palette and write it to the stream. + T[] palette = image.Palette; + int pixelCount = palette.Length; + + // Get max colors for bit depth. + int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3; + byte[] colorTable = new byte[colorTableLength]; + + Parallel.For(0, pixelCount, + i => + { + int offset = i * 3; + byte[] color = palette[i].ToBytes(); + + colorTable[offset] = color[0]; + colorTable[offset + 1] = color[1]; + colorTable[offset + 2] = color[2]; + }); + + writer.Write(colorTable, 0, colorTableLength); + } + + /// + /// Writes the image pixel data to the stream. + /// + /// The pixel format. + /// The packed format. long, float. + /// The containing indexed pixels. + /// The stream to write to. + private void WriteImageData(QuantizedImage image, EndianBinaryWriter writer) + where T : IPackedVector + where TP : struct + { + byte[] indexedPixels = image.Pixels; + + LzwEncoder encoder = new LzwEncoder(indexedPixels, (byte)this.bitDepth); + encoder.Encode(writer.BaseStream); + } + } +} diff --git a/src/ImageProcessorCore/Formats/Gif/GifFormat.cs b/src/ImageProcessorCore/Formats/Gif/GifFormat.cs new file mode 100644 index 0000000000..572815630f --- /dev/null +++ b/src/ImageProcessorCore/Formats/Gif/GifFormat.cs @@ -0,0 +1,19 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Encapsulates the means to encode and decode gif images. + /// + public class GifFormat : IImageFormat + { + /// + public IImageDecoder Decoder => new GifDecoder(); + + /// + public IImageEncoder Encoder => new GifEncoder(); + } +} diff --git a/src/ImageProcessorCore/Formats/Gif/LzwDecoder.cs b/src/ImageProcessorCore/Formats/Gif/LzwDecoder.cs new file mode 100644 index 0000000000..75d590673a --- /dev/null +++ b/src/ImageProcessorCore/Formats/Gif/LzwDecoder.cs @@ -0,0 +1,231 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + + /// + /// Decompresses and decodes data using the dynamic LZW algorithms. + /// + internal sealed class LzwDecoder + { + /// + /// The max decoder pixel stack size. + /// + private const int MaxStackSize = 4096; + + /// + /// The null code. + /// + private const int NullCode = -1; + + /// + /// The stream to decode. + /// + private readonly Stream stream; + + /// + /// Initializes a new instance of the class + /// and sets the stream, where the compressed data should be read from. + /// + /// The stream to read from. + /// is null. + public LzwDecoder(Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + + this.stream = stream; + } + + /// + /// Decodes and decompresses all pixel indices from the stream. + /// + /// + /// + /// The width of the pixel index array. + /// The height of the pixel index array. + /// Size of the data. + /// The decoded and uncompressed array. + public byte[] DecodePixels(int width, int height, int dataSize) + { + Guard.MustBeLessThan(dataSize, int.MaxValue, nameof(dataSize)); + + // The resulting index table. + byte[] pixels = new byte[width * height]; + + // Calculate the clear code. The value of the clear code is 2 ^ dataSize + int clearCode = 1 << dataSize; + + int codeSize = dataSize + 1; + + // Calculate the end code + int endCode = clearCode + 1; + + // Calculate the available code. + int availableCode = clearCode + 2; + + // Jillzhangs Code see: http://giflib.codeplex.com/ + // Adapted from John Cristy's ImageMagick. + int code; + int oldCode = NullCode; + int codeMask = (1 << codeSize) - 1; + int bits = 0; + + int[] prefix = new int[MaxStackSize]; + int[] suffix = new int[MaxStackSize]; + int[] pixelStatck = new int[MaxStackSize + 1]; + + int top = 0; + int count = 0; + int bi = 0; + int xyz = 0; + + int data = 0; + int first = 0; + + for (code = 0; code < clearCode; code++) + { + prefix[code] = 0; + suffix[code] = (byte)code; + } + + byte[] buffer = null; + while (xyz < pixels.Length) + { + if (top == 0) + { + if (bits < codeSize) + { + // Load bytes until there are enough bits for a code. + if (count == 0) + { + // Read a new data block. + buffer = this.ReadBlock(); + count = buffer.Length; + if (count == 0) + { + break; + } + + bi = 0; + } + + if (buffer != null) + { + data += buffer[bi] << bits; + } + + bits += 8; + bi++; + count--; + continue; + } + + // Get the next code + code = data & codeMask; + data >>= codeSize; + bits -= codeSize; + + // Interpret the code + if (code > availableCode || code == endCode) + { + break; + } + + if (code == clearCode) + { + // Reset the decoder + codeSize = dataSize + 1; + codeMask = (1 << codeSize) - 1; + availableCode = clearCode + 2; + oldCode = NullCode; + continue; + } + + if (oldCode == NullCode) + { + pixelStatck[top++] = suffix[code]; + oldCode = code; + first = code; + continue; + } + + int inCode = code; + if (code == availableCode) + { + pixelStatck[top++] = (byte)first; + + code = oldCode; + } + + while (code > clearCode) + { + pixelStatck[top++] = suffix[code]; + code = prefix[code]; + } + + first = suffix[code]; + + pixelStatck[top++] = suffix[code]; + + // Fix for Gifs that have "deferred clear code" as per here : + // https://bugzilla.mozilla.org/show_bug.cgi?id=55918 + if (availableCode < MaxStackSize) + { + prefix[availableCode] = oldCode; + suffix[availableCode] = first; + availableCode++; + if (availableCode == codeMask + 1 && availableCode < MaxStackSize) + { + codeSize++; + codeMask = (1 << codeSize) - 1; + } + } + + oldCode = inCode; + } + + // Pop a pixel off the pixel stack. + top--; + + // Clear missing pixels + pixels[xyz++] = (byte)pixelStatck[top]; + } + + return pixels; + } + + /// + /// Reads the next data block from the stream. A data block begins with a byte, + /// which defines the size of the block, followed by the block itself. + /// + /// + /// The . + /// + private byte[] ReadBlock() + { + int blockSize = this.stream.ReadByte(); + return this.ReadBytes(blockSize); + } + + /// + /// Reads the specified number of bytes from the data stream. + /// + /// + /// The number of bytes to read. + /// + /// + /// The . + /// + private byte[] ReadBytes(int length) + { + byte[] buffer = new byte[length]; + this.stream.Read(buffer, 0, length); + return buffer; + } + } +} diff --git a/src/ImageProcessorCore/Formats/Gif/LzwEncoder.cs b/src/ImageProcessorCore/Formats/Gif/LzwEncoder.cs new file mode 100644 index 0000000000..a9681d2c56 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Gif/LzwEncoder.cs @@ -0,0 +1,385 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + + /// + /// Encodes and compresses the image data using dynamic Lempel-Ziv compression. + /// + /// + /// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. K Weiner 12/00 + /// + /// GIFCOMPR.C - GIF Image compression routines + /// + /// Lempel-Ziv compression based on 'compress'. GIF modifications by + /// David Rowley (mgardi@watdcsu.waterloo.edu) + /// + /// + /// GIF Image compression - modified 'compress' + /// + /// Based on: compress.c - File compression ala IEEE Computer, June 1984. + /// + /// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) + /// Jim McKie (decvax!mcvax!jim) + /// Steve Davies (decvax!vax135!petsd!peora!srd) + /// Ken Turkowski (decvax!decwrl!turtlevax!ken) + /// James A. Woods (decvax!ihnp4!ames!jaw) + /// Joe Orost (decvax!vax135!petsd!joe) + /// + /// + internal sealed class LzwEncoder + { + private const int Eof = -1; + + private const int Bits = 12; + + private const int HashSize = 5003; // 80% occupancy + + private readonly byte[] pixelArray; + + private readonly int initialCodeSize; + + private int curPixel; + + /// + /// Number of bits/code + /// + private int bitCount; + + /// + /// User settable max # bits/code + /// + private int maxbits = Bits; + + private int maxcode; // maximum code, given bitCount + + private int maxmaxcode = 1 << Bits; // should NEVER generate this code + + private readonly int[] hashTable = new int[HashSize]; + + private readonly int[] codeTable = new int[HashSize]; + + /// + /// For dynamic table sizing + /// + private int hsize = HashSize; + + /// + /// First unused entry + /// + private int freeEntry; + + /// + /// Block compression parameters -- after all codes are used up, + /// and compression rate changes, start over. + /// + private bool clearFlag; + + // Algorithm: use open addressing double hashing (no chaining) on the + // prefix code / next character combination. We do a variant of Knuth's + // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime + // secondary probe. Here, the modular division first probe is gives way + // to a faster exclusive-or manipulation. Also do block compression with + // an adaptive reset, whereby the code table is cleared when the compression + // ratio decreases, but after the table fills. The variable-length output + // codes are re-sized at this point, and a special CLEAR code is generated + // for the decompressor. Late addition: construct the table according to + // file size for noticeable speed improvement on small files. Please direct + // questions about this implementation to ames!jaw. + + private int globalInitialBits; + + private int clearCode; + + private int eofCode; + + // output + // + // Output the given code. + // Inputs: + // code: A bitCount-bit integer. If == -1, then EOF. This assumes + // that bitCount =< wordsize - 1. + // Outputs: + // Outputs code to the file. + // Assumptions: + // Chars are 8 bits long. + // Algorithm: + // Maintain a BITS character long buffer (so that 8 codes will + // fit in it exactly). Use the VAX insv instruction to insert each + // code in turn. When the buffer fills up empty it and start over. + + private int currentAccumulator; + + private int currentBits; + + private readonly int[] masks = + { + 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, + 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF + }; + + /// + /// Number of characters so far in this 'packet' + /// + private int accumulatorCount; + + /// + /// Define the storage for the packet accumulator. + /// + private readonly byte[] accumulators = new byte[256]; + + /// + /// Initializes a new instance of the class. + /// + /// The array of indexed pixels. + /// The color depth in bits. + public LzwEncoder(byte[] indexedPixels, int colorDepth) + { + this.pixelArray = indexedPixels; + this.initialCodeSize = Math.Max(2, colorDepth); + } + + /// + /// Encodes and compresses the indexed pixels to the stream. + /// + /// The stream to write to. + public void Encode(Stream stream) + { + // Write "initial code size" byte + stream.WriteByte((byte)this.initialCodeSize); + + this.curPixel = 0; + + // Compress and write the pixel data + this.Compress(this.initialCodeSize + 1, stream); + + // Write block terminator + stream.WriteByte(GifConstants.Terminator); + } + + /// + /// Gets the maximum code value + /// + /// The number of bits + /// See + private static int GetMaxcode(int bitCount) + { + return (1 << bitCount) - 1; + } + + /// + /// Add a character to the end of the current packet, and if it is 254 characters, + /// flush the packet to disk. + /// + /// The character to add. + /// The stream to write to. + private void AddCharacter(byte c, Stream stream) + { + this.accumulators[this.accumulatorCount++] = c; + if (this.accumulatorCount >= 254) + { + this.FlushPacket(stream); + } + } + + /// + /// Table clear for block compress + /// + /// The output stream. + private void ClearBlock(Stream stream) + { + this.ResetCodeTable(this.hsize); + this.freeEntry = this.clearCode + 2; + this.clearFlag = true; + + this.Output(this.clearCode, stream); + } + + /// + /// Reset the code table. + /// + /// The hash size. + private void ResetCodeTable(int size) + { + for (int i = 0; i < size; ++i) + { + this.hashTable[i] = -1; + } + } + + /// + /// Compress the packets to the stream. + /// + /// The inital bits. + /// The stream to write to. + private void Compress(int intialBits, Stream stream) + { + int fcode; + int c; + int ent; + int hsizeReg; + int hshift; + + // Set up the globals: globalInitialBits - initial number of bits + this.globalInitialBits = intialBits; + + // Set up the necessary values + this.clearFlag = false; + this.bitCount = this.globalInitialBits; + this.maxcode = GetMaxcode(this.bitCount); + + this.clearCode = 1 << (intialBits - 1); + this.eofCode = this.clearCode + 1; + this.freeEntry = this.clearCode + 2; + + this.accumulatorCount = 0; // clear packet + + ent = this.NextPixel(); + + hshift = 0; + for (fcode = this.hsize; fcode < 65536; fcode *= 2) { ++hshift; } + hshift = 8 - hshift; // set hash code range bound + + hsizeReg = this.hsize; + + this.ResetCodeTable(hsizeReg); // clear hash table + + this.Output(this.clearCode, stream); + + while ((c = this.NextPixel()) != Eof) + { + fcode = (c << this.maxbits) + ent; + int i = (c << hshift) ^ ent /* = 0 */; + + if (this.hashTable[i] == fcode) + { + ent = this.codeTable[i]; + continue; + } + + // Non-empty slot + if (this.hashTable[i] >= 0) + { + int disp = hsizeReg - i; + if (i == 0) disp = 1; + do + { + if ((i -= disp) < 0) { i += hsizeReg; } + + if (this.hashTable[i] == fcode) + { + ent = this.codeTable[i]; + break; + } + } + while (this.hashTable[i] >= 0); + + if (this.hashTable[i] == fcode) { continue; } + } + + this.Output(ent, stream); + ent = c; + if (this.freeEntry < this.maxmaxcode) + { + this.codeTable[i] = this.freeEntry++; // code -> hashtable + this.hashTable[i] = fcode; + } + else this.ClearBlock(stream); + } + + // Put out the final code. + this.Output(ent, stream); + + this.Output(this.eofCode, stream); + } + + // Flush the packet to disk, and reset the accumulator + private void FlushPacket(Stream outs) + { + if (this.accumulatorCount > 0) + { + outs.WriteByte((byte)this.accumulatorCount); + outs.Write(this.accumulators, 0, this.accumulatorCount); + this.accumulatorCount = 0; + } + } + + /// + /// Return the next pixel from the image + /// + /// + /// The + /// + private int NextPixel() + { + if (this.curPixel == this.pixelArray.Length) + { + return Eof; + } + + if (this.curPixel == this.pixelArray.Length) + return Eof; + + this.curPixel++; + return this.pixelArray[this.curPixel - 1] & 0xff; + } + + /// + /// Output the current code to the stream. + /// + /// The code. + /// The stream to write to. + private void Output(int code, Stream outs) + { + this.currentAccumulator &= this.masks[this.currentBits]; + + if (this.currentBits > 0) this.currentAccumulator |= (code << this.currentBits); + else this.currentAccumulator = code; + + this.currentBits += this.bitCount; + + while (this.currentBits >= 8) + { + this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs); + this.currentAccumulator >>= 8; + this.currentBits -= 8; + } + + // If the next entry is going to be too big for the code size, + // then increase it, if possible. + if (this.freeEntry > this.maxcode || this.clearFlag) + { + if (this.clearFlag) + { + this.maxcode = GetMaxcode(this.bitCount = this.globalInitialBits); + this.clearFlag = false; + } + else + { + ++this.bitCount; + this.maxcode = this.bitCount == this.maxbits + ? this.maxmaxcode + : GetMaxcode(this.bitCount); + } + } + + if (code == this.eofCode) + { + // At EOF, write the rest of the buffer. + while (this.currentBits > 0) + { + this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs); + this.currentAccumulator >>= 8; + this.currentBits -= 8; + } + + this.FlushPacket(outs); + } + } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore/Formats/Gif/PackedField.cs b/src/ImageProcessorCore/Formats/Gif/PackedField.cs new file mode 100644 index 0000000000..0141d36c6a --- /dev/null +++ b/src/ImageProcessorCore/Formats/Gif/PackedField.cs @@ -0,0 +1,194 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + + /// + /// Represents a byte of data in a GIF data stream which contains a number + /// of data items. + /// + internal struct PackedField : IEquatable + { + /// + /// The individual bits representing the packed byte. + /// + private static readonly bool[] Bits = new bool[8]; + + /// + /// Gets the byte which represents the data items held in this instance. + /// + public byte Byte + { + get + { + int returnValue = 0; + int bitShift = 7; + foreach (bool bit in Bits) + { + int bitValue; + if (bit) + { + bitValue = 1 << bitShift; + } + else + { + bitValue = 0; + } + returnValue |= bitValue; + bitShift--; + } + return Convert.ToByte(returnValue & 0xFF); + } + } + + /// + /// Returns a new with the bits in the packed fields to + /// the corresponding bits from the supplied byte. + /// + /// The value to pack. + /// The + public static PackedField FromInt(byte value) + { + PackedField packed = new PackedField(); + packed.SetBits(0, 8, value); + return packed; + } + + /// + /// Sets the specified bit within the packed fields to the supplied + /// value. + /// + /// + /// The zero-based index within the packed fields of the bit to set. + /// + /// + /// The value to set the bit to. + /// + public void SetBit(int index, bool valueToSet) + { + if (index < 0 || index > 7) + { + string message + = "Index must be between 0 and 7. Supplied index: " + + index; + throw new ArgumentOutOfRangeException(nameof(index), message); + } + Bits[index] = valueToSet; + } + + /// + /// Sets the specified bits within the packed fields to the supplied + /// value. + /// + /// The zero-based index within the packed fields of the first bit to set. + /// The number of bits to set. + /// The value to set the bits to. + public void SetBits(int startIndex, int length, int valueToSet) + { + if (startIndex < 0 || startIndex > 7) + { + string message = $"Start index must be between 0 and 7. Supplied index: {startIndex}"; + throw new ArgumentOutOfRangeException(nameof(startIndex), message); + } + + if (length < 1 || startIndex + length > 8) + { + string message = "Length must be greater than zero and the sum of length and start index must be less than 8. " + + $"Supplied length: {length}. Supplied start index: {startIndex}"; + throw new ArgumentOutOfRangeException(nameof(length), message); + } + + int bitShift = length - 1; + for (int i = startIndex; i < startIndex + length; i++) + { + int bitValueIfSet = (1 << bitShift); + int bitValue = (valueToSet & bitValueIfSet); + int bitIsSet = (bitValue >> bitShift); + Bits[i] = (bitIsSet == 1); + bitShift--; + } + } + + /// + /// Gets the value of the specified bit within the byte. + /// + /// The zero-based index of the bit to get. + /// + /// The value of the specified bit within the byte. + /// + public bool GetBit(int index) + { + if (index < 0 || index > 7) + { + string message = $"Index must be between 0 and 7. Supplied index: {index}"; + throw new ArgumentOutOfRangeException(nameof(index), message); + } + return Bits[index]; + } + + /// + /// Gets the value of the specified bits within the byte. + /// + /// The zero-based index of the first bit to get. + /// The number of bits to get. + /// + /// The value of the specified bits within the byte. + /// + public int GetBits(int startIndex, int length) + { + if (startIndex < 0 || startIndex > 7) + { + string message = $"Start index must be between 0 and 7. Supplied index: {startIndex}"; + throw new ArgumentOutOfRangeException(nameof(startIndex), message); + } + + if (length < 1 || startIndex + length > 8) + { + string message = "Length must be greater than zero and the sum of length and start index must be less than 8. " + + $"Supplied length: {length}. Supplied start index: {startIndex}"; + + throw new ArgumentOutOfRangeException(nameof(length), message); + } + + int returnValue = 0; + int bitShift = length - 1; + for (int i = startIndex; i < startIndex + length; i++) + { + int bitValue = (Bits[i] ? 1 : 0) << bitShift; + returnValue += bitValue; + bitShift--; + } + return returnValue; + } + + /// + public override bool Equals(object obj) + { + PackedField? field = obj as PackedField?; + + return this.Byte == field?.Byte; + } + + /// + public bool Equals(PackedField other) + { + return this.Byte.Equals(other.Byte); + } + + /// + public override string ToString() + { + return $"PackedField [ Byte={this.Byte} ]"; + } + + /// + public override int GetHashCode() + { + return this.Byte.GetHashCode(); + } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore/Formats/Gif/README.md b/src/ImageProcessorCore/Formats/Gif/README.md new file mode 100644 index 0000000000..d47a4c6836 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Gif/README.md @@ -0,0 +1,4 @@ +Encoder/Decoder adapted and extended from: + +https://github.com/yufeih/Nine.Imaging/ +https://imagetools.codeplex.com/ diff --git a/src/ImageProcessorCore/Formats/Gif/Sections/GifGraphicsControlExtension.cs b/src/ImageProcessorCore/Formats/Gif/Sections/GifGraphicsControlExtension.cs new file mode 100644 index 0000000000..071dc62c84 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Gif/Sections/GifGraphicsControlExtension.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// The Graphic Control Extension contains parameters used when + /// processing a graphic rendering block. + /// + internal sealed class GifGraphicsControlExtension + { + /// + /// Gets or sets the disposal method which indicates the way in which the + /// graphic is to be treated after being displayed. + /// + public DisposalMethod DisposalMethod { get; set; } + + /// + /// Gets or sets a value indicating whether transparency flag is to be set. + /// This indicates whether a transparency index is given in the Transparent Index field. + /// (This field is the least significant bit of the byte.) + /// + public bool TransparencyFlag { get; set; } + + /// + /// Gets or sets the transparency index. + /// The Transparency Index is such that when encountered, the corresponding pixel + /// of the display device is not modified and processing goes on to the next pixel. + /// + public int TransparencyIndex { get; set; } + + /// + /// Gets or sets the delay time. + /// 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 DelayTime { get; set; } + } +} diff --git a/src/ImageProcessorCore/Formats/Gif/Sections/GifImageDescriptor.cs b/src/ImageProcessorCore/Formats/Gif/Sections/GifImageDescriptor.cs new file mode 100644 index 0000000000..62737de660 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Gif/Sections/GifImageDescriptor.cs @@ -0,0 +1,59 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Each image in the Data Stream is composed of an Image Descriptor, + /// an optional Local Color Table, and the image data. + /// Each image must fit within the boundaries of the + /// Logical Screen, as defined in the Logical Screen Descriptor. + /// + internal sealed class GifImageDescriptor + { + /// + /// Gets or sets the column number, in pixels, of the left edge of the image, + /// with respect to the left edge of the Logical Screen. + /// Leftmost column of the Logical Screen is 0. + /// + public short Left { get; set; } + + /// + /// Gets or sets the row number, in pixels, of the top edge of the image with + /// respect to the top edge of the Logical Screen. + /// Top row of the Logical Screen is 0. + /// + public short Top { get; set; } + + /// + /// Gets or sets the width of the image in pixels. + /// + public short Width { get; set; } + + /// + /// Gets or sets the height of the image in pixels. + /// + public short Height { get; set; } + + /// + /// Gets or sets a value indicating whether the presence of a Local Color Table immediately + /// follows this Image Descriptor. + /// + public bool LocalColorTableFlag { get; set; } + + /// + /// Gets or sets the local color table size. + /// If the Local Color Table Flag is set to 1, the value in this field + /// is used to calculate the number of bytes contained in the Local Color Table. + /// + public int LocalColorTableSize { get; set; } + + /// + /// Gets or sets a value indicating whether the image is to be interlaced. + /// An image is interlaced in a four-pass interlace pattern. + /// + public bool InterlaceFlag { get; set; } + } +} diff --git a/src/ImageProcessorCore/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs b/src/ImageProcessorCore/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs new file mode 100644 index 0000000000..8c0400f24d --- /dev/null +++ b/src/ImageProcessorCore/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// The Logical Screen Descriptor contains the parameters + /// necessary to define the area of the display device + /// within which the images will be rendered + /// + internal sealed class GifLogicalScreenDescriptor + { + /// + /// Gets or sets the width, in pixels, of the Logical Screen where the images will + /// be rendered in the displaying device. + /// + public short Width { get; set; } + + /// + /// Gets or sets the height, in pixels, of the Logical Screen where the images will be + /// rendered in the displaying device. + /// + public short Height { get; set; } + + /// + /// Gets or sets the index at the Global Color Table for the Background Color. + /// The Background Color is the color used for those + /// pixels on the screen that are not covered by an image. + /// + public byte BackgroundColorIndex { get; set; } + + /// + /// Gets or sets the pixel aspect ratio. Default to 0. + /// + public byte PixelAspectRatio { get; set; } + + /// + /// Gets or sets a value indicating whether a flag denoting the presence of a Global Color Table + /// should be set. + /// If the flag is set, the Global Color Table will immediately + /// follow the Logical Screen Descriptor. + /// + public bool GlobalColorTableFlag { get; set; } + + /// + /// Gets or sets the global color table size. + /// If the Global Color Table Flag is set to 1, + /// the value in this field is used to calculate the number of + /// bytes contained in the Global Color Table. + /// + public int GlobalColorTableSize { get; set; } + } +} diff --git a/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs b/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs index 4c76a5c00b..eeb7c651f4 100644 --- a/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs @@ -218,8 +218,7 @@ namespace ImageProcessorCore.Formats if (this.Quantizer == null) { - //this.Quantizer = new WuQuantizer { Threshold = this.Threshold }; - this.Quantizer = new OctreeQuantizer { Threshold = this.Threshold }; + this.Quantizer = new WuQuantizer { Threshold = this.Threshold }; } // Quantize the image returning a palette. This boxing is icky. diff --git a/src/ImageProcessorCore/Image/ImageExtensions.cs b/src/ImageProcessorCore/Image/ImageExtensions.cs index 544c346596..53133207fc 100644 --- a/src/ImageProcessorCore/Image/ImageExtensions.cs +++ b/src/ImageProcessorCore/Image/ImageExtensions.cs @@ -16,24 +16,35 @@ namespace ImageProcessorCore /// public static partial class ImageExtensions { - ///// - ///// Saves the image to the given stream with the bmp format. - ///// - ///// The image this method extends. - ///// The stream to save the image to. - ///// Thrown if the stream is null. - //public static void SaveAsBmp(this ImageBase source, Stream stream) => new BmpEncoder().Encode(source, stream); + /// + /// Saves the image to the given stream with the bmp format. + /// + /// The 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) + where T : IPackedVector + where TP : struct + => new BmpEncoder().Encode(source, stream); - ///// - ///// Saves the image to the given stream with the png format. - ///// - ///// The image this method extends. - ///// The stream to save the image to. - ///// The quality to save the image to representing the number of colors. - ///// Anything equal to 256 and below will cause the encoder to save the image in an indexed format. - ///// - ///// Thrown if the stream is null. - //public static void SaveAsPng(this ImageBase source, Stream stream, int quality = Int32.MaxValue) => new PngEncoder { Quality = quality }.Encode(source, stream); + + /// + /// Saves the image to the given stream with the png format. + /// + /// The 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 = 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. @@ -44,14 +55,19 @@ namespace ImageProcessorCore ///// Thrown if the stream is null. //public static void SaveAsJpeg(this ImageBase source, Stream stream, int quality = 75) => new JpegEncoder { Quality = quality }.Encode(source, stream); - ///// - ///// Saves the image to the given stream with the gif format. - ///// - ///// The image this method extends. - ///// The stream to save the image to. - ///// The quality to save the image to representing the number of colors. Between 1 and 256. - ///// Thrown if the stream is null. - //public static void SaveAsGif(this ImageBase source, Stream stream, int quality = 256) => new GifEncoder { Quality = quality }.Encode(source, stream); + /// + /// Saves the image to the given stream with the gif format. + /// + /// The 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) + where T : IPackedVector + where TP : struct + => new GifEncoder { Quality = quality }.Encode(source, stream); /// /// Applies the collection of processors to the image. diff --git a/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs index 9d94869ceb..344a1607e5 100644 --- a/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs +++ b/src/ImageProcessorCore/Quantizers/Octree/OctreeQuantizer.cs @@ -106,7 +106,10 @@ namespace ImageProcessorCore.Quantizers List palette = this.octree.Palletize(Math.Max(this.colors, 1)); int diff = this.colors - palette.Count; - palette.AddRange(Enumerable.Repeat(default(T), diff)); + if (diff > 0) + { + palette.AddRange(Enumerable.Repeat(default(T), diff)); + } this.TransparentIndex = this.colors; return palette; diff --git a/tests/ImageProcessorCore.Tests/FileTestBase.cs b/tests/ImageProcessorCore.Tests/FileTestBase.cs index 280e0e5120..0ef3403a18 100644 --- a/tests/ImageProcessorCore.Tests/FileTestBase.cs +++ b/tests/ImageProcessorCore.Tests/FileTestBase.cs @@ -25,14 +25,14 @@ namespace ImageProcessorCore.Tests //"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 "TestImages/Formats/Bmp/Car.bmp", - // "TestImages/Formats/Bmp/neg_height.bmp", // Perf: Enable for local testing only + //"TestImages/Formats/Bmp/neg_height.bmp", // Perf: Enable for local testing only //"TestImages/Formats/Png/blur.png", // Perf: Enable for local testing only //"TestImages/Formats/Png/indexed.png", // Perf: Enable for local testing only "TestImages/Formats/Png/splash.png", - //"TestImages/Formats/Gif/rings.gif", + "TestImages/Formats/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/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs index ba12cdf107..9d9726a9c6 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs @@ -114,7 +114,7 @@ namespace ImageProcessorCore.Tests { string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); - Image image = new Image(stream); + Image image = new Image(stream) {Quality=256}; using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) { image.Resize(image.Width / 2, image.Height / 2, sampler, false, this.ProgressUpdate) From 640583c9dfca4e811af708521169f49b6bd4e9d7 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 19 Jul 2016 14:11:18 +1000 Subject: [PATCH 44/91] Comments Former-commit-id: 89e20176e82973d3383abfc6c205349ed2db3e52 Former-commit-id: da4936d80e2a5dde736254b7b2fe8ee881e04336 Former-commit-id: 65c3212d30a4cae2f499c2369f59b176ace757b8 --- src/ImageProcessorCore/Bootstrapper.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ImageProcessorCore/Bootstrapper.cs b/src/ImageProcessorCore/Bootstrapper.cs index 3294086fe4..1479de0e9a 100644 --- a/src/ImageProcessorCore/Bootstrapper.cs +++ b/src/ImageProcessorCore/Bootstrapper.cs @@ -9,9 +9,10 @@ namespace ImageProcessorCore using System.Collections.Generic; using System.Collections.ObjectModel; - using ImageProcessorCore.Formats; using System.Threading.Tasks; + using Formats; + /// /// Provides initialization code which allows extending the library. /// @@ -77,6 +78,7 @@ namespace ImageProcessorCore /// 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) From 6eeea5d7a311b162f8acdb0c709ffd7374c2d164 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 19 Jul 2016 14:26:44 +1000 Subject: [PATCH 45/91] Can now add pixel accessors. Former-commit-id: 2540005d263d7cd223413b314faafe322eee30a5 Former-commit-id: b772bbb168bc62a9cac94fedba81c658780a42b1 Former-commit-id: 4e610b0abbbc4742c813b240694ffeb6d689e167 --- src/ImageProcessorCore/Bootstrapper.cs | 28 +++++++++++++++++++++++--- src/ImageProcessorCore/project.json | 1 + 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/ImageProcessorCore/Bootstrapper.cs b/src/ImageProcessorCore/Bootstrapper.cs index 1479de0e9a..932bcf434e 100644 --- a/src/ImageProcessorCore/Bootstrapper.cs +++ b/src/ImageProcessorCore/Bootstrapper.cs @@ -8,7 +8,7 @@ namespace ImageProcessorCore using System; using System.Collections.Generic; using System.Collections.ObjectModel; - + using System.Reflection; using System.Threading.Tasks; using Formats; @@ -56,9 +56,16 @@ 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 IReadOnlyCollection ImageFormats => new ReadOnlyCollection(this.imageFormats); + public IReadOnlyDictionary> PixelAccessors => + new ReadOnlyDictionary>(this.pixelAccessors); /// /// Gets or sets the global parallel options for processing tasks in parallel. @@ -74,6 +81,21 @@ 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. /// 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", From baf0443f241a1fbb569de117b52bd6b1c2890fe7 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 20 Jul 2016 17:52:55 +1000 Subject: [PATCH 46/91] Clean up block and FDCT Former-commit-id: af0313bdaeecaa56e576f25a1fa688a333468759 Former-commit-id: 64319b3f8faaa68997ce43ffb284a1d5d4937a78 Former-commit-id: 971cc28d2e1a019c99800f231c3451c89d1bca49 --- src/ImageProcessorCore/Formats/Jpg/Block.cs | 39 +++- src/ImageProcessorCore/Formats/Jpg/FDCT.cs | 106 ++++++----- .../Formats/Jpg/JpegConstants.cs | 171 ++++++++++++++++++ .../Jpg/JpegDecoderCore.cs.REMOVED.git-id | 2 +- .../Formats/Jpg/JpegEncoderCore.cs | 8 +- 5 files changed, 271 insertions(+), 55 deletions(-) create mode 100644 src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs 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..27514d667f --- /dev/null +++ b/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs @@ -0,0 +1,171 @@ +// +// 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 = { 1, 1, 1 }; + + /// + /// Represents high detail chroma vertical subsampling. + /// + public static readonly byte[] ChromaFourFourFourVertical = { 1, 1, 1 }; + + /// + /// Represents medium detail chroma horizontal subsampling. + /// + public static readonly byte[] ChromaFourTwoTwoHorizontal = { 2, 1, 1 }; + + /// + /// Represents medium detail chroma vertical subsampling. + /// + public static readonly byte[] ChromaFourTwoTwoVertical = { 1, 1, 1 }; + + /// + /// Represents low detail chroma horizontal subsampling. + /// + public static readonly byte[] ChromaFourTwoZeroHorizontal = { 2, 1, 1 }; + + /// + /// Represents low detail chroma vertical subsampling. + /// + public static readonly byte[] ChromaFourTwoZeroVertical = { 2, 1, 1 }; + + /// + /// 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 (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 = 0xc0; + + /// + /// 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; + + /// + /// 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; + } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id index 6cf3cee55e..8d3e51867e 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 +af4a353d1d290be5dd231656bdb558fcdc143805 \ No newline at end of file diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs index a1f49a0c83..f211e05f8b 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs @@ -298,7 +298,7 @@ namespace ImageProcessorCore.Formats // writeDQT writes the Define Quantization Table marker. private void writeDQT() { - int markerlen = 2 + nQuantIndex * (1 + Block.blockSize); + int markerlen = 2 + nQuantIndex * (1 + Block.BlockSize); writeMarkerHeader(dqtMarker, markerlen); for (int i = 0; i < nQuantIndex; i++) { @@ -396,7 +396,7 @@ namespace ImageProcessorCore.Formats var h = (huffIndex)(2 * (int)q + 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]); @@ -567,7 +567,7 @@ namespace ImageProcessorCore.Formats for (int i = 0; i < nQuantIndex; i++) { - quant[i] = new byte[Block.blockSize]; + quant[i] = new byte[Block.BlockSize]; } if (image.Width >= (1 << 16) || image.Height >= (1 << 16)) @@ -592,7 +592,7 @@ namespace ImageProcessorCore.Formats // Initialize the quantization tables. for (int i = 0; i < nQuantIndex; i++) { - for (int j = 0; j < Block.blockSize; j++) + for (int j = 0; j < Block.BlockSize; j++) { int x = unscaledQuant[i, j]; x = (x * scale + 50) / 100; From 76da7c461e195e0fedbd85327ea4fa6c365755a1 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 20 Jul 2016 18:20:41 +1000 Subject: [PATCH 47/91] Use qualifier Former-commit-id: e7f5254e6a05eef6e2c41a7113ec43117419f3ad Former-commit-id: d8fb7b19aa30b69f2c83f6e5e93a965f0e95a9ae Former-commit-id: 07ec0c3ba4120079b88203fbc98e3d73df7080bf --- .../Formats/Jpg/JpegEncoderCore.cs | 485 ++++++++++-------- 1 file changed, 258 insertions(+), 227 deletions(-) diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs index f211e05f8b..3274907d59 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; @@ -11,173 +10,194 @@ 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, }; + 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, - }; + private static readonly int[] unzig = new[] + { + 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, + quantIndexLuminance = 0, + + quantIndexChrominance = 1, } private enum huffIndex { - huffIndexLuminanceDC = 0, - huffIndexLuminanceAC = 1, - huffIndexChrominanceDC = 2, - huffIndexChrominanceAC = 3, + huffIndexLuminanceDC = 0, + + huffIndexLuminanceAC = 1, + + huffIndexChrominanceDC = 2, + + huffIndexChrominanceAC = 3, } // 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. - { - 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, - }, - }; + private byte[,] unscaledQuant = new byte[,] + { + { + // 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, + }, + }; private class huffmanSpec { - public huffmanSpec(byte[] c, byte[] v) { count = c; values = v; } + public huffmanSpec(byte[] c, byte[] v) + { + this.count = c; + this.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[] { + private huffmanSpec[] theHuffmanSpec = new[] + { // 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 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 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}), + 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 }), + 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 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. @@ -194,11 +214,10 @@ namespace ImageProcessorCore.Formats 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; @@ -208,10 +227,11 @@ namespace ImageProcessorCore.Formats int nBits = (i + 1) << 24; 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,21 +240,28 @@ 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; + private uint bits; + + private uint nBits; + // quant is the scaled quantization tables, in zig-zag order. - private byte[][] quant = new byte[nQuantIndex][];//[Block.blockSize]; + private byte[][] quant = new byte[nQuantIndex][]; // [Block.blockSize]; + // theHuffmanLUT are compiled representations of theHuffmanSpec. private huffmanLUT[] theHuffmanLUT = new huffmanLUT[4]; + private JpegSubsample subsample; 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. @@ -247,12 +274,12 @@ 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; } @@ -260,8 +287,8 @@ namespace ImageProcessorCore.Formats // emitHuff emits the given value with the given Huffman encoder. private void emitHuff(huffIndex h, int v) { - uint x = theHuffmanLUT[(int)h].values[v]; - emit(x & ((1 << 24) - 1), x >> 24); + uint x = this.theHuffmanLUT[(int)h].values[v]; + this.emit(x & ((1 << 24) - 1), x >> 24); } // emitHuffRLE emits a run of runLength copies of value encoded with the given @@ -275,46 +302,45 @@ namespace ImageProcessorCore.Formats a = -v; b = v - 1; } + uint nBits = 0; - if (a < 0x100) - nBits = bitCount[a]; - else - nBits = 8 + (uint)bitCount[a >> 8]; + if (a < 0x100) nBits = this.bitCount[a]; + else nBits = 8 + (uint)this.bitCount[a >> 8]; - emitHuff(h, (int)((uint)(runLength << 4) | nBits)); - if (nBits > 0) emit((uint)b & (uint)((1 << ((int)nBits)) - 1), nBits); + this.emitHuff(h, (int)((uint)(runLength << 4) | nBits)); + if (nBits > 0) this.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); + this.buf[0] = 0xff; + this.buf[1] = marker; + this.buf[2] = (byte)(markerlen >> 8); + this.buf[3] = (byte)(markerlen & 0xff); + this.outputStream.Write(this.buf, 0, 4); } // writeDQT writes the Define Quantization Table marker. private void writeDQT() { int markerlen = 2 + nQuantIndex * (1 + Block.BlockSize); - writeMarkerHeader(dqtMarker, markerlen); + this.writeMarkerHeader(dqtMarker, markerlen); for (int i = 0; i < nQuantIndex; i++) { - writeByte((byte)i); - outputStream.Write(quant[i], 0, quant[i].Length); + this.writeByte((byte)i); + this.outputStream.Write(this.quant[i], 0, this.quant[i].Length); } } // 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 }; + // "default" to 4:2:0 + byte[] subsamples = { 0x22, 0x11, 0x11 }; + byte[] chroma = { 0x00, 0x01, 0x01 }; - switch (subsample) + switch (this.subsample) { case JpegSubsample.Ratio444: subsamples = new byte[] { 0x11, 0x11, 0x11 }; @@ -325,31 +351,34 @@ namespace ImageProcessorCore.Formats } 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); + this.writeMarkerHeader(sof0Marker, markerlen); + this.buf[0] = 8; // 8-bit color. + this.buf[1] = (byte)(hei >> 8); + this.buf[2] = (byte)(hei & 0xff); + this.buf[3] = (byte)(wid >> 8); + this.buf[4] = (byte)(wid & 0xff); + this.buf[5] = (byte)nComponent; if (nComponent == 1) { - buf[6] = 1; + this.buf[6] = 1; + // No subsampling for grayscale image. - buf[7] = 0x11; - buf[8] = 0x00; + this.buf[7] = 0x11; + this.buf[8] = 0x00; } else { for (int i = 0; i < nComponent; i++) { - buf[3 * i + 6] = (byte)(i + 1); + this.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]; + this.buf[3 * i + 7] = subsamples[i]; + this.buf[3 * i + 8] = chroma[i]; } } - outputStream.Write(buf, 0, 3 * (nComponent - 1) + 9); + + this.outputStream.Write(this.buf, 0, 3 * (nComponent - 1) + 9); } // writeDHT writes the Define Huffman Table marker. @@ -357,12 +386,12 @@ namespace ImageProcessorCore.Formats { byte[] headers = new byte[] { 0x00, 0x10, 0x01, 0x11 }; int markerlen = 2; - huffmanSpec[] specs = theHuffmanSpec; + huffmanSpec[] specs = this.theHuffmanSpec; if (nComponent == 1) { // Drop the Chrominance tables. - specs = new huffmanSpec[] { theHuffmanSpec[0], theHuffmanSpec[1] }; + specs = new[] { this.theHuffmanSpec[0], this.theHuffmanSpec[1] }; } foreach (var s in specs) @@ -370,14 +399,14 @@ namespace ImageProcessorCore.Formats markerlen += 1 + 16 + s.values.Length; } - writeMarkerHeader(dhtMarker, markerlen); + this.writeMarkerHeader(dhtMarker, markerlen); for (int i = 0; i < specs.Length; i++) { var s = specs[i]; - writeByte(headers[i]); - outputStream.Write(s.count, 0, s.count.Length); - outputStream.Write(s.values, 0, s.values.Length); + this.writeByte(headers[i]); + this.outputStream.Write(s.count, 0, s.count.Length); + this.outputStream.Write(s.values, 0, s.values.Length); } } @@ -389,8 +418,8 @@ namespace ImageProcessorCore.Formats FDCT.Transform(b); // 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 = div(b[0], 8 * this.quant[(int)q][0]); + this.emitHuffRLE((huffIndex)(2 * (int)q + 0), 0, dc - prevDC); // Emit the AC components. var h = (huffIndex)(2 * (int)q + 1); @@ -398,7 +427,7 @@ namespace ImageProcessorCore.Formats for (int zig = 1; zig < Block.BlockSize; zig++) { - int ac = div(b[unzig[zig]], 8 * quant[(int)q][zig]); + int ac = div(b[unzig[zig]], 8 * this.quant[(int)q][zig]); if (ac == 0) { @@ -408,16 +437,16 @@ 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; } @@ -460,48 +489,46 @@ namespace ImageProcessorCore.Formats } // 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 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, }; // 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 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, }; - // writeSOS writes the StartOfScan marker. private void writeSOS(PixelAccessor pixels) { - outputStream.Write(sosHeaderYCbCr, 0, sosHeaderYCbCr.Length); + 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) @@ -515,10 +542,10 @@ 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)0, prevDCY); + prevDCCb = this.writeBlock(cb, (quantIndex)1, prevDCCb); + prevDCCr = this.writeBlock(cr, (quantIndex)1, prevDCCr); } } } @@ -542,37 +569,43 @@ 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)0, prevDCY); } - scale_16x16_8x8(b, cb); - prevDCCb = writeBlock(b, (quantIndex)1, prevDCCb); - scale_16x16_8x8(b, cr); - prevDCCr = writeBlock(b, (quantIndex)1, prevDCCr); + + this.scale_16x16_8x8(b, cb); + prevDCCb = this.writeBlock(b, (quantIndex)1, prevDCCb); + this.scale_16x16_8x8(b, cr); + prevDCCr = this.writeBlock(b, (quantIndex)1, 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) + public void Encode(Stream stream, ImageBase image, int quality, JpegSubsample sample) { - this.outputStream = stream; - this.subsample = subsample; + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); - for (int i = 0; i < theHuffmanSpec.Length; i++) + ushort max = JpegConstants.MaxLength; + if (image.Width >= max || image.Height >= max) { - theHuffmanLUT[i] = new huffmanLUT(theHuffmanSpec[i]); + throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}."); } - for (int i = 0; i < nQuantIndex; i++) + this.outputStream = stream; + this.subsample = sample; + + // TODO: This should be static should it not? + for (int i = 0; i < this.theHuffmanSpec.Length; i++) { - quant[i] = new byte[Block.BlockSize]; + this.theHuffmanLUT[i] = new huffmanLUT(this.theHuffmanSpec[i]); } - if (image.Width >= (1 << 16) || image.Height >= (1 << 16)) + for (int i = 0; i < nQuantIndex; i++) { - throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}."); + this.quant[i] = new byte[Block.BlockSize]; } if (quality < 1) quality = 1; @@ -594,11 +627,11 @@ namespace ImageProcessorCore.Formats { for (int j = 0; j < Block.BlockSize; j++) { - int x = unscaledQuant[i, j]; + int x = this.unscaledQuant[i, j]; x = (x * scale + 50) / 100; if (x < 1) x = 1; if (x > 255) x = 255; - quant[i][j] = (byte)x; + this.quant[i][j] = (byte)x; } } @@ -606,39 +639,37 @@ namespace ImageProcessorCore.Formats int nComponent = 3; // Write the Start Of Image marker. - buf[0] = 0xff; - buf[1] = 0xd8; - stream.Write(buf, 0, 2); + this.buf[0] = 0xff; + this.buf[1] = 0xd8; + stream.Write(this.buf, 0, 2); // Write the quantization tables. - writeDQT(); + this.writeDQT(); // Write the image dimensions. - writeSOF0(image.Width, image.Height, nComponent); + this.writeSOF0(image.Width, image.Height, nComponent); // Write the Huffman tables. - writeDHT(nComponent); + this.writeDHT(nComponent); // Write the image data. using (PixelAccessor pixels = image.Lock()) { - writeSOS(pixels); + this.writeSOS(pixels); } // Write the End Of Image marker. - buf[0] = 0xff; - buf[1] = 0xd9; - stream.Write(buf, 0, 2); + this.buf[0] = 0xff; + this.buf[1] = 0xd9; + stream.Write(this.buf, 0, 2); stream.Flush(); } // 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); + if (a >= 0) return (a + (b >> 1)) / b; + else return -((-a + (b >> 1)) / b); } } } From 39cda9e40082254547b990c470ccdcf1b8916bf2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 20 Jul 2016 18:22:30 +1000 Subject: [PATCH 48/91] Use hex Former-commit-id: ac8bf5b9037a86dbd4ee9d99957b6898a8fc11c0 Former-commit-id: b3eab977b14fa51d583340c859838d587eefb086 Former-commit-id: 63c923257bd9340b3a27b92899a1818ea79c9717 --- src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs b/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs index 27514d667f..91dab75c69 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs @@ -18,32 +18,32 @@ namespace ImageProcessorCore.Formats /// /// Represents high detail chroma horizontal subsampling. /// - public static readonly byte[] ChromaFourFourFourHorizontal = { 1, 1, 1 }; + public static readonly byte[] ChromaFourFourFourHorizontal = { 0x1, 0x1, 0x1 }; /// /// Represents high detail chroma vertical subsampling. /// - public static readonly byte[] ChromaFourFourFourVertical = { 1, 1, 1 }; + public static readonly byte[] ChromaFourFourFourVertical = { 0x1, 0x1, 0x1 }; /// /// Represents medium detail chroma horizontal subsampling. /// - public static readonly byte[] ChromaFourTwoTwoHorizontal = { 2, 1, 1 }; + public static readonly byte[] ChromaFourTwoTwoHorizontal = { 0x2, 0x1, 0x1 }; /// /// Represents medium detail chroma vertical subsampling. /// - public static readonly byte[] ChromaFourTwoTwoVertical = { 1, 1, 1 }; + public static readonly byte[] ChromaFourTwoTwoVertical = { 0x1, 0x1, 0x1 }; /// /// Represents low detail chroma horizontal subsampling. /// - public static readonly byte[] ChromaFourTwoZeroHorizontal = { 2, 1, 1 }; + public static readonly byte[] ChromaFourTwoZeroHorizontal = { 0x2, 0x1, 0x1 }; /// /// Represents low detail chroma vertical subsampling. /// - public static readonly byte[] ChromaFourTwoZeroVertical = { 2, 1, 1 }; + public static readonly byte[] ChromaFourTwoZeroVertical = { 0x2, 0x1, 0x1 }; /// /// Describes component ids for start of frame components. From 9fb1afb594cc05394ff84f591905adb63ba8ba54 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 25 Jul 2016 14:11:04 +1000 Subject: [PATCH 49/91] More jpg cleanup Former-commit-id: 404825d34ba9f39abb1cac223330a3075c9498df Former-commit-id: 56c5a78950278bcbd4af5b1e992e38ed1ddd3a9b Former-commit-id: f75cdaa144f70e3c7ed8e7e750977dfa92866a26 --- .../Formats/Jpg/JpegConstants.cs | 23 +- .../Formats/Jpg/JpegEncoderCore.cs | 624 +++++++++--------- 2 files changed, 346 insertions(+), 301 deletions(-) diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs b/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs index 91dab75c69..22c6a8c9b5 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs @@ -18,32 +18,32 @@ namespace ImageProcessorCore.Formats /// /// Represents high detail chroma horizontal subsampling. /// - public static readonly byte[] ChromaFourFourFourHorizontal = { 0x1, 0x1, 0x1 }; + public static readonly byte[] ChromaFourFourFourHorizontal = { 0x11, 0x11, 0x11 }; /// /// Represents high detail chroma vertical subsampling. /// - public static readonly byte[] ChromaFourFourFourVertical = { 0x1, 0x1, 0x1 }; + public static readonly byte[] ChromaFourFourFourVertical = { 0x11, 0x11, 0x11 }; /// /// Represents medium detail chroma horizontal subsampling. /// - public static readonly byte[] ChromaFourTwoTwoHorizontal = { 0x2, 0x1, 0x1 }; + public static readonly byte[] ChromaFourTwoTwoHorizontal = { 0x22, 0x11, 0x11 }; /// /// Represents medium detail chroma vertical subsampling. /// - public static readonly byte[] ChromaFourTwoTwoVertical = { 0x1, 0x1, 0x1 }; + public static readonly byte[] ChromaFourTwoTwoVertical = { 0x11, 0x11, 0x11 }; /// /// Represents low detail chroma horizontal subsampling. /// - public static readonly byte[] ChromaFourTwoZeroHorizontal = { 0x2, 0x1, 0x1 }; + public static readonly byte[] ChromaFourTwoZeroHorizontal = { 0x22, 0x11, 0x11 }; /// /// Represents low detail chroma vertical subsampling. /// - public static readonly byte[] ChromaFourTwoZeroVertical = { 0x2, 0x1, 0x1 }; + public static readonly byte[] ChromaFourTwoZeroVertical = { 0x22, 0x11, 0x11 }; /// /// Describes component ids for start of frame components. @@ -100,6 +100,15 @@ namespace ImageProcessorCore.Formats /// 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) /// @@ -107,7 +116,7 @@ namespace ImageProcessorCore.Formats /// and component subsampling (e.g., 4:2:0). /// /// - public const byte SOF2 = 0xc0; + public const byte SOF2 = 0xc2; /// /// Define Huffman Table(s) diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs index 3274907d59..d2a7e35e05 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs @@ -9,30 +9,6 @@ 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 @@ -45,29 +21,29 @@ namespace ImageProcessorCore.Formats // 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, + 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[] - { - 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, - }; + { + 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; @@ -75,20 +51,20 @@ namespace ImageProcessorCore.Formats private enum quantIndex { - quantIndexLuminance = 0, + quantIndexLuminance = 0, - quantIndexChrominance = 1, + quantIndexChrominance = 1, } private enum huffIndex { - huffIndexLuminanceDC = 0, + huffIndexLuminanceDC = 0, - huffIndexLuminanceAC = 1, + huffIndexLuminanceAC = 1, - huffIndexChrominanceDC = 2, + huffIndexChrominanceDC = 2, - huffIndexChrominanceAC = 3, + huffIndexChrominanceAC = 3, } // unscaledQuant are the unscaled quantization tables in zig-zag order. Each @@ -99,19 +75,19 @@ namespace ImageProcessorCore.Formats { { // 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, - }, + 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, - }, + 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 @@ -135,68 +111,68 @@ namespace ImageProcessorCore.Formats 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 }), + 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 - }, + 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, + 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, 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 - }, + 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, + 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, }) }; @@ -242,7 +218,7 @@ namespace ImageProcessorCore.Formats private Stream outputStream; // buf is a scratch buffer. - private byte[] buf = new byte[16]; + private byte[] buffer = new byte[16]; // bits and nBits are accumulated bits to write to w. private uint bits; @@ -311,104 +287,6 @@ namespace ImageProcessorCore.Formats if (nBits > 0) this.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) - { - this.buf[0] = 0xff; - this.buf[1] = marker; - this.buf[2] = (byte)(markerlen >> 8); - this.buf[3] = (byte)(markerlen & 0xff); - this.outputStream.Write(this.buf, 0, 4); - } - - // writeDQT writes the Define Quantization Table marker. - private void writeDQT() - { - int markerlen = 2 + nQuantIndex * (1 + Block.BlockSize); - this.writeMarkerHeader(dqtMarker, markerlen); - for (int i = 0; i < nQuantIndex; i++) - { - this.writeByte((byte)i); - this.outputStream.Write(this.quant[i], 0, this.quant[i].Length); - } - } - - // writeSOF0 writes the Start Of Frame (Baseline) marker. - private void writeSOF0(int wid, int hei, int nComponent) - { - // "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; - } - - int markerlen = 8 + 3 * nComponent; - this.writeMarkerHeader(sof0Marker, markerlen); - this.buf[0] = 8; // 8-bit color. - this.buf[1] = (byte)(hei >> 8); - this.buf[2] = (byte)(hei & 0xff); - this.buf[3] = (byte)(wid >> 8); - this.buf[4] = (byte)(wid & 0xff); - this.buf[5] = (byte)nComponent; - if (nComponent == 1) - { - this.buf[6] = 1; - - // No subsampling for grayscale image. - this.buf[7] = 0x11; - this.buf[8] = 0x00; - } - else - { - for (int i = 0; i < nComponent; i++) - { - this.buf[3 * i + 6] = (byte)(i + 1); - - // We use 4:2:0 chroma subsampling. - this.buf[3 * i + 7] = subsamples[i]; - this.buf[3 * i + 8] = chroma[i]; - } - } - - this.outputStream.Write(this.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 = 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; - } - - this.writeMarkerHeader(dhtMarker, markerlen); - for (int i = 0; i < specs.Length; i++) - { - var s = specs[i]; - - this.writeByte(headers[i]); - this.outputStream.Write(s.count, 0, s.count.Length); - this.outputStream.Write(s.values, 0, s.values.Length); - } - } // 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 @@ -418,7 +296,7 @@ namespace ImageProcessorCore.Formats FDCT.Transform(b); // Emit the DC delta. - int dc = div(b[0], 8 * this.quant[(int)q][0]); + int dc = Round(b[0], 8 * this.quant[(int)q][0]); this.emitHuffRLE((huffIndex)(2 * (int)q + 0), 0, dc - prevDC); // Emit the AC components. @@ -427,7 +305,7 @@ namespace ImageProcessorCore.Formats for (int zig = 1; zig < Block.BlockSize; zig++) { - int ac = div(b[unzig[zig]], 8 * this.quant[(int)q][zig]); + int ac = Round(b[unzig[zig]], 8 * this.quant[(int)q][zig]); if (ac == 0) { @@ -488,16 +366,26 @@ namespace ImageProcessorCore.Formats } } - // sosHeaderY is the SOS marker "\xff\xda" followed by 8 bytes: + // 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, }; + 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 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", @@ -506,81 +394,22 @@ namespace ImageProcessorCore.Formats // - 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[] + private readonly byte[] SOSHeaderYCbCr = { - 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, - 0x00, 0x3f, 0x00, + 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) }; - // writeSOS writes the StartOfScan marker. - private void writeSOS(PixelAccessor pixels) - { - this.outputStream.Write(this.sosHeaderYCbCr, 0, this.sosHeaderYCbCr.Length); - - switch (this.subsample) - { - case JpegSubsample.Ratio444: - this.encode444(pixels); - break; - case JpegSubsample.Ratio420: - this.encode420(pixels); - break; - } - - // Pad the last byte with 1's. - this.emit(0x7f, 7); - } - - private void encode444(PixelAccessor pixels) - { - Block b = new Block(); - Block cb = new Block(); - Block cr = new Block(); - int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - - for (int y = 0; y < pixels.Height; y += 8) - { - for (int x = 0; x < pixels.Width; x += 8) - { - this.toYCbCr(pixels, x, y, b, cb, cr); - prevDCY = this.writeBlock(b, (quantIndex)0, prevDCY); - prevDCCb = this.writeBlock(cb, (quantIndex)1, prevDCCb); - prevDCCr = this.writeBlock(cr, (quantIndex)1, prevDCCr); - } - } - } - - private void encode420(PixelAccessor pixels) - { - Block b = new Block(); - Block[] cb = new Block[4]; - Block[] cr = new Block[4]; - int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - - for (int i = 0; i < 4; i++) cb[i] = new Block(); - for (int i = 0; i < 4; i++) cr[i] = new Block(); - - for (int y = 0; y < pixels.Height; y += 16) - { - for (int x = 0; x < pixels.Width; x += 16) - { - for (int i = 0; i < 4; i++) - { - int xOff = (i & 1) * 8; - int yOff = (i & 2) * 4; - - this.toYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]); - prevDCY = this.writeBlock(b, (quantIndex)0, prevDCY); - } - - this.scale_16x16_8x8(b, cb); - prevDCCb = this.writeBlock(b, (quantIndex)1, prevDCCb); - this.scale_16x16_8x8(b, cr); - prevDCCr = this.writeBlock(b, (quantIndex)1, 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 sample) @@ -636,40 +465,247 @@ namespace ImageProcessorCore.Formats } // Compute number of components based on input image type. - int nComponent = 3; + int componentCount = 3; // Write the Start Of Image marker. - this.buf[0] = 0xff; - this.buf[1] = 0xd8; - stream.Write(this.buf, 0, 2); + // TODO: JFIF header etc. + this.buffer[0] = 0xff; + this.buffer[1] = 0xd8; + stream.Write(this.buffer, 0, 2); // Write the quantization tables. - this.writeDQT(); + this.WriteDQT(); // Write the image dimensions. - this.writeSOF0(image.Width, image.Height, nComponent); + this.WriteSOF0(image.Width, image.Height, componentCount); // Write the Huffman tables. - this.writeDHT(nComponent); + this.WriteDHT(componentCount); // Write the image data. using (PixelAccessor pixels = image.Lock()) { - this.writeSOS(pixels); + this.WriteSOS(pixels); } // Write the End Of Image marker. - this.buf[0] = 0xff; - this.buf[1] = 0xd9; - stream.Write(this.buf, 0, 2); + this.buffer[0] = 0xff; + this.buffer[1] = 0xd9; + stream.Write(this.buffer, 0, 2); stream.Flush(); } - // div returns a/b rounded to the nearest integer, instead of rounded to zero. - private static int div(int a, int b) + /// + /// 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 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 = grey 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; + } + + 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(PixelAccessor pixels) + { + // TODO: We should allow grayscale writing. + this.outputStream.Write(this.SOSHeaderYCbCr, 0, this.SOSHeaderYCbCr.Length); + + switch (this.subsample) + { + case JpegSubsample.Ratio444: + this.Encode444(pixels); + break; + case JpegSubsample.Ratio420: + this.Encode420(pixels); + break; + } + + // Pad the last byte with 1's. + this.emit(0x7f, 7); + } + + /// + /// Encodes the image with no subsampling. + /// + /// The pixel accessor providing acces to the image pixels. + private void Encode444(PixelAccessor pixels) + { + Block b = new Block(); + Block cb = new Block(); + Block cr = new Block(); + int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; + + for (int y = 0; y < pixels.Height; y += 8) + { + for (int x = 0; x < pixels.Width; x += 8) + { + this.toYCbCr(pixels, x, y, b, cb, cr); + prevDCY = this.writeBlock(b, (quantIndex)0, prevDCY); + prevDCCb = this.writeBlock(cb, (quantIndex)1, prevDCCb); + prevDCCr = this.writeBlock(cr, (quantIndex)1, prevDCCr); + } + } + } + + /// + /// 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(PixelAccessor pixels) + { + Block b = new Block(); + Block[] cb = new Block[4]; + Block[] cr = new Block[4]; + int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; + + for (int i = 0; i < 4; i++) cb[i] = new Block(); + for (int i = 0; i < 4; i++) cr[i] = new Block(); + + for (int y = 0; y < pixels.Height; y += 16) + { + for (int x = 0; x < pixels.Width; x += 16) + { + for (int i = 0; i < 4; i++) + { + int xOff = (i & 1) * 8; + int yOff = (i & 2) * 4; + + this.toYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]); + prevDCY = this.writeBlock(b, (quantIndex)0, prevDCY); + } + + this.scale_16x16_8x8(b, cb); + prevDCCb = this.writeBlock(b, (quantIndex)1, prevDCCb); + this.scale_16x16_8x8(b, cr); + prevDCCr = this.writeBlock(b, (quantIndex)1, prevDCCr); + } + } + } + + /// + /// Writes the header for a marker with the given length. + /// + /// The marker to write. + /// The marker length. + private void WriteMarkerHeader(byte marker, int length) { - if (a >= 0) return (a + (b >> 1)) / b; - else return -((-a + (b >> 1)) / b); + // 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); } } } From 46254f2a62fa5359f96841d93758c380f0a14e40 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 26 Jul 2016 13:47:33 +1000 Subject: [PATCH 50/91] More encoder cleanup. Former-commit-id: fe7074b76c3bb682667c1f00077a87373cf15b4b Former-commit-id: 875477a03fa94ab5c72ab2ebd4fc8303a36403fe Former-commit-id: b06f1d713c57778d7d6d29dd2e6000becc5ff929 --- .../Formats/Jpg/JpegEncoderCore.cs | 518 ++++++++++-------- 1 file changed, 295 insertions(+), 223 deletions(-) diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs index d2a7e35e05..384f18b36a 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs @@ -9,35 +9,12 @@ namespace ImageProcessorCore.Formats internal class JpegEncoderCore { - // "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[] + /// + /// 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, @@ -45,165 +22,154 @@ namespace ImageProcessorCore.Formats 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, }; - private const int nQuantIndex = 2; + private const int NQuantIndex = 2; - private const int nHuffIndex = 4; - - private enum quantIndex - { - quantIndexLuminance = 0, - - quantIndexChrominance = 1, - } - - 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. - 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, - }, - }; - - private class huffmanSpec - { - public huffmanSpec(byte[] c, byte[] v) + /// + /// 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 = { + { + // 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, + }, { - this.count = c; - this.values = v; + // 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, } + }; - 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[] - { + /// + /// The Huffman encoding specifications. + /// This encoder uses the same Huffman encoding for all images. + /// + private readonly HuffmanSpec[] theHuffmanSpec = { // Luminance DC. - new huffmanSpec( + new HuffmanSpec( new byte[] - { - 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 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 HuffmanSpec( new byte[] - { - 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125 - }, + { + 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( + { + 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, 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 HuffmanSpec( new byte[] - { - 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119 - }, + { + 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, - }) + { + 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; } - this.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++) { - this.values[s.values[k]] = (uint)(nBits | code); + this.Values[s.Values[k]] = (uint)(nBits | code); code++; k++; } @@ -217,32 +183,52 @@ namespace ImageProcessorCore.Formats // writing. All attempted writes after the first error become no-ops. private Stream outputStream; - // buf is a scratch buffer. - private byte[] buffer = new byte[16]; + /// + /// A scratch buffer to reduce allocations. + /// + private readonly byte[] buffer = new byte[16]; - // bits and nBits are accumulated bits to write to w. + /// + /// The accumulated bits to write to the stream. + /// private uint bits; + /// + /// The accumulated bits to write to the stream. + /// private uint nBits; - // quant is the scaled quantization tables, in zig-zag order. - private byte[][] quant = new byte[nQuantIndex][]; // [Block.blockSize]; + /// + /// The scaled quantization tables, in zig-zag order. + /// + private readonly byte[][] quant = new byte[NQuantIndex][]; // [Block.blockSize]; - // theHuffmanLUT are compiled representations of theHuffmanSpec. - private huffmanLUT[] theHuffmanLUT = new huffmanLUT[4]; + // 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; 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); @@ -250,8 +236,8 @@ namespace ImageProcessorCore.Formats while (nBits >= 8) { byte b = (byte)(bits >> 24); - this.writeByte(b); - if (b == 0xff) this.writeByte(0x00); + this.WriteByte(b); + if (b == 0xff) this.WriteByte(0x00); bits <<= 8; nBits -= 8; } @@ -260,52 +246,75 @@ namespace ImageProcessorCore.Formats 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 = this.theHuffmanLUT[(int)h].values[v]; - this.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; + a = -value; + b = value - 1; } - uint nBits = 0; - if (a < 0x100) nBits = this.bitCount[a]; - else nBits = 8 + (uint)this.bitCount[a >> 8]; + uint bt; + if (a < 0x100) + { + bt = this.bitCount[a]; + } + else + { + bt = 8 + (uint)this.bitCount[a >> 8]; + } - this.emitHuff(h, (int)((uint)(runLength << 4) | nBits)); - if (nBits > 0) this.emit((uint)b & (uint)((1 << ((int)nBits)) - 1), nBits); + this.EmitHuff(index, (int)((uint)(runLength << 4) | bt)); + if (bt > 0) + { + 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 = Round(b[0], 8 * this.quant[(int)q][0]); - this.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++) { - int ac = Round(b[unzig[zig]], 8 * this.quant[(int)q][zig]); + int ac = Round(block[Unzig[zig]], 8 * this.quant[(int)index][zig]); if (ac == 0) { @@ -315,22 +324,22 @@ namespace ImageProcessorCore.Formats { while (runLength > 15) { - this.emitHuff(h, 0xf0); + this.EmitHuff(h, 0xf0); runLength -= 16; } - this.emitHuffRLE(h, runLength, ac); + this.EmitHuffRLE(h, runLength, ac); runLength = 0; } } - if (runLength > 0) this.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(PixelAccessor pixels, int x, int y, Block yBlock, Block cbBlock, Block crBlock) { int xmax = pixels.Width - 1; int ymax = pixels.Height - 1; @@ -347,9 +356,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++) { @@ -359,8 +372,8 @@ 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; } } } @@ -429,10 +442,10 @@ namespace ImageProcessorCore.Formats // 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]); + this.theHuffmanLUT[i] = new HuffmanLut(this.theHuffmanSpec[i]); } - for (int i = 0; i < nQuantIndex; i++) + for (int i = 0; i < NQuantIndex; i++) { this.quant[i] = new byte[Block.BlockSize]; } @@ -452,7 +465,7 @@ namespace ImageProcessorCore.Formats } // Initialize the quantization tables. - for (int i = 0; i < nQuantIndex; i++) + for (int i = 0; i < NQuantIndex; i++) { for (int j = 0; j < Block.BlockSize; j++) { @@ -516,11 +529,11 @@ namespace ImageProcessorCore.Formats /// private void WriteDQT() { - int markerlen = 2 + nQuantIndex * (1 + Block.BlockSize); + int markerlen = 2 + NQuantIndex * (1 + Block.BlockSize); this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen); - for (int i = 0; i < nQuantIndex; i++) + for (int i = 0; i < NQuantIndex; i++) { - this.writeByte((byte)i); + this.WriteByte((byte)i); this.outputStream.Write(this.quant[i], 0, this.quant[i].Length); } } @@ -587,7 +600,7 @@ namespace ImageProcessorCore.Formats { byte[] headers = { 0x00, 0x10, 0x01, 0x11 }; int markerlen = 2; - huffmanSpec[] specs = this.theHuffmanSpec; + HuffmanSpec[] specs = this.theHuffmanSpec; if (nComponent == 1) { @@ -597,17 +610,17 @@ namespace ImageProcessorCore.Formats foreach (var s in specs) { - markerlen += 1 + 16 + s.values.Length; + markerlen += 1 + 16 + s.Values.Length; } this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen); for (int i = 0; i < specs.Length; i++) { - huffmanSpec spec = specs[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); + this.WriteByte(headers[i]); + this.outputStream.Write(spec.Count, 0, spec.Count.Length); + this.outputStream.Write(spec.Values, 0, spec.Values.Length); } } @@ -631,9 +644,11 @@ namespace ImageProcessorCore.Formats } // Pad the last byte with 1's. - this.emit(0x7f, 7); + this.Emit(0x7f, 7); } + + /// /// Encodes the image with no subsampling. /// @@ -649,10 +664,10 @@ namespace ImageProcessorCore.Formats { for (int x = 0; x < pixels.Width; x += 8) { - this.toYCbCr(pixels, x, y, b, cb, cr); - prevDCY = this.writeBlock(b, (quantIndex)0, prevDCY); - prevDCCb = this.writeBlock(cb, (quantIndex)1, prevDCCb); - prevDCCr = this.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); } } } @@ -681,14 +696,14 @@ namespace ImageProcessorCore.Formats int xOff = (i & 1) * 8; int yOff = (i & 2) * 4; - this.toYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]); - prevDCY = this.writeBlock(b, (quantIndex)0, prevDCY); + this.ToYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]); + prevDCY = this.WriteBlock(b, QuantIndex.Luminance, prevDCY); } - this.scale_16x16_8x8(b, cb); - prevDCCb = this.writeBlock(b, (quantIndex)1, prevDCCb); - this.scale_16x16_8x8(b, cr); - prevDCCr = this.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); } } } @@ -707,5 +722,62 @@ namespace ImageProcessorCore.Formats this.buffer[3] = (byte)(length & 0xff); this.outputStream.Write(this.buffer, 0, 4); } + + /// + /// Enumerates the Huffman tables + /// + private enum HuffIndex + { + LuminanceDC = 0, + + LuminanceAC = 1, + + ChrominanceDC = 2, + + ChrominanceAC = 3, + } + + /// + /// Enumerates the quantization tables + /// + private enum QuantIndex + { + /// + /// Luminance + /// + Luminance = 0, + + /// + /// Chrominance + /// + Chrominance = 1, + } + + /// + /// 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) + { + this.Count = count; + this.Values = values; + } + + /// + /// Gets count[i] - The number of codes of length i bits. + /// + public readonly byte[] Count; + + /// + /// Gets value[i] - The decoded value of the i'th codeword. + /// + public readonly byte[] Values; + } } } From ff49873ffc67e3ab161875937a0aaf80b3515d6e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 26 Jul 2016 13:53:46 +1000 Subject: [PATCH 51/91] Use "this" Former-commit-id: d4832e3e32185f30e5654c3abfb717e5ed44cfa2 Former-commit-id: 13f722c467d87236599c45bb0e4fe1f95a080740 Former-commit-id: 9204894e3776899bbf5ee33e2e35b953eb41ca35 --- .../Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id index 8d3e51867e..72b7f5308e 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id +++ b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id @@ -1 +1 @@ -af4a353d1d290be5dd231656bdb558fcdc143805 \ No newline at end of file +b643c4d82973c427e30384f20fdc46b29d913782 \ No newline at end of file From e35be06e1ad9415f87ea1234d95cb6170dcdf155 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 26 Jul 2016 16:00:50 +1000 Subject: [PATCH 52/91] Now decodes grayscale inside the core decoder Former-commit-id: 69bc6a527f9d013eba29649a5d6b0893cc9a1317 Former-commit-id: 0185f5dc2e1f738ce87f757c7427fdc4c6cb8335 Former-commit-id: 138fa9716bb44d037b7e5dbd014bcfbe9a8401e7 --- .../Formats/Bmp/BmpDecoderCore.cs | 2 +- .../Formats/Jpg/JpegConstants.cs | 29 ++++++++++++++++ .../Formats/Jpg/JpegDecoder.cs | 34 ------------------- .../Jpg/JpegDecoderCore.cs.REMOVED.git-id | 2 +- .../ImageProcessorCore.Tests/FileTestBase.cs | 12 +++---- 5 files changed, 37 insertions(+), 42 deletions(-) diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs index da92e6a63c..fadfc1ec66 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs @@ -51,7 +51,7 @@ namespace ImageProcessorCore.Formats /// /// 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. diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs b/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs index 22c6a8c9b5..4df086f292 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs @@ -143,6 +143,24 @@ namespace ImageProcessorCore.Formats /// 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 /// @@ -168,6 +186,7 @@ namespace ImageProcessorCore.Formats /// /// Application specific marker for marking the jpeg format. + /// /// public const byte APP0 = 0xe0; @@ -175,6 +194,16 @@ namespace ImageProcessorCore.Formats /// 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..89aa4e0014 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. @@ -96,39 +95,6 @@ namespace ImageProcessorCore.Formats 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."); - } } /// diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id index 72b7f5308e..a5c139745c 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id +++ b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id @@ -1 +1 @@ -b643c4d82973c427e30384f20fdc46b29d913782 \ No newline at end of file +7a5076971068e0f389da2fb1e8b25216f4049718 \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/FileTestBase.cs b/tests/ImageProcessorCore.Tests/FileTestBase.cs index 100e74d0ff..f5c83cd71d 100644 --- a/tests/ImageProcessorCore.Tests/FileTestBase.cs +++ b/tests/ImageProcessorCore.Tests/FileTestBase.cs @@ -19,17 +19,17 @@ namespace ImageProcessorCore.Tests /// protected static readonly List Files = new List { - //"TestImages/Formats/Jpg/Floorplan.jpeg", // Perf: Enable for local testing only + "TestImages/Formats/Jpg/Floorplan.jpeg", // Perf: Enable for local testing only "TestImages/Formats/Jpg/Calliphora.jpg", - //"TestImages/Formats/Jpg/fb.jpg", // Perf: Enable for local testing only - //"TestImages/Formats/Jpg/progress.jpg", // Perf: Enable for local testing only + "TestImages/Formats/Jpg/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 - "TestImages/Formats/Bmp/Car.bmp", + //"TestImages/Formats/Bmp/Car.bmp", // "TestImages/Formats/Bmp/neg_height.bmp", // Perf: Enable for local testing only //"TestImages/Formats/Png/blur.png", // Perf: Enable for local testing only //"TestImages/Formats/Png/indexed.png", // Perf: Enable for local testing only - "TestImages/Formats/Png/splash.png", - "TestImages/Formats/Gif/rings.gif", + //"TestImages/Formats/Png/splash.png", + //"TestImages/Formats/Gif/rings.gif", //"TestImages/Formats/Gif/giphy.gif" // Perf: Enable for local testing only }; From c891465b101eff668f2c2e66ee111f9fce4655ea Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 26 Jul 2016 17:05:57 +1000 Subject: [PATCH 53/91] Jpeg now generic Former-commit-id: 12923d7bed65f47787f046e0a8d625817ae3ff2f Former-commit-id: 7f64594e18a9e6bad02bf8be8fd9330515b69e0d Former-commit-id: 377629b2cbcb234c6eabdefe58d68151b00d883e --- src/ImageProcessorCore/Bootstrapper.cs | 2 +- .../Colors/Colorspaces/IAlmostEquatable.cs | 29 + .../Colors/Colorspaces/YCbCr.cs | 182 ++++ .../{ => Colors}/PackedVector/Color.cs | 0 .../PackedVector/ColorDefinitions.cs | 0 .../PackedVector/ColorspaceTransforms.cs | 285 +++++++ .../PackedVector/IPackedVector.cs | 0 .../Formats/Bmp/BmpDecoder.cs | 9 +- .../Formats/Bmp/BmpEncoder.cs | 4 +- .../Formats/IImageDecoder.cs | 7 +- src/ImageProcessorCore/Formats/Jpg/Block.cs | 44 + src/ImageProcessorCore/Formats/Jpg/FDCT.cs | 161 ++++ src/ImageProcessorCore/Formats/Jpg/IDCT.cs | 163 ++++ .../Formats/Jpg/JpegDecoder.cs | 139 +++ .../Jpg/JpegDecoderCore.cs.REMOVED.git-id | 1 + .../Formats/Jpg/JpegEncoder.cs | 96 +++ .../Formats/Jpg/JpegEncoderCore.cs | 796 ++++++++++++++++++ .../Formats/Jpg/JpegFormat.cs | 19 + .../Formats/Jpg/JpegSubsample.cs | 25 + src/ImageProcessorCore/Formats/Jpg/README.md | 3 + .../Image/ImageExtensions.cs | 21 +- 21 files changed, 1967 insertions(+), 19 deletions(-) create mode 100644 src/ImageProcessorCore/Colors/Colorspaces/IAlmostEquatable.cs create mode 100644 src/ImageProcessorCore/Colors/Colorspaces/YCbCr.cs rename src/ImageProcessorCore/{ => Colors}/PackedVector/Color.cs (100%) rename src/ImageProcessorCore/{ => Colors}/PackedVector/ColorDefinitions.cs (100%) create mode 100644 src/ImageProcessorCore/Colors/PackedVector/ColorspaceTransforms.cs rename src/ImageProcessorCore/{ => Colors}/PackedVector/IPackedVector.cs (100%) create mode 100644 src/ImageProcessorCore/Formats/Jpg/Block.cs create mode 100644 src/ImageProcessorCore/Formats/Jpg/FDCT.cs create mode 100644 src/ImageProcessorCore/Formats/Jpg/IDCT.cs create mode 100644 src/ImageProcessorCore/Formats/Jpg/JpegDecoder.cs create mode 100644 src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id create mode 100644 src/ImageProcessorCore/Formats/Jpg/JpegEncoder.cs create mode 100644 src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs create mode 100644 src/ImageProcessorCore/Formats/Jpg/JpegFormat.cs create mode 100644 src/ImageProcessorCore/Formats/Jpg/JpegSubsample.cs create mode 100644 src/ImageProcessorCore/Formats/Jpg/README.md diff --git a/src/ImageProcessorCore/Bootstrapper.cs b/src/ImageProcessorCore/Bootstrapper.cs index 932bcf434e..99fa6f1555 100644 --- a/src/ImageProcessorCore/Bootstrapper.cs +++ b/src/ImageProcessorCore/Bootstrapper.cs @@ -39,7 +39,7 @@ namespace ImageProcessorCore this.imageFormats = new List { new BmpFormat(), - //new JpegFormat(), + new JpegFormat(), new PngFormat(), new GifFormat() }; diff --git a/src/ImageProcessorCore/Colors/Colorspaces/IAlmostEquatable.cs b/src/ImageProcessorCore/Colors/Colorspaces/IAlmostEquatable.cs new file mode 100644 index 0000000000..4677c3415f --- /dev/null +++ b/src/ImageProcessorCore/Colors/Colorspaces/IAlmostEquatable.cs @@ -0,0 +1,29 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + + /// + /// Defines a generalized method that a value type or class implements to create + /// a type-specific method for determining approximate equality of instances. + /// + /// The type of objects to compare. + /// The object specifying the type to specify precision with. + public interface IAlmostEquatable where TP : struct, IComparable + { + /// + /// Indicates whether the current object is equal to another object of the same type + /// when compared to the specified precision level. + /// + /// An object to compare with this object. + /// The object specifying the level of precision. + /// + /// true if the current object is equal to the other parameter; otherwise, false. + /// + bool AlmostEquals(T other, TP precision); + } +} diff --git a/src/ImageProcessorCore/Colors/Colorspaces/YCbCr.cs b/src/ImageProcessorCore/Colors/Colorspaces/YCbCr.cs new file mode 100644 index 0000000000..faba036ade --- /dev/null +++ b/src/ImageProcessorCore/Colors/Colorspaces/YCbCr.cs @@ -0,0 +1,182 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Represents an YCbCr (luminance, chroma, chroma) color conforming to the + /// Full range standard used in digital imaging systems. + /// + /// + public struct YCbCr : IEquatable, IAlmostEquatable + { + /// + /// Represents a that has Y, Cb, and Cr values set to zero. + /// + public static readonly YCbCr Empty = default(YCbCr); + + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.001f; + + /// + /// The backing vector for SIMD support. + /// + private Vector3 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The y luminance component. + /// The cb chroma component. + /// The cr chroma component. + public YCbCr(float y, float cb, float cr) + : this() + { + this.backingVector = Vector3.Clamp(new Vector3(y, cb, cr), Vector3.Zero, new Vector3(255)); + } + + /// + /// Gets the Y luminance component. + /// A value ranging between 0 and 255. + /// + public float Y => this.backingVector.X; + + /// + /// Gets the Cb chroma component. + /// A value ranging between 0 and 255. + /// + public float Cb => this.backingVector.Y; + + /// + /// Gets the Cr chroma component. + /// A value ranging between 0 and 255. + /// + public float Cr => this.backingVector.Z; + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// + /// The instance of to convert. + /// + /// + /// An instance of . + /// + public static implicit operator YCbCr(Color color) + { + float r = color.R; + float g = color.G; + float b = color.B; + + float y = (float)((0.299 * r) + (0.587 * g) + (0.114 * b)); + float cb = 128 + (float)((-0.168736 * r) - (0.331264 * g) + (0.5 * b)); + float cr = 128 + (float)((0.5 * r) - (0.418688 * g) - (0.081312 * b)); + + return new YCbCr(y, cb, cr); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(YCbCr left, YCbCr right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(YCbCr left, YCbCr right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + return GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "YCbCr [ Empty ]"; + } + + return $"YCbCr [ Y={this.Y:#0.##}, Cb={this.Cb:#0.##}, Cr={this.Cr:#0.##} ]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is YCbCr) + { + return this.Equals((YCbCr)obj); + } + + return false; + } + + /// + public bool Equals(YCbCr other) + { + return this.AlmostEquals(other, Epsilon); + } + + /// + public bool AlmostEquals(YCbCr other, float precision) + { + Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); + + return result.X < precision + && result.Y < precision + && result.Z < precision; + } + + /// + /// 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(YCbCr color) => color.backingVector.GetHashCode(); + } +} diff --git a/src/ImageProcessorCore/PackedVector/Color.cs b/src/ImageProcessorCore/Colors/PackedVector/Color.cs similarity index 100% rename from src/ImageProcessorCore/PackedVector/Color.cs rename to src/ImageProcessorCore/Colors/PackedVector/Color.cs diff --git a/src/ImageProcessorCore/PackedVector/ColorDefinitions.cs b/src/ImageProcessorCore/Colors/PackedVector/ColorDefinitions.cs similarity index 100% rename from src/ImageProcessorCore/PackedVector/ColorDefinitions.cs rename to src/ImageProcessorCore/Colors/PackedVector/ColorDefinitions.cs diff --git a/src/ImageProcessorCore/Colors/PackedVector/ColorspaceTransforms.cs b/src/ImageProcessorCore/Colors/PackedVector/ColorspaceTransforms.cs new file mode 100644 index 0000000000..92b06762e5 --- /dev/null +++ b/src/ImageProcessorCore/Colors/PackedVector/ColorspaceTransforms.cs @@ -0,0 +1,285 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + + /// + /// 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 + { + ///// + ///// Allows the implicit conversion of an instance of to a + ///// . + ///// + ///// The instance of to convert. + ///// + ///// An instance of . + ///// + //public static implicit operator Color(Bgra32 color) + //{ + // return new Color(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f); + //} + + ///// + ///// Allows the implicit conversion of an instance of to a + ///// . + ///// + ///// The instance of to convert. + ///// + ///// An instance of . + ///// + //public static implicit operator Color(Cmyk cmykColor) + //{ + // 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); + //} + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + public static implicit operator Color(YCbCr color) + { + float y = color.Y; + float cb = color.Cb - 128; + float cr = color.Cr - 128; + + 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, 255); + } + + ///// + ///// Allows the implicit conversion of an instance of to a + ///// . + ///// + ///// The instance of to convert. + ///// + ///// An instance of . + ///// + //public static implicit operator Color(CieXyz color) + //{ + // float x = color.X / 100F; + // float y = color.Y / 100F; + // float z = color.Z / 100F; + + // // Then XYZ to RGB (multiplication by 100 was done above already) + // float r = (x * 3.2406F) + (y * -1.5372F) + (z * -0.4986F); + // 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)); + //} + + ///// + ///// Allows the implicit conversion of an instance of to a + ///// . + ///// + ///// The instance of to convert. + ///// + ///// An instance of . + ///// + //public static implicit operator Color(Hsv color) + //{ + // float s = color.S; + // float v = color.V; + + // if (Math.Abs(s) < Epsilon) + // { + // return new Color(v, v, v, 1); + // } + + // float h = (Math.Abs(color.H - 360) < Epsilon) ? 0 : color.H / 60; + // 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 r, g, b; + // switch (i) + // { + // case 0: + // r = v; + // g = t; + // b = p; + // break; + + // case 1: + // r = q; + // g = v; + // b = p; + // break; + + // case 2: + // r = p; + // g = v; + // b = t; + // break; + + // case 3: + // r = p; + // g = q; + // b = v; + // break; + + // case 4: + // r = t; + // g = p; + // b = v; + // break; + + // default: + // r = v; + // g = p; + // b = q; + // break; + // } + + // return new Color(r, g, b); + //} + + ///// + ///// Allows the implicit conversion of an instance of to a + ///// . + ///// + ///// The instance of to convert. + ///// + ///// An instance of . + ///// + //public static implicit operator Color(Hsl color) + //{ + // float rangedH = color.H / 360f; + // float r = 0; + // float g = 0; + // float b = 0; + // float s = color.S; + // float l = color.L; + + // if (Math.Abs(l) > Epsilon) + // { + // if (Math.Abs(s) < Epsilon) + // { + // r = g = b = l; + // } + // else + // { + // float temp2 = (l < 0.5f) ? l * (1f + s) : l + s - (l * s); + // float temp1 = (2f * l) - temp2; + + // r = GetColorComponent(temp1, temp2, rangedH + 0.3333333F); + // g = GetColorComponent(temp1, temp2, rangedH); + // b = GetColorComponent(temp1, temp2, rangedH - 0.3333333F); + // } + // } + + // return new Color(r, g, b); + //} + + ///// + ///// Allows the implicit conversion of an instance of to a + ///// . + ///// + ///// The instance of to convert. + ///// + ///// An instance of . + ///// + //public static implicit operator Color(CieLab cieLabColor) + //{ + // // First convert back to XYZ... + // float y = (cieLabColor.L + 16F) / 116F; + // float x = (cieLabColor.A / 500F) + y; + // float z = y - (cieLabColor.B / 200F); + + // float x3 = x * x * x; + // float y3 = y * y * y; + // float z3 = z * z * z; + + // x = x3 > 0.008856F ? x3 : (x - 0.137931F) / 7.787F; + // y = (cieLabColor.L > 7.999625F) ? y3 : (cieLabColor.L / 903.3F); + // z = (z3 > 0.008856F) ? z3 : (z - 0.137931F) / 7.787F; + + // x *= 0.95047F; + // z *= 1.08883F; + + // // Then XYZ to RGB (multiplication by 100 was done above already) + // float r = (x * 3.2406F) + (y * -1.5372F) + (z * -0.4986F); + // 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)); + //} + + /// + /// Gets the color component from the given values. + /// + /// The first value. + /// The second value. + /// The third value. + /// + /// The . + /// + private static float GetColorComponent(float first, float second, float third) + { + third = MoveIntoRange(third); + if (third < 0.1666667F) + { + return first + ((second - first) * 6.0f * third); + } + + if (third < 0.5) + { + return second; + } + + if (third < 0.6666667F) + { + return first + ((second - first) * (0.6666667F - third) * 6.0f); + } + + return first; + } + + /// + /// Moves the specific value within the acceptable range for + /// conversion. + /// Used for converting colors to this type. + /// + /// The value to shift. + /// + /// The . + /// + private static float MoveIntoRange(float value) + { + if (value < 0.0) + { + value += 1.0f; + } + else if (value > 1.0) + { + value -= 1.0f; + } + + return value; + } + } +} diff --git a/src/ImageProcessorCore/PackedVector/IPackedVector.cs b/src/ImageProcessorCore/Colors/PackedVector/IPackedVector.cs similarity index 100% rename from src/ImageProcessorCore/PackedVector/IPackedVector.cs rename to src/ImageProcessorCore/Colors/PackedVector/IPackedVector.cs diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs index e61f049fc3..bd05cc7e86 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoder.cs @@ -69,15 +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) 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/BmpEncoder.cs b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs index 0d95591435..08f73b181b 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpEncoder.cs @@ -44,8 +44,8 @@ namespace ImageProcessorCore.Formats /// public void Encode(ImageBase image, Stream stream) - where T : IPackedVector - where TP : struct + where T : IPackedVector + where TP : struct { BmpEncoderCore encoder = new BmpEncoderCore(); encoder.Encode(image, stream, this.BitsPerPixel); diff --git a/src/ImageProcessorCore/Formats/IImageDecoder.cs b/src/ImageProcessorCore/Formats/IImageDecoder.cs index 5b270cb6d2..8f9050905b 100644 --- a/src/ImageProcessorCore/Formats/IImageDecoder.cs +++ b/src/ImageProcessorCore/Formats/IImageDecoder.cs @@ -39,10 +39,11 @@ namespace ImageProcessorCore.Formats bool IsSupportedFileFormat(byte[] header); /// - /// Decodes the image from the specified stream to the . + /// Decodes the image from the specified stream to the . /// - /// The type of pixels contained within the image. - /// 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) where T : IPackedVector diff --git a/src/ImageProcessorCore/Formats/Jpg/Block.cs b/src/ImageProcessorCore/Formats/Jpg/Block.cs new file mode 100644 index 0000000000..35aa10f181 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Jpg/Block.cs @@ -0,0 +1,44 @@ +// +// 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 + { + /// + /// 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() + { + this.data = new int[BlockSize]; + } + + /// + /// Gets the pixel data at the given block index. + /// + /// The index of the data to return. + /// + /// The . + /// + public int this[int index] + { + 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 new file mode 100644 index 0000000000..e51ea64151 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Jpg/FDCT.cs @@ -0,0 +1,161 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +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; + private const int fix_0_765366865 = 6270; + private const int fix_0_899976223 = 7373; + private const int fix_1_175875602 = 9633; + private const int fix_1_501321110 = 12299; + private const int fix_1_847759065 = 15137; + private const int fix_1_961570560 = 16069; + private const int fix_2_053119869 = 16819; + private const int fix_2_562915447 = 20995; + private const int fix_3_072711026 = 25172; + + /// + /// 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 = 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; + int tmp2 = x2 + x5; + int tmp3 = x3 + x4; + + int tmp10 = tmp0 + tmp3; + int tmp12 = tmp0 - tmp3; + int tmp11 = tmp1 + tmp2; + int tmp13 = tmp1 - tmp2; + + tmp0 = x0 - x7; + tmp1 = x1 - x6; + tmp2 = x2 - x5; + tmp3 = x3 - x4; + + block[y8] = (tmp10 + tmp11 - (8 * CenterJSample)) << Pass1Bits; + block[y8 + 4] = (tmp10 - tmp11) << Pass1Bits; + int z1 = (tmp12 + tmp13) * fix_0_541196100; + 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 << (Bits - Pass1Bits - 1); + tmp0 = tmp0 * fix_1_501321110; + tmp1 = tmp1 * fix_3_072711026; + tmp2 = tmp2 * fix_2_053119869; + tmp3 = tmp3 * fix_0_298631336; + tmp10 = tmp10 * -fix_0_899976223; + tmp11 = tmp11 * -fix_2_562915447; + tmp12 = tmp12 * -fix_0_390180644; + tmp13 = tmp13 * -fix_1_961570560; + + tmp12 += z1; + tmp13 += z1; + 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 = 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 tmp12 = tmp0 - tmp3; + int tmp11 = tmp1 + tmp2; + int tmp13 = tmp1 - tmp2; + + 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]; + + block[x] = (tmp10 + tmp11) >> Pass1Bits; + block[32 + x] = (tmp10 - tmp11) >> Pass1Bits; + + int z1 = (tmp12 + tmp13) * fix_0_541196100; + 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 << (Bits + Pass1Bits - 1); + tmp0 = tmp0 * fix_1_501321110; + tmp1 = tmp1 * fix_3_072711026; + tmp2 = tmp2 * fix_2_053119869; + tmp3 = tmp3 * fix_0_298631336; + tmp10 = tmp10 * -fix_0_899976223; + tmp11 = tmp11 * -fix_2_562915447; + tmp12 = tmp12 * -fix_0_390180644; + tmp13 = tmp13 * -fix_1_961570560; + + tmp12 += z1; + tmp13 += z1; + 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/IDCT.cs b/src/ImageProcessorCore/Formats/Jpg/IDCT.cs new file mode 100644 index 0000000000..7542f4d383 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Jpg/IDCT.cs @@ -0,0 +1,163 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + internal class IDCT + { + private const int w1 = 2841; // 2048*sqrt(2)*cos(1*pi/16) + private const int w2 = 2676; // 2048*sqrt(2)*cos(2*pi/16) + private const int w3 = 2408; // 2048*sqrt(2)*cos(3*pi/16) + private const int w5 = 1609; // 2048*sqrt(2)*cos(5*pi/16) + private const int w6 = 1108; // 2048*sqrt(2)*cos(6*pi/16) + private const int w7 = 565; // 2048*sqrt(2)*cos(7*pi/16) + + private const int w1pw7 = w1 + w7; + private const int w1mw7 = w1 - w7; + private const int w2pw6 = w2 + w6; + private const int w2mw6 = w2 - w6; + private const int w3pw5 = w3 + w5; + private const int w3mw5 = w3 - w5; + + private const int r2 = 181; // 256/sqrt(2) + + // idct performs a 2-D Inverse Discrete Cosine Transformation. + // + // The input coefficients should already have been multiplied by the + // appropriate quantization table. We use fixed-point computation, with the + // number of bits for the fractional component varying over the intermediate + // stages. + // + // For more on the actual algorithm, see Z. Wang, "Fast algorithms for the + // discrete W transform and for the discrete Fourier transform", IEEE Trans. on + // ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. + public static void Transform(Block src) + { + // Horizontal 1-D IDCT. + for (int y = 0; y < 8; y++) + { + int y8 = y * 8; + + // If all the AC components are zero, then the IDCT is trivial. + if (src[y8 + 1] == 0 && src[y8 + 2] == 0 && src[y8 + 3] == 0 && + src[y8 + 4] == 0 && src[y8 + 5] == 0 && src[y8 + 6] == 0 && src[y8 + 7] == 0) + { + int dc = src[y8 + 0] << 3; + src[y8 + 0] = dc; + src[y8 + 1] = dc; + src[y8 + 2] = dc; + src[y8 + 3] = dc; + src[y8 + 4] = dc; + src[y8 + 5] = dc; + src[y8 + 6] = dc; + src[y8 + 7] = dc; + continue; + } + + // Prescale. + int x0 = (src[y8 + 0] << 11) + 128; + int x1 = src[y8 + 4] << 11; + int x2 = src[y8 + 6]; + int x3 = src[y8 + 2]; + int x4 = src[y8 + 1]; + int x5 = src[y8 + 7]; + int x6 = src[y8 + 5]; + int x7 = src[y8 + 3]; + + // Stage 1. + int x8 = w7 * (x4 + x5); + x4 = x8 + w1mw7 * x4; + x5 = x8 - w1pw7 * x5; + x8 = w3 * (x6 + x7); + x6 = x8 - w3mw5 * x6; + x7 = x8 - w3pw5 * x7; + + // Stage 2. + x8 = x0 + x1; + x0 -= x1; + x1 = w6 * (x3 + x2); + x2 = x1 - w2pw6 * x2; + x3 = x1 + w2mw6 * x3; + x1 = x4 + x6; + x4 -= x6; + x6 = x5 + x7; + x5 -= x7; + + // Stage 3. + x7 = x8 + x3; + x8 -= x3; + x3 = x0 + x2; + x0 -= x2; + x2 = (r2 * (x4 + x5) + 128) >> 8; + x4 = (r2 * (x4 - x5) + 128) >> 8; + + // Stage 4. + src[y8 + 0] = (x7 + x1) >> 8; + src[y8 + 1] = (x3 + x2) >> 8; + src[y8 + 2] = (x0 + x4) >> 8; + src[y8 + 3] = (x8 + x6) >> 8; + src[y8 + 4] = (x8 - x6) >> 8; + src[y8 + 5] = (x0 - x4) >> 8; + src[y8 + 6] = (x3 - x2) >> 8; + src[y8 + 7] = (x7 - x1) >> 8; + } + + // Vertical 1-D IDCT. + for (int x = 0; x < 8; x++) + { + // Similar to the horizontal 1-D IDCT case, if all the AC components are zero, then the IDCT is trivial. + // However, after performing the horizontal 1-D IDCT, there are typically non-zero AC components, so + // we do not bother to check for the all-zero case. + + // Prescale. + int y0 = (src[x] << 8) + 8192; + int y1 = src[32 + x] << 8; + int y2 = src[48 + x]; + int y3 = src[16 + x]; + int y4 = src[8 + x]; + int y5 = src[56 + x]; + int y6 = src[40 + x]; + int y7 = src[24 + x]; + + // Stage 1. + int y8 = w7 * (y4 + y5) + 4; + y4 = (y8 + w1mw7 * y4) >> 3; + y5 = (y8 - w1pw7 * y5) >> 3; + y8 = w3 * (y6 + y7) + 4; + y6 = (y8 - w3mw5 * y6) >> 3; + y7 = (y8 - w3pw5 * y7) >> 3; + + // Stage 2. + y8 = y0 + y1; + y0 -= y1; + y1 = w6 * (y3 + y2) + 4; + y2 = (y1 - w2pw6 * y2) >> 3; + y3 = (y1 + w2mw6 * y3) >> 3; + y1 = y4 + y6; + y4 -= y6; + y6 = y5 + y7; + y5 -= y7; + + // Stage 3. + y7 = y8 + y3; + y8 -= y3; + y3 = y0 + y2; + y0 -= y2; + y2 = (r2 * (y4 + y5) + 128) >> 8; + y4 = (r2 * (y4 - y5) + 128) >> 8; + + // Stage 4. + src[x] = (y7 + y1) >> 14; + src[8 + x] = (y3 + y2) >> 14; + src[16 + x] = (y0 + y4) >> 14; + src[24 + x] = (y8 + y6) >> 14; + src[32 + x] = (y8 - y6) >> 14; + src[40 + x] = (y0 - y4) >> 14; + src[48 + x] = (y3 - y2) >> 14; + src[56 + x] = (y7 - y1) >> 14; + } + } + } +} diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegDecoder.cs b/src/ImageProcessorCore/Formats/Jpg/JpegDecoder.cs new file mode 100644 index 0000000000..84e46d5a4c --- /dev/null +++ b/src/ImageProcessorCore/Formats/Jpg/JpegDecoder.cs @@ -0,0 +1,139 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + + /// + /// Image decoder for generating an image out of a jpg stream. + /// + public class JpegDecoder : IImageDecoder + { + /// + /// Gets the size of the header for this image type. + /// + /// The size of the header. + public int HeaderSize => 11; + + /// + /// Indicates if the image decoder supports the specified + /// file extension. + /// + /// The file extension. + /// + /// true, if the decoder supports the specified + /// extensions; otherwise false. + /// + /// + /// is null (Nothing in Visual Basic). + /// is a string + /// of length zero or contains only blanks. + public bool IsSupportedFileExtension(string extension) + { + Guard.NotNullOrEmpty(extension, "extension"); + + if (extension.StartsWith(".")) + { + extension = extension.Substring(1); + } + + return extension.Equals("JPG", StringComparison.OrdinalIgnoreCase) || + extension.Equals("JPEG", StringComparison.OrdinalIgnoreCase) || + extension.Equals("JFIF", StringComparison.OrdinalIgnoreCase); + } + + /// + /// Indicates if the image decoder supports the specified + /// file header. + /// + /// The file header. + /// + /// true, if the decoder supports the specified + /// file header; otherwise false. + /// + /// + /// is null (Nothing in Visual Basic). + public bool IsSupportedFileFormat(byte[] header) + { + Guard.NotNull(header, "header"); + + bool isSupported = false; + + if (header.Length >= 11) + { + bool isJfif = IsJfif(header); + bool isExif = IsExif(header); + bool isJpeg = IsJpeg(header); + + isSupported = isJfif || isExif || isJpeg; + } + + return isSupported; + } + + /// + 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(image, stream, false); + } + + /// + /// Returns a value indicating whether the given bytes identify Jfif data. + /// + /// The bytes representing the file header. + /// The + private static bool IsJfif(byte[] header) + { + bool isJfif = + header[6] == 0x4A && // J + header[7] == 0x46 && // F + header[8] == 0x49 && // I + header[9] == 0x46 && // F + header[10] == 0x00; + + return isJfif; + } + + /// + /// Returns a value indicating whether the given bytes identify EXIF data. + /// + /// The bytes representing the file header. + /// The + private static bool IsExif(byte[] header) + { + bool isExif = + header[6] == 0x45 && // E + header[7] == 0x78 && // X + header[8] == 0x69 && // I + header[9] == 0x66 && // F + header[10] == 0x00; + + return isExif; + } + + /// + /// Returns a value indicating whether the given bytes identify Jpeg data. + /// This is a last chance resort for jpegs that contain ICC information. + /// + /// The bytes representing the file header. + /// The + private static bool IsJpeg(byte[] header) + { + bool isJpg = + header[0] == 0xFF && // 255 + header[1] == 0xD8; // 216 + + return isJpg; + } + } +} diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id new file mode 100644 index 0000000000..fce8df5dc4 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id @@ -0,0 +1 @@ +3ef7ce74c01efdb8145d6b3d03c937c862025a00 \ No newline at end of file diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoder.cs b/src/ImageProcessorCore/Formats/Jpg/JpegEncoder.cs new file mode 100644 index 0000000000..9a267f79ba --- /dev/null +++ b/src/ImageProcessorCore/Formats/Jpg/JpegEncoder.cs @@ -0,0 +1,96 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + + /// + /// Encoder for writing the data image to a stream in jpeg format. + /// + public class JpegEncoder : IImageEncoder + { + /// + /// The quality used to encode the image. + /// + private int quality = 75; + + /// + /// The subsamples scheme used to encode the image. + /// + private JpegSubsample subsample = JpegSubsample.Ratio420; + + /// + /// Whether subsampling has been specifically set. + /// + private bool subsampleSet; + + /// + /// Gets or sets the quality, that will be used to encode the image. Quality + /// index must be between 0 and 100 (compression from max to min). + /// + /// + /// If the quality is less than or equal to 80, the subsampling ratio will switch to + /// + /// The quality of the jpg image from 0 to 100. + public int Quality + { + get { return this.quality; } + set { this.quality = value.Clamp(1, 100); } + } + + /// + /// Gets or sets the subsample ration, that will be used to encode the image. + /// + /// The subsample ratio of the jpg image. + public JpegSubsample Subsample + { + get { return this.subsample; } + set + { + this.subsample = value; + this.subsampleSet = true; + } + } + + /// + public string MimeType => "image/jpeg"; + + /// + public string Extension => "jpg"; + + /// + public bool IsSupportedFileExtension(string extension) + { + Guard.NotNullOrEmpty(extension, "extension"); + + if (extension.StartsWith(".")) + { + extension = extension.Substring(1); + } + + return extension.Equals(this.Extension, StringComparison.OrdinalIgnoreCase) || + extension.Equals("jpeg", StringComparison.OrdinalIgnoreCase) || + extension.Equals("jfif", StringComparison.OrdinalIgnoreCase); + } + + /// + public void Encode(ImageBase image, Stream stream) + where T : IPackedVector + where TP : struct + { + JpegEncoderCore encode = new JpegEncoderCore(); + if (this.subsampleSet) + { + encode.Encode(image, stream, this.Quality, this.Subsample); + } + else + { + 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 new file mode 100644 index 0000000000..c9a8427d59 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs @@ -0,0 +1,796 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// +namespace ImageProcessorCore.Formats +{ + using System; + using System.IO; + + internal class JpegEncoderCore + { + /// + /// 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; + + /// + /// 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, + }; + + /// + /// 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 = { + { + // 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, + } + }; + + /// + /// 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 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 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, + }) + }; + + /// + /// 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 readonly uint[] Values; + + public HuffmanLut(HuffmanSpec s) + { + int maxValue = 0; + + foreach (var v in s.Values) + { + if (v > maxValue) maxValue = v; + } + + this.Values = new uint[maxValue + 1]; + + int code = 0; + int k = 0; + + for (int i = 0; i < s.Count.Length; i++) + { + int nBits = (i + 1) << 24; + for (int j = 0; j < s.Count[i]; j++) + { + this.Values[s.Values[k]] = (uint)(nBits | code); + code++; + k++; + } + + code <<= 1; + } + } + } + + // 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; + + /// + /// 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; + + /// + /// Writes the given byte to the stream. + /// + /// + private void WriteByte(byte b) + { + var data = new byte[1]; + data[0] = b; + this.outputStream.Write(data, 0, 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); + bits |= this.bits; + while (nBits >= 8) + { + byte b = (byte)(bits >> 24); + this.WriteByte(b); + if (b == 0xff) this.WriteByte(0x00); + bits <<= 8; + nBits -= 8; + } + + this.bits = bits; + this.nBits = nBits; + } + + /// + /// 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 = this.theHuffmanLUT[(int)index].Values[value]; + this.Emit(x & ((1 << 24) - 1), x >> 24); + } + + /// + /// 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 = value; + int b = value; + if (a < 0) + { + a = -value; + b = value - 1; + } + + uint bt; + if (a < 0x100) + { + bt = this.bitCount[a]; + } + else + { + bt = 8 + (uint)this.bitCount[a >> 8]; + } + + this.EmitHuff(index, (int)((uint)(runLength << 4) | bt)); + if (bt > 0) + { + this.Emit((uint)b & (uint)((1 << ((int)bt)) - 1), bt); + } + } + + + /// + /// 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(block); + + // Emit the DC delta. + 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)index + 1); + int runLength = 0; + + for (int zig = 1; zig < Block.BlockSize; zig++) + { + int ac = Round(block[Unzig[zig]], 8 * this.quant[(int)index][zig]); + + if (ac == 0) + { + runLength++; + } + else + { + while (runLength > 15) + { + this.EmitHuff(h, 0xf0); + runLength -= 16; + } + + this.EmitHuffRLE(h, runLength, ac); + runLength = 0; + } + } + + 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(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; + for (int j = 0; j < 8; j++) + { + for (int i = 0; i < 8; i++) + { + // 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; + crBlock[index] = (int)color.Cr; + } + } + } + + /// + /// 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++) + { + int dstOff = ((i & 2) << 4) | ((i & 1) << 2); + for (int y = 0; y < 4; y++) + { + for (int x = 0; x < 4; x++) + { + int j = 16 * y + 2 * x; + 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; + } + } + } + } + + // 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) + }; + + // 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. + // TODO: JFIF header etc. + this.buffer[0] = 0xff; + this.buffer[1] = 0xd8; + stream.Write(this.buffer, 0, 2); + + // 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 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 = grey 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; + } + + 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 + { + // TODO: We should allow grayscale writing. + this.outputStream.Write(this.SOSHeaderYCbCr, 0, this.SOSHeaderYCbCr.Length); + + switch (this.subsample) + { + case JpegSubsample.Ratio444: + this.Encode444(pixels); + break; + case JpegSubsample.Ratio420: + this.Encode420(pixels); + break; + } + + // Pad the last byte with 1's. + this.Emit(0x7f, 7); + } + + + + /// + /// 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(); + Block cr = new Block(); + int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; + + for (int y = 0; y < pixels.Height; y += 8) + { + for (int x = 0; x < pixels.Width; x += 8) + { + 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); + } + } + } + + /// + /// 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]; + Block[] cr = new Block[4]; + int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; + + for (int i = 0; i < 4; i++) cb[i] = new Block(); + for (int i = 0; i < 4; i++) cr[i] = new Block(); + + for (int y = 0; y < pixels.Height; y += 16) + { + for (int x = 0; x < pixels.Width; x += 16) + { + for (int i = 0; i < 4; i++) + { + int xOff = (i & 1) * 8; + int yOff = (i & 2) * 4; + + this.ToYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]); + prevDCY = this.WriteBlock(b, QuantIndex.Luminance, prevDCY); + } + + this.Scale16X16_8X8(b, cb); + prevDCCb = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCb); + this.Scale16X16_8X8(b, cr); + prevDCCr = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCr); + } + } + } + + /// + /// Writes the header for a marker with the given length. + /// + /// The marker to write. + /// The marker length. + private void WriteMarkerHeader(byte marker, int length) + { + // 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); + } + + /// + /// Enumerates the Huffman tables + /// + private enum HuffIndex + { + LuminanceDC = 0, + + LuminanceAC = 1, + + ChrominanceDC = 2, + + ChrominanceAC = 3, + } + + /// + /// Enumerates the quantization tables + /// + private enum QuantIndex + { + /// + /// Luminance + /// + Luminance = 0, + + /// + /// Chrominance + /// + Chrominance = 1, + } + + /// + /// 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) + { + this.Count = count; + this.Values = values; + } + + /// + /// Gets count[i] - The number of codes of length i bits. + /// + public readonly byte[] Count; + + /// + /// Gets value[i] - The decoded value of the i'th codeword. + /// + public readonly byte[] Values; + } + } +} diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegFormat.cs b/src/ImageProcessorCore/Formats/Jpg/JpegFormat.cs new file mode 100644 index 0000000000..ec9ceb6dc0 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Jpg/JpegFormat.cs @@ -0,0 +1,19 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Encapsulates the means to encode and decode jpeg images. + /// + public class JpegFormat : IImageFormat + { + /// + public IImageDecoder Decoder => new JpegDecoder(); + + /// + public IImageEncoder Encoder => new JpegEncoder(); + } +} diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegSubsample.cs b/src/ImageProcessorCore/Formats/Jpg/JpegSubsample.cs new file mode 100644 index 0000000000..6098f6377b --- /dev/null +++ b/src/ImageProcessorCore/Formats/Jpg/JpegSubsample.cs @@ -0,0 +1,25 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Enumerates the chroma subsampling method applied to the image. + /// + public enum JpegSubsample + { + /// + /// High Quality - Each of the three Y'CbCr components have the same sample rate, + /// thus there is no chroma subsampling. + /// + Ratio444, + + /// + /// Medium Quality - The horizontal sampling is halved and the Cb and Cr channels are only + /// sampled on each alternate line. + /// + Ratio420 + } +} diff --git a/src/ImageProcessorCore/Formats/Jpg/README.md b/src/ImageProcessorCore/Formats/Jpg/README.md new file mode 100644 index 0000000000..54bc14847c --- /dev/null +++ b/src/ImageProcessorCore/Formats/Jpg/README.md @@ -0,0 +1,3 @@ +Encoder/Decoder adapted and extended from: + +https://golang.org/src/image/jpeg/ \ No newline at end of file diff --git a/src/ImageProcessorCore/Image/ImageExtensions.cs b/src/ImageProcessorCore/Image/ImageExtensions.cs index 53133207fc..28ba60f08a 100644 --- a/src/ImageProcessorCore/Image/ImageExtensions.cs +++ b/src/ImageProcessorCore/Image/ImageExtensions.cs @@ -46,14 +46,19 @@ namespace ImageProcessorCore where TP : struct => new PngEncoder { Quality = quality }.Encode(source, stream); - ///// - ///// Saves the image to the given stream with the jpeg format. - ///// - ///// The image this method extends. - ///// The stream to save the image to. - ///// The quality to save the image to. Between 1 and 100. - ///// Thrown if the stream is null. - //public static void SaveAsJpeg(this ImageBase source, Stream stream, int quality = 75) => new JpegEncoder { Quality = quality }.Encode(source, stream); + /// + /// Saves the image to the given stream with the jpeg format. + /// + /// The 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) + where T : IPackedVector + where TP : struct + => new JpegEncoder { Quality = quality }.Encode(source, stream); /// /// Saves the image to the given stream with the gif format. From 78b871ff2adde822e44780dbbdf387bd852ad0ec Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 26 Jul 2016 18:14:38 +1000 Subject: [PATCH 54/91] Add Crop Former-commit-id: 63386b63ab93e14d3c50383739c042a656efab9f Former-commit-id: 0a1b7a7367d9ea360cc4cf0a821c788bd468b607 Former-commit-id: 8430af726048214b3820969d1dfe31b9df72a695 --- src/ImageProcessorCore/Samplers/Crop.cs | 76 +++++++++++++++++++ .../Samplers/Processors/CropProcessor.cs | 41 ++++++++++ src/ImageProcessorCore/Samplers/Resize.cs | 37 +++++---- .../Samplers/Crop.cs | 72 +++++++++--------- 4 files changed, 174 insertions(+), 52 deletions(-) create mode 100644 src/ImageProcessorCore/Samplers/Crop.cs create mode 100644 src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs diff --git a/src/ImageProcessorCore/Samplers/Crop.cs b/src/ImageProcessorCore/Samplers/Crop.cs new file mode 100644 index 0000000000..6ff3f49c02 --- /dev/null +++ b/src/ImageProcessorCore/Samplers/Crop.cs @@ -0,0 +1,76 @@ +// +// 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 + { + /// + /// 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) + where T : IPackedVector + where TP : struct + { + return Crop(source, width, height, source.Bounds, progressHandler); + } + + /// + /// Crops an image to the given width and height with the given source rectangle. + /// + /// If the source rectangle is smaller than the target dimensions then the + /// area within the source is resized performing a zoomed crop. + /// + /// + /// The pixel format. + /// The packed format. long, float. + /// The image to crop. + /// The target image width. + /// The target image height. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// 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) + where T : IPackedVector + where TP : struct + { + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + if (sourceRectangle.Width < width || sourceRectangle.Height < height) + { + // If the source rectangle is smaller than the target perform a + // cropped zoom. + source = source.Resize(sourceRectangle.Width, sourceRectangle.Height); + } + + CropProcessor processor = new CropProcessor(); + processor.OnProgress += progressHandler; + + try + { + return source.Process(width, height, sourceRectangle, new Rectangle(0, 0, width, height), processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs new file mode 100644 index 0000000000..2029c33bd2 --- /dev/null +++ b/src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs @@ -0,0 +1,41 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Threading.Tasks; + + /// + /// Provides methods to allow the cropping of an image. + /// + public class CropProcessor : ImageSampler + { + /// + 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 (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => + { + for (int x = startX; x < endX; x++) + { + targetPixels[x, y] = sourcePixels[x + sourceX, y + sourceY]; + } + + this.OnRowProcessed(); + }); + } + } + } +} diff --git a/src/ImageProcessorCore/Samplers/Resize.cs b/src/ImageProcessorCore/Samplers/Resize.cs index 8e5405870d..aa2089d076 100644 --- a/src/ImageProcessorCore/Samplers/Resize.cs +++ b/src/ImageProcessorCore/Samplers/Resize.cs @@ -8,20 +8,21 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { /// /// Resizes an image in accordance with the given . /// - /// The type of pixels contained within the image. + /// The 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 { @@ -44,14 +45,15 @@ namespace ImageProcessorCore /// /// Resizes an image to the given width and height. /// - /// The type of pixels contained within the image. + /// 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 { @@ -61,15 +63,16 @@ namespace ImageProcessorCore /// /// Resizes an image to the given width and height. /// - /// The type of pixels contained within the image. + /// 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 { @@ -79,16 +82,17 @@ namespace ImageProcessorCore /// /// Resizes an image to the given width and height with the given sampler. /// - /// The type of pixels contained within the image. + /// The 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 { @@ -99,7 +103,8 @@ namespace ImageProcessorCore /// Resizes an image to the given width and height with the given sampler and /// source rectangle. /// - /// The type of pixels contained within the image. + /// The pixel format. + /// The packed format. long, float. /// The image to resize. /// The target image width. /// The target image height. @@ -112,9 +117,9 @@ 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 { diff --git a/tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs b/tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs index 5eae5cdefb..4eaeea6ecc 100644 --- a/tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs +++ b/tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs @@ -1,40 +1,40 @@ -//namespace ImageProcessorCore.Benchmarks -//{ -// using System.Drawing; -// using System.Drawing.Drawing2D; +namespace ImageProcessorCore.Benchmarks +{ + using System.Drawing; + using System.Drawing.Drawing2D; -// using BenchmarkDotNet.Attributes; -// using CoreImage = ImageProcessorCore.Image; -// using CoreSize = ImageProcessorCore.Size; + using BenchmarkDotNet.Attributes; + using CoreSize = ImageProcessorCore.Size; + using CoreImage = ImageProcessorCore.Image; -// public class Crop -// { -// [Benchmark(Baseline = true, Description = "System.Drawing Crop")] -// public Size CropSystemDrawing() -// { -// using (Bitmap source = new Bitmap(400, 400)) -// { -// using (Bitmap destination = new Bitmap(100, 100)) -// { -// using (Graphics graphics = Graphics.FromImage(destination)) -// { -// graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; -// graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; -// graphics.CompositingQuality = CompositingQuality.HighQuality; -// graphics.DrawImage(source, new Rectangle(0, 0, 100, 100), 0, 0, 100, 100, GraphicsUnit.Pixel); -// } + public class Crop + { + [Benchmark(Baseline = true, Description = "System.Drawing Crop")] + public Size CropSystemDrawing() + { + using (Bitmap source = new Bitmap(400, 400)) + { + using (Bitmap destination = new Bitmap(100, 100)) + { + using (Graphics graphics = Graphics.FromImage(destination)) + { + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.DrawImage(source, new Rectangle(0, 0, 100, 100), 0, 0, 100, 100, GraphicsUnit.Pixel); + } -// return destination.Size; -// } -// } -// } + return destination.Size; + } + } + } -// [Benchmark(Description = "ImageProcessorCore Crop")] -// public CoreSize CropResizeCore() -// { -// CoreImage image = new CoreImage(400, 400); -// image.Crop(100, 100); -// return new CoreSize(image.Width, image.Height); -// } -// } -//} + [Benchmark(Description = "ImageProcessorCore Crop")] + public CoreSize CropResizeCore() + { + CoreImage image = new CoreImage(400, 400); + image.Crop(100, 100); + return new CoreSize(image.Width, image.Height); + } + } +} From 493aa664cdcf7eafabb8a658e935f37748f68b25 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 26 Jul 2016 23:42:59 +1000 Subject: [PATCH 55/91] Add Pad Former-commit-id: a998b9d94d6da006ff1381e93e2acb27a14546a8 Former-commit-id: e5c6fd1bbbf94f02f2702a09dfd16fba18662352 Former-commit-id: bf339f877e151c783b9d462b133429e9dc28aeb3 --- src/ImageProcessorCore/Samplers/Pad.cs | 39 ++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/ImageProcessorCore/Samplers/Pad.cs diff --git a/src/ImageProcessorCore/Samplers/Pad.cs b/src/ImageProcessorCore/Samplers/Pad.cs new file mode 100644 index 0000000000..2bb6acc33b --- /dev/null +++ b/src/ImageProcessorCore/Samplers/Pad.cs @@ -0,0 +1,39 @@ +// +// 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 + { + /// + /// 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) + where T : IPackedVector + where TP : struct + { + ResizeOptions options = new ResizeOptions + { + Size = new Size(width, height), + Mode = ResizeMode.BoxPad, + Sampler = new NearestNeighborResampler() + }; + + return Resize(source, options, progressHandler); + } + } +} From 916e172b0a340273dfd79309b73fffe1c2457485 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 27 Jul 2016 16:29:06 +1000 Subject: [PATCH 56/91] Add all samplers. Former-commit-id: c60883d765b1372be2a9ab88f8494dfa3283d8a6 Former-commit-id: 9fbcdac0c826d57eaa2bafdc5c72ff90079e5f51 Former-commit-id: 2e8a4fba64de7233e5ea741122188eb86612a060 --- .../Colors/RgbaComponent.cs | 33 + .../Common/Helpers/ImageMaths.cs | 212 ++--- .../Binarization/ThresholdProcessor.cs | 90 +++ .../ColorMatrix/ColorMatrixFilter.cs | 74 ++ .../ColorMatrix/GreyscaleBt709Processor.cs | 32 + .../ColorMatrix/IColorMatrixFilter.cs | 29 + .../Convolution/Convolution2DFilter.cs | 118 +++ .../Convolution/ConvolutionFilter.cs | 95 +++ .../EdgeDetection/EdgeDetector2DFilter.cs | 28 + .../EdgeDetection/EdgeDetectorFilter.cs | 28 + .../EdgeDetection/IEdgeDetectorFilter.cs | 21 + .../EdgeDetection/SobelProcessor.cs | 32 + .../Image/IImageProcessor.cs | 18 +- src/ImageProcessorCore/Image/Image.cs | 5 +- .../Image/ImageExtensions.cs | 10 +- src/ImageProcessorCore/ImageProcessor.cs | 28 +- src/ImageProcessorCore/Samplers/Crop.cs | 2 +- .../Samplers/EntropyCrop.cs | 41 + .../Samplers/Processors/CropProcessor.cs | 6 +- .../Processors/EntropyCropProcessor.cs | 106 +++ .../Samplers/Processors/IImageSampler.cs | 4 +- .../Samplers/Processors/ImageSampler.cs | 4 +- .../Samplers/Processors/Matrix3x2Processor.cs | 53 ++ .../Samplers/Processors/ResizeProcessor.cs | 10 +- .../Processors/RotateFlipProcessor.cs | 243 ++++++ .../Samplers/Processors/RotateProcessor.cs | 70 ++ .../Samplers/Processors/SkewProcessor.cs | 75 ++ src/ImageProcessorCore/Samplers/Resize.cs | 2 +- src/ImageProcessorCore/Samplers/Rotate.cs | 58 ++ src/ImageProcessorCore/Samplers/RotateFlip.cs | 42 + src/ImageProcessorCore/Samplers/Skew.cs | 60 ++ tests/ImageProcessorCore.Benchmarks/Config.cs | 3 +- .../ImageProcessorCore.Tests/FileTestBase.cs | 12 +- .../Processors/Samplers/SamplerTests.cs | 721 +++++++++--------- 34 files changed, 1827 insertions(+), 538 deletions(-) create mode 100644 src/ImageProcessorCore/Colors/RgbaComponent.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/Convolution/ConvolutionFilter.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs create mode 100644 src/ImageProcessorCore/Samplers/EntropyCrop.cs create mode 100644 src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs create mode 100644 src/ImageProcessorCore/Samplers/Processors/Matrix3x2Processor.cs create mode 100644 src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs create mode 100644 src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs create mode 100644 src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs create mode 100644 src/ImageProcessorCore/Samplers/Rotate.cs create mode 100644 src/ImageProcessorCore/Samplers/RotateFlip.cs create mode 100644 src/ImageProcessorCore/Samplers/Skew.cs diff --git a/src/ImageProcessorCore/Colors/RgbaComponent.cs b/src/ImageProcessorCore/Colors/RgbaComponent.cs new file mode 100644 index 0000000000..946c47a377 --- /dev/null +++ b/src/ImageProcessorCore/Colors/RgbaComponent.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// Enumerates the RGBA (red, green, blue, alpha) color components. + /// + public enum RgbaComponent + { + /// + /// The red component. + /// + R = 0, + + /// + /// The green component. + /// + G = 1, + + /// + /// The blue component. + /// + B = 2, + + /// + /// The alpha component. + /// + A = 3 + } +} diff --git a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs index 6ec45549bc..9b890d1c0b 100644 --- a/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs +++ b/src/ImageProcessorCore/Common/Helpers/ImageMaths.cs @@ -156,116 +156,120 @@ 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) - //{ - // const float Epsilon = .00001f; - // int width = bitmap.Width; - // int height = bitmap.Height; - // Point topLeft = new Point(); - // Point bottomRight = new Point(); - - // Func delegateFunc; - - // // Determine which channel to check against - // switch (channel) - // { - // case RgbaComponent.R: - // delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].R - b) > Epsilon; - // break; - - // case RgbaComponent.G: - // delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].G - b) > Epsilon; - // break; - - // case RgbaComponent.A: - // delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].A - b) > Epsilon; - // break; - - // default: - // delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].B - b) > Epsilon; - // break; - // } - - // Func getMinY = pixels => - // { - // for (int y = 0; y < height; y++) - // { - // for (int x = 0; x < width; x++) - // { - // if (delegateFunc(pixels, x, y, componentValue)) - // { - // return y; - // } - // } - // } - - // return 0; - // }; - - // Func getMaxY = pixels => - // { - // for (int y = height - 1; y > -1; y--) - // { - // for (int x = 0; x < width; x++) - // { - // if (delegateFunc(pixels, x, y, componentValue)) - // { - // return y; - // } - // } - // } - - // return height; - // }; - - // Func getMinX = pixels => - // { - // for (int x = 0; x < width; x++) - // { - // for (int y = 0; y < height; y++) - // { - // if (delegateFunc(pixels, x, y, componentValue)) - // { - // return x; - // } - // } - // } - - // return 0; - // }; - - // Func getMaxX = pixels => - // { - // for (int x = width - 1; x > -1; x--) - // { - // for (int y = 0; y < height; y++) - // { - // if (delegateFunc(pixels, x, y, componentValue)) - // { - // return x; - // } - // } - // } - - // return height; - // }; - - // using (PixelAccessor bitmapPixels = bitmap.Lock()) - // { - // topLeft.Y = getMinY(bitmapPixels); - // topLeft.X = getMinX(bitmapPixels); - // bottomRight.Y = (getMaxY(bitmapPixels) + 1).Clamp(0, height); - // bottomRight.X = (getMaxX(bitmapPixels) + 1).Clamp(0, width); - // } - - // return GetBoundingRectangle(topLeft, bottomRight); - //} + public static Rectangle GetFilteredBoundingRectangle(ImageBase bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B) + where T : IPackedVector + where TP : struct + { + const float Epsilon = .00001f; + int width = bitmap.Width; + int height = bitmap.Height; + Point topLeft = new Point(); + Point bottomRight = new Point(); + + 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].ToBytes()[0] - b) > Epsilon; + break; + + case RgbaComponent.G: + delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].ToBytes()[1] - b) > Epsilon; + break; + + 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].ToBytes()[3] - b) > Epsilon; + break; + } + + Func, int> getMinY = pixels => + { + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + if (delegateFunc(pixels, x, y, componentValue)) + { + return y; + } + } + } + + return 0; + }; + + Func, int> getMaxY = pixels => + { + for (int y = height - 1; y > -1; y--) + { + for (int x = 0; x < width; x++) + { + if (delegateFunc(pixels, x, y, componentValue)) + { + return y; + } + } + } + + return height; + }; + + Func, int> getMinX = pixels => + { + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + if (delegateFunc(pixels, x, y, componentValue)) + { + return x; + } + } + } + + return 0; + }; + + Func, int> getMaxX = pixels => + { + for (int x = width - 1; x > -1; x--) + { + for (int y = 0; y < height; y++) + { + if (delegateFunc(pixels, x, y, componentValue)) + { + return x; + } + } + } + + return height; + }; + + using (IPixelAccessor bitmapPixels = bitmap.Lock()) + { + topLeft.Y = getMinY(bitmapPixels); + topLeft.X = getMinX(bitmapPixels); + bottomRight.Y = (getMaxY(bitmapPixels) + 1).Clamp(0, height); + bottomRight.X = (getMaxX(bitmapPixels) + 1).Clamp(0, width); + } + + return GetBoundingRectangle(topLeft, bottomRight); + } /// /// Ensures that any passed double is correctly rounded to zero diff --git a/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs new file mode 100644 index 0000000000..856ae6d615 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs @@ -0,0 +1,90 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Threading.Tasks; + + /// + /// An to perform binary threshold filtering against an + /// . The image will be converted to greyscale before thresholding + /// occurs. + /// + public class ThresholdProcessor : ImageProcessor + where T : IPackedVector + where TP : struct + { + /// + /// Initializes a new instance of the class. + /// + /// The threshold to split the image. Must be between 0 and 1. + /// + /// is less than 0 or is greater than 1. + /// + public ThresholdProcessor(float threshold) + { + // TODO: Check limit. + Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); + this.Value = threshold; + this.UpperColor.PackVector(Color.White.ToVector4()); + this.LowerColor.PackVector(Color.Black.ToVector4()); + } + + /// + /// Gets the threshold value. + /// + public float Value { get; } + + /// + /// The color to use for pixels that are above the threshold. + /// + public T UpperColor { get; set; } + + /// + /// The color to use for pixels that fall below the threshold. + /// + public T LowerColor { get; set; } + + /// + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + new GreyscaleBt709Processor().Apply(source, source, sourceRectangle); + } + + /// + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + float threshold = this.Value; + 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 (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => + { + if (y >= sourceY && y < sourceBottom) + { + for (int x = startX; x < endX; x++) + { + T color = sourcePixels[x, y]; + + // Any channel will do since it's greyscale. + targetPixels[x, y] = color.ToVector4().X >= threshold ? upper : lower; + } + this.OnRowProcessed(); + } + }); + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs new file mode 100644 index 0000000000..0b7600fbc6 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs @@ -0,0 +1,74 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + using System.Threading.Tasks; + + /// + /// The color matrix filter. + /// + public abstract class ColorMatrixFilter : ImageProcessor, IColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + public abstract Matrix4x4 Matrix { get; } + + /// + public virtual bool Compand => true; + + /// + 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; + + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => + { + for (int x = startX; x < endX; x++) + { + targetPixels[x, y] = this.ApplyMatrix(sourcePixels[x, y], matrix); + } + + this.OnRowProcessed(); + }); + } + } + + /// + /// Applies the color matrix against the given color. + /// + /// The source color. + /// The matrix. + /// + /// The . + /// + private T ApplyMatrix(T color, Matrix4x4 matrix) + { + bool compand = this.Compand; + + //if (compand) + //{ + // color = Color.Expand(color); + //} + + Vector4 transformed = Vector4.Transform(color.ToVector4(), matrix); + //Vector3 transformed = Vector3.Transform(color.ToVector3(), matrix); + //return compand ? Color.Compress(new Color(transformed, color.A)) : new Color(transformed, color.A); + T packed = default(T); + packed.PackVector(transformed); + return packed; + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs new file mode 100644 index 0000000000..f8741b00b7 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image to greyscale applying the formula as specified by + /// ITU-R Recommendation BT.709 . + /// + public class GreyscaleBt709Processor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = .2126f, + M12 = .2126f, + M13 = .2126f, + M21 = .7152f, + M22 = .7152f, + M23 = .7152f, + M31 = .0722f, + M32 = .0722f, + M33 = .0722f + }; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs new file mode 100644 index 0000000000..8e46f56b65 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs @@ -0,0 +1,29 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Encapsulates properties and methods for creating processors that utilize a matrix to + /// alter the image pixels. + /// + 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/Convolution/Convolution2DFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs new file mode 100644 index 0000000000..cc8ac82e35 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs @@ -0,0 +1,118 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +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 + where T : IPackedVector + where TP : struct + { + /// + /// Gets the horizontal gradient operator. + /// + public abstract float[,] KernelX { get; } + + /// + /// Gets the vertical gradient operator. + /// + public abstract float[,] KernelY { get; } + + /// + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + float[,] kernelX = this.KernelX; + float[,] kernelY = this.KernelY; + int kernelYHeight = kernelY.GetLength(0); + int kernelYWidth = kernelY.GetLength(1); + int kernelXHeight = kernelX.GetLength(0); + int kernelXWidth = kernelX.GetLength(1); + int radiusY = kernelYHeight >> 1; + int radiusX = kernelXWidth >> 1; + + int sourceY = sourceRectangle.Y; + int sourceBottom = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + int maxY = sourceBottom - 1; + int maxX = endX - 1; + + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => + { + if (y >= sourceY && y < sourceBottom) + { + for (int x = startX; x < endX; x++) + { + float rX = 0; + float gX = 0; + float bX = 0; + float rY = 0; + float gY = 0; + float bY = 0; + + // Apply each matrix multiplier to the color components for each pixel. + for (int fy = 0; fy < kernelYHeight; fy++) + { + int fyr = fy - radiusY; + int offsetY = y + fyr; + + offsetY = offsetY.Clamp(0, maxY); + + for (int fx = 0; fx < kernelXWidth; fx++) + { + int fxr = fx - radiusX; + int offsetX = x + fxr; + + offsetX = offsetX.Clamp(0, maxX); + + Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + float r = currentColor.X; + float g = currentColor.Y; + float b = currentColor.Z; + + if (fy < kernelXHeight) + { + rX += kernelX[fy, fx] * r; + gX += kernelX[fy, fx] * g; + bX += kernelX[fy, fx] * b; + } + + if (fx < kernelYWidth) + { + rY += kernelY[fy, fx] * r; + gY += kernelY[fy, fx] * g; + bY += kernelY[fy, fx] * b; + } + } + } + + float red = (float)Math.Sqrt((rX * rX) + (rY * rY)); + float green = (float)Math.Sqrt((gX * gX) + (gY * gY)); + float blue = (float)Math.Sqrt((bX * bX) + (bY * bY)); + + 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/ConvolutionFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/ConvolutionFilter.cs new file mode 100644 index 0000000000..ced19ababe --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/ConvolutionFilter.cs @@ -0,0 +1,95 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +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 + where T : IPackedVector + where TP : struct + { + /// + /// Gets the 2d gradient operator. + /// + public abstract float[,] KernelXY { get; } + + /// + 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); + int radius = kernelLength >> 1; + + int sourceY = sourceRectangle.Y; + int sourceBottom = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + int maxY = sourceBottom - 1; + int maxX = endX - 1; + + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => + { + if (y >= sourceY && y < sourceBottom) + { + for (int x = startX; x < endX; x++) + { + float rX = 0; + float gX = 0; + float bX = 0; + + // Apply each matrix multiplier to the color components for each pixel. + for (int fy = 0; fy < kernelLength; fy++) + { + int fyr = fy - radius; + int offsetY = y + fyr; + + offsetY = offsetY.Clamp(0, maxY); + + for (int fx = 0; fx < kernelLength; fx++) + { + int fxr = fx - radius; + int offsetX = x + fxr; + + offsetX = offsetX.Clamp(0, maxX); + + 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; + bX += kernelX[fy, fx] * b; + } + } + + float red = rX; + float green = gX; + float blue = bX; + + 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 new file mode 100644 index 0000000000..4340f4d60c --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// Defines a filter that detects edges within an image using two + /// one-dimensional matrices. + /// + public abstract class EdgeDetector2DFilter : Convolution2DFilter, IEdgeDetectorFilter + where T : IPackedVector + where TP : struct + { + /// + public bool Greyscale { get; set; } + + /// + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + if (this.Greyscale) + { + new GreyscaleBt709Processor().Apply(source, source, sourceRectangle); + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs new file mode 100644 index 0000000000..2762f664fc --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// Defines a filter that detects edges within an image using a single + /// two dimensional matrix. + /// + public abstract class EdgeDetectorFilter : ConvolutionFilter, IEdgeDetectorFilter + where T : IPackedVector + where TP : struct + { + /// + public bool Greyscale { get; set; } + + /// + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + if (this.Greyscale) + { + new GreyscaleBt709Processor().Apply(source, source, sourceRectangle); + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs new file mode 100644 index 0000000000..d2e2979f9c --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs @@ -0,0 +1,21 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// Provides properties and methods allowing the detection of edges within an image. + /// + public interface IEdgeDetectorFilter : IImageProcessor + where T : IPackedVector + where TP : struct + { + /// + /// Gets or sets a value indicating whether to convert the + /// image to greyscale before performing edge detection. + /// + bool Greyscale { get; set; } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs new file mode 100644 index 0000000000..a323b7cfe5 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// The Sobel operator filter. + /// + /// + public class SobelProcessor : EdgeDetector2DFilter + where T : IPackedVector + where TP : struct + { + /// + public override float[,] KernelX => new float[,] + { + { -1, 0, 1 }, + { -2, 0, 2 }, + { -1, 0, 1 } + }; + + /// + public override float[,] KernelY => new float[,] + { + { 1, 2, 1 }, + { 0, 0, 0 }, + { -1, -2, -1 } + }; + } +} diff --git a/src/ImageProcessorCore/Image/IImageProcessor.cs b/src/ImageProcessorCore/Image/IImageProcessor.cs index 0164077c5a..cba045963e 100644 --- a/src/ImageProcessorCore/Image/IImageProcessor.cs +++ b/src/ImageProcessorCore/Image/IImageProcessor.cs @@ -17,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. @@ -36,8 +40,6 @@ namespace ImageProcessorCore.Processors /// /// Applies the process to the specified portion of the specified . /// - /// The pixel format. - /// The packed format. long, float. /// Target image to apply the process to. /// The source image. Cannot be null. /// @@ -53,16 +55,12 @@ namespace ImageProcessorCore.Processors /// /// doesnt fit the dimension of the image. /// - void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) - where T : IPackedVector - where TP : struct; + void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle); /// /// Applies the process to the specified portion of the specified at the specified /// location and with the specified size. /// - /// The pixel format. - /// The packed format. long, float. /// Target image to apply the process to. /// The source image. Cannot be null. /// The target width. @@ -78,8 +76,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) - where T : IPackedVector - where TP : struct; + 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 index 748fada558..0b716f0dd1 100644 --- a/src/ImageProcessorCore/Image/Image.cs +++ b/src/ImageProcessorCore/Image/Image.cs @@ -40,7 +40,7 @@ namespace ImageProcessorCore /// public Image() { - this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(BmpFormat)); + this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat)); } /// @@ -52,8 +52,7 @@ namespace ImageProcessorCore public Image(int width, int height) : base(width, height) { - // TODO: Change to PNG - this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(BmpFormat)); + this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat)); } /// diff --git a/src/ImageProcessorCore/Image/ImageExtensions.cs b/src/ImageProcessorCore/Image/ImageExtensions.cs index 28ba60f08a..c97b6dfa65 100644 --- a/src/ImageProcessorCore/Image/ImageExtensions.cs +++ b/src/ImageProcessorCore/Image/ImageExtensions.cs @@ -69,7 +69,7 @@ namespace ImageProcessorCore /// 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) + 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); @@ -83,7 +83,7 @@ namespace ImageProcessorCore /// The image this method extends. /// The processor to apply to the image. /// The . - public static Image Process(this Image source, IImageProcessor processor) + internal static Image Process(this Image source, IImageProcessor processor) where T : IPackedVector where TP : struct { @@ -102,7 +102,7 @@ namespace ImageProcessorCore /// /// The processors to apply to the image. /// The . - public static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor) + internal static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor) where T : IPackedVector where TP : struct { @@ -122,7 +122,7 @@ namespace ImageProcessorCore /// 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) + internal static Image Process(this Image source, int width, int height, IImageSampler sampler) where T : IPackedVector where TP : struct { @@ -149,7 +149,7 @@ namespace ImageProcessorCore /// /// 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) + internal static Image Process(this Image source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler) where T : IPackedVector where TP : struct { diff --git a/src/ImageProcessorCore/ImageProcessor.cs b/src/ImageProcessorCore/ImageProcessor.cs index ae0b239873..09cc54ff57 100644 --- a/src/ImageProcessorCore/ImageProcessor.cs +++ b/src/ImageProcessorCore/ImageProcessor.cs @@ -12,7 +12,9 @@ namespace ImageProcessorCore.Processors /// /// 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; @@ -31,9 +33,7 @@ namespace ImageProcessorCore.Processors public virtual ParallelOptions ParallelOptions { get; set; } = Bootstrapper.Instance.ParallelOptions; /// - public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) - where T : IPackedVector - where TP : struct + public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) { try { @@ -54,9 +54,7 @@ namespace ImageProcessorCore.Processors } /// - public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) - where T : IPackedVector - where TP : struct + public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) { try { @@ -103,9 +101,7 @@ namespace ImageProcessorCore.Processors /// /// The structure that specifies the portion of the image object to draw. /// - protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - where T : IPackedVector - where TP : struct + protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { } @@ -113,8 +109,6 @@ namespace ImageProcessorCore.Processors /// Applies the process to the specified portion of the specified at the specified location /// and with the specified size. /// - /// The pixel format. - /// The packed format. long, float. /// Target image to apply the process to. /// The source image. Cannot be null. /// @@ -130,15 +124,11 @@ namespace ImageProcessorCore.Processors /// The method keeps the source image unchanged and returns the /// the result of image process as new image. /// - protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - where T : IPackedVector - where TP : struct; + 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. /// - /// The pixel format. - /// The packed format. long, float. /// Target image to apply the process to. /// The source image. Cannot be null. /// @@ -148,9 +138,7 @@ namespace ImageProcessorCore.Processors /// /// The structure that specifies the portion of the image object to draw. /// - protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - where T : IPackedVector - where TP : struct + protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { } diff --git a/src/ImageProcessorCore/Samplers/Crop.cs b/src/ImageProcessorCore/Samplers/Crop.cs index 6ff3f49c02..ff3196c430 100644 --- a/src/ImageProcessorCore/Samplers/Crop.cs +++ b/src/ImageProcessorCore/Samplers/Crop.cs @@ -60,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 new file mode 100644 index 0000000000..123378173c --- /dev/null +++ b/src/ImageProcessorCore/Samplers/EntropyCrop.cs @@ -0,0 +1,41 @@ +// +// 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 + { + /// + /// 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) + where T : IPackedVector + where TP : struct + { + EntropyCropProcessor processor = new EntropyCropProcessor(threshold); + processor.OnProgress += progressHandler; + + try + { + return source.Process(source.Width, source.Height, source.Bounds, Rectangle.Empty, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs index 2029c33bd2..0990711122 100644 --- a/src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs @@ -10,10 +10,12 @@ 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; diff --git a/src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs new file mode 100644 index 0000000000..29afff11a1 --- /dev/null +++ b/src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs @@ -0,0 +1,106 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System; + using System.Threading.Tasks; + + /// + /// Provides methods to allow the cropping of an image to preserve areas of highest + /// entropy. + /// + public class EntropyCropProcessor : ImageSampler + where T : IPackedVector + where TP : struct + { + /// + /// The rectangle for cropping + /// + private Rectangle cropRectangle; + + /// + /// Initializes a new instance of the class. + /// + /// The threshold to split the image. Must be between 0 and 1. + /// + /// is less than 0 or is greater than 1. + /// + public EntropyCropProcessor(float threshold) + { + Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); + this.Value = threshold; + } + + /// + /// Gets the threshold value. + /// + public float Value { get; } + + /// + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + ImageBase temp = new Image(source.Width, source.Height); + + // Detect the edges. + new SobelProcessor().Apply(temp, source, sourceRectangle); + + // Apply threshold binarization filter. + new ThresholdProcessor(.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 T[rectangle.Width * rectangle.Height]); + this.cropRectangle = rectangle; + } + + /// + 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) + { + return; + } + + int targetY = this.cropRectangle.Y; + int targetBottom = this.cropRectangle.Bottom; + int startX = this.cropRectangle.X; + int endX = this.cropRectangle.Right; + + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => + { + if (y >= targetY && y < targetBottom) + { + for (int x = startX; x < endX; x++) + { + targetPixels[x - startX, y - targetY] = sourcePixels[x, y]; + } + } + + this.OnRowProcessed(); + }); + } + } + + /// + protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + // Copy the pixels over. + if (source.Bounds == target.Bounds) + { + target.ClonePixels(target.Width, target.Height, source.Pixels); + } + } + } +} diff --git a/src/ImageProcessorCore/Samplers/Processors/IImageSampler.cs b/src/ImageProcessorCore/Samplers/Processors/IImageSampler.cs index 76a2c5a4d4..59326dee4a 100644 --- a/src/ImageProcessorCore/Samplers/Processors/IImageSampler.cs +++ b/src/ImageProcessorCore/Samplers/Processors/IImageSampler.cs @@ -8,7 +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 diff --git a/src/ImageProcessorCore/Samplers/Processors/ImageSampler.cs b/src/ImageProcessorCore/Samplers/Processors/ImageSampler.cs index adfe77432f..597fbb6faa 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ImageSampler.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ImageSampler.cs @@ -9,7 +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 new file mode 100644 index 0000000000..1d791f909c --- /dev/null +++ b/src/ImageProcessorCore/Samplers/Processors/Matrix3x2Processor.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Provides methods to transform an image using a . + /// + 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) + { + Matrix3x2 sizeMatrix; + if (Matrix3x2.Invert(processMatrix, out sizeMatrix)) + { + Rectangle rectangle = ImageMaths.GetBoundingRectangle(sourceRectangle, sizeMatrix); + 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) + { + Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(-target.Width / 2f, -target.Height / 2f); + Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width / 2f, source.Height / 2f); + return (translationToTargetCenter * matrix) * translateToSourceCenter; + } + } +} diff --git a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs index 3f78ed0bef..cd98b49c52 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs @@ -12,7 +12,9 @@ namespace ImageProcessorCore.Processors /// /// 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. @@ -43,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)) { @@ -53,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) @@ -203,7 +205,7 @@ namespace ImageProcessorCore.Processors } /// - protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { // Copy the pixels over. if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) diff --git a/src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs new file mode 100644 index 0000000000..5ca46cd44a --- /dev/null +++ b/src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs @@ -0,0 +1,243 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System; + using System.Threading.Tasks; + + /// + /// Provides methods that allow the rotation and flipping of an image around its center point. + /// + public class RotateFlipProcessor : ImageSampler + where T : IPackedVector + where TP : struct + { + /// + /// Initializes a new instance of the class. + /// + /// The used to perform rotation. + /// The used to perform flipping. + public RotateFlipProcessor(RotateType rotateType, FlipType flipType) + { + this.RotateType = rotateType; + this.FlipType = flipType; + } + + /// + /// Gets the used to perform flipping. + /// + public FlipType FlipType { get; } + + /// + /// Gets the used to perform rotation. + /// + public RotateType RotateType { get; } + + /// + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + switch (this.RotateType) + { + case RotateType.Rotate90: + this.Rotate90(target, source); + break; + case RotateType.Rotate180: + this.Rotate180(target, source); + break; + case RotateType.Rotate270: + this.Rotate270(target, source); + break; + default: + target.ClonePixels(target.Width, target.Height, source.Pixels); + break; + } + + switch (this.FlipType) + { + // No default needed as we have already set the pixels. + case FlipType.Vertical: + this.FlipX(target); + break; + case FlipType.Horizontal: + this.FlipY(target); + break; + } + } + + /// + /// 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) + { + int width = source.Width; + int height = source.Height; + Image temp = new Image(height, width); + + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor tempPixels = temp.Lock()) + { + Parallel.For( + 0, + height, + y => + { + for (int x = 0; x < width; x++) + { + int newX = height - y - 1; + newX = height - newX - 1; + int newY = width - x - 1; + newY = width - newY - 1; + tempPixels[newX, newY] = sourcePixels[x, y]; + } + + this.OnRowProcessed(); + }); + } + + target.SetPixels(height, width, temp.Pixels); + } + + /// + /// 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) + { + int width = source.Width; + int height = source.Height; + + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + 0, + height, + y => + { + for (int x = 0; x < width; x++) + { + int newX = width - x - 1; + int newY = height - y - 1; + targetPixels[newX, newY] = sourcePixels[x, y]; + } + + this.OnRowProcessed(); + }); + } + } + + /// + /// 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) + { + int width = source.Width; + int height = source.Height; + Image temp = new Image(height, width); + + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor tempPixels = temp.Lock()) + { + Parallel.For( + 0, + height, + y => + { + for (int x = 0; x < width; x++) + { + int newX = height - y - 1; + tempPixels[newX, x] = sourcePixels[x, y]; + } + + this.OnRowProcessed(); + }); + } + + target.SetPixels(height, width, temp.Pixels); + } + + /// + /// 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) + { + int width = target.Width; + int height = target.Height; + int halfHeight = (int)Math.Ceiling(target.Height * .5); + Image temp = new Image(width, height); + temp.ClonePixels(width, height, target.Pixels); + + using (IPixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor tempPixels = temp.Lock()) + { + Parallel.For( + 0, + halfHeight, + y => + { + for (int x = 0; x < width; x++) + { + int newY = height - y - 1; + targetPixels[x, y] = tempPixels[x, newY]; + targetPixels[x, newY] = tempPixels[x, y]; + } + + this.OnRowProcessed(); + }); + } + } + + /// + /// 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) + { + int width = target.Width; + int height = target.Height; + int halfWidth = (int)Math.Ceiling(width / 2d); + Image temp = new Image(width, height); + temp.ClonePixels(width, height, target.Pixels); + + using (IPixelAccessor targetPixels = target.Lock()) + using (IPixelAccessor tempPixels = temp.Lock()) + { + Parallel.For( + 0, + height, + y => + { + for (int x = 0; x < halfWidth; x++) + { + int newX = width - x - 1; + targetPixels[x, y] = tempPixels[newX, y]; + targetPixels[newX, y] = tempPixels[x, y]; + } + + this.OnRowProcessed(); + }); + } + } + } +} diff --git a/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs new file mode 100644 index 0000000000..71ae1aad2f --- /dev/null +++ b/src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs @@ -0,0 +1,70 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + using System.Threading.Tasks; + + /// + /// Provides methods that allow the rotating of images. + /// + public class RotateProcessor : Matrix3x2Processor + where T : IPackedVector + where TP : struct + { + /// + /// The tranform matrix to apply. + /// + private Matrix3x2 processMatrix; + + /// + /// Gets or sets the angle of processMatrix in degrees. + /// + public float Angle { get; set; } + + /// + /// Gets or sets a value indicating whether to expand the canvas to fit the rotated image. + /// + public bool Expand { get; set; } = true; + + /// + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + processMatrix = Point.CreateRotation(new Point(0, 0), -this.Angle); + if (this.Expand) + { + CreateNewTarget(target, sourceRectangle, processMatrix); + } + } + + /// + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + Matrix3x2 matrix = GetCenteredMatrix(target, source, this.processMatrix); + + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + 0, + target.Height, + y => + { + for (int x = 0; x < target.Width; x++) + { + Point transformedPoint = Point.Rotate(new Point(x, y), matrix); + if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) + { + targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; + } + } + + OnRowProcessed(); + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs new file mode 100644 index 0000000000..a595d3ee69 --- /dev/null +++ b/src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs @@ -0,0 +1,75 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + using System.Threading.Tasks; + + /// + /// Provides methods that allow the skewing of images. + /// + public class SkewProcessor : Matrix3x2Processor + where T : IPackedVector + where TP : struct + { + /// + /// The tranform matrix to apply. + /// + private Matrix3x2 processMatrix; + + /// + /// Gets or sets the angle of rotation along the x-axis in degrees. + /// + public float AngleX { get; set; } + + /// + /// Gets or sets the angle of rotation along the y-axis in degrees. + /// + public float AngleY { get; set; } + + /// + /// Gets or sets a value indicating whether to expand the canvas to fit the skewed image. + /// + public bool Expand { get; set; } = true; + + /// + 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) + { + CreateNewTarget(target, sourceRectangle, this.processMatrix); + } + } + + /// + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + Matrix3x2 matrix = GetCenteredMatrix(target, source, this.processMatrix); + + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + 0, + target.Height, + y => + { + for (int x = 0; x < target.Width; x++) + { + Point transformedPoint = Point.Skew(new Point(x, y), matrix); + if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) + { + targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; + } + } + + OnRowProcessed(); + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore/Samplers/Resize.cs b/src/ImageProcessorCore/Samplers/Resize.cs index aa2089d076..e9d3093f4d 100644 --- a/src/ImageProcessorCore/Samplers/Resize.cs +++ b/src/ImageProcessorCore/Samplers/Resize.cs @@ -138,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 new file mode 100644 index 0000000000..8a53b64981 --- /dev/null +++ b/src/ImageProcessorCore/Samplers/Rotate.cs @@ -0,0 +1,58 @@ +// +// 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 + { + /// + /// 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) + where T : IPackedVector + where TP : struct + { + return Rotate(source, degrees, true, progressHandler); + } + + /// + /// 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) + where T : IPackedVector + where TP : struct + { + RotateProcessor processor = new RotateProcessor { Angle = degrees, Expand = expand }; + processor.OnProgress += progressHandler; + + try + { + return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Samplers/RotateFlip.cs b/src/ImageProcessorCore/Samplers/RotateFlip.cs new file mode 100644 index 0000000000..407737dd37 --- /dev/null +++ b/src/ImageProcessorCore/Samplers/RotateFlip.cs @@ -0,0 +1,42 @@ +// +// 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 + { + /// + /// 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) + where T : IPackedVector + where TP : struct + { + RotateFlipProcessor processor = new RotateFlipProcessor(rotateType, flipType); + processor.OnProgress += progressHandler; + + try + { + return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Samplers/Skew.cs b/src/ImageProcessorCore/Samplers/Skew.cs new file mode 100644 index 0000000000..08e07ffd35 --- /dev/null +++ b/src/ImageProcessorCore/Samplers/Skew.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 + { + /// + /// 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) + where T : IPackedVector + where TP : struct + { + return Skew(source, degreesX, degreesY, true, progressHandler); + } + + /// + /// 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) + where T : IPackedVector + where TP : struct + { + SkewProcessor processor = new SkewProcessor { AngleX = degreesX, AngleY = degreesY, Expand = expand }; + processor.OnProgress += progressHandler; + + try + { + return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/tests/ImageProcessorCore.Benchmarks/Config.cs b/tests/ImageProcessorCore.Benchmarks/Config.cs index e2eb1f0ff9..126ecdaed4 100644 --- a/tests/ImageProcessorCore.Benchmarks/Config.cs +++ b/tests/ImageProcessorCore.Benchmarks/Config.cs @@ -8,7 +8,8 @@ namespace ImageProcessorCore.Benchmarks { // Uncomment if you want to use any of the diagnoser this.Add(new BenchmarkDotNet.Diagnostics.Windows.MemoryDiagnoser()); - this.Add(new BenchmarkDotNet.Diagnostics.Windows.InliningDiagnoser()); + // System.Drawing doesn't like this. + // this.Add(new BenchmarkDotNet.Diagnostics.Windows.InliningDiagnoser()); } } } \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/FileTestBase.cs b/tests/ImageProcessorCore.Tests/FileTestBase.cs index 8348d309c3..23b75e63a7 100644 --- a/tests/ImageProcessorCore.Tests/FileTestBase.cs +++ b/tests/ImageProcessorCore.Tests/FileTestBase.cs @@ -19,17 +19,17 @@ namespace ImageProcessorCore.Tests /// protected static readonly List Files = new List { - "TestImages/Formats/Jpg/Floorplan.jpeg", // Perf: Enable for local testing only + //"TestImages/Formats/Jpg/Floorplan.jpeg", // Perf: Enable for local testing only "TestImages/Formats/Jpg/Calliphora.jpg", - "TestImages/Formats/Jpg/fb.jpg", // Perf: Enable for local testing only - "TestImages/Formats/Jpg/progress.jpg", // Perf: Enable for local testing only + //"TestImages/Formats/Jpg/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 - //"TestImages/Formats/Bmp/Car.bmp", + "TestImages/Formats/Bmp/Car.bmp", // "TestImages/Formats/Bmp/neg_height.bmp", // Perf: Enable for local testing only //"TestImages/Formats/Png/blur.png", // Perf: Enable for local testing only //"TestImages/Formats/Png/indexed.png", // Perf: Enable for local testing only - //"TestImages/Formats/Png/splash.png", - //"TestImages/Formats/Gif/rings.gif", + "TestImages/Formats/Png/splash.png", + "TestImages/Formats/Gif/rings.gif", //"TestImages/Formats/Gif/giphy.gif" // Perf: Enable for local testing only }; diff --git a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs index 9d9726a9c6..0cb8ff0258 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs @@ -7,8 +7,6 @@ namespace ImageProcessorCore.Tests { using System.IO; - using Processors; - using Xunit; public class SamplerTests : FileTestBase @@ -24,7 +22,7 @@ namespace ImageProcessorCore.Tests //{ "Lanczos3", new Lanczos3Resampler() }, //{ "Lanczos5", new Lanczos5Resampler() }, //{ "Lanczos8", new Lanczos8Resampler() }, - { "MitchellNetravali", new MitchellNetravaliResampler() }, + //{ "MitchellNetravali", new MitchellNetravaliResampler() }, //{ "Hermite", new HermiteResampler() }, //{ "Spline", new SplineResampler() }, //{ "Robidoux", new RobidouxResampler() }, @@ -33,12 +31,6 @@ namespace ImageProcessorCore.Tests //{ "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 }, @@ -48,56 +40,29 @@ namespace ImageProcessorCore.Tests { RotateType.Rotate270, FlipType.None }, }; - //[Theory] - //[MemberData("Samplers")] - //public void SampleImage(string name, IImageSampler processor) - //{ - // if (!Directory.Exists("TestOutput/Sample")) - // { - // Directory.CreateDirectory("TestOutput/Sample"); - // } - - // foreach (string file in Files) - // { - // using (FileStream stream = File.OpenRead(file)) - // { - // Image image = new Image(stream); - // string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); - - // using (FileStream output = File.OpenWrite($"TestOutput/Sample/{ Path.GetFileName(filename) }")) - // { - // processor.OnProgress += this.ProgressUpdate; - // image = image.Process(image.Width / 2, image.Height / 2, processor); - // image.Save(output); - // processor.OnProgress -= this.ProgressUpdate; - // } - // } - // } - //} - - //[Fact] - //public void ImageShouldPad() - //{ - // if (!Directory.Exists("TestOutput/Pad")) - // { - // Directory.CreateDirectory("TestOutput/Pad"); - // } + [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); + 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); - // } - // } - // } - //} + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"TestOutput/Pad/{filename}")) + { + image.Pad(image.Width + 50, image.Height + 50, this.ProgressUpdate) + .Save(output); + } + } + } + } [Theory] [MemberData("ReSamplers")] @@ -114,7 +79,7 @@ namespace ImageProcessorCore.Tests { string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); - Image image = new Image(stream) {Quality=256}; + Image image = new Image(stream); using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) { image.Resize(image.Width / 2, image.Height / 2, sampler, false, this.ProgressUpdate) @@ -124,237 +89,237 @@ namespace ImageProcessorCore.Tests } } - //[Fact] - //public void ImageShouldResizeWidthAndKeepAspect() - //{ - // if (!Directory.Exists("TestOutput/Resize")) - // { - // Directory.CreateDirectory("TestOutput/Resize"); - // } - - // var name = "FixedWidth"; + [Fact] + public void ImageShouldResizeWidthAndKeepAspect() + { + 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); + var name = "FixedWidth"; - // 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); - // } - // } - // } - //} + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); - //[Fact] - //public void ImageShouldResizeHeightAndKeepAspect() - //{ - // if (!Directory.Exists("TestOutput/Resize")) - // { - // Directory.CreateDirectory("TestOutput/Resize"); - // } + 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); + } + } + } + } - // string name = "FixedHeight"; + [Fact] + public void ImageShouldResizeHeightAndKeepAspect() + { + 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); + string name = "FixedHeight"; - // 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); - // } - // } - // } - //} + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); - //[Fact] - //public void ImageShouldResizeWithCropMode() - //{ - // if (!Directory.Exists("TestOutput/ResizeCrop")) - // { - // Directory.CreateDirectory("TestOutput/ResizeCrop"); - // } + 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); + } + } + } + } - // foreach (string file in Files) - // { - // using (FileStream stream = File.OpenRead(file)) - // { - // string filename = Path.GetFileName(file); + [Fact] + public void ImageShouldResizeWithCropMode() + { + if (!Directory.Exists("TestOutput/ResizeCrop")) + { + Directory.CreateDirectory("TestOutput/ResizeCrop"); + } - // 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) - // }; + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileName(file); - // image.Resize(options, this.ProgressUpdate) - // .Save(output); - // } - // } - // } - //} + 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) + }; - //[Fact] - //public void ImageShouldResizeWithPadMode() - //{ - // if (!Directory.Exists("TestOutput/ResizePad")) - // { - // Directory.CreateDirectory("TestOutput/ResizePad"); - // } + image.Resize(options, this.ProgressUpdate) + .Save(output); + } + } + } + } - // foreach (string file in Files) - // { - // using (FileStream stream = File.OpenRead(file)) - // { - // string filename = Path.GetFileName(file); + [Fact] + public void ImageShouldResizeWithPadMode() + { + if (!Directory.Exists("TestOutput/ResizePad")) + { + Directory.CreateDirectory("TestOutput/ResizePad"); + } - // 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 - // }; + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileName(file); - // image.Resize(options, this.ProgressUpdate) - // .Save(output); - // } - // } - // } - //} + 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 + }; - //[Fact] - //public void ImageShouldResizeWithBoxPadMode() - //{ - // if (!Directory.Exists("TestOutput/ResizeBoxPad")) - // { - // Directory.CreateDirectory("TestOutput/ResizeBoxPad"); - // } + image.Resize(options, this.ProgressUpdate) + .Save(output); + } + } + } + } - // foreach (string file in Files) - // { - // using (FileStream stream = File.OpenRead(file)) - // { - // string filename = Path.GetFileName(file); + [Fact] + public void ImageShouldResizeWithBoxPadMode() + { + if (!Directory.Exists("TestOutput/ResizeBoxPad")) + { + Directory.CreateDirectory("TestOutput/ResizeBoxPad"); + } - // 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 - // }; + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileName(file); - // image.Resize(options, this.ProgressUpdate) - // .Save(output); - // } - // } - // } - //} + 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 + }; - //[Fact] - //public void ImageShouldResizeWithMaxMode() - //{ - // if (!Directory.Exists("TestOutput/ResizeMax")) - // { - // Directory.CreateDirectory("TestOutput/ResizeMax"); - // } + image.Resize(options, this.ProgressUpdate) + .Save(output); + } + } + } + } - // foreach (string file in Files) - // { - // using (FileStream stream = File.OpenRead(file)) - // { - // string filename = Path.GetFileName(file); + [Fact] + public void ImageShouldResizeWithMaxMode() + { + if (!Directory.Exists("TestOutput/ResizeMax")) + { + Directory.CreateDirectory("TestOutput/ResizeMax"); + } - // 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() - // }; + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileName(file); - // image.Resize(options, this.ProgressUpdate) - // .Save(output); - // } - // } - // } - //} + 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"); - // } + [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); + 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 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); - // } - // } - // } - //} + image.Resize(options, this.ProgressUpdate) + .Save(output); + } + } + } + } - //[Fact] - //public void ImageShouldResizeWithStretchMode() - //{ - // if (!Directory.Exists("TestOutput/ResizeStretch")) - // { - // Directory.CreateDirectory("TestOutput/ResizeStretch"); - // } + [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); + 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 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); - // } - // } - // } - //} + image.Resize(options, this.ProgressUpdate) + .Save(output); + } + } + } + } //[Fact] //public void ImageShouldNotDispose() @@ -387,140 +352,140 @@ namespace ImageProcessorCore.Tests // } //} - //[Theory] - //[MemberData("RotateFlips")] - //public void ImageShouldRotateFlip(RotateType rotateType, FlipType flipType) - //{ - // if (!Directory.Exists("TestOutput/RotateFlip")) - // { - // Directory.CreateDirectory("TestOutput/RotateFlip"); - // } + [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); + 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); - // } - // } - // } - //} + 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"); - // } + [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); + 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); - // } - // } - // } - //} + 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"); - // } + [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); + // 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); - // } - // } - // } - //} + 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"); - // } + [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); + 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); - // } - // } - // } - //} + 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"); - // } + [Fact] + public void ImageShouldCrop() + { + if (!Directory.Exists("TestOutput/Crop")) + { + Directory.CreateDirectory("TestOutput/Crop"); + } - // foreach (string file in Files) - // { - // using (FileStream stream = File.OpenRead(file)) - // { + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { - // string filename = Path.GetFileNameWithoutExtension(file) + "-Crop" + Path.GetExtension(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); - // } - // } - // } - //} + 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); + [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); - //} + Assert.Equal(result, expected); + } } -} +} \ No newline at end of file From a782e5fe49bcda016b5c9813dba01c8fbded2dfd Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 27 Jul 2016 17:55:22 +1000 Subject: [PATCH 57/91] Add resolution test Test Fails just now. Former-commit-id: da297b15e6ad9c6612378499e7506fade95174d8 Former-commit-id: 9c6106412771f797166983014b7cc976bdae3a3a Former-commit-id: 394581f30c0ef3e6beb50e32984d8b5315e81710 --- .../Formats/Jpg/JpegFileTests.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 tests/ImageProcessorCore.Tests/Formats/Jpg/JpegFileTests.cs diff --git a/tests/ImageProcessorCore.Tests/Formats/Jpg/JpegFileTests.cs b/tests/ImageProcessorCore.Tests/Formats/Jpg/JpegFileTests.cs new file mode 100644 index 0000000000..ae10b6cbde --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Formats/Jpg/JpegFileTests.cs @@ -0,0 +1,39 @@ +// +// 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 JpegFileTests : FileTestBase + { + [Fact] + public void ResolutionShouldChange() + { + if (!Directory.Exists("TestOutput/Resolution")) + { + Directory.CreateDirectory("TestOutput/Resolution"); + } + + 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/Resolution/{filename}")) + { + image.VerticalResolution = 150; + image.HorizontalResolution = 150; + image.Save(output); + } + } + } + } + } +} \ No newline at end of file From 439509f3be7678f7221d6cb3d3b345683394c2ee Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 27 Jul 2016 18:00:36 +1000 Subject: [PATCH 58/91] WIP JFIF header Former-commit-id: dd8bcac8f7d1170abb4796ae621e2a3409a1f2e3 Former-commit-id: f8f08b02228d80090537579c1581959ae4d03a9a Former-commit-id: 7f62ae7e5dac7a5412233c7a229a905c13306351 --- .../Formats/Jpg/JpegEncoderCore.cs | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs index c9a8427d59..99da0c5dfb 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs @@ -488,6 +488,11 @@ namespace ImageProcessorCore.Formats int componentCount = 3; // Write the Start Of Image marker. + double densityX = ((Image)image).HorizontalResolution; + double densityY = ((Image)image).VerticalResolution; + + //WriteApplicationHeader(densityX, densityY); + // TODO: JFIF header etc. this.buffer[0] = 0xff; this.buffer[1] = 0xd8; @@ -531,6 +536,55 @@ namespace ImageProcessorCore.Formats return -((-dividend + (divisor >> 1)) / divisor); } + /// + /// Writes the application header containing the Jfif identifier plus extra data. + /// + /// The image to encode from. + /// The writer to write to the stream. + private void WriteApplicationHeader(double horizontalResolution, double 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; + this.outputStream.Write(this.buffer, 0, 2); + + // Write the jfif headers + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[0] = JpegConstants.Markers.APP0; // Application Marker + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[0] = JpegConstants.Markers.XFF; + + byte[] jfif = { + JpegConstants.Markers.XFF, + JpegConstants.Markers.APP0, // Application Marker + 0x00, + 0x10, + 0x4a, // J + 0x46, // F + 0x49, // I + 0x46, // F + 0x00, // = "JFIF",'\0' + 0x01, // versionhi + 0x01, // versionlo + 0x01, // xyunits as dpi + }; + + // No thumbnail + byte[] thumbnail = { + 0x00, // Thumbnail width + 0x00 // Thumbnail height + }; + + // http://stackoverflow.com/questions/2188660/convert-short-to-byte-in-java + //writer.Write(jfif); + //writer.Write((short)densityX); + //writer.Write((short)densityY); + //writer.Write(thumbnail); + } + /// /// Writes the Define Quantization Marker and tables. /// From a938265ca36ea8139057a092103698835bbdf945 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 27 Jul 2016 22:28:44 +1000 Subject: [PATCH 59/91] jpeg now preserves dpi Former-commit-id: fcf404c9fede3784fd312aaba739185e12aba2d8 Former-commit-id: 08d5865cda85d173d37e895373cb110d6fa4b053 Former-commit-id: 1350e5c663eca447189095abfe5f8a316c5a970c --- .../Jpg/JpegDecoderCore.cs.REMOVED.git-id | 2 +- .../Formats/Jpg/JpegEncoderCore.cs | 74 ++++++++----------- ...JpegFileTests.cs => GeneralFormatTests.cs} | 4 +- 3 files changed, 33 insertions(+), 47 deletions(-) rename tests/ImageProcessorCore.Tests/Formats/Jpg/{JpegFileTests.cs => GeneralFormatTests.cs} (88%) diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id index fce8df5dc4..8c88878807 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id +++ b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id @@ -1 +1 @@ -3ef7ce74c01efdb8145d6b3d03c937c862025a00 \ No newline at end of file +64d830f6c3b8d21e7c1d250c9ab513a24cd54923 \ No newline at end of file diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs index 99da0c5dfb..fe2f018294 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs @@ -488,15 +488,10 @@ namespace ImageProcessorCore.Formats int componentCount = 3; // Write the Start Of Image marker. - double densityX = ((Image)image).HorizontalResolution; + double densityX = ((Image)image).HorizontalResolution; double densityY = ((Image)image).VerticalResolution; - //WriteApplicationHeader(densityX, densityY); - - // TODO: JFIF header etc. - this.buffer[0] = 0xff; - this.buffer[1] = 0xd8; - stream.Write(this.buffer, 0, 2); + WriteApplicationHeader((short)densityX, (short)densityY); // Write the quantization tables. this.WriteDQT(); @@ -539,50 +534,41 @@ namespace ImageProcessorCore.Formats /// /// Writes the application header containing the Jfif identifier plus extra data. /// - /// The image to encode from. - /// The writer to write to the stream. - private void WriteApplicationHeader(double horizontalResolution, double verticalResolution) + /// 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; - this.outputStream.Write(this.buffer, 0, 2); - - // Write the jfif headers - this.buffer[0] = JpegConstants.Markers.XFF; - this.buffer[0] = JpegConstants.Markers.APP0; // Application Marker - this.buffer[0] = JpegConstants.Markers.XFF; - this.buffer[0] = JpegConstants.Markers.XFF; - this.buffer[0] = JpegConstants.Markers.XFF; - this.buffer[0] = JpegConstants.Markers.XFF; - this.buffer[0] = JpegConstants.Markers.XFF; - byte[] jfif = { - JpegConstants.Markers.XFF, - JpegConstants.Markers.APP0, // Application Marker - 0x00, - 0x10, - 0x4a, // J - 0x46, // F - 0x49, // I - 0x46, // F - 0x00, // = "JFIF",'\0' - 0x01, // versionhi - 0x01, // versionlo - 0x01, // xyunits as dpi - }; + // 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 - byte[] thumbnail = { - 0x00, // Thumbnail width - 0x00 // Thumbnail height - }; - - // http://stackoverflow.com/questions/2188660/convert-short-to-byte-in-java - //writer.Write(jfif); - //writer.Write((short)densityX); - //writer.Write((short)densityY); - //writer.Write(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); } /// diff --git a/tests/ImageProcessorCore.Tests/Formats/Jpg/JpegFileTests.cs b/tests/ImageProcessorCore.Tests/Formats/Jpg/GeneralFormatTests.cs similarity index 88% rename from tests/ImageProcessorCore.Tests/Formats/Jpg/JpegFileTests.cs rename to tests/ImageProcessorCore.Tests/Formats/Jpg/GeneralFormatTests.cs index ae10b6cbde..69e2ef0f10 100644 --- a/tests/ImageProcessorCore.Tests/Formats/Jpg/JpegFileTests.cs +++ b/tests/ImageProcessorCore.Tests/Formats/Jpg/GeneralFormatTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -9,7 +9,7 @@ namespace ImageProcessorCore.Tests using Xunit; - public class JpegFileTests : FileTestBase + public class GeneralFormatTests : FileTestBase { [Fact] public void ResolutionShouldChange() From 31efe63e6ddb72b2e3e67e195cdf7e89b37d921f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 27 Jul 2016 23:23:08 +1000 Subject: [PATCH 60/91] Add ColorBlindness Former-commit-id: 5ce61ca1e508ed598370b5817fa54fa6aaf7ecff Former-commit-id: b04c8676420dad90a6cb9515bdc8b9db67752576 Former-commit-id: 98da045e3b41b5369a5d8ef393b2b22b8c011665 --- .../Filters/ColorBlindness.cs | 96 +++++++++++++++++++ .../Filters/Options/ColorBlindness.cs | 53 ++++++++++ .../ColorBlindness/AchromatomalyProcessor.cs | 36 +++++++ .../ColorBlindness/AchromatopsiaProcessor.cs | 36 +++++++ .../ColorBlindness/DeuteranomalyProcessor.cs | 33 +++++++ .../ColorBlindness/DeuteranopiaProcessor.cs | 33 +++++++ .../ColorBlindness/ProtanomalyProcessor.cs | 33 +++++++ .../ColorBlindness/ProtanopiaProcessor.cs | 33 +++++++ .../ColorMatrix/ColorBlindness/README.md | 4 + .../ColorBlindness/TritanomalyProcessor.cs | 33 +++++++ .../ColorBlindness/TritanopiaProcessor.cs | 33 +++++++ .../ColorMatrix/ColorMatrixFilter.cs | 10 +- src/ImageProcessorCore/Image.cs | 6 +- src/ImageProcessorCore/Image/Image.cs | 4 +- src/ImageProcessorCore/Image/ImageBase.cs | 2 + .../Image/ImageExtensions.cs | 2 +- src/ImageProcessorCore/Samplers/Pad.cs | 2 +- .../Processors/Filters/ColorBlindnessTest.cs | 52 ++++++++++ 18 files changed, 492 insertions(+), 9 deletions(-) create mode 100644 src/ImageProcessorCore/Filters/ColorBlindness.cs create mode 100644 src/ImageProcessorCore/Filters/Options/ColorBlindness.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/README.md create mode 100644 src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/ColorBlindnessTest.cs diff --git a/src/ImageProcessorCore/Filters/ColorBlindness.cs b/src/ImageProcessorCore/Filters/ColorBlindness.cs new file mode 100644 index 0000000000..b5848a5140 --- /dev/null +++ b/src/ImageProcessorCore/Filters/ColorBlindness.cs @@ -0,0 +1,96 @@ +// +// 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 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) + where T : IPackedVector + where TP : struct + { + return ColorBlindness(source, colorBlindness, source.Bounds, progressHandler); + } + + /// + /// 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) + where T : IPackedVector + where TP : struct + { + IImageProcessor processor; + + switch (colorBlindness) + { + case ImageProcessorCore.ColorBlindness.Achromatomaly: + processor = new AchromatomalyProcessor(); + break; + + case ImageProcessorCore.ColorBlindness.Achromatopsia: + processor = new AchromatopsiaProcessor(); + break; + + case ImageProcessorCore.ColorBlindness.Deuteranomaly: + processor = new DeuteranomalyProcessor(); + break; + + case ImageProcessorCore.ColorBlindness.Deuteranopia: + processor = new DeuteranopiaProcessor(); + break; + + case ImageProcessorCore.ColorBlindness.Protanomaly: + processor = new ProtanomalyProcessor(); + break; + + case ImageProcessorCore.ColorBlindness.Protanopia: + processor = new ProtanopiaProcessor(); + break; + + case ImageProcessorCore.ColorBlindness.Tritanomaly: + processor = new TritanomalyProcessor(); + break; + + default: + processor = new TritanopiaProcessor(); + break; + } + + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Options/ColorBlindness.cs b/src/ImageProcessorCore/Filters/Options/ColorBlindness.cs new file mode 100644 index 0000000000..6d7fe849bc --- /dev/null +++ b/src/ImageProcessorCore/Filters/Options/ColorBlindness.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + /// + /// Enumerates the various types of color blindness. + /// + public enum ColorBlindness + { + /// + /// Partial color desensitivity. + /// + Achromatomaly, + + /// + /// Complete color desensitivity (Monochrome) + /// + Achromatopsia, + + /// + /// Green weak + /// + Deuteranomaly, + + /// + /// Green blind + /// + Deuteranopia, + + /// + /// Red weak + /// + Protanomaly, + + /// + /// Red blind + /// + Protanopia, + + /// + /// Blue weak + /// + Tritanomaly, + + /// + /// Blue blind + /// + Tritanopia + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs new file mode 100644 index 0000000000..e8dd077ebc --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating Achromatomaly (Color desensitivity) color blindness. + /// + /// The pixel format. + /// The packed format. long, float. + public class AchromatomalyProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = .618f, + M12 = .163f, + M13 = .163f, + M21 = .320f, + M22 = .775f, + M23 = .320f, + M31 = .062f, + M32 = .062f, + M33 = .516f + }; + + /// + public override bool Compand => false; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs new file mode 100644 index 0000000000..0e7f69a131 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating Achromatopsia (Monochrome) color blindness. + /// + /// The pixel format. + /// The packed format. long, float. + public class AchromatopsiaProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = .299f, + M12 = .299f, + M13 = .299f, + M21 = .587f, + M22 = .587f, + M23 = .587f, + M31 = .114f, + M32 = .114f, + M33 = .114f + }; + + /// + public override bool Compand => false; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs new file mode 100644 index 0000000000..b7f48ea1dc --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating Deuteranomaly (Green-Weak) color blindness. + /// + /// The pixel format. + /// The packed format. long, float. + public class DeuteranomalyProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 0.8f, + M12 = 0.258f, + M21 = 0.2f, + M22 = 0.742f, + M23 = 0.142f, + M33 = 0.858f + }; + + /// + public override bool Compand => false; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs new file mode 100644 index 0000000000..82f50d07fb --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating Deuteranopia (Green-Blind) color blindness. + /// + /// The pixel format. + /// The packed format. long, float. + public class DeuteranopiaProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 0.625f, + M12 = 0.7f, + M21 = 0.375f, + M22 = 0.3f, + M23 = 0.3f, + M33 = 0.7f + }; + + /// + public override bool Compand => false; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs new file mode 100644 index 0000000000..32380cba13 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating Protanopia (Red-Weak) color blindness. + /// + /// The pixel format. + /// The packed format. long, float. + public class ProtanomalyProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 0.817f, + M12 = 0.333f, + M21 = 0.183f, + M22 = 0.667f, + M23 = 0.125f, + M33 = 0.875f + }; + + /// + public override bool Compand => false; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs new file mode 100644 index 0000000000..891b6e06ad --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating Protanopia (Red-Blind) color blindness. + /// + /// The pixel format. + /// The packed format. long, float. + public class ProtanopiaProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 0.567f, + M12 = 0.558f, + M21 = 0.433f, + M22 = 0.442f, + M23 = 0.242f, + M33 = 0.758f + }; + + /// + public override bool Compand => false; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/README.md b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/README.md new file mode 100644 index 0000000000..209f3b67bd --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/README.md @@ -0,0 +1,4 @@ +Color blindness matrices adapted from and tested against: + +http://web.archive.org/web/20090413045433/http://nofunc.org/Color_Matrix_Library +http://www.color-blindness.com/coblis-color-blindness-simulator/ \ No newline at end of file diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs new file mode 100644 index 0000000000..88e5fd5dcb --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating Tritanomaly (Blue-Weak) color blindness. + /// + /// The pixel format. + /// The packed format. long, float. + public class TritanomalyProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 0.967f, + M21 = 0.33f, + M22 = 0.733f, + M23 = 0.183f, + M32 = 0.267f, + M33 = 0.817f + }; + + /// + public override bool Compand => false; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs new file mode 100644 index 0000000000..b708927a24 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating Tritanopia (Blue-Blind) color blindness. + /// + /// The pixel format. + /// The packed format. long, float. + public class TritanopiaProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 0.95f, + M21 = 0.05f, + M22 = 0.433f, + M23 = 0.475f, + M32 = 0.567f, + M33 = 0.525f + }; + + /// + public override bool Compand => false; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs index 0b7600fbc6..cbab86a1c2 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs @@ -9,8 +9,10 @@ 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. /// + /// The pixel format. + /// The packed format. long, float. public abstract class ColorMatrixFilter : ImageProcessor, IColorMatrixFilter where T : IPackedVector where TP : struct @@ -63,11 +65,11 @@ namespace ImageProcessorCore.Processors // color = Color.Expand(color); //} - Vector4 transformed = Vector4.Transform(color.ToVector4(), matrix); - //Vector3 transformed = Vector3.Transform(color.ToVector3(), matrix); + Vector4 vector = color.ToVector4(); + Vector3 transformed = Vector3.Transform(new Vector3(vector.X, vector.Y, vector.Z), matrix); //return compand ? Color.Compress(new Color(transformed, color.A)) : new Color(transformed, color.A); T packed = default(T); - packed.PackVector(transformed); + packed.PackVector(new Vector4(transformed.X, transformed.Y, transformed.Z, vector.W)); return packed; } } diff --git a/src/ImageProcessorCore/Image.cs b/src/ImageProcessorCore/Image.cs index 9ab3e73ba2..e4e2186b97 100644 --- a/src/ImageProcessorCore/Image.cs +++ b/src/ImageProcessorCore/Image.cs @@ -5,12 +5,14 @@ namespace ImageProcessorCore { - using System; + using System.Diagnostics; using System.IO; /// - /// Represents an image. Each pixel is a made up four 8-bit components red, green, blue, and alpha. + /// 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. /// + [DebuggerDisplay("Image: {Width}x{Height}")] public class Image : Image { /// diff --git a/src/ImageProcessorCore/Image/Image.cs b/src/ImageProcessorCore/Image/Image.cs index 0b716f0dd1..a53c87c5f7 100644 --- a/src/ImageProcessorCore/Image/Image.cs +++ b/src/ImageProcessorCore/Image/Image.cs @@ -13,12 +13,14 @@ namespace ImageProcessorCore 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 @@ -75,6 +77,7 @@ namespace ImageProcessorCore /// The other image, where the clone should be made from. /// is null. public Image(Image other) + : base(other) { foreach (ImageFrame frame in other.Frames) { @@ -84,7 +87,6 @@ namespace ImageProcessorCore } } - this.Quality = other.Quality; this.RepeatCount = other.RepeatCount; this.HorizontalResolution = other.HorizontalResolution; this.VerticalResolution = other.VerticalResolution; diff --git a/src/ImageProcessorCore/Image/ImageBase.cs b/src/ImageProcessorCore/Image/ImageBase.cs index 3a7a8f99a9..adbe70d1b1 100644 --- a/src/ImageProcessorCore/Image/ImageBase.cs +++ b/src/ImageProcessorCore/Image/ImageBase.cs @@ -6,6 +6,7 @@ namespace ImageProcessorCore { using System; + using System.Diagnostics; /// /// The base class of all images. Encapsulates the basic properties and methods required to manipulate @@ -13,6 +14,7 @@ namespace ImageProcessorCore /// /// The pixel format. /// The packed format. long, float. + [DebuggerDisplay("Image: {Width}x{Height}")] public abstract class ImageBase : IImageBase where T : IPackedVector where TP : struct diff --git a/src/ImageProcessorCore/Image/ImageExtensions.cs b/src/ImageProcessorCore/Image/ImageExtensions.cs index c97b6dfa65..0b1bac55e6 100644 --- a/src/ImageProcessorCore/Image/ImageExtensions.cs +++ b/src/ImageProcessorCore/Image/ImageExtensions.cs @@ -174,7 +174,7 @@ namespace ImageProcessorCore : 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, diff --git a/src/ImageProcessorCore/Samplers/Pad.cs b/src/ImageProcessorCore/Samplers/Pad.cs index 2bb6acc33b..0b1b959b08 100644 --- a/src/ImageProcessorCore/Samplers/Pad.cs +++ b/src/ImageProcessorCore/Samplers/Pad.cs @@ -21,7 +21,7 @@ namespace ImageProcessorCore /// The new width. /// The new height. /// A delegate which is called as progress is made processing the image. - /// The . + /// The . public static Image Pad(this Image source, int width, int height, ProgressEventHandler progressHandler = null) where T : IPackedVector where TP : struct diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/ColorBlindnessTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/ColorBlindnessTest.cs new file mode 100644 index 0000000000..5b2300885f --- /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 From 743e3309b3cc11ca4a5763122ceb4bb5781cd923 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 28 Jul 2016 00:25:06 +1000 Subject: [PATCH 61/91] Begin add ColorMatrix filters. Former-commit-id: 87e8abdf10ff5c2068354690817dd32a86d613c2 Former-commit-id: 9eeb9dd07422fded5300087200f781d7d7064b50 Former-commit-id: a35e9e5e97952e268bcb3310194d738282817052 --- .../Common/Extensions/Vector4Extensions.cs | 91 +++++++++++++++++++ .../Filters/ColorBlindness.cs | 4 +- src/ImageProcessorCore/Filters/Lomograph.cs | 58 ++++++++++++ .../Filters/Options/GreyscaleMode.cs | 23 +++++ src/ImageProcessorCore/Filters/Polaroid.cs | 58 ++++++++++++ .../Binarization/ThresholdProcessor.cs | 2 + .../ColorMatrix/BlackWhiteProcessor.cs | 36 ++++++++ .../ColorMatrix/ColorMatrixFilter.cs | 21 +++-- .../ColorMatrix/GreyscaleBt601Processor.cs | 34 +++++++ .../Processors/ColorMatrix/HueProcessor.cs | 88 ++++++++++++++++++ .../ColorMatrix/KodachromeProcessor.cs | 30 ++++++ .../ColorMatrix/LomographProcessor.cs | 38 ++++++++ .../ColorMatrix/PolaroidProcessor.cs | 54 +++++++++++ .../ColorMatrix/SaturationProcessor.cs | 78 ++++++++++++++++ .../Processors/ColorMatrix/SepiaProcessor.cs | 37 ++++++++ .../Filters/Processors/GlowProcessor.cs | 80 ++++++++++++++++ .../Filters/Processors/VignetteProcessor.cs | 79 ++++++++++++++++ src/ImageProcessorCore/Samplers/Crop.cs | 2 +- .../Samplers/EntropyCrop.cs | 2 +- src/ImageProcessorCore/Samplers/Pad.cs | 2 +- src/ImageProcessorCore/Samplers/Rotate.cs | 2 +- src/ImageProcessorCore/Samplers/RotateFlip.cs | 2 +- src/ImageProcessorCore/Samplers/Skew.cs | 2 +- .../Processors/Filters/ColorBlindnessTest.cs | 2 +- .../Processors/Filters/LomographTest.cs | 38 ++++++++ .../Processors/Filters/PolaroidTest.cs | 38 ++++++++ 26 files changed, 882 insertions(+), 19 deletions(-) create mode 100644 src/ImageProcessorCore/Common/Extensions/Vector4Extensions.cs create mode 100644 src/ImageProcessorCore/Filters/Lomograph.cs create mode 100644 src/ImageProcessorCore/Filters/Options/GreyscaleMode.cs create mode 100644 src/ImageProcessorCore/Filters/Polaroid.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/ColorMatrix/KodachromeProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/ColorMatrix/LomographProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/ColorMatrix/PolaroidProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/ColorMatrix/SepiaProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/LomographTest.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/PolaroidTest.cs 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/Filters/ColorBlindness.cs b/src/ImageProcessorCore/Filters/ColorBlindness.cs index b5848a5140..6ab553ba2d 100644 --- a/src/ImageProcessorCore/Filters/ColorBlindness.cs +++ b/src/ImageProcessorCore/Filters/ColorBlindness.cs @@ -1,14 +1,14 @@ // // 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 { diff --git a/src/ImageProcessorCore/Filters/Lomograph.cs b/src/ImageProcessorCore/Filters/Lomograph.cs new file mode 100644 index 0000000000..5a484bcc87 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Lomograph.cs @@ -0,0 +1,58 @@ +// +// 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 + { + /// + /// 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) + where T : IPackedVector + where TP : struct + { + return Lomograph(source, source.Bounds, progressHandler); + } + + /// + /// 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) + where T : IPackedVector + where TP : struct + { + LomographProcessor processor = new LomographProcessor(); + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Options/GreyscaleMode.cs b/src/ImageProcessorCore/Filters/Options/GreyscaleMode.cs new file mode 100644 index 0000000000..269c1179ef --- /dev/null +++ b/src/ImageProcessorCore/Filters/Options/GreyscaleMode.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// Provides enumeration over the various greyscale methods available. + /// + public enum GreyscaleMode + { + /// + /// ITU-R Recommendation BT.709 + /// + Bt709, + + /// + /// ITU-R Recommendation BT.601 + /// + Bt601 + } +} diff --git a/src/ImageProcessorCore/Filters/Polaroid.cs b/src/ImageProcessorCore/Filters/Polaroid.cs new file mode 100644 index 0000000000..e56496322c --- /dev/null +++ b/src/ImageProcessorCore/Filters/Polaroid.cs @@ -0,0 +1,58 @@ +// +// 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 + { + /// + /// 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) + where T : IPackedVector + where TP : struct + { + return Polaroid(source, source.Bounds, progressHandler); + } + + /// + /// 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) + where T : IPackedVector + where TP : struct + { + PolaroidProcessor processor = new PolaroidProcessor(); + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs index 856ae6d615..9315c0b16a 100644 --- a/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs @@ -12,6 +12,8 @@ namespace ImageProcessorCore.Processors /// . The image will be converted to greyscale before thresholding /// occurs. /// + /// The pixel format. + /// The packed format. long, float. public class ThresholdProcessor : ImageProcessor where T : IPackedVector where TP : struct diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs new file mode 100644 index 0000000000..6893d2a8a0 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image to their black and white equivalent. + /// + /// The pixel format. + /// The packed format. long, float. + public class BlackWhiteProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 1.5f, + M12 = 1.5f, + M13 = 1.5f, + M21 = 1.5f, + M22 = 1.5f, + M23 = 1.5f, + M31 = 1.5f, + M32 = 1.5f, + M33 = 1.5f, + M41 = -1f, + M42 = -1f, + M43 = -1f, + }; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs index cbab86a1c2..dc5811133b 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs @@ -29,6 +29,7 @@ namespace ImageProcessorCore.Processors int startX = sourceRectangle.X; int endX = sourceRectangle.Right; Matrix4x4 matrix = this.Matrix; + bool compand = this.Compand; using (IPixelAccessor sourcePixels = source.Lock()) using (IPixelAccessor targetPixels = target.Lock()) @@ -40,7 +41,7 @@ namespace ImageProcessorCore.Processors { 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(); @@ -53,23 +54,23 @@ namespace ImageProcessorCore.Processors /// /// The source color. /// The matrix. + /// Whether to compand the color during processing. /// /// The . /// - private T ApplyMatrix(T 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); - //} + if (compand) + { + vector = vector.Expand(); + } - Vector4 vector = color.ToVector4(); Vector3 transformed = Vector3.Transform(new Vector3(vector.X, vector.Y, vector.Z), matrix); - //return compand ? Color.Compress(new Color(transformed, color.A)) : new Color(transformed, color.A); + vector = new Vector4(transformed.X, transformed.Y, transformed.Z, vector.W); T packed = default(T); - packed.PackVector(new Vector4(transformed.X, transformed.Y, transformed.Z, vector.W)); + packed.PackVector(compand ? vector.Compress() : vector); return packed; } } diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs new file mode 100644 index 0000000000..9e6b25ad68 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image to greyscale applying the formula as specified by + /// ITU-R Recommendation BT.601 . + /// + /// The pixel format. + /// The packed format. long, float. + public class GreyscaleBt601Processor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = .299f, + M12 = .299f, + M13 = .299f, + M21 = .587f, + M22 = .587f, + M23 = .587f, + M31 = .114f, + M32 = .114f, + M33 = .114f + }; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs new file mode 100644 index 0000000000..771abeab50 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs @@ -0,0 +1,88 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System; + using System.Numerics; + + /// + /// 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. + /// + private Matrix4x4 matrix; + + /// + /// Initializes a new instance of the class. + /// + /// The new brightness of the image. Must be between -100 and 100. + public HueProcessor(float angle) + { + // Wrap the angle round at 360. + angle = angle % 360; + + // Make sure it's not negative. + while (angle < 0) + { + angle += 360; + } + + 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); + double cosradians = Math.Cos(radians); + double sinradians = Math.Sin(radians); + + float lumR = .213f; + float lumG = .715f; + float lumB = .072f; + + float oneMinusLumR = 1 - lumR; + float oneMinusLumG = 1 - lumG; + float oneMinusLumB = 1 - lumB; + + // The matrix is set up to preserve the luminance of the image. + // See http://graficaobscura.com/matrix/index.html + // Number are taken from https://msdn.microsoft.com/en-us/library/jj192162(v=vs.85).aspx + Matrix4x4 matrix4X4 = new Matrix4x4() + { + M11 = (float)(lumR + (cosradians * oneMinusLumR) - (sinradians * lumR)), + M12 = (float)(lumR - (cosradians * lumR) - (sinradians * 0.143)), + M13 = (float)(lumR - (cosradians * lumR) - (sinradians * oneMinusLumR)), + M21 = (float)(lumG - (cosradians * lumG) - (sinradians * lumG)), + M22 = (float)(lumG + (cosradians * oneMinusLumG) + (sinradians * 0.140)), + M23 = (float)(lumG - (cosradians * lumG) + (sinradians * lumG)), + M31 = (float)(lumB - (cosradians * lumB) + (sinradians * oneMinusLumB)), + M32 = (float)(lumB - (cosradians * lumB) - (sinradians * 0.283)), + M33 = (float)(lumB + (cosradians * oneMinusLumB) + (sinradians * lumB)) + }; + + this.matrix = matrix4X4; + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/KodachromeProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/KodachromeProcessor.cs new file mode 100644 index 0000000000..f7a84fc082 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/KodachromeProcessor.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating an old Kodachrome camera effect. + /// + /// The pixel format. + /// The packed format. long, float. + public class KodachromeProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 0.6997023f, + M22 = 0.4609577f, + M33 = 0.397218f, + M41 = 0.005f, + M42 = -0.005f, + M43 = 0.005f + }; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/LomographProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/LomographProcessor.cs new file mode 100644 index 0000000000..cfdc628935 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/LomographProcessor.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating an old Lomograph effect. + /// + /// The pixel format. + /// The packed format. long, float. + public class LomographProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 1.5f, + M22 = 1.45f, + M33 = 1.11f, + M41 = -.1f, + M42 = .0f, + M43 = -.08f + }; + + /// + protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + 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 new file mode 100644 index 0000000000..7a0263bd05 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/PolaroidProcessor.cs @@ -0,0 +1,54 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image recreating an old Polaroid effect. + /// + /// The pixel format. + /// The packed format. long, float. + public class PolaroidProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = 1.538f, + M12 = -0.062f, + M13 = -0.262f, + M21 = -0.022f, + M22 = 1.578f, + M23 = -0.022f, + M31 = .216f, + M32 = -.16f, + M33 = 1.5831f, + M41 = 0.02f, + M42 = -0.05f, + M43 = -0.05f + }; + + /// + protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + 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 + { + GlowColor = packedG, + RadiusX = target.Width / 4f, + RadiusY = target.Width / 4f + } + .Apply(target, target, targetRectangle); + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs new file mode 100644 index 0000000000..39e7b0bd46 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs @@ -0,0 +1,78 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// An to change the saturation of an . + /// + /// 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. + /// + private readonly int saturation; + + /// + /// The used to alter the image. + /// + private Matrix4x4 matrix; + + /// + /// Initializes a new instance of the class. + /// + /// The new saturation of the image. Must be between -100 and 100. + /// + /// is less than -100 or is greater than 100. + /// + public SaturationProcessor(int saturation) + { + 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. + saturationFactor++; + + // The matrix is set up to "shear" the colour space using the following set of values. + // Note that each colour component has an effective luminance which contributes to the + // overall brightness of the pixel. + // See http://graficaobscura.com/matrix/index.html + float saturationComplement = 1.0f - saturationFactor; + float saturationComplementR = 0.3086f * saturationComplement; + float saturationComplementG = 0.6094f * saturationComplement; + float saturationComplementB = 0.0820f * saturationComplement; + + Matrix4x4 matrix4X4 = new Matrix4x4() + { + M11 = saturationComplementR + saturationFactor, + M12 = saturationComplementR, + M13 = saturationComplementR, + M21 = saturationComplementG, + M22 = saturationComplementG + saturationFactor, + M23 = saturationComplementG, + M31 = saturationComplementB, + M32 = saturationComplementB, + M33 = saturationComplementB + saturationFactor, + }; + + this.matrix = matrix4X4; + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SepiaProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SepiaProcessor.cs new file mode 100644 index 0000000000..60ba27c0e6 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SepiaProcessor.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + + /// + /// Converts the colors of the image to their sepia equivalent. + /// The formula used matches the svg specification. + /// + /// The pixel format. + /// The packed format. long, float. + public class SepiaProcessor : ColorMatrixFilter + where T : IPackedVector + where TP : struct + { + /// + public override Matrix4x4 Matrix => new Matrix4x4() + { + M11 = .393f, + M12 = .349f, + M13 = .272f, + M21 = .769f, + M22 = .686f, + M23 = .534f, + M31 = .189f, + M32 = .168f, + M33 = .131f + }; + + /// + public override bool Compand => false; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs b/src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs new file mode 100644 index 0000000000..fdffcd7809 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs @@ -0,0 +1,80 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// Creates a glow effect on the image + /// + /// 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 T GlowColor { get; set; } + + /// + /// Gets or sets the the x-radius. + /// + public float RadiusX { get; set; } + + /// + /// Gets or sets the the y-radius. + /// + public float RadiusY { get; set; } + + /// + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + 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 (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => + { + for (int x = startX; x < endX; x++) + { + // TODO: Premultiply? + float distance = Vector2.Distance(centre, new Vector2(x, y)); + 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/VignetteProcessor.cs b/src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs new file mode 100644 index 0000000000..2afd9e00cd --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs @@ -0,0 +1,79 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// Creates a vignette effect on the image + /// + /// 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 T VignetteColor { get; set; } + + /// + /// Gets or sets the the x-radius. + /// + public float RadiusX { get; set; } + + /// + /// Gets or sets the the y-radius. + /// + public float RadiusY { get; set; } + + /// + protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) + { + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + 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 (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => + { + for (int x = startX; x < endX; x++) + { + float distance = Vector2.Distance(centre, new Vector2(x, y)); + 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/Samplers/Crop.cs b/src/ImageProcessorCore/Samplers/Crop.cs index ff3196c430..04c96dbd1a 100644 --- a/src/ImageProcessorCore/Samplers/Crop.cs +++ b/src/ImageProcessorCore/Samplers/Crop.cs @@ -8,7 +8,7 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { diff --git a/src/ImageProcessorCore/Samplers/EntropyCrop.cs b/src/ImageProcessorCore/Samplers/EntropyCrop.cs index 123378173c..631662d800 100644 --- a/src/ImageProcessorCore/Samplers/EntropyCrop.cs +++ b/src/ImageProcessorCore/Samplers/EntropyCrop.cs @@ -8,7 +8,7 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { diff --git a/src/ImageProcessorCore/Samplers/Pad.cs b/src/ImageProcessorCore/Samplers/Pad.cs index 0b1b959b08..0a7277144e 100644 --- a/src/ImageProcessorCore/Samplers/Pad.cs +++ b/src/ImageProcessorCore/Samplers/Pad.cs @@ -8,7 +8,7 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { diff --git a/src/ImageProcessorCore/Samplers/Rotate.cs b/src/ImageProcessorCore/Samplers/Rotate.cs index 8a53b64981..ad00518f14 100644 --- a/src/ImageProcessorCore/Samplers/Rotate.cs +++ b/src/ImageProcessorCore/Samplers/Rotate.cs @@ -8,7 +8,7 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { diff --git a/src/ImageProcessorCore/Samplers/RotateFlip.cs b/src/ImageProcessorCore/Samplers/RotateFlip.cs index 407737dd37..093af8503f 100644 --- a/src/ImageProcessorCore/Samplers/RotateFlip.cs +++ b/src/ImageProcessorCore/Samplers/RotateFlip.cs @@ -8,7 +8,7 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { diff --git a/src/ImageProcessorCore/Samplers/Skew.cs b/src/ImageProcessorCore/Samplers/Skew.cs index 08e07ffd35..ab188ceeee 100644 --- a/src/ImageProcessorCore/Samplers/Skew.cs +++ b/src/ImageProcessorCore/Samplers/Skew.cs @@ -8,7 +8,7 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/ColorBlindnessTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/ColorBlindnessTest.cs index 5b2300885f..9e6c9c1cb7 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Filters/ColorBlindnessTest.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/ColorBlindnessTest.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // 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/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 From f549a7d3ec1c3bd62c0fe077deb9b0048e9edece Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 28 Jul 2016 09:43:36 +1000 Subject: [PATCH 62/91] Update comment Former-commit-id: d80cdbd81de76bcf557aba69ce3da92b7ff3221f Former-commit-id: 0dd9336f7b40f5a5908bb1dba2b2f5463f13dace Former-commit-id: 34de202caddbe055ecda7668feea649bc37863fc --- .../Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id index 8c88878807..9e20e51de1 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id +++ b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id @@ -1 +1 @@ -64d830f6c3b8d21e7c1d250c9ab513a24cd54923 \ No newline at end of file +45c6fbb81e384c9f75e75efc01c269f6e2b20006 \ No newline at end of file From a22df84a3024e83c416c8cbe94b7df6fe446c4b5 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 28 Jul 2016 10:01:09 +1000 Subject: [PATCH 63/91] Fix Color Former-commit-id: ccebbd4c9cf2d42c17a76d82af8081cc4ac3080f Former-commit-id: 04ba5ac8e84a81a2b8f11b5d73eefe2278aa8899 Former-commit-id: 7e6ad2c1f3967b97a86c681d0678b6a5a8942722 --- .../Colors/PackedVector/Color.cs | 34 +++++++++---------- .../Jpg/JpegDecoderCore.cs.REMOVED.git-id | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/ImageProcessorCore/Colors/PackedVector/Color.cs b/src/ImageProcessorCore/Colors/PackedVector/Color.cs index cc6664c0d0..4e4e817a85 100644 --- a/src/ImageProcessorCore/Colors/PackedVector/Color.cs +++ b/src/ImageProcessorCore/Colors/PackedVector/Color.cs @@ -54,33 +54,33 @@ namespace ImageProcessorCore /// /// Initializes a new instance of the struct. /// - /// The blue component. - /// The green component. /// The red component. + /// The green component. + /// The blue component. /// The alpha component. - public Color(float b, float g, float r, float a) + public Color(float r, float g, float b, float a) : this() { - Vector4 clamped = Vector4.Clamp(new Vector4(b, g, r, a), Vector4.Zero, Vector4.One) * 255F; - this.B = (byte)Math.Round(clamped.X); + 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.R = (byte)Math.Round(clamped.Z); + this.B = (byte)Math.Round(clamped.Z); this.A = (byte)Math.Round(clamped.W); } /// /// Initializes a new instance of the struct. /// - /// The blue component. - /// The green component. /// The red component. + /// The green component. + /// The blue component. /// The alpha component. - public Color(byte b, byte g, byte r, byte a) + public Color(byte r, byte g, byte b, byte a) : this() { - this.B = b; - this.G = g; this.R = r; + this.G = g; + this.B = b; this.A = a; } @@ -140,31 +140,31 @@ namespace ImageProcessorCore public void PackVector(Vector4 vector) { Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255F; - this.B = (byte)Math.Round(clamped.X); + this.R = (byte)Math.Round(clamped.X); this.G = (byte)Math.Round(clamped.Y); - this.R = (byte)Math.Round(clamped.Z); + this.B = (byte)Math.Round(clamped.Z); this.A = (byte)Math.Round(clamped.W); } /// public void PackBytes(byte x, byte y, byte z, byte w) { - this.B = x; + this.R = x; this.G = y; - this.R = z; + this.B = z; this.A = w; } /// public Vector4 ToVector4() { - return new Vector4(this.B, this.G, this.R, this.A) / 255F; + return new Vector4(this.R, this.G, this.B, this.A) / 255F; } /// public byte[] ToBytes() { - return new[] { this.B, this.G, this.R, this.A }; + return new[] { this.R, this.G, this.B, this.A }; } /// diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id index 9e20e51de1..de448dbb1c 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id +++ b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id @@ -1 +1 @@ -45c6fbb81e384c9f75e75efc01c269f6e2b20006 \ No newline at end of file +09cbf92c0d7d3e4659626b60663612836e1e90e7 \ No newline at end of file From b442c4a49176cc1cf9513ff0ea6ca876c34f86b5 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 28 Jul 2016 11:07:17 +1000 Subject: [PATCH 64/91] Add Kodachrome Former-commit-id: 395bbf802c958615ed5e497675200a9868bd816d Former-commit-id: ccc6ea01f5e2cf203991f4425e88faab7c56ac4c Former-commit-id: 3ff1d2812e905ee76bdbf50c4ccd4c6e0ad62a82 --- src/ImageProcessorCore/Filters/Kodachrome.cs | 58 +++++++++++++++++++ .../Processors/Filters/KodachromeTest.cs | 38 ++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 src/ImageProcessorCore/Filters/Kodachrome.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/KodachromeTest.cs diff --git a/src/ImageProcessorCore/Filters/Kodachrome.cs b/src/ImageProcessorCore/Filters/Kodachrome.cs new file mode 100644 index 0000000000..4ab33e8f75 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Kodachrome.cs @@ -0,0 +1,58 @@ +// +// 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 + { + /// + /// 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) + where T : IPackedVector + where TP : struct + { + return Kodachrome(source, source.Bounds, progressHandler); + } + + /// + /// 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) + where T : IPackedVector + where TP : struct + { + KodachromeProcessor processor = new KodachromeProcessor(); + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} 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 From e2fc7b04d2e470bd65687b3251ca08d3cbf45de0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 28 Jul 2016 11:20:24 +1000 Subject: [PATCH 65/91] Saturation, Sepia Former-commit-id: f0b02bfd300840689af44740b52a9bd5f7e2e49d Former-commit-id: 0883c72b9b8aed6625672c6a495613d4a2fbd9e1 Former-commit-id: 6a570bbd38d20705a3e1c025b083ab0b47b481b8 --- src/ImageProcessorCore/Filters/Kodachrome.cs | 2 +- src/ImageProcessorCore/Filters/Saturation.cs | 60 +++++++++++++++++++ src/ImageProcessorCore/Filters/Sepia.cs | 58 ++++++++++++++++++ .../Processors/Filters/SaturationTest.cs | 47 +++++++++++++++ .../Processors/Filters/SepiaTest.cs | 38 ++++++++++++ 5 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 src/ImageProcessorCore/Filters/Saturation.cs create mode 100644 src/ImageProcessorCore/Filters/Sepia.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/SaturationTest.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/SepiaTest.cs diff --git a/src/ImageProcessorCore/Filters/Kodachrome.cs b/src/ImageProcessorCore/Filters/Kodachrome.cs index 4ab33e8f75..0f4bf2ae83 100644 --- a/src/ImageProcessorCore/Filters/Kodachrome.cs +++ b/src/ImageProcessorCore/Filters/Kodachrome.cs @@ -8,7 +8,7 @@ namespace ImageProcessorCore using Processors; /// - /// Extension methods for the type. + /// Extension methods for the type. /// public static partial class ImageExtensions { diff --git a/src/ImageProcessorCore/Filters/Saturation.cs b/src/ImageProcessorCore/Filters/Saturation.cs new file mode 100644 index 0000000000..3c39d4e5c3 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Saturation.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 + { + /// + /// 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) + where T : IPackedVector + where TP : struct + { + return Saturation(source, amount, source.Bounds, progressHandler); + } + + /// + /// 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) + where T : IPackedVector + where TP : struct + { + SaturationProcessor processor = new SaturationProcessor(amount); + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Sepia.cs b/src/ImageProcessorCore/Filters/Sepia.cs new file mode 100644 index 0000000000..6a007d3b3b --- /dev/null +++ b/src/ImageProcessorCore/Filters/Sepia.cs @@ -0,0 +1,58 @@ +// +// 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 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) + where T : IPackedVector + where TP : struct + { + return Sepia(source, source.Bounds, progressHandler); + } + + /// + /// 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) + where T : IPackedVector + where TP : struct + { + SepiaProcessor processor = new SepiaProcessor(); + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} 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 From a3c1d1302f9fa9ca86f4b4d2dbecc02b25d9f260 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 28 Jul 2016 13:29:01 +1000 Subject: [PATCH 66/91] Hue, Brightness Former-commit-id: 390a36f397e55d0caeaca4d5353be432da1c96b9 Former-commit-id: a802e31bbbfe18164aa7f17b057bcf69d3f495b3 Former-commit-id: 6275da035cee61d2d7abf1da32acc7db5f6aae2d --- src/ImageProcessorCore/Filters/Brightness.cs | 60 +++++++++++++++ src/ImageProcessorCore/Filters/Hue.cs | 60 +++++++++++++++ .../Filters/Processors/BrightnessProcessor.cs | 77 +++++++++++++++++++ src/ImageProcessorCore/Filters/Saturation.cs | 2 +- .../Processors/Filters/BrightnessTest.cs | 47 +++++++++++ .../Processors/Filters/HueTest.cs | 47 +++++++++++ 6 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 src/ImageProcessorCore/Filters/Brightness.cs create mode 100644 src/ImageProcessorCore/Filters/Hue.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/BrightnessTest.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/HueTest.cs diff --git a/src/ImageProcessorCore/Filters/Brightness.cs b/src/ImageProcessorCore/Filters/Brightness.cs new file mode 100644 index 0000000000..fb29ff0d9c --- /dev/null +++ b/src/ImageProcessorCore/Filters/Brightness.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 + { + /// + /// 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) + where T : IPackedVector + where TP : struct + { + return Brightness(source, amount, source.Bounds, progressHandler); + } + + /// + /// 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) + where T : IPackedVector + where TP : struct + { + BrightnessProcessor processor = new BrightnessProcessor(amount); + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Hue.cs b/src/ImageProcessorCore/Filters/Hue.cs new file mode 100644 index 0000000000..a4de7c6102 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Hue.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 + { + /// + /// 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) + where T : IPackedVector + where TP : struct + { + return Hue(source, degrees, source.Bounds, progressHandler); + } + + /// + /// 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) + where T : IPackedVector + where TP : struct + { + HueProcessor processor = new HueProcessor(degrees); + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs b/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs new file mode 100644 index 0000000000..d253c4ebf0 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs @@ -0,0 +1,77 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + using System.Threading.Tasks; + + /// + /// An to change the brightness of an . + /// + /// 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. + /// + /// The new brightness of the image. Must be between -100 and 100. + /// + /// is less than -100 or is greater than 100. + /// + public BrightnessProcessor(int brightness) + { + Guard.MustBeBetweenOrEqualTo(brightness, -100, 100, nameof(brightness)); + this.Value = brightness; + } + + /// + /// Gets the brightness value. + /// + public int Value { get; } + + /// + 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; + int sourceBottom = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => + { + if (y >= sourceY && y < sourceBottom) + { + for (int x = startX; x < endX; x++) + { + // 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.X, transformed.Y, transformed.Z, vector.W); + + T packed = default(T); + packed.PackVector(vector.Compress()); + + targetPixels[x, y] = packed; + } + + this.OnRowProcessed(); + } + }); + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Saturation.cs b/src/ImageProcessorCore/Filters/Saturation.cs index 3c39d4e5c3..cdd2a1bc03 100644 --- a/src/ImageProcessorCore/Filters/Saturation.cs +++ b/src/ImageProcessorCore/Filters/Saturation.cs @@ -1,7 +1,7 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { 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/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 From 6671c58cac58b21d82d489bb6c46814391a13347 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 28 Jul 2016 13:45:19 +1000 Subject: [PATCH 67/91] BlackWhite Former-commit-id: 08361487c9988e874329dee40c21725ae6cc3a90 Former-commit-id: da89003e33f7ab41ee727e787c39d898af0b1e8d Former-commit-id: 7e90879b76b3a162d664e2c5bb0cd782e6dd6e77 --- src/ImageProcessorCore/Filters/BlackWhite.cs | 58 +++++++++++++++++++ .../Processors/Filters/BlackWhiteTest.cs | 38 ++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 src/ImageProcessorCore/Filters/BlackWhite.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/BlackWhiteTest.cs diff --git a/src/ImageProcessorCore/Filters/BlackWhite.cs b/src/ImageProcessorCore/Filters/BlackWhite.cs new file mode 100644 index 0000000000..8799cfe64f --- /dev/null +++ b/src/ImageProcessorCore/Filters/BlackWhite.cs @@ -0,0 +1,58 @@ +// +// 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 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) + where T : IPackedVector + where TP : struct + { + return BlackWhite(source, source.Bounds, progressHandler); + } + + /// + /// 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) + where T : IPackedVector + where TP : struct + { + BlackWhiteProcessor processor = new BlackWhiteProcessor(); + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} 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 From ff30ab089e7f12910eeac899fdc86971df61832c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 28 Jul 2016 13:53:55 +1000 Subject: [PATCH 68/91] Contrast Former-commit-id: 9aae78349c3150f3dff653fe4b065d6c305d3a1a Former-commit-id: 23d52315bcd126733230bd6ed8cc4f9566b6bd9a Former-commit-id: 29dfaaec8e8759096af680fc188541574002d297 --- src/ImageProcessorCore/Filters/Contrast.cs | 60 +++++++++++++++ .../Filters/Processors/ContrastProcessor.cs | 75 +++++++++++++++++++ .../Processors/Filters/ContrastTest.cs | 47 ++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 src/ImageProcessorCore/Filters/Contrast.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/ContrastTest.cs diff --git a/src/ImageProcessorCore/Filters/Contrast.cs b/src/ImageProcessorCore/Filters/Contrast.cs new file mode 100644 index 0000000000..5025daf641 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Contrast.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 + { + /// + /// 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) + where T : IPackedVector + where TP : struct + { + return Contrast(source, amount, source.Bounds, progressHandler); + } + + /// + /// 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) + where T : IPackedVector + where TP : struct + { + ContrastProcessor processor = new ContrastProcessor(amount); + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs new file mode 100644 index 0000000000..01aa128d3a --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs @@ -0,0 +1,75 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + using System.Threading.Tasks; + + /// + /// An to change the contrast of an . + /// + /// 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. + /// + /// The new contrast of the image. Must be between -100 and 100. + /// + /// is less than -100 or is greater than 100. + /// + public ContrastProcessor(int contrast) + { + Guard.MustBeBetweenOrEqualTo(contrast, -100, 100, nameof(contrast)); + this.Value = contrast; + } + + /// + /// Gets the contrast value. + /// + public int Value { get; } + + /// + 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; + int sourceBottom = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + Vector4 contrastVector = new Vector4(contrast, contrast, contrast, 1); + Vector4 shiftVector = new Vector4(.5f, .5f, .5f, 1); + + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => + { + if (y >= sourceY && y < sourceBottom) + { + for (int x = startX; x < endX; x++) + { + 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/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 From 92a328c6291a225824901782b5f6d6b840587dce Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 28 Jul 2016 14:15:17 +1000 Subject: [PATCH 69/91] Move Compand, Use constructor Former-commit-id: 7f66449a76aa60f30d3e9b11f6bd23b51b093625 Former-commit-id: 98673bbcdc3eb7995ef0367ead1d9cd00a669e9c Former-commit-id: cc097cdd6be39a6a44e6eed85388e314904c4ea7 --- .../ColorMatrix/ColorMatrixFilter.cs | 2 +- .../Processors/ColorMatrix/HueProcessor.cs | 28 ++++++++----------- .../ColorMatrix/IColorMatrixFilter.cs | 6 ---- .../ColorMatrix/SaturationProcessor.cs | 10 ++----- .../Image/IImageProcessor.cs | 6 ++++ src/ImageProcessorCore/ImageProcessor.cs | 3 ++ .../Samplers/Processors/IImageSampler.cs | 5 ---- .../Samplers/Processors/ImageSampler.cs | 2 -- 8 files changed, 25 insertions(+), 37 deletions(-) diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs index dc5811133b..3aeb33327f 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs @@ -21,7 +21,7 @@ namespace ImageProcessorCore.Processors 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) diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs index 771abeab50..03701483ec 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs @@ -38,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); @@ -84,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 8e46f56b65..a3fc7d65c5 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs @@ -19,11 +19,5 @@ namespace ImageProcessorCore.Processors /// 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/SaturationProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs index 39e7b0bd46..583cc6eab2 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs @@ -37,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. @@ -74,5 +67,8 @@ namespace ImageProcessorCore.Processors this.matrix = matrix4X4; } + + /// + public override Matrix4x4 Matrix => this.matrix; } } diff --git a/src/ImageProcessorCore/Image/IImageProcessor.cs b/src/ImageProcessorCore/Image/IImageProcessor.cs index cba045963e..fb325e0013 100644 --- a/src/ImageProcessorCore/Image/IImageProcessor.cs +++ b/src/ImageProcessorCore/Image/IImageProcessor.cs @@ -37,6 +37,12 @@ namespace ImageProcessorCore.Processors /// 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 . /// diff --git a/src/ImageProcessorCore/ImageProcessor.cs b/src/ImageProcessorCore/ImageProcessor.cs index 09cc54ff57..6d6796746d 100644 --- a/src/ImageProcessorCore/ImageProcessor.cs +++ b/src/ImageProcessorCore/ImageProcessor.cs @@ -32,6 +32,9 @@ namespace ImageProcessorCore.Processors /// 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) { diff --git a/src/ImageProcessorCore/Samplers/Processors/IImageSampler.cs b/src/ImageProcessorCore/Samplers/Processors/IImageSampler.cs index 59326dee4a..36fe5810fb 100644 --- a/src/ImageProcessorCore/Samplers/Processors/IImageSampler.cs +++ b/src/ImageProcessorCore/Samplers/Processors/IImageSampler.cs @@ -12,10 +12,5 @@ namespace ImageProcessorCore.Processors 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 597fbb6faa..d016f5628a 100644 --- a/src/ImageProcessorCore/Samplers/Processors/ImageSampler.cs +++ b/src/ImageProcessorCore/Samplers/Processors/ImageSampler.cs @@ -13,7 +13,5 @@ namespace ImageProcessorCore.Processors where T : IPackedVector where TP : struct { - /// - public virtual bool Compand { get; set; } = false; } } From 7503976f3a13bf5ce4c8d7fa2f6d00b0bbd70e43 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 28 Jul 2016 17:50:17 +1000 Subject: [PATCH 70/91] Add turtle for testing http://css3.bradshawenterprises.com/filters/ Former-commit-id: 65f2931f38d1b159291a39178ac743d628477483 Former-commit-id: 1cb0efb5f0c944a6f0aed5b191ae692b35f36b0f Former-commit-id: 71c2bbee979c71225b669ae0226c491f23996ede --- tests/ImageProcessorCore.Tests/FileTestBase.cs | 1 + .../TestImages/Formats/Jpg/turtle.jpg | Bin 0 -> 55126 bytes 2 files changed, 1 insertion(+) create mode 100644 tests/ImageProcessorCore.Tests/TestImages/Formats/Jpg/turtle.jpg diff --git a/tests/ImageProcessorCore.Tests/FileTestBase.cs b/tests/ImageProcessorCore.Tests/FileTestBase.cs index 23b75e63a7..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 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 0000000000000000000000000000000000000000..0e931949563621cfdf455620faefd799a1ebefab GIT binary patch literal 55126 zcmb@t1ymeM_b=MR;O@aCxP_p>-DdE?B>{pD5}csH3GNyk2KV5Qgy53kmIMeIoS*>` zg%f|{r+?v+KRmiHc_dF80AS);Sh(2&08Ss0&fsNfi~C!E0r}m}KXf}!6}`W)r-PpC-`L@q>OcI>Pc=0ErdztpBgg))y1sV027hBKYX!x> zvD-7)Km6`qI{(mp?c_E7=7C#4f6w=yHdweJ(SNYDqw+s`T|Bh@X{WQJ#y@?raZ~z- z$J+hh{(9Kys{Ad-!%g=e9vgcl$lr83HNpe4}K)6MHI zd8ofLGyoeQ07wB!fDT{+*a047`-A~;Kn73*pnxWz3m5@U0879Qa01)_Z@?c21|onM zAQ4CdvVnY{7$^s7fJUGd=mOpY1HdRS0n7qlfE8d9*aMD$AHXjV2!sK`1Cf9zLG&ON z5EqCaBm$BGDT35MFpv?*9Apb}26=+~L7|`+P%wCp zRUB0XRS(q?)fF`mH5N4+wH&ntwGVX~brtmp^%e~ejRuViO$-f!W{75s=8YDCmX216 z){NGVHjB22_5&RqogAGF{Smqfx)C}Y-48t$Jr}(Wy&HWJeI5NA0|SE+g9}3vLleUS z!viA%BMYM%;~mBn#wNxkCJrV&CO@VUrZJ{7W(a0FW+moZ%qh$*%wJf9SS(m#SejT? zSUy+@SS45;SmRimSii7|u-ULBv30TG*umJD*tOXG*o)YwI5;@>aYS*naqMw|a58b~ zafWbKajtNQaXE42aZPbOapQ5zaJz9o;hy5*;j!Y$;2Gn2;Kk!r;Pv7y<6YvD;6K2J z;9KFpz<-6`fwoz{G9xj{IB?D1n>lu1YQWV2^&AEt7Q-69?H4NHOpPdi^&JbcPoGulog^C#uSMa^%S!emzC}-*(y~j9VrVb z`zd#;psA>;B&p0pXd#x63dj-k5i|hWr;4kpqnfR{rpBq}rq-d3q7GG0QD4+x(Qwvi z)dVylnkkw~T5MXbTAkYH+FIJLw7P!2VV^IQ8Sy8vqR?#Cd4`b3}u464@ zN8`9tiORXiwaT5!lgX>ir_GPa|5;#Puu!O6*iyt= zlwOQp>{a}&M89MZA%dtVr7Ddny(x1pTPcT?_f|UyKk&twSQ

y1R3+aI^Rm3xJVl6dM}=I4J`FJR<-gnE(K6{J&z}KOCUH;&deb z9k2h1+5dO^S6uvuUl;&Ny8%Ei2ml~50FZ^u*#M+mB-TP6x-tNYfWSWovgCJiw-Mm~ z13);f$j~JH?(P&BCt`2_z|H*K-LKZWyPHyEZ2lPlx_|r?YyalPngoF7$kaX9Yu zPxb#k-F*S%0Wiw%^!p4(CghF&cfv$RM?=TL#Ky+L#KOYHCBVhT!NAM%6$x>$aFEphkLj)(Ai_ijK|Ns5eE@|B1SSIA^#QcV z(L@FRexCX}p#!LB7$7hv3Kp^$MhGAkqaYQdW1wQ7p<^H)$-t;+=tKZ91_>kIeOYZx zQVUlyCjQ{0LUK8nUTVXXz*D#P%z`1wMUzMOvcQyM8)6|CHgdrFWpX^@kN zhK$PoR*1|&NRb#|6ij5P8W8~cqYoVe4GjYoX&N9DA^?oah(^qZE~{-pa^DrH3xkv& z)-XvXr}NY;`8|_B(drTTJ;9JhW+7cm_o-t{mXsd(-s!u~04^AW>?@cEkOl;ASnC2W za<5I^%~l7d7`*cvyXE=1J7bgnHtpQ!$@URF`z6kf*w+?=Zw6y5(vYgJZ?;~Le?NDi zFPZ(meEKfN`)4y{iP-jkc%@Qmepv@v`>(-iH{IVqmnQF&Y)-fX+?v6s*v$03if8dc z^GpZMHMH$f6liuv_Af}jrCh;Vrrw`TLvvW8>jigYPHm(ZV^vp8EaF#2J}SfNMM$#P zt}8!Cgqh9caeg6tnA)lBixVkDsZjFbE&kVWMYYk3h~9=xcS7HA#*f`MD|_i6t$mgA z>CaBi6)NtpG;*%3ZP`*A6E*w1q@T~i>nxdT&t7BuP$y8+$HJdKD*f567Hgc#MRy`r zc9H=(ye~%8@Jy~cq|8b^{ty#?Aaz~2E(=(>S(ovAOTV0WA+FGd7(GaPNRvt3Z*Nr}lhZ z)Somw@MYy!GZh=25A-_K?|tNFqrHknNIPV~zaK~E1t=YsN?DF)>Beufb?-+eYmRd6 zUD&GQjXuFZuMxj&eNpuoe_huhgaZFQiyY^G@+Vhj-);W(_df-f#wIsKv3lcU2bx|{#4cDvTbOJoX*O8a-FI7EB`eE#834n zH{buLf$5FWo6d08DoScJN`Eij>J3zl!u=&gEreATle_`a5 z=R3!`4|O@ve{FB>Un_$Ps47>DTqp-!$)4pt`@(g~wMCr&kKr|a%ko(y{Txb?q?5jC zZDDecqbDsvFi}?tbEll!-h78lmBqZIfNw)edxxUgqZzH#EywJ$w8&1SlV0{FU)Hfz zN88Dlq25Ray7hN*VsYBuq+g;#jWVEabrLTR6GN=u%wkk(em-M5oxF$jAe7j+W;mWs zt(k0)t?}`o?lnr#weq0G<*2N9k-;a&jXR)NGRW_oo$ZwV`K&TQat<$v9?b-{!G0TU zv9}#x>AK#ua44X}rq-C1JZ^}h4AkIfv%9f{8QcN?bf`Ih3V!k3=WBoGjdA=&+Y7B7 zH`ANYDsNe~B|7jXWx|xuSNJ>RV5!{xvrs2r;Jm7R{K!=Y=37QS0a!C}8g{sW<6vk!L_%GwCgK+a+muA*1 zhOumgCII~HagEdaaW2JPi%s39nT-YpLXS}sZPZwxj^&-y-?>GXsa@axOd^`&@ztHD zcBM$-aT0+k1@`)2F#Ska+N;bs%YO6sRKL=L^Y6}Lkx`Ci&?m3ux;}Q%)im{&i0sA_ zoW9BHwldH3=nzJe5sQHryFF{)7X=adL7Ig!m)V3HxL`0MzngVBjV-`Ki7 zm#&ABu;i4UzDqze)v#Xeo_$NWjKxr9!*{U|Wq1_UDO;+1O!C{>S^GcppH$ZAqg!vO zUY<%ryPJNZoseZ<)+xqa`uVI}T(?ikAqu=lb4;KAB=m5mwmUCe#vkLcb^~o^8GbYt zaI*a<`rn@#37QbuMJ)_Xs7)i=?!<$zi(42ZW{`Jf0tcH_5 zUlaC7A|f73m2C*LR!n6TO`EmQ?o-M`JpP8yC;bI~YTZZ@$=eIwJ3y4_4ydlX+VNX_ zcfFBt+xTv)z$fEp7bFyo<4mV=7#RVbWAX3Ss1BLV7P6JA1_ILf@A43KKS;>REkzOW z*5e_8u7>>q{QFDb(hR}#fSFCrZ#q7#+`~eJn9dpcCJblP8%g=&T2q&y*wRX@>WO~O zO<~GwMM5Su^W;U%>}e#){^MW2J`qcq*YA*4KI1o>`2ZV#P=4aRMOn(V(|fPNV|9v% z-St7UPosYh*eG5$^-DZ5sm-K0(%1Keu|C*}VzssBwQMUY-EfUc~=58AQ zhk2>~KYV3F@n62O;HO6GLE2T?X_V%_LI?l#uuArp|C_ITQPTYNKR44N`Q z_kYxL{I_+p)ZYFT5&T~k4KhkdFi1O00G)*X9U@3t&e%JM86V(UQW*|9*{UiK#s4%O zOMl5kPs@#@GsIdNp_Xj2VXPt$MHqJO=w(vb-=$E?EZe91rbY-fO}&4eS*olZ3}e9A zPHbDGk98Ved}q5gMw^-ztCmV{61=i_Jbyet#`Z85(O)lQ)4NL=FZ1OM%as59qmpCz zr#uu+OkeCV5SPY=?KR}&D!2k`4w3e}$-16Nr1=i``LY(Z?mNeJ;EnqwGfik{&2i~S z#CIv~NJA$u`*ai%ttI-+2I#L%3=9@TfM&Gxh@&VefiO$n@^M0)G-En z4-6>#rsj-PO%OIirgXwoAQ!{)(dBXMA>$ZDg>!>qW*b0#d=IZ4^77nSt}luFU() zz9%A54dZnBj8+WGvA6duD&axp;MDX;bi=%&HFcd@I=7Lel7nR_zSSuRtSYY5mAWQ6 zo#+&NvW!P)LcRUEH;hicn{ne>B4MfWkmu2y7Aw;iS=W=>*8VMkTa&XYmc~lzp|U0OfKuh}I7X{dgB&&}Wu{1Gi@U#3{1d)FZhWj8D%x#(xzgWKnUh zoofqWefZoojwcnLn1=epWMM!GqWMj;G{)&ARx5{lt=j^>ZoYd6=8gL-^a?*eA~e6a z1`b;sNiWhx(CxBSuzmByj1G=kr)%Xu*?A8Y{b0~icY;V3P3d9zW--!JZ>h{V&8Y&r zQq`=_KjoP7K|PM1-GylG?i45L=JOafvcI{eip)~s7!Ng|)LS)K3)czdeB2$B#$R$l zb)3Kr4>ToQU@-*GoXuypG{bx&SvjYY1Ha^fuFj=hrYo9HI5y8-&k^S_2OrwP9S(5qsTDlw|P#Ui$`KQILxxmDEr%{<}H`? z1sx)IAHeLz=sGz)v}Y)@Dl9gLmxRwnp}`2zoP>Zne>EvL_SOh}Zp zvGp56)f%z*>=KZ=$63YA@~n{Wm89FwUcynrDU+h$7xG*h3_5$am%F-K;a%6Afud6x zeb{QwiIx?;q2Z`6ZZmG>!uGC%B8-(_}Leb?PKPx+C0cIv9zA(NjybY?o~Ll&a@Qy9;(#tM*k zf#Of5tWnX-AK(ffzPP_Ur@i?f z2Wn6BhZwwr_>&`I@qT;R5W$8&mkH_VuMtI<#>Qg;h&X1SSbGPNJwzNgQ5=sgmEn{u z#nhaOU_%k+K>F)oMDU+HjvJ{0QN6B8D1~A`jv%6hJGlEiGI!85E;`D-U*{EJVp{o) zM|@mBNr|=gwaWM4RISucpIf3;PM9>b6>qmTi@gdunhu_0=-eEqTwctbO=e4K3Fj+% z(#L03kr@8CepeQyyT2*+o$>JquD^WAl}vKQfG%p|n%l;+#sKY|3;RktxT|av6Q?$S z>$80z7!pGE`um|!L$;Px#*|p_$|cFxh3B;8%QJg@jl=2d@9X#mL0OfWP~O6C7$>b^ zX!;yGMmAtD-zrmjtQYJKcx>i#YaHnnbMw*g!PLv`FX9P<1=EkpVKl#-Q3(_XPA4^= zWE(xzt=C<)T1-~OU{3Z-AfuLomr|y@l#y`mHUz-00APx%LN~0eM_FgV=awoa%^Bgt zTf@!vp?u|*R&%q8XBWqbd4)b9-Q$r_8Dmo-A;wbKROMc!hVBRALB^~nEI~(n)H)I7 z3%<~K7M&T%^&~nhPr7qVRL@R$V$m*BJI9LDgof0?AloTbSov3yPiTVMxc#)Pbd}fd zs&w=+cQg*OF)K?@Jf-Ddp9##d&e~+xa%oHH6R8XcuTvDK+Sf0pPF+Ifbk>SUi+uLR znBWFNaO2sryze?W%m?%tX5(6${pD%SyAbB9t*x&n%$_cL`a)i%h$LuiG9j@jAcewD zBTrJ(S9+F~lE!DMTT~atvpD(NfK5EUF|AV>kD;dv)+-4%=h$09P%TxBkm1aqOP?Lf z`9Fm2se$z0CjPH!dnEqFulk$6`*P>6irr^Z123770+$$&5*`K!Ht*4sAmftQl^ye) zafZO*sf~nBVw*Y2FAQ{r-y?Sr^Hgq95JU8h9=3$O9fd9U>G(Nr7=?2_Og}VY*9iV* zQu1ToA(soB_;M;TnIpTq8?hi}DCQOcj>dE(EB}FYOy6z4@8|qlO2JNRMIy2c0%y|b z8kw3Re;qf%On@x|OL#A~s$+CN*|>CE3o66>4)okpFB_v8IBu-~0I6 zBuVHFBDBnF1FpVfRy+0F+f$~qV#XlbUQeaAkmt}))Jai_xB#Z1GmYxA-64(5bx8B} znpL^KpZit@&Z;qEAyC3vpaHOpQ_~&0;cDvdvr5Vv@kd*WMJF02c^p00{tyl|JwM4Q)BO&3l1xfwl>I33T9p6xn*2gEh=UV)2u zY59x1l0Zk&T_O2fW$-mUyyC`OmE? zflE^v0?q?NjyYt*S2BIG^hzy$y=;wYqSUrjbwE>NxBPx<%pJq$7=*)CRv9$wfWMm0 z5PILF^3u8c8Xfs0?*dw?yivsSm^#%@x`b(FNtc+_@57>@#&WtZi0A=n!+NTUE)Hbce7Sa&`B{?u}u3*Hm2^g ze3U<9V#A@=nF_BCmKI}wp}5Tf#eSN|nD2#jlpb_jkvvVTX)6?bROyb2(6i}eECR`lO}a2jZRWGhjCy+x0<+wYuuJcw0oGa zt)y30`@RZl8r^HKMZu}WD27c;-iM+{(!!eZicFSNrXS|*oF?DI&>LZj3!;E)LeXe> zeaJw zmrmNNcrWibCbRa)E9MR`Zi1Dq!P8%}2WL93FE}1j#4|)ZXHHxvK%vT|NZDv6r5~41 zt5kfvLB@xsSw{OAW^f%}WUV_FqV=XbK8!95j_qkQF!qDa6+s|82YbPzGQD~ZVT9R$ zFOzTW8LkxF3Hx_SnM>U;uQ{KwpxnQ|7A_`g&?S>Rf0?C4A20NH*3=#fldnn1v2+=$ z&JYRr?B4FTpC=Z-F9+Lwu||1)Hhjww1x|DMYapE|TS8&Ca1IO_ooQ zi+zuWCfxz&RnqCL0=>7y26K4$*E(G8`;EV%mTvb#rStNu99X6aetb5Xx;pcTjBP9= z1Y3NYt-1-RRhVCzvNG7rBn;%!I6(TIHs)WFGpQk)oU7qHZ2O0f@%e(~38PvhZ5#n| z5cX0&F4|sVCCwprw#OAeMnM&goXG@e#3|P@{uVX*-3Me0o>ScM9M%M@Y^!8HkUsBa znc=*iNWOn7Cr|K_Y)I2B`&i*pw4qDWPaIWEkM>mh_l9D*Nk*Ew0xE`D8?u)qWS?XC zD-$=%(@W^PFfW7gTs-PMHk211u`nlS&)B;u@8^1tF}|y+q;^6!)_oZ1U)%rjsGpI% zTglQ^JHOc|`{-)RokEdbPa^3Tn<|pLJ;reEDep+jO)L7_R}Uhc_Fq2tmj==~pC}hj zZ4i7C6L=~4SnCUhDu#v+_=iRYGmdZQxOrsVzuf$1kETCuviqZb zymCyme7sc2V>8?F69v%<)LXJe*$CtNfROiV6kLm!N12WDF4*y z`jYp;4(bhUC~BNQr?k6`Izmx`TijG4uoO2Qq6mY znGt+KIr+Bv&3y^0w4s2OWbw3ewyRx9Kb=@jQH3dKOz;X;l&Bwl@O12eigXlIniv(I zulJQ+5%y}=G5uv-b?AjHRdNFSi?)VgVK3yI2jzw2qYS?KfiKlSC*v zr)DxWm)0F%Z+0?k^pyX3&4C^#+U%pR>}N5)*5t4fsfZo}j)KG~0?itC9}bnhF!rA| z0`%`WkFt4IFu|MDN>!riU9wq~{riF~n9jWM{{`$A? z=hehm4m`ELR`QXj@JHgC@O0Fpr@@3 zIx-gCyPz7hqdw?wpJ4SwiQ2udr&L4)wR$j$_xE3S;$l9qDa z&sTA3*eF{Xrgu}4mXsZRbC{KdUP=KuiJ#KG%E?tg3N<09ZQ}ID!A0CGroJPYlkU3% zFbCib`^K*?Ue7FoMx?{v@_kc1vElL<)*NE1n`=YdDy$nP4)1@~z)04qsdco+_MF%H zOo*M@oWsK4&3uLT_!?v7G0J62hOgN}-10QCrb;zf8BvQEDKyuRQS?Ddd>C@>WrxK~ z4JgXqbMy^|nxICY-o}dD)^FJg>$k$IaI?@d#nQ_dT-@Ct!l27sa6dhj>C=<-M=S`j z1vDpwd4=>tu{gEiljJ3nP&+FC2i)AQsGpV$RS4avi-Jjt8g@1 zwmL;XnHT2tb3ZHDK8+$@`9o>v;Vinotgd$uY8hQ7BSd=uR+2-rt~KLlBx}Or&WV|- z)0V6BZS8pweaG5tw6D;BOJxO&9R^1oMM?Znv#Mdl6cPnQdNIZ>QUx!jmZY)&0Jl$a z@lt$5e5yUU2uLMps{4uD$lSKxMLbxB7Qv>n1jKnZ&!Gf;SNV}A-^*7G0=7s`zmwHG zhDJ@BM}Kgy<6%{cXPqV&B`8nn>xln0$4uJAYb9+h38|>2D!G@!ex%(BZ8(gOk(v~f z-Q`^Q(W8?WME`T_O@oV~&qYx)2ey~gG8{CL)MjPnuN0D1Nh~M#GcUWM248rf{J5pe zJ`#If|7Mwm2V$>KscIpE<}o5Sa6DgXtUWX|q6;nQ5u-#)Ca*4Tj&k#-$!I5VV=ATV z(t#^JhSOOj@s{XyTZOZ5Cppn1h6N2nU*e|F5(GYO`lT7s>=#|#d&0zlxMygd+P3dC z^nisE)oCfWvytzsq|iu)S+gb%OX3Yvu<#kXnn+7#{#obu5$sVvU8|b6uw1GAxxo63 z0&C^=9Vy1~*z$8u225Wesl5IBX}E|UDs7jfV{Pe(sON((T%K0J%RcQ0aD;3NzxH&M zSCgzzc%t&zjijC~3kySJctA+~bGGP?Y1B?u5~tt8+4uOGto2S8aki6rl3~M1Mv6u% zY-$jd4>lYOxRt2P%)(FWYa63*dmo(cj&@dD%QP0u+2gpXq~LYL-e2>oapXpUDVMWJ z2fh;5qi-(kP#mx)=q*=-dJSS4@Gt?zc4&YvX|gn)Z`a=326dreWq=n+!bfv8IOh|Z znL>GK+YR^ln{%%;sC`+KK;WDA6E<`cv~MS9i}<0HXKNnV)um_~=~~{BR8iYA>iN8f zGll^==8g)^BHx+$^q{%QR$U1PCbxx0V$s!uiG9yr#O|`Z*Y2?rnq~OO?hU!5#gWuu zlVYq8$7VV8T;xKyobWThoeJUPRnaEAUl6+{7bKn*U*W`*=}V98Z|p9WCF1cKmWCQ! zZqVrWD8&2W15b_1;L@sBahmFwa-}@_XpgkMJq}5J^o=nhnjD?gw7dFzMj;XI>3S$R@x{(dfcpKEtuFKWK|Sex#L_uHllQZEMv@Y`l-f_b8kO4r5jC`LNg+VV!ErA@}$-7Z-xV zZL9$NJtjMUurgY>w9Z+{-?DODOsnofm+xxVSNfhvfWFd57bTImhgQHWy z`YXDhSO)j@DUz%|R4o$O@5E>aW~Yj6Kag05el7}Q<75&&IhnO2T&T&x5Q~&^e$2%& zwFOhmc}4{9Y3YTRg+wvU0p_ylfc7eLc+N7IW8+48;mL(y;a3YbNL;)Z?URQlHt#L-85yrObEXXz)%|Ej_VT zF)8F1lKNjSARO0CGy4%7YAE$x8s^j?QCi1n@+0E8H9cG9qy6<2)^dF%sEOWf91v96 zvG3&E%Rn{Fbge+FR|+ZpM^revE$VlQjbDvBjnkVAC!elKQ?Ad~^7~(XLnq#p6P!ul zz{MN9DIvOUQU}M^dR2W1H(QRrG}x$@&U;mTuuQvOX?nbnfs9)-cT*kFSq@mOLT_zO zc2EsYE`5C8qYkyOCQ5w9ba~O^eg!92!_s;;|5PPz{`M-o^{O1ttX|}e<%jM-lL=kF zU%fbT%D?0}CYd=uzB6{@M1#@vR6ZEQQv@R@D-=6jk(I|N&5R>PxMY5my1V1%9 z>{1}<-q1cm!@i~13}b1|uQJxVCB|HEb$~RJp^?V;o z)pY4?_-0mjwp&OnPP7BtFt1=FaOnZu`i5q8|4PfLB-793q74c8gbexH;J&Y(xnSpV zqP!r41u`Uv1XY{`BuqP~=}PTD-=fm9*R)&SMl_7lp9z`?gkQ{Xe+x5Hxv?8(eDNXl z3QqhOy%k6ji_lWuzpN^>n-cP)IHcDmMP)1Eqo8G%uEJ2GIN7Jx}sQHDwPLZI+HN(K7)CbPR z;xk+(3UV9aNy&vj3oW46=#`)Nx71l+-=u!{eRov|q-`z`CVrS2Bnb?>?g)rV=hYU; z@1g$iu1Cl?u07L9lNle*yFtB9j$6w(nLQLmrIxZ+&+wwZ+}>2NQZxx4)dnNU=mC9i z!Nfb>RmA8e+&SOoVTa$DPr@?um7xNe#>~@M@*Vz!4&b!?Gz!aGviA|=?&}K3VS4?- zb;JuvMUjw`kgu>@ts7fX{TC#;PMg<)?Jw}uhm%+{1yfOfAh((r%0A}mKJgvtgh-S8 zRI14{9^`iL{-eyH#cp*{UNqr8sVIp)NX;vK2V)!#8T=aS@JeK{<*8|$4m&?}jAo2x zq^nGJYVHZ^s0D9rc{9nH3y(w--Nz!SJ|{GJ12c+R%(*fGxSkIjN`+RMj^MHbsRB9I z;4c~`=+pvZw-Jljamv~J0Jv90#VfuS| z@DGuWrtmUTJ;I@HHw66h(5;qTk_^I60T35n=l37nT~#zXtcoiR=1n>JjmyZGT=Cb+ z%}BOjyaP)nIy1c^wuw2p)y+*SyZUM}O;K@V%wmw9>(}k(;<7 z40;~O&qp;-#0*S;P_FaI;Mws@P4xxSN&XljZI9Fn+F17lLiH=xs64~zXeYV`lbr0w z?-X+Nvr9O>dPgk4dhHJzb>(3B+{ygDxOv&9$l3n5!ZWSdu=S)>-GettqqxV_x}Nl{ zQQ|HXmfwURBMK7FEkN<~9dO0U^d#P8!O*e^cp$U%h4R4%^G4S$eLXz6Xst3g$_3`i zv9*KaAXVYa>&}6s?{jawbg`W*uqzSKx{=8f@%o5D#iqm}@poON2+y-zJ~HXWZ} zrOtK=Wx1|7IVl1`{FodZ45Uf1gvuIU@$BlKy@FmN-sm3nk|Rm*fzeVk8Fsw=E4X;Cefm>s8i-W*8XFkw!V$AY`}cZYxZJridyKk z?iXIQ4r6OFG%h|)+X;XhJ;>jWvA$);X*R36-^sN^I62&nTq_bLORXQgsxL<9nipCt z29Ntug?O!M6Y8!*=^CX|dUH>7E}b}of~UrASv(B`ow#u3ndnZA*t#Z#NP@MgAa=lm zrBJY6GBd{~l7Q8$*g_DtW6J_`f`5&aTvIObp?js$ZpUHOZu!#F!z#bM$ti8J^v|2h z(J49UUxs^Liok}40sPR?k{^EHimKcP^}&;9k_(-*)U9!?hcEG3K7XbWP7kCYt<6<2 zCtiPG@l?;IWz`*#$IxrRfEomxP)!Q^bDD4@Ntw8QF^RJ0G;U~zi$Ikt*y^X3&3ff# zMw59Cp6hb&+33Ji6ibFGE9DBXYQ&n*n1v&HCLj>%)Ovpg8e3y>(O5Q&Vm)~@-rbV) z4^>9r+lP5E@LTs@+=cksN!TTD{jyf zgq=vNfxZcET0eoW$24%vtJ~X^Ok^WEC@oS81lN+kR2tcH8LL)Olj?O=>gG6&G&6c0 zv-XE7=#rVv<0S1|(7GL(t00&oN_8!}BEO87E)DtDh<_|%cmzn+Qs!S%YRs$J__^|!Qo`MgR>bhta7;k9j??{;UrDBM zqdE}Fo(au|Kyq<12kBS`gC0N%&YGQ+)>5n2QH~#!B1+COpU7X zjc63}Ib_zsY}NV>kdE|tb%>Yt)@KBSToHWV%r|D&YCLf!v)X$nn8FnaZd(x3GGh%#b&30lI1TI7Ooz7Kxe4 zM2z}8o}^1qEv5~amasT>&7f7BRkuI`>q-q0V{>r9d2jIZUA>kSU%zl!9%(-4Q+WeF z`mj6L*%fr!zgT{_)M%D~Ijvrolx{XH|(l zo=<4v(bye~92AShh-8-{wAB}%g>MqHIz)aPP-e${=72_#o#l45RU_a|09};Ua&^^2 zviLg8FmFPmPNBqpG9zG@NzP;CrsO0#5T_wC5Ui)iZ^WrE0VDQ+63l+>`|1L~zi}Fp z_mAHHdc)mbL9*=;p+q;5I8&jC_a!UVAT^BX=}bE+DFREPzL}=fJ0x({{eUa|RWH>A z?#A>sKZTbrN9rYqvxpVm;=oAmgCR*8F5_gdR@6T^EnSox^4;(eD%R;SXjQ14Vt<13Ykk{3 z0Hqe7l52^@R&YuA#`L*n$HHB7XFoll@Z&|aFSH+$N5z7zx4qz5PtzaI$bN7xc7GZ$ zAxQh}B0g%(pqcEeQt;Ww+Jm{nhTnEjCsTWouTBu_Bn39+fI${mawe|z> zlUHs-tF46lT*jR$H-Bkds#sK1Qou8@{1_@!W{Tov4S(s4V@ zIF(ZY1R-X<&+SoTaV9oh3DOWneg%OA#mC`bj*Zi~p#6E^dm8!d>qSwDX$5}-$5FYu zC|*%IaY!~wZ4RB>h)I}<>qvsyf) z4!Y+?ui9UJV>48}SW)CMONOb3g&FYUz&B;T>IHX6`kEN2sa< z2*-p)R-L=)RD+>q?*aZnTln+DhHuHBU(I9R_Q>Cngm8Z|DjkeY@bdXaNDK++@%i;D zL;fX(Z`AFi(U|A7zg{QPV?Auql1WAEJujSR_x)#uOEh1WPLH4{2YMtCQv0-P>;B)k2 znoBIl5{~+{U`%W+N|BTEc`40z>@7?DFK2ogg+A*+Rg(+&O}w9K?M{URNvLQ;^beNl z-Viw1^sS+#^s({A5JaB~>3nY%JTB5>>L}lTuVXG;I^w$k5jlHPT!APrgC^V0 z>Az~f{{d>q$0BC)lqZv--B;`r#}^{SmpTUA++?n&MXJ;({Zj~%|#n@2HP_Xb6^!q-1T>sLcVnp+k@ofnD*F)@}F zt;T+w5z&&331(u?MN7k~2ve&Zrlc$?q3kXu!#P!pMW=*t4(n^O8Ua;s5=)5e;+LjH zaW$B0TnjaJnnkB*c(g~mCgdTpzD@=8Y*h&2S))wQ6WpVds$NxMp(^9r(khnLugUsh zq%Gq@*49n7eK0IGPSIUITs%$X4h~oyWs43z@{PsxLb;UKrPf}IoU$Xy3iJ68s%_iM zVi~I8*ocm`y){}tKw!?VIfP#}4xC9g8);qoxi0vdd2O_B3AW!$WutLICw}Pkb;RpoQU^?-xkO zZ}{yD@~+l-d=_tIabszkbj5>U`})=7aAewVH>PYLk_q#(6LxobsR!9xyzz6R*97<> zsmTaBd2fCQfVUA$aMVRtOjVs0ImIM691Y<6YXab8Z$Qmn{oL(KL-CBIL4J&8^6n=K z4P99;71`d}hrPjy1FzVy4VfA~lcL8l9D5zTl*u?GNhYwR;weXNy{06Ln%)5e)NNex zWSwICQ`25GV#zNjlbU%7BSkCm7yw;gTteW4#aOz4d#Fgx<9w7uWq7!ITkPtLYFLu? z05@MHq{x|9m8+UW*boy0NpF;arc#tgKscLcpC&<@6c6tjNtqz%vJpaOieXSE((Gjret%I!~9&IQ0;&4gJlWnVu zXltQ1{M2QZZoIEcH3dg zkBP`QhAX=t7=-&HQBdPLWqXJH#n`60_l7gS zcm){6*So${oXdqoZkBF22v=c=!u1>OkbmC2N=-OdwrDiriou;3lDz{;8zn+w0<}ri zYKfMQqu7JVp~}t*1(08<7^gGbol4t5*rg_(}tH4N@wG} zI7xi_VOUBbfjzeT%mld&&^KG$${Cr7F(zs>gSq9HXe8aZ&6AIOy#;Y(w-#ZVaj2k+ zp@Kr0mF#EoR_(BFeZ#WMKc&nVuzn8hy6vD^sOuM*Ie&yO)-x{6f?RV#-!if?>`Id9 zmY$ts8LUB5jyQ?NuHQwZ*SYtYP$UP&B@68Eechd!$+*Z3^?NPNeh0i|deKQ^yPR>A zaFYbxzqEI}bZ{PzNFR@=OCZBdswe2`H|log%OMl!Wzu{Xp=J;FUeoCY3oZXf66+jhuy zXIlfDQ=TNS%vJ7rn!e|_KtGot`?>vGga$$@+=h<+P>d`7458~(Zof5MT%zL2>4o4U zdRo9yJUefYn0h93Fe$%U}E(f)5*f_b*PF7Cb3X_Wo%L${)(pQYz zM|kTUE>n-#)EycX&C-?)DComRs4e085xtP@u(H6J__sh_nbtkp`TgNR<4x9?@-L4b z;Z`QD@yQj>PHb5B+BeZC$DZ05Pu7!>meeqzS)rj6md6uU&a6D5o9@MGRkvy8uhUYe zs>#IbMGPjr_QM9L>QuS2U@ql`iD8(s(RrCrY7ji$Bf;VyQDG47h*!GaKU#P8vNll- z_YF%8)5O6>lc~p-ZS$g3CBJ90jM-WHfZbSsW8OX{-9|Hh*Eoqvn0zB(h!dKQMMpAW z<&#wF(kZP*Kp3tZY2e%c-t-muV@|=yASc-q-6_&jt1>@7U4-D82C$EhAHDxd&s%b- zkMiN}1MC^fDO~Pyk%D*6T`Jg-|5r{6QEJ^Jf6!SYMe3#G68_vognl2HlUm?121o@axXZDkFT6 z^@JL6W81=G0Bf66;v6kzdZaM*jDM~XXy72|d;8{-EN$Yg?zP*~sB$#~8HWKjOy=Im zs%)syn8Wus>s6MfWL%BhREQ_vga!+{ZSo?oUcxAynqmu3pUXwoQ(xMzzhbXhZk%jB-GLPOrtPfaFr%NL?@`OjOXzCM5S z5sw?SghRHA_i2Qe=fN_$1Em<|V6xRAMI_proPHL1){z0a1FdA)yIULm>1Psz~qBgLLWA6$Dh! z@9*-T`*82fc{p?CoSE~q_g;Ig&)P5R4D)jsH!;tT>VL#-*UZbp+R8*p%&BQE$x{%| zXP-|KSTB!#ye-iqEm^|Eq_@yH!w}N;j5SNslD>IGkQ(8Zx)_3LAJxuE5& z6n^4Ep5(;VHXk(935(o9C#d9#q?fggFVHC~bZT@Xj$bYH47Eg2m@A0nrl zVOjZO8LMeIEZQuU%}opCY z$BGFHVvKzFS9f*`Qh4?LpbK*zeh;UwO4?Ffz73agJ=FGT&AQ)oB66qWbXj{UTbZFS z>mz((A3Wa;=tsJSNnI3u=X>(b*;z!h&CjCw=u{2qJWFqoP5oDEJraGMOOQ{ewTC zt98fCjhl%Hy9kN#LddaUaXI`-m(=RD-H8;!(LNS5)hab}ks$t3hM~LFcte%&lI)<^ z;eN_-6NsX61v!VXaZlmj49`1aYLpeV@PQeeoVG8S?0r995y@Mb!d} zYQ8F13DKeXa5VV96Sq!6-LqRy{UahZM#rpZE59GHdpigWxJRdf9+c5K1T3F&aASAG^?DEj5q6H;?GzJ6xS+V)WYw{Tu@6vstpaE z%sZA|nfo6=3-POh7=l7pdAC6Z&>(&??=h2&haToUJM_!@ADBm?yc7A44RC`RHLjnm z@nGD{NQ%;>sIMioX*yacf5^)pe2zj)OMYD=+5 zPQZYd)2&9m)0TAp-^O5rXXEE$q*2WR&%$nBwQ9nMnxC*AUgE4xpN#!&4nZV%c799VG1+ zvlqmKTK)Da@5Hj;lzQi*UIy29ylunUyseOq<=a*o0WSkgQJoL-MO{hg8#()ZOnal= zO&uPw$cQCKM6NMU7iTAkkKEADu;{(U>>HxIX zS4dgvr}hDtiWn3j0s87=qX`WPpOPgR$Xx}uyQ*fuat<>CJK5{>?WhBMT~jq+c)BOv zkbL?`tWr}YMZkkKt8dGB45z4x_uEH z{tz4Q4%>n~a2%c}Ph`$ovh7RHMnhHB(zW9A?O_~GUcq8MLw-7eNq)(Sol18qU5HNd z$b7roPsp>}8K-;9$~fs;Ii%?=#U@%YA^V_KjM})eMp~$B^_k5-d*+8(n;xIoX`q`t zY4x>jO7tOD>?9inpAZ!tc%r?ISBzSvrM_hvENj7E5Pc}0BE{V(Ho`MJ5`}2 z*VR6crG$gqygt%=__^t0gi$=kW&XqTZhR=x$>F{TOfp;gDNgm02c+v`! z3>|tOP|S$xJu>AHB%7d+I6g^{d}#*^avU}H$TC9sXk9|@=*$#k<#`mdNgf?pnN;Pe z@enwm?O@Yjrcp97>d_bLLdHEuR+EI@9~0g{^0DPLyT`ltGK^8;HRI#o-OM)()qKs?5*!vlGr zJ)hJ1?0h<0wZ61!;VCr?Y>^(N6Kpiyn0T(MBCEnhvM!%a!WFO(XgPn`K!M~69O!iMMx6n>C%cj~UA2qDR>Y7rk84hw=sSHPh>Xk`a^UKKg~fwZD8UyenFG zm)DUFr)?A~7yD`9-qxgr0iM|6wvFWV~J>a}(+r6CE!ctc0ulqf_ z0R=Q~q`3>M=yCs%8t#@nxR0%b@iHDw&o^Bs`q_P=usN)0kqxkDXXB*HD*aZKNLe@j z#nhjh#mbnS?sIZGJ;~PE-1$OSiaX)+oM1U(uo%Ity7H6ZzZu-|DX7I9MLq~o&G;_J z^1|NjkLBb3V@9grMh5#XSLDzVNX0Nam#4G#Yg5PU=Z z?r+>J8^-c@uG(Fx<Bks%$s(qr;jVhQ*=bg192q*vH0dCCmpBOj&b|4dBdD1=(Sl2*>03V(>Q zXAfJFoqoKUDEdsEuH48`rjQw~)eX{xbP8u_|428~Q!8ukL$!q2msSA%DK}nBAx=)@ z`V%7Eb<4#A-JgEUTJdq5SGByZDW9#52K7DLE>fx@y-fn}0EdRWa5cGl)=G zwR%|@`a@o8{%vxg-~FX!5bTQ{gnTe2`bL8s7`c0X839OYLS94jjjlYmK9e3f&ES{Q z&a%R3qo&8(EGH$EgP*34c{PKxegM-GWe29b$$5p%S8N z+cyuxMX+?O3k+z+v~hXmU|6HULbN~ub?PqJgeOaZf19H#87$^4=C!L8GXINCV213f z@6pe+gFQ5aeiSJ*bCrR35Q>EFsbW8t&o@7p>SN5Dc=d^Z<|TxQfh+gvU+Ax^H^b&W zLLQ9R6=*4Ij7unCA{6;Y0w`7jG>JhxdA|532{O zzZ}-)B^Oe}MMr{oNh@5IH~3hl#E%<~Pd4C~B9O{-)qs`M+=Ef8&eTF+s} z=>~gp1Eo0_8+5)WAhnF7DsglZ%bwOh2JBEbKg-lhU^W34%>Zf$5qCj6o@1FG$w@NW zu93g$L33i`cA!CBc#gy?D4}(RA+nGreNQVw$A~B53(ndan&97fE$%0{_UC+)@iDxt zY@B5D>m%Np71PtLyHf@Ywy=`&V&e)qLkn~(PmZvckk5f$M|k-(I%7tM2AZfmv{T@J z@y_A>QS2|rPcjM6B5Uk2XP#oktSi+odnP@?N)i-d6>kL*R1HRwqq^gYzr0+KwRVGf z#`xS>ozTS+*v9?Jwv>HnjEr5^`hDL61A15G8CBRR-ph5=+)KRwxmB!6qutU4x)`3I zNGSR25uNV=7KEA}ZG2x=&1_MtjfU${gDt#0`qn+xHaRt@Al9xvoV8&kl~TyTz>I;! zwKk&ad(#k`_Zpi=u#!(wPfzQFgS~~<8P^}ca?W^89yuzT5FWA12CH?oMR>27ysnu$ z+)ATQGYlL*-xT!AyysnNF6Z)~&5JEi(OzxL z>K}(9UOofy3i8T=K$6*whx0UA=I)$L*!xtu!|UQ`O`0=BX(48 z#{+UwLV6L;2D6=9!S#oqtSWJn=~QD8whHBSuLSVEcu< zbLF{To+N41vWzzicn|&bJ-OS-{j~Zcmw$rcyVX(4g+`-gRapDS&TGc}3xPb~nKauO zqHY#hAeK7bX>4p~*j-=k545My83owvY7qh)e-M_po;9a^?Ad(A!jI5bxhrP3Ek*dk zBG<*!u@XV?P|#Gev;a@E0xVUlQRlRy{jfE{bbcV4lxv?YYDIp%p)R|R zk2UaoMme>=+UiQH*_rj2R_DfUOWkTOhF!SkDO*{Wfo)kC8rx3J*bsiNVcPK5Md*v@ z>Q$LtKB%WDWmy#HIY_F7sSU71fpz8;;#Izo zrVuNY`DBJ{K!myE@&R#(ySVgnI0h?g@R!jqvqh_T>@EA@B)5Jo-)5yz)jZzKUnn#& zZ|=vc<%eK`q&NYss5nCe+>7Gv{o*3h(lg z-;M$vUr@fx>sLLF07kQRzt_<%`sJ2PU2kgtg%4Urs9GxvufBB`UFNIV*6+(bHcT;a zOgK9{F3F0fj!7A@?6K6LhN3Xejaskx6!!UsI`IUhT~F<|m5;aHM(_z&FQa9#!;V9# z;;W7uvgas9Z<-OVkb{$xezy+RY_+!y+#f(M+_gKBY>D;94l>_FX#=VqX;#R^kvmTYE<8t&{`h65HnR7%(KkvQm!|M4?}Y$Cd*i@E%dLWlp(|ncqFBS zoFQ9BUv}>#H|+Q|r)XWACo&YunV(hFIDy-hM~H#lR(&^U%#?ir zWOK&Zw7jRcrYI_~Rqrm-3W+DvE%WzjbZMEVRMNd367H*8ASUbW#8g1^NEMJG*U4Ab z0p3=J#wv`iZ)8ntO)1pQb;-I~dE9p(@tVwS$wJaG{u9G1qS(AKI}G(7Yw0@;{cwSU zi!*qcUhN3ujD|Q4t6bNCn5d7ftTUmSyLa+AdJhX0zu4$(ItXh;6GOSz!ihtDYN>s= zRr;HLb!G-$-Is5897sb_3zeW!Q_g0BtI$@Vg*C3YN=gq^Ytrg(GMqt#NlV}+^i6CFBXvZnB*-cC6i?xm=DK!d#}Q*f^mpFVRrtj58KTwbf0V_ERX zQN$488X7L@soQo4wJfi!L_J8;ay+<8F0abjmwxs-;;Y>PhT#PX7g~i-GbyrkR zJYL7qR4)cjM7KWIm43W|W&S|R+v?c1MI)YV1*Y5WfiS3(;mg-Pf~OJ4%i7Uc5R zB)O>h97C{>=p|SBA#+3I^WI^#;{P7Nx9XCb1m~RF{>himFCD0&5U`&dnnR)Aid=_3 zt#Nrvts98p<++oKk}Cv0j^o}v_@*6^I&W%HG6Y$9!lU|%Fz~^qcU3lk))V7nBpQMx zs?=GZnOjyhltYEYl%3bvenYMFVhdoN)nLj#EMsA;|LCF8r{GsXN|465M{4OT-#Z$> zQh$y|J<8KrL6zF7OR#vyOWr+ ztfoPkD}E1bElDlI`FnYTOC$G2I;L#+F2qS6QfT4$8x)h9H@e|bHLUARG!r@Hm~G1q zu!cuV_^QVLgnqF71{#4_Ev^s2T7Aq(bhJ;}c92LGWdHe$F_M-FrVh?Na`}~wdgsc- z0d~y%DP&eF#iUU?-XR3?0Dq2KmF9WhmW ztm(xt^zahAhkc3l!RgYJDMx}{Rj=zzL0`7Qud0dO5gHo3=2#*a^gUPH#C9E16%tbM z>|$uIwodv>smYQ<^Ga>?j`$W0X53tx+VGYOxaj?HI1O)BnzECe#W4;`2u;GaXjWFX zq@nY!8OZdPxN)7`=LKbDn>tE;s5fX7X~a2?>so{?wYf7>AKARwcse9&sZ4(OWSa_3#80;-;OHKrThBn3O+dm^5d_1*MV(c zv#>!nyOWNyhYCf(_U^ByQYi53Lp9PxEv-Bk>az~D8oE_yfOM{dl%?T9uhj1N9}6!Y zkK=g3%eOxjLbv_5<>yV5|ES0r^2m{yJJmA(M8Z>m((_O4DLGTr%)=p--$(mv4OFVF zt}}rdjq)LnL@xgaPzt-de`WkqS{~<(idcs;^k}HfqI<+Uf;lGo-U%5_a^K3kTg}{h z%$;XNSx!LA#Q7VS+Fr~q2K>Lma3+sFQR@Hqj+9eWNeTx;c~<{H&CPT6F-u`12n-eS zf7+1SPUWrJK^j+MOZ5go+S^=_FSq+&qsI_ojBMgdMSYypamc-@ z1KFOQo(tlA7nwvmHdj|#8QcX^wR_;8PA*vbccS$_bb*55J|9iCdl*%8$$L>M2&@&E zOKyKQS!NwQeiA1n(uia9WlJr*JYQJyvy5H3HG?A3Z@DuiHJ$3lLn;`QQ@NMlS=H~H z_@JU<&$!EtTGc2W9ZL!#3@NEg+iw(zQi?hBl|XxYmE^gXEqU#;8n7uIE7(>mzu|E? zep9K2nofVh{%c9iYFBb*?>jojfbTe;O$C0u=KR~4{{fy@>kP+FygQ!5a33m7R=u8_ zH{N_A!ueBart^$zEn6}ACBc5?3e@Z&o0_QAU0iXK&|v0~Inhrwj}@jx1*R=h)~&zW z{e5UmNM9y3N{^-wC;8iQR1$jmVq9-`o-9~R9URuQQ{a3L*{ z+%$|k{#815PqEP;M)N1z>%0F0?CJuMuZR9HFDIul4TS>3ur9tI*`-HGu1zeWJLy#e zMe{F)z5Tv=^Dw@NCwCo7%r~}56f!d+^qT_2`Q+535l*JUuU z%Il1)_>~4vE|kPO-P62H$jDiSoPzKr>~|-y;S}>G0Mmi^*++= z)3WvlYaQAEb4V~)v=~g5Jy$|++3>HTc5%|@@#l^M8KObzua1S9_$oTs7H>Vq&z?}m zmM`r7Q$_?>Q4Kg$o@tqJoAF|&8%J&=d}nkjwNIcCw4^%36x-w3GGjpY6hKmG2^IV* z(O$O{eq^+TXm+xs8-pqEt<^Ml6*EH%kJnT+Ay9?{2vkKykTu!NaVAzj7iRDj0<&7( zQlQ`NRBIbbXyourehcrC{}==DcyEyI^X`q;BP-*N;;`eD*K%g>;l&6>2j%yAkZfI~ z6(>f%KXLt!d!Xr{AaEleSUR$Qbr4|`@!VwCamo&)s!WqhZ=eU>f$6m=Wig+ z6fe1-M5yJ5$AsUA+laZ5qcoX7pDNAZ`H@A%8Etea#+-re-`S9U7QX#$n>}5{wK>ha z)1zWik>vS>TYfrwbd^`a_cLH<-1FMD& zg`8YnVd>@(ZS^9d@n@+mPiMe<7Gee)vdjObp`Y{q%{dHJEM~jG+nCuQXa}{sIN9@p83`7B)uV@;f1K5A%{|m&=+g&9h2-gvJ>$JBPrC0 zRYyJyPqMsYItr_`u5$L6OVg=!$k?Yq$63#GWJ|1_bp*3JEJF4T5>LDh1zdJYfS& zvSyA)RD?a`isdP16q9Rmu^3UF{IP|m=3#0kTXe(8zUx;=t?8>u%pAP`(y84+H%+|9 z(FHOU`ZWU8{n`!DfA)dW`v(4JW%cd_^~7!KsYa>EASGbqrGm!_Yh5 zMv!*U7~ocjI9s-k2klcX1q9;AuNo?`)~GL4XsT)=;|fe-YXBQP-WyxJ(_lEYX>uC~ z&nccqLS0s86AXb`x_JLG|MloL)Eg~1at$X|kbk$G+^_W|o2==LO zVsbzc8TH6FJjyh^L=!?qL5}h@^zO+fm1b))E^1swE{!ukCe7xsr3ZU*rq#ITZA zRWAFbUMI9|jvzYz$rKpI9$hsDo7@NT-~G9m+6-*#Qx*u^Ejm%^2snH?Wo2dDG!xPk{}w9)i`8E-;#(-~`43Fw z*lFUx>$)c$1CJ3Z4+Z=vY=w^6)#jktl(bxBmeNN?&*(i&67>$Oa9k4~m6#s#extfg z@jn0`Cg1(2JdbJ6F8LyxAyXhVh@n~q;~xxw24x73pIds<{SNeSq(0bOE&Zy(>9kd_ z#+)H}K&bgcQvj|!w^z4>?pKwoHxDO{!BN4s5RXnVJ_j$YTjT>GSGK;J{SK=;ahnj9T|AI~I^?pUk5K2(WqclJY z#t`e-wxoeEEHnjCBrm$yeCTWc^tOUv%(1UrH8@Rt5dyFG)>>ecNK@SRtU2E8pYzZO zskx(u308puClKG@*K0vS<}_3Bq=rzypFrZP{{e0bF+)lnEbI3=c}&cB+n;oowAAQ> zh$59*-GM;ziyq)LlGZUWHrDPCPLsoO!w(jLsDv4EoU*&{EhX4o9I4m6(sW`YZwq+3 zv>4ZCOO6JqNyxYz-G;wb{Y^}?OiY+x;wTk6MRYsM zcSh336$v^9IHtDJq zl%F2>OG!x@IwTRClgjMqz{9#bR=hx>=o3`oJuhAzfQW-l zW_~_EjNk0&8N$+WP+lZ1Zh?1{#vz+FQPren7o-s4`{Tp#!?i-ui$iyX672Pp$5nb* zIH!k!G`pqTe^R)2B5gB^E8zhvZ58xw2j%m+sDsjJ>vH2l4@UeX@4;v3>}9doBwJUg zHk4_FN5GY`_Gy7J_`q0`jE8Rm;4MNMpfaX}m80(dC8C*~><$9v>)F5O(;G$2!z(qE zd3DT3nz4RNIgBIV&*Bl@G-~mD{!U`5c)RNuaYBd)cVJS-;NP{PI_#>bfVTHs9I^ zS6MU$=TN^1^Im>-@h0~?ea+-lcH*lFqJR-ze^n?0Oy#;z_B;|(k7GZx7lb4^RM6XS z$s`GT}aC67qPbNBk_xedoa>^RK_weOA zpJRri8!ltGSs7!Z39ql0AE#q#%BPfD#~UM~B%F@+YW9KViV%45+f+27NH{1ik%5l^izo-y?F#t0{1 z1o*~~8uj?-3i?*=kgW=-Sfiu&9LBW!s+i3OQ+5V`$t0JPIYCaQPrq~h=Kc*zQ#uSL zaBDIjz1(V%LQ%_(rn4CpuX^wo6OD(3mk_`b>%f-gMtoHL#Dgjw)d^>g;8vq+ROrR3 zs?U6Nu(OGYS8BG$xEt)Gqs0ko#%_n3YxkLM>vOB~;|SK@Kv`Li$E&si+hF?=uZFza ze>W$<&$Bb+Zs#%b7)h!ei`HA39mmnc!TqR7(p7J4IoRRGHf5GHLAzlduyHhH#?|U( zR=mb`L342*vXa#YuNhq~YrDgSVc=t-;@1_}!)?d>j%p%<9FH^8d~bMqHt z*ur-fh1je?Bq(Gtzpou-HL_dEEu|P0BUS$tT|r89&TrMtCm8sJ;^qB#Nxhxe7iQue z@XP_hP+;UFiA3}h$^ilgt0uo5&2F(}%7F}6sbs1&+&-GWSvF`Gyl_N+=WE9kzDGH5 zN^qqo$cm)^ck8VfH*f3)(~!G$_q%!>PUnNT8I=J0ce;bVKBKevvi#lCZasw^$F8o0 zAbWqBv996@F**&55q_~gdQ=d`0*R`m$2EnEgl)h0R{l1X83dg~mp>)ZHm2xxXlZV# zvAJ$G4|}p{!t&I`=`%TXyz!Vj)EXH1jE#3_i~r^SiNCkc}N^mprb1c^r}V54B(q0;0X z2GOtQ)0atoV~yBWSk2r^H4nz-`ZBKZpNERE9ZU)JV@9(&ZU7*!tKe{dZKah)Dmu-y z%*^!>z}{eSEtB?fD`U3paoJz(-Fhg|N-uc^yuDeV zL4s*|hfMp`Qim)5&AATk227$Uqd_J5CW4`?>Z^v?9!CT-Zz$uw@V) z_@T!(@1Z5Jg&O!*>M}2cDMl%#(2)05tShLwcqka<-wj!CPCiM|cN;AYcGm`&wrNBg9aNcCp^oDW_m)#Kgc3A!+p7S z-VfEp>Dn%=-qxyb4|r-5>kn!S6z|(S7fQkU7)2g+c6d;8w34j~+Ve!-5;W}ZQq@Rb zE~r;fA1oFgw#eeWEj2zH-NuN-RH0^g*n9Q8Hr>Va<@y)qvuN0eiO7Hx2bu3?|5R2VB>+7x@InAytB7XCX;_BN1>{g(>njS-Ao>NAP9p&#ay zZak^_6o2kfm9*|?YK>{HXMmQPwm`Hm>AxYo6yf1=m@j=U>iL1IBHH#ApY^Q-bWiwwZ29Q-; z?XKTI`Z3}FDy#8bS#1gp2l{{XZb#XP*>{iLt}T3C3RFNO=xWGU>Tt8=c;IxYj6@0O zV#g<Ra0O)LZLpt@O!d;ophb0TYllnsnco{MDMSmZPKbBcm_G!^dzG7Cs z0m?M}GsXyXszPCGS@H9P?e8*vQgrkB4HR12dw_d)`ul_ygoh`_H2QTwlnLP;rulAaR0zbKL<;m z#H>r=tpi;2_6=71Pxk4xVQ+}W5a-<~+?U-sF0*teHC-6xi_P78XOya+p)7-%w#6@) zRcf?Cq;1-^e7GfIBg^0O{A(FiHbY!cDgnOe|LBipOPTSU!NXjQYV(lOdFVtFOKJ}! z+V^(pavnPgwN!xSGghNa=~bMSH;IK@!$-e{Y4+g2d4U&(rE}ZL$)6t!S*Ke#*3Tk@ z%=BYC>*V^-%p^Sl&&VosLB6}yq^+IzFkc%MIs81Px8m4JG15<+<*0bVsg5MbDLW|v zA1o0)m+~u(YmH9p#(0W6I_lb~tTF(Xov=?9!cl=C#-Xt+wY@)g-=~T){>vb3IHzMGao}OrWa+w>A8KZS9ve*k`KmlXzHIAA6oOMr;%e^TZKjmK=HF=# zrW|SkX@Pr9rJp>neffY%<88K}&A_3ELDn<=Wc67-eQQgnsV4$2EaKnPEs$BYv_D`d zS-TpnQHvP3vuXDc?(_+f($E;$u<1z1D&C;ALgl}TPg%ugTfeXUTLFJmH&XaknwivESIc_{_!f%{$42I5zPatv8%0t^+DQURn};G~ z+gQPn&k+#~#X8Cr^tiR&UmJh>J1lyB9G73Y$*X71{2$^!CWEhEIC-lo|@u%G8m#8c`{*kfV#*oON=a*o-v{t~gW_9|lB zb}1*?H<#2EnXM`J#s9kcc2 ztH$8PW&s+n-WD})B~^oz<8A>%T<4umu0*Ly6i~^hoEa-9#a9L%YT)I;dV+Hcw9x`mZ1(3BK%P@ZAC|S9{5JFd{VT=-zLf4l%;r^MN5lrFFYM&USx`R+ zXo}5;WCa0A!>NWUf2Z=t5n=wm3w|+9^)l2nbp2Lc>(na@JfpN@fwhVqi5o=&5Nu-!b{xux%31~A zgc=NP*OFdbrOEh-D)4r=6D(2btBKLgARRO5?wplqEX3>hs^=)>k&$TN!NLjg4%XDy zu9ik`klMDn6#dA2SB4US7T^*5wd_}8BClVa1oqc3Db$cp?RXR2y&N8*c2|-xvuabR z%MlU|82x8$GWfd27y|~P%2IxLTkDU+eyslXAzQ%h@g^+9Uq?BMS%sW#>Fty_+nWJd zUzPDPs=33X601gjZP!t#Ip!2z?pW#)uxE-%xOM+M3Pu6nY>k$cjexq||Ah&~y+R7% zbzx2K(JfuY(eis6^#_wISnzx`bI9umuFF?)K4YCoXr^lUC-2c+FuGNje|4h~CDYih z%h_B;;i$rqJzu~!xan~u7AMi!aU$hpxl_z2Ix`L>@y8v3s_!FK!o5*wlzh|*6ov%a zHlVK7`hf)%?oCgU%j+HlVV>!N2;an9o0 z_T;C_H1XVt$EEx93?4L6y;YjohS$%-*zHWoE|!kn&4He(iH2GvkO;~F#jX}h!>4}(@)9dv8%1VZMQZ6q>YRomyEE0%gKYVgGhY+f{e%(J z>drZHZj08M^xh~<);Xa6$V1|d&vd2mTd1WI@-W#@b$jer^$^^TIFywJ3Ir>SJ~uzo zw{|D;5^rhtx2k+v+D`e8pb%c{Tb|{2Z3rPFN+q2?NDjuHFWNEsIC5Sgo~Wv)kth+h zg4=_jlGgh|)Ux9ZG9niIZP1J?*>u)wd?ukkuUla}`fo~6vB1&0F@toi6MV0Hp}f_=!;3FOpgt!#S3|>fj4qjP!nn@ZrZ}h`bbG{BY~#;@?kU^ zLv5w};?dhHP@dct@m%oRMLW%%K)b`SB3))>kpbHGvJ7LL@5N07-GX&g)*fE1-o4cP z!IIhh2d9+QU*4T*;m2y$cyc%e&5IEdDH9%bh3UvWy8C87*A;PCc&KR4nLm@bQX?MA(zu8HoqxWYgsCR+1^^X3b0mS zzfhpqOP8PrXShAZONJDrR7<0q;{}Rkdvp1j)@Py=%=P}vr0%051Bt5t`=s^%N?~8h zI;2?9*VNK2O5p4MJu}vZxeJ1+pCgGTDX;Qr>SotU6g1%}P49gS*yhD)->N@)ky3<+41pjcg$rhH5Ew314%!{?zA?r$^Jl|!j?W&>j0nuth4ZSs0rYBAXy%*j?k!i+T z6zyz&J9kQz;I{PD*W?(7{e)pI(NkOMm#6DuIp=Z<_8es)J`1G;)2%*e#zc`5v~lMIP&5J1!?VpEQAlFUI_i%d5DXet`|+iC zO!0#c_v2@&m8Mx;?0U?8V@AIz;gmmTXD^FIIsco0OK;skE}y>=^;niJ2)3M!z;LM< z6q+T@JLNzYs_y>l@#IQ=F5!hO?I(mJmqTDeP_@jW6XRzBxxiD7XwgqxG9g-r%_{$0 z)gM*)m5Ub|zWUIzZFbDyz{HD}Aj!4LYzsV7Y(7a2a#I3+uKKJM#T@P8e2@T227?Yj&k3N8bE--p9xVCKRKNAN$qOc+6cHoVBG(T8waK`z_ESwKbO<1dSrH{~$6xHG|1{pfJq-0!^ z=_`?8PavFYN;V}4dKC%Hj4Vb;!-iz~l4x^Wv8gDJRZ@WAmg#8^^>NbrREWZ$#1d(P zhmeXBv|xTGlqE2P@%w^`?(^yRH}wNrdoTvfB6lIE_A}j&GiunNdzo5@|E&b z`{xmPztqH{v)r3*TkNvw1SI+wLf5-gh%k=KX`~i}#4pEeFf%u%*YSVII~%O}a9pHX z=(5HJTr?O-j1+<+yX5TncPN0-BG^8{m$c?}YHoWIFWTQ;0ZVM3?U&_0Y5Crhagd%U zQqDuoC$vzKIL58HY|0sfMI38yzkXEN49{-E)er#o^*BT`#(z%_~U)|Kb68|6SE`s1?qbX1Ku{F9TM&^E`*RGPsk~;%|4an6cVsTBcZkZPmm^L(dKY!Z3}H_uiV1QDO0h zj_MKSA;sH2qVu-%s;Vlhhx2gy9vV_p<%hsIl+kUdFkY~Bq-E6*pxAyj(!Bls8SbC6zB1^h@T${?Ua76)VErn~HJ z78B@jU~U+P>Zb0XS`#4&IwYx2QtepV-i!gk%s*qH=cLUz6Q*1Zu$&5sg#m1Vq{kDp zBA$?`VXc0E<4d3xCbewLqGLu^nsu_#J!lJR5y?CMouFFqmIy*iw)@&i+!b1RNHD5? z9f_jJKV>VO0WyAwdfZliCO=;z9q9deY#{y$kN230u_y-(e@&1830c0sd#OrRNwc?6 zR#6?Y*Y6fbmY~6SnmC)+!rSrJ$)>qv+3g;*>AU1hevbF_80_7DnJo~7F4?6}Z}uqq ziS#y!WlwkV`+Vc^g@GYpvXb<&YyS~BatB5gev8R|_dk7jCwnNpy8hc#W8Zs%>9G7o z3`=+|zx1dKePk55L4bHW9ks%^>sp!dhnb+7s<1^LkJiufSeYb&^ehN9yaD8Ll3{f488{awsDh+fG1&HNp$KSI%;z!yiejH*Bs>(n;ITMLkRcU z)jUrK0pH#rSXA+#y&Uv)CY9=P-j4oveI2FkhU7}M@^d)jEi@%7%Q)X!+A&fAL;ZYQ zG_Ctx_Vmw_qWoi@-ZXlh>t@@Ad0Xqt$~=6sGP#(Msy}EoqsLr4w96kaC3zc4)_@o_ z1^9NK2l(`OMYb*Y-$IR->chz$N~RwzlSs=iTBoo1pE81ftx=N;*uhUp8B63zX(A@` ziK5Gq2QvTKJ$hqLsA=iughgd$*|W|WRo4GN^C9r)YLP15iIix6_MB1+ctB&e+B$(X<0&@(#U z+7!g*^;`h68r0t`r0(QSsdcFuJmC5GJTi?F!)ciz;U+tB4E&_9sJ) z{~H5)P$xbxl5W6mA}P-iKxCC=E;>ZD*s1}To09z>*A>6~r669ayu~@TMqkKnC*+5h zV4Tf-oU>>F4|IIm*0t#)dM~P78}O~~PKm=>>n^gGz2zEa5M|W1SNq|wL{os27WAd6 zUqJ;F6&;a$3L!mzhO{Ge)G=4m6arBr5PC#$;`xorX*xy_Lmo~hOGANh5w{Ep31HD@ z?gEQiXIPor92;!iBj@QuO@W=r_T zyYcO}EK{AGjnv_&dI07_JcNB{CJG~CAd}x;ZdS)q8|+^x`a}q2;0%mm{$d1g0gR5t z2?cV34_rYC~@#ShpG@+>RV-n5M^v1qZ z!{a9Isl1bizDy?hrKkT70H{D$ztNkV)?btoTQ3xpoU3ZcDq8ST1_H5-F!pfaHyfze zy^gz@-l$XUD)OC^9-}@fej-?DAxc|oY0daiLJ|gcl&wnIKqp;js7%!6aLFL(`Vnzs zadxx5myWoNRJ4OArOJYu?RG33!woL>9qrh>C*wB;t5(!H4gUaYQt8!-L+Oy@bh7qQ zQwZ@Oq~zcnAP}OAj9?68f`P@jJYKi1dWo{((cPmw9XT zb2l8KQeM2NRa#W_Jvy|vgQ19txr&mcYF#wT}l5uEBOkwK2_707iu zXDtbAa~e|qutpE5QP|v*yTqbZw>ba=`IDgF66$3K1_0h8=-e^lG`n2Rw0L7Cmu;hx!*v>qA_PP-ytZ6r zj*mJJ5*3Z83rSAZ_33$^V%jI@>^iN}#hT@1RXLE9kx3$*3AUE9;ZsR(F1e7RezC=j z0fFm6@yFVejFiiIgJi+GjHbEHA@nyL4$FDA-fxt-Wk?}SB<}5wVh~D>dK>!3_Nn2G zKEbvjUH3~g))i7>Z9Uh6j;GwxRnBe2r2!#KI--MtfR#1|aCJ|gh72aHGL!g4;v;=@G;F>d--62>XS*-21Aun9>6M`r-J4RHEhnk#z8v8~TKknL}SxBC|qH8|ov zi@BNiIc+I9NNh=EBn_!iKUms|vXWLaolv||;e)h^ZNlRzxhpn(gLJ_1RlBbACZ74nzy0;MG5QHT7 zmeC4WCqviQv@aRy^KN?@=CrK`*G!!4JuoK4M3XfDFn2fuoS-oB{3*(g+yS1N>xWTK zuLVg?Nr)`1QbE1gfZ%}Rk%1VMOo7qP8PIOZ8@pR^w)@8u%2yD$eDfj=RNZvf8}PAF za5XsqB|Q@$4X*cBMUPIp*461~Ue#}RsKA_p84Xmak76{mAu3SHNy@{&yH+#EIsU>MU&xwLlw<&YLauc~k<5o%>eyUPUHBru;4&@C+zm!$64VJ<& zIsUmuo`!h1;7yIjhU0zvW4Ha9=oZHq{hS)GEc?PWpHR1^vGm0SWGQC z2@;pl=XpO=mrF@XFob~LMJ$B%(buzY66l;3;mms$`>Dr_nAHhtidj*43u(m%d>cka z1DWbDe2%@{>_NA!8q54Wol%o&$e9h*$y$tl^_ocOR zTXvmg`+mo^D+z|xKj|-|BC1RAZXSW|?6rNmwcgRJAERWaTF|U_#uG zT_QUw4;(mIQ=w0}q1}y0q|#KBPoCUol*~0K0uu9q8&GhdaH2X8eDw)8i~JC)QfAz6 zxQR&+pgP+#C$+rCVaGqSL(2);g(&m^L=*AXA5`UWa{8rP4Op~xZ3bzSl`%GtM`6~f z;&(Yk$w8JkqCkBmva~!^w_#3BN!5p5vG}!mPbjBd6)Pr!8AD84mcPelgHAfZRLyRl?U!ed<(2Am!N(A#bT9c{GoyY<&5L9o4D0mHryjprN=?HsEddQ4!tE zpF)71pN6Gj*%v<&8FC*EIDa}xAU3H|6YenC$UPS0pJ;qrO3%YlHQ3H8h@_@#gm3_) zciR2ux+gHrp22Xng;Ri6xT!%(?!CK{-xQU0&3dg;i9&9co2IXDEX0cmQPTUfR-kf^ ztjODq#YEu=NKrTd>*+tU{e^OH%Y+uyjds>WPN_>*YMWu3RGBmiB2Kag)Lq!!{es_JaZ+YPEEkT+}lhQ zf-nk>0sFNBI%}9N94*7qDeMQ&;jb<)Y7|NFBz8W!k3DthoVr2)`p2($ng0Nq$H}p! zuR2=t<`sldi*P5P89LCQRC1%8d)x8S+pQZ3!6|Tre#kn`syd#|1CZ|~G0mgen*F4j z^Uhgd^-we%&%Ak_;3HnmEt=W4~eJ$!d8d%D5EzgZfh#{ z;Hk2nLot@>2E=lk$2B3XfP$ogbKfNDnYNhf@QO4_lBQhJKs*2rtL`Ph9i^Co7tcrA zH}w7@r7ZzSlrs=?B$h}n&MZkA*aKo{ zB~6x2;Jx8kDaj`}(QT8%*SOckl~|UPS&O1jpe~h7Ohu$E^hQ!L99px>5?&G>c1K?b zZ}rMFF-YQ#{cqiZ_=R&-*E`<5>QrXtx|Sw3M%N>jck;p%*;8mvFpmKpQ=Mt0(eGXo z+$N!ATQAmax`4$dwH}1^W+b|J1xz-yFhh;L(v(_SRJ1E8QQD!`SrrkIR8){u9^mSD zcRs#x^p$$*B&8`V7W!9q{JQx8IvoylmS(b75J-r1j zvK6?hQp13#AT&wbMgTe2F>oDM{C+o3*|TQU=+$T%?3br9euQ>SZJBI1~yR z30_JR{{Tf~XHrf~u0xHH)QIWaPSMb3 zUTT(|w@cvSF6&zRjxF1Yv~8>AzXFi-Qd*7>V$!LR9qRKAsYOe5O9^pl!=F?OLHd=` ztSwT)8-}O0z$1Oer@T}3Vt}Ss@tJ;ZEr6S^_+cX*z?9Qvh zP%3H3h7kmrT8fS@s=PiVHd7Z;3E9lvi&!m21xsg!$yfkUAo2~WAjNO`7bJhUU0 zq$r;V3mcRYaz;4x2pWfDVtM}nUNGp^&6uvLvV{w!BSs7d5O+PT;}U#V;a3)JjwoF> zr5e$VPqu4w8mqvr+%<^Q=w9aJW(o;XmR!SQD8gGIA-oSQcOMKmLc2SG^o|qM?#UGu z#EZ1l;iWBXQk~}~-O@sWTR|WoD|dtp9O3(KHzJ!gFEpV2 z88}=>F=HI6K2TDZihw!h5L8r>j`4D22$73zI?6+B?QOIu3;-~ck53>pi;*bi8GWaH z?lf+~=FuBdS2lFCOH!O93job;GSLRrHjzP_3Xqiq!D>4Zp|Vy~W4H=}RqNy1_KE7C zbKgWJGG2xvO~b)Wggc+t^(W`wLeZ5?HHi``PjipEvGs#KCpVS(9-BzV$0yfbE0x-d z(;PHn65FwPYFhdftS=)UFje_$pa1{_FnFVVqIhFA_pmBa7?j%T6qg>K1rDem5>%ps z72P1Dlo9B0ubP}h_Joy4q(`%?*Ve?8^_x5axuh!!4L6?4!NQgLy@y`kMZKsm&!Azo zMy6EEX^M*DvR0IDC}~H)F_Ds@lhfhmPPisVvxP3PQ>a}RB;HGt7P&s*7bt0>p_Kxn zNy%1JKsW_N4C&m8$x;DK!w^`8D@jc-p=#?5%@@2&vha)l0JGA`;uWbWaqIS6xf|4= z9SdohjIm=CU1ewd2e!Y9U=ZD0zZSSHhOy zTUO<_M}7ko^~bMQM5bC%PcA@thIWroHWAm$;B?d0jJY$yHF8J!oB6~11xTx{mV!E@ zzv~yZt7fqd0}$h&>}dfbKMg;t+HHmu7RVruppN>Pp;zd1_LAi4lr|HN?!JT%%;~+; z#LgZvreeEzQCoUbiD8Dl@da+UqlBRV9H~U~2^lBIYM=ttTmoZKRY5}8?G&YY-Jfi_ z?8`o;x;oSq&y1GR>^VU7A?V8mwS(q%?&@$yeM8Kw(5n&YRFC^I4Kq}tK(5Jhfl&8w zm!vkP)LV-yn8-oo^iK5c&rJ8xuM{4WNTSKAR4FxRaREz4REk`dnL^f}lmfLN07)PL zo}_`?jRRSgYk3Y(9FaM;+KXk<H}nuO_1GMQ{WJhEdX?_o>W z+EBGCqCm*q-y>GV4mH0irdu^QIgr?Kq^;DXxP#eA3dTY89W}1K!)g~P$q^xYo6AWa z^8v+2bbLdN5J4VCIT}fL)>(yB65YyA02GB41tlafQ=6UO07C#HjM-|MQ7sb!rKBii zY^hLEN=|N!q1nk62Qkd=))h^sSGFygg2S!T=E#`+!4-ox42IDAk{rWr$?BsPV* zR6y{R6!j+{9o;mVZ|Ev`di~1=nN+41xl<|>y{;yrrK=GXTRT_G4kzuCf>J>wj11S` zzUawqs&5b~ZnqyLELB-zq}q;hHy6;S2>C1@kDj3@_s!kz2H}3(wkj3G+{T*2%|vn| zXJij}am9eOrAj5nk(DJt4?_D3HrYNJ|8z2+P(=rey~%bt>Q%AT^2=-ECG_vSmEhP)TvpqqKq&6rzwaM`+%4MkxF`r0~|ts7*|}PPFmR-B1r)^~!9PYUzyJ8SPC?Rw^dBwklsj!ue>DkfgSf5>thxJq|l) z&dj`Bwk<0CHszu@BB^&XE>fj7>rEktp&;XmRDMb81mI9Xo2{fVA0b$S!yg zZLOKE_S194VH6ndFIK5jsYqO-UO^kz&R-yh~PuPC$$IA_WuA2u6uHeN{p&& zEktRCobjpWpO>_$O8Z4>!jx2cl`NeE`#I6wjZU~K?jk~3exj)HQus#Xwx1e@gk%lN z#?=x*PsbW@blo)mE82G=-=S3^F+`t6T$PmbOGrjihnA-Dl%zT`0*_D*MxRltmgT8z z+iY1@YZR}3fb`)R6IAoFWH_y%B?ECz_X$#W1e}e&9=i98PnStoDRamHPLXgf0a3Bs z6aN61??r~r!Z5kBtNMg3loWc-$zhZ^>t+qBn`>y5zbbP~_|#}jvg2*WRM~~a6qL2i zZM-D*+(OiFe1X(ma^s6bw^vwOfyX4W^+}YZkE}a`F_Jz6uW|O0K;d0-_qyrM zs#u3nnIa;XEHB;SJcK2pf=&r2bI%7lt!_Rk+qc!DOPxl6G9aIpl^Sef(~Po)!p7ev z3@9DQBn7V-%5;4rE}ofna)Xs4s$X$+Y2bOd9OIrDgdL~GaQZa@B`#np1Qd`s9qB9- z_H?uA0EikCdhK4}hYme)ALTQ39Q{bsed3#raNybz5aFU+H=x2xVsnsOhG&d!=7NI!_JjRuWLmje$e7X$d z&pIut(VL%fNvarh=+y@hnJu{;#~Sg3@`+`oZ$m?983-84v)tsfqrj=^DP>oxP@O&V zf~6FIOYrk>ZcT)C^qaQJUe?dRyXJZU-}<7Y%n&DkYWY#CxU% zzfZfaX)&Nw=mdStfdLgr!EQ=t8mn5Gb?o_rK+6Dmm)N|ijbh# zDGP6UR1b$AWk}9I$Wn4OaB!NVLi$>>ZqWVKRZ4t_u9+BIcVSW4a60QrN>#A)2v~8{ zY$xiFQLq9SqXooeiK?l&T7r~8Wa=3y4&X2ui{>M$H6&PRHm?Ci5-IT6sF<*xs3aAr z3zDF6mv>TwUzm%ks_(QM?kHMU15jwNE$Xp#ccdnsOXZiubRiBjNJ+|;)>KoSrFg== z(382Yn`X?TU)OEwjr2NFn{uF@Pdn-acDHyxU+o^Ac|c_YomnZ(prHVa1|5 zMtXv@Zg^CfkW!f?YDrPb0SCl841?cVDOaTm)tgXH66%ddg>p(WT9FVz1mQjKZ z0@_;%+l`}W^~sE#E@b(OT9}mT!HMf%zw~IXX92nVqKD&0vVDT>Mh(lu3XM8F5&5te zSD?sNVl@@#5Tz{`JJ?E^IW3X2r|j$`t45St4w+Knw9_uJDkPtORC--Dn&P4`!ef;t zGi@*u7DAaL2_WGmBxf7RZyar#K8a4bZwdMW)oGLUVf7sM0YVzF$;@r+(0o zoTwCx9-6AFmF^{~Q;VckVnwMfvWn!@#6yUA04O&EfP5|hLf>0OJ|Kp|u$3e=tCu>u zkuv2}Do9Gwr6>m^4NF2GHB|hKd&46?AbgO>HEhw^NX&+6S1m6sdbs4xy{Hstv!xs`VBehM+}7 zK2)lt#U!{QHs<2DT=P@zx~~dORN~Y~2MWc1LyKa2?X_u9C>5sf8Eb=;f;cKr3kg_1Z^GeIu}5 zpw{i*{rz~vrBvEWZpKwnX=mS~IDod-o`aa2lrJ4OBr5}O8sPg<-rRCAf$nVsro#2w+>!$FqiEky~**2EK6mu++{r&l8``q z0F&L9hWF6!kE?$3HjrCxd+kb!==!N~o(`cy;G~=%=cz`Y=O8%S#b_gRdXC4!S&VSFU9HR?9}wjno8uW*;eq| zj~*cJ9gtH#l*H_NpU8@*yc@NL4~Bsf#J~ z8^-WQ{tRD@KGUFWRtXu}dMJI`*`S&w@Y=#7*3y+@qGTk0_Yd!O+ceVnZnk6LtmDfX9t^RiZ6U-dwTz)@ zDNywUVE$U6kZ!4%sV<=W~(ZU(8o;j!kbi=a0i7wa0XP7jQuTJwV;kgR*6Ziv^%XI z=+6=HLV}wodE5z^?%?r_mq`^t5iFJ>!f6y{31MaPr; z1Z=YQ?w1ipQ9T#jMp8SJ-xCz|`7eSzXrOT8gfV@nXI7_KOLv)6MK#*9lKG94?p%Tk zOu3$ot;H)pW5iXcO3e$}nKgF!XTm#XWL_Fl!dj&gA;_aogy9aZbk#fyl3aMLYj7k5 zXTp#VjNR>0tT^cmPN_;Pp2n!$d6Jmc#~-#9NXRNNFJmZKv_H z0e}D+;$=-q>mY;Jf9(zkG7FUximUwB+~g-WOW6ix$tJw%j=MtB#ud zDoR;(#{-k0GzQhYrEYOeIFPfsA6TBFT_arfE#QUEU2@{NVppmXa=aHBR`Pj+B`seeRJ=hzyuG^O=i+Kn(0EK-84)j|<9}Y;xrZNy zo{gEs2!4yrKRAh2*5K(@UYp5H@QWrZOjDY0y)%1`th(!tEDWgndH?_bdTE2TS{*V> zs-(KY0eCjnHxh&-93%niO1hsunyD@}xV0K9{JVA?O$$!aDYFLf2^~UQ+H;SI(-tpy za#pAp3lbwd)Rxv_N@%pO60O^|s|RRS-n~ao`j4r@D(LS~ET_8OHy&{(Ns7@|{{V)X zm2w9D7Cuo)agyk2;^QU2xje+hjZsn%;*d%f)OOoLKpb}FDQD(27jMFYe7!f`>aS95 zT0)x@s=^VKlE3WYMNjLMf_Ik=%pw zBcbSPYdQy_L&6IN{P6*nka96YufsA(PsHt)U$)!usMP8E( z71)H?lgCvmJ>-&G!JoK9mkHjcKz{q3!ddq1fW(@2(i0%pX#KM+88Z_hS1JG?$D5udYqoRd1-q* za7M(hH*?xtGNIFIjBeyfX^g5qMig2KBsNGVB{=V|OU$+Ofq^>n96z6fMu|!&sWTLy zlepMjyjk=dHF)5AhJk$hI$xD|lztw$OOS`;!lu_MG^N#)^c512sP1j|WGU2Rei6R} zqZk{Fi)iqFjFb3DNWS<@!pm~dx#h~49-{mh38vL0Km-Wx!$h6QhK1o<+fKzV!nBlq zlitJvHlCw7!Rj-ldiU$|*O4upIc^*~A;Pf|q=rDvXE!;UaRjj&+egu(gE&WJdqdf@ z&LY=u-W^>v%Xr#z+m&wIH76$hH#aSoTWP*)%OoT{xk&(!q=C5O6m>I*yg9WijLeh4 z$^z#>buTvxyL(j@!)r;(O5#XBD!~AN=5Rng#=f!+ha;w#RHzgRxc>m(6gqU@KlGT5 zx%sFa85jT#oNw`tvhh)!YMU*>n#m<1!L$}V_wJ9LoN~MPf5n4maZ=?)zILmQ7Qj$C=AuCo1NFZcqtGKzq4i)i@5Z${WyB4EPXt55XO_T1@8EBmHjyx-G zJ$5J*=gUI8E%r^}hYD`^cf~sBxGI%64ZPfMewNZ%NOc<*xwepiyr-rWk>`wouLTM- zGY|;#{X{RXN;I7sRHUeA12(SC&I5CP*yrl65WSABU8-Vd6sT-TKvHG371@m>5tOTz zu3?YOqm!qL+P7u;^g#fgx za!4uKwRF!Dy@lzQ1=PCd3@>_Rvre4hc3m=TCCO?#gyl+&vBVUll20u}q#v?UI%-MY zHt@sP;-5|6T{e|;T~i=1s+{>TT#DICvfNvRXPV*@m8dwNgaQ%(2ftooPys<;0C$b! zpOigCU^xXLT*YgZMA^5!w0>ZNx+-Y3*f!NN8u+Jd&9ntcWu{|Qi!M5}ZQadVEiG;` z+3EtGLOPD1WM**L_WasUzwq|vW;{1gJP;Pts7oFEOsjKaBRzmUd+MA#FkSmus8#q& zdBMG^bKHKRCX+pXM_l+TB$YDx*)wkJa~p9fz#B(lqAnZzFL9c)K3#TwwQ<>$>T?cB zkuj9C-A-vjNeo0|ejRm0U>C61>Ct_IAx$+CUYXK3yN=j-Hf)v2%~Y^R)DXw81AfEa z;u==yH9K?Oz0QkAeVGUdeVTHerzPN(ZUt?Hmeg_v4gnr49;Z>Z_1jN&-Fl64d#$}b zhWaYYs>x-0B)H?jw$ddqd97ho3 z`@wApN>)#Fa*`5386&eRc8z!6F|Mlhxo1~uwAxHYJ>hidikO5;W&s6VVU6tW7m3q!^8qNS516T-wrNS{>2WaW5sHT-lmO`#<$qlImiis%+bvVqT zMTAo>HnqPVdR}rgsN|MiY0ax;eren~6r2o$(Eb=eB)OcND5P}9O>n~Nr_%)@Ox8}MGuwFpjeEpv8*?jU_RLGItHb(Q zp=6WPB(Y?wVpJVAVBtVHU;XVHVbjGu7`*ziSky@!yZTC$=oi?7sid$g_SVN`k zbCuJ`l@)k`h$mOwp~Wk9rkQpeiX2K(WKm{R<4=s-V8iNiC`uVojsPp%RkYw_06XJ6 zuWE4SWkOU3oVcbkU^dzi>u6}L^(91wkW++|gS2C(Rb+M5-i{p_#AG>EuGZ#+4-ywW zXlZ?O)wFF#<*4+E8Y-@mLex+$02xMQ5~Y=0lu0fNB!in=(UVHq+*wLg%C)5hO1Sob zN=X19FenYV)Z;FNEfTXmqb%MTip(iH=SzNKr9_Dwg*k^3la(PkT6}wlhz(Zqxaymd zp-fy`QJSMtVKp*2sXHCCq1MZ2bt-F?=?=2iEw$4Mne zme@{Dsr5e{QdPvN*QzDha3#uOY_NF{!c3N=uL(=61rU`J*=?wVts|*U&~)-mdS^_S zC8Q-w-fw;#hvODGTQwq7kv3r~%c#=aM=`CD&@>J;m#?%eSrhFj*XmBptEjd!7=xPN))!d|Z9E+{cK6 zhTGh=6o3hcYMaHRM4w8e(i2>)!B2*h73EV`Ege3ynII*53Qj<5bv+W{9Z3C6n@-to z(uwUSz)H1J>3L5klT3qJdX*FC8jj*Z*28GpBlFMz1t4G$Gdsji8eX$FVZuq(TdI$6O_I%V2%PoK)A9Y@r_)}zn@F%YgT{!qy{p9!pco2|Zc`~QS~4AhMC79igv-(0OFJ8J z4wb6|#Fg$*2n8bCV4^`qR@4gM_e!4tk_usjvnT zloX5-b=sv$SXoeUrH#$|+v6SRAP3AOK4e<8!gO8Z;AL(xLD!#2g2$ z^U+hP!8a9B>bIt(Dw52_sX@3PqcR)Bb#sp9i-4%3#6SjK&2*WTMdvezd|$n+O65tm zW4&y}T|pD-s7irxMGJKt#@kb6wV{Sr;z3nTwy$h-EG8x=;#W2ZSlnG)tfJ5@L z4kQA*?AdfwIE{47t6g^Uw&f1G^+vmJ3d>G85uSfMhngQ)+x)VG5UeFV2qXZVU3TZS zHwhrt)KwaBH6X-^~+YGs$!}SLtE_D zVhF^cD?@Tl*U;b&4v&Hf&fUQ$68Lp|{4#G%Oh6^Ud)m+NitaCkQ)7}k8t_U|56jBk0uz7ryTN92Y;CL z*A?)8jkiVD#tOB*^*$?fTXE#mAUuZ1k?y?p#M<+jA>HmYmqEbVK^=3h$g?GQ7_(^?9X7h(AS|-^9wt<*6aEwC zH0;so=_P55-tF6oP|m7W)aW5uDJkj*=yRfqRrx9-W;IDA@CFd?>jVAoQTgkvx`(o% z+?vpBGsY%sk13TFlAI;Hl%xy}zYRdN(%*AxdXjqUY^sTzqUs;!1$S6I{u=d7lGJG_ zTi;_NCr;cLd^B+n6Gd47@{eydX=sfVI$p%$YZ$=ptt}2XK*l+9ZkFA9+oiYTtySX% zXz{m8ZoTdJ>21*PWse(lw(H*Cj+WgI14oUzTRk=JZ^uh-$3wu;{VJZ;vRje|qt z1tx78;EYzoj47s{Z^Ha%6fl<>Uvw=!!5VY7RyGLOtdFQsr5_xG*jVCBh2yiWZk^)rP zljIJp>kfRnEJzotaCc4>mg>*5xG6;_d&JU7z$CU5l#_vyHGc7e#HL>{qO@EP>adcv zM4XgvQ(;)kYag!Ch#5|Nfdmi^x%OihJ*70;b*RXvt?=rTCEThfDF`cZ;#a-lcHiru?VSjnc>T8yVZ*l|e# z(bpMD&*Q3$s`^TahFqC|k-O!`x-H6)I$A=Ok^~saye?ZY#lpF$(rc01%9^NB+6#3} z=flopJA(1hlw~T$aGc{q7hfMDL#I13iw3U|age7SH5wzxXeB*P<+xi)K6{VA9ZJ-w z)wQ?Ghki34IC33sj)K3Q;ixMsu9|hfL3}prkfWH6NIf!9%_YR)L;<)YU=lzo#!Wp@ z_KoAUZiK3mFE4cPZwiS{X&{9p5sl$}Ms;g(n~C=AJKrsvK3sX%1cppt%t98J%2z_` zg*_91P7~cnx0w}NR+CigRMQs9i&3DdqLO;;OJ~p5r+q+Un4)Nk0t+4BaES^EQ#>fE zP0H*;R8TrA2~>)b1yoNK;Xva#Er-1K|UaF8(Of z)*OX=TrDVe-8-B=2SSE}c%g4xTvn+;u3Ytp)l`8QQ4=w!OeM$JD%_MjQjxqNMMXV` z+^)S&tzkkxv9Di2EA}+eIORfqx10Q4943hgIg?`?!6Sy8M7QO}sZgrTE<_j6CM<@W zTvy{dla#vIJCYU;!~@vm5;fWM&ulA&-V4cpxTaQnePv5DWD;-q%!a~w7 z@+b6$we;#J=?QlEhzv)zP1SYOQ9FosmksZx!OM)*t)|fK(mu#_3M+5#O3#+B+k=X4 z`$Ky@wMpO|GF7cswW+QPoHft;Ll>1xiqLr30P8O3#Zu zx?`?hOWFz*5}Au5yY(L^^tlrTS(4=dq?hD-K`r{k1uudX3S6dY99g}nTquI+>g@WH zZdTvx)iI*ST9v$v;cHRB?uBaL;}u#YYc7L)`!L*+Dt6T+Yk_jDr=6cFJH;pw)havT zPBw$%Dp(0Sl!AN&k;|1IXAUsY<-0PU4v$$|#Hraa<|(O99^|i@Hz=R)I8Kkfnrpm4 z;C=C3;sxJqT(F^2?Ws(voQApX)t^&{Qw%ni%F?ouhYnPHJkFgItvQoAig@VdEhHWJ zgTH%6dGqFmsF-@m`mD)3t$y-%LyA8Yc#(TdZi`8^1=Bg@a?;f%8{yKk*ehsLX!)&3 zNb@5?b>}5fE-TL2tf6hX>|~JiSH#v~SD~yeruQHMq>{IzyrhB?f--gzET#Vd?I7cH z7hGk|9NRSIxs`_GQ>V1H&^zQ5r9ACxFfb|(_~xO zkp`}UNOYz&md`3ydf~k9+zH%r1Bo=kM_Nl0&q6dhTh2Y4M6HED%7DaWAw&>d9z3*% z+&{jdeFpt*dlMqZrHHAiJmh7DqOJApWG!G23f2l!f^vFl(4+CL#C_)Iu`arMvz|iM zh^SdFEhrn7pp0%IAQDb60Lj%?V)2uR6QNW%W$gFt+U)ua)fLuYR_Kq`CDnH%0BR4d zO|Zg>00EGrun-g58oTM7Gr1kLQEJ)#!fn86^QN{hH)q$=PE>rW#|@b!YLMtpRSX0- zw1J+a4=BMz=1_L!r*YvPYVp?gfF@~~*v5JoFCY}N_)@r#E%dAB50?Q;(4ra+WozSUZq<1R;gVpq3o z$O}h-X(OgEMv195y`@o??o`P0s8tBrb(Uh(9Ob8{+h{11gYpAWlzVd9;pMu;3e2}4 zQR#7&hT_4D8)?1ADpu6vB%b8=&r_xXzafg$YP)+{b~}kXP%?Io;GaB<^wicsq1OP! z0Z!ewr|`D#xTDmovmw1MOS4kkXJN6s>Kc(5C|qZbQVZ@N6(I?1tpf^AU83u=>dlvC z)ni2|Cmq31_<}MHu}{2g_KMnOszP&sR)r@&FNT}VqLVXo9wHt|#404%DaEXAEvCF9 z=XL)8N}_v(j|a zv3pq5E(JYxw`jno(`VXsw22KteTNnAGU}7vNJAk?bDCdm+#Riir9W5!?cODD8-_ex zrE*hFq||v%nIaOOQw*)e0u)fTh;lQE1cKvu&QA2JT-(Hs7hW_TD9F4j7gXv$3hnAk zj@M{Qs6Nu@)P~_W!;spiwU%VEa)q|?G6~!iK|stz`X$Lz?k#bxxWw%)CYj00p#fG^ z(&tQ|3mdQmk=XDJLVtlzagyV`Y$}!0b=I4YTez*L@~L5}Yjq^Bw-l~sdD2j>W6ER! zvuQcW4dN?>JZIuh3ci)%Hx*f^&$ergp^8)#Q6s>cI-$HNN{HUZl$gyoxVYn|W=mlz zQuk$*y#b1CsTS(?To;|mQldguhYwTC4;#+8K4>oykwueKBO0X0PR6f!al9eT1X;3{5 zM!BnxJRh`eP9Vp(sJu(CSflM|-YDuOga^QtK7#_ss#1Dw`9~}!Q zk@MBRE_`@RC_I#suA4e_{B#*A*j-_2cAUp8z&8?nDPDi;zMD*^v!WxX(fw&&5HS(y z47s_Ll=}ID^BOOr8a-8?b$&{tPA9{=>?`0seqHo8CJd@`rXi-v zzYTlarMKg)apM_i@wZEF!(R63ZP4&Ec-y77UiSQSw)}KF4IVc9G`9RTwYHSv!V}!; zL8uKOBbYZUOe=9Y6bvR8Cves!aNHKB6eP+Fk-X{I+YwuS8e4uEkf_H(BPquGB|3;| zLEVL(p1NftXes9`AcB|Uq)An5on<_=Oi2oD!Qa9+?b6$?Nhw&$Rh?&UrqZyW=`JF& zscJ#WpvF9H(%bOYy}unTx>tl~+X64WOKqHVCo1_4`Ygc4H4$&d+Lt9LeXjz3UV4z8 z{{XwgbuFNDfTbjXpI-Xv8lM4OjMANh zbH#aC!cjJogSemXGPd#a#)NJj3-K-bOopvU)|sg&4oc)pl-p?srsAGjy?uIWy;HI{ z_1z&)zcBq;BxU0?4XGW`Lt9qke(fpN!c<mX3Uo)sVY*4A-9*~&K8prqfC?~O8|gBtsC7{!5 z1HvX%nQRmP0Lx18s=xmL!Vk11ZhY&Gv36N>I;`5PSt&7$xu|X=cGi|70ky8qy6dW6 znISmoolRhSIg6rt=1r7P$WuF9l#m9ei=N13U5QdESV~s+4$q(LkOOBy9TahTFDM!>7T~HtH=%Nn;Ev8Zh zMzdR;pR}KPOjI9`j+A5Z(I;WCdBx_b3GaU(AYMI4fz<@}eB+pZMPvkX6%lDi?JYYK z_?!)5HNEdkQ&jxTba!eBRVmN+Sql91*Kl}K!i5<3EPh>WLl#jS-YM;^Q7pbTx@TI)TKL}+rXpjE@j$euSN6wnh0l3e^tA*9G zZ9}%GsEE3Ia_bU+edb+p2132vu%!*iM%03&k-U;TwRuo@6H9{IE!>qgR0BW-M~v4Y zr!nNU7oR_`b zXTa}og1`0D)AAUMg5-x%+DDp>rkSl3nz;r<(w*C(w8xudsmGQQkUO0NRVvY(p5Q{1 z;>qx|+IJ`?%OlJ0*4u`v{ZgQ)boUCDv$YuP+J9(|UqPvQO|KFYJ6~)mN2vh!KBW1b zb1>gn5s?fam~=Sl@{ZNaSOQs@4?lFxIPFL*S^FUQI@Nr&j1PIb*DNLgF~7t4d@i*a2`5kMB;8u8ndvZqD1l z)xU5SWJjz~P5stY0e=SQi*lD!3laPdj=jEtdKJ0;bQV~;yl%KNWXSx;UH9N0S;u+NGP-@I_ zCbASiRYRhZeL?coe_gfbQAv1Z_`Xi0bx-(*m(RAW&0$Fj1u6i7FhTRzDUy>jwm43w zMB#mS#Wkg?JyxB(wH2CUc~Qy2ln3FREjRULafUvjGl-6HDIkzXZ7ok$ZF#iZz7I8` ze$srtdWnw}=}#qw5LC4s0j*wDRH@SXL?{s%WcnHO(|Q?+AzVIOVZJqu6FFteX@KjJ z8w>rDsHf=D*vV50Bh@{1%3dQ&H046W_Q!eHMRbJbnJBko{2n9mr*!IL^%r&{!i4ow z$mj;Qz@y2Q`cUIbZNBPR80qlQv|35!rI6B4(m=q*qw!2!QjU&I)O^33OJI9Gs^xUm z0E7+N6?}(32pSM+)b@`km)%!{sQC>rkt8q&)van#z)D7RaXy_Lxcg6tE1^mA)FP=( zHl-~gDoF>TzgJY*vu00OT*BrwSrqcAWrcfjYNz{~m|70fGoJl)>K#@rv#~4M zzeDGyuc0bvm1DL#okBjtkyZ~>=cx4>lcL3E(K11F@cBe8C4kc6PNfX1_lN2?!IqE( zL?t66^csYLaUdlvO+gqK-aP(#AfQW&QK$gU<)xJK6miqget*kW*?6@q$$8SC{5Qsb zI{~hzn~`WO#Aw`J31YA#M{R-niNX2#>N>vng}C7NJJ0!QTpi>9l5?n85g(|`Ta70u z2Vg$|(@dz=g-*`0EqHfXDq>{xa?)wCR&sXj0T{p?H5H8FCZSytJ(1@YU7f*&B0XYg>z^psE=nRsa!ZE^ zZ6xR})2%%!zayIADUp%^I-0%I@S-d-2?Y4D>8k3TF>od|EgjoXC*`OZwMEEC10>)L zMAmLveIht?x|dd?grO?#bpcwwL14#%&?UrmSv|BVF;wTD&yi5?(&jQWibL5)83 zi&cNz=SM9CoFPE=ILGIS^j?;5Q|zE0rE z4{(n?5JBBa;e$<(!w$HU%{U|OfL7?{?an){dYEktkI`fn3 zq6oJguF}|$+*f3ydxIe7rlab2r6QiLTV%em+#|5nXjY{hsYH?Lb>OH10X7q4wW#Y`(3p>)Tr;b&1Lj09-!)jpH&Q~rKRM7&<|gZnz3DN4lw|CQuBkVOih$uFQA>-&tAGEg>}93bCKmB zCE$_}pgZYdBGzW+Y4opSBPlyU-P1ZkzUKX^ZN8paI;(9Oi>Oh8`u7DA;c&00IqY>I zU8qx{Hm4Nyhn!d0NLku6KPVuk;-1Y#o=J6}>uN3`8^KmF)C~-S*XN)p?4nI!sctFI zwKzV@gkXAT@>EZEcV^}7ASpRMM_$sc(J_WjaAh9R`p1>H8adBjENP_aGA2G=kg|JY z&}qPJBd#)jI`&>IG~fvYEjYpT_0p>19oJLz@|_fxo~m&~543UI=(j^w z;CJa(%V@~Lg1UAZF)0V316~4ND{j!wuCe#&^NbY?DYXm?j+$zw7Mz@94^1Hr3h}IG z(@}M*aFT%YNhREXPkan&R04YguO%S$(n7#en_FjU(n0O73Q$PgqkuY+btzo|G7c0D z{{Rt49glAQeY6#G-7%=|H3hdrl3mW`8*IAi9|R7+dFi4fA)B1)WW_!X5BdJK|% zbq7qnBiXmbHoDA@sUdMH2|y|(zbvQE83bxVT_W|btqwz{3Vd?0;+nC&Nj^ofoF}hd zxzh)DFC?|U>2#Mild&z~DL#YXHR=*9%FShpnfK~7$xd5Oj$#xaM^yLioR5*vr9wSc zN`sFrx4{PrXo3!W`ghReeh;Y(sWNuB@!EH!V;JxA&a}T~L4X$>QQQswrZZ)iXI>C^^l z)F(N#x{8#7QlEB^6!S|SWaQ;1;tq*ul^B&d$X!B|x@)cBSuQ0_sQKWIzlhQhBhXTX zqDO_dvh4c0;LRjOlAxCX{Y7@px;i|TWk*DoRtsxR;H>ncxmXvoqgV$upyNCpvdP^ASeAd#W9qe`1YiNewd({Hqco> z^EvD5s97bF`pW|iD-5jKs%#g!@L5xGh|$HPbQ{Lw*d&wP=uAh#U z6c&mS-?C*?TpZ@q0X-51N!7_`@U^OJLSAE*x5!ercLT_3!ih zPc*4fNpT(~Bup7qqzod+h}uroBp;TTo*%|w5ZV;6p2>3>G&;_`6{zbTQz;0tLqLRR zjX-QiWwL;S*!R!JL@+0`@C_1_C;$R+t+F6SlA^Z};v*RcOeDaG8MQ5P14SSZrK_Pj z92oS`DDy2yX-vF;q$ps6pgV%ddNEwOZ;ig?zcKRE(>L7_0LOkwFSpS^4*vihC12ED z!7h>V*Nr38R|-myqLl&&?hdi3N$$d^)LxWia+_Jm8+9DwP?ZpcZXQQO5#visOr=zL zZgSZPQ60(cq6m1U1dNbR$Y)sQBRDDm`W~8zdZi`IY*xYj-#sXeu2g`x6iFRPI#z^u znAerI7J8{e;0`tGOGyh6k5OBB(y*lT6di1 zIo9^sX&|65l!4H69d)xyL%`95Hip!dwvd%1qtEh5JX^luKCzhao z?H;b}qdvz~ESz-#Ir;0>myVKVnKJ^$=#AHhdZZu>l6B-uj^Qd~l7dbN7&`JLCwWjw zJuo_J+**Qy(iENWRcJ(x@Kkoj*K@hWd6DFFZiww6DNevjMlwLj)oDPfyHB3n&4e~Q zD;}vm{{ZK!HMODCCC1bgl>k8=V_p{(`s#j$W?`X>l%+t006`c!VD%a^nJpn@@zDs;Q&b^)lj>+IaL-LP)wMF%q{~Jf zj#Nit4iDp@x}}g>jG0l|DmmQaX*p5{PKf|`vFD-&fSp9wsX}aW<1LPIdV}ktI;}QM zGErgnw&5Uyf&zi;diB#Uo`^`UR)rx&3I!?$1RZ%J8abCnfiS6SXA4*B4_yw(w`Mk0 z;MkUue$thWp1Sn#klu+>RVt*!uvN7R8^ApX^U?fQpim~RuS{8bB=qlFXgS)yTzP7_ zlLkyh5b8S|WE_oqhg6#f!o$QPJy3fc9N}WbcZqWu$%7A+vI=sgXQ9wsi7(4}awIyX zxU;!RQSNn@>eTsVBi+{0i0ZE_>sphr<4de0dHeLWocy%3K^-4Plor>!IIOKFpgx** zAjU+KF)iWDW2T^De{6rkbv+CHSZkP$%dxaW*DEYJ8BrrFsY*Cm$6@KAhuY^fTijEh zF|G1{)*b%<5&nAdrH*3p2PKupBsunT(D@xRa;k``Qd@BD0B5cN8gW(a{@?j%U4Luu z(im8qdf`$N{*L2>tEtYTCe``K9O8!h_ub!7wGZ`k^H1~ExnKM_?~u_15!#TPtWQf& zn&)lmNbEIXCevD9L7LiwT>(>LAtUAAP#h8e09KXvYSYre4dKF%20CL!M%L5VX~ve8 zh#_fdX=s8LmX?-?Bg=(B3Md&+2LsgVs<ez5Pk1qcJ5v_VdvC_gs%4$(a z&hkEEP7t)MA!s=Vs5&0h%{%>7KQpf+N1$QOmX?;178a2qAgM$EJCm<2{e!PEf3RQ6 zL=l%CWu)$L#DR?W@2IKL(;+}M2uL1&detlYb!&Sc>c{7=Ku4E&XQYYmMo=s-1RuLf zN7+})O$LHs0to5X1G8TYO+$Gj!lb7e`RZk^Tb)sZpqM}FjDL)1#s}Niq-homHDyvt zPU8{@PSK4RS@k|^DyF)x8zXah^GBHbkMq!mrEP!yIUnS|{?ny55aP00W=K=+zl0xY zX=!VeR$zMOZNx1yarJvKoOUBnmh~n>(UY-0Ckb(6^y+k7TK@o0U(A0!HMf7Ucl=r) zhqU^7AU0AJxk~PJxye!5QA>{@BzFYsVeO%rKij$fEhrBNZ4z=TNkWu{8{LoAC!y1( zCp5MY>&rqEuA6stC(vkGfBKuhA^gU-C;N4LoBsekdLTjr4K9XcRAnvoyxER`LrF?T z?%nc!Iq#r_r)tVZaFdN3Px}l100GcDpKV_=uO`qK^N(G4bqHH(L!V_s2d=&OecHhC z(m@e*2o7>o)F9de-Nw`2( literal 0 HcmV?d00001 From cab579310f2ec36265e96cf849407d7cc2953de1 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 28 Jul 2016 18:08:51 +1000 Subject: [PATCH 71/91] Invert Former-commit-id: 543588719050a2a824a61b3578bad8b1453937d4 Former-commit-id: 0e0ca33a0e82a2696f63609b1332c85949f42fda Former-commit-id: 2750fc6e82392ec3c9368d9b2f062e8823ffb363 --- src/ImageProcessorCore/Filters/Invert.cs | 54 +++++++++++++++++++ .../Filters/Processors/BrightnessProcessor.cs | 2 +- .../ColorMatrix/ColorMatrixFilter.cs | 2 +- .../Filters/Processors/InvertProcessor.cs | 54 +++++++++++++++++++ .../Processors/Filters/InvertTest.cs | 38 +++++++++++++ 5 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 src/ImageProcessorCore/Filters/Invert.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/InvertTest.cs diff --git a/src/ImageProcessorCore/Filters/Invert.cs b/src/ImageProcessorCore/Filters/Invert.cs new file mode 100644 index 0000000000..d7888be591 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Invert.cs @@ -0,0 +1,54 @@ +// +// 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 + { + /// + /// Inverts the colors of the image. + /// + /// 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) + where T : IPackedVector + where TP : struct + { + return Invert(source, source.Bounds, progressHandler); + } + + /// + /// Inverts the colors of the image. + /// + /// 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 Invert(this Image source, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + InvertProcessor processor = new InvertProcessor(); + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs b/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs index d253c4ebf0..3b3be22a51 100644 --- a/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs @@ -60,7 +60,7 @@ namespace ImageProcessorCore.Processors 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.X, transformed.Y, transformed.Z, vector.W); + vector = new Vector4(transformed, vector.W); T packed = default(T); packed.PackVector(vector.Compress()); diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs index 3aeb33327f..47145c23ec 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs @@ -68,7 +68,7 @@ namespace ImageProcessorCore.Processors } Vector3 transformed = Vector3.Transform(new Vector3(vector.X, vector.Y, vector.Z), matrix); - vector = new Vector4(transformed.X, transformed.Y, transformed.Z, vector.W); + 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/InvertProcessor.cs b/src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs new file mode 100644 index 0000000000..08cdb8f242 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs @@ -0,0 +1,54 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + using System.Threading.Tasks; + + /// + /// An to invert the colors of an . + /// + 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) + { + int sourceY = sourceRectangle.Y; + int sourceBottom = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + Vector3 inverseVector = Vector3.One; + + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => + { + if (y >= sourceY && y < sourceBottom) + { + for (int x = startX; x < endX; x++) + { + 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(); + } + }); + } + } + } +} + 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 From 0446ca66e0cea891ee1ab5b1b6de119955acaf6c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 29 Jul 2016 10:35:10 +1000 Subject: [PATCH 72/91] Greyscale Former-commit-id: ccc16cc88844e4cd046130b7f9366937ae10d48a Former-commit-id: 0859791c265f40f2a1dc6e3d1e5c3e05a8726414 Former-commit-id: 6fe4767553f7f3776e7116f9c0b1eb1c63be0eae --- src/ImageProcessorCore/Filters/Greyscale.cs | 63 +++++++++++++++++++ .../Processors/Filters/GreyscaleTest.cs | 48 ++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 src/ImageProcessorCore/Filters/Greyscale.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/GreyscaleTest.cs diff --git a/src/ImageProcessorCore/Filters/Greyscale.cs b/src/ImageProcessorCore/Filters/Greyscale.cs new file mode 100644 index 0000000000..90db305541 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Greyscale.cs @@ -0,0 +1,63 @@ +// +// 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 greyscale 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) + where T : IPackedVector + where TP : struct + { + return Greyscale(source, source.Bounds, mode, progressHandler); + } + + /// + /// Applies greyscale 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) + where T : IPackedVector + where TP : struct + { + IImageProcessor processor = mode == GreyscaleMode.Bt709 + ? (IImageProcessor)new GreyscaleBt709Processor() + : new GreyscaleBt601Processor(); + + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/GreyscaleTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/GreyscaleTest.cs new file mode 100644 index 0000000000..427cc346a4 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/GreyscaleTest.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 GreyscaleTest : FileTestBase + { + public static readonly TheoryData GreyscaleValues + = new TheoryData + { + GreyscaleMode.Bt709 , + GreyscaleMode.Bt601 , + }; + + [Theory] + [MemberData("GreyscaleValues")] + public void ImageShouldApplyGreyscaleFilter(GreyscaleMode value) + { + const string path = "TestOutput/Greyscale"; + 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.Greyscale(value) + .Save(output); + } + } + } + } + } +} \ No newline at end of file From c8e88a4967a357f88e45461a20017177b838afc9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 29 Jul 2016 11:29:47 +1000 Subject: [PATCH 73/91] Guassian Blur/Sharpen Former-commit-id: 8a5b4cf3f2f77979e055f7244a07152f3e7911b3 Former-commit-id: 3c88eca8695868dafeb4478865530b94a4fff195 Former-commit-id: e31d42ca178b55324a3700280a67fc7acd90f0a8 --- .../Filters/GuassianBlur.cs | 60 ++++++ .../Filters/GuassianSharpen.cs | 60 ++++++ .../Convolution/Convolution2DFilter.cs | 2 + .../Convolution/Convolution2PassFilter.cs | 108 +++++++++++ .../Convolution/GuassianBlurProcessor.cs | 144 ++++++++++++++ .../Convolution/GuassianSharpenProcessor.cs | 182 ++++++++++++++++++ .../Processors/Filters/GuassianBlurTest.cs | 47 +++++ .../Processors/Filters/GuassianSharpenTest.cs | 47 +++++ 8 files changed, 650 insertions(+) create mode 100644 src/ImageProcessorCore/Filters/GuassianBlur.cs create mode 100644 src/ImageProcessorCore/Filters/GuassianSharpen.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2PassFilter.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/Convolution/GuassianBlurProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/Convolution/GuassianSharpenProcessor.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/GuassianBlurTest.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/GuassianSharpenTest.cs diff --git a/src/ImageProcessorCore/Filters/GuassianBlur.cs b/src/ImageProcessorCore/Filters/GuassianBlur.cs new file mode 100644 index 0000000000..a57f43b2c2 --- /dev/null +++ b/src/ImageProcessorCore/Filters/GuassianBlur.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 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) + where T : IPackedVector + where TP : struct + { + return GuassianBlur(source, sigma, source.Bounds, progressHandler); + } + + /// + /// 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) + where T : IPackedVector + where TP : struct + { + GuassianBlurProcessor processor = new GuassianBlurProcessor(sigma); + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/GuassianSharpen.cs b/src/ImageProcessorCore/Filters/GuassianSharpen.cs new file mode 100644 index 0000000000..6fb888cdfc --- /dev/null +++ b/src/ImageProcessorCore/Filters/GuassianSharpen.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 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) + where T : IPackedVector + where TP : struct + { + return GuassianSharpen(source, sigma, source.Bounds, progressHandler); + } + + /// + /// 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) + where T : IPackedVector + where TP : struct + { + GuassianSharpenProcessor processor = new GuassianSharpenProcessor(sigma); + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs index cc8ac82e35..3638c1384f 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs @@ -12,6 +12,8 @@ namespace ImageProcessorCore.Processors /// /// Defines a filter that uses two one-dimensional matrices to perform convolution against an image. /// + /// The pixel format. + /// The packed format. long, float. public abstract class Convolution2DFilter : ImageProcessor where T : IPackedVector where TP : struct diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2PassFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2PassFilter.cs new file mode 100644 index 0000000000..231929bc2a --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2PassFilter.cs @@ -0,0 +1,108 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +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. + /// + /// The pixel format. + /// The packed format. long, float. + public abstract class Convolution2PassFilter : ImageProcessor + where T : IPackedVector + where TP : struct + { + /// + /// Gets the horizontal gradient operator. + /// + public abstract float[,] KernelX { get; } + + /// + /// Gets the vertical gradient operator. + /// + public abstract float[,] KernelY { get; } + + /// + 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); + 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 + /// and with the specified size. + /// + /// Target image to apply the process to. + /// The source image. Cannot be null. + /// + /// The structure that specifies the portion of the image object to draw. + /// + /// The 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) + { + int kernelHeight = kernel.GetLength(0); + int kernelWidth = kernel.GetLength(1); + int radiusY = kernelHeight >> 1; + int radiusX = kernelWidth >> 1; + + int sourceBottom = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + int maxY = sourceBottom - 1; + int maxX = endX - 1; + + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => + { + for (int x = startX; x < endX; x++) + { + Vector4 destination = new Vector4(); + + // Apply each matrix multiplier to the color components for each pixel. + for (int fy = 0; fy < kernelHeight; fy++) + { + int fyr = fy - radiusY; + int offsetY = y + fyr; + + offsetY = offsetY.Clamp(0, maxY); + + for (int fx = 0; fx < kernelWidth; fx++) + { + int fxr = fx - radiusX; + int offsetX = x + fxr; + + offsetX = offsetX.Clamp(0, maxX); + + Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + destination += kernel[fy, fx] * currentColor; + } + } + + T packed = default(T); + packed.PackVector(destination); + targetPixels[x, y] = packed; + } + + this.OnRowProcessed(); + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianBlurProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianBlurProcessor.cs new file mode 100644 index 0000000000..23d6d94378 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianBlurProcessor.cs @@ -0,0 +1,144 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System; + + /// + /// Applies a Gaussian blur filter to the image. + /// + /// 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. + /// + private readonly int kernelSize; + + /// + /// The spread of the blur. + /// + private readonly float sigma; + + /// + /// The vertical kernel + /// + private float[,] kernelY; + + /// + /// The horizontal kernel + /// + private float[,] kernelX; + + /// + /// Initializes a new instance of the class. + /// + /// The 'sigma' value representing the weight of the blur. + public GuassianBlurProcessor(float sigma = 3f) + { + this.kernelSize = ((int)Math.Ceiling(sigma) * 2) + 1; + this.sigma = sigma; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// + public GuassianBlurProcessor(int radius) + { + this.kernelSize = (radius * 2) + 1; + this.sigma = radius; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'sigma' value representing the weight of the blur. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// This should be at least twice the sigma value. + /// + public GuassianBlurProcessor(float sigma, int radius) + { + this.kernelSize = (radius * 2) + 1; + this.sigma = sigma; + } + + /// + public override float[,] KernelX => this.kernelX; + + /// + public override float[,] KernelY => this.kernelY; + + /// + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + if (this.kernelY == null) + { + this.kernelY = this.CreateGaussianKernel(false); + } + + if (this.kernelX == null) + { + this.kernelX = this.CreateGaussianKernel(true); + } + } + + /// + /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function + /// + /// Whether to calculate a horizontal kernel. + /// The + private float[,] CreateGaussianKernel(bool horizontal) + { + int size = this.kernelSize; + float weight = this.sigma; + float[,] kernel = horizontal ? new float[1, size] : new float[size, 1]; + float sum = 0.0f; + + float midpoint = (size - 1) / 2f; + for (int i = 0; i < size; i++) + { + float x = i - midpoint; + float gx = ImageMaths.Gaussian(x, weight); + sum += gx; + if (horizontal) + { + kernel[0, i] = gx; + } + else + { + kernel[i, 0] = gx; + } + } + + // Normalise kernel so that the sum of all weights equals 1 + if (horizontal) + { + for (int i = 0; i < size; i++) + { + kernel[0, i] = kernel[0, i] / sum; + } + } + else + { + for (int i = 0; i < size; i++) + { + kernel[i, 0] = kernel[i, 0] / sum; + } + } + + return kernel; + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianSharpenProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianSharpenProcessor.cs new file mode 100644 index 0000000000..edfe594513 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/GuassianSharpenProcessor.cs @@ -0,0 +1,182 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System; + + /// + /// Applies a Gaussian sharpening filter to the image. + /// + /// 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. + /// + private readonly int kernelSize; + + /// + /// The spread of the blur. + /// + private readonly float sigma; + + /// + /// The vertical kernel + /// + private float[,] kernelY; + + /// + /// The horizontal kernel + /// + private float[,] kernelX; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'sigma' value representing the weight of the sharpening. + /// + public GuassianSharpenProcessor(float sigma = 3f) + { + this.kernelSize = ((int)Math.Ceiling(sigma) * 2) + 1; + this.sigma = sigma; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// + public GuassianSharpenProcessor(int radius) + { + this.kernelSize = (radius * 2) + 1; + this.sigma = radius; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'sigma' value representing the weight of the sharpen. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// This should be at least twice the sigma value. + /// + public GuassianSharpenProcessor(float sigma, int radius) + { + this.kernelSize = (radius * 2) + 1; + this.sigma = sigma; + } + + /// + public override float[,] KernelX => this.kernelX; + + /// + public override float[,] KernelY => this.kernelY; + + /// + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + if (this.kernelY == null) + { + this.kernelY = this.CreateGaussianKernel(false); + } + + if (this.kernelX == null) + { + this.kernelX = this.CreateGaussianKernel(true); + } + } + + /// + /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function + /// + /// Whether to calculate a horizontal kernel. + /// The + private float[,] CreateGaussianKernel(bool horizontal) + { + int size = this.kernelSize; + float weight = this.sigma; + float[,] kernel = horizontal ? new float[1, size] : new float[size, 1]; + float sum = 0; + + float midpoint = (size - 1) / 2f; + for (int i = 0; i < size; i++) + { + float x = i - midpoint; + float gx = ImageMaths.Gaussian(x, weight); + sum += gx; + if (horizontal) + { + kernel[0, i] = gx; + } + else + { + kernel[i, 0] = gx; + } + } + + // Invert the kernel for sharpening. + int midpointRounded = (int)midpoint; + + if (horizontal) + { + for (int i = 0; i < size; i++) + { + if (i == midpointRounded) + { + // Calculate central value + kernel[0, i] = (2f * sum) - kernel[0, i]; + } + else + { + // invert value + kernel[0, i] = -kernel[0, i]; + } + } + } + else + { + for (int i = 0; i < size; i++) + { + if (i == midpointRounded) + { + // Calculate central value + kernel[i, 0] = (2 * sum) - kernel[i, 0]; + } + else + { + // invert value + kernel[i, 0] = -kernel[i, 0]; + } + } + } + + // Normalise kernel so that the sum of all weights equals 1 + if (horizontal) + { + for (int i = 0; i < size; i++) + { + kernel[0, i] = kernel[0, i] / sum; + } + } + else + { + for (int i = 0; i < size; i++) + { + kernel[i, 0] = kernel[i, 0] / sum; + } + } + + return kernel; + } + } +} 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 From 4568bfdb9138b85a32108321c251192ec5122c79 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 29 Jul 2016 11:43:42 +1000 Subject: [PATCH 74/91] Box Blur Former-commit-id: 5ac93d8e7ce1bb397a8ee7a68307aa423a85882a Former-commit-id: 7b8b0c704f7f272867516203133bb1c3fbbbf5a5 Former-commit-id: e495b72e24e1f723023cc537749e03a987b9eb8c --- src/ImageProcessorCore/Filters/BoxBlur.cs | 59 ++++++++++ .../Convolution/BoxBlurProcessor.cs | 107 ++++++++++++++++++ .../Processors/Filters/BoxBlurTest.cs | 47 ++++++++ 3 files changed, 213 insertions(+) create mode 100644 src/ImageProcessorCore/Filters/BoxBlur.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/Convolution/BoxBlurProcessor.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/BoxBlurTest.cs diff --git a/src/ImageProcessorCore/Filters/BoxBlur.cs b/src/ImageProcessorCore/Filters/BoxBlur.cs new file mode 100644 index 0000000000..527950a209 --- /dev/null +++ b/src/ImageProcessorCore/Filters/BoxBlur.cs @@ -0,0 +1,59 @@ +// +// 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 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) + where T : IPackedVector + where TP : struct + { + return BoxBlur(source, radius, source.Bounds, progressHandler); + } + + /// + /// 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) + where T : IPackedVector + where TP : struct + { + BoxBlurProcessor processor = new BoxBlurProcessor(radius); + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/BoxBlurProcessor.cs new file mode 100644 index 0000000000..a4fe4da0f1 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/BoxBlurProcessor.cs @@ -0,0 +1,107 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// Applies a Box blur filter to the image. + /// + /// 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. + /// + private readonly int kernelSize; + + /// + /// The vertical kernel + /// + private float[,] kernelY; + + /// + /// The horizontal kernel + /// + private float[,] kernelX; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The 'radius' value representing the size of the area to sample. + /// + public BoxBlurProcessor(int radius = 7) + { + this.kernelSize = (radius * 2) + 1; + } + + /// + public override float[,] KernelX => this.kernelX; + + /// + public override float[,] KernelY => this.kernelY; + + /// + protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) + { + if (this.kernelY == null) + { + this.kernelY = this.CreateBoxKernel(false); + } + + if (this.kernelX == null) + { + this.kernelX = this.CreateBoxKernel(true); + } + } + + /// + /// Create a 1 dimensional Box kernel. + /// + /// Whether to calculate a horizontal kernel. + /// The + private float[,] CreateBoxKernel(bool horizontal) + { + int size = this.kernelSize; + float[,] kernel = horizontal ? new float[1, size] : new float[size, 1]; + float sum = 0.0f; + + for (int i = 0; i < size; i++) + { + float x = 1; + sum += x; + if (horizontal) + { + kernel[0, i] = x; + } + else + { + kernel[i, 0] = x; + } + } + + // Normalise kernel so that the sum of all weights equals 1 + if (horizontal) + { + for (int i = 0; i < size; i++) + { + kernel[0, i] = kernel[0, i] / sum; + } + } + else + { + for (int i = 0; i < size; i++) + { + kernel[i, 0] = kernel[i, 0] / sum; + } + } + + return kernel; + } + } +} 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 From 216d2af3b33cdb160c4993efee0c645bb7151e7f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 29 Jul 2016 11:44:01 +1000 Subject: [PATCH 75/91] BoxBlur missed file Former-commit-id: 958ce2c8cbd0ac27babbc39493ec77b42a926d85 Former-commit-id: 07d296a044d712bd0390558aec74fafc643a390e Former-commit-id: 2a64e2f642fafc3e8689dfa3e01165b085057c35 --- src/ImageProcessorCore/Filters/BoxBlur.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ImageProcessorCore/Filters/BoxBlur.cs b/src/ImageProcessorCore/Filters/BoxBlur.cs index 527950a209..e1970228f3 100644 --- a/src/ImageProcessorCore/Filters/BoxBlur.cs +++ b/src/ImageProcessorCore/Filters/BoxBlur.cs @@ -1,6 +1,7 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. +// namespace ImageProcessorCore { From 254e4ae4e1c4e37de7617b8f26289313e6b7657c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 29 Jul 2016 16:29:28 +1000 Subject: [PATCH 76/91] Edge detection Former-commit-id: c2a7aaf6443e721d1ef18fff5a7c8e9cf91b962a Former-commit-id: 5022db552fc42c6dabdc66b65e96e604f2222013 Former-commit-id: a9a34ac649b51cb6cedaabc13d7a31b1bb88aed3 --- src/ImageProcessorCore/Filters/DetectEdges.cs | 131 ++++++++++++++++++ .../Filters/Options/ColorBlindness.cs | 2 +- .../Filters/Options/EdgeDetection.cs | 58 ++++++++ .../Filters/Options/GreyscaleMode.cs | 4 +- .../EdgeDetection/IEdgeDetectorFilter.cs | 9 +- .../EdgeDetection/KayyaliProcessor.cs | 34 +++++ .../EdgeDetection/KirschProcessor.cs | 34 +++++ .../EdgeDetection/Laplacian3X3Processor.cs | 26 ++++ .../EdgeDetection/Laplacian5X5Processor.cs | 28 ++++ .../LaplacianOfGaussianProcessor.cs | 28 ++++ .../EdgeDetection/PrewittProcessor.cs | 34 +++++ .../EdgeDetection/RobertsCrossProcessor.cs | 32 +++++ .../EdgeDetection/ScharrProcessor.cs | 34 +++++ .../EdgeDetection/SobelProcessor.cs | 2 + .../Processors/Filters/DetectEdgesTest.cs | 53 +++++++ 15 files changed, 505 insertions(+), 4 deletions(-) create mode 100644 src/ImageProcessorCore/Filters/DetectEdges.cs create mode 100644 src/ImageProcessorCore/Filters/Options/EdgeDetection.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KirschProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/PrewittProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/ScharrProcessor.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/DetectEdgesTest.cs diff --git a/src/ImageProcessorCore/Filters/DetectEdges.cs b/src/ImageProcessorCore/Filters/DetectEdges.cs new file mode 100644 index 0000000000..2912e04f72 --- /dev/null +++ b/src/ImageProcessorCore/Filters/DetectEdges.cs @@ -0,0 +1,131 @@ +// +// 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 + { + /// + /// Detects any edges within the image. Uses the filter + /// operating in greyscale 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) + where T : IPackedVector + where TP : struct + { + return DetectEdges(source, source.Bounds, new SobelProcessor { Greyscale = 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 greyscale first. Defaults to true. + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image DetectEdges(this Image source, EdgeDetection filter, bool greyscale = true, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + IEdgeDetectorFilter processor; + + switch (filter) + { + case EdgeDetection.Kayyali: + processor = new KayyaliProcessor { Greyscale = greyscale }; + break; + + case EdgeDetection.Kirsch: + processor = new KirschProcessor { Greyscale = greyscale }; + break; + + case EdgeDetection.Lapacian3X3: + processor = new Laplacian3X3Processor { Greyscale = greyscale }; + break; + + case EdgeDetection.Lapacian5X5: + processor = new Laplacian5X5Processor { Greyscale = greyscale }; + break; + + case EdgeDetection.LaplacianOfGaussian: + processor = new LaplacianOfGaussianProcessor { Greyscale = greyscale }; + break; + + case EdgeDetection.Prewitt: + processor = new PrewittProcessor { Greyscale = greyscale }; + break; + + case EdgeDetection.RobertsCross: + processor = new RobertsCrossProcessor { Greyscale = greyscale }; + break; + + case EdgeDetection.Scharr: + processor = new ScharrProcessor { Greyscale = greyscale }; + break; + + default: + processor = new ScharrProcessor { Greyscale = greyscale }; + 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); + } + + /// + /// Detects any edges within the image. + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// 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) + where T : IPackedVector + where TP : struct + { + filter.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, filter); + } + finally + { + filter.OnProgress -= progressHandler; + } + } + } +} 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/Options/GreyscaleMode.cs b/src/ImageProcessorCore/Filters/Options/GreyscaleMode.cs index 269c1179ef..e1cc5917ec 100644 --- a/src/ImageProcessorCore/Filters/Options/GreyscaleMode.cs +++ b/src/ImageProcessorCore/Filters/Options/GreyscaleMode.cs @@ -3,10 +3,10 @@ // 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 greyscale filters. /// public enum GreyscaleMode { diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs index d2e2979f9c..ef0ceea9b4 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs @@ -8,9 +8,16 @@ 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 diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs new file mode 100644 index 0000000000..59327467ee --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// The Kayyali operator filter. + /// + /// + /// The pixel format. + /// The packed format. long, float. + public class KayyaliProcessor : EdgeDetector2DFilter + where T : IPackedVector + where TP : struct + { + /// + public override float[,] KernelX => new float[,] + { + { 6, 0, -6 }, + { 0, 0, 0 }, + { -6, 0, 6 } + }; + + /// + public override float[,] KernelY => new float[,] + { + { -6, 0, 6 }, + { 0, 0, 0 }, + { 6, 0, -6 } + }; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KirschProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KirschProcessor.cs new file mode 100644 index 0000000000..aba7e26d25 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/KirschProcessor.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// The Kirsch operator filter. + /// + /// + /// The pixel format. + /// The packed format. long, float. + public class KirschProcessor : EdgeDetector2DFilter + where T : IPackedVector + where TP : struct + { + /// + public override float[,] KernelX => new float[,] + { + { 5, 5, 5 }, + { -3, 0, -3 }, + { -3, -3, -3 } + }; + + /// + public override float[,] KernelY => new float[,] + { + { 5, -3, -3 }, + { 5, 0, -3 }, + { 5, -3, -3 } + }; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs new file mode 100644 index 0000000000..31829a4220 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// The Laplacian 3 x 3 operator filter. + /// + /// + /// The pixel format. + /// The packed format. long, float. + public class Laplacian3X3Processor : EdgeDetectorFilter + where T : IPackedVector + where TP : struct + { + /// + public override float[,] KernelXY => new float[,] + { + { -1, -1, -1 }, + { -1, 8, -1 }, + { -1, -1, -1 } + }; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs new file mode 100644 index 0000000000..d43876b56f --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// The Laplacian 5 x 5 operator filter. + /// + /// + /// The pixel format. + /// The packed format. long, float. + public class Laplacian5X5Processor : EdgeDetectorFilter + where T : IPackedVector + where TP : struct + { + /// + public override float[,] KernelXY => new float[,] + { + { -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1 }, + { -1, -1, 24, -1, -1 }, + { -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1 } + }; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs new file mode 100644 index 0000000000..e470136100 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// The Laplacian of Gaussian operator filter. + /// + /// + /// The pixel format. + /// The packed format. long, float. + public class LaplacianOfGaussianProcessor : EdgeDetectorFilter + where T : IPackedVector + where TP : struct + { + /// + public override float[,] KernelXY => new float[,] + { + { 0, 0, -1, 0, 0 }, + { 0, -1, -2, -1, 0 }, + { -1, -2, 16, -2, -1 }, + { 0, -1, -2, -1, 0 }, + { 0, 0, -1, 0, 0 } + }; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/PrewittProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/PrewittProcessor.cs new file mode 100644 index 0000000000..4ce4c7675c --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/PrewittProcessor.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// The Prewitt operator filter. + /// + /// + /// The pixel format. + /// The packed format. long, float. + public class PrewittProcessor : EdgeDetector2DFilter + where T : IPackedVector + where TP : struct + { + /// + public override float[,] KernelX => new float[,] + { + { -1, 0, 1 }, + { -1, 0, 1 }, + { -1, 0, 1 } + }; + + /// + public override float[,] KernelY => new float[,] + { + { 1, 1, 1 }, + { 0, 0, 0 }, + { -1, -1, -1 } + }; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs new file mode 100644 index 0000000000..b933280c60 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// The Roberts Cross operator filter. + /// + /// + /// The pixel format. + /// The packed format. long, float. + public class RobertsCrossProcessor : EdgeDetector2DFilter + where T : IPackedVector + where TP : struct + { + /// + public override float[,] KernelX => new float[,] + { + { 1, 0 }, + { 0, -1 } + }; + + /// + public override float[,] KernelY => new float[,] + { + { 0, 1 }, + { -1, 0 } + }; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/ScharrProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/ScharrProcessor.cs new file mode 100644 index 0000000000..1dd4408a01 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/ScharrProcessor.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + /// + /// The Scharr operator filter. + /// + /// + /// The pixel format. + /// The packed format. long, float. + public class ScharrProcessor : EdgeDetector2DFilter + where T : IPackedVector + where TP : struct + { + /// + public override float[,] KernelX => new float[,] + { + { -3, 0, 3 }, + { -10, 0, 10 }, + { -3, 0, 3 } + }; + + /// + public override float[,] KernelY => new float[,] + { + { 3, 10, 3 }, + { 0, 0, 0 }, + { -3, -10, -3 } + }; + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs index a323b7cfe5..9459e2ed8d 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs @@ -9,6 +9,8 @@ namespace ImageProcessorCore.Processors /// The Sobel operator filter. /// /// + /// The pixel format. + /// The packed format. long, float. public class SobelProcessor : EdgeDetector2DFilter where T : IPackedVector where TP : struct 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 From ec50f7ab4f96315fc3850187362c2a08798ae601 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 29 Jul 2016 16:50:37 +1000 Subject: [PATCH 77/91] Alpha Former-commit-id: a4b0ac45cb416e863496495ff9ac3d71585a46e6 Former-commit-id: ea2e5a6d2315d14f6ed566ff6c91b73c83b1feb5 Former-commit-id: 8a3cf0d04075c5da03b5d1ab747013d1efdd6491 --- src/ImageProcessorCore/Filters/Alpha.cs | 60 +++++++++++++++ .../Filters/Processors/AlphaProcessor.cs | 75 +++++++++++++++++++ .../Filters/Processors/BrightnessProcessor.cs | 2 +- .../Processors/Filters/AlphaTest.cs | 47 ++++++++++++ 4 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 src/ImageProcessorCore/Filters/Alpha.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/AlphaTest.cs diff --git a/src/ImageProcessorCore/Filters/Alpha.cs b/src/ImageProcessorCore/Filters/Alpha.cs new file mode 100644 index 0000000000..545639afa0 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Alpha.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 + { + /// + /// 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) + where T : IPackedVector + where TP : struct + { + return Alpha(source, percent, source.Bounds, progressHandler); + } + + /// + /// 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. + /// + /// 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 Alpha(this Image source, int percent, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + AlphaProcessor processor = new AlphaProcessor(percent); + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs b/src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs new file mode 100644 index 0000000000..b4595ac4b2 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs @@ -0,0 +1,75 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + using System.Threading.Tasks; + + /// + /// An to change the Alpha of an . + /// + /// 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. + /// + /// The percentage to adjust the opacity of the image. Must be between 0 and 100. + /// + /// is less than 0 or is greater than 100. + /// + public AlphaProcessor(int percent) + { + Guard.MustBeBetweenOrEqualTo(percent, 0, 100, nameof(percent)); + this.Value = percent; + } + + /// + /// Gets the alpha value. + /// + public int Value { get; } + + /// + 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; + int sourceBottom = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + Vector4 alphaVector = new Vector4(1, 1, 1, alpha); + + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => + { + if (y >= sourceY && y < sourceBottom) + { + for (int x = startX; x < endX; x++) + { + Vector4 color = sourcePixels[x, y].ToVector4(); + color *= alphaVector; + + 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 3b3be22a51..f9197d1432 100644 --- a/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs @@ -9,7 +9,7 @@ namespace ImageProcessorCore.Processors using System.Threading.Tasks; /// - /// An to change the brightness of an . + /// An to change the brightness of an . /// /// The pixel format. /// The packed format. long, float. 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 From a8af901bf931ba55ba1b64ed10d7ae21f113a901 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 30 Jul 2016 10:07:59 +1000 Subject: [PATCH 78/91] BackgroundColor Former-commit-id: abafddef9276f279c204a312c097cd28aa1d3e58 Former-commit-id: 27079155a41cd95690c7b676601e9df4044a05ca Former-commit-id: 0c77f74fd0194f4bab444d9bb4522422f65ad029 --- .../Filters/BackgroundColor.cs | 41 +++++++++ .../Processors/BackgroundColorProcessor.cs | 84 +++++++++++++++++++ .../Processors/Filters/BackgroundColorTest.cs | 38 +++++++++ 3 files changed, 163 insertions(+) create mode 100644 src/ImageProcessorCore/Filters/BackgroundColor.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/BackgroundColorProcessor.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/BackgroundColorTest.cs diff --git a/src/ImageProcessorCore/Filters/BackgroundColor.cs b/src/ImageProcessorCore/Filters/BackgroundColor.cs new file mode 100644 index 0000000000..44f46598b5 --- /dev/null +++ b/src/ImageProcessorCore/Filters/BackgroundColor.cs @@ -0,0 +1,41 @@ +// +// 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 + { + /// + /// 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, T color, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + BackgroundColorProcessor processor = new BackgroundColorProcessor(color); + processor.OnProgress += progressHandler; + + try + { + return source.Process(source.Bounds, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/BackgroundColorProcessor.cs b/src/ImageProcessorCore/Filters/Processors/BackgroundColorProcessor.cs new file mode 100644 index 0000000000..cc59d2d4fc --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/BackgroundColorProcessor.cs @@ -0,0 +1,84 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + /// + /// Sets the background color of the image. + /// + public class BackgroundColorProcessor : ImageProcessor + where T : IPackedVector + where TP : struct + { + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.001f; + + /// + /// Initializes a new instance of the class. + /// + /// The to set the background color to. + public BackgroundColorProcessor(T color) + { + this.Value = color; + } + + /// + /// Gets the background color value. + /// + public T Value { get; } + + /// + 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; + Vector4 backgroundColor = this.Value.ToVector4(); + + 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 = sourcePixels[x, y].ToVector4(); + float a = color.W; + + if (a < 1 && a > 0) + { + color = Vector4.Lerp(color, backgroundColor, .5f); + } + + if (Math.Abs(a) < Epsilon) + { + color = backgroundColor; + } + + T packed = default(T); + packed.PackVector(color); + targetPixels[x, y] = packed; + } + + this.OnRowProcessed(); + } + }); + } + } + } +} 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 From b1dd1b8693f8a193f0979f7591cc2265c6f85806 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 30 Jul 2016 10:08:14 +1000 Subject: [PATCH 79/91] Cleanup comments Former-commit-id: 8880134094b7c7af23c0946203a9f66cdded936f Former-commit-id: 2ea9df3c4bf904ce6d9d26a9c65d6f0c1acd53cd Former-commit-id: 326d76e008a4824b3c4335f1684e03b504ecc691 --- src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs | 4 ++-- .../Filters/Processors/Binarization/ThresholdProcessor.cs | 2 +- .../Filters/Processors/BrightnessProcessor.cs | 2 +- .../Filters/Processors/ColorMatrix/HueProcessor.cs | 2 +- .../Filters/Processors/ColorMatrix/SaturationProcessor.cs | 2 +- .../Filters/Processors/ContrastProcessor.cs | 2 +- src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs b/src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs index b4595ac4b2..5e66fed5a1 100644 --- a/src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs @@ -9,7 +9,7 @@ namespace ImageProcessorCore.Processors using System.Threading.Tasks; /// - /// An to change the Alpha of an . + /// An to change the Alpha of an . /// /// The pixel format. /// The packed format. long, float. @@ -18,7 +18,7 @@ namespace ImageProcessorCore.Processors 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. /// diff --git a/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs index 9315c0b16a..63b3e3d3df 100644 --- a/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs @@ -8,7 +8,7 @@ namespace ImageProcessorCore.Processors using System.Threading.Tasks; /// - /// An to perform binary threshold filtering against an + /// An to perform binary threshold filtering against an /// . The image will be converted to greyscale before thresholding /// occurs. /// diff --git a/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs b/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs index f9197d1432..1b35f836be 100644 --- a/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs @@ -9,7 +9,7 @@ namespace ImageProcessorCore.Processors using System.Threading.Tasks; /// - /// An to change the brightness of an . + /// An to change the brightness of an . /// /// The pixel format. /// The packed format. long, float. diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs index 03701483ec..ab68741696 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/HueProcessor.cs @@ -9,7 +9,7 @@ namespace ImageProcessorCore.Processors using System.Numerics; /// - /// An to change the hue of an . + /// An to change the hue of an . /// /// The pixel format. /// The packed format. long, float. diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs index 583cc6eab2..1afd3db5ff 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/SaturationProcessor.cs @@ -8,7 +8,7 @@ namespace ImageProcessorCore.Processors using System.Numerics; /// - /// An to change the saturation of an . + /// An to change the saturation of an . /// /// The pixel format. /// The packed format. long, float. diff --git a/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs index 01aa128d3a..060dc75778 100644 --- a/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs @@ -9,7 +9,7 @@ namespace ImageProcessorCore.Processors using System.Threading.Tasks; /// - /// An to change the contrast of an . + /// An to change the contrast of an . /// /// The pixel format. /// The packed format. long, float. diff --git a/src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs b/src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs index 08cdb8f242..d3fb82ef28 100644 --- a/src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs @@ -9,7 +9,7 @@ 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 where T : IPackedVector From e68247d9fcd7c945295fcbfad94ce2fdc1582eda Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 30 Jul 2016 10:16:50 +1000 Subject: [PATCH 80/91] Use global parallel options Former-commit-id: 330b88b731105c42a7664c108f8c626dbe8a6259 Former-commit-id: 502758b5da2f45a24fd54520be0099a29c3b500d Former-commit-id: 69363a78966bbc994ea8ef2a9aa0896a1f585623 --- src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs | 1 + .../Filters/Processors/Binarization/ThresholdProcessor.cs | 1 + src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs | 1 + .../Filters/Processors/ColorMatrix/ColorMatrixFilter.cs | 1 + src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs | 1 + .../Filters/Processors/Convolution/Convolution2DFilter.cs | 1 + .../Filters/Processors/Convolution/Convolution2PassFilter.cs | 1 + .../Filters/Processors/Convolution/ConvolutionFilter.cs | 1 + src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs | 1 + src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs | 1 + src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs | 1 + 11 files changed, 11 insertions(+) diff --git a/src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs b/src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs index 5e66fed5a1..b2a2520c3b 100644 --- a/src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs @@ -51,6 +51,7 @@ namespace ImageProcessorCore.Processors Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { if (y >= sourceY && y < sourceBottom) diff --git a/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs index 63b3e3d3df..38e32415dc 100644 --- a/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs @@ -72,6 +72,7 @@ namespace ImageProcessorCore.Processors Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { if (y >= sourceY && y < sourceBottom) diff --git a/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs b/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs index 1b35f836be..bedbd6b71d 100644 --- a/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs @@ -50,6 +50,7 @@ namespace ImageProcessorCore.Processors Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { if (y >= sourceY && y < sourceBottom) diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs index 47145c23ec..dddb1b2b34 100644 --- a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs @@ -37,6 +37,7 @@ namespace ImageProcessorCore.Processors Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { for (int x = startX; x < endX; x++) diff --git a/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs index 060dc75778..1415027209 100644 --- a/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs @@ -52,6 +52,7 @@ namespace ImageProcessorCore.Processors Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { if (y >= sourceY && y < sourceBottom) diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs index 3638c1384f..1113d0640a 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2DFilter.cs @@ -53,6 +53,7 @@ namespace ImageProcessorCore.Processors Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { if (y >= sourceY && y < sourceBottom) diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2PassFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2PassFilter.cs index 231929bc2a..6e1fb78419 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2PassFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/Convolution2PassFilter.cs @@ -69,6 +69,7 @@ namespace ImageProcessorCore.Processors Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { for (int x = startX; x < endX; x++) diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/ConvolutionFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/ConvolutionFilter.cs index ced19ababe..eb0c454b44 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/ConvolutionFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/ConvolutionFilter.cs @@ -40,6 +40,7 @@ namespace ImageProcessorCore.Processors Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { if (y >= sourceY && y < sourceBottom) diff --git a/src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs b/src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs index fdffcd7809..6215393810 100644 --- a/src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs @@ -58,6 +58,7 @@ namespace ImageProcessorCore.Processors Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { for (int x = startX; x < endX; x++) diff --git a/src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs b/src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs index d3fb82ef28..3d8465fa4c 100644 --- a/src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs @@ -30,6 +30,7 @@ namespace ImageProcessorCore.Processors Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { if (y >= sourceY && y < sourceBottom) diff --git a/src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs b/src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs index 2afd9e00cd..7e7f02317e 100644 --- a/src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs @@ -58,6 +58,7 @@ namespace ImageProcessorCore.Processors Parallel.For( startY, endY, + Bootstrapper.Instance.ParallelOptions, y => { for (int x = startX; x < endX; x++) From b8cc804a531398fa18c656e201ad47343a92be02 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 30 Jul 2016 10:35:53 +1000 Subject: [PATCH 81/91] Pixelate Former-commit-id: e1687a44c91ad29a9a20aa0ac0a5f6bf640c7202 Former-commit-id: d536480c39f7f575a40279ab96f7d7312a9dee91 Former-commit-id: 2ca77c98a64414096b01f4e41221ab17bea2ddeb --- src/ImageProcessorCore/Filters/Pixelate.cs | 64 ++++++++++++ .../Filters/Processors/PixelateProcessor.cs | 97 +++++++++++++++++++ .../Processors/Filters/PixelateTest.cs | 47 +++++++++ 3 files changed, 208 insertions(+) create mode 100644 src/ImageProcessorCore/Filters/Pixelate.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/PixelateProcessor.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/PixelateTest.cs diff --git a/src/ImageProcessorCore/Filters/Pixelate.cs b/src/ImageProcessorCore/Filters/Pixelate.cs new file mode 100644 index 0000000000..8bfb7cd2d5 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Pixelate.cs @@ -0,0 +1,64 @@ +// +// 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. + /// + /// The pixel format. + /// The packed format. long, float. + public static partial class ImageExtensions + { + /// + /// Pixelates and image with the given pixel size. + /// + /// 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) + where T : IPackedVector + where TP : struct + { + return Pixelate(source, size, source.Bounds, progressHandler); + } + + /// + /// Pixelates and image with the given pixel size. + /// + /// The image this method extends. + /// The size of the pixels. + /// + /// 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) + where T : IPackedVector + where TP : struct + { + if (size <= 0 || size > source.Height || size > source.Width) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + PixelateProcessor processor = new PixelateProcessor(size); + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/PixelateProcessor.cs b/src/ImageProcessorCore/Filters/Processors/PixelateProcessor.cs new file mode 100644 index 0000000000..816ce2f42b --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/PixelateProcessor.cs @@ -0,0 +1,97 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Collections.Generic; + using System.Threading.Tasks; + + /// + /// An to invert the colors of an . + /// + /// 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. + /// + /// The size of the pixels. Must be greater than 0. + /// + /// is less than 0 or equal to 0. + /// + public PixelateProcessor(int size) + { + Guard.MustBeGreaterThan(size, 0, nameof(size)); + this.Value = size; + } + + /// + /// Gets or the pixel size. + /// + public int Value { get; } + + /// + 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; + int size = this.Value; + int offset = this.Value / 2; + + // Get the range on the y-plane to choose from. + IEnumerable range = EnumerableExtensions.SteppedRange(startY, i => i < endY, size); + + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + { + Parallel.ForEach( + range, + y => + { + if (y >= sourceY && y < sourceBottom) + { + for (int x = startX; x < endX; x += size) + { + int offsetX = offset; + int offsetY = offset; + + // Make sure that the offset is within the boundary of the + // image. + while (y + offsetY >= sourceBottom) + { + offsetY--; + } + + while (x + offsetX >= endX) + { + offsetX--; + } + + // Get the pixel color in the centre of the soon to be pixelated area. + // ReSharper disable AccessToDisposedClosure + 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++) + { + for (int k = x; k < x + size && k < endX; k++) + { + targetPixels[k, l] = pixel; + } + } + } + + this.OnRowProcessed(); + } + }); + } + } + } +} 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 From cce2265f7ec1560ed5567d549f634f63e8ebbf4d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 30 Jul 2016 10:53:14 +1000 Subject: [PATCH 82/91] Blend Former-commit-id: ff1aec34e42b49f728dfd7f370f709c48be34762 Former-commit-id: ee75403396e61e34b0d97760ee36634973d5a5ec Former-commit-id: 326da67dbfdb92272ab63c08e30a9e85904f7e60 --- src/ImageProcessorCore/Filters/Blend.cs | 62 +++++++++++++ .../Filters/Processors/BlendProcessor.cs | 93 +++++++++++++++++++ .../Processors/Filters/BlendTest.cs | 44 +++++++++ 3 files changed, 199 insertions(+) create mode 100644 src/ImageProcessorCore/Filters/Blend.cs create mode 100644 src/ImageProcessorCore/Filters/Processors/BlendProcessor.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/BlendTest.cs diff --git a/src/ImageProcessorCore/Filters/Blend.cs b/src/ImageProcessorCore/Filters/Blend.cs new file mode 100644 index 0000000000..96f8f60cda --- /dev/null +++ b/src/ImageProcessorCore/Filters/Blend.cs @@ -0,0 +1,62 @@ +// +// 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 + { + /// + /// 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. + /// 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) + where T : IPackedVector + where TP : struct + { + return Blend(source, image, percent, source.Bounds, progressHandler); + } + + /// + /// 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. + /// 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) + where T : IPackedVector + where TP : struct + { + BlendProcessor processor = new BlendProcessor(image, percent); + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/BlendProcessor.cs b/src/ImageProcessorCore/Filters/Processors/BlendProcessor.cs new file mode 100644 index 0000000000..56c754729a --- /dev/null +++ b/src/ImageProcessorCore/Filters/Processors/BlendProcessor.cs @@ -0,0 +1,93 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Processors +{ + using System.Numerics; + using System.Threading.Tasks; + + /// + /// Combines two images together by blending the pixels. + /// + /// 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; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The image to blend with the currently processing image. + /// 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) + { + Guard.MustBeBetweenOrEqualTo(alpha, 0, 100, nameof(alpha)); + this.blend = image; + this.Value = alpha; + } + + /// + /// Gets the alpha percentage value. + /// + public int Value { get; } + + /// + 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; + Rectangle bounds = this.blend.Bounds; + float alpha = this.Value / 100f; + + using (IPixelAccessor toBlendPixels = this.blend.Lock()) + using (IPixelAccessor sourcePixels = source.Lock()) + using (IPixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + startY, + endY, + y => + { + if (y >= sourceY && y < sourceBottom) + { + for (int x = startX; x < endX; x++) + { + Vector4 color = sourcePixels[x, y].ToVector4(); + + if (bounds.Contains(x, y)) + { + Vector4 blendedColor = toBlendPixels[x, y].ToVector4(); + + if (blendedColor.W > 0) + { + // Lerping colors is dependent on the alpha of the blended color + float alphaFactor = alpha > 0 ? alpha : blendedColor.W; + color = Vector4.Lerp(color, blendedColor, alphaFactor); + } + } + + T packed = default(T); + packed.PackVector(color); + targetPixels[x, y] = packed; + } + + this.OnRowProcessed(); + } + }); + } + } + } +} 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 From 1f3ba21f68b68b0f6b17fc54933fb8487094e491 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 30 Jul 2016 11:55:31 +1000 Subject: [PATCH 83/91] Better sampler tests Fixes thresholder. Former-commit-id: 38af3bd0e8028989997c45c12f865601e36e1368 Former-commit-id: b9e84eb7f0a56c5f896498f71838f790651b3398 Former-commit-id: 7c5d5789d6fca88d9b5f751124dbb9d24efd8ea2 --- .../Filters/BinaryThreshold.cs | 60 ++++ ...ocessor.cs => BinaryThresholdProcessor.cs} | 19 +- .../Processors/EntropyCropProcessor.cs | 2 +- .../Processors/Filters/BinaryThreshold.cs | 47 +++ .../Processors/Samplers/CropTest.cs | 38 +++ .../Processors/Samplers/EntropyCropTest.cs | 47 +++ .../Processors/Samplers/PadTest.cs | 38 +++ .../{SamplerTests.cs => ResizeTests.cs} | 280 ++++-------------- .../Processors/Samplers/RotateFlipTest.cs | 50 ++++ .../Processors/Samplers/RotateTest.cs | 47 +++ .../Processors/Samplers/SkewTest.cs | 49 +++ 11 files changed, 445 insertions(+), 232 deletions(-) create mode 100644 src/ImageProcessorCore/Filters/BinaryThreshold.cs rename src/ImageProcessorCore/Filters/Processors/Binarization/{ThresholdProcessor.cs => BinaryThresholdProcessor.cs} (86%) create mode 100644 tests/ImageProcessorCore.Tests/Processors/Filters/BinaryThreshold.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Samplers/CropTest.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Samplers/EntropyCropTest.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Samplers/PadTest.cs rename tests/ImageProcessorCore.Tests/Processors/Samplers/{SamplerTests.cs => ResizeTests.cs} (51%) create mode 100644 tests/ImageProcessorCore.Tests/Processors/Samplers/RotateFlipTest.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Samplers/RotateTest.cs create mode 100644 tests/ImageProcessorCore.Tests/Processors/Samplers/SkewTest.cs 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/Processors/Binarization/ThresholdProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Binarization/BinaryThresholdProcessor.cs similarity index 86% rename from src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs rename to src/ImageProcessorCore/Filters/Processors/Binarization/BinaryThresholdProcessor.cs index 38e32415dc..78da97f111 100644 --- a/src/ImageProcessorCore/Filters/Processors/Binarization/ThresholdProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/Binarization/BinaryThresholdProcessor.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -14,7 +14,7 @@ namespace ImageProcessorCore.Processors ///
/// The pixel format. /// The packed format. long, float. - public class ThresholdProcessor : ImageProcessor + public class BinaryThresholdProcessor : ImageProcessor where T : IPackedVector where TP : struct { @@ -25,13 +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; - this.UpperColor.PackVector(Color.White.ToVector4()); - this.LowerColor.PackVector(Color.Black.ToVector4()); + + T upper = default(T); + upper.PackVector(Color.White.ToVector4()); + this.UpperColor = upper; + + T lower = default(T); + lower.PackVector(Color.Black.ToVector4()); + this.LowerColor = lower; } /// @@ -58,6 +64,9 @@ namespace ImageProcessorCore.Processors /// 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; T upper = this.UpperColor; T lower = this.LowerColor; diff --git a/src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs index 29afff11a1..6cd7726c92 100644 --- a/src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs @@ -48,7 +48,7 @@ namespace ImageProcessorCore.Processors 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); 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/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/SamplerTests.cs b/tests/ImageProcessorCore.Tests/Processors/Samplers/ResizeTests.cs similarity index 51% rename from tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs rename to tests/ImageProcessorCore.Tests/Processors/Samplers/ResizeTests.cs index 0cb8ff0258..dbd36bee7a 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Samplers/ResizeTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -9,8 +9,10 @@ namespace ImageProcessorCore.Tests using Xunit; - public class SamplerTests : FileTestBase + public class ResizeTests : FileTestBase { + private const string path = "TestOutput/Resize"; + public static readonly TheoryData ReSamplers = new TheoryData { @@ -31,46 +33,13 @@ namespace ImageProcessorCore.Tests //{ "Welch", new WelchResampler() } }; - 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 }, - }; - - [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")) + if (!Directory.Exists(path)) { - Directory.CreateDirectory("TestOutput/Resize"); + Directory.CreateDirectory(path); } foreach (string file in Files) @@ -80,7 +49,7 @@ namespace ImageProcessorCore.Tests string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) + using (FileStream output = File.OpenWrite($"{path}/{filename}")) { image.Resize(image.Width / 2, image.Height / 2, sampler, false, this.ProgressUpdate) .Save(output); @@ -92,13 +61,13 @@ namespace ImageProcessorCore.Tests [Fact] public void ImageShouldResizeWidthAndKeepAspect() { - if (!Directory.Exists("TestOutput/Resize")) + const string name = "FixedWidth"; + + if (!Directory.Exists(path)) { - Directory.CreateDirectory("TestOutput/Resize"); + Directory.CreateDirectory(path); } - var name = "FixedWidth"; - foreach (string file in Files) { using (FileStream stream = File.OpenRead(file)) @@ -106,7 +75,7 @@ namespace ImageProcessorCore.Tests string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) + using (FileStream output = File.OpenWrite($"{path}/{filename}")) { image.Resize(image.Width / 3, 0, new TriangleResampler(), false, this.ProgressUpdate) .Save(output); @@ -118,13 +87,13 @@ namespace ImageProcessorCore.Tests [Fact] public void ImageShouldResizeHeightAndKeepAspect() { - if (!Directory.Exists("TestOutput/Resize")) + const string name = "FixedHeight"; + + if (!Directory.Exists(path)) { - Directory.CreateDirectory("TestOutput/Resize"); + Directory.CreateDirectory(path); } - string name = "FixedHeight"; - foreach (string file in Files) { using (FileStream stream = File.OpenRead(file)) @@ -132,7 +101,7 @@ namespace ImageProcessorCore.Tests string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}")) + using (FileStream output = File.OpenWrite($"{path}/{filename}")) { image.Resize(0, image.Height / 3, new TriangleResampler(), false, this.ProgressUpdate) .Save(output); @@ -144,19 +113,21 @@ namespace ImageProcessorCore.Tests [Fact] public void ImageShouldResizeWithCropMode() { - if (!Directory.Exists("TestOutput/ResizeCrop")) + const string name = "Crop"; + + if (!Directory.Exists(path)) { - Directory.CreateDirectory("TestOutput/ResizeCrop"); + Directory.CreateDirectory(path); } foreach (string file in Files) { using (FileStream stream = File.OpenRead(file)) { - string filename = Path.GetFileName(file); + string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/ResizeCrop/{filename}")) + using (FileStream output = File.OpenWrite($"{path}/{filename}")) { ResizeOptions options = new ResizeOptions() { @@ -173,19 +144,21 @@ namespace ImageProcessorCore.Tests [Fact] public void ImageShouldResizeWithPadMode() { - if (!Directory.Exists("TestOutput/ResizePad")) + const string name = "Pad"; + + if (!Directory.Exists(path)) { - Directory.CreateDirectory("TestOutput/ResizePad"); + Directory.CreateDirectory(path); } foreach (string file in Files) { using (FileStream stream = File.OpenRead(file)) { - string filename = Path.GetFileName(file); + string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/ResizePad/{filename}")) + using (FileStream output = File.OpenWrite($"{path}/{filename}")) { ResizeOptions options = new ResizeOptions() { @@ -203,19 +176,21 @@ namespace ImageProcessorCore.Tests [Fact] public void ImageShouldResizeWithBoxPadMode() { - if (!Directory.Exists("TestOutput/ResizeBoxPad")) + const string name = "BoxPad"; + + if (!Directory.Exists(path)) { - Directory.CreateDirectory("TestOutput/ResizeBoxPad"); + Directory.CreateDirectory(path); } foreach (string file in Files) { using (FileStream stream = File.OpenRead(file)) { - string filename = Path.GetFileName(file); + string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/ResizeBoxPad/{filename}")) + using (FileStream output = File.OpenWrite($"{path}/{filename}")) { ResizeOptions options = new ResizeOptions() { @@ -233,25 +208,26 @@ namespace ImageProcessorCore.Tests [Fact] public void ImageShouldResizeWithMaxMode() { - if (!Directory.Exists("TestOutput/ResizeMax")) + const string name = "Max"; + + if (!Directory.Exists(path)) { - Directory.CreateDirectory("TestOutput/ResizeMax"); + Directory.CreateDirectory(path); } foreach (string file in Files) { using (FileStream stream = File.OpenRead(file)) { - string filename = Path.GetFileName(file); + string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/ResizeMax/{filename}")) + using (FileStream output = File.OpenWrite($"{path}/{filename}")) { ResizeOptions options = new ResizeOptions() { Size = new Size(300, 300), - Mode = ResizeMode.Max, - //Sampler = new NearestNeighborResampler() + Mode = ResizeMode.Max }; image.Resize(options, this.ProgressUpdate) @@ -264,19 +240,21 @@ namespace ImageProcessorCore.Tests [Fact] public void ImageShouldResizeWithMinMode() { - if (!Directory.Exists("TestOutput/ResizeMin")) + const string name = "Min"; + + if (!Directory.Exists(path)) { - Directory.CreateDirectory("TestOutput/ResizeMin"); + Directory.CreateDirectory(path); } foreach (string file in Files) { using (FileStream stream = File.OpenRead(file)) { - string filename = Path.GetFileName(file); + string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/ResizeMin/{filename}")) + using (FileStream output = File.OpenWrite($"{path}/{filename}")) { ResizeOptions options = new ResizeOptions() { @@ -294,19 +272,21 @@ namespace ImageProcessorCore.Tests [Fact] public void ImageShouldResizeWithStretchMode() { - if (!Directory.Exists("TestOutput/ResizeStretch")) + const string name = "Stretch"; + + if (!Directory.Exists(path)) { - Directory.CreateDirectory("TestOutput/ResizeStretch"); + Directory.CreateDirectory(path); } foreach (string file in Files) { using (FileStream stream = File.OpenRead(file)) { - string filename = Path.GetFileName(file); + string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); Image image = new Image(stream); - using (FileStream output = File.OpenWrite($"TestOutput/ResizeStretch/{filename}")) + using (FileStream output = File.OpenWrite($"{path}/{filename}")) { ResizeOptions options = new ResizeOptions() { @@ -321,158 +301,6 @@ namespace ImageProcessorCore.Tests } } - //[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)] 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/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 From 8cc51c584d696921596bc219cef869be40e9c21e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 30 Jul 2016 11:57:47 +1000 Subject: [PATCH 84/91] Use parallel options Former-commit-id: aae845c84ad301e7d943131659014bae575fb5c6 Former-commit-id: 261f2e2042032e8c7ac8e4da0f18b44faa969b1c Former-commit-id: 1d4b93a2b7e871ef95a647915d2d30a459617d7e --- .../Samplers/Processors/RotateFlipProcessor.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs index 5ca46cd44a..3ea12c369a 100644 --- a/src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs @@ -86,6 +86,7 @@ namespace ImageProcessorCore.Processors Parallel.For( 0, height, + Bootstrapper.Instance.ParallelOptions, y => { for (int x = 0; x < width; x++) @@ -122,6 +123,7 @@ namespace ImageProcessorCore.Processors Parallel.For( 0, height, + Bootstrapper.Instance.ParallelOptions, y => { for (int x = 0; x < width; x++) @@ -155,6 +157,7 @@ namespace ImageProcessorCore.Processors Parallel.For( 0, height, + Bootstrapper.Instance.ParallelOptions, y => { for (int x = 0; x < width; x++) @@ -191,6 +194,7 @@ namespace ImageProcessorCore.Processors Parallel.For( 0, halfHeight, + Bootstrapper.Instance.ParallelOptions, y => { for (int x = 0; x < width; x++) @@ -226,6 +230,7 @@ namespace ImageProcessorCore.Processors Parallel.For( 0, height, + Bootstrapper.Instance.ParallelOptions, y => { for (int x = 0; x < halfWidth; x++) From a19bd1f024657fb66545ff7f151716a76a6a86be Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 30 Jul 2016 11:59:56 +1000 Subject: [PATCH 85/91] Add missing class docs Former-commit-id: 74c642cce368d8e8a917a71b05e06c9f59a665b8 Former-commit-id: 0b8453f330d8be2187f941ddbd4e9d81431e2cf0 Former-commit-id: 4b4a7e399c6662f0646ddb144b28fe71084a9ca8 --- .../Samplers/Processors/RotateFlipProcessor.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs b/src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs index 3ea12c369a..7a6259f79b 100644 --- a/src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs +++ b/src/ImageProcessorCore/Samplers/Processors/RotateFlipProcessor.cs @@ -11,6 +11,8 @@ namespace ImageProcessorCore.Processors /// /// Provides methods that allow the rotation and flipping of an image around its center point. /// + /// The pixel format. + /// The packed format. long, float. public class RotateFlipProcessor : ImageSampler where T : IPackedVector where TP : struct From 4717597e7096836acabe763c89716f2d463d3359 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 30 Jul 2016 19:37:16 +1000 Subject: [PATCH 86/91] Fix up colors Former-commit-id: 7d1bd38103443c71e8bfd1b0f0188e280302b410 Former-commit-id: 720bc4cad102119f67274389f0f15be803b24ce8 Former-commit-id: b1cf87199086fc50dcdd98b915de13e8c871c688 --- .../Colors/{PackedVector => }/Color.cs | 85 ++- .../Colors/ColorConstants.cs | 179 +++++++ .../{PackedVector => }/ColorDefinitions.cs | 0 .../Colors/ColorTransforms.cs | 74 +++ .../Colors/ColorspaceTransforms.cs | 292 +++++++++++ .../Colors/Colorspaces/Bgra32.cs | 167 ++++++ .../Colors/Colorspaces/CieLab.cs | 192 +++++++ .../Colors/Colorspaces/CieXyz.cs | 184 +++++++ .../Colors/Colorspaces/Cmyk.cs | 195 +++++++ .../Colors/Colorspaces/Hsl.cs | 213 ++++++++ .../Colors/Colorspaces/Hsv.cs | 206 ++++++++ .../Colors/Colorspaces/YCbCr.cs | 2 +- .../PackedVector/ColorspaceTransforms.cs | 285 ---------- src/ImageProcessorCore/Filters/DetectEdges.cs | 26 +- .../Filters/{Greyscale.cs => Grayscale.cs} | 18 +- .../{GreyscaleMode.cs => GrayscaleMode.cs} | 6 +- .../Binarization/BinaryThresholdProcessor.cs | 6 +- ...rocessor.cs => GrayscaleBt601Processor.cs} | 6 +- ...rocessor.cs => GrayscaleBt709Processor.cs} | 6 +- .../EdgeDetection/EdgeDetector2DFilter.cs | 6 +- .../EdgeDetection/EdgeDetectorFilter.cs | 6 +- .../EdgeDetection/IEdgeDetectorFilter.cs | 4 +- .../Formats/Jpg/JpegEncoderCore.cs | 2 +- .../Formats/Png/PngDecoder.cs | 4 +- .../Quantizers/Palette/PaletteQuantizer.cs | 145 ++++++ .../ImageProcessorCore.Tests/Colors/Class.cs | 109 ++++ .../Colors/ColorConversionTests.cs | 493 ++++++++++++++++++ .../{GreyscaleTest.cs => GrayscaleTest.cs} | 20 +- 28 files changed, 2578 insertions(+), 353 deletions(-) rename src/ImageProcessorCore/Colors/{PackedVector => }/Color.cs (72%) create mode 100644 src/ImageProcessorCore/Colors/ColorConstants.cs rename src/ImageProcessorCore/Colors/{PackedVector => }/ColorDefinitions.cs (100%) create mode 100644 src/ImageProcessorCore/Colors/ColorTransforms.cs create mode 100644 src/ImageProcessorCore/Colors/ColorspaceTransforms.cs create mode 100644 src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs create mode 100644 src/ImageProcessorCore/Colors/Colorspaces/CieLab.cs create mode 100644 src/ImageProcessorCore/Colors/Colorspaces/CieXyz.cs create mode 100644 src/ImageProcessorCore/Colors/Colorspaces/Cmyk.cs create mode 100644 src/ImageProcessorCore/Colors/Colorspaces/Hsl.cs create mode 100644 src/ImageProcessorCore/Colors/Colorspaces/Hsv.cs delete mode 100644 src/ImageProcessorCore/Colors/PackedVector/ColorspaceTransforms.cs rename src/ImageProcessorCore/Filters/{Greyscale.cs => Grayscale.cs} (76%) rename src/ImageProcessorCore/Filters/Options/{GreyscaleMode.cs => GrayscaleMode.cs} (72%) rename src/ImageProcessorCore/Filters/Processors/ColorMatrix/{GreyscaleBt601Processor.cs => GrayscaleBt601Processor.cs} (83%) rename src/ImageProcessorCore/Filters/Processors/ColorMatrix/{GreyscaleBt709Processor.cs => GrayscaleBt709Processor.cs} (80%) create mode 100644 src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs create mode 100644 tests/ImageProcessorCore.Tests/Colors/Class.cs create mode 100644 tests/ImageProcessorCore.Tests/Colors/ColorConversionTests.cs rename tests/ImageProcessorCore.Tests/Processors/Filters/{GreyscaleTest.cs => GrayscaleTest.cs} (66%) diff --git a/src/ImageProcessorCore/Colors/PackedVector/Color.cs b/src/ImageProcessorCore/Colors/Color.cs similarity index 72% rename from src/ImageProcessorCore/Colors/PackedVector/Color.cs rename to src/ImageProcessorCore/Colors/Color.cs index 4e4e817a85..69bce48536 100644 --- a/src/ImageProcessorCore/Colors/PackedVector/Color.cs +++ b/src/ImageProcessorCore/Colors/Color.cs @@ -58,7 +58,68 @@ namespace ImageProcessorCore /// The green component. /// The blue component. /// The alpha component. - public Color(float r, float g, float b, float a) + public Color(byte r, byte g, byte b, byte a = 255) + : this() + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The hexadecimal representation of the combined color components arranged + /// in rgb, rrggbb, or aarrggbb format to match web syntax. + /// + public Color(string hex) + : this() + { + // Hexadecimal representations are layed out AARRGGBB to we need to do some reordering. + hex = hex.StartsWith("#") ? hex.Substring(1) : hex; + + if (hex.Length != 8 && hex.Length != 6 && hex.Length != 3) + { + throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex)); + } + + if (hex.Length == 8) + { + 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) + { + 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 + { + string rh = char.ToString(hex[0]); + string gh = char.ToString(hex[1]); + string bh = char.ToString(hex[2]); + + 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. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + public Color(float r, float g, float b, float a = 1) : this() { Vector4 clamped = Vector4.Clamp(new Vector4(r, g, b, a), Vector4.Zero, Vector4.One) * 255F; @@ -71,17 +132,17 @@ namespace ImageProcessorCore /// /// 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) + /// + /// The vector containing the components for the packed vector. + /// + public Color(Vector3 vector) : this() { - this.R = r; - this.G = g; - this.B = b; - this.A = a; + 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; } /// @@ -94,9 +155,9 @@ namespace ImageProcessorCore : this() { Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255F; - this.B = (byte)Math.Round(clamped.X); + this.R = (byte)Math.Round(clamped.X); this.G = (byte)Math.Round(clamped.Y); - this.R = (byte)Math.Round(clamped.Z); + this.B = (byte)Math.Round(clamped.Z); this.A = (byte)Math.Round(clamped.W); } diff --git a/src/ImageProcessorCore/Colors/ColorConstants.cs b/src/ImageProcessorCore/Colors/ColorConstants.cs new file mode 100644 index 0000000000..ed9718423d --- /dev/null +++ b/src/ImageProcessorCore/Colors/ColorConstants.cs @@ -0,0 +1,179 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.Collections.Generic; + + /// + /// Provides useful color definitions. + /// + public static class ColorConstants + { + /// + /// Provides a lazy, one time method of returning the colors. + /// + private static readonly Lazy SafeColors = new Lazy(GetWebSafeColors); + + /// + /// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4. + /// + public static Color[] WebSafeColors => SafeColors.Value; + + /// + /// Returns an array of web safe colors. + /// + /// + private static Color[] GetWebSafeColors() + { + return new List + { + Color.AliceBlue, + Color.AntiqueWhite, + Color.Aqua, + Color.Aquamarine, + Color.Azure, + Color.Beige, + Color.Bisque, + Color.Black, + Color.BlanchedAlmond, + Color.Blue, + Color.BlueViolet, + Color.Brown, + Color.BurlyWood, + Color.CadetBlue, + Color.Chartreuse, + Color.Chocolate, + Color.Coral, + Color.CornflowerBlue, + Color.Cornsilk, + Color.Crimson, + Color.Cyan, + Color.DarkBlue, + Color.DarkCyan, + Color.DarkGoldenrod, + Color.DarkGray, + Color.DarkGreen, + Color.DarkKhaki, + Color.DarkMagenta, + Color.DarkOliveGreen, + Color.DarkOrange, + Color.DarkOrchid, + Color.DarkRed, + Color.DarkSalmon, + Color.DarkSeaGreen, + Color.DarkSlateBlue, + Color.DarkSlateGray, + Color.DarkTurquoise, + Color.DarkViolet, + Color.DeepPink, + Color.DeepSkyBlue, + Color.DimGray, + Color.DodgerBlue, + Color.Firebrick, + Color.FloralWhite, + Color.ForestGreen, + Color.Fuchsia, + Color.Gainsboro, + Color.GhostWhite, + Color.Gold, + Color.Goldenrod, + Color.Gray, + Color.Green, + Color.GreenYellow, + Color.Honeydew, + Color.HotPink, + Color.IndianRed, + Color.Indigo, + Color.Ivory, + Color.Khaki, + Color.Lavender, + Color.LavenderBlush, + Color.LawnGreen, + Color.LemonChiffon, + Color.LightBlue, + Color.LightCoral, + Color.LightCyan, + Color.LightGoldenrodYellow, + Color.LightGray, + Color.LightGreen, + Color.LightPink, + Color.LightSalmon, + Color.LightSeaGreen, + Color.LightSkyBlue, + Color.LightSlateGray, + Color.LightSteelBlue, + Color.LightYellow, + Color.Lime, + Color.LimeGreen, + Color.Linen, + Color.Magenta, + Color.Maroon, + Color.MediumAquamarine, + Color.MediumBlue, + Color.MediumOrchid, + Color.MediumPurple, + Color.MediumSeaGreen, + Color.MediumSlateBlue, + Color.MediumSpringGreen, + Color.MediumTurquoise, + Color.MediumVioletRed, + Color.MidnightBlue, + Color.MintCream, + Color.MistyRose, + Color.Moccasin, + Color.NavajoWhite, + Color.Navy, + Color.OldLace, + Color.Olive, + Color.OliveDrab, + Color.Orange, + Color.OrangeRed, + Color.Orchid, + Color.PaleGoldenrod, + Color.PaleGreen, + Color.PaleTurquoise, + Color.PaleVioletRed, + Color.PapayaWhip, + Color.PeachPuff, + Color.Peru, + Color.Pink, + Color.Plum, + Color.PowderBlue, + Color.Purple, + Color.RebeccaPurple, + Color.Red, + Color.RosyBrown, + Color.RoyalBlue, + Color.SaddleBrown, + Color.Salmon, + Color.SandyBrown, + Color.SeaGreen, + Color.SeaShell, + Color.Sienna, + Color.Silver, + Color.SkyBlue, + Color.SlateBlue, + Color.SlateGray, + Color.Snow, + Color.SpringGreen, + Color.SteelBlue, + Color.Tan, + Color.Teal, + Color.Thistle, + Color.Tomato, + Color.Transparent, + Color.Turquoise, + Color.Violet, + Color.Wheat, + Color.White, + Color.WhiteSmoke, + Color.Yellow, + Color.YellowGreen + }.ToArray(); + } + } +} diff --git a/src/ImageProcessorCore/Colors/PackedVector/ColorDefinitions.cs b/src/ImageProcessorCore/Colors/ColorDefinitions.cs similarity index 100% rename from src/ImageProcessorCore/Colors/PackedVector/ColorDefinitions.cs rename to src/ImageProcessorCore/Colors/ColorDefinitions.cs diff --git a/src/ImageProcessorCore/Colors/ColorTransforms.cs b/src/ImageProcessorCore/Colors/ColorTransforms.cs new file mode 100644 index 0000000000..cc05f6bb49 --- /dev/null +++ b/src/ImageProcessorCore/Colors/ColorTransforms.cs @@ -0,0 +1,74 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.Numerics; + + /// + /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 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 + { + /// + /// Blends two colors by multiplication. + /// + /// The source color is multiplied by the destination color and replaces the destination. + /// The resultant color is always at least as dark as either the source or destination color. + /// Multiplying any color with black results in black. Multiplying any color with white preserves the + /// original color. + /// + /// + /// The source color. + /// The destination color. + /// + /// The . + /// + public static Color Multiply(Color source, Color destination) + { + if (destination == Color.Black) + { + return Color.Black; + } + + if (destination == Color.White) + { + return source; + } + + // 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 weighting. + /// + /// The first color value. + /// The second color value. + /// + /// 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 from, Color to, float amount) + { + return new Color(Vector4.Lerp(from.ToVector4(), to.ToVector4(), amount)); + } + } +} diff --git a/src/ImageProcessorCore/Colors/ColorspaceTransforms.cs b/src/ImageProcessorCore/Colors/ColorspaceTransforms.cs new file mode 100644 index 0000000000..db7e7536fc --- /dev/null +++ b/src/ImageProcessorCore/Colors/ColorspaceTransforms.cs @@ -0,0 +1,292 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.Numerics; + + /// + /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 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 + { + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.001F; + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + public static implicit operator Color(Bgra32 color) + { + return new Color(color.R, color.G, color.B, color.A); + } + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + public static implicit operator Color(Cmyk cmykColor) + { + 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, 1); + } + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + public static implicit operator Color(YCbCr color) + { + float y = color.Y; + float cb = color.Cb - 128; + float cr = color.Cr - 128; + + 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, 255); + } + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + public static implicit operator Color(CieXyz color) + { + float x = color.X / 100F; + float y = color.Y / 100F; + float z = color.Z / 100F; + + // Then XYZ to RGB (multiplication by 100 was done above already) + float r = (x * 3.2406F) + (y * -1.5372F) + (z * -0.4986F); + float g = (x * -0.9689F) + (y * 1.8758F) + (z * 0.0415F); + float b = (x * 0.0557F) + (y * -0.2040F) + (z * 1.0570F); + + Vector4 vector = new Vector4(r, g, b, 1).Compress(); + return new Color(vector); + } + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + public static implicit operator Color(Hsv color) + { + float s = color.S; + float v = color.V; + + if (Math.Abs(s) < Epsilon) + { + return new Color(v, v, v, 1); + } + + float h = (Math.Abs(color.H - 360) < Epsilon) ? 0 : color.H / 60; + 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 r, g, b; + switch (i) + { + case 0: + r = v; + g = t; + b = p; + break; + + case 1: + r = q; + g = v; + b = p; + break; + + case 2: + r = p; + g = v; + b = t; + break; + + case 3: + r = p; + g = q; + b = v; + break; + + case 4: + r = t; + g = p; + b = v; + break; + + default: + r = v; + g = p; + b = q; + break; + } + + return new Color(r, g, b, 1); + } + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + public static implicit operator Color(Hsl color) + { + float rangedH = color.H / 360F; + float r = 0; + float g = 0; + float b = 0; + float s = color.S; + float l = color.L; + + if (Math.Abs(l) > Epsilon) + { + if (Math.Abs(s) < Epsilon) + { + r = g = b = l; + } + else + { + float temp2 = (l < 0.5f) ? l * (1f + s) : l + s - (l * s); + float temp1 = (2f * l) - temp2; + + r = GetColorComponent(temp1, temp2, rangedH + 0.3333333F); + g = GetColorComponent(temp1, temp2, rangedH); + b = GetColorComponent(temp1, temp2, rangedH - 0.3333333F); + } + } + + return new Color(r, g, b, 1); + } + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + public static implicit operator Color(CieLab cieLabColor) + { + // First convert back to XYZ... + float y = (cieLabColor.L + 16F) / 116F; + float x = (cieLabColor.A / 500F) + y; + float z = y - (cieLabColor.B / 200F); + + float x3 = x * x * x; + float y3 = y * y * y; + float z3 = z * z * z; + + x = x3 > 0.008856F ? x3 : (x - 0.137931F) / 7.787F; + y = (cieLabColor.L > 7.999625F) ? y3 : (cieLabColor.L / 903.3F); + z = (z3 > 0.008856F) ? z3 : (z - 0.137931F) / 7.787F; + + x *= 0.95047F; + z *= 1.08883F; + + // Then XYZ to RGB (multiplication by 100 was done above already) + float r = (x * 3.2406F) + (y * -1.5372F) + (z * -0.4986F); + float g = (x * -0.9689F) + (y * 1.8758F) + (z * 0.0415F); + float b = (x * 0.0557F) + (y * -0.2040F) + (z * 1.0570F); + + return new Color(new Vector4(r, g, b, 1F).Compress()); + } + + /// + /// Gets the color component from the given values. + /// + /// The first value. + /// The second value. + /// The third value. + /// + /// The . + /// + private static float GetColorComponent(float first, float second, float third) + { + third = MoveIntoRange(third); + if (third < 0.1666667F) + { + return first + ((second - first) * 6.0f * third); + } + + if (third < 0.5) + { + return second; + } + + if (third < 0.6666667F) + { + return first + ((second - first) * (0.6666667F - third) * 6.0f); + } + + return first; + } + + /// + /// Moves the specific value within the acceptable range for + /// conversion. + /// Used for converting colors to this type. + /// + /// The value to shift. + /// + /// The . + /// + private static float MoveIntoRange(float value) + { + if (value < 0.0) + { + value += 1.0f; + } + else if (value > 1.0) + { + value -= 1.0f; + } + + return value; + } + } +} diff --git a/src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs b/src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs new file mode 100644 index 0000000000..76a37a7a7b --- /dev/null +++ b/src/ImageProcessorCore/Colors/Colorspaces/Bgra32.cs @@ -0,0 +1,167 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Represents an BGRA (blue, green, red, alpha) color. + /// + public struct Bgra32 : IEquatable + { + /// + /// Represents a 32 bit that has B, G, R, and A values set to zero. + /// + public static readonly Bgra32 Empty = default(Bgra32); + + /// + /// The backing vector for SIMD support. + /// + private Vector4 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The blue component of this . + /// The green component of this . + /// The red component of this . + /// The alpha component of this . + public Bgra32(byte b, byte g, byte r, byte a = 255) + : this() + { + this.backingVector = Vector4.Clamp(new Vector4(b, g, r, a), Vector4.Zero, new Vector4(255)); + } + + /// + /// Gets the blue component of the color + /// + public byte B => (byte)this.backingVector.X; + + /// + /// Gets the green component of the color + /// + public byte G => (byte)this.backingVector.Y; + + /// + /// Gets the red component of the color + /// + public byte R => (byte)this.backingVector.Z; + + /// + /// Gets the alpha component of the color + /// + public byte A => (byte)this.backingVector.W; + + /// + /// Gets the integer representation of the color. + /// + public int Bgra => (this.R << 16) | (this.G << 8) | (this.B << 0) | (this.A << 24); + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// + /// The instance of to convert. + /// + /// + /// An instance of . + /// + public static implicit operator Bgra32(Color color) + { + return new Bgra32(color.B, color.G, color.R, color.A); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Bgra32 left, Bgra32 right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(Bgra32 left, Bgra32 right) + { + return !left.Equals(right); + } + + /// + public override bool Equals(object obj) + { + if (obj is Bgra32) + { + Bgra32 color = (Bgra32)obj; + + return this.backingVector == color.backingVector; + } + + return false; + } + + /// + public override int GetHashCode() + { + return GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Bgra32 [ Empty ]"; + } + + return $"Bgra32 [ B={this.B}, G={this.G}, R={this.R}, A={this.A} ]"; + } + + /// + public bool Equals(Bgra32 other) + { + return this.backingVector.Equals(other.backingVector); + } + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private static int GetHashCode(Bgra32 color) => color.backingVector.GetHashCode(); + } +} diff --git a/src/ImageProcessorCore/Colors/Colorspaces/CieLab.cs b/src/ImageProcessorCore/Colors/Colorspaces/CieLab.cs new file mode 100644 index 0000000000..2480262e00 --- /dev/null +++ b/src/ImageProcessorCore/Colors/Colorspaces/CieLab.cs @@ -0,0 +1,192 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Represents an CIE LAB 1976 color. + /// + /// + public struct CieLab : IEquatable, IAlmostEquatable + { + /// + /// Represents a that has L, A, B values set to zero. + /// + public static readonly CieLab Empty = default(CieLab); + + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.001f; + + /// + /// The backing vector for SIMD support. + /// + private Vector3 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The lightness dimension. + /// The a (green - magenta) component. + /// The b (blue - yellow) component. + public CieLab(float l, float a, float b) + : this() + { + this.backingVector = Vector3.Clamp(new Vector3(l, a, b), new Vector3(0, -100, -100), new Vector3(100)); + } + + /// + /// Gets the lightness dimension. + /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). + /// + public float L => this.backingVector.X; + + /// + /// Gets the a color component. + /// Negative is green, positive magenta. + /// + public float A => this.backingVector.Y; + + /// + /// Gets the b color component. + /// Negative is blue, positive is yellow + /// + public float B => this.backingVector.Z; + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// + /// The instance of to convert. + /// + /// + /// An instance of . + /// + public static implicit operator CieLab(Color color) + { + // First convert to CIE XYZ + 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; + //y /= 1F; + z /= 1.08883F; + + x = x > 0.008856F ? (float)Math.Pow(x, 0.3333333F) : (903.3F * x + 16F) / 116F; + y = y > 0.008856F ? (float)Math.Pow(y, 0.3333333F) : (903.3F * y + 16F) / 116F; + z = z > 0.008856F ? (float)Math.Pow(z, 0.3333333F) : (903.3F * z + 16F) / 116F; + + float l = Math.Max(0, (116F * y) - 16F); + float a = 500F * (x - y); + float b = 200F * (y - z); + + return new CieLab(l, a, b); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(CieLab left, CieLab right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(CieLab left, CieLab right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + return GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "CieLab [Empty]"; + } + + return $"CieLab [ L={this.L:#0.##}, A={this.A:#0.##}, B={this.B:#0.##}]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is CieLab) + { + return this.Equals((CieLab)obj); + } + + return false; + } + + /// + public bool Equals(CieLab other) + { + return this.AlmostEquals(other, Epsilon); + } + + /// + public bool AlmostEquals(CieLab other, float precision) + { + Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); + + return result.X < precision + && result.Y < precision + && result.Z < precision; + } + + /// + /// 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(CieLab color) => color.backingVector.GetHashCode(); + } +} diff --git a/src/ImageProcessorCore/Colors/Colorspaces/CieXyz.cs b/src/ImageProcessorCore/Colors/Colorspaces/CieXyz.cs new file mode 100644 index 0000000000..528e4faf34 --- /dev/null +++ b/src/ImageProcessorCore/Colors/Colorspaces/CieXyz.cs @@ -0,0 +1,184 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Represents an CIE 1931 color + /// + /// + public struct CieXyz : IEquatable, IAlmostEquatable + { + /// + /// Represents a that has Y, Cb, and Cr values set to zero. + /// + public static readonly CieXyz Empty = default(CieXyz); + + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.001f; + + /// + /// The backing vector for SIMD support. + /// + private Vector3 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The y luminance component. + /// X is a mix (a linear combination) of cone response curves chosen to be nonnegative + /// Z is quasi-equal to blue stimulation, or the S cone of the human eye. + public CieXyz(float x, float y, float z) + : this() + { + // Not clamping as documentation about this space seems to indicate "usual" ranges + this.backingVector = new Vector3(x, y, z); + } + + /// + /// Gets the Y luminance component. + /// A value ranging between 380 and 780. + /// + public float X => this.backingVector.X; + + /// + /// Gets the Cb chroma component. + /// A value ranging between 380 and 780. + /// + public float Y => this.backingVector.Y; + + /// + /// Gets the Cr chroma component. + /// A value ranging between 380 and 780. + /// + public float Z => this.backingVector.Z; + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// + /// The instance of to convert. + /// + /// + /// An instance of . + /// + public static implicit operator CieXyz(Color color) + { + 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); + + x *= 100F; + y *= 100F; + z *= 100F; + + return new CieXyz(x, y, z); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(CieXyz left, CieXyz right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(CieXyz left, CieXyz right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + return GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "CieXyz [ Empty ]"; + } + + return $"CieXyz [ X={this.X:#0.##}, Y={this.Y:#0.##}, Z={this.Z:#0.##} ]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is CieXyz) + { + return this.Equals((CieXyz)obj); + } + + return false; + } + + /// + public bool Equals(CieXyz other) + { + return this.AlmostEquals(other, Epsilon); + } + + /// + public bool AlmostEquals(CieXyz other, float precision) + { + Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); + + return result.X < precision + && result.Y < precision + && result.Z < precision; + } + + /// + /// 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(CieXyz color) => color.backingVector.GetHashCode(); + } +} diff --git a/src/ImageProcessorCore/Colors/Colorspaces/Cmyk.cs b/src/ImageProcessorCore/Colors/Colorspaces/Cmyk.cs new file mode 100644 index 0000000000..6cb717e624 --- /dev/null +++ b/src/ImageProcessorCore/Colors/Colorspaces/Cmyk.cs @@ -0,0 +1,195 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Represents an CMYK (cyan, magenta, yellow, keyline) color. + /// + public struct Cmyk : IEquatable, IAlmostEquatable + { + /// + /// Represents a that has C, M, Y, and K values set to zero. + /// + public static readonly Cmyk Empty = default(Cmyk); + + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.001f; + + /// + /// The backing vector for SIMD support. + /// + private Vector4 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The cyan component. + /// The magenta component. + /// The yellow component. + /// The keyline black component. + public Cmyk(float c, float m, float y, float k) + : this() + { + this.backingVector = Vector4.Clamp(new Vector4(c, m, y, k), Vector4.Zero, Vector4.One); + } + + /// + /// Gets the cyan color component. + /// A value ranging between 0 and 1. + /// + public float C => this.backingVector.X; + + /// + /// Gets the magenta color component. + /// A value ranging between 0 and 1. + /// + public float M => this.backingVector.Y; + + /// + /// Gets the yellow color component. + /// A value ranging between 0 and 1. + /// + public float Y => this.backingVector.Z; + + /// + /// Gets the keyline black color component. + /// A value ranging between 0 and 1. + /// + public float K => this.backingVector.W; + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// + /// The instance of to convert. + /// + /// + /// An instance of . + /// + public static implicit operator Cmyk(Color color) + { + 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)); + + if (Math.Abs(k - 1.0f) <= Epsilon) + { + return new Cmyk(0, 0, 0, 1); + } + + c = (c - k) / (1 - k); + m = (m - k) / (1 - k); + y = (y - k) / (1 - k); + + return new Cmyk(c, m, y, k); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Cmyk left, Cmyk right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(Cmyk left, Cmyk right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + return GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Cmyk [Empty]"; + } + + return $"Cmyk [ C={this.C:#0.##}, M={this.M:#0.##}, Y={this.Y:#0.##}, K={this.K:#0.##}]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is Cmyk) + { + return this.Equals((Cmyk)obj); + } + + return false; + } + + /// + public bool Equals(Cmyk other) + { + return this.AlmostEquals(other, Epsilon); + } + + /// + public bool AlmostEquals(Cmyk other, float precision) + { + Vector4 result = Vector4.Abs(this.backingVector - other.backingVector); + + return result.X < precision + && result.Y < precision + && result.Z < precision + && result.W < precision; + } + + /// + /// 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(Cmyk color) => color.backingVector.GetHashCode(); + } +} diff --git a/src/ImageProcessorCore/Colors/Colorspaces/Hsl.cs b/src/ImageProcessorCore/Colors/Colorspaces/Hsl.cs new file mode 100644 index 0000000000..0e8812a257 --- /dev/null +++ b/src/ImageProcessorCore/Colors/Colorspaces/Hsl.cs @@ -0,0 +1,213 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Represents a Hsl (hue, saturation, lightness) color. + /// + public struct Hsl : IEquatable, IAlmostEquatable + { + /// + /// Represents a that has H, S, and L values set to zero. + /// + public static readonly Hsl Empty = default(Hsl); + + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.001F; + + /// + /// The backing vector for SIMD support. + /// + private Vector3 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The h hue component. + /// The s saturation component. + /// The l value (lightness) component. + public Hsl(float h, float s, float l) + { + this.backingVector = Vector3.Clamp(new Vector3(h, s, l), Vector3.Zero, new Vector3(360, 1, 1)); + } + + /// + /// Gets the hue component. + /// A value ranging between 0 and 360. + /// + public float H => this.backingVector.X; + + /// + /// Gets the saturation component. + /// A value ranging between 0 and 1. + /// + public float S => this.backingVector.Y; + + /// + /// Gets the lightness component. + /// A value ranging between 0 and 1. + /// + public float L => this.backingVector.Z; + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + public static implicit operator Hsl(Color color) + { + 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)); + float chroma = max - min; + float h = 0; + float s = 0; + float l = (max + min) / 2; + + if (Math.Abs(chroma) < Epsilon) + { + return new Hsl(0, s, l); + } + + if (Math.Abs(r - max) < Epsilon) + { + h = (g - b) / chroma; + } + else if (Math.Abs(g - max) < Epsilon) + { + h = 2 + ((b - r) / chroma); + } + else if (Math.Abs(b - max) < Epsilon) + { + h = 4 + ((r - g) / chroma); + } + + h *= 60; + if (h < 0.0) + { + h += 360; + } + + if (l <= .5f) + { + s = chroma / (max + min); + } + else + { + s = chroma / (2 - chroma); + } + + return new Hsl(h, s, l); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Hsl left, Hsl right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(Hsl left, Hsl right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + return GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Hsl [ Empty ]"; + } + + return $"Hsl [ H={this.H:#0.##}, S={this.S:#0.##}, L={this.L:#0.##} ]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is Hsl) + { + return this.Equals((Hsl)obj); + } + + return false; + } + + /// + public bool Equals(Hsl other) + { + return this.AlmostEquals(other, Epsilon); + } + + /// + public bool AlmostEquals(Hsl other, float precision) + { + Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); + + return result.X < precision + && result.Y < precision + && result.Z < precision; + } + + /// + /// 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(Hsl color) => color.backingVector.GetHashCode(); + } +} diff --git a/src/ImageProcessorCore/Colors/Colorspaces/Hsv.cs b/src/ImageProcessorCore/Colors/Colorspaces/Hsv.cs new file mode 100644 index 0000000000..0a22ac2731 --- /dev/null +++ b/src/ImageProcessorCore/Colors/Colorspaces/Hsv.cs @@ -0,0 +1,206 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using System; + using System.ComponentModel; + using System.Numerics; + + /// + /// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness). + /// + public struct Hsv : IEquatable, IAlmostEquatable + { + /// + /// Represents a that has H, S, and V values set to zero. + /// + public static readonly Hsv Empty = default(Hsv); + + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.001F; + + /// + /// The backing vector for SIMD support. + /// + private Vector3 backingVector; + + /// + /// Initializes a new instance of the struct. + /// + /// The h hue component. + /// The s saturation component. + /// The v value (brightness) component. + public Hsv(float h, float s, float v) + { + this.backingVector = Vector3.Clamp(new Vector3(h, s, v), Vector3.Zero, new Vector3(360, 1, 1)); + } + + /// + /// Gets the hue component. + /// A value ranging between 0 and 360. + /// + public float H => this.backingVector.X; + + /// + /// Gets the saturation component. + /// A value ranging between 0 and 1. + /// + public float S => this.backingVector.Y; + + /// + /// Gets the value (brightness) component. + /// A value ranging between 0 and 1. + /// + public float V => this.backingVector.Z; + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Allows the implicit conversion of an instance of to a + /// . + /// + /// The instance of to convert. + /// + /// An instance of . + /// + public static implicit operator Hsv(Color color) + { + 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)); + float chroma = max - min; + float h = 0; + float s = 0; + float v = max; + + if (Math.Abs(chroma) < Epsilon) + { + return new Hsv(0, s, v); + } + + if (Math.Abs(r - max) < Epsilon) + { + h = (g - b) / chroma; + } + else if (Math.Abs(g - max) < Epsilon) + { + h = 2 + ((b - r) / chroma); + } + else if (Math.Abs(b - max) < Epsilon) + { + h = 4 + ((r - g) / chroma); + } + + h *= 60; + if (h < 0.0) + { + h += 360; + } + + s = chroma / v; + + return new Hsv(h, s, v); + } + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + public static bool operator ==(Hsv left, Hsv right) + { + return left.Equals(right); + } + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + public static bool operator !=(Hsv left, Hsv right) + { + return !left.Equals(right); + } + + /// + public override int GetHashCode() + { + return GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "Hsv [ Empty ]"; + } + + return $"Hsv [ H={this.H:#0.##}, S={this.S:#0.##}, V={this.V:#0.##} ]"; + } + + /// + public override bool Equals(object obj) + { + if (obj is Hsv) + { + return this.Equals((Hsv)obj); + } + + return false; + } + + /// + public bool Equals(Hsv other) + { + return this.AlmostEquals(other, Epsilon); + } + + /// + public bool AlmostEquals(Hsv other, float precision) + { + Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); + + return result.X < precision + && result.Y < precision + && result.Z < precision; + } + + /// + /// 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(Hsv color) => color.backingVector.GetHashCode(); + } +} diff --git a/src/ImageProcessorCore/Colors/Colorspaces/YCbCr.cs b/src/ImageProcessorCore/Colors/Colorspaces/YCbCr.cs index faba036ade..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. diff --git a/src/ImageProcessorCore/Colors/PackedVector/ColorspaceTransforms.cs b/src/ImageProcessorCore/Colors/PackedVector/ColorspaceTransforms.cs deleted file mode 100644 index 92b06762e5..0000000000 --- a/src/ImageProcessorCore/Colors/PackedVector/ColorspaceTransforms.cs +++ /dev/null @@ -1,285 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - - /// - /// 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 - { - ///// - ///// Allows the implicit conversion of an instance of to a - ///// . - ///// - ///// The instance of to convert. - ///// - ///// An instance of . - ///// - //public static implicit operator Color(Bgra32 color) - //{ - // return new Color(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f); - //} - - ///// - ///// Allows the implicit conversion of an instance of to a - ///// . - ///// - ///// The instance of to convert. - ///// - ///// An instance of . - ///// - //public static implicit operator Color(Cmyk cmykColor) - //{ - // 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); - //} - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// - /// An instance of . - /// - public static implicit operator Color(YCbCr color) - { - float y = color.Y; - float cb = color.Cb - 128; - float cr = color.Cr - 128; - - 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, 255); - } - - ///// - ///// Allows the implicit conversion of an instance of to a - ///// . - ///// - ///// The instance of to convert. - ///// - ///// An instance of . - ///// - //public static implicit operator Color(CieXyz color) - //{ - // float x = color.X / 100F; - // float y = color.Y / 100F; - // float z = color.Z / 100F; - - // // Then XYZ to RGB (multiplication by 100 was done above already) - // float r = (x * 3.2406F) + (y * -1.5372F) + (z * -0.4986F); - // 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)); - //} - - ///// - ///// Allows the implicit conversion of an instance of to a - ///// . - ///// - ///// The instance of to convert. - ///// - ///// An instance of . - ///// - //public static implicit operator Color(Hsv color) - //{ - // float s = color.S; - // float v = color.V; - - // if (Math.Abs(s) < Epsilon) - // { - // return new Color(v, v, v, 1); - // } - - // float h = (Math.Abs(color.H - 360) < Epsilon) ? 0 : color.H / 60; - // 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 r, g, b; - // switch (i) - // { - // case 0: - // r = v; - // g = t; - // b = p; - // break; - - // case 1: - // r = q; - // g = v; - // b = p; - // break; - - // case 2: - // r = p; - // g = v; - // b = t; - // break; - - // case 3: - // r = p; - // g = q; - // b = v; - // break; - - // case 4: - // r = t; - // g = p; - // b = v; - // break; - - // default: - // r = v; - // g = p; - // b = q; - // break; - // } - - // return new Color(r, g, b); - //} - - ///// - ///// Allows the implicit conversion of an instance of to a - ///// . - ///// - ///// The instance of to convert. - ///// - ///// An instance of . - ///// - //public static implicit operator Color(Hsl color) - //{ - // float rangedH = color.H / 360f; - // float r = 0; - // float g = 0; - // float b = 0; - // float s = color.S; - // float l = color.L; - - // if (Math.Abs(l) > Epsilon) - // { - // if (Math.Abs(s) < Epsilon) - // { - // r = g = b = l; - // } - // else - // { - // float temp2 = (l < 0.5f) ? l * (1f + s) : l + s - (l * s); - // float temp1 = (2f * l) - temp2; - - // r = GetColorComponent(temp1, temp2, rangedH + 0.3333333F); - // g = GetColorComponent(temp1, temp2, rangedH); - // b = GetColorComponent(temp1, temp2, rangedH - 0.3333333F); - // } - // } - - // return new Color(r, g, b); - //} - - ///// - ///// Allows the implicit conversion of an instance of to a - ///// . - ///// - ///// The instance of to convert. - ///// - ///// An instance of . - ///// - //public static implicit operator Color(CieLab cieLabColor) - //{ - // // First convert back to XYZ... - // float y = (cieLabColor.L + 16F) / 116F; - // float x = (cieLabColor.A / 500F) + y; - // float z = y - (cieLabColor.B / 200F); - - // float x3 = x * x * x; - // float y3 = y * y * y; - // float z3 = z * z * z; - - // x = x3 > 0.008856F ? x3 : (x - 0.137931F) / 7.787F; - // y = (cieLabColor.L > 7.999625F) ? y3 : (cieLabColor.L / 903.3F); - // z = (z3 > 0.008856F) ? z3 : (z - 0.137931F) / 7.787F; - - // x *= 0.95047F; - // z *= 1.08883F; - - // // Then XYZ to RGB (multiplication by 100 was done above already) - // float r = (x * 3.2406F) + (y * -1.5372F) + (z * -0.4986F); - // 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)); - //} - - /// - /// Gets the color component from the given values. - /// - /// The first value. - /// The second value. - /// The third value. - /// - /// The . - /// - private static float GetColorComponent(float first, float second, float third) - { - third = MoveIntoRange(third); - if (third < 0.1666667F) - { - return first + ((second - first) * 6.0f * third); - } - - if (third < 0.5) - { - return second; - } - - if (third < 0.6666667F) - { - return first + ((second - first) * (0.6666667F - third) * 6.0f); - } - - return first; - } - - /// - /// Moves the specific value within the acceptable range for - /// conversion. - /// Used for converting colors to this type. - /// - /// The value to shift. - /// - /// The . - /// - private static float MoveIntoRange(float value) - { - if (value < 0.0) - { - value += 1.0f; - } - else if (value > 1.0) - { - value -= 1.0f; - } - - return value; - } - } -} diff --git a/src/ImageProcessorCore/Filters/DetectEdges.cs b/src/ImageProcessorCore/Filters/DetectEdges.cs index 2912e04f72..33fa00d30d 100644 --- a/src/ImageProcessorCore/Filters/DetectEdges.cs +++ b/src/ImageProcessorCore/Filters/DetectEdges.cs @@ -14,7 +14,7 @@ namespace ImageProcessorCore { /// /// 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. @@ -25,7 +25,7 @@ namespace ImageProcessorCore 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); } /// @@ -35,10 +35,10 @@ namespace ImageProcessorCore /// The packed format. long, float. /// The image this method extends. /// The filter for detecting edges. - /// Whether to convert the image to greyscale first. Defaults to true. + /// 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, EdgeDetection filter, bool greyscale = true, ProgressEventHandler progressHandler = null) + public static Image DetectEdges(this Image source, EdgeDetection filter, bool Grayscale = true, ProgressEventHandler progressHandler = null) where T : IPackedVector where TP : struct { @@ -47,39 +47,39 @@ namespace ImageProcessorCore switch (filter) { case EdgeDetection.Kayyali: - processor = new KayyaliProcessor { Greyscale = greyscale }; + processor = new KayyaliProcessor { Grayscale = Grayscale }; break; case EdgeDetection.Kirsch: - processor = new KirschProcessor { Greyscale = greyscale }; + processor = new KirschProcessor { Grayscale = Grayscale }; break; case EdgeDetection.Lapacian3X3: - processor = new Laplacian3X3Processor { Greyscale = greyscale }; + processor = new Laplacian3X3Processor { Grayscale = Grayscale }; break; case EdgeDetection.Lapacian5X5: - processor = new Laplacian5X5Processor { Greyscale = greyscale }; + processor = new Laplacian5X5Processor { Grayscale = Grayscale }; break; case EdgeDetection.LaplacianOfGaussian: - processor = new LaplacianOfGaussianProcessor { Greyscale = greyscale }; + processor = new LaplacianOfGaussianProcessor { Grayscale = Grayscale }; break; case EdgeDetection.Prewitt: - processor = new PrewittProcessor { Greyscale = greyscale }; + processor = new PrewittProcessor { Grayscale = Grayscale }; break; case EdgeDetection.RobertsCross: - processor = new RobertsCrossProcessor { Greyscale = greyscale }; + processor = new RobertsCrossProcessor { Grayscale = Grayscale }; break; case EdgeDetection.Scharr: - processor = new ScharrProcessor { Greyscale = greyscale }; + processor = new ScharrProcessor { Grayscale = Grayscale }; break; default: - processor = new ScharrProcessor { Greyscale = greyscale }; + processor = new ScharrProcessor { Grayscale = Grayscale }; break; } diff --git a/src/ImageProcessorCore/Filters/Greyscale.cs b/src/ImageProcessorCore/Filters/Grayscale.cs similarity index 76% rename from src/ImageProcessorCore/Filters/Greyscale.cs rename to src/ImageProcessorCore/Filters/Grayscale.cs index 90db305541..46ef445266 100644 --- a/src/ImageProcessorCore/Filters/Greyscale.cs +++ b/src/ImageProcessorCore/Filters/Grayscale.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -13,7 +13,7 @@ namespace ImageProcessorCore 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. @@ -21,15 +21,15 @@ namespace ImageProcessorCore /// 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) + 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. @@ -40,13 +40,13 @@ namespace ImageProcessorCore /// 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) + 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/Options/GreyscaleMode.cs b/src/ImageProcessorCore/Filters/Options/GrayscaleMode.cs similarity index 72% rename from src/ImageProcessorCore/Filters/Options/GreyscaleMode.cs rename to src/ImageProcessorCore/Filters/Options/GrayscaleMode.cs index e1cc5917ec..9c1b9df5d4 100644 --- a/src/ImageProcessorCore/Filters/Options/GreyscaleMode.cs +++ b/src/ImageProcessorCore/Filters/Options/GrayscaleMode.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -6,9 +6,9 @@ namespace ImageProcessorCore { /// - /// Enumerates the various types of defined greyscale filters. + /// 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/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Binarization/BinaryThresholdProcessor.cs index 78da97f111..ffe5c4452a 100644 --- a/src/ImageProcessorCore/Filters/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/Binarization/BinaryThresholdProcessor.cs @@ -9,7 +9,7 @@ namespace ImageProcessorCore.Processors /// /// An to perform binary threshold filtering against an - /// . The image will be converted to greyscale before thresholding + /// . The image will be converted to Grayscale before thresholding /// occurs. /// /// The pixel format. @@ -58,7 +58,7 @@ namespace ImageProcessorCore.Processors /// protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) { - new GreyscaleBt709Processor().Apply(source, source, sourceRectangle); + new GrayscaleBt709Processor().Apply(source, source, sourceRectangle); } /// @@ -90,7 +90,7 @@ namespace ImageProcessorCore.Processors { T color = sourcePixels[x, y]; - // Any channel will do since it's greyscale. + // 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/ColorMatrix/GreyscaleBt601Processor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GrayscaleBt601Processor.cs similarity index 83% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs rename to src/ImageProcessorCore/Filters/Processors/ColorMatrix/GrayscaleBt601Processor.cs index 9e6b25ad68..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,12 +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.601 . /// /// The pixel format. /// The packed format. long, float. - public class GreyscaleBt601Processor : ColorMatrixFilter + public class GrayscaleBt601Processor : ColorMatrixFilter where T : IPackedVector where TP : struct { diff --git a/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs b/src/ImageProcessorCore/Filters/Processors/ColorMatrix/GrayscaleBt709Processor.cs similarity index 80% rename from src/ImageProcessorCore/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs rename to src/ImageProcessorCore/Filters/Processors/ColorMatrix/GrayscaleBt709Processor.cs index f8741b00b7..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,10 @@ 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 { diff --git a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs index 4340f4d60c..4effae8b0e 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs @@ -14,14 +14,14 @@ namespace ImageProcessorCore.Processors 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) { - 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 2762f664fc..a82f0d4caf 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs @@ -14,14 +14,14 @@ namespace ImageProcessorCore.Processors 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) { - 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 ef0ceea9b4..9d9485875b 100644 --- a/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs +++ b/src/ImageProcessorCore/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs @@ -21,8 +21,8 @@ namespace ImageProcessorCore.Processors { /// /// 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/Formats/Jpg/JpegEncoderCore.cs b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs index fe2f018294..041fcf9406 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs @@ -615,7 +615,7 @@ namespace ImageProcessorCore.Formats 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 = grey scaled, 3 = color YCbCr or YIQ, 4 = color CMYK) + 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; diff --git a/src/ImageProcessorCore/Formats/Png/PngDecoder.cs b/src/ImageProcessorCore/Formats/Png/PngDecoder.cs index 0ce7c3c887..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). /// diff --git a/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs b/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs new file mode 100644 index 0000000000..6fafad5f09 --- /dev/null +++ b/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs @@ -0,0 +1,145 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Quantizers +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; + + /// + /// Encapsulates methods to create a quantized image based upon the given palette. + /// + /// + /// The pixel format. + /// The packed format. long, float. + public class PaletteQuantizer : Quantizer + where T : IPackedVector + where TP : struct + { + /// + /// A lookup table for colors + /// + private readonly ConcurrentDictionary colorMap = new ConcurrentDictionary(); + + /// + /// List of all colors in the palette + /// + private T[] colors; + + /// + /// 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(T[] palette = null) + : base(true) + { + if (palette == null) + { + 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; + } + } + + /// + 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(T pixel) + { + byte colorIndex = 0; + string colorHash = pixel.ToString(); + + // Check if the color is in the lookup table + if (this.colorMap.ContainsKey(colorHash)) + { + colorIndex = this.colorMap[colorHash]; + } + else + { + // 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 + 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].ToBytes()[3] == 0) + { + colorIndex = (byte)index; + this.TransparentIndex = colorIndex; + break; + } + } + } + else + { + // Not transparent... + int leastDistance = int.MaxValue; + int red = bytes[0]; + int green = bytes[1]; + int blue = bytes[3]; + + // Loop through the entire palette, looking for the closest color match + for (int index = 0; index < this.colors.Length; index++) + { + 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) + + (blueDistance * blueDistance); + + if (distance < leastDistance) + { + colorIndex = (byte)index; + leastDistance = distance; + + // And if it's an exact match, exit the loop + if (distance == 0) + { + break; + } + } + } + } + + // Now I have the color, pop it into the cache for next time + this.colorMap.TryAdd(colorHash, colorIndex); + } + + return colorIndex; + } + + /// + protected override List GetPalette() + { + return this.colors.ToList(); + } + } +} diff --git a/tests/ImageProcessorCore.Tests/Colors/Class.cs b/tests/ImageProcessorCore.Tests/Colors/Class.cs new file mode 100644 index 0000000000..a93aac5f9b --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Colors/Class.cs @@ -0,0 +1,109 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +using System.Numerics; + +namespace ImageProcessorCore.Tests +{ + using System; + using Xunit; + + /// + /// Tests the struct. + /// + public class ColorTests + { + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + Color color1 = new Color(0, 0, 0); + Color color2 = new Color(0, 0, 0, 1F); + Color color3 = new Color("#000"); + Color color4 = new Color("#000000"); + Color color5 = new Color("#FF000000"); + + Assert.Equal(color1, color2); + Assert.Equal(color1, color3); + Assert.Equal(color1, color4); + Assert.Equal(color1, color5); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + Color color1 = new Color(255, 0, 0, 255); + Color color2 = new Color(0, 0, 0, 255); + Color color3 = new Color("#000"); + Color color4 = new Color("#000000"); + Color color5 = new Color("#FF000000"); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color1, color3); + Assert.NotEqual(color1, color4); + Assert.NotEqual(color1, color5); + } + + /// + /// Tests whether the color constructor correctly assign properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + Color color1 = new Color(1, .1f, .133f, .864f); + Assert.Equal(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(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(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 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(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); + } + + /// + /// Tests to see that in the input hex matches that of the output. + /// + [Fact] + public void ConvertHex() + { + const string First = "FF000000"; + 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 new file mode 100644 index 0000000000..091ce2abf5 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Colors/ColorConversionTests.cs @@ -0,0 +1,493 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System; + using System.Diagnostics.CodeAnalysis; + + using Xunit; + + /// + /// Test conversion between the various color structs. + /// + /// + /// Output values have been compared with + /// and for accuracy. + /// + public class ColorConversionTests + { + /// + /// Tests the implicit conversion from to . + /// + [Fact] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", + Justification = "Reviewed. Suppression is OK here.")] + public void ColorToYCbCr() + { + // White + Color color = Color.White; + YCbCr yCbCr = color; + + Assert.Equal(255, yCbCr.Y, 0); + Assert.Equal(128, yCbCr.Cb, 0); + Assert.Equal(128, yCbCr.Cr, 0); + + // Black + 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); + + // Gray + Color color3 = Color.Gray; + YCbCr yCbCr3 = color3; + Assert.Equal(128, yCbCr3.Y, 0); + Assert.Equal(128, yCbCr3.Cb, 0); + Assert.Equal(128, yCbCr3.Cr, 0); + } + + /// + /// Tests the implicit conversion from to . + /// + [Fact] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", + Justification = "Reviewed. Suppression is OK here.")] + public void YCbCrToColor() + { + // White + YCbCr yCbCr = new YCbCr(255, 128, 128); + Color color = yCbCr; + + Assert.Equal(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); + Color color2 = yCbCr2; + + Assert.Equal(0, color2.R); + Assert.Equal(0, color2.G); + Assert.Equal(0, color2.B); + Assert.Equal(255, color2.A); + + // Gray + YCbCr yCbCr3 = new YCbCr(128, 128, 128); + Color color3 = yCbCr3; + + Assert.Equal(128, color3.R); + Assert.Equal(128, color3.G); + Assert.Equal(128, color3.B); + Assert.Equal(255, color3.A); + } + + /// + /// Tests the implicit conversion from to . + /// Comparison values obtained from + /// http://colormine.org/convert/rgb-to-xyz + /// + [Fact] + public void ColorToCieXyz() + { + // White + Color color = Color.White; + CieXyz ciexyz = color; + + Assert.Equal(95.05f, ciexyz.X, 3); + Assert.Equal(100.0f, ciexyz.Y, 3); + Assert.Equal(108.900f, ciexyz.Z, 3); + + // Black + Color color2 = Color.Black; + CieXyz ciexyz2 = color2; + Assert.Equal(0, ciexyz2.X, 3); + Assert.Equal(0, ciexyz2.Y, 3); + Assert.Equal(0, ciexyz2.Z, 3); + + // 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 = Color.Cyan; + CieXyz ciexyz4 = color4; + Assert.Equal(53.810f, ciexyz4.X, 3); + Assert.Equal(78.740f, ciexyz4.Y, 3); + Assert.Equal(106.970f, ciexyz4.Z, 3); + } + + /// + /// Tests the implicit conversion from to . + /// Comparison values obtained from + /// http://colormine.org/convert/rgb-to-xyz + /// + [Fact] + public void CieXyzToColor() + { + // Dark moderate pink. + CieXyz ciexyz = new CieXyz(13.337f, 9.297f, 14.727f); + Color color = ciexyz; + + Assert.Equal(128, 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, color2.R); + Assert.Equal(119, color2.G); + Assert.Equal(34, color2.B); + + // Black + CieXyz ciexyz3 = new CieXyz(0, 0, 0); + Color color3 = ciexyz3; + + Assert.Equal(0, color3.R); + Assert.Equal(0, color3.G); + Assert.Equal(0, color3.B); + + //// Check others. + //Random random = new Random(0); + //for (int i = 0; i < 1000; i++) + //{ + // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); + // CieXyz ciexyz4 = color4; + // Assert.Equal(color4, (Color)ciexyz4); + //} + } + + /// + /// Tests the implicit conversion from to . + /// + [Fact] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", + Justification = "Reviewed. Suppression is OK here.")] + public void ColorToHsv() + { + // Black + Color b = Color.Black; + Hsv h = b; + + Assert.Equal(0, h.H, 1); + Assert.Equal(0, h.S, 1); + Assert.Equal(0, h.V, 1); + + // White + Color color = Color.White; + Hsv hsv = color; + + Assert.Equal(0f, hsv.H, 1); + Assert.Equal(0f, hsv.S, 1); + Assert.Equal(1f, hsv.V, 1); + + // Dark moderate pink. + Color color2 = new Color(128, 64, 106); + Hsv hsv2 = color2; + + Assert.Equal(320.6f, hsv2.H, 1); + Assert.Equal(0.5f, hsv2.S, 1); + Assert.Equal(0.502f, hsv2.V, 2); + + // Ochre. + Color color3 = new Color(204, 119, 34); + Hsv hsv3 = color3; + + Assert.Equal(30f, hsv3.H, 1); + Assert.Equal(0.833f, hsv3.S, 3); + Assert.Equal(0.8f, hsv3.V, 1); + } + + /// + /// Tests the implicit conversion from to . + /// + [Fact] + public void HsvToColor() + { + // Dark moderate pink. + Hsv hsv = new Hsv(320.6f, 0.5f, 0.502f); + Color color = hsv; + + Assert.Equal(color.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.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, 255); + Assert.Equal(color3.G, 255); + Assert.Equal(color3.R, 255); + + // Check others. + //Random random = new Random(0); + //for (int i = 0; i < 1000; i++) + //{ + // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); + // Hsv hsv4 = color4; + // Assert.Equal(color4, (Color)hsv4); + //} + } + + /// + /// Tests the implicit conversion from to . + /// + [Fact] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", + Justification = "Reviewed. Suppression is OK here.")] + public void ColorToHsl() + { + // Black + Color b = Color.Black; + Hsl h = b; + + Assert.Equal(0, h.H, 1); + Assert.Equal(0, h.S, 1); + Assert.Equal(0, h.L, 1); + + // White + Color color = Color.White; + Hsl hsl = color; + + Assert.Equal(0f, hsl.H, 1); + Assert.Equal(0f, hsl.S, 1); + Assert.Equal(1f, hsl.L, 1); + + // Dark moderate pink. + Color color2 = new Color(128, 64, 106); + Hsl hsl2 = color2; + + Assert.Equal(320.6f, hsl2.H, 1); + Assert.Equal(0.33f, hsl2.S, 1); + Assert.Equal(0.376f, hsl2.L, 2); + + // Ochre. + Color color3 = new Color(204, 119, 34); + Hsl hsl3 = color3; + + Assert.Equal(30f, hsl3.H, 1); + Assert.Equal(0.714f, hsl3.S, 3); + Assert.Equal(0.467f, hsl3.L, 3); + } + + /// + /// Tests the implicit conversion from to . + /// + [Fact] + public void HslToColor() + { + // Dark moderate pink. + Hsl hsl = new Hsl(320.6f, 0.33f, 0.376f); + Color color = hsl; + + Assert.Equal(color.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.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.R, 255); + Assert.Equal(color3.G, 255); + Assert.Equal(color3.B, 255); + + // Check others. + //Random random = new Random(0); + //for (int i = 0; i < 1000; i++) + //{ + // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); + // Hsl hsl4 = color4; + // Assert.Equal(color4, (Color)hsl4); + //} + } + + /// + /// Tests the implicit conversion from to . + /// + [Fact] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", + Justification = "Reviewed. Suppression is OK here.")] + public void ColorToCmyk() + { + // White + Color color = Color.White; + Cmyk cmyk = color; + + Assert.Equal(0, cmyk.C, 1); + Assert.Equal(0, cmyk.M, 1); + Assert.Equal(0, cmyk.Y, 1); + Assert.Equal(0, cmyk.K, 1); + + // Black + Color color2 = 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); + + // Gray + Color color3 = Color.Gray; + Cmyk cmyk3 = color3; + Assert.Equal(0f, cmyk3.C, 1); + Assert.Equal(0f, cmyk3.M, 1); + Assert.Equal(0f, cmyk3.Y, 1); + Assert.Equal(0.498, cmyk3.K, 2); // Checked with other online converters. + + // Cyan + Color color4 = Color.Cyan; + Cmyk cmyk4 = color4; + Assert.Equal(1, cmyk4.C, 1); + Assert.Equal(0f, cmyk4.M, 1); + Assert.Equal(0f, cmyk4.Y, 1); + Assert.Equal(0f, cmyk4.K, 1); + } + + /// + /// Tests the implicit conversion from to . + /// + [Fact] + public void CmykToColor() + { + // Dark moderate pink. + Cmyk cmyk = new Cmyk(0f, .5f, .171f, .498f); + Color color = cmyk; + + Assert.Equal(color.R, 128); + 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); + 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, 255); + Assert.Equal(color3.G, 255); + Assert.Equal(color3.B, 255); + + // Check others. + //Random random = new Random(0); + //for (int i = 0; i < 1000; i++) + //{ + // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); + // Cmyk cmyk4 = color4; + // Assert.Equal(color4, (Color)cmyk4); + //} + } + + /// + /// Tests the implicit conversion from to . + /// Comparison values obtained from + /// http://colormine.org/convert/rgb-to-lab + /// + [Fact] + public void ColorToCieLab() + { + // White + Color color = Color.White; + CieLab cielab = color; + + Assert.Equal(100, cielab.L, 3); + Assert.Equal(0.005, cielab.A, 3); + Assert.Equal(-0.010, cielab.B, 3); + + // Black + Color color2 = Color.Black; + CieLab cielab2 = color2; + Assert.Equal(0, cielab2.L, 3); + Assert.Equal(0, cielab2.A, 3); + Assert.Equal(0, cielab2.B, 3); + + // 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 = Color.Cyan; + CieLab cielab4 = color4; + Assert.Equal(91.117, cielab4.L, 3); + Assert.Equal(-48.080, cielab4.A, 3); + Assert.Equal(-14.138, cielab4.B, 3); + } + + /// + /// Tests the implicit conversion from to . + /// + /// Comparison values obtained from + /// http://colormine.org/convert/rgb-to-lab + [Fact] + public void CieLabToColor() + { + // Dark moderate pink. + CieLab cielab = new CieLab(36.5492f, 33.3173f, -12.0615f); + Color color = cielab; + + Assert.Equal(color.R, 128); + 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); + Assert.Equal(color2.G, 119); + Assert.Equal(color2.B, 34); + + // Black + CieLab cielab3 = new CieLab(0, 0, 0); + Color color3 = cielab3; + + Assert.Equal(color3.R, 0); + Assert.Equal(color3.G, 0); + Assert.Equal(color3.B, 0); + + // Check others. + //Random random = new Random(0); + //for (int i = 0; i < 1000; i++) + //{ + // Color color4 = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()); + // CieLab cielab4 = color4; + // Assert.Equal(color4, (Color)cielab4); + //} + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/GreyscaleTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/GrayscaleTest.cs similarity index 66% rename from tests/ImageProcessorCore.Tests/Processors/Filters/GreyscaleTest.cs rename to tests/ImageProcessorCore.Tests/Processors/Filters/GrayscaleTest.cs index 427cc346a4..da60c06d39 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Filters/GreyscaleTest.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/GrayscaleTest.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -10,20 +10,20 @@ namespace ImageProcessorCore.Tests using Xunit; - public class GreyscaleTest : FileTestBase + public class GrayscaleTest : FileTestBase { - public static readonly TheoryData GreyscaleValues - = new TheoryData + public static readonly TheoryData GrayscaleValues + = new TheoryData { - GreyscaleMode.Bt709 , - GreyscaleMode.Bt601 , + GrayscaleMode.Bt709 , + GrayscaleMode.Bt601 , }; [Theory] - [MemberData("GreyscaleValues")] - public void ImageShouldApplyGreyscaleFilter(GreyscaleMode value) + [MemberData("GrayscaleValues")] + public void ImageShouldApplyGrayscaleFilter(GrayscaleMode value) { - const string path = "TestOutput/Greyscale"; + const string path = "TestOutput/Grayscale"; if (!Directory.Exists(path)) { Directory.CreateDirectory(path); @@ -38,7 +38,7 @@ namespace ImageProcessorCore.Tests Image image = new Image(stream); using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Greyscale(value) + image.Grayscale(value) .Save(output); } } From 45d9c0b76968536e407adb31a5b5c009f50f1308 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 30 Jul 2016 19:37:39 +1000 Subject: [PATCH 87/91] Add Lanczos2 resampler Former-commit-id: 7190b01b85ed5933e0f5dd276b3eb718a8d78e30 Former-commit-id: 56df11ca6ffb6d0075115dc3b2c714317a379ab8 Former-commit-id: becd320711a19b21267790300429105e071fc5de --- README.md | 33 ++++-------------- .../Samplers/Resamplers/Lanczos2Resampler.cs | 34 +++++++++++++++++++ 2 files changed, 41 insertions(+), 26 deletions(-) create mode 100644 src/ImageProcessorCore/Samplers/Resamplers/Lanczos2Resampler.cs 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/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; + } + } +} From 682f5b73e3e8c98ec5a860903380a8afa8893f72 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 30 Jul 2016 21:20:13 +1000 Subject: [PATCH 88/91] Add format tests Former-commit-id: 05ae692a75c4b11f2e4474f84412f432f93e8753 Former-commit-id: 782629c7d712f5496c6ff4cdeea242eef0896e31 Former-commit-id: da773565a12c5de9186d546542b2b8f976903567 --- .../Quantizers/Options/Quantization.cs | 28 +++ .../Quantizers/Palette/PaletteQuantizer.cs | 2 +- src/ImageProcessorCore/Quantizers/Quantize.cs | 65 ++++++ .../Quantizers/QuantizedImage.cs | 3 +- src/ImageProcessorCore/Samplers/Crop.cs | 2 +- .../Samplers/EntropyCrop.cs | 2 +- src/ImageProcessorCore/Samplers/Pad.cs | 2 +- src/ImageProcessorCore/Samplers/Resize.cs | 2 +- .../Formats/Bmp/BitmapTests.cs | 47 +++++ .../Formats/GeneralFormatTests.cs | 199 ++++++++++++++++++ .../GeneralFormatTests.cs => Png/PngTests.cs} | 22 +- 11 files changed, 356 insertions(+), 18 deletions(-) create mode 100644 src/ImageProcessorCore/Quantizers/Options/Quantization.cs create mode 100644 src/ImageProcessorCore/Quantizers/Quantize.cs create mode 100644 tests/ImageProcessorCore.Tests/Formats/Bmp/BitmapTests.cs create mode 100644 tests/ImageProcessorCore.Tests/Formats/GeneralFormatTests.cs rename tests/ImageProcessorCore.Tests/Formats/{Jpg/GeneralFormatTests.cs => Png/PngTests.cs} (52%) 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 6fafad5f09..9de4a4dc4a 100644 --- a/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs +++ b/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs @@ -101,7 +101,7 @@ namespace ImageProcessorCore.Quantizers int leastDistance = int.MaxValue; int red = bytes[0]; int green = bytes[1]; - int blue = bytes[3]; + int blue = bytes[2]; // Loop through the entire palette, looking for the closest color match for (int index = 0; index < this.colors.Length; index++) 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 123efe6533..680f8b7ab0 100644 --- a/src/ImageProcessorCore/Quantizers/QuantizedImage.cs +++ b/src/ImageProcessorCore/Quantizers/QuantizedImage.cs @@ -90,9 +90,8 @@ namespace ImageProcessorCore.Quantizers Bootstrapper.Instance.ParallelOptions, i => { - int offset = i * 4; T color = this.Palette[Math.Min(palletCount, this.Pixels[i])]; - pixels[offset] = color; + pixels[i] = color; }); image.SetPixels(this.Width, this.Height, pixels); diff --git a/src/ImageProcessorCore/Samplers/Crop.cs b/src/ImageProcessorCore/Samplers/Crop.cs index 04c96dbd1a..84d343d6d6 100644 --- a/src/ImageProcessorCore/Samplers/Crop.cs +++ b/src/ImageProcessorCore/Samplers/Crop.cs @@ -1,7 +1,7 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { diff --git a/src/ImageProcessorCore/Samplers/EntropyCrop.cs b/src/ImageProcessorCore/Samplers/EntropyCrop.cs index 631662d800..cbc9eedee8 100644 --- a/src/ImageProcessorCore/Samplers/EntropyCrop.cs +++ b/src/ImageProcessorCore/Samplers/EntropyCrop.cs @@ -1,7 +1,7 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { diff --git a/src/ImageProcessorCore/Samplers/Pad.cs b/src/ImageProcessorCore/Samplers/Pad.cs index 0a7277144e..6be9f654c4 100644 --- a/src/ImageProcessorCore/Samplers/Pad.cs +++ b/src/ImageProcessorCore/Samplers/Pad.cs @@ -1,7 +1,7 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { diff --git a/src/ImageProcessorCore/Samplers/Resize.cs b/src/ImageProcessorCore/Samplers/Resize.cs index e9d3093f4d..40b07469ec 100644 --- a/src/ImageProcessorCore/Samplers/Resize.cs +++ b/src/ImageProcessorCore/Samplers/Resize.cs @@ -1,7 +1,7 @@ // // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- +// namespace ImageProcessorCore { diff --git a/tests/ImageProcessorCore.Tests/Formats/Bmp/BitmapTests.cs b/tests/ImageProcessorCore.Tests/Formats/Bmp/BitmapTests.cs new file mode 100644 index 0000000000..23884e9ed6 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Formats/Bmp/BitmapTests.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 Formats; + + using Xunit; + + public class BitmapTests : FileTestBase + { + [Fact] + public void BitmapCanEncodeDifferentBitRates() + { + const string path = "TestOutput/Bmp"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + Image image = new Image(stream); + string encodeFilename = path + "/24-" + Path.GetFileNameWithoutExtension(file) + ".bmp"; + + using (FileStream output = File.OpenWrite(encodeFilename)) + { + image.Save(output, new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel24 }); + } + + encodeFilename = path + "/32-" + Path.GetFileNameWithoutExtension(file) + ".bmp"; + + using (FileStream output = File.OpenWrite(encodeFilename)) + { + image.Save(output, new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel32 }); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Formats/GeneralFormatTests.cs b/tests/ImageProcessorCore.Tests/Formats/GeneralFormatTests.cs new file mode 100644 index 0000000000..462a39cebc --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Formats/GeneralFormatTests.cs @@ -0,0 +1,199 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System; + using System.IO; + + using Xunit; + + 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() + { + const string path = "TestOutput/ToString"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + Image image = new Image(stream); + string filename = path + "/" + Path.GetFileNameWithoutExtension(file) + ".txt"; + File.WriteAllText(filename, image.ToString()); + } + } + } + + [Fact] + public void DecodeThenEncodeImageFromStreamShouldSucceed() + { + const string path = "TestOutput/Encode"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + Image image = new Image(stream); + string encodeFilename = path + "/" + Path.GetFileName(file); + + using (FileStream output = File.OpenWrite(encodeFilename)) + { + image.Save(output); + } + } + } + } + + [Fact] + public void QuantizeImageShouldPreserveMaximumColorPrecision() + { + const string path = "TestOutput/Quantize"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + Image image = new Image(stream); + + // 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.Quantize(Quantization.Octree) + .Save(output, image.CurrentImageFormat); + + } + + image.SetPixels(image.Width, image.Height, pixels); + using (FileStream output = File.OpenWrite($"{path}/Wu-{Path.GetFileName(file)}")) + { + image.Quantize(Quantization.Wu) + .Save(output, image.CurrentImageFormat); + } + + image.SetPixels(image.Width, image.Height, pixels); + using (FileStream output = File.OpenWrite($"{path}/Palette-{Path.GetFileName(file)}")) + { + image.Quantize(Quantization.Palette) + .Save(output, image.CurrentImageFormat); + } + } + } + } + + [Fact] + public void ImageCanConvertFormat() + { + const string path = "TestOutput/Format"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{Path.GetFileNameWithoutExtension(file)}.gif")) + { + image.SaveAsGif(output); + } + + using (FileStream output = File.OpenWrite($"{path}/{Path.GetFileNameWithoutExtension(file)}.bmp")) + { + image.SaveAsBmp(output); + } + + using (FileStream output = File.OpenWrite($"{path}/{Path.GetFileNameWithoutExtension(file)}.jpg")) + { + image.SaveAsJpeg(output); + } + + using (FileStream output = File.OpenWrite($"{path}/{Path.GetFileNameWithoutExtension(file)}.png")) + { + image.SaveAsPng(output); + } + } + } + } + + [Fact] + public void ImageShouldPreservePixelByteOrderWhenSerialized() + { + const string path = "TestOutput/Serialized"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + Image image = new Image(stream); + byte[] serialized; + using (MemoryStream memoryStream = new MemoryStream()) + { + image.Save(memoryStream); + memoryStream.Flush(); + serialized = memoryStream.ToArray(); + } + + using (MemoryStream memoryStream = new MemoryStream(serialized)) + { + Image image2 = new Image(memoryStream); + using (FileStream output = File.OpenWrite($"{path}/{Path.GetFileName(file)}")) + { + image2.Save(output); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Formats/Jpg/GeneralFormatTests.cs b/tests/ImageProcessorCore.Tests/Formats/Png/PngTests.cs similarity index 52% rename from tests/ImageProcessorCore.Tests/Formats/Jpg/GeneralFormatTests.cs rename to tests/ImageProcessorCore.Tests/Formats/Png/PngTests.cs index 69e2ef0f10..c86238cbac 100644 --- a/tests/ImageProcessorCore.Tests/Formats/Jpg/GeneralFormatTests.cs +++ b/tests/ImageProcessorCore.Tests/Formats/Png/PngTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -7,30 +7,30 @@ namespace ImageProcessorCore.Tests { using System.IO; + using Formats; + using Xunit; - public class GeneralFormatTests : FileTestBase + public class PngTests : FileTestBase { [Fact] - public void ResolutionShouldChange() + public void ImageCanSaveIndexedPng() { - if (!Directory.Exists("TestOutput/Resolution")) + const string path = "TestOutput/Png"; + if (!Directory.Exists(path)) { - Directory.CreateDirectory("TestOutput/Resolution"); + 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($"TestOutput/Resolution/{filename}")) + using (FileStream output = File.OpenWrite($"{path}/{Path.GetFileNameWithoutExtension(file)}.png")) { - image.VerticalResolution = 150; - image.HorizontalResolution = 150; - image.Save(output); + image.Quality = 256; + image.Save(output, new PngFormat()); } } } From 73523a4ae21d26624a1d4ebcc480c9a5af3b97b1 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 30 Jul 2016 21:30:26 +1000 Subject: [PATCH 89/91] Add additional tests Former-commit-id: 5d53804087b6c10289b272881b77e4ca45b2e055 Former-commit-id: 0a4c7ab500246b11b97034706e166b380decf3ec Former-commit-id: 453702e4cd1ea1cde1479ef7a7a0a6187d64de90 --- .../Helpers/GuardTests.cs | 204 ++++++++++++++++++ .../Numerics/PointTests.cs | 11 + .../Numerics/RectangleTests.cs | 66 ++++++ .../Numerics/SizeTests.cs | 50 +++++ 4 files changed, 331 insertions(+) create mode 100644 tests/ImageProcessorCore.Tests/Helpers/GuardTests.cs create mode 100644 tests/ImageProcessorCore.Tests/Numerics/PointTests.cs create mode 100644 tests/ImageProcessorCore.Tests/Numerics/RectangleTests.cs create mode 100644 tests/ImageProcessorCore.Tests/Numerics/SizeTests.cs diff --git a/tests/ImageProcessorCore.Tests/Helpers/GuardTests.cs b/tests/ImageProcessorCore.Tests/Helpers/GuardTests.cs new file mode 100644 index 0000000000..14b81708ff --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Helpers/GuardTests.cs @@ -0,0 +1,204 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests.Helpers +{ + using System; + using System.Diagnostics.CodeAnalysis; + + using Xunit; + + /// + /// Tests the helper. + /// + public class GuardTests + { + /// + /// Tests that the method throws when the argument is null. + /// + [Fact] + public void NotNullThrowsWhenArgIsNull() + { + Assert.Throws(() => Guard.NotNull(null, "foo")); + } + + /// + /// Tests that the method throws when the argument name is empty. + /// + [Fact] + public void NotNullThrowsWhenArgNameEmpty() + { + Assert.Throws(() => Guard.NotNull(null, string.Empty)); + } + + /// + /// Tests that the method throws when the argument is empty. + /// + [Fact] + [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1122:UseStringEmptyForEmptyStrings", Justification = "Reviewed. Suppression is OK here.")] + public void NotEmptyThrowsWhenEmpty() + { + Assert.Throws(() => Guard.NotNullOrEmpty("", string.Empty)); + } + + /// + /// Tests that the method throws when the argument is whitespace. + /// + [Fact] + public void NotEmptyThrowsWhenWhitespace() + { + Assert.Throws(() => Guard.NotNullOrEmpty(" ", string.Empty)); + } + + /// + /// Tests that the method throws when the argument name is null. + /// + [Fact] + public void NotEmptyThrowsWhenParameterNameNull() + { + Assert.Throws(() => Guard.NotNullOrEmpty(null, null)); + } + + /// + /// Tests that the method throws when the argument is greater. + /// + [Fact] + public void LessThanThrowsWhenArgIsGreater() + { + Assert.Throws(() => Guard.MustBeLessThan(1, 0, "foo")); + } + + /// + /// Tests that the method throws when the argument is equal. + /// + [Fact] + public void LessThanThrowsWhenArgIsEqual() + { + Assert.Throws(() => Guard.MustBeLessThan(1, 1, "foo")); + } + + /// + /// Tests that the method throws when the argument is greater. + /// + [Fact] + public void LessThanOrEqualToThrowsWhenArgIsGreater() + { + Assert.Throws(() => Guard.MustBeLessThanOrEqualTo(1, 0, "foo")); + } + + /// + /// Tests that the method does not throw when the argument + /// is less. + /// + [Fact] + public void LessThanOrEqualToDoesNotThrowWhenArgIsLess() + { + Exception ex = Record.Exception(() => Guard.MustBeLessThanOrEqualTo(0, 1, "foo")); + Assert.Null(ex); + } + + /// + /// Tests that the method does not throw when the argument + /// is equal. + /// + [Fact] + public void LessThanOrEqualToDoesNotThrowWhenArgIsEqual() + { + Exception ex = Record.Exception(() => Guard.MustBeLessThanOrEqualTo(1, 1, "foo")); + Assert.Equal(1, 1); + Assert.Null(ex); + } + + /// + /// Tests that the method throws when the argument is greater. + /// + [Fact] + public void GreaterThanThrowsWhenArgIsLess() + { + Assert.Throws(() => Guard.MustBeGreaterThan(0, 1, "foo")); + } + + /// + /// Tests that the method throws when the argument is greater. + /// + [Fact] + public void GreaterThanThrowsWhenArgIsEqual() + { + Assert.Throws(() => Guard.MustBeGreaterThan(1, 1, "foo")); + } + + /// + /// Tests that the method throws when the argument name is greater. + /// + [Fact] + public void GreaterThanOrEqualToThrowsWhenArgIsLess() + { + Assert.Throws(() => Guard.MustBeGreaterThanOrEqualTo(0, 1, "foo")); + } + + /// + /// Tests that the method does not throw when the argument + /// is less. + /// + [Fact] + public void GreaterThanOrEqualToDoesNotThrowWhenArgIsGreater() + { + Exception ex = Record.Exception(() => Guard.MustBeGreaterThanOrEqualTo(1, 0, "foo")); + Assert.Null(ex); + } + + /// + /// Tests that the method does not throw when the argument + /// is equal. + /// + [Fact] + public void GreaterThanOrEqualToDoesNotThrowWhenArgIsEqual() + { + Exception ex = Record.Exception(() => Guard.MustBeGreaterThanOrEqualTo(1, 1, "foo")); + Assert.Equal(1, 1); + Assert.Null(ex); + } + + /// + /// Tests that the method throws when the argument is less. + /// + [Fact] + public void BetweenOrEqualToThrowsWhenArgIsLess() + { + Assert.Throws(() => Guard.MustBeBetweenOrEqualTo(-2, -1, 1, "foo")); + } + + /// + /// Tests that the method throws when the argument is greater. + /// + [Fact] + public void BetweenOrEqualToThrowsWhenArgIsGreater() + { + Assert.Throws(() => Guard.MustBeBetweenOrEqualTo(2, -1, 1, "foo")); + } + + /// + /// Tests that the method does not throw when the argument + /// is equal. + /// + [Fact] + public void BetweenOrEqualToDoesNotThrowWhenArgIsEqual() + { + Exception ex = Record.Exception(() => Guard.MustBeBetweenOrEqualTo(1, 1, 1, "foo")); + Assert.Null(ex); + } + + /// + /// Tests that the method does not throw when the argument + /// is equal. + /// + [Fact] + public void BetweenOrEqualToDoesNotThrowWhenArgIsBetween() + { + Exception ex = Record.Exception(() => Guard.MustBeBetweenOrEqualTo(0, -1, 1, "foo")); + Assert.Null(ex); + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Numerics/PointTests.cs b/tests/ImageProcessorCore.Tests/Numerics/PointTests.cs new file mode 100644 index 0000000000..f67e5f5b35 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Numerics/PointTests.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ImageProcessor.Tests.Numerics +{ + public class Class + { + } +} diff --git a/tests/ImageProcessorCore.Tests/Numerics/RectangleTests.cs b/tests/ImageProcessorCore.Tests/Numerics/RectangleTests.cs new file mode 100644 index 0000000000..c67e9a7048 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Numerics/RectangleTests.cs @@ -0,0 +1,66 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using Xunit; + + /// + /// Tests the struct. + /// + public class RectangleTests + { + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + Rectangle first = new Rectangle(1, 1, 100, 100); + Rectangle second = new Rectangle(1, 1, 100, 100); + + Assert.Equal(first, second); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + Rectangle first = new Rectangle(1, 1, 0, 100); + Rectangle second = new Rectangle(1, 1, 100, 100); + + Assert.NotEqual(first, second); + } + + /// + /// Tests whether the rectangle constructors correctly assign properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + Rectangle first = new Rectangle(1, 1, 50, 100); + Assert.Equal(1, first.X); + Assert.Equal(1, first.Y); + Assert.Equal(50, first.Width); + Assert.Equal(100, first.Height); + Assert.Equal(1, first.Top); + Assert.Equal(51, first.Right); + Assert.Equal(101, first.Bottom); + Assert.Equal(1, first.Left); + + Rectangle second = new Rectangle(new Point(1, 1), new Size(50, 100)); + Assert.Equal(1, second.X); + Assert.Equal(1, second.Y); + Assert.Equal(50, second.Width); + Assert.Equal(100, second.Height); + Assert.Equal(1, second.Top); + Assert.Equal(51, second.Right); + Assert.Equal(101, second.Bottom); + Assert.Equal(1, second.Left); + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Numerics/SizeTests.cs b/tests/ImageProcessorCore.Tests/Numerics/SizeTests.cs new file mode 100644 index 0000000000..56b2ffa209 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Numerics/SizeTests.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using Xunit; + + /// + /// Tests the struct. + /// + public class SizeTests + { + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + Size first = new Size(100, 100); + Size second = new Size(100, 100); + + Assert.Equal(first, second); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + Size first = new Size(0, 100); + Size second = new Size(100, 100); + + Assert.NotEqual(first, second); + } + + /// + /// Tests whether the size constructor correctly assign properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + Size first = new Size(4, 5); + Assert.Equal(4, first.Width); + Assert.Equal(5, first.Height); + } + } +} \ No newline at end of file From b9442f1ed72b81a65c95d1dfb43774becb9bcfd1 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 30 Jul 2016 21:36:41 +1000 Subject: [PATCH 90/91] Point tests Former-commit-id: 925454bb65a6cf78842aa7dd2b34fb6207a5175f Former-commit-id: f79b8487d8f247d7b84ec41fd702cafacb7bba85 Former-commit-id: 4a53a2ab32bb8d7e73ce3ecdf43de0de8c48cc54 --- .../Numerics/PointTests.cs | 53 ++++++++++++++++--- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/tests/ImageProcessorCore.Tests/Numerics/PointTests.cs b/tests/ImageProcessorCore.Tests/Numerics/PointTests.cs index f67e5f5b35..e45a4fb2de 100644 --- a/tests/ImageProcessorCore.Tests/Numerics/PointTests.cs +++ b/tests/ImageProcessorCore.Tests/Numerics/PointTests.cs @@ -1,11 +1,50 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// -namespace ImageProcessor.Tests.Numerics +namespace ImageProcessorCore.Tests { - public class Class + using Xunit; + + /// + /// Tests the struct. + /// + public class PointTests { + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + Point first = new Point(100, 100); + Point second = new Point(100, 100); + + Assert.Equal(first, second); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + Point first = new Point(0, 100); + Point second = new Point(100, 100); + + Assert.NotEqual(first, second); + } + + /// + /// Tests whether the point constructor correctly assign properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + Point first = new Point(4, 5); + Assert.Equal(4, first.X); + Assert.Equal(5, first.Y); + } } -} +} \ No newline at end of file From 803922a97a01bbde9c668837314a70ac41d6b217 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 30 Jul 2016 21:38:36 +1000 Subject: [PATCH 91/91] Delete old code. Former-commit-id: c1c2d867171de871262331e5db1e55f0fe462312 Former-commit-id: 41d014fd7ba8e3a7f8a84d40d368eb330baab3c5 Former-commit-id: 78ac6e4c0afaeefd95f3604bf778584b48c3cadd --- src/ImageProcessorCore - Copy/Bootstrapper.cs | 124 --- src/ImageProcessorCore - Copy/Colors/Color.cs | 518 ------------ .../Colors/ColorConstants.cs | 179 ---- .../Colors/ColorDefinitions.cs | 728 ---------------- .../Colors/ColorTransforms.cs | 73 -- .../Colors/ColorspaceTransforms.cs | 285 ------- .../Colors/Colorspaces/Bgra32.cs | 168 ---- .../Colors/Colorspaces/CieLab.cs | 193 ----- .../Colors/Colorspaces/CieXyz.cs | 184 ---- .../Colors/Colorspaces/Cmyk.cs | 197 ----- .../Colors/Colorspaces/Hsl.cs | 213 ----- .../Colors/Colorspaces/Hsv.cs | 207 ----- .../Colors/Colorspaces/YCbCr.cs | 183 ---- .../Colors/IAlmostEquatable.cs | 29 - .../Colors/RgbaComponent.cs | 33 - .../Common/Exceptions/ImageFormatException.cs | 45 - .../Exceptions/ImageProcessingException.cs | 44 - .../Common/Extensions/ByteExtensions.cs | 60 -- .../Common/Extensions/ComparableExtensions.cs | 145 ---- .../Common/Extensions/EnumerableExtensions.cs | 88 -- .../Common/Helpers/Guard.cs | 192 ----- .../Common/Helpers/ImageMaths.cs | 289 ------- .../Filters/Alpha.cs | 52 -- .../Filters/BackgroundColor.cs | 37 - .../Filters/BlackWhite.cs | 50 -- .../Filters/Blend.cs | 60 -- .../Filters/BoxBlur.cs | 52 -- .../Filters/Brightness.cs | 52 -- .../Filters/ColorBlindness.cs | 88 -- .../Filters/Contrast.cs | 52 -- .../Filters/DetectEdges.cs | 63 -- .../Filters/Greyscale.cs | 55 -- .../Filters/GuassianBlur.cs | 52 -- .../Filters/GuassianSharpen.cs | 52 -- src/ImageProcessorCore - Copy/Filters/Hue.cs | 52 -- .../Filters/Invert.cs | 50 -- .../Filters/Kodachrome.cs | 50 -- .../Filters/Lomograph.cs | 50 -- .../Filters/Options/ColorBlindness.cs | 53 -- .../Filters/Pixelate.cs | 52 -- .../Filters/Polaroid.cs | 50 -- .../Filters/Processors/AlphaProcessor.cs | 69 -- .../Processors/BackgroundColorProcessor.cs | 78 -- .../Binarization/ThresholdProcessor.cs | 86 -- .../Filters/Processors/BlendProcessor.cs | 86 -- .../Filters/Processors/BrightnessProcessor.cs | 69 -- .../ColorMatrix/BlackWhiteProcessor.cs | 32 - .../ColorBlindness/AchromatomalyProcessor.cs | 32 - .../ColorBlindness/AchromatopsiaProcessor.cs | 32 - .../ColorBlindness/DeuteranomalyProcessor.cs | 29 - .../ColorBlindness/DeuteranopiaProcessor.cs | 29 - .../ColorBlindness/ProtanomalyProcessor.cs | 29 - .../ColorBlindness/ProtanopiaProcessor.cs | 29 - .../ColorMatrix/ColorBlindness/README.md | 4 - .../ColorBlindness/TritanomalyProcessor.cs | 29 - .../ColorBlindness/TritanopiaProcessor.cs | 29 - .../ColorMatrix/ColorMatrixFilter.cs | 68 -- .../ColorMatrix/GreyscaleBt601Processor.cs | 30 - .../ColorMatrix/GreyscaleBt709Processor.cs | 30 - .../Processors/ColorMatrix/GreyscaleMode.cs | 23 - .../Processors/ColorMatrix/HueProcessor.cs | 81 -- .../ColorMatrix/IColorMatrixFilter.cs | 27 - .../ColorMatrix/KodachromeProcessor.cs | 26 - .../ColorMatrix/LomographProcessor.cs | 32 - .../ColorMatrix/PolaroidProcessor.cs | 45 - .../ColorMatrix/SaturationProcessor.cs | 75 -- .../Processors/ColorMatrix/SepiaProcessor.cs | 33 - .../Filters/Processors/ContrastProcessor.cs | 70 -- .../Convolution/BoxBlurProcessor.cs | 103 --- .../Convolution/Convolution2DFilter.cs | 113 --- .../Convolution/Convolution2PassFilter.cs | 113 --- .../Convolution/ConvolutionFilter.cs | 88 -- .../EdgeDetection/EdgeDetector2DFilter.cs | 26 - .../EdgeDetection/EdgeDetectorFilter.cs | 26 - .../EdgeDetection/IEdgeDetectorFilter.cs | 19 - .../EdgeDetection/KayyaliProcessor.cs | 30 - .../EdgeDetection/KirschProcessor.cs | 30 - .../EdgeDetection/Laplacian3X3Processor.cs | 22 - .../EdgeDetection/Laplacian5X5Processor.cs | 24 - .../LaplacianOfGaussianProcessor.cs | 24 - .../EdgeDetection/PrewittProcessor.cs | 30 - .../EdgeDetection/RobertsCrossProcessor.cs | 28 - .../EdgeDetection/ScharrProcessor.cs | 30 - .../EdgeDetection/SobelProcessor.cs | 30 - .../Convolution/GuassianBlurProcessor.cs | 140 ---- .../Convolution/GuassianSharpenProcessor.cs | 178 ---- .../Filters/Processors/GlowProcessor.cs | 64 -- .../Filters/Processors/InvertProcessor.cs | 48 -- .../Filters/Processors/PixelateProcessor.cs | 94 --- .../Filters/Processors/VignetteProcessor.cs | 63 -- .../Filters/Saturation.cs | 52 -- .../Filters/Sepia.cs | 50 -- .../Formats/Bmp/BmpBitsPerPixel.cs | 23 - .../Formats/Bmp/BmpCompression.cs | 63 -- .../Formats/Bmp/BmpDecoder.cs | 82 -- .../Formats/Bmp/BmpDecoderCore.cs | 445 ---------- .../Formats/Bmp/BmpEncoder.cs | 53 -- .../Formats/Bmp/BmpEncoderCore.cs | 205 ----- .../Formats/Bmp/BmpFileHeader.cs | 49 -- .../Formats/Bmp/BmpFormat.cs | 19 - .../Formats/Bmp/BmpInfoHeader.cs | 82 -- .../Formats/Bmp/README.md | 8 - .../Formats/Gif/BitEncoder.cs | 132 --- .../Formats/Gif/DisposalMethod.cs | 37 - .../Formats/Gif/GifConstants.cs | 83 -- .../Formats/Gif/GifDecoder.cs | 67 -- .../Formats/Gif/GifDecoderCore.cs | 428 ---------- .../Formats/Gif/GifEncoder.cs | 62 -- .../Formats/Gif/GifEncoderCore.cs | 289 ------- .../Formats/Gif/GifFormat.cs | 19 - .../Formats/Gif/LzwDecoder.cs | 231 ----- .../Formats/Gif/LzwEncoder.cs | 385 --------- .../Formats/Gif/PackedField.cs | 194 ----- .../Formats/Gif/README.md | 4 - .../Sections/GifGraphicsControlExtension.cs | 42 - .../Gif/Sections/GifImageDescriptor.cs | 59 -- .../Sections/GifLogicalScreenDescriptor.cs | 55 -- .../Formats/IImageDecoder.cs | 49 -- .../Formats/IImageEncoder.cs | 53 -- .../Formats/IImageFormat.cs | 23 - .../Formats/Jpg/Block.cs | 44 - .../Formats/Jpg/FDCT.cs | 161 ---- .../Formats/Jpg/IDCT.cs | 163 ---- .../Formats/Jpg/JpegDecoder.cs | 149 ---- .../Jpg/JpegDecoderCore.cs.REMOVED.git-id | 1 - .../Formats/Jpg/JpegEncoder.cs | 97 --- .../Formats/Jpg/JpegEncoderCore.cs | 783 ----------------- .../Formats/Jpg/JpegFormat.cs | 19 - .../Formats/Jpg/JpegSubsample.cs | 25 - .../Formats/Jpg/README.md | 3 - .../Formats/Png/GrayscaleReader.cs | 80 -- .../Formats/Png/IColorReader.cs | 25 - .../Formats/Png/PaletteIndexReader.cs | 99 --- .../Formats/Png/PngChunk.cs | 39 - .../Formats/Png/PngChunkTypes.cs | 62 -- .../Formats/Png/PngColorTypeInformation.cs | 61 -- .../Formats/Png/PngDecoder.cs | 87 -- .../Formats/Png/PngDecoderCore.cs | 527 ------------ .../Formats/Png/PngEncoder.cs | 85 -- .../Formats/Png/PngEncoderCore.cs | 470 ----------- .../Formats/Png/PngFormat.cs | 19 - .../Formats/Png/PngHeader.cs | 62 -- .../Formats/Png/README.md | 6 - .../Formats/Png/TrueColorReader.cs | 78 -- .../Formats/Png/Zlib/Adler32.cs | 174 ---- .../Formats/Png/Zlib/Crc32.cs | 180 ---- .../Formats/Png/Zlib/IChecksum.cs | 60 -- .../Formats/Png/Zlib/README.md | 2 - .../Formats/Png/Zlib/ZlibDeflateStream.cs | 210 ----- .../Formats/Png/Zlib/ZlibInflateStream.cs | 205 ----- src/ImageProcessorCore - Copy/IImageBase.cs | 29 - src/ImageProcessorCore - Copy/IImageFrame.cs | 7 - .../IImageProcessor.cs | 72 -- .../IO/BigEndianBitConverter.cs | 48 -- .../IO/EndianBinaryReader.cs | 616 -------------- .../IO/EndianBinaryWriter.cs | 385 --------- .../IO/EndianBitConverter.cs | 724 ---------------- .../IO/Endianness.cs | 23 - .../IO/LittleEndianBitConverter.cs | 47 -- src/ImageProcessorCore - Copy/Image.cs | 291 ------- src/ImageProcessorCore - Copy/ImageBase.cs | 201 ----- .../ImageExtensions.cs | 167 ---- src/ImageProcessorCore - Copy/ImageFrame.cs | 34 - .../ImageProcessor.cs | 163 ---- .../ImageProcessorCore.xproj | 22 - .../ImageProperty.cs | 153 ---- .../Numerics/Ellipse.cs | 174 ---- .../Numerics/Point.cs | 281 ------- .../Numerics/Rectangle.cs | 291 ------- .../Numerics/Size.cs | 208 ----- .../PackedVector/Bgra32.cs | 168 ---- .../PackedVector/IPackedVector.cs | 61 -- .../PixelAccessor/Bgra32PixelAccessor.cs | 155 ---- .../PixelAccessor/IPixelAccessor.cs | 43 - .../ProgressEventArgs.cs | 23 - .../Properties/AssemblyInfo.cs | 34 - .../Quantizers/IQuantizer.cs | 28 - .../Quantizers/Octree/OctreeQuantizer.cs | 534 ------------ .../Quantizers/Octree/Quantizer.cs | 146 ---- .../Quantizers/Palette/PaletteQuantizer.cs | 134 --- .../Quantizers/QuantizedImage.cs | 98 --- .../Quantizers/Wu/Box.cs | 58 -- .../Quantizers/Wu/WuQuantizer.cs | 786 ------------------ .../Samplers/Crop.cs | 68 -- .../Samplers/EntropyCrop.cs | 37 - .../Samplers/Options/AnchorPosition.cs | 58 -- .../Samplers/Options/FlipType.cs | 28 - .../Samplers/Options/ResizeHelper.cs | 430 ---------- .../Samplers/Options/ResizeMode.cs | 49 -- .../Samplers/Options/ResizeOptions.cs | 47 -- .../Samplers/Options/RotateType.cs | 33 - src/ImageProcessorCore - Copy/Samplers/Pad.cs | 35 - .../Samplers/Processors/CropProcessor.cs | 41 - .../Processors/EntropyCropProcessor.cs | 104 --- .../Samplers/Processors/IImageSampler.cs | 19 - .../Samplers/Processors/ImageSampler.cs | 17 - .../Samplers/Processors/Matrix3x2Processor.cs | 47 -- .../Samplers/Processors/ResizeProcessor.cs | 362 -------- .../Processors/RotateFlipProcessor.cs | 231 ----- .../Samplers/Processors/RotateProcessor.cs | 68 -- .../Samplers/Processors/SkewProcessor.cs | 73 -- .../Samplers/Resamplers/BicubicResampler.cs | 43 - .../Samplers/Resamplers/BoxResampler.cs | 28 - .../Resamplers/CatmullRomResampler.cs | 28 - .../Samplers/Resamplers/HermiteResampler.cs | 27 - .../Samplers/Resamplers/IResampler.cs | 27 - .../Samplers/Resamplers/Lanczos3Resampler.cs | 34 - .../Samplers/Resamplers/Lanczos5Resampler.cs | 34 - .../Samplers/Resamplers/Lanczos8Resampler.cs | 34 - .../Resamplers/MitchellNetravaliResampler.cs | 26 - .../Resamplers/NearestNeighborResampler.cs | 23 - .../Samplers/Resamplers/RobidouxResampler.cs | 26 - .../Resamplers/RobidouxSharpResampler.cs | 26 - .../Resamplers/RobidouxSoftResampler.cs | 26 - .../Samplers/Resamplers/SplineResampler.cs | 26 - .../Samplers/Resamplers/TriangleResampler.cs | 34 - .../Samplers/Resamplers/WelchResampler.cs | 33 - .../Samplers/Resize.cs | 134 --- .../Samplers/Rotate.cs | 50 -- .../Samplers/RotateFlip.cs | 38 - .../Samplers/Skew.cs | 52 -- src/ImageProcessorCore - Copy/project.json | 38 - 222 files changed, 23781 deletions(-) delete mode 100644 src/ImageProcessorCore - Copy/Bootstrapper.cs delete mode 100644 src/ImageProcessorCore - Copy/Colors/Color.cs delete mode 100644 src/ImageProcessorCore - Copy/Colors/ColorConstants.cs delete mode 100644 src/ImageProcessorCore - Copy/Colors/ColorDefinitions.cs delete mode 100644 src/ImageProcessorCore - Copy/Colors/ColorTransforms.cs delete mode 100644 src/ImageProcessorCore - Copy/Colors/ColorspaceTransforms.cs delete mode 100644 src/ImageProcessorCore - Copy/Colors/Colorspaces/Bgra32.cs delete mode 100644 src/ImageProcessorCore - Copy/Colors/Colorspaces/CieLab.cs delete mode 100644 src/ImageProcessorCore - Copy/Colors/Colorspaces/CieXyz.cs delete mode 100644 src/ImageProcessorCore - Copy/Colors/Colorspaces/Cmyk.cs delete mode 100644 src/ImageProcessorCore - Copy/Colors/Colorspaces/Hsl.cs delete mode 100644 src/ImageProcessorCore - Copy/Colors/Colorspaces/Hsv.cs delete mode 100644 src/ImageProcessorCore - Copy/Colors/Colorspaces/YCbCr.cs delete mode 100644 src/ImageProcessorCore - Copy/Colors/IAlmostEquatable.cs delete mode 100644 src/ImageProcessorCore - Copy/Colors/RgbaComponent.cs delete mode 100644 src/ImageProcessorCore - Copy/Common/Exceptions/ImageFormatException.cs delete mode 100644 src/ImageProcessorCore - Copy/Common/Exceptions/ImageProcessingException.cs delete mode 100644 src/ImageProcessorCore - Copy/Common/Extensions/ByteExtensions.cs delete mode 100644 src/ImageProcessorCore - Copy/Common/Extensions/ComparableExtensions.cs delete mode 100644 src/ImageProcessorCore - Copy/Common/Extensions/EnumerableExtensions.cs delete mode 100644 src/ImageProcessorCore - Copy/Common/Helpers/Guard.cs delete mode 100644 src/ImageProcessorCore - Copy/Common/Helpers/ImageMaths.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Alpha.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/BackgroundColor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/BlackWhite.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Blend.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/BoxBlur.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Brightness.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/ColorBlindness.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Contrast.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/DetectEdges.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Greyscale.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/GuassianBlur.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/GuassianSharpen.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Hue.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Invert.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Kodachrome.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Lomograph.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Options/ColorBlindness.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Pixelate.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Polaroid.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/AlphaProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/BackgroundColorProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/Binarization/ThresholdProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/BlendProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/BrightnessProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/README.md delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleMode.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/HueProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/KodachromeProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/LomographProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/PolaroidProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/SaturationProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/SepiaProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/ContrastProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/Convolution/BoxBlurProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/Convolution/Convolution2DFilter.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/Convolution/Convolution2PassFilter.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/Convolution/ConvolutionFilter.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/KirschProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/PrewittProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/ScharrProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/Convolution/GuassianBlurProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/Convolution/GuassianSharpenProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/GlowProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/InvertProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/PixelateProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Processors/VignetteProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Saturation.cs delete mode 100644 src/ImageProcessorCore - Copy/Filters/Sepia.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Bmp/BmpBitsPerPixel.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Bmp/BmpCompression.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoder.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoderCore.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoder.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoderCore.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Bmp/BmpFileHeader.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Bmp/BmpFormat.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Bmp/BmpInfoHeader.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Bmp/README.md delete mode 100644 src/ImageProcessorCore - Copy/Formats/Gif/BitEncoder.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Gif/DisposalMethod.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Gif/GifConstants.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Gif/GifDecoder.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Gif/GifDecoderCore.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Gif/GifEncoder.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Gif/GifEncoderCore.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Gif/GifFormat.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Gif/LzwDecoder.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Gif/LzwEncoder.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Gif/PackedField.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Gif/README.md delete mode 100644 src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifGraphicsControlExtension.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifImageDescriptor.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/IImageDecoder.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/IImageEncoder.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/IImageFormat.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Jpg/Block.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Jpg/FDCT.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Jpg/IDCT.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Jpg/JpegDecoder.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id delete mode 100644 src/ImageProcessorCore - Copy/Formats/Jpg/JpegEncoder.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Jpg/JpegEncoderCore.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Jpg/JpegFormat.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Jpg/JpegSubsample.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Jpg/README.md delete mode 100644 src/ImageProcessorCore - Copy/Formats/Png/GrayscaleReader.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Png/IColorReader.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Png/PaletteIndexReader.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Png/PngChunk.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Png/PngChunkTypes.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Png/PngColorTypeInformation.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Png/PngDecoder.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Png/PngDecoderCore.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Png/PngEncoder.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Png/PngEncoderCore.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Png/PngFormat.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Png/PngHeader.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Png/README.md delete mode 100644 src/ImageProcessorCore - Copy/Formats/Png/TrueColorReader.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Png/Zlib/Adler32.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Png/Zlib/Crc32.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Png/Zlib/IChecksum.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Png/Zlib/README.md delete mode 100644 src/ImageProcessorCore - Copy/Formats/Png/Zlib/ZlibDeflateStream.cs delete mode 100644 src/ImageProcessorCore - Copy/Formats/Png/Zlib/ZlibInflateStream.cs delete mode 100644 src/ImageProcessorCore - Copy/IImageBase.cs delete mode 100644 src/ImageProcessorCore - Copy/IImageFrame.cs delete mode 100644 src/ImageProcessorCore - Copy/IImageProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/IO/BigEndianBitConverter.cs delete mode 100644 src/ImageProcessorCore - Copy/IO/EndianBinaryReader.cs delete mode 100644 src/ImageProcessorCore - Copy/IO/EndianBinaryWriter.cs delete mode 100644 src/ImageProcessorCore - Copy/IO/EndianBitConverter.cs delete mode 100644 src/ImageProcessorCore - Copy/IO/Endianness.cs delete mode 100644 src/ImageProcessorCore - Copy/IO/LittleEndianBitConverter.cs delete mode 100644 src/ImageProcessorCore - Copy/Image.cs delete mode 100644 src/ImageProcessorCore - Copy/ImageBase.cs delete mode 100644 src/ImageProcessorCore - Copy/ImageExtensions.cs delete mode 100644 src/ImageProcessorCore - Copy/ImageFrame.cs delete mode 100644 src/ImageProcessorCore - Copy/ImageProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/ImageProcessorCore.xproj delete mode 100644 src/ImageProcessorCore - Copy/ImageProperty.cs delete mode 100644 src/ImageProcessorCore - Copy/Numerics/Ellipse.cs delete mode 100644 src/ImageProcessorCore - Copy/Numerics/Point.cs delete mode 100644 src/ImageProcessorCore - Copy/Numerics/Rectangle.cs delete mode 100644 src/ImageProcessorCore - Copy/Numerics/Size.cs delete mode 100644 src/ImageProcessorCore - Copy/PackedVector/Bgra32.cs delete mode 100644 src/ImageProcessorCore - Copy/PackedVector/IPackedVector.cs delete mode 100644 src/ImageProcessorCore - Copy/PixelAccessor/Bgra32PixelAccessor.cs delete mode 100644 src/ImageProcessorCore - Copy/PixelAccessor/IPixelAccessor.cs delete mode 100644 src/ImageProcessorCore - Copy/ProgressEventArgs.cs delete mode 100644 src/ImageProcessorCore - Copy/Properties/AssemblyInfo.cs delete mode 100644 src/ImageProcessorCore - Copy/Quantizers/IQuantizer.cs delete mode 100644 src/ImageProcessorCore - Copy/Quantizers/Octree/OctreeQuantizer.cs delete mode 100644 src/ImageProcessorCore - Copy/Quantizers/Octree/Quantizer.cs delete mode 100644 src/ImageProcessorCore - Copy/Quantizers/Palette/PaletteQuantizer.cs delete mode 100644 src/ImageProcessorCore - Copy/Quantizers/QuantizedImage.cs delete mode 100644 src/ImageProcessorCore - Copy/Quantizers/Wu/Box.cs delete mode 100644 src/ImageProcessorCore - Copy/Quantizers/Wu/WuQuantizer.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Crop.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/EntropyCrop.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Options/AnchorPosition.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Options/FlipType.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Options/ResizeHelper.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Options/ResizeMode.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Options/ResizeOptions.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Options/RotateType.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Pad.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Processors/CropProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Processors/EntropyCropProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Processors/IImageSampler.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Processors/ImageSampler.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Processors/Matrix3x2Processor.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Processors/ResizeProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Processors/RotateFlipProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Processors/RotateProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Processors/SkewProcessor.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/BicubicResampler.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/BoxResampler.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/CatmullRomResampler.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/HermiteResampler.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/IResampler.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos3Resampler.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos5Resampler.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos8Resampler.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/MitchellNetravaliResampler.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/NearestNeighborResampler.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxResampler.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxSharpResampler.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxSoftResampler.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/SplineResampler.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/TriangleResampler.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Resamplers/WelchResampler.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Resize.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Rotate.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/RotateFlip.cs delete mode 100644 src/ImageProcessorCore - Copy/Samplers/Skew.cs delete mode 100644 src/ImageProcessorCore - Copy/project.json diff --git a/src/ImageProcessorCore - Copy/Bootstrapper.cs b/src/ImageProcessorCore - Copy/Bootstrapper.cs deleted file mode 100644 index 5f2978534c..0000000000 --- a/src/ImageProcessorCore - Copy/Bootstrapper.cs +++ /dev/null @@ -1,124 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Text; - - using ImageProcessorCore.Formats; - - /// - /// Provides initialization code which allows extending the library. - /// - public class Bootstrapper - { - /// - /// A new instance Initializes a new instance of the class. - /// with lazy initialization. - /// - private static readonly Lazy Lazy = new Lazy(() => new Bootstrapper()); - - /// - /// The default list of supported - /// - private readonly List imageFormats; - - private readonly Dictionary pixelAccessors; - - /// - /// Prevents a default instance of the class from being created. - /// - private Bootstrapper() - { - this.imageFormats = new List - { - new BmpFormat(), - new JpegFormat(), - new PngFormat(), - new GifFormat() - }; - - this.pixelAccessors = new Dictionary - { - { typeof(Bgra32), typeof(Bgra32PixelAccessor) } - }; - } - - /// - /// Gets the current bootstrapper instance. - /// - public static Bootstrapper Instance = Lazy.Value; - - /// - /// Gets the list of supported - /// - public IReadOnlyCollection ImageFormats => new ReadOnlyCollection(this.imageFormats); - - /// - /// Adds a new to the collection of supported image formats. - /// - /// The new format to add. - public void AddImageFormat(IImageFormat format) - { - this.imageFormats.Add(format); - } - - /// - /// Gets an instance of the correct for the packed vector. - /// - /// The type of pixel data. - /// The image - /// The - public IPixelAccessor GetPixelAccessor(Image image) - where TPackedVector : IPackedVector - { - Type packed = typeof(TPackedVector); - if (!this.pixelAccessors.ContainsKey(packed)) - { - // TODO: Double check this. It should work... - return (IPixelAccessor)Activator.CreateInstance(this.pixelAccessors[packed], image); - } - - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.AppendLine("PixelAccessor cannot be loaded. Available accessors:"); - - foreach (Type value in this.pixelAccessors.Values) - { - stringBuilder.AppendLine("-" + value.Name); - } - - throw new NotSupportedException(stringBuilder.ToString()); - } - - /// - /// Gets an instance of the correct for the packed vector. - /// - /// The type of pixel data. - /// The image - /// The - public IPixelAccessor GetPixelAccessor(ImageFrame image) - where TPackedVector : IPackedVector - { - Type packed = typeof(TPackedVector); - if (!this.pixelAccessors.ContainsKey(packed)) - { - return (IPixelAccessor)Activator.CreateInstance(this.pixelAccessors[packed], image); - } - - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.AppendLine("PixelAccessor cannot be loaded. Available accessors:"); - - foreach (Type value in this.pixelAccessors.Values) - { - stringBuilder.AppendLine("-" + value.Name); - } - - throw new NotSupportedException(stringBuilder.ToString()); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Colors/Color.cs b/src/ImageProcessorCore - Copy/Colors/Color.cs deleted file mode 100644 index 0e47e03e1b..0000000000 --- a/src/ImageProcessorCore - Copy/Colors/Color.cs +++ /dev/null @@ -1,518 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - using System.ComponentModel; - using System.Numerics; - using System.Runtime.CompilerServices; - - /// - /// Represents a four-component color using red, green, blue, and alpha data. - /// Each component is stored in premultiplied format multiplied by the alpha component. - /// - /// - /// 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 - { - /// - /// Represents an empty that has R, G, B, and A values set to zero. - /// - public static readonly Color Empty = default(Color); - - /// - /// The epsilon for comparing floating point numbers. - /// - private const float Epsilon = 0.001f; - - /// - /// The backing vector for SIMD support. - /// - private Vector4 backingVector; - - /// - /// Initializes a new instance of the struct. - /// - /// 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) - : this() - { - this.backingVector = new Vector4(r, g, b, a); - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The hexadecimal representation of the combined color components arranged - /// in rgb, rrggbb, or aarrggbb format to match web syntax. - /// - public Color(string hex) - : this() - { - // Hexadecimal representations are layed out AARRGGBB to we need to do some reordering. - hex = hex.StartsWith("#") ? hex.Substring(1) : hex; - - if (hex.Length != 8 && hex.Length != 6 && hex.Length != 3) - { - throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex)); - } - - 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); - - } - 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; - } - else - { - string rh = char.ToString(hex[0]); - 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; - } - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector. - public Color(Vector4 vector) - { - this.backingVector = vector; - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector representing the red, green, and blue componenets. - /// - public Color(Vector3 vector) - { - this.backingVector = new Vector4(vector, 1); - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector representing the red, green, and blue componenets. - /// - /// 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) - { - return new Color(left.backingVector - right.backingVector); - } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(Color left, Color right) - { - return left.Equals(right); - } - - /// - /// Compares two objects for inequality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(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); - } - - /// - /// Converts a premultipled alpha to a - /// that contains non-premultiplied alpha. - /// - /// The to convert. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Color ToNonPremultiplied(Color color) - { - 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)); - } - - /// - /// Gets a representation for this . - /// - /// A representation for this object. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 ToVector4() - { - return new Vector4(this.R, this.G, this.B, this.A); - } - - /// - /// Gets a representation for this . - /// - /// A representation for this object. - public Vector3 ToVector3() - { - return new Vector3(this.R, this.G, this.B); - } - - /// - public override int GetHashCode() - { - return GetHashCode(this); - } - - /// - public override string ToString() - { - if (this.IsEmpty) - { - return "Color [ Empty ]"; - } - - return $"Color [ R={this.R:#0.##}, G={this.G:#0.##}, B={this.B:#0.##}, A={this.A:#0.##} ]"; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool Equals(object obj) - { - if (obj is Color) - { - return this.Equals((Color)obj); - } - - return false; - } - - /// - 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; - } - - /// - /// 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); - } - - /// - /// 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(); - } -} diff --git a/src/ImageProcessorCore - Copy/Colors/ColorConstants.cs b/src/ImageProcessorCore - Copy/Colors/ColorConstants.cs deleted file mode 100644 index ed9718423d..0000000000 --- a/src/ImageProcessorCore - Copy/Colors/ColorConstants.cs +++ /dev/null @@ -1,179 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - using System.Collections.Generic; - - /// - /// Provides useful color definitions. - /// - public static class ColorConstants - { - /// - /// Provides a lazy, one time method of returning the colors. - /// - private static readonly Lazy SafeColors = new Lazy(GetWebSafeColors); - - /// - /// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4. - /// - public static Color[] WebSafeColors => SafeColors.Value; - - /// - /// Returns an array of web safe colors. - /// - /// - private static Color[] GetWebSafeColors() - { - return new List - { - Color.AliceBlue, - Color.AntiqueWhite, - Color.Aqua, - Color.Aquamarine, - Color.Azure, - Color.Beige, - Color.Bisque, - Color.Black, - Color.BlanchedAlmond, - Color.Blue, - Color.BlueViolet, - Color.Brown, - Color.BurlyWood, - Color.CadetBlue, - Color.Chartreuse, - Color.Chocolate, - Color.Coral, - Color.CornflowerBlue, - Color.Cornsilk, - Color.Crimson, - Color.Cyan, - Color.DarkBlue, - Color.DarkCyan, - Color.DarkGoldenrod, - Color.DarkGray, - Color.DarkGreen, - Color.DarkKhaki, - Color.DarkMagenta, - Color.DarkOliveGreen, - Color.DarkOrange, - Color.DarkOrchid, - Color.DarkRed, - Color.DarkSalmon, - Color.DarkSeaGreen, - Color.DarkSlateBlue, - Color.DarkSlateGray, - Color.DarkTurquoise, - Color.DarkViolet, - Color.DeepPink, - Color.DeepSkyBlue, - Color.DimGray, - Color.DodgerBlue, - Color.Firebrick, - Color.FloralWhite, - Color.ForestGreen, - Color.Fuchsia, - Color.Gainsboro, - Color.GhostWhite, - Color.Gold, - Color.Goldenrod, - Color.Gray, - Color.Green, - Color.GreenYellow, - Color.Honeydew, - Color.HotPink, - Color.IndianRed, - Color.Indigo, - Color.Ivory, - Color.Khaki, - Color.Lavender, - Color.LavenderBlush, - Color.LawnGreen, - Color.LemonChiffon, - Color.LightBlue, - Color.LightCoral, - Color.LightCyan, - Color.LightGoldenrodYellow, - Color.LightGray, - Color.LightGreen, - Color.LightPink, - Color.LightSalmon, - Color.LightSeaGreen, - Color.LightSkyBlue, - Color.LightSlateGray, - Color.LightSteelBlue, - Color.LightYellow, - Color.Lime, - Color.LimeGreen, - Color.Linen, - Color.Magenta, - Color.Maroon, - Color.MediumAquamarine, - Color.MediumBlue, - Color.MediumOrchid, - Color.MediumPurple, - Color.MediumSeaGreen, - Color.MediumSlateBlue, - Color.MediumSpringGreen, - Color.MediumTurquoise, - Color.MediumVioletRed, - Color.MidnightBlue, - Color.MintCream, - Color.MistyRose, - Color.Moccasin, - Color.NavajoWhite, - Color.Navy, - Color.OldLace, - Color.Olive, - Color.OliveDrab, - Color.Orange, - Color.OrangeRed, - Color.Orchid, - Color.PaleGoldenrod, - Color.PaleGreen, - Color.PaleTurquoise, - Color.PaleVioletRed, - Color.PapayaWhip, - Color.PeachPuff, - Color.Peru, - Color.Pink, - Color.Plum, - Color.PowderBlue, - Color.Purple, - Color.RebeccaPurple, - Color.Red, - Color.RosyBrown, - Color.RoyalBlue, - Color.SaddleBrown, - Color.Salmon, - Color.SandyBrown, - Color.SeaGreen, - Color.SeaShell, - Color.Sienna, - Color.Silver, - Color.SkyBlue, - Color.SlateBlue, - Color.SlateGray, - Color.Snow, - Color.SpringGreen, - Color.SteelBlue, - Color.Tan, - Color.Teal, - Color.Thistle, - Color.Tomato, - Color.Transparent, - Color.Turquoise, - Color.Violet, - Color.Wheat, - Color.White, - Color.WhiteSmoke, - Color.Yellow, - Color.YellowGreen - }.ToArray(); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Colors/ColorDefinitions.cs b/src/ImageProcessorCore - Copy/Colors/ColorDefinitions.cs deleted file mode 100644 index aaf99bb1b3..0000000000 --- a/src/ImageProcessorCore - Copy/Colors/ColorDefinitions.cs +++ /dev/null @@ -1,728 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -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. - /// - /// - /// 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 - { - /// - /// Represents a matching the W3C definition that has a hex triplet value of #F0F8FF. - /// - public static readonly Color AliceBlue = new Color(240 / 255f, 248 / 255f, 1); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FAEBD7. - /// - public static readonly Color AntiqueWhite = new Color(250 / 255f, 235 / 255f, 215 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #00FFFF. - /// - public static readonly Color Aqua = new Color(0, 1, 1); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #7FFFD4. - /// - public static readonly Color Aquamarine = new Color(127 / 255f, 1, 212 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #F0FFFF. - /// - public static readonly Color Azure = new Color(240 / 255f, 1, 1); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #F5F5DC. - /// - public static readonly Color Beige = new Color(245 / 255f, 245 / 255f, 220 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFE4C4. - /// - public static readonly Color Bisque = new Color(1, 228 / 255f, 196 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #000000. - /// - public static readonly Color Black = new Color(0, 0, 0); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFEBCD. - /// - public static readonly Color BlanchedAlmond = new Color(1, 235 / 255f, 205 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #0000FF. - /// - public static readonly Color Blue = new Color(0, 0, 1); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #8A2BE2. - /// - public static readonly Color BlueViolet = new Color(138 / 255f, 43 / 255f, 226 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #A52A2A. - /// - public static readonly Color Brown = new Color(165 / 255f, 42 / 255f, 42 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #DEB887. - /// - public static readonly Color BurlyWood = new Color(222 / 255f, 184 / 255f, 135 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #5F9EA0. - /// - public static readonly Color CadetBlue = new Color(95 / 255f, 158 / 255f, 160 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #7FFF00. - /// - public static readonly Color Chartreuse = new Color(127 / 255f, 1, 0); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #D2691E. - /// - public static readonly Color Chocolate = new Color(210 / 255f, 105 / 255f, 30 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FF7F50. - /// - public static readonly Color Coral = new Color(1, 127 / 255f, 80 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #6495ED. - /// - public static readonly Color CornflowerBlue = new Color(100 / 255f, 149 / 255f, 237 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFF8DC. - /// - public static readonly Color Cornsilk = new Color(1, 248 / 255f, 220 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #DC143C. - /// - public static readonly Color Crimson = new Color(220 / 255f, 20 / 255f, 60 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #00FFFF. - /// - public static readonly Color Cyan = new Color(0, 1, 1); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #00008B. - /// - public static readonly Color DarkBlue = new Color(0, 0, 139 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #008B8B. - /// - public static readonly Color DarkCyan = new Color(0, 139 / 255f, 139 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #B8860B. - /// - public static readonly Color DarkGoldenrod = new Color(184 / 255f, 134 / 255f, 11 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #A9A9A9. - /// - public static readonly Color DarkGray = new Color(169 / 255f, 169 / 255f, 169 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #006400. - /// - public static readonly Color DarkGreen = new Color(0, 100 / 255f, 0); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #BDB76B. - /// - public static readonly Color DarkKhaki = new Color(189 / 255f, 183 / 255f, 107 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #8B008B. - /// - public static readonly Color DarkMagenta = new Color(139 / 255f, 0, 139 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #556B2F. - /// - public static readonly Color DarkOliveGreen = new Color(85 / 255f, 107 / 255f, 47 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FF8C00. - /// - public static readonly Color DarkOrange = new Color(1, 140 / 255f, 0); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #9932CC. - /// - public static readonly Color DarkOrchid = new Color(153 / 255f, 50 / 255f, 204 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #8B0000. - /// - public static readonly Color DarkRed = new Color(139 / 255f, 0, 0); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #E9967A. - /// - public static readonly Color DarkSalmon = new Color(233 / 255f, 150 / 255f, 122 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #8FBC8B. - /// - public static readonly Color DarkSeaGreen = new Color(143 / 255f, 188 / 255f, 139 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #483D8B. - /// - public static readonly Color DarkSlateBlue = new Color(72 / 255f, 61 / 255f, 139 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #2F4F4F. - /// - public static readonly Color DarkSlateGray = new Color(47 / 255f, 79 / 255f, 79 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #00CED1. - /// - public static readonly Color DarkTurquoise = new Color(0, 206 / 255f, 209 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #9400D3. - /// - public static readonly Color DarkViolet = new Color(148 / 255f, 0, 211 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FF1493. - /// - public static readonly Color DeepPink = new Color(1, 20 / 255f, 147 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #00BFFF. - /// - public static readonly Color DeepSkyBlue = new Color(0, 191 / 255f, 1); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #696969. - /// - public static readonly Color DimGray = new Color(105 / 255f, 105 / 255f, 105 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #1E90FF. - /// - public static readonly Color DodgerBlue = new Color(30 / 255f, 144 / 255f, 1); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #B22222. - /// - public static readonly Color Firebrick = new Color(178 / 255f, 34 / 255f, 34 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFFAF0. - /// - public static readonly Color FloralWhite = new Color(1, 250 / 255f, 240 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #228B22. - /// - public static readonly Color ForestGreen = new Color(34 / 255f, 139 / 255f, 34 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FF00FF. - /// - public static readonly Color Fuchsia = new Color(1, 0, 1); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #DCDCDC. - /// - public static readonly Color Gainsboro = new Color(220 / 255f, 220 / 255f, 220 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #F8F8FF. - /// - public static readonly Color GhostWhite = new Color(248 / 255f, 248 / 255f, 1); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFD700. - /// - public static readonly Color Gold = new Color(1, 215 / 255f, 0); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #DAA520. - /// - public static readonly Color Goldenrod = new Color(218 / 255f, 165 / 255f, 32 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #808080. - /// - public static readonly Color Gray = new Color(128 / 255f, 128 / 255f, 128 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #008000. - /// - public static readonly Color Green = new Color(0, 128 / 255f, 0); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #ADFF2F. - /// - public static readonly Color GreenYellow = new Color(173 / 255f, 1, 47 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #F0FFF0. - /// - public static readonly Color Honeydew = new Color(240 / 255f, 1, 240 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FF69B4. - /// - public static readonly Color HotPink = new Color(1, 105 / 255f, 180 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #CD5C5C. - /// - public static readonly Color IndianRed = new Color(205 / 255f, 92 / 255f, 92 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #4B0082. - /// - public static readonly Color Indigo = new Color(75 / 255f, 0, 130 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFFFF0. - /// - public static readonly Color Ivory = new Color(1, 1, 240 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #F0E68C. - /// - public static readonly Color Khaki = new Color(240 / 255f, 230 / 255f, 140 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #E6E6FA. - /// - public static readonly Color Lavender = new Color(230 / 255f, 230 / 255f, 250 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFF0F5. - /// - public static readonly Color LavenderBlush = new Color(1, 240 / 255f, 245 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #7CFC00. - /// - public static readonly Color LawnGreen = new Color(124 / 255f, 252 / 255f, 0); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFFACD. - /// - public static readonly Color LemonChiffon = new Color(1, 250 / 255f, 205 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #ADD8E6. - /// - public static readonly Color LightBlue = new Color(173 / 255f, 216 / 255f, 230 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #F08080. - /// - public static readonly Color LightCoral = new Color(240 / 255f, 128 / 255f, 128 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #E0FFFF. - /// - public static readonly Color LightCyan = new Color(224 / 255f, 1, 1); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FAFAD2. - /// - public static readonly Color LightGoldenrodYellow = new Color(250 / 255f, 250 / 255f, 210 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #90EE90. - /// - public static readonly Color LightGreen = new Color(144 / 255f, 238 / 255f, 144 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #D3D3D3. - /// - public static readonly Color LightGray = new Color(211 / 255f, 211 / 255f, 211 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFB6C1. - /// - public static readonly Color LightPink = new Color(1, 182 / 255f, 193 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFA07A. - /// - public static readonly Color LightSalmon = new Color(1, 160 / 255f, 122 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #20B2AA. - /// - public static readonly Color LightSeaGreen = new Color(32 / 255f, 178 / 255f, 170 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #87CEFA. - /// - public static readonly Color LightSkyBlue = new Color(135 / 255f, 206 / 255f, 250 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #778899. - /// - public static readonly Color LightSlateGray = new Color(119 / 255f, 136 / 255f, 153 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #B0C4DE. - /// - public static readonly Color LightSteelBlue = new Color(176 / 255f, 196 / 255f, 222 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFFFE0. - /// - public static readonly Color LightYellow = new Color(1, 1, 224 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #00FF00. - /// - public static readonly Color Lime = new Color(0, 1, 0); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #32CD32. - /// - public static readonly Color LimeGreen = new Color(50 / 255f, 205 / 255f, 50 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FAF0E6. - /// - public static readonly Color Linen = new Color(250 / 255f, 240 / 255f, 230 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FF00FF. - /// - public static readonly Color Magenta = new Color(1, 0, 1); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #800000. - /// - public static readonly Color Maroon = new Color(128 / 255f, 0, 0); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #66CDAA. - /// - public static readonly Color MediumAquamarine = new Color(102 / 255f, 205 / 255f, 170 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #0000CD. - /// - public static readonly Color MediumBlue = new Color(0, 0, 205 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #BA55D3. - /// - public static readonly Color MediumOrchid = new Color(186 / 255f, 85 / 255f, 211 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #9370DB. - /// - public static readonly Color MediumPurple = new Color(147 / 255f, 112 / 255f, 219 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #3CB371. - /// - public static readonly Color MediumSeaGreen = new Color(60 / 255f, 179 / 255f, 113 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #7B68EE. - /// - public static readonly Color MediumSlateBlue = new Color(123 / 255f, 104 / 255f, 238 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #00FA9A. - /// - public static readonly Color MediumSpringGreen = new Color(0, 250 / 255f, 154 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #48D1CC. - /// - public static readonly Color MediumTurquoise = new Color(72 / 255f, 209 / 255f, 204 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #C71585. - /// - public static readonly Color MediumVioletRed = new Color(199 / 255f, 21 / 255f, 133 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #191970. - /// - public static readonly Color MidnightBlue = new Color(25 / 255f, 25 / 255f, 112 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #F5FFFA. - /// - public static readonly Color MintCream = new Color(245 / 255f, 1, 250 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFE4E1. - /// - public static readonly Color MistyRose = new Color(1, 228 / 255f, 225 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFE4B5. - /// - public static readonly Color Moccasin = new Color(1, 228 / 255f, 181 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFDEAD. - /// - public static readonly Color NavajoWhite = new Color(1, 222 / 255f, 173 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #000080. - /// - public static readonly Color Navy = new Color(0, 0, 128 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FDF5E6. - /// - public static readonly Color OldLace = new Color(253 / 255f, 245 / 255f, 230 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #808000. - /// - public static readonly Color Olive = new Color(128 / 255f, 128 / 255f, 0); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #6B8E23. - /// - public static readonly Color OliveDrab = new Color(107 / 255f, 142 / 255f, 35 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFA500. - /// - public static readonly Color Orange = new Color(1, 165 / 255f, 0); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FF4500. - /// - public static readonly Color OrangeRed = new Color(1, 69 / 255f, 0); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #DA70D6. - /// - public static readonly Color Orchid = new Color(218 / 255f, 112 / 255f, 214 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #EEE8AA. - /// - public static readonly Color PaleGoldenrod = new Color(238 / 255f, 232 / 255f, 170 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #98FB98. - /// - public static readonly Color PaleGreen = new Color(152 / 255f, 251 / 255f, 152 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #AFEEEE. - /// - public static readonly Color PaleTurquoise = new Color(175 / 255f, 238 / 255f, 238 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #DB7093. - /// - public static readonly Color PaleVioletRed = new Color(219 / 255f, 112 / 255f, 147 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFEFD5. - /// - public static readonly Color PapayaWhip = new Color(1, 239 / 255f, 213 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFDAB9. - /// - public static readonly Color PeachPuff = new Color(1, 218 / 255f, 185 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #CD853F. - /// - public static readonly Color Peru = new Color(205 / 255f, 133 / 255f, 63 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFC0CB. - /// - public static readonly Color Pink = new Color(1, 192 / 255f, 203 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #DDA0DD. - /// - public static readonly Color Plum = new Color(221 / 255f, 160 / 255f, 221 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #B0E0E6. - /// - public static readonly Color PowderBlue = new Color(176 / 255f, 224 / 255f, 230 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #800080. - /// - public static readonly Color Purple = new Color(128 / 255f, 0, 128 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #663399. - /// - public static readonly Color RebeccaPurple = new Color(102 / 255f, 51 / 255f, 153 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FF0000. - /// - public static readonly Color Red = new Color(1, 0, 0); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #BC8F8F. - /// - public static readonly Color RosyBrown = new Color(188 / 255f, 143 / 255f, 143 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #4169E1. - /// - public static readonly Color RoyalBlue = new Color(65 / 255f, 105 / 255f, 225 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #8B4513. - /// - public static readonly Color SaddleBrown = new Color(139 / 255f, 69 / 255f, 19 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FA8072. - /// - public static readonly Color Salmon = new Color(250 / 255f, 128 / 255f, 114 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #F4A460. - /// - public static readonly Color SandyBrown = new Color(244 / 255f, 164 / 255f, 96 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #2E8B57. - /// - public static readonly Color SeaGreen = new Color(46 / 255f, 139 / 255f, 87 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFF5EE. - /// - public static readonly Color SeaShell = new Color(1, 245 / 255f, 238 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #A0522D. - /// - public static readonly Color Sienna = new Color(160 / 255f, 82 / 255f, 45 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #C0C0C0. - /// - public static readonly Color Silver = new Color(192 / 255f, 192 / 255f, 192 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #87CEEB. - /// - public static readonly Color SkyBlue = new Color(135 / 255f, 206 / 255f, 235 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #6A5ACD. - /// - public static readonly Color SlateBlue = new Color(106 / 255f, 90 / 255f, 205 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #708090. - /// - public static readonly Color SlateGray = new Color(112 / 255f, 128 / 255f, 144 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFFAFA. - /// - public static readonly Color Snow = new Color(1, 250 / 255f, 250 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #00FF7F. - /// - public static readonly Color SpringGreen = new Color(0, 1, 127 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #4682B4. - /// - public static readonly Color SteelBlue = new Color(70 / 255f, 130 / 255f, 180 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #D2B48C. - /// - public static readonly Color Tan = new Color(210 / 255f, 180 / 255f, 140 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #008080. - /// - public static readonly Color Teal = new Color(0, 128 / 255f, 128 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #D8BFD8. - /// - public static readonly Color Thistle = new Color(216 / 255f, 191 / 255f, 216 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FF6347. - /// - public static readonly Color Tomato = new Color(1, 99 / 255f, 71 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #40E0D0. - /// - public static readonly Color Turquoise = new Color(64 / 255f, 224 / 255f, 208 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #EE82EE. - /// - public static readonly Color Violet = new Color(238 / 255f, 130 / 255f, 238 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #F5DEB3. - /// - public static readonly Color Wheat = new Color(245 / 255f, 222 / 255f, 179 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFFFFF. - /// - public static readonly Color White = new Color(1, 1, 1); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #F5F5F5. - /// - public static readonly Color WhiteSmoke = new Color(245 / 255f, 245 / 255f, 245 / 255f); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #FFFF00. - /// - public static readonly Color Yellow = new Color(1, 1, 0); - - /// - /// Represents a matching the W3C definition that has a hex triplet value of #9ACD32. - /// - public static readonly Color YellowGreen = new Color(154 / 255f, 205 / 255f, 50 / 255f); - - /// - /// Represents a system-defined that has an ARGB value of #00FFFFFF. - /// - public static readonly Color Transparent = new Color(1, 1, 1, 0); - } -} diff --git a/src/ImageProcessorCore - Copy/Colors/ColorTransforms.cs b/src/ImageProcessorCore - Copy/Colors/ColorTransforms.cs deleted file mode 100644 index 2a0da9407f..0000000000 --- a/src/ImageProcessorCore - Copy/Colors/ColorTransforms.cs +++ /dev/null @@ -1,73 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - - /// - /// Represents a four-component color using red, green, blue, and alpha data. - /// Each component is stored in premultiplied format multiplied by the alpha component. - /// - /// - /// 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 - { - /// - /// Blends two colors by multiplication. - /// - /// The source color is multiplied by the destination color and replaces the destination. - /// The resultant color is always at least as dark as either the source or destination color. - /// Multiplying any color with black results in black. Multiplying any color with white preserves the - /// original color. - /// - /// - /// The source color. - /// The destination color. - /// - /// The . - /// - public static Color Multiply(Color source, Color destination) - { - if (destination == Color.Black) - { - return Color.Black; - } - - if (destination == Color.White) - { - return source; - } - - return new Color(source.backingVector * destination.backingVector); - } - - /// - /// Linearly interpolates from one color to another based on the given amount. - /// - /// The first color value. - /// The second color value. - /// - /// The weight value. At amount = 0, "from" is returned, at amount = 1, "to" is returned. - /// - /// - /// The - /// - public static Color Lerp(Color source, Color destination, 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; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Colors/ColorspaceTransforms.cs b/src/ImageProcessorCore - Copy/Colors/ColorspaceTransforms.cs deleted file mode 100644 index 50d0af3150..0000000000 --- a/src/ImageProcessorCore - Copy/Colors/ColorspaceTransforms.cs +++ /dev/null @@ -1,285 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - - /// - /// Represents a four-component color using red, green, blue, and alpha data. - /// Each component is stored in premultiplied format multiplied by the alpha component. - /// - /// - /// 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 - { - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// - /// An instance of . - /// - public static implicit operator Color(Bgra32 color) - { - return new Color(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f); - } - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// - /// An instance of . - /// - public static implicit operator Color(Cmyk cmykColor) - { - 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); - } - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// - /// An instance of . - /// - public static implicit operator Color(YCbCr color) - { - float y = color.Y; - 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; - - return new Color(r, g, b); - } - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// - /// An instance of . - /// - public static implicit operator Color(CieXyz color) - { - float x = color.X / 100F; - float y = color.Y / 100F; - float z = color.Z / 100F; - - // Then XYZ to RGB (multiplication by 100 was done above already) - float r = (x * 3.2406F) + (y * -1.5372F) + (z * -0.4986F); - 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)); - } - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// - /// An instance of . - /// - public static implicit operator Color(Hsv color) - { - float s = color.S; - float v = color.V; - - if (Math.Abs(s) < Epsilon) - { - return new Color(v, v, v, 1); - } - - float h = (Math.Abs(color.H - 360) < Epsilon) ? 0 : color.H / 60; - 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 r, g, b; - switch (i) - { - case 0: - r = v; - g = t; - b = p; - break; - - case 1: - r = q; - g = v; - b = p; - break; - - case 2: - r = p; - g = v; - b = t; - break; - - case 3: - r = p; - g = q; - b = v; - break; - - case 4: - r = t; - g = p; - b = v; - break; - - default: - r = v; - g = p; - b = q; - break; - } - - return new Color(r, g, b); - } - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// - /// An instance of . - /// - public static implicit operator Color(Hsl color) - { - float rangedH = color.H / 360f; - float r = 0; - float g = 0; - float b = 0; - float s = color.S; - float l = color.L; - - if (Math.Abs(l) > Epsilon) - { - if (Math.Abs(s) < Epsilon) - { - r = g = b = l; - } - else - { - float temp2 = (l < 0.5f) ? l * (1f + s) : l + s - (l * s); - float temp1 = (2f * l) - temp2; - - r = GetColorComponent(temp1, temp2, rangedH + 0.3333333F); - g = GetColorComponent(temp1, temp2, rangedH); - b = GetColorComponent(temp1, temp2, rangedH - 0.3333333F); - } - } - - return new Color(r, g, b); - } - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// - /// An instance of . - /// - public static implicit operator Color(CieLab cieLabColor) - { - // First convert back to XYZ... - float y = (cieLabColor.L + 16F) / 116F; - float x = (cieLabColor.A / 500F) + y; - float z = y - (cieLabColor.B / 200F); - - float x3 = x * x * x; - float y3 = y * y * y; - float z3 = z * z * z; - - x = x3 > 0.008856F ? x3 : (x - 0.137931F) / 7.787F; - y = (cieLabColor.L > 7.999625F) ? y3 : (cieLabColor.L / 903.3F); - z = (z3 > 0.008856F) ? z3 : (z - 0.137931F) / 7.787F; - - x *= 0.95047F; - z *= 1.08883F; - - // Then XYZ to RGB (multiplication by 100 was done above already) - float r = (x * 3.2406F) + (y * -1.5372F) + (z * -0.4986F); - 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)); - } - - /// - /// Gets the color component from the given values. - /// - /// The first value. - /// The second value. - /// The third value. - /// - /// The . - /// - private static float GetColorComponent(float first, float second, float third) - { - third = MoveIntoRange(third); - if (third < 0.1666667F) - { - return first + ((second - first) * 6.0f * third); - } - - if (third < 0.5) - { - return second; - } - - if (third < 0.6666667F) - { - return first + ((second - first) * (0.6666667F - third) * 6.0f); - } - - return first; - } - - /// - /// Moves the specific value within the acceptable range for - /// conversion. - /// Used for converting colors to this type. - /// - /// The value to shift. - /// - /// The . - /// - private static float MoveIntoRange(float value) - { - if (value < 0.0) - { - value += 1.0f; - } - else if (value > 1.0) - { - value -= 1.0f; - } - - return value; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Colors/Colorspaces/Bgra32.cs b/src/ImageProcessorCore - Copy/Colors/Colorspaces/Bgra32.cs deleted file mode 100644 index cc4fad541e..0000000000 --- a/src/ImageProcessorCore - Copy/Colors/Colorspaces/Bgra32.cs +++ /dev/null @@ -1,168 +0,0 @@ -//// -//// Copyright (c) James Jackson-South and contributors. -//// Licensed under the Apache License, Version 2.0. -//// - -//namespace ImageProcessorCore -//{ -// using System; -// using System.ComponentModel; -// using System.Numerics; - -// /// -// /// Represents an BGRA (blue, green, red, alpha) color. -// /// -// public struct Bgra32 : IEquatable -// { -// /// -// /// Represents a 32 bit that has B, G, R, and A values set to zero. -// /// -// public static readonly Bgra32 Empty = default(Bgra32); - -// /// -// /// The backing vector for SIMD support. -// /// -// private Vector4 backingVector; - -// /// -// /// Initializes a new instance of the struct. -// /// -// /// The blue component of this . -// /// The green component of this . -// /// The red component of this . -// /// The alpha component of this . -// public Bgra32(byte b, byte g, byte r, byte a = 255) -// : this() -// { -// this.backingVector = Vector4.Clamp(new Vector4(b, g, r, a), Vector4.Zero, new Vector4(255)); -// } - -// /// -// /// Gets the blue component of the color -// /// -// public byte B => (byte)this.backingVector.X; - -// /// -// /// Gets the green component of the color -// /// -// public byte G => (byte)this.backingVector.Y; - -// /// -// /// Gets the red component of the color -// /// -// public byte R => (byte)this.backingVector.Z; - -// /// -// /// Gets the alpha component of the color -// /// -// public byte A => (byte)this.backingVector.W; - -// /// -// /// Gets the integer representation of the color. -// /// -// public int Bgra => (this.R << 16) | (this.G << 8) | (this.B << 0) | (this.A << 24); - -// /// -// /// Gets a value indicating whether this is empty. -// /// -// [EditorBrowsable(EditorBrowsableState.Never)] -// public bool IsEmpty => this.Equals(Empty); - -// /// -// /// Allows the implicit conversion of an instance of to a -// /// . -// /// -// /// -// /// The instance of to convert. -// /// -// /// -// /// An instance of . -// /// -// 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); -// } - -// /// -// /// Compares two objects for equality. -// /// -// /// -// /// The on the left side of the operand. -// /// -// /// -// /// The on the right side of the operand. -// /// -// /// -// /// True if the current left is equal to the parameter; otherwise, false. -// /// -// public static bool operator ==(Bgra32 left, Bgra32 right) -// { -// return left.Equals(right); -// } - -// /// -// /// Compares two objects for inequality. -// /// -// /// -// /// The on the left side of the operand. -// /// -// /// -// /// The on the right side of the operand. -// /// -// /// -// /// True if the current left is unequal to the parameter; otherwise, false. -// /// -// public static bool operator !=(Bgra32 left, Bgra32 right) -// { -// return !left.Equals(right); -// } - -// /// -// public override bool Equals(object obj) -// { -// if (obj is Bgra32) -// { -// Bgra32 color = (Bgra32)obj; - -// return this.backingVector == color.backingVector; -// } - -// return false; -// } - -// /// -// public override int GetHashCode() -// { -// return GetHashCode(this); -// } - -// /// -// public override string ToString() -// { -// if (this.IsEmpty) -// { -// return "Bgra32 [ Empty ]"; -// } - -// return $"Bgra32 [ B={this.B}, G={this.G}, R={this.R}, A={this.A} ]"; -// } - -// /// -// public bool Equals(Bgra32 other) -// { -// return this.backingVector.Equals(other.backingVector); -// } - -// /// -// /// Returns the hash code for this instance. -// /// -// /// -// /// The instance of to return the hash code for. -// /// -// /// -// /// A 32-bit signed integer that is the hash code for this instance. -// /// -// private static int GetHashCode(Bgra32 color) => color.backingVector.GetHashCode(); -// } -//} diff --git a/src/ImageProcessorCore - Copy/Colors/Colorspaces/CieLab.cs b/src/ImageProcessorCore - Copy/Colors/Colorspaces/CieLab.cs deleted file mode 100644 index cce92601ad..0000000000 --- a/src/ImageProcessorCore - Copy/Colors/Colorspaces/CieLab.cs +++ /dev/null @@ -1,193 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - using System.ComponentModel; - using System.Numerics; - - /// - /// Represents an CIE LAB 1976 color. - /// - /// - public struct CieLab : IEquatable, IAlmostEquatable - { - /// - /// Represents a that has L, A, B values set to zero. - /// - public static readonly CieLab Empty = default(CieLab); - - /// - /// The epsilon for comparing floating point numbers. - /// - private const float Epsilon = 0.001f; - - /// - /// The backing vector for SIMD support. - /// - private Vector3 backingVector; - - /// - /// Initializes a new instance of the struct. - /// - /// The lightness dimension. - /// The a (green - magenta) component. - /// The b (blue - yellow) component. - public CieLab(float l, float a, float b) - : this() - { - this.backingVector = Vector3.Clamp(new Vector3(l, a, b), new Vector3(0, -100, -100), new Vector3(100)); - } - - /// - /// Gets the lightness dimension. - /// A value ranging between 0 (black), 100 (diffuse white) or higher (specular white). - /// - public float L => this.backingVector.X; - - /// - /// Gets the a color component. - /// Negative is green, positive magenta. - /// - public float A => this.backingVector.Y; - - /// - /// Gets the b color component. - /// Negative is blue, positive is yellow - /// - public float B => this.backingVector.Z; - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// - /// The instance of to convert. - /// - /// - /// An instance of . - /// - 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); - - // Now to LAB - x /= 0.95047F; - //y /= 1F; - z /= 1.08883F; - - x = x > 0.008856F ? (float)Math.Pow(x, 0.3333333F) : (903.3F * x + 16F) / 116F; - y = y > 0.008856F ? (float)Math.Pow(y, 0.3333333F) : (903.3F * y + 16F) / 116F; - z = z > 0.008856F ? (float)Math.Pow(z, 0.3333333F) : (903.3F * z + 16F) / 116F; - - float l = Math.Max(0, (116F * y) - 16F); - float a = 500F * (x - y); - float b = 200F * (y - z); - - return new CieLab(l, a, b); - } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(CieLab left, CieLab right) - { - return left.Equals(right); - } - - /// - /// Compares two objects for inequality - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(CieLab left, CieLab right) - { - return !left.Equals(right); - } - - /// - public override int GetHashCode() - { - return GetHashCode(this); - } - - /// - public override string ToString() - { - if (this.IsEmpty) - { - return "CieLab [Empty]"; - } - - return $"CieLab [ L={this.L:#0.##}, A={this.A:#0.##}, B={this.B:#0.##}]"; - } - - /// - public override bool Equals(object obj) - { - if (obj is CieLab) - { - return this.Equals((CieLab)obj); - } - - return false; - } - - /// - public bool Equals(CieLab other) - { - return this.AlmostEquals(other, Epsilon); - } - - /// - public bool AlmostEquals(CieLab other, float precision) - { - Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); - - return result.X < precision - && result.Y < precision - && result.Z < precision; - } - - /// - /// 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(CieLab color) => color.backingVector.GetHashCode(); - } -} diff --git a/src/ImageProcessorCore - Copy/Colors/Colorspaces/CieXyz.cs b/src/ImageProcessorCore - Copy/Colors/Colorspaces/CieXyz.cs deleted file mode 100644 index 8f41c8abbe..0000000000 --- a/src/ImageProcessorCore - Copy/Colors/Colorspaces/CieXyz.cs +++ /dev/null @@ -1,184 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - using System.ComponentModel; - using System.Numerics; - - /// - /// Represents an CIE 1931 color - /// - /// - public struct CieXyz : IEquatable, IAlmostEquatable - { - /// - /// Represents a that has Y, Cb, and Cr values set to zero. - /// - public static readonly CieXyz Empty = default(CieXyz); - - /// - /// The epsilon for comparing floating point numbers. - /// - private const float Epsilon = 0.001f; - - /// - /// The backing vector for SIMD support. - /// - private Vector3 backingVector; - - /// - /// Initializes a new instance of the struct. - /// - /// The y luminance component. - /// X is a mix (a linear combination) of cone response curves chosen to be nonnegative - /// Z is quasi-equal to blue stimulation, or the S cone of the human eye. - public CieXyz(float x, float y, float z) - : this() - { - // Not clamping as documentation about this space seems to indicate "usual" ranges - this.backingVector = new Vector3(x, y, z); - } - - /// - /// Gets the Y luminance component. - /// A value ranging between 380 and 780. - /// - public float X => this.backingVector.X; - - /// - /// Gets the Cb chroma component. - /// A value ranging between 380 and 780. - /// - public float Y => this.backingVector.Y; - - /// - /// Gets the Cr chroma component. - /// A value ranging between 380 and 780. - /// - public float Z => this.backingVector.Z; - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// - /// The instance of to convert. - /// - /// - /// An instance of . - /// - public static implicit operator CieXyz(Color color) - { - 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); - - x *= 100F; - y *= 100F; - z *= 100F; - - return new CieXyz(x, y, z); - } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(CieXyz left, CieXyz right) - { - return left.Equals(right); - } - - /// - /// Compares two objects for inequality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(CieXyz left, CieXyz right) - { - return !left.Equals(right); - } - - /// - public override int GetHashCode() - { - return GetHashCode(this); - } - - /// - public override string ToString() - { - if (this.IsEmpty) - { - return "CieXyz [ Empty ]"; - } - - return $"CieXyz [ X={this.X:#0.##}, Y={this.Y:#0.##}, Z={this.Z:#0.##} ]"; - } - - /// - public override bool Equals(object obj) - { - if (obj is CieXyz) - { - return this.Equals((CieXyz)obj); - } - - return false; - } - - /// - public bool Equals(CieXyz other) - { - return this.AlmostEquals(other, Epsilon); - } - - /// - public bool AlmostEquals(CieXyz other, float precision) - { - Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); - - return result.X < precision - && result.Y < precision - && result.Z < precision; - } - - /// - /// 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(CieXyz color) => color.backingVector.GetHashCode(); - } -} diff --git a/src/ImageProcessorCore - Copy/Colors/Colorspaces/Cmyk.cs b/src/ImageProcessorCore - Copy/Colors/Colorspaces/Cmyk.cs deleted file mode 100644 index b343288a68..0000000000 --- a/src/ImageProcessorCore - Copy/Colors/Colorspaces/Cmyk.cs +++ /dev/null @@ -1,197 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - using System.ComponentModel; - using System.Numerics; - - /// - /// Represents an CMYK (cyan, magenta, yellow, keyline) color. - /// - public struct Cmyk : IEquatable, IAlmostEquatable - { - /// - /// Represents a that has C, M, Y, and K values set to zero. - /// - public static readonly Cmyk Empty = default(Cmyk); - - /// - /// The epsilon for comparing floating point numbers. - /// - private const float Epsilon = 0.001f; - - /// - /// The backing vector for SIMD support. - /// - private Vector4 backingVector; - - /// - /// Initializes a new instance of the struct. - /// - /// The cyan component. - /// The magenta component. - /// The yellow component. - /// The keyline black component. - public Cmyk(float c, float m, float y, float k) - : this() - { - this.backingVector = Vector4.Clamp(new Vector4(c, m, y, k), Vector4.Zero, Vector4.One); - } - - /// - /// Gets the cyan color component. - /// A value ranging between 0 and 1. - /// - public float C => this.backingVector.X; - - /// - /// Gets the magenta color component. - /// A value ranging between 0 and 1. - /// - public float M => this.backingVector.Y; - - /// - /// Gets the yellow color component. - /// A value ranging between 0 and 1. - /// - public float Y => this.backingVector.Z; - - /// - /// Gets the keyline black color component. - /// A value ranging between 0 and 1. - /// - public float K => this.backingVector.W; - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// - /// The instance of to convert. - /// - /// - /// An instance of . - /// - 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 k = Math.Min(c, Math.Min(m, y)); - - if (Math.Abs(k - 1.0f) <= Epsilon) - { - return new Cmyk(0, 0, 0, 1); - } - - c = (c - k) / (1 - k); - m = (m - k) / (1 - k); - y = (y - k) / (1 - k); - - return new Cmyk(c, m, y, k); - } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(Cmyk left, Cmyk right) - { - return left.Equals(right); - } - - /// - /// Compares two objects for inequality - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(Cmyk left, Cmyk right) - { - return !left.Equals(right); - } - - /// - public override int GetHashCode() - { - return GetHashCode(this); - } - - /// - public override string ToString() - { - if (this.IsEmpty) - { - return "Cmyk [Empty]"; - } - - return $"Cmyk [ C={this.C:#0.##}, M={this.M:#0.##}, Y={this.Y:#0.##}, K={this.K:#0.##}]"; - } - - /// - public override bool Equals(object obj) - { - if (obj is Cmyk) - { - return this.Equals((Cmyk)obj); - } - - return false; - } - - /// - public bool Equals(Cmyk other) - { - return this.AlmostEquals(other, Epsilon); - } - - /// - public bool AlmostEquals(Cmyk other, float precision) - { - Vector4 result = Vector4.Abs(this.backingVector - other.backingVector); - - return result.X < precision - && result.Y < precision - && result.Z < precision - && result.W < precision; - } - - /// - /// 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(Cmyk color) => color.backingVector.GetHashCode(); - } -} diff --git a/src/ImageProcessorCore - Copy/Colors/Colorspaces/Hsl.cs b/src/ImageProcessorCore - Copy/Colors/Colorspaces/Hsl.cs deleted file mode 100644 index e6eee42e35..0000000000 --- a/src/ImageProcessorCore - Copy/Colors/Colorspaces/Hsl.cs +++ /dev/null @@ -1,213 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - using System.ComponentModel; - using System.Numerics; - - /// - /// Represents a Hsl (hue, saturation, lightness) color. - /// - public struct Hsl : IEquatable, IAlmostEquatable - { - /// - /// Represents a that has H, S, and L values set to zero. - /// - public static readonly Hsl Empty = default(Hsl); - - /// - /// The epsilon for comparing floating point numbers. - /// - private const float Epsilon = 0.001f; - - /// - /// The backing vector for SIMD support. - /// - private Vector3 backingVector; - - /// - /// Initializes a new instance of the struct. - /// - /// The h hue component. - /// The s saturation component. - /// The l value (lightness) component. - public Hsl(float h, float s, float l) - { - this.backingVector = Vector3.Clamp(new Vector3(h, s, l), Vector3.Zero, new Vector3(360, 1, 1)); - } - - /// - /// Gets the hue component. - /// A value ranging between 0 and 360. - /// - public float H => this.backingVector.X; - - /// - /// Gets the saturation component. - /// A value ranging between 0 and 1. - /// - public float S => this.backingVector.Y; - - /// - /// Gets the lightness component. - /// A value ranging between 0 and 1. - /// - public float L => this.backingVector.Z; - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// - /// An instance of . - /// - 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 max = Math.Max(r, Math.Max(g, b)); - float min = Math.Min(r, Math.Min(g, b)); - float chroma = max - min; - float h = 0; - float s = 0; - float l = (max + min) / 2; - - if (Math.Abs(chroma) < Epsilon) - { - return new Hsl(0, s, l); - } - - if (Math.Abs(r - max) < Epsilon) - { - h = (g - b) / chroma; - } - else if (Math.Abs(g - max) < Epsilon) - { - h = 2 + ((b - r) / chroma); - } - else if (Math.Abs(b - max) < Epsilon) - { - h = 4 + ((r - g) / chroma); - } - - h *= 60; - if (h < 0.0) - { - h += 360; - } - - if (l <= .5f) - { - s = chroma / (max + min); - } - else { - s = chroma / (2 - chroma); - } - - return new Hsl(h, s, l); - } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(Hsl left, Hsl right) - { - return left.Equals(right); - } - - /// - /// Compares two objects for inequality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(Hsl left, Hsl right) - { - return !left.Equals(right); - } - - /// - public override int GetHashCode() - { - return GetHashCode(this); - } - - /// - public override string ToString() - { - if (this.IsEmpty) - { - return "Hsl [ Empty ]"; - } - - return $"Hsl [ H={this.H:#0.##}, S={this.S:#0.##}, L={this.L:#0.##} ]"; - } - - /// - public override bool Equals(object obj) - { - if (obj is Hsl) - { - return this.Equals((Hsl)obj); - } - - return false; - } - - /// - public bool Equals(Hsl other) - { - return this.AlmostEquals(other, Epsilon); - } - - /// - public bool AlmostEquals(Hsl other, float precision) - { - Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); - - return result.X < precision - && result.Y < precision - && result.Z < precision; - } - - /// - /// 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(Hsl color) => color.backingVector.GetHashCode(); - } -} diff --git a/src/ImageProcessorCore - Copy/Colors/Colorspaces/Hsv.cs b/src/ImageProcessorCore - Copy/Colors/Colorspaces/Hsv.cs deleted file mode 100644 index 914df51c97..0000000000 --- a/src/ImageProcessorCore - Copy/Colors/Colorspaces/Hsv.cs +++ /dev/null @@ -1,207 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - using System.ComponentModel; - using System.Numerics; - - /// - /// Represents a HSV (hue, saturation, value) color. Also known as HSB (hue, saturation, brightness). - /// - public struct Hsv : IEquatable, IAlmostEquatable - { - /// - /// Represents a that has H, S, and V values set to zero. - /// - public static readonly Hsv Empty = default(Hsv); - - /// - /// The epsilon for comparing floating point numbers. - /// - private const float Epsilon = 0.001f; - - /// - /// The backing vector for SIMD support. - /// - private Vector3 backingVector; - - /// - /// Initializes a new instance of the struct. - /// - /// The h hue component. - /// The s saturation component. - /// The v value (brightness) component. - public Hsv(float h, float s, float v) - { - this.backingVector = Vector3.Clamp(new Vector3(h, s, v), Vector3.Zero, new Vector3(360, 1, 1)); - } - - /// - /// Gets the hue component. - /// A value ranging between 0 and 360. - /// - public float H => this.backingVector.X; - - /// - /// Gets the saturation component. - /// A value ranging between 0 and 1. - /// - public float S => this.backingVector.Y; - - /// - /// Gets the value (brightness) component. - /// A value ranging between 0 and 1. - /// - public float V => this.backingVector.Z; - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// The instance of to convert. - /// - /// An instance of . - /// - 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 max = Math.Max(r, Math.Max(g, b)); - float min = Math.Min(r, Math.Min(g, b)); - float chroma = max - min; - float h = 0; - float s = 0; - float v = max; - - if (Math.Abs(chroma) < Epsilon) - { - return new Hsv(0, s, v); - } - - if (Math.Abs(r - max) < Epsilon) - { - h = (g - b) / chroma; - } - else if (Math.Abs(g - max) < Epsilon) - { - h = 2 + ((b - r) / chroma); - } - else if (Math.Abs(b - max) < Epsilon) - { - h = 4 + ((r - g) / chroma); - } - - h *= 60; - if (h < 0.0) - { - h += 360; - } - - s = chroma / v; - - return new Hsv(h, s, v); - } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(Hsv left, Hsv right) - { - return left.Equals(right); - } - - /// - /// Compares two objects for inequality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(Hsv left, Hsv right) - { - return !left.Equals(right); - } - - /// - public override int GetHashCode() - { - return GetHashCode(this); - } - - /// - public override string ToString() - { - if (this.IsEmpty) - { - return "Hsv [ Empty ]"; - } - - return $"Hsv [ H={this.H:#0.##}, S={this.S:#0.##}, V={this.V:#0.##} ]"; - } - - /// - public override bool Equals(object obj) - { - if (obj is Hsv) - { - return this.Equals((Hsv)obj); - } - - return false; - } - - /// - public bool Equals(Hsv other) - { - return this.AlmostEquals(other, Epsilon); - } - - /// - public bool AlmostEquals(Hsv other, float precision) - { - Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); - - return result.X < precision - && result.Y < precision - && result.Z < precision; - } - - /// - /// 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(Hsv color) => color.backingVector.GetHashCode(); - } -} diff --git a/src/ImageProcessorCore - Copy/Colors/Colorspaces/YCbCr.cs b/src/ImageProcessorCore - Copy/Colors/Colorspaces/YCbCr.cs deleted file mode 100644 index 5c47c6c087..0000000000 --- a/src/ImageProcessorCore - Copy/Colors/Colorspaces/YCbCr.cs +++ /dev/null @@ -1,183 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - using System.ComponentModel; - using System.Numerics; - - /// - /// Represents an YCbCr (luminance, chroma, chroma) color conforming to the - /// Full range standard used in digital imaging systems. - /// - /// - public struct YCbCr : IEquatable, IAlmostEquatable - { - /// - /// Represents a that has Y, Cb, and Cr values set to zero. - /// - public static readonly YCbCr Empty = default(YCbCr); - - /// - /// The epsilon for comparing floating point numbers. - /// - private const float Epsilon = 0.001f; - - /// - /// The backing vector for SIMD support. - /// - private Vector3 backingVector; - - /// - /// Initializes a new instance of the struct. - /// - /// The y luminance component. - /// The cb chroma component. - /// The cr chroma component. - public YCbCr(float y, float cb, float cr) - : this() - { - this.backingVector = Vector3.Clamp(new Vector3(y, cb, cr), Vector3.Zero, new Vector3(255)); - } - - /// - /// Gets the Y luminance component. - /// A value ranging between 0 and 255. - /// - public float Y => this.backingVector.X; - - /// - /// Gets the Cb chroma component. - /// A value ranging between 0 and 255. - /// - public float Cb => this.backingVector.Y; - - /// - /// Gets the Cr chroma component. - /// A value ranging between 0 and 255. - /// - public float Cr => this.backingVector.Z; - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Allows the implicit conversion of an instance of to a - /// . - /// - /// - /// The instance of to convert. - /// - /// - /// An instance of . - /// - 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; - - float y = (float)((0.299 * r) + (0.587 * g) + (0.114 * b)); - float cb = 128 + (float)((-0.168736 * r) - (0.331264 * g) + (0.5 * b)); - float cr = 128 + (float)((0.5 * r) - (0.418688 * g) - (0.081312 * b)); - - return new YCbCr(y, cb, cr); - } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(YCbCr left, YCbCr right) - { - return left.Equals(right); - } - - /// - /// Compares two objects for inequality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(YCbCr left, YCbCr right) - { - return !left.Equals(right); - } - - /// - public override int GetHashCode() - { - return GetHashCode(this); - } - - /// - public override string ToString() - { - if (this.IsEmpty) - { - return "YCbCr [ Empty ]"; - } - - return $"YCbCr [ Y={this.Y:#0.##}, Cb={this.Cb:#0.##}, Cr={this.Cr:#0.##} ]"; - } - - /// - public override bool Equals(object obj) - { - if (obj is YCbCr) - { - return this.Equals((YCbCr)obj); - } - - return false; - } - - /// - public bool Equals(YCbCr other) - { - return this.AlmostEquals(other, Epsilon); - } - - /// - public bool AlmostEquals(YCbCr other, float precision) - { - Vector3 result = Vector3.Abs(this.backingVector - other.backingVector); - - return result.X < precision - && result.Y < precision - && result.Z < precision; - } - - /// - /// 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(YCbCr color) => color.backingVector.GetHashCode(); - } -} diff --git a/src/ImageProcessorCore - Copy/Colors/IAlmostEquatable.cs b/src/ImageProcessorCore - Copy/Colors/IAlmostEquatable.cs deleted file mode 100644 index 4677c3415f..0000000000 --- a/src/ImageProcessorCore - Copy/Colors/IAlmostEquatable.cs +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - - /// - /// Defines a generalized method that a value type or class implements to create - /// a type-specific method for determining approximate equality of instances. - /// - /// The type of objects to compare. - /// The object specifying the type to specify precision with. - public interface IAlmostEquatable where TP : struct, IComparable - { - /// - /// Indicates whether the current object is equal to another object of the same type - /// when compared to the specified precision level. - /// - /// An object to compare with this object. - /// The object specifying the level of precision. - /// - /// true if the current object is equal to the other parameter; otherwise, false. - /// - bool AlmostEquals(T other, TP precision); - } -} diff --git a/src/ImageProcessorCore - Copy/Colors/RgbaComponent.cs b/src/ImageProcessorCore - Copy/Colors/RgbaComponent.cs deleted file mode 100644 index a4b668cb62..0000000000 --- a/src/ImageProcessorCore - Copy/Colors/RgbaComponent.cs +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - /// - /// Enumerates the RGBA (red, green, blue, alpha) color components. - /// - public enum RgbaComponent - { - /// - /// The blue component. - /// - B = 0, - - /// - /// The green component. - /// - G = 1, - - /// - /// The red component. - /// - R = 2, - - /// - /// The alpha component. - /// - A = 3 - } -} diff --git a/src/ImageProcessorCore - Copy/Common/Exceptions/ImageFormatException.cs b/src/ImageProcessorCore - Copy/Common/Exceptions/ImageFormatException.cs deleted file mode 100644 index 87fb381597..0000000000 --- a/src/ImageProcessorCore - Copy/Common/Exceptions/ImageFormatException.cs +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - - /// - /// The exception that is thrown when the library tries to load - /// an image, which has an invalid format. - /// - public class ImageFormatException : Exception - { - /// - /// Initializes a new instance of the class. - /// - public ImageFormatException() - { - } - - /// - /// Initializes a new instance of the class with the name of the - /// parameter that causes this exception. - /// - /// The error message that explains the reason for this exception. - public ImageFormatException(string errorMessage) - : base(errorMessage) - { - } - - /// - /// Initializes a new instance of the class with a specified - /// error message and the exception that is the cause of this exception. - /// - /// The error message that explains the reason for this exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) - /// if no inner exception is specified. - public ImageFormatException(string errorMessage, Exception innerException) - : base(errorMessage, innerException) - { - } - } -} diff --git a/src/ImageProcessorCore - Copy/Common/Exceptions/ImageProcessingException.cs b/src/ImageProcessorCore - Copy/Common/Exceptions/ImageProcessingException.cs deleted file mode 100644 index 7899dcf44c..0000000000 --- a/src/ImageProcessorCore - Copy/Common/Exceptions/ImageProcessingException.cs +++ /dev/null @@ -1,44 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - - /// - /// The exception that is thrown when an error occurs when applying a process to an image. - /// - public class ImageProcessingException : Exception - { - /// - /// Initializes a new instance of the class. - /// - public ImageProcessingException() - { - } - - /// - /// Initializes a new instance of the class with the name of the - /// parameter that causes this exception. - /// - /// The error message that explains the reason for this exception. - public ImageProcessingException(string errorMessage) - : base(errorMessage) - { - } - - /// - /// Initializes a new instance of the class with a specified - /// error message and the exception that is the cause of this exception. - /// - /// The error message that explains the reason for this exception. - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) - /// if no inner exception is specified. - public ImageProcessingException(string errorMessage, Exception innerException) - : base(errorMessage, innerException) - { - } - } -} diff --git a/src/ImageProcessorCore - Copy/Common/Extensions/ByteExtensions.cs b/src/ImageProcessorCore - Copy/Common/Extensions/ByteExtensions.cs deleted file mode 100644 index 05b71bb2f6..0000000000 --- a/src/ImageProcessorCore - Copy/Common/Extensions/ByteExtensions.cs +++ /dev/null @@ -1,60 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - - /// - /// Extension methods for the struct. - /// - internal static class ByteExtensions - { - /// - /// Converts a byte array to a new array where each value in the original array is represented - /// by a the specified number of bits. - /// - /// The bytes to convert from. Cannot be null. - /// The number of bits per value. - /// The resulting array. Is never null. - /// is null. - /// is less than or equals than zero. - public static byte[] ToArrayByBitsLength(this byte[] bytes, int bits) - { - Guard.NotNull(bytes, "bytes"); - Guard.MustBeGreaterThan(bits, 0, "bits"); - - byte[] result; - - if (bits < 8) - { - result = new byte[bytes.Length * 8 / bits]; - - // BUGFIX I dont think it should be there, but I am not sure if it breaks something else - // int factor = (int)Math.Pow(2, bits) - 1; - int mask = 0xFF >> (8 - bits); - int resultOffset = 0; - - foreach (byte b in bytes) - { - for (int shift = 0; shift < 8; shift += bits) - { - int colorIndex = (b >> (8 - bits - shift)) & mask; // * (255 / factor); - - result[resultOffset] = (byte)colorIndex; - - resultOffset++; - } - } - } - else - { - result = bytes; - } - - return result; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Common/Extensions/ComparableExtensions.cs b/src/ImageProcessorCore - Copy/Common/Extensions/ComparableExtensions.cs deleted file mode 100644 index cb0288fb7b..0000000000 --- a/src/ImageProcessorCore - Copy/Common/Extensions/ComparableExtensions.cs +++ /dev/null @@ -1,145 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - - /// - /// Extension methods for classes that implement . - /// - internal static class ComparableExtensions - { - /// - /// Restricts a to be within a specified range. - /// - /// The The value to clamp. - /// The minimum value. If value is less than min, min will be returned. - /// The maximum value. If value is greater than max, max will be returned. - /// - /// The representing the clamped value. - /// - public static byte Clamp(this byte value, byte min, byte max) - { - // Order is important here as someone might set min to higher than max. - if (value > max) - { - return max; - } - - if (value < min) - { - return min; - } - - return value; - } - - /// - /// Restricts a to be within a specified range. - /// - /// The The value to clamp. - /// The minimum value. If value is less than min, min will be returned. - /// The maximum value. If value is greater than max, max will be returned. - /// - /// The representing the clamped value. - /// - public static int Clamp(this int value, int min, int max) - { - if (value > max) - { - return max; - } - - if (value < min) - { - return min; - } - - return value; - } - - /// - /// Restricts a to be within a specified range. - /// - /// The The value to clamp. - /// The minimum value. If value is less than min, min will be returned. - /// The maximum value. If value is greater than max, max will be returned. - /// - /// The representing the clamped value. - /// - public static float Clamp(this float value, float min, float max) - { - if (value > max) - { - return max; - } - - if (value < min) - { - return min; - } - - return value; - } - - /// - /// Restricts a to be within a specified range. - /// - /// The The value to clamp. - /// The minimum value. If value is less than min, min will be returned. - /// The maximum value. If value is greater than max, max will be returned. - /// - /// The representing the clamped value. - /// - public static double Clamp(this double value, double min, double max) - { - if (value > max) - { - return max; - } - - if (value < min) - { - return min; - } - - return value; - } - - /// - /// Converts an to a first restricting the value between the - /// minimum and maximum allowable ranges. - /// - /// The this method extends. - /// The - public static byte ToByte(this int value) - { - return (byte)value.Clamp(0, 255); - } - - /// - /// Converts an to a first restricting the value between the - /// minimum and maximum allowable ranges. - /// - /// The this method extends. - /// The - public static byte ToByte(this float value) - { - return (byte)value.Clamp(0, 255); - } - - /// - /// Converts an to a first restricting the value between the - /// minimum and maximum allowable ranges. - /// - /// The this method extends. - /// The - public static byte ToByte(this double value) - { - return (byte)value.Clamp(0, 255); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Common/Extensions/EnumerableExtensions.cs b/src/ImageProcessorCore - Copy/Common/Extensions/EnumerableExtensions.cs deleted file mode 100644 index 107320412e..0000000000 --- a/src/ImageProcessorCore - Copy/Common/Extensions/EnumerableExtensions.cs +++ /dev/null @@ -1,88 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - using System.Collections.Generic; - - /// - /// Encapsulates a series of time saving extension methods to the interface. - /// - public static class EnumerableExtensions - { - /// - /// Generates a sequence of integral numbers within a specified range. - /// - /// - /// The start index, inclusive. - /// - /// - /// The end index, exclusive. - /// - /// - /// The incremental step. - /// - /// - /// The that contains a range of sequential integral numbers. - /// - public static IEnumerable SteppedRange(int fromInclusive, int toExclusive, int step) - { - // Borrowed from Enumerable.Range - long num = (fromInclusive + toExclusive) - 1L; - if ((toExclusive < 0) || (num > 0x7fffffffL)) - { - throw new ArgumentOutOfRangeException(nameof(toExclusive)); - } - - return RangeIterator(fromInclusive, i => i < toExclusive, step); - } - - /// - /// Generates a sequence of integral numbers within a specified range. - /// - /// - /// The start index, inclusive. - /// - /// - /// A method that has one parameter and returns a calculating the end index - /// - /// - /// The incremental step. - /// - /// - /// The that contains a range of sequential integral numbers. - /// - public static IEnumerable SteppedRange(int fromInclusive, Func toDelegate, int step) - { - return RangeIterator(fromInclusive, toDelegate, step); - } - - /// - /// Generates a sequence of integral numbers within a specified range. - /// - /// - /// The start index, inclusive. - /// - /// - /// A method that has one parameter and returns a calculating the end index - /// - /// - /// The incremental step. - /// - /// - /// The that contains a range of sequential integral numbers. - /// - private static IEnumerable RangeIterator(int fromInclusive, Func toDelegate, int step) - { - int i = fromInclusive; - while (toDelegate(i)) - { - yield return i; - i += step; - } - } - } -} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/Common/Helpers/Guard.cs b/src/ImageProcessorCore - Copy/Common/Helpers/Guard.cs deleted file mode 100644 index 96c7023d43..0000000000 --- a/src/ImageProcessorCore - Copy/Common/Helpers/Guard.cs +++ /dev/null @@ -1,192 +0,0 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// -// -// Provides methods to protect against invalid parameters. -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("ImageProcessorCore.Tests")] -namespace ImageProcessorCore -{ - using System; - using System.Diagnostics; - - /// - /// Provides methods to protect against invalid parameters. - /// - [DebuggerStepThrough] - internal static class Guard - { - /// - /// Verifies, that the method parameter with specified object value is not null - /// and throws an exception if it is found to be so. - /// - /// - /// The target object, which cannot be null. - /// - /// - /// The name of the parameter that is to be checked. - /// - /// - /// The error message, if any to add to the exception. - /// - /// - /// is null - /// - public static void NotNull(object target, string parameterName, string message = "") - { - if (target == null) - { - if (string.IsNullOrWhiteSpace(message)) - { - throw new ArgumentNullException(parameterName, message); - } - - throw new ArgumentNullException(parameterName); - } - } - - /// - /// Verifies, that the string method parameter with specified object value and message - /// is not null, not empty and does not contain only blanks and throws an exception - /// if the object is null. - /// - /// The target string, which should be checked against being null or empty. - /// Name of the parameter. - /// - /// is null. - /// - /// - /// is - /// empty or contains only blanks. - /// - public static void NotNullOrEmpty(string target, string parameterName) - { - if (target == null) - { - throw new ArgumentNullException(parameterName); - } - - if (string.IsNullOrWhiteSpace(target)) - { - throw new ArgumentException("Value cannot be null or empty and cannot contain only blanks.", parameterName); - } - } - - /// - /// Verifies that the specified value is less than a maximum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// The type of the value. - /// - /// is greater than the maximum value. - /// - public static void MustBeLessThan(TValue value, TValue max, string parameterName) - where TValue : IComparable - { - if (value.CompareTo(max) >= 0) - { - throw new ArgumentOutOfRangeException( - parameterName, - $"Value must be less than {max}."); - } - } - - /// - /// Verifies that the specified value is less than or equal to a maximum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// The type of the value. - /// - /// is greater than the maximum value. - /// - public static void MustBeLessThanOrEqualTo(TValue value, TValue max, string parameterName) - where TValue : IComparable - { - if (value.CompareTo(max) > 0) - { - throw new ArgumentOutOfRangeException( - parameterName, - $"Value must be less than or equal to {max}."); - } - } - - /// - /// Verifies that the specified value is greater than a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// The type of the value. - /// - /// is less than the minimum value. - /// - public static void MustBeGreaterThan(TValue value, TValue min, string parameterName) - where TValue : IComparable - { - if (value.CompareTo(min) <= 0) - { - throw new ArgumentOutOfRangeException( - parameterName, - $"Value must be greater than {min}."); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value - /// and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The name of the parameter that is to be checked. - /// The type of the value. - /// - /// is less than the minimum value. - /// - public static void MustBeGreaterThanOrEqualTo(TValue value, TValue min, string parameterName) - where TValue : IComparable - { - if (value.CompareTo(min) < 0) - { - throw new ArgumentOutOfRangeException( - parameterName, - $"Value must be greater than or equal to {min}."); - } - } - - /// - /// Verifies that the specified value is greater than or equal to a minimum value and less than - /// or equal to a maximum value and throws an exception if it is not. - /// - /// The target value, which should be validated. - /// The minimum value. - /// The maximum value. - /// The name of the parameter that is to be checked. - /// The type of the value. - /// - /// is less than the minimum value of greater than the maximum value. - /// - public static void MustBeBetweenOrEqualTo(TValue value, TValue min, TValue max, string parameterName) - where TValue : IComparable - { - if (value.CompareTo(min) < 0 || value.CompareTo(max) > 0) - { - throw new ArgumentOutOfRangeException( - parameterName, - $"Value must be greater than or equal to {min} and less than or equal to {max}."); - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Common/Helpers/ImageMaths.cs b/src/ImageProcessorCore - Copy/Common/Helpers/ImageMaths.cs deleted file mode 100644 index 051b75f70f..0000000000 --- a/src/ImageProcessorCore - Copy/Common/Helpers/ImageMaths.cs +++ /dev/null @@ -1,289 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - using System.Linq; - using System.Numerics; - - /// - /// Provides common mathematical methods. - /// - internal static class ImageMaths - { - /// - /// Returns how many bits are required to store the specified number of colors. - /// Performs a Log2() on the value. - /// - /// The number of colors. - /// - /// The - /// - public static int GetBitsNeededForColorDepth(int colors) - { - return (int)Math.Ceiling(Math.Log(colors, 2)); - } - - /// - /// Implementation of 1D Gaussian G(x) function - /// - /// The x provided to G(x). - /// The spread of the blur. - /// The Gaussian G(x) - public static float Gaussian(float x, float sigma) - { - const float Numerator = 1.0f; - float denominator = (float)(Math.Sqrt(2 * Math.PI) * sigma); - - float exponentNumerator = -x * x; - float exponentDenominator = (float)(2 * Math.Pow(sigma, 2)); - - float left = Numerator / denominator; - float right = (float)Math.Exp(exponentNumerator / exponentDenominator); - - return left * right; - } - - /// - /// Returns the result of a B-C filter against the given value. - /// - /// - /// The value to process. - /// The B-Spline curve variable. - /// The Cardinal curve variable. - /// - /// The . - /// - public static float GetBcValue(float x, float b, float c) - { - float temp; - - if (x < 0) - { - x = -x; - } - - temp = x * x; - if (x < 1) - { - x = ((12 - (9 * b) - (6 * c)) * (x * temp)) + ((-18 + (12 * b) + (6 * c)) * temp) + (6 - (2 * b)); - return x / 6; - } - - if (x < 2) - { - x = ((-b - (6 * c)) * (x * temp)) + (((6 * b) + (30 * c)) * temp) + (((-12 * b) - (48 * c)) * x) + ((8 * b) + (24 * c)); - return x / 6; - } - - return 0; - } - - /// - /// Gets the result of a sine cardinal function for the given value. - /// - /// The value to calculate the result for. - /// - /// The . - /// - public static float SinC(float x) - { - const float Epsilon = .00001f; - - if (Math.Abs(x) > Epsilon) - { - x *= (float)Math.PI; - return Clean((float)Math.Sin(x) / x); - } - - return 1.0f; - } - - /// - /// Returns the given degrees converted to radians. - /// - /// The angle in degrees. - /// - /// The representing the degree as radians. - /// - public static float DegreesToRadians(float degrees) - { - return degrees * (float)(Math.PI / 180); - } - - /// - /// Gets the bounding from the given points. - /// - /// - /// The designating the top left position. - /// - /// - /// The designating the bottom right position. - /// - /// - /// The bounding . - /// - public static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) - { - return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y); - } - - /// - /// Gets the bounding from the given matrix. - /// - /// The source rectangle. - /// The transformation matrix. - /// - /// The . - /// - public static Rectangle GetBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix) - { - Vector2 leftTop = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix); - Vector2 rightTop = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix); - Vector2 leftBottom = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix); - Vector2 rightBottom = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix); - - Vector2[] allCorners = { leftTop, rightTop, leftBottom, rightBottom }; - float extentX = allCorners.Select(v => v.X).Max() - allCorners.Select(v => v.X).Min(); - float extentY = allCorners.Select(v => v.Y).Max() - allCorners.Select(v => v.Y).Min(); - return new Rectangle(0, 0, (int)extentX, (int)extentY); - } - - /// - /// Finds the bounding rectangle based on the first instance of any color component other - /// than the given one. - /// - /// The to search within. - /// The color component value to remove. - /// The channel to test against. - /// - /// The . - /// - public static Rectangle GetFilteredBoundingRectangle(ImageBase bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B) - { - const float Epsilon = .00001f; - int width = bitmap.Width; - int height = bitmap.Height; - Point topLeft = new Point(); - Point bottomRight = new Point(); - - Func delegateFunc; - - // Determine which channel to check against - switch (channel) - { - case RgbaComponent.R: - delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].R - b) > Epsilon; - break; - - case RgbaComponent.G: - delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].G - b) > Epsilon; - break; - - case RgbaComponent.A: - delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].A - b) > Epsilon; - break; - - default: - delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].B - b) > Epsilon; - break; - } - - Func getMinY = pixels => - { - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - if (delegateFunc(pixels, x, y, componentValue)) - { - return y; - } - } - } - - return 0; - }; - - Func getMaxY = pixels => - { - for (int y = height - 1; y > -1; y--) - { - for (int x = 0; x < width; x++) - { - if (delegateFunc(pixels, x, y, componentValue)) - { - return y; - } - } - } - - return height; - }; - - Func getMinX = pixels => - { - for (int x = 0; x < width; x++) - { - for (int y = 0; y < height; y++) - { - if (delegateFunc(pixels, x, y, componentValue)) - { - return x; - } - } - } - - return 0; - }; - - Func getMaxX = pixels => - { - for (int x = width - 1; x > -1; x--) - { - for (int y = 0; y < height; y++) - { - if (delegateFunc(pixels, x, y, componentValue)) - { - return x; - } - } - } - - return height; - }; - - using (PixelAccessor bitmapPixels = bitmap.Lock()) - { - topLeft.Y = getMinY(bitmapPixels); - topLeft.X = getMinX(bitmapPixels); - bottomRight.Y = (getMaxY(bitmapPixels) + 1).Clamp(0, height); - bottomRight.X = (getMaxX(bitmapPixels) + 1).Clamp(0, width); - } - - return GetBoundingRectangle(topLeft, bottomRight); - } - - /// - /// Ensures that any passed double is correctly rounded to zero - /// - /// The value to clean. - /// - /// The - /// . - private static float Clean(float x) - { - const float Epsilon = .00001f; - - if (Math.Abs(x) < Epsilon) - { - return 0f; - } - - return x; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Alpha.cs b/src/ImageProcessorCore - Copy/Filters/Alpha.cs deleted file mode 100644 index 3532bb675d..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Alpha.cs +++ /dev/null @@ -1,52 +0,0 @@ -// -// 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 - { - /// - /// Alters the alpha component of the image. - /// - /// 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) - { - return Alpha(source, percent, source.Bounds, progressHandler); - } - - /// - /// Alters the alpha component of the image. - /// - /// The image this method extends. - /// The new opacity of the image. 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 Alpha(this Image source, int percent, Rectangle rectangle, ProgressEventHandler progressHandler = null) - { - AlphaProcessor processor = new AlphaProcessor(percent); - processor.OnProgress += progressHandler; - - try - { - return source.Process(rectangle, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/BackgroundColor.cs b/src/ImageProcessorCore - Copy/Filters/BackgroundColor.cs deleted file mode 100644 index 98709cb32c..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/BackgroundColor.cs +++ /dev/null @@ -1,37 +0,0 @@ -// -// 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 - { - /// - /// Combines the given image together with the current one by blending their pixels. - /// - /// 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) - { - BackgroundColorProcessor processor = new BackgroundColorProcessor(color); - processor.OnProgress += progressHandler; - - try - { - return source.Process(source.Bounds, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/BlackWhite.cs b/src/ImageProcessorCore - Copy/Filters/BlackWhite.cs deleted file mode 100644 index 98c3b7744a..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/BlackWhite.cs +++ /dev/null @@ -1,50 +0,0 @@ -// -// 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 black and white toning to the image. - /// - /// 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) - { - return BlackWhite(source, source.Bounds, progressHandler); - } - - /// - /// Applies black and white toning to the image. - /// - /// 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) - { - BlackWhiteProcessor processor = new BlackWhiteProcessor(); - processor.OnProgress += progressHandler; - - try - { - return source.Process(rectangle, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Blend.cs b/src/ImageProcessorCore - Copy/Filters/Blend.cs deleted file mode 100644 index 77a94e3fa3..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Blend.cs +++ /dev/null @@ -1,60 +0,0 @@ -// -// 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 - { - /// - /// 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 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) - { - return Blend(source, image, percent, source.Bounds, progressHandler); - } - - /// - /// 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 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) - { - BlendProcessor processor = new BlendProcessor(image, percent); - processor.OnProgress += progressHandler; - - try - { - return source.Process(rectangle, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/BoxBlur.cs b/src/ImageProcessorCore - Copy/Filters/BoxBlur.cs deleted file mode 100644 index 474f0d6e77..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/BoxBlur.cs +++ /dev/null @@ -1,52 +0,0 @@ -// -// 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 a box blur to the image. - /// - /// 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) - { - return BoxBlur(source, radius, source.Bounds, progressHandler); - } - - /// - /// Applies a box blur to the image. - /// - /// 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) - { - BoxBlurProcessor processor = new BoxBlurProcessor(radius); - processor.OnProgress += progressHandler; - - try - { - return source.Process(rectangle, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Brightness.cs b/src/ImageProcessorCore - Copy/Filters/Brightness.cs deleted file mode 100644 index 52944c8010..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Brightness.cs +++ /dev/null @@ -1,52 +0,0 @@ -// -// 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 - { - /// - /// Alters the brightness component of the image. - /// - /// 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) - { - return Brightness(source, amount, source.Bounds, progressHandler); - } - - /// - /// Alters the brightness component of the image. - /// - /// 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) - { - BrightnessProcessor processor = new BrightnessProcessor(amount); - processor.OnProgress += progressHandler; - - try - { - return source.Process(rectangle, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/ColorBlindness.cs b/src/ImageProcessorCore - Copy/Filters/ColorBlindness.cs deleted file mode 100644 index 78267b05e3..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/ColorBlindness.cs +++ /dev/null @@ -1,88 +0,0 @@ -// -// 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 the given colorblindness simulator to the image. - /// - /// 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) - { - return ColorBlindness(source, colorBlindness, source.Bounds, progressHandler); - } - - /// - /// Applies the given colorblindness simulator to the image. - /// - /// 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) - { - IImageProcessor processor; - - switch (colorBlindness) - { - case ImageProcessorCore.ColorBlindness.Achromatomaly: - processor = new AchromatomalyProcessor(); - break; - - case ImageProcessorCore.ColorBlindness.Achromatopsia: - processor = new AchromatopsiaProcessor(); - break; - - case ImageProcessorCore.ColorBlindness.Deuteranomaly: - processor = new DeuteranomalyProcessor(); - break; - - case ImageProcessorCore.ColorBlindness.Deuteranopia: - processor = new DeuteranopiaProcessor(); - break; - - case ImageProcessorCore.ColorBlindness.Protanomaly: - processor = new ProtanomalyProcessor(); - break; - - case ImageProcessorCore.ColorBlindness.Protanopia: - processor = new ProtanopiaProcessor(); - break; - - case ImageProcessorCore.ColorBlindness.Tritanomaly: - processor = new TritanomalyProcessor(); - break; - - default: - processor = new TritanopiaProcessor(); - break; - } - - processor.OnProgress += progressHandler; - - try - { - return source.Process(rectangle, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Contrast.cs b/src/ImageProcessorCore - Copy/Filters/Contrast.cs deleted file mode 100644 index cab12ca4e5..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Contrast.cs +++ /dev/null @@ -1,52 +0,0 @@ -// -// 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 - { - /// - /// Alters the contrast component of the image. - /// - /// 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) - { - return Contrast(source, amount, source.Bounds, progressHandler); - } - - /// - /// Alters the contrast component of the image. - /// - /// 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) - { - ContrastProcessor processor = new ContrastProcessor(amount); - processor.OnProgress += progressHandler; - - try - { - return source.Process(rectangle, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/DetectEdges.cs b/src/ImageProcessorCore - Copy/Filters/DetectEdges.cs deleted file mode 100644 index ac3a0282d5..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/DetectEdges.cs +++ /dev/null @@ -1,63 +0,0 @@ -// -// 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 - { - /// - /// Detects any edges within the image. Uses the filter - /// operating in greyscale mode. - /// - /// 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) - { - return DetectEdges(source, source.Bounds, new SobelProcessor { Greyscale = true }, progressHandler); - } - - /// - /// Detects any edges within the image. - /// - /// 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) - { - return DetectEdges(source, source.Bounds, filter, progressHandler); - } - - /// - /// Detects any edges within the image. - /// - /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// 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) - { - filter.OnProgress += progressHandler; - - try - { - return source.Process(rectangle, filter); - } - finally - { - filter.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Greyscale.cs b/src/ImageProcessorCore - Copy/Filters/Greyscale.cs deleted file mode 100644 index 786a935766..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Greyscale.cs +++ /dev/null @@ -1,55 +0,0 @@ -// -// 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 greyscale toning to the image. - /// - /// 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) - { - return Greyscale(source, source.Bounds, mode, progressHandler); - } - - /// - /// Applies greyscale toning to the image. - /// - /// 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) - { - IImageProcessor processor = mode == GreyscaleMode.Bt709 - ? (IImageProcessor)new GreyscaleBt709Processor() - : new GreyscaleBt601Processor(); - - processor.OnProgress += progressHandler; - - try - { - return source.Process(rectangle, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/GuassianBlur.cs b/src/ImageProcessorCore - Copy/Filters/GuassianBlur.cs deleted file mode 100644 index 646c6bdc08..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/GuassianBlur.cs +++ /dev/null @@ -1,52 +0,0 @@ -// -// 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 a Guassian blur to the image. - /// - /// 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) - { - return GuassianBlur(source, sigma, source.Bounds, progressHandler); - } - - /// - /// Applies a Guassian blur to the image. - /// - /// 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) - { - GuassianBlurProcessor processor = new GuassianBlurProcessor(sigma); - processor.OnProgress += progressHandler; - - try - { - return source.Process(rectangle, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/GuassianSharpen.cs b/src/ImageProcessorCore - Copy/Filters/GuassianSharpen.cs deleted file mode 100644 index 06993070d8..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/GuassianSharpen.cs +++ /dev/null @@ -1,52 +0,0 @@ -// -// 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 a Guassian sharpening filter to the image. - /// - /// 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) - { - return GuassianSharpen(source, sigma, source.Bounds, progressHandler); - } - - /// - /// Applies a Guassian sharpening filter to the image. - /// - /// 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) - { - GuassianSharpenProcessor processor = new GuassianSharpenProcessor(sigma); - processor.OnProgress += progressHandler; - - try - { - return source.Process(rectangle, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Hue.cs b/src/ImageProcessorCore - Copy/Filters/Hue.cs deleted file mode 100644 index 45a995c301..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Hue.cs +++ /dev/null @@ -1,52 +0,0 @@ -// -// 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 - { - /// - /// Alters the hue component of the image. - /// - /// 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) - { - return Hue(source, degrees, source.Bounds, progressHandler); - } - - /// - /// Alters the hue component of the image. - /// - /// 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) - { - HueProcessor processor = new HueProcessor(degrees); - processor.OnProgress += progressHandler; - - try - { - return source.Process(rectangle, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Invert.cs b/src/ImageProcessorCore - Copy/Filters/Invert.cs deleted file mode 100644 index eaeddfc62b..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Invert.cs +++ /dev/null @@ -1,50 +0,0 @@ -// -// 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 - { - /// - /// Inverts the colors of the image. - /// - /// 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) - { - return Invert(source, source.Bounds, progressHandler); - } - - /// - /// Inverts the colors of the image. - /// - /// 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 Invert(this Image source, Rectangle rectangle, ProgressEventHandler progressHandler = null) - { - InvertProcessor processor = new InvertProcessor(); - processor.OnProgress += progressHandler; - - try - { - return source.Process(rectangle, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Kodachrome.cs b/src/ImageProcessorCore - Copy/Filters/Kodachrome.cs deleted file mode 100644 index 3b4e3f1c66..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Kodachrome.cs +++ /dev/null @@ -1,50 +0,0 @@ -// -// 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 - { - /// - /// Alters the colors of the image recreating an old Kodachrome camera effect. - /// - /// 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) - { - return Kodachrome(source, source.Bounds, progressHandler); - } - - /// - /// Alters the colors of the image recreating an old Kodachrome camera effect. - /// - /// 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) - { - KodachromeProcessor processor = new KodachromeProcessor(); - processor.OnProgress += progressHandler; - - try - { - return source.Process(rectangle, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Lomograph.cs b/src/ImageProcessorCore - Copy/Filters/Lomograph.cs deleted file mode 100644 index 5ecf0bcf4a..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Lomograph.cs +++ /dev/null @@ -1,50 +0,0 @@ -// -// 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 - { - /// - /// Alters the colors of the image recreating an old Lomograph camera effect. - /// - /// 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) - { - return Lomograph(source, source.Bounds, progressHandler); - } - - /// - /// Alters the colors of the image recreating an old Lomograph camera effect. - /// - /// 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) - { - LomographProcessor processor = new LomographProcessor(); - processor.OnProgress += progressHandler; - - try - { - return source.Process(rectangle, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Options/ColorBlindness.cs b/src/ImageProcessorCore - Copy/Filters/Options/ColorBlindness.cs deleted file mode 100644 index 6d7fe849bc..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Options/ColorBlindness.cs +++ /dev/null @@ -1,53 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - /// - /// Enumerates the various types of color blindness. - /// - public enum ColorBlindness - { - /// - /// Partial color desensitivity. - /// - Achromatomaly, - - /// - /// Complete color desensitivity (Monochrome) - /// - Achromatopsia, - - /// - /// Green weak - /// - Deuteranomaly, - - /// - /// Green blind - /// - Deuteranopia, - - /// - /// Red weak - /// - Protanomaly, - - /// - /// Red blind - /// - Protanopia, - - /// - /// Blue weak - /// - Tritanomaly, - - /// - /// Blue blind - /// - Tritanopia - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Pixelate.cs b/src/ImageProcessorCore - Copy/Filters/Pixelate.cs deleted file mode 100644 index 6f86848d6a..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Pixelate.cs +++ /dev/null @@ -1,52 +0,0 @@ -// -// 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 - { - /// - /// Pixelates and image with the given pixel size. - /// - /// 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) - { - return Pixelate(source, size, source.Bounds, progressHandler); - } - - /// - /// Pixelates and image with the given pixel size. - /// - /// The image this method extends. - /// The size of the pixels. - /// - /// 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) - { - PixelateProcessor processor = new PixelateProcessor(size); - processor.OnProgress += progressHandler; - - try - { - return source.Process(rectangle, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Polaroid.cs b/src/ImageProcessorCore - Copy/Filters/Polaroid.cs deleted file mode 100644 index 165f1d59a5..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Polaroid.cs +++ /dev/null @@ -1,50 +0,0 @@ -// -// 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 - { - /// - /// Alters the colors of the image recreating an old Polaroid camera effect. - /// - /// 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) - { - return Polaroid(source, source.Bounds, progressHandler); - } - - /// - /// Alters the colors of the image recreating an old Polaroid camera effect. - /// - /// 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) - { - PolaroidProcessor processor = new PolaroidProcessor(); - processor.OnProgress += progressHandler; - - try - { - return source.Process(rectangle, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/AlphaProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/AlphaProcessor.cs deleted file mode 100644 index 8ed703e034..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/AlphaProcessor.cs +++ /dev/null @@ -1,69 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System; - using System.Numerics; - using System.Threading.Tasks; - - /// - /// An to change the Alpha of an . - /// - public class AlphaProcessor : ImageProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The percentage to adjust the opacity of the image. Must be between 0 and 100. - /// - /// is less than 0 or is greater than 100. - /// - public AlphaProcessor(int percent) - { - Guard.MustBeBetweenOrEqualTo(percent, 0, 100, nameof(percent)); - this.Value = percent; - } - - /// - /// Gets the alpha value. - /// - public int Value { get; } - - /// - 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; - int sourceBottom = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - Vector4 alphaVector = new Vector4(1, 1, 1, alpha); - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - startY, - endY, - y => - { - if (y >= sourceY && y < sourceBottom) - { - for (int x = startX; x < endX; x++) - { - Vector4 color = Color.ToNonPremultiplied(sourcePixels[x, y]).ToVector4(); - color *= alphaVector; - targetPixels[x, y] = Color.FromNonPremultiplied(new Color(color)); - } - - this.OnRowProcessed(); - } - }); - - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/BackgroundColorProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/BackgroundColorProcessor.cs deleted file mode 100644 index e78987d23a..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/BackgroundColorProcessor.cs +++ /dev/null @@ -1,78 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System; - using System.Threading.Tasks; - - /// - /// Sets the background color of the image. - /// - public class BackgroundColorProcessor : ImageProcessor - { - /// - /// The epsilon for comparing floating point numbers. - /// - private const float Epsilon = 0.001f; - - /// - /// Initializes a new instance of the class. - /// - /// The to set the background color to. - public BackgroundColorProcessor(Color color) - { - this.Value = Color.FromNonPremultiplied(color); - } - - /// - /// Gets the background color value. - /// - public Color Value { get; } - - /// - 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; - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - startY, - endY, - y => - { - if (y >= sourceY && y < sourceBottom) - { - for (int x = startX; x < endX; x++) - { - Color color = sourcePixels[x, y]; - float a = color.A; - - if (a < 1 && a > 0) - { - color = Color.Lerp(color, backgroundColor, .5f); - } - - if (Math.Abs(a) < Epsilon) - { - color = backgroundColor; - } - - targetPixels[x, y] = color; - } - - this.OnRowProcessed(); - } - }); - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/Binarization/ThresholdProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Binarization/ThresholdProcessor.cs deleted file mode 100644 index 60b49d0eec..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/Binarization/ThresholdProcessor.cs +++ /dev/null @@ -1,86 +0,0 @@ -// -// 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 - /// occurs. - /// - public class ThresholdProcessor : ImageProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The threshold to split the image. Must be between 0 and 1. - /// - /// is less than 0 or is greater than 1. - /// - public ThresholdProcessor(float threshold) - { - Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); - this.Value = threshold; - } - - /// - /// Gets the threshold value. - /// - public float Value { get; } - - /// - /// The color to use for pixels that are above the threshold. - /// - public Color UpperColor => Color.White; - - /// - /// The color to use for pixels that fall below the threshold. - /// - public Color LowerColor => Color.Black; - - /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - new GreyscaleBt709Processor().Apply(source, source, sourceRectangle); - } - - /// - protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - float threshold = this.Value; - Color upper = this.UpperColor; - Color 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()) - { - Parallel.For( - startY, - endY, - y => - { - if (y >= sourceY && y < sourceBottom) - { - for (int x = startX; x < endX; x++) - { - Color color = sourcePixels[x, y]; - - // Any channel will do since it's greyscale. - targetPixels[x, y] = color.B >= threshold ? upper : lower; - } - this.OnRowProcessed(); - } - }); - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/BlendProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/BlendProcessor.cs deleted file mode 100644 index 5de0741fae..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/BlendProcessor.cs +++ /dev/null @@ -1,86 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Threading.Tasks; - - /// - /// Combines two images together by blending the pixels. - /// - public class BlendProcessor : ImageProcessor - { - /// - /// The image to blend. - /// - private readonly ImageBase blend; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The image to blend with the currently processing image. - /// 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) - { - Guard.MustBeBetweenOrEqualTo(alpha, 0, 100, nameof(alpha)); - this.blend = image; - this.Value = alpha; - } - - /// - /// Gets the alpha percentage value. - /// - public int Value { get; } - - /// - 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; - 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()) - { - Parallel.For( - startY, - endY, - y => - { - if (y >= sourceY && y < sourceBottom) - { - for (int x = startX; x < endX; x++) - { - Color color = sourcePixels[x, y]; - - if (bounds.Contains(x, y)) - { - Color blendedColor = toBlendPixels[x, y]; - - if (blendedColor.A > 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); - } - } - - targetPixels[x, y] = color; - } - - this.OnRowProcessed(); - } - }); - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/BrightnessProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/BrightnessProcessor.cs deleted file mode 100644 index 6710ff5938..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/BrightnessProcessor.cs +++ /dev/null @@ -1,69 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System; - using System.Numerics; - using System.Threading.Tasks; - - /// - /// An to change the brightness of an . - /// - public class BrightnessProcessor : ImageProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The new brightness of the image. Must be between -100 and 100. - /// - /// is less than -100 or is greater than 100. - /// - public BrightnessProcessor(int brightness) - { - Guard.MustBeBetweenOrEqualTo(brightness, -100, 100, nameof(brightness)); - this.Value = brightness; - } - - /// - /// Gets the brightness value. - /// - public int Value { get; } - - /// - 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; - int sourceBottom = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - startY, - endY, - y => - { - if (y >= sourceY && y < sourceBottom) - { - for (int x = startX; x < endX; x++) - { - Color color = Color.Expand(sourcePixels[x, y]); - - Vector3 vector3 = color.ToVector3(); - vector3 += new Vector3(brightness); - - targetPixels[x, y] = Color.Compress(new Color(vector3, color.A)); - } - this.OnRowProcessed(); - } - }); - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs deleted file mode 100644 index 7fc3240d00..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/BlackWhiteProcessor.cs +++ /dev/null @@ -1,32 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Numerics; - - /// - /// Converts the colors of the image to their black and white equivalent. - /// - public class BlackWhiteProcessor : ColorMatrixFilter - { - /// - public override Matrix4x4 Matrix => new Matrix4x4() - { - M11 = 1.5f, - M12 = 1.5f, - M13 = 1.5f, - M21 = 1.5f, - M22 = 1.5f, - M23 = 1.5f, - M31 = 1.5f, - M32 = 1.5f, - M33 = 1.5f, - M41 = -1f, - M42 = -1f, - M43 = -1f, - }; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs deleted file mode 100644 index d5740ec284..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs +++ /dev/null @@ -1,32 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Numerics; - - /// - /// Converts the colors of the image recreating Achromatomaly (Color desensitivity) color blindness. - /// - public class AchromatomalyProcessor : ColorMatrixFilter - { - /// - public override Matrix4x4 Matrix => new Matrix4x4() - { - M11 = .618f, - M12 = .163f, - M13 = .163f, - M21 = .320f, - M22 = .775f, - M23 = .320f, - M31 = .062f, - M32 = .062f, - M33 = .516f - }; - - /// - public override bool Compand => false; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs deleted file mode 100644 index 6f2f7c2693..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs +++ /dev/null @@ -1,32 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Numerics; - - /// - /// Converts the colors of the image recreating Achromatopsia (Monochrome) color blindness. - /// - public class AchromatopsiaProcessor : ColorMatrixFilter - { - /// - public override Matrix4x4 Matrix => new Matrix4x4() - { - M11 = .299f, - M12 = .299f, - M13 = .299f, - M21 = .587f, - M22 = .587f, - M23 = .587f, - M31 = .114f, - M32 = .114f, - M33 = .114f - }; - - /// - public override bool Compand => false; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs deleted file mode 100644 index fed09991f6..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Numerics; - - /// - /// Converts the colors of the image recreating Deuteranomaly (Green-Weak) color blindness. - /// - public class DeuteranomalyProcessor : ColorMatrixFilter - { - /// - public override Matrix4x4 Matrix => new Matrix4x4() - { - M11 = 0.8f, - M12 = 0.258f, - M21 = 0.2f, - M22 = 0.742f, - M23 = 0.142f, - M33 = 0.858f - }; - - /// - public override bool Compand => false; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs deleted file mode 100644 index 0ef190861b..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Numerics; - - /// - /// Converts the colors of the image recreating Deuteranopia (Green-Blind) color blindness. - /// - public class DeuteranopiaProcessor : ColorMatrixFilter - { - /// - public override Matrix4x4 Matrix => new Matrix4x4() - { - M11 = 0.625f, - M12 = 0.7f, - M21 = 0.375f, - M22 = 0.3f, - M23 = 0.3f, - M33 = 0.7f - }; - - /// - public override bool Compand => false; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs deleted file mode 100644 index b7152a68e4..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Numerics; - - /// - /// Converts the colors of the image recreating Protanopia (Red-Weak) color blindness. - /// - public class ProtanomalyProcessor : ColorMatrixFilter - { - /// - public override Matrix4x4 Matrix => new Matrix4x4() - { - M11 = 0.817f, - M12 = 0.333f, - M21 = 0.183f, - M22 = 0.667f, - M23 = 0.125f, - M33 = 0.875f - }; - - /// - public override bool Compand => false; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs deleted file mode 100644 index 7984be139b..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Numerics; - - /// - /// Converts the colors of the image recreating Protanopia (Red-Blind) color blindness. - /// - public class ProtanopiaProcessor : ColorMatrixFilter - { - /// - public override Matrix4x4 Matrix => new Matrix4x4() - { - M11 = 0.567f, - M12 = 0.558f, - M21 = 0.433f, - M22 = 0.442f, - M23 = 0.242f, - M33 = 0.758f - }; - - /// - public override bool Compand => false; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/README.md b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/README.md deleted file mode 100644 index 209f3b67bd..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/README.md +++ /dev/null @@ -1,4 +0,0 @@ -Color blindness matrices adapted from and tested against: - -http://web.archive.org/web/20090413045433/http://nofunc.org/Color_Matrix_Library -http://www.color-blindness.com/coblis-color-blindness-simulator/ \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs deleted file mode 100644 index 618da36bb9..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Numerics; - - /// - /// Converts the colors of the image recreating Tritanomaly (Blue-Weak) color blindness. - /// - public class TritanomalyProcessor : ColorMatrixFilter - { - /// - public override Matrix4x4 Matrix => new Matrix4x4() - { - M11 = 0.967f, - M21 = 0.33f, - M22 = 0.733f, - M23 = 0.183f, - M32 = 0.267f, - M33 = 0.817f - }; - - /// - public override bool Compand => false; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs deleted file mode 100644 index e53de7a69a..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Numerics; - - /// - /// Converts the colors of the image recreating Tritanopia (Blue-Blind) color blindness. - /// - public class TritanopiaProcessor : ColorMatrixFilter - { - /// - public override Matrix4x4 Matrix => new Matrix4x4() - { - M11 = 0.95f, - M21 = 0.05f, - M22 = 0.433f, - M23 = 0.475f, - M32 = 0.567f, - M33 = 0.525f - }; - - /// - public override bool Compand => false; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs deleted file mode 100644 index d27ea5c6d5..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/ColorMatrixFilter.cs +++ /dev/null @@ -1,68 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Numerics; - using System.Threading.Tasks; - - /// - /// The color matrix filter. - /// - public abstract class ColorMatrixFilter : ImageProcessor, IColorMatrixFilter - { - /// - public abstract Matrix4x4 Matrix { get; } - - /// - public virtual bool Compand => true; - - /// - 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; - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - startY, - endY, - y => - { - for (int x = startX; x < endX; x++) - { - targetPixels[x, y] = this.ApplyMatrix(sourcePixels[x, y], matrix); - } - - this.OnRowProcessed(); - }); - } - } - - /// - /// Applies the color matrix against the given color. - /// - /// The source color. - /// The matrix. - /// - /// The . - /// - private Color ApplyMatrix(Color color, Matrix4x4 matrix) - { - bool compand = this.Compand; - - if (compand) - { - color = Color.Expand(color); - } - - Vector3 transformed = Vector3.Transform(color.ToVector3(), matrix); - return compand ? Color.Compress(new Color(transformed, color.A)) : new Color(transformed, color.A); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs deleted file mode 100644 index b9a6daa6e7..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleBt601Processor.cs +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Numerics; - - /// - /// Converts the colors of the image to greyscale applying the formula as specified by - /// ITU-R Recommendation BT.601 . - /// - public class GreyscaleBt601Processor : ColorMatrixFilter - { - /// - public override Matrix4x4 Matrix => new Matrix4x4() - { - M11 = .299f, - M12 = .299f, - M13 = .299f, - M21 = .587f, - M22 = .587f, - M23 = .587f, - M31 = .114f, - M32 = .114f, - M33 = .114f - }; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs deleted file mode 100644 index 949e51e6a9..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleBt709Processor.cs +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Numerics; - - /// - /// Converts the colors of the image to greyscale applying the formula as specified by - /// ITU-R Recommendation BT.709 . - /// - public class GreyscaleBt709Processor : ColorMatrixFilter - { - /// - public override Matrix4x4 Matrix => new Matrix4x4() - { - M11 = .2126f, - M12 = .2126f, - M13 = .2126f, - M21 = .7152f, - M22 = .7152f, - M23 = .7152f, - M31 = .0722f, - M32 = .0722f, - M33 = .0722f - }; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleMode.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleMode.cs deleted file mode 100644 index 269c1179ef..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/GreyscaleMode.cs +++ /dev/null @@ -1,23 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - /// - /// Provides enumeration over the various greyscale methods available. - /// - public enum GreyscaleMode - { - /// - /// ITU-R Recommendation BT.709 - /// - Bt709, - - /// - /// ITU-R Recommendation BT.601 - /// - Bt601 - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/HueProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/HueProcessor.cs deleted file mode 100644 index da7d4631c5..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/HueProcessor.cs +++ /dev/null @@ -1,81 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System; - using System.Numerics; - - public class HueProcessor : ColorMatrixFilter - { - /// - /// The used to alter the image. - /// - private Matrix4x4 matrix; - - /// - /// Initializes a new instance of the class. - /// - /// The new brightness of the image. Must be between -100 and 100. - public HueProcessor(float angle) - { - // Wrap the angle round at 360. - angle = angle % 360; - - // Make sure it's not negative. - while (angle < 0) - { - angle += 360; - } - - 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); - double cosradians = Math.Cos(radians); - double sinradians = Math.Sin(radians); - - float lumR = .213f; - float lumG = .715f; - float lumB = .072f; - - float oneMinusLumR = 1 - lumR; - float oneMinusLumG = 1 - lumG; - float oneMinusLumB = 1 - lumB; - - // The matrix is set up to preserve the luminance of the image. - // See http://graficaobscura.com/matrix/index.html - // Number are taken from https://msdn.microsoft.com/en-us/library/jj192162(v=vs.85).aspx - Matrix4x4 matrix4X4 = new Matrix4x4() - { - M11 = (float)(lumR + (cosradians * oneMinusLumR) - (sinradians * lumR)), - M12 = (float)(lumR - (cosradians * lumR) - (sinradians * 0.143)), - M13 = (float)(lumR - (cosradians * lumR) - (sinradians * oneMinusLumR)), - M21 = (float)(lumG - (cosradians * lumG) - (sinradians * lumG)), - M22 = (float)(lumG + (cosradians * oneMinusLumG) + (sinradians * 0.140)), - M23 = (float)(lumG - (cosradians * lumG) + (sinradians * lumG)), - M31 = (float)(lumB - (cosradians * lumB) + (sinradians * oneMinusLumB)), - M32 = (float)(lumB - (cosradians * lumB) - (sinradians * 0.283)), - M33 = (float)(lumB + (cosradians * oneMinusLumB) + (sinradians * lumB)) - }; - - this.matrix = matrix4X4; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs deleted file mode 100644 index 5a0c766848..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/IColorMatrixFilter.cs +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Numerics; - - /// - /// Encapsulates properties and methods for creating processors that utilize a matrix to - /// alter the image pixels. - /// - public interface IColorMatrixFilter : IImageProcessor - { - /// - /// 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 - Copy/Filters/Processors/ColorMatrix/KodachromeProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/KodachromeProcessor.cs deleted file mode 100644 index 91c436460e..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/KodachromeProcessor.cs +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Numerics; - - /// - /// Converts the colors of the image recreating an old Kodachrome camera effect. - /// - public class KodachromeProcessor : ColorMatrixFilter - { - /// - public override Matrix4x4 Matrix => new Matrix4x4() - { - M11 = 0.6997023f, - M22 = 0.4609577f, - M33 = 0.397218f, - M41 = 0.005f, - M42 = -0.005f, - M43 = 0.005f - }; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/LomographProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/LomographProcessor.cs deleted file mode 100644 index 2422994337..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/LomographProcessor.cs +++ /dev/null @@ -1,32 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Numerics; - - /// - /// Converts the colors of the image recreating an old Lomograph effect. - /// - public class LomographProcessor : ColorMatrixFilter - { - /// - public override Matrix4x4 Matrix => new Matrix4x4() - { - M11 = 1.5f, - M22 = 1.45f, - M33 = 1.11f, - M41 = -.1f, - M42 = .0f, - M43 = -.08f - }; - - /// - 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); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/PolaroidProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/PolaroidProcessor.cs deleted file mode 100644 index ea6f85a390..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/PolaroidProcessor.cs +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Numerics; - - /// - /// Converts the colors of the image recreating an old Polaroid effect. - /// - public class PolaroidProcessor : ColorMatrixFilter - { - /// - public override Matrix4x4 Matrix => new Matrix4x4() - { - M11 = 1.538f, - M12 = -0.062f, - M13 = -0.262f, - M21 = -0.022f, - M22 = 1.578f, - M23 = -0.022f, - M31 = .216f, - M32 = -.16f, - M33 = 1.5831f, - M41 = 0.02f, - M42 = -0.05f, - M43 = -0.05f - }; - - /// - 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 - { - Color = new Color(1, 153 / 255f, 102 / 255f, .7f), - RadiusX = target.Width / 4f, - RadiusY = target.Width / 4f - } - .Apply(target, target, targetRectangle); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/SaturationProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/SaturationProcessor.cs deleted file mode 100644 index abc00bfb61..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/SaturationProcessor.cs +++ /dev/null @@ -1,75 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System; - using System.Numerics; - - /// - /// An to change the saturation of an . - /// - public class SaturationProcessor : ColorMatrixFilter - { - /// - /// The saturation to be applied to the image. - /// - private readonly int saturation; - - /// - /// The used to alter the image. - /// - private Matrix4x4 matrix; - - /// - /// Initializes a new instance of the class. - /// - /// The new saturation of the image. Must be between -100 and 100. - /// - /// is less than -100 or is greater than 100. - /// - public SaturationProcessor(int saturation) - { - 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. - saturationFactor++; - - // The matrix is set up to "shear" the colour space using the following set of values. - // Note that each colour component has an effective luminance which contributes to the - // overall brightness of the pixel. - // See http://graficaobscura.com/matrix/index.html - float saturationComplement = 1.0f - saturationFactor; - float saturationComplementR = 0.3086f * saturationComplement; - float saturationComplementG = 0.6094f * saturationComplement; - float saturationComplementB = 0.0820f * saturationComplement; - - Matrix4x4 matrix4X4 = new Matrix4x4() - { - M11 = saturationComplementR + saturationFactor, - M12 = saturationComplementR, - M13 = saturationComplementR, - M21 = saturationComplementG, - M22 = saturationComplementG + saturationFactor, - M23 = saturationComplementG, - M31 = saturationComplementB, - M32 = saturationComplementB, - M33 = saturationComplementB + saturationFactor, - }; - - this.matrix = matrix4X4; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/SepiaProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/SepiaProcessor.cs deleted file mode 100644 index da68013110..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ColorMatrix/SepiaProcessor.cs +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Numerics; - - /// - /// Converts the colors of the image to their sepia equivalent. - /// The formula used matches the svg specification. - /// - public class SepiaProcessor : ColorMatrixFilter - { - /// - public override Matrix4x4 Matrix => new Matrix4x4() - { - M11 = .393f, - M12 = .349f, - M13 = .272f, - M21 = .769f, - M22 = .686f, - M23 = .534f, - M31 = .189f, - M32 = .168f, - M33 = .131f - }; - - /// - public override bool Compand => false; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/ContrastProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/ContrastProcessor.cs deleted file mode 100644 index 19f564be4d..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/ContrastProcessor.cs +++ /dev/null @@ -1,70 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System; - using System.Numerics; - using System.Threading.Tasks; - - /// - /// An to change the contrast of an . - /// - public class ContrastProcessor : ImageProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The new contrast of the image. Must be between -100 and 100. - /// - /// is less than -100 or is greater than 100. - /// - public ContrastProcessor(int contrast) - { - Guard.MustBeBetweenOrEqualTo(contrast, -100, 100, nameof(contrast)); - this.Value = contrast; - } - - /// - /// Gets the contrast value. - /// - public int Value { get; } - - /// - 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; - int sourceBottom = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - 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()) - { - Parallel.For( - startY, - endY, - 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)); - } - this.OnRowProcessed(); - } - }); - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/BoxBlurProcessor.cs deleted file mode 100644 index 042288ffdc..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/BoxBlurProcessor.cs +++ /dev/null @@ -1,103 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - /// - /// Applies a Box blur filter to the image. - /// - public class BoxBlurProcessor : Convolution2PassFilter - { - /// - /// The maximum size of the kernal in either direction. - /// - private readonly int kernelSize; - - /// - /// The vertical kernel - /// - private float[,] kernelY; - - /// - /// The horizontal kernel - /// - private float[,] kernelX; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// - public BoxBlurProcessor(int radius = 7) - { - this.kernelSize = (radius * 2) + 1; - } - - /// - public override float[,] KernelX => this.kernelX; - - /// - public override float[,] KernelY => this.kernelY; - - /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - if (this.kernelY == null) - { - this.kernelY = this.CreateBoxKernel(false); - } - - if (this.kernelX == null) - { - this.kernelX = this.CreateBoxKernel(true); - } - } - - /// - /// Create a 1 dimensional Box kernel. - /// - /// Whether to calculate a horizontal kernel. - /// The - private float[,] CreateBoxKernel(bool horizontal) - { - int size = this.kernelSize; - float[,] kernel = horizontal ? new float[1, size] : new float[size, 1]; - float sum = 0.0f; - - for (int i = 0; i < size; i++) - { - float x = 1; - sum += x; - if (horizontal) - { - kernel[0, i] = x; - } - else - { - kernel[i, 0] = x; - } - } - - // Normalise kernel so that the sum of all weights equals 1 - if (horizontal) - { - for (int i = 0; i < size; i++) - { - kernel[0, i] = kernel[0, i] / sum; - } - } - else - { - for (int i = 0; i < size; i++) - { - kernel[i, 0] = kernel[i, 0] / sum; - } - } - - return kernel; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/Convolution2DFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/Convolution2DFilter.cs deleted file mode 100644 index b16a528db4..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/Convolution2DFilter.cs +++ /dev/null @@ -1,113 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System; - using System.Threading.Tasks; - - /// - /// Defines a filter that uses two one-dimensional matrices to perform convolution against an image. - /// - public abstract class Convolution2DFilter : ImageProcessor - { - /// - /// Gets the horizontal gradient operator. - /// - public abstract float[,] KernelX { get; } - - /// - /// Gets the vertical gradient operator. - /// - public abstract float[,] KernelY { get; } - - /// - protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - float[,] kernelX = this.KernelX; - float[,] kernelY = this.KernelY; - int kernelYHeight = kernelY.GetLength(0); - int kernelYWidth = kernelY.GetLength(1); - int kernelXHeight = kernelX.GetLength(0); - int kernelXWidth = kernelX.GetLength(1); - int radiusY = kernelYHeight >> 1; - int radiusX = kernelXWidth >> 1; - - int sourceY = sourceRectangle.Y; - int sourceBottom = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int maxY = sourceBottom - 1; - int maxX = endX - 1; - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - startY, - endY, - y => - { - if (y >= sourceY && y < sourceBottom) - { - for (int x = startX; x < endX; x++) - { - float rX = 0; - float gX = 0; - float bX = 0; - float rY = 0; - float gY = 0; - float bY = 0; - - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelYHeight; fy++) - { - int fyr = fy - radiusY; - int offsetY = y + fyr; - - offsetY = offsetY.Clamp(0, maxY); - - for (int fx = 0; fx < kernelXWidth; fx++) - { - int fxr = fx - radiusX; - int offsetX = x + fxr; - - offsetX = offsetX.Clamp(0, maxX); - - Color currentColor = sourcePixels[offsetX, offsetY]; - float r = currentColor.R; - float g = currentColor.G; - float b = currentColor.B; - - if (fy < kernelXHeight) - { - rX += kernelX[fy, fx] * r; - gX += kernelX[fy, fx] * g; - bX += kernelX[fy, fx] * b; - } - - if (fx < kernelYWidth) - { - rY += kernelY[fy, fx] * r; - gY += kernelY[fy, fx] * g; - bY += kernelY[fy, fx] * b; - } - } - } - - float red = (float)Math.Sqrt((rX * rX) + (rY * rY)); - 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); - } - this.OnRowProcessed(); - } - }); - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/Convolution2PassFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/Convolution2PassFilter.cs deleted file mode 100644 index c9031dfd7a..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/Convolution2PassFilter.cs +++ /dev/null @@ -1,113 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - 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 - { - /// - /// Gets the horizontal gradient operator. - /// - public abstract float[,] KernelX { get; } - - /// - /// Gets the vertical gradient operator. - /// - public abstract float[,] KernelY { get; } - - /// - 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); - 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 - /// and with the specified size. - /// - /// Target image to apply the process to. - /// The source image. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The 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) - { - int kernelHeight = kernel.GetLength(0); - int kernelWidth = kernel.GetLength(1); - int radiusY = kernelHeight >> 1; - int radiusX = kernelWidth >> 1; - - int sourceBottom = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int maxY = sourceBottom - 1; - int maxX = endX - 1; - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - startY, - endY, - y => - { - for (int x = startX; x < endX; x++) - { - Color destination = new Color(); - - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelHeight; fy++) - { - int fyr = fy - radiusY; - int offsetY = y + fyr; - - offsetY = offsetY.Clamp(0, maxY); - - for (int fx = 0; fx < kernelWidth; fx++) - { - int fxr = fx - radiusX; - int offsetX = x + fxr; - - offsetX = offsetX.Clamp(0, maxX); - - Color currentColor = sourcePixels[offsetX, offsetY]; - destination += kernel[fy, fx] * currentColor; - } - } - - targetPixels[x, y] = destination; - } - - this.OnRowProcessed(); - }); - } - } - } -} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/ConvolutionFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/ConvolutionFilter.cs deleted file mode 100644 index 8ddcab4a5d..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/ConvolutionFilter.cs +++ /dev/null @@ -1,88 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Threading.Tasks; - - /// - /// Defines a filter that uses a 2 dimensional matrix to perform convolution against an image. - /// - public abstract class ConvolutionFilter : ImageProcessor - { - /// - /// Gets the 2d gradient operator. - /// - public abstract float[,] KernelXY { get; } - - /// - 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); - int radius = kernelLength >> 1; - - int sourceY = sourceRectangle.Y; - int sourceBottom = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int maxY = sourceBottom - 1; - int maxX = endX - 1; - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - startY, - endY, - y => - { - if (y >= sourceY && y < sourceBottom) - { - for (int x = startX; x < endX; x++) - { - float rX = 0; - float gX = 0; - float bX = 0; - - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelLength; fy++) - { - int fyr = fy - radius; - int offsetY = y + fyr; - - offsetY = offsetY.Clamp(0, maxY); - - for (int fx = 0; fx < kernelLength; fx++) - { - int fxr = fx - radius; - int offsetX = x + fxr; - - offsetX = offsetX.Clamp(0, maxX); - - Color currentColor = sourcePixels[offsetX, offsetY]; - float r = currentColor.R; - float g = currentColor.G; - float b = currentColor.B; - - rX += kernelX[fy, fx] * r; - gX += kernelX[fy, fx] * g; - bX += kernelX[fy, fx] * b; - } - } - - float red = rX; - float green = gX; - float blue = bX; - - targetPixels[x, y] = new Color(red, green, blue); - } - this.OnRowProcessed(); - } - }); - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs deleted file mode 100644 index ce4aa0b2aa..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/EdgeDetector2DFilter.cs +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - /// - /// Defines a filter that detects edges within an image using two - /// one-dimensional matrices. - /// - public abstract class EdgeDetector2DFilter : Convolution2DFilter, IEdgeDetectorFilter - { - /// - public bool Greyscale { get; set; } - - /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - if (this.Greyscale) - { - new GreyscaleBt709Processor().Apply(source, source, sourceRectangle); - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs deleted file mode 100644 index 7a18bb96b3..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/EdgeDetectorFilter.cs +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -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 bool Greyscale { get; set; } - - /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - if (this.Greyscale) - { - new GreyscaleBt709Processor().Apply(source, source, sourceRectangle); - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs deleted file mode 100644 index b90f0d2ea2..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/IEdgeDetectorFilter.cs +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - /// - /// Provides properties and methods allowing the detection of edges within an image. - /// - public interface IEdgeDetectorFilter : IImageProcessor - { - /// - /// Gets or sets a value indicating whether to convert the - /// image to greyscale before performing edge detection. - /// - bool Greyscale { get; set; } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs deleted file mode 100644 index 5af8182412..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - /// - /// The Kayyali operator filter. - /// - /// - public class KayyaliProcessor : EdgeDetector2DFilter - { - /// - public override float[,] KernelX => new float[,] - { - { 6, 0, -6 }, - { 0, 0, 0 }, - { -6, 0, 6 } - }; - - /// - public override float[,] KernelY => new float[,] - { - { -6, 0, 6 }, - { 0, 0, 0 }, - { 6, 0, -6 } - }; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/KirschProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/KirschProcessor.cs deleted file mode 100644 index 24a561572e..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/KirschProcessor.cs +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - /// - /// The Kirsch operator filter. - /// - /// - public class KirschProcessor : EdgeDetector2DFilter - { - /// - public override float[,] KernelX => new float[,] - { - { 5, 5, 5 }, - { -3, 0, -3 }, - { -3, -3, -3 } - }; - - /// - public override float[,] KernelY => new float[,] - { - { 5, -3, -3 }, - { 5, 0, -3 }, - { 5, -3, -3 } - }; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs deleted file mode 100644 index a66d692537..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs +++ /dev/null @@ -1,22 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - /// - /// The Laplacian 3 x 3 operator filter. - /// - /// - public class Laplacian3X3Processor : EdgeDetectorFilter - { - /// - public override float[,] KernelXY => new float[,] - { - { -1, -1, -1 }, - { -1, 8, -1 }, - { -1, -1, -1 } - }; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs deleted file mode 100644 index f2d3d885ee..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs +++ /dev/null @@ -1,24 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - /// - /// The Laplacian 5 x 5 operator filter. - /// - /// - public class Laplacian5X5Processor : EdgeDetectorFilter - { - /// - public override float[,] KernelXY => new float[,] - { - { -1, -1, -1, -1, -1 }, - { -1, -1, -1, -1, -1 }, - { -1, -1, 24, -1, -1 }, - { -1, -1, -1, -1, -1 }, - { -1, -1, -1, -1, -1 } - }; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs deleted file mode 100644 index 0aae3020a9..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs +++ /dev/null @@ -1,24 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - /// - /// The Laplacian of Gaussian operator filter. - /// - /// - public class LaplacianOfGaussianProcessor : EdgeDetectorFilter - { - /// - public override float[,] KernelXY => new float[,] - { - { 0, 0, -1, 0, 0 }, - { 0, -1, -2, -1, 0 }, - { -1, -2, 16, -2, -1 }, - { 0, -1, -2, -1, 0 }, - { 0, 0, -1, 0, 0 } - }; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/PrewittProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/PrewittProcessor.cs deleted file mode 100644 index 3a5bbe9860..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/PrewittProcessor.cs +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - /// - /// The Prewitt operator filter. - /// - /// - public class PrewittProcessor : EdgeDetector2DFilter - { - /// - public override float[,] KernelX => new float[,] - { - { -1, 0, 1 }, - { -1, 0, 1 }, - { -1, 0, 1 } - }; - - /// - public override float[,] KernelY => new float[,] - { - { 1, 1, 1 }, - { 0, 0, 0 }, - { -1, -1, -1 } - }; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs deleted file mode 100644 index 0a61664184..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs +++ /dev/null @@ -1,28 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - /// - /// The Roberts Cross operator filter. - /// - /// - public class RobertsCrossProcessor : EdgeDetector2DFilter - { - /// - public override float[,] KernelX => new float[,] - { - { 1, 0 }, - { 0, -1 } - }; - - /// - public override float[,] KernelY => new float[,] - { - { 0, 1 }, - { -1, 0 } - }; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/ScharrProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/ScharrProcessor.cs deleted file mode 100644 index 80308a1409..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/ScharrProcessor.cs +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - /// - /// The Scharr operator filter. - /// - /// - public class ScharrProcessor : EdgeDetector2DFilter - { - /// - public override float[,] KernelX => new float[,] - { - { -3, 0, 3 }, - { -10, 0, 10 }, - { -3, 0, 3 } - }; - - /// - public override float[,] KernelY => new float[,] - { - { 3, 10, 3 }, - { 0, 0, 0 }, - { -3, -10, -3 } - }; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs deleted file mode 100644 index 39f7d350d2..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/EdgeDetection/SobelProcessor.cs +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - /// - /// The Sobel operator filter. - /// - /// - public class SobelProcessor : EdgeDetector2DFilter - { - /// - public override float[,] KernelX => new float[,] - { - { -1, 0, 1 }, - { -2, 0, 2 }, - { -1, 0, 1 } - }; - - /// - public override float[,] KernelY => new float[,] - { - { 1, 2, 1 }, - { 0, 0, 0 }, - { -1, -2, -1 } - }; - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/GuassianBlurProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/GuassianBlurProcessor.cs deleted file mode 100644 index 6288ea963b..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/GuassianBlurProcessor.cs +++ /dev/null @@ -1,140 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System; - - /// - /// Applies a Gaussian blur filter to the image. - /// - public class GuassianBlurProcessor : Convolution2PassFilter - { - /// - /// The maximum size of the kernal in either direction. - /// - private readonly int kernelSize; - - /// - /// The spread of the blur. - /// - private readonly float sigma; - - /// - /// The vertical kernel - /// - private float[,] kernelY; - - /// - /// The horizontal kernel - /// - private float[,] kernelX; - - /// - /// Initializes a new instance of the class. - /// - /// The 'sigma' value representing the weight of the blur. - public GuassianBlurProcessor(float sigma = 3f) - { - this.kernelSize = ((int)Math.Ceiling(sigma) * 2) + 1; - this.sigma = sigma; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// - public GuassianBlurProcessor(int radius) - { - this.kernelSize = (radius * 2) + 1; - this.sigma = radius; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'sigma' value representing the weight of the blur. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// This should be at least twice the sigma value. - /// - public GuassianBlurProcessor(float sigma, int radius) - { - this.kernelSize = (radius * 2) + 1; - this.sigma = sigma; - } - - /// - public override float[,] KernelX => this.kernelX; - - /// - public override float[,] KernelY => this.kernelY; - - /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - if (this.kernelY == null) - { - this.kernelY = this.CreateGaussianKernel(false); - } - - if (this.kernelX == null) - { - this.kernelX = this.CreateGaussianKernel(true); - } - } - - /// - /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function - /// - /// Whether to calculate a horizontal kernel. - /// The - private float[,] CreateGaussianKernel(bool horizontal) - { - int size = this.kernelSize; - float weight = this.sigma; - float[,] kernel = horizontal ? new float[1, size] : new float[size, 1]; - float sum = 0.0f; - - float midpoint = (size - 1) / 2f; - for (int i = 0; i < size; i++) - { - float x = i - midpoint; - float gx = ImageMaths.Gaussian(x, weight); - sum += gx; - if (horizontal) - { - kernel[0, i] = gx; - } - else - { - kernel[i, 0] = gx; - } - } - - // Normalise kernel so that the sum of all weights equals 1 - if (horizontal) - { - for (int i = 0; i < size; i++) - { - kernel[0, i] = kernel[0, i] / sum; - } - } - else - { - for (int i = 0; i < size; i++) - { - kernel[i, 0] = kernel[i, 0] / sum; - } - } - - return kernel; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/GuassianSharpenProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/GuassianSharpenProcessor.cs deleted file mode 100644 index 9d70732d20..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/Convolution/GuassianSharpenProcessor.cs +++ /dev/null @@ -1,178 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System; - - /// - /// Applies a Gaussian sharpening filter to the image. - /// - public class GuassianSharpenProcessor : Convolution2PassFilter - { - /// - /// The maximum size of the kernal in either direction. - /// - private readonly int kernelSize; - - /// - /// The spread of the blur. - /// - private readonly float sigma; - - /// - /// The vertical kernel - /// - private float[,] kernelY; - - /// - /// The horizontal kernel - /// - private float[,] kernelX; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'sigma' value representing the weight of the sharpening. - /// - public GuassianSharpenProcessor(float sigma = 3f) - { - this.kernelSize = ((int)Math.Ceiling(sigma) * 2) + 1; - this.sigma = sigma; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// - public GuassianSharpenProcessor(int radius) - { - this.kernelSize = (radius * 2) + 1; - this.sigma = radius; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The 'sigma' value representing the weight of the sharpen. - /// - /// - /// The 'radius' value representing the size of the area to sample. - /// This should be at least twice the sigma value. - /// - public GuassianSharpenProcessor(float sigma, int radius) - { - this.kernelSize = (radius * 2) + 1; - this.sigma = sigma; - } - - /// - public override float[,] KernelX => this.kernelX; - - /// - public override float[,] KernelY => this.kernelY; - - /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - if (this.kernelY == null) - { - this.kernelY = this.CreateGaussianKernel(false); - } - - if (this.kernelX == null) - { - this.kernelX = this.CreateGaussianKernel(true); - } - } - - /// - /// Create a 1 dimensional Gaussian kernel using the Gaussian G(x) function - /// - /// Whether to calculate a horizontal kernel. - /// The - private float[,] CreateGaussianKernel(bool horizontal) - { - int size = this.kernelSize; - float weight = this.sigma; - float[,] kernel = horizontal ? new float[1, size] : new float[size, 1]; - float sum = 0; - - float midpoint = (size - 1) / 2f; - for (int i = 0; i < size; i++) - { - float x = i - midpoint; - float gx = ImageMaths.Gaussian(x, weight); - sum += gx; - if (horizontal) - { - kernel[0, i] = gx; - } - else - { - kernel[i, 0] = gx; - } - } - - // Invert the kernel for sharpening. - int midpointRounded = (int)midpoint; - - if (horizontal) - { - for (int i = 0; i < size; i++) - { - if (i == midpointRounded) - { - // Calculate central value - kernel[0, i] = (2f * sum) - kernel[0, i]; - } - else - { - // invert value - kernel[0, i] = -kernel[0, i]; - } - } - } - else - { - for (int i = 0; i < size; i++) - { - if (i == midpointRounded) - { - // Calculate central value - kernel[i, 0] = (2 * sum) - kernel[i, 0]; - } - else - { - // invert value - kernel[i, 0] = -kernel[i, 0]; - } - } - } - - // Normalise kernel so that the sum of all weights equals 1 - if (horizontal) - { - for (int i = 0; i < size; i++) - { - kernel[0, i] = kernel[0, i] / sum; - } - } - else - { - for (int i = 0; i < size; i++) - { - kernel[i, 0] = kernel[i, 0] / sum; - } - } - - return kernel; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/GlowProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/GlowProcessor.cs deleted file mode 100644 index e046be6a9d..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/GlowProcessor.cs +++ /dev/null @@ -1,64 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System; - using System.Numerics; - using System.Threading.Tasks; - - /// - /// Creates a glow effect on the image - /// - public class GlowProcessor : ImageProcessor - { - /// - /// Gets or sets the glow color to apply. - /// - public Color Color { get; set; } = Color.White; - - /// - /// Gets or sets the the x-radius. - /// - public float RadiusX { get; set; } - - /// - /// Gets or sets the the y-radius. - /// - public float RadiusY { get; set; } - - /// - 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; - 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()) - { - Parallel.For( - startY, - endY, - 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(glowColor, sourceColor, .5f * (distance / maxDistance)); - } - - this.OnRowProcessed(); - }); - } - } - } -} - diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/InvertProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/InvertProcessor.cs deleted file mode 100644 index 1f319b92b5..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/InvertProcessor.cs +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Numerics; - using System.Threading.Tasks; - - /// - /// An to invert the colors of an . - /// - public class InvertProcessor : ImageProcessor - { - /// - 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; - Vector3 inverseVector = Vector3.One; - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - startY, - endY, - 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); - } - - this.OnRowProcessed(); - } - }); - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/PixelateProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/PixelateProcessor.cs deleted file mode 100644 index 4769799b5d..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/PixelateProcessor.cs +++ /dev/null @@ -1,94 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System; - using System.Collections.Generic; - using System.Threading.Tasks; - - /// - /// An to invert the colors of an . - /// - public class PixelateProcessor : ImageProcessor - { - /// - /// 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) - { - Guard.MustBeGreaterThan(size, 0, nameof(size)); - this.Value = size; - } - - /// - /// Gets or the pixel size. - /// - public int Value { get; } - - /// - 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; - int size = this.Value; - int offset = this.Value / 2; - - // 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()) - { - Parallel.ForEach( - range, - y => - { - if (y >= sourceY && y < sourceBottom) - { - for (int x = startX; x < endX; x += size) - { - int offsetX = offset; - int offsetY = offset; - - // Make sure that the offset is within the boundary of the - // image. - while (y + offsetY >= sourceBottom) - { - offsetY--; - } - - while (x + offsetX >= endX) - { - offsetX--; - } - - // Get the pixel color in the centre of the soon to be pixelated area. - // ReSharper disable AccessToDisposedClosure - Color 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++) - { - for (int k = x; k < x + size && k < endX; k++) - { - targetPixels[k, l] = pixel; - } - } - } - - this.OnRowProcessed(); - } - }); - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Processors/VignetteProcessor.cs b/src/ImageProcessorCore - Copy/Filters/Processors/VignetteProcessor.cs deleted file mode 100644 index 1fd4630ed3..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Processors/VignetteProcessor.cs +++ /dev/null @@ -1,63 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System; - using System.Numerics; - using System.Threading.Tasks; - - /// - /// Creates a vignette effect on the image - /// - public class VignetteProcessor : ImageProcessor - { - /// - /// Gets or sets the vignette color to apply. - /// - public Color Color { get; set; } = Color.Black; - - /// - /// Gets or sets the the x-radius. - /// - public float RadiusX { get; set; } - - /// - /// Gets or sets the the y-radius. - /// - public float RadiusY { get; set; } - - /// - 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; - 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()) - { - Parallel.For( - startY, - endY, - 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); - } - this.OnRowProcessed(); - }); - } - } - } -} - diff --git a/src/ImageProcessorCore - Copy/Filters/Saturation.cs b/src/ImageProcessorCore - Copy/Filters/Saturation.cs deleted file mode 100644 index 93c69fe3e7..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Saturation.cs +++ /dev/null @@ -1,52 +0,0 @@ -// -// 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 - { - /// - /// Alters the saturation component of the image. - /// - /// 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) - { - return Saturation(source, amount, source.Bounds, progressHandler); - } - - /// - /// Alters the saturation component of the image. - /// - /// 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) - { - SaturationProcessor processor = new SaturationProcessor(amount); - processor.OnProgress += progressHandler; - - try - { - return source.Process(rectangle, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Filters/Sepia.cs b/src/ImageProcessorCore - Copy/Filters/Sepia.cs deleted file mode 100644 index 27d19b3194..0000000000 --- a/src/ImageProcessorCore - Copy/Filters/Sepia.cs +++ /dev/null @@ -1,50 +0,0 @@ -// -// 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 sepia toning to the image. - /// - /// 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) - { - return Sepia(source, source.Bounds, progressHandler); - } - - /// - /// Applies sepia toning to the image. - /// - /// 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) - { - SepiaProcessor processor = new SepiaProcessor(); - processor.OnProgress += progressHandler; - - try - { - return source.Process(rectangle, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpBitsPerPixel.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpBitsPerPixel.cs deleted file mode 100644 index e7de3bc295..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpBitsPerPixel.cs +++ /dev/null @@ -1,23 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Enumerates the available bits per pixel for bitmap. - /// - public enum BmpBitsPerPixel - { - /// - /// 24 bits per pixel. Each pixel consists of 3 bytes. - /// - Pixel24 = 3, - - /// - /// 32 bits per pixel. Each pixel consists of 4 bytes. - /// - Pixel32 = 4, - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpCompression.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpCompression.cs deleted file mode 100644 index de3c66495d..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpCompression.cs +++ /dev/null @@ -1,63 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Defines how the compression type of the image data - /// in the bitmap file. - /// - internal enum BmpCompression - { - /// - /// Each image row has a multiple of four elements. If the - /// row has less elements, zeros will be added at the right side. - /// The format depends on the number of bits, stored in the info header. - /// If the number of bits are one, four or eight each pixel data is - /// a index to the palette. If the number of bits are sixteen, - /// twenty-four or thirty-two each pixel contains a color. - /// - RGB = 0, - - /// - /// Two bytes are one data record. If the first byte is not zero, the - /// next two half bytes will be repeated as much as the value of the first byte. - /// If the first byte is zero, the record has different meanings, depending - /// on the second byte. If the second byte is zero, it is the end of the row, - /// if it is one, it is the end of the image. - /// Not supported at the moment. - /// - RLE8 = 1, - - /// - /// Two bytes are one data record. If the first byte is not zero, the - /// next byte will be repeated as much as the value of the first byte. - /// If the first byte is zero, the record has different meanings, depending - /// on the second byte. If the second byte is zero, it is the end of the row, - /// if it is one, it is the end of the image. - /// Not supported at the moment. - /// - RLE4 = 2, - - /// - /// Each image row has a multiple of four elements. If the - /// row has less elements, zeros will be added at the right side. - /// Not supported at the moment. - /// - BitFields = 3, - - /// - /// The bitmap contains a JPG image. - /// Not supported at the moment. - /// - JPEG = 4, - - /// - /// The bitmap contains a PNG image. - /// Not supported at the moment. - /// - PNG = 5 - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoder.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoder.cs deleted file mode 100644 index 03d45f1122..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoder.cs +++ /dev/null @@ -1,82 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - using System.IO; - - /// - /// Image decoder for generating an image out of a Windows bitmap stream. - /// - /// - /// Does not support the following formats at the moment: - /// - /// JPG - /// PNG - /// RLE4 - /// RLE8 - /// BitFields - /// - /// Formats will be supported in a later releases. We advise always - /// to use only 24 Bit Windows bitmaps. - /// - public class BmpDecoder : IImageDecoder - { - /// - /// Gets the size of the header for this image type. - /// - /// The size of the header. - public int HeaderSize => 2; - - /// - /// Returns a value indicating whether the supports the specified - /// file header. - /// - /// The containing the file extension. - /// - /// True if the decoder supports the file extension; otherwise, false. - /// - public bool IsSupportedFileExtension(string extension) - { - Guard.NotNullOrEmpty(extension, "extension"); - - extension = extension.StartsWith(".") ? extension.Substring(1) : extension; - - return extension.Equals("BMP", StringComparison.OrdinalIgnoreCase) - || extension.Equals("DIP", StringComparison.OrdinalIgnoreCase); - } - - /// - /// Returns a value indicating whether the supports the specified - /// file header. - /// - /// The containing the file header. - /// - /// True if the decoder supports the file header; otherwise, false. - /// - public bool IsSupportedFileFormat(byte[] header) - { - bool isBmp = false; - if (header.Length >= 2) - { - isBmp = header[0] == 0x42 && // B - header[1] == 0x4D; // M - } - - return isBmp; - } - - /// - /// Decodes the image from the specified stream to the . - /// - /// The to decode to. - /// The containing image data. - public void Decode(Image image, Stream stream) - { - new BmpDecoderCore().Decode(image, stream); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoderCore.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoderCore.cs deleted file mode 100644 index 9c2bc45b9a..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpDecoderCore.cs +++ /dev/null @@ -1,445 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - using System.IO; - using System.Threading.Tasks; - - /// - /// Performs the bmp decoding operation. - /// - internal sealed class BmpDecoderCore - { - /// - /// The mask for the red part of the color for 16 bit rgb bitmaps. - /// - private const int Rgb16RMask = 0x00007C00; - - /// - /// The mask for the green part of the color for 16 bit rgb bitmaps. - /// - private const int Rgb16GMask = 0x000003E0; - - /// - /// The mask for the blue part of the color for 16 bit rgb bitmaps. - /// - private const int Rgb16BMask = 0x0000001F; - - /// - /// The stream to decode from. - /// - private Stream currentStream; - - /// - /// The file header containing general information. - /// TODO: Why is this not used? We advance the stream but do not use the values parsed. - /// - private BmpFileHeader fileHeader; - - /// - /// The info header containing detailed information about the bitmap. - /// - private BmpInfoHeader infoHeader; - - /// - /// Decodes the image from the specified this._stream and sets - /// the data to image. - /// - /// The type of pixels contained within the image. - /// The image, where the data should be set to. - /// Cannot be null (Nothing in Visual Basic). - /// The this._stream, where the image should be - /// decoded from. Cannot be null (Nothing in Visual Basic). - /// - /// is null. - /// - or - - /// is null. - /// - public void Decode(Image image, Stream stream) - where TPackedVector : IPackedVector, new() - { - this.currentStream = stream; - - try - { - this.ReadFileHeader(); - this.ReadInfoHeader(); - - // see http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517 - // If the height is negative, then this is a Windows bitmap whose origin - // is the upper-left corner and not the lower-left.The inverted flag - // indicates a lower-left origin.Our code will be outputting an - // upper-left origin pixel array. - bool inverted = false; - if (this.infoHeader.Height < 0) - { - inverted = true; - this.infoHeader.Height = -this.infoHeader.Height; - } - - int colorMapSize = -1; - - if (this.infoHeader.ClrUsed == 0) - { - if (this.infoHeader.BitsPerPixel == 1 || - this.infoHeader.BitsPerPixel == 4 || - this.infoHeader.BitsPerPixel == 8) - { - colorMapSize = (int)Math.Pow(2, this.infoHeader.BitsPerPixel) * 4; - } - } - else - { - colorMapSize = this.infoHeader.ClrUsed * 4; - } - - byte[] palette = null; - - if (colorMapSize > 0) - { - // 255 * 4 - if (colorMapSize > 1020) - { - throw new ImageFormatException($"Invalid bmp colormap size '{colorMapSize}'"); - } - - palette = new byte[colorMapSize]; - - this.currentStream.Read(palette, 0, colorMapSize); - } - - if (this.infoHeader.Width > image.MaxWidth || this.infoHeader.Height > image.MaxHeight) - { - throw new ArgumentOutOfRangeException( - $"The input bitmap '{this.infoHeader.Width}x{this.infoHeader.Height}' is " - + $"bigger then the max allowed size '{image.MaxWidth}x{image.MaxHeight}'"); - } - - TPackedVector[] imageData = new TPackedVector[this.infoHeader.Width * this.infoHeader.Height]; - - switch (this.infoHeader.Compression) - { - case BmpCompression.RGB: - if (this.infoHeader.HeaderSize != 40) - { - throw new ImageFormatException( - $"Header Size value '{this.infoHeader.HeaderSize}' is not valid."); - } - - if (this.infoHeader.BitsPerPixel == 32) - { - this.ReadRgb32(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted); - } - else if (this.infoHeader.BitsPerPixel == 24) - { - this.ReadRgb24(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted); - } - else if (this.infoHeader.BitsPerPixel == 16) - { - this.ReadRgb16(imageData, this.infoHeader.Width, this.infoHeader.Height, inverted); - } - else if (this.infoHeader.BitsPerPixel <= 8) - { - this.ReadRgbPalette( - imageData, - palette, - this.infoHeader.Width, - this.infoHeader.Height, - this.infoHeader.BitsPerPixel, - inverted); - } - - break; - default: - throw new NotSupportedException("Does not support this kind of bitmap files."); - } - - image.SetPixels(this.infoHeader.Width, this.infoHeader.Height, imageData); - } - catch (IndexOutOfRangeException e) - { - throw new ImageFormatException("Bitmap does not have a valid format.", e); - } - } - - /// - /// Returns the y- value based on the given height. - /// - /// The y- value representing the current row. - /// The height of the bitmap. - /// The representing the inverted value. - private static int Invert(int y, int height, bool inverted) - { - int row; - - if (!inverted) - { - row = height - y - 1; - } - else - { - row = y; - } - - return row; - } - - /// - /// Reads the color palette from the stream. - /// - /// The image data to assign the palette to. - /// The containing the colors. - /// The width of the bitmap. - /// The height of the bitmap. - /// The number of bits per pixel. - private void ReadRgbPalette(float[] imageData, byte[] colors, int width, int height, int bits, bool inverted) - { - // Pixels per byte (bits per pixel) - int ppb = 8 / bits; - - int arrayWidth = (width + ppb - 1) / ppb; - - // Bit mask - int mask = 0xFF >> (8 - bits); - - byte[] data = new byte[arrayWidth * height]; - - this.currentStream.Read(data, 0, data.Length); - - // Rows are aligned on 4 byte boundaries - int alignment = arrayWidth % 4; - if (alignment != 0) - { - alignment = 4 - alignment; - } - - Parallel.For( - 0, - height, - y => - { - int rowOffset = y * (arrayWidth + alignment); - - for (int x = 0; x < arrayWidth; x++) - { - int offset = rowOffset + x; - - // Revert the y value, because bitmaps are saved from down to top - int row = Invert(y, height, inverted); - - int colOffset = x * ppb; - - for (int shift = 0; shift < ppb && (colOffset + shift) < width; shift++) - { - int colorIndex = ((data[offset] >> (8 - bits - (shift * bits))) & mask) * 4; - int arrayOffset = ((row * width) + (colOffset + shift)) * 4; - - // We divide by 255 as we will store the colors in our floating point format. - // Stored in r-> g-> b-> a order. - imageData[arrayOffset] = colors[colorIndex + 2] / 255f; // r - imageData[arrayOffset + 1] = colors[colorIndex + 1] / 255f; // g - imageData[arrayOffset + 2] = colors[colorIndex] / 255f; // b - imageData[arrayOffset + 3] = 1; // a - } - } - }); - } - - /// - /// Reads the 16 bit color palette from the stream - /// - /// The type of pixels contained within the image. - /// The image data to assign the palette to. - /// The width of the bitmap. - /// The height of the bitmap. - private void ReadRgb16(TPackedVector[] imageData, int width, int height, bool inverted) - where TPackedVector : IPackedVector, new() - { - // We divide here as we will store the colors in our floating point format. - const int ScaleR = 8; // 256/32 - const int ScaleG = 4; // 256/64 - - int alignment; - byte[] data = this.GetImageArray(width, height, 2, out alignment); - - Parallel.For( - 0, - height, - y => - { - int rowOffset = y * ((width * 2) + alignment); - - // Revert the y value, because bitmaps are saved from down to top - int row = Invert(y, height, inverted); - - for (int x = 0; x < width; x++) - { - int offset = rowOffset + (x * 2); - - short temp = BitConverter.ToInt16(data, offset); - - byte r = (byte)(((temp & Rgb16RMask) >> 11) * ScaleR); - byte g = (byte)(((temp & Rgb16GMask) >> 5) * ScaleG); - byte b = (byte)((temp & Rgb16BMask) * ScaleR); - - int arrayOffset = ((row * width) + x); - - // Stored in b-> g-> r-> a order. - TPackedVector packed = new TPackedVector(); - packed.PackBytes(b, g, r, 255); - imageData[arrayOffset] = packed; - } - }); - } - - /// - /// Reads the 24 bit color palette from the stream - /// - /// The type of pixels contained within the image. - /// The image data to assign the palette to. - /// The width of the bitmap. - /// The height of the bitmap. - private void ReadRgb24(TPackedVector[] imageData, int width, int height, bool inverted) - where TPackedVector : IPackedVector, new() - { - int alignment; - byte[] data = this.GetImageArray(width, height, 3, out alignment); - - Parallel.For( - 0, - height, - y => - { - int rowOffset = y * ((width * 3) + alignment); - - // Revert the y value, because bitmaps are saved from down to top - int row = Invert(y, height, inverted); - - for (int x = 0; x < width; x++) - { - int offset = rowOffset + (x * 3); - int arrayOffset = ((row * width) + x); - - // We divide by 255 as we will store the colors in our floating point format. - // Stored in b-> g-> r-> a order. - TPackedVector packed = new TPackedVector(); - packed.PackBytes(data[offset], data[offset + 1], data[offset + 2], 255); - imageData[arrayOffset] = packed; - } - }); - } - - /// - /// Reads the 32 bit color palette from the stream - /// - /// The type of pixels contained within the image. - /// The image data to assign the palette to. - /// The width of the bitmap. - /// The height of the bitmap. - private void ReadRgb32(TPackedVector[] imageData, int width, int height, bool inverted) - where TPackedVector : IPackedVector, new() - { - int alignment; - byte[] data = this.GetImageArray(width, height, 4, out alignment); - - Parallel.For( - 0, - height, - y => - { - int rowOffset = y * ((width * 4) + alignment); - - // Revert the y value, because bitmaps are saved from down to top - int row = Invert(y, height, inverted); - - for (int x = 0; x < width; x++) - { - int offset = rowOffset + (x * 4); - int arrayOffset = ((row * width) + x); - - // Stored in b-> g-> r-> a order. - TPackedVector packed = new TPackedVector(); - packed.PackBytes(data[offset], data[offset + 1], data[offset + 2], data[offset + 3]); - imageData[arrayOffset] = packed; - } - }); - } - - /// - /// Returns a containing the pixels for the current bitmap. - /// - /// The width of the bitmap. - /// The height. - /// The number of bytes per pixel. - /// The alignment of the pixels. - /// - /// The containing the pixels. - /// - private byte[] GetImageArray(int width, int height, int bytes, out int alignment) - { - int dataWidth = width; - - alignment = (width * bytes) % 4; - - if (alignment != 0) - { - alignment = 4 - alignment; - } - - int size = ((dataWidth * bytes) + alignment) * height; - - byte[] data = new byte[size]; - - this.currentStream.Read(data, 0, size); - - return data; - } - - /// - /// Reads the from the stream. - /// - private void ReadInfoHeader() - { - byte[] data = new byte[BmpInfoHeader.Size]; - - this.currentStream.Read(data, 0, BmpInfoHeader.Size); - - this.infoHeader = new BmpInfoHeader - { - HeaderSize = BitConverter.ToInt32(data, 0), - Width = BitConverter.ToInt32(data, 4), - Height = BitConverter.ToInt32(data, 8), - Planes = BitConverter.ToInt16(data, 12), - BitsPerPixel = BitConverter.ToInt16(data, 14), - ImageSize = BitConverter.ToInt32(data, 20), - XPelsPerMeter = BitConverter.ToInt32(data, 24), - YPelsPerMeter = BitConverter.ToInt32(data, 28), - ClrUsed = BitConverter.ToInt32(data, 32), - ClrImportant = BitConverter.ToInt32(data, 36), - Compression = (BmpCompression)BitConverter.ToInt32(data, 16) - }; - } - - /// - /// Reads the from the stream. - /// - private void ReadFileHeader() - { - byte[] data = new byte[BmpFileHeader.Size]; - - this.currentStream.Read(data, 0, BmpFileHeader.Size); - - this.fileHeader = new BmpFileHeader - { - Type = BitConverter.ToInt16(data, 0), - FileSize = BitConverter.ToInt32(data, 2), - Reserved = BitConverter.ToInt32(data, 6), - Offset = BitConverter.ToInt32(data, 10) - }; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoder.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoder.cs deleted file mode 100644 index 4b212d4ea0..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoder.cs +++ /dev/null @@ -1,53 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - using System.IO; - - /// - /// Image encoder for writing an image to a stream as a Windows bitmap. - /// - /// The encoder can currently only write 24-bit rgb images to streams. - public class BmpEncoder : IImageEncoder - { - /// - /// Gets or sets the quality of output for images. - /// - /// Bitmap is a lossless format so this is not used in this encoder. - public int Quality { get; set; } - - /// - public string MimeType => "image/bmp"; - - /// - public string Extension => "bmp"; - - /// - /// Gets or sets the number of bits per pixel. - /// - public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24; - - /// - public bool IsSupportedFileExtension(string extension) - { - Guard.NotNullOrEmpty(extension, nameof(extension)); - - extension = extension.StartsWith(".") ? extension.Substring(1) : extension; - - return extension.Equals(this.Extension, StringComparison.OrdinalIgnoreCase) - || extension.Equals("dip", StringComparison.OrdinalIgnoreCase); - } - - /// - public void Encode(ImageBase image, Stream stream) - where TPackedVector: IPackedVector - { - BmpEncoderCore encoder = new BmpEncoderCore(); - encoder.Encode(image, stream, this.BitsPerPixel); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoderCore.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoderCore.cs deleted file mode 100644 index 2ad8e24e6e..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpEncoderCore.cs +++ /dev/null @@ -1,205 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - using System.IO; - - using IO; - - /// - /// Image encoder for writing an image to a stream as a Windows bitmap. - /// - /// The encoder can currently only write 24-bit rgb images to streams. - internal sealed class BmpEncoderCore - { - /// - /// The number of bits per pixel. - /// - private BmpBitsPerPixel bmpBitsPerPixel; - - /// - /// Encodes the image to the specified stream from the . - /// - /// The type of pixels contained within the image. - /// The to encode from. - /// The to encode the image data to. - /// The - public void Encode(ImageBase image, Stream stream, BmpBitsPerPixel bitsPerPixel) - where TPackedVector : IPackedVector - { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); - - this.bmpBitsPerPixel = bitsPerPixel; - - int rowWidth = image.Width; - - // TODO: Check this for varying file formats. - int amount = (image.Width * (int)this.bmpBitsPerPixel) % 4; - if (amount != 0) - { - rowWidth += 4 - amount; - } - - // Do not use IDisposable pattern here as we want to preserve the stream. - EndianBinaryWriter writer = new EndianBinaryWriter(EndianBitConverter.Little, stream); - - int bpp = (int)this.bmpBitsPerPixel; - - BmpFileHeader fileHeader = new BmpFileHeader - { - Type = 19778, // BM - Offset = 54, - FileSize = 54 + (image.Height * rowWidth * bpp) - }; - - BmpInfoHeader infoHeader = new BmpInfoHeader - { - HeaderSize = 40, - Height = image.Height, - Width = image.Width, - BitsPerPixel = (short)(8 * bpp), - Planes = 1, - ImageSize = image.Height * rowWidth * bpp, - ClrUsed = 0, - ClrImportant = 0 - }; - - WriteHeader(writer, fileHeader); - this.WriteInfo(writer, infoHeader); - this.WriteImage(writer, image); - - writer.Flush(); - } - - /// - /// Writes the bitmap header data to the binary stream. - /// - /// - /// The containing the stream to write to. - /// - /// - /// The containing the header data. - /// - private static void WriteHeader(EndianBinaryWriter writer, BmpFileHeader fileHeader) - { - writer.Write(fileHeader.Type); - writer.Write(fileHeader.FileSize); - writer.Write(fileHeader.Reserved); - writer.Write(fileHeader.Offset); - } - - /// - /// Writes the bitmap information to the binary stream. - /// - /// - /// The containing the stream to write to. - /// - /// - /// The containing the detailed information about the image. - /// - private void WriteInfo(EndianBinaryWriter writer, BmpInfoHeader infoHeader) - { - writer.Write(infoHeader.HeaderSize); - writer.Write(infoHeader.Width); - writer.Write(infoHeader.Height); - writer.Write(infoHeader.Planes); - writer.Write(infoHeader.BitsPerPixel); - writer.Write((int)infoHeader.Compression); - writer.Write(infoHeader.ImageSize); - writer.Write(infoHeader.XPelsPerMeter); - writer.Write(infoHeader.YPelsPerMeter); - writer.Write(infoHeader.ClrUsed); - writer.Write(infoHeader.ClrImportant); - } - - /// - /// Writes the pixel data to the binary stream. - /// - /// The type of pixels contained within the image. - /// - /// The containing the stream to write to. - /// - /// - /// The containing pixel data. - /// - private void WriteImage(EndianBinaryWriter writer, ImageBase image) - where TPackedVector : IPackedVector - { - // TODO: Add more compression formats. - int amount = (image.Width * (int)this.bmpBitsPerPixel) % 4; - if (amount != 0) - { - amount = 4 - amount; - } - - using (IPixelAccessor pixels = image.Lock()) - { - switch (this.bmpBitsPerPixel) - { - case BmpBitsPerPixel.Pixel32: - this.Write32bit(writer, pixels, amount); - break; - - case BmpBitsPerPixel.Pixel24: - this.Write24bit(writer, pixels, amount); - break; - } - } - } - - /// - /// Writes the 32bit color palette to the stream. - /// - /// The containing the stream to write to. - /// The containing pixel data. - /// The amount to pad each row by. - private void Write32bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) - { - for (int y = pixels.Height - 1; y >= 0; y--) - { - for (int x = 0; x < pixels.Width; x++) - { - // Convert back to b-> g-> r-> a order. - byte[] bytes = pixels[x, y].ToBytes(); - writer.Write(bytes); - } - - // Pad - for (int i = 0; i < amount; i++) - { - writer.Write((byte)0); - } - } - } - - /// - /// Writes the 24bit color palette to the stream. - /// - /// The containing the stream to write to. - /// The containing pixel data. - /// The amount to pad each row by. - private void Write24bit(EndianBinaryWriter writer, IPixelAccessor pixels, int amount) - { - for (int y = pixels.Height - 1; y >= 0; y--) - { - for (int x = 0; x < pixels.Width; x++) - { - // Convert back to b-> g-> r-> a order. - byte[] bytes = pixels[x, y].ToBytes(); - writer.Write(new[] { bytes[0], bytes[1], bytes[2] }); - } - - // Pad - for (int i = 0; i < amount; i++) - { - writer.Write((byte)0); - } - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpFileHeader.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpFileHeader.cs deleted file mode 100644 index 6f626ee703..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpFileHeader.cs +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Stores general information about the Bitmap file. - /// - /// - /// - /// The first two bytes of the Bitmap file format - /// (thus the Bitmap header) are stored in big-endian order. - /// All of the other integer values are stored in little-endian format - /// (i.e. least-significant byte first). - /// - internal class BmpFileHeader - { - /// - /// Defines of the data structure in the bitmap file. - /// - public const int Size = 14; - - /// - /// Gets or sets the Bitmap identifier. - /// The field used to identify the bitmap file: 0x42 0x4D - /// (Hex code points for B and M) - /// - public short Type { get; set; } - - /// - /// Gets or sets the size of the bitmap file in bytes. - /// - public int FileSize { get; set; } - - /// - /// Gets or sets any reserved data; actual value depends on the application - /// that creates the image. - /// - public int Reserved { get; set; } - - /// - /// Gets or sets the offset, i.e. starting address, of the byte where - /// the bitmap data can be found. - /// - public int Offset { get; set; } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpFormat.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpFormat.cs deleted file mode 100644 index 6f640c4b9e..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpFormat.cs +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Encapsulates the means to encode and decode bitmap images. - /// - public class BmpFormat : IImageFormat - { - /// - public IImageDecoder Decoder => new BmpDecoder(); - - /// - public IImageEncoder Encoder => new BmpEncoder(); - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpInfoHeader.cs b/src/ImageProcessorCore - Copy/Formats/Bmp/BmpInfoHeader.cs deleted file mode 100644 index c21a52d21b..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Bmp/BmpInfoHeader.cs +++ /dev/null @@ -1,82 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// -namespace ImageProcessorCore.Formats -{ - /// - /// This block of bytes tells the application detailed information - /// about the image, which will be used to display the image on - /// the screen. - /// - /// - internal class BmpInfoHeader - { - /// - /// Defines of the data structure in the bitmap file. - /// - public const int Size = 40; - - /// - /// Gets or sets the size of this header (40 bytes) - /// - public int HeaderSize { get; set; } - - /// - /// Gets or sets the bitmap width in pixels (signed integer). - /// - public int Width { get; set; } - - /// - /// Gets or sets the bitmap height in pixels (signed integer). - /// - public int Height { get; set; } - - /// - /// Gets or sets the number of color planes being used. Must be set to 1. - /// - public short Planes { get; set; } - - /// - /// Gets or sets the number of bits per pixel, which is the color depth of the image. - /// Typical values are 1, 4, 8, 16, 24 and 32. - /// - public short BitsPerPixel { get; set; } - - /// - /// Gets or sets the compression method being used. - /// See the next table for a list of possible values. - /// - public BmpCompression Compression { get; set; } - - /// - /// Gets or sets the image size. This is the size of the raw bitmap data (see below), - /// and should not be confused with the file size. - /// - public int ImageSize { get; set; } - - /// - /// Gets or sets the horizontal resolution of the image. - /// (pixel per meter, signed integer) - /// - public int XPelsPerMeter { get; set; } - - /// - /// Gets or sets the vertical resolution of the image. - /// (pixel per meter, signed integer) - /// - public int YPelsPerMeter { get; set; } - - /// - /// Gets or sets the number of colors in the color palette, - /// or 0 to default to 2^n. - /// - public int ClrUsed { get; set; } - - /// - /// Gets or sets the number of important colors used, - /// or 0 when every color is important{ get; set; } generally ignored. - /// - public int ClrImportant { get; set; } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Bmp/README.md b/src/ImageProcessorCore - Copy/Formats/Bmp/README.md deleted file mode 100644 index d072838438..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Bmp/README.md +++ /dev/null @@ -1,8 +0,0 @@ -Encoder/Decoder adapted from: - -https://github.com/yufeih/Nine.Imaging/ -https://imagetools.codeplex.com/ - -TODO: - -- Add support for all bitmap formats. diff --git a/src/ImageProcessorCore - Copy/Formats/Gif/BitEncoder.cs b/src/ImageProcessorCore - Copy/Formats/Gif/BitEncoder.cs deleted file mode 100644 index a0c633a194..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Gif/BitEncoder.cs +++ /dev/null @@ -1,132 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System.Collections.Generic; - - /// - /// Handles the encoding of bits for compression. - /// - internal class BitEncoder - { - /// - /// The inner list for collecting the bits. - /// - private readonly List list = new List(); - - /// - /// The current working bit. - /// - private int currentBit; - - /// - /// The current value. - /// - private int currentValue; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The initial bits. - /// - public BitEncoder(int initial) - { - this.IntitialBit = initial; - } - - /// - /// Gets or sets the intitial bit. - /// - public int IntitialBit { get; set; } - - /// - /// The number of bytes in the encoder. - /// - public int Length => this.list.Count; - - /// - /// Adds the current byte to the end of the encoder. - /// - /// - /// The byte to add. - /// - public void Add(int item) - { - this.currentValue |= item << this.currentBit; - - this.currentBit += this.IntitialBit; - - while (this.currentBit >= 8) - { - byte value = (byte)(this.currentValue & 0XFF); - this.currentValue = this.currentValue >> 8; - this.currentBit -= 8; - this.list.Add(value); - } - } - - /// - /// Adds the collection of bytes to the end of the encoder. - /// - /// - /// The collection of bytes to add. - /// The collection itself cannot be null but can contain elements that are null. - public void AddRange(byte[] collection) - { - this.list.AddRange(collection); - } - - /// - /// Copies a range of elements from the encoder to a compatible one-dimensional array, - /// starting at the specified index of the target array. - /// - /// - /// The zero-based index in the source at which copying begins. - /// - /// - /// The one-dimensional Array that is the destination of the elements copied - /// from . The Array must have zero-based indexing - /// - /// The zero-based index in array at which copying begins. - /// The number of bytes to copy. - public void CopyTo(int index, byte[] array, int arrayIndex, int count) - { - this.list.CopyTo(index, array, arrayIndex, count); - } - - /// - /// Removes all the bytes from the encoder. - /// - public void Clear() - { - this.list.Clear(); - } - - /// - /// Copies the bytes into a new array. - /// - /// - public byte[] ToArray() - { - return this.list.ToArray(); - } - - /// - /// The end. - /// - internal void End() - { - while (this.currentBit > 0) - { - byte value = (byte)(this.currentValue & 0XFF); - this.currentValue = this.currentValue >> 8; - this.currentBit -= 8; - this.list.Add(value); - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Gif/DisposalMethod.cs b/src/ImageProcessorCore - Copy/Formats/Gif/DisposalMethod.cs deleted file mode 100644 index 4b0a019734..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Gif/DisposalMethod.cs +++ /dev/null @@ -1,37 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Provides enumeration for instructing the decoder what to do with the last image - /// in an animation sequence. - /// section 23 - /// - public enum DisposalMethod - { - /// - /// No disposal specified. The decoder is not required to take any action. - /// - Unspecified = 0, - - /// - /// Do not dispose. The graphic is to be left in place. - /// - NotDispose = 1, - - /// - /// Restore to background color. The area used by the graphic must be restored to - /// the background color. - /// - RestoreToBackground = 2, - - /// - /// Restore to previous. The decoder is required to restore the area overwritten by the - /// graphic with what was there prior to rendering the graphic. - /// - RestoreToPrevious = 3 - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Gif/GifConstants.cs b/src/ImageProcessorCore - Copy/Formats/Gif/GifConstants.cs deleted file mode 100644 index 42949cf168..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Gif/GifConstants.cs +++ /dev/null @@ -1,83 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Constants that define specific points within a gif. - /// - internal sealed class GifConstants - { - /// - /// The file type. - /// - public const string FileType = "GIF"; - - /// - /// The file version. - /// - public const string FileVersion = "89a"; - - /// - /// The extension block introducer !. - /// - public const byte ExtensionIntroducer = 0x21; - - /// - /// The graphic control label. - /// - public const byte GraphicControlLabel = 0xF9; - - /// - /// The application extension label. - /// - public const byte ApplicationExtensionLabel = 0xFF; - - /// - /// The application identification. - /// - public const string ApplicationIdentification = "NETSCAPE2.0"; - - /// - /// The application block size. - /// - public const byte ApplicationBlockSize = 0x0b; - - /// - /// The comment label. - /// - public const byte CommentLabel = 0xFE; - - /// - /// The maximum comment length. - /// - public const int MaxCommentLength = 1024 * 8; - - /// - /// The image descriptor label ,. - /// - public const byte ImageDescriptorLabel = 0x2C; - - /// - /// The plain text label. - /// - public const byte PlainTextLabel = 0x01; - - /// - /// The image label introducer ,. - /// - public const byte ImageLabel = 0x2C; - - /// - /// The terminator. - /// - public const byte Terminator = 0; - - /// - /// The end introducer trailer ;. - /// - public const byte EndIntroducer = 0x3B; - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Gif/GifDecoder.cs b/src/ImageProcessorCore - Copy/Formats/Gif/GifDecoder.cs deleted file mode 100644 index 1abbcb2fb7..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Gif/GifDecoder.cs +++ /dev/null @@ -1,67 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - using System.IO; - - /// - /// Decoder for generating an image out of a gif encoded stream. - /// - public class GifDecoder : IImageDecoder - { - /// - /// Gets the size of the header for this image type. - /// - /// The size of the header. - public int HeaderSize => 6; - - /// - /// Returns a value indicating whether the supports the specified - /// file header. - /// - /// The containing the file extension. - /// - /// True if the decoder supports the file extension; otherwise, false. - /// - public bool IsSupportedFileExtension(string extension) - { - Guard.NotNullOrEmpty(extension, nameof(extension)); - - extension = extension.StartsWith(".") ? extension.Substring(1) : extension; - return extension.Equals("GIF", StringComparison.OrdinalIgnoreCase); - } - - /// - /// Returns a value indicating whether the supports the specified - /// file header. - /// - /// The containing the file header. - /// - /// True if the decoder supports the file header; otherwise, false. - /// - public bool IsSupportedFileFormat(byte[] header) - { - return header.Length >= 6 && - header[0] == 0x47 && // G - header[1] == 0x49 && // I - header[2] == 0x46 && // F - header[3] == 0x38 && // 8 - (header[4] == 0x39 || header[4] == 0x37) && // 9 or 7 - 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) - { - new GifDecoderCore().Decode(image, stream); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Gif/GifDecoderCore.cs b/src/ImageProcessorCore - Copy/Formats/Gif/GifDecoderCore.cs deleted file mode 100644 index 693f974c7a..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Gif/GifDecoderCore.cs +++ /dev/null @@ -1,428 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - using System.IO; - - /// - /// Performs the gif decoding operation. - /// - internal class GifDecoderCore - { - /// - /// The image to decode the information to. - /// - private Image decodedImage; - - /// - /// The currently loaded stream. - /// - private Stream currentStream; - - /// - /// The global color table. - /// - private byte[] globalColorTable; - - /// - /// The current frame. - /// - private float[] currentFrame; - - /// - /// The logical screen descriptor. - /// - private GifLogicalScreenDescriptor logicalScreenDescriptor; - - /// - /// The graphics control extension. - /// - private GifGraphicsControlExtension graphicsControlExtension; - - /// - /// Decodes the stream to the image. - /// - /// The image to decode to. - /// The stream containing image data. - public void Decode(Image image, Stream stream) - { - this.decodedImage = image; - - this.currentStream = stream; - - // Skip the identifier - this.currentStream.Seek(6, SeekOrigin.Current); - this.ReadLogicalScreenDescriptor(); - - if (this.logicalScreenDescriptor.GlobalColorTableFlag) - { - this.globalColorTable = new byte[this.logicalScreenDescriptor.GlobalColorTableSize * 3]; - - // Read the global color table from the stream - stream.Read(this.globalColorTable, 0, this.globalColorTable.Length); - } - - // Loop though the respective gif parts and read the data. - int nextFlag = stream.ReadByte(); - while (nextFlag != GifConstants.Terminator) - { - if (nextFlag == GifConstants.ImageLabel) - { - this.ReadFrame(); - } - else if (nextFlag == GifConstants.ExtensionIntroducer) - { - int label = stream.ReadByte(); - switch (label) - { - case GifConstants.GraphicControlLabel: - this.ReadGraphicalControlExtension(); - break; - case GifConstants.CommentLabel: - this.ReadComments(); - break; - case GifConstants.ApplicationExtensionLabel: - this.Skip(12); // No need to read. - break; - case GifConstants.PlainTextLabel: - this.Skip(13); // Not supported by any known decoder. - break; - } - } - else if (nextFlag == GifConstants.EndIntroducer) - { - break; - } - - nextFlag = stream.ReadByte(); - } - } - - /// - /// Reads the graphic control extension. - /// - private void ReadGraphicalControlExtension() - { - byte[] buffer = new byte[6]; - - this.currentStream.Read(buffer, 0, buffer.Length); - - byte packed = buffer[1]; - - this.graphicsControlExtension = new GifGraphicsControlExtension - { - DelayTime = BitConverter.ToInt16(buffer, 2), - TransparencyIndex = buffer[4], - TransparencyFlag = (packed & 0x01) == 1, - DisposalMethod = (DisposalMethod)((packed & 0x1C) >> 2) - }; - } - - /// - /// Reads the image descriptor - /// - /// - private GifImageDescriptor ReadImageDescriptor() - { - byte[] buffer = new byte[9]; - - this.currentStream.Read(buffer, 0, buffer.Length); - - byte packed = buffer[8]; - - GifImageDescriptor imageDescriptor = new GifImageDescriptor - { - Left = BitConverter.ToInt16(buffer, 0), - Top = BitConverter.ToInt16(buffer, 2), - Width = BitConverter.ToInt16(buffer, 4), - Height = BitConverter.ToInt16(buffer, 6), - LocalColorTableFlag = ((packed & 0x80) >> 7) == 1, - LocalColorTableSize = 2 << (packed & 0x07), - InterlaceFlag = ((packed & 0x40) >> 6) == 1 - }; - - return imageDescriptor; - } - - /// - /// Reads the logical screen descriptor. - /// - private void ReadLogicalScreenDescriptor() - { - byte[] buffer = new byte[7]; - - this.currentStream.Read(buffer, 0, buffer.Length); - - byte packed = buffer[4]; - - this.logicalScreenDescriptor = new GifLogicalScreenDescriptor - { - Width = BitConverter.ToInt16(buffer, 0), - Height = BitConverter.ToInt16(buffer, 2), - BackgroundColorIndex = buffer[5], - PixelAspectRatio = buffer[6], - GlobalColorTableFlag = ((packed & 0x80) >> 7) == 1, - GlobalColorTableSize = 2 << (packed & 0x07) - }; - - if (this.logicalScreenDescriptor.GlobalColorTableSize > 255 * 4) - { - throw new ImageFormatException( - $"Invalid gif colormap size '{this.logicalScreenDescriptor.GlobalColorTableSize}'"); - } - - if (this.logicalScreenDescriptor.Width > ImageBase.MaxWidth || this.logicalScreenDescriptor.Height > ImageBase.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}'"); - } - } - - /// - /// Skips the designated number of bytes in the stream. - /// - /// The number of bytes to skip. - private void Skip(int length) - { - this.currentStream.Seek(length, SeekOrigin.Current); - - int flag; - - while ((flag = this.currentStream.ReadByte()) != 0) - { - this.currentStream.Seek(flag, SeekOrigin.Current); - } - } - - /// - /// Reads the gif comments. - /// - private void ReadComments() - { - int flag; - - while ((flag = this.currentStream.ReadByte()) != 0) - { - if (flag > GifConstants.MaxCommentLength) - { - throw new ImageFormatException($"Gif comment length '{flag}' exceeds max '{GifConstants.MaxCommentLength}'"); - } - - byte[] buffer = new byte[flag]; - - this.currentStream.Read(buffer, 0, flag); - - this.decodedImage.Properties.Add(new ImageProperty("Comments", BitConverter.ToString(buffer))); - } - } - - /// - /// Reads an individual gif frame. - /// - private void ReadFrame() - { - GifImageDescriptor imageDescriptor = this.ReadImageDescriptor(); - - byte[] localColorTable = this.ReadFrameLocalColorTable(imageDescriptor); - - byte[] indices = this.ReadFrameIndices(imageDescriptor); - - // Determine the color table for this frame. If there is a local one, use it - // otherwise use the global color table. - byte[] colorTable = localColorTable ?? this.globalColorTable; - - this.ReadFrameColors(indices, colorTable, imageDescriptor); - - // Skip any remaining blocks - this.Skip(0); - } - - /// - /// Reads the frame indices marking the color to use for each pixel. - /// - /// The . - /// The - private byte[] ReadFrameIndices(GifImageDescriptor imageDescriptor) - { - int dataSize = this.currentStream.ReadByte(); - LzwDecoder lzwDecoder = new LzwDecoder(this.currentStream); - - byte[] indices = lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize); - - return indices; - } - - /// - /// Reads the local color table from the current frame. - /// - /// The . - /// The - private byte[] ReadFrameLocalColorTable(GifImageDescriptor imageDescriptor) - { - byte[] localColorTable = null; - - if (imageDescriptor.LocalColorTableFlag) - { - localColorTable = new byte[imageDescriptor.LocalColorTableSize * 3]; - - this.currentStream.Read(localColorTable, 0, localColorTable.Length); - } - - return localColorTable; - } - - /// - /// Reads the frames colors, mapping indices to colors. - /// - /// The indexed pixels. - /// The color table containing the available colors. - /// The - private void ReadFrameColors(byte[] indices, byte[] colorTable, GifImageDescriptor descriptor) - { - int imageWidth = this.logicalScreenDescriptor.Width; - int imageHeight = this.logicalScreenDescriptor.Height; - - if (this.currentFrame == null) - { - this.currentFrame = new float[imageWidth * imageHeight * 4]; - } - - float[] lastFrame = null; - - if (this.graphicsControlExtension != null && - this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious) - { - lastFrame = new float[imageWidth * imageHeight * 4]; - - Array.Copy(this.currentFrame, lastFrame, lastFrame.Length); - } - - int offset, i = 0; - int interlacePass = 0; // The interlace pass - int interlaceIncrement = 8; // The interlacing line increment - int interlaceY = 0; // The current interlaced line - - for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) - { - // Check if this image is interlaced. - int writeY; // the target y offset to write to - if (descriptor.InterlaceFlag) - { - // If so then we read lines at predetermined offsets. - // When an entire image height worth of offset lines has been read we consider this a pass. - // With each pass the number of offset lines changes and the starting line changes. - if (interlaceY >= descriptor.Height) - { - interlacePass++; - switch (interlacePass) - { - case 1: - interlaceY = 4; - break; - case 2: - interlaceY = 2; - interlaceIncrement = 4; - break; - case 3: - interlaceY = 1; - interlaceIncrement = 2; - break; - } - } - - writeY = interlaceY + descriptor.Top; - - interlaceY += interlaceIncrement; - } - else - { - writeY = y; - } - - for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++) - { - offset = ((writeY * imageWidth) + x) * 4; - 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 - } - - i++; - } - } - - float[] pixels = new float[imageWidth * imageHeight * 4]; - - Array.Copy(this.currentFrame, pixels, pixels.Length); - - ImageBase currentImage; - - if (this.decodedImage.Pixels == null) - { - currentImage = this.decodedImage; - currentImage.SetPixels(imageWidth, imageHeight, pixels); - currentImage.Quality = colorTable.Length / 3; - - if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0) - { - this.decodedImage.FrameDelay = this.graphicsControlExtension.DelayTime; - } - } - else - { - ImageFrame frame = new ImageFrame(); - - currentImage = frame; - currentImage.SetPixels(imageWidth, imageHeight, pixels); - currentImage.Quality = colorTable.Length / 3; - - if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0) - { - currentImage.FrameDelay = this.graphicsControlExtension.DelayTime; - } - - this.decodedImage.Frames.Add(frame); - } - - if (this.graphicsControlExtension != null) - { - if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground) - { - for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) - { - for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++) - { - offset = ((y * imageWidth) + x) * 4; - - // 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; - } - } - } - else if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious) - { - this.currentFrame = lastFrame; - } - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Gif/GifEncoder.cs b/src/ImageProcessorCore - Copy/Formats/Gif/GifEncoder.cs deleted file mode 100644 index 7a1c81c2b7..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Gif/GifEncoder.cs +++ /dev/null @@ -1,62 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - using System.IO; - - using ImageProcessorCore.Quantizers; - - /// - /// Image encoder for writing image data to a stream in gif format. - /// - public class GifEncoder : IImageEncoder - { - /// - /// Gets or sets the quality of output for images. - /// - /// For gifs the value ranges from 1 to 256. - public int Quality { get; set; } - - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } = 128; - - /// - /// The quantizer for reducing the color count. - /// - public IQuantizer Quantizer { get; set; } - - /// - public string Extension => "gif"; - - /// - public string MimeType => "image/gif"; - - /// - public bool IsSupportedFileExtension(string extension) - { - Guard.NotNullOrEmpty(extension, nameof(extension)); - - extension = extension.StartsWith(".") ? extension.Substring(1) : extension; - return extension.Equals(this.Extension, StringComparison.OrdinalIgnoreCase); - } - - /// - public void Encode(ImageBase image, Stream stream) - { - GifEncoderCore encoder = new GifEncoderCore - { - Quality = this.Quality, - Quantizer = this.Quantizer, - Threshold = this.Threshold - }; - - encoder.Encode(image, stream); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Gif/GifEncoderCore.cs b/src/ImageProcessorCore - Copy/Formats/Gif/GifEncoderCore.cs deleted file mode 100644 index c4846c44f5..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Gif/GifEncoderCore.cs +++ /dev/null @@ -1,289 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - using System.IO; - using System.Linq; - using System.Threading.Tasks; - - using ImageProcessorCore.IO; - using ImageProcessorCore.Quantizers; - - /// - /// Performs the gif encoding operation. - /// - internal sealed class GifEncoderCore - { - /// - /// The number of bits requires to store the image palette. - /// - private int bitDepth; - - /// - /// Gets or sets the quality of output for images. - /// - /// For gifs the value ranges from 1 to 256. - public int Quality { get; set; } - - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } = 128; - - /// - /// The quantizer for reducing the color count. - /// - public IQuantizer Quantizer { get; set; } - - /// - /// Encodes the image to the specified stream from the . - /// - /// The to encode from. - /// The to encode the image data to. - public void Encode(ImageBase imageBase, Stream stream) - { - Guard.NotNull(imageBase, nameof(imageBase)); - Guard.NotNull(stream, nameof(stream)); - - Image image = (Image)imageBase; - - if (this.Quantizer == null) - { - this.Quantizer = new OctreeQuantizer { Threshold = this.Threshold }; - } - - // Do not use IDisposable pattern here as we want to preserve the stream. - EndianBinaryWriter writer = new EndianBinaryWriter(EndianBitConverter.Little, stream); - - // Ensure that quality can be set but has a fallback. - int quality = this.Quality > 0 ? this.Quality : imageBase.Quality; - this.Quality = quality > 0 ? quality.Clamp(1, 256) : 256; - - // Get the number of bits. - this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(this.Quality); - - // Quantize the image returning a palette. - QuantizedImage quantized = this.Quantizer.Quantize(image, this.Quality); - - // Write the header. - this.WriteHeader(writer); - - // Write the LSD. We'll use local color tables for now. - this.WriteLogicalScreenDescriptor(image, writer, quantized.TransparentIndex); - - // Write the first frame. - this.WriteGraphicalControlExtension(imageBase, writer, quantized.TransparentIndex); - this.WriteImageDescriptor(image, writer); - this.WriteColorTable(quantized, writer); - this.WriteImageData(quantized, writer); - - // Write additional frames. - if (image.Frames.Any()) - { - this.WriteApplicationExtension(writer, image.RepeatCount, image.Frames.Count); - foreach (ImageFrame frame in image.Frames) - { - QuantizedImage quantizedFrame = this.Quantizer.Quantize(frame, this.Quality); - this.WriteGraphicalControlExtension(frame, writer, quantizedFrame.TransparentIndex); - this.WriteImageDescriptor(frame, writer); - this.WriteColorTable(quantizedFrame, writer); - this.WriteImageData(quantizedFrame, writer); - } - } - - // TODO: Write Comments extension etc - writer.Write(GifConstants.EndIntroducer); - } - - /// - /// Writes the file header signature and version to the stream. - /// - /// The writer to write to the stream with. - private void WriteHeader(EndianBinaryWriter writer) - { - writer.Write((GifConstants.FileType + GifConstants.FileVersion).ToCharArray()); - } - - /// - /// Writes the logical screen descriptor to the stream. - /// - /// 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) - { - GifLogicalScreenDescriptor descriptor = new GifLogicalScreenDescriptor - { - Width = (short)image.Width, - Height = (short)image.Height, - GlobalColorTableFlag = false, // Always false for now. - GlobalColorTableSize = this.bitDepth - 1, - BackgroundColorIndex = (byte)(tranparencyIndex > -1 ? tranparencyIndex : 255) - }; - - writer.Write((ushort)descriptor.Width); - writer.Write((ushort)descriptor.Height); - - PackedField field = new PackedField(); - field.SetBit(0, descriptor.GlobalColorTableFlag); // 1 : Global color table flag = 1 || 0 (GCT used/ not used) - field.SetBits(1, 3, descriptor.GlobalColorTableSize); // 2-4 : color resolution - field.SetBit(4, false); // 5 : GCT sort flag = 0 - field.SetBits(5, 3, descriptor.GlobalColorTableSize); // 6-8 : GCT size. 2^(N+1) - - // Reduce the number of writes - byte[] arr = { - field.Byte, - descriptor.BackgroundColorIndex, // Background Color Index - descriptor.PixelAspectRatio // Pixel aspect ratio. Assume 1:1 - }; - - writer.Write(arr); - } - - /// - /// Writes the application exstension to the stream. - /// - /// The writer to write to the stream with. - /// The animated image repeat count. - /// Th number of image frames. - private void WriteApplicationExtension(EndianBinaryWriter writer, ushort repeatCount, int frames) - { - // Application Extension Header - if (repeatCount != 1 && frames > 0) - { - byte[] ext = - { - GifConstants.ExtensionIntroducer, - GifConstants.ApplicationExtensionLabel, - GifConstants.ApplicationBlockSize - }; - - writer.Write(ext); - - writer.Write(GifConstants.ApplicationIdentification.ToCharArray()); // NETSCAPE2.0 - writer.Write((byte)3); // Application block length - writer.Write((byte)1); // Data sub-block index (always 1) - - // 0 means loop indefinitely. Count is set as play n + 1 times. - repeatCount = (ushort)(Math.Max((ushort)0, repeatCount) - 1); - - writer.Write(repeatCount); // Repeat count for images. - - writer.Write(GifConstants.Terminator); // Terminator - } - } - - /// - /// Writes the graphics control extension to the stream. - /// - /// 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) - { - // TODO: Check transparency logic. - bool hasTransparent = transparencyIndex > -1; - DisposalMethod disposalMethod = hasTransparent - ? DisposalMethod.RestoreToBackground - : DisposalMethod.Unspecified; - - GifGraphicsControlExtension extension = new GifGraphicsControlExtension() - { - DisposalMethod = disposalMethod, - TransparencyFlag = hasTransparent, - TransparencyIndex = transparencyIndex, - DelayTime = image.FrameDelay - }; - - // Reduce the number of writes. - byte[] intro = { - GifConstants.ExtensionIntroducer, - GifConstants.GraphicControlLabel, - 4 // Size - }; - - writer.Write(intro); - - PackedField field = new PackedField(); - field.SetBits(3, 3, (int)extension.DisposalMethod); // 1-3 : Reserved, 4-6 : Disposal - - // TODO: Allow this as an option. - field.SetBit(6, false); // 7 : User input - 0 = none - field.SetBit(7, extension.TransparencyFlag); // 8: Has transparent. - - writer.Write(field.Byte); - writer.Write((ushort)extension.DelayTime); - writer.Write((byte)(extension.TransparencyIndex == -1 ? 255 : extension.TransparencyIndex)); - writer.Write(GifConstants.Terminator); - } - - /// - /// Writes the image descriptor to the stream. - /// - /// The to be encoded. - /// The stream to write to. - private void WriteImageDescriptor(ImageBase image, EndianBinaryWriter writer) - { - writer.Write(GifConstants.ImageDescriptorLabel); // 2c - // TODO: Can we capture this? - writer.Write((ushort)0); // Left position - writer.Write((ushort)0); // Top position - writer.Write((ushort)image.Width); - writer.Write((ushort)image.Height); - - PackedField field = new PackedField(); - field.SetBit(0, true); // 1: Local color table flag = 1 (LCT used) - field.SetBit(1, false); // 2: Interlace flag 0 - field.SetBit(2, false); // 3: Sort flag 0 - field.SetBits(5, 3, this.bitDepth - 1); // 4-5: Reserved, 6-8 : LCT size. 2^(N+1) - - writer.Write(field.Byte); - } - - /// - /// Writes the color table to the stream. - /// - /// The to encode. - /// The writer to write to the stream with. - private void WriteColorTable(QuantizedImage image, EndianBinaryWriter writer) - { - // Grab the palette and write it to the stream. - Bgra32[] palette = image.Palette; - int pixelCount = palette.Length; - - // Get max colors for bit depth. - int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3; - byte[] colorTable = new byte[colorTableLength]; - - Parallel.For(0, pixelCount, - i => - { - int offset = i * 3; - Bgra32 color = palette[i]; - - colorTable[offset] = color.R; - colorTable[offset + 1] = color.G; - colorTable[offset + 2] = color.B; - }); - - writer.Write(colorTable, 0, colorTableLength); - } - - /// - /// Writes the image pixel data to the stream. - /// - /// The containing indexed pixels. - /// The stream to write to. - private void WriteImageData(QuantizedImage image, EndianBinaryWriter writer) - { - byte[] indexedPixels = image.Pixels; - - LzwEncoder encoder = new LzwEncoder(indexedPixels, (byte)this.bitDepth); - encoder.Encode(writer.BaseStream); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Gif/GifFormat.cs b/src/ImageProcessorCore - Copy/Formats/Gif/GifFormat.cs deleted file mode 100644 index 572815630f..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Gif/GifFormat.cs +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Encapsulates the means to encode and decode gif images. - /// - public class GifFormat : IImageFormat - { - /// - public IImageDecoder Decoder => new GifDecoder(); - - /// - public IImageEncoder Encoder => new GifEncoder(); - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Gif/LzwDecoder.cs b/src/ImageProcessorCore - Copy/Formats/Gif/LzwDecoder.cs deleted file mode 100644 index 75d590673a..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Gif/LzwDecoder.cs +++ /dev/null @@ -1,231 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - using System.IO; - - /// - /// Decompresses and decodes data using the dynamic LZW algorithms. - /// - internal sealed class LzwDecoder - { - /// - /// The max decoder pixel stack size. - /// - private const int MaxStackSize = 4096; - - /// - /// The null code. - /// - private const int NullCode = -1; - - /// - /// The stream to decode. - /// - private readonly Stream stream; - - /// - /// Initializes a new instance of the class - /// and sets the stream, where the compressed data should be read from. - /// - /// The stream to read from. - /// is null. - public LzwDecoder(Stream stream) - { - Guard.NotNull(stream, nameof(stream)); - - this.stream = stream; - } - - /// - /// Decodes and decompresses all pixel indices from the stream. - /// - /// - /// - /// The width of the pixel index array. - /// The height of the pixel index array. - /// Size of the data. - /// The decoded and uncompressed array. - public byte[] DecodePixels(int width, int height, int dataSize) - { - Guard.MustBeLessThan(dataSize, int.MaxValue, nameof(dataSize)); - - // The resulting index table. - byte[] pixels = new byte[width * height]; - - // Calculate the clear code. The value of the clear code is 2 ^ dataSize - int clearCode = 1 << dataSize; - - int codeSize = dataSize + 1; - - // Calculate the end code - int endCode = clearCode + 1; - - // Calculate the available code. - int availableCode = clearCode + 2; - - // Jillzhangs Code see: http://giflib.codeplex.com/ - // Adapted from John Cristy's ImageMagick. - int code; - int oldCode = NullCode; - int codeMask = (1 << codeSize) - 1; - int bits = 0; - - int[] prefix = new int[MaxStackSize]; - int[] suffix = new int[MaxStackSize]; - int[] pixelStatck = new int[MaxStackSize + 1]; - - int top = 0; - int count = 0; - int bi = 0; - int xyz = 0; - - int data = 0; - int first = 0; - - for (code = 0; code < clearCode; code++) - { - prefix[code] = 0; - suffix[code] = (byte)code; - } - - byte[] buffer = null; - while (xyz < pixels.Length) - { - if (top == 0) - { - if (bits < codeSize) - { - // Load bytes until there are enough bits for a code. - if (count == 0) - { - // Read a new data block. - buffer = this.ReadBlock(); - count = buffer.Length; - if (count == 0) - { - break; - } - - bi = 0; - } - - if (buffer != null) - { - data += buffer[bi] << bits; - } - - bits += 8; - bi++; - count--; - continue; - } - - // Get the next code - code = data & codeMask; - data >>= codeSize; - bits -= codeSize; - - // Interpret the code - if (code > availableCode || code == endCode) - { - break; - } - - if (code == clearCode) - { - // Reset the decoder - codeSize = dataSize + 1; - codeMask = (1 << codeSize) - 1; - availableCode = clearCode + 2; - oldCode = NullCode; - continue; - } - - if (oldCode == NullCode) - { - pixelStatck[top++] = suffix[code]; - oldCode = code; - first = code; - continue; - } - - int inCode = code; - if (code == availableCode) - { - pixelStatck[top++] = (byte)first; - - code = oldCode; - } - - while (code > clearCode) - { - pixelStatck[top++] = suffix[code]; - code = prefix[code]; - } - - first = suffix[code]; - - pixelStatck[top++] = suffix[code]; - - // Fix for Gifs that have "deferred clear code" as per here : - // https://bugzilla.mozilla.org/show_bug.cgi?id=55918 - if (availableCode < MaxStackSize) - { - prefix[availableCode] = oldCode; - suffix[availableCode] = first; - availableCode++; - if (availableCode == codeMask + 1 && availableCode < MaxStackSize) - { - codeSize++; - codeMask = (1 << codeSize) - 1; - } - } - - oldCode = inCode; - } - - // Pop a pixel off the pixel stack. - top--; - - // Clear missing pixels - pixels[xyz++] = (byte)pixelStatck[top]; - } - - return pixels; - } - - /// - /// Reads the next data block from the stream. A data block begins with a byte, - /// which defines the size of the block, followed by the block itself. - /// - /// - /// The . - /// - private byte[] ReadBlock() - { - int blockSize = this.stream.ReadByte(); - return this.ReadBytes(blockSize); - } - - /// - /// Reads the specified number of bytes from the data stream. - /// - /// - /// The number of bytes to read. - /// - /// - /// The . - /// - private byte[] ReadBytes(int length) - { - byte[] buffer = new byte[length]; - this.stream.Read(buffer, 0, length); - return buffer; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Gif/LzwEncoder.cs b/src/ImageProcessorCore - Copy/Formats/Gif/LzwEncoder.cs deleted file mode 100644 index a9681d2c56..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Gif/LzwEncoder.cs +++ /dev/null @@ -1,385 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - using System.IO; - - /// - /// Encodes and compresses the image data using dynamic Lempel-Ziv compression. - /// - /// - /// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. K Weiner 12/00 - /// - /// GIFCOMPR.C - GIF Image compression routines - /// - /// Lempel-Ziv compression based on 'compress'. GIF modifications by - /// David Rowley (mgardi@watdcsu.waterloo.edu) - /// - /// - /// GIF Image compression - modified 'compress' - /// - /// Based on: compress.c - File compression ala IEEE Computer, June 1984. - /// - /// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) - /// Jim McKie (decvax!mcvax!jim) - /// Steve Davies (decvax!vax135!petsd!peora!srd) - /// Ken Turkowski (decvax!decwrl!turtlevax!ken) - /// James A. Woods (decvax!ihnp4!ames!jaw) - /// Joe Orost (decvax!vax135!petsd!joe) - /// - /// - internal sealed class LzwEncoder - { - private const int Eof = -1; - - private const int Bits = 12; - - private const int HashSize = 5003; // 80% occupancy - - private readonly byte[] pixelArray; - - private readonly int initialCodeSize; - - private int curPixel; - - /// - /// Number of bits/code - /// - private int bitCount; - - /// - /// User settable max # bits/code - /// - private int maxbits = Bits; - - private int maxcode; // maximum code, given bitCount - - private int maxmaxcode = 1 << Bits; // should NEVER generate this code - - private readonly int[] hashTable = new int[HashSize]; - - private readonly int[] codeTable = new int[HashSize]; - - /// - /// For dynamic table sizing - /// - private int hsize = HashSize; - - /// - /// First unused entry - /// - private int freeEntry; - - /// - /// Block compression parameters -- after all codes are used up, - /// and compression rate changes, start over. - /// - private bool clearFlag; - - // Algorithm: use open addressing double hashing (no chaining) on the - // prefix code / next character combination. We do a variant of Knuth's - // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime - // secondary probe. Here, the modular division first probe is gives way - // to a faster exclusive-or manipulation. Also do block compression with - // an adaptive reset, whereby the code table is cleared when the compression - // ratio decreases, but after the table fills. The variable-length output - // codes are re-sized at this point, and a special CLEAR code is generated - // for the decompressor. Late addition: construct the table according to - // file size for noticeable speed improvement on small files. Please direct - // questions about this implementation to ames!jaw. - - private int globalInitialBits; - - private int clearCode; - - private int eofCode; - - // output - // - // Output the given code. - // Inputs: - // code: A bitCount-bit integer. If == -1, then EOF. This assumes - // that bitCount =< wordsize - 1. - // Outputs: - // Outputs code to the file. - // Assumptions: - // Chars are 8 bits long. - // Algorithm: - // Maintain a BITS character long buffer (so that 8 codes will - // fit in it exactly). Use the VAX insv instruction to insert each - // code in turn. When the buffer fills up empty it and start over. - - private int currentAccumulator; - - private int currentBits; - - private readonly int[] masks = - { - 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, - 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF - }; - - /// - /// Number of characters so far in this 'packet' - /// - private int accumulatorCount; - - /// - /// Define the storage for the packet accumulator. - /// - private readonly byte[] accumulators = new byte[256]; - - /// - /// Initializes a new instance of the class. - /// - /// The array of indexed pixels. - /// The color depth in bits. - public LzwEncoder(byte[] indexedPixels, int colorDepth) - { - this.pixelArray = indexedPixels; - this.initialCodeSize = Math.Max(2, colorDepth); - } - - /// - /// Encodes and compresses the indexed pixels to the stream. - /// - /// The stream to write to. - public void Encode(Stream stream) - { - // Write "initial code size" byte - stream.WriteByte((byte)this.initialCodeSize); - - this.curPixel = 0; - - // Compress and write the pixel data - this.Compress(this.initialCodeSize + 1, stream); - - // Write block terminator - stream.WriteByte(GifConstants.Terminator); - } - - /// - /// Gets the maximum code value - /// - /// The number of bits - /// See - private static int GetMaxcode(int bitCount) - { - return (1 << bitCount) - 1; - } - - /// - /// Add a character to the end of the current packet, and if it is 254 characters, - /// flush the packet to disk. - /// - /// The character to add. - /// The stream to write to. - private void AddCharacter(byte c, Stream stream) - { - this.accumulators[this.accumulatorCount++] = c; - if (this.accumulatorCount >= 254) - { - this.FlushPacket(stream); - } - } - - /// - /// Table clear for block compress - /// - /// The output stream. - private void ClearBlock(Stream stream) - { - this.ResetCodeTable(this.hsize); - this.freeEntry = this.clearCode + 2; - this.clearFlag = true; - - this.Output(this.clearCode, stream); - } - - /// - /// Reset the code table. - /// - /// The hash size. - private void ResetCodeTable(int size) - { - for (int i = 0; i < size; ++i) - { - this.hashTable[i] = -1; - } - } - - /// - /// Compress the packets to the stream. - /// - /// The inital bits. - /// The stream to write to. - private void Compress(int intialBits, Stream stream) - { - int fcode; - int c; - int ent; - int hsizeReg; - int hshift; - - // Set up the globals: globalInitialBits - initial number of bits - this.globalInitialBits = intialBits; - - // Set up the necessary values - this.clearFlag = false; - this.bitCount = this.globalInitialBits; - this.maxcode = GetMaxcode(this.bitCount); - - this.clearCode = 1 << (intialBits - 1); - this.eofCode = this.clearCode + 1; - this.freeEntry = this.clearCode + 2; - - this.accumulatorCount = 0; // clear packet - - ent = this.NextPixel(); - - hshift = 0; - for (fcode = this.hsize; fcode < 65536; fcode *= 2) { ++hshift; } - hshift = 8 - hshift; // set hash code range bound - - hsizeReg = this.hsize; - - this.ResetCodeTable(hsizeReg); // clear hash table - - this.Output(this.clearCode, stream); - - while ((c = this.NextPixel()) != Eof) - { - fcode = (c << this.maxbits) + ent; - int i = (c << hshift) ^ ent /* = 0 */; - - if (this.hashTable[i] == fcode) - { - ent = this.codeTable[i]; - continue; - } - - // Non-empty slot - if (this.hashTable[i] >= 0) - { - int disp = hsizeReg - i; - if (i == 0) disp = 1; - do - { - if ((i -= disp) < 0) { i += hsizeReg; } - - if (this.hashTable[i] == fcode) - { - ent = this.codeTable[i]; - break; - } - } - while (this.hashTable[i] >= 0); - - if (this.hashTable[i] == fcode) { continue; } - } - - this.Output(ent, stream); - ent = c; - if (this.freeEntry < this.maxmaxcode) - { - this.codeTable[i] = this.freeEntry++; // code -> hashtable - this.hashTable[i] = fcode; - } - else this.ClearBlock(stream); - } - - // Put out the final code. - this.Output(ent, stream); - - this.Output(this.eofCode, stream); - } - - // Flush the packet to disk, and reset the accumulator - private void FlushPacket(Stream outs) - { - if (this.accumulatorCount > 0) - { - outs.WriteByte((byte)this.accumulatorCount); - outs.Write(this.accumulators, 0, this.accumulatorCount); - this.accumulatorCount = 0; - } - } - - /// - /// Return the next pixel from the image - /// - /// - /// The - /// - private int NextPixel() - { - if (this.curPixel == this.pixelArray.Length) - { - return Eof; - } - - if (this.curPixel == this.pixelArray.Length) - return Eof; - - this.curPixel++; - return this.pixelArray[this.curPixel - 1] & 0xff; - } - - /// - /// Output the current code to the stream. - /// - /// The code. - /// The stream to write to. - private void Output(int code, Stream outs) - { - this.currentAccumulator &= this.masks[this.currentBits]; - - if (this.currentBits > 0) this.currentAccumulator |= (code << this.currentBits); - else this.currentAccumulator = code; - - this.currentBits += this.bitCount; - - while (this.currentBits >= 8) - { - this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs); - this.currentAccumulator >>= 8; - this.currentBits -= 8; - } - - // If the next entry is going to be too big for the code size, - // then increase it, if possible. - if (this.freeEntry > this.maxcode || this.clearFlag) - { - if (this.clearFlag) - { - this.maxcode = GetMaxcode(this.bitCount = this.globalInitialBits); - this.clearFlag = false; - } - else - { - ++this.bitCount; - this.maxcode = this.bitCount == this.maxbits - ? this.maxmaxcode - : GetMaxcode(this.bitCount); - } - } - - if (code == this.eofCode) - { - // At EOF, write the rest of the buffer. - while (this.currentBits > 0) - { - this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs); - this.currentAccumulator >>= 8; - this.currentBits -= 8; - } - - this.FlushPacket(outs); - } - } - } -} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/Formats/Gif/PackedField.cs b/src/ImageProcessorCore - Copy/Formats/Gif/PackedField.cs deleted file mode 100644 index 0141d36c6a..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Gif/PackedField.cs +++ /dev/null @@ -1,194 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - - /// - /// Represents a byte of data in a GIF data stream which contains a number - /// of data items. - /// - internal struct PackedField : IEquatable - { - /// - /// The individual bits representing the packed byte. - /// - private static readonly bool[] Bits = new bool[8]; - - /// - /// Gets the byte which represents the data items held in this instance. - /// - public byte Byte - { - get - { - int returnValue = 0; - int bitShift = 7; - foreach (bool bit in Bits) - { - int bitValue; - if (bit) - { - bitValue = 1 << bitShift; - } - else - { - bitValue = 0; - } - returnValue |= bitValue; - bitShift--; - } - return Convert.ToByte(returnValue & 0xFF); - } - } - - /// - /// Returns a new with the bits in the packed fields to - /// the corresponding bits from the supplied byte. - /// - /// The value to pack. - /// The - public static PackedField FromInt(byte value) - { - PackedField packed = new PackedField(); - packed.SetBits(0, 8, value); - return packed; - } - - /// - /// Sets the specified bit within the packed fields to the supplied - /// value. - /// - /// - /// The zero-based index within the packed fields of the bit to set. - /// - /// - /// The value to set the bit to. - /// - public void SetBit(int index, bool valueToSet) - { - if (index < 0 || index > 7) - { - string message - = "Index must be between 0 and 7. Supplied index: " - + index; - throw new ArgumentOutOfRangeException(nameof(index), message); - } - Bits[index] = valueToSet; - } - - /// - /// Sets the specified bits within the packed fields to the supplied - /// value. - /// - /// The zero-based index within the packed fields of the first bit to set. - /// The number of bits to set. - /// The value to set the bits to. - public void SetBits(int startIndex, int length, int valueToSet) - { - if (startIndex < 0 || startIndex > 7) - { - string message = $"Start index must be between 0 and 7. Supplied index: {startIndex}"; - throw new ArgumentOutOfRangeException(nameof(startIndex), message); - } - - if (length < 1 || startIndex + length > 8) - { - string message = "Length must be greater than zero and the sum of length and start index must be less than 8. " - + $"Supplied length: {length}. Supplied start index: {startIndex}"; - throw new ArgumentOutOfRangeException(nameof(length), message); - } - - int bitShift = length - 1; - for (int i = startIndex; i < startIndex + length; i++) - { - int bitValueIfSet = (1 << bitShift); - int bitValue = (valueToSet & bitValueIfSet); - int bitIsSet = (bitValue >> bitShift); - Bits[i] = (bitIsSet == 1); - bitShift--; - } - } - - /// - /// Gets the value of the specified bit within the byte. - /// - /// The zero-based index of the bit to get. - /// - /// The value of the specified bit within the byte. - /// - public bool GetBit(int index) - { - if (index < 0 || index > 7) - { - string message = $"Index must be between 0 and 7. Supplied index: {index}"; - throw new ArgumentOutOfRangeException(nameof(index), message); - } - return Bits[index]; - } - - /// - /// Gets the value of the specified bits within the byte. - /// - /// The zero-based index of the first bit to get. - /// The number of bits to get. - /// - /// The value of the specified bits within the byte. - /// - public int GetBits(int startIndex, int length) - { - if (startIndex < 0 || startIndex > 7) - { - string message = $"Start index must be between 0 and 7. Supplied index: {startIndex}"; - throw new ArgumentOutOfRangeException(nameof(startIndex), message); - } - - if (length < 1 || startIndex + length > 8) - { - string message = "Length must be greater than zero and the sum of length and start index must be less than 8. " - + $"Supplied length: {length}. Supplied start index: {startIndex}"; - - throw new ArgumentOutOfRangeException(nameof(length), message); - } - - int returnValue = 0; - int bitShift = length - 1; - for (int i = startIndex; i < startIndex + length; i++) - { - int bitValue = (Bits[i] ? 1 : 0) << bitShift; - returnValue += bitValue; - bitShift--; - } - return returnValue; - } - - /// - public override bool Equals(object obj) - { - PackedField? field = obj as PackedField?; - - return this.Byte == field?.Byte; - } - - /// - public bool Equals(PackedField other) - { - return this.Byte.Equals(other.Byte); - } - - /// - public override string ToString() - { - return $"PackedField [ Byte={this.Byte} ]"; - } - - /// - public override int GetHashCode() - { - return this.Byte.GetHashCode(); - } - } -} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/Formats/Gif/README.md b/src/ImageProcessorCore - Copy/Formats/Gif/README.md deleted file mode 100644 index d47a4c6836..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Gif/README.md +++ /dev/null @@ -1,4 +0,0 @@ -Encoder/Decoder adapted and extended from: - -https://github.com/yufeih/Nine.Imaging/ -https://imagetools.codeplex.com/ diff --git a/src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifGraphicsControlExtension.cs b/src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifGraphicsControlExtension.cs deleted file mode 100644 index 071dc62c84..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifGraphicsControlExtension.cs +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// The Graphic Control Extension contains parameters used when - /// processing a graphic rendering block. - /// - internal sealed class GifGraphicsControlExtension - { - /// - /// Gets or sets the disposal method which indicates the way in which the - /// graphic is to be treated after being displayed. - /// - public DisposalMethod DisposalMethod { get; set; } - - /// - /// Gets or sets a value indicating whether transparency flag is to be set. - /// This indicates whether a transparency index is given in the Transparent Index field. - /// (This field is the least significant bit of the byte.) - /// - public bool TransparencyFlag { get; set; } - - /// - /// Gets or sets the transparency index. - /// The Transparency Index is such that when encountered, the corresponding pixel - /// of the display device is not modified and processing goes on to the next pixel. - /// - public int TransparencyIndex { get; set; } - - /// - /// Gets or sets the delay time. - /// 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 DelayTime { get; set; } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifImageDescriptor.cs b/src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifImageDescriptor.cs deleted file mode 100644 index 62737de660..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifImageDescriptor.cs +++ /dev/null @@ -1,59 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Each image in the Data Stream is composed of an Image Descriptor, - /// an optional Local Color Table, and the image data. - /// Each image must fit within the boundaries of the - /// Logical Screen, as defined in the Logical Screen Descriptor. - /// - internal sealed class GifImageDescriptor - { - /// - /// Gets or sets the column number, in pixels, of the left edge of the image, - /// with respect to the left edge of the Logical Screen. - /// Leftmost column of the Logical Screen is 0. - /// - public short Left { get; set; } - - /// - /// Gets or sets the row number, in pixels, of the top edge of the image with - /// respect to the top edge of the Logical Screen. - /// Top row of the Logical Screen is 0. - /// - public short Top { get; set; } - - /// - /// Gets or sets the width of the image in pixels. - /// - public short Width { get; set; } - - /// - /// Gets or sets the height of the image in pixels. - /// - public short Height { get; set; } - - /// - /// Gets or sets a value indicating whether the presence of a Local Color Table immediately - /// follows this Image Descriptor. - /// - public bool LocalColorTableFlag { get; set; } - - /// - /// Gets or sets the local color table size. - /// If the Local Color Table Flag is set to 1, the value in this field - /// is used to calculate the number of bytes contained in the Local Color Table. - /// - public int LocalColorTableSize { get; set; } - - /// - /// Gets or sets a value indicating whether the image is to be interlaced. - /// An image is interlaced in a four-pass interlace pattern. - /// - public bool InterlaceFlag { get; set; } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs b/src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs deleted file mode 100644 index 8c0400f24d..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs +++ /dev/null @@ -1,55 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// The Logical Screen Descriptor contains the parameters - /// necessary to define the area of the display device - /// within which the images will be rendered - /// - internal sealed class GifLogicalScreenDescriptor - { - /// - /// Gets or sets the width, in pixels, of the Logical Screen where the images will - /// be rendered in the displaying device. - /// - public short Width { get; set; } - - /// - /// Gets or sets the height, in pixels, of the Logical Screen where the images will be - /// rendered in the displaying device. - /// - public short Height { get; set; } - - /// - /// Gets or sets the index at the Global Color Table for the Background Color. - /// The Background Color is the color used for those - /// pixels on the screen that are not covered by an image. - /// - public byte BackgroundColorIndex { get; set; } - - /// - /// Gets or sets the pixel aspect ratio. Default to 0. - /// - public byte PixelAspectRatio { get; set; } - - /// - /// Gets or sets a value indicating whether a flag denoting the presence of a Global Color Table - /// should be set. - /// If the flag is set, the Global Color Table will immediately - /// follow the Logical Screen Descriptor. - /// - public bool GlobalColorTableFlag { get; set; } - - /// - /// Gets or sets the global color table size. - /// If the Global Color Table Flag is set to 1, - /// the value in this field is used to calculate the number of - /// bytes contained in the Global Color Table. - /// - public int GlobalColorTableSize { get; set; } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/IImageDecoder.cs b/src/ImageProcessorCore - Copy/Formats/IImageDecoder.cs deleted file mode 100644 index 0f3a8504c9..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/IImageDecoder.cs +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System.IO; - - /// - /// Encapsulates properties and methods required for decoding an image from a stream. - /// - public interface IImageDecoder - { - /// - /// Gets the size of the header for this image type. - /// - /// The size of the header. - int HeaderSize { get; } - - /// - /// Returns a value indicating whether the supports the specified - /// file header. - /// - /// The containing the file extension. - /// - /// True if the decoder supports the file extension; otherwise, false. - /// - bool IsSupportedFileExtension(string extension); - - /// - /// Returns a value indicating whether the supports the specified - /// file header. - /// - /// The containing the file header. - /// - /// True if the decoder supports the file header; otherwise, false. - /// - bool IsSupportedFileFormat(byte[] header); - - /// - /// Decodes the image from the specified stream to the . - /// - /// The type of pixels contained within the image. - /// The to decode to. - /// The containing image data. - void Decode(Image image, Stream stream) where TPackedVector : IPackedVector; - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/IImageEncoder.cs b/src/ImageProcessorCore - Copy/Formats/IImageEncoder.cs deleted file mode 100644 index df7234aad0..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/IImageEncoder.cs +++ /dev/null @@ -1,53 +0,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 -{ - using System.IO; - - /// - /// Encapsulates properties and methods required for encoding an image to a stream. - /// - public interface IImageEncoder - { - /// - /// Gets or sets the quality of output for images. - /// - int Quality { get; set; } - - /// - /// Gets the standard identifier used on the Internet to indicate the type of data that a file contains. - /// - string MimeType { get; } - - /// - /// Gets the default file extension for this encoder. - /// - string Extension { get; } - - /// - /// Returns a value indicating whether the supports the specified - /// file header. - /// - /// The containing the file extension. - /// - /// True if the decoder supports the file extension; otherwise, false. - /// - bool IsSupportedFileExtension(string extension); - - /// - /// Encodes the image to the specified stream from the . - /// - /// The type of pixels contained within the image. - /// The to encode from. - /// The to encode the image data to. - void Encode(ImageBase image, Stream stream) where TPackedVector : IPackedVector; - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/IImageFormat.cs b/src/ImageProcessorCore - Copy/Formats/IImageFormat.cs deleted file mode 100644 index 62b4b78916..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/IImageFormat.cs +++ /dev/null @@ -1,23 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Encapsulates a supported image format, providing means to encode and decode an image. - /// - public interface IImageFormat - { - /// - /// Gets the image encoder for encoding an image from a stream. - /// - IImageEncoder Encoder { get; } - - /// - /// Gets the image decoder for decoding an image from a stream. - /// - IImageDecoder Decoder { get; } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Jpg/Block.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/Block.cs deleted file mode 100644 index 35aa10f181..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Jpg/Block.cs +++ /dev/null @@ -1,44 +0,0 @@ -// -// 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 - { - /// - /// 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() - { - this.data = new int[BlockSize]; - } - - /// - /// Gets the pixel data at the given block index. - /// - /// The index of the data to return. - /// - /// The . - /// - public int this[int index] - { - get { return this.data[index]; } - set { this.data[index] = value; } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Jpg/FDCT.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/FDCT.cs deleted file mode 100644 index e51ea64151..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Jpg/FDCT.cs +++ /dev/null @@ -1,161 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -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; - private const int fix_0_765366865 = 6270; - private const int fix_0_899976223 = 7373; - private const int fix_1_175875602 = 9633; - private const int fix_1_501321110 = 12299; - private const int fix_1_847759065 = 15137; - private const int fix_1_961570560 = 16069; - private const int fix_2_053119869 = 16819; - private const int fix_2_562915447 = 20995; - private const int fix_3_072711026 = 25172; - - /// - /// 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 = 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; - int tmp2 = x2 + x5; - int tmp3 = x3 + x4; - - int tmp10 = tmp0 + tmp3; - int tmp12 = tmp0 - tmp3; - int tmp11 = tmp1 + tmp2; - int tmp13 = tmp1 - tmp2; - - tmp0 = x0 - x7; - tmp1 = x1 - x6; - tmp2 = x2 - x5; - tmp3 = x3 - x4; - - block[y8] = (tmp10 + tmp11 - (8 * CenterJSample)) << Pass1Bits; - block[y8 + 4] = (tmp10 - tmp11) << Pass1Bits; - int z1 = (tmp12 + tmp13) * fix_0_541196100; - 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 << (Bits - Pass1Bits - 1); - tmp0 = tmp0 * fix_1_501321110; - tmp1 = tmp1 * fix_3_072711026; - tmp2 = tmp2 * fix_2_053119869; - tmp3 = tmp3 * fix_0_298631336; - tmp10 = tmp10 * -fix_0_899976223; - tmp11 = tmp11 * -fix_2_562915447; - tmp12 = tmp12 * -fix_0_390180644; - tmp13 = tmp13 * -fix_1_961570560; - - tmp12 += z1; - tmp13 += z1; - 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 = 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 tmp12 = tmp0 - tmp3; - int tmp11 = tmp1 + tmp2; - int tmp13 = tmp1 - tmp2; - - 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]; - - block[x] = (tmp10 + tmp11) >> Pass1Bits; - block[32 + x] = (tmp10 - tmp11) >> Pass1Bits; - - int z1 = (tmp12 + tmp13) * fix_0_541196100; - 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 << (Bits + Pass1Bits - 1); - tmp0 = tmp0 * fix_1_501321110; - tmp1 = tmp1 * fix_3_072711026; - tmp2 = tmp2 * fix_2_053119869; - tmp3 = tmp3 * fix_0_298631336; - tmp10 = tmp10 * -fix_0_899976223; - tmp11 = tmp11 * -fix_2_562915447; - tmp12 = tmp12 * -fix_0_390180644; - tmp13 = tmp13 * -fix_1_961570560; - - tmp12 += z1; - tmp13 += z1; - 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 - Copy/Formats/Jpg/IDCT.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/IDCT.cs deleted file mode 100644 index 7542f4d383..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Jpg/IDCT.cs +++ /dev/null @@ -1,163 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - internal class IDCT - { - private const int w1 = 2841; // 2048*sqrt(2)*cos(1*pi/16) - private const int w2 = 2676; // 2048*sqrt(2)*cos(2*pi/16) - private const int w3 = 2408; // 2048*sqrt(2)*cos(3*pi/16) - private const int w5 = 1609; // 2048*sqrt(2)*cos(5*pi/16) - private const int w6 = 1108; // 2048*sqrt(2)*cos(6*pi/16) - private const int w7 = 565; // 2048*sqrt(2)*cos(7*pi/16) - - private const int w1pw7 = w1 + w7; - private const int w1mw7 = w1 - w7; - private const int w2pw6 = w2 + w6; - private const int w2mw6 = w2 - w6; - private const int w3pw5 = w3 + w5; - private const int w3mw5 = w3 - w5; - - private const int r2 = 181; // 256/sqrt(2) - - // idct performs a 2-D Inverse Discrete Cosine Transformation. - // - // The input coefficients should already have been multiplied by the - // appropriate quantization table. We use fixed-point computation, with the - // number of bits for the fractional component varying over the intermediate - // stages. - // - // For more on the actual algorithm, see Z. Wang, "Fast algorithms for the - // discrete W transform and for the discrete Fourier transform", IEEE Trans. on - // ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. - public static void Transform(Block src) - { - // Horizontal 1-D IDCT. - for (int y = 0; y < 8; y++) - { - int y8 = y * 8; - - // If all the AC components are zero, then the IDCT is trivial. - if (src[y8 + 1] == 0 && src[y8 + 2] == 0 && src[y8 + 3] == 0 && - src[y8 + 4] == 0 && src[y8 + 5] == 0 && src[y8 + 6] == 0 && src[y8 + 7] == 0) - { - int dc = src[y8 + 0] << 3; - src[y8 + 0] = dc; - src[y8 + 1] = dc; - src[y8 + 2] = dc; - src[y8 + 3] = dc; - src[y8 + 4] = dc; - src[y8 + 5] = dc; - src[y8 + 6] = dc; - src[y8 + 7] = dc; - continue; - } - - // Prescale. - int x0 = (src[y8 + 0] << 11) + 128; - int x1 = src[y8 + 4] << 11; - int x2 = src[y8 + 6]; - int x3 = src[y8 + 2]; - int x4 = src[y8 + 1]; - int x5 = src[y8 + 7]; - int x6 = src[y8 + 5]; - int x7 = src[y8 + 3]; - - // Stage 1. - int x8 = w7 * (x4 + x5); - x4 = x8 + w1mw7 * x4; - x5 = x8 - w1pw7 * x5; - x8 = w3 * (x6 + x7); - x6 = x8 - w3mw5 * x6; - x7 = x8 - w3pw5 * x7; - - // Stage 2. - x8 = x0 + x1; - x0 -= x1; - x1 = w6 * (x3 + x2); - x2 = x1 - w2pw6 * x2; - x3 = x1 + w2mw6 * x3; - x1 = x4 + x6; - x4 -= x6; - x6 = x5 + x7; - x5 -= x7; - - // Stage 3. - x7 = x8 + x3; - x8 -= x3; - x3 = x0 + x2; - x0 -= x2; - x2 = (r2 * (x4 + x5) + 128) >> 8; - x4 = (r2 * (x4 - x5) + 128) >> 8; - - // Stage 4. - src[y8 + 0] = (x7 + x1) >> 8; - src[y8 + 1] = (x3 + x2) >> 8; - src[y8 + 2] = (x0 + x4) >> 8; - src[y8 + 3] = (x8 + x6) >> 8; - src[y8 + 4] = (x8 - x6) >> 8; - src[y8 + 5] = (x0 - x4) >> 8; - src[y8 + 6] = (x3 - x2) >> 8; - src[y8 + 7] = (x7 - x1) >> 8; - } - - // Vertical 1-D IDCT. - for (int x = 0; x < 8; x++) - { - // Similar to the horizontal 1-D IDCT case, if all the AC components are zero, then the IDCT is trivial. - // However, after performing the horizontal 1-D IDCT, there are typically non-zero AC components, so - // we do not bother to check for the all-zero case. - - // Prescale. - int y0 = (src[x] << 8) + 8192; - int y1 = src[32 + x] << 8; - int y2 = src[48 + x]; - int y3 = src[16 + x]; - int y4 = src[8 + x]; - int y5 = src[56 + x]; - int y6 = src[40 + x]; - int y7 = src[24 + x]; - - // Stage 1. - int y8 = w7 * (y4 + y5) + 4; - y4 = (y8 + w1mw7 * y4) >> 3; - y5 = (y8 - w1pw7 * y5) >> 3; - y8 = w3 * (y6 + y7) + 4; - y6 = (y8 - w3mw5 * y6) >> 3; - y7 = (y8 - w3pw5 * y7) >> 3; - - // Stage 2. - y8 = y0 + y1; - y0 -= y1; - y1 = w6 * (y3 + y2) + 4; - y2 = (y1 - w2pw6 * y2) >> 3; - y3 = (y1 + w2mw6 * y3) >> 3; - y1 = y4 + y6; - y4 -= y6; - y6 = y5 + y7; - y5 -= y7; - - // Stage 3. - y7 = y8 + y3; - y8 -= y3; - y3 = y0 + y2; - y0 -= y2; - y2 = (r2 * (y4 + y5) + 128) >> 8; - y4 = (r2 * (y4 - y5) + 128) >> 8; - - // Stage 4. - src[x] = (y7 + y1) >> 14; - src[8 + x] = (y3 + y2) >> 14; - src[16 + x] = (y0 + y4) >> 14; - src[24 + x] = (y8 + y6) >> 14; - src[32 + x] = (y8 - y6) >> 14; - src[40 + x] = (y0 - y4) >> 14; - src[48 + x] = (y3 - y2) >> 14; - src[56 + x] = (y7 - y1) >> 14; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Jpg/JpegDecoder.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/JpegDecoder.cs deleted file mode 100644 index 89aa4e0014..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Jpg/JpegDecoder.cs +++ /dev/null @@ -1,149 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - using System.IO; - - /// - /// Image decoder for generating an image out of a jpg stream. - /// - public class JpegDecoder : IImageDecoder - { - /// - /// Gets the size of the header for this image type. - /// - /// The size of the header. - public int HeaderSize => 11; - - /// - /// Indicates if the image decoder supports the specified - /// file extension. - /// - /// The file extension. - /// - /// true, if the decoder supports the specified - /// extensions; otherwise false. - /// - /// - /// is null (Nothing in Visual Basic). - /// is a string - /// of length zero or contains only blanks. - public bool IsSupportedFileExtension(string extension) - { - Guard.NotNullOrEmpty(extension, "extension"); - - if (extension.StartsWith(".")) - { - extension = extension.Substring(1); - } - - return extension.Equals("JPG", StringComparison.OrdinalIgnoreCase) || - extension.Equals("JPEG", StringComparison.OrdinalIgnoreCase) || - extension.Equals("JFIF", StringComparison.OrdinalIgnoreCase); - } - - /// - /// Indicates if the image decoder supports the specified - /// file header. - /// - /// The file header. - /// - /// true, if the decoder supports the specified - /// file header; otherwise false. - /// - /// - /// is null (Nothing in Visual Basic). - public bool IsSupportedFileFormat(byte[] header) - { - Guard.NotNull(header, "header"); - - bool isSupported = false; - - if (header.Length >= 11) - { - bool isJfif = IsJfif(header); - bool isExif = IsExif(header); - bool isJpeg = IsJpeg(header); - - isSupported = isJfif || isExif || isJpeg; - } - - 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) - { - Guard.NotNull(image, "image"); - Guard.NotNull(stream, "stream"); - - JpegDecoderCore decoder = new JpegDecoderCore(); - decoder.Decode(stream, image, false); - } - - /// - /// Returns a value indicating whether the given bytes identify Jfif data. - /// - /// The bytes representing the file header. - /// The - private static bool IsJfif(byte[] header) - { - bool isJfif = - header[6] == 0x4A && // J - header[7] == 0x46 && // F - header[8] == 0x49 && // I - header[9] == 0x46 && // F - header[10] == 0x00; - - return isJfif; - } - - /// - /// Returns a value indicating whether the given bytes identify EXIF data. - /// - /// The bytes representing the file header. - /// The - private static bool IsExif(byte[] header) - { - bool isExif = - header[6] == 0x45 && // E - header[7] == 0x78 && // X - header[8] == 0x69 && // I - header[9] == 0x66 && // F - header[10] == 0x00; - - return isExif; - } - - /// - /// Returns a value indicating whether the given bytes identify Jpeg data. - /// This is a last chance resort for jpegs that contain ICC information. - /// - /// The bytes representing the file header. - /// The - private static bool IsJpeg(byte[] header) - { - bool isJpg = - header[0] == 0xFF && // 255 - header[1] == 0xD8; // 216 - - return isJpg; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id b/src/ImageProcessorCore - Copy/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id deleted file mode 100644 index a5c139745c..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -7a5076971068e0f389da2fb1e8b25216f4049718 \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/Formats/Jpg/JpegEncoder.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/JpegEncoder.cs deleted file mode 100644 index 32c4d5f288..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Jpg/JpegEncoder.cs +++ /dev/null @@ -1,97 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - using System.IO; - - /// - /// Encoder for writing the data image to a stream in jpeg format. - /// - public class JpegEncoder : IImageEncoder - { - /// - /// The quality used to encode the image. - /// - private int quality = 75; - - /// - /// The subsamples scheme used to encode the image. - /// - private JpegSubsample subsample = JpegSubsample.Ratio420; - - /// - /// Whether subsampling has been specifically set. - /// - private bool subsampleSet; - - /// - /// Gets or sets the quality, that will be used to encode the image. Quality - /// index must be between 0 and 100 (compression from max to min). - /// - /// - /// If the quality is less than or equal to 80, the subsampling ratio will switch to - /// - /// The quality of the jpg image from 0 to 100. - public int Quality - { - get { return this.quality; } - set { this.quality = value.Clamp(1, 100); } - } - - /// - /// Gets or sets the subsample ration, that will be used to encode the image. - /// - /// The subsample ratio of the jpg image. - public JpegSubsample Subsample - { - get { return this.subsample; } - set - { - this.subsample = value; - this.subsampleSet = true; - } - } - - /// - public string MimeType => "image/jpeg"; - - /// - public string Extension => "jpg"; - - /// - public bool IsSupportedFileExtension(string extension) - { - Guard.NotNullOrEmpty(extension, "extension"); - - if (extension.StartsWith(".")) - { - extension = extension.Substring(1); - } - - return extension.Equals(this.Extension, StringComparison.OrdinalIgnoreCase) || - extension.Equals("jpeg", StringComparison.OrdinalIgnoreCase) || - extension.Equals("jfif", StringComparison.OrdinalIgnoreCase); - } - - /// - public void Encode(ImageBase image, Stream stream) - { - 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); - } - else - { - encode.Encode(stream, image, this.Quality, this.Quality >= 80 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Jpg/JpegEncoderCore.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/JpegEncoderCore.cs deleted file mode 100644 index 384f18b36a..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Jpg/JpegEncoderCore.cs +++ /dev/null @@ -1,783 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// -namespace ImageProcessorCore.Formats -{ - using System; - using System.IO; - - internal class JpegEncoderCore - { - /// - /// 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; - - /// - /// 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, - }; - - /// - /// 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 = { - { - // 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, - } - }; - - /// - /// 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 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 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, - }) - }; - - /// - /// 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 readonly uint[] Values; - - public HuffmanLut(HuffmanSpec s) - { - int maxValue = 0; - - foreach (var v in s.Values) - { - if (v > maxValue) maxValue = v; - } - - this.Values = new uint[maxValue + 1]; - - int code = 0; - int k = 0; - - for (int i = 0; i < s.Count.Length; i++) - { - int nBits = (i + 1) << 24; - for (int j = 0; j < s.Count[i]; j++) - { - this.Values[s.Values[k]] = (uint)(nBits | code); - code++; - k++; - } - - code <<= 1; - } - } - } - - // 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; - - /// - /// 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; - - /// - /// Writes the given byte to the stream. - /// - /// - private void WriteByte(byte b) - { - var data = new byte[1]; - data[0] = b; - this.outputStream.Write(data, 0, 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); - bits |= this.bits; - while (nBits >= 8) - { - byte b = (byte)(bits >> 24); - this.WriteByte(b); - if (b == 0xff) this.WriteByte(0x00); - bits <<= 8; - nBits -= 8; - } - - this.bits = bits; - this.nBits = nBits; - } - - /// - /// 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 = this.theHuffmanLUT[(int)index].Values[value]; - this.Emit(x & ((1 << 24) - 1), x >> 24); - } - - /// - /// 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 = value; - int b = value; - if (a < 0) - { - a = -value; - b = value - 1; - } - - uint bt; - if (a < 0x100) - { - bt = this.bitCount[a]; - } - else - { - bt = 8 + (uint)this.bitCount[a >> 8]; - } - - this.EmitHuff(index, (int)((uint)(runLength << 4) | bt)); - if (bt > 0) - { - this.Emit((uint)b & (uint)((1 << ((int)bt)) - 1), bt); - } - } - - - /// - /// 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(block); - - // Emit the DC delta. - 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)index + 1); - int runLength = 0; - - for (int zig = 1; zig < Block.BlockSize; zig++) - { - int ac = Round(block[Unzig[zig]], 8 * this.quant[(int)index][zig]); - - if (ac == 0) - { - runLength++; - } - else - { - while (runLength > 15) - { - this.EmitHuff(h, 0xf0); - runLength -= 16; - } - - this.EmitHuffRLE(h, runLength, ac); - runLength = 0; - } - } - - 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) - { - int xmax = pixels.Width - 1; - int ymax = pixels.Height - 1; - for (int j = 0; j < 8; j++) - { - for (int i = 0; i < 8; i++) - { - YCbCr color = pixels[Math.Min(x + i, xmax), Math.Min(y + j, ymax)]; - int index = (8 * j) + i; - yBlock[index] = (int)color.Y; - cbBlock[index] = (int)color.Cb; - crBlock[index] = (int)color.Cr; - } - } - } - - /// - /// 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++) - { - int dstOff = ((i & 2) << 4) | ((i & 1) << 2); - for (int y = 0; y < 4; y++) - { - for (int x = 0; x < 4; x++) - { - int j = 16 * y + 2 * x; - 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; - } - } - } - } - - // 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) - }; - - // 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(Stream stream, ImageBase image, int quality, JpegSubsample sample) - { - 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. - // TODO: JFIF header etc. - this.buffer[0] = 0xff; - this.buffer[1] = 0xd8; - stream.Write(this.buffer, 0, 2); - - // 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 (PixelAccessor 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 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 = grey 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; - } - - 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(PixelAccessor pixels) - { - // TODO: We should allow grayscale writing. - this.outputStream.Write(this.SOSHeaderYCbCr, 0, this.SOSHeaderYCbCr.Length); - - switch (this.subsample) - { - case JpegSubsample.Ratio444: - this.Encode444(pixels); - break; - case JpegSubsample.Ratio420: - this.Encode420(pixels); - break; - } - - // Pad the last byte with 1's. - this.Emit(0x7f, 7); - } - - - - /// - /// Encodes the image with no subsampling. - /// - /// The pixel accessor providing acces to the image pixels. - private void Encode444(PixelAccessor pixels) - { - Block b = new Block(); - Block cb = new Block(); - Block cr = new Block(); - int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - - for (int y = 0; y < pixels.Height; y += 8) - { - for (int x = 0; x < pixels.Width; x += 8) - { - 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); - } - } - } - - /// - /// 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(PixelAccessor pixels) - { - Block b = new Block(); - Block[] cb = new Block[4]; - Block[] cr = new Block[4]; - int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - - for (int i = 0; i < 4; i++) cb[i] = new Block(); - for (int i = 0; i < 4; i++) cr[i] = new Block(); - - for (int y = 0; y < pixels.Height; y += 16) - { - for (int x = 0; x < pixels.Width; x += 16) - { - for (int i = 0; i < 4; i++) - { - int xOff = (i & 1) * 8; - int yOff = (i & 2) * 4; - - this.ToYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]); - prevDCY = this.WriteBlock(b, QuantIndex.Luminance, prevDCY); - } - - this.Scale16X16_8X8(b, cb); - prevDCCb = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCb); - this.Scale16X16_8X8(b, cr); - prevDCCr = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCr); - } - } - } - - /// - /// Writes the header for a marker with the given length. - /// - /// The marker to write. - /// The marker length. - private void WriteMarkerHeader(byte marker, int length) - { - // 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); - } - - /// - /// Enumerates the Huffman tables - /// - private enum HuffIndex - { - LuminanceDC = 0, - - LuminanceAC = 1, - - ChrominanceDC = 2, - - ChrominanceAC = 3, - } - - /// - /// Enumerates the quantization tables - /// - private enum QuantIndex - { - /// - /// Luminance - /// - Luminance = 0, - - /// - /// Chrominance - /// - Chrominance = 1, - } - - /// - /// 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) - { - this.Count = count; - this.Values = values; - } - - /// - /// Gets count[i] - The number of codes of length i bits. - /// - public readonly byte[] Count; - - /// - /// Gets value[i] - The decoded value of the i'th codeword. - /// - public readonly byte[] Values; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Jpg/JpegFormat.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/JpegFormat.cs deleted file mode 100644 index ec9ceb6dc0..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Jpg/JpegFormat.cs +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Encapsulates the means to encode and decode jpeg images. - /// - public class JpegFormat : IImageFormat - { - /// - public IImageDecoder Decoder => new JpegDecoder(); - - /// - public IImageEncoder Encoder => new JpegEncoder(); - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Jpg/JpegSubsample.cs b/src/ImageProcessorCore - Copy/Formats/Jpg/JpegSubsample.cs deleted file mode 100644 index 6098f6377b..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Jpg/JpegSubsample.cs +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Enumerates the chroma subsampling method applied to the image. - /// - public enum JpegSubsample - { - /// - /// High Quality - Each of the three Y'CbCr components have the same sample rate, - /// thus there is no chroma subsampling. - /// - Ratio444, - - /// - /// Medium Quality - The horizontal sampling is halved and the Cb and Cr channels are only - /// sampled on each alternate line. - /// - Ratio420 - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Jpg/README.md b/src/ImageProcessorCore - Copy/Formats/Jpg/README.md deleted file mode 100644 index 54bc14847c..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Jpg/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Encoder/Decoder adapted and extended from: - -https://golang.org/src/image/jpeg/ \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/Formats/Png/GrayscaleReader.cs b/src/ImageProcessorCore - Copy/Formats/Png/GrayscaleReader.cs deleted file mode 100644 index 87f074e0bd..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Png/GrayscaleReader.cs +++ /dev/null @@ -1,80 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Color reader for reading grayscale colors from a png file. - /// - internal sealed class GrayscaleReader : IColorReader - { - /// - /// Whether t also read the alpha channel. - /// - private readonly bool useAlpha; - - /// - /// The current row. - /// - private int row; - - /// - /// Initializes a new instance of the class. - /// - /// - /// If set to true the color reader will also read the - /// alpha channel from the scanline. - /// - public GrayscaleReader(bool useAlpha) - { - this.useAlpha = useAlpha; - } - - /// - public void ReadScanline(byte[] scanline, float[] pixels, PngHeader header) - { - 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; - - // 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; - - 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; - } - } - else - { - for (int x = 0; x < header.Width; x++) - { - offset = ((this.row * header.Width) + x) * 4; - - pixels[offset] = newScanline[x] / 255f; - pixels[offset + 1] = newScanline[x] / 255f; - pixels[offset + 2] = newScanline[x] / 255f; - pixels[offset + 3] = 1; - } - } - - this.row++; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Png/IColorReader.cs b/src/ImageProcessorCore - Copy/Formats/Png/IColorReader.cs deleted file mode 100644 index 68183fac72..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Png/IColorReader.cs +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Encapsulates methods for color readers, which are responsible for reading - /// different color formats from a png file. - /// - public interface IColorReader - { - /// - /// Reads the specified scanline. - /// - /// The scanline. - /// The pixels, where the colors should be stored in RGBA format. - /// - /// 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); - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Png/PaletteIndexReader.cs b/src/ImageProcessorCore - Copy/Formats/Png/PaletteIndexReader.cs deleted file mode 100644 index 40de59dbb8..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Png/PaletteIndexReader.cs +++ /dev/null @@ -1,99 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// A color reader for reading palette indices from the png file. - /// - internal sealed class PaletteIndexReader : IColorReader - { - /// - /// The palette. - /// - private readonly byte[] palette; - - /// - /// The alpha palette. - /// - private readonly byte[] paletteAlpha; - - /// - /// The current row. - /// - private int row; - - /// - /// Initializes a new instance of the class. - /// - /// The palette as simple byte array. It will contains 3 values for each - /// color, which represents the red-, the green- and the blue channel. - /// The alpha palette. Can be null, if the image does not have an - /// alpha channel and can contain less entries than the number of colors in the palette. - public PaletteIndexReader(byte[] palette, byte[] paletteAlpha) - { - this.palette = palette; - this.paletteAlpha = paletteAlpha; - } - - /// - public void ReadScanline(byte[] scanline, float[] pixels, PngHeader header) - { - byte[] newScanline = scanline.ToArrayByBitsLength(header.BitDepth); - int offset, index; - - if (this.paletteAlpha != null && this.paletteAlpha.Length > 0) - { - // If the alpha palette is not null and does one or - // more entries, this means, that the image contains and alpha - // channel and we should try to read it. - for (int i = 0; i < header.Width; i++) - { - index = newScanline[i]; - - offset = ((this.row * header.Width) + i) * 4; - 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; - } - } - else - { - for (int i = 0; i < header.Width; i++) - { - index = newScanline[i]; - - offset = ((this.row * header.Width) + i) * 4; - 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; - } - } - - this.row++; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Png/PngChunk.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngChunk.cs deleted file mode 100644 index 31ea703a65..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Png/PngChunk.cs +++ /dev/null @@ -1,39 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Stores header information about a chunk. - /// - internal sealed class PngChunk - { - /// - /// Gets or sets the length. - /// An unsigned integer giving the number of bytes in the chunk's - /// data field. The length counts only the data field, not itself, - /// the chunk type code, or the CRC. Zero is a valid length - /// - public int Length { get; set; } - - /// - /// Gets or sets the chunk type as string with 4 chars. - /// - public string Type { get; set; } - - /// - /// Gets or sets the data bytes appropriate to the chunk type, if any. - /// This field can be of zero length. - /// - public byte[] Data { get; set; } - - /// - /// Gets or sets a CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk, - /// including the chunk type code and chunk data fields, but not including the length field. - /// The CRC is always present, even for chunks containing no data - /// - public uint Crc { get; set; } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Png/PngChunkTypes.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngChunkTypes.cs deleted file mode 100644 index 5c35b3d4d2..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Png/PngChunkTypes.cs +++ /dev/null @@ -1,62 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Contains a list of possible chunk type identifiers. - /// - internal static class PngChunkTypes - { - /// - /// The first chunk in a png file. Can only exists once. Contains - /// common information like the width and the height of the image or - /// the used compression method. - /// - public const string Header = "IHDR"; - - /// - /// The PLTE chunk contains from 1 to 256 palette entries, each a three byte - /// series in the RGB format. - /// - public const string Palette = "PLTE"; - - /// - /// The IDAT chunk contains the actual image data. The image can contains more - /// than one chunk of this type. All chunks together are the whole image. - /// - public const string Data = "IDAT"; - - /// - /// This chunk must appear last. It marks the end of the PNG data stream. - /// The chunk's data field is empty. - /// - public const string End = "IEND"; - - /// - /// This chunk specifies that the image uses simple transparency: - /// either alpha values associated with palette entries (for indexed-color images) - /// or a single transparent color (for grayscale and true color images). - /// - public const string PaletteAlpha = "tRNS"; - - /// - /// Textual information that the encoder wishes to record with the image can be stored in - /// tEXt chunks. Each tEXt chunk contains a keyword and a text string. - /// - public const string Text = "tEXt"; - - /// - /// This chunk specifies the relationship between the image samples and the desired - /// display output intensity. - /// - public const string Gamma = "gAMA"; - - /// - /// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. - /// - public const string Physical = "pHYs"; - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Png/PngColorTypeInformation.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngColorTypeInformation.cs deleted file mode 100644 index 9909cf47cc..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Png/PngColorTypeInformation.cs +++ /dev/null @@ -1,61 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - - /// - /// Contains information that are required when loading a png with a specific color type. - /// - internal sealed class PngColorTypeInformation - { - /// - /// Initializes a new instance of the class with - /// the scanline factory, the function to create the color reader and the supported bit depths. - /// - /// The scanline factor. - /// The supported bit depths. - /// The factory to create the color reader. - public PngColorTypeInformation(int scanlineFactor, int[] supportedBitDepths, Func scanlineReaderFactory) - { - this.ChannelsPerColor = scanlineFactor; - this.ScanlineReaderFactory = scanlineReaderFactory; - this.SupportedBitDepths = supportedBitDepths; - } - - /// - /// Gets an array with the bit depths that are supported for the color type - /// where this object is created for. - /// - /// The supported bit depths that can be used in combination with this color type. - public int[] SupportedBitDepths { get; private set; } - - /// - /// Gets a function that is used the create the color reader for the color type where - /// this object is created for. - /// - /// The factory function to create the color type. - public Func ScanlineReaderFactory { get; private set; } - - /// - /// Gets a factor that is used when iterating through the scan lines. - /// - /// The scanline factor. - public int ChannelsPerColor { get; private set; } - - /// - /// Creates the color reader for the color type where this object is create for. - /// - /// The palette of the image. Can be null when no palette is used. - /// The alpha palette of the image. Can be null when - /// no palette is used for the image or when the image has no alpha. - /// The color reader for the image. - public IColorReader CreateColorReader(byte[] palette, byte[] paletteAlpha) - { - return this.ScanlineReaderFactory(palette, paletteAlpha); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Png/PngDecoder.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngDecoder.cs deleted file mode 100644 index 7e92f26145..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Png/PngDecoder.cs +++ /dev/null @@ -1,87 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - using System.IO; - - /// - /// Encoder for generating an image out of a png encoded stream. - /// - /// - /// At the moment the following features are supported: - /// - /// Filters: all filters are supported. - /// - /// - /// Pixel 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). - /// Palette Index with alpha (8 bit). - /// Palette Index without alpha (8 bit). - /// - /// - /// - public class PngDecoder : IImageDecoder - { - /// - /// Gets the size of the header for this image type. - /// - /// The size of the header. - public int HeaderSize => 8; - - /// - /// Returns a value indicating whether the supports the specified - /// file header. - /// - /// The containing the file extension. - /// - /// True if the decoder supports the file extension; otherwise, false. - /// - public bool IsSupportedFileExtension(string extension) - { - Guard.NotNullOrEmpty(extension, "extension"); - - extension = extension.StartsWith(".") ? extension.Substring(1) : extension; - - return extension.Equals("PNG", StringComparison.OrdinalIgnoreCase); - } - - /// - /// Returns a value indicating whether the supports the specified - /// file header. - /// - /// The containing the file header. - /// - /// True if the decoder supports the file header; otherwise, false. - /// - public bool IsSupportedFileFormat(byte[] header) - { - return header.Length >= 8 && - header[0] == 0x89 && - header[1] == 0x50 && // P - header[2] == 0x4E && // N - header[3] == 0x47 && // G - header[4] == 0x0D && // CR - header[5] == 0x0A && // LF - header[6] == 0x1A && // EOF - header[7] == 0x0A; // LF - } - - /// - /// Decodes the image from the specified stream to the . - /// - /// The to decode to. - /// The containing image data. - public void Decode(Image image, Stream stream) - { - new PngDecoderCore().Decode(image, stream); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Png/PngDecoderCore.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngDecoderCore.cs deleted file mode 100644 index a8e3596b05..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Png/PngDecoderCore.cs +++ /dev/null @@ -1,527 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Text; - - /// - /// Performs the png decoding operation. - /// - internal class PngDecoderCore - { - /// - /// The dictionary of available color types. - /// - private static readonly Dictionary ColorTypes - = new Dictionary(); - - /// - /// The image to decode. - /// - private Image currentImage; - - /// - /// The stream to decode from. - /// - private Stream currentStream; - - /// - /// The png header. - /// - private PngHeader header; - - /// - /// Initializes static members of the class. - /// - static PngDecoderCore() - { - ColorTypes.Add( - 0, - new PngColorTypeInformation(1, new[] { 1, 2, 4, 8 }, (p, a) => new GrayscaleReader(false))); - - ColorTypes.Add( - 2, - new PngColorTypeInformation(3, new[] { 8 }, (p, a) => new TrueColorReader(false))); - - ColorTypes.Add( - 3, - new PngColorTypeInformation(1, new[] { 1, 2, 4, 8 }, (p, a) => new PaletteIndexReader(p, a))); - - ColorTypes.Add( - 4, - new PngColorTypeInformation(2, new[] { 8 }, (p, a) => new GrayscaleReader(true))); - - ColorTypes.Add(6, - new PngColorTypeInformation(4, new[] { 8 }, (p, a) => new TrueColorReader(true))); - } - - /// - /// Decodes the stream to the image. - /// - /// The image to decode to. - /// The stream containing image data. - /// - /// Thrown if the stream does not contain and end chunk. - /// - /// - /// Thrown if the image is larger than the maximum allowable size. - /// - public void Decode(Image image, Stream stream) - { - this.currentImage = image; - this.currentStream = stream; - this.currentStream.Seek(8, SeekOrigin.Current); - - bool isEndChunkReached = false; - - byte[] palette = null; - byte[] paletteAlpha = null; - - using (MemoryStream dataStream = new MemoryStream()) - { - PngChunk currentChunk; - while ((currentChunk = this.ReadChunk()) != null) - { - if (isEndChunkReached) - { - throw new ImageFormatException("Image does not end with end chunk."); - } - - if (currentChunk.Type == PngChunkTypes.Header) - { - this.ReadHeaderChunk(currentChunk.Data); - this.ValidateHeader(); - } - else if (currentChunk.Type == PngChunkTypes.Physical) - { - this.ReadPhysicalChunk(currentChunk.Data); - } - else if (currentChunk.Type == PngChunkTypes.Data) - { - dataStream.Write(currentChunk.Data, 0, currentChunk.Data.Length); - } - else if (currentChunk.Type == PngChunkTypes.Palette) - { - palette = currentChunk.Data; - } - else if (currentChunk.Type == PngChunkTypes.PaletteAlpha) - { - paletteAlpha = currentChunk.Data; - } - else if (currentChunk.Type == PngChunkTypes.Text) - { - this.ReadTextChunk(currentChunk.Data); - } - else if (currentChunk.Type == PngChunkTypes.End) - { - isEndChunkReached = true; - } - } - - if (this.header.Width > ImageBase.MaxWidth || this.header.Height > ImageBase.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}'"); - } - - float[] pixels = new float[this.header.Width * this.header.Height * 4]; - - PngColorTypeInformation colorTypeInformation = ColorTypes[this.header.ColorType]; - - if (colorTypeInformation != null) - { - IColorReader colorReader = colorTypeInformation.CreateColorReader(palette, paletteAlpha); - - this.ReadScanlines(dataStream, pixels, colorReader, colorTypeInformation); - } - - image.SetPixels(this.header.Width, this.header.Height, pixels); - } - } - - /// - /// Computes a simple linear function of the three neighboring pixels (left, above, upper left), then chooses - /// as predictor the neighboring pixel closest to the computed value. - /// - /// The left neighbour pixel. - /// The above neighbour pixel. - /// The upper left neighbour pixel. - /// - /// The . - /// - private static byte PaethPredicator(byte left, byte above, byte upperLeft) - { - byte predicator; - - int p = left + above - upperLeft; - int pa = Math.Abs(p - left); - int pb = Math.Abs(p - above); - int pc = Math.Abs(p - upperLeft); - - if (pa <= pb && pa <= pc) - { - predicator = left; - } - else if (pb <= pc) - { - predicator = above; - } - else - { - predicator = upperLeft; - } - - return predicator; - } - - /// - /// Reads the data chunk containing physical dimension data. - /// - /// The data containing physical data. - private void ReadPhysicalChunk(byte[] data) - { - 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; - } - - /// - /// Calculates the scanline length. - /// - /// The color type information. - /// The representing the length. - private int CalculateScanlineLength(PngColorTypeInformation colorTypeInformation) - { - int scanlineLength = this.header.Width * this.header.BitDepth * colorTypeInformation.ChannelsPerColor; - - int amount = scanlineLength % 8; - if (amount != 0) - { - scanlineLength += 8 - amount; - } - - return scanlineLength / 8; - } - - /// - /// Calculates a scanline step. - /// - /// The color type information. - /// The representing the length of each step. - private int CalculateScanlineStep(PngColorTypeInformation colorTypeInformation) - { - int scanlineStep = 1; - - if (this.header.BitDepth >= 8) - { - scanlineStep = (colorTypeInformation.ChannelsPerColor * this.header.BitDepth) / 8; - } - - return scanlineStep; - } - - /// - /// Reads the scanlines within the image. - /// - /// The containing data. - /// - /// The containing pixel data. - /// The color reader. - /// The color type information. - private void ReadScanlines(MemoryStream dataStream, float[] pixels, IColorReader colorReader, PngColorTypeInformation colorTypeInformation) - { - dataStream.Position = 0; - - int scanlineLength = this.CalculateScanlineLength(colorTypeInformation); - int scanlineStep = this.CalculateScanlineStep(colorTypeInformation); - - byte[] lastScanline = new byte[scanlineLength]; - byte[] currentScanline = new byte[scanlineLength]; - int filter = 0, column = -1; - - using (ZlibInflateStream compressedStream = new ZlibInflateStream(dataStream)) - { - int readByte; - while ((readByte = compressedStream.ReadByte()) >= 0) - { - if (column == -1) - { - filter = readByte; - - column++; - } - else - { - currentScanline[column] = (byte)readByte; - - byte a; - byte b; - byte c; - - if (column >= scanlineStep) - { - a = currentScanline[column - scanlineStep]; - c = lastScanline[column - scanlineStep]; - } - else - { - a = 0; - c = 0; - } - - b = lastScanline[column]; - - if (filter == 1) - { - currentScanline[column] = (byte)(currentScanline[column] + a); - } - else if (filter == 2) - { - currentScanline[column] = (byte)(currentScanline[column] + b); - } - else if (filter == 3) - { - currentScanline[column] = (byte)(currentScanline[column] + (byte)((a + b) / 2)); - } - else if (filter == 4) - { - currentScanline[column] = (byte)(currentScanline[column] + PaethPredicator(a, b, c)); - } - - column++; - - if (column == scanlineLength) - { - colorReader.ReadScanline(currentScanline, pixels, this.header); - column = -1; - - this.Swap(ref currentScanline, ref lastScanline); - } - } - } - } - } - - /// - /// Reads a text chunk containing image properties from the data. - /// - /// The containing data. - private void ReadTextChunk(byte[] data) - { - int zeroIndex = 0; - - for (int i = 0; i < data.Length; i++) - { - if (data[i] == 0) - { - zeroIndex = i; - break; - } - } - - 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)); - } - - /// - /// Reads a header chunk from the data. - /// - /// The containing data. - private void ReadHeaderChunk(byte[] data) - { - this.header = new PngHeader(); - - Array.Reverse(data, 0, 4); - Array.Reverse(data, 4, 4); - - this.header.Width = BitConverter.ToInt32(data, 0); - this.header.Height = BitConverter.ToInt32(data, 4); - - this.header.BitDepth = data[8]; - this.header.ColorType = data[9]; - this.header.FilterMethod = data[11]; - this.header.InterlaceMethod = data[12]; - this.header.CompressionMethod = data[10]; - } - - /// - /// Validates the png header. - /// - /// - /// Thrown if the image does pass validation. - /// - private void ValidateHeader() - { - if (!ColorTypes.ContainsKey(this.header.ColorType)) - { - throw new ImageFormatException("Color type is not supported or not valid."); - } - - if (!ColorTypes[this.header.ColorType].SupportedBitDepths.Contains(this.header.BitDepth)) - { - throw new ImageFormatException("Bit depth is not supported or not valid."); - } - - if (this.header.FilterMethod != 0) - { - throw new ImageFormatException("The png specification only defines 0 as filter method."); - } - - if (this.header.InterlaceMethod != 0) - { - throw new ImageFormatException("Interlacing is not supported."); - } - } - - /// - /// Reads a chunk from the stream. - /// - /// - /// The . - /// - private PngChunk ReadChunk() - { - PngChunk chunk = new PngChunk(); - - if (this.ReadChunkLength(chunk) == 0) - { - return null; - } - - byte[] typeBuffer = this.ReadChunkType(chunk); - - this.ReadChunkData(chunk); - this.ReadChunkCrc(chunk, typeBuffer); - - return chunk; - } - - /// - /// Reads the cycle redundancy chunk from the data. - /// - /// The chunk. - /// The type buffer. - /// - /// Thrown if the input stream is not valid or corrupt. - /// - private void ReadChunkCrc(PngChunk chunk, byte[] typeBuffer) - { - byte[] crcBuffer = new byte[4]; - - int numBytes = this.currentStream.Read(crcBuffer, 0, 4); - if (numBytes >= 1 && numBytes <= 3) - { - throw new ImageFormatException("Image stream is not valid!"); - } - - Array.Reverse(crcBuffer); - - chunk.Crc = BitConverter.ToUInt32(crcBuffer, 0); - - Crc32 crc = new Crc32(); - crc.Update(typeBuffer); - crc.Update(chunk.Data); - - if (crc.Value != chunk.Crc) - { - throw new ImageFormatException("CRC Error. PNG Image chunk is corrupt!"); - } - } - - /// - /// Reads the chunk data from the stream. - /// - /// The chunk. - private void ReadChunkData(PngChunk chunk) - { - chunk.Data = new byte[chunk.Length]; - this.currentStream.Read(chunk.Data, 0, chunk.Length); - } - - /// - /// Identifies the chunk type from the chunk. - /// - /// The chunk. - /// - /// The containing identifying information. - /// - /// - /// Thrown if the input stream is not valid. - /// - private byte[] ReadChunkType(PngChunk chunk) - { - byte[] typeBuffer = new byte[4]; - - int numBytes = this.currentStream.Read(typeBuffer, 0, 4); - if (numBytes >= 1 && numBytes <= 3) - { - throw new ImageFormatException("Image stream is not valid!"); - } - - char[] chars = new char[4]; - chars[0] = (char)typeBuffer[0]; - chars[1] = (char)typeBuffer[1]; - chars[2] = (char)typeBuffer[2]; - chars[3] = (char)typeBuffer[3]; - - chunk.Type = new string(chars); - - return typeBuffer; - } - - /// - /// Calculates the length of the given chunk. - /// - /// he chunk. - /// - /// The representing the chunk length. - /// - /// - /// Thrown if the input stream is not valid. - /// - private int ReadChunkLength(PngChunk chunk) - { - byte[] lengthBuffer = new byte[4]; - - int numBytes = this.currentStream.Read(lengthBuffer, 0, 4); - if (numBytes >= 1 && numBytes <= 3) - { - throw new ImageFormatException("Image stream is not valid!"); - } - - Array.Reverse(lengthBuffer); - - chunk.Length = BitConverter.ToInt32(lengthBuffer, 0); - - return numBytes; - } - - /// - /// Swaps two references. - /// - /// The type of the references to swap. - /// The first reference. - /// The second reference. - private void Swap(ref TRef lhs, ref TRef rhs) - where TRef : class - { - TRef tmp = lhs; - - lhs = rhs; - rhs = tmp; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Png/PngEncoder.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngEncoder.cs deleted file mode 100644 index 18deb10466..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Png/PngEncoder.cs +++ /dev/null @@ -1,85 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - using System.IO; - - using ImageProcessorCore.Quantizers; - - /// - /// Image encoder for writing image data to a stream in png format. - /// - public class PngEncoder : IImageEncoder - { - /// - /// Gets or sets the quality of output for images. - /// - public int Quality { get; set; } - - /// - public string MimeType => "image/png"; - - /// - public string Extension => "png"; - - /// - /// The compression level 1-9. - /// Defaults to 6. - /// - public int CompressionLevel { get; set; } = 6; - - /// - /// Gets or sets the gamma value, that will be written - /// the the stream, when the property - /// is set to true. The default value is 2.2F. - /// - /// The gamma value of the image. - public float Gamma { get; set; } = 2.2F; - - /// - /// The quantizer for reducing the color count. - /// - public IQuantizer Quantizer { get; set; } - - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } = 128; - - /// - /// Gets or sets a value indicating whether this instance should write - /// gamma information to the stream. The default value is false. - /// - public bool WriteGamma { get; set; } - - /// - public bool IsSupportedFileExtension(string extension) - { - Guard.NotNullOrEmpty(extension, nameof(extension)); - - extension = extension.StartsWith(".") ? extension.Substring(1) : extension; - - return extension.Equals(this.Extension, StringComparison.OrdinalIgnoreCase); - } - - /// - public void Encode(ImageBase image, Stream stream) - { - PngEncoderCore encoder = new PngEncoderCore - { - CompressionLevel = this.CompressionLevel, - Gamma = this.Gamma, - Quality = this.Quality, - Quantizer = this.Quantizer, - WriteGamma = this.WriteGamma, - Threshold = this.Threshold - }; - - encoder.Encode(image, stream); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Png/PngEncoderCore.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngEncoderCore.cs deleted file mode 100644 index 7e61f736ef..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Png/PngEncoderCore.cs +++ /dev/null @@ -1,470 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - using System.IO; - using System.Threading.Tasks; - - using ImageProcessorCore.Quantizers; - - /// - /// Performs the png encoding operation. - /// TODO: Perf. There's lots of array parsing going on here. This should be unmanaged. - /// - internal sealed class PngEncoderCore - { - /// - /// The maximum block size, defaults at 64k for uncompressed blocks. - /// - private const int MaxBlockSize = 65535; - - /// - /// The number of bits required to encode the colors in the png. - /// - private byte bitDepth; - - /// - /// The quantized image result. - /// - private QuantizedImage quantized; - - /// - /// Gets or sets the quality of output for images. - /// - public int Quality { get; set; } - - /// - /// The compression level 1-9. - /// Defaults to 6. - /// - public int CompressionLevel { get; set; } = 6; - - /// - /// Gets or sets a value indicating whether this instance should write - /// gamma information to the stream. The default value is false. - /// - public bool WriteGamma { get; set; } - - /// - /// Gets or sets the gamma value, that will be written - /// the the stream, when the property - /// is set to true. The default value is 2.2F. - /// - /// The gamma value of the image. - public float Gamma { get; set; } = 2.2F; - - /// - /// The quantizer for reducing the color count. - /// - public IQuantizer Quantizer { get; set; } - - /// - /// Gets or sets the transparency threshold. - /// - public byte Threshold { get; set; } = 128; - - /// - public void Encode(ImageBase image, Stream stream) - { - Guard.NotNull(image, nameof(image)); - Guard.NotNull(stream, nameof(stream)); - - // Write the png header. - stream.Write( - new byte[] - { - 0x89, // Set the high bit. - 0x50, // P - 0x4E, // N - 0x47, // G - 0x0D, // Line ending CRLF - 0x0A, // Line ending CRLF - 0x1A, // EOF - 0x0A // LF - }, - 0, - 8); - - // Ensure that quality can be set but has a fallback. - int quality = this.Quality > 0 ? this.Quality : image.Quality; - this.Quality = quality > 0 ? quality.Clamp(1, int.MaxValue) : int.MaxValue; - - this.bitDepth = this.Quality <= 256 - ? (byte)(ImageMaths.GetBitsNeededForColorDepth(this.Quality).Clamp(1, 8)) - : (byte)8; - - // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk - if (this.bitDepth == 3) - { - this.bitDepth = 4; - } - else if (this.bitDepth >= 5 || this.bitDepth <= 7) - { - this.bitDepth = 8; - } - - // TODO: Add more color options here. - PngHeader header = new PngHeader - { - Width = image.Width, - Height = image.Height, - ColorType = (byte)(this.Quality <= 256 ? 3 : 6), // 3 = indexed, 6= Each pixel is an R,G,B triple, followed by an alpha sample. - BitDepth = this.bitDepth, - FilterMethod = 0, // None - CompressionMethod = 0, - InterlaceMethod = 0 - }; - - this.WriteHeaderChunk(stream, header); - this.WritePaletteChunk(stream, header, image); - this.WritePhysicalChunk(stream, image); - this.WriteGammaChunk(stream); - - using (PixelAccessor pixels = image.Lock()) - { - this.WriteDataChunks(stream, pixels); - } - - this.WriteEndChunk(stream); - stream.Flush(); - } - - /// - /// Writes an integer to the byte array. - /// - /// The containing image data. - /// The amount to offset by. - /// The value to write. - private static void WriteInteger(byte[] data, int offset, int value) - { - byte[] buffer = BitConverter.GetBytes(value); - - Array.Reverse(buffer); - Array.Copy(buffer, 0, data, offset, 4); - } - - /// - /// Writes an integer to the stream. - /// - /// The containing image data. - /// The value to write. - private static void WriteInteger(Stream stream, int value) - { - byte[] buffer = BitConverter.GetBytes(value); - - Array.Reverse(buffer); - - stream.Write(buffer, 0, 4); - } - - /// - /// Writes an unsigned integer to the stream. - /// - /// The containing image data. - /// The value to write. - private static void WriteInteger(Stream stream, uint value) - { - byte[] buffer = BitConverter.GetBytes(value); - - Array.Reverse(buffer); - - stream.Write(buffer, 0, 4); - } - - /// - /// Writes the header chunk to the stream. - /// - /// The containing image data. - /// The . - private void WriteHeaderChunk(Stream stream, PngHeader header) - { - byte[] chunkData = new byte[13]; - - WriteInteger(chunkData, 0, header.Width); - WriteInteger(chunkData, 4, header.Height); - - chunkData[8] = header.BitDepth; - chunkData[9] = header.ColorType; - chunkData[10] = header.CompressionMethod; - chunkData[11] = header.FilterMethod; - chunkData[12] = header.InterlaceMethod; - - this.WriteChunk(stream, PngChunkTypes.Header, chunkData); - } - - /// - /// Writes the palette chunk to the stream. - /// - /// The containing image data. - /// The . - /// The image to encode. - private void WritePaletteChunk(Stream stream, PngHeader header, ImageBase image) - { - if (this.Quality > 256) - { - return; - } - - if (this.Quantizer == null) - { - this.Quantizer = new WuQuantizer { Threshold = this.Threshold }; - } - - // Quantize the image returning a palette. - this.quantized = this.Quantizer.Quantize(image, this.Quality); - - // Grab the palette and write it to the stream. - Bgra32[] palette = this.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, - i => - { - int offset = i * 3; - Bgra32 color = palette[i]; - - colorTable[offset] = color.R; - colorTable[offset + 1] = color.G; - colorTable[offset + 2] = color.B; - }); - - this.WriteChunk(stream, PngChunkTypes.Palette, colorTable); - - // Write the transparency data - if (this.quantized.TransparentIndex > -1) - { - this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, new[] { (byte)this.quantized.TransparentIndex }); - } - } - - /// - /// Writes the physical dimension information to the stream. - /// - /// The containing image data. - /// The image base. - private void WritePhysicalChunk(Stream stream, ImageBase imageBase) - { - 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); - - byte[] chunkData = new byte[9]; - - WriteInteger(chunkData, 0, dpmX); - WriteInteger(chunkData, 4, dpmY); - - chunkData[8] = 1; - - this.WriteChunk(stream, PngChunkTypes.Physical, chunkData); - } - } - - /// - /// Writes the gamma information to the stream. - /// - /// The containing image data. - private void WriteGammaChunk(Stream stream) - { - if (this.WriteGamma) - { - int gammaValue = (int)(this.Gamma * 100000f); - - byte[] fourByteData = new byte[4]; - - byte[] size = BitConverter.GetBytes(gammaValue); - - fourByteData[0] = size[3]; - fourByteData[1] = size[2]; - fourByteData[2] = size[1]; - fourByteData[3] = size[0]; - - this.WriteChunk(stream, PngChunkTypes.Gamma, fourByteData); - } - } - - /// - /// Writes the pixel information to the stream. - /// - /// The containing image data. - /// The image pixels. - private void WriteDataChunks(Stream stream, PixelAccessor pixels) - { - byte[] data; - int imageWidth = pixels.Width; - int imageHeight = pixels.Height; - - // Indexed image. - if (this.Quality <= 256) - { - int rowLength = imageWidth + 1; - data = new byte[rowLength * imageHeight]; - - Parallel.For(0, imageHeight, y => - { - int dataOffset = (y * rowLength); - byte compression = 0; - if (y > 0) - { - compression = 2; - } - data[dataOffset++] = compression; - for (int x = 0; x < imageWidth; x++) - { - data[dataOffset++] = this.quantized.Pixels[(y * imageWidth) + x]; - if (y > 0) - { - data[dataOffset - 1] -= this.quantized.Pixels[((y - 1) * imageWidth) + x]; - } - } - }); - } - else - { - // TrueColor image. - data = new byte[(imageWidth * imageHeight * 4) + pixels.Height]; - - int rowLength = (imageWidth * 4) + 1; - - Parallel.For(0, imageHeight, y => - { - byte compression = 0; - if (y > 0) - { - compression = 2; - } - - data[y * rowLength] = compression; - - for (int x = 0; x < imageWidth; x++) - { - Bgra32 color = Color.ToNonPremultiplied(pixels[x, y]); - - // 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; - - if (y > 0) - { - color = Color.ToNonPremultiplied(pixels[x, y - 1]); - - data[dataOffset] -= color.R; - data[dataOffset + 1] -= color.G; - data[dataOffset + 2] -= color.B; - data[dataOffset + 3] -= color.A; - } - } - }); - } - - byte[] buffer; - int bufferLength; - - MemoryStream memoryStream = null; - try - { - memoryStream = new MemoryStream(); - - using (ZlibDeflateStream deflateStream = new ZlibDeflateStream(memoryStream, this.CompressionLevel)) - { - deflateStream.Write(data, 0, data.Length); - } - - bufferLength = (int)memoryStream.Length; - buffer = memoryStream.ToArray(); - } - finally - { - memoryStream?.Dispose(); - } - - int numChunks = bufferLength / MaxBlockSize; - - if (bufferLength % MaxBlockSize != 0) - { - numChunks++; - } - - for (int i = 0; i < numChunks; i++) - { - int length = bufferLength - (i * MaxBlockSize); - - if (length > MaxBlockSize) - { - length = MaxBlockSize; - } - - this.WriteChunk(stream, PngChunkTypes.Data, buffer, i * MaxBlockSize, length); - } - } - - /// - /// Writes the chunk end to the stream. - /// - /// The containing image data. - private void WriteEndChunk(Stream stream) - { - this.WriteChunk(stream, PngChunkTypes.End, null); - } - - /// - /// Writes a chunk to the stream. - /// - /// The to write to. - /// The type of chunk to write. - /// The containing data. - private void WriteChunk(Stream stream, string type, byte[] data) - { - this.WriteChunk(stream, type, data, 0, data?.Length ?? 0); - } - - /// - /// Writes a chunk of a specified length to the stream at the given offset. - /// - /// The to write to. - /// The type of chunk to write. - /// The containing data. - /// The position to offset the data at. - /// The of the data to write. - private void WriteChunk(Stream stream, string type, byte[] data, int offset, int length) - { - WriteInteger(stream, length); - - byte[] typeArray = new byte[4]; - typeArray[0] = (byte)type[0]; - typeArray[1] = (byte)type[1]; - typeArray[2] = (byte)type[2]; - typeArray[3] = (byte)type[3]; - - stream.Write(typeArray, 0, 4); - - if (data != null) - { - stream.Write(data, offset, length); - } - - Crc32 crc32 = new Crc32(); - crc32.Update(typeArray); - - if (data != null) - { - crc32.Update(data, offset, length); - } - - WriteInteger(stream, (uint)crc32.Value); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Png/PngFormat.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngFormat.cs deleted file mode 100644 index 38a0a7c384..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Png/PngFormat.cs +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Encapsulates the means to encode and decode png images. - /// - public class PngFormat : IImageFormat - { - /// - public IImageDecoder Decoder => new PngDecoder(); - - /// - public IImageEncoder Encoder => new PngEncoder(); - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Png/PngHeader.cs b/src/ImageProcessorCore - Copy/Formats/Png/PngHeader.cs deleted file mode 100644 index dfa30794a8..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Png/PngHeader.cs +++ /dev/null @@ -1,62 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Represents the png header chunk. - /// - public sealed class PngHeader - { - /// - /// Gets or sets the dimension in x-direction of the image in pixels. - /// - public int Width { get; set; } - - /// - /// Gets or sets the dimension in y-direction of the image in pixels. - /// - public int Height { get; set; } - - /// - /// Gets or sets the bit depth. - /// Bit depth is a single-byte integer giving the number of bits per sample - /// or per palette index (not per pixel). Valid values are 1, 2, 4, 8, and 16, - /// although not all values are allowed for all color types. - /// - public byte BitDepth { get; set; } - - /// - /// Gets or sets the color type. - /// Color type is a integer that describes the interpretation of the - /// image data. Color type codes represent sums of the following values: - /// 1 (palette used), 2 (color used), and 4 (alpha channel used). - /// - public byte ColorType { get; set; } - - /// - /// Gets or sets the compression method. - /// Indicates the method used to compress the image data. At present, - /// only compression method 0 (deflate/inflate compression with a sliding - /// window of at most 32768 bytes) is defined. - /// - public byte CompressionMethod { get; set; } - - /// - /// Gets or sets the preprocessing method. - /// Indicates the preprocessing method applied to the image - /// data before compression. At present, only filter method 0 - /// (adaptive filtering with five basic filter types) is defined. - /// - public byte FilterMethod { get; set; } - - /// - /// Gets or sets the transmission order. - /// Indicates the transmission order of the image data. - /// Two values are currently defined: 0 (no interlace) or 1 (Adam7 interlace). - /// - public byte InterlaceMethod { get; set; } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Png/README.md b/src/ImageProcessorCore - Copy/Formats/Png/README.md deleted file mode 100644 index 8ade379560..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Png/README.md +++ /dev/null @@ -1,6 +0,0 @@ -Encoder/Decoder adapted from: - -https://github.com/yufeih/Nine.Imaging/ -https://imagetools.codeplex.com/ -https://github.com/leonbloy/pngcs - diff --git a/src/ImageProcessorCore - Copy/Formats/Png/TrueColorReader.cs b/src/ImageProcessorCore - Copy/Formats/Png/TrueColorReader.cs deleted file mode 100644 index 47c39b029f..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Png/TrueColorReader.cs +++ /dev/null @@ -1,78 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Color reader for reading true colors from a png file. Only colors - /// with 24 or 32 bit (3 or 4 bytes) per pixel are supported at the moment. - /// - internal sealed class TrueColorReader : IColorReader - { - /// - /// Whether t also read the alpha channel. - /// - private readonly bool useAlpha; - - /// - /// The current row. - /// - private int row; - - /// - /// Initializes a new instance of the class. - /// - /// if set to true the color reader will also read the - /// alpha channel from the scanline. - public TrueColorReader(bool useAlpha) - { - this.useAlpha = useAlpha; - } - - /// - public void ReadScanline(byte[] scanline, float[] pixels, PngHeader header) - { - int offset; - - byte[] newScanline = scanline.ToArrayByBitsLength(header.BitDepth); - - if (this.useAlpha) - { - for (int x = 0; x < newScanline.Length; x += 4) - { - offset = ((this.row * header.Width) + (x >> 2)) * 4; - - // 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; - - 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; - } - } - else - { - for (int x = 0; x < newScanline.Length / 3; x++) - { - offset = ((this.row * header.Width) + x) * 4; - 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; - } - } - - this.row++; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Png/Zlib/Adler32.cs b/src/ImageProcessorCore - Copy/Formats/Png/Zlib/Adler32.cs deleted file mode 100644 index f58ec34c2d..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Png/Zlib/Adler32.cs +++ /dev/null @@ -1,174 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - - /// - /// Computes Adler32 checksum for a stream of data. An Adler32 - /// checksum is not as reliable as a CRC32 checksum, but a lot faster to - /// compute. - /// - /// - /// The specification for Adler32 may be found in RFC 1950. - /// ZLIB Compressed Data Format Specification version 3.3) - /// - /// - /// From that document: - /// - /// "ADLER32 (Adler-32 checksum) - /// This contains a checksum value of the uncompressed data - /// (excluding any dictionary data) computed according to Adler-32 - /// algorithm. This algorithm is a 32-bit extension and improvement - /// of the Fletcher algorithm, used in the ITU-T X.224 / ISO 8073 - /// standard. - /// - /// Adler-32 is composed of two sums accumulated per byte: s1 is - /// the sum of all bytes, s2 is the sum of all s1 values. Both sums - /// are done modulo 65521. s1 is initialized to 1, s2 to zero. The - /// Adler-32 checksum is stored as s2*65536 + s1 in most- - /// significant-byte first (network) order." - /// - /// "8.2. The Adler-32 algorithm - /// - /// The Adler-32 algorithm is much faster than the CRC32 algorithm yet - /// still provides an extremely low probability of undetected errors. - /// - /// The modulo on unsigned long accumulators can be delayed for 5552 - /// bytes, so the modulo operation time is negligible. If the bytes - /// are a, b, c, the second sum is 3a + 2b + c + 3, and so is position - /// and order sensitive, unlike the first sum, which is just a - /// checksum. That 65521 is prime is important to avoid a possible - /// large class of two-byte errors that leave the check unchanged. - /// (The Fletcher checksum uses 255, which is not prime and which also - /// makes the Fletcher check insensitive to single byte changes 0 - - /// 255.) - /// - /// The sum s1 is initialized to 1 instead of zero to make the length - /// of the sequence part of s2, so that the length does not have to be - /// checked separately. (Any sequence of zeroes has a Fletcher - /// checksum of zero.)" - /// - /// - /// - internal sealed class Adler32 : IChecksum - { - /// - /// largest prime smaller than 65536 - /// - private const uint Base = 65521; - - /// - /// The checksum calculated to far. - /// - private uint checksum; - - /// - /// Initializes a new instance of the class. - /// The checksum starts off with a value of 1. - /// - public Adler32() - { - this.Reset(); - } - - /// - public long Value => this.checksum; - - /// - public void Reset() - { - this.checksum = 1; - } - - /// - /// Updates the checksum with a byte value. - /// - /// - /// The data value to add. The high byte of the int is ignored. - /// - public void Update(int value) - { - // We could make a length 1 byte array and call update again, but I - // would rather not have that overhead - uint s1 = this.checksum & 0xFFFF; - uint s2 = this.checksum >> 16; - - s1 = (s1 + ((uint)value & 0xFF)) % Base; - s2 = (s1 + s2) % Base; - - this.checksum = (s2 << 16) + s1; - } - - /// - public void Update(byte[] buffer) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - this.Update(buffer, 0, buffer.Length); - } - - /// - public void Update(byte[] buffer, int offset, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(offset), "cannot be negative"); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), "cannot be negative"); - } - - if (offset >= buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset), "not a valid index into buffer"); - } - - if (offset + count > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(count), "exceeds buffer size"); - } - - // (By Per Bothner) - uint s1 = this.checksum & 0xFFFF; - uint s2 = this.checksum >> 16; - - while (count > 0) - { - // We can defer the modulo operation: - // s1 maximally grows from 65521 to 65521 + 255 * 3800 - // s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31 - int n = 3800; - if (n > count) - { - n = count; - } - - count -= n; - while (--n >= 0) - { - s1 = s1 + (uint)(buffer[offset++] & 0xff); - s2 = s2 + s1; - } - - s1 %= Base; - s2 %= Base; - } - - this.checksum = (s2 << 16) | s1; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Png/Zlib/Crc32.cs b/src/ImageProcessorCore - Copy/Formats/Png/Zlib/Crc32.cs deleted file mode 100644 index da42e8dae4..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Png/Zlib/Crc32.cs +++ /dev/null @@ -1,180 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - - /// - /// Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: - /// x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. - /// - /// - /// - /// Polynomials over GF(2) are represented in binary, one bit per coefficient, - /// with the lowest powers in the most significant bit. Then adding polynomials - /// is just exclusive-or, and multiplying a polynomial by x is a right shift by - /// one. If we call the above polynomial p, and represent a byte as the - /// polynomial q, also with the lowest power in the most significant bit (so the - /// byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, - /// where a mod b means the remainder after dividing a by b. - /// - /// - /// This calculation is done using the shift-register method of multiplying and - /// taking the remainder. The register is initialized to zero, and for each - /// incoming bit, x^32 is added mod p to the register if the bit is a one (where - /// x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by - /// x (which is shifting right by one and adding x^32 mod p if the bit shifted - /// out is a one). We start with the highest power (least significant bit) of - /// q and repeat for all eight bits of q. - /// - /// - /// The table is simply the CRC of all possible eight bit values. This is all - /// the information needed to generate CRC's on data a byte at a time for all - /// combinations of CRC register values and incoming bytes. - /// - /// - internal sealed class Crc32 : IChecksum - { - /// - /// The cycle redundancy check seed - /// - private const uint CrcSeed = 0xFFFFFFFF; - - /// - /// The table of all possible eight bit values for fast lookup. - /// - private static readonly uint[] CrcTable = - { - 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, - 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, - 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, - 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, - 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, - 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, - 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, - 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, - 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, - 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, - 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, - 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, - 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, - 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, - 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, - 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, - 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, - 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, - 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, - 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, - 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, - 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, - 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, - 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, - 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, - 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, - 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, - 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, - 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, - 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, - 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, - 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, - 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, - 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, - 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, - 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, - 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, - 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, - 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, - 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, - 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, - 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, - 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, - 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, - 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, - 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, - 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, - 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, - 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, - 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, - 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, - 0x2D02EF8D - }; - - /// - /// The data checksum so far. - /// - private uint crc; - - /// - public long Value - { - get - { - return this.crc; - } - - set - { - this.crc = (uint)value; - } - } - - /// - public void Reset() - { - this.crc = 0; - } - - /// - /// Updates the checksum with the given value. - /// - /// The byte is taken as the lower 8 bits of value. - public void Update(int value) - { - this.crc ^= CrcSeed; - this.crc = CrcTable[(this.crc ^ value) & 0xFF] ^ (this.crc >> 8); - this.crc ^= CrcSeed; - } - - /// - public void Update(byte[] buffer) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - this.Update(buffer, 0, buffer.Length); - } - - /// - public void Update(byte[] buffer, int offset, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), "Count cannot be less than zero"); - } - - if (offset < 0 || offset + count > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - this.crc ^= CrcSeed; - - while (--count >= 0) - { - this.crc = CrcTable[(this.crc ^ buffer[offset++]) & 0xFF] ^ (this.crc >> 8); - } - - this.crc ^= CrcSeed; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Png/Zlib/IChecksum.cs b/src/ImageProcessorCore - Copy/Formats/Png/Zlib/IChecksum.cs deleted file mode 100644 index 077a5ad2a6..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Png/Zlib/IChecksum.cs +++ /dev/null @@ -1,60 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - /// - /// Interface to compute a data checksum used by checked input/output streams. - /// A data checksum can be updated by one byte or with a byte array. After each - /// update the value of the current checksum can be returned by calling - /// Value. The complete checksum object can also be reset - /// so it can be used again with new data. - /// - public interface IChecksum - { - /// - /// Gets the data checksum computed so far. - /// - long Value - { - get; - } - - /// - /// Resets the data checksum as if no update was ever called. - /// - void Reset(); - - /// - /// Adds one byte to the data checksum. - /// - /// - /// The data value to add. The high byte of the integer is ignored. - /// - void Update(int value); - - /// - /// Updates the data checksum with the bytes taken from the array. - /// - /// - /// buffer an array of bytes - /// - void Update(byte[] buffer); - - /// - /// Adds the byte array to the data checksum. - /// - /// - /// The buffer which contains the data - /// - /// - /// The offset in the buffer where the data starts - /// - /// - /// the number of data bytes to add. - /// - void Update(byte[] buffer, int offset, int count); - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Png/Zlib/README.md b/src/ImageProcessorCore - Copy/Formats/Png/Zlib/README.md deleted file mode 100644 index c297a91d5e..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Png/Zlib/README.md +++ /dev/null @@ -1,2 +0,0 @@ -Adler32.cs and Crc32.cs have been copied from -https://github.com/ygrenier/SharpZipLib.Portable diff --git a/src/ImageProcessorCore - Copy/Formats/Png/Zlib/ZlibDeflateStream.cs b/src/ImageProcessorCore - Copy/Formats/Png/Zlib/ZlibDeflateStream.cs deleted file mode 100644 index a2c0ca202f..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Png/Zlib/ZlibDeflateStream.cs +++ /dev/null @@ -1,210 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - using System.IO; - using System.IO.Compression; - - /// - /// Provides methods and properties for compressing streams by using the Zlib Deflate algorithm. - /// - internal sealed class ZlibDeflateStream : Stream - { - /// - /// The raw stream containing the uncompressed image data. - /// - private readonly Stream rawStream; - - /// - /// Computes the checksum for the data stream. - /// - private readonly Adler32 adler32 = new Adler32(); - - /// - /// A value indicating whether this instance of the given entity has been disposed. - /// - /// if this instance has been disposed; otherwise, . - /// - /// If the entity is disposed, it must not be disposed a second - /// time. The isDisposed field is set the first time the entity - /// is disposed. If the isDisposed field is true, then the Dispose() - /// method will not dispose again. This help not to prolong the entity's - /// life in the Garbage Collector. - /// - private bool isDisposed; - - // The stream responsible for decompressing the input stream. - private DeflateStream deflateStream; - - /// - /// Initializes a new instance of - /// - /// The stream to compress. - /// The compression level. - public ZlibDeflateStream(Stream stream, int compressionLevel) - { - this.rawStream = stream; - - // Write the zlib header : http://tools.ietf.org/html/rfc1950 - // CMF(Compression Method and flags) - // This byte is divided into a 4 - bit compression method and a - // 4-bit information field depending on the compression method. - // bits 0 to 3 CM Compression method - // bits 4 to 7 CINFO Compression info - // - // 0 1 - // +---+---+ - // |CMF|FLG| - // +---+---+ - int cmf = 0x78; - int flg = 218; - - // http://stackoverflow.com/a/2331025/277304 - if (compressionLevel >= 5 && compressionLevel <= 6) - { - flg = 156; - } - else if (compressionLevel >= 3 && compressionLevel <= 4) - { - flg = 94; - } - - else if (compressionLevel <= 2) - { - flg = 1; - } - - // Just in case - flg -= (cmf * 256 + flg) % 31; - - if (flg < 0) - { - flg += 31; - } - - this.rawStream.WriteByte((byte)cmf); - this.rawStream.WriteByte((byte)flg); - - // Initialize the deflate Stream. - CompressionLevel level = CompressionLevel.Optimal; - - if (compressionLevel >= 1 && compressionLevel <= 5) - { - level = CompressionLevel.Fastest; - } - - else if (compressionLevel == 0) - { - level = CompressionLevel.NoCompression; - } - - this.deflateStream = new DeflateStream(this.rawStream, level, true); - } - - /// - public override bool CanRead => false; - - /// - public override bool CanSeek => false; - - /// - public override bool CanWrite => true; - - /// - public override long Length - { - get - { - throw new NotSupportedException(); - } - } - - /// - public override long Position - { - get - { - throw new NotSupportedException(); - } - - set - { - throw new NotSupportedException(); - } - } - - /// - public override void Flush() - { - this.deflateStream?.Flush(); - } - - /// - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - - /// - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - /// - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - /// - public override void Write(byte[] buffer, int offset, int count) - { - this.deflateStream.Write(buffer, offset, count); - this.adler32.Update(buffer, offset, count); - } - - /// - protected override void Dispose(bool disposing) - { - if (this.isDisposed) - { - return; - } - - if (disposing) - { - // dispose managed resources - if (this.deflateStream != null) - { - this.deflateStream.Dispose(); - this.deflateStream = null; - } - else { - - // Hack: empty input? - this.rawStream.WriteByte(3); - this.rawStream.WriteByte(0); - } - - // Add the crc - uint crc = (uint)this.adler32.Value; - this.rawStream.WriteByte((byte)((crc >> 24) & 0xFF)); - this.rawStream.WriteByte((byte)((crc >> 16) & 0xFF)); - this.rawStream.WriteByte((byte)((crc >> 8) & 0xFF)); - this.rawStream.WriteByte((byte)((crc) & 0xFF)); - } - - base.Dispose(disposing); - - // Call the appropriate methods to clean up - // unmanaged resources here. - // Note disposing is done. - this.isDisposed = true; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Formats/Png/Zlib/ZlibInflateStream.cs b/src/ImageProcessorCore - Copy/Formats/Png/Zlib/ZlibInflateStream.cs deleted file mode 100644 index 4373b5fd17..0000000000 --- a/src/ImageProcessorCore - Copy/Formats/Png/Zlib/ZlibInflateStream.cs +++ /dev/null @@ -1,205 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Formats -{ - using System; - using System.IO; - using System.IO.Compression; - - /// - /// Provides methods and properties for decompressing streams by using the Zlib Deflate algorithm. - /// - internal sealed class ZlibInflateStream : Stream - { - /// - /// A value indicating whether this instance of the given entity has been disposed. - /// - /// if this instance has been disposed; otherwise, . - /// - /// If the entity is disposed, it must not be disposed a second - /// time. The isDisposed field is set the first time the entity - /// is disposed. If the isDisposed field is true, then the Dispose() - /// method will not dispose again. This help not to prolong the entity's - /// life in the Garbage Collector. - /// - private bool isDisposed; - - /// - /// The raw stream containing the uncompressed image data. - /// - private readonly Stream rawStream; - - /// - /// The read crc data. - /// - private byte[] crcread; - - // The stream responsible for decompressing the input stream. - private DeflateStream deflateStream; - - public ZlibInflateStream(Stream stream) - { - // The DICT dictionary identifier identifying the used dictionary. - - // The preset dictionary. - bool fdict; - this.rawStream = stream; - - // Read the zlib header : http://tools.ietf.org/html/rfc1950 - // CMF(Compression Method and flags) - // This byte is divided into a 4 - bit compression method and a - // 4-bit information field depending on the compression method. - // bits 0 to 3 CM Compression method - // bits 4 to 7 CINFO Compression info - // - // 0 1 - // +---+---+ - // |CMF|FLG| - // +---+---+ - int cmf = this.rawStream.ReadByte(); - int flag = this.rawStream.ReadByte(); - if (cmf == -1 || flag == -1) - { - return; - } - - if ((cmf & 0x0f) != 8) - { - throw new Exception($"Bad compression method for ZLIB header: cmf={cmf}"); - } - - // CINFO is the base-2 logarithm of the LZ77 window size, minus eight. - // int cinfo = ((cmf & (0xf0)) >> 8); - fdict = (flag & 32) != 0; - - if (fdict) - { - // The DICT dictionary identifier identifying the used dictionary. - byte[] dictId = new byte[4]; - - for (int i = 0; i < 4; i++) - { - // We consume but don't use this. - dictId[i] = (byte)this.rawStream.ReadByte(); - } - } - - // Initialize the deflate Stream. - this.deflateStream = new DeflateStream(this.rawStream, CompressionMode.Decompress, true); - } - - /// - public override bool CanRead => true; - - /// - public override bool CanSeek => false; - - /// - public override bool CanWrite => false; - - /// - public override long Length - { - get - { - throw new NotSupportedException(); - } - } - - /// - public override long Position - { - get - { - throw new NotSupportedException(); - } - - set - { - throw new NotSupportedException(); - } - } - - /// - public override void Flush() - { - this.deflateStream?.Flush(); - } - - /// - public override int Read(byte[] buffer, int offset, int count) - { - // We dont't check CRC on reading - int read = this.deflateStream.Read(buffer, offset, count); - if (read < 1 && this.crcread == null) - { - // The deflater has ended. We try to read the next 4 bytes from raw stream (crc) - this.crcread = new byte[4]; - for (int i = 0; i < 4; i++) - { - // we dont really check/use this - this.crcread[i] = (byte)this.rawStream.ReadByte(); - } - } - - return read; - } - - /// - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - /// - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - /// - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - - /// - protected override void Dispose(bool disposing) - { - if (this.isDisposed) - { - return; - } - - if (disposing) - { - // dispose managed resources - if (this.deflateStream != null) - { - this.deflateStream.Dispose(); - this.deflateStream = null; - - if (this.crcread == null) - { - // Consume the trailing 4 bytes - this.crcread = new byte[4]; - for (int i = 0; i < 4; i++) - { - this.crcread[i] = (byte)this.rawStream.ReadByte(); - } - } - } - } - - base.Dispose(disposing); - - // Call the appropriate methods to clean up - // unmanaged resources here. - // Note disposing is done. - this.isDisposed = true; - } - } -} diff --git a/src/ImageProcessorCore - Copy/IImageBase.cs b/src/ImageProcessorCore - Copy/IImageBase.cs deleted file mode 100644 index a721033770..0000000000 --- a/src/ImageProcessorCore - Copy/IImageBase.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace ImageProcessorCore -{ - public interface IImageBase - where TPackedVector : IPackedVector - { - Rectangle Bounds { get; } - int FrameDelay { get; set; } - int Height { get; } - double PixelRatio { get; } - TPackedVector[] Pixels { get; } - int Quality { get; set; } - - /// - /// Gets or sets the maximum allowable width in pixels. - /// - int MaxWidth { get; set; } - - /// - /// Gets or sets the maximum allowable height in pixels. - /// - int MaxHeight { get; set; } - - int Width { get; } - - void ClonePixels(int width, int height, TPackedVector[] pixels); - IPixelAccessor Lock(); - void SetPixels(int width, int height, TPackedVector[] pixels); - } -} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/IImageFrame.cs b/src/ImageProcessorCore - Copy/IImageFrame.cs deleted file mode 100644 index 1565879a76..0000000000 --- a/src/ImageProcessorCore - Copy/IImageFrame.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace ImageProcessorCore -{ - public interface IImageFrame : IImageBase - where TPacked : IPackedVector - { - } -} diff --git a/src/ImageProcessorCore - Copy/IImageProcessor.cs b/src/ImageProcessorCore - Copy/IImageProcessor.cs deleted file mode 100644 index ad6e4ac761..0000000000 --- a/src/ImageProcessorCore - Copy/IImageProcessor.cs +++ /dev/null @@ -1,72 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - /// - /// A delegate which is called as progress is made processing an image. - /// - /// The source of the event. - /// An object that contains the event data. - public delegate void ProgressEventHandler(object sender, ProgressEventArgs e); - - /// - /// Encapsulates methods to alter the pixels of an image. - /// - public interface IImageProcessor - { - /// - /// Event fires when each row of the source image has been processed. - /// - /// - /// This event may be called from threads other than the client thread, and from multiple threads simultaneously. - /// Individual row notifications may arrived out of order. - /// - event ProgressEventHandler OnProgress; - - /// - /// Applies the process to the specified portion of the specified . - /// - /// The type of pixels contained within the image. - /// Target image to apply the process to. - /// The source image. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// - /// The method keeps the source image unchanged and returns the - /// the result of image processing filter as new image. - /// - /// - /// is null or is null. - /// - /// - /// doesnt fit the dimension of the image. - /// - void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) where TPackedVector : IPackedVector; - - /// - /// Applies the process to the specified portion of the specified at the specified - /// location and with the specified size. - /// - /// The type of pixels contained within the image. - /// Target image to apply the process to. - /// The source image. Cannot be null. - /// The target width. - /// The target height. - /// - /// The structure that specifies the location and size of the drawn image. - /// The image is scaled to fit the rectangle. - /// - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// - /// The method keeps the source image unchanged and returns the - /// the result of image process as new image. - /// - void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle, Rectangle sourceRectangle) where TPackedVector : IPackedVector; - } -} diff --git a/src/ImageProcessorCore - Copy/IO/BigEndianBitConverter.cs b/src/ImageProcessorCore - Copy/IO/BigEndianBitConverter.cs deleted file mode 100644 index 6c624c6a6b..0000000000 --- a/src/ImageProcessorCore - Copy/IO/BigEndianBitConverter.cs +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.IO -{ - /// - /// Implementation of EndianBitConverter which converts to/from big-endian - /// byte arrays. - /// - /// Adapted from Miscellaneous Utility Library - /// This product includes software developed by Jon Skeet and Marc Gravell. Contact , or see - /// . - /// - /// - internal sealed class BigEndianBitConverter : EndianBitConverter - { - /// - public override Endianness Endianness => Endianness.BigEndian; - - /// - public override bool IsLittleEndian() => false; - - /// - protected internal override void CopyBytesImpl(long value, int bytes, byte[] buffer, int index) - { - int endOffset = index + bytes - 1; - for (int i = 0; i < bytes; i++) - { - buffer[endOffset - i] = unchecked((byte)(value & 0xff)); - value = value >> 8; - } - } - - /// - protected internal override long FromBytes(byte[] buffer, int startIndex, int bytesToConvert) - { - long ret = 0; - for (int i = 0; i < bytesToConvert; i++) - { - ret = unchecked((ret << 8) | buffer[startIndex + i]); - } - - return ret; - } - } -} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/IO/EndianBinaryReader.cs b/src/ImageProcessorCore - Copy/IO/EndianBinaryReader.cs deleted file mode 100644 index e2fc14a050..0000000000 --- a/src/ImageProcessorCore - Copy/IO/EndianBinaryReader.cs +++ /dev/null @@ -1,616 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.IO -{ - using System; - using System.IO; - using System.Text; - - /// - /// Equivalent of , but with either endianness, depending on - /// the EndianBitConverter it is constructed with. No data is buffered in the - /// reader; the client may seek within the stream at will. - /// - internal class EndianBinaryReader : IDisposable - { - /// - /// Decoder to use for string conversions. - /// - private readonly Decoder decoder; - - /// - /// Buffer used for temporary storage before conversion into primitives - /// - private readonly byte[] buffer = new byte[16]; - - /// - /// Buffer used for temporary storage when reading a single character - /// - private readonly char[] charBuffer = new char[1]; - - /// - /// Minimum number of bytes used to encode a character - /// - private readonly int minBytesPerChar; - - /// - /// Whether or not this reader has been disposed yet. - /// - private bool disposed; - - /// - /// Equivalent of System.IO.BinaryWriter, but with either endianness, depending on - /// the EndianBitConverter it is constructed with. - /// - /// Converter to use when reading data - /// Stream to read data from - public EndianBinaryReader(EndianBitConverter bitConverter, Stream stream) - : this(bitConverter, stream, Encoding.UTF8) - { - } - - /// - /// Constructs a new binary reader with the given bit converter, reading - /// to the given stream, using the given encoding. - /// - /// Converter to use when reading data - /// Stream to read data from - /// Encoding to use when reading character data - public EndianBinaryReader(EndianBitConverter bitConverter, Stream stream, Encoding encoding) - { - // TODO: Use Guard - if (bitConverter == null) - { - throw new ArgumentNullException("bitConverter"); - } - - if (stream == null) - { - throw new ArgumentNullException("stream"); - } - - if (encoding == null) - { - throw new ArgumentNullException("encoding"); - } - - if (!stream.CanRead) - { - throw new ArgumentException("Stream isn't writable", "stream"); - } - - this.BaseStream = stream; - this.BitConverter = bitConverter; - this.Encoding = encoding; - this.decoder = encoding.GetDecoder(); - this.minBytesPerChar = 1; - - if (encoding is UnicodeEncoding) - { - this.minBytesPerChar = 2; - } - } - - /// - /// Gets the bit converter used to read values from the stream. - /// - public EndianBitConverter BitConverter { get; } - - /// - /// Gets the encoding used to read strings - /// - public Encoding Encoding { get; } - - /// - /// Gets the underlying stream of the EndianBinaryReader. - /// - public Stream BaseStream { get; } - - /// - /// Closes the reader, including the underlying stream. - /// - public void Close() - { - this.Dispose(); - } - - /// - /// Seeks within the stream. - /// - /// Offset to seek to. - /// Origin of seek operation. - public void Seek(int offset, SeekOrigin origin) - { - this.CheckDisposed(); - this.BaseStream.Seek(offset, origin); - } - - /// - /// Reads a single byte from the stream. - /// - /// The byte read - public byte ReadByte() - { - this.ReadInternal(this.buffer, 1); - return this.buffer[0]; - } - - /// - /// Reads a single signed byte from the stream. - /// - /// The byte read - public sbyte ReadSByte() - { - this.ReadInternal(this.buffer, 1); - return unchecked((sbyte)this.buffer[0]); - } - - /// - /// Reads a boolean from the stream. 1 byte is read. - /// - /// The boolean read - public bool ReadBoolean() - { - this.ReadInternal(this.buffer, 1); - return this.BitConverter.ToBoolean(this.buffer, 0); - } - - /// - /// Reads a 16-bit signed integer from the stream, using the bit converter - /// for this reader. 2 bytes are read. - /// - /// The 16-bit integer read - public short ReadInt16() - { - this.ReadInternal(this.buffer, 2); - return this.BitConverter.ToInt16(this.buffer, 0); - } - - /// - /// Reads a 32-bit signed integer from the stream, using the bit converter - /// for this reader. 4 bytes are read. - /// - /// The 32-bit integer read - public int ReadInt32() - { - this.ReadInternal(this.buffer, 4); - return this.BitConverter.ToInt32(this.buffer, 0); - } - - /// - /// Reads a 64-bit signed integer from the stream, using the bit converter - /// for this reader. 8 bytes are read. - /// - /// The 64-bit integer read - public long ReadInt64() - { - this.ReadInternal(this.buffer, 8); - return this.BitConverter.ToInt64(this.buffer, 0); - } - - /// - /// Reads a 16-bit unsigned integer from the stream, using the bit converter - /// for this reader. 2 bytes are read. - /// - /// The 16-bit unsigned integer read - public ushort ReadUInt16() - { - this.ReadInternal(this.buffer, 2); - return this.BitConverter.ToUInt16(this.buffer, 0); - } - - /// - /// Reads a 32-bit unsigned integer from the stream, using the bit converter - /// for this reader. 4 bytes are read. - /// - /// The 32-bit unsigned integer read - public uint ReadUInt32() - { - this.ReadInternal(this.buffer, 4); - return this.BitConverter.ToUInt32(this.buffer, 0); - } - - /// - /// Reads a 64-bit unsigned integer from the stream, using the bit converter - /// for this reader. 8 bytes are read. - /// - /// The 64-bit unsigned integer read - public ulong ReadUInt64() - { - this.ReadInternal(this.buffer, 8); - return this.BitConverter.ToUInt64(this.buffer, 0); - } - - /// - /// Reads a single-precision floating-point value from the stream, using the bit converter - /// for this reader. 4 bytes are read. - /// - /// The floating point value read - public float ReadSingle() - { - this.ReadInternal(this.buffer, 4); - return this.BitConverter.ToSingle(this.buffer, 0); - } - - /// - /// Reads a double-precision floating-point value from the stream, using the bit converter - /// for this reader. 8 bytes are read. - /// - /// The floating point value read - public double ReadDouble() - { - this.ReadInternal(this.buffer, 8); - return this.BitConverter.ToDouble(this.buffer, 0); - } - - /// - /// Reads a decimal value from the stream, using the bit converter - /// for this reader. 16 bytes are read. - /// - /// The decimal value read - public decimal ReadDecimal() - { - this.ReadInternal(this.buffer, 16); - return this.BitConverter.ToDecimal(this.buffer, 0); - } - - /// - /// Reads a single character from the stream, using the character encoding for - /// this reader. If no characters have been fully read by the time the stream ends, - /// -1 is returned. - /// - /// The character read, or -1 for end of stream. - public int Read() - { - int charsRead = this.Read(this.charBuffer, 0, 1); - if (charsRead == 0) - { - return -1; - } - else - { - return this.charBuffer[0]; - } - } - - /// - /// Reads the specified number of characters into the given buffer, starting at - /// the given index. - /// - /// The buffer to copy data into - /// The first index to copy data into - /// The number of characters to read - /// The number of characters actually read. This will only be less than - /// the requested number of characters if the end of the stream is reached. - /// - public int Read(char[] data, int index, int count) - { - this.CheckDisposed(); - - // TODO: Use Guard - if (this.buffer == null) - { - throw new ArgumentNullException("buffer"); - } - - if (index < 0) - { - throw new ArgumentOutOfRangeException("index"); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException("index"); - } - - if (count + index > data.Length) - { - throw new ArgumentException("Not enough space in buffer for specified number of characters starting at specified index"); - } - - int read = 0; - bool firstTime = true; - - // Use the normal buffer if we're only reading a small amount, otherwise - // use at most 4K at a time. - byte[] byteBuffer = this.buffer; - - if (byteBuffer.Length < count * this.minBytesPerChar) - { - byteBuffer = new byte[4096]; - } - - while (read < count) - { - int amountToRead; - - // First time through we know we haven't previously read any data - if (firstTime) - { - amountToRead = count * this.minBytesPerChar; - firstTime = false; - } - - // After that we can only assume we need to fully read 'chars left -1' characters - // and a single byte of the character we may be in the middle of - else - { - amountToRead = ((count - read - 1) * this.minBytesPerChar) + 1; - } - - if (amountToRead > byteBuffer.Length) - { - amountToRead = byteBuffer.Length; - } - - int bytesRead = this.TryReadInternal(byteBuffer, amountToRead); - if (bytesRead == 0) - { - return read; - } - - int decoded = this.decoder.GetChars(byteBuffer, 0, bytesRead, data, index); - read += decoded; - index += decoded; - } - - return read; - } - - /// - /// Reads the specified number of bytes into the given buffer, starting at - /// the given index. - /// - /// The buffer to copy data into - /// The first index to copy data into - /// The number of bytes to read - /// The number of bytes actually read. This will only be less than - /// the requested number of bytes if the end of the stream is reached. - /// - public int Read(byte[] buffer, int index, int count) - { - this.CheckDisposed(); - if (buffer == null) - { - throw new ArgumentNullException("buffer"); - } - - if (index < 0) - { - throw new ArgumentOutOfRangeException("index"); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException("index"); - } - - if (count + index > buffer.Length) - { - throw new ArgumentException("Not enough space in buffer for specified number of bytes starting at specified index"); - } - - int read = 0; - while (count > 0) - { - int block = this.BaseStream.Read(buffer, index, count); - if (block == 0) - { - return read; - } - - index += block; - read += block; - count -= block; - } - - return read; - } - - /// - /// Reads the specified number of bytes, returning them in a new byte array. - /// If not enough bytes are available before the end of the stream, this - /// method will return what is available. - /// - /// The number of bytes to read - /// The bytes read - public byte[] ReadBytes(int count) - { - this.CheckDisposed(); - if (count < 0) - { - throw new ArgumentOutOfRangeException("count"); - } - - byte[] ret = new byte[count]; - int index = 0; - while (index < count) - { - int read = this.BaseStream.Read(ret, index, count - index); - - // Stream has finished half way through. That's fine, return what we've got. - if (read == 0) - { - byte[] copy = new byte[index]; - Buffer.BlockCopy(ret, 0, copy, 0, index); - return copy; - } - - index += read; - } - - return ret; - } - - /// - /// Reads the specified number of bytes, returning them in a new byte array. - /// If not enough bytes are available before the end of the stream, this - /// method will throw an IOException. - /// - /// The number of bytes to read - /// The bytes read - public byte[] ReadBytesOrThrow(int count) - { - byte[] ret = new byte[count]; - this.ReadInternal(ret, count); - return ret; - } - - /// - /// Reads a 7-bit encoded integer from the stream. This is stored with the least significant - /// information first, with 7 bits of information per byte of value, and the top - /// bit as a continuation flag. This method is not affected by the endianness - /// of the bit converter. - /// - /// The 7-bit encoded integer read from the stream. - public int Read7BitEncodedInt() - { - this.CheckDisposed(); - - int ret = 0; - for (int shift = 0; shift < 35; shift += 7) - { - int b = this.BaseStream.ReadByte(); - if (b == -1) - { - throw new EndOfStreamException(); - } - - ret = ret | ((b & 0x7f) << shift); - if ((b & 0x80) == 0) - { - return ret; - } - } - - // Still haven't seen a byte with the high bit unset? Dodgy data. - throw new IOException("Invalid 7-bit encoded integer in stream."); - } - - /// - /// Reads a 7-bit encoded integer from the stream. This is stored with the most significant - /// information first, with 7 bits of information per byte of value, and the top - /// bit as a continuation flag. This method is not affected by the endianness - /// of the bit converter. - /// - /// The 7-bit encoded integer read from the stream. - public int ReadBigEndian7BitEncodedInt() - { - this.CheckDisposed(); - - int ret = 0; - for (int i = 0; i < 5; i++) - { - int b = this.BaseStream.ReadByte(); - if (b == -1) - { - throw new EndOfStreamException(); - } - - ret = (ret << 7) | (b & 0x7f); - if ((b & 0x80) == 0) - { - return ret; - } - } - - // Still haven't seen a byte with the high bit unset? Dodgy data. - throw new IOException("Invalid 7-bit encoded integer in stream."); - } - - /// - /// Reads a length-prefixed string from the stream, using the encoding for this reader. - /// A 7-bit encoded integer is first read, which specifies the number of bytes - /// to read from the stream. These bytes are then converted into a string with - /// the encoding for this reader. - /// - /// The string read from the stream. - public string ReadString() - { - int bytesToRead = this.Read7BitEncodedInt(); - - byte[] data = new byte[bytesToRead]; - this.ReadInternal(data, bytesToRead); - return this.Encoding.GetString(data, 0, data.Length); - } - - /// - /// Disposes of the underlying stream. - /// - public void Dispose() - { - if (!this.disposed) - { - this.disposed = true; - ((IDisposable)this.BaseStream).Dispose(); - } - } - - /// - /// Checks whether or not the reader has been disposed, throwing an exception if so. - /// - private void CheckDisposed() - { - if (this.disposed) - { - throw new ObjectDisposedException("EndianBinaryReader"); - } - } - - /// - /// Reads the given number of bytes from the stream, throwing an exception - /// if they can't all be read. - /// - /// Buffer to read into - /// Number of bytes to read - private void ReadInternal(byte[] data, int size) - { - this.CheckDisposed(); - int index = 0; - while (index < size) - { - int read = this.BaseStream.Read(data, index, size - index); - if (read == 0) - { - throw new EndOfStreamException - ( - string.Format( - "End of stream reached with {0} byte{1} left to read.", - size - index, - size - index == 1 ? "s" : string.Empty)); - } - - index += read; - } - } - - /// - /// Reads the given number of bytes from the stream if possible, returning - /// the number of bytes actually read, which may be less than requested if - /// (and only if) the end of the stream is reached. - /// - /// Buffer to read into - /// Number of bytes to read - /// Number of bytes actually read - private int TryReadInternal(byte[] data, int size) - { - this.CheckDisposed(); - int index = 0; - while (index < size) - { - int read = this.BaseStream.Read(data, index, size - index); - if (read == 0) - { - return index; - } - - index += read; - } - - return index; - } - } -} diff --git a/src/ImageProcessorCore - Copy/IO/EndianBinaryWriter.cs b/src/ImageProcessorCore - Copy/IO/EndianBinaryWriter.cs deleted file mode 100644 index 0f37b9a13d..0000000000 --- a/src/ImageProcessorCore - Copy/IO/EndianBinaryWriter.cs +++ /dev/null @@ -1,385 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.IO -{ - using System; - using System.IO; - using System.Text; - - /// - /// Equivalent of , but with either endianness, depending on - /// the it is constructed with. - /// - internal class EndianBinaryWriter : IDisposable - { - /// - /// Buffer used for temporary storage during conversion from primitives - /// - private readonly byte[] buffer = new byte[16]; - - /// - /// Buffer used for Write(char) - /// - private readonly char[] charBuffer = new char[1]; - - /// - /// Whether or not this writer has been disposed yet. - /// - private bool disposed; - - /// - /// Initializes a new instance of the class - /// with the given bit converter, writing to the given stream, using UTF-8 encoding. - /// - /// Converter to use when writing data - /// Stream to write data to - public EndianBinaryWriter(EndianBitConverter bitConverter, Stream stream) - : this(bitConverter, stream, Encoding.UTF8) - { - } - - /// - /// Initializes a new instance of the class - /// with the given bit converter, writing to the given stream, using the given encoding. - /// - /// Converter to use when writing data - /// Stream to write data to - /// - /// Encoding to use when writing character data - /// - public EndianBinaryWriter(EndianBitConverter bitConverter, Stream stream, Encoding encoding) - { - // TODO: Use Guard - if (bitConverter == null) - { - throw new ArgumentNullException("bitConverter"); - } - - if (stream == null) - { - throw new ArgumentNullException("stream"); - } - - if (encoding == null) - { - throw new ArgumentNullException("encoding"); - } - - if (!stream.CanWrite) - { - throw new ArgumentException("Stream isn't writable", "stream"); - } - - this.BaseStream = stream; - this.BitConverter = bitConverter; - this.Encoding = encoding; - } - - /// - /// Gets the bit converter used to write values to the stream - /// - public EndianBitConverter BitConverter { get; } - - /// - /// Gets the encoding used to write strings - /// - public Encoding Encoding { get; } - - /// - /// Gets the underlying stream of the EndianBinaryWriter. - /// - public Stream BaseStream { get; } - - /// - /// Closes the writer, including the underlying stream. - /// - public void Close() - { - this.Dispose(); - } - - /// - /// Flushes the underlying stream. - /// - public void Flush() - { - this.CheckDisposed(); - this.BaseStream.Flush(); - } - - /// - /// Seeks within the stream. - /// - /// Offset to seek to. - /// Origin of seek operation. - public void Seek(int offset, SeekOrigin origin) - { - this.CheckDisposed(); - this.BaseStream.Seek(offset, origin); - } - - /// - /// Writes a boolean value to the stream. 1 byte is written. - /// - /// The value to write - public void Write(bool value) - { - this.BitConverter.CopyBytes(value, this.buffer, 0); - this.WriteInternal(this.buffer, 1); - } - - /// - /// Writes a 16-bit signed integer to the stream, using the bit converter - /// for this writer. 2 bytes are written. - /// - /// The value to write - public void Write(short value) - { - this.BitConverter.CopyBytes(value, this.buffer, 0); - this.WriteInternal(this.buffer, 2); - } - - /// - /// Writes a 32-bit signed integer to the stream, using the bit converter - /// for this writer. 4 bytes are written. - /// - /// The value to write - public void Write(int value) - { - this.BitConverter.CopyBytes(value, this.buffer, 0); - this.WriteInternal(this.buffer, 4); - } - - /// - /// Writes a 64-bit signed integer to the stream, using the bit converter - /// for this writer. 8 bytes are written. - /// - /// The value to write - public void Write(long value) - { - this.BitConverter.CopyBytes(value, this.buffer, 0); - this.WriteInternal(this.buffer, 8); - } - - /// - /// Writes a 16-bit unsigned integer to the stream, using the bit converter - /// for this writer. 2 bytes are written. - /// - /// The value to write - public void Write(ushort value) - { - this.BitConverter.CopyBytes(value, this.buffer, 0); - this.WriteInternal(this.buffer, 2); - } - - /// - /// Writes a 32-bit unsigned integer to the stream, using the bit converter - /// for this writer. 4 bytes are written. - /// - /// The value to write - public void Write(uint value) - { - this.BitConverter.CopyBytes(value, this.buffer, 0); - this.WriteInternal(this.buffer, 4); - } - - /// - /// Writes a 64-bit unsigned integer to the stream, using the bit converter - /// for this writer. 8 bytes are written. - /// - /// The value to write - public void Write(ulong value) - { - this.BitConverter.CopyBytes(value, this.buffer, 0); - this.WriteInternal(this.buffer, 8); - } - - /// - /// Writes a single-precision floating-point value to the stream, using the bit converter - /// for this writer. 4 bytes are written. - /// - /// The value to write - public void Write(float value) - { - this.BitConverter.CopyBytes(value, this.buffer, 0); - this.WriteInternal(this.buffer, 4); - } - - /// - /// Writes a double-precision floating-point value to the stream, using the bit converter - /// for this writer. 8 bytes are written. - /// - /// The value to write - public void Write(double value) - { - this.BitConverter.CopyBytes(value, this.buffer, 0); - this.WriteInternal(this.buffer, 8); - } - - /// - /// Writes a decimal value to the stream, using the bit converter for this writer. - /// 16 bytes are written. - /// - /// The value to write - public void Write(decimal value) - { - this.BitConverter.CopyBytes(value, this.buffer, 0); - this.WriteInternal(this.buffer, 16); - } - - /// - /// Writes a signed byte to the stream. - /// - /// The value to write - public void Write(byte value) - { - this.buffer[0] = value; - this.WriteInternal(this.buffer, 1); - } - - /// - /// Writes an unsigned byte to the stream. - /// - /// The value to write - public void Write(sbyte value) - { - this.buffer[0] = unchecked((byte)value); - this.WriteInternal(this.buffer, 1); - } - - /// - /// Writes an array of bytes to the stream. - /// - /// The values to write - public void Write(byte[] value) - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - this.WriteInternal(value, value.Length); - } - - /// - /// Writes a portion of an array of bytes to the stream. - /// - /// An array containing the bytes to write - /// The index of the first byte to write within the array - /// The number of bytes to write - public void Write(byte[] value, int offset, int count) - { - this.CheckDisposed(); - this.BaseStream.Write(value, offset, count); - } - - /// - /// Writes a single character to the stream, using the encoding for this writer. - /// - /// The value to write - public void Write(char value) - { - this.charBuffer[0] = value; - this.Write(this.charBuffer); - } - - /// - /// Writes an array of characters to the stream, using the encoding for this writer. - /// - /// An array containing the characters to write - public void Write(char[] value) - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - this.CheckDisposed(); - byte[] data = this.Encoding.GetBytes(value, 0, value.Length); - this.WriteInternal(data, data.Length); - } - - /// - /// Writes a string to the stream, using the encoding for this writer. - /// - /// The value to write. Must not be null. - /// value is null - public void Write(string value) - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - this.CheckDisposed(); - byte[] data = this.Encoding.GetBytes(value); - this.Write7BitEncodedInt(data.Length); - this.WriteInternal(data, data.Length); - } - - /// - /// Writes a 7-bit encoded integer from the stream. This is stored with the least significant - /// information first, with 7 bits of information per byte of value, and the top - /// bit as a continuation flag. - /// - /// The 7-bit encoded integer to write to the stream - public void Write7BitEncodedInt(int value) - { - this.CheckDisposed(); - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value), "Value must be greater than or equal to 0."); - } - - int index = 0; - while (value >= 128) - { - this.buffer[index++] = (byte)((value & 0x7f) | 0x80); - value = value >> 7; - index++; - } - - this.buffer[index++] = (byte)value; - this.BaseStream.Write(this.buffer, 0, index); - } - - /// - /// Checks whether or not the writer has been disposed, throwing an exception if so. - /// - private void CheckDisposed() - { - if (this.disposed) - { - throw new ObjectDisposedException("EndianBinaryWriter"); - } - } - - /// - /// Writes the specified number of bytes from the start of the given byte array, - /// after checking whether or not the writer has been disposed. - /// - /// The array of bytes to write from - /// The number of bytes to write - private void WriteInternal(byte[] bytes, int length) - { - this.CheckDisposed(); - this.BaseStream.Write(bytes, 0, length); - } - - /// - /// Disposes of the underlying stream. - /// - public void Dispose() - { - if (!this.disposed) - { - this.Flush(); - this.disposed = true; - ((IDisposable)this.BaseStream).Dispose(); - } - } - } -} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/IO/EndianBitConverter.cs b/src/ImageProcessorCore - Copy/IO/EndianBitConverter.cs deleted file mode 100644 index d95527002b..0000000000 --- a/src/ImageProcessorCore - Copy/IO/EndianBitConverter.cs +++ /dev/null @@ -1,724 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.IO -{ - using System; - using System.Diagnostics.CodeAnalysis; - using System.Runtime.InteropServices; - - /// - /// Equivalent of , but with either endianness. - /// - /// Adapted from Miscellaneous Utility Library - /// This product includes software developed by Jon Skeet and Marc Gravell. Contact , or see - /// . - /// - /// - [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:ElementsMustAppearInTheCorrectOrder", Justification = "Reviewed. Suppression is OK here. Better readability.")] - [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:ElementsMustBeOrderedByAccess", Justification = "Reviewed. Suppression is OK here. Better readability.")] - [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204:StaticElementsMustAppearBeforeInstanceElements", Justification = "Reviewed. Suppression is OK here. Better readability.")] - internal abstract class EndianBitConverter - { - #region Endianness of this converter - /// - /// Indicates the byte order ("endianness") in which data is converted using this class. - /// - /// - /// Different computer architectures store data using different byte orders. "Big-endian" - /// means the most significant byte is on the left end of a word. "Little-endian" means the - /// most significant byte is on the right end of a word. - /// - /// true if this converter is little-endian, false otherwise. - public abstract bool IsLittleEndian(); - - /// - /// Gets the byte order ("endianness") in which data is converted using this class. - /// - public abstract Endianness Endianness { get; } - #endregion - - #region Factory properties - /// - /// The little-endian bit converter. - /// - private static readonly LittleEndianBitConverter LittleConverter = new LittleEndianBitConverter(); - - /// - /// Gets a little-endian bit converter instance. The same instance is - /// always returned. - /// - public static LittleEndianBitConverter Little => LittleConverter; - - /// - /// The big-endian bit converter. - /// - private static readonly BigEndianBitConverter BigConverter = new BigEndianBitConverter(); - - /// - /// Gets a big-endian bit converter instance. The same instance is - /// always returned. - /// - public static BigEndianBitConverter Big => BigConverter; - #endregion - - #region Double/primitive conversions - /// - /// Converts the specified double-precision floating point number to a - /// 64-bit signed integer. Note: the endianness of this converter does not - /// affect the returned value. - /// - /// The number to convert. - /// A 64-bit signed integer whose value is equivalent to value. - public long DoubleToInt64Bits(double value) - { - return BitConverter.DoubleToInt64Bits(value); - } - - /// - /// Converts the specified 64-bit signed integer to a double-precision - /// floating point number. Note: the endianness of this converter does not - /// affect the returned value. - /// - /// The number to convert. - /// A double-precision floating point number whose value is equivalent to value. - public double Int64BitsToDouble(long value) - { - return BitConverter.Int64BitsToDouble(value); - } - - /// - /// Converts the specified single-precision floating point number to a - /// 32-bit signed integer. Note: the endianness of this converter does not - /// affect the returned value. - /// - /// The number to convert. - /// A 32-bit signed integer whose value is equivalent to value. - public int SingleToInt32Bits(float value) - { - return new Int32SingleUnion(value).AsInt32; - } - - /// - /// Converts the specified 32-bit signed integer to a single-precision floating point - /// number. Note: the endianness of this converter does not - /// affect the returned value. - /// - /// The number to convert. - /// A single-precision floating point number whose value is equivalent to value. - public float Int32BitsToSingle(int value) - { - return new Int32SingleUnion(value).AsSingle; - } - #endregion - - #region To(PrimitiveType) conversions - /// - /// Returns a Boolean value converted from one byte at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// true if the byte at startIndex in value is nonzero; otherwise, false. - public bool ToBoolean(byte[] value, int startIndex) - { - CheckByteArgument(value, startIndex, 1); - return BitConverter.ToBoolean(value, startIndex); - } - - /// - /// Returns a Unicode character converted from two bytes at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// A character formed by two bytes beginning at startIndex. - public char ToChar(byte[] value, int startIndex) - { - return unchecked((char)this.CheckedFromBytes(value, startIndex, 2)); - } - - /// - /// Returns a double-precision floating point number converted from eight bytes - /// at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// A double precision floating point number formed by eight bytes beginning at startIndex. - public double ToDouble(byte[] value, int startIndex) - { - return this.Int64BitsToDouble(this.ToInt64(value, startIndex)); - } - - /// - /// Returns a single-precision floating point number converted from four bytes - /// at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// A single precision floating point number formed by four bytes beginning at startIndex. - public float ToSingle(byte[] value, int startIndex) - { - return this.Int32BitsToSingle(this.ToInt32(value, startIndex)); - } - - /// - /// Returns a 16-bit signed integer converted from two bytes at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// A 16-bit signed integer formed by two bytes beginning at startIndex. - public short ToInt16(byte[] value, int startIndex) - { - return unchecked((short)this.CheckedFromBytes(value, startIndex, 2)); - } - - /// - /// Returns a 32-bit signed integer converted from four bytes at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// A 32-bit signed integer formed by four bytes beginning at startIndex. - public int ToInt32(byte[] value, int startIndex) - { - return unchecked((int)this.CheckedFromBytes(value, startIndex, 4)); - } - - /// - /// Returns a 64-bit signed integer converted from eight bytes at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// A 64-bit signed integer formed by eight bytes beginning at startIndex. - public long ToInt64(byte[] value, int startIndex) - { - return this.CheckedFromBytes(value, startIndex, 8); - } - - /// - /// Returns a 16-bit unsigned integer converted from two bytes at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// A 16-bit unsigned integer formed by two bytes beginning at startIndex. - public ushort ToUInt16(byte[] value, int startIndex) - { - return unchecked((ushort)this.CheckedFromBytes(value, startIndex, 2)); - } - - /// - /// Returns a 32-bit unsigned integer converted from four bytes at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// A 32-bit unsigned integer formed by four bytes beginning at startIndex. - public uint ToUInt32(byte[] value, int startIndex) - { - return unchecked((uint)this.CheckedFromBytes(value, startIndex, 4)); - } - - /// - /// Returns a 64-bit unsigned integer converted from eight bytes at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// A 64-bit unsigned integer formed by eight bytes beginning at startIndex. - public ulong ToUInt64(byte[] value, int startIndex) - { - return unchecked((ulong)this.CheckedFromBytes(value, startIndex, 8)); - } - - /// - /// Convert the given number of bytes from the given array, from the given start - /// position, into a long, using the bytes as the least significant part of the long. - /// By the time this is called, the arguments have been checked for validity. - /// - /// The bytes to convert - /// The index of the first byte to convert - /// The number of bytes to use in the conversion - /// The converted number - protected internal abstract long FromBytes(byte[] value, int startIndex, int bytesToConvert); - - /// - /// Checks the given argument for validity. - /// - /// The byte array passed in - /// The start index passed in - /// The number of bytes required - /// value is a null reference - /// - /// startIndex is less than zero or greater than the length of value minus bytesRequired. - /// - [SuppressMessage("ReSharper", "UnusedParameter.Local", Justification = "Keeps code DRY")] - private static void CheckByteArgument(byte[] value, int startIndex, int bytesRequired) - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - if (startIndex < 0 || startIndex > value.Length - bytesRequired) - { - throw new ArgumentOutOfRangeException(nameof(startIndex)); - } - } - - /// - /// Checks the arguments for validity before calling FromBytes - /// (which can therefore assume the arguments are valid). - /// - /// The bytes to convert after checking - /// The index of the first byte to convert - /// The number of bytes to convert - /// The - private long CheckedFromBytes(byte[] value, int startIndex, int bytesToConvert) - { - CheckByteArgument(value, startIndex, bytesToConvert); - return this.FromBytes(value, startIndex, bytesToConvert); - } - #endregion - - #region ToString conversions - /// - /// Returns a String converted from the elements of a byte array. - /// - /// An array of bytes. - /// All the elements of value are converted. - /// - /// A String of hexadecimal pairs separated by hyphens, where each pair - /// represents the corresponding element in value; for example, "7F-2C-4A". - /// - public static string ToString(byte[] value) - { - return BitConverter.ToString(value); - } - - /// - /// Returns a String converted from the elements of a byte array starting at a specified array position. - /// - /// An array of bytes. - /// The starting position within value. - /// The elements from array position startIndex to the end of the array are converted. - /// - /// A String of hexadecimal pairs separated by hyphens, where each pair - /// represents the corresponding element in value; for example, "7F-2C-4A". - /// - public static string ToString(byte[] value, int startIndex) - { - return BitConverter.ToString(value, startIndex); - } - - /// - /// Returns a String converted from a specified number of bytes at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// The number of bytes to convert. - /// The length elements from array position startIndex are converted. - /// - /// A String of hexadecimal pairs separated by hyphens, where each pair - /// represents the corresponding element in value; for example, "7F-2C-4A". - /// - public static string ToString(byte[] value, int startIndex, int length) - { - return BitConverter.ToString(value, startIndex, length); - } - #endregion - - #region Decimal conversions - /// - /// Returns a decimal value converted from sixteen bytes - /// at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// A decimal formed by sixteen bytes beginning at startIndex. - public decimal ToDecimal(byte[] value, int startIndex) - { - // HACK: This always assumes four parts, each in their own endianness, - // starting with the first part at the start of the byte array. - // On the other hand, there's no real format specified... - int[] parts = new int[4]; - for (int i = 0; i < 4; i++) - { - parts[i] = this.ToInt32(value, startIndex + (i * 4)); - } - - return new decimal(parts); - } - - /// - /// Returns the specified decimal value as an array of bytes. - /// - /// The number to convert. - /// An array of bytes with length 16. - public byte[] GetBytes(decimal value) - { - byte[] bytes = new byte[16]; - int[] parts = decimal.GetBits(value); - for (int i = 0; i < 4; i++) - { - this.CopyBytesImpl(parts[i], 4, bytes, i * 4); - } - - return bytes; - } - - /// - /// Copies the specified decimal value into the specified byte array, - /// beginning at the specified index. - /// - /// A character to convert. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(decimal value, byte[] buffer, int index) - { - int[] parts = decimal.GetBits(value); - for (int i = 0; i < 4; i++) - { - this.CopyBytesImpl(parts[i], 4, buffer, (i * 4) + index); - } - } - #endregion - - #region GetBytes conversions - /// - /// Returns an array with the given number of bytes formed - /// from the least significant bytes of the specified value. - /// This is used to implement the other GetBytes methods. - /// - /// The value to get bytes for - /// The number of significant bytes to return - /// - /// The . - /// - private byte[] GetBytes(long value, int bytes) - { - byte[] buffer = new byte[bytes]; - this.CopyBytes(value, bytes, buffer, 0); - return buffer; - } - - /// - /// Returns the specified Boolean value as an array of bytes. - /// - /// A Boolean value. - /// An array of bytes with length 1. - /// - /// The . - /// - public byte[] GetBytes(bool value) - { - return BitConverter.GetBytes(value); - } - - /// - /// Returns the specified Unicode character value as an array of bytes. - /// - /// A character to convert. - /// An array of bytes with length 2. - /// - /// The . - /// - public byte[] GetBytes(char value) - { - return this.GetBytes(value, 2); - } - - /// - /// Returns the specified double-precision floating point value as an array of bytes. - /// - /// The number to convert. - /// An array of bytes with length 8. - public byte[] GetBytes(double value) - { - return this.GetBytes(this.DoubleToInt64Bits(value), 8); - } - - /// - /// Returns the specified 16-bit signed integer value as an array of bytes. - /// - /// The number to convert. - /// An array of bytes with length 2. - public byte[] GetBytes(short value) - { - return this.GetBytes(value, 2); - } - - /// - /// Returns the specified 32-bit signed integer value as an array of bytes. - /// - /// The number to convert. - /// An array of bytes with length 4. - public byte[] GetBytes(int value) - { - return this.GetBytes(value, 4); - } - - /// - /// Returns the specified 64-bit signed integer value as an array of bytes. - /// - /// The number to convert. - /// An array of bytes with length 8. - public byte[] GetBytes(long value) - { - return this.GetBytes(value, 8); - } - - /// - /// Returns the specified single-precision floating point value as an array of bytes. - /// - /// The number to convert. - /// An array of bytes with length 4. - public byte[] GetBytes(float value) - { - return this.GetBytes(this.SingleToInt32Bits(value), 4); - } - - /// - /// Returns the specified 16-bit unsigned integer value as an array of bytes. - /// - /// The number to convert. - /// An array of bytes with length 2. - public byte[] GetBytes(ushort value) - { - return this.GetBytes(value, 2); - } - - /// - /// Returns the specified 32-bit unsigned integer value as an array of bytes. - /// - /// The number to convert. - /// An array of bytes with length 4. - public byte[] GetBytes(uint value) - { - return this.GetBytes(value, 4); - } - - /// - /// Returns the specified 64-bit unsigned integer value as an array of bytes. - /// - /// The number to convert. - /// An array of bytes with length 8. - public byte[] GetBytes(ulong value) - { - return this.GetBytes(unchecked((long)value), 8); - } - - #endregion - - #region CopyBytes conversions - /// - /// Copies the given number of bytes from the least-specific - /// end of the specified value into the specified byte array, beginning - /// at the specified index. - /// This is used to implement the other CopyBytes methods. - /// - /// The value to copy bytes for - /// The number of significant bytes to copy - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - private void CopyBytes(long value, int bytes, byte[] buffer, int index) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer), "Byte array must not be null"); - } - - if (buffer.Length < index + bytes) - { - throw new ArgumentOutOfRangeException(nameof(buffer), "Buffer not big enough for value"); - } - - this.CopyBytesImpl(value, bytes, buffer, index); - } - - /// - /// Copies the given number of bytes from the least-specific - /// end of the specified value into the specified byte array, beginning - /// at the specified index. - /// This must be implemented in concrete derived classes, but the implementation - /// may assume that the value will fit into the buffer. - /// - /// The value to copy bytes for - /// The number of significant bytes to copy - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - protected internal abstract void CopyBytesImpl(long value, int bytes, byte[] buffer, int index); - - /// - /// Copies the specified Boolean value into the specified byte array, - /// beginning at the specified index. - /// - /// A Boolean value. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(bool value, byte[] buffer, int index) - { - this.CopyBytes(value ? 1 : 0, 1, buffer, index); - } - - /// - /// Copies the specified Unicode character value into the specified byte array, - /// beginning at the specified index. - /// - /// A character to convert. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(char value, byte[] buffer, int index) - { - this.CopyBytes(value, 2, buffer, index); - } - - /// - /// Copies the specified double-precision floating point value into the specified byte array, - /// beginning at the specified index. - /// - /// The number to convert. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(double value, byte[] buffer, int index) - { - this.CopyBytes(this.DoubleToInt64Bits(value), 8, buffer, index); - } - - /// - /// Copies the specified 16-bit signed integer value into the specified byte array, - /// beginning at the specified index. - /// - /// The number to convert. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(short value, byte[] buffer, int index) - { - this.CopyBytes(value, 2, buffer, index); - } - - /// - /// Copies the specified 32-bit signed integer value into the specified byte array, - /// beginning at the specified index. - /// - /// The number to convert. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(int value, byte[] buffer, int index) - { - this.CopyBytes(value, 4, buffer, index); - } - - /// - /// Copies the specified 64-bit signed integer value into the specified byte array, - /// beginning at the specified index. - /// - /// The number to convert. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(long value, byte[] buffer, int index) - { - this.CopyBytes(value, 8, buffer, index); - } - - /// - /// Copies the specified single-precision floating point value into the specified byte array, - /// beginning at the specified index. - /// - /// The number to convert. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(float value, byte[] buffer, int index) - { - this.CopyBytes(this.SingleToInt32Bits(value), 4, buffer, index); - } - - /// - /// Copies the specified 16-bit unsigned integer value into the specified byte array, - /// beginning at the specified index. - /// - /// The number to convert. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(ushort value, byte[] buffer, int index) - { - this.CopyBytes(value, 2, buffer, index); - } - - /// - /// Copies the specified 32-bit unsigned integer value into the specified byte array, - /// beginning at the specified index. - /// - /// The number to convert. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(uint value, byte[] buffer, int index) - { - this.CopyBytes(value, 4, buffer, index); - } - - /// - /// Copies the specified 64-bit unsigned integer value into the specified byte array, - /// beginning at the specified index. - /// - /// The number to convert. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(ulong value, byte[] buffer, int index) - { - this.CopyBytes(unchecked((long)value), 8, buffer, index); - } - - #endregion - - #region Private struct used for Single/Int32 conversions - /// - /// Union used solely for the equivalent of DoubleToInt64Bits and vice versa. - /// - [StructLayout(LayoutKind.Explicit)] - private struct Int32SingleUnion - { - /// - /// Int32 version of the value. - /// - [FieldOffset(0)] - private readonly int i; - - /// - /// Single version of the value. - /// - [FieldOffset(0)] - private readonly float f; - - /// - /// Initializes a new instance of the struct. - /// - /// The integer value of the new instance. - internal Int32SingleUnion(int i) - { - this.f = 0; // Just to keep the compiler happy - this.i = i; - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The floating point value of the new instance. - /// - internal Int32SingleUnion(float f) - { - this.i = 0; // Just to keep the compiler happy - this.f = f; - } - - /// - /// Gets the value of the instance as an integer. - /// - internal int AsInt32 => this.i; - - /// - /// Gets the value of the instance as a floating point number. - /// - internal float AsSingle => this.f; - } - #endregion - } -} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/IO/Endianness.cs b/src/ImageProcessorCore - Copy/IO/Endianness.cs deleted file mode 100644 index e8e4ef4c2d..0000000000 --- a/src/ImageProcessorCore - Copy/IO/Endianness.cs +++ /dev/null @@ -1,23 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.IO -{ - /// - /// Endianness of a converter - /// - internal enum Endianness - { - /// - /// Little endian - least significant byte first - /// - LittleEndian, - - /// - /// Big endian - most significant byte first - /// - BigEndian - } -} diff --git a/src/ImageProcessorCore - Copy/IO/LittleEndianBitConverter.cs b/src/ImageProcessorCore - Copy/IO/LittleEndianBitConverter.cs deleted file mode 100644 index 70e65d9091..0000000000 --- a/src/ImageProcessorCore - Copy/IO/LittleEndianBitConverter.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.IO -{ - /// - /// Implementation of EndianBitConverter which converts to/from little-endian - /// byte arrays. - /// - /// Adapted from Miscellaneous Utility Library - /// This product includes software developed by Jon Skeet and Marc Gravell. Contact , or see - /// . - /// - /// - internal sealed class LittleEndianBitConverter : EndianBitConverter - { - /// - public override Endianness Endianness => Endianness.LittleEndian; - - /// - public override bool IsLittleEndian() => true; - - /// - protected internal override void CopyBytesImpl(long value, int bytes, byte[] buffer, int index) - { - for (int i = 0; i < bytes; i++) - { - buffer[i + index] = unchecked((byte)(value & 0xff)); - value = value >> 8; - } - } - - /// - protected internal override long FromBytes(byte[] buffer, int startIndex, int bytesToConvert) - { - long ret = 0; - for (int i = 0; i < bytesToConvert; i++) - { - ret = unchecked((ret << 8) | buffer[startIndex + bytesToConvert - 1 - i]); - } - - return ret; - } - } -} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/Image.cs b/src/ImageProcessorCore - Copy/Image.cs deleted file mode 100644 index c8dc861d6a..0000000000 --- a/src/ImageProcessorCore - Copy/Image.cs +++ /dev/null @@ -1,291 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System.IO; - using System.Text; - - using System; - using System.Collections.Generic; - using System.Linq; - - using Formats; - - /// - /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. - /// - /// - /// The packed vector containing pixel information. - /// - public class Image : ImageBase - where TPackedVector : IPackedVector - { - /// - /// The default horizontal resolution value (dots per inch) in x direction. - /// The default value is 96 dots per inch. - /// - public const double DefaultHorizontalResolution = 96; - - /// - /// The default vertical resolution value (dots per inch) in y direction. - /// The default value is 96 dots per inch. - /// - public const double DefaultVerticalResolution = 96; - - /// - /// Initializes a new instance of the class. - /// - public Image() - { - this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat)); - } - - /// - /// Initializes a new instance of the class - /// with the height and the width of the image. - /// - /// The width of the image in pixels. - /// The height of the image in pixels. - public Image(int width, int height) - : base(width, height) - { - this.CurrentImageFormat = Bootstrapper.Instance.ImageFormats.First(f => f.GetType() == typeof(PngFormat)); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The stream containing image information. - /// - /// Thrown if the is null. - public Image(Stream stream) - { - Guard.NotNull(stream, nameof(stream)); - this.Load(stream); - } - - /// - /// Initializes a new instance of the class - /// by making a copy from another image. - /// - /// The other image, where the clone should be made from. - /// is null. - public Image(Image other) - { - foreach (ImageFrame frame in other.Frames) - { - if (frame != null) - { - this.Frames.Add(new ImageFrame(frame)); - } - } - - this.RepeatCount = other.RepeatCount; - this.HorizontalResolution = other.HorizontalResolution; - this.VerticalResolution = other.VerticalResolution; - this.CurrentImageFormat = other.CurrentImageFormat; - } - - /// - /// Gets a list of supported image formats. - /// - public IReadOnlyCollection Formats { get; } = Bootstrapper.Instance.ImageFormats; - - /// - /// Gets or sets the resolution of the image in x- direction. It is defined as - /// number of dots per inch and should be an positive value. - /// - /// The density of the image in x- direction. - public double HorizontalResolution { get; set; } = DefaultHorizontalResolution; - - /// - /// Gets or sets the resolution of the image in y- direction. It is defined as - /// number of dots per inch and should be an positive value. - /// - /// The density of the image in y- direction. - public double VerticalResolution { get; set; } = DefaultVerticalResolution; - - /// - /// Gets the width of the image in inches. It is calculated as the width of the image - /// in pixels multiplied with the density. When the density is equals or less than zero - /// the default value is used. - /// - /// The width of the image in inches. - public double InchWidth - { - get - { - double resolution = this.HorizontalResolution; - - if (resolution <= 0) - { - resolution = DefaultHorizontalResolution; - } - - return this.Width / resolution; - } - } - - /// - /// Gets the height of the image in inches. It is calculated as the height of the image - /// in pixels multiplied with the density. When the density is equals or less than zero - /// the default value is used. - /// - /// The height of the image in inches. - public double InchHeight - { - get - { - double resolution = this.VerticalResolution; - - if (resolution <= 0) - { - resolution = DefaultVerticalResolution; - } - - return this.Height / resolution; - } - } - - /// - /// Gets a value indicating whether this image is animated. - /// - /// - /// True if this image is animated; otherwise, false. - /// - public bool IsAnimated => this.Frames.Count > 0; - - /// - /// Gets or sets the number of times any animation is repeated. - /// 0 means to repeat indefinitely. - /// - public ushort RepeatCount { get; set; } - - /// - /// Gets the other frames for the animation. - /// - /// The list of frame images. - public IList> Frames { get; } = new List>(); - - /// - /// Gets the list of properties for storing meta information about this image. - /// - /// A list of image properties. - public IList Properties { get; } = new List(); - - /// - /// Gets the currently loaded image format. - /// - public IImageFormat CurrentImageFormat { get; internal set; } - - /// - public override IPixelAccessor Lock() - { - return Bootstrapper.Instance.GetPixelAccessor(this); - } - - /// - /// Saves the image to the given stream using the currently loaded image format. - /// - /// The stream to save the image to. - /// Thrown if the stream is null. - public void Save(Stream stream) - { - Guard.NotNull(stream, nameof(stream)); - this.CurrentImageFormat.Encoder.Encode(this, stream); - } - - /// - /// Saves the image to the given stream using the given image format. - /// - /// The stream to save the image to. - /// The format to save the image as. - /// Thrown if the stream is null. - public void Save(Stream stream, IImageFormat format) - { - Guard.NotNull(stream, nameof(stream)); - format.Encoder.Encode(this, stream); - } - - /// - /// Saves the image to the given stream using the given image encoder. - /// - /// The stream to save the image to. - /// The encoder to save the image with. - /// Thrown if the stream is null. - public void Save(Stream stream, IImageEncoder encoder) - { - Guard.NotNull(stream, nameof(stream)); - encoder.Encode(this, stream); - } - - /// - /// Returns a Base64 encoded string from the given image. - /// - /// data:image/gif;base64,R0lGODlhAQABAIABAEdJRgAAACwAAAAAAQABAAACAkQBAA== - /// The - public override string ToString() - { - using (MemoryStream stream = new MemoryStream()) - { - this.Save(stream); - stream.Flush(); - return $"data:{this.CurrentImageFormat.Encoder.MimeType};base64,{Convert.ToBase64String(stream.ToArray())}"; - } - } - - /// - /// Loads the image from the given stream. - /// - /// The stream containing image information. - /// - /// Thrown if the stream is not readable nor seekable. - /// - private void Load(Stream stream) - { - if (!this.Formats.Any()) { return; } - - if (!stream.CanRead) - { - throw new NotSupportedException("Cannot read from the stream."); - } - - if (!stream.CanSeek) - { - throw new NotSupportedException("The stream does not support seeking."); - } - - int maxHeaderSize = this.Formats.Max(x => x.Decoder.HeaderSize); - if (maxHeaderSize > 0) - { - byte[] header = new byte[maxHeaderSize]; - - stream.Position = 0; - stream.Read(header, 0, maxHeaderSize); - stream.Position = 0; - - IImageFormat format = this.Formats.FirstOrDefault(x => x.Decoder.IsSupportedFileFormat(header)); - if (format != null) - { - format.Decoder.Decode(this, stream); - this.CurrentImageFormat = format; - return; - } - } - - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.AppendLine("Image cannot be loaded. Available formats:"); - - foreach (IImageFormat format in this.Formats) - { - stringBuilder.AppendLine("-" + format); - } - - throw new NotSupportedException(stringBuilder.ToString()); - } - } -} diff --git a/src/ImageProcessorCore - Copy/ImageBase.cs b/src/ImageProcessorCore - Copy/ImageBase.cs deleted file mode 100644 index 5fa83df4c7..0000000000 --- a/src/ImageProcessorCore - Copy/ImageBase.cs +++ /dev/null @@ -1,201 +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 - /// in different pixel formats. - /// - /// - /// The packed vector pixels format. - /// - public abstract class ImageBase : IImageBase - where TPacked : IPackedVector - { - /// - /// Initializes a new instance of the class. - /// - protected ImageBase() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The width of the image in pixels. - /// The height of the image in pixels. - /// - /// Thrown if either or are less than or equal to 0. - /// - protected ImageBase(int width, int height) - { - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - this.Width = width; - this.Height = height; - this.Pixels = new TPacked[width * height]; - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The other to create this instance from. - /// - /// - /// Thrown if the given is null. - /// - protected ImageBase(ImageBase other) - { - Guard.NotNull(other, nameof(other), "Other image cannot be null."); - - this.Width = other.Width; - this.Height = other.Height; - this.Quality = other.Quality; - this.FrameDelay = other.FrameDelay; - - // Copy the pixels. - this.Pixels = new TPacked[this.Width * this.Height]; - Array.Copy(other.Pixels, this.Pixels, other.Pixels.Length); - } - - /// - /// Gets or sets the maximum allowable width in pixels. - /// - public int MaxWidth { get; set; } = int.MaxValue; - - /// - /// Gets or sets the maximum allowable height in pixels. - /// - public int MaxHeight { get; set; } = int.MaxValue; - - /// - /// Gets the pixels as an array of the given packed pixel format. - /// - public TPacked[] Pixels { get; private set; } - - /// - /// Gets the width in pixels. - /// - public int Width { get; private set; } - - /// - /// Gets the height in pixels. - /// - public int Height { get; private set; } - - /// - /// Gets the pixel ratio made up of the width and height. - /// - public double PixelRatio => (double)this.Width / this.Height; - - /// - /// Gets the representing the bounds of the image. - /// - public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height); - - /// - /// Gets or sets th quality of the image. This affects the output quality of lossy image formats. - /// - public int Quality { get; set; } - - /// - /// Gets or sets the frame delay for animated images. - /// If not 0, this field specifies the number of hundredths (1/100) of a second to - /// wait before continuing with the processing of the Data Stream. - /// The clock starts ticking immediately after the graphic is rendered. - /// - public int FrameDelay { get; set; } - - /// - /// Sets the pixel array of the image to the given value. - /// - /// The new width of the image. Must be greater than zero. - /// The new height of the image. Must be greater than zero. - /// - /// The array with colors. Must be a multiple of the width and height. - /// - /// - /// Thrown if either or are less than or equal to 0. - /// - /// - /// Thrown if the length is not equal to Width * Height. - /// - public void SetPixels(int width, int height, TPacked[] pixels) - { - if (width <= 0) - { - throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than or equals than zero."); - } - - if (height <= 0) - { - throw new ArgumentOutOfRangeException(nameof(height), "Height must be greater than or equal than zero."); - } - - if (pixels.Length != width * height) - { - throw new ArgumentException("Pixel array must have the length of Width * Height."); - } - - this.Width = width; - this.Height = height; - this.Pixels = pixels; - } - - /// - /// Sets the pixel array of the image to the given value, creating a copy of - /// the original pixels. - /// - /// The new width of the image. Must be greater than zero. - /// The new height of the image. Must be greater than zero. - /// - /// The array with colors. Must be a multiple of four times the width and height. - /// - /// - /// Thrown if either or are less than or equal to 0. - /// - /// - /// Thrown if the length is not equal to Width * Height. - /// - public void ClonePixels(int width, int height, TPacked[] pixels) - { - if (width <= 0) - { - throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than or equals than zero."); - } - - if (height <= 0) - { - throw new ArgumentOutOfRangeException(nameof(height), "Height must be greater than or equal than zero."); - } - - if (pixels.Length != width * height) - { - throw new ArgumentException("Pixel array must have the length of Width * Height."); - } - - this.Width = width; - this.Height = height; - - // Copy the pixels. - this.Pixels = new TPacked[pixels.Length]; - Array.Copy(pixels, this.Pixels, pixels.Length); - } - - /// - /// Locks the image providing access to the pixels. - /// - /// It is imperative that the accessor is correctly disposed off after use. - /// - /// - /// The - public abstract IPixelAccessor Lock(); - } -} diff --git a/src/ImageProcessorCore - Copy/ImageExtensions.cs b/src/ImageProcessorCore - Copy/ImageExtensions.cs deleted file mode 100644 index ea320aad4d..0000000000 --- a/src/ImageProcessorCore - Copy/ImageExtensions.cs +++ /dev/null @@ -1,167 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - using System.IO; - - using Formats; - using Processors; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Saves the image to the given stream with the bmp format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// Thrown if the stream is null. - public static void SaveAsBmp(this ImageBase source, Stream stream) => new BmpEncoder().Encode(source, stream); - - /// - /// Saves the image to the given stream with the png format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The quality to save the image to representing the number of colors. - /// Anything equal to 256 and below will cause the encoder to save the image in an indexed format. - /// - /// Thrown if the stream is null. - public static void SaveAsPng(this ImageBase source, Stream stream, int quality = Int32.MaxValue) => new PngEncoder { Quality = quality }.Encode(source, stream); - - /// - /// Saves the image to the given stream with the jpeg format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The quality to save the image to. Between 1 and 100. - /// Thrown if the stream is null. - public static void SaveAsJpeg(this ImageBase source, Stream stream, int quality = 75) => new JpegEncoder { Quality = quality }.Encode(source, stream); - - /// - /// Saves the image to the given stream with the gif format. - /// - /// The image this method extends. - /// The stream to save the image to. - /// The quality to save the image to representing the number of colors. Between 1 and 256. - /// Thrown if the stream is null. - public static void SaveAsGif(this ImageBase source, Stream stream, int quality = 256) => new GifEncoder { Quality = quality }.Encode(source, stream); - - /// - /// Applies the collection of processors to the image. - /// This method does not resize the target image. - /// - /// The image this method extends. - /// The processor to apply to the image. - /// The . - public static Image Process(this Image source, IImageProcessor processor) - { - return Process(source, source.Bounds, processor); - } - - /// - /// Applies the collection of processors to the image. - /// This method does not resize the target image. - /// - /// The image this method extends. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The processors to apply to the image. - /// The . - public static Image Process(this Image source, Rectangle sourceRectangle, IImageProcessor processor) - { - return PerformAction(source, true, (sourceImage, targetImage) => processor.Apply(targetImage, sourceImage, sourceRectangle)); - } - - /// - /// Applies the collection of processors to the image. - /// - /// This method is not chainable. - /// - /// - /// The source image. Cannot be null. - /// The target image width. - /// The target image height. - /// The processor to apply to the image. - /// The . - public static Image Process(this Image source, int width, int height, IImageSampler sampler) - { - return Process(source, width, height, source.Bounds, default(Rectangle), sampler); - } - - /// - /// Applies the collection of processors to the image. - /// - /// This method does will resize the target image if the source and target rectangles are different. - /// - /// - /// The source image. Cannot be null. - /// The target image width. - /// The target image height. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// - /// The structure that specifies the location and size of the drawn image. - /// The image is scaled to fit the rectangle. - /// - /// The processor to apply to the image. - /// The . - public static Image Process(this Image source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler) - { - return PerformAction(source, false, (sourceImage, targetImage) => sampler.Apply(targetImage, sourceImage, width, height, targetRectangle, sourceRectangle)); - } - - /// - /// Performs the given action on the source image. - /// - /// The image to perform the action against. - /// Whether to clone the image. - /// The to perform against the image. - /// The . - /// Thrown if the has been disposed. - private static Image PerformAction(Image source, bool clone, Action action) - { - Image transformedImage = clone - ? new Image(source) - : new Image - { - // Several properties require copying - // TODO: Check why we need to set these? - Quality = source.Quality, - HorizontalResolution = source.HorizontalResolution, - VerticalResolution = source.VerticalResolution, - CurrentImageFormat = source.CurrentImageFormat, - RepeatCount = source.RepeatCount - }; - - action(source, transformedImage); - - for (int i = 0; i < source.Frames.Count; i++) - { - ImageFrame sourceFrame = source.Frames[i]; - ImageFrame tranformedFrame = clone ? new ImageFrame(sourceFrame) : new ImageFrame { FrameDelay = sourceFrame.FrameDelay }; - action(sourceFrame, tranformedFrame); - - if (!clone) - { - transformedImage.Frames.Add(tranformedFrame); - } - else - { - transformedImage.Frames[i] = tranformedFrame; - } - } - - source = transformedImage; - return source; - } - } -} diff --git a/src/ImageProcessorCore - Copy/ImageFrame.cs b/src/ImageProcessorCore - Copy/ImageFrame.cs deleted file mode 100644 index 479d9dd9b0..0000000000 --- a/src/ImageProcessorCore - Copy/ImageFrame.cs +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - /// - /// Represents a single frame in a animation. - /// - /// - /// The packed vector containing pixel information. - /// - public class ImageFrame : ImageBase - where TPackedVector : IPackedVector - { - /// - /// Initializes a new instance of the class. - /// - /// - /// The frame to create the frame from. - /// - public ImageFrame(ImageFrame frame) - : base(frame) - { - } - - /// - public override IPixelAccessor Lock() - { - return Bootstrapper.Instance.GetPixelAccessor(this); - } - } -} diff --git a/src/ImageProcessorCore - Copy/ImageProcessor.cs b/src/ImageProcessorCore - Copy/ImageProcessor.cs deleted file mode 100644 index e7057a3f9c..0000000000 --- a/src/ImageProcessorCore - Copy/ImageProcessor.cs +++ /dev/null @@ -1,163 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System; - using System.Threading; - - /// - /// Allows the application of processors to images. - /// - public abstract class ImageProcessor : IImageProcessor - { - /// - public event ProgressEventHandler OnProgress; - - /// - /// The number of rows processed by a derived class. - /// - private int numRowsProcessed; - - /// - /// The total number of rows that will be processed by a derived class. - /// - private int totalRows; - - /// - public void Apply(ImageBase target, ImageBase source, Rectangle sourceRectangle) - where TPackedVector : IPackedVector - { - try - { - this.OnApply(target, source, target.Bounds, sourceRectangle); - - this.numRowsProcessed = 0; - this.totalRows = sourceRectangle.Height; - - this.Apply(target, source, target.Bounds, sourceRectangle, sourceRectangle.Y, sourceRectangle.Bottom); - - this.AfterApply(target, source, target.Bounds, sourceRectangle); - } - catch (Exception ex) - { - - throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); - } - } - - /// - public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) - where TPackedVector : IPackedVector - { - try - { - TPackedVector[] pixels = new TPackedVector[width * height]; - target.SetPixels(width, height, pixels); - - // Ensure we always have bounds. - if (sourceRectangle == Rectangle.Empty) - { - sourceRectangle = source.Bounds; - } - - if (targetRectangle == Rectangle.Empty) - { - targetRectangle = target.Bounds; - } - - this.OnApply(target, source, targetRectangle, sourceRectangle); - - this.numRowsProcessed = 0; - this.totalRows = targetRectangle.Height; - - this.Apply(target, source, targetRectangle, sourceRectangle, targetRectangle.Y, targetRectangle.Bottom); - - this.AfterApply(target, source, target.Bounds, sourceRectangle); - } - catch (Exception ex) - { - throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); - } - } - - /// - /// This method is called before the process is applied to prepare the processor. - /// - /// Target image to apply the process to. - /// The source image. Cannot be null. - /// - /// The structure that specifies the location and size of the drawn image. - /// The image is scaled to fit the rectangle. - /// - /// - /// The structure that specifies the portion of the image object to draw. - /// - protected virtual void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - where TPackedVector : IPackedVector - { - } - - /// - /// Applies the process to the specified portion of the specified at the specified location - /// and with the specified size. - /// - /// The type of pixels contained within the image. - /// Target image to apply the process to. - /// The source image. Cannot be null. - /// - /// The structure that specifies the location and size of the drawn image. - /// The image is scaled to fit the rectangle. - /// - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The index of the row within the source image to start processing. - /// The index of the row within the source image to end processing. - /// - /// The method keeps the source image unchanged and returns the - /// the result of image process as new image. - /// - protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) where TPackedVector : IPackedVector; - - /// - /// This method is called after the process is applied to prepare the processor. - /// - /// The type of pixels contained within the image. - /// Target image to apply the process to. - /// The source image. Cannot be null. - /// - /// The structure that specifies the location and size of the drawn image. - /// The image is scaled to fit the rectangle. - /// - /// - /// The structure that specifies the portion of the image object to draw. - /// - protected virtual void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - where TPackedVector : IPackedVector - { - } - - /// - /// Must be called by derived classes after processing a single row. - /// - protected void OnRowProcessed() - { - if (this.OnProgress != null) - { - int currThreadNumRows = Interlocked.Add(ref this.numRowsProcessed, 1); - - // Multi-pass filters process multiple times more rows than totalRows, so update totalRows on the fly - if (currThreadNumRows > this.totalRows) - { - this.totalRows = currThreadNumRows; - } - - // Report progress. This may be on the client's thread, or on a Task library thread. - this.OnProgress(this, new ProgressEventArgs { RowsProcessed = currThreadNumRows, TotalRows = this.totalRows }); - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/ImageProcessorCore.xproj b/src/ImageProcessorCore - Copy/ImageProcessorCore.xproj deleted file mode 100644 index ffe5b1cea0..0000000000 --- a/src/ImageProcessorCore - Copy/ImageProcessorCore.xproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - 2aa31a1f-142c-43f4-8687-09abca4b3a26 - ImageProcessorCore - .\obj - .\bin\ - v4.5.1 - - - 2.0 - - - True - - - \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/ImageProperty.cs b/src/ImageProcessorCore - Copy/ImageProperty.cs deleted file mode 100644 index 1d8f5bb8ef..0000000000 --- a/src/ImageProcessorCore - Copy/ImageProperty.cs +++ /dev/null @@ -1,153 +0,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 -{ - using System; - - /// - /// Stores meta information about a image, like the name of the author, - /// the copyright information, the date, where the image was created - /// or some other information. - /// - public struct ImageProperty : IEquatable - { - /// - /// Initializes a new instance of the struct. - /// - /// - /// The name of the property. - /// - /// - /// The value of the property. - /// - public ImageProperty(string name, string value) - { - this.Name = name; - this.Value = value; - } - - /// - /// Gets the name of this indicating which kind of - /// information this property stores. - /// - /// - /// Typical properties are the author, copyright - /// information or other meta information. - /// - public string Name { get; } - - /// - /// The value of this . - /// - public string Value { get; } - - /// - /// Compares two objects. The result specifies whether the values - /// of the or properties of the two - /// objects are equal. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(ImageProperty left, ImageProperty right) - { - return left.Equals(right); - } - - /// - /// Compares two objects. The result specifies whether the values - /// of the or properties of the two - /// objects are unequal. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(ImageProperty left, ImageProperty right) - { - return !left.Equals(right); - } - - /// - /// Indicates whether this instance and a specified object are equal. - /// - /// - /// The object to compare with the current instance. - /// - /// - /// true if and this instance are the same type and represent the - /// same value; otherwise, false. - /// - public override bool Equals(object obj) - { - if (!(obj is ImageProperty)) - { - return false; - } - - ImageProperty other = (ImageProperty)obj; - - return other.Name == this.Name && other.Value == this.Value; - } - - /// - /// Returns the hash code for this instance. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// - public override int GetHashCode() - { - unchecked - { - int hashCode = this.Name.GetHashCode(); - hashCode = (hashCode * 397) ^ this.Value.GetHashCode(); - return hashCode; - } - } - - /// - /// Returns the fully qualified type name of this instance. - /// - /// - /// A containing a fully qualified type name. - /// - public override string ToString() - { - return $"ImageProperty [ Name={this.Name}, Value={this.Value} ]"; - } - - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// - /// True if the current object is equal to the parameter; otherwise, false. - /// - /// An object to compare with this object. - public bool Equals(ImageProperty other) - { - return this.Name.Equals(other.Name) && this.Value.Equals(other.Value); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Numerics/Ellipse.cs b/src/ImageProcessorCore - Copy/Numerics/Ellipse.cs deleted file mode 100644 index 5d87d1247b..0000000000 --- a/src/ImageProcessorCore - Copy/Numerics/Ellipse.cs +++ /dev/null @@ -1,174 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - using System.ComponentModel; - using System.Numerics; - - public struct Ellipse : IEquatable - { - /// - /// The center point. - /// - private Point center; - - /// - /// Represents a that has X and Y values set to zero. - /// - public static readonly Ellipse Empty = default(Ellipse); - - public Ellipse(Point center, float radiusX, float radiusY) - { - this.center = center; - this.RadiusX = radiusX; - this.RadiusY = radiusY; - } - - /// - /// Gets the x-radius of this . - /// - public float RadiusX { get; } - - /// - /// Gets the y-radius of this . - /// - public float RadiusY { get; } - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(Ellipse left, Ellipse right) - { - return left.Equals(right); - } - - /// - /// Compares two objects for inequality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(Ellipse left, Ellipse right) - { - return !left.Equals(right); - } - - /// - /// Returns the center point of the given - /// - /// The ellipse - /// - public static Vector2 Center(Ellipse ellipse) - { - return new Vector2(ellipse.center.X, ellipse.center.Y); - } - - /// - /// Determines if the specfied point is contained within the rectangular region defined by - /// this . - /// - /// The x-coordinate of the given point. - /// The y-coordinate of the given point. - /// The - public bool Contains(int x, int y) - { - if (this.RadiusX <= 0 || this.RadiusY <= 0) - { - return false; - } - - // TODO: SIMD? - // This is a more general form of the circle equation - // X^2/a^2 + Y^2/b^2 <= 1 - Point normalized = new Point(x - this.center.X, y - this.center.Y); - int nX = normalized.X; - int nY = normalized.Y; - - return (double)(nX * nX) / (this.RadiusX * this.RadiusX) - + (double)(nY * nY) / (this.RadiusY * this.RadiusY) - <= 1.0; - } - - /// - public override int GetHashCode() - { - return this.GetHashCode(this); - } - - /// - public override string ToString() - { - if (this.IsEmpty) - { - return "Ellipse [ Empty ]"; - } - - return - $"Ellipse [ RadiusX={this.RadiusX}, RadiusY={this.RadiusX}, Centre={this.center.X},{this.center.Y} ]"; - } - - /// - public override bool Equals(object obj) - { - if (obj is Ellipse) - { - return this.Equals((Ellipse)obj); - } - - return false; - } - - /// - public bool Equals(Ellipse other) - { - return this.center.Equals(other.center) - && this.RadiusX.Equals(other.RadiusX) - && this.RadiusY.Equals(other.RadiusY); - } - - /// - /// Returns the hash code for this instance. - /// - /// - /// The instance of to return the hash code for. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// - private int GetHashCode(Ellipse ellipse) - { - unchecked - { - int hashCode = ellipse.center.GetHashCode(); - hashCode = (hashCode * 397) ^ ellipse.RadiusX.GetHashCode(); - hashCode = (hashCode * 397) ^ ellipse.RadiusY.GetHashCode(); - return hashCode; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Numerics/Point.cs b/src/ImageProcessorCore - Copy/Numerics/Point.cs deleted file mode 100644 index 818002f9ef..0000000000 --- a/src/ImageProcessorCore - Copy/Numerics/Point.cs +++ /dev/null @@ -1,281 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// -namespace ImageProcessorCore -{ - using System; - using System.ComponentModel; - using System.Numerics; - - /// - /// Represents an ordered pair of integer x- and y-coordinates that defines a point in - /// a two-dimensional plane. - /// - /// - /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, - /// as it avoids the need to create new values for modification operations. - /// - public struct Point : IEquatable - { - /// - /// Represents a that has X and Y values set to zero. - /// - public static readonly Point Empty = default(Point); - - /// - /// The backing vector for SIMD support. - /// - private Vector2 backingVector; - - /// - /// Initializes a new instance of the struct. - /// - /// The horizontal position of the point. - /// The vertical position of the point. - public Point(int x, int y) - : this() - { - this.backingVector = new Vector2(x, y); - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector representing the width and height. - /// - public Point(Vector2 vector) - { - this.backingVector = new Vector2(vector.X, vector.Y); - } - - /// - /// The x-coordinate of this . - /// - public int X - { - get - { - return (int)this.backingVector.X; - } - - set - { - this.backingVector.X = value; - } - } - - /// - /// The y-coordinate of this . - /// - public int Y - { - get - { - return (int)this.backingVector.Y; - } - - set - { - this.backingVector.Y = value; - } - } - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Computes the sum of adding two points. - /// - /// The point on the left hand of the operand. - /// The point on the right hand of the operand. - /// - /// The - /// - public static Point operator +(Point left, Point right) - { - return new Point(left.backingVector + right.backingVector); - } - - /// - /// Computes the difference left by subtracting one point from another. - /// - /// The point on the left hand of the operand. - /// The point on the right hand of the operand. - /// - /// The - /// - public static Point operator -(Point left, Point right) - { - return new Point(left.backingVector - right.backingVector); - } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(Point left, Point right) - { - return left.Equals(right); - } - - /// - /// Compares two objects for inequality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(Point left, Point right) - { - return !left.Equals(right); - } - - /// - /// Gets a representation for this . - /// - /// A representation for this object. - public Vector2 ToVector2() - { - return new Vector2(this.X, this.Y); - } - - /// - /// Creates a rotation matrix for the given point and angle. - /// - /// The origin point to rotate around - /// Rotation in degrees - /// The rotation - public static Matrix3x2 CreateRotation(Point origin, float degrees) - { - float radians = ImageMaths.DegreesToRadians(degrees); - return Matrix3x2.CreateRotation(radians, origin.backingVector); - } - - /// - /// Rotates a point around a given a rotation matrix. - /// - /// The point to rotate - /// Rotation matrix used - /// The rotated - public static Point Rotate(Point point, Matrix3x2 rotation) - { - return new Point(Vector2.Transform(point.backingVector, rotation)); - } - - /// - /// Rotates a point around a given origin by the specified angle in degrees. - /// - /// The point to rotate - /// The center point to rotate around. - /// The angle in degrees. - /// The rotated - public static Point Rotate(Point point, Point origin, float degrees) - { - return new Point(Vector2.Transform(point.backingVector, CreateRotation(origin, degrees))); - } - - /// - /// Creates a skew matrix for the given point and angle. - /// - /// The origin point to rotate around - /// The x-angle in degrees. - /// The y-angle in degrees. - /// The rotation - public static Matrix3x2 CreateSkew(Point origin, float degreesX, float degreesY) - { - float radiansX = ImageMaths.DegreesToRadians(degreesX); - float radiansY = ImageMaths.DegreesToRadians(degreesY); - return Matrix3x2.CreateSkew(radiansX, radiansY, origin.backingVector); - } - - /// - /// Skews a point using a given a skew matrix. - /// - /// The point to rotate - /// Rotation matrix used - /// The rotated - public static Point Skew(Point point, Matrix3x2 skew) - { - return new Point(Vector2.Transform(point.backingVector, skew)); - } - - /// - /// Skews a point around a given origin by the specified angles in degrees. - /// - /// The point to skew. - /// The center point to rotate around. - /// The x-angle in degrees. - /// The y-angle in degrees. - /// The skewed - public static Point Skew(Point point, Point origin, float degreesX, float degreesY) - { - return new Point(Vector2.Transform(point.backingVector, CreateSkew(origin, degreesX, degreesY))); - } - - /// - public override int GetHashCode() - { - return this.GetHashCode(this); - } - - /// - public override string ToString() - { - if (this.IsEmpty) - { - return "Point [ Empty ]"; - } - - return $"Point [ X={this.X}, Y={this.Y} ]"; - } - - /// - public override bool Equals(object obj) - { - if (obj is Point) - { - return this.Equals((Point)obj); - } - - return false; - } - - /// - public bool Equals(Point other) - { - return this.backingVector.Equals(other.backingVector); - } - - /// - /// Returns the hash code for this instance. - /// - /// - /// The instance of to return the hash code for. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// - private int GetHashCode(Point point) - { - return point.backingVector.GetHashCode(); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Numerics/Rectangle.cs b/src/ImageProcessorCore - Copy/Numerics/Rectangle.cs deleted file mode 100644 index 6fe2c3a497..0000000000 --- a/src/ImageProcessorCore - Copy/Numerics/Rectangle.cs +++ /dev/null @@ -1,291 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - using System.ComponentModel; - using System.Numerics; - - /// - /// Stores a set of four integers that represent the location and size of a rectangle. - /// - /// - /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, - /// as it avoids the need to create new values for modification operations. - /// - public struct Rectangle : IEquatable - { - /// - /// Represents a that has X, Y, Width, and Height values set to zero. - /// - public static readonly Rectangle Empty = default(Rectangle); - - /// - /// The backing vector for SIMD support. - /// - private Vector4 backingVector; - - /// - /// Initializes a new instance of the struct. - /// - /// The horizontal position of the rectangle. - /// The vertical position of the rectangle. - /// The width of the rectangle. - /// The height of the rectangle. - public Rectangle(int x, int y, int width, int height) - { - this.backingVector = new Vector4(x, y, width, height); - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The which specifies the rectangles point in a two-dimensional plane. - /// - /// - /// The which specifies the rectangles height and width. - /// - public Rectangle(Point point, Size size) - { - this.backingVector = new Vector4(point.X, point.Y, size.Width, size.Height); - } - - /// - /// Initializes a new instance of the struct. - /// - /// The vector. - public Rectangle(Vector4 vector) - { - this.backingVector = vector; - } - - /// - /// The x-coordinate of this . - /// - public int X - { - get - { - return (int)this.backingVector.X; - } - - set - { - this.backingVector.X = value; - } - } - - /// - /// The y-coordinate of this . - /// - public int Y - { - get - { - return (int)this.backingVector.Y; - } - - set - { - this.backingVector.Y = value; - } - } - - /// - /// The width of this . - /// - public int Width - { - get - { - return (int)this.backingVector.Z; - } - - set - { - this.backingVector.Z = value; - } - } - - /// - /// The height of this . - /// - public int Height - { - get - { - return (int)this.backingVector.W; - } - - set - { - this.backingVector.W = value; - } - } - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Gets the y-coordinate of the top edge of this . - /// - public int Top => this.Y; - - /// - /// Gets the x-coordinate of the right edge of this . - /// - public int Right => this.X + this.Width; - - /// - /// Gets the y-coordinate of the bottom edge of this . - /// - public int Bottom => this.Y + this.Height; - - /// - /// Gets the x-coordinate of the left edge of this . - /// - public int Left => this.X; - - /// - /// Computes the sum of adding two rectangles. - /// - /// The rectangle on the left hand of the operand. - /// The rectangle on the right hand of the operand. - /// - /// The - /// - public static Rectangle operator +(Rectangle left, Rectangle right) - { - return new Rectangle(left.backingVector + right.backingVector); - } - - /// - /// Computes the difference left by subtracting one rectangle from another. - /// - /// The rectangle on the left hand of the operand. - /// The rectangle on the right hand of the operand. - /// - /// The - /// - public static Rectangle operator -(Rectangle left, Rectangle right) - { - return new Rectangle(left.backingVector - right.backingVector); - } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(Rectangle left, Rectangle right) - { - return left.Equals(right); - } - - /// - /// Compares two objects for inequality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(Rectangle left, Rectangle right) - { - return !left.Equals(right); - } - - /// - /// Determines if the specfied point is contained within the rectangular region defined by - /// this . - /// - /// The x-coordinate of the given point. - /// The y-coordinate of the given point. - /// The - public bool Contains(int x, int y) - { - // TODO: SIMD? - return this.X <= x - && x < this.Right - && this.Y <= y - && y < this.Bottom; - } - - /// - /// Returns the center point of the given - /// - /// The rectangle - /// - public static Point Center(Rectangle rectangle) - { - return new Point(rectangle.Left + rectangle.Width / 2, rectangle.Top + rectangle.Height / 2); - } - - /// - public override int GetHashCode() - { - return this.GetHashCode(this); - } - - /// - public override string ToString() - { - if (this.IsEmpty) - { - return "Rectangle [ Empty ]"; - } - - return - $"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; - } - - /// - public override bool Equals(object obj) - { - if (obj is Rectangle) - { - return this.Equals((Rectangle)obj); - } - - return false; - } - - /// - public bool Equals(Rectangle other) - { - return this.backingVector.Equals(other.backingVector); - } - - /// - /// Returns the hash code for this instance. - /// - /// - /// The instance of to return the hash code for. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// - private int GetHashCode(Rectangle rectangle) - { - return rectangle.backingVector.GetHashCode(); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Numerics/Size.cs b/src/ImageProcessorCore - Copy/Numerics/Size.cs deleted file mode 100644 index 4b416b2182..0000000000 --- a/src/ImageProcessorCore - Copy/Numerics/Size.cs +++ /dev/null @@ -1,208 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - using System.ComponentModel; - using System.Numerics; - - /// - /// Stores an ordered pair of integers, which specify a height and width. - /// - /// - /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, - /// as it avoids the need to create new values for modification operations. - /// - public struct Size : IEquatable - { - /// - /// Represents a that has Width and Height values set to zero. - /// - public static readonly Size Empty = default(Size); - - /// - /// The backing vector for SIMD support. - /// - private Vector2 backingVector; - - /// - /// Initializes a new instance of the struct. - /// - /// The width of the size. - /// The height of the size. - public Size(int width, int height) - { - this.backingVector = new Vector2(width, height); - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector representing the width and height. - /// - public Size(Vector2 vector) - { - this.backingVector = new Vector2(vector.X, vector.Y); - } - - /// - /// The width of this . - /// - public int Width - { - get - { - return (int)this.backingVector.X; - } - - set - { - this.backingVector.X = value; - } - } - - /// - /// The height of this . - /// - public int Height - { - get - { - return (int)this.backingVector.Y; - } - - set - { - this.backingVector.Y = value; - } - } - - /// - /// Gets a value indicating whether this is empty. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); - - /// - /// Computes the sum of adding two sizes. - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// The - /// - public static Size operator +(Size left, Size right) - { - return new Size(left.backingVector + right.backingVector); - } - - /// - /// Computes the difference left by subtracting one size from another. - /// - /// The size on the left hand of the operand. - /// The size on the right hand of the operand. - /// - /// The - /// - public static Size operator -(Size left, Size right) - { - return new Size(left.backingVector - right.backingVector); - } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(Size left, Size right) - { - return left.Equals(right); - } - - /// - /// Compares two objects for inequality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(Size left, Size right) - { - return !left.Equals(right); - } - - /// - /// Gets a representation for this . - /// - /// A representation for this object. - public Vector2 ToVector2() - { - return new Vector2(this.Width, this.Height); - } - - /// - public override int GetHashCode() - { - return this.GetHashCode(this); - } - - /// - public override string ToString() - { - if (this.IsEmpty) - { - return "Size [ Empty ]"; - } - - return - $"Size [ Width={this.Width}, Height={this.Height} ]"; - } - - /// - public override bool Equals(object obj) - { - if (obj is Size) - { - return this.Equals((Size)obj); - } - - return false; - } - - /// - public bool Equals(Size other) - { - return this.backingVector.Equals(other.backingVector); - } - - /// - /// Returns the hash code for this instance. - /// - /// - /// The instance of to return the hash code for. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// - private int GetHashCode(Size size) - { - return size.backingVector.GetHashCode(); - } - } -} diff --git a/src/ImageProcessorCore - Copy/PackedVector/Bgra32.cs b/src/ImageProcessorCore - Copy/PackedVector/Bgra32.cs deleted file mode 100644 index fc6a788b08..0000000000 --- a/src/ImageProcessorCore - Copy/PackedVector/Bgra32.cs +++ /dev/null @@ -1,168 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - using System.Numerics; - - /// - /// Packed vector type containing four 8-bit unsigned normalized values ranging from 0 to 1. - /// - public struct Bgra32 : IPackedVector, IEquatable - { - /// - /// Initializes a new instance of the struct. - /// - /// The blue component. - /// The green component. - /// The red component. - /// The alpha component. - public Bgra32(float b, float g, float r, float a) - { - Vector4 clamped = Vector4.Clamp(new Vector4(b, g, r, a), Vector4.Zero, Vector4.One) * 255f; - this.PackedValue = Pack(ref clamped); - } - - /// - /// Initializes a new instance of the struct. - /// - /// - /// The vector containing the components for the packed vector. - /// - public Bgra32(Vector4 vector) - { - Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; - this.PackedValue = Pack(ref clamped); - } - - /// - public uint PackedValue { get; set; } - - /// - /// Compares two objects for equality. - /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(Bgra32 left, Bgra32 right) - { - return left.PackedValue == right.PackedValue; - } - - /// - /// Compares two objects for equality. - /// - /// The on the left side of the operand. - /// The on the right side of the operand. - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator !=(Bgra32 left, Bgra32 right) - { - return left.PackedValue != right.PackedValue; - } - - /// - public void PackVector(Vector4 vector) - { - Vector4 clamped = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * 255f; - this.PackedValue = Pack(ref clamped); - } - - /// - public void PackBytes(byte x, byte y, byte z, byte w) - { - Vector4 vector = new Vector4(x, y, z, w); - this.PackedValue = Pack(ref vector); - } - - /// - public Vector4 ToVector4() - { - return new Vector4( - this.PackedValue & 0xFF, - (this.PackedValue >> 8) & 0xFF, - (this.PackedValue >> 16) & 0xFF, - (this.PackedValue >> 24) & 0xFF) / 255f; - } - - /// - public byte[] ToBytes() - { - return new[] - { - (byte)(this.PackedValue & 0xFF), - (byte)((this.PackedValue >> 8) & 0xFF), - (byte)((this.PackedValue >> 16) & 0xFF), - (byte)((this.PackedValue >> 24) & 0xFF) - }; - } - - /// - public override bool Equals(object obj) - { - return (obj is Bgra32) && this.Equals((Bgra32)obj); - } - - /// - public bool Equals(Bgra32 other) - { - return this.PackedValue == other.PackedValue; - } - - /// - /// Gets a string representation of the packed vector. - /// - /// A string representation of the packed vector. - public override string ToString() - { - return this.ToVector4().ToString(); - } - - /// - public override int GetHashCode() - { - return this.GetHashCode(this); - } - - /// - /// Sets the packed representation from the given component values. - /// - /// - /// The vector containing the components for the packed vector. - /// - /// - /// The . - /// - private static uint Pack(ref Vector4 vector) - { - return (uint)Math.Round(vector.X) | - ((uint)Math.Round(vector.Y) << 8) | - ((uint)Math.Round(vector.Z) << 16) | - ((uint)Math.Round(vector.W) << 24); - } - - /// - /// Returns the hash code for this instance. - /// - /// - /// The instance of to return the hash code for. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// - private int GetHashCode(Bgra32 packed) - { - return packed.PackedValue.GetHashCode(); - } - } -} diff --git a/src/ImageProcessorCore - Copy/PackedVector/IPackedVector.cs b/src/ImageProcessorCore - Copy/PackedVector/IPackedVector.cs deleted file mode 100644 index 02e10cc3a5..0000000000 --- a/src/ImageProcessorCore - Copy/PackedVector/IPackedVector.cs +++ /dev/null @@ -1,61 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System.Numerics; - - /// - /// An interface that converts packed vector types to and from values, - /// allowing multiple encodings to be manipulated in a generic way. - /// - /// - /// The type of object representing the packed value. - /// - public interface IPackedVector : IPackedVector - where TPacked : struct - { - /// - /// Gets or sets the packed representation of the value. - /// Typically packed in least to greatest significance order. - /// - TPacked PackedValue { get; set; } - } - - /// - /// An interface that converts packed vector types to and from values. - /// - public interface IPackedVector - { - /// - /// Sets the packed representation from a . - /// - /// The vector to pack. - void PackVector(Vector4 vector); - - /// - /// Sets the packed representation from a . - /// - /// The x-component. - /// The y-component. - /// The z-component. - /// The w-component. - void PackBytes(byte x, byte y, byte z, byte w); - - /// - /// Expands the packed representation into a . - /// The vector components are typically expanded in least to greatest significance order. - /// - /// The . - Vector4 ToVector4(); - - /// - /// Expands the packed representation into a . - /// The bytes are typically expanded in least to greatest significance order. - /// - /// The . - byte[] ToBytes(); - } -} diff --git a/src/ImageProcessorCore - Copy/PixelAccessor/Bgra32PixelAccessor.cs b/src/ImageProcessorCore - Copy/PixelAccessor/Bgra32PixelAccessor.cs deleted file mode 100644 index cecc148b93..0000000000 --- a/src/ImageProcessorCore - Copy/PixelAccessor/Bgra32PixelAccessor.cs +++ /dev/null @@ -1,155 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - using System.Runtime.InteropServices; - - /// - /// Provides per-pixel access to an images pixels. - /// - /// - /// The image data is always stored in format, where the blue, green, red, and - /// alpha values are 8 bit unsigned bytes. - /// - public sealed unsafe class Bgra32PixelAccessor : IPixelAccessor - { - /// - /// The position of the first pixel in the bitmap. - /// - private Bgra32* pixelsBase; - - /// - /// Provides a way to access the pixels from unmanaged memory. - /// - private GCHandle pixelsHandle; - - /// - /// A value indicating whether this instance of the given entity has been disposed. - /// - /// if this instance has been disposed; otherwise, . - /// - /// If the entity is disposed, it must not be disposed a second - /// time. The isDisposed field is set the first time the entity - /// is disposed. If the isDisposed field is true, then the Dispose() - /// method will not dispose again. This help not to prolong the entity's - /// life in the Garbage Collector. - /// - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The image to provide pixel access for. - /// - public Bgra32PixelAccessor(IImageBase image) - { - Guard.NotNull(image, nameof(image)); - Guard.MustBeGreaterThan(image.Width, 0, "image width"); - Guard.MustBeGreaterThan(image.Height, 0, "image height"); - - this.Width = image.Width; - this.Height = image.Height; - - this.pixelsHandle = GCHandle.Alloc(image.Pixels, GCHandleType.Pinned); - this.pixelsBase = (Bgra32*)this.pixelsHandle.AddrOfPinnedObject().ToPointer(); - } - - /// - /// Finalizes an instance of the class. - /// - ~Bgra32PixelAccessor() - { - this.Dispose(); - } - - /// - /// Gets the width of the image. - /// - public int Width { get; } - - /// - /// Gets the height of the image. - /// - public int Height { get; } - - /// - /// Gets or sets the color of a pixel at the specified position. - /// - /// - /// The x-coordinate of the pixel. Must be greater - /// than zero and smaller than the width of the pixel. - /// - /// - /// The y-coordinate of the pixel. Must be greater - /// than zero and smaller than the width of the pixel. - /// - /// The at the specified position. - public IPackedVector this[int x, int y] - { - get - { -#if DEBUG - if ((x < 0) || (x >= this.Width)) - { - throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width."); - } - - if ((y < 0) || (y >= this.Height)) - { - throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); - } -#endif - return *(this.pixelsBase + ((y * this.Width) + x)); - } - - set - { -#if DEBUG - if ((x < 0) || (x >= this.Width)) - { - throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width."); - } - - if ((y < 0) || (y >= this.Height)) - { - throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); - } -#endif - *(this.pixelsBase + ((y * this.Width) + x)) = (Bgra32)value; - } - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - if (this.isDisposed) - { - return; - } - - if (this.pixelsHandle.IsAllocated) - { - this.pixelsHandle.Free(); - } - - this.pixelsBase = null; - - // Note disposing is done. - this.isDisposed = true; - - // This object will be cleaned up by the Dispose method. - // Therefore, you should call GC.SuppressFinalize to - // take this object off the finalization queue - // and prevent finalization code for this object - // from executing a second time. - GC.SuppressFinalize(this); - } - } -} diff --git a/src/ImageProcessorCore - Copy/PixelAccessor/IPixelAccessor.cs b/src/ImageProcessorCore - Copy/PixelAccessor/IPixelAccessor.cs deleted file mode 100644 index bf3a4067c4..0000000000 --- a/src/ImageProcessorCore - Copy/PixelAccessor/IPixelAccessor.cs +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - - /// - /// Encapsulates properties to provides per-pixel access to an images pixels. - /// - public interface IPixelAccessor : IDisposable - { - /// - /// Gets the width of the image in pixels. - /// - int Width { get; } - - /// - /// Gets the height of the image in pixels. - /// - int Height { get; } - - /// - /// Gets or sets the pixel at the specified position. - /// - /// - /// The x-coordinate of the pixel. Must be greater - /// than zero and smaller than the width of the pixel. - /// - /// - /// The y-coordinate of the pixel. Must be greater - /// than zero and smaller than the width of the pixel. - /// - /// The at the specified position. - IPackedVector this[int x, int y] - { - get; - set; - } - } -} diff --git a/src/ImageProcessorCore - Copy/ProgressEventArgs.cs b/src/ImageProcessorCore - Copy/ProgressEventArgs.cs deleted file mode 100644 index 0f4c027f5e..0000000000 --- a/src/ImageProcessorCore - Copy/ProgressEventArgs.cs +++ /dev/null @@ -1,23 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - /// - /// Contains event data related to the progress made processing an image. - /// - public class ProgressEventArgs : System.EventArgs - { - /// - /// Gets or sets the number of rows processed. - /// - public int RowsProcessed { get; set; } - - /// - /// Gets or sets the total number of rows. - /// - public int TotalRows { get; set; } - } -} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/Properties/AssemblyInfo.cs b/src/ImageProcessorCore - Copy/Properties/AssemblyInfo.cs deleted file mode 100644 index ea81ff60c6..0000000000 --- a/src/ImageProcessorCore - Copy/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Resources; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ImageProcessorCore")] -[assembly: AssemblyDescription("A cross-platform library for processing of image files written in C#")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("James Jackson-South")] -[assembly: AssemblyProduct("ImageProcessorCore")] -[assembly: AssemblyCopyright("Copyright (c) James Jackson-South and contributors.")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] - -// Ensure the internals can be tested. -[assembly: InternalsVisibleTo("ImageProcessorCore.Benchmarks")] -[assembly: InternalsVisibleTo("ImageProcessorCore.Tests")] diff --git a/src/ImageProcessorCore - Copy/Quantizers/IQuantizer.cs b/src/ImageProcessorCore - Copy/Quantizers/IQuantizer.cs deleted file mode 100644 index 3196ebf9f1..0000000000 --- a/src/ImageProcessorCore - Copy/Quantizers/IQuantizer.cs +++ /dev/null @@ -1,28 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Quantizers -{ - /// - /// Provides methods for allowing quantization of images pixels. - /// - public interface IQuantizer - { - /// - /// Gets or sets the transparency threshold. - /// - byte Threshold { get; set; } - - /// - /// Quantize an image and return the resulting output pixels. - /// - /// The image to quantize. - /// The maximum number of colors to return. - /// - /// A representing a quantized version of the image pixels. - /// - QuantizedImage Quantize(ImageBase image, int maxColors); - } -} diff --git a/src/ImageProcessorCore - Copy/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageProcessorCore - Copy/Quantizers/Octree/OctreeQuantizer.cs deleted file mode 100644 index c0c900145b..0000000000 --- a/src/ImageProcessorCore - Copy/Quantizers/Octree/OctreeQuantizer.cs +++ /dev/null @@ -1,534 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Quantizers -{ - using System; - using System.Collections.Generic; - - /// - /// Encapsulates methods to calculate the colour palette if an image using an Octree pattern. - /// - /// - public sealed class OctreeQuantizer : Quantizer - { - /// - /// Stores the tree - /// - private Octree octree; - - /// - /// Maximum allowed color depth - /// - private int colors; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree, - /// the second pass quantizes a color based on the nodes in the tree - /// - public OctreeQuantizer() - : base(false) - { - } - - /// - public override QuantizedImage Quantize(ImageBase image, int maxColors) - { - this.colors = maxColors.Clamp(1, 255); - - if (this.octree == null) - { - // Construct the Octree - this.octree = new Octree(this.GetBitsNeededForColorDepth(maxColors)); - } - - return base.Quantize(image, maxColors); - } - - /// - /// Process the pixel in the first pass of the algorithm - /// - /// - /// The pixel to quantize - /// - /// - /// This function need only be overridden if your quantize algorithm needs two passes, - /// such as an Octree quantizer. - /// - protected override void InitialQuantizePixel(Bgra32 pixel) - { - // Add the color to the Octree - this.octree.AddColor(pixel); - } - - /// - /// Override this to process the pixel in the second pass of the algorithm - /// - /// - /// The pixel to quantize - /// - /// - /// The quantized value - /// - protected override byte QuantizePixel(Bgra32 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) - { - paletteIndex = (byte)this.octree.GetPaletteIndex(pixel); - } - - return paletteIndex; - } - - /// - /// Retrieve the palette for the quantized image. - /// - /// - /// The new color palette - /// - protected override List GetPalette() - { - // First off convert the Octree to maxColors colors - List palette = this.octree.Palletize(Math.Max(this.colors, 1)); - - palette.Add(Bgra32.Empty); - this.TransparentIndex = this.colors; - - return palette; - } - - /// - /// Returns how many bits are required to store the specified number of colors. - /// Performs a Log2() on the value. - /// - /// The number of colors. - /// - /// The - /// - private int GetBitsNeededForColorDepth(int colorCount) - { - return (int)Math.Ceiling(Math.Log(colorCount, 2)); - } - - /// - /// Class which does the actual quantization - /// - private class Octree - { - /// - /// Mask used when getting the appropriate pixels for a given node - /// - private static readonly int[] Mask = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; - - /// - /// The root of the Octree - /// - private readonly OctreeNode root; - - /// - /// Array of reducible nodes - /// - private readonly OctreeNode[] reducibleNodes; - - /// - /// Maximum number of significant bits in the image - /// - private readonly int maxColorBits; - - /// - /// Store the last node quantized - /// - private OctreeNode previousNode; - - /// - /// Cache the previous color quantized - /// - private int previousColor; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The maximum number of significant bits in the image - /// - public Octree(int maxColorBits) - { - this.maxColorBits = maxColorBits; - this.Leaves = 0; - this.reducibleNodes = new OctreeNode[9]; - this.root = new OctreeNode(0, this.maxColorBits, this); - this.previousColor = 0; - this.previousNode = null; - } - - /// - /// Gets or sets the number of leaves in the tree - /// - private int Leaves { get; set; } - - /// - /// Gets the array of reducible nodes - /// - private OctreeNode[] ReducibleNodes => this.reducibleNodes; - - /// - /// Add a given color value to the Octree - /// - /// - /// The containing color information to add. - /// - public void AddColor(Bgra32 pixel) - { - // Check if this request is for the same color as the last - if (this.previousColor == pixel.Bgra) - { - // 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.root.AddColor(pixel, this.maxColorBits, 0, this); - } - else - { - // Just update the previous node - this.previousNode.Increment(pixel); - } - } - else - { - this.previousColor = pixel.Bgra; - this.root.AddColor(pixel, this.maxColorBits, 0, this); - } - } - - /// - /// Convert the nodes in the Octree to a palette with a maximum of colorCount colors - /// - /// - /// The maximum number of colors - /// - /// - /// An with the palletized colors - /// - public List Palletize(int colorCount) - { - while (this.Leaves > colorCount) - { - this.Reduce(); - } - - // Now palletize the nodes - List palette = new List(this.Leaves); - int paletteIndex = 0; - this.root.ConstructPalette(palette, ref paletteIndex); - - // And return the palette - return palette; - } - - /// - /// Get the palette index for the passed color - /// - /// - /// The containing the pixel data. - /// - /// - /// The index of the given structure. - /// - public int GetPaletteIndex(Bgra32 pixel) - { - return this.root.GetPaletteIndex(pixel, 0); - } - - /// - /// Keep track of the previous node that was quantized - /// - /// - /// The node last quantized - /// - protected void TrackPrevious(OctreeNode node) - { - this.previousNode = node; - } - - /// - /// Reduce the depth of the tree - /// - private void Reduce() - { - // Find the deepest level containing at least one reducible node - int index = this.maxColorBits - 1; - while ((index > 0) && (this.reducibleNodes[index] == null)) - { - index--; - } - - // Reduce the node most recently added to the list at level 'index' - OctreeNode node = this.reducibleNodes[index]; - this.reducibleNodes[index] = node.NextReducible; - - // Decrement the leaf count after reducing the node - this.Leaves -= node.Reduce(); - - // And just in case I've reduced the last color to be added, and the next color to - // be added is the same, invalidate the previousNode... - this.previousNode = null; - } - - /// - /// Class which encapsulates each node in the tree - /// - protected class OctreeNode - { - /// - /// Pointers to any child nodes - /// - private readonly OctreeNode[] children; - - /// - /// Flag indicating that this is a leaf node - /// - private bool leaf; - - /// - /// Number of pixels in this node - /// - private int pixelCount; - - /// - /// Red component - /// - private int red; - - /// - /// Green Component - /// - private int green; - - /// - /// Blue component - /// - private int blue; - - /// - /// The index of this node in the palette - /// - private int paletteIndex; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The level in the tree = 0 - 7 - /// - /// - /// The number of significant color bits in the image - /// - /// - /// The tree to which this node belongs - /// - public OctreeNode(int level, int colorBits, Octree octree) - { - // Construct the new node - this.leaf = level == colorBits; - - this.red = this.green = this.blue = 0; - this.pixelCount = 0; - - // If a leaf, increment the leaf count - if (this.leaf) - { - octree.Leaves++; - this.NextReducible = null; - this.children = null; - } - else - { - // Otherwise add this to the reducible nodes - this.NextReducible = octree.ReducibleNodes[level]; - octree.ReducibleNodes[level] = this; - this.children = new OctreeNode[8]; - } - } - - /// - /// Gets the next reducible node - /// - public OctreeNode NextReducible { get; } - - /// - /// 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) - { - // Update the color information if this is a leaf - if (this.leaf) - { - this.Increment(pixel); - - // Setup the previous node - octree.TrackPrevious(this); - } - else - { - // 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); - - OctreeNode child = this.children[index]; - - if (child == null) - { - // Create a new child node and store it in the array - child = new OctreeNode(level + 1, colorBits, octree); - this.children[index] = child; - } - - // Add the color to the child node - child.AddColor(pixel, colorBits, level + 1, octree); - } - } - - /// - /// Reduce this node by removing all of its children - /// - /// The number of leaves removed - public int Reduce() - { - this.red = this.green = this.blue = 0; - int childNodes = 0; - - // Loop through all children and add their information to this node - for (int index = 0; index < 8; index++) - { - if (this.children[index] != null) - { - this.red += this.children[index].red; - this.green += this.children[index].green; - this.blue += this.children[index].blue; - this.pixelCount += this.children[index].pixelCount; - ++childNodes; - this.children[index] = null; - } - } - - // Now change this to a leaf node - this.leaf = true; - - // Return the number of nodes to decrement the leaf count by - return childNodes - 1; - } - - /// - /// Traverse the tree, building up the color palette - /// - /// - /// The palette - /// - /// - /// The current palette index - /// - public void ConstructPalette(List palette, ref int index) - { - if (this.leaf) - { - // Consume the next palette index - this.paletteIndex = index++; - - byte r = (this.red / this.pixelCount).ToByte(); - byte g = (this.green / this.pixelCount).ToByte(); - byte b = (this.blue / this.pixelCount).ToByte(); - - // And set the color of the palette entry - palette.Add(new Bgra32(b, g, r)); - } - else - { - // Loop through children looking for leaves - for (int i = 0; i < 8; i++) - { - if (this.children[i] != null) - { - this.children[i].ConstructPalette(palette, ref index); - } - } - } - } - - /// - /// Return the palette index for the passed color - /// - /// - /// The representing the pixel. - /// - /// - /// The level. - /// - /// - /// The representing the index of the pixel in the palette. - /// - public int GetPaletteIndex(Bgra32 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); - - if (this.children[pixelIndex] != null) - { - index = this.children[pixelIndex].GetPaletteIndex(pixel, level + 1); - } - else - { - throw new Exception("Didn't expect this!"); - } - } - - return index; - } - - /// - /// Increment the pixel count and add to the color information - /// - /// - /// The pixel to add. - /// - public void Increment(Bgra32 pixel) - { - this.pixelCount++; - this.red += pixel.R; - this.green += pixel.G; - this.blue += pixel.B; - } - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Quantizers/Octree/Quantizer.cs b/src/ImageProcessorCore - Copy/Quantizers/Octree/Quantizer.cs deleted file mode 100644 index 40d281015c..0000000000 --- a/src/ImageProcessorCore - Copy/Quantizers/Octree/Quantizer.cs +++ /dev/null @@ -1,146 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Quantizers -{ - using System.Collections.Generic; - using System.Threading.Tasks; - - /// - /// Encapsulates methods to calculate the color palette of an image. - /// - public abstract class Quantizer : IQuantizer - { - /// - /// Flag used to indicate whether a single pass or two passes are needed for quantization. - /// - private readonly bool singlePass; - - /// - /// Initializes a new instance of the class. - /// - /// - /// If true, the quantization only needs to loop through the source pixels once - /// - /// - /// If you construct this class with a true value for singlePass, then the code will, when quantizing your image, - /// only call the 'QuantizeImage' function. If two passes are required, the code will call 'InitialQuantizeImage' - /// and then 'QuantizeImage'. - /// - protected Quantizer(bool singlePass) - { - this.singlePass = singlePass; - } - - /// - /// Gets or sets the transparency index. - /// - public int TransparentIndex { get; protected set; } = -1; - - /// - public byte Threshold { get; set; } - - /// - public virtual QuantizedImage Quantize(ImageBase image, int maxColors) - { - Guard.NotNull(image, nameof(image)); - - // Get the size of the source image - int height = image.Height; - int width = image.Width; - byte[] quantizedPixels = new byte[width * height]; - List palette; - - using (PixelAccessor pixels = image.Lock()) - { - // Call the FirstPass function if not a single pass algorithm. - // For something like an Octree quantizer, this will run through - // all image pixels, build a data structure, and create a palette. - if (!this.singlePass) - { - 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); - } - - /// - /// Execute the first pass through the pixels in the image - /// - /// 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) - { - // Loop through each row - for (int y = 0; y < height; y++) - { - // And loop through each column - for (int x = 0; x < width; x++) - { - // Now I have the pixel, call the FirstPassQuantize function... - this.InitialQuantizePixel(source[x, y]); - } - } - } - - /// - /// Execute a second pass through the bitmap - /// - /// The source image. - /// 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) - { - Parallel.For( - 0, - source.Height, - y => - { - for (int x = 0; x < source.Width; x++) - { - Bgra32 sourcePixel = source[x, y]; - output[(y * source.Width) + x] = this.QuantizePixel(sourcePixel); - } - }); - } - - /// - /// Override this to process the pixel in the first pass of the algorithm - /// - /// The pixel to quantize - /// - /// This function need only be overridden if your quantize algorithm needs two passes, - /// such as an Octree quantizer. - /// - protected virtual void InitialQuantizePixel(Bgra32 pixel) - { - } - - /// - /// Override this to process the pixel in the second pass of the algorithm - /// - /// The pixel to quantize - /// - /// The quantized value - /// - protected abstract byte QuantizePixel(Bgra32 pixel); - - /// - /// Retrieve the palette for the quantized image - /// - /// - /// The new color palette - /// - protected abstract List GetPalette(); - } -} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/Quantizers/Palette/PaletteQuantizer.cs b/src/ImageProcessorCore - Copy/Quantizers/Palette/PaletteQuantizer.cs deleted file mode 100644 index db2a4c59cb..0000000000 --- a/src/ImageProcessorCore - Copy/Quantizers/Palette/PaletteQuantizer.cs +++ /dev/null @@ -1,134 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Quantizers -{ - using System; - using System.Collections.Concurrent; - using System.Collections.Generic; - using System.Linq; - - /// - /// Encapsulates methods to create a quantized image based upon the given palette. - /// - /// - public class PaletteQuantizer : Quantizer - { - /// - /// A lookup table for colors - /// - private readonly ConcurrentDictionary colorMap = new ConcurrentDictionary(); - - /// - /// List of all colors in the palette - /// - private Bgra32[] colors; - - /// - /// 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) - : base(true) - { - if (palette == null) - { - List safe = ColorConstants.WebSafeColors.Select(c => (Bgra32)c).ToList(); - safe.Insert(0, Bgra32.Empty); - this.colors = safe.ToArray(); - } - else - { - this.colors = palette.Select(c => (Bgra32)c).ToArray(); - } - } - - /// - 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) - { - byte colorIndex = 0; - string colorHash = pixel.ToString(); - - // Check if the color is in the lookup table - if (this.colorMap.ContainsKey(colorHash)) - { - colorIndex = this.colorMap[colorHash]; - } - else - { - // 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)) - { - // 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) - { - colorIndex = (byte)index; - this.TransparentIndex = colorIndex; - break; - } - } - } - else - { - // Not transparent... - int leastDistance = int.MaxValue; - int red = pixel.R; - int green = pixel.G; - int blue = pixel.B; - - // 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; - - int distance = (redDistance * redDistance) + - (greenDistance * greenDistance) + - (blueDistance * blueDistance); - - if (distance < leastDistance) - { - colorIndex = (byte)index; - leastDistance = distance; - - // And if it's an exact match, exit the loop - if (distance == 0) - { - break; - } - } - } - } - - // Now I have the color, pop it into the cache for next time - this.colorMap.TryAdd(colorHash, colorIndex); - } - - return colorIndex; - } - - /// - protected override List GetPalette() - { - return this.colors.ToList(); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Quantizers/QuantizedImage.cs b/src/ImageProcessorCore - Copy/Quantizers/QuantizedImage.cs deleted file mode 100644 index fdf93abd33..0000000000 --- a/src/ImageProcessorCore - Copy/Quantizers/QuantizedImage.cs +++ /dev/null @@ -1,98 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Quantizers -{ - using System; - using System.Threading.Tasks; - - /// - /// Represents a quantized image where the pixels indexed by a color palette. - /// - public class QuantizedImage - { - /// - /// 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) - { - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - Guard.NotNull(palette, nameof(palette)); - Guard.NotNull(pixels, nameof(pixels)); - - if (pixels.Length != width * height) - { - throw new ArgumentException( - $"Pixel array size must be {nameof(width)} * {nameof(height)}", nameof(pixels)); - } - - this.Width = width; - this.Height = height; - this.Palette = palette; - this.Pixels = pixels; - this.TransparentIndex = transparentIndex; - } - - /// - /// Gets the width of this . - /// - public int Width { get; } - - /// - /// Gets the height of this . - /// - public int Height { get; } - - /// - /// Gets the color palette of this . - /// - public Bgra32[] Palette { get; } - - /// - /// Gets the pixels of this . - /// - public byte[] Pixels { get; } - - /// - /// Gets the transparent index - /// - public int TransparentIndex { get; } - - /// - /// Converts this quantized image to a normal image. - /// - /// - /// The - /// - public Image ToImage() - { - Image image = new Image(); - - int pixelCount = this.Pixels.Length; - int palletCount = this.Palette.Length - 1; - float[] bgraPixels = new float[pixelCount * 4]; - - Parallel.For(0, pixelCount, - 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; - }); - - image.SetPixels(this.Width, this.Height, bgraPixels); - return image; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Quantizers/Wu/Box.cs b/src/ImageProcessorCore - Copy/Quantizers/Wu/Box.cs deleted file mode 100644 index b9300b0870..0000000000 --- a/src/ImageProcessorCore - Copy/Quantizers/Wu/Box.cs +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Quantizers -{ - /// - /// Represents a box color cube. - /// - internal sealed class Box - { - /// - /// Gets or sets the min red value, exclusive. - /// - public int R0 { get; set; } - - /// - /// Gets or sets the max red value, inclusive. - /// - public int R1 { get; set; } - - /// - /// Gets or sets the min green value, exclusive. - /// - public int G0 { get; set; } - - /// - /// Gets or sets the max green value, inclusive. - /// - public int G1 { get; set; } - - /// - /// Gets or sets the min blue value, exclusive. - /// - public int B0 { get; set; } - - /// - /// Gets or sets the max blue value, inclusive. - /// - public int B1 { get; set; } - - /// - /// Gets or sets the min alpha value, exclusive. - /// - public int A0 { get; set; } - - /// - /// Gets or sets the max alpha value, inclusive. - /// - public int A1 { get; set; } - - /// - /// Gets or sets the volume. - /// - public int Volume { get; set; } - } -} diff --git a/src/ImageProcessorCore - Copy/Quantizers/Wu/WuQuantizer.cs b/src/ImageProcessorCore - Copy/Quantizers/Wu/WuQuantizer.cs deleted file mode 100644 index c06262503d..0000000000 --- a/src/ImageProcessorCore - Copy/Quantizers/Wu/WuQuantizer.cs +++ /dev/null @@ -1,786 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Quantizers -{ - using System; - using System.Collections.Generic; - using System.Threading.Tasks; - - /// - /// An implementation of Wu's color quantizer with alpha channel. - /// - /// - /// - /// Based on C Implementation of Xiaolin Wu's Color Quantizer (v. 2) - /// (see Graphics Gems volume II, pages 126-133) - /// (). - /// - /// - /// This adaptation is based on the excellent JeremyAnsel.ColorQuant by Jérémy Ansel - /// - /// - /// - /// Algorithm: Greedy orthogonal bipartition of RGB space for variance - /// minimization aided by inclusion-exclusion tricks. - /// For speed no nearest neighbor search is done. Slightly - /// better performance can be expected by more sophisticated - /// but more expensive versions. - /// - /// - public sealed class WuQuantizer : IQuantizer - { - /// - /// The epsilon for comparing floating point numbers. - /// - private const float Epsilon = 0.001f; - - /// - /// The index bits. - /// - private const int IndexBits = 6; - - /// - /// The index alpha bits. - /// - private const int IndexAlphaBits = 3; - - /// - /// The index count. - /// - private const int IndexCount = (1 << IndexBits) + 1; - - /// - /// The index alpha count. - /// - private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1; - - /// - /// The table length. - /// - private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; - - /// - /// Moment of P(c). - /// - private readonly long[] vwt; - - /// - /// Moment of r*P(c). - /// - private readonly long[] vmr; - - /// - /// Moment of g*P(c). - /// - private readonly long[] vmg; - - /// - /// Moment of b*P(c). - /// - private readonly long[] vmb; - - /// - /// Moment of a*P(c). - /// - private readonly long[] vma; - - /// - /// Moment of c^2*P(c). - /// - private readonly double[] m2; - - /// - /// Color space tag. - /// - private readonly byte[] tag; - - /// - /// Initializes a new instance of the class. - /// - public WuQuantizer() - { - this.vwt = new long[TableLength]; - this.vmr = new long[TableLength]; - this.vmg = new long[TableLength]; - this.vmb = new long[TableLength]; - this.vma = new long[TableLength]; - this.m2 = new double[TableLength]; - this.tag = new byte[TableLength]; - } - - /// - public byte Threshold { get; set; } - - /// - public QuantizedImage Quantize(ImageBase image, int maxColors) - { - Guard.NotNull(image, nameof(image)); - - int colorCount = maxColors.Clamp(1, 256); - - this.Clear(); - - using (PixelAccessor imagePixels = image.Lock()) - { - this.Build3DHistogram(imagePixels); - this.Get3DMoments(); - - Box[] cube; - this.BuildCube(out cube, ref colorCount); - - return this.GenerateResult(imagePixels, colorCount, cube); - } - } - - /// - /// Gets an index. - /// - /// The red value. - /// The green value. - /// The blue value. - /// The alpha value. - /// The index. - private static int GetPaletteIndex(int r, int g, int b, int a) - { - return (r << ((IndexBits * 2) + IndexAlphaBits)) - + (r << (IndexBits + IndexAlphaBits + 1)) - + (g << (IndexBits + IndexAlphaBits)) - + (r << (IndexBits * 2)) - + (r << (IndexBits + 1)) - + (g << IndexBits) - + ((r + g + b) << IndexAlphaBits) - + r + g + b + a; - } - - /// - /// Computes sum over a box of any given statistic. - /// - /// The cube. - /// The moment. - /// The result. - private static double Volume(Box cube, long[] moment) - { - return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] - - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] - - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] - + moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] - - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] - + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] - + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] - - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)] - + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] - - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; - } - - /// - /// Computes part of Volume(cube, moment) that doesn't depend on r1, g1, or b1 (depending on direction). - /// - /// The cube. - /// The direction. - /// The moment. - /// The result. - private static long Bottom(Box cube, int direction, long[] moment) - { - switch (direction) - { - // Red - case 0: - return -moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)] - + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] - - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; - - // Green - case 1: - return -moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] - + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] - + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] - - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; - - // Blue - case 2: - return -moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] - + moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] - + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] - - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] - - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; - - // Alpha - case 3: - return -moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] - + moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] - + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] - - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; - - default: - throw new ArgumentOutOfRangeException(nameof(direction)); - } - } - - /// - /// Computes remainder of Volume(cube, moment), substituting position for r1, g1, or b1 (depending on direction). - /// - /// The cube. - /// The direction. - /// The position. - /// The moment. - /// The result. - private static long Top(Box cube, int direction, int position, long[] moment) - { - switch (direction) - { - // Red - case 0: - return moment[GetPaletteIndex(position, cube.G1, cube.B1, cube.A1)] - - moment[GetPaletteIndex(position, cube.G1, cube.B1, cube.A0)] - - moment[GetPaletteIndex(position, cube.G1, cube.B0, cube.A1)] - + moment[GetPaletteIndex(position, cube.G1, cube.B0, cube.A0)] - - moment[GetPaletteIndex(position, cube.G0, cube.B1, cube.A1)] - + moment[GetPaletteIndex(position, cube.G0, cube.B1, cube.A0)] - + moment[GetPaletteIndex(position, cube.G0, cube.B0, cube.A1)] - - moment[GetPaletteIndex(position, cube.G0, cube.B0, cube.A0)]; - - // Green - case 1: - return moment[GetPaletteIndex(cube.R1, position, cube.B1, cube.A1)] - - moment[GetPaletteIndex(cube.R1, position, cube.B1, cube.A0)] - - moment[GetPaletteIndex(cube.R1, position, cube.B0, cube.A1)] - + moment[GetPaletteIndex(cube.R1, position, cube.B0, cube.A0)] - - moment[GetPaletteIndex(cube.R0, position, cube.B1, cube.A1)] - + moment[GetPaletteIndex(cube.R0, position, cube.B1, cube.A0)] - + moment[GetPaletteIndex(cube.R0, position, cube.B0, cube.A1)] - - moment[GetPaletteIndex(cube.R0, position, cube.B0, cube.A0)]; - - // Blue - case 2: - return moment[GetPaletteIndex(cube.R1, cube.G1, position, cube.A1)] - - moment[GetPaletteIndex(cube.R1, cube.G1, position, cube.A0)] - - moment[GetPaletteIndex(cube.R1, cube.G0, position, cube.A1)] - + moment[GetPaletteIndex(cube.R1, cube.G0, position, cube.A0)] - - moment[GetPaletteIndex(cube.R0, cube.G1, position, cube.A1)] - + moment[GetPaletteIndex(cube.R0, cube.G1, position, cube.A0)] - + moment[GetPaletteIndex(cube.R0, cube.G0, position, cube.A1)] - - moment[GetPaletteIndex(cube.R0, cube.G0, position, cube.A0)]; - - // Alpha - case 3: - return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, position)] - - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, position)] - - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, position)] - + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, position)] - - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, position)] - + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, position)] - + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, position)] - - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, position)]; - - default: - throw new ArgumentOutOfRangeException(nameof(direction)); - } - } - - /// - /// Clears the tables. - /// - private void Clear() - { - Array.Clear(this.vwt, 0, TableLength); - Array.Clear(this.vmr, 0, TableLength); - Array.Clear(this.vmg, 0, TableLength); - Array.Clear(this.vmb, 0, TableLength); - Array.Clear(this.vma, 0, TableLength); - Array.Clear(this.m2, 0, TableLength); - - Array.Clear(this.tag, 0, TableLength); - } - - /// - /// Builds a 3-D color histogram of counts, r/g/b, c^2. - /// - /// The image. - private void Build3DHistogram(PixelAccessor image) - { - for (int y = 0; y < image.Height; y++) - { - for (int x = 0; x < image.Width; x++) - { - Bgra32 color = image[x, y]; - - byte r = color.R; - byte g = color.G; - byte b = color.B; - byte a = color.A; - - int inr = r >> (8 - IndexBits); - int ing = g >> (8 - IndexBits); - int inb = b >> (8 - IndexBits); - int ina = a >> (8 - IndexAlphaBits); - - int ind = GetPaletteIndex(inr + 1, ing + 1, inb + 1, ina + 1); - - this.vwt[ind]++; - this.vmr[ind] += r; - this.vmg[ind] += g; - this.vmb[ind] += b; - this.vma[ind] += a; - this.m2[ind] += (r * r) + (g * g) + (b * b) + (a * a); - } - } - } - - /// - /// Converts the histogram into moments so that we can rapidly calculate - /// the sums of the above quantities over any desired box. - /// - private void Get3DMoments() - { - long[] volume = new long[IndexCount * IndexAlphaCount]; - long[] volumeR = new long[IndexCount * IndexAlphaCount]; - long[] volumeG = new long[IndexCount * IndexAlphaCount]; - long[] volumeB = new long[IndexCount * IndexAlphaCount]; - long[] volumeA = new long[IndexCount * IndexAlphaCount]; - double[] volume2 = new double[IndexCount * IndexAlphaCount]; - - long[] area = new long[IndexAlphaCount]; - long[] areaR = new long[IndexAlphaCount]; - long[] areaG = new long[IndexAlphaCount]; - long[] areaB = new long[IndexAlphaCount]; - long[] areaA = new long[IndexAlphaCount]; - double[] area2 = new double[IndexAlphaCount]; - - for (int r = 1; r < IndexCount; r++) - { - Array.Clear(volume, 0, IndexCount * IndexAlphaCount); - Array.Clear(volumeR, 0, IndexCount * IndexAlphaCount); - Array.Clear(volumeG, 0, IndexCount * IndexAlphaCount); - Array.Clear(volumeB, 0, IndexCount * IndexAlphaCount); - Array.Clear(volumeA, 0, IndexCount * IndexAlphaCount); - Array.Clear(volume2, 0, IndexCount * IndexAlphaCount); - - for (int g = 1; g < IndexCount; g++) - { - Array.Clear(area, 0, IndexAlphaCount); - Array.Clear(areaR, 0, IndexAlphaCount); - Array.Clear(areaG, 0, IndexAlphaCount); - Array.Clear(areaB, 0, IndexAlphaCount); - Array.Clear(areaA, 0, IndexAlphaCount); - Array.Clear(area2, 0, IndexAlphaCount); - - for (int b = 1; b < IndexCount; b++) - { - long line = 0; - long lineR = 0; - long lineG = 0; - long lineB = 0; - long lineA = 0; - double line2 = 0; - - for (int a = 1; a < IndexAlphaCount; a++) - { - int ind1 = GetPaletteIndex(r, g, b, a); - - line += this.vwt[ind1]; - lineR += this.vmr[ind1]; - lineG += this.vmg[ind1]; - lineB += this.vmb[ind1]; - lineA += this.vma[ind1]; - line2 += this.m2[ind1]; - - area[a] += line; - areaR[a] += lineR; - areaG[a] += lineG; - areaB[a] += lineB; - areaA[a] += lineA; - area2[a] += line2; - - int inv = (b * IndexAlphaCount) + a; - - volume[inv] += area[a]; - volumeR[inv] += areaR[a]; - volumeG[inv] += areaG[a]; - volumeB[inv] += areaB[a]; - volumeA[inv] += areaA[a]; - volume2[inv] += area2[a]; - - int ind2 = ind1 - GetPaletteIndex(1, 0, 0, 0); - - this.vwt[ind1] = this.vwt[ind2] + volume[inv]; - this.vmr[ind1] = this.vmr[ind2] + volumeR[inv]; - this.vmg[ind1] = this.vmg[ind2] + volumeG[inv]; - this.vmb[ind1] = this.vmb[ind2] + volumeB[inv]; - this.vma[ind1] = this.vma[ind2] + volumeA[inv]; - this.m2[ind1] = this.m2[ind2] + volume2[inv]; - } - } - } - } - } - - /// - /// Computes the weighted variance of a box cube. - /// - /// The cube. - /// The . - private double Variance(Box cube) - { - double dr = Volume(cube, this.vmr); - double dg = Volume(cube, this.vmg); - double db = Volume(cube, this.vmb); - double da = Volume(cube, this.vma); - - double xx = - this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] - - this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] - - this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] - + this.m2[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] - - this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] - + this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] - + this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] - - this.m2[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - - this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)] - + this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] - + this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] - - this.m2[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] - + this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] - - this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - - this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] - + this.m2[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; - - return xx - (((dr * dr) + (dg * dg) + (db * db) + (da * da)) / Volume(cube, this.vwt)); - } - - /// - /// We want to minimize the sum of the variances of two sub-boxes. - /// The sum(c^2) terms can be ignored since their sum over both sub-boxes - /// is the same (the sum for the whole box) no matter where we split. - /// The remaining terms have a minus sign in the variance formula, - /// so we drop the minus sign and maximize the sum of the two terms. - /// - /// The cube. - /// The direction. - /// The first position. - /// The last position. - /// The cutting point. - /// The whole red. - /// The whole green. - /// The whole blue. - /// The whole alpha. - /// The whole weight. - /// The . - private double Maximize(Box cube, int direction, int first, int last, out int cut, double wholeR, double wholeG, double wholeB, double wholeA, double wholeW) - { - long baseR = Bottom(cube, direction, this.vmr); - long baseG = Bottom(cube, direction, this.vmg); - long baseB = Bottom(cube, direction, this.vmb); - long baseA = Bottom(cube, direction, this.vma); - long baseW = Bottom(cube, direction, this.vwt); - - double max = 0.0; - cut = -1; - - for (int i = first; i < last; i++) - { - double halfR = baseR + Top(cube, direction, i, this.vmr); - double halfG = baseG + Top(cube, direction, i, this.vmg); - double halfB = baseB + Top(cube, direction, i, this.vmb); - double halfA = baseA + Top(cube, direction, i, this.vma); - double halfW = baseW + Top(cube, direction, i, this.vwt); - - double temp; - - if (Math.Abs(halfW) < Epsilon) - { - continue; - } - - temp = ((halfR * halfR) + (halfG * halfG) + (halfB * halfB) + (halfA * halfA)) / halfW; - - halfR = wholeR - halfR; - halfG = wholeG - halfG; - halfB = wholeB - halfB; - halfA = wholeA - halfA; - halfW = wholeW - halfW; - - if (Math.Abs(halfW) < Epsilon) - { - continue; - } - - temp += ((halfR * halfR) + (halfG * halfG) + (halfB * halfB) + (halfA * halfA)) / halfW; - - if (temp > max) - { - max = temp; - cut = i; - } - } - - return max; - } - - /// - /// Cuts a box. - /// - /// The first set. - /// The second set. - /// Returns a value indicating whether the box has been split. - private bool Cut(Box set1, Box set2) - { - double wholeR = Volume(set1, this.vmr); - double wholeG = Volume(set1, this.vmg); - double wholeB = Volume(set1, this.vmb); - double wholeA = Volume(set1, this.vma); - double wholeW = Volume(set1, this.vwt); - - int cutr; - int cutg; - int cutb; - int cuta; - - double maxr = this.Maximize(set1, 0, set1.R0 + 1, set1.R1, out cutr, wholeR, wholeG, wholeB, wholeA, wholeW); - double maxg = this.Maximize(set1, 1, set1.G0 + 1, set1.G1, out cutg, wholeR, wholeG, wholeB, wholeA, wholeW); - double maxb = this.Maximize(set1, 2, set1.B0 + 1, set1.B1, out cutb, wholeR, wholeG, wholeB, wholeA, wholeW); - double maxa = this.Maximize(set1, 3, set1.A0 + 1, set1.A1, out cuta, wholeR, wholeG, wholeB, wholeA, wholeW); - - int dir; - - if ((maxr >= maxg) && (maxr >= maxb) && (maxr >= maxa)) - { - dir = 0; - - if (cutr < 0) - { - return false; - } - } - else if ((maxg >= maxr) && (maxg >= maxb) && (maxg >= maxa)) - { - dir = 1; - } - else if ((maxb >= maxr) && (maxb >= maxg) && (maxb >= maxa)) - { - dir = 2; - } - else - { - dir = 3; - } - - set2.R1 = set1.R1; - set2.G1 = set1.G1; - set2.B1 = set1.B1; - set2.A1 = set1.A1; - - switch (dir) - { - // Red - case 0: - set2.R0 = set1.R1 = cutr; - set2.G0 = set1.G0; - set2.B0 = set1.B0; - set2.A0 = set1.A0; - break; - - // Green - case 1: - set2.G0 = set1.G1 = cutg; - set2.R0 = set1.R0; - set2.B0 = set1.B0; - set2.A0 = set1.A0; - break; - - // Blue - case 2: - set2.B0 = set1.B1 = cutb; - set2.R0 = set1.R0; - set2.G0 = set1.G0; - set2.A0 = set1.A0; - break; - - // Alpha - case 3: - set2.A0 = set1.A1 = cuta; - set2.R0 = set1.R0; - set2.G0 = set1.G0; - set2.B0 = set1.B0; - break; - } - - set1.Volume = (set1.R1 - set1.R0) * (set1.G1 - set1.G0) * (set1.B1 - set1.B0) * (set1.A1 - set1.A0); - set2.Volume = (set2.R1 - set2.R0) * (set2.G1 - set2.G0) * (set2.B1 - set2.B0) * (set2.A1 - set2.A0); - - return true; - } - - /// - /// Marks a color space tag. - /// - /// The cube. - /// A label. - private void Mark(Box cube, byte label) - { - for (int r = cube.R0 + 1; r <= cube.R1; r++) - { - for (int g = cube.G0 + 1; g <= cube.G1; g++) - { - for (int b = cube.B0 + 1; b <= cube.B1; b++) - { - for (int a = cube.A0 + 1; a <= cube.A1; a++) - { - this.tag[GetPaletteIndex(r, g, b, a)] = label; - } - } - } - } - } - - /// - /// Builds the cube. - /// - /// The cube. - /// The color count. - private void BuildCube(out Box[] cube, ref int colorCount) - { - cube = new Box[colorCount]; - double[] vv = new double[colorCount]; - - for (int i = 0; i < colorCount; i++) - { - cube[i] = new Box(); - } - - cube[0].R0 = cube[0].G0 = cube[0].B0 = cube[0].A0 = 0; - cube[0].R1 = cube[0].G1 = cube[0].B1 = IndexCount - 1; - cube[0].A1 = IndexAlphaCount - 1; - - int next = 0; - - for (int i = 1; i < colorCount; i++) - { - if (this.Cut(cube[next], cube[i])) - { - vv[next] = cube[next].Volume > 1 ? this.Variance(cube[next]) : 0.0; - vv[i] = cube[i].Volume > 1 ? this.Variance(cube[i]) : 0.0; - } - else - { - vv[next] = 0.0; - i--; - } - - next = 0; - - double temp = vv[0]; - for (int k = 1; k <= i; k++) - { - if (vv[k] > temp) - { - temp = vv[k]; - next = k; - } - } - - if (temp <= 0.0) - { - colorCount = i + 1; - break; - } - } - } - - /// - /// Generates the quantized result. - /// - /// The image pixels. - /// The color count. - /// The cube. - /// The result. - private QuantizedImage GenerateResult(PixelAccessor imagePixels, int colorCount, Box[] cube) - { - List pallette = new List(); - byte[] pixels = new byte[imagePixels.Width * imagePixels.Height]; - int transparentIndex = -1; - int width = imagePixels.Width; - int height = imagePixels.Height; - - for (int k = 0; k < colorCount; k++) - { - this.Mark(cube[k], (byte)k); - - double weight = Volume(cube[k], this.vwt); - - if (Math.Abs(weight) > Epsilon) - { - byte r = (byte)(Volume(cube[k], this.vmr) / weight); - byte g = (byte)(Volume(cube[k], this.vmg) / weight); - 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); - - if (color == Bgra32.Empty) - { - transparentIndex = k; - } - - pallette.Add(color); - } - else - { - pallette.Add(Bgra32.Empty); - transparentIndex = k; - } - } - - Parallel.For( - 0, - height, - 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) - { - pixels[(y * width) + x] = (byte)transparentIndex; - continue; - } - - int ind = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); - pixels[(y * width) + x] = this.tag[ind]; - } - }); - - - return new QuantizedImage(width, height, pallette.ToArray(), pixels, transparentIndex); - } - } -} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/Samplers/Crop.cs b/src/ImageProcessorCore - Copy/Samplers/Crop.cs deleted file mode 100644 index 239fd63f14..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Crop.cs +++ /dev/null @@ -1,68 +0,0 @@ -// -// 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 - { - /// - /// Crops an image to the given width and height. - /// - /// The image to resize. - /// The target image width. - /// The target image height. - /// A delegate which is called as progress is made processing the image. - /// The - public static Image Crop(this Image source, int width, int height, ProgressEventHandler progressHandler = null) - { - return Crop(source, width, height, source.Bounds, progressHandler); - } - - /// - /// Crops an image to the given width and height with the given source rectangle. - /// - /// If the source rectangle is smaller than the target dimensions then the - /// area within the source is resized performing a zoomed crop. - /// - /// - /// The image to crop. - /// The target image width. - /// The target image height. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// 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) - { - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - if (sourceRectangle.Width < width || sourceRectangle.Height < height) - { - // If the source rectangle is smaller than the target perform a - // cropped zoom. - source = source.Resize(sourceRectangle.Width, sourceRectangle.Height); - } - - CropProcessor processor = new CropProcessor(); - processor.OnProgress += progressHandler; - - try - { - return source.Process(width, height, sourceRectangle, new Rectangle(0, 0, width, height), processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/EntropyCrop.cs b/src/ImageProcessorCore - Copy/Samplers/EntropyCrop.cs deleted file mode 100644 index 55284668ca..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/EntropyCrop.cs +++ /dev/null @@ -1,37 +0,0 @@ -// -// 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 - { - /// - /// Crops an image to the area of greatest entropy. - /// - /// 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) - { - EntropyCropProcessor processor = new EntropyCropProcessor(threshold); - processor.OnProgress += progressHandler; - - try - { - return source.Process(source.Width, source.Height, source.Bounds, Rectangle.Empty, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Options/AnchorPosition.cs b/src/ImageProcessorCore - Copy/Samplers/Options/AnchorPosition.cs deleted file mode 100644 index af840b292a..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Options/AnchorPosition.cs +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - /// - /// Enumerated anchor positions to apply to resized images. - /// - public enum AnchorPosition - { - /// - /// Anchors the position of the image to the center of it's bounding container. - /// - Center, - - /// - /// Anchors the position of the image to the top of it's bounding container. - /// - Top, - - /// - /// Anchors the position of the image to the bottom of it's bounding container. - /// - Bottom, - - /// - /// Anchors the position of the image to the left of it's bounding container. - /// - Left, - - /// - /// Anchors the position of the image to the right of it's bounding container. - /// - Right, - - /// - /// Anchors the position of the image to the top left side of it's bounding container. - /// - TopLeft, - - /// - /// Anchors the position of the image to the top right side of it's bounding container. - /// - TopRight, - - /// - /// Anchors the position of the image to the bottom right side of it's bounding container. - /// - BottomRight, - - /// - /// Anchors the position of the image to the bottom left side of it's bounding container. - /// - BottomLeft - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Options/FlipType.cs b/src/ImageProcessorCore - Copy/Samplers/Options/FlipType.cs deleted file mode 100644 index c9b21a37e5..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Options/FlipType.cs +++ /dev/null @@ -1,28 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - /// - /// Provides enumeration over how a image should be flipped. - /// - public enum FlipType - { - /// - /// Don't flip the image. - /// - None, - - /// - /// Flip the image horizontally. - /// - Horizontal, - - /// - /// Flip the image vertically. - /// - Vertical, - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Options/ResizeHelper.cs b/src/ImageProcessorCore - Copy/Samplers/Options/ResizeHelper.cs deleted file mode 100644 index a80ea47778..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Options/ResizeHelper.cs +++ /dev/null @@ -1,430 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System; - using System.Linq; - - /// - /// Provides methods to help calculate the target rectangle when resizing using the - /// enumeration. - /// - internal static class ResizeHelper - { - /// - /// Calculates the target location and bounds to perform the resize operation against. - /// - /// The source image. - /// The resize options. - /// - /// The . - /// - public static Rectangle CalculateTargetLocationAndBounds(ImageBase source, ResizeOptions options) - { - switch (options.Mode) - { - case ResizeMode.Crop: - return CalculateCropRectangle(source, options); - case ResizeMode.Pad: - return CalculatePadRectangle(source, options); - case ResizeMode.BoxPad: - return CalculateBoxPadRectangle(source, options); - case ResizeMode.Max: - return CalculateMaxRectangle(source, options); - case ResizeMode.Min: - return CalculateMinRectangle(source, options); - - // Last case ResizeMode.Stretch: - default: - return CalculateStretchRectangle(source, options); - } - } - - /// - /// Calculates the target rectangle for crop mode. - /// - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculateCropRectangle(ImageBase source, ResizeOptions options) - { - int width = options.Size.Width; - int height = options.Size.Height; - - if (width <= 0 || height <= 0) - { - return new Rectangle(0, 0, source.Width, source.Height); - } - - double ratio; - int sourceWidth = source.Width; - int sourceHeight = source.Height; - - int destinationX = 0; - int destinationY = 0; - int destinationWidth = width; - int destinationHeight = height; - - // Fractional variants for preserving aspect ratio. - double percentHeight = Math.Abs(height / (double)sourceHeight); - double percentWidth = Math.Abs(width / (double)sourceWidth); - - if (percentHeight < percentWidth) - { - ratio = percentWidth; - - if (options.CenterCoordinates.Any()) - { - double center = -(ratio * sourceHeight) * options.CenterCoordinates.First(); - destinationY = (int)center + (height / 2); - - if (destinationY > 0) - { - destinationY = 0; - } - - if (destinationY < (int)(height - (sourceHeight * ratio))) - { - destinationY = (int)(height - (sourceHeight * ratio)); - } - } - else - { - switch (options.Position) - { - case AnchorPosition.Top: - case AnchorPosition.TopLeft: - case AnchorPosition.TopRight: - destinationY = 0; - break; - case AnchorPosition.Bottom: - case AnchorPosition.BottomLeft: - case AnchorPosition.BottomRight: - destinationY = (int)(height - (sourceHeight * ratio)); - break; - default: - destinationY = (int)((height - (sourceHeight * ratio)) / 2); - break; - } - } - - destinationHeight = (int)Math.Ceiling(sourceHeight * percentWidth); - } - else - { - ratio = percentHeight; - - if (options.CenterCoordinates.Any()) - { - double center = -(ratio * sourceWidth) * options.CenterCoordinates.ToArray()[1]; - destinationX = (int)center + (width / 2); - - if (destinationX > 0) - { - destinationX = 0; - } - - if (destinationX < (int)(width - (sourceWidth * ratio))) - { - destinationX = (int)(width - (sourceWidth * ratio)); - } - } - else - { - switch (options.Position) - { - case AnchorPosition.Left: - case AnchorPosition.TopLeft: - case AnchorPosition.BottomLeft: - destinationX = 0; - break; - case AnchorPosition.Right: - case AnchorPosition.TopRight: - case AnchorPosition.BottomRight: - destinationX = (int)(width - (sourceWidth * ratio)); - break; - default: - destinationX = (int)((width - (sourceWidth * ratio)) / 2); - break; - } - } - - destinationWidth = (int)Math.Ceiling(sourceWidth * percentHeight); - } - - return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); - } - - /// - /// Calculates the target rectangle for pad mode. - /// - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculatePadRectangle(ImageBase source, ResizeOptions options) - { - int width = options.Size.Width; - int height = options.Size.Height; - - if (width <= 0 || height <= 0) - { - return new Rectangle(0, 0, source.Width, source.Height); - } - - double ratio; - int sourceWidth = source.Width; - int sourceHeight = source.Height; - - int destinationX = 0; - int destinationY = 0; - int destinationWidth = width; - int destinationHeight = height; - - // Fractional variants for preserving aspect ratio. - double percentHeight = Math.Abs(height / (double)sourceHeight); - double percentWidth = Math.Abs(width / (double)sourceWidth); - - if (percentHeight < percentWidth) - { - ratio = percentHeight; - destinationWidth = Convert.ToInt32(sourceWidth * percentHeight); - - switch (options.Position) - { - case AnchorPosition.Left: - case AnchorPosition.TopLeft: - case AnchorPosition.BottomLeft: - destinationX = 0; - break; - case AnchorPosition.Right: - case AnchorPosition.TopRight: - case AnchorPosition.BottomRight: - destinationX = (int)(width - (sourceWidth * ratio)); - break; - default: - destinationX = Convert.ToInt32((width - (sourceWidth * ratio)) / 2); - break; - } - } - else - { - ratio = percentWidth; - destinationHeight = Convert.ToInt32(sourceHeight * percentWidth); - - switch (options.Position) - { - case AnchorPosition.Top: - case AnchorPosition.TopLeft: - case AnchorPosition.TopRight: - destinationY = 0; - break; - case AnchorPosition.Bottom: - case AnchorPosition.BottomLeft: - case AnchorPosition.BottomRight: - destinationY = (int)(height - (sourceHeight * ratio)); - break; - default: - destinationY = (int)((height - (sourceHeight * ratio)) / 2); - break; - } - } - - return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); - } - - /// - /// Calculates the target rectangle for box pad mode. - /// - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculateBoxPadRectangle(ImageBase source, ResizeOptions options) - { - int width = options.Size.Width; - int height = options.Size.Height; - - if (width <= 0 || height <= 0) - { - return new Rectangle(0, 0, source.Width, source.Height); - } - - int sourceWidth = source.Width; - int sourceHeight = source.Height; - - // Fractional variants for preserving aspect ratio. - double percentHeight = Math.Abs(height / (double)sourceHeight); - double percentWidth = Math.Abs(width / (double)sourceWidth); - - int boxPadHeight = height > 0 ? height : Convert.ToInt32(sourceHeight * percentWidth); - int boxPadWidth = width > 0 ? width : Convert.ToInt32(sourceWidth * percentHeight); - - // Only calculate if upscaling. - if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight) - { - int destinationX; - int destinationY; - int destinationWidth = sourceWidth; - int destinationHeight = sourceHeight; - width = boxPadWidth; - height = boxPadHeight; - - switch (options.Position) - { - case AnchorPosition.Left: - destinationY = (height - sourceHeight) / 2; - destinationX = 0; - break; - case AnchorPosition.Right: - destinationY = (height - sourceHeight) / 2; - destinationX = width - sourceWidth; - break; - case AnchorPosition.TopRight: - destinationY = 0; - destinationX = width - sourceWidth; - break; - case AnchorPosition.Top: - destinationY = 0; - destinationX = (width - sourceWidth) / 2; - break; - case AnchorPosition.TopLeft: - destinationY = 0; - destinationX = 0; - break; - case AnchorPosition.BottomRight: - destinationY = height - sourceHeight; - destinationX = width - sourceWidth; - break; - case AnchorPosition.Bottom: - destinationY = height - sourceHeight; - destinationX = (width - sourceWidth) / 2; - break; - case AnchorPosition.BottomLeft: - destinationY = height - sourceHeight; - destinationX = 0; - break; - default: - destinationY = (height - sourceHeight) / 2; - destinationX = (width - sourceWidth) / 2; - break; - } - - return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); - } - - // Switch to pad mode to downscale and calculate from there. - return CalculatePadRectangle(source, options); - } - - /// - /// Calculates the target rectangle for max mode. - /// - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculateMaxRectangle(ImageBase source, ResizeOptions options) - { - int width = options.Size.Width; - int height = options.Size.Height; - int destinationWidth = width; - int destinationHeight = height; - - // Fractional variants for preserving aspect ratio. - double percentHeight = Math.Abs(height / (double)source.Height); - double percentWidth = Math.Abs(width / (double)source.Width); - - // Integers must be cast to doubles to get needed precision - double ratio = (double)options.Size.Height / options.Size.Width; - double sourceRatio = (double)source.Height / source.Width; - - if (sourceRatio < ratio) - { - destinationHeight = Convert.ToInt32(source.Height * percentWidth); - height = destinationHeight; - } - else - { - destinationWidth = Convert.ToInt32(source.Width * percentHeight); - width = destinationWidth; - } - - // Replace the size to match the rectangle. - options.Size = new Size(width, height); - return new Rectangle(0, 0, destinationWidth, destinationHeight); - } - - /// - /// Calculates the target rectangle for min mode. - /// - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculateMinRectangle(ImageBase source, ResizeOptions options) - { - int width = options.Size.Width; - int height = options.Size.Height; - int destinationWidth; - int destinationHeight; - - // Don't upscale - if (width > source.Width || height > source.Height) - { - options.Size = new Size(source.Width, source.Height); - return new Rectangle(0, 0, source.Width, source.Height); - } - - double sourceRatio = (double)source.Height / source.Width; - - // Find the shortest distance to go. - int widthDiff = source.Width - width; - int heightDiff = source.Height - height; - - if (widthDiff < heightDiff) - { - destinationHeight = Convert.ToInt32(width * sourceRatio); - height = destinationHeight; - destinationWidth = width; - } - else if (widthDiff > heightDiff) - { - destinationWidth = Convert.ToInt32(height / sourceRatio); - destinationHeight = height; - width = destinationWidth; - } - else - { - destinationWidth = width; - destinationHeight = height; - } - - // Replace the size to match the rectangle. - options.Size = new Size(width, height); - return new Rectangle(0, 0, destinationWidth, destinationHeight); - } - - /// - /// Calculates the target rectangle for stretch mode. - /// - /// The source image. - /// The resize options. - /// - /// The . - /// - private static Rectangle CalculateStretchRectangle(ImageBase source, ResizeOptions options) - { - return new Rectangle(0, 0, options.Size.Width, options.Size.Height); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Options/ResizeMode.cs b/src/ImageProcessorCore - Copy/Samplers/Options/ResizeMode.cs deleted file mode 100644 index a0ce943417..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Options/ResizeMode.cs +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - /// - /// Enumerated resize modes to apply to resized images. - /// - public enum ResizeMode - { - /// - /// Crops the resized image to fit the bounds of its container. - /// - Crop, - - /// - /// Pads the resized image to fit the bounds of its container. - /// If only one dimension is passed, will maintain the original aspect ratio. - /// - Pad, - - /// - /// Pads the image to fit the bound of the container without resizing the - /// original source. - /// When downscaling, performs the same functionality as - /// - BoxPad, - - /// - /// Constrains the resized image to fit the bounds of its container maintaining - /// the original aspect ratio. - /// - Max, - - /// - /// Resizes the image until the shortest side reaches the set given dimension. - /// Upscaling is disabled in this mode and the original image will be returned - /// if attempted. - /// - Min, - - /// - /// Stretches the resized image to fit the bounds of its container. - /// - Stretch - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Options/ResizeOptions.cs b/src/ImageProcessorCore - Copy/Samplers/Options/ResizeOptions.cs deleted file mode 100644 index 82db8ed860..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Options/ResizeOptions.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - using System.Collections.Generic; - using System.Linq; - - /// - /// The resize options for resizing images against certain modes. - /// - public class ResizeOptions - { - /// - /// Gets or sets the resize mode. - /// - public ResizeMode Mode { get; set; } = ResizeMode.Crop; - - /// - /// Gets or sets the anchor position. - /// - public AnchorPosition Position { get; set; } = AnchorPosition.Center; - - /// - /// Gets or sets the center coordinates. - /// - public IEnumerable CenterCoordinates { get; set; } = Enumerable.Empty(); - - /// - /// Gets or sets the target size. - /// - public Size Size { get; set; } - - /// - /// Gets or sets the sampler to perform the resize operation. - /// - public IResampler Sampler { get; set; } = new BicubicResampler(); - - /// - /// Gets or sets a value indicating whether to compress - /// or expand individual pixel colors the value on processing. - /// - public bool Compand { get; set; } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Options/RotateType.cs b/src/ImageProcessorCore - Copy/Samplers/Options/RotateType.cs deleted file mode 100644 index 43644de858..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Options/RotateType.cs +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - /// - /// Provides enumeration over how the image should be rotated. - /// - public enum RotateType - { - /// - /// Do not rotate the image. - /// - None, - - /// - /// Rotate the image by 90 degrees clockwise. - /// - Rotate90, - - /// - /// Rotate the image by 180 degrees clockwise. - /// - Rotate180, - - /// - /// Rotate the image by 270 degrees clockwise. - /// - Rotate270 - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Pad.cs b/src/ImageProcessorCore - Copy/Samplers/Pad.cs deleted file mode 100644 index de973d3454..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Pad.cs +++ /dev/null @@ -1,35 +0,0 @@ -// -// 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 - { - /// - /// Evenly pads an image to fit the new dimensions. - /// - /// 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) - { - ResizeOptions options = new ResizeOptions - { - Size = new Size(width, height), - Mode = ResizeMode.BoxPad, - Sampler = new NearestNeighborResampler() - }; - - return Resize(source, options, progressHandler); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Processors/CropProcessor.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/CropProcessor.cs deleted file mode 100644 index adcd3c180c..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Processors/CropProcessor.cs +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Threading.Tasks; - - /// - /// Provides methods to allow the cropping of an image. - /// - public class CropProcessor : ImageSampler - { - /// - 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()) - { - Parallel.For( - startY, - endY, - y => - { - for (int x = startX; x < endX; x++) - { - targetPixels[x, y] = sourcePixels[x + sourceX, y + sourceY]; - } - - this.OnRowProcessed(); - }); - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Processors/EntropyCropProcessor.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/EntropyCropProcessor.cs deleted file mode 100644 index f9f8601f52..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Processors/EntropyCropProcessor.cs +++ /dev/null @@ -1,104 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System; - using System.Threading.Tasks; - - /// - /// Provides methods to allow the cropping of an image to preserve areas of highest - /// entropy. - /// - public class EntropyCropProcessor : ImageSampler - { - /// - /// The rectangle for cropping - /// - private Rectangle cropRectangle; - - /// - /// Initializes a new instance of the class. - /// - /// The threshold to split the image. Must be between 0 and 1. - /// - /// is less than 0 or is greater than 1. - /// - public EntropyCropProcessor(float threshold) - { - Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); - this.Value = threshold; - } - - /// - /// Gets the threshold value. - /// - public float Value { get; } - - /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - ImageBase temp = new Image(source.Width, source.Height); - - // Detect the edges. - new SobelProcessor().Apply(temp, source, sourceRectangle); - - // Apply threshold binarization filter. - new ThresholdProcessor(.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]); - this.cropRectangle = rectangle; - } - - /// - 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) - { - return; - } - - int targetY = this.cropRectangle.Y; - int targetBottom = this.cropRectangle.Bottom; - int startX = this.cropRectangle.X; - int endX = this.cropRectangle.Right; - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - startY, - endY, - y => - { - if (y >= targetY && y < targetBottom) - { - for (int x = startX; x < endX; x++) - { - targetPixels[x - startX, y - targetY] = sourcePixels[x, y]; - } - } - - this.OnRowProcessed(); - }); - } - } - - /// - protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - // Copy the pixels over. - if (source.Bounds == target.Bounds) - { - target.ClonePixels(target.Width, target.Height, source.Pixels); - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Processors/IImageSampler.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/IImageSampler.cs deleted file mode 100644 index 76a2c5a4d4..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Processors/IImageSampler.cs +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - /// - /// Acts as a marker for generic parameters that require an image sampler. - /// - public interface IImageSampler : IImageProcessor - { - /// - /// Gets or sets a value indicating whether to compress - /// or expand individual pixel colors the value on processing. - /// - bool Compand { get; set; } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Processors/ImageSampler.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/ImageSampler.cs deleted file mode 100644 index adfe77432f..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Processors/ImageSampler.cs +++ /dev/null @@ -1,17 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - /// - /// Applies sampling methods to an image. - /// All processors requiring resampling or resizing should inherit from this. - /// - public abstract class ImageSampler : ImageProcessor, IImageSampler - { - /// - public virtual bool Compand { get; set; } = false; - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Processors/Matrix3x2Processor.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/Matrix3x2Processor.cs deleted file mode 100644 index e9b0441e3d..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Processors/Matrix3x2Processor.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Numerics; - - /// - /// Provides methods to transform an image using a . - /// - public abstract class Matrix3x2Processor : ImageSampler - { - /// - /// Creates a new target to contain the results of the matrix transform. - /// - /// Target image to apply the process to. - /// The source rectangle. - /// The processing matrix. - 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]); - } - } - - /// - /// Gets a transform matrix adjusted to center upon the target image bounds. - /// - /// Target image to apply the process to. - /// The source image. - /// The transform matrix. - /// - /// The . - /// - 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); - return (translationToTargetCenter * matrix) * translateToSourceCenter; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Processors/ResizeProcessor.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/ResizeProcessor.cs deleted file mode 100644 index 95ead2bf04..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Processors/ResizeProcessor.cs +++ /dev/null @@ -1,362 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System; - using System.Threading.Tasks; - - /// - /// Provides methods that allow the resizing of images using various algorithms. - /// - public class ResizeProcessor : ImageSampler - { - /// - /// The image used for storing the first pass pixels. - /// - private Image firstPass; - - /// - /// Initializes a new instance of the class. - /// - /// - /// The sampler to perform the resize operation. - /// - public ResizeProcessor(IResampler sampler) - { - Guard.NotNull(sampler, nameof(sampler)); - - this.Sampler = sampler; - } - - /// - /// Gets the sampler to perform the resize operation. - /// - public IResampler Sampler { get; } - - /// - /// Gets or sets the horizontal weights. - /// - protected Weights[] HorizontalWeights { get; set; } - - /// - /// Gets or sets the vertical weights. - /// - protected Weights[] VerticalWeights { get; set; } - - /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - if (!(this.Sampler is NearestNeighborResampler)) - { - this.HorizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width); - this.VerticalWeights = this.PrecomputeWeights(targetRectangle.Height, sourceRectangle.Height); - } - - this.firstPass = new Image(target.Width, source.Height); - } - - /// - protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - // Jump out, we'll deal with that later. - if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) - { - return; - } - - int width = target.Width; - int height = target.Height; - int sourceHeight = sourceRectangle.Height; - int targetX = target.Bounds.X; - int targetY = target.Bounds.Y; - int targetRight = target.Bounds.Right; - int targetBottom = target.Bounds.Bottom; - int startX = targetRectangle.X; - int endX = targetRectangle.Right; - bool compand = this.Compand; - - if (this.Sampler is NearestNeighborResampler) - { - // Scaling factors - float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width; - float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height; - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - startY, - endY, - y => - { - if (targetY <= y && y < targetBottom) - { - // Y coordinates of source points - int originY = (int)((y - startY) * heightFactor); - - for (int x = startX; x < endX; x++) - { - if (targetX <= x && x < targetRight) - { - // X coordinates of source points - int originX = (int)((x - startX) * widthFactor); - - targetPixels[x, y] = sourcePixels[originX, originY]; - } - } - - this.OnRowProcessed(); - } - }); - } - - // Break out now. - return; - } - - // Interpolate the image using the calculated weights. - // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm - // First process the columns. Since we are not using multiple threads startY and endY - // are the upper and lower bounds of the source rectangle. - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor firstPassPixels = this.firstPass.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - 0, - sourceHeight, - y => - { - for (int x = startX; x < endX; x++) - { - if (x >= 0 && x < width) - { - // Ensure offsets are normalised for cropping and padding. - int offsetX = x - startX; - float sum = this.HorizontalWeights[offsetX].Sum; - Weight[] horizontalValues = this.HorizontalWeights[offsetX].Values; - - // Destination color components - Color destination = new Color(); - - for (int i = 0; i < sum; i++) - { - Weight xw = horizontalValues[i]; - int originX = xw.Index; - Color sourceColor = compand - ? Color.Expand(sourcePixels[originX, y]) - : sourcePixels[originX, y]; - - destination += sourceColor * xw.Value; - } - - if (compand) - { - destination = Color.Compress(destination); - } - - firstPassPixels[x, y] = destination; - } - } - }); - - // Now process the rows. - Parallel.For( - startY, - endY, - y => - { - if (y >= 0 && y < height) - { - // Ensure offsets are normalised for cropping and padding. - int offsetY = y - startY; - float sum = this.VerticalWeights[offsetY].Sum; - Weight[] verticalValues = this.VerticalWeights[offsetY].Values; - - for (int x = 0; x < width; x++) - { - // Destination color components - Color destination = new Color(); - - for (int i = 0; i < sum; i++) - { - Weight yw = verticalValues[i]; - int originY = yw.Index; - Color sourceColor = compand - ? Color.Expand(firstPassPixels[x, originY]) - : firstPassPixels[x, originY]; - - destination += sourceColor * yw.Value; - } - - if (compand) - { - destination = Color.Compress(destination); - } - - targetPixels[x, y] = destination; - } - } - - this.OnRowProcessed(); - }); - - } - } - - /// - protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - // Copy the pixels over. - if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle) - { - target.ClonePixels(target.Width, target.Height, source.Pixels); - } - } - - /// - /// Computes the weights to apply at each pixel when resizing. - /// - /// The destination section size. - /// The source section size. - /// - /// The . - /// - protected Weights[] PrecomputeWeights(int destinationSize, int sourceSize) - { - float scale = (float)destinationSize / sourceSize; - IResampler sampler = this.Sampler; - float radius = sampler.Radius; - double left; - double right; - float weight; - int index; - int sum; - - Weights[] result = new Weights[destinationSize]; - - // When shrinking, broaden the effective kernel support so that we still - // visit every source pixel. - if (scale < 1) - { - float width = radius / scale; - float filterScale = 1 / scale; - - // Make the weights slices, one source for each column or row. - for (int i = 0; i < destinationSize; i++) - { - float centre = i / scale; - left = Math.Ceiling(centre - width); - right = Math.Floor(centre + width); - - result[i] = new Weights - { - Values = new Weight[(int)(right - left + 1)] - }; - - for (double j = left; j <= right; j++) - { - weight = sampler.GetValue((float)((centre - j) / filterScale)) / filterScale; - if (j < 0) - { - index = (int)-j; - } - else if (j >= sourceSize) - { - index = (int)((sourceSize - j) + sourceSize - 1); - } - else - { - index = (int)j; - } - - sum = (int)result[i].Sum++; - result[i].Values[sum] = new Weight(index, weight); - } - } - } - else - { - // Make the weights slices, one source for each column or row. - for (int i = 0; i < destinationSize; i++) - { - float centre = i / scale; - left = Math.Ceiling(centre - radius); - right = Math.Floor(centre + radius); - result[i] = new Weights - { - Values = new Weight[(int)(right - left + 1)] - }; - - for (double j = left; j <= right; j++) - { - weight = sampler.GetValue((float)(centre - j)); - if (j < 0) - { - index = (int)-j; - } - else if (j >= sourceSize) - { - index = (int)((sourceSize - j) + sourceSize - 1); - } - else - { - index = (int)j; - } - - sum = (int)result[i].Sum++; - result[i].Values[sum] = new Weight(index, weight); - } - } - } - - return result; - } - - /// - /// Represents the weight to be added to a scaled pixel. - /// - protected struct Weight - { - /// - /// Initializes a new instance of the struct. - /// - /// The index. - /// The value. - public Weight(int index, float value) - { - this.Index = index; - this.Value = value; - } - - /// - /// Gets the pixel index. - /// - public int Index { get; } - - /// - /// Gets the result of the interpolation algorithm. - /// - public float Value { get; } - } - - /// - /// Represents a collection of weights and their sum. - /// - protected class Weights - { - /// - /// Gets or sets the values. - /// - public Weight[] Values { get; set; } - - /// - /// Gets or sets the sum. - /// - public float Sum { get; set; } - } - } -} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/Samplers/Processors/RotateFlipProcessor.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/RotateFlipProcessor.cs deleted file mode 100644 index 6ef8866ba1..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Processors/RotateFlipProcessor.cs +++ /dev/null @@ -1,231 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System; - using System.Threading.Tasks; - - /// - /// Provides methods that allow the rotation and flipping of an image around its center point. - /// - public class RotateFlipProcessor : ImageSampler - { - /// - /// Initializes a new instance of the class. - /// - /// The used to perform rotation. - /// The used to perform flipping. - public RotateFlipProcessor(RotateType rotateType, FlipType flipType) - { - this.RotateType = rotateType; - this.FlipType = flipType; - } - - /// - /// Gets the used to perform flipping. - /// - public FlipType FlipType { get; } - - /// - /// Gets the used to perform rotation. - /// - public RotateType RotateType { get; } - - /// - protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) - { - switch (this.RotateType) - { - case RotateType.Rotate90: - this.Rotate90(target, source); - break; - case RotateType.Rotate180: - this.Rotate180(target, source); - break; - case RotateType.Rotate270: - this.Rotate270(target, source); - break; - default: - target.ClonePixels(target.Width, target.Height, source.Pixels); - break; - } - - switch (this.FlipType) - { - // No default needed as we have already set the pixels. - case FlipType.Vertical: - this.FlipX(target); - break; - case FlipType.Horizontal: - this.FlipY(target); - break; - } - } - - /// - /// Rotates the image 270 degrees clockwise at the centre point. - /// - /// The target image. - /// The source image. - private void Rotate270(ImageBase target, ImageBase source) - { - int width = source.Width; - int height = source.Height; - Image temp = new Image(height, width); - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor tempPixels = temp.Lock()) - { - Parallel.For( - 0, - height, - y => - { - for (int x = 0; x < width; x++) - { - int newX = height - y - 1; - newX = height - newX - 1; - int newY = width - x - 1; - newY = width - newY - 1; - tempPixels[newX, newY] = sourcePixels[x, y]; - } - - this.OnRowProcessed(); - }); - } - - target.SetPixels(height, width, temp.Pixels); - } - - /// - /// Rotates the image 180 degrees clockwise at the centre point. - /// - /// The target image. - /// The source image. - 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()) - { - Parallel.For( - 0, - height, - y => - { - for (int x = 0; x < width; x++) - { - int newX = width - x - 1; - int newY = height - y - 1; - targetPixels[newX, newY] = sourcePixels[x, y]; - } - - this.OnRowProcessed(); - }); - } - } - - /// - /// Rotates the image 90 degrees clockwise at the centre point. - /// - /// The target image. - /// The source image. - private void Rotate90(ImageBase target, ImageBase source) - { - int width = source.Width; - int height = source.Height; - Image temp = new Image(height, width); - - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor tempPixels = temp.Lock()) - { - Parallel.For( - 0, - height, - y => - { - for (int x = 0; x < width; x++) - { - int newX = height - y - 1; - tempPixels[newX, x] = sourcePixels[x, y]; - } - - this.OnRowProcessed(); - }); - } - - target.SetPixels(height, width, temp.Pixels); - } - - /// - /// Swaps the image at the X-axis, which goes horizontally through the middle - /// at half the height of the image. - /// - /// Target image to apply the process to. - 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); - temp.ClonePixels(width, height, target.Pixels); - - using (PixelAccessor targetPixels = target.Lock()) - using (PixelAccessor tempPixels = temp.Lock()) - { - Parallel.For( - 0, - halfHeight, - y => - { - for (int x = 0; x < width; x++) - { - int newY = height - y - 1; - targetPixels[x, y] = tempPixels[x, newY]; - targetPixels[x, newY] = tempPixels[x, y]; - } - - this.OnRowProcessed(); - }); - } - } - - /// - /// Swaps the image at the Y-axis, which goes vertically through the middle - /// at half of the width of the image. - /// - /// Target image to apply the process to. - 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); - temp.ClonePixels(width, height, target.Pixels); - - using (PixelAccessor targetPixels = target.Lock()) - using (PixelAccessor tempPixels = temp.Lock()) - { - Parallel.For( - 0, - height, - y => - { - for (int x = 0; x < halfWidth; x++) - { - int newX = width - x - 1; - targetPixels[x, y] = tempPixels[newX, y]; - targetPixels[newX, y] = tempPixels[x, y]; - } - - this.OnRowProcessed(); - }); - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Processors/RotateProcessor.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/RotateProcessor.cs deleted file mode 100644 index 7aafe0e082..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Processors/RotateProcessor.cs +++ /dev/null @@ -1,68 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Numerics; - using System.Threading.Tasks; - - /// - /// Provides methods that allow the rotating of images. - /// - public class RotateProcessor : Matrix3x2Processor - { - /// - /// The tranform matrix to apply. - /// - private Matrix3x2 processMatrix; - - /// - /// Gets or sets the angle of processMatrix in degrees. - /// - public float Angle { get; set; } - - /// - /// Gets or sets a value indicating whether to expand the canvas to fit the rotated image. - /// - public bool Expand { get; set; } = true; - - /// - protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle) - { - processMatrix = Point.CreateRotation(new Point(0, 0), -this.Angle); - if (this.Expand) - { - CreateNewTarget(target, sourceRectangle, processMatrix); - } - } - - /// - 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()) - { - Parallel.For( - 0, - target.Height, - y => - { - for (int x = 0; x < target.Width; x++) - { - Point transformedPoint = Point.Rotate(new Point(x, y), matrix); - if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) - { - targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; - } - } - - OnRowProcessed(); - }); - } - } - } -} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/Samplers/Processors/SkewProcessor.cs b/src/ImageProcessorCore - Copy/Samplers/Processors/SkewProcessor.cs deleted file mode 100644 index 02d5dd9e44..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Processors/SkewProcessor.cs +++ /dev/null @@ -1,73 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - using System.Numerics; - using System.Threading.Tasks; - - /// - /// Provides methods that allow the skewing of images. - /// - public class SkewProcessor : Matrix3x2Processor - { - /// - /// The tranform matrix to apply. - /// - private Matrix3x2 processMatrix; - - /// - /// Gets or sets the angle of rotation along the x-axis in degrees. - /// - public float AngleX { get; set; } - - /// - /// Gets or sets the angle of rotation along the y-axis in degrees. - /// - public float AngleY { get; set; } - - /// - /// Gets or sets a value indicating whether to expand the canvas to fit the skewed image. - /// - public bool Expand { get; set; } = true; - - /// - 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) - { - CreateNewTarget(target, sourceRectangle, this.processMatrix); - } - } - - /// - 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()) - { - Parallel.For( - 0, - target.Height, - y => - { - for (int x = 0; x < target.Width; x++) - { - Point transformedPoint = Point.Skew(new Point(x, y), matrix); - if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) - { - targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; - } - } - - OnRowProcessed(); - }); - } - } - } -} \ No newline at end of file diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/BicubicResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/BicubicResampler.cs deleted file mode 100644 index 8aecac7a60..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Resamplers/BicubicResampler.cs +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - /// - /// The function implements the bicubic kernel algorithm W(x) as described on - /// Wikipedia - /// A commonly used algorithm within imageprocessing that preserves sharpness better than triangle interpolation. - /// - public class BicubicResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - // The coefficient. - float a = -0.5f; - - if (x < 0) - { - x = -x; - } - - float result = 0; - - if (x <= 1) - { - result = (((1.5f * x) - 2.5f) * x * x) + 1; - } - else if (x < 2) - { - result = (((((a * x) + 2.5f) * x) - 4) * x) + 2; - } - - return result; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/BoxResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/BoxResampler.cs deleted file mode 100644 index b1234e415d..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Resamplers/BoxResampler.cs +++ /dev/null @@ -1,28 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - /// - /// The function implements the box algorithm. Similar to nearest neighbour when upscaling. - /// When downscaling the pixels will average, merging together. - /// - public class BoxResampler : IResampler - { - /// - public float Radius => 0.5F; - - /// - public float GetValue(float x) - { - if (x > -0.5 && x <= 0.5) - { - return 1; - } - - return 0; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/CatmullRomResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/CatmullRomResampler.cs deleted file mode 100644 index 0b5031df88..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Resamplers/CatmullRomResampler.cs +++ /dev/null @@ -1,28 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - /// - /// The Catmull-Rom filter is a well known standard Cubic Filter often used as a interpolation function. - /// This filter produces a reasonably sharp edge, but without a the pronounced gradient change on large - /// scale image enlargements that a 'Lagrange' filter can produce. - /// - /// - public class CatmullRomResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 0; - const float C = 1 / 2f; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/HermiteResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/HermiteResampler.cs deleted file mode 100644 index 49193a3de3..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Resamplers/HermiteResampler.cs +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore.Processors -{ - /// - /// The Hermite filter is type of smoothed triangular interpolation Filter, - /// This filter rounds off strong edges while preserving flat 'color levels' in the original image. - /// - /// - public class HermiteResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 0; - const float C = 0; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/IResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/IResampler.cs deleted file mode 100644 index 0dea58440c..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Resamplers/IResampler.cs +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - /// - /// Encapsulates an interpolation algorithm for resampling images. - /// - public interface IResampler - { - /// - /// Gets the radius in which to sample pixels. - /// - float Radius { get; } - - /// - /// Gets the result of the interpolation algorithm. - /// - /// The value to process. - /// - /// The - /// - float GetValue(float x); - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos3Resampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos3Resampler.cs deleted file mode 100644 index a78b6c066a..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos3Resampler.cs +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - /// - /// The function implements the Lanczos kernel algorithm as described on - /// Wikipedia - /// with a radius of 3 pixels. - /// - public class Lanczos3Resampler : IResampler - { - /// - public float Radius => 3; - - /// - public float GetValue(float x) - { - if (x < 0) - { - x = -x; - } - - if (x < 3) - { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 3f); - } - - return 0; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos5Resampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos5Resampler.cs deleted file mode 100644 index 05af2dd7f2..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos5Resampler.cs +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - /// - /// The function implements the Lanczos kernel algorithm as described on - /// Wikipedia - /// with a radius of 5 pixels. - /// - public class Lanczos5Resampler : IResampler - { - /// - public float Radius => 5; - - /// - public float GetValue(float x) - { - if (x < 0) - { - x = -x; - } - - if (x < 5) - { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 5f); - } - - return 0; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos8Resampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos8Resampler.cs deleted file mode 100644 index 8c9a9237d9..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Resamplers/Lanczos8Resampler.cs +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - /// - /// The function implements the Lanczos kernel algorithm as described on - /// Wikipedia - /// with a radius of 8 pixels. - /// - public class Lanczos8Resampler : IResampler - { - /// - public float Radius => 8; - - /// - public float GetValue(float x) - { - if (x < 0) - { - x = -x; - } - - if (x < 8) - { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 8f); - } - - return 0; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/MitchellNetravaliResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/MitchellNetravaliResampler.cs deleted file mode 100644 index f609f26450..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Resamplers/MitchellNetravaliResampler.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 mitchell algorithm as described on - /// Wikipedia - /// - public class MitchellNetravaliResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 1 / 3f; - const float C = 1 / 3f; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/NearestNeighborResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/NearestNeighborResampler.cs deleted file mode 100644 index 58b6a9d584..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Resamplers/NearestNeighborResampler.cs +++ /dev/null @@ -1,23 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - /// - /// The function implements the nearest neighbour algorithm. This uses an unscaled filter - /// which will select the closest pixel to the new pixels position. - /// - public class NearestNeighborResampler : IResampler - { - /// - public float Radius => 1; - - /// - public float GetValue(float x) - { - return x; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxResampler.cs deleted file mode 100644 index caead12d5d..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxResampler.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 algorithm. - /// - /// - public class RobidouxResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 0.3782158F; - const float C = 0.3108921F; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxSharpResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxSharpResampler.cs deleted file mode 100644 index 633503cd16..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxSharpResampler.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 Sharp algorithm. - /// - /// - public class RobidouxSharpResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 0.26201451F; - const float C = 0.36899274F; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxSoftResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/RobidouxSoftResampler.cs deleted file mode 100644 index 8706f492bb..0000000000 --- a/src/ImageProcessorCore - Copy/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 - Copy/Samplers/Resamplers/SplineResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/SplineResampler.cs deleted file mode 100644 index 55ef5656a9..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Resamplers/SplineResampler.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 spline algorithm. - /// - /// - public class SplineResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 1; - const float C = 0; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/TriangleResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/TriangleResampler.cs deleted file mode 100644 index cb404b7369..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Resamplers/TriangleResampler.cs +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - /// - /// The function implements the triangle (bilinear) algorithm. - /// Bilinear interpolation can be used where perfect image transformation with pixel matching is impossible, - /// so that one can calculate and assign appropriate intensity values to pixels. - /// - public class TriangleResampler : IResampler - { - /// - public float Radius => 1; - - /// - public float GetValue(float x) - { - if (x < 0) - { - x = -x; - } - - if (x < 1) - { - return 1 - x; - } - - return 0; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resamplers/WelchResampler.cs b/src/ImageProcessorCore - Copy/Samplers/Resamplers/WelchResampler.cs deleted file mode 100644 index 3ecaa6a747..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Resamplers/WelchResampler.cs +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageProcessorCore -{ - /// - /// The function implements the welch algorithm. - /// - /// - public class WelchResampler : IResampler - { - /// - public float Radius => 3; - - /// - public float GetValue(float x) - { - if (x < 0) - { - x = -x; - } - - if (x < 3) - { - return ImageMaths.SinC(x) * (1.0f - (x * x / 9.0f)); - } - - return 0; - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Resize.cs b/src/ImageProcessorCore - Copy/Samplers/Resize.cs deleted file mode 100644 index 2eadd7a11c..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Resize.cs +++ /dev/null @@ -1,134 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// ------------------------------------------------------------------------------------------------------------------- - -namespace ImageProcessorCore -{ - using Processors; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Resizes an image in accordance with the given . - /// - /// The image to resize. - /// The resize options. - /// A delegate which is called as progress is made processing the image. - /// The - /// Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, ResizeOptions options, ProgressEventHandler progressHandler = null) - { - // Ensure size is populated across both dimensions. - if (options.Size.Width == 0 && options.Size.Height > 0) - { - options.Size = new Size(source.Width * options.Size.Height / source.Height, options.Size.Height); - } - - if (options.Size.Height == 0 && options.Size.Width > 0) - { - options.Size = new Size(options.Size.Width, source.Height * options.Size.Width / source.Width); - } - - Rectangle targetRectangle = ResizeHelper.CalculateTargetLocationAndBounds(source, options); - - return Resize(source, options.Size.Width, options.Size.Height, options.Sampler, source.Bounds, targetRectangle, options.Compand, progressHandler); - } - - /// - /// Resizes an image to the given width and height. - /// - /// The image to resize. - /// The target image width. - /// The target image height. - /// A delegate which is called as progress is made processing the image. - /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, ProgressEventHandler progressHandler = null) - { - return Resize(source, width, height, new BicubicResampler(), false, progressHandler); - } - - /// - /// Resizes an image to the given width and height. - /// - /// The image to resize. - /// The target image width. - /// The target image height. - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// A delegate which is called as progress is made processing the image. - /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, bool compand, ProgressEventHandler progressHandler = null) - { - return Resize(source, width, height, new BicubicResampler(), compand, progressHandler); - } - - /// - /// Resizes an image to the given width and height with the given sampler. - /// - /// The image to resize. - /// The target image width. - /// The target image height. - /// The to perform the resampling. - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// A delegate which is called as progress is made processing the image. - /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, IResampler sampler, bool compand, ProgressEventHandler progressHandler = null) - { - return Resize(source, width, height, sampler, source.Bounds, new Rectangle(0, 0, width, height), compand, progressHandler); - } - - /// - /// Resizes an image to the given width and height with the given sampler and - /// source rectangle. - /// - /// The image to resize. - /// The target image width. - /// The target image height. - /// The to perform the resampling. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// - /// The structure that specifies the portion of the target image object to draw to. - /// - /// Whether to compress and expand the image color-space to gamma correct the image during processing. - /// A delegate which is called as progress is made processing the image. - /// The - /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand = false, ProgressEventHandler progressHandler = null) - { - if (width == 0 && height > 0) - { - width = source.Width * height / source.Height; - targetRectangle.Width = width; - } - - if (height == 0 && width > 0) - { - height = source.Height * width / source.Width; - targetRectangle.Height = height; - } - - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - ResizeProcessor processor = new ResizeProcessor(sampler) { Compand = compand }; - processor.OnProgress += progressHandler; - - try - { - return source.Process(width, height, sourceRectangle, targetRectangle, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Rotate.cs b/src/ImageProcessorCore - Copy/Samplers/Rotate.cs deleted file mode 100644 index fa30d9d342..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Rotate.cs +++ /dev/null @@ -1,50 +0,0 @@ -// -// 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 - { - /// - /// Rotates an image by the given angle in degrees, expanding the image to fit the rotated result. - /// - /// 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) - { - return Rotate(source, degrees, true, progressHandler); - } - - /// - /// Rotates an image by the given angle in degrees. - /// - /// 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) - { - RotateProcessor processor = new RotateProcessor { Angle = degrees, Expand = expand }; - processor.OnProgress += progressHandler; - - try - { - return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/RotateFlip.cs b/src/ImageProcessorCore - Copy/Samplers/RotateFlip.cs deleted file mode 100644 index 93449bcd06..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/RotateFlip.cs +++ /dev/null @@ -1,38 +0,0 @@ -// -// 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 - { - /// - /// Rotates and flips an image by the given instructions. - /// - /// 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) - { - RotateFlipProcessor processor = new RotateFlipProcessor(rotateType, flipType); - processor.OnProgress += progressHandler; - - try - { - return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/Samplers/Skew.cs b/src/ImageProcessorCore - Copy/Samplers/Skew.cs deleted file mode 100644 index 904f1d89df..0000000000 --- a/src/ImageProcessorCore - Copy/Samplers/Skew.cs +++ /dev/null @@ -1,52 +0,0 @@ -// -// 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 - { - /// - /// Skews an image by the given angles in degrees, expanding the image to fit the skewed result. - /// - /// 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) - { - return Skew(source, degreesX, degreesY, true, progressHandler); - } - - /// - /// Skews an image by the given angles in degrees. - /// - /// 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) - { - SkewProcessor processor = new SkewProcessor { AngleX = degreesX, AngleY = degreesY, Expand = expand }; - processor.OnProgress += progressHandler; - - try - { - return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, processor); - } - finally - { - processor.OnProgress -= progressHandler; - } - } - } -} diff --git a/src/ImageProcessorCore - Copy/project.json b/src/ImageProcessorCore - Copy/project.json deleted file mode 100644 index e3800d495e..0000000000 --- a/src/ImageProcessorCore - Copy/project.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "version": "1.0.0-*", - "title": "ImageProcessorCore", - "description": "A cross-platform library for processing of image files written in C#", - "authors": [ - "James Jackson-South and contributors" - ], - "packOptions": { - "projectUrl": "https://github.com/JimBobSquarePants/ImageProcessor", - "licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0", - "tags": [ - "Image Resize Crop Quality Gif Jpg Jpeg Bitmap Png Fluent Animated" - ] - }, - "buildOptions": { - "allowUnsafe": true, - "debugType": "portable" - }, - "dependencies": { - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Diagnostics.Tools": "4.0.1", - "System.IO": "4.1.0", - "System.IO.Compression": "4.1.0", - "System.Linq": "4.1.0", - "System.Numerics.Vectors": "4.1.1", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime.Extensions": "4.1.0", - "System.Runtime.InteropServices": "4.1.0", - "System.Text.Encoding.Extensions": "4.0.11", - "System.Threading": "4.0.11", - "System.Threading.Tasks": "4.0.11", - "System.Threading.Tasks.Parallel": "4.0.1" - }, - "frameworks": { - "netstandard1.1": {} - } -} \ No newline at end of file