Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[css-color] Fallback color in @color-profile that uses RCS #6767

Open
LeaVerou opened this issue Oct 26, 2021 · 19 comments
Open

[css-color] Fallback color in @color-profile that uses RCS #6767

LeaVerou opened this issue Oct 26, 2021 · 19 comments
Labels
css-color-5 Color modification

Comments

@LeaVerou
Copy link
Member

There is the open question of what to do if a color profile fails to load, cannot be decoded, or simply while it's loading. #6129 contains the existing discussion on this.

Some implementors have expressed a reluctance to implement @color-profile because they do not want something as basic as colors to depend on an HTTP request. So, a good fallback strategy is essential for getting this rule implemented.

This is to propose another idea, which serves both as a fallback strategy, but also as a separate feature in its own right.

A mandatory fallback-color descriptor for @color-profile, which would accept any <color>, as well as support Relative Color Syntax with the components that the components descriptor has defined for the current color space.

In its most basic form, it can be a solid color, though that should be discouraged, for accessibility:

@color-profile --fancy {
	src: url('...');
	components: foo, bar, baz;
	fallback-color: red;
}

It can be used to define a fallback color space, e.g. in this case display-p3:

@color-profile --fancyrgb {
	src: url('...');
	components: r, g, b;
	fallback-color: color(display-p3 r g b / alpha);
}

But, most importantly, if there's no good fallback color space, it can be used to define a mapping to an existing space:

@color-profile --fancyrgb {
	src: url('...');
	components: r, g, b;
	fallback-color: color(display-p3 calc(r * .8) calc(g * .8) calc(b * .8));
}

@color-profile --betterlch {
	src: url('...');
	components: l, c, h;
	fallback: lch(calc(l / 100) calc(c / 300) h / alpha);
}

And, bonus, if we make the src descriptor optional, it can even be used to define custom color functions!

@color-profile --brand-red {
	components: l;
	fallback: lch(l 40 20 / alpha);
}

/*  use like color(--brand-red 40) or color(--brand-red 50 / .4) */

(in which case maybe we should rename it to something more generic like @color-space or simply @colors)

@LeaVerou LeaVerou added css-color-4 Current Work css-color-5 Color modification labels Oct 26, 2021
@svgeesus
Copy link
Contributor

Interesting idea.

The dependency on RCS would be another reason to Defer @color-profile to L5 because RCS is defined in Color 5.

@tabatkins
Copy link
Member

I'm a fan of this "provide a relative color function" idea. It seems like a really elegant solution.

Given that the relative color functions here would have a slightly unique syntax (no from <color> argument), do we really need to accept a generic <color> as well? I don't see how your example like fallback-color: red; is at all good for users of the page, but if an author really wants to do it they can with fallback-color: rgb(255 0 0) (since that's technically a relative color function, just a constant one).

The ability to use a @color-profile as a makeshift color-function syntax is also pretty cute, I must admit. I wouldn't take it as justification for this syntax, but it's nice that it falls out.

@LeaVerou
Copy link
Member Author

LeaVerou commented Oct 27, 2021

Given that the relative color functions here would have a slightly unique syntax (no from <color> argument), do we really need to accept a generic <color> as well? I don't see how your example like fallback-color: red; is at all good for users of the page, but if an author really wants to do it they can with fallback-color: rgb(255 0 0) (since that's technically a relative color function, just a constant one).

I thought it would be easier to define as a <color>. I wouldn't want to repeat the grammar of (relative) color functions in there. There is also no precedent of allowing only a color via functional syntax but not other syntaxes. Btw, while fallback-color: red is obviously bad for general usage, it can be useful for debugging.

Regarding the missing from <color>, I was thinking of defining the syntax in RCS, something along the lines of "When used in descriptors, in some cases the host syntax can define what the origin color would be. In that case, the from <color> argument can be omitted.

Alternatively, we can use a keyword (e.g. this-color), and use from this-color. Then no weird new syntax needs to be defined, though it's not a good idea to let editorial convenience drive syntax.

Or, we can do both: define the keyword as a means to explain the magic, then say if the argument is this-color, it can be omitted.

@faceless2
Copy link

Oh this looks familiar! In fact your proposed syntax is almost identical. #2023

@LeaVerou
Copy link
Member Author

Oh this looks familiar! In fact your proposed syntax is almost identical. #2023

I don't see how? Note the components descriptor is already in there, this is about fallback-color.

@faceless2
Copy link

faceless2 commented Oct 27, 2021

(I missed the addition of the components descriptor in May).

