コンテンツへスキップ

QualiArtsengineer blog

Photoshop UXP への移行について 前編

Photoshop UXP への移行について 前編

11 min read

はじめに

テクニカルアーティスト室所属の dockurage です。 当室では、Maya、Unity、Photoshop といったソフトウェアにおける業務効率化のためにプラグインを作成しています。 本記事では私が担当していた Photoshop Extension の UXP 移行についてのお話をしたいと思います。 以前より Photoshop のプラグイン開発では CEP(Common Extensibility Platform)を使用していましたが、次世代の開発プラットフォームである UXP(Unified Extensibility Platform)に移行を行いました。

Adobe の拡張機能 CEP と UXP について

Photoshop で拡張を行う場合、いくつかの選択肢があります。その中でアプリケーションに統合されたパネルを開発できる CEP という方法があり、この後継にあたるのが UXP です。最大の違いとして UXP は ExtendScript の使用する必要がないことです。

ExtendScript とは、Adobe が開発した ECMAScript に準拠した JavaScript のような言語です。ExtendScript は Photoshop で拡張を行おうとすると、切っても切れない関係にあり、CEP も例外なく同じ状態にありました。また ExtendScript は歴史が古いため、最新の JavaScript よりも不便な状態のままになっていて、効率的な開発の妨げになっていました。そういったことから ExtendScript を使用しないというのは革命的なことでもあります。

CEP では Web ライクに開発できる Panel VM (JavaScript) で UI 部分を作成し Photoshop に処理を実行させる Host VM (ExtendScript) を適所に実装するハイブリットな環境でした。そのため一貫性がなく実装やデバッグを効率よく行うことが難しくなっていました。UXP はその状況を改善し JavaScript だけで実装できる開発環境を目指しているものです。ただし CEP の中でも特に ExtendScript は歴史が古く、開発期間が浅い UXP ではその機能全てを再現することができておらず注意が必要です。これは将来改善することを期待したいところです。

Adobe UXP とは

UXP は最新の JavaScript エンジンを搭載しています。ExtendScript と比べてだいぶモダンな実装ができるようになりました。また Spectrum という UI コンポーネントも用意されており、一般的な UI を構築する程度なら余計な実装をしなくてよい状態になっています。CEP では UI の実装にサードパーティ製の UI フレームワークなどを使用していたので、公式の UI フレームワークがあることはよいことだと思います。公式だけあって Photoshop の UI デザインとの親和性もいいです。 ただし UXP にも問題があります。CEP は Chromium で動作していたので Web でできることはおおよそ動作したのですが、UXP は独自実装のため Web でできたことができなかったりする落とし穴が CEP に比べて多いようです。そういった理由から公式では Vue.js 非対応をうたっており (該当する記述)、用意されているテンプレートなどを見る限り React を前提としてる節があります。弊社では CEP + Vue.js で開発を行なっていたので、このタイミングで React へ移行することにしました。

※ Chromium から独自実装になったのは、CEP ではプラグインを複数起動すると Chromium のインスタンスが同じだけ増えてしまうなどの問題があり、シンプルなものにするという思想があるようです。

CEPUXP
実行環境Chromium独自実装
言語HTML
CSS
JavaScript
ExtendScript
HTML
CSS
JavaScript
デバッグ環境Chromium Develop Tools
ExtendScript Debugger
UXP Developer Tools
UI フレームワークSpectrum
明記されているアンサポート
(Web 機能)
Drag & Drop
iFrame
Canvas
window.location(プラグイン内で Web ページをロードする場合)
データ属性とfont-face
CSS float(を使用flexbox)
明記されているアンサポート
(フレームワーク)
jQuery
Vue.js
明記されているアンサポート
(Javascript)
window.event.cancelBubble = true
getElementsByClassName(something)

UXP の開発環境準備

UXP の開発には Photoshop と UXP Developer Tools を使用して行います。まずは Adobe Creative Cloud からそれぞれをインストールをします。 UXP は Photoshop のバージョン 22.0 以降から使用できますが、随時機能が拡充されていっているのでできるだけ最新のバージョンを使用することをお勧めします。弊社では当時最新だった 24.41 を採用しています。UXP Developer Tools は特にインストールするバージョンの指定はできないので最新のバージョンがインストールされるようです。

image 01

UXP Developer Tools について

UXP Developer Tools 起動するとこのような画面が表示されます。 Photoshop を起動すると左のペインに起動中の Photoshop が表示されます。開発には UXP Developer Tools と Photoshop を使用するのでどちらも必ず起動しておきます。 

「Create Plugin」ボタンからプラグインのテンプレートを作成することができます。

image 02

プラグイン作成の際には名称、バージョンといったいくつかの初期設定を行う必要があります。

image 03

