# psd_tools.composite

Composite module for layer rendering and blending.

This subpackage provides the rendering engine for compositing PSD layers into raster images. It implements Photoshop’s blend modes, layer effects, and vector shape rasterization.

Note: This module requires optional dependencies. Install with:

    pip install 'psd-tools[composite]'

Or using uv:

    uv sync --extra composite

The composite extra includes:

- `aggdraw`: For vector path and bezier curve rasterization

- `scipy`: For advanced image processing operations

- `scikit-image`: For morphological operations in effects

Key modules:

- `psd_tools.composite.composite`: Main compositing functions

- `psd_tools.composite.blend`: Blend mode implementations

- `psd_tools.composite.effects`: Layer effects (stroke, shadow, etc.)

- `psd_tools.composite.vector`: Vector shape and path rendering

- `psd_tools.composite.paint`: Fill rendering (gradients, patterns)

Example usage:

    from psd_tools import PSDImage

    psd = PSDImage.open('document.psd')

    # Composite entire document to PIL Image
    image = psd.composite()
    image.save('output.png')

    # Composite specific layer
    layer_image = psd[0].composite()

The compositing engine uses NumPy arrays for efficient pixel manipulation and supports all of Photoshop’s standard blend modes including multiply, screen, overlay, soft light, and more.

Performance considerations:

- Compositing can be memory-intensive for large documents

- Vector shapes require aggdraw for accurate rendering

- Some effects have limited support compared to Photoshop

This module provides layer rendering and compositing functionality.

Installation: Requires optional dependencies:

    pip install psd-tools[composite]

Or with uv:

    uv sync --extra composite

## Composite Functions

psd_tools.composite.composite(group: Layer | PSDImage, color: float | tuple[float, ...] | ndarray = 1.0, alpha: float | ndarray = 0.0, viewport: tuple[int, int, int, int] | None = None, layer_filter: Callable[[Layer], bool] | None = None, force: bool = False, as_layer: bool = False) → tuple[ndarray, ndarray, ndarray]

  Composite layers and return NumPy arrays.

  This function composites the given layer or document into NumPy arrays representing color, shape, and alpha channels. It applies layer blending, effects, masks, and clipping according to the PSD specification.

  Parameters:

    - group – Layer or PSDImage to composite

    - color – Initial backdrop color (0.0-1.0, default: 1.0). Can be a scalar float applied to all channels, a tuple of per-channel values, or an ndarray.

    - alpha – Initial backdrop alpha (0.0-1.0, default: 0.0). Can be scalar or ndarray.

    - viewport – Bounding box (left, top, right, bottom) to composite. If None, uses layer bounds

    - layer_filter – Optional callable(layer) -> bool to filter which layers to composite

    - force – If True, force re-rendering of all layers including vector shapes and fills

    - as_layer – If True, treat the group as a layer (apply blend mode to backdrop)

  Returns:

    - color: RGB/CMYK/Grayscale values in range [0.0, 1.0]

    - shape: Layer shape/coverage mask in range [0.0, 1.0]

    - alpha: Composite alpha channel in range [0.0, 1.0]

  Return type:

    Tuple of (color, shape, alpha) as float32 ndarrays with shape (height, width, channels)

  Examples

      >>> from psd_tools import PSDImage
      >>> psd = PSDImage.open('example.psd')
      >>> color, shape, alpha = composite(psd)
      >>> # Apply custom backdrop
      >>> color, shape, alpha = composite(psd, color=(1.0, 0.0, 0.0), alpha=1.0)
      >>> # Force re-render vector layers
      >>> color, shape, alpha = composite(psd, force=True)
      >>> # Composite only visible layers
      >>> color, shape, alpha = composite(psd, layer_filter=lambda l: l.visible)

  Note

  - Requires optional composite dependencies (aggdraw, scipy, scikit-image) for vector shape rendering, gradient fills, and layer effects.

  - Adjustment layers have limited support.

  - Text rendering is not supported (text layers show as raster if available).

