Description
Background and Motivation
System.Drawing.Color
contains extra metadata that makes it unusable in interop scenarios and inefficient in arrays.
namespace System.Drawing
{
public readonly struct Color : IEquatable<Color>
{
private readonly string? name;
private readonly long value;
private readonly short knownColor;
private readonly short state;
}
}
Color
is also mutable, despite being readonly
. If it is constructed from a system KnownColor
value, it always looks up the value on every access.
In order to facilitate exchange of raw color data throughout the .NET ecosystem and external libraries we should expose common color types that only contain raw color data.
The intent is to follow up with these base types to provide a basic set of the most common functionality needed around colors as described here: #48615 (comment)
The intent is NOT to start building a general purpose image manipulation library. Libraries such as ImageSharp are the right answer for this.
WinForms and System.Drawing will be able to leverage this for performance, which was the original driver for this request.
// Native Win32 definitions
// GDI
typedef DWORD COLORREF;
// GDI+
typedef DWORD ARGB;
Proposed API
using System;
namespace System.Drawing
{
partial struct Color : IEquatable<Color>
{
public static Color FromArgb(System.Numerics.Colors.Argb<byte> argb);
public static implicit operator Color(System.Numerics.Colors.Argb<byte> argb);
// ToNumericsArgb? ToArgbNumerics?
public System.Numerics.Colors.Argb<byte> ToArgbValue();
public static explicit operator System.Numerics.Colors.Argb<byte>(in Color color);
}
}
// WPF
namespace System.Windows.Media
{
public struct Color : IEquatable<Color>, IFormattable
{
public static Color FromArgb(System.Numerics.Colors.Argb<byte> argb);
public static implicit operator Color(System.Numerics.Colors.Argb<byte> argb);
public static Color FromArgb(System.Numerics.Colors.Argb<float> argb);
public static implicit operator Color(System.Numerics.Colors.Argb<float> argb);
public System.Numerics.Colors.Argb<byte> ToArgbValue();
public static explicit operator System.Numerics.Colors.Argb<byte>(in Color color);
public System.Numerics.Colors.Argb<float> ToArgbValue();
public static explicit operator System.Numerics.Colors.Argb<float>(in Color color);
}
}
namespace System.Numerics.Colors
{
public static class Argb
{
public static Argb<byte> CreateBigEndian(uint color);
public static Argb<byte> CreateLittleEndian(uint color);
public static uint ToUInt32LittleEndian(this Argb<byte> color);
public static uint ToUInt32BigEndian(this Argb<byte> color);
}
public readonly struct Argb<T> : IEquatable<Argb<T>>, IFormattable, ISpanFormattable where T : struct
{
public T A { get; }
public T R { get; }
public T G { get; }
public T B { get; }
public Argb(T a, T r, T g, T b);
public Argb(ReadOnlySpan<T> values);
public void CopyTo(Span<T> destination);
public bool Equals(Argb<T> other);
public string ToString(string format, IFormatProvider formatProvider);
// whatever ISpanFormattable says
public Rgba<T> ToRgba();
}
public static class Rgba
{
public static Rgba<byte> CreateLittleEndian(uint color);
public static Rgba<byte> CreateBigEndian(uint color);
public static uint ToUInt32LittleEndian(this Rgba<byte> color);
public static uint ToUInt32BigEndian(this Rgba<byte> color);
}
public readonly struct Rgba<T> : IEquatable<Rgba<T>>, IFormattable, ISpanFormattable where T : struct
{
public T R { get; }
public T G { get; }
public T B { get; }
public T A { get; }
public Rgba(T r, T g, T b, T a);
public Rgba(ReadOnlySpan<T> values);
public void CopyTo(Span<T> destination);
public bool Equals(Rgba<T> other);
public string ToString(string format, IFormatProvider formatProvider);
// whatever ISpanFormattable says
public Argb<T> ToArgb();
}
}
Original Proposal
public readonly struct ColorArgb
{
// Layout matches GDI COLORREF and GDI+ ARGB
public uint Value { get; }
public byte A => (byte)(Value >> 24);
public byte R => (byte)(Value >> 16);
public byte G => (byte)(Value >> 8);
public byte B => (byte)(Value);
public ColorArgb(uint value) => Value = value;
public ColorArgb(byte r, byte g, byte b) : this(r, g, b, byte.MaxValue) { }
public ColorArgb(byte r, byte g, byte b, byte a) => Value = (uint)(a << 24 | r << 16 | g << 8 | b);
public ColorArgb(KnownColor knownColor) => Value = KnownColorTable.KnownColorToArgb(knownColor);
public ColorArgb(Color color) => Value = (uint)color.ToArgb();
// Color has these 3 methods.
public float GetHue() => 0;
public float GetSaturation() => 0;
public float GetBrightness() => 0;
// Frequently checked as only GDI+ supports transparency.
public bool HasTransparency => A != byte.MaxValue;
public bool FullyTransparent => A == 0;
public static implicit operator Color(ColorArgb color) => Color.FromArgb((int)color.Value);
public static implicit operator ARGB(ColorArgb color) => (ARGB)color.Value;
public static implicit operator ColorArgb(ARGB argb) => new ColorArgb((uint)argb);
public static implicit operator ColorArgb(Color color) => new ColorArgb(color);
public static implicit operator ColorArgb(KnownColor knownColor) => new ColorArgb(knownColor);
}
// For ease of use in interop definitions (P/Invoke, function pointers, COM)
// (as close as we get to a typedef in C#)
public enum ARGB : uint { }
Alternative Designs
We could drop a System.Drawing specific color type in System.Drawing for this purpose (the original proposal). Discussing this with @pgovind and @tannergooding we feel there is value in defining more broadly available color types in System.Numerics.
Using Vector4 is possible for this purpose, but that makes data interchange and just general usage difficult. (Was this ARGB or RGBA, etc?) The intent is also to add additional methods in the future that are dependent on specific layout (such as conversion to HSL/HSV, etc.).
Risks
No known risks.