Skip to content

API Proposal: Add blittable Color to System.Numerics #48615

@JeremyKuhne

Description

@JeremyKuhne

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.

Metadata

Metadata

Assignees

Labels

api-approvedAPI was approved in API review, it can be implementedarea-System.Numericsin-prThere is an active PR which will close this issue when it is merged

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions