Open raphlinus opened 9 hours ago
It should be noted if you were to treat the cylindrical space in the rectangular space and apply the premultiplication, you'd still have the same hue, that's why it doesn't make sense to premultiply the hues. The hue doesn't change, just the colorfulness when it is mixed.
>>> color = Color('oklch', [0.5, 0.2, 85], 0.5)
>>> a, b = color.convert('oklab').get(['a', 'b'])
>>> a *= color.alpha()
>>> b *= color.alpha()
>>> Color('oklab', [color['lightness'] * color.alpha(), a, b]).convert('oklch')
color(--oklch 0.25 0.1 85 / 1)
I don't think compositing should really be done in a cylindrical space, but interpolating between two cylindrical colors, which is what color-mix
is doing seems fine.
Another way of phrasing this is that the premultiplication method in the Color Level 4 draft mismatches the definition of premultiplication for color-mix in Color Level 5, which does not make an exception for hue.
If I had to guess, I think omitting the statement about not premultiplying hue is just an accident, not an explicit intention.
I do realize that, in this scenario, we are referring to this alpha blending as compositing. When I say compositing probably shouldn't be done in a cylindrical space, I mean the browser itself should not apply compositing in this way when rendering colors or overlaying images, etc. due to how hue interpolations work, but there is nothing wrong with interpolating in a cylindrical space if that is what you want to do.
Oops, I realize I made a mistake in formulating this question, as I believed that color-mix
was capable of representing the Porter-Duff over
operator, and, now, looking at it more closely, it seems that it is only capable of representing lerp
but with an additional alpha scaling step.
So I think there are two separate concerns. One is the mismatch as pointed out, which I agree with @facelessuser is most likely a spec drafting issue. The second is whether the color representation is suitable for compositing, which is the main question I'm trying to raise right now. We do already have an interpolate
method which does the color-mix
functionality except for the scaling by 1 / (p1 + p2), and we have a separate mul_alpha
which can perform that additional step.
To me, interpolation and compositing are closely related. In particular, I believe compositing color-a / alpha over opaque color-b should match color-mix(color-a alpha, color-b)
. In rectangular spaces, that is not controversial, but if we allow compositing in cylindrical spaces, it is.
So strike "another way of phrasing this," and the last paragraph should read: For context, this came up when we were starting to contemplate an over
method in our new Rust color crate. My original hope is that the PremulColor
type we defined for CSS Color Level 4 interpolation would also efficiently support compositing, but that is not looking hopeful at the moment.
Apologies for the confusion.
To me, interpolation and compositing are closely related
Sure, interpolation is used in compositing.
I think when defining a general-purpose interpolation function, like what is done in CSS, you need to define clear, sane rules, and the rules for cylindrical spaces seem perfectly reasonable.
Due to how hue interpolation behaves, it doesn't lend itself well to compositing images and layers, but there are plenty of reasons to desire a hue interpolation, such as when doing gradients. I'm not sure there is a reason to specifically forbid alpha blending/compositing in cylindrical spaces as the current rules are quite reasonable. Would I choose to specifically use a cylindrical space to do compositing in an image? Probably not.
The current spec describes premultiplication as not multiplying the hue component in cylindrical spaces. I think I understand the motivation of that for doing interpolation and gradients, but it does not seem to be the correct logic for doing mixing and compositing. I'm wondering what implementations should do, and specifically whether there need to be two forms of premultiplication, one for lerp, one for mixing.
The basic rule for
x over y
in premultiplied spaces isx + (1 - x.alpha) * y
. But this breaks down for cylindrical spaces premultiplied as per the spec, as the sum of the weights on the hue components exceeds 1. (It's not a problem for lerping, as the sum of weights is always 1)Another way of phrasing this is that the premultiplication method in the Color Level 4 draft mismatches the definition of premultiplication for
color-mix
in Color Level 5, which does not make an exception for hue.I can think of several ways of dealing with this:
over
operation as(x.hue * x.alpha + y.hue * y.alpha * (1 - x.alpha)) / (x.alpha + y.alpha * (1 - x.alpha))
for the hue component.PremulColor
andLerpPremulColor
as separate types, with the former premultiplying all components, and the latter holding out hue.One reason that choices (1) and (2) are on this list is that I'm not sure how useful it is to do compositing in a cylindrical space. I'm happy to be pointed to evidence on this.
The difference in behavior seems subtle, and it's not obvious to me that the CSS specified behavior is clearly more better or more correct than the simpler, compositing-friendly behavior. I searched for discussion where this was decided and couldn't find it. I can generate color ramps to illustrate the difference if that would be useful.
For context, this came up when we were starting to contemplate a
color_mix
method in our new Rust color crate. My original hope is that thePremulColor
type we defined for CSS Color Level 4 interpolation would also efficiently support mixing/compositing, but that is not looking hopeful at the moment.