├── .gitignore ├── ActiveLayer.py ├── ApplyCrystallizeFilterAction.py ├── ApplyFilters.py ├── CompareColors.py ├── ConvertColor.py ├── CopyAndPaste.py ├── CopyAndRotate.py ├── EmbossAction.py ├── ExecuteMoltenLead.py ├── FillSelection.py ├── HelloWorld.py ├── ImportDataSetsAction.py ├── LayerKind.py ├── LinkLayer.py ├── LoadSelection.py ├── MakeSelection.py ├── NewDocument.py ├── OpenDocument.py ├── PS_Samples_Files ├── CheeziPuffs.mov ├── Doors.dng ├── Ducky.tif ├── Fish.psd ├── Layer Comps.psd ├── Merge to HDR │ ├── 1.jpg │ ├── 2.jpg │ ├── 3.jpg │ ├── 4.jpg │ └── Result │ │ └── HDR.psd ├── Photomerge │ ├── Result │ │ └── Panorama.jpg │ ├── photo1.jpg │ ├── photo2.jpg │ └── photo3.jpg ├── Smart Objects.psd ├── Vanishing Point.psd └── ps_data_sets.txt ├── README.md ├── RotateLayer.py ├── SelectTool.py ├── SelectionStroke.py ├── SmartSharpen.py ├── WorkingWithText.py ├── api_reference ├── photoshop_2020.py ├── photoshop_2021.py ├── photoshop_CC_2018.py └── photoshop_CC_2019.py ├── gui_tool_example ├── README.md ├── app.py ├── resizer_ui.py └── resizer_ui.ui ├── interop assemblies ├── Interop.Photoshop.2020.dll ├── Interop.Photoshop.cc2019.dll └── README.md ├── mac_scripting ├── ActiveLayer.py ├── ApplyCrystallizeFilterAction.py ├── ApplyFilters.py ├── CompareColors.py ├── ConvertColor.py ├── CopyAndPaste.py ├── CopyAndRotate.py ├── EmbossAction.py ├── FillSelection.py ├── HelloWorld.py ├── LayerKind.py ├── LinkLayer.py ├── LoadSelection.py ├── MakeSelection.py ├── NewDocument.py ├── OpenDocument.py ├── README.md ├── RotateLayer.py ├── SelectTool.py ├── SelectionStroke.py ├── SmartSharpen.py ├── WorkingWithText.py └── doc_reference │ └── PhotoshopCC2018_docs_reference_appscript.pdf └── ps.py /.gitignore: -------------------------------------------------------------------------------- 1 | .git 2 | .idea 3 | env 4 | venv 5 | pscc2018.pyc 6 | my-test.py 7 | .DS_Store 8 | ReadMeFirst.txt 9 | 10 | 11 | # Default stuff for Github, Python 12 | # Byte-compiled / optimized / DLL files 13 | __pycache__/ 14 | *.py[cod] 15 | 16 | # C extensions 17 | *.so 18 | 19 | # Distribution / packaging 20 | .Python 21 | env/ 22 | build/ 23 | develop-eggs/ 24 | dist/ 25 | downloads/ 26 | eggs/ 27 | .eggs/ 28 | lib/ 29 | lib64/ 30 | parts/ 31 | sdist/ 32 | var/ 33 | *.egg-info/ 34 | .installed.cfg 35 | *.egg 36 | 37 | # PyInstaller 38 | # Usually these files are written by a python script from a template 39 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 40 | *.manifest 41 | *.spec 42 | 43 | # Installer logs 44 | pip-log.txt 45 | pip-delete-this-directory.txt 46 | 47 | # Unit test / coverage reports 48 | htmlcov/ 49 | .tox/ 50 | .coverage 51 | .coverage.* 52 | .cache 53 | nosetests.xml 54 | coverage.xml 55 | *,cover 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ -------------------------------------------------------------------------------- /ActiveLayer.py: -------------------------------------------------------------------------------- 1 | # Set the active layer to the last art layer of the active document, or the 2 | # first if the last is already active. 3 | 4 | from win32com.client import Dispatch, GetActiveObject, GetObject 5 | # Start up Photoshop application 6 | # app = Dispatch('Photoshop.Application') 7 | # Or get Reference to already running Photoshop application instance 8 | # app = GetObject(Class="Photoshop.Application") 9 | app = GetActiveObject("Photoshop.Application") 10 | 11 | if len(app.Documents) < 1: 12 | docRef = app.Documents.Add() 13 | else: 14 | docRef = app.ActiveDocument 15 | 16 | if len(docRef.Layers) < 2: 17 | docRef.ArtLayers.Add() 18 | 19 | activeLayerName = docRef.ActiveLayer.Name 20 | SetLayerName = '' 21 | 22 | if docRef.ActiveLayer.Name != app.ActiveDocument.Layers.Item(len(docRef.Layers)).Name: 23 | docRef.ActiveLayer = docRef.Layers.Item(len(docRef.Layers)) 24 | else: 25 | docRef.ActiveLayer = docRef.Layers.Item(1) 26 | -------------------------------------------------------------------------------- /ApplyCrystallizeFilterAction.py: -------------------------------------------------------------------------------- 1 | # This script demonstrates how you can use the action manager 2 | # to execute the Crystallize filter. 3 | # In order to find all the IDs, see https://helpx.adobe.com/photoshop/kb/downloadable-plugins-and-content.html#ScriptingListenerplugin 4 | # This blog here explains what a script listener is http://blogs.adobe.com/crawlspace/2006/05/installing_and_1.html 5 | 6 | from win32com.client import Dispatch, GetActiveObject 7 | 8 | # Start up Photoshop application 9 | # app = Dispatch('Photoshop.Application') 10 | 11 | # Or get Reference to already running Photoshop instance 12 | app = GetActiveObject("Photoshop.Application") 13 | 14 | fileName = "C:\Git\photoshop-scripting-python\PS_Samples_Files\Layer Comps.psd" 15 | docRef = app.Open(fileName) 16 | 17 | nLayerSets = docRef.LayerSets 18 | nArtLayers = docRef.LayerSets.Item(len(nLayerSets)).ArtLayers 19 | 20 | # get the last layer in LayerSets 21 | docRef.ActiveLayer = docRef.LayerSets.Item(len(nLayerSets)).ArtLayers.Item(len(nArtLayers)) 22 | 23 | def applyCrystallize(cellSize): 24 | cellSizeID = app.CharIDToTypeID("ClSz") 25 | eventCrystallizeID = app.CharIDToTypeID("Crst") 26 | 27 | filterDescriptor = Dispatch('Photoshop.ActionDescriptor') 28 | filterDescriptor.PutInteger(cellSizeID, cellSize) 29 | 30 | app.ExecuteAction(eventCrystallizeID, filterDescriptor) 31 | 32 | applyCrystallize(25) 33 | 34 | -------------------------------------------------------------------------------- /ApplyFilters.py: -------------------------------------------------------------------------------- 1 | # This sample script shows how to apply 3 different filters to 2 | # selections in the open document. 3 | 4 | # from win32com.client import Dispatch, GetActiveObject, GetObject 5 | from comtypes.client import GetActiveObject, CreateObject 6 | 7 | # Start up Photoshop application 8 | # Or get Reference to already running Photoshop application instance 9 | # app = Dispatch('Photoshop.Application') 10 | app = GetActiveObject("Photoshop.Application") 11 | 12 | # We don't want any Photoshop dialogs displayed during automated execution 13 | psDisplayNoDialogs = 3 # from enum PsDialogModes 14 | app.displayDialogs = psDisplayNoDialogs 15 | 16 | psPixels = 1 17 | strtRulerUnits = app.Preferences.RulerUnits 18 | if strtRulerUnits is not psPixels: 19 | app.Preferences.RulerUnits = psPixels 20 | 21 | fileName = "C:\Git\PS_Samples_Files\Layer Comps.psd" 22 | docRef = app.Open(fileName) 23 | 24 | nLayerSets = len([(i, x) for i, x in enumerate(docRef.LayerSets, 1)]) 25 | # for some reason, len(docRef.LayerSets) return errors 26 | # So above list comprehension is same as below 27 | # nLayerSets = 0 28 | # for layerSet in docRef.LayerSets: 29 | # nLayerSets += 1 30 | 31 | nArtLayers = len([(i, x) for i, x in enumerate(docRef.LayerSets[nLayerSets].ArtLayers, 1)]) 32 | 33 | active_layer = docRef.ActiveLayer = docRef.LayerSets[nLayerSets].ArtLayers[nArtLayers] 34 | # print(docRef.ActiveLayer.Name) 35 | psReplaceSelection = 1 # from enum PsSelectionType 36 | # # sel_area argument not accepted if using win32com, using comtypes instead 37 | sel_area = ((0, 212), (300, 212), (300, 300), (0, 300)) 38 | docRef.Selection.Select(sel_area, psReplaceSelection, 20, True) 39 | psGaussianNoise = 2 # from enum PsNoiseDistribution 40 | active_layer.ApplyAddNoise(15, psGaussianNoise, False) 41 | 42 | backColor = CreateObject("Photoshop.SolidColor") 43 | backColor.HSB.Hue = 0 44 | backColor.HSB.Saturation = 0 45 | backColor.HSB.Brightness = 100 46 | app.BackgroundColor = backColor 47 | 48 | sel_area2 = ((120, 20), (210, 20), (210, 110), (120, 110)) 49 | docRef.Selection.Select(sel_area2, psReplaceSelection, 25, False) 50 | active_layer.ApplyDiffuseGlow(9, 12, 15) 51 | psTinyLensTexture = 4 # from enum PsTextureType 52 | active_layer.ApplyGlassEffect(7, 3, 7, False, psTinyLensTexture, None) 53 | docRef.Selection.Deselect() 54 | 55 | # Set ruler units back the way we found it 56 | if strtRulerUnits is not psPixels: 57 | app.Preferences.RulerUnits = strtRulerUnits 58 | 59 | -------------------------------------------------------------------------------- /CompareColors.py: -------------------------------------------------------------------------------- 1 | # This script compares the app.foregroundColor 2 | # to the app.backgroundColor. 3 | 4 | from win32com.client import Dispatch, GetActiveObject, GetObject 5 | # Start up Photoshop application 6 | # app = Dispatch('Photoshop.Application') 7 | 8 | # Or get Reference to already running Photoshop application instance 9 | # app = GetObject(Class="Photoshop.Application") 10 | app = GetActiveObject("Photoshop.Application") 11 | 12 | if app.ForegroundColor.IsEqual(app.BackgroundColor): 13 | print("They're Equal") 14 | else: 15 | print("NOT Equal") 16 | -------------------------------------------------------------------------------- /ConvertColor.py: -------------------------------------------------------------------------------- 1 | # Convert the foreground color to RGB. 2 | 3 | from win32com.client import Dispatch, GetActiveObject, GetObject 4 | # Start up Photoshop application 5 | # app = win32com.client.Dispatch('Photoshop.Application') 6 | 7 | # Or get Reference to already running Photoshop application instance 8 | # app = GetObject(Class="Photoshop.Application") 9 | app = GetActiveObject("Photoshop.Application") 10 | 11 | # fgColor = app.SolidColor() 12 | fgColor = app.ForegroundColor 13 | 14 | fgRGBColor = fgColor.RGB 15 | print("Red:" + str(fgRGBColor.Red) + " Green:" + str(fgRGBColor.Green) + " Blue:" + str(fgRGBColor.Blue)) 16 | 17 | 18 | -------------------------------------------------------------------------------- /CopyAndPaste.py: -------------------------------------------------------------------------------- 1 | # This example makes a creates a selection in the activeDocument, copies the selection, 2 | # to the clipboard, creates a new document of the same dimensions 3 | # and pastes the contents of the clipboard into it. 4 | # It ensures that rulerUnits are set before creating the new document. 5 | # It checks the kind of the layer before making the selection to be 6 | # sure not to copy a text layer. 7 | 8 | # from win32com.client import Dispatch, GetActiveObject, GetObject 9 | from comtypes.client import GetActiveObject 10 | 11 | # Start up Photoshop application 12 | # app = Dispatch('Photoshop.Application') 13 | 14 | # Or get Reference to already running Photoshop application instance 15 | # app = GetObject(Class="Photoshop.Application") 16 | # app = GetActiveObject("Photoshop.Application") 17 | 18 | # using comtypes instead on win32com 19 | # why? see sel_area below, also http://discourse.techart.online/t/selecting-in-photoshop-using-python-doesnt-work/205 20 | app = GetActiveObject("Photoshop.Application") 21 | 22 | # PS constants, see psCC2018.py 23 | psInches = 2 24 | psPixels = 1 25 | psNewRGB = 2 26 | psWhite = 1 27 | psTextLayer = 2 28 | psReplaceSelection = 1 29 | 30 | strtRulerUnits = app.Preferences.RulerUnits 31 | if strtRulerUnits is not psInches: 32 | app.Preferences.RulerUnits = psInches 33 | 34 | srcDoc = app.Documents.Add(7, 5, 72, None, psNewRGB, psWhite) 35 | 36 | # Make sure the active layer is not a text layer, which cannot be copied to the clipboard 37 | if srcDoc.ActiveLayer.Kind != psTextLayer: 38 | # Select the left half of the document. Selections are always expressed 39 | # in pixels regardless of the current ruler unit type, so we're computing 40 | # the selection corner points based on the inch unit width and height 41 | # of the document 42 | x2 = (srcDoc.Width * srcDoc.Resolution) / 2 43 | y2 = srcDoc.Height * srcDoc.Resolution 44 | 45 | # srcDoc.Selection.Select([(0, 0), (x2, 0), (x2), (y2), (0, y2)], psReplaceSelection, 0, False) 46 | 47 | sel_area = ((0, 0), (x2, 0), (x2, y2), (0, y2)) 48 | # sel_area argument not accepted if using win32com, using comtypes instead 49 | srcDoc.Selection.Select(sel_area, psReplaceSelection, 0, False) 50 | 51 | srcDoc.Selection.Copy() 52 | 53 | # The new doc is created 54 | # need to change ruler units to pixels because x2 and y2 are pixel units. 55 | app.Preferences.RulerUnits = psPixels 56 | pasteDoc = app.Documents.Add(x2, y2, srcDoc.Resolution, "Paste Target") 57 | pasteDoc.Paste() 58 | else: 59 | print("You cannot copy from a text layer") 60 | 61 | if strtRulerUnits != app.Preferences.RulerUnits: 62 | app.Preferences.RulerUnits = strtRulerUnits 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /CopyAndRotate.py: -------------------------------------------------------------------------------- 1 | # Crop and rotate the active document. 2 | 3 | from win32com.client import Dispatch, GetActiveObject, GetObject 4 | 5 | # Start up Photoshop application 6 | # Or get Reference to already running Photoshop application instance 7 | 8 | # app = Dispatch('Photoshop.Application') 9 | app = GetActiveObject("Photoshop.Application") 10 | 11 | # PS constants, see psCC2018.py 12 | psPixels = 1 13 | psNewRGB = 2 14 | psWhite = 1 15 | 16 | fileName = "C:\Git\photoshop-scripting-python\PS_Samples_Files\Layer Comps.psd" 17 | srcDoc = app.Open(fileName) 18 | 19 | strtRulerUnits = app.Preferences.RulerUnits 20 | if strtRulerUnits is not psPixels: 21 | app.Preferences.RulerUnits = psPixels 22 | 23 | # crop a 10 pixel border from the image 24 | bounds = [10, 10, srcDoc.Width - 10, srcDoc.Height - 10] 25 | srcDoc.RotateCanvas(45) 26 | srcDoc.Crop(bounds) 27 | 28 | # set ruler back to where it was 29 | app.Preferences.RulerUnits = strtRulerUnits 30 | -------------------------------------------------------------------------------- /EmbossAction.py: -------------------------------------------------------------------------------- 1 | # This script demonstrates how you can use the action manager 2 | # to execute the Emboss filter. 3 | # Inorder to find all the IDs, see https://helpx.adobe.com/photoshop/kb/downloadable-plugins-and-content.html#ScriptingListenerplugin 4 | # This blog here exlains what a script listener is http://blogs.adobe.com/crawlspace/2006/05/installing_and_1.html 5 | 6 | from win32com.client import Dispatch, GetActiveObject, GetObject 7 | 8 | # Start up Photoshop application 9 | # Or get Reference to already running Photoshop application instance 10 | # app = Dispatch('Photoshop.Application') 11 | app = GetActiveObject("Photoshop.Application") 12 | 13 | fileName = "C:\Git\photoshop-scripting-python\PS_Samples_Files\Layer Comps.psd" 14 | docRef = app.Open(fileName) 15 | 16 | nLayerSets = docRef.LayerSets 17 | nArtLayers = docRef.LayerSets.Item(len(nLayerSets)).ArtLayers 18 | 19 | docRef.ActiveLayer = docRef.LayerSets.Item(len(nLayerSets)).ArtLayers.Item(len(nArtLayers)) 20 | 21 | def emboss(inAngle, inHeight, inAmount): 22 | # Get ID's for the related keys 23 | keyAngleID = app.CharIDToTypeID("Angl") 24 | keyHeightID = app.CharIDToTypeID("Hght") 25 | keyAmountID = app.CharIDToTypeID("Amnt") 26 | eventEmbossID = app.CharIDToTypeID("Embs") 27 | 28 | filterDescriptor = Dispatch('Photoshop.ActionDescriptor') 29 | filterDescriptor.PutInteger(keyAngleID, inAngle) 30 | filterDescriptor.PutInteger(keyHeightID, inHeight) 31 | filterDescriptor.PutInteger(keyAmountID, inAmount) 32 | 33 | app.ExecuteAction(eventEmbossID, filterDescriptor) 34 | 35 | emboss( 120, 10, 100) 36 | -------------------------------------------------------------------------------- /ExecuteMoltenLead.py: -------------------------------------------------------------------------------- 1 | # This script demonstrates how to use the action manager to execute a 2 | # previously defined action liek the default actions that ships with Photoshop 3 | # Or one that you've created yourself. The name of the action comes from 4 | # Photoshop's Actions Palette 5 | 6 | from win32com.client import Dispatch, GetActiveObject, GetObject 7 | 8 | # Start up Photoshop application 9 | # Or get Reference to already running Photoshop application instance 10 | # app = Dispatch('Photoshop.Application') 11 | app = GetActiveObject("Photoshop.Application") 12 | 13 | fileName = "C:\Git\photoshop-scripting-python\PS_Samples_Files\Layer Comps.psd" 14 | docRef = app.Open(fileName) 15 | 16 | app.DoAction('Molten Lead', 'Default Actions') 17 | 18 | -------------------------------------------------------------------------------- /FillSelection.py: -------------------------------------------------------------------------------- 1 | # Fill the current selection with an RGB color. 2 | 3 | from win32com.client import Dispatch, GetActiveObject, GetObject 4 | 5 | # Start up Photoshop application 6 | # Or get Reference to already running Photoshop application instance 7 | # app = win32com.client.Dispatch('Photoshop.Application') 8 | app = GetActiveObject("Photoshop.Application") 9 | 10 | 11 | # PS constants, see psCC2018.py 12 | psPixels = 1 13 | psNewRGB = 2 14 | psWhite = 1 15 | psNormalBlendColor = 2 16 | 17 | strtRulerUnits = app.Preferences.RulerUnits 18 | 19 | if len(app.Documents) < 1: 20 | if strtRulerUnits is not psPixels: 21 | app.Preferences.RulerUnits = psPixels 22 | docRef = app.Documents.Add(320, 240, 72, None, psNewRGB, psWhite) 23 | docRef.ArtLayers.Add() 24 | app.Preferences.RulerUnits = strtRulerUnits 25 | 26 | if app.ActiveDocument.ActiveLayer.IsBackgroundLayer == False: 27 | selRef = app.ActiveDocument.Selection 28 | fillcolor = Dispatch("Photoshop.SolidColor") 29 | fillcolor.RGB.Red = 225 30 | fillcolor.RGB.Green = 0 31 | fillcolor.RGB.Blue = 0 32 | selRef.Fill(fillcolor, psNormalBlendColor, 25, False) 33 | else: 34 | print("Can't perform operation on background layer") 35 | -------------------------------------------------------------------------------- /HelloWorld.py: -------------------------------------------------------------------------------- 1 | # Hello World! 2 | from win32com.client import Dispatch 3 | 4 | app = Dispatch("Photoshop.Application") 5 | 6 | psTextLayer = 2 # from enum PsLayerKind 7 | docRef = app.Documents.Add(320, 240) 8 | layerRef = docRef.ArtLayers.Add() 9 | layerRef.Kind = psTextLayer 10 | textItem = layerRef.TextItem 11 | textItem.Contents = "HELLO WORLD!" 12 | textItem.Position = (120, 120) 13 | -------------------------------------------------------------------------------- /ImportDataSetsAction.py: -------------------------------------------------------------------------------- 1 | from win32com.client import Dispatch, GetActiveObject, GetObject 2 | # Start up Photoshop application 3 | # app = Dispatch('Photoshop.Application') 4 | # Or get Reference to already running Photoshop application instance 5 | app = GetObject(Class="Photoshop.Application") 6 | # app = GetActiveObject("Photoshop.Application") 7 | 8 | docRef = app.ActiveDocument 9 | 10 | # runtimeKeyID = app.StringIDToTypeID("kcanDispatchWhileModal") 11 | # runtimeEnumID = app.StringIDToTypeID("enter") 12 | # runtimeEventID = app.StringIDToTypeID("modalStateChanged") 13 | # 14 | # typeState = app.stringIDToTypeID("typeState") 15 | # keyState = app.stringIDToTypeID("keyState") 16 | # keyLevel = app.stringIDToTypeID("keyLevel") 17 | # keyTitle = app.stringIDToTypeID("keyTitle") 18 | # 19 | # importDescriptor = Dispatch('Photoshop.ActionDescriptor') 20 | # importDescriptor.PutInteger(keyLevel, 1) 21 | # importDescriptor.PutEnumerated(keyState, typeState, runtimeEnumID) 22 | # importDescriptor.PutBoolean(runtimeKeyID, True) 23 | # importDescriptor.PutString(keyTitle, "Import Data Set") 24 | # 25 | # app.ExecuteAction(runtimeEventID, importDescriptor) 26 | 27 | 28 | dialogMode = 3 29 | 30 | # idmodalStateChanged = app.StringIDToTypeID("importDataSets") 31 | # importDescriptor = Dispatch('Photoshop.ActionDescriptor') 32 | # 33 | # keyLevel = app.CharIDToTypeID("Lvl ") 34 | # importDescriptor.PutInteger(keyLevel, 1) 35 | # 36 | # keyState = app.CharIDToTypeID("Stte") 37 | # typeState = app.CharIDToTypeID("Stte") 38 | # runtimeEnumID = app.StringIDToTypeID("enter") 39 | # 40 | # importDescriptor.PutEnumerated(keyState, typeState, runtimeEnumID) 41 | # 42 | # runtimeKeyID = app.StringIDToTypeID("kcanDispatchWhileModal") 43 | # importDescriptor.PutBoolean(runtimeKeyID, True) 44 | # 45 | # keyTitle = app.CharIDToTypeID("Ttl ") 46 | # importDescriptor.PutString(keyTitle, "Import Data Set") 47 | # 48 | # app.ExecuteAction(idmodalStateChanged, importDescriptor, dialogMode) 49 | 50 | 51 | file = "C:\Git\photoshop-scripting-python\PS_Samples_Files\ps_data_sets.txt" 52 | 53 | desc = Dispatch('Photoshop.ActionDescriptor') 54 | ref = Dispatch('Photoshop.ActionReference') 55 | ref.putClass(app.StringIDToTypeID("dataSetClass")) 56 | desc.putReference(app.charIDToTypeID("null"), ref) 57 | desc.putPath(app.charIDToTypeID("Usng"), file) 58 | desc.putEnumerated(app.charIDToTypeID("Encd"), app.stringIDToTypeID("dataSetEncoding"), app.stringIDToTypeID("dataSetEncodingAuto")) 59 | desc.putBoolean(app.stringIDToTypeID( "eraseAll" ), True) 60 | desc.putBoolean(app.stringIDToTypeID("useFirstColumn"), True) 61 | app.ExecuteAction(app.stringIDToTypeID( "importDataSets"), desc, dialogMode) -------------------------------------------------------------------------------- /LayerKind.py: -------------------------------------------------------------------------------- 1 | # This script demonstrates how to create a new layer and set its kind. 2 | 3 | from win32com.client import Dispatch, GetActiveObject, GetObject 4 | 5 | # Start up Photoshop application 6 | # Or get Reference to already running Photoshop application instance 7 | # app = Dispatch('Photoshop.Application') 8 | app = GetActiveObject("Photoshop.Application") 9 | 10 | # PS constants, see psCC2018.py 11 | psPixels = 1 12 | psNewRGB = 2 13 | psWhite = 1 14 | psTextLayer = 2 # from enum PsLayerKind 15 | 16 | strtRulerUnits = app.Preferences.RulerUnits 17 | 18 | if len(app.Documents) < 1: 19 | if strtRulerUnits is not psPixels: 20 | app.Preferences.RulerUnits = psPixels 21 | docRef = app.Documents.Add(320, 240, 72, None, psNewRGB, psWhite) 22 | else: 23 | docRef = app.ActiveDocument 24 | 25 | layerRef = docRef.ArtLayers.Add() 26 | layerRef.Kind = psTextLayer 27 | # Set the ruler back to where it was 28 | app.Preferences.RulerUnits = strtRulerUnits 29 | -------------------------------------------------------------------------------- /LinkLayer.py: -------------------------------------------------------------------------------- 1 | # This scripts demonstrates how to link two layers. 2 | 3 | from win32com.client import Dispatch, GetActiveObject, GetObject 4 | 5 | # Start up Photoshop application 6 | # Or get Reference to already running Photoshop application instance 7 | # app = Dispatch('Photoshop.Application') 8 | app = GetActiveObject("Photoshop.Application") 9 | 10 | # PS constants, see psCC2018.py 11 | psPixels = 1 12 | psNewRGB = 2 13 | psWhite = 1 14 | 15 | strtRulerUnits = app.Preferences.RulerUnits 16 | 17 | if len(app.Documents) < 1: 18 | if strtRulerUnits is not psPixels: 19 | app.Preferences.RulerUnits = psPixels 20 | docRef = app.Documents.Add(320, 240, 72, None, psNewRGB, psWhite) 21 | else: 22 | docRef = app.ActiveDocument 23 | 24 | layerRef = docRef.ArtLayers.Add() 25 | layerRef2 = docRef.ArtLayers.Add() 26 | layerRef.Link(layerRef2) 27 | 28 | # Set the ruler back to where it was 29 | app.Preferences.RulerUnits = strtRulerUnits -------------------------------------------------------------------------------- /LoadSelection.py: -------------------------------------------------------------------------------- 1 | # This script will demonstrate how to load a selection from a saved alpha channel. 2 | 3 | from comtypes.client import GetActiveObject, CreateObject 4 | 5 | # Get Reference to already running Photoshop application instance 6 | # using comtypes instead on win32com why? cos win32com has problem accepting 2 dimension array 7 | # http://discourse.techart.online/t/selecting-in-photoshop-using-python-doesnt-work/205 8 | app = GetActiveObject("Photoshop.Application") 9 | 10 | # PS constants, see psCC2018.py 11 | psPixels = 1 12 | psExtendSelection = 2 # from enum PsSelectionType 13 | 14 | strtRulerUnits = app.Preferences.RulerUnits 15 | 16 | if strtRulerUnits is not psPixels: 17 | app.Preferences.RulerUnits = psPixels 18 | docRef = app.Documents.Add(320, 240) 19 | 20 | # Save a rectangular selection area offset by 50 pixels from the image border into an alpha channel 21 | offset = 50 22 | selBounds1 = ((offset, offset), (docRef.Width - offset, offset), (docRef.Width - offset, docRef.Height - offset), (offset, docRef.Height - offset)) 23 | docRef.Selection.Select(selBounds1) 24 | selAlpha = docRef.Channels.Add() 25 | docRef.Selection.Store(selAlpha) 26 | 27 | # Now create a second wider but less tall selection 28 | selBounds2 = ((0, 75), (docRef.Width, 75), (docRef.Width, 150), (0, 150)) 29 | docRef.Selection.Select(selBounds2) 30 | 31 | # Load the selection from the just saved alpha channel, combining it with the active selection 32 | docRef.Selection.Load(selAlpha, psExtendSelection, False) 33 | 34 | # Set ruler back to where it was 35 | app.Preferences.RulerUnits = strtRulerUnits 36 | 37 | -------------------------------------------------------------------------------- /MakeSelection.py: -------------------------------------------------------------------------------- 1 | # Get the active document and make a new selection. 2 | from comtypes.client import GetActiveObject, CreateObject 3 | 4 | # Start up Photoshop application 5 | # Or get Reference to already running Photoshop application instance 6 | # app = Dispatch('Photoshop.Application') 7 | app = GetActiveObject("Photoshop.Application") 8 | 9 | # create new document if no document is opened 10 | if len([(i, x) for i, x in enumerate(app.Documents, 1)]) < 1: 11 | psPixels = 1 12 | strtRulerUnits = app.Preferences.RulerUnits 13 | app.Preferences.RulerUnits = psPixels 14 | psNewRGB = 2 # from enum PsNewDocumentMode 15 | psWhite = 1 # from enum PsDocumentFill 16 | docRef = app.Documents.Add(320, 240, 72, None, psNewRGB, psWhite) 17 | app.preferences.rulerUnits = strtRulerUnits 18 | else: 19 | docRef = app.ActiveDocument 20 | 21 | sel_area = ((50, 60), (150, 60), (150, 120), (50, 120)) 22 | psReplaceSelection = 1 # from enum PsSelectionType 23 | docRef.Selection.Select(sel_area, psReplaceSelection, 5.5, False) 24 | -------------------------------------------------------------------------------- /NewDocument.py: -------------------------------------------------------------------------------- 1 | # Create a new Photoshop document with diminsions 4 inches by 4 inches. 2 | from comtypes.client import GetActiveObject 3 | 4 | # Start up Photoshop application 5 | # Or get Reference to already running Photoshop application instance 6 | # app = Dispatch('Photoshop.Application') 7 | app = GetActiveObject("Photoshop.Application") 8 | 9 | strtRulerUnits = app.Preferences.RulerUnits 10 | psInches = 2 # from enum PsUnits 11 | app.Preferences.RulerUnits = psInches 12 | 13 | # Create the document 14 | docRef = app.Documents.Add(4, 4, 72.0, "My New Document") 15 | 16 | # Make sure to set the ruler units prior to creating the document. 17 | app.Preferences.RulerUnits = strtRulerUnits -------------------------------------------------------------------------------- /OpenDocument.py: -------------------------------------------------------------------------------- 1 | # Open a Photoshop document located in the Photoshop samples folder 2 | # You must first create a File object to pass into the open method. 3 | from comtypes.client import GetActiveObject 4 | 5 | # Start up Photoshop application 6 | # Or get Reference to already running Photoshop application instance 7 | # app = Dispatch('Photoshop.Application') 8 | app = GetActiveObject("Photoshop.Application") 9 | 10 | 11 | fileName = "C:\Git\PS_Samples_Files\Layer Comps.psd" 12 | docRef = app.Open(fileName) -------------------------------------------------------------------------------- /PS_Samples_Files/CheeziPuffs.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lohriialo/photoshop-scripting-python/5b9d6953945d96737477ab2ce6f9ebe6fc204318/PS_Samples_Files/CheeziPuffs.mov -------------------------------------------------------------------------------- /PS_Samples_Files/Doors.dng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lohriialo/photoshop-scripting-python/5b9d6953945d96737477ab2ce6f9ebe6fc204318/PS_Samples_Files/Doors.dng -------------------------------------------------------------------------------- /PS_Samples_Files/Ducky.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lohriialo/photoshop-scripting-python/5b9d6953945d96737477ab2ce6f9ebe6fc204318/PS_Samples_Files/Ducky.tif -------------------------------------------------------------------------------- /PS_Samples_Files/Fish.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lohriialo/photoshop-scripting-python/5b9d6953945d96737477ab2ce6f9ebe6fc204318/PS_Samples_Files/Fish.psd -------------------------------------------------------------------------------- /PS_Samples_Files/Layer Comps.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lohriialo/photoshop-scripting-python/5b9d6953945d96737477ab2ce6f9ebe6fc204318/PS_Samples_Files/Layer Comps.psd -------------------------------------------------------------------------------- /PS_Samples_Files/Merge to HDR/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lohriialo/photoshop-scripting-python/5b9d6953945d96737477ab2ce6f9ebe6fc204318/PS_Samples_Files/Merge to HDR/1.jpg -------------------------------------------------------------------------------- /PS_Samples_Files/Merge to HDR/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lohriialo/photoshop-scripting-python/5b9d6953945d96737477ab2ce6f9ebe6fc204318/PS_Samples_Files/Merge to HDR/2.jpg -------------------------------------------------------------------------------- /PS_Samples_Files/Merge to HDR/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lohriialo/photoshop-scripting-python/5b9d6953945d96737477ab2ce6f9ebe6fc204318/PS_Samples_Files/Merge to HDR/3.jpg -------------------------------------------------------------------------------- /PS_Samples_Files/Merge to HDR/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lohriialo/photoshop-scripting-python/5b9d6953945d96737477ab2ce6f9ebe6fc204318/PS_Samples_Files/Merge to HDR/4.jpg -------------------------------------------------------------------------------- /PS_Samples_Files/Merge to HDR/Result/HDR.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lohriialo/photoshop-scripting-python/5b9d6953945d96737477ab2ce6f9ebe6fc204318/PS_Samples_Files/Merge to HDR/Result/HDR.psd -------------------------------------------------------------------------------- /PS_Samples_Files/Photomerge/Result/Panorama.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lohriialo/photoshop-scripting-python/5b9d6953945d96737477ab2ce6f9ebe6fc204318/PS_Samples_Files/Photomerge/Result/Panorama.jpg -------------------------------------------------------------------------------- /PS_Samples_Files/Photomerge/photo1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lohriialo/photoshop-scripting-python/5b9d6953945d96737477ab2ce6f9ebe6fc204318/PS_Samples_Files/Photomerge/photo1.jpg -------------------------------------------------------------------------------- /PS_Samples_Files/Photomerge/photo2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lohriialo/photoshop-scripting-python/5b9d6953945d96737477ab2ce6f9ebe6fc204318/PS_Samples_Files/Photomerge/photo2.jpg -------------------------------------------------------------------------------- /PS_Samples_Files/Photomerge/photo3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lohriialo/photoshop-scripting-python/5b9d6953945d96737477ab2ce6f9ebe6fc204318/PS_Samples_Files/Photomerge/photo3.jpg -------------------------------------------------------------------------------- /PS_Samples_Files/Smart Objects.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lohriialo/photoshop-scripting-python/5b9d6953945d96737477ab2ce6f9ebe6fc204318/PS_Samples_Files/Smart Objects.psd -------------------------------------------------------------------------------- /PS_Samples_Files/Vanishing Point.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lohriialo/photoshop-scripting-python/5b9d6953945d96737477ab2ce6f9ebe6fc204318/PS_Samples_Files/Vanishing Point.psd -------------------------------------------------------------------------------- /PS_Samples_Files/ps_data_sets.txt: -------------------------------------------------------------------------------- 1 | {contents of FlowerShow.txt} 2 | Variable 1, Variable 2, Variable 3 3 | true, TULIP, c:\My Documents\tulip.jpg 4 | false, SUNFLOWER, c:\My Documents\sunflower.jpg 5 | false, CALLA LILY, c:\My Documents\calla.jpg 6 | true, VIOLET, c:\My Documents\violet.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Photoshop Scripting in Python 2 | ![](https://i.imgur.com/8wOWcPX.png "Photoshop Python") 3 | 4 | Scripting in Photoshop is used to automate repetitive tasks and are often used as a creative tool to streamline tasks that might be too 5 | time consuming to do manually. For example, you could write a script to generate a number of localized 6 | versions of a particular image or to gather information about the various color profiles used by a collection 7 | of images. 8 | 9 | # Photoshop COM & DOM 10 | Photoshop can be scripted through COM(Component Object Model). Its DOM(Document Object Model) is the same when accessing it through either its own JavaScript engine or Python or any other scripting language it supports. The Photoshop DOM consists of a hierarchical representation of the Photoshop application, the documents used in it, and the components of the documents. The DOM allows you to programmatically access and manipulate the document and its components. For example, through the DOM, you can create 11 | a new document, add a layer to an existing document, or change the background color of a layer. Most of 12 | the functionality available through the Photoshop user interface is available through the DOM. 13 | 14 | # But why Python? 15 | Photoshop scripting officially supports JavaScript, AppleScript & VBScript. However, Scripting in Python is also fairly easy if not easier if you're already comfortable with Python. You may have already heard that Python is gaining in popularity, but did you know it’s now the most popular introductory programming language in U.S. universities? Python is also cross platform just like JavaScript is and lately becoming one of the fastest growing programming language according to StackOverflow [as of 2017](https://stackoverflow.blog/2017/09/06/incredible-growth-python) / [as of 2019](https://insights.stackoverflow.com/survey/2019#key-results) 16 | 17 | Python is easy to use, powerful, and versatile, making it a great choice for beginners and experts alike. Python’s readability makes it a great first programming language - it allows you to think like a programmer and not waste time understanding the mysterious syntax that other programming languages can require. 18 | 19 | # Getting Started 20 | Python allows you to access COM and it's DOM with the help of a Python extensions like "pypiwin32" or "comtypes". Install these modules and you're ready to start scripting Photoshop in Python 21 | 22 | * `pip install pypiwin32` or `pip install comtypes` 23 | 24 | # Hello World! 25 | ```python 26 | from win32com.client import Dispatch 27 | 28 | app = Dispatch("Photoshop.Application") 29 | doc = app.Documents.Add(320, 240) 30 | layerRef = doc.ArtLayers.Add() 31 | 32 | psTextLayer = 2 # from enum PsLayerKind 33 | layerRef.Kind = psTextLayer 34 | 35 | textItem = layerRef.TextItem 36 | textItem.Contents = "HELLO WORLD!" 37 | textItem.Position = (120, 120) 38 | ``` 39 | # How to inspect scripting object properties? 40 | There's not a straight forward way, you need to read the documentation to understand what properties/attributes are available for a scripting object, or possibly a COM browser. For example, I've extracted the Python scripting object reference for Photoshop CC 2018 at [api_reference](https://github.com/lohriialo/photoshop-scripting-python/tree/master/api_reference) 41 | 42 | # GUI tool example 43 | See [`gui_tool`](https://github.com/lohriialo/photoshop-scripting-python/tree/master/gui_tool_example) for an example of how you can use Photoshop Scripting to develop your own tool/utilities 44 | 45 | # Scripting on Mac? 46 | Yes, scripting on Mac is also possible, see [mac_scripting](https://github.com/lohriialo/photoshop-scripting-python/tree/master/mac_scripting) for more details 47 | 48 | # Photoshop Scripting Resources 49 | * [Photoshop API landing page](https://developer.adobe.com/photoshop) 50 | * [Photoshop Scripting Documentation](https://developer.adobe.com/console/1127/servicesandapis/ps) 51 | * [Photoshop Scripting Developer Forum](https://community.adobe.com/t5/photoshop-ecosystem/ct-p/ct-photoshop?page=1&sort=latest_replies&lang=all&tabid=all&topics=label-sdk%2Clabel-actionsandscripting) 52 | * [Photoshop Scripting API Reference - PDF](https://github.com/Adobe-CEP/CEP-Resources/blob/master/Documentation/Product%20specific%20Documentation/Photoshop%20Scripting/photoshop-javascript-ref-2020.pdf) 53 | * [Photoshop Scripting API Reference - Online](https://theiviaxx.github.io/photoshop-docs/Photoshop/index.html) 54 | * [Photoshop Scripting Tutorials](https://www.youtube.com/playlist?list=PLUEniN8BpU8-Qmjyv3zyWaNvDYwJOJZ4m) 55 | 56 | # Also see 57 | * [InDesign Scripting in Python](https://github.com/lohriialo/indesign-scripting-python) 58 | * [Illustrator Scripting in Python](https://github.com/lohriialo/illustrator-scripting-python) 59 | 60 | # Contribution 61 | If you've written a useful Photoshop Python script and wants to share with the world, please create a new issue with the file as an attachment to the issue. 62 | 63 | When you submit a script, please try to include the following information at the start of your script 64 | ```python 65 | # script_file_name.py 66 | 67 | # Created: 1st January 2019 68 | __author__ = 'Your Name or Original Author Name' 69 | __version__ = '1.0' 70 | 71 | """ 72 | A short description of what the script does 73 | """ 74 | 75 | """ 76 | Instructions on how to use the script, if any 77 | """ 78 | 79 | ``` 80 | * Go to [photoshop-scripting-python/issues/new](https://github.com/lohriialo/photoshop-scripting-python/issues/new) 81 | * Add title as `Useful Script` 82 | * Drag & drop your .py script file into the description area 83 | * Click `Submit new issue` 84 | -------------------------------------------------------------------------------- /RotateLayer.py: -------------------------------------------------------------------------------- 1 | # This scripts demonstrates how to rotate a layer 45 degrees clockwise. 2 | from win32com.client import Dispatch, GetActiveObject 3 | 4 | # Start up Photoshop application 5 | # Or get Reference to already running Photoshop application instance 6 | # app = Dispatch('Photoshop.Application') 7 | app = GetActiveObject("Photoshop.Application") 8 | 9 | if len(app.Documents) > 0: 10 | if app.ActiveDocument.ActiveLayer.IsBackgroundLayer == False: 11 | docRef = app.ActiveDocument 12 | layerRef = docRef.Layers[0] 13 | layerRef.Rotate(45.0) 14 | else: 15 | print("Operation cannot be performed on background layer") 16 | else: 17 | print("You must have at least one open document to run this script!") -------------------------------------------------------------------------------- /SelectTool.py: -------------------------------------------------------------------------------- 1 | from win32com.client import Dispatch, GetActiveObject, GetObject 2 | # Start up Photoshop application 3 | # app = Dispatch('Photoshop.Application') 4 | # Or get Reference to already running Photoshop application instance 5 | # app = GetObject(Class="Photoshop.Application") 6 | app = GetActiveObject("Photoshop.Application") 7 | 8 | # app.CurrentTool = 'cropTool' 9 | print(app.CurrentTool) -------------------------------------------------------------------------------- /SelectionStroke.py: -------------------------------------------------------------------------------- 1 | # Create a stroke around the current selection. 2 | # Set the stroke color and width of the new stroke. 3 | from win32com.client import Dispatch, GetActiveObject 4 | from comtypes.client import GetActiveObject, CreateObject 5 | 6 | # Start up Photoshop application 7 | # Or get Reference to already running Photoshop application instance 8 | # app = Dispatch('Photoshop.Application') 9 | app = GetActiveObject("Photoshop.Application") 10 | 11 | if len([(i, x) for i, x in enumerate(app.Documents, 1)]) > 0: 12 | if app.ActiveDocument.ActiveLayer.IsBackgroundLayer == False: 13 | 14 | psPixels = 1 15 | strtRulerUnits = app.Preferences.RulerUnits 16 | app.Preferences.RulerUnits = psPixels 17 | 18 | selRef = app.ActiveDocument.Selection 19 | offset = 10 20 | selBounds = ((offset, offset), 21 | (app.ActiveDocument.Width - offset, offset), 22 | (app.ActiveDocument.Width - offset, app.ActiveDocument.Height - offset), 23 | (offset, app.ActiveDocument.Height - offset)) 24 | 25 | selRef.Select(selBounds) 26 | selRef.SelectBorder(5) 27 | 28 | # create text color properties 29 | strokeColor = CreateObject("Photoshop.SolidColor") 30 | strokeColor.CMYK.Cyan = 20 31 | strokeColor.CMYK.Magenta = 90 32 | strokeColor.CMYK.Yellow = 50 33 | strokeColor.CMYK.Black = 50 34 | 35 | # We don't want any Photoshop dialogs displayed during automated execution 36 | psDisplayNoDialogs = 3 # from enum PsDialogModes 37 | app.displayDialogs = psDisplayNoDialogs 38 | psOutsideStroke = 3 # from enum PsStrokeLocation 39 | psVividLightBlend = 15 # from enum PsColorBlendMode 40 | selRef.Stroke(strokeColor, 2, psOutsideStroke, psVividLightBlend, 75, True) 41 | 42 | # Set ruler units back the way we found it 43 | app.Preferences.RulerUnits = strtRulerUnits 44 | 45 | 46 | else: 47 | print("Operation cannot be performed on background layer") 48 | else: 49 | print("Create a document with an active selection before running this script!") -------------------------------------------------------------------------------- /SmartSharpen.py: -------------------------------------------------------------------------------- 1 | # This script demonstrates how you can use the action manager 2 | # to execute the Smart Sharpen filter. 3 | 4 | from win32com.client import Dispatch, GetActiveObject, GetObject 5 | 6 | # Start up Photoshop application 7 | # Or get Reference to already running Photoshop application instance 8 | # app = Dispatch('Photoshop.Application') 9 | app = GetActiveObject("Photoshop.Application") 10 | 11 | fileName = "C:\Git\PS_Samples_Files\Layer Comps.psd" 12 | docRef = app.Open(fileName) 13 | 14 | nLayerSets = docRef.LayerSets 15 | nArtLayers = docRef.LayerSets.Item(len(nLayerSets)).ArtLayers 16 | 17 | docRef.ActiveLayer = docRef.LayerSets.Item(len(nLayerSets)).ArtLayers.Item(len(nArtLayers)) 18 | 19 | 20 | def SmartSharpen(inAmount, inRadius, inNoise): 21 | 22 | idsmartSharpenID = app.stringIDToTypeID("smartSharpen") 23 | desc37 = Dispatch('Photoshop.ActionDescriptor') 24 | 25 | idpresetKind = app.stringIDToTypeID("presetKind") 26 | idpresetKindType = app.stringIDToTypeID("presetKindType") 27 | idpresetKindCustom = app.stringIDToTypeID("presetKindCustom") 28 | desc37.putEnumerated(idpresetKind, idpresetKindType, idpresetKindCustom) 29 | 30 | idAmnt = app.charIDToTypeID("Amnt") 31 | idPrc = app.charIDToTypeID("#Prc") 32 | desc37.putUnitDouble(idAmnt, idPrc, inAmount) 33 | 34 | idRds = app.charIDToTypeID("Rds ") 35 | idPxl = app.charIDToTypeID("#Pxl") 36 | desc37.putUnitDouble(idRds, idPxl, inRadius) 37 | 38 | idnoiseReduction = app.stringIDToTypeID("noiseReduction") 39 | idPrc = app.charIDToTypeID("#Prc") 40 | desc37.putUnitDouble(idnoiseReduction, idPrc, inNoise) 41 | 42 | idblur = app.charIDToTypeID("blur") 43 | idblurType = app.stringIDToTypeID("blurType") 44 | idGsnB = app.charIDToTypeID("GsnB") 45 | desc37.putEnumerated(idblur, idblurType, idGsnB) 46 | 47 | # now execute the action 48 | app.ExecuteAction(idsmartSharpenID, desc37) 49 | 50 | SmartSharpen(300, 2.0, 20) 51 | -------------------------------------------------------------------------------- /WorkingWithText.py: -------------------------------------------------------------------------------- 1 | # Create a new art layer and convert it to a text layer. 2 | # Set its contents, size and color. 3 | # from win32com.client import Dispatch, GetActiveObject 4 | from comtypes.client import GetActiveObject, CreateObject 5 | 6 | # Start up Photoshop application 7 | # Or get Reference to already running Photoshop application instance 8 | # app = Dispatch('Photoshop.Application') 9 | app = GetActiveObject("Photoshop.Application") 10 | 11 | strtRulerUnits = app.Preferences.RulerUnits 12 | strtTypeUnits = app.Preferences.TypeUnits 13 | psInches = 2 # from enum PsUnits 14 | psTypePoints = 5 # from enum PsTypeUnits 15 | app.Preferences.RulerUnits = psInches 16 | app.Preferences.TypeUnits = psTypePoints 17 | 18 | # suppress all dialogs 19 | psDisplayNoDialogs = 3 # from enum PsDialogModes 20 | app.displayDialogs = psDisplayNoDialogs 21 | 22 | # create a new document 23 | docRef = app.Documents.Add(7, 5, 72) 24 | 25 | # create text color properties 26 | textColor = CreateObject("Photoshop.SolidColor") 27 | textColor.RGB.Red = 225 28 | textColor.RGB.Green = 0 29 | textColor.RGB.Blue = 0 30 | 31 | # add a new text layer to document and apply the text color 32 | newTextLayer = docRef.ArtLayers.Add() 33 | psTextLayer = 2 # from enum PsLayerKind 34 | newTextLayer.Kind = psTextLayer 35 | newTextLayer.TextItem.Contents = "Hello, World!" 36 | newTextLayer.TextItem.Position = [0.75, 0.75] 37 | newTextLayer.TextItem.Size = 36 38 | newTextLayer.TextItem.Color = textColor 39 | 40 | # set the app preference the way it was before the operation 41 | app.Preferences.RulerUnits = strtRulerUnits 42 | app.Preferences.TypeUnits = strtTypeUnits 43 | 44 | -------------------------------------------------------------------------------- /gui_tool_example/README.md: -------------------------------------------------------------------------------- 1 | # GUI tool example with Photoshop Scripting in Python 2 | This example demonstrates how to use a GUI toolkit like PyQt to build your own custom tool/utility which can then interact with Photoshop via COM on Windows. 3 | 4 | ![](https://i.imgur.com/6l3kXw8.png "Photoshop Bulk Image Resizer") 5 | 6 | # Requiremments: 7 | - `Python 3.x` 8 | - `PyQt5: https://www.riverbankcomputing.com/software/pyqt/download5` 9 | - `http://www.pyinstaller.org/` 10 | 11 | # Convert .ui to .py with pyuic5 12 | - `pyuic5 -o ` 13 | 14 | # Compile the app to executable using pyInstaller 15 | - `pyinstaller ` 16 | -------------------------------------------------------------------------------- /gui_tool_example/app.py: -------------------------------------------------------------------------------- 1 | from comtypes.client import GetActiveObject, CreateObject 2 | 3 | from PyQt5.QtWidgets import * 4 | from PyQt5.QtCore import * 5 | from PyQt5.QtGui import QIntValidator 6 | 7 | import os 8 | import sys 9 | import time 10 | 11 | from resizer_ui import Ui_Dialog 12 | 13 | class MainUIClass(QDialog): 14 | def __init__(self): 15 | super().__init__() 16 | self.ui = Ui_Dialog() 17 | self.ui.setupUi(self) 18 | self.show() 19 | 20 | # thread 21 | self.myworker = WorkerThread() 22 | # self.photoshop = GetActiveObject("Photoshop.Application") 23 | self.filestoprocess = [] 24 | 25 | # aspect ratio is maintained by default 26 | self.ui.height_text.setReadOnly(True) 27 | 28 | # To allow only int 29 | self.onlyInt = QIntValidator() 30 | self.ui.height_text.setValidator(self.onlyInt) 31 | self.ui.width_text.setValidator(self.onlyInt) 32 | self.ui.resolution_text.setValidator(self.onlyInt) 33 | 34 | # UI buttons clicked handler 35 | self.ui.browse_btn.clicked.connect(self.browse_folder) 36 | self.ui.resize_btn.clicked.connect(self.resize_image) 37 | self.ui.reset_btn.clicked.connect(self.reset_input) 38 | self.ui.aspect_ratio_checkbox.stateChanged.connect(self.aspect_ratio) 39 | 40 | def browse_folder(self): 41 | in_folder = QFileDialog.getExistingDirectory() 42 | self.ui.browse_text.setText(QDir.toNativeSeparators(in_folder)) 43 | 44 | def reset_input(self): 45 | self.ui.browse_text.setText("") 46 | self.ui.height_text.setText("") 47 | self.ui.width_text.setText("") 48 | self.ui.resolution_text.setText("") 49 | self.ui.aspect_ratio_checkbox.setChecked(True) 50 | self.ui.resample_combobox.setCurrentIndex(0) 51 | 52 | 53 | def aspect_ratio(self): 54 | print("state changed") 55 | if self.ui.aspect_ratio_checkbox.isChecked(): 56 | print("checked") 57 | self.ui.height_text.setText("") 58 | # self.ui.height_text.setDisabled() 59 | self.ui.height_text.setReadOnly(True) 60 | else: 61 | print("unchecked") 62 | self.ui.height_text.setReadOnly(False) 63 | 64 | 65 | def resize_image(self): 66 | try: 67 | in_dir = self.ui.browse_text.text() 68 | in_height = self.ui.height_text.text() 69 | in_width = self.ui.width_text.text() 70 | in_resolution = self.ui.resolution_text.text() 71 | in_resample = self.ui.resample_combobox.currentIndex() 72 | 73 | if in_dir is not "": 74 | if os.path.isdir(in_dir): 75 | if self.ui.aspect_ratio_checkbox.isChecked(): 76 | if in_width is not "": 77 | for filename in os.listdir(in_dir): 78 | if filename.lower().endswith(".png") \ 79 | or filename.lower().endswith(".jpg") \ 80 | or filename.lower().endswith(".jpeg") \ 81 | or filename.lower().endswith(".tif") \ 82 | or filename.lower().endswith(".tiff") \ 83 | or filename.lower().endswith(".gif"): 84 | self.filestoprocess.append(os.path.join(in_dir, filename)) 85 | continue 86 | else: 87 | continue 88 | if len(self.filestoprocess) > 0: 89 | # Got all files to process, process in thread 90 | self.myworker.process_args(self.filestoprocess, in_width, in_resolution, in_resample) 91 | self.myworker.start() 92 | else: 93 | print("No files in selected directory") 94 | else: 95 | if in_width is "": 96 | print("Width is empty") 97 | else: 98 | if in_width is not "" and in_height is not "": 99 | for filename in os.listdir(in_dir): 100 | if filename.lower().endswith(".png") \ 101 | or filename.lower().endswith(".jpg") \ 102 | or filename.lower().endswith(".jpeg") \ 103 | or filename.lower().endswith(".tif") \ 104 | or filename.lower().endswith(".tiff") \ 105 | or filename.lower().endswith(".gif"): 106 | self.filestoprocess.append(os.path.join(in_dir, filename)) 107 | continue 108 | else: 109 | continue 110 | if len(self.filestoprocess) > 0: 111 | # Got all files to process, process in thread 112 | print("2") 113 | self.myworker.process_args2(self.filestoprocess, in_width, in_height, in_resolution, 114 | in_resample) 115 | self.myworker.start() 116 | else: 117 | print("No files in selected directory") 118 | else: 119 | if in_width is "": 120 | print("Width is empty") 121 | if in_height is "": 122 | print("Height is empty") 123 | else: 124 | print("Input directory does not exist") 125 | else: 126 | print("Input directory is empty") 127 | 128 | except Exception as erro: 129 | print(erro) 130 | 131 | 132 | # prompt before application exit, overidding the default closeEvent() 133 | def closeEvent(self, event): 134 | quit_msg = "Are you sure you want to exit?" 135 | reply = QMessageBox.question(self, 'Message', quit_msg, QMessageBox.Yes, QMessageBox.No) 136 | if reply == QMessageBox.Yes: 137 | event.accept() 138 | else: 139 | event.ignore() 140 | 141 | class WorkerThread(QThread): 142 | def __init__(self, parent=None): 143 | super(WorkerThread, self).__init__(parent) 144 | 145 | self.filestoprocess = [] 146 | self.in_width = 0 147 | self.in_height = 0 148 | self.in_resample = 0 149 | self.in_resolution = 0 150 | 151 | def process_args(self, files, width, resolution, resample): 152 | self.filestoprocess = files 153 | self.in_width = int(width) 154 | self.in_resolution = resolution 155 | self.in_resample = resample 156 | 157 | def process_args2(self, files, width, height, resolution, resample): 158 | self.filestoprocess = files 159 | self.in_width = int(width) 160 | self.in_height = int(height) 161 | self.in_resolution = resolution 162 | self.in_resample = resample 163 | 164 | def run(self): 165 | try: 166 | self.psapp = GetActiveObject("Photoshop.Application") 167 | # We don't want any Photoshop dialogs displayed during automated execution 168 | psDisplayNoDialogs = 3 # from enum PsDialogModes 169 | self.psapp.displayDialogs = psDisplayNoDialogs 170 | 171 | psAutomatic = 8 # from enum PsResampleMethod 172 | psPreserveDetails = 9 # from enum PsResampleMethod 173 | psBicubicSmoother = 6 # from enum PsResampleMethod 174 | psBicubicSharper = 5 # from enum PsResampleMethod 175 | psBicubicAutomatic = 7 # from enum PsResampleMethod 176 | psNearestNeighbor = 2 # from enum PsResampleMethod 177 | psBilinear = 3 # from enum PsResampleMethod 178 | 179 | psBicubic = 4 # from enum PsResampleMethod 180 | psNoResampling = 1 # from enum PsResampleMethod 181 | 182 | for file in self.filestoprocess: 183 | print("thread: " + file) 184 | docRef = self.psapp.Open(file) 185 | 186 | # if height is given, don't maintain aspect ratio 187 | if int(self.in_height) > 0: 188 | docRef.ResizeImage(self.in_width, self.in_height, None, psAutomatic) 189 | # time.sleep(3) 190 | 191 | # maintain aspect ratio 192 | else: 193 | doc_width = docRef.Width 194 | doc_height = docRef.Height 195 | # maintain aspect ratio 196 | new_height = (doc_height / doc_width) * self.in_width 197 | docRef.ResizeImage(self.in_width, new_height, None, psAutomatic) 198 | 199 | docRef.Save() 200 | docRef.Close() 201 | # to prevent application busy COM error 202 | time.sleep(1) 203 | except Exception as thread_err: 204 | print(thread_err) 205 | 206 | if __name__ == '__main__': 207 | app = QApplication(sys.argv) 208 | ui = MainUIClass() 209 | ui.show() 210 | app.exec_() 211 | 212 | -------------------------------------------------------------------------------- /gui_tool_example/resizer_ui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'C:\Users\lalo\PycharmProjects\ImageResizerUI\resizer_ui.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.11.2 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore, QtGui, QtWidgets 10 | 11 | class Ui_Dialog(object): 12 | def setupUi(self, Dialog): 13 | Dialog.setObjectName("Dialog") 14 | Dialog.resize(463, 250) 15 | Dialog.setFocusPolicy(QtCore.Qt.NoFocus) 16 | self.browse_btn = QtWidgets.QPushButton(Dialog) 17 | self.browse_btn.setGeometry(QtCore.QRect(370, 30, 71, 21)) 18 | font = QtGui.QFont() 19 | font.setPointSize(10) 20 | self.browse_btn.setFont(font) 21 | self.browse_btn.setObjectName("browse_btn") 22 | self.browse_text = QtWidgets.QLineEdit(Dialog) 23 | self.browse_text.setGeometry(QtCore.QRect(120, 30, 231, 21)) 24 | self.browse_text.setObjectName("browse_text") 25 | self.browse_label = QtWidgets.QLabel(Dialog) 26 | self.browse_label.setGeometry(QtCore.QRect(20, 30, 91, 21)) 27 | font = QtGui.QFont() 28 | font.setPointSize(10) 29 | self.browse_label.setFont(font) 30 | self.browse_label.setObjectName("browse_label") 31 | self.resize_btn = QtWidgets.QPushButton(Dialog) 32 | self.resize_btn.setGeometry(QtCore.QRect(360, 210, 81, 23)) 33 | font = QtGui.QFont() 34 | font.setPointSize(10) 35 | self.resize_btn.setFont(font) 36 | self.resize_btn.setObjectName("resize_btn") 37 | self.reset_btn = QtWidgets.QPushButton(Dialog) 38 | self.reset_btn.setGeometry(QtCore.QRect(260, 210, 75, 23)) 39 | font = QtGui.QFont() 40 | font.setPointSize(10) 41 | self.reset_btn.setFont(font) 42 | self.reset_btn.setObjectName("reset_btn") 43 | self.width_text = QtWidgets.QLineEdit(Dialog) 44 | self.width_text.setGeometry(QtCore.QRect(90, 80, 61, 20)) 45 | self.width_text.setObjectName("width_text") 46 | self.width_label = QtWidgets.QLabel(Dialog) 47 | self.width_label.setGeometry(QtCore.QRect(20, 80, 51, 21)) 48 | font = QtGui.QFont() 49 | font.setPointSize(10) 50 | self.width_label.setFont(font) 51 | self.width_label.setObjectName("width_label") 52 | self.height_text = QtWidgets.QLineEdit(Dialog) 53 | self.height_text.setGeometry(QtCore.QRect(90, 120, 61, 20)) 54 | self.height_text.setObjectName("height_text") 55 | self.height_label = QtWidgets.QLabel(Dialog) 56 | self.height_label.setGeometry(QtCore.QRect(20, 120, 51, 21)) 57 | font = QtGui.QFont() 58 | font.setPointSize(10) 59 | self.height_label.setFont(font) 60 | self.height_label.setObjectName("height_label") 61 | self.px_label_width = QtWidgets.QLabel(Dialog) 62 | self.px_label_width.setGeometry(QtCore.QRect(160, 70, 21, 21)) 63 | font = QtGui.QFont() 64 | font.setPointSize(10) 65 | self.px_label_width.setFont(font) 66 | self.px_label_width.setObjectName("px_label_width") 67 | self.px_label_height = QtWidgets.QLabel(Dialog) 68 | self.px_label_height.setGeometry(QtCore.QRect(160, 110, 21, 21)) 69 | font = QtGui.QFont() 70 | font.setPointSize(10) 71 | self.px_label_height.setFont(font) 72 | self.px_label_height.setObjectName("px_label_height") 73 | self.aspect_ratio_checkbox = QtWidgets.QCheckBox(Dialog) 74 | self.aspect_ratio_checkbox.setGeometry(QtCore.QRect(210, 100, 151, 17)) 75 | font = QtGui.QFont() 76 | font.setPointSize(10) 77 | self.aspect_ratio_checkbox.setFont(font) 78 | self.aspect_ratio_checkbox.setChecked(True) 79 | self.aspect_ratio_checkbox.setObjectName("aspect_ratio_checkbox") 80 | self.line = QtWidgets.QFrame(Dialog) 81 | self.line.setGeometry(QtCore.QRect(150, 80, 51, 16)) 82 | self.line.setFrameShape(QtWidgets.QFrame.HLine) 83 | self.line.setFrameShadow(QtWidgets.QFrame.Sunken) 84 | self.line.setObjectName("line") 85 | self.line_2 = QtWidgets.QFrame(Dialog) 86 | self.line_2.setGeometry(QtCore.QRect(150, 120, 51, 16)) 87 | self.line_2.setFrameShape(QtWidgets.QFrame.HLine) 88 | self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) 89 | self.line_2.setObjectName("line_2") 90 | self.line_3 = QtWidgets.QFrame(Dialog) 91 | self.line_3.setGeometry(QtCore.QRect(190, 90, 20, 40)) 92 | self.line_3.setFrameShape(QtWidgets.QFrame.VLine) 93 | self.line_3.setFrameShadow(QtWidgets.QFrame.Sunken) 94 | self.line_3.setObjectName("line_3") 95 | self.resample_combobox = QtWidgets.QComboBox(Dialog) 96 | self.resample_combobox.setGeometry(QtCore.QRect(260, 160, 181, 21)) 97 | font = QtGui.QFont() 98 | font.setPointSize(9) 99 | self.resample_combobox.setFont(font) 100 | self.resample_combobox.setObjectName("resample_combobox") 101 | self.resample_combobox.addItem("") 102 | self.resample_combobox.addItem("") 103 | self.resample_combobox.addItem("") 104 | self.resample_combobox.addItem("") 105 | self.resample_combobox.addItem("") 106 | self.resample_combobox.addItem("") 107 | self.resample_combobox.addItem("") 108 | self.resample_combobox.addItem("") 109 | self.resample_label = QtWidgets.QLabel(Dialog) 110 | self.resample_label.setGeometry(QtCore.QRect(190, 160, 61, 20)) 111 | font = QtGui.QFont() 112 | font.setPointSize(10) 113 | self.resample_label.setFont(font) 114 | self.resample_label.setObjectName("resample_label") 115 | self.resolution_label = QtWidgets.QLabel(Dialog) 116 | self.resolution_label.setGeometry(QtCore.QRect(20, 160, 61, 20)) 117 | font = QtGui.QFont() 118 | font.setPointSize(10) 119 | self.resolution_label.setFont(font) 120 | self.resolution_label.setObjectName("resolution_label") 121 | self.resolution_text = QtWidgets.QLineEdit(Dialog) 122 | self.resolution_text.setGeometry(QtCore.QRect(90, 160, 61, 20)) 123 | self.resolution_text.setObjectName("resolution_text") 124 | 125 | self.retranslateUi(Dialog) 126 | QtCore.QMetaObject.connectSlotsByName(Dialog) 127 | 128 | def retranslateUi(self, Dialog): 129 | _translate = QtCore.QCoreApplication.translate 130 | Dialog.setWindowTitle(_translate("Dialog", "Photoshop Bulk Image Resizer")) 131 | self.browse_btn.setText(_translate("Dialog", "Browse")) 132 | self.browse_label.setText(_translate("Dialog", "Browse Folder")) 133 | self.resize_btn.setText(_translate("Dialog", "Resize")) 134 | self.reset_btn.setText(_translate("Dialog", "Reset")) 135 | self.width_label.setText(_translate("Dialog", "Width")) 136 | self.height_label.setText(_translate("Dialog", "Height")) 137 | self.px_label_width.setText(_translate("Dialog", "px")) 138 | self.px_label_height.setText(_translate("Dialog", "px")) 139 | self.aspect_ratio_checkbox.setText(_translate("Dialog", "Maintain Aspect Ratio")) 140 | self.resample_combobox.setItemText(0, _translate("Dialog", "Automatic")) 141 | self.resample_combobox.setItemText(1, _translate("Dialog", "Preserve Details (Enlargement)")) 142 | self.resample_combobox.setItemText(2, _translate("Dialog", "Preserve Details (2.0)")) 143 | self.resample_combobox.setItemText(3, _translate("Dialog", "Bicubic Smoother (Enlargement)")) 144 | self.resample_combobox.setItemText(4, _translate("Dialog", "Bicubic Shaper (Reduction)")) 145 | self.resample_combobox.setItemText(5, _translate("Dialog", "Bicubic (Smooth Gradients)")) 146 | self.resample_combobox.setItemText(6, _translate("Dialog", "Nearest Neighbour (Hard Edges)")) 147 | self.resample_combobox.setItemText(7, _translate("Dialog", "Bi-linear")) 148 | self.resample_label.setText(_translate("Dialog", "Resample")) 149 | self.resolution_label.setText(_translate("Dialog", "Resolution")) 150 | self.resolution_text.setText(_translate("Dialog", "72")) 151 | 152 | 153 | if __name__ == "__main__": 154 | import sys 155 | app = QtWidgets.QApplication(sys.argv) 156 | Dialog = QtWidgets.QDialog() 157 | ui = Ui_Dialog() 158 | ui.setupUi(Dialog) 159 | Dialog.show() 160 | sys.exit(app.exec_()) 161 | 162 | -------------------------------------------------------------------------------- /gui_tool_example/resizer_ui.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 463 10 | 250 11 | 12 | 13 | 14 | Qt::NoFocus 15 | 16 | 17 | Photoshop Bulk Image Resizer 18 | 19 | 20 | 21 | 22 | 370 23 | 30 24 | 71 25 | 21 26 | 27 | 28 | 29 | 30 | 10 31 | 32 | 33 | 34 | Browse 35 | 36 | 37 | 38 | 39 | 40 | 120 41 | 30 42 | 231 43 | 21 44 | 45 | 46 | 47 | 48 | 49 | 50 | 20 51 | 30 52 | 91 53 | 21 54 | 55 | 56 | 57 | 58 | 10 59 | 60 | 61 | 62 | Browse Folder 63 | 64 | 65 | 66 | 67 | 68 | 360 69 | 210 70 | 81 71 | 23 72 | 73 | 74 | 75 | 76 | 10 77 | 78 | 79 | 80 | Resize 81 | 82 | 83 | 84 | 85 | 86 | 260 87 | 210 88 | 75 89 | 23 90 | 91 | 92 | 93 | 94 | 10 95 | 96 | 97 | 98 | Reset 99 | 100 | 101 | 102 | 103 | 104 | 90 105 | 80 106 | 61 107 | 20 108 | 109 | 110 | 111 | 112 | 113 | 114 | 20 115 | 80 116 | 51 117 | 21 118 | 119 | 120 | 121 | 122 | 10 123 | 124 | 125 | 126 | Width 127 | 128 | 129 | 130 | 131 | 132 | 90 133 | 120 134 | 61 135 | 20 136 | 137 | 138 | 139 | 140 | 141 | 142 | 20 143 | 120 144 | 51 145 | 21 146 | 147 | 148 | 149 | 150 | 10 151 | 152 | 153 | 154 | Height 155 | 156 | 157 | 158 | 159 | 160 | 160 161 | 70 162 | 21 163 | 21 164 | 165 | 166 | 167 | 168 | 10 169 | 170 | 171 | 172 | px 173 | 174 | 175 | 176 | 177 | 178 | 160 179 | 110 180 | 21 181 | 21 182 | 183 | 184 | 185 | 186 | 10 187 | 188 | 189 | 190 | px 191 | 192 | 193 | 194 | 195 | 196 | 210 197 | 100 198 | 151 199 | 17 200 | 201 | 202 | 203 | 204 | 10 205 | 206 | 207 | 208 | Maintain Aspect Ratio 209 | 210 | 211 | true 212 | 213 | 214 | 215 | 216 | 217 | 150 218 | 80 219 | 51 220 | 16 221 | 222 | 223 | 224 | Qt::Horizontal 225 | 226 | 227 | 228 | 229 | 230 | 150 231 | 120 232 | 51 233 | 16 234 | 235 | 236 | 237 | Qt::Horizontal 238 | 239 | 240 | 241 | 242 | 243 | 190 244 | 90 245 | 20 246 | 40 247 | 248 | 249 | 250 | Qt::Vertical 251 | 252 | 253 | 254 | 255 | 256 | 260 257 | 160 258 | 181 259 | 21 260 | 261 | 262 | 263 | 264 | 9 265 | 266 | 267 | 268 | 269 | Automatic 270 | 271 | 272 | 273 | 274 | Preserve Details (Enlargement) 275 | 276 | 277 | 278 | 279 | Preserve Details (2.0) 280 | 281 | 282 | 283 | 284 | Bicubic Smoother (Enlargement) 285 | 286 | 287 | 288 | 289 | Bicubic Shaper (Reduction) 290 | 291 | 292 | 293 | 294 | Bicubic (Smooth Gradients) 295 | 296 | 297 | 298 | 299 | Nearest Neighbour (Hard Edges) 300 | 301 | 302 | 303 | 304 | Bi-linear 305 | 306 | 307 | 308 | 309 | 310 | 311 | 190 312 | 160 313 | 61 314 | 20 315 | 316 | 317 | 318 | 319 | 10 320 | 321 | 322 | 323 | Resample 324 | 325 | 326 | 327 | 328 | 329 | 20 330 | 160 331 | 61 332 | 20 333 | 334 | 335 | 336 | 337 | 10 338 | 339 | 340 | 341 | Resolution 342 | 343 | 344 | 345 | 346 | 347 | 90 348 | 160 349 | 61 350 | 20 351 | 352 | 353 | 354 | 72 355 | 356 | 357 | 358 | 359 | 360 | 361 | -------------------------------------------------------------------------------- /interop assemblies/Interop.Photoshop.2020.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lohriialo/photoshop-scripting-python/5b9d6953945d96737477ab2ce6f9ebe6fc204318/interop assemblies/Interop.Photoshop.2020.dll -------------------------------------------------------------------------------- /interop assemblies/Interop.Photoshop.cc2019.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lohriialo/photoshop-scripting-python/5b9d6953945d96737477ab2ce6f9ebe6fc204318/interop assemblies/Interop.Photoshop.cc2019.dll -------------------------------------------------------------------------------- /interop assemblies/README.md: -------------------------------------------------------------------------------- 1 | These Interop DLLs here are provided as examples for scripting in C# For more information on how to generate Interop DLLs, see https://docs.microsoft.com/en-us/dotnet/framework/interop/how-to-generate-interop-assemblies-from-type-libraries 2 | -------------------------------------------------------------------------------- /mac_scripting/ActiveLayer.py: -------------------------------------------------------------------------------- 1 | # Set the active layer to the last art layer of the active document, or the 2 | # first if the last is already active. 3 | 4 | from appscript import app, k 5 | ps = app(id="com.adobe.Photoshop", terms="sdef") 6 | 7 | # We could also use "ps.count(None, k.documents)" here, if 8 | # we expect many documents to be open and speed was of concern. 9 | if len(ps.documents()) < 1: 10 | docRef = ps.make(new=k.document) 11 | else: 12 | docRef = ps.current_document 13 | 14 | if len(docRef.layers()) < 2: 15 | docRef.make(new=k.art_layer) 16 | 17 | if docRef.current_layer() != docRef.layers[-1](): 18 | docRef.current_layer.set(docRef.layers[-1]) 19 | else: 20 | docRef.current_layer.set(docRef.layers[1]) 21 | -------------------------------------------------------------------------------- /mac_scripting/ApplyCrystallizeFilterAction.py: -------------------------------------------------------------------------------- 1 | # This script demonstrates how you can use the action manager 2 | # to execute the Crystallize filter. 3 | 4 | from os.path import abspath 5 | from appscript import app, mactypes 6 | 7 | # Start up Photoshop application 8 | ps = app(id="com.adobe.Photoshop", terms="sdef") 9 | 10 | # This must be an absolute path. 11 | fileName = abspath("../PS_Samples_Files/Layer Comps.psd") 12 | ps.open(fileName) 13 | docRef = ps.current_document() 14 | 15 | nLayerSets = docRef.layer_sets 16 | nArtLayers = docRef.layer_sets[-1].art_layers 17 | 18 | # get the last layer in LayerSets 19 | docRef.current_layer.set(docRef.layer_sets[-1].art_layers[-1]) 20 | 21 | # custom actions like this must be done as Javascript, unfortunately 22 | def applyCrystallize(cellSize): 23 | script = """ 24 | cellSizeID = charIDToTypeID("ClSz"); 25 | eventCrystallizeID = charIDToTypeID("Crst"); 26 | filterDescriptor = new ActionDescriptor(); 27 | filterDescriptor.putInteger(cellSizeID, arguments[0]); 28 | executeAction(eventCrystallizeID, filterDescriptor); 29 | """ 30 | ps.do_javascript(script, with_arguments=[cellSize]) 31 | 32 | applyCrystallize(25) 33 | -------------------------------------------------------------------------------- /mac_scripting/ApplyFilters.py: -------------------------------------------------------------------------------- 1 | # This sample script shows how to apply 3 different filters to 2 | # selections in the open document. 3 | 4 | from os.path import abspath 5 | from appscript import app, k, mactypes 6 | 7 | # Start up Photoshop application 8 | ps = app(id="com.adobe.Photoshop", terms="sdef") 9 | 10 | # We don't want any Photoshop dialogs displayed during automated execution 11 | ps.display_dialogs.set(k.never) 12 | 13 | start_rulers = ps.settings.ruler_units.get() 14 | ps.settings.ruler_units.set(k.pixel_units) 15 | 16 | # This needs to be an absolute path 17 | fileName = abspath("../PS_Samples_Files/Layer Comps.psd") 18 | ps.open(fileName) 19 | docRef = ps.current_document() 20 | 21 | docRef.current_layer.set(docRef.layer_sets[-1].art_layers[-1]) 22 | active_layer = docRef.current_layer() 23 | 24 | sel_area = ((0, 212), (300, 212), (300, 300), (0, 300)) 25 | ps.select( 26 | docRef, region=sel_area, combination_type=k.replaced, feather_amount=20, antialiasing=True 27 | ) 28 | 29 | active_layer.filter( 30 | using=k.add_noise, 31 | with_options={k.amount: 15, k.distribution: k.Gaussian, k.monochromatic: False} 32 | ) 33 | 34 | ps.background_color.set({k.class_: k.HSB_color, k.hue: 0, k.saturation: 0, k.brightness: 100}) 35 | 36 | 37 | sel_area2 = ((120, 20), (210, 20), (210, 110), (120, 110)) 38 | ps.select( 39 | docRef, region=sel_area2, combination_type=k.replaced, feather_amount=25, antialiasing=False 40 | ) 41 | 42 | active_layer.filter( 43 | using=k.diffuse_glow, 44 | with_options={k.graininess: 9, k.glow_amount: 12, k.clear_amount: 15} 45 | ) 46 | 47 | active_layer.filter( 48 | using=k.glass_filter, 49 | with_options={ 50 | k.distortion: 7, 51 | k.smoothness: 3, 52 | k.scaling: 7, 53 | k.invert_texture: False, 54 | k.texture_kind: k.tiny_lens 55 | } 56 | ) 57 | 58 | docRef.deselect() 59 | 60 | # Set ruler units back the way we found it 61 | ps.settings.ruler_units.set(start_rulers) 62 | -------------------------------------------------------------------------------- /mac_scripting/CompareColors.py: -------------------------------------------------------------------------------- 1 | # This script compares the app.foregroundColor 2 | # to the app.backgroundColor. 3 | 4 | from appscript import app 5 | 6 | # Start up Photoshop application 7 | ps = app(id="com.adobe.Photoshop", terms="sdef") 8 | 9 | if ps.foreground_color() == ps.background_color(): 10 | print("They're Equal") 11 | else: 12 | print("NOT Equal") 13 | -------------------------------------------------------------------------------- /mac_scripting/ConvertColor.py: -------------------------------------------------------------------------------- 1 | # Convert the foreground color to RGB. 2 | 3 | from appscript import app, k 4 | 5 | ps = app(id="com.adobe.Photoshop", terms="sdef") 6 | 7 | fgColor = ps.foreground_color 8 | 9 | # Unlike JavaScript and COM, we have to use a function to convert colors 10 | fgRGBColor = fgColor.convert_color(to=k.RGB) 11 | print(f"Red: {fgRGBColor[k.red]} Green: {fgRGBColor[k.green]} Blue: {fgRGBColor[k.blue]}") 12 | -------------------------------------------------------------------------------- /mac_scripting/CopyAndPaste.py: -------------------------------------------------------------------------------- 1 | # This example makes a creates a selection in the activeDocument, copies the selection, 2 | # to the clipboard, creates a new document of the same dimensions 3 | # and pastes the contents of the clipboard into it. 4 | # It ensures that rulerUnits are set before creating the new document. 5 | # It checks the kind of the layer before making the selection to be 6 | # sure not to copy a text layer. 7 | 8 | from appscript import app, k 9 | 10 | ps = app(id="com.adobe.Photoshop", terms="sdef") 11 | 12 | strtRulerUnits = ps.settings.ruler_units() 13 | ps.settings.ruler_units.set(k.inch_units) 14 | 15 | srcDoc = ps.make( 16 | new=k.document, 17 | with_properties={k.width: 7, k.height: 5, k.resolution: 72, k.mode: k.RGB, k.initial_fill: k.white} 18 | ) 19 | 20 | # Make sure the active layer is not a text layer, which cannot be copied to the clipboard 21 | if srcDoc.current_layer.kind() != k.text_layer: 22 | # Select the left half of the document. Selections are always expressed 23 | # in pixels regardless of the current ruler unit type, so we're computing 24 | # the selection corner points based on the inch unit width and height 25 | # of the document 26 | x2 = (srcDoc.width() * srcDoc.height()) / 2 27 | y2 = srcDoc.height() * srcDoc.resolution() 28 | 29 | sel_area = ((0, 0), (x2, 0), (x2, y2), (0, y2)) 30 | srcDoc.select( 31 | region=sel_area, 32 | combination_type=k.replaced, 33 | feather_amount=0, 34 | antialiasing=False 35 | ) 36 | 37 | # AppleScript implementation of this function 38 | # can only copy and paste from the current_document 39 | # and only if it is the frontmost application 40 | ps.activate() 41 | ps.copy() 42 | 43 | # The new doc is created 44 | # need to change ruler units to pixels because x2 and y2 are pixel units. 45 | ps.settings.ruler_units = k.pixel_units 46 | pasteDoc = ps.make( 47 | new=k.document, 48 | with_properties={ 49 | k.width: x2, k.height: y2, k.resolution: srcDoc.resolution(), k.name: "Paste Target" 50 | } 51 | ) 52 | ps.paste() 53 | else: 54 | print("You cannot copy from a text layer") 55 | 56 | 57 | ps.settings.ruler_units.set(strtRulerUnits) 58 | -------------------------------------------------------------------------------- /mac_scripting/CopyAndRotate.py: -------------------------------------------------------------------------------- 1 | # Crop and rotate the active document. 2 | 3 | from os.path import abspath 4 | from appscript import app, k, mactypes 5 | 6 | # Start up Photoshop application 7 | # Or get Reference to already running Photoshop application instance 8 | 9 | ps = app(id="com.adobe.Photoshop", terms="sdef") 10 | 11 | # This must be an absolute path 12 | fileName = abspath("../PS_Samples_Files/Layer Comps.psd") 13 | ps.open(fileName) 14 | srcDoc = ps.current_document() 15 | 16 | strtRulerUnits = ps.settings.ruler_units() 17 | ps.settings.ruler_units.set(k.pixel_units) 18 | 19 | # crop a 10 pixel border from the image 20 | bounds = [10, 10, srcDoc.width() - 10, srcDoc.height() - 10] 21 | srcDoc.rotate_canvas(angle=45) 22 | srcDoc.crop(bounds=bounds) 23 | 24 | # set ruler back to where it was 25 | ps.settings.ruler_units.set(strtRulerUnits) 26 | -------------------------------------------------------------------------------- /mac_scripting/EmbossAction.py: -------------------------------------------------------------------------------- 1 | # This script demonstrates how you can use the action manager 2 | # to execute the Emboss filter. 3 | # Inorder to find all the IDs, see https://helpx.adobe.com/photoshop/kb/downloadable-plugins-and-content.html#ScriptingListenerplugin 4 | # This blog here exlains what a script listener is http://blogs.adobe.com/crawlspace/2006/05/installing_and_1.html 5 | 6 | from os.path import abspath 7 | from appscript import app, k, mactypes 8 | 9 | # Start up Photoshop application 10 | ps = app(id="com.adobe.Photoshop", terms="sdef") 11 | 12 | fileName = abspath("../PS_Samples_Files/Layer Comps.psd") 13 | ps.open(fileName) 14 | docRef = ps.current_document() 15 | 16 | docRef.current_layer.set(docRef.layer_sets[-1].art_layers[-1]) 17 | 18 | # Unfortunately, since there is no way to send raw Photoshop actions 19 | # via AppleScript, we must use JavaScript for this 20 | # Note: ScriptListener generated code looks awful, but you can tidy it up quite a lot ;) 21 | def emboss(angle, height, amount): 22 | ps.do_javascript(""" 23 | var desc = new ActionDescriptor() 24 | var c = charIDToTypeID 25 | desc.putInteger(c("Angl"), arguments[0]) 26 | desc.putInteger(c("Hght"), arguments[1]) 27 | desc.putInteger(c("Amnt"), arguments[2]) 28 | executeAction(c("Embs"), desc) 29 | """, with_arguments=[angle, height, amount]) 30 | 31 | emboss(120, 10, 100) 32 | -------------------------------------------------------------------------------- /mac_scripting/FillSelection.py: -------------------------------------------------------------------------------- 1 | # Fill the current selection with an RGB color. 2 | 3 | from appscript import app, k 4 | 5 | # Start up Photoshop application 6 | ps = app(id="com.adobe.Photoshop", terms="sdef") 7 | 8 | strtRulerUnits = ps.settings.ruler_units() 9 | 10 | if len(ps.documents()) < 1: 11 | ps.settings.ruler_units.set(k.pixel_units) 12 | docRef = ps.make( 13 | new=k.document, 14 | with_properties={ 15 | k.width: 320, k.height: 240, k.resolution: 72, k.mode: k.RGB, k.initial_fill: k.white 16 | } 17 | ) 18 | docRef.make(new=k.art_layer) 19 | ps.settings.ruler_units.set(strtRulerUnits) 20 | 21 | if ps.current_document.current_layer.background_layer() == False: 22 | selRef = ps.current_document.selection 23 | # Note the "_" here in both RGB_color_ and class_ 24 | fillcolor = {k.class_: k.RGB_color_, k.red: 255, k.green: 0, k.blue: 0} 25 | selRef.fill( 26 | with_contents=fillcolor, blend_mode=k.normal, opacity=25, preserving_transparency=False 27 | ) 28 | else: 29 | print("Can't perform operation on background layer") 30 | -------------------------------------------------------------------------------- /mac_scripting/HelloWorld.py: -------------------------------------------------------------------------------- 1 | # Hello World! 2 | 3 | from appscript import app, k 4 | 5 | ps = app(id="com.adobe.Photoshop", terms="sdef") 6 | doc = ps.make(new=k.document, with_properties={k.width: 320, k.height: 240}) 7 | layer_ref = doc.make(new=k.art_layer, with_properties={k.kind: k.text_layer}) 8 | 9 | 10 | text_item = layer_ref.text_object 11 | text_item.contents.set("HELLO WORLD") 12 | text_item.position.set([120, 120]) 13 | -------------------------------------------------------------------------------- /mac_scripting/LayerKind.py: -------------------------------------------------------------------------------- 1 | # This script demonstrates how to create a new layer and set its kind. 2 | 3 | from appscript import app, k 4 | 5 | # Start up Photoshop application 6 | ps = app(id="com.adobe.Photoshop", terms="sdef") 7 | 8 | strtRulerUnits = ps.settings.ruler_units() 9 | 10 | if len(ps.documents()) < 1: 11 | ps.settings.ruler_units.set(k.pixel_units) 12 | docRef = ps.make( 13 | new=k.document, 14 | with_properties={ 15 | k.width: 320, k.height: 240, k.resolution: 72, k.mode: k.RGB, k.initial_fill: k.white 16 | } 17 | ) 18 | else: 19 | docRef = ps.current_document() 20 | 21 | layerRef = docRef.make(new=k.art_layer) 22 | layerRef.kind.set(k.text_layer) 23 | # Set the ruler back to where it was 24 | ps.settings.ruler_units.set(strtRulerUnits) 25 | -------------------------------------------------------------------------------- /mac_scripting/LinkLayer.py: -------------------------------------------------------------------------------- 1 | # This scripts demonstrates how to link two layers. 2 | 3 | from appscript import app, k 4 | 5 | # Start up Photoshop application 6 | ps = app(id="com.adobe.Photoshop", terms="sdef") 7 | 8 | strtRulerUnits = ps.settings.ruler_units() 9 | 10 | if len(ps.documents()) < 1: 11 | ps.settings.ruler_units.set(k.pixel_units) 12 | docRef = ps.make(new=k.document, with_properties={ 13 | k.width: 320, k.height: 240, k.resolution: 72, k.mode: k.RGB, k.initial_fill: k.white 14 | } 15 | ) 16 | else: 17 | docRef = ps.current_document() 18 | 19 | layerRef = docRef.make(new=k.art_layer) 20 | layerRef2 = docRef.make(new=k.art_layer) 21 | layerRef.link(with_=layerRef2) 22 | 23 | # Set the ruler back to where it was 24 | ps.settings.ruler_units.set(strtRulerUnits) -------------------------------------------------------------------------------- /mac_scripting/LoadSelection.py: -------------------------------------------------------------------------------- 1 | # This script will demonstrate how to load a selection from a saved alpha channel. 2 | 3 | from appscript import app, k 4 | 5 | ps = app(id="com.adobe.Photoshop", terms="sdef") 6 | 7 | strtRulerUnits = ps.settings.ruler_units() 8 | ps.settings.ruler_units.set(k.pixel_units) 9 | 10 | docRef = ps.make(new=k.document, with_properties={k.width: 320, k.height: 240}) 11 | 12 | # To save querying Photoshop for every width/height 13 | # reference, it's faster to just store the values here 14 | width, height = docRef.width(), docRef.height() 15 | 16 | # Save a rectangular selection area offset by 50 pixels from the image border into an alpha channel 17 | offset = 50 18 | selBounds1 = ((offset, offset), (width - offset, offset), (width - offset, height - offset), (offset, height - offset)) 19 | docRef.select(region=selBounds1) 20 | selAlpha = docRef.make(new=k.channel) 21 | docRef.selection.store(into=selAlpha) 22 | 23 | # Now create a second wider but less tall selection 24 | selBounds2 = ((0, 75), (width, 75), (width, 150), (0, 150)) 25 | docRef.select(region=selBounds2) 26 | 27 | # Load the selection from the just saved alpha channel, combining it with the active selection 28 | docRef.selection.load(from_=selAlpha, combination_type=k.extended, inverting=False) 29 | 30 | # Set ruler back to where it was 31 | ps.settings.ruler_units.set(strtRulerUnits) 32 | -------------------------------------------------------------------------------- /mac_scripting/MakeSelection.py: -------------------------------------------------------------------------------- 1 | # Get the active document and make a new selection. 2 | 3 | from appscript import app, k 4 | 5 | # Start up Photoshop application 6 | ps = app(id="com.adobe.Photoshop", terms="sdef") 7 | 8 | # create new document if no document is opened 9 | # can also use len(ps.documents()) here 10 | if ps.count(None, each=k.document) < 1: 11 | strtRulerUnits = ps.settings.ruler_units() 12 | ps.settings.ruler_units.set(k.pixel_units) 13 | docRef = ps.make(new=k.document, with_properties={ 14 | k.width: 320, k.height: 240, k.resolution: 72, k.mode:k.RGB, k.initial_fill: k.white 15 | } 16 | ) 17 | ps.settings.ruler_units.set(strtRulerUnits) 18 | else: 19 | docRef = ps.current_document 20 | 21 | sel_area = ((50, 60), (150, 60), (150, 120), (50, 120)) 22 | docRef.select(region=sel_area, combination_type=k.replaced, feather_amount=5.5, antialiasing=False) 23 | -------------------------------------------------------------------------------- /mac_scripting/NewDocument.py: -------------------------------------------------------------------------------- 1 | # Create a new Photoshop document with diminsions 4 inches by 4 inches. 2 | 3 | from appscript import app, k 4 | 5 | # Start up Photoshop application 6 | ps = app(id="com.adobe.Photoshop", terms="sdef") 7 | 8 | strtRulerUnits = ps.settings.ruler_units() 9 | ps.settings.ruler_units.set(k.inch_units) 10 | 11 | # Create the document 12 | docRef = ps.make( 13 | new=k.document, 14 | with_properties={ 15 | k.width: 4, k.height: 4, k.resolution: 72, k.name: "My New Document" 16 | } 17 | ) 18 | 19 | # Make sure to set the ruler units prior to creating the document. 20 | ps.settings.ruler_units.set(strtRulerUnits) 21 | -------------------------------------------------------------------------------- /mac_scripting/OpenDocument.py: -------------------------------------------------------------------------------- 1 | # Open a Photoshop document located in the Photoshop samples folder 2 | # You must first create a File object to pass into the open method. 3 | 4 | from os.path import abspath 5 | from appscript import app, mactypes 6 | 7 | # Start up Photoshop application 8 | ps = app(id="com.adobe.Photoshop", terms="sdef") 9 | 10 | # Must be an absolute path 11 | fileName = abspath("../PS_Samples_Files/Layer Comps.psd") 12 | 13 | 14 | # Note: the open command (frustratingly) does not return a value 15 | # but it will always make the opened document the current document 16 | # even if it's already open. 17 | ps.open(fileName) 18 | docRef = ps.current_document() 19 | 20 | 21 | # There is also the mactype.Alias or mactype.File that you can use classes like so: 22 | # ps.open(mactypes.Alias(fileName)) 23 | # ps.open(mactypes.File(fileName)) 24 | # These are equivalent to the AppleScript "file" and "alias" classes 25 | -------------------------------------------------------------------------------- /mac_scripting/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Photoshop Scripting in Python on Mac 3 | Just like `pypiwin32` or `comtypes` is used to script via COM on windows, `appscript` is used to script on Mac. Photoshop scripting is exposed via it's scriptable dictionary. 4 | 5 | [appscript](https://github.com/lohriialo/appscript) is a high-level, user-friendly Apple event bridge that allows you to control Apple's Scriptable macOS applications from Python. 6 | 7 | `pip install appscript` 8 | 9 | Usage: 10 | 11 | ```python 12 | from appscript import app 13 | 14 | psApp = app(id="com.adobe.Photoshop", terms="sdef") 15 | psApp.open("/absolute/reference/to/file.psd") 16 | ``` 17 | 18 | # Hello World! 19 | ```python 20 | from appscript import app, k 21 | 22 | ps = app(id="com.adobe.Photoshop", terms="sdef") 23 | doc = ps.make(new=k.document, with_properties={k.width: 320, k.height: 240}) 24 | layer_ref = doc.make(new=k.art_layer, with_properties={k.kind: k.text_layer}) 25 | 26 | text_item = layer_ref.text_object 27 | text_item.contents.set("HELLO WORLD") 28 | text_item.position.set([120, 120]) 29 | ``` 30 | 31 | # Differences to Windows/COM based scripting. 32 | Anything possible in Windows' COM based scripting is also possible in `appscript`, but I hope the example above shows that the syntax is quite different, and scripts designed for COM do not pass over to the macOS based scripting without tweaking. 33 | 34 | Here are a few differences to look out for: 35 | 36 | 1. The syntax Adobe produces for AppleScript (which `appscript` bases it's terminology on) is just different to what it produces for the COM API. 37 | 2. In `appscript`, referencing an object or property does not return the _value_ of that object or property, to do that you must _call_ the reference, or its `get()` method. For example, `ps.documents[1].art_layers[1].name` returns a reference, not a string of the name. To retrieve the actual value of the layer's name, you must call it `ps.documents[1].art_layers[1].name()`. To set the value, you call `...art_layers[1].name.set(value)` 38 | 3. When using `comtypes`, you need to manually define constants/enums (e.g. the values for RulerUnits that are used a lot in the examples here). This is not the case with `appscript`, they are parsed from the terminology and stored in a global `k` object. 39 | 4. Keyword arguments! As mentioned in the [`appscript` documentation](https://appscript.sourceforge.io/py-appscript/doc_3x/appscript-manual/11_applicationcommands.html), if an argument (a.k.a. a parameter) has a keyword in the AppleScript documentation, it must have that keyword when you call it. 40 | 5. AppleScript container indcies start at 1, not 0, and `appscript` inherits this. If you're iterating over a list of appscript objects using `enumerate(...)` or `range(len(...))`, always start at 1, or add 1. 41 | 42 | # API Reference 43 | The most up‑to‐date reference for functions, arguments, keywords, and other Photoshop specific information will always be the Scripting Dictionary that comes with your install of Photoshop. 44 | 45 | You can access this by opening the Script Editor app, then going to: 46 | `File` > `Open Dictionary...` > `Adobe Photoshop 20XX.app`. 47 | 48 | To translate this to `appscript` syntax, you can simply follow the rules specified [here](https://appscript.sourceforge.io/py-appscript/doc_3x/appscript-manual/05_keywordconversion.html): 49 | 50 | - Replace `"&"` with `"and"` 51 | - Replace any remaining spaces or symbols with `"_"` 52 | - Any names that are a reserved Python keyword (i.e. `"def"`, `"class"`, `"in"`, `"from"`) have an `"_"` appended to the end 53 | 54 | The developer of `appscript` has a tool called [ASDictionary](https://appscript.sourceforge.io/tools.html#asdictionary) which can be useful as a reference. 55 | 56 | It's worth having a cursory browse of appscript's [documentation](https://appscript.sourceforge.io/py-appscript/doc_3x/appscript-manual) to get a feel for how to translate AppleScript documentation for use in appscript. It's very verbose though, be warned. 57 | 58 | See [Photoshop CC 2018 doc/reference](https://github.com/lohriialo/photoshop-scripting-python/tree/master/mac_scripting/doc_reference) for an appscript specific reference. 59 | 60 | Also see Adobe's [AppleScript developer reference](https://github.com/Adobe-CEP/CEP-Resources/blob/master/Documentation/Product%20specific%20Documentation/Photoshop%20Scripting/photoshop-applescript-ref-2020.pdf). 61 | -------------------------------------------------------------------------------- /mac_scripting/RotateLayer.py: -------------------------------------------------------------------------------- 1 | # This scripts demonstrates how to rotate a layer 45 degrees clockwise. 2 | 3 | from appscript import app, k 4 | 5 | # Start up Photoshop application 6 | ps = app(id="com.adobe.Photoshop", terms="sdef") 7 | 8 | # ps.count(None, each=k.documents) also works instead of len() here 9 | if len(ps.documents()) > 0: 10 | if ps.current_document.current_layer.background_layer() == False: 11 | docRef = ps.current_document() 12 | layerRef = docRef.current_layer() 13 | layerRef.rotate(angle=45.0) 14 | else: 15 | print("Operation cannot be performed on background layer") 16 | else: 17 | print("You must have at least one open document to run this script!") 18 | -------------------------------------------------------------------------------- /mac_scripting/SelectTool.py: -------------------------------------------------------------------------------- 1 | from appscript import app 2 | 3 | ps = app(id="com.adobe.Photoshop", terms="sdef") 4 | 5 | # i.e. 'marqueeRectTool', 'moveTool' 6 | print(ps.current_tool()) 7 | -------------------------------------------------------------------------------- /mac_scripting/SelectionStroke.py: -------------------------------------------------------------------------------- 1 | # Create a stroke around the current selection. 2 | # Set the stroke color and width of the new stroke. 3 | 4 | from appscript import app, k 5 | 6 | # Start up Photoshop application 7 | ps = app(id="com.adobe.Photoshop", terms="sdef") 8 | 9 | # ps.count(None, each=k.documents) also works instead of len() here 10 | if len(ps.documents()) > 0: 11 | if ps.current_document.current_layer.background_layer() == False: 12 | strtRulerUnits = ps.settings.ruler_units() 13 | ps.settings.ruler_units.set(k.pixel_units) 14 | 15 | width, height = ps.current_document.width(), ps.current_document.height() 16 | 17 | selRef = ps.current_document 18 | offset = 10 19 | selBounds = ( 20 | (offset, offset), 21 | (width - offset, offset), 22 | (width - offset, height - offset), 23 | (offset, height - offset) 24 | ) 25 | 26 | selRef.select(region=selBounds) 27 | selRef.select_border(width=5) 28 | 29 | # create text color properties 30 | strokeColor = { 31 | k.class_: k.CMYK_color, 32 | k.cyan: 20, 33 | k.magenta: 90, 34 | k.yellow: 50, 35 | k.black: 50 36 | } 37 | 38 | # We don't want any Photoshop dialogs displayed during automated execution 39 | ps.display_dialogs.set(k.never) 40 | selRef.stroke( 41 | using_color=strokeColor, 42 | width=2, 43 | location=k.outside, 44 | blend_mode=k.vivid_light, 45 | opacity=75, 46 | preserving_transparency=True 47 | ) 48 | 49 | # Set ruler units back the way we found it 50 | ps.settings.ruler_units.set(strtRulerUnits) 51 | else: 52 | print("Operation cannot be performed on background layer") 53 | else: 54 | print("Create a document with an active selection before running this script!") 55 | -------------------------------------------------------------------------------- /mac_scripting/SmartSharpen.py: -------------------------------------------------------------------------------- 1 | # This script demonstrates how you can use the action manager 2 | # to execute the Smart Sharpen filter. 3 | 4 | from os.path import abspath 5 | from appscript import app, k 6 | 7 | # Start up Photoshop application 8 | ps = app(id="com.adobe.Photoshop", terms="sdef") 9 | 10 | fileName = abspath("../PS_Samples_Files/Layer Comps.psd") 11 | ps.open(fileName) 12 | docRef = ps.current_document() 13 | 14 | docRef.current_layer.set(docRef.layer_sets[-1].art_layers[-1]) 15 | 16 | # While the AppleScript/appscript implementation does not expose an 17 | # equivalent to executeAction, we can still use do_javascript to build 18 | # and pass arguments to do the exact same thing from Python. 19 | def SmartSharpen(amount, radius, noise): 20 | # Note: I have cleaned up the ScriptListener generated code here 21 | ps.do_javascript(""" 22 | var s = stringIDToTypeID 23 | var c = charIDToTypeID 24 | maindesc = new ActionDescriptor() 25 | maindesc.putEnumerated(s("presetKind"), s("presetKindType"), s("presetKindCustom")) 26 | maindesc.putUnitDouble(c("Amnt"), c("#Prc"), arguments[0]) 27 | maindesc.putUnitDouble(c("Rds "), c("#Pxl"), arguments[1]) 28 | maindesc.putUnitDouble(s("noiseReduction"), c("#Prc"), arguments[2]) 29 | maindesc.putEnumerated(c("blur"), s("blurType"), c("GsnB")) 30 | executeAction(s("smartSharpen"), maindesc, DialogModes.NO) 31 | """, with_arguments=[amount, radius, noise]) 32 | 33 | SmartSharpen(300, 2.0, 20) 34 | -------------------------------------------------------------------------------- /mac_scripting/WorkingWithText.py: -------------------------------------------------------------------------------- 1 | # Create a new art layer and convert it to a text layer. 2 | # Set its contents, size and color. 3 | 4 | from appscript import app, k 5 | 6 | # Start up Photoshop application 7 | ps = app(id="com.adobe.Photoshop", terms="sdef") 8 | 9 | strtRulerUnits = ps.settings.ruler_units() 10 | strtTypeUnits = ps.settings.type_units() 11 | ps.settings.ruler_units.set(k.inch_units) 12 | ps.settings.type_units.set(k.point_units) 13 | 14 | # suppress all dialogs 15 | ps.display_dialogs.set(k.never) 16 | 17 | # create a new document 18 | docRef = ps.make(new=k.document, with_properties={k.width:7, k.height:5, k.resolution:72}) 19 | 20 | # create text color properties 21 | textColor = {k.class_: k.RGB_color_, k.red: 225, k.green: 0, k.blue: 0} 22 | 23 | # add a new text layer to document and apply the text color 24 | # note: we can make the new layer and set the kind property in one command. 25 | newTextLayer = docRef.make( 26 | new=k.art_layer, 27 | with_properties={k.kind: k.text_layer} 28 | ) 29 | newTextLayer.text_object.contents.set("Hello, World!") 30 | newTextLayer.text_object.position.set([0.75, 0.75]) 31 | newTextLayer.text_object.size.set(36) 32 | newTextLayer.text_object.stroke_color.set(textColor) 33 | 34 | # set the app preference the way it was before the operation 35 | ps.settings.ruler_units.set(strtRulerUnits) 36 | ps.settings.type_units.set(strtTypeUnits) 37 | -------------------------------------------------------------------------------- /mac_scripting/doc_reference/PhotoshopCC2018_docs_reference_appscript.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lohriialo/photoshop-scripting-python/5b9d6953945d96737477ab2ce6f9ebe6fc204318/mac_scripting/doc_reference/PhotoshopCC2018_docs_reference_appscript.pdf -------------------------------------------------------------------------------- /ps.py: -------------------------------------------------------------------------------- 1 | import time 2 | from win32com.client import Dispatch, GetActiveObject, GetObject 3 | 4 | # This is a test script written to see how Python scripting works in executing 5 | # Photoshop actions, ExecuteAction() 6 | # Where did I get all this action stuffs(ActionDescriptor, ActionReference, CharIDToTypeID etc)? 7 | # It's generated by a plugin Photoshop provides or you can generate one yourself 8 | # See https://www.adobe.com/devnet/photoshop/scripting.html 9 | 10 | # Start up Photoshop application 11 | # app = Dispatch('Photoshop.Application') 12 | 13 | # Or get Reference to already running Photoshop application 14 | # app = GetObject(Class="Photoshop.Application") 15 | app = GetActiveObject("Photoshop.Application") 16 | 17 | # psDisplayNoDialogs is a PS COM constant, see pscc2018.py or scripting COM 18 | psDisplayNoDialogs = 3 19 | for index, x in enumerate(range(50)): 20 | # app.DoAction('Sepia Toning (layer)', 'Default Actions') 21 | 22 | # Execute an existing action from action palette 23 | idPly = app.CharIDToTypeID("Ply ") 24 | desc8 = Dispatch('Photoshop.ActionDescriptor') 25 | idnull = app.CharIDToTypeID("null") 26 | ref3 = Dispatch('Photoshop.ActionReference') 27 | idActn = app.CharIDToTypeID("Actn") 28 | ref3.PutName(idActn, "Sepia Toning (layer)") 29 | idASet = app.CharIDToTypeID("ASet") 30 | ref3.PutName(idASet, "Default Actions") 31 | desc8.PutReference(idnull, ref3) 32 | app.ExecuteAction(idPly, desc8, psDisplayNoDialogs) 33 | # This time delay prevents COM busy error 34 | time.sleep(0.5) 35 | 36 | # Create solid color fill layer. 37 | idMk = app.CharIDToTypeID("Mk ") 38 | desc21 = Dispatch('Photoshop.ActionDescriptor') 39 | idNull = app.CharIDToTypeID("null") 40 | ref12 = Dispatch('Photoshop.ActionReference') 41 | idContentLayer1 = app.StringIDToTypeID("contentLayer") 42 | ref12.PutClass(idContentLayer1) 43 | desc21.PutReference(idNull, ref12) 44 | idUsng = app.CharIDToTypeID("Usng") 45 | desc22 = Dispatch('Photoshop.ActionDescriptor') 46 | idType = app.CharIDToTypeID("Type") 47 | desc23 = Dispatch('Photoshop.ActionDescriptor') 48 | idClr = app.CharIDToTypeID("Clr ") 49 | desc24 = Dispatch('Photoshop.ActionDescriptor') 50 | idRd = app.CharIDToTypeID("Rd ") 51 | desc24.PutDouble(idRd, index) 52 | idGrn = app.CharIDToTypeID("Grn ") 53 | desc24.PutDouble(idGrn, index) 54 | idBl = app.CharIDToTypeID("Bl ") 55 | desc24.PutDouble(idBl, index) 56 | idRGBC = app.CharIDToTypeID("RGBC") 57 | desc23.PutObject(idClr, idRGBC, desc24) 58 | idSolidColorLayer = app.StringIDToTypeID("solidColorLayer") 59 | desc22.PutObject(idType, idSolidColorLayer, desc23) 60 | idContentLayer2 = app.StringIDToTypeID("contentLayer") 61 | desc21.PutObject(idUsng, idContentLayer2, desc22) 62 | app.ExecuteAction(idMk, desc21, psDisplayNoDialogs) 63 | time.sleep(0.5) 64 | 65 | # Select mask 66 | idSlct = app.CharIDToTypeID("slct") 67 | desc38 = Dispatch('Photoshop.ActionDescriptor') 68 | idNull1 = app.CharIDToTypeID("null") 69 | ref20 = Dispatch('Photoshop.ActionReference') 70 | idChnl1 = app.CharIDToTypeID("Chnl") 71 | idChnl2 = app.CharIDToTypeID("Chnl") 72 | idMsk = app.CharIDToTypeID("Msk ") 73 | ref20.PutEnumerated(idChnl1, idChnl2, idMsk) 74 | desc38.PutReference(idNull1, ref20) 75 | idMkVs = app.CharIDToTypeID("MkVs") 76 | desc38.PutBoolean(idMkVs, False) 77 | app.ExecuteAction(idSlct, desc38, psDisplayNoDialogs) 78 | time.sleep(0.5) 79 | 80 | app.ActiveDocument.ActiveLayer.Invert() 81 | 82 | 83 | 84 | --------------------------------------------------------------------------------