WebKit Bugzilla
New
Browse
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
RESOLVED FIXED
212396
Extended Color: Experiment with strongly typed ColorComponents
https://bugs.webkit.org/show_bug.cgi?id=212396
Summary
Extended Color: Experiment with strongly typed ColorComponents
Sam Weinig
Reported
2020-05-26 19:12:45 PDT
Extended Color: Experiment with strongly typed FloatComponents
Attachments
Patch
(68.73 KB, patch)
2020-05-26 19:17 PDT
,
Sam Weinig
no flags
Details
Formatted Diff
Diff
Patch
(54.28 KB, patch)
2020-06-12 11:27 PDT
,
Sam Weinig
no flags
Details
Formatted Diff
Diff
Patch
(58.03 KB, patch)
2020-06-12 11:57 PDT
,
Sam Weinig
no flags
Details
Formatted Diff
Diff
Patch
(68.70 KB, patch)
2020-06-12 12:22 PDT
,
Sam Weinig
no flags
Details
Formatted Diff
Diff
Patch
(68.28 KB, patch)
2020-06-12 13:58 PDT
,
Sam Weinig
no flags
Details
Formatted Diff
Diff
Patch
(68.30 KB, patch)
2020-06-12 14:41 PDT
,
Sam Weinig
no flags
Details
Formatted Diff
Diff
Patch
(68.08 KB, patch)
2020-06-12 14:45 PDT
,
Sam Weinig
no flags
Details
Formatted Diff
Diff
Patch
(68.99 KB, patch)
2020-06-13 09:04 PDT
,
Sam Weinig
no flags
Details
Formatted Diff
Diff
Show Obsolete
(7)
View All
Add attachment
proposed patch, testcase, etc.
Sam Weinig
Comment 1
2020-05-26 19:17:09 PDT
Created
attachment 400296
[details]
Patch
Sam Weinig
Comment 2
2020-05-26 19:23:18 PDT
Posting my proof-of-concept for strongly typed FloatComponents. - Separate types for SRGBFloatComponents, DisplayP3FloatComponents, etc. (uses templates so that they all are implemented using template<> FloatComponents). - ExtendedColor now has a Variant<SRGBFloatComponents, DisplayP3FloatComponents> (didn't add LinearSRGB yet since it's just a proof-of-concept). Currently very explicit about using switchOn() to visit each type even if they are identical to show what it would it would look like with color spaces that were no quite as similar as sRGB and P3 are. - Conversion from one to the other can now have more simplified names, e.g. toSRGB() since the from is implicit in the type. Curious to know what others thoughts on this are.
Darin Adler
Comment 3
2020-05-27 10:25:05 PDT
Comment on
attachment 400296
[details]
Patch I think that the typed components should have a member that contains the untyped components; that member can be sort of like a tuple. Some functions can take the typed components. Others can take untyped. This could let us share as much code as we like and write short forwarding functions rather than writing out the logic.
Darin Adler
Comment 4
2020-05-27 10:25:25 PDT
Generally type is a powerful tool for helping us get this right.
Sam Weinig
Comment 5
2020-05-27 11:43:07 PDT
(In reply to Darin Adler from
comment #3
)
> Comment on
attachment 400296
[details]
> Patch > > I think that the typed components should have a member that contains the > untyped components; that member can be sort of like a tuple. Some functions > can take the typed components. Others can take untyped. This could let us > share as much code as we like and write short forwarding functions rather > than writing out the logic.
They essentially have that already since each of them wraps a std::array<float, 4>. (Note, I think we should probably switch this to a WTF based simd wrapper at somepoint, using simd::float4 on Darwin at least would actually simplify things here and model things better).
Darin Adler
Comment 6
2020-05-27 11:52:12 PDT
Another thought: I suppose the layers of meaning would be: 1) just a bunch of floats 2) one of these is alpha and the rest are components in some color space 3) here's what color space Not sure if there’s much code that could be shared in (2). Also is there some kind of thing where the floats are known to be in the range [0,1]? I guess for P3 that’s not how it works?
Sam Weinig
Comment 7
2020-06-12 11:27:24 PDT
Created
attachment 401754
[details]
Patch
Sam Weinig
Comment 8
2020-06-12 11:57:33 PDT
Created
attachment 401761
[details]
Patch
Simon Fraser (smfr)
Comment 9
2020-06-12 12:08:34 PDT
> Source/WebCore/ChangeLog:10 > + ColorComponents<float> is east but explicit to make conversions
east
> Source/WebCore/platform/graphics/ColorTypes.h:35 > +struct SRGBAColor { > + float red; > + float green; > + float blue; > + float alpha; > +};
I wonder if this should be more like template<typename T> class ColorComponents { std::array<float, 4> ... } enum class sRGBType { }; using sRGBColor = ObjectIdentifier< sRGBType >; I think I'd like the name to suggest that it's 0-1 float components too. Just SRGBColor doesn't make it sound different enough from Color.
Darin Adler
Comment 10
2020-06-12 12:16:17 PDT
Comment on
attachment 401761
[details]
Patch View in context:
https://bugs.webkit.org/attachment.cgi?id=401761&action=review
My review comments are more of a "response to the experiment" than a "checking over whether this is OK to land".
> Source/WebCore/ChangeLog:17 > + us to simplify the naming of functions that convert between color > + types as now only the output type needs to be in the function name.
That’s the part I really like.
> Source/WebCore/platform/graphics/Color.h:136 > + WEBCORE_EXPORT SRGBAColor toSRGBAColorLossy() const;
I don’t think this function needs the word "Color" in its name.
> Source/WebCore/platform/graphics/ColorTypes.h:35 > +struct SRGBAColor { > + float red; > + float green; > + float blue; > + float alpha; > +};
Seems limiting that we only have this for float, not for 0-255 bytes, and not for double (matches Core Graphics API). On the other hand, I do like that this is just a struct and not a template. Makes me think about the premultiplication issue. Is this intrinsically not premultiplied, or can it be used for both? Should the r/g/b component values be typed, or just the struct? Should we initialize everything to 0 to make it harder to make the mistake of leaving things uninitialized? Are there any range restrictions? What about use of infinity and NaN?
> Source/WebCore/platform/graphics/ColorUtilities.cpp:35 > +static float linearToRGBColorComponent(float c)
Seems like the same kind of issue comes up here. Without type safety, we can accidentally do this twice, or forget to do it. More food for thought, does this function work on all float values, or must they be in the 0-1 range?
> Source/WebCore/platform/graphics/ColorUtilities.cpp:142 > + auto linearDisplayP3 = toLinearDisplayP3(displayP3); > + auto xyz = toXYZ(linearDisplayP3); > + auto linearSRGB = toLinearSRGBA(xyz); > + return toSRGBA(linearSRGB);
Better without the local variables, don't you think? return toSRGBA(toLinearSRGBA(toXYZ(toLinearDisplayP3(sRGBA)))); Not beautiful, but easier to see it’s correct.
> Source/WebCore/platform/graphics/ColorUtilities.cpp:150 > + auto linearSRGBA = toLinearSRGBA(sRGBA); > + auto xyz = toXYZ(linearSRGBA); > + auto linearDisplayP3 = toLinearDisplayP3(xyz); > + return toDisplayP3(linearDisplayP3);
return toDisplayP3(toLinearDisplayP3(toXYZ(toLinearSRGBA(sRGBA))));
> Source/WebCore/platform/graphics/ColorUtilities.cpp:155 > + auto [r, g, b, a] = sRGBA;
Annoying that this could scramble values if we wrote the left side wrong. Also annoying that we end up defining the unused alpha. In Swift it would be _ or something.
> Source/WebCore/platform/graphics/ColorUtilities.cpp:165 > + // auto linearSRGBAComponents = toLinearSRGBA(sRGBA); > + // return linearSRGBAComponents * linearSRGBToXYZMatrix.row(1);
Code in comment should be a one-liner: return toLinearSRGBA(sRGBA) * linearSRGBToXYZMatrix.row(1);
> Source/WebCore/platform/graphics/ColorUtilities.cpp:170 > +float contrastRatio(const SRGBAColor& sRGBA1, const SRGBAColor& sRGBA2)
Having the color space in the variable name seems way less valuable when it’s also in the type! Could just be color1, color2. Same applies many other places.
> Source/WebCore/platform/graphics/ExtendedColor.h:55 > + SRGBAColor toSRGBAColorLossy() const;
Don’t need the word "Color" in the function name here.
> Source/WebCore/platform/graphics/SimpleColor.h:69 > + constexpr SRGBAColor asSRGBAColor() const
Don’t need the word "Color" in the function name here.
> Source/WebCore/platform/graphics/filters/FilterOperation.cpp:92 > auto matrix = grayscaleColorMatrix(m_amount);
No need to have this in a local variable. Could just make it part of the one-liner. Same below.
> Source/WebCore/platform/graphics/filters/FilterOperation.cpp:163 > + sRGBAColor.red = 1 - (oneMinusAmount + sRGBAColor.red * (m_amount - oneMinusAmount));
Gotta find a way to apply operations/functions across the three channels other than writing them 3 times.
> Source/WebCore/platform/graphics/filters/FilterOperation.cpp:263 > + hsla.hue = fmod(hsla.hue + 0.5f, 1.0f);
Why are we using the double overload of fmod here? Using std::fmod or fmodf would give us the float version. And I think that’s what we would prefer?
Sam Weinig
Comment 11
2020-06-12 12:19:54 PDT
(In reply to Simon Fraser (smfr) from
comment #9
)
> > Source/WebCore/platform/graphics/ColorTypes.h:35 > > +struct SRGBAColor { > > + float red; > > + float green; > > + float blue; > > + float alpha; > > +}; > > I wonder if this should be more like template<typename T> class > ColorComponents { std::array<float, 4> ... } > > enum class sRGBType { }; > using sRGBColor = ObjectIdentifier< sRGBType >;
It could, but you wouldn't get the benefit of having nice names. All the versions I tried ended up being less nice to use for callers.
> > I think I'd like the name to suggest that it's 0-1 float components too. > Just SRGBColor doesn't make it sound different enough from Color.
Yeah, that's the biggest issue in my mind as well. I am still struggling with what nomenclature to use to suggest float vs. byte. We could go ham and just templatize it: SRGBAColor<float> and then SimpleColor essentially becomes SRGBAColor<uint8_t>.
Sam Weinig
Comment 12
2020-06-12 12:22:41 PDT
Created
attachment 401762
[details]
Patch
Darin Adler
Comment 13
2020-06-12 12:23:23 PDT
I can think of a number of separate dimensions that *might* be worth considering: 1) named components vs. numbered (possibly unclear which is which), or std::array with named indices or accessors? 2) aggregates that support operating across multiple elements vs. structs (member function that ties the elements into a tuple?) 3) color space in the name, but also as a value to use for other purposes? using ColorSpace = xxx? 4) premultiplication somehow specified 5) with/without alpha 6) already normalized values (in range, not infinite) vs. unconstrained values 7) 0-1 vs. 0-255 vs. 0-1 concept but valid outside 0-1 as used in HDR 8) float vs. integer vs. double
Sam Weinig
Comment 14
2020-06-12 13:58:45 PDT
Created
attachment 401777
[details]
Patch
Sam Weinig
Comment 15
2020-06-12 14:14:15 PDT
I quite like this last version, at least as a first step in the right direction: - SRGBA<float> replaces ColorComponents<float> in most places. - color.red replaces color[0]
Sam Weinig
Comment 16
2020-06-12 14:41:52 PDT
Created
attachment 401784
[details]
Patch
Sam Weinig
Comment 17
2020-06-12 14:45:16 PDT
Created
attachment 401786
[details]
Patch
Sam Weinig
Comment 18
2020-06-12 17:32:26 PDT
(In reply to Sam Weinig from
comment #17
)
> Created
attachment 401786
[details]
> Patch
I think this is ready for review now.
Darin Adler
Comment 19
2020-06-12 17:53:09 PDT
Comment on
attachment 401786
[details]
Patch View in context:
https://bugs.webkit.org/attachment.cgi?id=401786&action=review
> Source/WebCore/platform/graphics/Color.cpp:245 > + return { ColorSpace::SRGB, asColorComponents(asSimple().asSRGBA<float>()) };
my head is spinning about to vs. as
> Source/WebCore/platform/graphics/ColorTypes.h:33 > +template<typename T> > +struct SRGBA {
I always prefer these on the same line, but I guess I haven’t convinced you yet.
> Source/WebCore/platform/graphics/ColorUtilities.cpp:156 > + // NOTE: This is the equivilent of `toXYZ(toLinearSRGBA(color)).y`
spelling error I noticed in the existing comment here: "equivilent". Also the use of backquote here ... ?
> Source/WebCore/platform/graphics/ColorUtilities.h:28 > +#include "ColorTypes.h"
Seems like this dependency could be satisfied with a forward declaration?
> Source/WebCore/platform/graphics/ExtendedColor.h:30 > +#include "ColorTypes.h"
Seems like this dependency could be satisfied with a forward declaration?
> Source/WebCore/platform/graphics/SimpleColor.h:76 > - constexpr ColorComponents<float> asSRGBFloatComponents() const > + template<typename T> > + constexpr SRGBA<T> asSRGBA() const > { > - return { convertToComponentFloat(redComponent()), convertToComponentFloat(greenComponent()), convertToComponentFloat(blueComponent()), convertToComponentFloat(alphaComponent()) }; > + if constexpr (std::is_floating_point_v<T>) > + return { convertToComponentFloat(redComponent()), convertToComponentFloat(greenComponent()), convertToComponentFloat(blueComponent()), convertToComponentFloat(alphaComponent()) }; > + else > + return { redComponent(), greenComponent(), blueComponent(), alphaComponent() }; > }
Consider moving these longer function bodies out of the class definition?
> Source/WebCore/platform/graphics/filters/FilterOperation.cpp:160 > + return 1.0f - (oneMinusAmount + component * (m_amount - oneMinusAmount));
I find this math truly mysterious, but at least it’s not changing.
> Source/WebCore/platform/graphics/filters/FilterOperation.cpp:167 > + return std::clamp<float>(intercept + m_amount * component, 0.0f, 1.0f);
Surprised that <float> is needed here.
> Source/WebCore/platform/graphics/filters/FilterOperation.cpp:173 > + return std::max<float>(m_amount * component, 0.0f);
Surprised that <float> is needed here.
> Source/WebCore/platform/graphics/filters/FilterOperation.cpp:225 > +bool InvertLightnessFilterOperation::transformColor(SRGBA<float>& color) const
So sad that these use bool plus out argument. Obviously we want these to be Optional instead some day.
Sam Weinig
Comment 20
2020-06-13 09:03:33 PDT
(In reply to Darin Adler from
comment #19
)
> Comment on
attachment 401786
[details]
> Patch > > View in context: >
https://bugs.webkit.org/attachment.cgi?id=401786&action=review
> > > Source/WebCore/platform/graphics/Color.cpp:245 > > + return { ColorSpace::SRGB, asColorComponents(asSimple().asSRGBA<float>()) }; > > my head is spinning about to vs. as
Yeah, I was thinking that might be too easy to mix up. The intent was to* were conversions that included some potentially non-invertible math and as* was really just a glorified cast. I am thinking in a follow up of replacing the to* functions with more explicit convertTo* functions to make it clearer.
> > > Source/WebCore/platform/graphics/ColorTypes.h:33 > > +template<typename T> > > +struct SRGBA { > > I always prefer these on the same line, but I guess I haven’t convinced you > yet.
Heh. My brain does seem to have this preference, but it is such a weak preference I don't think I would remember it if you asked me which one I actually prefer. I think it comes down to not knowing what to do for weird cases like a templated mem
> > > Source/WebCore/platform/graphics/ColorUtilities.cpp:156 > > + // NOTE: This is the equivilent of `toXYZ(toLinearSRGBA(color)).y` > > spelling error I noticed in the existing comment here: "equivilent". Also > the use of backquote here ... ?
Fixed.
> > > Source/WebCore/platform/graphics/ColorUtilities.h:28 > > +#include "ColorTypes.h" > > Seems like this dependency could be satisfied with a forward declaration?
Fixed.
> > > Source/WebCore/platform/graphics/ExtendedColor.h:30 > > +#include "ColorTypes.h" > > Seems like this dependency could be satisfied with a forward declaration?
Fixed.
> > > Source/WebCore/platform/graphics/SimpleColor.h:76 > > - constexpr ColorComponents<float> asSRGBFloatComponents() const > > + template<typename T> > > + constexpr SRGBA<T> asSRGBA() const > > { > > - return { convertToComponentFloat(redComponent()), convertToComponentFloat(greenComponent()), convertToComponentFloat(blueComponent()), convertToComponentFloat(alphaComponent()) }; > > + if constexpr (std::is_floating_point_v<T>) > > + return { convertToComponentFloat(redComponent()), convertToComponentFloat(greenComponent()), convertToComponentFloat(blueComponent()), convertToComponentFloat(alphaComponent()) }; > > + else > > + return { redComponent(), greenComponent(), blueComponent(), alphaComponent() }; > > } > > Consider moving these longer function bodies out of the class definition?
Fixed.
> > > Source/WebCore/platform/graphics/filters/FilterOperation.cpp:167 > > + return std::clamp<float>(intercept + m_amount * component, 0.0f, 1.0f); > > Surprised that <float> is needed here.
I believe it's due to m_amount being a double.
> > > Source/WebCore/platform/graphics/filters/FilterOperation.cpp:173 > > + return std::max<float>(m_amount * component, 0.0f); > > Surprised that <float> is needed here.
Same. Thanks so much for the review.
Sam Weinig
Comment 21
2020-06-13 09:04:09 PDT
Created
attachment 401837
[details]
Patch
EWS
Comment 22
2020-06-13 09:43:01 PDT
Committed
r263000
: <
https://trac.webkit.org/changeset/263000
> All reviewed patches have been landed. Closing bug and clearing flags on
attachment 401837
[details]
.
Radar WebKit Bug Importer
Comment 23
2020-06-13 09:44:17 PDT
<
rdar://problem/64329874
>
Note
You need to
log in
before you can comment on or make changes to this bug.
Top of Page
Format For Printing
XML
Clone This Bug