From 5346e83b84a73867363f10cb9fe20572222f6d54 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Fri, 22 Mar 2024 23:43:50 +0100 Subject: [PATCH] Added set of helpers for easier image handling --- .../Extensions/ImageExtensions.cs | 9 + .../Helper/Images/Colors/ColorABGR.cs | 66 +++ .../Helper/Images/Colors/ColorARGB.cs | 66 +++ .../Helper/Images/Colors/ColorBGR.cs | 63 +++ .../Helper/Images/Colors/ColorBGRA.cs | 66 +++ .../Helper/Images/Colors/ColorFormat.cs | 58 +++ .../Helper/Images/Colors/ColorRGB.cs | 63 +++ .../Helper/Images/Colors/ColorRGBA.cs | 66 +++ .../Helper/Images/Colors/IColor.cs | 34 ++ StableDiffusion.NET/Helper/Images/IImage.cs | 193 ++++++++ StableDiffusion.NET/Helper/Images/Image.cs | 441 ++++++++++++++++++ .../Helper/Images/ReadOnlyRefEnumerable.cs | 266 +++++++++++ StableDiffusion.NET/Helper/Images/RefImage.cs | 368 +++++++++++++++ 13 files changed, 1759 insertions(+) create mode 100644 StableDiffusion.NET/Extensions/ImageExtensions.cs create mode 100644 StableDiffusion.NET/Helper/Images/Colors/ColorABGR.cs create mode 100644 StableDiffusion.NET/Helper/Images/Colors/ColorARGB.cs create mode 100644 StableDiffusion.NET/Helper/Images/Colors/ColorBGR.cs create mode 100644 StableDiffusion.NET/Helper/Images/Colors/ColorBGRA.cs create mode 100644 StableDiffusion.NET/Helper/Images/Colors/ColorFormat.cs create mode 100644 StableDiffusion.NET/Helper/Images/Colors/ColorRGB.cs create mode 100644 StableDiffusion.NET/Helper/Images/Colors/ColorRGBA.cs create mode 100644 StableDiffusion.NET/Helper/Images/Colors/IColor.cs create mode 100644 StableDiffusion.NET/Helper/Images/IImage.cs create mode 100644 StableDiffusion.NET/Helper/Images/Image.cs create mode 100644 StableDiffusion.NET/Helper/Images/ReadOnlyRefEnumerable.cs create mode 100644 StableDiffusion.NET/Helper/Images/RefImage.cs diff --git a/StableDiffusion.NET/Extensions/ImageExtensions.cs b/StableDiffusion.NET/Extensions/ImageExtensions.cs new file mode 100644 index 0000000..704251b --- /dev/null +++ b/StableDiffusion.NET/Extensions/ImageExtensions.cs @@ -0,0 +1,9 @@ +using StableDiffusion.NET.Helper.Images; +using StableDiffusion.NET.Helper.Images.Colors; + +namespace StableDiffusion.NET; + +public static class ImageExtension +{ + public static Image ToImage(this StableDiffusionImage image) => new(image.Data.ToArray(), 0, 0, image.Width, image.Height, image.Width); +} \ No newline at end of file diff --git a/StableDiffusion.NET/Helper/Images/Colors/ColorABGR.cs b/StableDiffusion.NET/Helper/Images/Colors/ColorABGR.cs new file mode 100644 index 0000000..5f1d95c --- /dev/null +++ b/StableDiffusion.NET/Helper/Images/Colors/ColorABGR.cs @@ -0,0 +1,66 @@ +// ReSharper disable ConvertToAutoProperty + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace StableDiffusion.NET.Helper.Images.Colors; + +/// +/// Represents a color in 32 bit ABGR-format. +/// +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly struct ColorABGR : IColor +{ + #region Properties & Fields + + /// + public static ColorFormat ColorFormat => ColorFormat.ABGR; + + private readonly byte _a; + private readonly byte _b; + private readonly byte _g; + private readonly byte _r; + + // ReSharper disable ConvertToAutoPropertyWhenPossible + /// + public byte A => _a; + + /// + public byte B => _b; + + /// + public byte G => _g; + + /// + public byte R => _r; + // ReSharper restore ConvertToAutoPropertyWhenPossible + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The alpha-component of the color. + /// The blue-component of the color. + /// The green-component of the color. + /// The red-component of the color. + public ColorABGR(byte a, byte b, byte g, byte r) + { + this._a = a; + this._b = b; + this._g = g; + this._r = r; + } + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {_a}, R: {_r}, G: {_g}, B: {_b}]"; + + #endregion +} \ No newline at end of file diff --git a/StableDiffusion.NET/Helper/Images/Colors/ColorARGB.cs b/StableDiffusion.NET/Helper/Images/Colors/ColorARGB.cs new file mode 100644 index 0000000..4bb6f3f --- /dev/null +++ b/StableDiffusion.NET/Helper/Images/Colors/ColorARGB.cs @@ -0,0 +1,66 @@ +// ReSharper disable ConvertToAutoProperty + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace StableDiffusion.NET.Helper.Images.Colors; + +/// +/// Represents a color in 32 bit ARGB-format. +/// +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly struct ColorARGB : IColor +{ + #region Properties & Fields + + /// + public static ColorFormat ColorFormat => ColorFormat.ARGB; + + private readonly byte _a; + private readonly byte _r; + private readonly byte _g; + private readonly byte _b; + + // ReSharper disable ConvertToAutoPropertyWhenPossible + /// + public byte A => _a; + + /// + public byte R => _r; + + /// + public byte G => _g; + + /// + public byte B => _b; + // ReSharper restore ConvertToAutoPropertyWhenPossible + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The alpha-component of the color. + /// The red-component of the color. + /// The green-component of the color. + /// The blue-component of the color. + public ColorARGB(byte a, byte r, byte g, byte b) + { + this._a = a; + this._r = r; + this._g = g; + this._b = b; + } + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {_a}, R: {_r}, G: {_g}, B: {_b}]"; + + #endregion +} \ No newline at end of file diff --git a/StableDiffusion.NET/Helper/Images/Colors/ColorBGR.cs b/StableDiffusion.NET/Helper/Images/Colors/ColorBGR.cs new file mode 100644 index 0000000..510e738 --- /dev/null +++ b/StableDiffusion.NET/Helper/Images/Colors/ColorBGR.cs @@ -0,0 +1,63 @@ +// ReSharper disable ConvertToAutoProperty + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace StableDiffusion.NET.Helper.Images.Colors; + +/// +/// Represents a color in 24 bit BGR-format. +/// +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly struct ColorBGR : IColor +{ + #region Properties & Fields + + /// + public static ColorFormat ColorFormat => ColorFormat.BGR; + + private readonly byte _b; + private readonly byte _g; + private readonly byte _r; + + // ReSharper disable ConvertToAutoPropertyWhenPossible + /// + public byte A => byte.MaxValue; + + /// + public byte B => _b; + + /// + public byte G => _g; + + /// + public byte R => _r; + // ReSharper restore ConvertToAutoPropertyWhenPossible + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The blue-component of the color. + /// The green-component of the color. + /// The red-component of the color. + public ColorBGR(byte b, byte g, byte r) + { + this._b = b; + this._g = g; + this._r = r; + } + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {A}, R: {_r}, G: {_g}, B: {_b}]"; + + #endregion +} \ No newline at end of file diff --git a/StableDiffusion.NET/Helper/Images/Colors/ColorBGRA.cs b/StableDiffusion.NET/Helper/Images/Colors/ColorBGRA.cs new file mode 100644 index 0000000..f1c557e --- /dev/null +++ b/StableDiffusion.NET/Helper/Images/Colors/ColorBGRA.cs @@ -0,0 +1,66 @@ +// ReSharper disable ConvertToAutoProperty + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace StableDiffusion.NET.Helper.Images.Colors; + +/// +/// Represents a color in 32 bit BGRA-format. +/// +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly struct ColorBGRA : IColor +{ + #region Properties & Fields + + /// + public static ColorFormat ColorFormat => ColorFormat.BGRA; + + private readonly byte _b; + private readonly byte _g; + private readonly byte _r; + private readonly byte _a; + + // ReSharper disable ConvertToAutoPropertyWhenPossible + /// + public byte B => _b; + + /// + public byte G => _g; + + /// + public byte R => _r; + + /// + public byte A => _a; + // ReSharper restore ConvertToAutoPropertyWhenPossible + +#endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The blue-component of the color. + /// The green-component of the color. + /// The red-component of the color. + /// The alpha-component of the color. + public ColorBGRA(byte b, byte g, byte r, byte a) + { + this._b = b; + this._g = g; + this._r = r; + this._a = a; + } + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {_a}, R: {_r}, G: {_g}, B: {_b}]"; + + #endregion +} \ No newline at end of file diff --git a/StableDiffusion.NET/Helper/Images/Colors/ColorFormat.cs b/StableDiffusion.NET/Helper/Images/Colors/ColorFormat.cs new file mode 100644 index 0000000..5ef40af --- /dev/null +++ b/StableDiffusion.NET/Helper/Images/Colors/ColorFormat.cs @@ -0,0 +1,58 @@ +namespace StableDiffusion.NET.Helper.Images.Colors; + +/// +/// Represents a color format. +/// +public readonly struct ColorFormat +{ + #region Instances + + public static readonly ColorFormat BGRA = new(1, 4); + public static readonly ColorFormat ABGR = new(2, 4); + public static readonly ColorFormat RGBA = new(3, 4); + public static readonly ColorFormat ARGB = new(4, 4); + public static readonly ColorFormat BGR = new(5, 3); + public static readonly ColorFormat RGB = new(6, 3); + + #endregion + + #region Properties & Fields + + /// + /// Gets the Id of the color-format. + /// + public readonly int Id; + + /// + /// Gets the Bytes per pixel for this color-format. + /// + public readonly int BytesPerPixel; + + #endregion + + #region Constructors + + private ColorFormat(int id, int bytesPerPixel) + { + this.Id = id; + this.BytesPerPixel = bytesPerPixel; + } + + #endregion + + #region Methods + + public bool Equals(ColorFormat other) => Id == other.Id; + public override bool Equals(object? obj) => obj is ColorFormat other && Equals(other); + + public override int GetHashCode() => Id; + + #endregion + + #region Operators + + public static bool operator ==(ColorFormat left, ColorFormat right) => left.Equals(right); + public static bool operator !=(ColorFormat left, ColorFormat right) => !(left == right); + + #endregion +} \ No newline at end of file diff --git a/StableDiffusion.NET/Helper/Images/Colors/ColorRGB.cs b/StableDiffusion.NET/Helper/Images/Colors/ColorRGB.cs new file mode 100644 index 0000000..c3db227 --- /dev/null +++ b/StableDiffusion.NET/Helper/Images/Colors/ColorRGB.cs @@ -0,0 +1,63 @@ +// ReSharper disable ConvertToAutoProperty + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace StableDiffusion.NET.Helper.Images.Colors; + +/// +/// Represents a color in 24 bit RGB-format. +/// +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly struct ColorRGB : IColor +{ + #region Properties & Fields + + /// + public static ColorFormat ColorFormat => ColorFormat.RGB; + + private readonly byte _r; + private readonly byte _g; + private readonly byte _b; + + // ReSharper disable ConvertToAutoPropertyWhenPossible + /// + public byte A => byte.MaxValue; + + /// + public byte R => _r; + + /// + public byte G => _g; + + /// + public byte B => _b; + // ReSharper restore ConvertToAutoPropertyWhenPossible + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The red-component of the color. + /// The green-component of the color. + /// The blue-component of the color. + public ColorRGB(byte r, byte g, byte b) + { + this._r = r; + this._g = g; + this._b = b; + } + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {A}, R: {_r}, G: {_g}, B: {_b}]"; + + #endregion +} \ No newline at end of file diff --git a/StableDiffusion.NET/Helper/Images/Colors/ColorRGBA.cs b/StableDiffusion.NET/Helper/Images/Colors/ColorRGBA.cs new file mode 100644 index 0000000..53ab01d --- /dev/null +++ b/StableDiffusion.NET/Helper/Images/Colors/ColorRGBA.cs @@ -0,0 +1,66 @@ +// ReSharper disable ConvertToAutoProperty + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace StableDiffusion.NET.Helper.Images.Colors; + +/// +/// Represents a color in 32 bit RGBA-format. +/// +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly struct ColorRGBA : IColor +{ + #region Properties & Fields + + /// + public static ColorFormat ColorFormat => ColorFormat.RGBA; + + private readonly byte _r; + private readonly byte _g; + private readonly byte _b; + private readonly byte _a; + + // ReSharper disable ConvertToAutoPropertyWhenPossible + /// + public byte R => _r; + + /// + public byte G => _g; + + /// + public byte B => _b; + + /// + public byte A => _a; + // ReSharper restore ConvertToAutoPropertyWhenPossible + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The red-component of the color. + /// The green-component of the color. + /// The blue-component of the color. + /// The alpha-component of the color. + public ColorRGBA(byte r, byte g, byte b, byte a) + { + this._r = r; + this._g = g; + this._b = b; + this._a = a; + } + + #endregion + + #region Methods + + /// + public override string ToString() => $"[A: {_a}, R: {_r}, G: {_g}, B: {_b}]"; + + #endregion +} \ No newline at end of file diff --git a/StableDiffusion.NET/Helper/Images/Colors/IColor.cs b/StableDiffusion.NET/Helper/Images/Colors/IColor.cs new file mode 100644 index 0000000..b7c1c6c --- /dev/null +++ b/StableDiffusion.NET/Helper/Images/Colors/IColor.cs @@ -0,0 +1,34 @@ +using System; + +namespace StableDiffusion.NET.Helper.Images.Colors; + +/// +/// Represents a generic color made of 4 bytes (alpha, red, green and blue) +/// +public interface IColor +{ + /// + /// Gets the red-component of this color. + /// + byte R { get; } + + /// + /// Gets the green-component of this color. + /// + byte G { get; } + + /// + /// Gets the blue-component of this color. + /// + byte B { get; } + + /// + /// Gets the alpha-component of this color. + /// + byte A { get; } + + /// + /// Gets the color-format of this color. + /// + public static virtual ColorFormat ColorFormat => throw new NotSupportedException(); +} \ No newline at end of file diff --git a/StableDiffusion.NET/Helper/Images/IImage.cs b/StableDiffusion.NET/Helper/Images/IImage.cs new file mode 100644 index 0000000..b06aaaf --- /dev/null +++ b/StableDiffusion.NET/Helper/Images/IImage.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using StableDiffusion.NET.Helper.Images.Colors; + +namespace StableDiffusion.NET.Helper.Images; + +/// +/// Represents a image. +/// +public interface IImage : IEnumerable +{ + /// + /// Gets the color format used in this image. + /// + ColorFormat ColorFormat { get; } + + /// + /// Gets the width of this image. + /// + int Width { get; } + + /// + /// Gets the height of this image. + /// + int Height { get; } + + /// + /// Gets the size in bytes of this image. + /// + int SizeInBytes { get; } + + /// + /// Gets the color at the specified location. + /// + /// The X-location to read. + /// The Y-location to read. + /// The color at the specified location. + IColor this[int x, int y] { get; } + + /// + /// Gets an image representing the specified location. + /// + /// The X-location of the image. + /// The Y-location of the image. + /// The width of the sub-image. + /// + /// + IImage this[int x, int y, int width, int height] { get; } + + /// + /// Gets a list of all rows of this image. + /// + IImageRows Rows { get; } + + /// + /// Gets a list of all columns of this image. + /// + IImageColumns Columns { get; } + + /// + /// Gets an representing this . + /// + /// The color-type of the iamge. + /// The . + RefImage AsRefImage() where TColor : struct, IColor; + + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + void CopyTo(in Span destination); + + /// + /// Allocates a new array and copies this into it. + /// + /// The new array containing the data of this . + byte[] ToArray(); + + /// + /// Represents a list of rows of an image. + /// + public interface IImageRows : IEnumerable + { + /// + /// Gets the amount of rows in this list. + /// + int Count { get; } + + /// + /// Gets a specific . + /// + /// The ´row to get. + /// The requested . + IImageRow this[int column] { get; } + } + + /// + /// Represents a list of columns of an image. + /// + public interface IImageColumns : IEnumerable + { + /// + /// Gets the amount of columns in this list. + /// + int Count { get; } + + /// + /// Gets a specific . + /// + /// The column to get. + /// The requested . + IImageColumn this[int column] { get; } + } + + /// + /// Represents a single row of an image. + /// + public interface IImageRow : IEnumerable + { + /// + /// Gets the length of the row. + /// + int Length { get; } + + /// + /// Gets the size in bytes of this row. + /// + int SizeInBytes { get; } + + /// + /// Gets the at the specified location. + /// + /// The location to get the color from. + /// The at the specified location. + IColor this[int x] { get; } + + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + void CopyTo(in Span destination); + + /// + /// Allocates a new array and copies this into it. + /// + /// The new array containing the data of this . + byte[] ToArray(); + } + + /// + /// Represents a single column of an image. + /// + public interface IImageColumn : IEnumerable + { + /// + /// Gets the length of the column. + /// + int Length { get; } + + /// + /// Gets the size in bytes of this column. + /// + int SizeInBytes { get; } + + /// + /// Gets the at the specified location. + /// + /// The location to get the color from. + /// The at the specified location. + IColor this[int y] { get; } + + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + void CopyTo(in Span destination); + + /// + /// Allocates a new array and copies this into it. + /// + /// The new array containing the data of this . + byte[] ToArray(); + } +} \ No newline at end of file diff --git a/StableDiffusion.NET/Helper/Images/Image.cs b/StableDiffusion.NET/Helper/Images/Image.cs new file mode 100644 index 0000000..f0e1db4 --- /dev/null +++ b/StableDiffusion.NET/Helper/Images/Image.cs @@ -0,0 +1,441 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using StableDiffusion.NET.Helper.Images.Colors; + +namespace StableDiffusion.NET.Helper.Images; + +/// +public sealed class Image : IImage + where TColor : struct, IColor +{ + #region Properties & Fields + + private readonly byte[] _buffer; + + private readonly int _x; + private readonly int _y; + private readonly int _stride; + + /// + public ColorFormat ColorFormat + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => TColor.ColorFormat; + } + + /// + public int Width { get; } + + /// + public int Height { get; } + + /// + public int SizeInBytes => Width * Height * ColorFormat.BytesPerPixel; + + #endregion + + #region Indexer + + /// + public IColor this[int x, int y] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((x < 0) || (y < 0) || (x >= Width) || (y >= Height)) throw new IndexOutOfRangeException(); + + return MemoryMarshal.Cast(_buffer)[((_y + y) * _stride) + (_x + x)]; + } + } + + /// + public IImage this[int x, int y, int width, int height] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) > Width) || ((y + height) > Height)) throw new IndexOutOfRangeException(); + + return new Image(_buffer, _x + x, _y + y, width, height, _stride); + } + } + + /// + public IImage.IImageRows Rows + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new ImageRows(_buffer, _x, _y, Width, Height, _stride); + } + + /// + public IImage.IImageColumns Columns + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new ImageColumns(_buffer, _x, _y, Width, Height, _stride); + } + + #endregion + + #region Constructors + + public Image(byte[] buffer, int x, int y, int width, int height, int stride) + { + this._buffer = buffer; + this._x = x; + this._y = y; + this.Width = width; + this.Height = height; + this._stride = stride; + } + + #endregion + + #region Methods + + /// + public void CopyTo(in Span destination) + { + if (destination == null) throw new ArgumentNullException(nameof(destination)); + if (destination.Length < SizeInBytes) throw new ArgumentException("The destination is too small to fit this image.", nameof(destination)); + + int targetStride = Width * ColorFormat.BytesPerPixel; + IImage.IImageRows rows = Rows; + Span target = destination; + foreach (IImage.IImageRow row in rows) + { + row.CopyTo(target); + target = target[targetStride..]; + } + } + + /// + public byte[] ToArray() + { + byte[] array = new byte[SizeInBytes]; + CopyTo(array); + return array; + } + + /// + public RefImage AsRefImage() + where T : struct, IColor + { + if (typeof(T) != typeof(TColor)) throw new ArgumentException("The requested color format does not fit this image.", nameof(T)); + + return new RefImage(MemoryMarshal.Cast(_buffer), _x, _y, Width, Height, _stride); + } + + /// + public IEnumerator GetEnumerator() + { + for (int y = 0; y < Height; y++) + for (int x = 0; x < Width; x++) + yield return this[x, y]; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion + + #region Indexer-Classes + + /// + private sealed class ImageRows : IImage.IImageRows + { + #region Properties & Fields + + private readonly byte[] _buffer; + private readonly int _x; + private readonly int _y; + private readonly int _width; + private readonly int _height; + private readonly int _stride; + + /// + public int Count => _height; + + #endregion + + #region Indexer + + /// + public IImage.IImageRow this[int row] + { + get + { + if ((row < 0) || (row >= _height)) throw new IndexOutOfRangeException(); + + return new ImageRow(_buffer, (((row + _y) * _stride) + _x), _width); + } + } + + #endregion + + #region Constructors + + internal ImageRows(byte[] buffer, int x, int y, int width, int height, int stride) + { + this._buffer = buffer; + this._x = x; + this._y = y; + this._width = width; + this._height = height; + this._stride = stride; + } + + #endregion + + #region Methods + + /// + public IEnumerator GetEnumerator() + { + for (int y = 0; y < _height; y++) + yield return this[y]; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion + } + + /// + private sealed class ImageRow : IImage.IImageRow + { + #region Properties & Fields + + private readonly byte[] _buffer; + private readonly int _start; + private readonly int _length; + + /// + public int Length => _length; + +#if NET7_0_OR_GREATER + /// + public int SizeInBytes => Length * TColor.ColorFormat.BytesPerPixel; +#else + /// + public int SizeInBytes => Length * default(TColor).Net6ColorFormat.BytesPerPixel; +#endif + + #endregion + + #region Indexer + + /// + public IColor this[int x] + { + get + { + if ((x < 0) || (x >= _length)) throw new IndexOutOfRangeException(); + + ReadOnlySpan row = MemoryMarshal.Cast(_buffer)[_start..]; + return row[x]; + } + } + + #endregion + + #region Constructors + + internal ImageRow(byte[] buffer, int start, int length) + { + this._buffer = buffer; + this._start = start; + this._length = length; + } + + #endregion + + #region Methods + + /// + public void CopyTo(in Span destination) + { + if (destination == null) throw new ArgumentNullException(nameof(destination)); + if (destination.Length < SizeInBytes) throw new ArgumentException("The destination is too small to fit this image.", nameof(destination)); + + MemoryMarshal.Cast(_buffer).Slice(_start, _length).CopyTo(MemoryMarshal.Cast(destination)); + } + + /// + public byte[] ToArray() + { + byte[] array = new byte[SizeInBytes]; + CopyTo(array); + return array; + } + + /// + public IEnumerator GetEnumerator() + { + for (int x = 0; x < _length; x++) + yield return this[x]; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion + } + + /// + private sealed class ImageColumns : IImage.IImageColumns + { + #region Properties & Fields + + private readonly byte[] _buffer; + private readonly int _x; + private readonly int _y; + private readonly int _width; + private readonly int _height; + private readonly int _stride; + + /// + public int Count => _width; + + #endregion + + #region Indexer + + /// + public IImage.IImageColumn this[int column] + { + get + { + if ((column < 0) || (column >= _width)) throw new IndexOutOfRangeException(); + + return new ImageColumn(_buffer, (_y * _stride) + _x + column, _height, _stride); + } + } + + #endregion + + #region Constructors + + internal ImageColumns(byte[] buffer, int x, int y, int width, int height, int stride) + { + this._buffer = buffer; + this._x = x; + this._y = y; + this._width = width; + this._height = height; + this._stride = stride; + } + + #endregion + + #region Methods + + /// + public IEnumerator GetEnumerator() + { + for (int y = 0; y < _height; y++) + yield return this[y]; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion + } + + /// + private sealed class ImageColumn : IImage.IImageColumn + { + #region Properties & Fields + + private readonly byte[] _buffer; + private readonly int _start; + private readonly int _length; + private readonly int _step; + + /// + public int Length => _length; + +#if NET7_0_OR_GREATER + /// + public int SizeInBytes => Length * TColor.ColorFormat.BytesPerPixel; +#else + /// + public int SizeInBytes => Length * default(TColor).Net6ColorFormat.BytesPerPixel; +#endif + + #endregion + + #region Indexer + + /// + public IColor this[int y] + { + get + { + if ((y < 0) || (y >= _length)) throw new IndexOutOfRangeException(); + + ReadOnlySpan data = MemoryMarshal.Cast(_buffer)[_start..]; + return data[y * _step]; + } + } + + #endregion + + #region Constructors + + internal ImageColumn(byte[] buffer, int start, int length, int step) + { + this._buffer = buffer; + this._start = start; + this._length = length; + this._step = step; + } + + #endregion + + #region Methods + + /// + public void CopyTo(in Span destination) + { + if (destination == null) throw new ArgumentNullException(nameof(destination)); + if (destination.Length < SizeInBytes) throw new ArgumentException("The destination is too small to fit this image.", nameof(destination)); + + if (_step == 1) + _buffer.AsSpan(_start, SizeInBytes).CopyTo(destination); + else + { + ReadOnlySpan data = MemoryMarshal.Cast(_buffer)[_start..]; + Span target = MemoryMarshal.Cast(destination); + for (int i = 0; i < Length; i++) + target[i] = data[i * _step]; + } + } + + /// + public byte[] ToArray() + { + byte[] array = new byte[SizeInBytes]; + CopyTo(array); + return array; + } + + /// + public IEnumerator GetEnumerator() + { + for (int y = 0; y < _length; y++) + yield return this[y]; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion + } + + #endregion +} \ No newline at end of file diff --git a/StableDiffusion.NET/Helper/Images/ReadOnlyRefEnumerable.cs b/StableDiffusion.NET/Helper/Images/ReadOnlyRefEnumerable.cs new file mode 100644 index 0000000..97b188a --- /dev/null +++ b/StableDiffusion.NET/Helper/Images/ReadOnlyRefEnumerable.cs @@ -0,0 +1,266 @@ +// DarthAffe 05.09.2023: Based on https://github.com/CommunityToolkit/dotnet/blob/b0d6c4f9c0cfb5d860400abb00b0ca1b3e94dfa4/src/CommunityToolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable%7BT%7D.cs + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace StableDiffusion.NET.Helper.Images; + +/// +/// A that iterates readonly items from arbitrary memory locations. +/// +/// The type of items to enumerate. +public readonly ref struct ReadOnlyRefEnumerable +{ + #region Properties & Fields + + /// + /// The instance pointing to the first item in the target memory area. + /// + /// The field maps to the total available length. + private readonly ReadOnlySpan _span; + + /// + /// The distance between items in the sequence to enumerate. + /// + /// The distance refers to items, not byte offset. + private readonly int _step; + + /// + /// Gets the total available length for the sequence. + /// + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _span.Length; + } + + /// + /// Gets the element at the specified zero-based index. + /// + /// The zero-based index of the element. + /// A reference to the element at the specified index. + /// + /// Thrown when is invalid. + /// + public ref readonly T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((uint)index >= (uint)Length) throw new IndexOutOfRangeException(); + + ref T r0 = ref MemoryMarshal.GetReference(_span); + nint offset = (nint)(uint)index * (nint)(uint)_step; + ref T ri = ref Unsafe.Add(ref r0, offset); + + return ref ri; + } + } + + /// + /// Gets the element at the specified zero-based index. + /// + /// The zero-based index of the element. + /// A reference to the element at the specified index. + /// + /// Thrown when is invalid. + /// + public ref readonly T this[Index index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref this[index.GetOffset(Length)]; + } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the struct. + /// + /// A reference to the first item of the sequence. + /// The number of items in the sequence. + /// The distance between items in the sequence to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlyRefEnumerable(in T reference, int length, int step) + { + this._step = step; + + _span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(reference), length); + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Enumerator GetEnumerator() => new(_span, _step); + + public T[] ToArray() + { + int length = _span.Length; + + // Empty array if no data is mapped + if (length == 0) + return Array.Empty(); + + T[] array = new T[length]; + CopyTo(array); + + return array; + } + + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + public void CopyTo(Span destination) + { + if (_step == 1) + { + _span.CopyTo(destination); + return; + } + + ref T sourceRef = ref MemoryMarshal.GetReference(_span); + int length = _span.Length; + if ((uint)destination.Length < (uint)length) + throw new ArgumentException("The target span is too short to copy all the current items to."); + + ref T destinationRef = ref MemoryMarshal.GetReference(destination); + + CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)length, (nint)(uint)_step); + } + + /// + /// Attempts to copy the current instance to a destination . + /// + /// The target of the copy operation. + /// Whether or not the operation was successful. + public bool TryCopyTo(Span destination) + { + if (destination.Length >= _span.Length) + { + CopyTo(destination); + return true; + } + + return false; + } + + private static void CopyTo(ref T sourceRef, ref T destinationRef, nint length, nint sourceStep) + { + nint sourceOffset = 0; + nint destinationOffset = 0; + + while (length >= 8) + { + Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset); + Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 4) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 5) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 6) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 7) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + + length -= 8; + sourceOffset += sourceStep; + destinationOffset += 8; + } + + if (length >= 4) + { + Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset); + Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); + + length -= 4; + sourceOffset += sourceStep; + destinationOffset += 4; + } + + while (length > 0) + { + Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset); + + length -= 1; + sourceOffset += sourceStep; + destinationOffset += 1; + } + } + + #endregion + + /// + /// A custom enumerator type to traverse items within a instance. + /// + public ref struct Enumerator + { + #region Properties & Fields + + /// + private readonly ReadOnlySpan _span; + + /// + private readonly int _step; + + /// + /// The current position in the sequence. + /// + private int _position; + + /// + public readonly ref readonly T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + ref T r0 = ref MemoryMarshal.GetReference(_span); + + nint offset = (nint)(uint)_position * (nint)(uint)_step; + ref T ri = ref Unsafe.Add(ref r0, offset); + + return ref ri; + } + } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the struct. + /// + /// The instance with the info on the items to traverse. + /// The distance between items in the sequence to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Enumerator(ReadOnlySpan span, int step) + { + this._span = span; + this._step = step; + + _position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _span.Length; + + #endregion + } +} diff --git a/StableDiffusion.NET/Helper/Images/RefImage.cs b/StableDiffusion.NET/Helper/Images/RefImage.cs new file mode 100644 index 0000000..728e262 --- /dev/null +++ b/StableDiffusion.NET/Helper/Images/RefImage.cs @@ -0,0 +1,368 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using StableDiffusion.NET.Helper.Images.Colors; + +namespace StableDiffusion.NET.Helper.Images; + +public readonly ref struct RefImage + where TColor : struct, IColor +{ + #region Properties & Fields + + private readonly ReadOnlySpan _pixels; + + private readonly int _x; + private readonly int _y; + + /// + /// Gets the width of the image. + /// + public int Width { get; } + + /// + /// Gets the height of the image. + /// + public int Height { get; } + + /// + /// Gets the stride (entries per row) of the underlying buffer. + /// Only useful if you want to work with a pinned buffer. + /// + public int RawStride { get; } + + #endregion + + #region Indexer + + public ref readonly TColor this[int x, int y] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((x < 0) || (y < 0) || (x >= Width) || (y >= Height)) throw new IndexOutOfRangeException(); + + ref TColor r0 = ref MemoryMarshal.GetReference(_pixels); + nint offset = (nint)(uint)((_y + y) * RawStride) + (_x + x); + return ref Unsafe.Add(ref r0, offset); + } + } + + public RefImage this[int x, int y, int width, int height] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) > Width) || ((y + height) > Height)) throw new IndexOutOfRangeException(); + + return new RefImage(_pixels, _x + x, _y + y, width, height, RawStride); + } + } + + public ImageRows Rows + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(_pixels, _x, _y, Width, Height, RawStride); + } + public ImageColumns Columns + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(_pixels, _x, _y, Width, Height, RawStride); + } + + #endregion + + #region Constructors + + public RefImage(ReadOnlySpan pixels, int x, int y, int width, int height, int stride) + { + this._pixels = pixels; + this._x = x; + this._y = y; + this.Width = width; + this.Height = height; + this.RawStride = stride; + } + + #endregion + + #region Methods + + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + public void CopyTo(in Span destination) + { + if (destination == null) throw new ArgumentNullException(nameof(destination)); + if (destination.Length < (Width * Height)) throw new ArgumentException("The destination is too small to fit this image.", nameof(destination)); + + ImageRows rows = Rows; + Span target = destination; + foreach (ReadOnlyRefEnumerable row in rows) + { + row.CopyTo(target); + target = target[Width..]; + } + } + + /// + /// Allocates a new array and copies this into it. + /// + /// The new array containing the data of this . + public TColor[] ToArray() + { + TColor[] array = new TColor[Width * Height]; + CopyTo(array); + return array; + } + + /// + /// Returns a reference to the first element of this image inside the full image buffer. + /// + public ref readonly TColor GetPinnableReference() + { + if (_pixels.Length == 0) + return ref Unsafe.NullRef(); + + int offset = (_y * RawStride) + _x; + return ref MemoryMarshal.GetReference(_pixels[offset..]); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImageEnumerator GetEnumerator() => new(_pixels); + + #endregion + + public ref struct ImageEnumerator + { + #region Properties & Fields + + private readonly ReadOnlySpan _pixels; + private int _position; + + /// + public TColor Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _pixels[_position]; + } + + #endregion + + #region Constructors + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ImageEnumerator(ReadOnlySpan pixels) + { + this._pixels = pixels; + + _position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _pixels.Length; + + #endregion + } + + #region Indexer-Structs + + public readonly ref struct ImageRows + { + #region Properties & Fields + + private readonly ReadOnlySpan _pixels; + private readonly int _x; + private readonly int _y; + private readonly int _width; + private readonly int _height; + private readonly int _stride; + + public int Count => _height; + + #endregion + + #region Indexer + + public readonly ReadOnlyRefEnumerable this[int row] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((row < 0) || (row > _height)) throw new IndexOutOfRangeException(); + + ref TColor r0 = ref MemoryMarshal.GetReference(_pixels); + ref TColor rr = ref Unsafe.Add(ref r0, (nint)(uint)(((row + _y) * _stride) + _x)); + + return new ReadOnlyRefEnumerable(rr, _width, 1); + } + } + + #endregion + + #region Constructors + + public ImageRows(ReadOnlySpan pixels, int x, int y, int width, int height, int stride) + { + this._pixels = pixels; + this._x = x; + this._y = y; + this._width = width; + this._height = height; + this._stride = stride; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImageRowsEnumerator GetEnumerator() => new(this); + + #endregion + + public ref struct ImageRowsEnumerator + { + #region Properties & Fields + + private readonly ImageRows _rows; + private int _position; + + /// + public ReadOnlyRefEnumerable Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _rows[_position]; + } + + #endregion + + #region Constructors + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ImageRowsEnumerator(ImageRows rows) + { + this._rows = rows; + + _position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _rows._height; + + #endregion + } + } + + public readonly ref struct ImageColumns + { + #region Properties & Fields + + private readonly ReadOnlySpan _pixels; + private readonly int _x; + private readonly int _y; + private readonly int _width; + private readonly int _height; + private readonly int _stride; + + public int Count => _width; + + #endregion + + #region Indexer + + public ReadOnlyRefEnumerable this[int column] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((column < 0) || (column > _width)) throw new IndexOutOfRangeException(); + + ref TColor r0 = ref MemoryMarshal.GetReference(_pixels); + ref TColor rc = ref Unsafe.Add(ref r0, (nint)(uint)((_y * _stride) + (column + _x))); + + return new ReadOnlyRefEnumerable(rc, _height, _stride); + } + } + + #endregion + + #region Constructors + + public ImageColumns(ReadOnlySpan pixels, int x, int y, int width, int height, int stride) + { + this._pixels = pixels; + this._x = x; + this._y = y; + this._width = width; + this._height = height; + this._stride = stride; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImageColumnsEnumerator GetEnumerator() => new(this); + + #endregion + + public ref struct ImageColumnsEnumerator + { + #region Properties & Fields + + private readonly ImageColumns _columns; + private int _position; + + /// + public ReadOnlyRefEnumerable Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _columns[_position]; + } + + #endregion + + #region Constructors + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ImageColumnsEnumerator(ImageColumns columns) + { + this._columns = columns; + this._position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _columns._width; + + #endregion + } + } + + #endregion +} \ No newline at end of file