-
Notifications
You must be signed in to change notification settings - Fork 4.5k
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 don't think that the layout has to be explicitly stated in the name for it to be well-defined. Concepts like HSV and others exposed as properties are still well-defined. In fact, the HSV values don't at all depend on the in-memory layout of the channels.
That's basically what the general-purpose color type is for. It provides basic functionality of storing colors in RGBA format with basic operations like addition, subtraction, interpolation, etc. Methods like lightened and darkened present in the proposal can be excluded if it's decided that they are not desired. Converting to color spaces to me would be considered out-of-scope, however, as there are hundreds of color spaces in use and an infinite amount of them that could exist, so I think that's best left to user code (and/or would be handled transparently if we need to do so when converting to other color types in .NET). |
What are you seeing in #32418 that isn't covered by this proposal then (and the planned future API additions to the base types described here)? |
A general Is it valueable to express such things as part of the type? If we don't express that as part of the type, then what use does it have over e.g. |
The typical pattern is that It is simply a red, green, blue, and alpha component where Conversion to a given color space, such as |
If the pattern is standard, why not call it |
Because the pattern is standard but the ordering of components is not and the ordering of components is the important information here. |
https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format and https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkFormat.html go more in depth into the possible color formats. For the most part, they differ on:
It is not the goal or scope of this proposal to expose everything. But most concepts can be cleanly represented via the explicit |
From a Microsoft perspective, maybe. For the rest of the world, RGBA is quite standard. Formats like ARGB are used in DirectX and pretty much nowhere else. Even in .NET, we have System.Drawing.Color and MAUI's Color which are both RGBA (and aren't named "Rgba"). I think it makes perfect sense to call it "Color" and have everyone just learn what it means. It is very trivial for someone to Ctrl+Click on a type and see what the layout of its members are. I'm not sure that having this information in the name is of any help at all. Most users don't even need this information anyway. I don't want to expose everything either. I want a |
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). |
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: