-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
API Proposal: Add blittable Color to System.Numerics #48615
Comments
Tagging subscribers to this area: @safern, @tarekgh Issue DetailsBackground and Motivation
namespace System.Drawing
{
public readonly struct Color : IEquatable<Color>
{
private readonly string? name; // Do not rename (binary serialization)
private readonly long value; // Do not rename (binary serialization)
private readonly short knownColor; // Do not rename (binary serialization)
private readonly short state; // Do not rename (binary serialization)
}
}
In order to facilitate GDI and GDI+ interop scenarios and creation of more performant overloads for // Native definitions
// GDI
typedef DWORD COLORREF;
// GDI+
typedef DWORD ARGB; Proposed API 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 DesignsWe could make a more generic set of color structs to handle all typical color usage scenarios, but that isn't the intent here. #32418 tracks such a request. This type is specifically to address the existing space that RisksNo known risks.
|
Wow what horrors. ColorArgb is clearly better for the reasons given. But then Color8 in the linked thread solves the same problem. So apart from the namespace it's the same proposal. |
I dislike this proposal for a few reasons:
|
@charlesroddie Yes, it is very close. This is the native color, and this is specifically for direct interop.
True, and I think
I agree, but it doesn't address the specific interop need here. I want to be super clear- this is not intended to be the "new" |
Sorry I didn't see the interop part. If it's a Windows-specific feature then I don't think it's a candidate for dotnet/runtime. Perhaps a WindowsGraphicsPrimitives library that WPF/WinForms can depend on? Or a GraphicsPrimitives library that does the same thing for a broader set of consumers? |
Well, it is Windows specific in the sense that it is GDI/GDI+ specific, which is what System.Drawing is a projection/mapping of. So generally I agree with you, but this assembly is really a legacy Windows thing we're carrying and not the future of drawing/graphics support for .NET I think. |
Given that this is needed by Winforms to improve perf and System.Drawing.Common could benefit from it, let's mark is as api-ready-for-review and discuss it in the review meeting. |
If this is reviewed, then #32418 should also be reviewed. Since so many things need a Color type, it makes sense to include one (or two) in .NET for everyone to use, including Winforms and System.Drawing and Maui and much more. |
In API Review we feel that this is just setting us up for an API bifurcation between Color and ColorArgb, and that there doesn't seem to be a compelling scenario for that bifurcation. With a compelling scenario we could have a different discussion. |
It seems like there may be scenarios, but we should reconcile the layering (above or below Color) and determine if we need this, or a companion, for MAUI. |
Updated the proposal based on offline discussions with @pgovind and @tannergooding. |
Tagging subscribers to this area: @tannergooding, @pgovind Issue DetailsBackground and Motivation
namespace System.Drawing
{
public readonly struct Color : IEquatable<Color>
{
private readonly string? name; // Do not rename (binary serialization)
private readonly long value; // Do not rename (binary serialization)
private readonly short knownColor; // Do not rename (binary serialization)
private readonly short state; // Do not rename (binary serialization)
}
}
In order to facilitate GDI and GDI+ interop scenarios and creation of more performant overloads for // Native definitions
// GDI
typedef DWORD COLORREF;
// GDI+
typedef DWORD ARGB; Proposed APIusing System;
namespace System.Drawing
{
public readonly struct Color : IEquatable<Color>
{
public static implicit operator Color(System.Numerics.Colors.Argb<byte> argb);
public static explicit operator System.Numerics.Colors.Argb<byte>(in Color color);
public static implicit operator Color(System.Numerics.Colors.Rgba<byte> rgba);
public static explicit operator System.Numerics.Colors.Rgba<byte>(in Color color);
}
}
namespace System.Numerics.Colors
{
public static class Argb
{
public static Argb<byte> CreateLittleEndian(uint color);
public static uint ToUInt32LittleEndian(this Argb<byte> color);
}
public readonly struct Argb<T> : IEquatable<Argb<T>>, IFormattable 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 readonly void CopyTo(Span<T> destination);
public bool Equals(Argb<T> other);
public string ToString(string format, IFormatProvider formatProvider);
}
public static class Rgba
{
public static Rgba<byte> CreateLittleEndian(uint color);
public static uint ToUInt32LittleEndian(this Rgba<byte> color);
}
public readonly struct Rgba<T> : IEquatable<Rgba<T>>, IFormattable 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 readonly void CopyTo(Span<T> destination);
public bool Equals(Rgba<T> other);
public string ToString(string format, IFormatProvider formatProvider);
}
} Original Proposal
``` C#
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);
|
I'm not familiar with the term "full color space" -- is it a misnomer for "any color space"? What you're describing here isn't how I would expect the type to be used. Anywhere I see Anywhere I see |
If we're having trouble with the naming, I have a few suggestions.
This is similar to naming of |
Unrelated to the issue... The original proposal in the description is not rendered properly. To render the details/spoiler tag properly, just leave an empty line between the tags, like so... <details>
<summary>Title text here...</summary>
<!-- insert markdown here... -->
</details> |
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();
}
} |
I think we can label this "easy". I think new contributors would do well with this issue. |
This one is being primarily driven by WinForms and need in other areas. It's going to likely be done by that team to ensure it and its usages are going in at the same time/release |
So why are they named
|
This is a complex space and depends on native surface format of a given OS vs what is used everywhere. The native Win32 surface format is often referred to as It may be that we want/need to eventually expose The conversion between formats can also be done as part of the copy or write operation in a majority of these cases, leading to no real additional cost in terms of the operation. |
I disagree -- the swizzling definitely has a cost, especially when it has to be done on both ends of an operation, especially if has to be done multiple times as a buffer is passed around internally and has various operations performed on it that only operate on the swizzled format. So in my app/library, imagine I'm passing a BGRA bitmap from A to B to C, and each one wants to execute a filter on the bitmap that is supported by these primitives (but only in ARGB format!). That means I have to swizzle 6 times to get the job done, unless I add a way for the components to communicate the component ordering of the buffer. This affects architecture of apps and libraries, which may not be malleable enough to reasonably accommodate this.
That sounds completely contradictory. You're stating that a key target scenario here is WinForms and WPF, which will be dealing with BGRA because that's what the platform (Win32) uses, so therefore BGRA doesn't fit with their immediate needs? 😕 Paint.NET would also be hamstrung, performance-wise, if I wanted to use these primitives -- I have seen good performance improvements by optimizing and eliminating things like swizzling, copying, and format conversions. To be blunt, if BGRA isn't supported, these primitives are completely useless for Paint.NET and for a large number of Windows/desktop scenarios. The performance cost of swizzling is not super high but it does lower the upper bound of performance, especially for real-time/interactive scenarios. |
I think you're misunderstanding. In terms of basic operations, the swizzle is performed as part of the load/store. So if you require a copy, then you can convert as part of that copy at no-additional cost (there is no instruction latency difference between a If you are having to swizzle, do an operation, and then re-swizzle; then yes there can be a cost for vectorized code since you can't do the swizzle as part of a vectorized store.
This is a complex area and what the docs describe vs how its implemented can differ a bit.
The actual underlying format for GDI+ and the recommended format for D2D1 is really equivalent to
|
Hmm, well that seems to clear up the confusion on my part (along with the conversation we had on Discord). |
I implemented this API in PR #106575. I think that the methods I suggest renaming them to |
Will it be possible to convert float colors to vector types and back? For example, when reading normal maps, colors need to be converted to vector. |
Does CMYK factor as an alternate schema/form in here at all as something to think about? Just trying to think beyond the base cases here and what could be useful to other industries/use-cases beyond the traditional graphics rendering. |
Worth noting is that colors composed of different primitive types, e.g. Put another way, we can't just constrain to Some imaging toolkits, notably WIC, also have a default policy that pixel formats using unsigned integers (e.g. |
Background and Motivation
System.Drawing.Color
contains extra metadata that makes it unusable in interop scenarios and inefficient in arrays.Color
is also mutable, despite beingreadonly
. If it is constructed from a systemKnownColor
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.
Proposed API
Original Proposal
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.
The text was updated successfully, but these errors were encountered: