Compositing and Blending
Alpha compositing and blending are tied together under the umbrella of compositing. Each is just an aspect of the overall compositing of colors. Compositing simply controls how colors are resolved when they are layered on top of each other.
ColorAide implements both alpha compositing and blending as described in the Compositing and Blending Level 1 specification. Alpha composting is based on Porter Duff compositing. By default, the layer
method is used to layer colors on top of each other applying the appropriate compositing, using the normal
blend mode and the source-over
Porter Duff operator by default.
Deprecated 4.0
compose
method was deprecated in favor of the new layer
method and will be removed at some future time.
New in 4.0
A new layer
method was added which uses a more intuitive name and aligns with how multiple colors are handled in other similar APIs such as average()
, interpolate()
, etc.
Blending
Blending is the aspect of compositing that calculates the mixing of colors where the source element and backdrop overlap. Conceptually, the colors in the source element (top layer) are blended in place with the backdrop (bottom layer).
There are various blend modes, the most common is the normal
blend mode which is the default blending mode for browsers. The normal
blend mode simply returns the top layer's color when one is overlaid onto another.
But there are many blend modes that could be used, all of which yield different results. If we were to apply a multiply
blend mode, we would get something very different:
When layering colors, the blend mode can be controlled separately in ColorAide. Below, we use the multiply
example and replicate it in ColorAide. To apply various blend modes in ColorAide, simply call layer
with a list of colors. Colors will be layered on top of each other where the left most color is on top and the right most color will be on the bottom.
>>> c1 = Color('#07c7ed')
>>> c2 = Color('#fc3d99')
>>> c1, c2
(color(srgb 0.02745 0.78039 0.92941 / 1), color(srgb 0.98824 0.23922 0.6 / 1))
>>> Color.layer([c1, c2], blend='multiply', space="display-p3")
color(display-p3 0.32281 0.23703 0.54084 / 1)
>>> c1 = Color('#07c7ed')
>>> c2 = Color('#fc3d99')
>>> c1, c2
(color(srgb 0.02745 0.78039 0.92941 / 1), color(srgb 0.98824 0.23922 0.6 / 1))
>>> Color.layer([c1, c2], blend='multiply', space="srgb")
color(srgb 0.02713 0.18668 0.55765 / 1)
Tip
layer()
can output the results in any color space you need by setting out_space
.
>>> Color.layer(['#07c7ed', '#fc3d99'], blend='multiply', space='srgb', out_space='hsl')
color(--hsl 221.95 0.90722 0.29239 / 1)
Display Differences
As some browsers apply compositing based on the display's current color space, we've provided examples in both sRGB and Display P3 so that the examples can be compared on different displays. Which of the above matches your browser?
ColorAide allows for layering any number of colors as well, simply list of as many colors as you like ordered from left to right, left being the top most color.
>>> c1 = Color('#07c7ed')
>>> c2 = Color('#fc3d99')
>>> c3 = Color('#f5d311')
>>> c1, c2, c3
(color(srgb 0.02745 0.78039 0.92941 / 1), color(srgb 0.98824 0.23922 0.6 / 1), color(srgb 0.96078 0.82745 0.06667 / 1))
>>> Color.layer([c1, c2, c3], blend='multiply', space="display-p3")
color(display-p3 0.3031 0.19729 0.15625 / 1)
>>> c1 = Color('#07c7ed')
>>> c2 = Color('#fc3d99')
>>> c3 = Color('#f5d311')
>>> c1, c2, c3
(color(srgb 0.02745 0.78039 0.92941 / 1), color(srgb 0.98824 0.23922 0.6 / 1), color(srgb 0.96078 0.82745 0.06667 / 1))
>>> Color.layer([c1, c2, c3], blend='multiply', space="srgb")
color(srgb 0.02606 0.15447 0.03718 / 1)
Lastly, if for any reason, it is desired to layer the colors with the blending step disabled (e.g. just run alpha compositing), then you can simply set blend
to False
.
multiply
is just one of many blend modes that are offered in ColorAide, check out Blend Modes to learn about other blend modes.
Alpha Compositing
Alpha compositing or alpha blending is the process of combining one image with a background to create the appearance of partial or full transparency.
When dealing with layers, there are many possible ways to handle them:
Porter Duff compositing covers all possible configurations of layers. Many of these configurations can be useful for all sorts of operations, such as masking. While this library supports all of them, the most commonly used one is source-over
which is used to implement simple alpha compositing to simulate semi-transparent layers on top of each other.
Given two colors layered on top of each other, ColorAide can replicate this behavior and determine the resultant color by applying alpha compositing. We will use the demonstration above and replicate the result in the example below by setting the source color to rgb(7 199 237 / 0.5)
and the backdrop color to #fc3d99
and then running it through the layer
method.
>>> c1 = Color('#07c7ed').set('alpha', 0.5)
>>> c2 = Color('#fc3d99')
>>> c1, c2
(color(srgb 0.02745 0.78039 0.92941 / 0.5), color(srgb 0.98824 0.23922 0.6 / 1))
>>> Color.layer([c1, c2], space="display-p3")
color(display-p3 0.63261 0.53855 0.75261 / 1)
>>> c1 = Color('#07c7ed').set('alpha', 0.5)
>>> c2 = Color('#fc3d99')
>>> c1, c2
(color(srgb 0.02745 0.78039 0.92941 / 0.5), color(srgb 0.98824 0.23922 0.6 / 1))
>>> Color.layer([c1, c2], space="srgb")
color(srgb 0.50784 0.5098 0.76471 / 1)
Display Differences
As some browsers apply compositing based on the display's current color space, we've provided examples in both sRGB and Display P3 so that the examples can be compared on different displays. Which of the above matches your browser?
While the average user will be content with the default alpha compositing, Porter Duff offers many other configurations. If desired, we can change the Porter Duff operator used and apply different composite logic. For instance, in this case we can get the resultant of the backdrop over the source color by setting the operator
to destination-over
. As the backdrop is fully opaque, we just get the backdrop color unaltered.
>>> c1 = Color('#07c7ed').set('alpha', 0.5)
>>> c2 = Color('#fc3d99')
>>> c1, c2
(color(srgb 0.02745 0.78039 0.92941 / 0.5), color(srgb 0.98824 0.23922 0.6 / 1))
>>> Color.layer([c1, c2], operator='destination-over', space="display-p3")
color(display-p3 0.91078 0.30832 0.59266 / 1)
>>> c1 = Color('#07c7ed').set('alpha', 0.5)
>>> c2 = Color('#fc3d99')
>>> c1, c2
(color(srgb 0.02745 0.78039 0.92941 / 0.5), color(srgb 0.98824 0.23922 0.6 / 1))
>>> Color.layer([c1, c2], operator='destination-over', space="srgb")
color(srgb 0.98824 0.23922 0.6 / 1)
You can also apply alpha compositing to multiple layers at once. Simply send in a list of colors of any length, and the colors will be layered on top of each other from right to left with the right most color being on the bottom of the stack and the left color (the source) being on the very top.
Here we are using the normal blend mode and 50% transparency on all the circles with an opaque white background. We will calculate the center color where all three layers overlap.
>>> c1 = Color('#07c7ed').set('alpha', 0.5)
>>> c2 = Color('#fc3d99').set('alpha', 0.5)
>>> c3 = Color('#f5d311').set('alpha', 0.5)
>>> bg = Color('white')
>>> c1, c2, c3, bg
(color(srgb 0.02745 0.78039 0.92941 / 0.5), color(srgb 0.98824 0.23922 0.6 / 0.5), color(srgb 0.96078 0.82745 0.06667 / 0.5), color(srgb 1 1 1 / 1))
>>> Color.layer([c1, c2, c3, bg], blend='normal', space="display-p3")
color(display-p3 0.64728 0.69051 0.76555 / 1)
>>> c1 = Color('#07c7ed').set('alpha', 0.5)
>>> c2 = Color('#fc3d99').set('alpha', 0.5)
>>> c3 = Color('#f5d311').set('alpha', 0.5)
>>> bg = Color('white')
>>> c1, c2, c3, bg
(color(srgb 0.02745 0.78039 0.92941 / 0.5), color(srgb 0.98824 0.23922 0.6 / 0.5), color(srgb 0.96078 0.82745 0.06667 / 0.5), color(srgb 1 1 1 / 1))
>>> Color.layer([c1, c2, c3, bg], blend='normal', space="srgb")
color(srgb 0.50588 0.67843 0.74804 / 1)
Lastly, if for any reason, it is desired to layer colors with alpha compositing disabled (e.g. just run blending), then you can simply set operator
to False
.
Check out Compositing Operators to learn about the many variations that are supported.
Complex Compositing
We've covered alpha compositing and blending and have demonstrated their use with simple two color examples and multi-layered examples, but what about different blend modes mixed with alpha compositing?
In this example, we will consider three circles, each with a unique color: #07c7ed
, #fc3d99
, and #f5d311
. We apply 50% transparency to all the circles and place them on a white
background. We then perform a multiply
blend on all the circles but isolate them so the multiply
blend does not apply to the background. The circles are all represented with CSS. We will now try and replicate the colors with ColorAide.
So in the code below, we work our way from the bottom of the stack to the top. Since the background is isolated from the multiply
blending, in each region, we start by performing a normal
blend on the bottom circle against the background. We then apply multiply
blending on each color that is stacked on top. We've provided both the P3 and sRGB outputs to make it easy to compare in case your browser blends in one instead of the other.
>>> c1 = Color('#07c7ed').set('alpha', 0.5)
>>> c2 = Color('#fc3d99').set('alpha', 0.5)
>>> c3 = Color('#f5d311').set('alpha', 0.5)
>>> cw2 = Color.layer([c2, 'white'], blend='normal', space='display-p3')
>>> cw3 = Color.layer([c3, 'white'], blend='normal', space='display-p3')
>>> r1 = Color.layer([c2, cw3], blend='multiply', space='display-p3')
>>> r2 = Color.layer([c1, cw2], blend='multiply', space='display-p3')
>>> r3 = Color.layer([c1, cw3], blend='multiply', space='display-p3')
>>> r1, r2, r3
(color(display-p3 0.92621 0.59932 0.5132 / 1), color(display-p3 0.64701 0.57853 0.76151 / 1), color(display-p3 0.65654 0.81024 0.61627 / 1))
>>> Color.layer([c1, c2, cw3], blend='multiply', space='display-p3')
color(display-p3 0.62725 0.53003 0.49076 / 1)
>>> c1 = Color('#07c7ed').set('alpha', 0.5)
>>> c2 = Color('#fc3d99').set('alpha', 0.5)
>>> c3 = Color('#f5d311').set('alpha', 0.5)
>>> cw2 = Color.layer([c2, 'white'], blend='normal', space='srgb')
>>> cw3 = Color.layer([c3, 'white'], blend='normal', space='srgb')
>>> r1 = Color.layer([c2, cw3], blend='multiply', space='srgb')
>>> r2 = Color.layer([c1, cw2], blend='multiply', space='srgb')
>>> r3 = Color.layer([c1, cw3], blend='multiply', space='srgb')
>>> r1, r2, r3
(color(srgb 0.97463 0.56615 0.42667 / 1), color(srgb 0.5107 0.55157 0.77176 / 1), color(srgb 0.50365 0.81339 0.51451 / 1))
>>> Color.layer([c1, c2, cw3], blend='multiply', space='srgb')
color(srgb 0.50069 0.50399 0.41161 / 1)
Results may vary depending on the browser, but we can see (ignoring rounding differences) that the colors match up. This was performed on Chrome in macOS using a display that uses display-p3
.
Blend Modes
Multiply
The source color is multiplied by the destination color and replaces the destination. The resultant color is always at least as dark as either the source or destination color. Multiplying any color with black results in black. Multiplying any color with white preserves the original color.
Specified as 'multiply'
.
Screen
Multiplies the complements of the backdrop and source color values, then complements the result. The result color is always at least as light as either of the two constituent colors. Screening any color with white produces white; screening with black leaves the original color unchanged. The effect is similar to projecting multiple photographic slides simultaneously onto a single screen.
Specified as 'screen'
.
Overlay
Multiplies or screens the colors, depending on the backdrop color value. Source colors overlay the backdrop while preserving its highlights and shadows. The backdrop color is not replaced but is mixed with the source color to reflect the lightness or darkness of the backdrop.
Specified as 'overlay'
.
Luminosity
Creates a color with the luminosity of the source color and the hue and saturation of the backdrop color. This produces an inverse effect to that of the Color mode. This mode is the one you can use to create monochrome "tinted" image effects like the ones you can see in different website headers.
Specified as 'luminosity'
.
Compositing Operators
Clear
No regions are enabled.
Source | Destination | Result |
---|---|---|
Specified as 'clear'
.
Copy
Only the source will be present.
Source | Destination | Result |
---|---|---|
Specified as 'copy'
.
Destination
Only the destination will be present.
Source | Destination | Result |
---|---|---|
Specified as 'destination'
.
Source Over
Source is placed over the destination.
Source | Destination | Result |
---|---|---|
Specified as 'source-over'
.
Destination Over
Destination is placed over the source.
Source | Destination | Result |
---|---|---|
Specified as 'destination-over'
.
Source In
The source that overlaps the destination, replaces the destination.
Source | Destination | Result |
---|---|---|
Specified as 'source-in'
.
Destination In
Destination which overlaps the source, replaces the source.
Source | Destination | Result |
---|---|---|
Specified as 'destination-in'
.
Source Out
Source is placed, where it falls outside of the destination.
Source | Destination | Result |
---|---|---|
Specified as 'source-out'
.
Destination Out
Destination is placed, where it falls outside of the source.
Source | Destination | Result |
---|---|---|
Specified as 'destination-out'
.
Source Atop
Source which overlaps the destination, replaces the destination. Destination is placed elsewhere.
Source | Destination | Result |
---|---|---|
Specified as 'source-atop'
.
Destination Atop
Destination which overlaps the source replaces the source. Source is placed elsewhere.
Source | Destination | Result |
---|---|---|
Specified as 'destination-atop'
.
XOR
Destination which overlaps the source replaces the source. Source is placed elsewhere.
Source | Destination | Result |
---|---|---|
Specified as 'xor'
.
Lighter
Display the sum of the source image and destination image.
Source | Destination | Result |
---|---|---|
Specified as 'lighter'
.