Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a440496
feat: Add extensive type annotations, add classes for path handling, …
pappnu Aug 12, 2025
a2b308f
fix(Layer): return parent with correct type
pappnu Aug 13, 2025
2089887
fix(LayerSet): fix iterating LayerSet object directly
pappnu Aug 13, 2025
6d7429f
test(TestNewDocument): test getting layer parent and expand layer ite…
pappnu Aug 13, 2025
121328a
feat: add py.typed file to indicate that the package contains type an…
pappnu Aug 13, 2025
94d4344
fix(executeActionGet): correct input argument type
pappnu Aug 13, 2025
a8dedc4
fix(Application): executeAction can be called without an ActionDescri…
pappnu Aug 14, 2025
0b975b9
fix(Select): select takes an array of four coordinates
pappnu Aug 14, 2025
9d8de09
style: Format and sort imports
pappnu Aug 15, 2025
1a7b953
feat(Layer): Add default value to rotate anchor
pappnu Aug 15, 2025
e790d52
fix(Documents): Document width and height can be given as floats, tho…
pappnu Aug 16, 2025
9f8496e
fix(path_item.py): Import TYPE_CHECKING from typing instead of errone…
pappnu Aug 26, 2025
15309e3
refactor: Avoid unnecessary getattribute calls to the wrapper objects
pappnu Feb 15, 2026
22a673d
test(manual_test_new_document.py): Add tests for layer querying
pappnu Feb 15, 2026
f6c72c3
feat: Bump Python requirement to >=3.10, modernize pyproject.toml, up…
pappnu Feb 15, 2026
cee5fe1
docs(README.md): Instruct how to browse the COM API with Visual Studio
pappnu Feb 15, 2026
4430335
fix(Selection): Selection's select can take a sequence of more than f…
pappnu Feb 22, 2026
4acf07c
fix(FontSize): Don't use StrEnum as Python 3.10 doesn't have it
pappnu Feb 25, 2026
53965a6
fix(Notifiers): Access the Photoshop Application object correctly
pappnu Feb 25, 2026
4cd7f34
feat(Photoshop): Allow accessing the Photoshop Application object fro…
pappnu Feb 25, 2026
4fc7bcc
fix(import-test.yml): Fix and update test workflow
pappnu May 18, 2026
876d3f8
Merge remote-tracking branch 'origin/main' into type-annotation
pappnu May 18, 2026
e3dd8ae
build(pyproject.toml): Align and simplify Ruff and isort rules
pappnu May 19, 2026
3a61b25
build(.flake8): Ignore flake8 rule that clashes with Black formatting
pappnu May 19, 2026
7050109
style: Autoformat code
pappnu May 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .flake8
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[flake8]
ignore = BLK100
ignore = BLK100,W503

# flake8-quotes:
# Use double quotes as our default to comply with black, we like it and
Expand All @@ -15,7 +15,7 @@ avoid-escape = True
# Use the Google Python Styleguide Docstring format.
docstring-convention = google

exclude =
exclude =
.git,
__pycache__,
docs/source/conf.py,
Expand Down
22 changes: 11 additions & 11 deletions .github/workflows/import-test.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
name: Import Test
on: [pull_request]
on:
pull_request:
workflow_dispatch:

jobs:
python-check:
runs-on: windows-2022
runs-on: windows-latest
strategy:
max-parallel: 3
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
- uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install -U pip poetry
poetry --version
poetry install -vvv || poetry install -vvv || poetry install -vvv
poetry run pytest
python -m pip install poetry
python -m poetry --version
python -m poetry install -vvv
python -m poetry run pytest
2 changes: 1 addition & 1 deletion .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
strategy:
max-parallel: 3
matrix:
python-version: ["3.8", "3.9", "3.10"]
python-version: ["3.10"]

steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
Expand Down
2 changes: 0 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
language: python
python:
- "3.8"
- "3.9"
- "3.10"
before_script:
- pip install poetry
Expand Down
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ Useful links
- http://shining-lucy.com/wiki/page.php?id=appwiki:photoshop:ps_script
- http://web.archive.org/web/20140121053819/http://www.pcpix.com/Photoshop/char.html
- http://www.tonton-pixel.com/scripts/utility-scripts/get-equivalent-id-code/index.html
- https://github.com/Adobe-CEP/CEP-Resources/tree/master/Documentation/Product%20specific%20Documentation/Photoshop%20Scripting
- https://github.com/Adobe-CEP/Samples/tree/master/PhotoshopEvents
- https://evanmccall.wordpress.com/2015/03/09/how-to-develop-photoshop-tools-in-python

Expand Down Expand Up @@ -279,7 +280,7 @@ We value thorough testing to ensure reliability:

### 📦 Development Environment

- **Python Versions**: We support Python 3.8+ (see `pyproject.toml` for specifics)
- **Python Versions**: We support Python 3.10+ (see `pyproject.toml` for specifics)
- **Dependency Management**: [Poetry](https://python-poetry.org/) for consistent environments
- **Virtual Environment**: Poetry automatically creates and manages virtual environments

Expand All @@ -295,3 +296,18 @@ We value thorough testing to ensure reliability:
8. **Merge**: Once approved, your PR will be merged

Thank you for contributing to the Photoshop Python API project! 🎉

### Development Notes

One way to view the COM API structure is to use Visual Studio.

1. Install Visual Studio with the .NET desktop development workload
2. Open Visual Studio
3. Choose View -> Object Browser
4. In the Object Browser from the Browse dropdown choose *Edit Custom Component Set...*
5. In the Edit Custom Component Set window's COM tab add Photoshop's Object and Type libraries
6. After pressing OK the libraries should show up in the Object Browser

If the libraries aren't visible in the COM tab make sure you have the .NET desktop development workload installed. Other things you may try are running Photoshop once as administrator and reinstalling Photoshop and/or your PC. The object library is defined usually in */your/path/to/Adobe Photoshop \<version\>/Required/Plug-ins/Extensions/ScriptingSupport.8li*

The [VBS reference 2020](https://github.com/Adobe-CEP/CEP-Resources/blob/master/Documentation/Product%20specific%20Documentation/Photoshop%20Scripting/photoshop-vbs-ref-2020.pdf) is also quite trustworthy, since Adobe isn't actively developing the COM API anymore.
2 changes: 1 addition & 1 deletion docs/gen_api_nav.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def main():
full_doc_path = full_doc_path.as_posix().replace("\\", "/")
with mkdocs_gen_files.open(full_doc_path, "w") as fd:
ident = ".".join(parts)
print(f"::: " + ident, file=fd)
print("::: " + ident, file=fd)

mkdocs_gen_files.set_edit_path(full_doc_path, path.as_posix().replace("\\", "/"))

Expand Down
4 changes: 3 additions & 1 deletion docs/gen_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

# Import built-in modules
import os

from pathlib import Path

# Import third-party modules
from jinja2 import Template
import mkdocs_gen_files
import stringcase

from jinja2 import Template


template = Template(
r"""
Expand Down
4 changes: 2 additions & 2 deletions examples/active_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
docRef.artLayers.add()

# Display current active layer name
ps.echo(docRef.activeLayer.name)
print(docRef.activeLayer.name)

# Create and rename a new layer
new_layer = docRef.artLayers.add()
ps.echo(new_layer.name)
print(new_layer.name)
new_layer.name = "test"
2 changes: 1 addition & 1 deletion examples/add_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@
doc.info.provinceState = "Beijing"
doc.info.title = "My Demo"
print("Print all metadata of current active document.")
ps.echo(doc.info)
print(doc.info)
6 changes: 3 additions & 3 deletions examples/add_slate.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
"""

# Import built-in modules
from datetime import datetime
import os

from datetime import datetime
from tempfile import mkdtemp

# Import third-party modules
# Import local modules
import examples._psd_files as psd # Import from examples.

# Import local modules
from photoshop import Session


Expand Down
1 change: 1 addition & 0 deletions examples/add_start_application_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

# Import built-in modules
import os

from tempfile import mkdtemp

# Import local modules
Expand Down
3 changes: 1 addition & 2 deletions examples/apply_crystallize_filter_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@

"""

# Import third-party modules
# Import local modules
import examples._psd_files as psd # Import from examples.

# Import local modules
from photoshop import Session


Expand Down
4 changes: 2 additions & 2 deletions examples/change_color_of_background_and_foreground.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@
ps.app.backgroundColor = bg_color

# Print current colors
ps.echo(f"Foreground RGB: {ps.app.foregroundColor.rgb.red}, "
print(f"Foreground RGB: {ps.app.foregroundColor.rgb.red}, "
f"{ps.app.foregroundColor.rgb.green}, "
f"{ps.app.foregroundColor.rgb.blue}")

ps.echo(f"Background RGB: {ps.app.backgroundColor.rgb.red}, "
print(f"Background RGB: {ps.app.backgroundColor.rgb.red}, "
f"{ps.app.backgroundColor.rgb.green}, "
f"{ps.app.backgroundColor.rgb.blue}")
2 changes: 1 addition & 1 deletion examples/compare_colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@
color1.rgb.green == color2.rgb.green and
color1.rgb.blue == color2.rgb.blue)

ps.echo(f"Colors are {'same' if is_same else 'different'}")
print(f"Colors are {'same' if is_same else 'different'}")
6 changes: 3 additions & 3 deletions examples/convert_smartobject_to_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@

# Convert to smart object
layer.convertToSmartObject()
ps.echo("Layer converted to Smart Object")
print("Layer converted to Smart Object")

# Check if it's a smart object
if layer.kind == ps.LayerKind.SmartObjectLayer:
ps.echo("Layer is now a Smart Object")
print("Layer is now a Smart Object")

# Convert back to regular layer
layer.rasterize(ps.RasterizeType.EntireLayer)
ps.echo("Smart Object converted back to regular layer")
print("Smart Object converted back to regular layer")
1 change: 1 addition & 0 deletions examples/create_thumbnail.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

# Import built-in modules
import os

from tempfile import mkdtemp

# Import local modules
Expand Down
2 changes: 1 addition & 1 deletion examples/current_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@
current = ps.app.currentTool

# Print current tool name
ps.echo(f"Current tool: {current}")
print(f"Current tool: {current}")
9 changes: 5 additions & 4 deletions examples/delete_and_fill_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
import os

# Import third-party modules
from photoshop import Session
from photoshop.api import SolidColor
import photoshop.api as ps
from _psd_files import get_psd_files

# Import local modules
from _psd_files import get_psd_files
import photoshop.api as ps

from photoshop.api import SolidColor


def delete_and_fill_selection(doc, fill_type, mode=None, opacity=None, preserve_transparency=None):
"""Delete current selection and fill it with specified color.
Expand Down
2 changes: 1 addition & 1 deletion examples/eval_javascript.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@
# Execute JavaScript command
js_code = "app.documents.length"
result = ps.app.eval_javascript(js_code)
ps.echo(f"Number of open documents: {result}")
print(f"Number of open documents: {result}")
1 change: 1 addition & 0 deletions examples/export_artboards.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""
# Import built-in modules
import os.path

from pathlib import Path
from typing import List
from typing import Union
Expand Down
4 changes: 2 additions & 2 deletions examples/export_document_with_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
# Import built-in modules
import os

# Import third-party modules
# Import local modules
from photoshop import Session
from photoshop.api.enumerations import DitherType
from photoshop.api.enumerations import ExportType
from photoshop.api.enumerations import SaveDocumentType
from photoshop.api.save_options.png import PNGSaveOptions
from photoshop.api.save_options.jpg import JPEGSaveOptions
from photoshop.api.save_options.png import ExportOptionsSaveForWeb
from photoshop.api.save_options.png import PNGSaveOptions


def main():
Expand Down
5 changes: 2 additions & 3 deletions examples/export_layers_use_export_options_saveforweb.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
# Import built-in modules
import os

# Import third-party modules
# Import local modules
import examples._psd_files as psd # Import from examples.

# Import local modules
from photoshop import Session


Expand Down Expand Up @@ -33,7 +32,7 @@ def main():
image_path = os.path.join(layer_path, f"{layer.name}.png")
doc.exportDocument(image_path, exportAs=ps.ExportType.SaveForWeb, options=options)
ps.alert("Task done!")
ps.echo(doc.activeLayer)
print(doc.activeLayer)


if __name__ == "__main__":
Expand Down
4 changes: 2 additions & 2 deletions examples/get_document_by_name.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
# Try to get document named 'test.psd'
for doc in ps.app.documents:
if doc.name == "test.psd":
ps.echo(doc.name)
print(doc.name)
break
else:
ps.echo("Document not found!")
print("Document not found!")
2 changes: 1 addition & 1 deletion examples/get_layer_by_name.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@
doc = ps.app.activeDocument
for layer in doc.layers:
if layer.name == "example layer":
ps.echo(layer.name)
print(layer.name)
break
6 changes: 3 additions & 3 deletions examples/link_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@
layer2.link(layer3)

# Check link status
ps.echo(f"Layer 1 linked: {layer1.linked}")
ps.echo(f"Layer 2 linked: {layer2.linked}")
ps.echo(f"Layer 3 linked: {layer3.linked}")
print(f"Layer 1 linked: {layer1.linked}")
print(f"Layer 2 linked: {layer2.linked}")
print(f"Layer 3 linked: {layer3.linked}")

# Move linked layers together
layer1.translate(100, 100)
4 changes: 2 additions & 2 deletions examples/load_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,5 @@
doc.selection.combine(doc.channels[-1], ps.SelectionType.ExtendSelection)

# Clean up - delete added channels
doc.channels[-1].remove()
doc.channels[-1].remove()
doc.channels[-1].delete()
doc.channels[-1].delete()
5 changes: 3 additions & 2 deletions examples/open_psd.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# Import local modules
from photoshop import Session
import photoshop.api as ps

from photoshop import Session


# style 1
app = ps.Application()
app.load("your/psd/or/psb/file_path.psd")

# style 2
with Session("your/psd/or/psb/file_path.psd", action="open") as ps:
ps.echo(ps.active_document.name)
print(ps.active_document.name)
2 changes: 1 addition & 1 deletion examples/operate_channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

# List all channels
for channel in doc.channels:
ps.echo(f"Channel: {channel.name}")
print(f"Channel: {channel.name}")

# Create a new alpha channel
new_channel = doc.channels.add()
Expand Down
4 changes: 2 additions & 2 deletions examples/operate_layerSet.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@

# List layers in groups
for layer in main_group.layers:
ps.echo(f"Layer in main group: {layer.name}")
print(f"Layer in main group: {layer.name}")

for layer in sub_group.layers:
ps.echo(f"Layer in sub group: {layer.name}")
print(f"Layer in sub group: {layer.name}")

# Move a layer between groups
layer1.move(sub_group, ps.ElementPlacement.INSIDE)
Loading