W3C home > Mailing lists > Public > www-style@w3.org > August 2014

Re: [css-color] Preemptive rebuttal to requests to merge the JS color classes

From: Lea Verou <lea@verou.me>
Date: Sat, 9 Aug 2014 20:23:55 +0300
Cc: www-style list <www-style@w3.org>
Message-Id: <11952389-A9F7-46E6-AA57-E22356AAB81E@verou.me>
To: "Tab Atkins Jr." <jackalmage@gmail.com>
On Aug 8, 2014, at 23:11, Tab Atkins Jr. <jackalmage@gmail.com> wrote:
> 1. Naming Collisions
> ================
> 
> Currently, the members of each class are named by a single letter,
> just like the function: RGBColor has r, g, b, and a members, etc.  If
> we merged color classes together, then the "b" member of rgb() and the
> "b" member of hwb() would clash, as they mean different things.  (The
> "h" from hsl() and hwb() also clash, as do the "a" members of all of
> the classes, but they're interpreted identically, so that doesn't
> matter.)

Like I’ve mentioned earlier, regardless of the way we go with this, single letter properties are cryptic. Sure, in a snippet like the following, there is little confusion:

var color = new RGBColor(...);
color.b += 10;

However, imagine the color variable used somewhere else, many lines farther from the instance creation, especially if the color variable has a name that doesn’t include the word “color” in it. If I saw a random accent.b or accent.r I’d have no idea what on Earth it’s supposed to represent.

Single letter variables or properties are a well established bad practice in all of CS literature, with few exceptions (e.g. loop counters). Sure, they might make long expressions shorter, but that’s an isolated case that could be solved with local single letter variables, which are easier for a human to resolve when reading the code.

> The obvious fix for this is to expand the names from single-letter
> (from the function names) to words.  That doesn't actually work,
> though: "black" is shared, with different meaning, by both CMYK and
> HWB.  

Like I said on IRC yesterday:
The K in CMYK stands for Key, not blacK: “The "K" in CMYK stands for key because in four-color printing, cyan, magenta, and yellow printing plates are carefully keyed, or aligned, with the key of the black key plate. Some sources suggest that the "K" in CMYK comes from the last letter in "black" and was chosen because B already means blue. However, this explanation, although useful as a mnemonic, is incorrect.” [1].
The B in HWB stands for Blackness, not Black, as documented in the original paper by Alvy Ray Smith [2].
So, not only there is no collision there, but there’s no black property at all!

> Those two live in different color spaces, but if we ever add
> HSV, which I think is reasonable, both it and HSL share "saturation"
> with different meaning.

This is the only real problem about expanding the properties to full words, and it’s not even a current problem, but a *potential future problem*, since there is currently no hsv() in css-color.
It could be solved either by naming HSV saturation as saturationHSV and HSL saturation as saturation, which I agree is awkward (but design decisions of that sort are not unheard of in the OWP), or doing what SimonSapin suggested in this week’s telcon: color.hsl.saturation, which could also include single letter getters (since .hsl provides enough context) or even numbers (color.hsl[0]).

> In general, I don't think we can come up with a non-ridiculous scheme
> that will avoid these kinds of collisions.

Are there any other potential collisions of that sort? Can’t think of any.

> 2. We still need multiple classes, and the categorization is non-obvious
> ======================================================
> 
> As Florian argued in his recent mail, CMYK *really isn't* comparable
> to RGB and the others.  Most of the color functions in CSS define
> colors in the sRGB colorspace, but device-cmyk() (and its analogue,
> the CMYKColor class) specify colors in a device-specific CMYK
> colorspace.  You can convert trivially between colors in the same
> colorspace, but not between ones in different colorspaces.  (The
> "naive conversion" algorithm defined in the spec allows you to treat
> device-cmyk() as if it was in the sRGB colorspace, but it's a pretty
> terrible conversion.)
> 
> I don't think there's a reasonable way to combine things in different
> colorspaces into a single class; dealing with the color profiles would
> get really clumsy.  So we probably need two color classes, at minimum,
> to address the current color syntaxes - one for rgb/hsl/hwb/hex, and
> one for cmyk.  As we add more syntaxes for things in other colorspaces
> (CIELab, anyone?), we'll need more classes as well.
That’s a strawman. Nobody suggested mixing CMYK and RGB in the same class. The argument was that RGB-based classes should be merged, as HSL/HSV/HWB/RGB/Hex/named are all different ways to *present* an RGB color. It would be like having a different Canvas2D context to deal with polar coordinates. CMYK vs RGB is a semantic distinction, as they correspond to a different underlying model, they are not just different ways to present the same data. 
Similarly, CIELab is also a different color space and should have its own class. Ideally though, they should all inherit from the same main Color class.

> However, most authors have no idea what colorspaces are, nor do they
> need to worry about this the vast majority of the time.  I suspect
> that the grouping of syntaxes by colorspace will be very non-obvious
> and potentially confusing.
I’d argue that authors that are likely to perform color calculations with different color formats at least would know that CMYK is not like RGB/HSL/HSV/HWB/etc.
Also, are you saying we should misrepresent the underlying model because authors don’t know what color spaces are?!

