Re: [csswg-drafts] [css-color] Separation / DeviceN color support

OK, here's an alternative suggestion for how to handle _DeviceN_ and _Separation_ colors in CSS in a way that maps *directly* to the approach used in PDF (and also PostScript Level 3, which I can confirm now I have reread the spec).

First, this involves no changes at all to the color() function, and no additional "separation" function as I initially suggested. All changes are local to the @color-profile rule.

Currently @color-profile can reference an ICC profile, which maps _M_ color components in the colorspace being define to _N_ components in a reference colorspace. It is essentially an _MxN_ function. I'm proposing to add some new methods of doing this that don't make use of an ICC profile.

The reason for this is that specifying custom color-spaces in PDF and PostScript without reference to an ICC profile is common practice, and I am trying to ensure that css-color-4 can be used to do the same.

PDF and PostScript have several ways to do this, but the following apply to custom inks:
1. Interpolation between two sets of values - for example from (0 0 0 0) to (1 0.89 0 0). Interpolation is exponential (like a gamma function) but will be linear if the exponent is 1.
2. A lookup table
3. A PostScript function
4. A "stitching function" which uses two or more of the above, each applying to a different range of values (the same way the conversion from sRGB to XYZ is done)

As it's just a  first proposal I'll demonstrate how you could do two of them.

The @color-profile rule would get three new declarations:

