diff --git a/GenericImage/Common/Helpers/ImageMaths.cs b/GenericImage/Common/Helpers/ImageMaths.cs new file mode 100644 index 000000000..2c5e4f884 --- /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 000000000..9ffcf7223 --- /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 000000000..4de7d3968 --- /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 000000000..7eb9b445b --- /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 000000000..1b673079d --- /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 000000000..be87f6d83 --- /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 000000000..832366ba1 --- /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 000000000..46dbd8dea --- /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 000000000..8dd035951 --- /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 000000000..617c22489 --- /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 9d828a74a..6e8766591 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