-
-
Notifications
You must be signed in to change notification settings - Fork 83
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
Add alpha blending #231
base: main
Are you sure you want to change the base?
Add alpha blending #231
Conversation
I don't know if it was intentional, but pre-multiplication was left out. This is actually what browsers do when overlaying colors. If this is not desired, feel free to disregard it. If another compositing is desired in the future, additionally you would likely want to clamp the resultant alpha, but source over shouldn't yield an alpha greater than 1 or less than zero unless the input alpha were somehow out of range (I assume Colorjs.io doesn't allow this, but maybe I'm wrong). diff --git a/src/over.js b/src/over.js
index 20b4a1e..40851b6 100644
--- a/src/over.js
+++ b/src/over.js
@@ -20,13 +20,14 @@ export default function over (source, backdrop, {
else {
let source_xyz = to(source, space);
let backdrop_xyz = to(backdrop, space);
+ let alpha = source.alpha + backdrop.alpha * (1 - source.alpha)
result = {
space,
- coords: source_xyz.map((s, i) => {
- let b = backdrop_xyz[i];
- return s + b * (1 - source.alpha);
+ coords: source_xyz.coords.map((s, i) => {
+ let b = backdrop_xyz.coords[i];
+ return (s * source.alpha + b * backdrop.alpha * (1 - source.alpha)) / (alpha ? alpha : 1);
}),
- alpha: source.alpha + backdrop.alpha * (1 - source.alpha)
+ alpha: alpha
}
} Additionally, Makes chaining |
This is why this PR is draft :) Yes, there should absolutely be a |
I honestly don't know how to submit a diff that is auto-committable 😅. That is just a code block, sorry.
Everything I've read about compositing usually suggests premultiplying, but I see no harm in making it optional. On a side note, there probably should be some accounting for |
👍🏼 @svgeesus ?
Good point! |
There should be code for premultiplication, and it should be the default because CSS Color 4 requires premultiplication when interpolating non-opaque colors. |
Handling interpolation with missing values is also defined in CSS Color 4
No, because
|
@LeaVerou why is this re-implementing (a subset of) interpolation instead of calling the interpolation function? @svgeesus why didn't you finish implementing premultiplication?? :) if (premultiplied) {
// not coping with polar spaces yet
color1.coords = color1.coords.map(c => c * color1.alpha);
color2.coords = color2.coords.map(c => c * color2.alpha);
} |
… is used for compositing
@svgeesus It’s implementing alpha blending, not interpolation (or at least trying to). |
@LeaVerou @svgeesus I've had some time to revisit this to see if we can push this conversation forward. Locally, I checked out this branch, rebased it, and made the following changes. diff --git a/src/color.js b/src/color.js
index d78f6cf..cc25c80 100644
--- a/src/color.js
+++ b/src/color.js
@@ -17,6 +17,7 @@ import {
set,
setAll,
display,
+ over,
} from "./index-fn.js";
@@ -185,6 +186,7 @@ Color.defineFunctions({
inGamut,
toGamut,
distance,
+ over,
toString: serialize,
});
diff --git a/src/over.js b/src/over.js
index 22193fb..36e4874 100644
--- a/src/over.js
+++ b/src/over.js
@@ -1,12 +1,32 @@
import getColor from "./getColor.js";
import ColorSpace from "./space.js";
+import * as util from "./util.js";
import xyz_d65 from "./spaces/xyz-d65.js";
import to from "./to.js";
+function porterDuffSourceOver (cba, csa) {
+ // The normal way in which two colors are overlaid.
+
+ let fa = 1;
+ let fb = 1 - csa;
+
+ return {
+ co: (cb, cs) => {
+ return csa * fa * cs + cba * fb * cb;
+ },
+ ao: () => {
+ return csa * fa + cba * fb;
+ }
+ }
+}
+
+
export default function over (source, backdrop, {
space = xyz_d65,
outputSpace = source.space,
} = {}) {
+ // Colors should be an RGB like space.
+
source = getColor(source);
backdrop = getColor(backdrop);
@@ -17,31 +37,32 @@ export default function over (source, backdrop, {
throw new Error("Compositing in polar color spaces is not supported.");
}
- let result;
-
- if (source.alpha === 0) {
- result = backdrop;
- }
- else if (source.alpha === 1 || backdrop.alpha === 0) {
- result = source;
- }
- else {
- let source_xyz = to(source, space);
- let backdrop_xyz = to(backdrop, space);
-
- result = {
- space,
- coords: source_xyz.map((s, i) => {
- let b = backdrop_xyz[i];
- return s + b * (1 - source.alpha);
- }),
- alpha: source.alpha + backdrop.alpha * (1 - source.alpha)
- }
- }
+ // This could be changed to use other methods if desired.
+ let compositor = porterDuffSourceOver(backdrop.alpha, source.alpha);
+ let cra = util.clamp(0.0, compositor.ao(), 1.0);
+ let source_xyz = to(source, space);
+ let backdrop_xyz = to(backdrop, space).coords;
+ let result = {
+ space,
+ coords: source_xyz.coords.map((s, i) => {
+ // Only normal blending is supported, so the result is the source.
+ // A blend function for different blend modes could be used.
+ // ```
+ // let cr = util.clamp(0.0, blend(backdrop_xyz[i], s), 1.0);
+ // ```
+ // The result of the mixing formula should be clamped, per
+ // https://www.w3.org/TR/compositing/#blending
+ let cr = util.clamp(0.0, s, 1.0);
+ // Apply compositing
+ cr = compositor.co(backdrop_xyz[i], cr);
+ if (cra !== 0 || cra !== 1) {
+ cr /= cra;
+ }
+ // Alpha blended color
+ return cr;
+ }),
+ alpha: cra
+ };
return to(result, outputSpace);
}
-
-export function register (Color) {
- Color.defineFunction("over", over, {returns: "color"});
-}
\ No newline at end of file This should give results like what I have here, but we are only implementing source over compositing with normal blending, this provides simple alpha blending. This is expected to be performed in RGB gamuts. This would give results like I have here: https://facelessuser.github.io/coloraide/compositing/. I'm willing run with this if this is what is desired, if not , we would need to clearly define what is wanted for me to push this forward. I've left it open to providing other blend modes and even other compositing approaches, but realize none of that may be wanted, but it is written in such a way that it could be if desired in the future. Anyways, at the very least, hopefully, this can get discussions moving again. |
I assume it may be reasonable to enforce gamut mapping of colors entering the function as well. I assume if this was extended beyond RGB spaces, then some of the channel clamping rules may not apply in those scenarios. Also, if expanded beyond RGB gamuts, not all blending modes and composition operators would make sense, but as mentioned earlier, these may not be desired. |
Closes #230
Currently WIP, opening a draft PR so we can iterate.