Replies: 4 comments
|
You can drop the uint argb = ...;
Color color = Color.FromPixel(new Bgra32 { PackedValue = argb });
public uint Bgra { get; set; }
public uint PackedValue { get; set; }so you can use whichever reads better. This is the library's own idiom, incidentally — its pixel conversion does the same thing: public static Bgra32 FromBgra32(Bgra32 source) => new() { PackedValue = source.PackedValue };That gets you off a reinterpret-cast that depends on layout assumptions, onto something the type explicitly supports, without changing the shape of your hot path. On the request itself, you are right that And there is a decent argument in the hex parsers for adding one, because they reach for exactly the trick you did: result = Unsafe.As<uint, Rgba32>(ref packedValue);with the same line for |
|
Yes, the current way to convert an Argb/Bgra uint to Color is a bit kludgy. Argb/Bgra is just so common, I wouldn't even think more than 0.1 seconds about it and add conversion methods for those. It also saves people for having to search for how to do this, so discoverability wise it's also more convenient. And conceptually it doesn't feel right you have to take an intermediate step through a TPixel to get from an Argb/Bgra uint to a Color. |
|
I don't think
A That distinction is important here because "ARGB uint" is already ambiguous. For example, the conventional integer notation: describes the components by numeric significance. But ImageSharp's so its packed That is why the value described in this discussion as an "ARGB uint" is actually represented by ImageSharp also has an actual so its packed That immediately makes this API problematic: Color.FromArgb(0xFF112233)What exactly does Does it mean the common textual/numeric convention or does it mean "the packed value of ImageSharp's Those are different interpretations of the same The proposed
This is precisely what the pixel types exist to solve. When you write: Color.FromPixel(new Bgra32(value))
The The and That separation is intentional and is exactly the abstraction we want. It also scales correctly. ImageSharp supports many pixel formats. If packed representation conversion is placed on Do we add: Color.FromRgba(...)
Color.FromBgra(...)
Color.FromArgb(...)
Color.FromAbgr(...)and then equivalents for every other packed representation that somebody considers common? We already have the generic API for exactly this operation: Color.FromPixel<TPixel>(TPixel source)The pixel type identifies the representation. There is also no GC argument for avoiding the pixel here.
So introducing the pixel value here isn't creating the kind of allocation that would make this problematic in a hot rendering loop. The current example: uint argb = ...;
Color color = Color.FromPixel(Unsafe.As<uint, Bgra32>(ref argb));does expose a real API inconsistency, but it isn't on The
public uint Bgra { get; set; }
public uint PackedValue { get; set; }so this is already safe and allocation-free: Color color = Color.FromPixel(new Bgra32 { PackedValue = argb });However, looking at the pixel types, For example, public Rgba32(uint packed)
: this() => this.Rgba = packed;and public Argb32(uint packed)
: this() => this.Argb = packed;
public Bgra32(uint packed)
: this() => this.Bgra = packed;That's the actual API gap here. Adding that constructor makes the original operation: uint argb = ...;
Color color = Color.FromPixel(new Bgra32(argb));and converting back is equally explicit: uint argb = color.ToPixel<Bgra32>().PackedValue;That gives the packed representation semantics to the type whose job it is to define those semantics, while keeping I also disagree with the argument above that the hex parser provides precedent for It actually demonstrates the opposite.
then rearranges those parsed components into the actual packed layout required by Color.FromPixel(argb)In other words, the numeric value created internally by the ARGB hex parser is not simply the numeric interpretation of the input string. For input: the parser reads A=FF, R=11, G=22, B=33, then packs those bytes in before reinterpreting that value as The In fact, the parser having to rearrange the components makes the ambiguity of a public Likewise, I don't agree that going "through a TPixel" is conceptually wrong. It is conceptually necessary because
A So I don't think The useful issue this discussion has identified is simply that |
|
What I'm trying to avoid is any unnecessary overhead in interfacing with ImageSharp, and even though public static Vector4 operator /(Vector4 value1, float value2)
{
return value1 / new Vector4(value2);
}As division is usually more expensive than multiplication, it often pays off to implement as like a multiplication (e.g. in const float ByteClamper = 1f / 255f;
public readonly Vector4 ToVector4() => new Vector4(this.R * ByteClamper , this.G * ByteClamper , this.B * ByteClamper , this.A * ByteClamper);This replaces 4 divisions by 4 multiplications and saves 1 Looking at the
So I would like to cut out all this overhead, and construct the const float ByteClamper = 1f / 255f;
private Color(Vector4 clampedData, bool isAssociated, bool dataIsAssociated)
{
this.data = clampedData;
this.boxedHighPrecisionPixel = null;
this.isAssociated = isAssociated;
this.dataIsAssociated = dataIsAssociated;
}
public static Color FromBgra32(Bgra32 value) {
return new Color(new Vector4(ByteClamper * value.R, ByteClamper * value.G, ByteClamper * value.B, ByteClamper * value.A), false, false);
}
public static Color FromBgra32(Argb32 value) {
return new Color(new Vector4(ByteClamper * value.R, ByteClamper * value.G, ByteClamper * value.B, ByteClamper * value.A), false, false);
}These specific methods could perhaps go into the pixel implementations instead, e.g.
This is exactly my case, I internally use an uint packed color representation that is clearly defined. But I'm fine doing an intermediate Edit: here's a quick benchmark comparing uint argb = 0x12345678;
const int n = 10_000_000;
var pixel = Unsafe.As<uint, SixLabors.ImageSharp.PixelFormats.Bgra32>(ref argb);
const float ByteClamper = 1f / 255f;
for (int j = 0; j < 5; ++j) {
SixLabors.ImageSharp.Color c = default;
DateTime t1 = DateTime.Now;
for (int i = 0; i < n; ++i) {
c = SixLabors.ImageSharp.Color.FromPixel(pixel);
}
DateTime t2 = DateTime.Now;
for (int i = 0; i < n; ++i) {
c = SixLabors.ImageSharp.Color.FromScaledVector(
new Vector4(ByteClamper * pixel.R, ByteClamper * pixel.G, ByteClamper * pixel.B, ByteClamper * pixel.A)
);
}
DateTime t3 = DateTime.Now;
Console.WriteLine(c.ToString());
Console.WriteLine($"FromPixel : {(t2 - t1).TotalSeconds}");
Console.WriteLine($"FromScaledVector: {(t3 - t2).TotalSeconds}");
}Release build output: So that's about 10 times as fast (about 4 times in debug mode), and this is without removing the |
Uh oh!
There was an error while loading. Please reload this page.
In my CAD library internally all colors are represented by argb uint values (Bgra32 in SixLabors.ImageSharp).
Currently I have to cast to a pixel first to convert:
It works, but it would be nice if the Color struct allowed direct conversion from/to argb/bgra uint values as these are so ubiquitous.
I need the Color struct to create brushes/pens, and this is in the hot path of my CAD rendering.
All reactions