WICG / color-api

A proposal and draft spec for a Color object for the Web Platform, loosely influenced by the Color.js work. Heavily WIP, if you landed here randomly, please move along.
https://wicg.github.io/color-api/
Other
130 stars 3 forks source link

Supporting anonymous color spaces #19

Closed LeaVerou closed 1 year ago

LeaVerou commented 2 years ago

@tabatkins pointed out that with the current design, anonymous color spaces are possible, by just creating ColorSpace objects that are never registered, and passing them around instead of string ids.

This would allow web components to use color spaces without polluting the global name space.

However, supporting this use case does bring up some questions.

Currently, color.colorSpace is a string, which facilitates easy comparison with strings, i.e. authors can do things like if (color.colorSpace === "lch") .... However, if we want to support anonymous color spaces, there is no string to return in those cases. There are several options here, none ideal:

  1. color.colorSpace would sometimes return a string and sometimes a ColorSpace object. This is not ideal because it's inconsistent, and thus author code would either not take the latter into account and break with such colors, or would need to branch, which is a hassle
  2. We'd have two properties, one of which returns the colorSpace id (if the color space is registered) and one the color space object, which is always present.
  3. We just make color.colorSpace always return an object
LeaVerou commented 2 years ago

Another complication: what does color.toJSON() return for anonymous color spaces?

Perhaps it can just use the color space's name, even if it's not registered?

tabatkins commented 2 years ago

I think having it always return an object is fine. Object identity will allow comparison just as easily as strings.

Comparing with a built-in is slightly more verbose:

if(color.colorSpace == ColorSpace.get("lch")) {...}

but honestly that doesn't feel too bad? And if it's used a bunch, author code can always stash it with const lch = ColorSpace.get("lch");.

We could also expose the built-in spaces as statics, so they could type ColorSpace.lch, if those character savings seem worthwhile.

Another complication: what does color.toJSON() return for anonymous color spaces?

Hrm. The point of toJSON() is to give you an object that, well, is JSON, but also that can be passed directly to the Color constructor to recreate the color. You can't satisfy both of those with an anonymous space; one has to give. (Either the object has a ColorSpace value, which isn't JSON, or the object has a string like "[anonymous] foobar", which doesn't recreate the color.)

So, an alternative might be that if you .toJSON() a color using an anonymous space, it automatically converts to the nearest registered ancestor space and outputs in that. You won't get an exact recreation of the color when you pass it to new Color(), but you'll get a precise equivalent (assuming it's in-gamut, I guess).

LeaVerou commented 2 years ago

Comparing with a built-in is slightly more verbose:

It's a bit better than that:

if (color.colorSpace.name === "lch") { ... }

Another complication: what does color.toJSON() return for anonymous color spaces?

Hrm. The point of toJSON() is to give you an object that, well, is JSON, but also that can be passed directly to the Color constructor to recreate the color. You can't satisfy both of those with an anonymous space; one has to give. (Either the object has a ColorSpace value, which isn't JSON, or the object has a string like "[anonymous] foobar", which doesn't recreate the color.)

So, an alternative might be that if you .toJSON() a color using an anonymous space, it automatically converts to the nearest registered ancestor space and outputs in that. You won't get an exact recreation of the color when you pass it to new Color(), but you'll get a precise equivalent (assuming it's in-gamut, I guess).

Another alternative is to keep the anonymous ColorSpace around as long as possible so it can be fed back to the Color constructor if need be, and just rely on the regular stringification if converted to JSON that will just turn it into {}, which is useless, but just as useless as "(anonymous)".

I need to think more about the idea of silently converting to an ancestor space, is it helpful or too much unpredictable magic?

tabatkins commented 2 years ago

Using the name isn't quite reliable (an unregistered space could also say its name is "lch") but yeah, in practice that's just fine. ^_^

just rely on the regular stringification if converted to JSON that will just turn it into {}

Oooh, I didn't realize it would do this. Yes, this seems perfectly fine to me, let's go with that for now.