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: Tab Atkins Jr. <jackalmage@gmail.com>
Date: Sat, 9 Aug 2014 23:42:00 -0700
Message-ID: <CAAWBYDBkrKPisYww3qz7njDXj2DH=B+F_3YWxDizJUwnoNahtg@mail.gmail.com>
To: Lea Verou <lea@verou.me>
Cc: www-style list <www-style@w3.org>
On Sat, Aug 9, 2014 at 10:23 AM, Lea Verou <lea@verou.me> wrote:
> 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.

If you dont' know what type your object is, relying on the name of the
property as a reminder really isn't sufficient.  Use better naming
standards.  I don't consider this a reasonable counterargument.

> 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.

Valid, but these syntaxes have precedent, given the single-letter
names of the channels *in the names of the functions/classes*.

>> 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!

As I've argued, "key" is confusing and non-obvious, and others have
said that interpreting it as "black" is fine.

>> 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]).

Using a named subobject is a decent way to disambiguate, but still
several characters more verbose than otherwise necessary.  I'm still
not sure *why* we'd want to do a merge.

>> 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.

The L in Lab is for "lightness".  There are a number of functions
closely related to HSL/HSV that clash in similar ways.


>> 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?!

No, why do you think I said anything like that?

I'm not sure why the average author would have any reason to know that
CMYK is different from RGB, but HSL isn't.  If anything, CMYK and RGB
seem more closely related, as they're both color syntaxes where the
channels are individual colors; HSL seem like a completely different
beast.

(And, of course, the fact that HSL lives in the sRGB space rather than
the CMYK space is a historical accident, rather than a consequence of
anything real.  It's just as easy to define a CMYK version of HSL,
since the CMYK and RGB are identical except for the hue rotation for
their channels.)

The choices of which syntaxes are mergeable and which aren't
arbitrary, but they're *definitely* not intuitive or reasonable
without significant domain knowledge.

>> 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).

UAs are below authors and users in the hierarchy of constituencies,
but not *infinitely* below.  Their concerns still matter.

> 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.

That's irrelevant when working with color before it actually gets put
on the screen.  "Sorry, using HSL is always going to be more expensive
than it really needs to be, because we'll *eventually* turn it into
RGB" isn't a good answer.

>> 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.

Floating point math is weird, yes, but *numbers*, by themselves,
aren't.  Integers, especially, are always precise until you hit very
high values.  Fractional values are also round-trippable, so "foo.x =
y; foo.x == y;" will always return true for numbers.  This proposal
would break that invariant, which seems like a bad idea if we can
avoid it.

>> 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.

I'm not sure what such a thing would look like.

>> (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.

It already does.

> Apart from that, it could also accept object literals,

No it can't, for the reason I just stated.

> RGB(A) values,

What are these?  Instances of a color class?  Yes, that's already
possible, and will continue to be regardless.

> a single array with RGB(A) values

No it can't, for the reason I just stated.

> and yes, a few factory functions.

Those aren't natural in JS, and we avoid them when possible in favor
of constructors.

> 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.

I have no idea what you're talking about.  If you want to parse a
string into a CSS color, you decide what format you want to *work with
it* in, and pass it to the corresponding constructor.  Or use
CSSColor.parse() to get it in the class corresponding to its syntax,
whatever that is.  There's no unnecessary conversions?

> 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.

We have to be careful here, and make sure that examples we gather are
designed to be extensible with new color syntaxes.  If they're only
designed for a small predefined set of color syntaxes, then it hardly
matters what organization you use, and looking at them is irrelevant.

>> 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!

Urf, you seem to have no idea just how hard color spaces are to grasp
for most people.  Look at any thread on colorspace conversion or
handling in Photoshop or related software.  Heck, look at how terribly
wrong *browsers* dealt with colorspaces in the past (and to some
extent still do today).

Plus, I simply don't need to give a shit about colorspaces most of the
time.  I can write the HWBColor class without having to touch on
anything even remotely colorspace-related, for example.  I can go look
up formulas for "converting" between CMYK and RGB with little or no
mention of colorspace issues.

Colorspaces, along with text encodings and other "similar values that
aren't interoperable because they're encoded in slightly different
ways", are very complicated and hard for most people to grasp, as can
be easily demonstrated.  We shouldn't expose people to them unless
necessary, and it's not necessary here.

> 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.

Some people are more comfortable working with RGB in the [0-255]
range, and others prefer the [0-1] range.  We have syntaxes for both,
so I don't see why we shouldn't offer both possibilities to people in
JS too.

> Also, having all classes inherit from the same parent Color (CSSColor?) class.

They already do.

And finally, note that these are just rebuttals to any explanations of
why we should merge the color classes.  I still haven't seen a good
reason provided as to *why* we should merge classes.

~TJ
Received on Sunday, 10 August 2014 06:42:51 UTC

This archive was generated by hypermail 2.3.1 : Sunday, 10 August 2014 06:42:52 UTC