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