using System; using System.Diagnostics.CodeAnalysis; using System.Linq; namespace OpenMacroBoard.SDK { /// /// Represents a bitmap that can be used as key images /// public sealed class KeyBitmap : IEquatable, IKeyBitmapDataAccess { /// /// Byte order is B-G-R, and pixels are stored left-to-right and top-to-bottom /// private readonly Byte[] rawBitmapData; private Int32? cachedHashCode = null; internal KeyBitmap(Int32 width, Int32 height, Byte[] bitmapData) { this.Width = width; this.Height = height; this.rawBitmapData = bitmapData ?? throw new ArgumentNullException(nameof(bitmapData)); } /// /// This property can be used to create new KeyBitmaps /// /// /// This property just serves as an anchor point for extension methods /// to create new objects /// public static IKeyBitmapFactory Create { get; } /// /// Solid black bitmap /// /// /// If you need a black bitmap (for example to clear keys) use this property for better performance (in theory ^^) /// public static KeyBitmap Black { get; } = new(1, 1, []); /// /// Gets the width of the bitmap. /// public Int32 Width { get; } /// /// Gets the height of the bitmap. /// public Int32 Height { get; } Boolean IKeyBitmapDataAccess.IsEmpty => this.rawBitmapData.Length == 0; /// /// The == operator /// public static Boolean operator ==(KeyBitmap a, KeyBitmap b) => Equals(a, b); /// /// The != operator /// public static Boolean operator !=(KeyBitmap a, KeyBitmap b) => !Equals(a, b); /// /// Compares the content of two given s /// /// KeyBitmap a /// KeyBitmap b /// Returns true of the s are equal and false otherwise. public static Boolean Equals(KeyBitmap a, KeyBitmap b) => ReferenceEquals(a, b) || ( a is not null && b is not null && a.Width == b.Width && a.Height == b.Height && ( ReferenceEquals(a.rawBitmapData, b.rawBitmapData) || ( a.rawBitmapData is not null && b.rawBitmapData is not null && a.rawBitmapData.SequenceEqual(b.rawBitmapData) ) ) ); /// /// Compares the content of this to another KeyBitmap /// /// The other /// True if both bitmaps are equals and false otherwise. public Boolean Equals(KeyBitmap other) => Equals(this, other); /// /// Compares the content of this to another object /// /// The other object /// Return true if the other object is a and equal to this one. Returns false otherwise. public override Boolean Equals(Object obj) => Equals(this, obj as KeyBitmap); /// /// Get the hash code for this object. /// /// The hash code public override Int32 GetHashCode() => this.cachedHashCode ??= this.CalculateObjectHash(); /// ReadOnlySpan IKeyBitmapDataAccess.GetData() => new(this.rawBitmapData); private Int32 CalculateObjectHash() { const Int32 initalValue = 17; const Int32 primeFactor = 23; const Int32 imageSampleSize = 1000; unchecked { Int32 hash = initalValue; hash = (hash * primeFactor) + this.Width; hash = (hash * primeFactor) + this.Height; if(this.rawBitmapData.Length == 0) { return hash; } Int32 stepSize = 1; if(this.rawBitmapData.Length > imageSampleSize) { stepSize = this.rawBitmapData.Length / imageSampleSize; } for(Int32 i = 0; i < this.rawBitmapData.Length; i += stepSize) { hash *= 23; hash += this.rawBitmapData[i]; } return hash; } } } }