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.
__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
__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
__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.
__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
__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 channeldo_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