color_tools.image.blend

Photoshop-compatible blend mode operations for image layers.

This module provides all 27 standard Photoshop blend modes for compositing two images together, using numpy for efficient per-pixel math and Pillow for image I/O.

Requires: pip install color-match-tools[image]

Blend Mode Categories:

Normal:      normal, dissolve
Darken:      darken, multiply, color_burn, linear_burn, darker_color
Lighten:     lighten, screen, color_dodge, linear_dodge, lighter_color
Contrast:    overlay, soft_light, hard_light, vivid_light, linear_light,
             pin_light, hard_mix
Comparative: difference, exclusion, subtract, divide
Component:   hue, saturation, color, luminosity

Primary API:

For most users, the only two names you need are:

  • blend_images(base_path, blend_path, mode, opacity, output_path) — loads two images, applies a blend mode, and returns a PIL Image.

  • BLEND_MODES — dict mapping every mode name to its numpy function, useful for listing or validating available modes.

The 27 individual mode functions (multiply, screen, etc.) are also exposed for advanced use. They operate directly on normalized float32 numpy arrays with shape (H, W, 3) in the [0.0, 1.0] range. If you call them directly you are responsible for array preparation, clipping, and reconverting back to uint8. Use blend_images() unless you are managing your own image pipeline.

Example:

>>> from color_tools.image import blend_images, BLEND_MODES
>>>
>>> # Blend two images using multiply mode at 80% opacity
>>> result = blend_images("base.png", "layer.png", mode="multiply", opacity=0.8)
>>> result.save("output.png")
>>>
>>> # List all available blend modes
>>> print(sorted(BLEND_MODES.keys()))
color_tools.image.blend.normal(a, b)[source]

Normal: blend layer fully replaces base.

Return type:

ndarray

Parameters:
color_tools.image.blend.dissolve(a, b)[source]

Dissolve: random 50/50 pixel selection from base or blend.

For opacity-controlled dissolve, use blend_images() which uses the opacity value as the random selection threshold.

Return type:

ndarray

Parameters:
color_tools.image.blend.darken(a, b)[source]

Darken: keep the darker value per channel.

Return type:

ndarray

Parameters:
color_tools.image.blend.multiply(a, b)[source]

Multiply: darkens by multiplying both layers (like overlapping transparencies).

Return type:

ndarray

Parameters:
color_tools.image.blend.color_burn(a, b)[source]

Color Burn: darkens base by increasing contrast toward blend color.

Return type:

ndarray

Parameters:
color_tools.image.blend.linear_burn(a, b)[source]

Linear Burn: darkens base by decreasing brightness.

Return type:

ndarray

Parameters:
color_tools.image.blend.darker_color(a, b)[source]

Darker Color: keep whichever full pixel has lower overall luminance.

Return type:

ndarray

Parameters:
color_tools.image.blend.lighten(a, b)[source]

Lighten: keep the lighter value per channel.

Return type:

ndarray

Parameters:
color_tools.image.blend.screen(a, b)[source]

Screen: lightens by inverting, multiplying, then inverting again.

Return type:

ndarray

Parameters:
color_tools.image.blend.color_dodge(a, b)[source]

Color Dodge: lightens base by decreasing contrast toward blend color.

Return type:

ndarray

Parameters:
color_tools.image.blend.linear_dodge(a, b)[source]

Linear Dodge (Add): lightens base by adding blend brightness.

Return type:

ndarray

Parameters:
color_tools.image.blend.lighter_color(a, b)[source]

Lighter Color: keep whichever full pixel has higher overall luminance.

Return type:

ndarray

Parameters:
color_tools.image.blend.overlay(a, b)[source]

Overlay: multiply where base is dark, screen where base is light.

Return type:

ndarray

Parameters:
color_tools.image.blend.soft_light(a, b)[source]

Soft Light: subtle lighten/darken using the W3C/Photoshop piecewise formula.

Matches Photoshop output (not the simpler Pegtop approximation):

if b <= 0.5: a - (1 - 2b) × a × (1 - a) else: a + (2b - 1) × (D(a) - a)

where D(a) = ((16a - 12)a + 4)a if a <= 0.25

= √a if a > 0.25

Return type:

ndarray

Parameters:
color_tools.image.blend.hard_light(a, b)[source]

Hard Light: overlay with base and blend roles swapped.

Return type:

ndarray

Parameters:
color_tools.image.blend.vivid_light(a, b)[source]

Vivid Light: color burn where blend is dark, color dodge where blend is light.

Return type:

ndarray

Parameters:
color_tools.image.blend.linear_light(a, b)[source]

Linear Light: linear burn or linear dodge depending on blend brightness.

Return type:

ndarray

Parameters:
color_tools.image.blend.pin_light(a, b)[source]

Pin Light: darken or lighten depending on blend layer value.

Return type:

ndarray

Parameters:
color_tools.image.blend.hard_mix(a, b)[source]

Hard Mix: extreme posterization — each channel becomes 0 or 1.

Return type:

ndarray

Parameters:
color_tools.image.blend.difference(a, b)[source]

Difference: absolute difference between layers.

Return type:

ndarray

Parameters:
color_tools.image.blend.exclusion(a, b)[source]

Exclusion: lower-contrast alternative to Difference.

Return type:

ndarray

Parameters:
color_tools.image.blend.subtract(a, b)[source]

Subtract: subtracts blend from base, clamped to black.

Return type:

ndarray

Parameters:
color_tools.image.blend.divide(a, b)[source]

Divide: divides base by blend, lightening the result.

Return type:

ndarray

Parameters:
color_tools.image.blend.hue(a, b)[source]

Hue: hue from blend layer, saturation and luminosity from base.

Return type:

ndarray

Parameters:
color_tools.image.blend.saturation(a, b)[source]

Saturation: saturation from blend layer, hue and luminosity from base.

Return type:

ndarray

Parameters:
color_tools.image.blend.color(a, b)[source]

Color: hue and saturation from blend layer, luminosity from base.

Return type:

ndarray

Parameters:
color_tools.image.blend.luminosity(a, b)[source]

Luminosity: luminosity from blend layer, hue and saturation from base.

Return type:

ndarray

Parameters:
color_tools.image.blend.blend_images(base_path, blend_path, mode='normal', opacity=1.0, output_path=None)[source]

Blend two images using a Photoshop-compatible blend mode.

Both images are converted to RGBA before blending. The blend mode is applied only to the RGB channels; alpha is composited separately using standard src-over with opacity. The blend layer is resized to match the base image if their sizes differ.

Parameters:
  • base_path (str | Path) – Path to the base (background) image.

  • blend_path (str | Path) – Path to the blend (top) layer image.

  • mode (str) – Blend mode name. See BLEND_MODES for all options.

  • opacity (float) – Blend layer opacity in [0.0, 1.0]. Default 1.0.

  • output_path (str | Path | None) – If provided, the result is saved to this path.

Return type:

Image

Returns:

Composited PIL Image in RGBA mode.

Raises:
  • ValueError – If mode is not in BLEND_MODES or opacity is out of range.

  • ImportError – If Pillow or numpy are not installed.

Example

>>> result = blend_images("base.png", "layer.png", mode="multiply", opacity=0.8)
>>> result.save("output.png")