StreamDeckSharp/OpenMacroBoard.SDK/KeyBitmap.cs
2026-08-03 22:34:16 +02:00

140 lines
4.5 KiB
C#

using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace OpenMacroBoard.SDK {
/// <summary>
/// Represents a bitmap that can be used as key images
/// </summary>
public sealed class KeyBitmap : IEquatable<KeyBitmap>, IKeyBitmapDataAccess {
/// <summary>
/// Byte order is B-G-R, and pixels are stored left-to-right and top-to-bottom
/// </summary>
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));
}
/// <summary>
/// This property can be used to create new KeyBitmaps
/// </summary>
/// <remarks>
/// <para>This property just serves as an anchor point for extension methods
/// to create new <see cref="KeyBitmap"/> objects</para>
/// </remarks>
public static IKeyBitmapFactory Create {
get;
}
/// <summary>
/// Solid black bitmap
/// </summary>
/// <remarks>
/// <para>If you need a black bitmap (for example to clear keys) use this property for better performance (in theory ^^)</para>
/// </remarks>
public static KeyBitmap Black { get; } = new(1, 1, []);
/// <summary>
/// Gets the width of the bitmap.
/// </summary>
public Int32 Width {
get;
}
/// <summary>
/// Gets the height of the bitmap.
/// </summary>
public Int32 Height {
get;
}
Boolean IKeyBitmapDataAccess.IsEmpty => this.rawBitmapData.Length == 0;
/// <summary>
/// The == operator
/// </summary>
public static Boolean operator ==(KeyBitmap a, KeyBitmap b) => Equals(a, b);
/// <summary>
/// The != operator
/// </summary>
public static Boolean operator !=(KeyBitmap a, KeyBitmap b) => !Equals(a, b);
/// <summary>
/// Compares the content of two given <see cref="KeyBitmap"/>s
/// </summary>
/// <param name="a">KeyBitmap a</param>
/// <param name="b">KeyBitmap b</param>
/// <returns>Returns true of the <see cref="KeyBitmap"/>s are equal and false otherwise.</returns>
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)
)
)
);
/// <summary>
/// Compares the content of this <see cref="KeyBitmap"/> to another KeyBitmap
/// </summary>
/// <param name="other">The other <see cref="KeyBitmap"/></param>
/// <returns>True if both bitmaps are equals and false otherwise.</returns>
public Boolean Equals(KeyBitmap other) => Equals(this, other);
/// <summary>
/// Compares the content of this <see cref="KeyBitmap"/> to another object
/// </summary>
/// <param name="obj">The other object</param>
/// <returns>Return true if the other object is a <see cref="KeyBitmap"/> and equal to this one. Returns false otherwise.</returns>
public override Boolean Equals(Object obj) => Equals(this, obj as KeyBitmap);
/// <summary>
/// Get the hash code for this object.
/// </summary>
/// <returns>The hash code</returns>
public override Int32 GetHashCode() => this.cachedHashCode ??= this.CalculateObjectHash();
/// <inheritdoc />
ReadOnlySpan<Byte> 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;
}
}
}
}