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

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

From: Tab Atkins Jr. <jackalmage@gmail.com>
Date: Fri, 8 Aug 2014 13:11:54 -0700
Message-ID: <CAAWBYDBMH7_Ntzu-rWDZiHGEwhq55-+2kLGa2BaoapvtauHXKw@mail.gmail.com>
To: www-style list <www-style@w3.org>
Currently, the JS color classes in the Color spec are one-to-one with
the color functions - there's an RGBColor for rgb(), an HSLColor() for
hsl(), etc.  Some people have suggested merging some of these
together, so you don't have to manually convert between them.  As I
mentioned on the last telcon, I have several reasons why this would be
a bad idea:

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

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

We could further disambiguate these on a case-by-case basis with
something like "hslSaturation" and "hsvSaturation", but that's
obviously terrible, both by itself and as a special-case. (And
applying that to *all* the members would just be ridiculous.)

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

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.

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.

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.

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.

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.

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

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

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.

Or you can just make your color class separate, which again sets it
apart from the other color classes and makes it feel non-native.

~TJ
Received on Friday, 8 August 2014 20:12:41 UTC

This archive was generated by hypermail 2.3.1 : Friday, 8 August 2014 20:12:42 UTC