>   >  COYOTE流テクニカルTIPS:TIPS 07:【Photoshop】PythonによるPhotoshop操作
TIPS 07:【Photoshop】PythonによるPhotoshop操作

TIPS 07:【Photoshop】PythonによるPhotoshop操作

クリーク・アンド・リバー社の社内CGスタジオであり、ゲームの3DCGグラフィックス制作を中心に手がけているCOYOTE 3DCG STUDIO。本連載では、同社のTAチームによる制作に役立つ技術TIPSを紹介していく。

TEXT_中林伸和 / Nobukazu Nakabayashi(COYOTE 3DCG STUDIO)
EDIT_三村ゆにこ / Uniko Mimura(@UNIKO_LITTLE

はじめに

戦国三英傑の織田信長の好きなところは革新的なものを取り入れるイメージです。当時、最先端の西洋技術を率先して取り入れた姿勢はTAとして見習いたいです。こんにちは、COYOTE 3DCG STUDIO テクニカルアーティスト(以下、TA)戦国大好き人間の中林です。

当スタジオではテクニカルサポートのためのコーディングとして、Pythonをメインで使用しています。そのため、Photoshopでもpythonを使ったツールサポート方法については以前から調べていました。少し前までは情報が少なかったものの、最近はPythonモジュールの「photoshop-python-api」などを使用する方法などが紹介されており、PythonでPhotoshopのツールが作れる可能性が高まってきました。

そこで今回は、Photoshopのツール開発の話・番外編として、Pythonによるツール制作を紹介します。前回までのPhotoshop編3回の記事で紹介した内容と比較することで、その利便性がよくわかると思います。それでも基本的にPhotoshopのJavaScriptコマンド(以下、VBSコマンド)の知識を使うことは変わらないので、基礎として大事になります! 正確にはVBScriptコマンド(以下、VBSコマンド)ですが、ほとんどのコマンドは最初の1文字が大文字か小文字の差なので意識せずに使えます。

なお、今回はWindows環境での説明とさせていただきます。Macユーザーの皆さんごめんなさい......!

Step 01:使うにはまだ早いけど希望は大きい

Pythonには、Photoshopを操作するためのモジュールがいくつか存在しています。これらのモジュールを用途に合わせてインポートして使用することで、簡単にPythonからPhotoshopを操作できるようになります。実際にいくつかのモジュールでツールを作ってみたところ、「win32com」と「photoshop-python-api」、「comtypes」あたりが実用的でした。ただ、それぞれのモジュールで全てのVBSコマンドが正しく動いているわけではないので、 全面的に信じて使うのには早いかもしれません。

だがしかし!
試して問題がなかったVBSコマンドはバシバシ使えます! また、今は不具合があるVBSコマンドでも将来的に修正される可能性もある(特にphotoshop-python-api)ので、将来性は大きいと思います。実際、当スタジオ内で開発しているPhotoshopツールは順次Pythonに移行しています。

Step 02:主なPythonモジュールの紹介

今、筆者が主に試しているPythonモジュールは下記で紹介する3つです。将来的には安定している方に統一するとは思いますが、まだR&Dの時期なのであえてそれぞれ試しに使っている状態です。それでは各モジュールを紹介していきます。


2-1:win32com(pywin32)

システムを提供するWindowsが時期によって名前を色々と変えているので呼び方に迷いますが、モジュールが「win32com.client」なので本稿では「win32com」で統一します。同一システムが時期によって名前を変えるようなものは、「最近はその呼び方をしないよ」と言われるため筆者としては困るので、あえてファイル拡張子やモジュール名など普遍的なものを選んでいます。

「com」は「Component Object Model」の略です。詳しくはわかりませんが、マイクロソフトが提唱する環境に合わせて作ると、プログラム言語に関係なくアプリケーション間で簡単にする取り組みです。幸いなことにAdobe製品はwin32comに対応しているので、Pythonを通じてVBSコマンドを使用することができます。使おうと思えばExcelでも使えるので幅は広いです。

インストール方法は「pip」と言いたいのですが、Python3.9と相性が悪いとの報告を散見したので、筆者は以下のアドレスの「git」からpythonに合った「.exe」をダウンロードしてインストールしました。

●Releases · mhammond/pywin32 · GitHub
github.com/mhammond/pywin32/releases


●コマンド例 

毎度お馴染み、画像の複製のコマンド例です。Pythonで以下のコマンドを実行するだけで複製できます。あくまでもPhotoshopを外部から操作しているので、JSXファイルのように特定のフォルダに入れる必要はありません。

Python

import win32com.client
psapp = win32com.client.Dispatch('PhotoShop.Application') 
psapp.ActiveDocument.Duplicate();

「win32com」をimportして、「PhotoShop.Application」のclassを関連付けるだけです。これだけでVBSコマンドを使えます。


2-2:photoshop-python-api 

こちらは最近台頭してきたapiモジュールです。まだ発展途上の部分はありますが、「COMオブジェクト」を簡単にPythonで使うことができるモジュールです。

●photoshop-python-api · PyPI
pypi.org/project/photoshop-python-api/

Console

pip install photoshop-python-api

基本的には「pip」でインストール可能ですが、Pythonのバージョンによっては必要なモジュールがインストールされないこともあります。このあたりも自由に使うのには早いという感じですが、将来への期待感は大きいです。


●コマンド例 

こちらも画像の複製のコマンド例です。

Python

import photoshop.api as psapi
psapp = psapi.Application() 
psapp.activeDocument.duplicate();

こちらのコマンドは頭文字は大文字小文字の好きな方で良いので、JSXコマンドからコピー&ペーストしたいときに便利です。


2-3:comtypes

これもCOMオブジェクトを簡単に扱うことができる拡張モジュールです。

●comtypes · PyPI
pypi.org/project/comtypes/

Console

pip install comtypes

「pip」でインストールできますが、実は上記の「photoshop-python-api」をインストールした時点で一緒にインストールされています。最新のモジュールが昔の便利なモジュールを内包している感じです。


●コマンド例 

同じく複製のコマンド例です。

Python

import comtypes.client
psapp = comtypes.client.CreateObject('Photoshop.Application') 
psapp.activeDocument.duplicate()

ぶっちゃけて言うとphotoshop-python-apiもcomtypesも、win32comを使いやすくするためのモジュールです。そのため、コマンドの「duplicate()」は先頭を大文字にした「Duplicate()」にしてもどちらでも動きます。こんな地味な大文字小文字以外にも、便利になっている部分を紹介します。

Step 03:Pythonモジュールのコマンドのちがい

上記のcomtypesとphotoshop-python-apiが、どのような部分でwin32comよりコマンドが使いやすくなっているか、またはいないのかを紹介します。


3-1:2次元座標の配列の使用

「2次元座標の配列」と書くと「何のことだ?」となるので、Photoshopの「範囲選択」で説明します。


例1:JSX

これで、編集中の画像でスクリプトから範囲選択ができます。この範囲を指定する変数が2次元配列になっています。

JavaScript

var region= [[50,50], [50,200], [200,200], [200,50]];
app.activeDocument.selection.select(region);

例2:win32com(失敗)

Python

import win32com.client
psapp = win32com.client.Dispatch('PhotoShop.Application')
region = [[50,50], [50,200], [200,200], [200,50]]
psapp.activeDocument.Selection.Select(region)

win32comでこれを実行すると「一次元配列のみがサポートされています」とエラーが発生して、Pythonから実行できません。


例3:photoshop-python-api

Python

import photoshop.api as psapi
psapp = psapi.Application()
selReg = [[50, 50], [50, 200], [200, 200], [200, 50]]
psapp.activeDocument.Selection.Select(selReg)

例4:comtypes

Python

import comtypes.client
psapp = comtypes.client.CreateObject('Photoshop.Application')
selReg = [[50, 50], [50, 200], [200, 200], [200, 50]]
psapp.activeDocument.Selection.Select(selReg)

しかしこの2つのモジュールは、簡単に2次元配列が使えて範囲選択ができます。


3-2:定数の使用

Photoshopのソースにはいくつかの定数があるので、VBSコマンド例を基に説明していきます。


例1:JSX

JavaScript

app.preferences.rulerUnits  =  Units.PIXELS;

これはPhotoshopの作業単位を変える命令です。COYOTEスタジオは主にテクスチャ作成で作業するので、ドット数がわかりやすいピクセル単位が重要です。

でも、意外とPhotoshopの作業単位を初期状態のままで使っているアーティストもいます。スクリプトの処理によってはピクセル数で指定する場面もあるので、筆者は(こっそりと)変更しています。


例2:win32com(失敗)

Python

import win32com.client
psapp = win32com.client.Dispatch('PhotoShop.Application') 
psapp.preferences.rulerUnits = Units.Pixels

そもそも定数がないので、何を入れたら良いのかわかりません。定数はcomtypesでも使えないと思います。無理やり設定するなら「1」を代入すれば意図したことができます。


例3:win32com(無理やり)

Python

import win32com.client
psapp = win32com.client.Dispatch('PhotoShop.Application')
psapp.preferences.rulerUnits = 1

しかし、この定数の値の情報を知る方法がなかなかありません。どうしても知りたいなら、公式の「Adobe Photoshop CC VBScript Scripting Reference」から推測して知ることができます。なぜか「Adobe Photoshop CC JavaScript Reference」には数値は明記されていません。


例4:photoshop-python-api

Python

import photoshop.api as psapi
psapp = psapi.Application()
psapp.preferences.rulerUnits = psapi.Units.Pixels

photoshop-python-apiでは大文字と小文字の差はありますが、定数として使うことができます。定数の確認に時間がかかるので、あるだけでも便利です。


3-3:レイヤーのタイプ

Photoshopのレイヤーは大きくわけて2種類のタイプがあります。主に画像編集をする「ArtLayer」と、これをまとめるフォルダみたいな「LayerSet」です。それぞれ、選択したレイヤーのタイプをスクリプトから知ることができます。


例1:JSX

JavaScript

alert(app.activeDocument.activeLayer.typename);
結果1:LayerSet
結果2:ArtLayer

これは選択していたレイヤーによって変わります。当然ながらwin32comでやっても......


例2:win32com

Python

import win32com.client
psapp = win32com.client.Dispatch('PhotoShop.Application')
print(psapp.activeDocument.activeLayer.typename)
結果1:LayerSet
結果2:ArtLayer

同じ結果が出ます。しかし、photoshop-python-apiでは......


例3:photoshop-python-api

Python

import photoshop.api as psapi psapp = psapi.Application()
print(psapp.activeDocument.activeLayer.typename)
結果1:ArtLayer
結果2:ArtLayer

「LayerSet」を選択しても「ArtLayer」と結果を返されてしまいます(2021年6月現在の情報)。

comtypesにいたっては......


例4:comtypes

Python

import comtypes.client
psapp = comtypes.client.CreateObject('Photoshop.Application') 
print(psapp.activeDocument.activeLayer.typename)
結果:NameError: Name typename not found

とエラーが返ってきます。レイヤーのタイプを取得することはできません。では、この2つのモジュールでは、レイヤータイプを知ることはできないのでしょうか。

Photoshopのデータ構造を利用すると近いことはできます。それには、「LayerSet」には子レイヤーが存在するが「ArtLayer」には子レイヤーが絶対に作られない、という決まりを利用します。これを利用して「.layers」で子レイヤー用のClassが存在するかしないかで疑似的に分けることが可能です。


例5:photoshop-python-api(無理やり)

Python

import photoshop.api as psapi

psapp = psapi.Application()

try:
	print(psapp.activeDocument.activeLayer.layers)
	# LayerSetはここに来る
except:
	# ArtLayerはここに来る

例6:comtypes(無理やり)

Python

import comtypes.client
psapp = comtypes.client.CreateObject('Photoshop.Application')

try:
	print(psapp.activeDocument.activeLayer.layers)
	# LayerSetはここに来る
except:
	# ArtLayerはここに来る

「print」などを利用してclassがなかったら、無理やりエラーを出すことで処理を分けます。もちろん、typenameで判別できるのがベストですが、構造次第では別の方法を模索することも可能です。


3-4:共通するエラーの回避方法

上記ではちがいを紹介してきましたが、共通で使えないコマンドもあります。それについての例と回避方法を挙げていきます。


例1:JSX

JavaScript

app.activeDocument.activeLayer.remove();

これはレイヤーを削除するJSXコマンドですが、win32comで実行するとエラーが発生します。


例2:win32com(失敗)

Python

import win32com.client
psapp = win32com.client.Dispatch('PhotoShop.Application') psapp.activeDocument.activeLayer.remove()

残念なことに、このコマンドはwin32comの大元のエラーなので、photoshop-python-apiでもcomtypesでもエラーは回避できません。では、この手のエラーは回避できないのか......。いえ、普通にJavaScriptでJSXコマンドを使えば良いだけです。「DoJavaScript("""JSXコマンド;""")」で、簡単にJavaScriptとしてJSXコマンドを使うこともできます。


例3:win32com(成功)

Python

import win32com.client
psapp = win32com.client.Dispatch('PhotoShop.Application')
com  =  r"""app.activeDocument.activeLayer.remove();"""
psapp.DoJavaScript(com)

コマンド的には一方通行ですが、JSXコマンドを使うこともできます。当然、「DoJavaScript」はwin32comにあるコマンドなのでphotoshop-python-apiでもcomtypesでも使えます。 上手く使えば3-1:2次元座標の配列の使用で失敗した2次元座標も、変数を与えながら実行することもできます。


例4:win32com(3-1/成功)

Python

import win32com.client
psapp    =    win32com.client.Dispatch('PhotoShop.Application')
com = r"""var selReg = [[{0},{0}],[{0},{1}],[{1},{1}],[{1},{0}]];""".format(50, 200)
com += r"""app.activeDocument.Selection.Select(region);"""
psapp.DoJavaScript(com)

意外とモジュールでエラーの発生するVBSコマンドでも、JSXコマンドとして使うことでエラーを回避することができます。

モジュールを使えば楽になる部分もありますし、モジュールが使えない場合にも探せば回避方法はあります。「エラーがあるので使うのには早い」とは言いましたが、使いようによってはいくらでもエラーを回避する方法があるということです。



次ページ:
Step 04:Pythonを使うメリット