from psd_tools import PSDImage
psd = PSDImage.open('example.psd')
psd.composite().save('example.png')
for layer in psd:
print(layer)
image = layer.composite()
Check out the Usage <> documentation for more examples.
The package provides command line tools to handle a PSD
document:
psd-tools export <input_file> <output_file> [options]
psd-tools show <input_file> [options]
psd-tools debug <input_file> [options]
psd-tools -h | --help
psd-tools --version
Example:
psd-tools show example.psd # Show the file content
psd-tools export example.psd example.png # Export as PNG
psd-tools export example.psd[0] example-0.png # Export layer as PNG
psd_tools.api <#module-psd_tools.api> package
provides the user-friendly API to work with PSD files. PSDImage
<#psd_tools.PSDImage> represents a PSD file.
Open an image:
from psd_tools import PSDImage
psdimage = PSDImage.open('my_image.psd')
Most of the data structure in the psd-tools suppports
pretty printing in IPython environment.
In [1]: PSDImage.open('example.psd')
Out[1]:
PSDImage(mode=RGB size=101x55 depth=8 channels=3)
[0] PixelLayer('Background' size=101x55)
[1] PixelLayer('Layer 1' size=85x46)
Internal layers are accessible by iterator or indexing:
for layer in psdimage:
print(layer)
if layer.is_group():
for child in layer:
print(child)
child = psdimage[0][0]
Note:
The iteration order is from background to foreground,
which is reversed from version prior to 1.7.x. Use reversed(psd) to
iterate from foreground to background.
The opened PSD file can be saved:
psdimage.save('output.psd')
If the PSD File's layer structure was updated, saving it will
update the ImageData section. However, the rendered image is likely
different from the Photoshop's rendering due to the limited rendering
support in psd_tools.
There are various layer kinds in Photoshop.
The most basic layer type is PixelLayer
<#psd_tools.api.layers.PixelLayer>:
print(layer.name)
layer.kind == 'pixel'
Some of the layer attributes are editable, such as a layer
name:
layer.name = 'Updated layer 1'
psd_tools experimentally supports the creation of a new PixelLayer
from a PIL object, the PIL image will be converted to the color mode of the
PSD File given in parameter:
psdimage.create_pixel_layer(pil_image, name="Layer name")
To construct a layered PSD file from scratch:
psdimage = PSDImage.new(mode='RGB', size=(640, 480), depth=8)
layer = psdimage.create_pixel_layer(pil_image, name="Layer 1", top=0, left=0, opacity=255)
group = psdimage.create_group(name="Group 1")
group.append(layer)
psdimage.save('new_image.psd')
See the function documentation for further parameter
explanations.
Group <#psd_tools.api.layers.Group> has internal
layers:
for layer in group:
print(layer)
first_layer = group[0]
Create a new group object.:
group = psdimage.create_group(name="Group name")
group.append(layer)
TypeLayer <#psd_tools.api.layers.TypeLayer> is a
layer with texts:
ShapeLayer <#psd_tools.api.layers.ShapeLayer> draws a
vector shape, and the shape information is stored in vector_mask and
origination property. Other layers can also have shape information as
a mask:
print(layer.vector_mask)
for shape in layer.origination:
print(shape)
SmartObjectLayer
<#psd_tools.api.layers.SmartObjectLayer> embeds or links an external
file for non-destructive editing. The file content is accessible via
smart_object property:
import io
if layer.smart_object.filetype in ('jpg', 'png'):
image = Image.open(io.BytesIO(layer.smart_object.data))
SolidColorFill
<#psd_tools.api.adjustments.SolidColorFill>, PatternFill
<#psd_tools.api.adjustments.PatternFill>, and GradientFill
<#psd_tools.api.adjustments.GradientFill> are fill layers that paint
the entire region if there is no associated mask. Sub-classes of
AdjustmentLayer represents layer adjustment applied to the composed
image. See Adjustment layers <#adjustment-layers>.
The layer structure of a PSD object can be modified through
methods emulating a python list.
The internal model of the psd layer structure will be
automatically updated. Moving a layer from a PSD to another will also
automatically convert the PixelLayer to the target psd's color mode.
The follwing are valid for both PSDImage and Group objects.
Set an item:
Add a layer or layers to a group:
group.append(layer)
group.extend(layers)
Insert a layer to a specific index in the group:
Remove a layer from the a group:
Pop a layer from the group:
Emptying a layer group:
Get the index of a layer in the group:
index = group.index(layer)
Count the occurrences of a layer in a group:
count = group.count(layer)
Move a given list of layers in a newly created Group. If no parent
group is given in parameter, the new group will replace the first layer of
the list in the PSD structure:
group = psdimage.create_group(layer_list=[layer1, layer2, ...], name="New Group")
Some operations are available for all Layer objects.
Delete a layer from its layer structure:
Layers can be moved from a group to another:
target_group.append(layer)
Layers can be moved within the group to change their order:
layer.move_up() # Will send the layer upward in the group
layer.move_down() # Will send the layer downward in the group
Export the entire document as PIL.Image:
image = psd.composite()
image.save('exported.png')
Export a single layer including masks and clipping layers:
image = layer.composite()
Export layer and mask separately without composition:
image = layer.topil()
mask = layer.mask.topil()
To composite specific layers, such as layers except for texts, use
layer_filter option:
image = psd.composite(
layer_filter=lambda layer: layer.is_visible() and layer.kind != 'type')
Note:
Advanced compositing features require optional
dependencies. Install with:
pip install psd-tools[composite]
These dependencies (aggdraw, scipy,
scikit-image) are needed for:
- Vector shapes and strokes
- Gradient and pattern fills
- Layer effects
Basic compositing works without them by using cached previews or
simple pixel-based operations. If a feature requires missing dependencies,
an ImportError with installation instructions will be raised.
Note that most of the layer effects and adjustment layers are not
supported. The compositing result may look different from Photoshop.
PSDImage or layers can be exported to NumPy array by
numpy() method:
image = psd.numpy()
layer_image = layer.numpy()
psd-tools 1.12 makes composite dependencies optional to support
more platforms and Python versions.
The main breaking change in version 1.12 is that advanced
compositing features now require optional dependencies that must be
explicitly installed.
What changed:
- Dependencies aggdraw, scipy, and scikit-image are now
optional
- Basic compositing with NumPy continues to work without these
dependencies
- Advanced features (vector masks, gradients, patterns, effects) require the
composite extra
Migration steps:
If you use advanced compositing features, install with the
composite extra:
pip install 'psd-tools[composite]'
Or install the dependencies separately:
pip install psd-tools aggdraw scipy scikit-image
What works without composite dependencies:
- Reading and writing PSD files
- Accessing layer information (names, dimensions, etc.)
- Extracting raw pixel data with NumPy
- Basic pixel layer compositing
- Using cached layer previews
What requires composite dependencies:
- Vector shape and stroke rendering
- Gradient fills
- Pattern fills
- Layer effects rendering (drop shadows, strokes, etc.)
Error handling:
If you try to use advanced features without the dependencies
installed, you'll see a clear error:
ImportError: Advanced compositing features require optional dependencies.
Install with: pip install 'psd-tools[composite]'
Why this change:
This change enables psd-tools to run on platforms where some
composite dependencies are unavailable, particularly Python 3.14 on Windows
where aggdraw is not yet available.
Version 1.12 includes some internal refactoring that generally
doesn't affect public APIs:
- The PSD class moved from psd_tools.psd to
psd_tools.psd.document (still importable from
psd_tools.psd)
- Utils module split into registry and bin_utils (internal
change)
- Composite module reorganized for better type safety (internal change)
These changes maintain backward compatibility for public
imports.
Version 1.12 adds comprehensive type annotations throughout the
codebase. If you use type checkers like mypy, you may discover type errors
in your code that were previously undetected. This is a good thing - the
annotations help catch bugs earlier!
psd-tools 1.11 introduces stronger type-safety via annotation and
new public APIs for layer creation. Now the following approach is possible
to create a new layered document:
from PIL import Image
from psd_tools import PSDImage
image = Image.new("RGBA", (width, height))
psdimage = PSDImage.new(mode='RGB', size=(640, 480), depth=8)
layer = psdimage.create_pixel_layer(image, name="Layer 1", top=0, left=0, opacity=255)
psdimage.save('new_image.psd')
Version 1.11 introduces some breaking changes.
Layer creation now disables orphaned layers. They must be given a
valid PSDImage object.
version 1.11.x:
image = Image.new("RGBA", (width, height))
psdimage.create_pixel_layer(psdimage, image)
version 1.10.x:
image = Image.new("RGBA", (width, height))
PixelLayer.frompil(None, image, parent=None)
The same layer cannot be shared between multiple container
objects.
version 1.11.x:
layer = psdimage.create_pixel_layer(group, image)
psdimage.append(layer) # This won't duplicate the layer.
psd-tools 1.10 has a few breaking changes.
Basic layer structure editing is supported in 1.10. You can add or
remove a pixel layer, or change the grouping of layers.
psd-tools 1.10 drops compose module. Use composite
instead.
version 1.10.x:
image = psd.composite()
layer_image = layer.composite()
psd-tools 1.9 switches to NumPy based compositing.
version 1.8.x:
psd = PSDImage.open(filename)
image = psd.compose()
layer = psd[0]
layer_image = layer.compose()
version 1.9.x:
psd = PSDImage.open(filename)
image = psd.composite()
layer = psd[0]
layer_image = layer.composite()
NumPy array API is introduced:
image = psd.numpy()
layer_image = layer.numpy()
There are major API changes in version 1.8.x.
Note:
In version 1.8.0 - 1.8.7, the package name was
psd_tools2.
File open method is changed from load to open()
<#psd_tools.PSDImage.open>.
version 1.7.x:
psd = PSDImage.load(filename)
with open(filename, 'rb') as f:
psd = PSDImage.from_stream(f)
version 1.8.x:
psd = PSDImage.open(filename)
with open(filename, 'rb') as f:
psd = PSDImage.open(f)
Children of PSDImage or Group is directly accessible by iterator
or indexing.
version 1.7.x:
for layer in group.layers:
print(layer)
first_child = group.layers[0]
version 1.8.x:
for layer in group:
print(layer)
first_child = group[0]
In version 1.8.x, the order of layers is reversed to reflect that
the index should not change when a new layer is added on top.
Primary PIL export method is compose().
version 1.7.x:
image = psd.as_PIL()
layer_image = compose(layer)
raw_layer_image = layer.as_PIL()
version 1.8.x:
image = psd.compose()
layer_image = layer.compose()
raw_layer_image = layer.topil()
Low-level data structure
Data structures are completely rewritten to support writing
functionality. See psd_tools.psd <#module-psd_tools.psd>
subpackage.
version 1.7.x:
version 1.8.x:
Pymaging support is dropped.
- [api] Fix preview image mode when saving PSD files (#542)
- [packaging] Upgrade aggdraw to >=1.4.1 for Python 3.14 Windows support
(#543)
- [packaging] Make composite dependencies optional via
psd-tools[composite] extra (#525)
- [api] Lazy-load advanced composite features (vector masks, gradients,
effects) to avoid importing optional dependencies unless needed
- [api] Basic numpy-based compositing works without scipy,
scikit-image, or aggdraw
- [api] Composite functionality raises ImportError with installation
instructions only when advanced features are used
- [api] Handle missing composite dependencies gracefully in
PSDImage.save() (#532)
- [packaging] Move aggdraw, scipy, and scikit-image to
optional dependencies
- [packaging] Keep numpy as core dependency for raw pixel data
access
- [refactor] Move PSD class to dedicated document module
(#530)
- [refactor] Reorganize composite module structure and add type safety
(#524)
- [refactor] Split utils module into registry and bin_utils (#537)
- [refactor] Create shared API utils module (#538)
- [refactor] Improve type annotations and standardize imports (#539)
- [api] Add comprehensive type annotations to psd_tools package (#536)
- [tests] Add comprehensive type annotations to test suite (#534, #535)
- [tests] Add CI testing without composite dependencies (#533)
- [docs] Enhance package and module docstrings with comprehensive
documentation (#531)
- [docs] Update installation instructions for optional composite support
(#527)
- [docs] Convert README to Markdown (#527)
- [ci] Fix CI status badge in README (#523)
- [ci] Fix ReadTheDocs build by adding Self import fallback
- [ci] Fix Python 3.10 mypy compatibility (#526)
- [chore] Remove unused deprecated decorator (#529)
- [chore] Remove redundant MANIFEST.in file (#528)
Breaking change: Users who rely on advanced composite
features (vector masks, gradient fills, pattern fills, stroke effects) must
now install with pip install 'psd-tools[composite]' or install the
optional dependencies separately. Basic pixel layer compositing continues to
work with just numpy. This change enables support for Python 3.14 on Windows
and other platforms where composite dependencies may not be available.
- [ci] Disable Python 3.14 on Windows due to aggdraw unavailability
(#521)
- [api] Add public APIs for layer and group creation (#517)
- [api] Type safety improvements (#516)
- [api] Refactor psd_tools.api import dependencies (#515)
- [api] Fix group blend mode returning None (#514)
- [dev] Add Claude Code support (#511)
- [api] Add type annotation to more APIs (#509, #510, #512)
- [api] Add fill_opacity and reference_point attributes (#507,
#508)
- [psd] Improve pretty print of Subpath (#506)
- [docs] Fix incorrect method name in the usage (#504)
- [api] Fix updated status flag (#503)
- [api] Add tree traversal API (#502)
- [psd] Fix crash when reading malformed Levels record (#501)
- [api] Drop docopt dependency (#498)
- [api] Remove unused imports (#497)
- [api] Fix stacked clip layer handling (#496)
- [tests] Drop python2 compatibility code (#494)
- [api] Fix clip layer handling (#493)
- [psd] Workaround CAI tagged block reconstruction (#492)
- [api] Fix clipping with stroke composite (#489)
- [ci] Fix documentation build (#486, #487)
- [ci] Introduce ABI3 wheels (#483, #485)
- [api] Fix PyCMSError in composite (#484)
- [api] Fix ImageMath deprecation warning (#482)
- [psd] Allow linked layer version 8 (#476)
- [ci] Update CI configuration (#471)
- [psd] Workaround levels adjustment layer parsing (#470)
- [psd] Support CAI, GenI, OCIO tagged blocks (#469)
- [psd] Fix missing gradient method (#465)
- [security] Update pillow dependency (#462)
- [security] Update pillow dependency (#461)
- [api] Allow Path objects for PSDImage open (#452)
- [psd] Fix data corruption by irregular OSType (#449)
- [api] Add type annotation to the high-level APIs (#448)
- [api] Add channel info via DisplayInfo (#443)
- [api] Support layer locking (#442)
- [api] Fix artboard creation (#438)
- [api] Fix layer conversion issue (#435)
- [api] Support basic layer structure editing (#428)
- [api] Drop deprecated compose module (#432)
- [api] Support text type property (#419)
- [psd] Improve RLE decoding error handling (#417)
- [psd] Raise IO error instead of assertion (#413)
- [api] Add a new property to SmartObject: transform_box (#412)
- [ci] Migrate code formatter to ruff (#408)
- [psd] Fix incorrect group divider handling (#399)
- [psd] Reworked packbits/rle algorithms (#392)
- [ci] Fix missing pyx file in sdist (#386)
- [ci] Update CI configuration (#383)
- [dev] Migrate the builder to pyproject.toml
- [dev] Update linter and formatter to pysen
- [dev] Deprecate tox
- [psd] Add new color sheet (#380)
- [psd] Fix transparency check (#370)
- [psd] Add alternate 8ELE signiture for 8BIM tagged block (#367)
- [composite] Fix regression by #361 (#364)
- [composite] Read HSB colors in RGB and CMYK color modes (#361)
- [ci] Update CI configuration (#362)
- [composite] Fix hue, sat, and vivid light (#359)
- [psd] Support float RGB values (#350)
- [psd] Workaround stroke class ID (#346)
- [ci] Update CI configuration (#347)
- [composite] Fix group clipping (#336)
- [api] Add bbox invalidation when toggling layer visibility (#334)
- [psd] Add support for v3 gradient map adjustment layer (#330)
- [api] Fix incorrect has_effects behavior (#322)
- [composite] Improve blending numerical stability (#321)
- [composite] Improve non-RGB modes and transparency (#319,
@Etienne-Gautier)
- [psd] Workaround assertion error in broken file (#320)
- [ci] Update CI configuration (#313 #314)
- [composite] Fix composite errors (#312)
- [psd] Suppress vowv tagged blocks (#306)
- [composite] Fix rasterized shape composite (#301 #302)
- [api] Fix missing effect attributes (#284)
- [package] Support additional platforms (i686, aarch64, universal2,
win32)
- [package] Drop py36 support
- [api] Fix incorrect fill layer parse (fix #254)
- [package] Drop py27 and py35 support
- [psd] Workaround Enum bug (fix #241)
- [composite] Fix transparency issue (fix #242)
- [composite] Fix mask disable flag (fix #243)
- [api] Add workaround for creating PSB (fix #246)
- [api] Fix incorrect adjustment parse (fix #247)
- [composite] Fix ignored clip layers for groups.
- [composite] Fix out-of-viewport stroke effect.
- [api] Bugfix for PSDImage composite layer_filter option.
- [api] Bugfix for transparency and alpha distinction.
- [psd] Rename COMPOSITOR_INFO.
- [composite] Fix stroke effect target shape.
- [api] Bugfix for PSDImage init internal.
- [psd] Bugfix for CurvesExtraMarker read.
- [composite] Fix layer check.
- [psd] Fix engine data parser.
- [composite] Fix stroke effect argument.
- [composite] Fix incorrect fill opacity handling in compositing.
- [composite] Fix incorrect alpha for patterns.
- [composite] Fix path operation for merged components.
- [composite] Fix vector mask compositing condition.
- [composite] Fix incorrect alpha channel handling in composite.
- [api] Add ignore_preview option to PSDImage.composite.
- [composite] Improve stroke effect composition for vector masks.
- [composite] Avoid crash when there is an erroneous subpath.
- [composite] Workaround possible divide-by-zero warn in stroke
composition.
- [composite] Fix incorrect pattern transparency handling.
- [composite] Fix ignored effects in direct group composition.
- [composite] Fix incorrect opacity handling for clip layers.
- [compression] Security fix, affected versions are 1.8.37 - 1.9.3.
- [composite] Fix memory corruption crash for pattern data in PSB
files.
- [psd] Add image data pretty printing.
- [psd] Add missing resource ID.
- [psd] Fix pretty printing regression.
- [psd] Fix big tag key for linked layers.
- [psd] Support frgb tag.
- [psd] Support sgrp metadata key.
- [psd] Support patt tag.
- [psd] Workaround unknown engine data.
- [composite] Implement NumPy-based compositing functionality.
- [composite] Support blending modes other than dissolve.
- [composite] Support blending in RGB, CMYK, Grayscale.
- [api] Introduce NumPy array export method.
- [api] Drop deprecated methods from v1.7.x such as as_PIL.
- [api] Deprecate compose method.
- [compression] Rename packbits to rle.
- [compression] Improve RLE decode efficiency.
- [tests] Additional compositing tests.
- [composer] fix crash when gradient fill is in stroke.
- [compression] Remove packbits dependency and introduce cython
implementation.
- [deploy] Move CI provider from Travis-CI to Github Actions.
- [deploy] Start distributing binary wheels.
- [psd] add safeguard for malformed global layer mask info parser.
- [api] remove duplicate has_mask() definition.
- [composer] fix empty effects check.
- [api] fix compose() arguments.
- [psd] fix attrs version dependency.
- [api] add include_invisible option to
Group.extract_bbox.
- [psd] fix deprecated attrs api.
- [psd] fix 16/32 bit file parsing bug introduced in 1.8.17.
- [psd] bugfix reading psb.
- [psd] bugfix reading slices resource.
- [security] update dependency to pillow >= 6.2.0.
- [psd] workaround for reading less-than-4-byte int in malformed psd
files.
- [composer] fix vector mask bbox in composition.
- [api] fix Effects.__repr__() when data is empty.
- [api] accept encoding param in PSDImage.open and
PSDImage.save.
- [deploy] bugfix travis deployment condition.
- [composer] support group mask.
- [api] change return type of PSDImage.color_mode to enum.
- [api] support reading of bitmap color mode.
- [api] support channel option in topil() method.
- [composer] experimental support of commutative blending modes.
- [composer] fix clipping on alpha-less image;
- [composer] fix stroke effect for flat plane;
- [composer] workaround for insufficient knots;
- [composer] fix for custom color space.
- fix pass-through composing bug;
- fix alpha blending in effect;
- fix vector mask composition;
- experimental support for shape stroke;
- experimental support for stroke effect.
- change effect property return type from str to enum;
- improve gradient quality;
- support fill opacity and layer opacity;
- add tmln key in metadata setting.
- fix broken psd_tools.composer.vector module in 1.8.17;
- experimental support for color noise gradient;
- bugfix for clip masks;
- bugfix for CMYK composing.
- move psd_tools.api.composer module to psd_tools.composer
package;
- support 19 blending modes in composer;
- support fill opacity;
- fix image size when composing with masks;
- rename TaggedBlockID to Tag;
- rename ImageResourceID to Resource;
- add bytes mixin to Enum constants;
- replace Enum keys with raw values in psd_tools.psd.base.Dict
classes.
- fix broken group compose in 1.8.15;
- fix missing pattern / gradient composition in vector stroke content.
- coding style fix;
- fix compose() bbox option.
- add dependency to aggdraw;
- support bezier curves in vector masks;
- support path operations;
- fix compose(force=True) behavior;
- fix default background color in composer;
- improve pattern overlay parameters support;
- fix gradient map generation for a single stop.
- fix engine_data unknown tag format;
- fix compose for extra alpha channels;
- workaround for pillow 6.0.0 bug.
- add apply_icc option in pil io.
- introduce terminology module;
- reduce memory use in read;
- add main testing.
- fix PSB extn key size bug.
- documentation updates;
- introduce Artboard class.
- revert package name to psd_tools;
- prepare merging to the main repo.
- change _psd pointer in PSDImage;
- add version property;
- support fill effects in composer.
- change tagged block/image resource singleton accessor in user API;
- add documentation on iterator order;
- fix export setting 1 big key config;
- fix computer info big key config.
- add channel size checking in topil;
- add mlst metadata decoding;
- fix key collision issue in descriptor;
- performance improvement for packbit encoding/decoding;
- drop cython dependency in travis config;
- implement thumbnail, is_group, and parent methods in PSDImage.
- major API changes;
- package name changed to psd_tools2;
- completely rewritten decoding subpackage psd_tools2.psd;
- improved composer functionality;
- file write support;
- drop cython compression module and makes the package pure-python;
- drop pymaging support.
- composer alpha blending fix;
- documentation fix.
- support cinf tagged block.
- add missing extra image resource block signatures.
- move psd_tools tests under tests/psd_tools.
- fix alpha channel visibility of composed image.
- fix unit rectangle drawing size.
- fix ignored visibility in bbox calculation.
- drop py34 support;
- fix tobytes deprecation warning.
- fix gradient descriptor bug.
- fix coloroverlay bug;
- fix gradient angle bug;
- fix curves decoder bug.
- add shape rendering in compose();
- add grayscale support.
- fix has_pixel() condition.
- fix fill opacity in compose();
- workaround for broken PrintFlags.
- fix color overlay issue in compose().
- fix verbose arg for python 3.7 compatibility.
- fix has_pixel() for partial channels;
- support color overlay in compose().
- fix mask rendering in compose (Thanks @andrey-hider and @nkato).
- fix descriptor decoding errors;
- minor bugfixes.
- fix UnicodeError in exif;
- workaround for irregular descriptor name;
- add undocumented extn tagged block decoding;
- move duplicated icc module to subpackage;
- support PIL rendering with extra alpha channels.
- update documentation;
- fix PEP8 compliance;
- rename merge_layers to compose.
- fix white background issue in as_PIL().
- add quality testing;
- fix disabled mask.
- fix has_mask() condition;
- add mask composition in merge_layers();
- fix mask display.
- fix infinity loop in print_tree().
- add vector origination API;
- fix shape and vector mask identification;
- change enum name conversion;
- update docs.
- add adjustments API;
- add mask API;
- bugfix for tagged_blocks decoders.
- add mask user API;
- add layer coordinate user API;
- add vector mask and vector stroke API;
- cleanup user API;
- add automatic descriptor conversion.
- cleanup user API organization;
- remove json encoder api;
- make cli a package main.
- workaround for anaconda 2.7 pillow;
- bbox existence checkf.
- experimental clipping support in merge_layer();
- revert as_PIL() in AdjustmentLayer.
- Small fix for erroneous unicode path name
- Add all_layers() method;
- Add _image_resource_blocks property;
- Add thumbnail() method.
- documentation updates;
- github repository renamed to psd-tools2;
- AdjustmentLayer fix.
- layer class structure reorganization;
- add Effects API;
- add TypeLayer API methods.
- PSDImage user API update;
- user API adds distinct layer types;
- Sphinx documentation.
- implemented many decodings of image resources and tagged blocks;
- implemented EngineData text information;
- user API for getting mask and patterns;
- user API to calculate bbox for shape layers;
- Fixed reading of layer mask data (thanks Evgeny Kopylov);
- Python 2.6 support is dropped;
- Python 3.6 support is added (thanks Leendert Brouwer);
- extension is rebuilt with Cython 0.25.2.
- fixed references decoding (thanks Josh Drake);
- fixed PIL support for CMYK files (thanks Michael Wu);
- optional C extension is rebuilt with Cython 0.23.4;
- Python 3.2 support is dropped; the package still works in Python 3.2, but
the compatibility is no longer checked by tests, and so it can break in
future.
- declare Python 3.5 as supported.
- implemented extraction of embedded files (embedded smart objects) - thanks
Volker Braun;
- optional C extension is rebuilt with Cython 0.21.2.
- hg mirror on bitbucket is dropped, sorry!
- improved METADATA_SETTING decoding (thanks Evgeny Kopylov);
- layer comps decoding (thanks Evgeny Kopylov);
- improved smart objects decoding (thanks Joey Gentry);
- user API for getting layer transforms and placed layer size (thanks Joey
Gentry);
- IPython import is deferred to speedup psd-tools.py command-line
utility;
- _RootGroup.__repr__ is fixed;
- warning message building is more robust;
- optional C extension is rebuilt with Cython 0.21.1.
- Fixed reading of images with layer masks (thanks Evgeny Kopylov);
- improved mask data decoding (thanks Evgeny Kopylov);
- fixed synchronization in case of 8B64 signatures (thanks Evgeny
Kopylov);
- fixed reading of layers with zero length (thanks Evgeny Kopylov);
- fixed Descriptor parsing (thanks Evgeny Kopylov);
- some of the descriptor structures and tagged block constants are renamed
(thanks Evgeny Kopylov);
- PATH_SELECTION_STATE decoding (thanks Evgeny Kopylov);
- the library is switched to setuptools; docopt is now installed
automatically.
- Layer effects parsing (thanks Evgeny Kopylov);
- trailing null bytes are stripped from descriptor strings (thanks Evgeny
Kopylov);
- "Reference" and "List" descriptor parsing is fixed
(thanks Evgeny Kopylov);
- scalar descriptor values (doubles, floats, booleans) are now returned as
scalars, not as lists of size 1 (thanks Evgeny Kopylov);
- fixed reading of EngineData past declared length (thanks Carlton P.
Taylor);
- "background color" Image Resource parsing (thanks Evgeny
Kopylov);
- psd_tools.decoder.actions.Enum.enum field is renamed to
psd_tools.decoder.actions.Enum.value (thanks Evgeny Kopylov);
- code simplification - constants are now bytestrings as they should be
(thanks Evgeny Kopylov);
- Python 3.4 is supported.
- Improved merging of transparent layers (thanks Vladimir Timofeev);
- fixed layer merging and bounding box calculations for empty layers (thanks
Vladimir Timofeev);
- C extension is rebuilt with Cython 0.20.1.
- psd-tools.py command-line interface is changed, 'debug' command is
added;
- pretty-printing of internal structures;
- pymaging support is fixed;
- allow 'MeSa' to be a signature for image resource blocks (thanks Alexey
Buzanov);
- psd_tools.debug.debug_view utility function is fixed;
- Photoshop CC constants are added;
- Photoshop CC vector origination data is decoded;
- binary data is preserved if descriptor parsing fails;
- more verbose logging for PSD reader;
- channel data reader became more robust - now it doesn't read past declared
channel length;
- psd-tools.py --version command is fixed;
- lsdk tagged blocks parsing: this fixes some issues with layer
grouping (thanks Ivan Maradzhyiski for the bug report and the patch);
- CMYK images support is added (thanks Alexey Buzanov, Guillermo Rauch and
<https://github.com/a-e-m> for the help);
- Grayscale images support is added (thanks
<https://github.com/a-e-m>);
- LittleCMS is now optional (but it is still required to get proper
colors).
- Point and Millimeter types are added to UnitFloatType (thanks Doug
Ellwanger).
- Some issues with descriptor parsing are fixed (thanks Luke Petre).
- Python 2.x: reading data from file-like objects is fixed (thanks Pavel
Zinovkin).
- Fixed parsing of layer groups without explicit OPEN_FOLDER mark;
- Cython extension is rebuilt with Cython 0.18.
- Descriptor parsing (thanks Oliver Zheng);
- text (as string) is extracted from text layers (thanks Oliver Zheng);
- improved support for optional building of Cython extension.
- Typo is fixed: LayerRecord.cilpping should be
LayerRecord.clipping. Thanks Oliver Zheng.
- Highly experimental: basic layer merging is implemented (e.g. it is now
possible to export layer group to a PIL image);
- Layer.visible no longer takes group visibility in account;
- Layer.visible_global is the old Layer.visible;
- psd_tools.user_api.combined_bbox made public;
- Layer.width and Layer.height are removed (use
layer.bbox.width and layer.bbox.height instead);
- pil_support.composite_image_to_PIL is renamed to
pil_support.extract_composite_image and
pil_support.layer_to_PIL is renamed to
pil_support.extract_layer_image in order to have the same API for
pil_support and pymaging_support.
- psd.composite_image() is renamed to psd.as_PIL();
- Pymaging support: psd.as_pymaging() and layer.as_pymaging()
methods.
- Support for zip and zip-with-prediction compression methods is added;
- support for 16/32bit layers is added;
- optional Cython extension for faster zip-with-prediction
decompression;
- other speed improvements.
- Initial support for 16bit and 32bit PSD files: psd-tools v0.2 can
read composite (merged) images for such files and extract information
(names, dimensions, hierarchy, etc.) about layers and groups of 16/32bit
PSD; extracting image data for distinct layers in 16/32bit PSD files is
not suported yet;
- better Layer.__repr__;
- bbox property for Group.
Packaging is fixed in this release.
- Better support for 32bit images (still incomplete);
- reader is able to handle "global" tagged layer info blocks that
was previously discarded.
- warn about 32bit images;
- transparency support for composite images.
Initial release (v0.1 had packaging issues).
Development happens at github: bug tracker
<https://github.com/psd-tools/psd-tools/issues>. Feel free to submit
bug reports <https://github.com/psd-tools/psd-tools/issues/new> or
pull requests. Attaching an erroneous PSD file makes the debugging process
faster. Such PSD file might be added to the test suite.
The license is MIT.
The package consists of four major subpackages:
- 1.
- psd_tools.psd <#module-psd_tools.psd>: subpackage that
reads/writes low-level binary
- structure of the PSD/PSB file. The core data structures are built around
attrs <https://www.attrs.org/en/stable/index.html#> classes that all
implement read and write methods. Each data object tries to
resemble the structure described in the specification
<https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/>.
Although documented, the specification
<https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/> is
far from complete and some are even inaccurate. When psd-tools
finds unknown data structure, the package keeps such data as bytes
in the parsed result.
- 2.
- psd_tools.api <#module-psd_tools.api>: User-facing API
that implements various
- easy-to-use methods that manipulate low-level psd_tools.psd
<#module-psd_tools.psd> data structures. This is the primary
interface for most users.
- 3.
- psd_tools.composite <#module-psd_tools.composite>:
Rendering engine for layer compositing and
- blending. This subpackage implements blend modes, layer effects (drop
shadows, strokes, etc.), and vector shape rasterization. It uses NumPy
arrays for efficient pixel manipulation and includes optional dependencies
(scipy, scikit-image, aggdraw) that must be installed
via the composite extra.
- 4.
- psd_tools.compression <#module-psd_tools.compression>:
Image compression codecs for raw data,
- RLE (Run-Length Encoding), and ZIP compression. The RLE codec includes a
Cython-optimized implementation (_rle.pyx) that falls back to pure
Python if not compiled, providing significant performance improvements for
large files.
In the future, it might be good to implement the Photoshop API
<https://developer.adobe.com/photoshop/uxp/2022/ps_reference/> on top
of the existing psd-tools API.
In order to run tests, make sure PIL/Pillow is built with
LittleCMS or LittleCMS2 support. For example, on Ubuntu, install the
following packages:
apt-get install liblcms2-dev libjpeg-dev libfreetype6-dev zlib1g-dev
Then install psd-tools with development dependencies:
uv sync --group dev --extra composite
Finally, run tests:
Install documentation dependencies:
Once installed, use Makefile:
Releases are automated via GitHub Actions. To create a new
release:
- 1.
- Ensure all changes are committed and pushed to main:
git checkout main
git pull origin main
- 2.
- Create and push a version tag:
git tag v1.x.x
git push origin v1.x.x
- 3.
- Automated workflow:
Once the tag is pushed, the release workflow
automatically:
- Builds wheels for all supported platforms (Linux, Windows, macOS including
ARM)
- Generates release notes from git commits since the previous tag
- Creates a GitHub release with the auto-generated changelog
- Publishes the package to PyPI
- 4.
- Verify the release:
- Check the Actions tab
<https://github.com/psd-tools/psd-tools/actions> for workflow
status
- Verify the release on GitHub
<https://github.com/psd-tools/psd-tools/releases>
- Confirm the package is available on PyPI
<https://pypi.org/project/psd-tools/>
Note: Only maintainers with appropriate repository
permissions can push tags and trigger releases. PyPI credentials are stored
as repository secrets.
Great thanks to all the contributors
<https://github.com/psd-tools/psd-tools/graphs/contributors>.
Supported:
- Read and write of the low-level PSD/PSB file structure
- Raw layer image export in NumPy and PIL format
Limited support:
- Composition of basic pixel-based layers
- Composition of fill layer effects
- Vector masks
- Editing of some layer attributes such as layer name
- Basic editing of pixel layers and groups, such as adding or removing a
layer
- Blending modes except for dissolve
- Drawing of bezier curves
Not supported:
- Editing of various layers such as type layers, shape layers, smart
objects, etc.
- Editing of texts in type layers
- Composition of adjustment layers
- Composition of many layer effects
- Font rendering
See Usage <> for examples.
- class
psd_tools.PSDImage(data: PSD <#psd_tools.psd.PSD>)
- Photoshop PSD/PSB document.
The low-level data structure is accessible at
PSDImage._record.
Example:
from psd_tools import PSDImage
psdimage = PSDImage.open('example.psd')
image = psdimage.composite()
for layer in psdimage:
layer_image = layer.composite()
- append(layer:
Layer <#psd_tools.api.layers.Layer>) -> None
- Add a layer to the end (top) of the group.
This operation rewrites the internal references of the layer.
Adding the same layer will not create a duplicate.
- Parameters
- layer -- The layer to add.
- Raises
- TypeError -- If the provided object is not a Layer instance.
- ValueError -- If attempting to add a group to itself.
- property bbox:
tuple[int, int, int, int]
- Minimal bounding box that contains all the visible layers.
Use viewbox
<#psd_tools.api.psd_image.PSDImage.viewbox> to get viewport
bounding box. When the psd is empty, bbox is equal to the canvas
bounding box.
- Returns
- (left, top, right, bottom) tuple.
- clear() ->
None
- Clears the group.
This operation rewrites the internal references of the
layers.
- property
compatibility_mode: CompatibilityMode
<#psd_tools.constants.CompatibilityMode>
- Set the compositing and layer organization compatibility mode. Writable.
This property checks whether the PSD file is compatible with
specific authoring tools, such as Photoshop or CLIP Studio Paint.
Different authoring tools may have different ways of handling layers,
such as the use of clipping masks for groups. Default is Photoshop
compatibility mode.
- Returns
- CompatibilityMode
<#psd_tools.constants.CompatibilityMode>
- composite(viewport:
tuple[int, int, int, int] | None = None, force: bool = False, color: float |
tuple[float, ...] | ndarray | None = 1.0, alpha: float | ndarray = 0.0,
layer_filter: Callable | None = None, ignore_preview: bool = False,
apply_icc: bool = True) -> Image
- Composite the PSD image.
- Parameters
- viewport -- Viewport bounding box specified by (x1, y1, x2, y2)
tuple. Default is the viewbox of the PSD.
- ignore_preview -- Boolean flag to whether skip compositing when a
pre-composited preview is available.
- force -- Boolean flag to force vector drawing.
- color -- Backdrop color specified by scalar or tuple of scalar. The
color value should be in [0.0, 1.0]. For example, (1., 0., 0.) specifies
red in RGB color mode.
- alpha -- Backdrop alpha in [0.0, 1.0].
- layer_filter -- Callable that takes a layer as argument and returns
whether if the layer is composited. Default is is_visible().
- Returns
- PIL.Image.
- create_pixel_layer(image:
Image, name: str = 'Layer', top: int = 0, left: int = 0, compression:
Compression <#psd_tools.constants.Compression> = Compression.RLE,
opacity: int = 255, blend_mode: BlendMode
<#psd_tools.constants.BlendMode> = BlendMode.NORMAL) -> PixelLayer
<#psd_tools.api.layers.PixelLayer>
- Create a new pixel layer and add it to the PSDImage.
Example:
psdimage = PSDImage.new("RGB", (640, 480))
layer = psdimage.create_pixel_layer(image, name='Layer 1')
- Parameters
- name -- Name of the new layer.
- image -- PIL Image object.
- top -- Top coordinate of the new layer.
- left -- Left coordinate of the new layer.
- compression -- Compression method for the layer image data.
- opacity -- Opacity of the new layer (0-255).
- blend_mode -- Blend mode of the new layer, default is
BlendMode.NORMAL.
- Returns
- The created PixelLayer <#psd_tools.api.layers.PixelLayer>
object.
- has_preview()
-> bool
- Returns if the document has real merged data. When True, topil()
returns pre-composed data.
- property
image_resources: ImageResources
<#psd_tools.psd.image_resources.ImageResources>
- Document image resources. ImageResources
<#psd_tools.psd.image_resources.ImageResources> is a dict-like
structure that keeps various document settings.
See psd_tools.constants.Resource
<#psd_tools.constants.Resource> for available keys.
- Returns
- ImageResources
<#psd_tools.psd.image_resources.ImageResources>
Example:
from psd_tools.constants import Resource
version_info = psd.image_resources.get_data(Resource.VERSION_INFO)
slices = psd.image_resources.get_data(Resource.SLICES)
Image resources contain an ICC profile. The following shows how to
export a PNG file with embedded ICC profile:
from psd_tools.constants import Resource
icc_profile = psd.image_resources.get_data(Resource.ICC_PROFILE)
image = psd.compose(apply_icc=False)
image.save('output.png', icc_profile=icc_profile)
- thumbnail()
-> Image | None
- Returns a thumbnail image in PIL.Image. When the file does not contain an
embedded thumbnail image, returns None.
High-level API for working with PSD/PSB files.
This subpackage provides the user-facing API for psd-tools,
offering Pythonic interfaces for manipulating Photoshop documents. It wraps
the low-level psd_tools.psd <#module-psd_tools.psd> binary
structures with convenient methods and properties.
The main entry point is PSDImage, which provides
document-level operations. Individual layers are represented by various
layer classes in psd_tools.api.layers
<#module-psd_tools.api.layers>.
Key modules:
- psd_tools.api.psd_image: Main PSDImage class for document
operations
- psd_tools.api.layers <#module-psd_tools.api.layers>: Layer
type hierarchy (Layer, GroupMixin, etc.)
- psd_tools.api.adjustments
<#module-psd_tools.api.adjustments>: Adjustment layer types
- psd_tools.api.mask <#module-psd_tools.api.mask>: Layer mask
operations
- psd_tools.api.shape <#module-psd_tools.api.shape>: Vector
shape and stroke operations
- psd_tools.api.effects <#module-psd_tools.api.effects>: Layer
effects (shadows, glows, etc.)
- psd_tools.api.pil_io: PIL/Pillow image I/O utilities
- psd_tools.api.numpy_io: NumPy array I/O utilities
Example usage:
from psd_tools import PSDImage
# Open a PSD file
psd = PSDImage.open('document.psd')
# Access layers
for layer in psd:
print(f"{layer.name}: {layer.kind}")
# Modify a layer
layer = psd[0]
layer.name = "New Name"
layer.opacity = 128
# Save changes
psd.save('modified.psd')
The API layer automatically reconstructs the layer tree from the
flat list of layer records in the PSD file, establishing proper parent-child
relationships for groups.
This module provides the high-level API for working with PSD
files. It wraps the low-level psd_tools.psd
<#module-psd_tools.psd> structures with convenient Pythonic
interfaces.
- class
psd_tools.api.psd_image.PSDImage(data: PSD
<#psd_tools.psd.PSD>)
- Photoshop PSD/PSB document.
The low-level data structure is accessible at
PSDImage._record.
Example:
from psd_tools import PSDImage
psdimage = PSDImage.open('example.psd')
image = psdimage.composite()
for layer in psdimage:
layer_image = layer.composite()
- append(layer:
Layer <#psd_tools.api.layers.Layer>) -> None
- Add a layer to the end (top) of the group.
This operation rewrites the internal references of the layer.
Adding the same layer will not create a duplicate.
- Parameters
- layer -- The layer to add.
- Raises
- TypeError -- If the provided object is not a Layer instance.
- ValueError -- If attempting to add a group to itself.
- property
bbox: tuple[int, int, int, int]
- Minimal bounding box that contains all the visible layers.
Use viewbox to get viewport bounding box. When the psd
is empty, bbox is equal to the canvas bounding box.
- Returns
- (left, top, right, bottom) tuple.
- clear() ->
None
- Clears the group.
This operation rewrites the internal references of the
layers.
- property
compatibility_mode: CompatibilityMode
<#psd_tools.constants.CompatibilityMode>
- Set the compositing and layer organization compatibility mode. Writable.
This property checks whether the PSD file is compatible with
specific authoring tools, such as Photoshop or CLIP Studio Paint.
Different authoring tools may have different ways of handling layers,
such as the use of clipping masks for groups. Default is Photoshop
compatibility mode.
- Returns
- CompatibilityMode
<#psd_tools.constants.CompatibilityMode>
- composite(viewport:
tuple[int, int, int, int] | None = None, force: bool = False, color: float |
tuple[float, ...] | ndarray | None = 1.0, alpha: float | ndarray = 0.0,
layer_filter: Callable | None = None, ignore_preview: bool = False,
apply_icc: bool = True) -> Image
- Composite the PSD image.
- Parameters
- viewport -- Viewport bounding box specified by (x1, y1, x2, y2)
tuple. Default is the viewbox of the PSD.
- ignore_preview -- Boolean flag to whether skip compositing when a
pre-composited preview is available.
- force -- Boolean flag to force vector drawing.
- color -- Backdrop color specified by scalar or tuple of scalar. The
color value should be in [0.0, 1.0]. For example, (1., 0., 0.) specifies
red in RGB color mode.
- alpha -- Backdrop alpha in [0.0, 1.0].
- layer_filter -- Callable that takes a layer as argument and returns
whether if the layer is composited. Default is is_visible().
- Returns
- PIL.Image.
- create_pixel_layer(image:
Image, name: str = 'Layer', top: int = 0, left: int = 0, compression:
Compression <#psd_tools.constants.Compression> = Compression.RLE,
opacity: int = 255, blend_mode: BlendMode
<#psd_tools.constants.BlendMode> = BlendMode.NORMAL) -> PixelLayer
<#psd_tools.api.layers.PixelLayer>
- Create a new pixel layer and add it to the PSDImage.
Example:
psdimage = PSDImage.new("RGB", (640, 480))
layer = psdimage.create_pixel_layer(image, name='Layer 1')
- Parameters
- name -- Name of the new layer.
- image -- PIL Image object.
- top -- Top coordinate of the new layer.
- left -- Left coordinate of the new layer.
- compression -- Compression method for the layer image data.
- opacity -- Opacity of the new layer (0-255).
- blend_mode -- Blend mode of the new layer, default is
BlendMode.NORMAL.
- Returns
- The created PixelLayer <#psd_tools.api.layers.PixelLayer>
object.
- has_preview()
-> bool
- Returns if the document has real merged data. When True, topil()
returns pre-composed data.
- property
image_resources: ImageResources
<#psd_tools.psd.image_resources.ImageResources>
- Document image resources. ImageResources
<#psd_tools.psd.image_resources.ImageResources> is a dict-like
structure that keeps various document settings.
See psd_tools.constants.Resource
<#psd_tools.constants.Resource> for available keys.
- Returns
- ImageResources
<#psd_tools.psd.image_resources.ImageResources>
Example:
from psd_tools.constants import Resource
version_info = psd.image_resources.get_data(Resource.VERSION_INFO)
slices = psd.image_resources.get_data(Resource.SLICES)
Image resources contain an ICC profile. The following shows how to
export a PNG file with embedded ICC profile:
from psd_tools.constants import Resource
icc_profile = psd.image_resources.get_data(Resource.ICC_PROFILE)
image = psd.compose(apply_icc=False)
image.save('output.png', icc_profile=icc_profile)
- thumbnail()
-> Image | None
- Returns a thumbnail image in PIL.Image. When the file does not contain an
embedded thumbnail image, returns None.
For detailed layer types documentation, see psd_tools.api.layers
<>.
Adjustment and fill layers.
Example:
if layer.kind == 'brightnesscontrast':
print(layer.brightness)
if layer.kind == 'gradientfill':
print(layer.gradient_kind)
Fill layers are similar to ShapeLayer
<#psd_tools.api.layers.ShapeLayer> except that the layer might not
have an associated vector mask. The layer therefore expands the entire
canvas of the PSD document.
Fill layers all inherit from FillLayer.
Example:
if isinstance(layer, psd_tools.layers.FillLayer):
image = layer.compose()
- class
psd_tools.api.adjustments.SolidColorFill(*args: Any)
- Solid color fill.
- composite(viewport:
tuple[int, int, int, int] | None = None, force: bool = False, color: float |
tuple[float, ...] | ndarray = 1.0, alpha: float | ndarray = 0.0,
layer_filter: Callable | None = None, apply_icc: bool = True) -> Image |
None
- Composite layer and masks (mask, vector mask, and clipping layers).
- Parameters
- viewport -- Viewport bounding box specified by (x1, y1, x2, y2)
tuple. Default is the layer's bbox.
- force -- Boolean flag to force vector drawing.
- color -- Backdrop color specified by scalar or tuple of scalar. The
color value should be in [0.0, 1.0]. For example, (1., 0., 0.) specifies
red in RGB color mode.
- alpha -- Backdrop alpha in [0.0, 1.0].
- layer_filter -- Callable that takes a layer as argument and returns
whether if the layer is composited. Default is is_visible().
- Returns
- PIL.Image.Image or None.
- has_pixels()
-> bool
- Returns True if the layer has associated pixels. When this is True,
topil method returns PIL.Image.Image.
- property
kind: str
- Kind of this layer, such as group, pixel, shape, type, smartobject, or
psdimage. Class name without layer suffix.
- lock(lock_flags:
int = ProtectedFlags.COMPLETE) -> None
- Locks a layer accordind to the combination of flags.
- Parameters
- lockflags -- An integer representing the locking state
Example using the constants of ProtectedFlags and bitwise or
operation to lock both pixels and positions:
layer.lock(ProtectedFlags.COMPOSITE | ProtectedFlags.POSITION)
- move_down(offset:
int = 1) -> Self
- Moves the layer down a certain offset within the group the layer is
in.
- Parameters
- offset -- The number of positions to move the layer down (can be
negative).
- Raises
- ValueError -- If layer has no parent or parent is not a group
- IndexError -- If the new index is out of bounds
- Returns
- self
- move_up(offset:
int = 1) -> Self
- Moves the layer up a certain offset within the group the layer is in.
- Parameters
- offset -- The number of positions to move the layer up (can be
negative).
- Raises
- ValueError -- If layer has no parent or parent is not a group
- IndexError -- If the new index is out of bounds
- Returns
- self
- property
origination: list[Origination]
- Property for a list of live shapes or a line.
Some of the vector masks have associated live shape
properties, that are Photoshop feature to handle primitive shapes such
as a rectangle, an ellipse, or a line. Vector masks without live shape
properties are plain path objects.
See psd_tools.api.shape
<#module-psd_tools.api.shape>.
- Returns
- List of Invalidated <#psd_tools.api.shape.Invalidated>,
Rectangle <#psd_tools.api.shape.Rectangle>,
RoundedRectangle <#psd_tools.api.shape.RoundedRectangle>,
Ellipse <#psd_tools.api.shape.Ellipse>, or Line
<#psd_tools.api.shape.Line>.
- topil(channel:
int | None = None, apply_icc: bool = True) -> Image | None
- Get PIL Image of the layer.
- Parameters
- channel -- Which channel to return; e.g., 0 for 'R' channel in RGB
image. See ChannelID <#psd_tools.constants.ChannelID>. When
None, the method returns all the channels supported by PIL
modes.
- apply_icc -- Whether to apply ICC profile conversion to sRGB.
- Returns
- PIL.Image.Image, or None if the layer has no pixels.
Example:
from psd_tools.constants import ChannelID
image = layer.topil()
red = layer.topil(ChannelID.CHANNEL_0)
alpha = layer.topil(ChannelID.TRANSPARENCY_MASK)
Note:
Not all of the PSD image modes are supported in
PIL.Image.Image. For example, 'CMYK' mode cannot include alpha channel
in PIL. In this case, topil drops alpha channel.
- class
psd_tools.api.adjustments.PatternFill(*args: Any)
- Pattern fill.
- composite(viewport:
tuple[int, int, int, int] | None = None, force: bool = False, color: float |
tuple[float, ...] | ndarray = 1.0, alpha: float | ndarray = 0.0,
layer_filter: Callable | None = None, apply_icc: bool = True) -> Image |
None
- Composite layer and masks (mask, vector mask, and clipping layers).
- Parameters
- viewport -- Viewport bounding box specified by (x1, y1, x2, y2)
tuple. Default is the layer's bbox.
- force -- Boolean flag to force vector drawing.
- color -- Backdrop color specified by scalar or tuple of scalar. The
color value should be in [0.0, 1.0]. For example, (1., 0., 0.) specifies
red in RGB color mode.
- alpha -- Backdrop alpha in [0.0, 1.0].
- layer_filter -- Callable that takes a layer as argument and returns
whether if the layer is composited. Default is is_visible().
- Returns
- PIL.Image.Image or None.
- has_pixels()
-> bool
- Returns True if the layer has associated pixels. When this is True,
topil method returns PIL.Image.Image.
- property
kind: str
- Kind of this layer, such as group, pixel, shape, type, smartobject, or
psdimage. Class name without layer suffix.
- lock(lock_flags:
int = ProtectedFlags.COMPLETE) -> None
- Locks a layer accordind to the combination of flags.
- Parameters
- lockflags -- An integer representing the locking state
Example using the constants of ProtectedFlags and bitwise or
operation to lock both pixels and positions:
layer.lock(ProtectedFlags.COMPOSITE | ProtectedFlags.POSITION)
- move_down(offset:
int = 1) -> Self
- Moves the layer down a certain offset within the group the layer is
in.
- Parameters
- offset -- The number of positions to move the layer down (can be
negative).
- Raises
- ValueError -- If layer has no parent or parent is not a group
- IndexError -- If the new index is out of bounds
- Returns
- self
- move_up(offset:
int = 1) -> Self
- Moves the layer up a certain offset within the group the layer is in.
- Parameters
- offset -- The number of positions to move the layer up (can be
negative).
- Raises
- ValueError -- If layer has no parent or parent is not a group
- IndexError -- If the new index is out of bounds
- Returns
- self
- property
origination: list[Origination]
- Property for a list of live shapes or a line.
Some of the vector masks have associated live shape
properties, that are Photoshop feature to handle primitive shapes such
as a rectangle, an ellipse, or a line. Vector masks without live shape
properties are plain path objects.
See psd_tools.api.shape
<#module-psd_tools.api.shape>.
- Returns
- List of Invalidated <#psd_tools.api.shape.Invalidated>,
Rectangle <#psd_tools.api.shape.Rectangle>,
RoundedRectangle <#psd_tools.api.shape.RoundedRectangle>,
Ellipse <#psd_tools.api.shape.Ellipse>, or Line
<#psd_tools.api.shape.Line>.
- topil(channel:
int | None = None, apply_icc: bool = True) -> Image | None
- Get PIL Image of the layer.
- Parameters
- channel -- Which channel to return; e.g., 0 for 'R' channel in RGB
image. See ChannelID <#psd_tools.constants.ChannelID>. When
None, the method returns all the channels supported by PIL
modes.
- apply_icc -- Whether to apply ICC profile conversion to sRGB.
- Returns
- PIL.Image.Image, or None if the layer has no pixels.
Example:
from psd_tools.constants import ChannelID
image = layer.topil()
red = layer.topil(ChannelID.CHANNEL_0)
alpha = layer.topil(ChannelID.TRANSPARENCY_MASK)
Note:
Not all of the PSD image modes are supported in
PIL.Image.Image. For example, 'CMYK' mode cannot include alpha channel
in PIL. In this case, topil drops alpha channel.
- class
psd_tools.api.adjustments.GradientFill(*args: Any)
- Gradient fill.
- composite(viewport:
tuple[int, int, int, int] | None = None, force: bool = False, color: float |
tuple[float, ...] | ndarray = 1.0, alpha: float | ndarray = 0.0,
layer_filter: Callable | None = None, apply_icc: bool = True) -> Image |
None
- Composite layer and masks (mask, vector mask, and clipping layers).
- Parameters
- viewport -- Viewport bounding box specified by (x1, y1, x2, y2)
tuple. Default is the layer's bbox.
- force -- Boolean flag to force vector drawing.
- color -- Backdrop color specified by scalar or tuple of scalar. The
color value should be in [0.0, 1.0]. For example, (1., 0., 0.) specifies
red in RGB color mode.
- alpha -- Backdrop alpha in [0.0, 1.0].
- layer_filter -- Callable that takes a layer as argument and returns
whether if the layer is composited. Default is is_visible().
- Returns
- PIL.Image.Image or None.
- has_pixels()
-> bool
- Returns True if the layer has associated pixels. When this is True,
topil method returns PIL.Image.Image.
- property
kind: str
- Kind of this layer, such as group, pixel, shape, type, smartobject, or
psdimage. Class name without layer suffix.
- lock(lock_flags:
int = ProtectedFlags.COMPLETE) -> None
- Locks a layer accordind to the combination of flags.
- Parameters
- lockflags -- An integer representing the locking state
Example using the constants of ProtectedFlags and bitwise or
operation to lock both pixels and positions:
layer.lock(ProtectedFlags.COMPOSITE | ProtectedFlags.POSITION)
- move_down(offset:
int = 1) -> Self
- Moves the layer down a certain offset within the group the layer is
in.
- Parameters
- offset -- The number of positions to move the layer down (can be
negative).
- Raises
- ValueError -- If layer has no parent or parent is not a group
- IndexError -- If the new index is out of bounds
- Returns
- self
- move_up(offset:
int = 1) -> Self
- Moves the layer up a certain offset within the group the layer is in.
- Parameters
- offset -- The number of positions to move the layer up (can be
negative).
- Raises
- ValueError -- If layer has no parent or parent is not a group
- IndexError -- If the new index is out of bounds
- Returns
- self
- property
origination: list[Origination]
- Property for a list of live shapes or a line.
Some of the vector masks have associated live shape
properties, that are Photoshop feature to handle primitive shapes such
as a rectangle, an ellipse, or a line. Vector masks without live shape
properties are plain path objects.
See psd_tools.api.shape
<#module-psd_tools.api.shape>.
- Returns
- List of Invalidated <#psd_tools.api.shape.Invalidated>,
Rectangle <#psd_tools.api.shape.Rectangle>,
RoundedRectangle <#psd_tools.api.shape.RoundedRectangle>,
Ellipse <#psd_tools.api.shape.Ellipse>, or Line
<#psd_tools.api.shape.Line>.
- topil(channel:
int | None = None, apply_icc: bool = True) -> Image | None
- Get PIL Image of the layer.
- Parameters
- channel -- Which channel to return; e.g., 0 for 'R' channel in RGB
image. See ChannelID <#psd_tools.constants.ChannelID>. When
None, the method returns all the channels supported by PIL
modes.
- apply_icc -- Whether to apply ICC profile conversion to sRGB.
- Returns
- PIL.Image.Image, or None if the layer has no pixels.
Example:
from psd_tools.constants import ChannelID
image = layer.topil()
red = layer.topil(ChannelID.CHANNEL_0)
alpha = layer.topil(ChannelID.TRANSPARENCY_MASK)
Note:
Not all of the PSD image modes are supported in
PIL.Image.Image. For example, 'CMYK' mode cannot include alpha channel
in PIL. In this case, topil drops alpha channel.
Adjustment layers apply image filtering to the composed result.
All adjustment layers inherit from AdjustmentLayer. Adjustment layers
do not have pixels, and currently ignored in compose. Attempts to
call topil on adjustment layers always return None.
Just as any other layer, adjustment layers might have an
associated mask or vector mask. Adjustment can appear in other layers'
clipping layers.
Example:
if isinstance(layer, psd_tools.layers.AdjustmentLayer):
print(layer.kind)
Effects module.
- class
psd_tools.api.effects.Effects(layer: LayerProtocol)
- List-like effects.
Only present effects are kept.
- find(name:
str, enabled: bool = True) -> Iterator[_Effect]
- Iterate effect items by name.
- Parameters
- name -- Effect name, e.g. DropShadow, InnerShadow,
OuterGlow, InnerGlow, ColorOverlay,
GradientOverlay, PatternOverlay, Stroke,
BevelEmboss, or Satin.
- enabled -- If true, only return enabled effects.
- Return
type
- Iterator[Effect]
Layer module.
This module implements the high-level layer API for psd-tools,
providing Pythonic interfaces for working with Photoshop layers. It defines
the layer type hierarchy and common operations.
Key classes:
- Layer: Base class for all layer types
- GroupMixin: Mixin for layers that contain children (groups,
documents)
- Group: Folder/group layer containing other layers
- PixelLayer: Regular raster layer with pixel data
- TypeLayer: Text layer with typography information
- ShapeLayer: Vector shape layer
- SmartObjectLayer: Embedded or linked smart object
- AdjustmentLayer: Non-destructive adjustment (curves, levels,
etc.)
Layer hierarchy:
Layers are organized in a tree structure where groups can contain
child layers. The GroupMixin provides iteration, indexing, and search
capabilities:
# Iterate through all layers
for layer in psd:
print(layer.name)
# Access by index
first_layer = psd[0]
# Check if layer is a specific type
if layer.kind == 'pixel':
pixels = layer.numpy()
Common layer properties:
- name: Layer name
- visible: Visibility flag
- opacity: Opacity (0-255)
- blend_mode: Blend mode enum
- bbox: Bounding box (left, top, right, bottom)
- width, height: Dimensions
- kind: Layer type string ('pixel', 'group', 'type', etc.)
- parent: Parent layer or document
Layer operations:
- composite(): Render layer to PIL Image
- numpy(): Get pixel data as NumPy array
- topil(): Convert to PIL Image
- has_mask(): Check if layer has a mask
- has_clip_layers(): Check if layer has clipping mask
Example usage:
from psd_tools import PSDImage
psd = PSDImage.open('document.psd')
# Access first layer
layer = psd[0]
# Modify layer properties
layer.visible = False
layer.opacity = 128
layer.name = "New Name"
# Get pixel data
pixels = layer.numpy() # NumPy array
image = layer.topil() # PIL Image
# Work with groups
for group in psd.descendants():
if group.kind == 'group':
print(f"Group: {group.name} with {len(group)} layers")
# Composite specific layer
rendered = layer.composite()
rendered.save('layer.png')
Layer types are automatically determined from the underlying PSD
structures and exposed through the kind property for easy type
checking.
- class
psd_tools.api.layers.Layer(parent: GroupMixin, record: LayerRecord
<#psd_tools.psd.layer_and_mask.LayerRecord>, channels: ChannelDataList
<#psd_tools.psd.layer_and_mask.ChannelDataList>)
- composite(viewport:
tuple[int, int, int, int] | None = None, force: bool = False, color: float |
tuple[float, ...] | ndarray = 1.0, alpha: float | ndarray = 0.0,
layer_filter: Callable | None = None, apply_icc: bool = True) -> Image |
None
- Composite layer and masks (mask, vector mask, and clipping layers).
- Parameters
- viewport -- Viewport bounding box specified by (x1, y1, x2, y2)
tuple. Default is the layer's bbox.
- force -- Boolean flag to force vector drawing.
- color -- Backdrop color specified by scalar or tuple of scalar. The
color value should be in [0.0, 1.0]. For example, (1., 0., 0.) specifies
red in RGB color mode.
- alpha -- Backdrop alpha in [0.0, 1.0].
- layer_filter -- Callable that takes a layer as argument and returns
whether if the layer is composited. Default is is_visible().
- Returns
- PIL.Image.Image or None.
- has_pixels()
-> bool
- Returns True if the layer has associated pixels. When this is True,
topil method returns PIL.Image.Image.
- property
kind: str
- Kind of this layer, such as group, pixel, shape, type, smartobject, or
psdimage. Class name without layer suffix.
- lock(lock_flags:
int = ProtectedFlags.COMPLETE) -> None
- Locks a layer accordind to the combination of flags.
- Parameters
- lockflags -- An integer representing the locking state
Example using the constants of ProtectedFlags and bitwise or
operation to lock both pixels and positions:
layer.lock(ProtectedFlags.COMPOSITE | ProtectedFlags.POSITION)
- move_down(offset:
int = 1) -> Self
- Moves the layer down a certain offset within the group the layer is
in.
- Parameters
- offset -- The number of positions to move the layer down (can be
negative).
- Raises
- ValueError -- If layer has no parent or parent is not a group
- IndexError -- If the new index is out of bounds
- Returns
- self
- move_up(offset:
int = 1) -> Self
- Moves the layer up a certain offset within the group the layer is in.
- Parameters
- offset -- The number of positions to move the layer up (can be
negative).
- Raises
- ValueError -- If layer has no parent or parent is not a group
- IndexError -- If the new index is out of bounds
- Returns
- self
- property
origination: list[Origination]
- Property for a list of live shapes or a line.
Some of the vector masks have associated live shape
properties, that are Photoshop feature to handle primitive shapes such
as a rectangle, an ellipse, or a line. Vector masks without live shape
properties are plain path objects.
See psd_tools.api.shape
<#module-psd_tools.api.shape>.
- Returns
- List of Invalidated <#psd_tools.api.shape.Invalidated>,
Rectangle <#psd_tools.api.shape.Rectangle>,
RoundedRectangle <#psd_tools.api.shape.RoundedRectangle>,
Ellipse <#psd_tools.api.shape.Ellipse>, or Line
<#psd_tools.api.shape.Line>.
- topil(channel:
int | None = None, apply_icc: bool = True) -> Image | None
- Get PIL Image of the layer.
- Parameters
- channel -- Which channel to return; e.g., 0 for 'R' channel in RGB
image. See ChannelID <#psd_tools.constants.ChannelID>. When
None, the method returns all the channels supported by PIL
modes.
- apply_icc -- Whether to apply ICC profile conversion to sRGB.
- Returns
- PIL.Image.Image, or None if the layer has no pixels.
Example:
from psd_tools.constants import ChannelID
image = layer.topil()
red = layer.topil(ChannelID.CHANNEL_0)
alpha = layer.topil(ChannelID.TRANSPARENCY_MASK)
Note:
Not all of the PSD image modes are supported in
PIL.Image.Image. For example, 'CMYK' mode cannot include alpha channel
in PIL. In this case, topil drops alpha channel.
- class
psd_tools.api.layers.Group(parent: GroupMixin, record: LayerRecord
<#psd_tools.psd.layer_and_mask.LayerRecord>, channels: ChannelDataList
<#psd_tools.psd.layer_and_mask.ChannelDataList>)
- Group of layers.
Example:
group = psd[1]
for layer in group:
if layer.kind == 'pixel':
print(layer.name)
- composite(viewport:
tuple[int, int, int, int] | None = None, force: bool = False, color: float |
tuple[float, ...] | ndarray = 1.0, alpha: float | ndarray = 0.0,
layer_filter: Callable | None = None, apply_icc: bool = True) -> Image |
None
- Composite layer and masks (mask, vector mask, and clipping layers).
- Parameters
- viewport -- Viewport bounding box specified by (x1, y1, x2, y2)
tuple. Default is the layer's bbox.
- force -- Boolean flag to force vector drawing.
- color -- Backdrop color specified by scalar or tuple of scalar. The
color value should be in [0.0, 1.0]. For example, (1., 0., 0.) specifies
red in RGB color mode.
- alpha -- Backdrop alpha in [0.0, 1.0].
- layer_filter -- Callable that takes a layer as argument and returns
whether if the layer is composited. Default is is_visible().
- Returns
- PIL.Image.Image.
- class
psd_tools.api.layers.TypeLayer(*args: Any)
- Layer that has text and styling information for fonts or paragraphs.
Text is accessible at text property. Styling
information for paragraphs is in engine_dict. Document styling
information such as font list is is resource_dict.
Currently, textual information is read-only.
Example:
if layer.kind == 'type':
print(layer.text)
print(layer.engine_dict['StyleRun'])
# Extract font for each substring in the text.
text = layer.engine_dict['Editor']['Text'].value
fontset = layer.resource_dict['FontSet']
runlength = layer.engine_dict['StyleRun']['RunLengthArray']
rundata = layer.engine_dict['StyleRun']['RunArray']
index = 0
for length, style in zip(runlength, rundata):
substring = text[index:index + length]
stylesheet = style['StyleSheet']['StyleSheetData']
font = fontset[stylesheet['Font']]
print('%r gets %s' % (substring, font))
index += length
- property
text: str
- Text in the layer. Read-only.
Note:
New-line character in Photoshop is '\r'.
- property
transform: tuple[float, float, float, float, float, float]
- Matrix (xx, xy, yx, yy, tx, ty) applies affine transformation.
- class
psd_tools.api.mask.Mask(layer: LayerProtocol)
- Mask data attached to a layer.
There are two distinct internal mask data: user mask and
vector mask. User mask refers any pixel-based mask whereas vector mask
refers a mask from a shape path. Internally, two masks are combined and
referred real mask.
Shape module.
In PSD/PSB, shapes are all represented as VectorMask in
each layer, and optionally there might be Origination object to
control live shape properties and Stroke to specify how outline is
stylized.
- class
psd_tools.api.shape.VectorMask(data: VectorMaskSetting
<#psd_tools.psd.vector.VectorMaskSetting>)
- Vector mask data.
Vector mask is a resolution-independent mask that consists of
one or more Path objects. In Photoshop, all the path objects are
represented as Bezier curves. Check paths property for how to
deal with path objects.
- property
paths: list[Subpath <#psd_tools.psd.vector.Subpath>]
- List of Subpath <#psd_tools.psd.vector.Subpath>. Subpath is a
list-like structure that contains one or more Knot
<#psd_tools.psd.vector.Knot> items. Knot contains relative
coordinates of control points for a Bezier curve. index
<#psd_tools.psd.vector.Subpath.index> indicates which origination
item the subpath belongs, and operation
<#psd_tools.psd.vector.Subpath.operation> indicates how to combine
multiple shape paths.
In PSD, path fill rule is even-odd.
Example:
for subpath in layer.vector_mask.paths:
anchors = [(
int(knot.anchor[1] * psd.width),
int(knot.anchor[0] * psd.height),
) for knot in subpath]
Origination keeps live shape properties for some of the primitive
shapes. Origination objects are accessible via origination
<#psd_tools.api.layers.Layer.origination> property of layers.
Following primitive shapes are defined: Invalidated, Line,
Rectangle, Ellipse, and RoundedRectangle.
- class
psd_tools.api.smart_object.SmartObject(layer: LayerProtocol)
- Smart object that represents embedded or external file.
Smart objects are attached to SmartObjectLayer
<#psd_tools.api.layers.SmartObjectLayer>.
- property
transform_box: tuple[float, float, float, float, float, float, float, float]
| None
- A tuple representing the coordinates of the smart objects's transformed
box. This box is the result of one or more transformations such as
scaling, rotation, translation, or skewing to the original bounding box of
the smart object.
The format of the tuple is as follows: (x1, y1, x2, y2, x3,
y3, x4, y4)
Where 1 is top left corner, 2 is top right, 3 is bottom right
and 4 is bottom left.
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
- psd_tools.composite.composite(group:
Layer <#psd_tools.api.layers.Layer> | PSDImage
<#psd_tools.api.psd_image.PSDImage>, color: float | tuple[float, ...]
| ndarray = 1.0, alpha: float | ndarray = 0.0, viewport: tuple[int, int,
int, int] | None = None, layer_filter: Callable | 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.
- Args:
- group: Layer or PSDImage to composite color: Initial backdrop color
(0.0-1.0, default: 1.0). Can be:
- Scalar (float): Applied to all channels
- Tuple: Per-channel values (R,G,B or C,M,Y,K)
- ndarray: Full backdrop image
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:
- 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:
- Adjustment layers have limited support
- Text rendering is not supported (text layers show as raster if
available)
- psd_tools.composite.composite_pil(layer:
Layer <#psd_tools.api.layers.Layer> | PSDImage
<#psd_tools.api.psd_image.PSDImage>, color: float | tuple[float, ...]
| ndarray, alpha: float | ndarray, viewport: tuple[int, int, int, int] |
None, layer_filter: Callable | 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.
- Args:
- 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 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
<#psd_tools.constants.BlendMode> enums to their corresponding
functions for easy lookup during compositing.
- 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.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.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.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 shapes and path operations for compositing.
Vector shape and path rendering using aggdraw for bezier curve
rasterization.
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.
Image compression utilities for PSD channel data.
This subpackage provides compression and decompression codecs for
raw pixel data in PSD files. Adobe Photoshop supports multiple compression
methods for channel data to reduce file size.
Supported compression methods:
- RAW (Compression.RAW): Uncompressed raw pixel data
- RLE (Compression.RLE): Apple PackBits run-length
encoding
- ZIP (Compression.ZIP): ZIP/Deflate compression without
prediction
- ZIP_WITH_PREDICTION (Compression.ZIP_WITH_PREDICTION): ZIP
with delta encoding
The RLE codec includes both a pure Python implementation and a
Cython-optimized version (_rle.pyx) that provides significant
performance improvements. The Cython version is used automatically when
available, with graceful fallback to pure Python.
Key functions:
- compress(): Compress raw pixel data using specified method
- decompress(): Decompress pixel data back to raw bytes
- encode_rle(): RLE encoding for a single channel
- decode_rle(): RLE decoding for a single channel
Example usage:
from psd_tools.compression import compress, decompress
from psd_tools.constants import Compression
# Compress raw channel data
compressed = compress(
data=raw_pixels,
compression=Compression.RLE,
width=100,
height=100,
depth=8,
version=1
)
# Decompress back to raw data
raw_pixels = decompress(
data=compressed,
compression=Compression.RLE,
width=100,
height=100,
depth=8,
version=1
)
Performance notes:
- RLE is most effective for images with large uniform areas
- ZIP with prediction works well for continuous-tone images
- The Cython RLE codec can be 10-100x faster than pure Python
- Compression method is chosen per-channel when saving PSD files
The compression module handles various bit depths (8, 16, 32-bit
per channel) and implements delta encoding for improved compression ratios
on certain image types.
This module provides compression and decompression codecs for PSD
channel data.
The RLE (Run-Length Encoding) codec implements Apple PackBits
compression. A Cython-optimized version (_rle.pyx) is used when
available, providing 10-100x performance improvement over the pure Python
fallback.
Prediction encoding applies delta compression before ZIP
compression, improving compression ratios for continuous-tone images.
Supports 8, 16, and 32-bit depths.
Various constants for psd_tools
Low-level API that translates binary data to Python structure.
All the data structure in this subpackage inherits from one of the
object defined in psd_tools.psd.base
<#module-psd_tools.psd.base> module.
Base data structures intended for inheritance.
All the data objects in this subpackage inherit from the base
classes here. That means, all the data structures in the
psd_tools.psd <#module-psd_tools.psd> subpackage implements the
methods of BaseElement for serialization and decoding.
Objects that inherit from the BaseElement typically gets
attrs <https://www.attrs.org/en/stable/index.html> decoration to have
data fields.
Color mode data structure.
Descriptor data structure.
Descriptors are basic data structure used throughout PSD files.
Descriptor is one kind of serialization protocol for data objects, and enum
classes in psd_tools.terminology
<#module-psd_tools.terminology> or bytes indicates what kind of
descriptor it is.
The class ID can be pre-defined enum if the tag is 4-byte length
or plain bytes if the length is arbitrary. They depend on the internal
version of Adobe Photoshop but the detail is unknown.
Pretty printing is the best approach to check the descriptor
content:
from IPython.pretty import pprint
pprint(descriptor)
EngineData structure.
PSD file embeds text formatting data in its own markup language
referred EngineData. The format looks like the following:
<<
/EngineDict
<<
/Editor
<<
/Text (˛ˇMake a change and save.)
>>
>>
/Font
<<
/Name (˛ˇHelveticaNeue-Light)
/FillColor
<<
/Type 1
/Values [ 1.0 0.0 0.0 0.0 ]
>>
/StyleSheetSet [
<<
/Name (˛ˇNormal RGB)
>>
]
>>
>>
Effects layer structure.
Note the structures in this module is obsolete and object-based
layer effects are stored in tagged blocks.
- class
psd_tools.psd.effects_layer.ShadowInfo(version: int = 0, blur: int = 0,
intensity: int = 0, angle: int = 0, distance: int = 0, color: Color =
NOTHING, blend_mode=BlendMode.NORMAL, enabled: int = 0, use_global_angle:
int = 0, opacity: int = 0, native_color: Color = NOTHING)
- Effects layer shadow info.
- class
psd_tools.psd.effects_layer.BevelInfo(version: int = 0, angle: int = 0,
depth: int = 0, blur: int = 0, highlight_blend_mode=BlendMode.NORMAL,
shadow_blend_mode=BlendMode.NORMAL, highlight_color: Color = NOTHING,
shadow_color: Color = NOTHING, bevel_style: int = 0, highlight_opacity: int
= 0, shadow_opacity: int = 0, enabled: int = 0, use_global_angle: int = 0,
direction: int = 0, real_highlight_color: object = None, real_shadow_color:
object = None)
- Effects layer bevel info.
Filter effects structure.
- class
psd_tools.psd.header.FileHeader(signature: bytes = b'8BPS', version: int =
1, channels: int = 4, height: int = 64, width: int = 64, depth: int = 8,
color_mode=ColorMode.RGB)
- Header section of the PSD file.
Example:
from psd_tools.psd.header import FileHeader
from psd_tools.constants import ColorMode
header = FileHeader(channels=2, height=359, width=400, depth=8,
color_mode=ColorMode.GRAYSCALE)
- signature
- Signature: always equal to b'8BPS'.
- version
- Version number. PSD is 1, and PSB is 2.
- channels
- The number of channels in the image, including any user-defined alpha
channel.
- height
- The height of the image in pixels.
- width
- The width of the image in pixels.
- depth
- The number of bits per channel.
- color_mode
- The color mode of the file. See ColorMode
<#psd_tools.constants.ColorMode>
Image data section structure.
ImageData corresponds to the last section of the PSD/PSB
file where a composited image is stored. When the file does not contain
layers, this is the only place pixels are saved.
Image resources section structure. Image resources are used to
store non-pixel data associated with images, such as pen tool paths or
slices.
See Resource <#psd_tools.constants.Resource> to check
available resource names.
Example:
from psd_tools.constants import Resource
version_info = psd.image_resources.get_data(Resource.VERSION_INFO)
The following resources are plain bytes:
Resource.OBSOLETE1: 1000
Resource.MAC_PRINT_MANAGER_INFO: 1001
Resource.MAC_PAGE_FORMAT_INFO: 1002
Resource.OBSOLETE2: 1003
Resource.DISPLAY_INFO_OBSOLETE: 1007
Resource.BORDER_INFO: 1009
Resource.DUOTONE_IMAGE_INFO: 1018
Resource.EFFECTIVE_BW: 1019
Resource.OBSOLETE3: 1020
Resource.EPS_OPTIONS: 1021
Resource.QUICK_MASK_INFO: 1022
Resource.OBSOLETE4: 1023
Resource.WORKING_PATH: 1025
Resource.OBSOLETE5: 1027
Resource.IPTC_NAA: 1028
Resource.IMAGE_MODE_RAW: 1029
Resource.JPEG_QUALITY: 1030
Resource.URL: 1035
Resource.COLOR_SAMPLERS_RESOURCE_OBSOLETE: 1038
Resource.ICC_PROFILE: 1039
Resource.SPOT_HALFTONE: 1043
Resource.JUMP_TO_XPEP: 1052
Resource.EXIF_DATA_1: 1058
Resource.EXIF_DATA_3: 1059
Resource.XMP_METADATA: 1060
Resource.CAPTION_DIGEST: 1061
Resource.ALTERNATE_DUOTONE_COLORS: 1066
Resource.ALTERNATE_SPOT_COLORS: 1067
Resource.HDR_TONING_INFO: 1070
Resource.PRINT_INFO_CS2: 1071
Resource.COLOR_SAMPLERS_RESOURCE: 1073
Resource.DISPLAY_INFO: 1077
Resource.MAC_NSPRINTINFO: 1084
Resource.WINDOWS_DEVMODE: 1085
Resource.PATH_INFO_N: 2000-2999
Resource.PLUGIN_RESOURCES_N: 4000-4999
Resource.IMAGE_READY_VARIABLES: 7000
Resource.IMAGE_READY_DATA_SETS: 7001
Resource.IMAGE_READY_DEFAULT_SELECTED_STATE: 7002
Resource.IMAGE_READY_7_ROLLOVER_EXPANDED_STATE: 7003
Resource.IMAGE_READY_ROLLOVER_EXPANDED_STATE: 7004
Resource.IMAGE_READY_SAVE_LAYER_SETTINGS: 7005
Resource.IMAGE_READY_VERSION: 7006
Resource.LIGHTROOM_WORKFLOW: 8000
- class
psd_tools.psd.image_resources.SliceV6(slice_id: int = 0, group_id: int = 0,
origin: int = 0, associated_id: int | None = None, name: str = '',
slice_type: int = 0, bbox=NOTHING, url: str = '', target: str = '', message:
str = '', alt_tag: str = '', cell_is_html: bool = False, cell_text: str =
'', horizontal_align: int = 0, vertical_align: int = 0, alpha: int = 0, red:
int = 0, green: int = 0, blue: int = 0, data: object = None)
- Slice element for version 6.
Layer and mask data structures.
This module implements the low-level binary structures for PSD
layers and masks, corresponding to the "Layer and Mask
Information" section of PSD files. This is one of the most complex
parts of the PSD format.
Key classes:
- LayerAndMaskInformation: Top-level container for all layer
data
- LayerInfo: Contains layer records and channel image data
- LayerRecords: List of individual layer records
- LayerRecord: Single layer metadata (name, bounds, blend mode,
etc.)
- ChannelInfo: Channel metadata within a layer record
- ChannelImageData: Compressed pixel data for all channels
- ChannelData: Single channel's compressed pixel data
- MaskData: Layer mask parameters
- GlobalLayerMaskInfo: Document-wide mask settings
- TaggedBlocks: Extended layer metadata (see
psd_tools.psd.tagged_blocks
<#module-psd_tools.psd.tagged_blocks>)
The layer structure in PSD files is stored as a flat list with
implicit hierarchy. Group boundaries are marked by special layers with
SectionDivider tagged blocks:
- BOUNDING_SECTION_DIVIDER: Marks the start of a group (the layer
that opens the group)
- OPEN_FOLDER or CLOSED_FOLDER: Marks the end of a group (the
closing divider layer) - OPEN_FOLDER: Group was open in Photoshop
UI - CLOSED_FOLDER: Group was closed in Photoshop UI
The high-level API (psd_tools.api
<#module-psd_tools.api>) reconstructs this into a proper tree
structure with parent-child relationships.
Each layer record contains:
- 1.
- Metadata: Rectangle bounds, blend mode, opacity, flags
- 2.
- Channel info: List of channels (R, G, B, A, masks, etc.) with byte
offsets
- 3.
- Blend ranges: Advanced blending parameters
- 4.
- Layer name: Pascal string (legacy, inaccurate for Unicode
names)
- 5.
- Tagged blocks: Extended metadata in key-value format
The channel image data section follows all layer records and
contains the actual compressed pixel data for each channel, referenced by
the channel info structures.
Example of reading layer metadata:
from psd_tools.psd import PSD
with open('file.psd', 'rb') as f:
psd = PSD.read(f)
layer_info = psd.layer_and_mask_information.layer_info
for record in layer_info.layer_records:
print(f"Layer: {record.name}")
print(f" Bounds: {record.top}, {record.left}, {record.bottom}, {record.right}")
print(f" Blend mode: {record.blend_mode}")
print(f" Channels: {len(record.channel_info)}")
For most use cases, prefer the high-level Layer
<#psd_tools.api.layers.Layer> API which provides easier access to this
data.
- class
psd_tools.psd.layer_and_mask.LayerRecord(top: int = 0, left: int = 0,
bottom: int = 0, right: int = 0, channel_info: list[ChannelInfo
<#psd_tools.psd.layer_and_mask.ChannelInfo>] = NOTHING, signature:
bytes = b'8BIM', blend_mode=BlendMode.NORMAL, opacity: int = 255,
clipping=Clipping.BASE, flags: LayerFlags
<#psd_tools.psd.layer_and_mask.LayerFlags> = NOTHING, mask_data:
object = None, blending_ranges: LayerBlendingRanges
<#psd_tools.psd.layer_and_mask.LayerBlendingRanges> = NOTHING, name:
str = '', tagged_blocks: TaggedBlocks
<#psd_tools.psd.tagged_blocks.TaggedBlocks> = NOTHING)
- Layer record.
- blend_mode
- Blend mode key. See BlendMode
<#psd_tools.constants.BlendMode>.
- opacity
- Opacity, 0 = transparent, 255 = opaque.
- clipping
- Clipping, 0 = base, 1 = non-base. See Clipping
<#psd_tools.constants.Clipping>.
- tagged_blocks
- See TaggedBlocks
<#psd_tools.psd.tagged_blocks.TaggedBlocks>.
- class
psd_tools.psd.layer_and_mask.MaskData(top: int = 0, left: int = 0, bottom:
int = 0, right: int = 0, background_color: int = 0, flags: MaskFlags
<#psd_tools.psd.layer_and_mask.MaskFlags> = NOTHING, parameters:
object = None, real_flags: object = None, real_background_color: int | None
= None, real_top: int | None = None, real_left: int | None = None,
real_bottom: int | None = None, real_right: int | None = None)
- Mask data.
Real user mask is a final composite mask of vector and pixel
masks.
- real_top
- Top position of real user mask.
- class
psd_tools.psd.linked_layer.LinkedLayer(kind: LinkedLayerType
<#psd_tools.constants.LinkedLayerType> = LinkedLayerType.ALIAS,
version: int = 1, uuid: str = '', filename: str = '', filetype: bytes =
b'\x00\x00\x00\x00', creator: bytes = b'\x00\x00\x00\x00', filesize: int |
None = None, open_file: DescriptorBlock | None = None, linked_file:
DescriptorBlock | None = None, timestamp: tuple | None = None, data: bytes |
None = None, child_id: str | None = None, mod_time: float | None = None,
lock_state: int | None = None)
- LinkedLayer structure.
- class
psd_tools.psd.patterns.Pattern(version: int = 1, image_mode=ColorMode.RGB,
point: tuple[int, int] = (0, 0), name: str = '', pattern_id: str = '',
color_table: list[tuple[int, int, int]] = NOTHING, data:
VirtualMemoryArrayList
<#psd_tools.psd.patterns.VirtualMemoryArrayList> = None)
- Pattern structure.
- name
- str name of the pattern.
- data
- See VirtualMemoryArrayList
Tagged block data structure.
Support the following tagged blocks: Tag.PATTERN_DATA,
Tag.TYPE_TOOL_INFO, Tag.LAYER, Tag.ALPHA
- class
psd_tools.psd.tagged_blocks.TaggedBlocks(items=NOTHING)
- Dict of tagged block items.
See Tag <#psd_tools.constants.Tag> for available
keys.
Example:
from psd_tools.constants import Tag
# Iterate over fields
for key in tagged_blocks:
print(key)
# Get a field
value = tagged_blocks.get_data(Tag.TYPE_TOOL_OBJECT_SETTING)
- class
psd_tools.psd.tagged_blocks.Annotation(kind: bytes = b'txtA', is_open: int =
0, flags: int = 0, optional_blocks: int = 1, icon_location=NOTHING,
popup_location=NOTHING, color: Color = NOTHING, author: str = '', name: str
= '', mod_date: str = '', marker: bytes = b'txtC', data: bytes =
b'')
- Annotation structure.
- class
psd_tools.psd.tagged_blocks.PlacedLayerData(kind: bytes = b'plcL', version:
int = 3, uuid: bytes = b'', page: int = 0, total_pages: int = 0, anti_alias:
int = 0, layer_type=PlacedLayerType.UNKNOWN, transform: tuple = (0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0), warp: object = None)
- PlacedLayerData structure.
- class
psd_tools.psd.tagged_blocks.TypeToolObjectSetting(version: int = 1,
transform: tuple = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0), text_version: int = 1,
text_data: DescriptorBlock = NOTHING, warp_version: int = 1, warp:
DescriptorBlock = NOTHING, left: int = 0, top: int = 0, right: int = 0,
bottom: int = 0)
- TypeToolObjectSetting structure.
- transform
- Tuple of affine transform parameters (xx, xy, yx, yy, tx, ty).
Vector mask, path, and stroke structure.
Constants for descriptor.
This file is automaticaly generated by
tools/extract_terminology.py