ImageLayer#

About#

As with LayeredFile, we only provide documentation for the 8bit type here but 16- and 32-bit are both equally supported, all the classes can be seen below.

ImageLayer_8bit

ImageLayer_16bit

ImageLayer_32bit

One major change in the python bindings compared to the C++ code is that we allow for dict-like indexing of channels through the __getitem__ method. This is very convenient as it significantly reduces the amount of code that has to be written.

We currently support two methods of accessing channel data this way, by logical index or by channel id. Both of these methods are documented below

Examples#

Important

When accessing image data this way the image data is always copied out of the underlying structure meaning we can keep accessing the same channel.

While this does mirror more pythonic behaviour, when wanting to explicitly remove a channels information from the layer (to save on memory) we have to revert to the individual psapi.LayeredFile_8bit.get_channel_by_id() and psapi.LayeredFile_8bit.get_channel_by_index() methods with the do_copy parameter set to false.

import psapi
import numpy as np

layered_file = psapi.LayeredFile.read("File.psb")
layer_red = layered_file["Layer_Red"]
channel_r = layer_red[0] # 0-based indexing, raises KeyError if index is out of range
# We can also extract alpha and masks this way with index -1 and -2 respectively!
import psapi
import numpy as np

layered_file = psapi.LayeredFile.read("File.psb")
layer_red = layered_file["Layer_Red"]
channel_r = layer_red[psapi.enum.ChannelID.red] # raises KeyError if index doesnt exist

Construction of an ImageLayer class is also intended to be as easy as possible which is why we support 3 different ways of passing data into the ImageLayer,

  • construction by dict with integer indexing

  • construction by dict with ChannelID enum indexing

  • construction by np.ndarray

Check out the examples below to find out how to leverage each of these to your advantage.

This method is likely the simplest (but least explicit) way of constructing an image layer. It does make some assumptions to achieve this simplicity. One of these assumptions is that a layer (for RGB) must have at least 3, but not more than 4 channels. This is due to us taking the required channels (for RGB this would be R, G, B. For CMYK this would be C, M, Y, K etc.) and allowing for one last optional alpha channel.

So if we continue our RGB example we could have a np.ndarray with shape (3, 32, 32) which would tell the PhotoshopAPI that we are dealing with no alpha channel (white alpha). If we however specify the following shape (4, 32, 32), the 4th channel is assumed to be the alpha channel and is interpreted as such.

We support both 2D arrays (flattened image data) or 3D arrays (explicit rows and columns) which work interchangeably as long as the data is in row-major order.

import psapi
import numpy as np

document_color_mode = psapi.enum.ColorMode.rgb
width = 32
height = 32
file = psapi.LayeredFile_8bit(document_color_mode, width, height)

# This method has the image data flattened (each channel is a 1D array)
img_data_np = np.zeros((4, height * width), np.uint8)
img_data_np[0] = 255    # Set the red component
img_data_np[3] = 128    # Set the alpha component

# Construct our layer instance, width and height must be specified for this to work!
img_lr_np = psapi.ImageLayer_8bit(
        img_data_np,
        layer_name="img_lr_np",
        width=width,
        height=height,
        color_mode=document_color_mode
)

# Add to the file and write out
file.add_layer(img_lr_np)
file.write("Out.psd")
import psapi
import numpy as np

document_color_mode = psapi.enum.ColorMode.rgb
width = 32
height = 32
file = psapi.LayeredFile_8bit(document_color_mode, width, height)

# This method has the channel data as 2D array in row-major fashion
img_data_np = np.zeros((4, height, width), np.uint8)
img_data_np[0] = 255    # Set the red component
img_data_np[3] = 128    # Set the alpha component

# Construct our layer instance, width and height must be specified for this to work!
img_lr_np = psapi.ImageLayer_8bit(
        img_data_np,
        layer_name="img_lr_np",
        width=width,
        height=height,
        color_mode=document_color_mode
)

# Add to the file and write out
file.add_layer(img_lr_np)
file.write("Out.psd")

