Python and photoshop- code snippets

I thought I might make a little list of commands that I use *alot* in my Photoshop Python scripts in case they might be useful to other people out there trying to do the same thing. These examples are written using Python 2.7.


An invaluable help is the Photoshop Cs4 Visual basic scripting guide. Although the scripting guide does not specifically deal with Python, the majority of the VB code works straight out of the box.

Before anything else, you need to set up the COM interface! I recommend using the comtypes module. The win32com module can also do the job, but you will find it very limiting if you want to do anything with paths or selection arrays.

This is how I usually create my photoshop COM reference in my scripts:

import comtypes.client

psApp = comtypes.client.CreateObject('Photoshop.Application')


The psApp object that I have created is my reference to the photoshop application. From now on, I can call psApp to execute various actions. It's worth noting that if you call your script and Photoshop is not already open, the comtypes client will start the program for you.

Most of my scripts deal with the current active photoshop document- if you have more than one photoshop document open, the reference will point to whichever one is active in the layers window. This is how you create a reference to that object. 

doc = psApp.activeDocument

In some instances you may need to create a new photoshop document to perform work on. Notice that I included setting the default units. If you skip this you might end up making a 1024cm x 1024cm document by mistake! (true story.)

#Set units to pixels.
psApp.Preferences.RulerUnits = 1

#Make a new photoshop document
doc = psApp.Documents.Add(1024, 1024, 72, "new_document", 2, 1, 1)

As you will notice, its a little more complicated and involves a few seemingly random arguments. Here is a breakdown of the arguments in order:

Add(width, height, dpi/resolution, document name, mode, initial-fill, aspect-ratio, bits per channel, color profile)

Most of these are pretty straight forward, but some need a little bit of clarification- I suggest looking at page 71 of the Photoshop Cs4 Visual basic scripting guide for more details, under Methods- Add.

Adding groups and art layers with a script is very handy. It can be a powerful tool in automatically setting up document templates. Here is how to set up a base color, create a new art layer and then fill it with your color.

#Define the color- in this case, flat normal blue
baseColor = comtypes.client.CreateObject('Photoshop.SolidColor')
baseColor.rgb.red = 128
baseColor.rgb.green = 128
baseColor.rgb.blue = 255

#Create a new layer to apply our color to. When you add a new layer, it becomes the active layer. 
new_art_layer = new_layerSet.ArtLayers.Add()
new_art_layer.name = 'flatNormal'
            
#Fill the layer with appropriate color
psApp.activeDocument.selection.selectAll
psApp.activeDocument.selection.Fill(baseColor)



Defining save options is also something I do alot. Depending on which format your project demands, you will find you need to use different methods of defining options. I use the TGA and PSD formats, but Adam Pletcher has a good example of PNG save options.

This is the code to define TGA and PSD save options, and how to execute it:

doc = psApp.activeDocument
# Define 24bit Targa save options
options = comtypes.client.CreateObject('Photoshop.TargaSaveOptions')
PsTargaBitsPerPixels = 24
options.Resolution = PsTargaBitsPerPixels
options.AlphaChannels = False
options.RLECompression = False 

# Define 32bit Targa save options
optionsAlpha = comtypes.client.CreateObject('Photoshop.TargaSaveOptions')
PsTargaBitsPerPixels = 32
optionsAlpha.Resolution = PsTargaBitsPerPixels
optionsAlpha.AlphaChannels = True
optionsAlpha.RLECompression = False 

# Define PSD save options
psdOptions = comtypes.client.CreateObject('Photoshop.PhotoshopSaveOptions')
psdOptions.annotations = False
psdOptions.alphaChannels = True
psdOptions.layers = True
psdOptions.spotColors = True
psdOptions.embedColorProfile = True

#Save in the different ways
#PSD
doc.SaveAs('c:\\yourpath\\filename.psd',psdOptions)

