Skip to main content

Blending and compositing

All three functions take source first, destination second — the same order as palette's source.multiply(destination). If the second color is in a different space it is converted into the first color's space first, and the result carries that space.

Because the modes are defined per component, they need an RGB-like stimulus space. Which spaces are accepted differs per function:

FunctionSpaces accepted as the first argument
plt_blend15 RGB spaces, xyz, xyz_d50, luma, luma_linear, luma_gamma, lms, lms_von_kries
plt_composethe same, plus lab, luv, yxy, oklab, cam16_ucs_jab
plt_blend_equationsthe same as plt_compose

Hue spaces (hsl, lch, oklch, CAM16 JCH/QCH/… ) are rejected — blending a hue angle component-wise has no meaning. Convert first:

SELECT plt_blend(plt_hsl(0, 1, 0.5), plt_hsl(120, 1, 0.5), 'multiply');
SELECT plt_to_hex(plt_blend(plt_convert(plt_hsl(0, 1, 0.5), 'srgb'),
plt_convert(plt_hsl(120, 1, 0.5), 'srgb'),
'multiply')) AS multiplied;

plt_blend(source, destination, mode)​

Signature: plt_blend(source plt_color, destination plt_color, mode VARCHAR) -> plt_color

The 11 separable modes, as in the CSS/PDF compositing model:

ModeResult in one line
multiplydarker: source × destination
screenlighter: inverse of multiplying the inverses
overlaymultiply or screen, depending on the destination
darkenthe darker of the two, per component
lightenthe lighter of the two, per component
dodgebrightens the destination towards white
burndarkens the destination towards black
hard_lightoverlay with source and destination swapped
soft_lighta softer hard light
differenceabsolute difference
exclusiondifference with lower contrast
SELECT plt_blend_modes() AS modes;
SELECT plt_to_hex(plt_blend(plt_srgb(0.5, 0.2, 0.1), plt_srgb(0.4, 0.6, 0.8), 'multiply')) AS multiply,
plt_to_hex(plt_blend(plt_srgb(0.5, 0.2, 0.1), plt_srgb(0.4, 0.6, 0.8), 'screen')) AS screen,
plt_to_hex(plt_blend(plt_srgb(0.5, 0.2, 0.1), plt_srgb(0.4, 0.6, 0.8), 'overlay')) AS overlay;

The alpha of the result follows from the blend: opaque source and destination give an opaque result, and the destination shows through a translucent source:

SELECT plt_blend(plt_with_alpha(plt_srgb(0.5, 0.2, 0.1), 0.5),
plt_with_alpha(plt_srgb(0.4, 0.6, 0.8), 0.5),
'hard-light') AS result;

Mode names are normalized: case-insensitive, and - or a space counts as _, so 'Hard Light' is the same as 'hard_light'. An unknown name fails the statement:

SELECT plt_blend(plt_srgb(1, 0, 0), plt_srgb(0, 0, 1), 'glow');

plt_compose(source, destination, operator)​

Signature: plt_compose(source plt_color, destination plt_color, operator VARCHAR) -> plt_color

The six Porter-Duff operators:

OperatorKeeps
oversource over destination (the usual alpha compositing)
insidethe part of the source inside the destination
outsidethe part of the source outside the destination
atopsource over destination, limited to the destination's area
xorthe non-overlapping parts of both
plusthe sum of both, alpha included
SELECT plt_compose_modes() AS operators;
SELECT plt_to_hex(plt_compose(plt_with_alpha(plt_srgb(1, 0, 0), 0.5),
plt_with_alpha(plt_srgb(0, 0, 1), 0.5), 'over')) AS over,
plt_to_hex(plt_compose(plt_with_alpha(plt_srgb(1, 0, 0), 0.5),
plt_with_alpha(plt_srgb(0, 0, 1), 0.5), 'xor')) AS xor_,
plt_compose(plt_with_alpha(plt_srgb(1, 0, 0), 0.5),
plt_with_alpha(plt_srgb(0, 0, 1), 0.5), 'plus') AS plus_alpha;

plt_compose also works in lab, luv, yxy, oklab and cam16_ucs_jab, which plt_blend does not:

SELECT plt_compose(plt_with_alpha(plt_oklab(0.5, 0.1, 0.1), 0.5),
plt_with_alpha(plt_oklab(0.7, -0.1, 0.1), 0.5), 'over') AS in_oklab;

plt_blend_equations(source, destination, source_factor, destination_factor)​

Signature: plt_blend_equations(source plt_color, destination plt_color, source_factor VARCHAR, destination_factor VARCHAR) -> plt_color

The OpenGL-style form: you choose how much of the source and of the destination goes into the result. Any pair of the ten factors is accepted:

SELECT plt_blend_parameters() AS factors;
SELECT plt_to_hex(plt_blend_equations(plt_with_alpha(plt_srgb(1, 0, 0), 0.5),
plt_with_alpha(plt_srgb(0, 0, 1), 0.5),
'source_alpha', 'one_minus_source_alpha')) AS blended;

'source_alpha' / 'one_minus_source_alpha' reproduces ordinary over compositing, which is a good way to check that a pair of factors does what you expect:

SELECT plt_to_hex(plt_compose(src, dst, 'over')) AS compose_over,
plt_to_hex(plt_blend_equations(src, dst, 'source_alpha', 'one_minus_source_alpha')) AS equations
FROM (SELECT plt_with_alpha(plt_srgb(1, 0, 0), 0.5) AS src,
plt_with_alpha(plt_srgb(0, 0, 1), 0.5) AS dst);

An unknown factor or operator is an error:

SELECT plt_blend_equations(plt_srgb(1, 0, 0), plt_srgb(0, 0, 1), 'half', 'one');
SELECT plt_compose(plt_srgb(1, 0, 0), plt_srgb(0, 0, 1), 'around');

Listing the supported names​

plt_blend_modes()​

Signature: plt_blend_modes() -> VARCHAR[]

The 11 mode names accepted by plt_blend, in a stable order. Use it to drive a list_transform over a table of colors instead of hard-coding the list.

plt_compose_modes()​

Signature: plt_compose_modes() -> VARCHAR[]

The six operator names accepted by plt_compose.

plt_blend_parameters()​

Signature: plt_blend_parameters() -> VARCHAR[]

The ten factor names accepted by plt_blend_equations: zero, one, source_color, one_minus_source_color, destination_color, one_minus_destination_color, source_alpha, one_minus_source_alpha, destination_alpha, one_minus_destination_alpha.

SELECT unnest(plt_blend_modes()) AS mode, 'blend' AS used_by
FROM (SELECT 1)
UNION ALL
SELECT unnest(plt_compose_modes()), 'compose'
UNION ALL
SELECT unnest(plt_blend_parameters()), 'equations';