Similarly to the numpy example these can also be constructed and passed either as flattened array or 2D array by simply interchanging this np.full((height, width), 0, np.uint8) to this np.full((height * width), 0, np.uint8).

import psapi
import numpy as np

document_color_mode = psapi.enum.ColorMode.rgb
width = 32
height = 32
file = psapi.LayeredFile_8bit(document_color_mode, width, height)

# We can use logical indices for these as should be familiar from other software
image_dict = {}
image_dict[0] = np.full((height, width), 0, np.uint8)           # Red channel
image_dict[1] = np.full((height, width), 255, np.uint8)         # Green channel
image_dict[2] = np.full((height, width), 0, np.uint8)           # Blue channel
image_dict[-1] = np.full((height, width), 128, np.uint8)        # Alpha channel

# Construct our layer instance, width and height must be specified for this to work!
img_lr = psapi.ImageLayer_8bit(
        image_dict,
        layer_name="img_lr_dict",
        width=width,
        height=height,
        color_mode=document_color_mode)

# Add to the file and write out
file.add_layer(img_lr)
file.write("Out.psd")

Similarly to the numpy example these can also be constructed and passed either as flattened array or 2D array by simply interchanging this np.full((height, width), 0, np.uint8) to this np.full((height * width), 0, np.uint8).

Attention

This method of construction is unfortunately currently unsupported and will result in photoshop files that cannot be opened due to limitations in pybind11 as seen in this issue. Support for this is planned however once that is resolved so keep tuned. This message will disappear once it is fixed :)

import psapi
import numpy as np

document_color_mode = psapi.enum.ColorMode.rgb
width = 32
height = 32
file = psapi.LayeredFile_8bit(document_color_mode, width, height)

# This method is a bit more explicit about the naming of channels
image_dict = {}
image_dict[psapi.enum.ChannelID.red] = np.full((height, width), 0, np.uint8)
image_dict[psapi.enum.ChannelID.green] = np.full((height, width), 255, np.uint8)
image_dict[psapi.enum.ChannelID.blue] = np.full((height, width), 0, np.uint8)
image_dict[psapi.enum.ChannelID.alpha] = np.full((height, width), 128, np.uint8)

# Construct our layer instance, width and height must be specified for this to work!
img_lr = psapi.ImageLayer_8bit(
        image_dict,
        layer_name="img_lr_dict",
        width=width,
        height=height,
        color_mode=document_color_mode
)

# Add to the file and write out
file.add_layer(img_lr)
file.write("Out.psd")

Class Reference ImageLayer#


class psapi.ImageLayer_8bit#

This class defines a single image layer in a LayeredFile. There must be at least one of these in any given file for it to be valid

Attributes:
image_datadict[numpy.ndarray]

A dictionary of the image data mapped by psapi.util.ChannelIDInfo

namestr

The name of the layer, cannot be longer than 255

layer_maskLayerMask_*bit

The pixel mask applied to the layer

blend_modeenum.BlendMode

The blend mode of the layer, ‘Passthrough’ is reserved for group layers

opacityint

The layers opacity from 0-255 with 255 being 100%

widthint

The width of the layer ranging up to 30,000 for PSD and 300,000 for PSB, this does not have to match the files width

heightint

The height of the layer ranging up to 30,000 for PSD and 300,000 for PSB, this does not have to match the files height

center_xfloat

The center of the layer in regards to the canvas, a layer at center_x = 0 is perfectly centered around the document

center_yfloat

The center of the layer in regards to the canvas, a layer at center_y = 0 is perfectly centered around the document

__init__(*args, **kwargs)#

