diff --git a/GenericImage/ImageBase.cs b/GenericImage/ImageBase.cs new file mode 100644 index 000000000..9eed0dccc --- /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 8ec301c2e..c5a48d0f9 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 53b01d1c3..f62eeb4de 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 3af32aae2..25b49ea21 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 e31d693bb..cc4fad541 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 000000000..b0dbe341c --- /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 000000000..85ffa0086 --- /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 000000000..1565879a7 --- /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 000000000..10838726c --- /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 3cde69543..88d4d1b6c 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 5b7ab65f8..4dcb73174 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 e9ff7214b..42cf08da4 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 000000000..fc6a788b0 --- /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 000000000..02e10cc3a --- /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 2553b2536..bf01cb856 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 +}