#2023 was suggesting the use of @color-profile to define print separations. Print essentially means PDF, and in PDF separations are described by giving them a name and a "fallback color" in a process colorspace such as CMYK or RGB.

In other words, the new ColorSpace is described as a function on an existing ColorSpace, which is exactly what you're proposing here. Your syntax:

@color-profile --brand-red {
	components: l;
	fallback: lch(l 40 20 / alpha);
}

my original proposal looked like this:

@color-profile --reflex-blue {
  src: device-cmyk;
  components: "PANTONE Reflex Blue C" 1 0.723 0 0.02;
}

but it would be very easy to change the syntax to this:

@color-profile --reflex-blue {
  components: "PANTONE Reflex Blue C";
  fallback: device-cmyk(1 0.723 0 0.02);
}

all that would be required is to allow components to be specified as a string as well as an ident. It would, at a stroke, provide a standardized CSS way to specify spot colors for print.

@LeaVerou
Copy link
Member Author

Beyond the ability to specify components as a string, I'm not sure what you're trying to accomplish with that code snippet. The color space you've defined there always produces a specific color (device-cmyk(1 0.723 0 0.02)) since you haven't used any components in there. So people can use color(--reflex-blue) to specify that color, but that could have done with a simple variable, since there's no parameterization:

--reflex-blue: device-cmyk(1 0.723 0 0.02);

Also, an additional complication of specifying components as a string is that then they need to be able to be used inside calc(), and it's pretty weird to have special behavior for only some strings in calc().

@svgeesus
Copy link
Contributor

svgeesus commented Oct 27, 2021

The difference between

--reflex-blue: device-cmyk(1 0.723 0 0.02);

and

color(--reflex-blue)

is that the latter, as proposed, would render using a spot ink (if available) and fallback to device-cmyk only if not available. While the first one will always use device-cmyk.

I do have concerns about specifying inks using freeform strings and without any colorimetric interpretation, but that is apparently what PDF requires for spot inks.

Edit: I see that colorimetric interpretation was addressed

@LeaVerou
Copy link
Member Author

@svgeesus I’m quite likely missing something, but I'm not sure how that would work. No profile is linked in the sample code. Where does this spot ink come from?

@svgeesus
Copy link
Contributor

The printer either has it or doesn't have it.

If it doesn't, then it prints using the fallback.

@svgeesus
Copy link
Contributor

Although, I am not seeing how tints would work.

@faceless2
Copy link

faceless2 commented Oct 27, 2021

For displaying these colors on screen, you're absolutely correct. Chris has just posted while i'm typing this out, and I agree - they're identical (although see below).

