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;
}
}
}
}