#TGA
doc.SaveAs('c:\\yourpath\\filename.tga', options)

#TGA32
doc.SaveAs('c:\\yourpath\\filename.tga', optionsAlpha)


I have found in photoshop Cs2 that in order to save as a TGA from a layered PSD, you have to flatten the document before saving it, otherwise it will think you want to save it as a copy, which has to be one of the most annoying things ever. Without going into all the hacky work arounds, here is a good chance to bring up one final handy thing... how to undo hacky work-arounds with history states!

Here is how you can save and recall a history state in a document using python:

#save the current setup
saved_state = doc.activeHistoryState

#Do some stuff, like flatten the document and copy it...
doc.flatten()
doc.activeLayer.Copy()

#Now revert to the saved state and paste the merged copy in as a new layer
doc.ActiveHistoryState = saved_state 

doc.Paste()


Finally, calling your script from Photoshop is pretty important if you want the artists not to hate you for making them use the console (more likely they just won't use your tool!).

I have yet to find a way to execute my Python script directly, but you can use intermediate scripts to call them from an action. I will readily admit this is a clumsy way of doing it, but if anyone has a more streamlined approach to this I would love to know!

First, make a .bat file that references your python script... the following script has the handy addition that it references the scripts directory relative to it's current location using %~dp0.

@echo off
call python %~dp0\\yourPythonScript.py

Then, you can have a Photoshop javascript call the bat file...

var script = new File("/c/scriptLocation/run_yourPythonScript.bat");

script.execute();


Finally, you have a photoshop action that calls the javascript.

Pros to this method:
Callable from within Photoshop via an action.
It works!

Cons:
-Convoluted setup.
-Calls up a cmd window on execution (which can be good for error feedback, but is also a little irritating). 

To finish off with, here is a full example of a working Python script that makes use of some of the code listed above. This script creates a new document, complete with pre-named groups and art layers, ready for an artist to work on.

##############################################################################
# 
# Photoshop doc setup script
# Author: Pete Hanshaw, 2012
# http://peterhanshawart.blogspot.com.au/
#
##############################################################################
#
# Creates a new PSD document and sets up placeholder groups and layers 
# ready for texture work.
#
# Creates:
# -'n' group for normal map
# -'s' group for specular map
# -'d' group for diffuse map
#
# Requires comtypes:
# http://sourceforge.net/projects/comtypes/
#
##############################################################################

#Import required modules
import comtypes.client
from sys import exit
import pythoncom

#Set up the document. More groups can be added easily by creating more dict keys
group_names = {}

#Group name : artLayer name
group_names['d'] = 'dif_base'
group_names['s'] = 'spec_base'
group_names['n'] = 'nrm_base'

#Begin the script            
if (__name__ == '__main__'):
    #COM dispatch for Photoshop
    psApp = comtypes.client.CreateObject('Photoshop.Application')

    #Define the fill colors
    #Normal base
    nrm_SolidColor = comtypes.client.CreateObject('Photoshop.SolidColor')
    nrm_SolidColor.rgb.red = 128
    nrm_SolidColor.rgb.green = 128
    nrm_SolidColor.rgb.blue = 255

    #Define the fill colors
    #Spec base
    spec_SolidColor = comtypes.client.CreateObject('Photoshop.SolidColor')
    spec_SolidColor.rgb.red = 0
    spec_SolidColor.rgb.green = 0
    spec_SolidColor.rgb.blue = 0
    
    #Define the fill colors
    #Diffuse base
    dif_SolidColor = comtypes.client.CreateObject('Photoshop.SolidColor')
    dif_SolidColor.rgb.red = 128
    dif_SolidColor.rgb.green = 128
    dif_SolidColor.rgb.blue = 128

    #Set the default unit to pixels!
    psApp.Preferences.RulerUnits = 1
    
    #w, h, res, name, mode, initial-fill, asp-ratio, Bits-P/Ch, ColProfile
    new_doc = psApp.Documents.Add(1024, 1024, 72, "new_source_texture", 2, 1, 1)

    print "Setting up a", new_doc.name
    
    for group, layer in group_names.items():

        new_layerSet = new_doc.LayerSets.Add()
        new_layerSet.name = group

        if group == 'n':
            new_art_layer = new_layerSet.ArtLayers.Add()
            new_art_layer.name = layer
            
            #Fill the layer with appropriate color
            #Filltype, model, opacity, preserveTransparancy
            psApp.activeDocument.selection.selectAll
            psApp.activeDocument.selection.Fill(nrm_SolidColor)
            new_layerSet.Visible = False

        elif group == 's':
            new_art_layer = new_layerSet.ArtLayers.Add()
            new_art_layer.name = layer
            
            #Fill the layer with appropriate color
            #Filltype, model, opacity, preserveTransparancy
            psApp.activeDocument.selection.selectAll
            psApp.activeDocument.selection.Fill(spec_SolidColor)
            new_layerSet.Visible = False

        elif group == 'd':
            new_art_layer = new_layerSet.ArtLayers.Add()
            new_art_layer.name = layer
            
            #Fill the layer with appropriate color
            #Filltype, model, opacity, preserveTransparancy
            psApp.activeDocument.selection.selectAll
            psApp.activeDocument.selection.Fill(dif_SolidColor)

        else:
            new_art_layer = new_layerSet.ArtLayers.Add()
            new_art_layer.name = layer
            
            #Fill the layer with appropriate color
            #Filltype, model, opacity, preserveTransparancy
            psApp.activeDocument.selection.selectAll
            psApp.activeDocument.selection.Fill(dif_SolidColor)

    #Reorder the photoshop layers so that it reads from top down:
    #normal, spec, diffuse
    dGroup = psApp.activeDocument.layerSets['d']

    #See Adobe's photoshop_cs4_vbscript_ref.pdf to make sense of this-
    dGroup.Move(new_doc, 2)

    #Deselect the fill area
    psApp.activeDocument.selection.deselect

    #Set the active layer to the diffuse
    psApp.activeDocument.activeLayer = (psApp.activeDocument.layerSets['d'].
                                        artLayers['dif_base'])
    
    print "All done!"
    exit(1)


So that's just a handful of useful bits and bobs. If you try any of the code here, and it doesn't work, let me know and I'll update the post with a fix.

-Pete

11 comments:

  1. This is great.
    Is there a way to close PhotoShop after running a script?
    psApp = comtypes.client.Close?

    ReplyDelete
    Replies
    1. Hi Zack,

      Glad you found the post useful! Yeah, its pretty simple to add. Just use your object reference with the quit command, for example:

      #Object reference
      psApp = comtypes.client.CreateObject('Photoshop.Application')

      #Quit command
      psApp.Quit()

      Delete
  2. Thanks for this! Very helpful. I've dug around Adobe's COM scripting manual, but haven't found what to call in order to add a drop shadow to a layer. Any idea how this would be done?

    ReplyDelete
    Replies
    1. Hi Daniel, I'm glad that you have found the snippets helpful. I don't think that the drop shadow function is exposed to the COM interface, but you can get around this limitation (as well as many others) by using the Photoshop script listener.

      I've written a post that shows an example of how to go through script-listener output and hammer it into useful python code:

      http://peterhanshawart.blogspot.com.au/2012/05/python-and-photoshop-script-listener.html

      The example is for selecting a color range in an image, but the same ideas can be applied to recording the drop shadow functionality.

      Good luck :)

      Delete
  3. Thanks Pete for this. I am just starting out with Python & Photoshop COM interface.

    Presently i am trying to open the PSD file, do some stuff and save as a TIFF file with custom settings. Almost everything worked without hassles, thanks to your snippets.

    But i am stuck with, the photoshop Save options part.
    Trying the following code gives error.
    psdOptions = comtypes.client.CreateObject('Photoshop.PhotoshopSaveOptions')

    Python is unable to Create the SaveOptions or TiffSaveOptions Object.

    ------------------------ERROR START----------------------------------------------
    Traceback (most recent call last):
    File "D:\dev\eclipse\workspace\PsAppConnection\src\ConnectionModule.py", line 14, in
    psSave = PSCOM.CreateObject('Photoshop.PhotoshopSaveOptions')
    File "C:\Python27\lib\site-packages\comtypes\client\__init__.py", line 224, in CreateObject
    clsid = comtypes.GUID.from_progid(progid)
    File "C:\Python27\lib\site-packages\comtypes\GUID.py", line 78, in from_progid
    _CLSIDFromProgID(unicode(progid), byref(inst))
    File "_ctypes/callproc.c", line 941, in GetResult
    WindowsError: [Error -2147221005] Invalid class string
    ------------------------ERROR END----------------------------------------------

    I am aware of the option of getting Action Descriptor Code, but wanted to know how it worked for you and why it is not working for me.

    Thanks.
    Priyabrata

    ReplyDelete
    Replies
    1. Hi Priyabrata,

      Sorry for the late response! If you have not already fixed your issue, the information you need is contained in the VBScripting documentation (See the links window on the right side of my blog :) )

      The reason your code was not working is because TIFF files have their own save options, separate to those used to save a PSD.

      Here is a quick example of how to save a TIFF:

      ---
      import win32com.client as w32

      PS_APP = w32.Dispatch('Photoshop.Application')

      # Make a new document
      doc = PS_APP.Documents.Add(512, 512, 72, "SaveTest", 2, 1, 1)

      fileName = "C:\\test.tif"

      # Tiff save options are a unique object, with it's own methods
      # Seperate from the PhotoshopSaveOptions
      tiffOptions = w32.Dispatch('Photoshop.TiffSaveOptions')
      tiffOptions.AlphaChannels = False
      tiffOptions.EmbedColorProfile = True
      tiffOptions.ImageCompression = 3
      tiffOptions.Layers = False

      # Save it!
      doc.SaveAs(fileName, tiffOptions)
      ---

      Delete
  4. This comment has been removed by the author.

    ReplyDelete
  5. Heya

    Thanks for this great tutorial ! I'm just starting my adventure with Python for PS and this is great start!
    However quick issue I found in it...
    ur code is:

    new_art_layer = new_layerSet.ArtLayers.Add()

    should it not be
    new_art_layer = doc.ArtLayers.Add()
    This one works on my end at least:)
    Thanks again for help !

    ReplyDelete
    Replies
    1. Hey Dariusz,
      I'm glad you are finding the examples useful! About the issue you found- Yes, calling new_layerSet.ArtLayers.Add() by itself will not work.

      In the example above, "new_layerSet" is actually a reference to the object returned by calling "new_layerSet = new_doc.LayerSets.Add()" on line 71. Out of the context of the for loop this will not work, as the program will not know what you are referring to when you say "new_layerSet".

      In the context of the script example above it's doing something along the lines of calling:

      # In Photoshop's current active document, in the group called 'd', add another art layer, and assign the returned the object reference as 'new_art_layer'
      new_art_layer = ps_app.activeDocument.LayerSets['d'].ArtLayers.Add()

      The result will be that the script will place a new art layer in the Layer Set (or group) that is named 'd'.

      Delete
  6. Hi :)

    Great tutorial, but i have question. Why class didn't have a Adobe Fireworks?

    Regards,
    Robert.

    ReplyDelete
    Replies
    1. Hi Robert,

      Glad you found the tutorial useful! I'm not sure if I understand you about the Adobe Fireworks. I believe that Fireworks is being Phased out by Adobe, but other than that I am not too familiar with the Fireworks package.

      All the best!
      -Pete

      Delete

Comments?