psd_tools.composite.composite_pil(layer: Layer | PSDImage, color: float | tuple[float, ...] | ndarray, alpha: float | ndarray, viewport: tuple[int, int, int, int] | None, layer_filter: Callable[[Layer], bool] | None, force: bool, as_layer: bool = False, apply_icc: bool = True) → Image | None

  Composite layers and return a PIL Image.

  This function composites the given layer or document into a PIL Image, applying blend modes, effects, and color management.

  Parameters:

    - layer – Layer or PSDImage to composite

    - color – Initial backdrop color (0.0-1.0). Can be scalar, tuple, or ndarray

    - alpha – Initial backdrop alpha (0.0-1.0). Can be scalar or ndarray

    - viewport – Bounding box (left, top, right, bottom) to composite. If None, uses layer bounds

    - layer_filter – Optional callable to filter which layers to composite. Should return True to include

    - force – If True, force re-rendering of all layers (ignore cached pixels)

    - as_layer – If True, apply layer blend modes (default: False for document-level compositing)

    - apply_icc – If True, apply ICC profile color correction (default: True)

  Returns:

    PIL Image with composited result, or None if viewport is empty

  Note

  - Requires optional composite dependencies (aggdraw, scipy, scikit-image)

  - LAB and Duotone color modes have limited blending support

  - Alpha channel handling varies by color mode

## Blend Modes

Blend mode implementations.

This module implements Photoshop’s blend modes for compositing layers. Blend modes determine how a layer’s colors interact with the layers beneath it. Each function implements the mathematical formula for a specific blend mode.

The blend functions operate on NumPy arrays with normalized float32 values (0.0-1.0) representing pixel color channels. They follow Adobe’s PDF Blend Mode specification.

Blend mode categories:

Normal modes: - `normal`: Source replaces backdrop (no blending)

Darken modes: - `darken`: Selects darker of source and backdrop - `multiply`: Multiplies colors (darkens) - `color_burn`: Darkens backdrop to reflect source - `linear_burn`: Similar to multiply but more extreme - `darker_color`: Selects darker color (non-separable)

Lighten modes: - `lighten`: Selects lighter of source and backdrop - `screen`: Inverted multiply (lightens) - `color_dodge`: Brightens backdrop to reflect source - `linear_dodge`: Same as addition (Add blend mode) - `lighter_color`: Selects lighter color (non-separable)

Contrast modes: - `overlay`: Combination of multiply and screen - `soft_light`: Soft version of overlay - `hard_light`: Hard version of overlay - `vivid_light`: Combination of color dodge and burn - `linear_light`: Combination of linear dodge and burn - `pin_light`: Replaces colors based on brightness - `hard_mix`: Posterizes to primary colors

Inversion modes: - `difference`: Absolute difference between colors - `exclusion`: Similar to difference but lower contrast

Component modes (non-separable): - `hue`: Preserves luminosity and saturation, replaces hue - `saturation`: Preserves luminosity and hue, replaces saturation - `color`: Preserves luminosity, replaces hue and saturation - `luminosity`: Preserves hue and saturation, replaces luminosity

Implementation details:

- Separable blend modes process each color channel independently

- Non-separable modes convert to HSL color space first

- All functions expect normalized float32 arrays (0.0-1.0 range)

- Division by zero is protected with small epsilon values

Example usage:

    import numpy as np
    from psd_tools.composite.blend import multiply, screen

    # Create backdrop and source colors (normalized)
    backdrop = np.array([0.5, 0.3, 0.8], dtype=np.float32)
    source = np.array([0.7, 0.6, 0.2], dtype=np.float32)

    # Apply blend mode
    result = multiply(backdrop, source)
    # Result: [0.35, 0.18, 0.16]

The `BLEND_FUNC` dictionary maps `BlendMode` enums to their corresponding functions for easy lookup during compositing.

psd_tools.composite.blend.divide(Cb: ndarray, Cs: ndarray) → ndarray

  Looks at the color information in each channel and divides the blend color from the base color.

psd_tools.composite.blend.hard_mix(Cb: ndarray, Cs: ndarray) → ndarray

  Adds the red, green and blue channel values of the blend color to the RGB values of the base color. If the resulting sum for a channel is 255 or greater, it receives a value of 255; if less than 255, a value of 0. Therefore, all blended pixels have red, green, and blue channel values of either 0 or 255. This changes all pixels to primary additive colors (red, green, or blue), white, or black.