names : string {1,n}
fallback: ident
function: function-type [ , function-type ]*
with the following type definitions
function-type = interpolate( component* [ , gamma ] ) | url format
component = number-or-percentage
gamma = number
* "names" is the list of components (inks) in the colorspace
* "fallback" is the fallback colorspace if those components are not available, which must be "srgb", "lab", "device-cmyk" or some other process color space which is known to the CSS engine (i.e. it's not also defined in a @color-profile rule)
* "function" is a list of functions which map the components in this color space to the components in the fallback space. The CSS engine would use the first one that is able to process - it's the same model used to compute "src" in the @font-face rule, where the "url" and "format" syntax is also used
* "component" is a value from 0..1 (and which could also be specified as 0..100%)

Some examples as to show how those would work:
## Example 1
@color-profile reflexblue {
    names: "Pantone Reflex Blue";
    fallback: device-cmyk;
    function: interpolate(0 0 0 0 1 0.89 0 0, 1)
#logo { color: color(reflexblue 0.9, device-cmyk(90% 80% 0 0)) }
This defines a color-profile with a single component, "Pantone Reflex Blue". If the component is available in the output device (true if converting this CSS to PDF or PostScript, most certainly false otherwise), then it will be used directly. If not, the "fallback" and "function" object are used to convert the color into an equivalent in a known color-space.

_Note: For single colors as shown here this is unnecessarily complex - color() already specified a fallback, so you could just use that. But for gradients, the intermediate values in the gradient need to be converted too, which requires evaluating the function._

"function" in this example is "interpolate", which takes _N_*2 arguments (where _N_ is the number of components in the fallback colorspace), and an optional exponent with a default value of 1. A CSS engine aware of this syntax would take the value of the component specified in the color() function - 0.9 in this example - and use the interpolate function to convert it to the fallback color in the device-cmyk space:
c = (0.9 * (1-0) + 0) ^ 1;
m = (0.9 * (0.89-0) + 0) ^ 1
y = (0.9 * (0-0) + 0) ^ 1;
k = (0.9 * (0-0) + 0) ^ 1;
A CSS engine unaware of the syntax would simply use the fallback color specified in the color() function, `device-cmyk(0.9 0.8 0 0)`

Finally, a CSS engine that _was_ able to use the specified color directly would do so. For reference, the above would translate into this syntax in PDF:
ColorSpace Resource R1: [/Separation /Pantone#20Reflex#Blue /DeviceCMYK <</C0 [0 0 0 0] /C1 [1 0.89 0 0] /FunctionType 2 /N 1 /Domain [0 1]>> ]
Stream: /R1 cs 0.9 scn
and PostScript
[ /Separation (Pantone Reflex Blue) /DeviceCMYK { dup 0.89 mul 0 0 } ] setcolorspace 0.9 setcolor

## Example 2
I have trawled our collection of sample documents to make sure any DeviceN color-spaces they define could be described with this syntax. The most complex use gradients which shade over multiple inks simultaneously. This could be reproduced with this format:
@color-profile myink {
    names: "Pantone 5815" "Yellow" "Black";
    fallback: device-cmyk;
    function: url( format("application/postscript"), url(myink.js) format("text/javascript");

#block {
    background: linear-gradient(0deg, color(myink 1 0 0.4, device-cmyk(0 0 0.91 0.87)), color(myink 0 0.8 0.6, device-cmyk(0 0 0.8 0.6)))
The background for #block is defined as a linear gradient between colors in our custom color-space. Both color() objects specify a fallback color, so if the CSS engine is unable to parse the @color-profile rule for any reason, it can just calculate the gradient between the two fallback colors.

The @color-profile function converting our custom color-space to the fallback "device-cmyk" is defined by way of an external function, which is loaded from "" if the CSS engine can handle the format "application/postscript", falling back to "myink.js" if the CSS engine can handle the format "text/javascript".

In both cases the function takes three arguments (the number of components in "names") and returns four values (the number of components in device-cmyk). The two files could look like this (incidentally this is a real-world PostScript function extracted from a PDF which I converted to JavaScript, so can be considered fairly typical)

return function(c1,c2,c3) {
    return [ 0, 0, 1-((1-c2)*(1-(c1*0.91))), 1-((1-c3)*(1-(c1*0.79))) ];
1 4 1 roll 1 4 1 roll 1 index 1 cvr exch sub 4 1 roll 0 index 1 cvr exch sub 4 1 roll 7 -1 roll 3 index 0 mul 1 cvr exch sub mul 1 cvr exch sub 7 1 roll 6 -1 roll 3 index 0 mul 1 cvr exch sub mul 1 cvr exch sub 6 1 roll 5 -1 roll 3 index 0.91 mul 1 cvr exch sub mul 1 cvr exch sub 5 1 roll 4 -1 roll 3 index 0.790 mul 1 cvr exch sub mul 1 cvr exch sub 4 1 roll pop pop pop

To demonstrate how to calculate the color stops at the start of the gradient if the CSS engine can use a function defined in "text/javascript":
1. Take the components specified in the color() function - 1, 0, 0.4
2. Pass those components into the function returned from evaluating the "mygradient.js" script.
3. Use the resulting values (0, 0, 0.2728, 0.3553) as components to the fallback space, "device-cmyk".

This approach is a bit unconventional but, if you want to be able to create the kind of color-spaces we see in real-world PDF documents, then I can't see how else to do it other than creating a function. Both PDF and PostScript will require the "application/postscript" format, and simply embed it in the generated file; it's impossible to convert from JS to PS and impractical to go the other way, so providing two representations of the same function struck me as a better option. 

I'm presuming that if done this way, any JS would be limited to its local context only and have no access to the global document context. And if the function isn't evaluated (by user-agent choice, or because it's invalid), a reasonable approximation of the gradient will just interpolate between the two fallback colors.

## Points to address
1. If the overall idea gets approval I'm happy to draft additional `function-type` definitions which will allow a table-lookup to be used for a function, as is commonly seen in PDF.
2. The existing syntax for color() function allows a "name" - @tabatkins implies this would be used when an ICC profile has a "named color". I'm not famiilar enough with ICC profile internals to know about that, but it does strike me that adding "named" to the @color-profile rule, as suggested here, might be a cleaner way to achieve the same thing.
3. Given that a @color-profile no longer necessarily references an ICC profile, would @color-space be a better name for the rule?
4. This approach is only focused on DeviceN and Separation colors, as these use custom inks and there's really no other way to do this - creating a custom ICC profile for each combination of inks is impractical. But PDF and PostScript have other methods of define color-spaces for process colors that don't use ICC profiles, e.g. CalRGB. Extending the @color-profile rule to handle these would be quite easy to do, although in this case an ICC profile would probably be available to do the same job.

Thanks for your consideration!

GitHub Notification of comment by faceless2
Please view or discuss this issue at using your GitHub account

Received on Monday, 4 December 2017 13:28:22 UTC