Overloaded function.

  1. __init__(self: psapi.ImageLayer_8bit, image_data: numpy.ndarray[numpy.uint8], layer_name: str, layer_mask: Optional[numpy.ndarray[numpy.uint8]] = None, width: int = 0, height: int = 0, blend_mode: psapi.enum.BlendMode = <BlendMode.normal: 1>, pos_x: int = 0, pos_y: int = 0, opacity: int = 255, compression: psapi.enum.Compression = <Compression.zipprediction: 3>, color_mode: psapi.enum.ColorMode = <ColorMode.rgb: 3>) -> None

    Construct an image layer from image data passed as numpy.ndarray

    param image_data:

    The image data as 2- or 3-Dimensional numpy array where the first dimension is the number of channels.

    If its a 2D ndarray the second dimension must hold the image data in row-major order with the size being height*width. An example could be the following shape: (3, 1024) for an RGB layer that is 32*32px.

    If its a 3D ndarray the second and third dimension hold height and width respectively. An example could be the following shape: (3, 32, 32) for the same RGB layer

    We also support adding alpha channels this way, those are always stored as the last channel and are optional. E.g. for RGB there could be a ndarray like this (4, 32, 32) and it would automatically identify the last channel as alpha. For the individual color modes there is always a set of required channels such as R, G and B for RGB or C, M, Y, K for CMYK with the optional alpha that can be appended to the end.

    The size must be the same as the width and height parameter

    type image_data:

    numpy.ndarray

    param layer_name:

    The name of the group, its length must not exceed 255

    type layer_name:

    str

    param layer_mask:

    Optional layer mask, must have the same dimensions as height * width but can be a 1- or 2-dimensional array with row-major ordering (for a numpy 2D array this would mean with a shape of (height, width)

    type layer_mask:

    numpy.ndarray

    param width:

    Optional, width of the layer, does not have to be the same size as the document, limited to 30,000 for PSD files and 300,000 for PSB files. For group layers this is only relevant for the layer mask and can be left out otherwise

    type width:

    int

    param height:

    Optional, height of the layer, does not have to be the same size as the document, limited to 30,000 for PSD files and 300,000 for PSB files. For group layers this is only relevant for the layer mask and can be left out otherwise

    type height:

    int

    param blend_mode:

    Optional, the blend mode of the layer, ‘Passthrough’ is the default for groups.

    type blend_mode:

    psapi.enum.BlendMode

    param pos_x:

    Optional, the relative offset of the layer to the center of the document, 0 indicates the layer is centered. For group layers this is only relevant for the layer mask and can be left out otherwise

    type pos_x:

    int

    param pos_y:

    Optional, the relative offset of the layer to the center of the document, 0 indicates the layer is centered. For group layers this is only relevant for the layer mask and can be left out otherwise

    type pos_y:

    int

    param opacity:

    The opacity of the layer from 0-255 where 0 is 0% and 255 is 100%. Defaults to 255

    type opacity:

    int

    param compression:

    The compression to apply to all the channels of the layer, including mask channels

    type compression:

    psapi.enum.Compression

    param color_mode:

    The color mode of the Layer, this must be identical to the color mode of the document. Defaults to RGB

    type color_mode:

    psapi.enum.ColorMode

    raises:

    ValueError: if length of layer name is greater than 255

    ValueError: if size of layer mask is not width*height

    ValueError: if width of layer is negative

    ValueError: if height of layer is negative

    ValueError: if opacity is not between 0-255

    ValueError: if the channel size is not the same as width * height

  2. __init__(self: psapi.ImageLayer_8bit, image_data: Dict[int, numpy.ndarray[numpy.uint8]], layer_name: str, layer_mask: Optional[numpy.ndarray[numpy.uint8]] = None, width: int = 0, height: int = 0, blend_mode: psapi.enum.BlendMode = <BlendMode.normal: 1>, pos_x: int = 0, pos_y: int = 0, opacity: int = 255, compression: psapi.enum.Compression = <Compression.zipprediction: 3>, color_mode: psapi.enum.ColorMode = <ColorMode.rgb: 3>) -> None

    Construct an image layer from image data passed as dict with integers as key

    param image_data:

    The image data as a dictionary with channel indices as integers. E.g. for a RGB image layer

    data = {
        0 : numpy.ndarray,
        1 : numpy.ndarray,
        2 : numpy.ndarray
    }
    
    type image_data:

    dict[numpy.ndarray]

    param layer_name:

    The name of the group, its length must not exceed 255

    type layer_name:

    str

    param layer_mask:

    Optional layer mask, must have the same dimensions as height * width but can be a 1- or 2-dimensional array with row-major ordering (for a numpy 2D array this would mean with a shape of (height, width)

    type layer_mask:

    numpy.ndarray

    param width:

    Optional, width of the layer, does not have to be the same size as the document, limited to 30,000 for PSD files and 300,000 for PSB files. For group layers this is only relevant for the layer mask and can be left out otherwise

    type width:

    int

    param height:

    Optional, height of the layer, does not have to be the same size as the document, limited to 30,000 for PSD files and 300,000 for PSB files. For group layers this is only relevant for the layer mask and can be left out otherwise

    type height:

    int

    param blend_mode:

    Optional, the blend mode of the layer, ‘Passthrough’ is the default for groups.

    type blend_mode:

    psapi.enum.BlendMode

    param pos_x:

    Optional, the relative offset of the layer to the center of the document, 0 indicates the layer is centered. For group layers this is only relevant for the layer mask and can be left out otherwise

    type pos_x:

    int

    param pos_y:

    Optional, the relative offset of the layer to the center of the document, 0 indicates the layer is centered. For group layers this is only relevant for the layer mask and can be left out otherwise

    type pos_y:

    int

    param opacity:

    The opacity of the layer from 0-255 where 0 is 0% and 255 is 100%. Defaults to 255

    type opacity:

    int

    param compression:

    The compression to apply to all the channels of the layer, including mask channels

    type compression:

    psapi.enum.Compression

    param color_mode:

    The color mode of the Layer, this must be identical to the color mode of the document. Defaults to RGB

    type color_mode:

    psapi.enum.ColorMode

    raises:

    ValueError: if length of layer name is greater than 255

    ValueError: if size of layer mask is not width*height

    ValueError: if width of layer is negative

    ValueError: if height of layer is negative

    ValueError: if opacity is not between 0-255

    ValueError: if the channel size is not the same as width * height

  3. __init__(self: psapi.ImageLayer_8bit, image_data: Dict[psapi.enum.ChannelID, numpy.ndarray[numpy.uint8]], layer_name: str, layer_mask: Optional[numpy.ndarray[numpy.uint8]] = None, width: int = 0, height: int = 0, blend_mode: psapi.enum.BlendMode = <BlendMode.normal: 1>, pos_x: int = 0, pos_y: int = 0, opacity: int = 255, compression: psapi.enum.Compression = <Compression.zipprediction: 3>, color_mode: psapi.enum.ColorMode = <ColorMode.rgb: 3>) -> None

    Construct an image layer from image data passed as dict with psapi.enum.ChannelID as key

    param image_data:

    The image data as a dictionary with channel IDs as enums. E.g. for a RGB image layer

    data = {
        psapi.enum.ChannelID.red : numpy.ndarray,
        psapi.enum.ChannelID.green : numpy.ndarray,
        psapi.enum.ChannelID.blue : numpy.ndarray
    }
    
    type image_data:

    dict[numpy.ndarray]

    param layer_name:

    The name of the group, its length must not exceed 255

    type layer_name:

    str

    param layer_mask:

    Optional layer mask, must have the same dimensions as height * width but can be a 1- or 2-dimensional array with row-major ordering (for a numpy 2D array this would mean with a shape of (height, width)

    type layer_mask:

    numpy.ndarray

    param width:

    Optional, width of the layer, does not have to be the same size as the document, limited to 30,000 for PSD files and 300,000 for PSB files. For group layers this is only relevant for the layer mask and can be left out otherwise

    type width:

    int

    param height:

    Optional, height of the layer, does not have to be the same size as the document, limited to 30,000 for PSD files and 300,000 for PSB files. For group layers this is only relevant for the layer mask and can be left out otherwise

    type height:

    int

    param blend_mode:

    Optional, the blend mode of the layer, ‘Passthrough’ is the default for groups.

    type blend_mode:

    psapi.enum.BlendMode

    param pos_x:

    Optional, the relative offset of the layer to the center of the document, 0 indicates the layer is centered. For group layers this is only relevant for the layer mask and can be left out otherwise

    type pos_x:

    int

    param pos_y:

    Optional, the relative offset of the layer to the center of the document, 0 indicates the layer is centered. For group layers this is only relevant for the layer mask and can be left out otherwise

    type pos_y:

    int

    param opacity:

    The opacity of the layer from 0-255 where 0 is 0% and 255 is 100%. Defaults to 255

    type opacity:

    int

    param compression:

    The compression to apply to all the channels of the layer, including mask channels

    type compression:

    psapi.enum.Compression

    param color_mode:

    The color mode of the Layer, this must be identical to the color mode of the document. Defaults to RGB

    type color_mode:

    psapi.enum.ColorMode

    raises:

    ValueError: if length of layer name is greater than 255

    ValueError: if size of layer mask is not width*height

    ValueError: if width of layer is negative

    ValueError: if height of layer is negative

    ValueError: if opacity is not between 0-255

    ValueError: if the channel size is not the same as width * height

__getitem__(*args, **kwargs)#

Overloaded function.

  1. __getitem__(self: psapi.ImageLayer_8bit, key: psapi.enum.ChannelID) -> numpy.ndarray[numpy.uint8]

    Extract a specified channel from the layer given its channel index.

    param key:

    The ID or index of the channel

    type key:

    psapi.enum.ChannelID | int

    return:

    The extracted channel with dimensions (height, width)

    rtype:

    np.ndarray

  2. __getitem__(self: psapi.ImageLayer_8bit, key: int) -> numpy.ndarray[numpy.uint8]

get_channel_by_id(self: psapi.ImageLayer_8bit, id: psapi.enum.ChannelID, do_copy: bool = True) numpy.ndarray[numpy.uint8]#

Extract a specified channel from the layer given its channel ID.

Parameters:
  • id (psapi.enum.ChannelID) – The ID of the channel

  • do_copy (bool) – Defaults to true, whether to copy the data on extraction (if false the channel is invalidated)

Returns:

The extracted channel

Return type:

numpy.ndarray

get_channel_by_index(self: psapi.ImageLayer_8bit, index: int, do_copy: bool = True) numpy.ndarray[numpy.uint8]#

Extract a specified channel from the layer given its channel index.

Parameters:
  • index (int) – The index of the channel

  • do_copy (bool) – Defaults to true, whether to copy the data on extraction (if false the channel is invalidated)

Returns:

The extracted channel with dimensions (height, width)

Return type:

numpy.ndarray

get_image_data(self: psapi.ImageLayer_8bit, do_copy: bool = True) Dict[int, numpy.ndarray[numpy.uint8]]#

Extract all the channels of the ImageLayer into an unordered_map.

Parameters:

do_copy (bool) – Defaults to true, Whether to copy the data

Returns:

The extracted image data

Return type:

dict[psapi.util.ChannelIDInfo, numpy.ndarray]

get_mask_data(self: psapi.Layer_8bit, do_copy: bool = True) numpy.ndarray[numpy.uint8]#

Get the pixel mask data associated with the layer (if it exists), if it doesnt a warning gets raised and a null-size numpy.ndarray is returned.

The size of the mask is not necessarily the same as the layer

Parameters:

do_copy (bool) – Whether or not to copy the image data on extraction, if False the mask channel is freed

Returns:

The extracted channel with dimensions (mask_height, mask_width)

Return type:

numpy.ndarray

property image_data#
set_compression(self: psapi.ImageLayer_8bit, compression: psapi.enum.Compression) None#

Change the compression codec of all the image channels.

Parameters:

compression (psapi.enum.Compression) – The compression codec