psd_tools.composite.blend.hsl2hsv(img: ndarray) → ndarray

  Convert an HSL image to HSV space using vectorized arrays.

psd_tools.composite.blend.hsl2rgb(img: ndarray) → ndarray

  Convert an HSL image to RGB space using vectorized arrays.

psd_tools.composite.blend.hsv2hsl(img: ndarray) → ndarray

  Convert an HSV image to HSL space using vectorized arrays.

psd_tools.composite.blend.linear_light(Cb: ndarray, Cs: ndarray) → ndarray

  Burns or dodges the colors by decreasing or increasing the brightness, depending on the blend color. If the blend color (light source) is lighter than 50% gray, the image is lightened by increasing the brightness. If the blend color is darker than 50% gray, the image is darkened by decreasing the brightness.

psd_tools.composite.blend.non_separable(k: str = 's')

  Wrap non-separable blending function for CMYK handling.

psd_tools.composite.blend.pin_light(Cb: ndarray, Cs: ndarray) → ndarray

  Replaces the colors, depending on the blend color. If the blend color (light source) is lighter than 50% gray, pixels darker than the blend color are replaced, and pixels lighter than the blend color do not change. If the blend color is darker than 50% gray, pixels lighter than the blend color are replaced, and pixels darker than the blend color do not change. This is useful for adding special effects to an image.

psd_tools.composite.blend.rgb2hsl(img: ndarray) → ndarray

  Convert an RGB image to HSL space using vectorized arrays.

psd_tools.composite.blend.vivid_light(Cb: ndarray, Cs: ndarray) → ndarray

  Burns or dodges the colors by increasing or decreasing the contrast, depending on the blend color. If the blend color (light source) is lighter than 50% gray, the image is lightened by decreasing the contrast. If the blend color is darker than 50% gray, the image is darkened by increasing the contrast.

The blend module implements Photoshop’s blend modes following the Adobe PDF specification. All blend functions operate on normalized float32 NumPy arrays.

## Vector Rendering

Vector shapes and path operations for compositing.

psd_tools.composite.vector.draw_stroke(layer: Layer) → ndarray

  Draw a stroke.

  Requires aggdraw for bezier curve rasterization.

psd_tools.composite.vector.draw_vector_mask(layer: Layer) → ndarray

  Draw a vector mask.

  Requires aggdraw for bezier curve rasterization.

Vector shape and path rendering using aggdraw for bezier curve rasterization.

## Effects Rendering

Layer effects rendering.

This module implements rendering for Photoshop layer effects (also known as layer styles). Effects are non-destructive visual enhancements applied to layers such as strokes, shadows, glows, and overlays.

Note: Effects rendering requires scikit-image. Install with:

    pip install 'psd-tools[composite]'

Currently supported effects:

- Stroke: Outline around layer shape or pixels - Supports solid color, gradient, and pattern fills - Position: inside, outside, or centered - Limited compared to Photoshop’s full implementation

Partially supported or limited effects:

- Drop shadow, inner shadow, outer glow, inner glow

- These may render but with reduced accuracy

The main function `draw_stroke_effect()` handles stroke rendering by:

1. Extracting the layer’s alpha channel or shape mask

2. Applying morphological operations (dilation/erosion) based on stroke size and position

3. Filling the stroke region with the specified paint (solid color, gradient, pattern)

4. Returning the rendered stroke as a NumPy array

Implementation notes:

- Effects are image-based rather than vector-based, which may differ from Photoshop

- For layers with vector paths, ideally strokes should be drawn geometrically

- Some effect parameters may not be fully supported

- Complex effect combinations may not render identically to Photoshop

Example usage (internal):

    from psd_tools.composite.effects import draw_stroke_effect

    # Called during layer compositing
    viewport = (0, 0, 100, 100)  # Region to render
    shape = layer_alpha_channel    # NumPy array
    desc = stroke_descriptor       # Effect parameters

    color, alpha = draw_stroke_effect(viewport, shape, desc, psd)

The effects system integrates with the main compositing pipeline and is automatically applied when rendering layers that have effects enabled.

Layer effects rendering including strokes, shadows, and glows. Requires scikit-image for morphological operations.