But in print, this approach allows us to define our new color as a separation - if the output device recognises it, it can be used as a fifth "ink" alongside cyan/magenta/yellow/black (and if it doesn't, the fallback is used). This is significant even with modern printing methods:

  • the output media may not be white. Being able to specify a "white ink" is different to specifying white as an absence of CMYK. This requires a separation.
  • separations are not just used for ink: glue, glitter, foil, fold-lines are all specified with separations on devices that support them. For many of these the fallback color doesn't matter, but we still need a way to specify them.
  • separations are required for overprinting. This isn't part of CSS nor should it be, but it's a common extension in css-to-print engines.

I'm not going to make a case for Spot colors in PDF, as they're common. I am trying to make a case for a standardized way to specify them in CSS, so that print engines can do so in a compatible way instead of the situation we have now. Just like we've done for device-cmyk.

Finally to your point about strings-in-calc. That's a very good point, particularly as I realise the concept I was actually going for was this:

@color-profile --reflex-blue {
  components: "PANTONE Reflex Blue C";
  fallback: device-cmyk("PANTONE Reflex Blue C" calc("PANTONE Reflex Blue C"*0.723) 0 calc("PANTONE Reflex Blue C"*0.02));
}

That's pretty awful. Some alternatives might be:

Use color-mix?

@color-profile --reflex-blue {
  components: "PANTONE Reflex Blue C";
  fallback: color-mix(in device-cmyk, white, device-cmyk(1 0.723 0 0.02) "PANTONE Reflex Blue C");
}

Use idents, and only idents?

@color-profile --reflex-blue {
  components: PANTONE\ Reflex\ Blue\ C;
  fallback: device-cmyk(PANTONE\ Reflex\ Blue\ C calc(PANTONE\ Reflex\ Blue\ C*0.723) 0 calc(PANTONE\ Reflex\ Blue\ C*0.02));

Or perhaps a new descriptor which would be completely ignored by anything not PDF-based?

@color-profile --reflex-blue {
  components: x;
  component-names: "PANTONE Reflex Blue C";
  fallback: device-cmyk(x calc(x*0.723) 0 calc(x*0.02));
}

The last one is the least horrible to my eyes.

@LeaVerou
Copy link
Member Author

Since components is comma-separated, we can have names right there (in another issue we were even discussing adding ranges):

components: x "PANTONE Reflex Blue C";

That could even be useful for dev tools, or metaprogramming (e.g. making color pickers automatically from color space definitions).
But I'm still unclear on how the PDF renderer would handle these colors and associate them with inks. Is it some magic pattern matching on the names?

@faceless2
Copy link

faceless2 commented Oct 27, 2021

The names are embedded into the PDF file, along with the fallback function mapping it to device-cmyk (or another process space). Anything that doesn't recognise the name is just going to use the fallback function to convert it at display time.

Edit: on reflection this is the crucial piece of information, sorry for not making it clear earlier. Separations in PDF are defined by, and stored with, both their name AND a function mapping them to a process color across their entire range. So they're as colorimetric as any other type of color.

@LeaVerou
Copy link
Member Author

LeaVerou commented Oct 27, 2021

One issue is that if you specify it like that, then using the color requires a component. You can't just say color(--royal-blue) because you've defined this with one component, so if you use fewer components they will be backfilled with 0, i.e. color(--royal-blue) is equivalent to color(--royal-blue 0). So this still feels like a hack 😕

@faceless2
Copy link

If you're going to add names and possibly ranges to components, why not a default value as well?

@svgeesus svgeesus removed the css-color-4 Current Work label Nov 19, 2021
@rvveber
Copy link

rvveber commented Nov 20, 2021

hmm yes interesting

@tabatkins
Copy link
Member

@ColorSpace look, everyone with usernames the same as CSS rules has to deal with this sort of accidental spam, anywhere that uses an @ to indicate usernames. It's something you'll have to get used to. ^_^

@faceless2
Copy link

faceless2 commented May 26, 2022

I had another look at this one to see how hard it would be to implement.

One issue is that it requires a reworking of calc() expressions to take custom idents, which is a new thing (I know we have pi and e, but constants are different).

Given the proposed syntax allows a constant color but we all agree it's a pretty useless thing to do, how about: any constant component in the supplied color is multiplied by the corresponding component in the input color, mod the number of input components.

So for any RGB colorspace you can just do:

@color-profile --fancyrgb {
	src: url('...');
	components: r, g, b;
        fallback: white;
	fallback: color(display-p3 1 1 1); /* if you really want P3 */
}

for any CMYK colorspace:

@color-profile --cmyk {
	src: url('...');
	components: c, m, y, k;
        fallback: device-cmyk(1 1 1 1)
}

or any gray colorspace:

@color-profile --gray {
	src: url('...');
	components: g;
        fallback: device-cmyk(0 0 0 1);    /* if gray(0) is white, gray(1) is black */
        fallback: lab(1 0 0);    /* if gray(0) is black, gray(1) is white */
}

The "gray" example is the reason for that mod the number of input components - this also ensures that this works even if the number of components between the two spaces differ. It also very neatly solves my desire to define spot colors for print:

@color-profile --brand-blue {
        components: x "PANTONE Reflex Blue C";
        fallback: device-cmyk(1 0.723 0 0.02);
}

My pitch:

  • No changes required to calc() or color parsing - fallback is now a regular color, parsed with the spec as it is now.
  • It works without the components property being required.
  • It gives a simple syntax that can be used for spot colors, including defining exactly how they're rendered on screen - the last example has no src, we can just just treat that the same as an ICC profile that is 404
  • It doesn't close any doors for implementing component-names in calc later if they're needed, although the more I think about the less likely it seems they'd be required, because...
  • ICC profiles will almost always be Gray, RGB or CMYK profiles, which fallback easily as shown above. Lab profiles are very rare, but also fallback to either CSS Lab function if required. Anything more exotic, the maths to fallback, eg, YCbCr or FOGRA55 to an existing CSS color is not something you could represent with calc() anyway. In all realistic situations, every component would be used in the same order, possibly multiplied by a constant.

EDIT: somehow I hadn't fully clocked that this was building on "relative color syntax" already proposed for color-5, so I guess we can drop my first argument, as that parsing change is going to happen anyway. However it still does do away with a lot of calc() expressions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
css-color-5 Color modification
Projects
None yet
Development

No branches or pull requests

5 participants