項目説明
Plugin Nameプラグインの名前です。プラグインのインストール画面などで表示されます。
Plugin IDプラグインのIDです。マーケットプレイスで共有する際に Adobe Developer Distribution で一意のプラグイン ID を取得し設定するようです。弊社では社内利用しか考えてないので任意の ID を振っています。
Plugin Versionプラグインのバージョン情報です。
Host Applicationプラグインが動作するアプリケーションを設定します。
Host Application Version対象アプリケーションの動作バージョンを設定します。
Template生成するテンプレートの種類を設定します。

テンプレートは 3 種類あります。それぞれ UXP の使い方のサンプルが入っています。弊社では React で実装をすることを決めていたので react-starter を選択して、そこからプラグイン開発を行いました。以降は react-starter を使用した時の操作について解説していきます。

項目説明
quick-layers-starterVanilla JS で作成する表示中のレイヤーを取得するサンプル
react-starterReact で作成するカラーピッカーのサンプル
webview-starterUXP で Webview を使用するためのサンプル

UXP Developer Tools でプラグインを作成したら、ターミナルを使って、プラグインのプロジェクトフォルダで下記のコマンドを実行して、node のパッケージをインストールします。

npm i

node のパッケージをインストールし終わったら、続けて下記のコマンドを実行します。

npm run watch

ここまできたら UXP Developer Tools に戻り、「Load」を選択します。これで Photoshop でプラグインが起動できるようになります。また「Watch」を実行しておくことでソースコードを変更した時に即時に変更が反映されるようになります。

image 04

「Debug」を選択するとデバッグ用のツールが起動します。これでプラグインを開発する環境が整います。

image 05

パッケージ出力

「Package」を選択すると「.ccx」という拡張子のプラグインファイルが生成されます。 この uxp をプラグイン利用者に配布することで、利用者は簡単に Photoshop へプラグインをインストールすることができるようになります。

UXP 開発

この項では React での開発を行った時に必要になった基礎的な知識についてを解説します。ただし UXP 開発に必要な最低限のテクニックに焦点をあてるため JavaScript や React での細かい実装方法についての話は割愛します。

作成したアプリケーションの概要

今回移行を行ったプラグインは 3D モデルで使用するテクスチャのエクスポータです。 弊社では、キャラクターモデル用、フェイシャル用、背景モデル用それぞれをわけて用意していました。それに加えてプラグインの設定情報(出力先のパス情報など)を管理する設定パネルも用意しています。 UXP は1つのプラグインの中に複数のパネルを持つことができますので、ユーザーが使用したい機能だけを使うことができるように、機能ごとにパネルをわけて実装しました。

image 06

このように プラグインに関する情報を定義するためのファイルである manifest.json に entrypoints の項目へパネルの初期値情報などを記載して、プログラム側 (jsx ファイル) で entrypoints.setup に作成したいパネルを設定しておくことで実現できます。

// manifest.json
{
    ...
    entrypoints: [
        {
            "type": "panel",
            "id": "characterExporter",
            "label": {
                "default": "Character Exporter"
            },
            "minimumSize": {
                "width": 230,
                "height": 200
            },
            "maximumSize": {
                "width": 2000,
                "height": 2000
            },
            "preferredDockedSize": {
                "width": 230,
                "height": 300
            },
            "preferredFloatingSize": {
                "width": 230,
                "height": 300
            },
            ...
        },
    ]
};
// index.jsx
entrypoints.setup({
    panels: {
        characterExporter: characterExporterController,
        environmentExporter: environmentExporterController,
        facialEditor: facialEditorController,
        setting: settingController
    }
});

Photoshop のプラグインパネルに entrypoints で登録したパネルの一覧が表示され、各項目を選択するとそれぞれのパネルを起動できます。

image 07

React 関連ライブラリの導入

UXP での React を使ったパネル実装は一般的な実装方法と大きく変わるものではありませんので、ここでは割愛します。 上記で述べた社内用プラグインでは各パネルで連携して処理をしたい場合があったので、React の状態管理ライブラリ zustand を導入しています。UXP 開発では React を前提して実装ができるので React の関連ライブラリが利用できるのはコミュニティやエコシステムにのることができるため大きなメリットです。

zustand を使って各パネルの動作を他のパネルに伝えています。例えば設定パネルでパスの設定をすると他のパネルでそれを踏まえた形で再レンダリングをするといった使い方をしています。

image 08

例として挙げると設定パネルで必要なパスの設定がされているかを zustand の Store で管理し、各パネルでバリデーションを行い、存在しない場合にはエラー通知を操作パネルの上に被せて表示させ、利用者が操作できないようにしています。

image 09

ライブラリの追加は CEP と同様で package.json に追記してインストールするだけで済みます。この辺は JavaScript のプロジェクトと同じです。

