potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

C# WinAPIを使ったHLS色構造体

動作確認環境:C# 9.0(.NET 5)

WinAPIを使ったHLS色構造体のコードです。Color型およびタプル(Tuple<ushort, ushort, ushort>およびValueTuple<ushort, ushort, ushort>)との相互変換をサポートしています。Colorとは異なりひとつの整数で表すことを前程としないため、IComparableおよびIComparable<T>は実装しません。同様の理由からColorとの明示的なキャストをサポートしません。

C# 9.0機能の練習も兼ねています。

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;

var hls = new HLSColor(0, 100, 20);
var rgb = hls.ToColor();
hls += new HLSColor(2, 2, 2);

public struct HLSColor : IEquatable<HLSColor>, ISerializable
{
    public ushort H;
    public ushort L;
    public ushort S;

    #region コンストラクタ
    public HLSColor(ushort h, ushort l, ushort s)
        => (H, L, S) = (h, l, s);
    public HLSColor(Tuple<ushort, ushort, ushort> value)
        => (H, L, S) = (value.Item1, value.Item2, value.Item3);
    public HLSColor(in ValueTuple<ushort, ushort, ushort> value)
        => (H, L, S) = (value.Item1, value.Item2, value.Item3);
    public HLSColor(SerializationInfo info, StreamingContext context)
        => (H, L, S) = (info.GetUInt16("H"), info.GetUInt16("L"), info.GetUInt16("S"));
    #endregion

    #region 一般の比較・文字列化関数
    public bool Equals(HLSColor other)
        => H == other.H && L == other.L && S == other.S;
    public override bool Equals(object obj)
        => obj is HLSColor hls2 && Equals(hls2);
    public override int GetHashCode()
        => ToValueTuple().GetHashCode();
    public override string ToString()
        => string.Format("HLS=({0},{1},{2})", H, L, S);
    #endregion

    #region Colorとの相互変換関数
    public static HLSColor FromColor(Color color)
    {
        NativeMethods.ColorRGBToHLS(unchecked((uint)color.ToArgb()), out var h, out var l, out var s);
        return new HLSColor(h, l, s);
    }
    public Color ToColor()
        => Color.FromArgb(unchecked((int)NativeMethods.ColorHLSToRGB(H, L, S)));
    #endregion

    #region シリアル化
    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        if (info == null)
            throw new ArgumentNullException(nameof(info));
        info.AddValue("H", H);
        info.AddValue("L", L);
        info.AddValue("S", S);
    }
    #endregion

    #region 比較演算子
    public static bool operator ==(HLSColor v1, HLSColor v2)
        => v1.Equals(v2);
    public static bool operator !=(HLSColor v1, HLSColor v2)
        => !v1.Equals(v2);
    /// <summary>加算。和がushortの最大値超過の場合は0xffffに抑えます。</summary>
    public static HLSColor operator +(HLSColor v1, HLSColor v2)
        => new HLSColor(ClampAddUInt16(v1.H, v2.H), ClampAddUInt16(v1.L, v2.L), ClampAddUInt16(v1.S, v2.S));
    /// <summary>減算。差がushortの最小値未満の場合は0に抑えます。</summary>
    public static HLSColor operator -(HLSColor v1, HLSColor v2)
        => new HLSColor(ClampDiffUInt16(v1.H, v2.H), ClampDiffUInt16(v1.L, v2.L), ClampDiffUInt16(v1.S, v2.S));
    #endregion

    #region タプルとの相互変換
    public ValueTuple<ushort, ushort, ushort> ToValueTuple()
        => ValueTuple.Create(H, S, L);
    public Tuple<ushort, ushort, ushort> ToTuple()
        => Tuple.Create(H, S, L);
    public static explicit operator Tuple<ushort, ushort, ushort>(HLSColor value)
        => value.ToTuple();
    public static explicit operator ValueTuple<ushort, ushort, ushort>(HLSColor value)
        => value.ToValueTuple();
    public static explicit operator HLSColor(Tuple<ushort, ushort, ushort> value)
        => new HLSColor(value);
    public static explicit operator HLSColor(ValueTuple<ushort, ushort, ushort> value)
        => new HLSColor(value);
    #endregion

    #region 補助関数
    private static ushort ClampAddUInt16(ushort x, ushort y)
        => (x + y) switch { int z when z > 0xffffu => (ushort)0xffffu, int z => (ushort)z };
    private static ushort ClampDiffUInt16(ushort x, ushort y)
        => (x - y) switch { int z when z > 0 => (ushort)z, _ => 0 };
    #endregion

    private static class NativeMethods
    {
        [DllImport("shlwapi.dll")]
        public static extern uint ColorHLSToRGB(ushort wHue, ushort wLuminance, ushort wSaturation);
        [DllImport("shlwapi.dll")]
        public static extern void ColorRGBToHLS(uint clrRGB, out ushort pwHue, out ushort pwLuminance, out ushort pwSaturation);
    }
}