csswg-drafts
csswg-drafts copied to clipboard
[css-color-3][css-color-4] why opacity is applied on premultiplied color ?
<div
style=
"opacity: 0.6; background-color: rgba( 155 , 66 , 200 , 0.5 );
width:200px; height: 200px;
">
<div
style=
"background-color: rgba( 0, 77 , 100 , 0.8 );
width: 200px; height: 200px;
">
</div>
</div>
I'm trying to figure out how the resulting color is calculated in the above div ( the div containing the opacity style )
First of all i calculated the composition of the inner div on the first div I apply the simple alpha compositing :
co = Cs * as + Cb * ab * ( 1- as) ao = as + ab * ( 1 - as)
here the Cs is the inner div and the Cb is the outer div (the one that contains the opacity style. Cs = ( 0 , 77/255 , 100/255 ) as = 0.8 Cb = ( 155/255 , 66/255 , 200/255 ) ab = 0.5
after calculation :
co = ( 0.06 , 0.27 , 0.39 ) ao = 0.9
this color obtained after compositing is premultiplied
I multiply all RGB and A by the opacity value
co = co * 0.6 ao = ao * 0.6
co = ( 0.04 , 0.16 , 0.23 ) ao = 0.54
finally I compose this color with the color of the body ( rgb=1,1,1 alpha=1) As this color is premultiplied, I apply the formula: co + (1,1,1) * 1 * (1 - ao) = 0.5 , 0.62 , 0.69
this result corresponds to the one after a screenshot and the reading of the color of a point of the displayed div
The formulas that I applied are not from my intuition, but after several tests of other forms of calculation
My question is : why is opacity applied to premultiplied terms?
I tried to apply after premultiplication but the screenshot gives a difference.
When trying to simulate the compositing that a browser currently does, and you have multiple transparent colors, it is important to ensure that you take all colors into consideration. Most likely, you are missing the inclusion of white
.
Now, I'm on a mac that uses a Display P3 monitor with a Display P3 color profile. So color management of the browser is going to cause my browser's composition to operate in Display P3. So for the sake of simplicity, I'm going to force my mac to use an sRGB profile to force the browser to calculate composition in sRGB. Behavior may vary from browser to browser depending on how they implement their color management.
Here are the browser results using Safari in https://codepen.io/. This is what we are going to simulate.
![Screen Shot 2022-08-24 at 6 58 29 AM](https://user-images.githubusercontent.com/1055125/186425872-de32a74d-8a55-4d6c-9423-4efda67356bd.png)
Now, we have to layer our composition, not just with rgba( 155 , 66 , 200 , 0.5 )
and rgba( 0, 77 , 100 , 0.8 )
, but with white
as well, as the background will be visible through both transparent colors. So we must include the white
background into the composition. So we are actually compositing 3 colors, not just the 2 we specified.
This is applying compositing via https://www.w3.org/TR/compositing/ using source-over
.
We'll work from the bottom up overlaying each color until we get the final composition. After we are done calculating our final color, we will apply the div
opacity to our result and overlay it on the white
background.
-
c3
isrgba( 155 , 66 , 200 , 0.5 )
overlayed onwhite
. -
c4
takesc1
and overlays it on top of the result above (c3
). - Lastly,
c5
applies the0.6
opacity toc4
and overlays it onwhite
.
>>> working_space = 'srgb'
>>> c1 = Color('rgba( 0, 77 , 100 , 0.8 )')
>>> c2 = Color('rgba( 155 , 66 , 200 , 0.5 )')
>>> bg = 'white'
>>> c3 = c2.compose(bg, space=working_space, out_space=working_space)
>>> c4 = c1.compose(c3, space=working_space, out_space=working_space)
>>> c5 = c4.clone().set('alpha', 0.6).compose(bg)
>>> HtmlSteps([c3, c4, c5])
[color(srgb 0.80392 0.62941 0.89216 / 1), color(srgb 0.16078 0.36745 0.49216 / 1), color(srgb 0.49647 0.62047 0.69529 / 1)]
We can see that the final result (c5
) matches the browser:
![Screen Shot 2022-08-24 at 7 00 53 AM](https://user-images.githubusercontent.com/1055125/186425912-465e9ebb-0cdb-43da-9ec5-f40dd07e26c5.png)
Now, is there some shortcut to apply the opacity of the div
earlier? I'm not sure. I haven't looked into to exactly how browsers sequence these events, but what we are doing above is equivalent.
You can play with the results live here.
Results may vary depending on the browser, your monitor, etc. You can change the working color space to display-p3
if you are on a mac using a Display P3 monitor and color profile.
My question is not "where did I make a mistake ?",
Apologies, I probably should have read better. You are correct, your values are identical.
My question is: Does the calculation I show correspond to the "theory", and if so, why the "opacity" property is applied on the premultiplied color.
I would argue the color returned by composing the two divs isn't premultiplied anymore, as the resultant color is the combined color. The premultiplication already occurred to apply the weight to each color creating the resultant. In the example I demonstrated, the result of each step of the composition was fed back through with the next layer treating each color as if they were not premultipled. The end result was the same.
What I'm not quite following is the premultiplying the opacity to the resultant, and the modification of the composition. Though it does seem to give the same value, at least in this case. It seems to combine some steps, which alters the end equation slightly. Does this hold true with more complex cases?
I'd have to work through the math to see if why this working out. Maybe someone with more experience in this area will have an answer.
Turns out that what I suggested only works when the background is white. So, I clearly don't understand how the opacity: 0.6
is distributed across already transparent colors. I'd be curious if the OP example also works when the background is not white.
For any element where opacity
is non-unity, that element and all it's children are rendered to an initially transparent black RGBA buffer (including the effect of any alpha components in the colors). The A channel of that buffer is then multiplied by the opacity. Then the modified buffer is composited against the background of that element.
Okay, this makes sense, and I do remember seeing that in the spec. I apparently had a bug with my premultiplication handling in composition which broke my source-over
being associative - I could only properly resolve bottom-up, so handling the isolated case within the div forced me out of this bottom-up model causing the breakage and my confusion.