> 3. Constant conversions are unpredictably expensive
> ========================================
> 
> The current situation, with multiple classes, means that as long as
> you're operating on a color in a given syntax, getting/setting is
> trivial and cheap - it's just accessing ordinary numeric properties,
> with no unexpected overhead.  There's an obvious cost when converting
> between classes, but that's easy to understand and spot; I think
> authors can tell quite easily that regularly converting back and forth
> between color classes is expensive.
> 
> However, if the classes are merged, *one* syntax will be privileged as
> the "canonical" storage format, and getting/setting any other format
> will have to invoke the conversion algorithm every single time.  For
> example, if RGB is used as the main format, setting hue means
> converting the resultant color back to RGB and storing it that way.
> This cost, while not huge, means that anyone going for high-throughput
> color manipulation will have to work with colors in the canonical
> format, even if it's not convenient for the purpose at hand.
> 
> There are various ways to make this less bad, like caching read
> results until there's a change, or switching between canonical formats
> on the fly to deal with usage patterns, but they incur a decent amount
> of implementation complexity for something that's ultimately
> unnecessary.
Implementation complexity should not affect UI design (and APIs are effectively a UI, where authors are its users).
Also, RGB *is* the canonical format for screens anyway, so for the browser to do anything meaningful with the color, such as displaying it, it would have to be converted to RGB anyway.

> Also, conversions mean that you'll often get situations where if you
> set an integer and then read it right back you'll get a non-integer,
> due to precision loss during the conversion.  For example, most
> integer hue angles in HSL aren't preserved when converting to/from
> RGB.  This isn't a huge deal, as the precision loss is far too small
> to be noticed, or even represented internally, but it does make for a
> clumsier interaction with the API.
If the browser implements some sort of caching like you mentioned above, that won’t be an issue.
Even if not, we’re talking about a language where adding 0.1 + 0.2 gives you 0.30000000000000004. I’m pretty sure anyone working with JS is used to that sort of thing.

> 4. Allowing author extension is much more difficult
> ======================================
> 
> I carefully designed the current system of classes to make it very
> easy for authors to add new color classes that feel "first-class",
> like they were provided by the browser itself.  There are no special
> abilities or non-configurable magic that authors dont' have access to.
I believe there’s an ongoing effort to turn any “non-confugurable magic” in JS APIs into syntax authors can use. For example, accessors were once “non-configurable magic”, imagine if all APIs back then were designed with Java-style setFoo()/getFoo() accessors to make them “extension-friendly”. Same with non-enumerable properties and many other things.

> Merging classes makes this much more difficult.  You can't provide
> your own extensions to an existing constructor, so it's impossible to
> make the constructor accept arguments for your own color syntax.  You
> also can't hook into any of the possible optimizations that the UA
> might be applying to avoid constant conversions, so your syntax is
> guaranteed to be slower.  (Unless you did something really
> complicated, like switching the built-in class with a Proxy that
> intercepted gets/sets, so you can implement your own caching layer.)
Or there could be an API method for adding new color formats, which would make it as easy as possible for authors to extend it.

> (Actually, I'm not sure how the constructor would work in the first
> place.  You can't distinguish between different types of dictionaries
> in WebIDL, and most color functions have basically identical argument
> number and types once you strip their units.  We'd need to either have
> a type argument in the constructor, like `Color("rgb", 1, 0, 0)`, or
> just give it a trivial constructor and instead rely solely on factory
> functions, like `Color.fromRGB(1, 0, 0)`.  Either way is rather
> clumsy.)
The constructor could accept CSS <color> values as strings too, which would solve many cases. Apart from that, it could also accept object literals, RGB(A) values, a single array with RGB(A) values and yes, a few factory functions. By your proposal, to create a color from a CSS color string, we’d need to decide whether to store it as RGB, HSL or something else first, and call the appropriate constructor, which is also clumsy, especially given that in most cases, we won’t know what format the color is in, so we’d end up with unnecessary conversions anyway.

In any case, I think our best bet is to collect as many existing Color classes as possible and see what authors are currently doing about this, rather than speculating what they want. There are literally dozens of Color classes on the Web that we can look into.

> Finally, you'd have to decide which class to extend, which again means
> you have to understand colorspaces well enough to make an informed
> decision.
So one wants to *extend* the Color class with a new color format, write all the calculations for it, but still has no idea what color spaces are? That person must have the weirdest case of selective knowledge ever!


In any case, if we decide to have separate classes, I’d at least argue merging the RGB-based classes. Having both RGBColor and HexColor is ridiculous, and the conversion argument is moot there, as they’re all RGB values. Imagine having different Number classes in JS for different number bases, that’s how weird this is.
Also, having all classes inherit from the same parent Color (CSSColor?) class.

~Lea

[1]: https://en.wikipedia.org/wiki/CMYK_color_model
[2]: http://alvyray.com/Papers/CG/HWB_JGTv208.pdf
Received on Saturday, 9 August 2014 17:24:23 UTC

This archive was generated by hypermail 2.3.1 : Saturday, 9 August 2014 17:24:24 UTC