w3c / fxtf-drafts

Mirror of https://hg.fxtf.org/drafts
https://drafts.fxtf.org/
Other
69 stars 49 forks source link

[compositing-2] Add support for add, subtract and divide blending modes. #449

Open VodBox opened 2 years ago

VodBox commented 2 years ago

I propose adding to the Compositing and Blending spec, to include "add"/"linear-dodge", "subtract" and "divide" as additional separable blending modes.

Add, subtract and divide are all common blending modes that a designer would be familiar with from most image editing or compositing software, and their results cannot easily or exactly be replicated through the use of the existing blending modes defined in the spec.

Add is very useful for certain kinds of effects like with things that glow, since while the screen blending mode might be closer to how physical light would combine if being projected, it can appear to lack saturation and not look correct for things like neon or very bright objects that exist in front of a surface, rather than being projected. A designer may also just prefer the look of add over screen, even if it's not accurate to how light would actually behave.

Subtract and divide, while their use cases are significantly less common, are typically also seen as options in editors alongside add, and including them means that designers who expect those blending modes to be there will see support from browsers.

The proposed blending modes would be added to the separable blending modes section of the spec, and use the following definitions:

Add: B(Cb, Cs) = min(1, Cb + Cs)

Subtract: B(Cb, Cs) = max(0, Cb - Cs)

Divide: if (Cb == 0)     B(Cb, Cs) = 0 else if (Cs == 0)     B(Cb, Cs) = 1 else     B(Cb, Cs) = min(1, Cb / Cs)

Note, that plus-lighter and plus-darker, while they also perform addition and subtraction on the color components respectively like these proposed blending modes, they also perform addition and subtraction on the alpha component, which is not desirable with a true add and subtract blending mode. Moreover, add and subtract as blending modes should be expected to work with the other standard compositing operations, such as source-over.

Relevant part of the spec: https://drafts.fxtf.org/compositing/#blendingseparable

VodBox commented 2 years ago

I started to draft a spec change and also write WPT tests based on the existing plus-lighter tests. In the process of this, it's been made clear that while add is trivially simple, subtract and divide are harder than would first appear, at least depending on which existing implementations the math should aim to match.

Firstly, it seems that how Subtract and Divide is implemented is not universal across software. When the alpha component of colors being subtracted or divided is 1.0, all programs produce the same result, but when blending two or more colors with alphas of less than 1.0, the results diverge.

When testing with less than 1.0 alpha colors, about half of the software I tried[^1] would produce the same result, while the other half[^2] would all produce wildly different results from even each other. Of all programs tested, only DaVinci Resolve matches the trivial math I originally posted (at least, for subtract).

As an example, taking a bottom layer of pure white with an alpha of 0.5, and a top layer of pure blue with an alpha of 0.5, the former group would all produce a perfect grey[^4], while the latter group would all produce colors that would tend more towards yellow.

The definition that the former group follows for subtract is represented like this...

Subtract:
B(Cb, Cs, αb) = max(0, Cb + Cs * (1 - 2αb))

The definition that that group follows for divide is represented like this...

Divide:
if (Cb == 0)
    B(Cb, Cs, αb) = 0 + Cs * (1 - αb)
else
    B(Cb, Cs, αb, αs) = Cd * (1 - αs) + min(Cd / Cs, αd) * αs + Cs * (1 - αd)

Implementing these alternative definitions in my tests based on the plus-lighter WPT tests produce identical results as Photoshop with two colors, and are seemingly within a margin of error of each other when combining several colors (possible rounding step that Photoshop and AE do in 8bpc color mode that the test avoids?).

While I can't speak for whether the subtract definition should be considered "correct", the trivial definition for divide I originally provided definitely has problems—namely that as the alpha of the top color decreases, rather than having less of an effect on the backdrop, it would actually make the backdrop whiter as the premultiplied color component gets closer to, and eventually reaches 0. An alternative definition based on the trivial math but fixes that particular bug might look like the following:

Divide:
if (Cb == 0)
    B(Cb, Cs) = 0
else
    B(Cb, Cs, αb, αs) = Cd * (1 - αs) + min(Cd / Cs, αd) * αs

I'm inclined to believe that the Photoshop math is correct in this instance and should be what such a blend mode on the web should follow.

Whether it uses the Photoshop math or not, at least one of these definitions would need the alpha components to be included in the calculation to produce a sensible result, which does not match any of the existing blending modes. Would that therefore make them compositing operations instead? If not, would it still be considered a separable blending mode, as it still applies to the individual color components separately, even though the alpha is now included in the calculation?

Would there be any implementation concerns, given the use of alpha components in the calculation, but still needing to work with all other existing compositing operations in the context of Canvas 2D?

Is there any strong preferences that others have for which is the "correct" way of implementing these blending modes?

[^1]: Photoshop, After Effects, paint.net, Krita and ImageMagick. [^2]: Clip Studio Paint, GNU Image Manipulation Program, FFmpeg[^3], OBS Studio (Beta), and DaVinci Resolve. [^3]: Despite multiple attempts with the filtergraph to rectify this, when subtracting color components FFmpeg would also seemingly subtract the alpha components. This may mean that I simply was executing the filter incorrectly, and it may actually produce the result of the former group if ran correctly. Until I can find out otherwise, I am ignoring the alpha component and assuming the result for the color component to be correct. [^4]: The reason for the perfect grey is because those programs are using the inverse of the alpha of the backdrop as a multiplier for how much of the top layers color to keep visible. If the backdrop has an alpha of 0, then those programs expect the color on top to passthrough unchanged (in this case, showing blue), but if the backdrop has an alpha of 1, then those programs expect the result to match the result of the trivial subtraction (in this case, it would show yellow). The midpoint of these two colors is grey, so the alpha of 0.5 on the backdrop results in a grey.