// package.json
{
    "name": "qualiarts_plugin",
    "version": "1.2.0",
    ...
    "dependencies": {
        "react": "^16.8.6",
        "react-dom": "^16.8.6",
        "zustand": "^4.3.2" <- 追加
    },
}

package.json で登録したライブラリを jsx 側で使用する場合は、import 文で行います。

// xxx.jsx
import { create } from "zustand";

このように JavaScript としては一般的な導入方法なので、大きな混乱なくプロジェクトへの導入ができると思います。

batchPlay

UXP では、レイヤーを作成する、ファイルを保存するといった Photoshop への動作指示を行うための Photoshop API と UI やストレージ操作などを行う UXP API が用意されています。 しかし発展途上のため ExtendScript にある API 全てを網羅できているわけではありません。そのためプラグインを制作していると実装に困ることがあります。その場合には batchPlay というメソッドを使うことで回避できます。 batchPlay とは 1つ以上の Photoshop アクションコマンドを実行し、その結果を返す API です。これにより、たとえ UXP で未実装な処理だとしても ExtendScript でできたことは実装することができるようになります。

// UXP
require("photoshop").action.batchPlay(
    descriptors: ActionDescriptor[], 
    options: Object
): Promise&gt;Object[]&lt;
// CEP
csInterface.evalScript("ExtendScriptの処理を文字列で記述")

// 例:
csInterface.evalScript("function test() { alert('test'); }; test();")

この機能は CEP でいうところの csInterface.evalScript(<ExtendScriptの処理>) にあたりますが、この処理は同期動作だったため重い処理を行った時に UI がフリーズすることがありました。UXP ではその辺が進化しており batchPlay は非同期処理になったため、起きにくくなりました。ただし非同期なため処理の完了を正しくハンドリングして次の動作を行う必要も出てきました。また非同期だからといって大量に処理を並列して行おうとすると Photoshop 自体が落ちてしまったため、その辺は上手に扱う必要がありそうです。

さて batchPlay が使えることで CEP で実装できていたものは実装できることはわかりましたが、 ExtendScript で実装していた方法を batchPlay でどう実装するかは公式ドキュメントにはほとんど記載はありません。そういった情報は2種類の方法を使うことで簡単に知ることができます。

アクションから取得する

もっとも簡単な方法は Photoshop のアクションを使うことです。知りたいアクションの項目を選択してメニューから「JavaScript としてコピー」を選択するとクリップボードに batchPlay の処理が登録されます。この情報を batchPlay のサンプルとして利用することができます。

image 10

// アクションのメニュー「JavaScript としてコピー」でクリップボードに登録される内容
async function actionCommands() {
    let command;
    let result;
    let psAction = require("photoshop").action;

    // 設定 :  現在のレイヤー
    command = {"_obj":"set","_target":[{"_enum":"ordinal","_ref":"layer","_value":"targetEnum"}],"to":{"_obj":"layer","name":"リネーム"}};
    result = await psAction.batchPlay([command], {});
}

async function runModalFunction() {
    await require("photoshop").core.executeAsModal(actionCommands, {"commandName": "Action Commands"});
}

await runModalFunction();

ps-es-to-uxp を使って取得する

ps-es-to-uxp というロガーがあるので、これを使用して取得することも可能です。

ExtendScript のファイルにこのライブラリを読み込み、executeAction/executeActionGet を executeActionForUXP/executeActionGetForUXP で実行すると batchPlay の処理をログとして出力することができます。

var mainScriptPath = Folder($.fileName).parent.parent ; 
$.evalFile(new File(mainScriptPath + '/ad-to-uxp.jsx'));

var ref = new ActionReference();
ref.putProperty(charIDToTypeID('Prpr'), charIDToTypeID("Nm  "));
ref.putIndex(charIDToTypeID('Lyr '), 1);
// executeActionGet(ref);
executeActionGetForUXP(ref);
require('photoshop').action.batchPlay([{ "_obj": "get", "_target":{ "_ref": [{ "_property": "name" }, { "_ref": "layer", "_index": 1 }]} }], {})

まとめ

このように、一通りUXPでのプラグイン開発を経験して、以下のようなことがわかりました。

  • React を使って UI 部分を作成する
  • Photoshop への指示はUXP API もしくは batchPlay を使用する
  • batchPlay の内容はアクション や ps-es-to-uxp からほぼ取得することができる

最後の項目に関して「ほぼ」という表現をした通り、だいたいの対応は簡単に行うことができましたが、いくつかつまづくケースもありました。 こちらについては次回UXP開発でつまづいた点をまとめる予定なので、そちらで解説しようと思います。 本記事がUXP開発を行う方々の助けになれば幸いです。