-
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. |
|
@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 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. |
Sorry, but IMHO this makes no sense with this small surface/scope. First. If you are going to reinvent the wheel to make an unification of code, the reinvented wheel should be better than the current wheel. That means to implement a wellformed api surface around the color theory, color spaces, conversions, etc... Even if you don't implement it, create an good structure of interfaces and let the people cook. If this is only "create a new small, non extrapolable struct to make it a little bit better to interop and help us to improve performance without encouraging the developers to use these things" is ok, but is an enormous missed opportunity. Second. Json serialization. If this is looking for make the interoperability better, why not the serialization?. Look third statement. Third. Create a logical framework around the concept of color (again, first point, color theory, color spaces, conversions). Create an "agnostic color-color space tuple" or "color in space" (naming is hard) struct, and work around there. Is it a pain in the *ss? Yeah, but it is "the right way". In a pure way, a color is just a vector of values (I think this must be make with SIMD in mind), each one represeting something in one concrete color space or representation system. Fourth. More ahead, you'll never break binary compatibility in order to remove The color animations in WPF could be improved a lot with this work, a lot of other things too. Fifth. Generic Math interfaces! And, if this become bigger and has the sufficient scope, just create namespace for it PS: I understand that the great majority of developers just use sRGB (or any other permutation of the (A)RGB model). And that's the core funcionality you should implement. But you could let the API more open to expansion for other developers. |
Any API the BCL exposes has to consider the Framework Design Guidelines (FDG) and the general scope of the proposal. We don't just expose APIs unnecessarily, we don't try and expose foundational things without good reason, and extensibility support or lack there of is an explicit part of the design considerations. All the points you've raised have already been considered and were rejected for various reasons such as going directly against the FDG, coupling things together that should not be coupled or which may only be "point in time", not being "foundational" but rather encroaching into incredibly complex spaces that are better handled by explicit 3rd party libraries dedicated to the scenario, etc. The APIs being exposed here are explicitly scoped, they are done the way they are to provide a means of simple primitive interop, to allow exposing some baseline functionality that is common to other graphics oriented vector math libraries (such as DirectX Math or GLM), and to provide a means of accelerating/improving WinForms/WPF. |
What about something like this? public readonly struct Color: IEquatable<Color> //etc...
{
public enum ColorFormat
{
Rgba,
Argb,
Rgb,
Bgr,
Xyz,
Xyy,
Lab,
//and so on
}
// Color components. Meaningless, the ColorFormat will be the element that gives them the meaning by the order.
public float Component1 { get; set; }
public float Component2 { get; set; }
public float Component3 { get; set; }
public float Component4 { get; set; }
//future proofing?
//public float Component5 { get; set; }
//public float Component6 { get; set; }
public ColorFormat Format { get; set; }
public Color(ColorFormat format, float c1, float c2, float c3, float c4 = 0.0f)
{
Format = format;
Component1 = c1;
Component2 = c2;
Component3 = c3;
Component4 = c4;
}
}
//among all the other methods and static classes in the api proposal This is still blittable, it's based on floating point (which is more relatable to something like the color) and is the most generic way a 3-4 components color can be represented, without restraints. To make use of vectorization, just convert it to This could be the basic element, and then use the proposed |
It's blittable in the strictest sense that it contains all unmanaged data and would not require marshalling. It is not blittable in the useful sense in that it allows bit-blitting of the data in the common scenarios that require it. So you wouldn't be able to use such a The design here is very intentional and considerate of the real world needs and use-cases; not some generic shape that tries to do everything at once. |
I will reiterate what I've stated before: The most important color type to include in .NET is the one already used in the C# ecosystem, such as Unity, Godot, Stride, and MAUI (see dotnet/maui#16903). Namely, a Color made out of 32-bit This is specifically what would provide the most benefit for interoperability scenarios in the general case, so that code can use a general color type that has seamless blittability with these others. For the non-general case, such as a need for multiple layouts, multiple color spaces, compact representations, and so on, these are what should be left to libraries. I urge people to not get distracted by legacy Microsoft-specific technologies with ARGB, which is not generally useful in the .NET ecosystem. |
This is covered in the existing proposal via the
This is not a legacy or Microsoft specific thing, it's actually rather the standard name for the This historical start to the "layout" is still visible in almost every system used to talk about colors today. It's used in the industry standard hex representation of colors, such as There are still many scenarios that require or otherwise benefit (such as perf wise) from -- This isn't universally the default and some systems like OpenGL have historically preferred RGBA32, likewise when working with |
@tannergooding Industry-standard color hex codes are RGB(A): Placing alpha at the end has the benefit of making it easier to handle cases where the alpha is optional. With an array of floats that can be 3 or 4 long, read index 0 as red, index 1 as green, index 2 as blue, and if index 3 exists, read it as alpha. But yeah, the proposal has |
I was giving a history lesson on how/why What was being stated is that prior to alpha existing, it was simply This is distinct from what the web standardized on ( This is entirely from how things work when working with bytes, not with floats, and from it being far more efficient for data to be manipulated as a single
Calling it |
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: