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. Blend is run first, followed by alpha compositing.
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 compose
method uses the normal
blend mode and the source-over
Porter Duff operator.
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 composing, the blend mode can be controlled separately in ColorAide. Here, we again use the multiply
example and replicate it in ColorAide. To apply blending in ColorAide, simply call compose
with a backdrop color, and the calling color will be used as the source.
>>> 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))
>>> c1.compose(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))
>>> c1.compose(c2, blend='multiply', space="srgb")
color(srgb 0.02713 0.18668 0.55765 / 1)
Tip
compose()
can output the results in any color space you need by setting out_space
.
>>> Color('#07c7ed').compose(Color('#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 you to blend a source over multiple backdrops quite easily as well. Simply send in a list, and the colors will be blended from right to left with the right most color being on the bottom of the stack, and the base color being used as the source (on the very top).
>>> 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))
>>> c1.compose([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))
>>> c1.compose([c2, c3], blend='multiply', space="srgb")
color(srgb 0.02606 0.15447 0.03718 / 1)
Lastly, if for any reason, it is desired to compose with blending 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, ColorAide can replicate this behavior and determine the resultant color by applying compositing. We will use the demonstration above and replicate the result in the example below. Below we set the source color to rgb(7 199 237 / 0.5)
and the backdrop color to #fc3d99
and run it through the compose
method. It should be noted that the default blend mode of normal
is used in conjunction by default.
>>> 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))
>>> c1.compose(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))
>>> c1.compose(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))
>>> c1.compose(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))
>>> c1.compose(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 as the backdrop, and the colors will be composed from right to left with the right most color being on the bottom of the stack and the base 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))
>>> c1.compose([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))
>>> c1.compose([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 run compose 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 = c2.compose('white', blend='normal', space='display-p3')
>>> cw3 = c3.compose('white', blend='normal', space='display-p3')
>>> r1 = c2.compose(cw3, blend='multiply', space='display-p3')
>>> r2 = c1.compose(cw2, blend='multiply', space='display-p3')
>>> r3 = c1.compose(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))
>>> c1.compose([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 = c2.compose('white', blend='normal', space='srgb')
>>> cw3 = c3.compose('white', blend='normal', space='srgb')
>>> r1 = c2.compose(cw3, blend='multiply', space='srgb')
>>> r2 = c1.compose(cw2, blend='multiply', space='srgb')
>>> r3 = c1.compose(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))
>>> c1.compose([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'
.