記事の目次

    クリーク・アンド・リバー社の社内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を使うメリット

    [[SplitPage]]

    Step 04:Pythonを使うメリット

    そもそもPythonを使うメリットについては、人や環境によってケース・バイ・ケースで、JavaScriptに慣れた人が無理にPythonを使う必要はないと思います。ただ、筆者や当社スタジオに関していうと、「Pythonが最も使い慣れた言語なため、メリットが高かった」というわけです。では、Pythonの「使いやすさ以外のメリット」についていくつか紹介します。


    メリット 1:スクリプトを置く場所を選ばない

    JSXファイルは、Photoshopのメニューから表示するためには決まったフォルダに入れなければなりません。単純で1ファイルで実行できるものは下記のフォルダに入れる必要があります。

    C:\Program  Files\Adobe\Adobe  Photoshop  XX\Presets\Scripts\
    

    これをPhotoshopのバージョンごとにコピーしないと大変です。JSXファイルはPhotoshop内にドラッグ&ドロップすれば場所を選ばずに使うこともできますが、あまり一般的ではありません。

    エクステンションを使う場合も、TIPS 04のStep 02で挙げた通りに下記のフォルダに様々なデータと一緒に入れなければなりません。

    C:/Users/%USERNAME%/AppData/Roaming/Adobe/CEP/extensions/"IDと同じフォルダ"
    

    しかし、Pythonからはあくまで起動中のPhotoshopを外部から操るので、ソースファイルはどこにあっても実行できます。極端なことを言えば、デスクトップに置いていても実行できます。チームでソースファイルをSubversionで一元管理する場合には、どこにでも置けるこのアドバンテージは大きいです。


    メリット 2:Python専用のモジュールを使える

    Pythonには様々な使いやすいモジュールが存在しています。「numpy」などの便利モジュールをはじめ、UI作成に便利な「Qt Designer」、Pythonをexeファイルにまとめられる「pyinstaller」を使うことができます。特にMayaにも同梱されている、お馴染みのQt DesignerでUIを作ることができるのは嬉しいメリットです。

    以下の画像はPhotoshopで「自動定期保存」するために作ったツールのUIですが、筆者はHTMLやCSSよりもQt Designerの方が慣れているので、より短時間でUIを作成できました。

    また、作成したツールをpyinstallerなどを利用して1つのexeにまとめてアーティストに渡すことで、簡単に配布することもできます。exeファイルなので、アーティストが複製したりショートカットを作成したりと、好きなところに置くことも可能です。

    この「exeで渡す方法」を使えば、TIPS 06で費やした認証作業を全て飛ばすことができます!

    またZXPファイルでは「ZXPSignCmd.exe」が必要でしたが、今回は必要ないので配布するファイルは1つだけで済みます。各種モジュールとそこから派生するメリットは大変大きいと思います。

    Step 05:Pythonを使うデメリット

    一方でデメリットもあります。ただ、その内容もデメリットと感じるどうかは人それぞれとは思うので、今回は筆者自身が作成中に感じたデメリットを紹介します。


    デメリット 1:Photoshopのメニューに登録できない

    当然ながらPhotoshopを外部から動かすので、メニューに登録するのが難しいです。 ただ、難しいというだけで、やろうとすれば登録する方法はあるかもしれません。何しろJavaScriptからexeを実行するだけですから。ただ、それを実現するために「exeをどこに置いても大丈夫」というアドバンテージを失うことが良いことかは判断が難しいです。将来的にexeの数が多くなったときに表面化はすると思いますが、現時点では数も少ないので大きなデメリットにはなっていません。


    デメリット 2:複数バージョンに弱い

    筆者のマシンにはすでにサポートが終わったPhotoshopを含め、CC 2017~2021と趣味で複数のPhotoshopが入っています。この状態だと、各モジュールがどのPhotoshopで使えるかバラバラだということです。

    検証を始めた頃は、「そんな複数のPhotoshopを使っているアーティストはいないだろう」と予想して後回しにしていた問題です。調べてみたら、意外と多くのアーティストが複数のPhotoshopを使っているということがわかってきました。ただこれに関しては、環境変数などを調べて変更できれば解決できる問題だと思っています。

    ※:このデメリット5-2は解決しています。今回のブログには間に合いませんでしたが、当社スタジオのブログで解決方法を追記しています。
    【Photoshop】PythonによるPhotoshop操作:追記


    デメリット 3:VBSコマンド以上のことはできない

    PhotoshopのVBSコマンドを利用しているので、VBSコマンド以上のことはできません。ただし、あくまでもできないのはPhotoshopに対しての最終的なコマンドだけで、途中の処理などはPythonで担うことはできます。UI例で挙げた「自動定期保存」では、実はVBSコマンド は2〜3個しか使っていません。VBSコマンドの使いどころを絞れば、Pythonならではのツールを作ることは可能です。

    まとめ:PythonによるPhotoshop操作について

    PythonでのPhotoshopにおけるツールサポートについては、まだ全てが可能なわけではなく 「まだ早い」側面は確かにありますが、そこまで複雑なツールでなければ十分活用できるのではないでしょうか。

    例えば、アーティストの欲しいツールの中にはレイヤー名の「~のコピー」を自動で全部消したい、という程度のものもあります。これぐらいの簡単な「"〜のコピー" 撲滅ツール」であれば、Pythonでも十分に作ることができます。「Pythonに慣れてるけどJavaScriptは慣れてない」という方は、ぜひ簡単なPhotoshopのツールから、試しにPythonを使って作ってみていただけたらと思います。

    正直、簡単なツールであればVBSコマンドの知識と「win32com(のDoJavaScript)」だけでも作ることはできます。将来的にはPythonで新たなPhotoshop用のモジュールが生まれるかも......? 今からPythonでPhotoshopのツールを作成しておくのも良いかもしれませんね!

    それでは皆さん Ate breve! Obrigado!(アテブレーベ!オブリガード!)
    「また近いうちに!ありがとうございました!」



    Profile.

    • 中林伸和 / Nobukazu Nakabayashi(COYOTE 3DCG STUDIO

      ゲーム開発会社にてマップデザイン、レベルデザイン、エフェクト作成など様々な経験をする中でMELと出会い、DCCツール作成の面白さに目覚めてテクニカルアーティストに転身。現在は株式会社クリーク・アンド・リバー社 COYOTE 3DCG STUDIOにて、Mayaに限らずPhotoshopやMotionBulderのグラフィックソフト、Unityなどのゲームエンジンと言語にとらわれずユーザーフレンドリーのツール開発に従事