PhotoshopのGenerator機能で出力されたレイヤーおよびレイヤーセットの位置情報をリストアップするJSXを作りました

PhotoshopのGenerator機能で出力されたレイヤーおよびレイヤーセットの位置情報をリストアップするJSXを作りました。

私の手元の作業では、このJSX、もっと特化させているのですが、それには非破壊ではない処理もあったり、流れが分かりにくい処理もあったので、本エントリーでは8割の状態で止めておき、JSXを触れる方であれば、おのおのの作業パートに合わせて拡張できるような形でアップしました。

概要

PhotoshopのGenerator機能というものがあってとても便利です。出力されるものは綺麗に透明部分も切り取られた画像なわけですが、実際にHTML5やFLASHに移植する際に、位置情報が飛んでしまうわけです。これは透明部分を削るということをする時点で当然ではあるのですが、結局再配置している作業フローを考えると2度手間と言わざるえません。

そのため、PhotoshopのGenerator機能で出力されたレイヤーおよびレイヤーセットの位置情報をリストアップ出来ないかというふうに考えたわけです。

データダウンロード

実際に動くものがないと分かりにくいので実際に動くソースアップしました。

データダウンロード:generator_png_layer_listup_jsx.zip

処理の流れ

処理すべきsample.psdを開いてみよう

こちらを解凍すると以下のようになっています。

image_20150116_221046_3

JSXとサンプルのPSDが入っています。

サンプルのPSD開いてみるとこのようになっています。

image_20150116_221046_1

レイヤはこんな感じ。

image_20150116_221046_2

Generatorが発動

このファイルを開いた時点でGeneratorが発動するので以下のようにファイルが出力されます。

image_20150116_221046_4

sample-assetsフォルダはこんな感じ。

image_20150116_221046_5

こちらに出力されるわけですが、もちろんどの位置に配置されているかの情報は無くなってしまうわけです。

JSXを動かしてみる。

つづいていよいよJSXを動かします。なお、今回は.pngのついたものに特化しています。

ダブルクリックなりでExtendScript Toolkit開いて、Photoshopをターゲットにします。ここは他のJSXの一緒ですね。

image_20150116_221046_12

sample.pndを開いた状態で実行すると、今のようなダイアログが出るので

image_20150116_221046_7

はいを押します。

処理が行われ、今のダイアログが出ます。

image_20150116_221046_8

これで終了です。

ExtendScript ToolkitのjavaScriptコンソールにも以下のように結果が出ます。

image_20150116_221046_9

同階層にCSVが出力されるので開いてみる

先ほどの処理が終わると、PSDと同じ階層にCSVが生成されます。

image_20150116_221046_10

このCSVを開いてみると、各パーツのレイヤーの位置情報が入っています。

CSVを開いてみると以下のようになっていて名前・X・Yの順で情報が入っています。

image_20150116_221046_11

このような処理を作りました。

ソースコード

ソースコードも置いておきます。

/**
 * generator_png_layer_listup.jsx
 */

// 処理に該当する拡張子名の配列
// 今回は png のみなので .png
var extensions = [".png"];
// 書き出す際のテキストを貯める変数
var strData = "";

// 以下処理 ///////////////////////////////////////////////////////
if( app.documents.length == 0 ){
    alert( "【!】実行するドキュメントがありません。" );
} else {
    main();
}

function main(){
    if(confirm( activeDocument.name + "から、pngのつくレイヤーから矩形抽出しますか?" )){
        section();
    }
    alert( "すべての処理が完了しました。お疲れ様でした。" );
    return "完了";
}

function section(){

    // ファイルを作成し、テキストレイヤー書き出し処理へ
    var fileName = activeDocument.fullName + "_export.csv";
    var file = new File(fileName);
    var openFlag = file.open("w");

    if(openFlag) {
        // 再帰的に処理
        allLayerSetsBounds( activeDocument );
        // CSVテキストの出力
        file.write(strData);
        file.close();
        // 完了
        alert("書き出しが完了しました。");
    } else {
        alert("ファイルが開けませんでした。");
    }
}

// レイヤー名に処理に該当する拡張子が含まれているか捜索する
function checkNameExtensions( name ) {
    var len = extensions.length;
    var ret = false;
    for (var i=0; i<len; i++){
        var checkName = extensions[i];
        if( name.indexOf(checkName) > -1 ){
            ret = true;
        }
    }
    return ret;
}

function getGeneratedLayersName( artLayers ) {
    var ret = [];
    var ns = artLayers.length;
    for (var i=0; i<ns; i++){
        var focusLayer = artLayers[i];
        if( checkNameExtensions( focusLayer.name ) ){
            ret.push( focusLayer.name );
        }
    }
    return ret;
}

function allLayerSetsBounds( layObj ){
    var n = layObj.artLayers.length;

    // レイヤーフォルダ内のレイヤー捜索 ////////////////////////////////////////

    // レイヤー名に処理に該当する拡張子が含まれているか捜索する
    var lList = getGeneratedLayersName(layObj.artLayers);
    // あれば1つ1つ捜索しテキスト生成しCSV用テキストへ連結
    var n = lList.length;
    if( n > 0 ) {
        $.writeln ( "[" + lList + "]" );
        for (var i = 0; i < n; i++) {
            var lname = lList[i];
            var focusLayer = layObj.artLayers.getByName(lname);

            var focusLayerArea = focusLayer.bounds;  // フォーカスエリアを取得
            var x1 = parseInt(focusLayerArea[0]);  // 左上 x座標
            var y1 = parseInt(focusLayerArea[1]);  // 左上 y座標

            var addStr = focusLayer.name + "," + x1 + "," + y1;  // テキスト生成

            // デバック出力
            $.writeln(addStr);

            strData += addStr + "\n";  // CSV用テキストへ連結
        }
    }

    // 配下のレイヤーフォルダを捜索
    var ns = layObj.layerSets.length;
    for (var i=0; i<ns; i++){
        var focusLayerSet = layObj.layerSets[i];

        // デバック出力
        $.writeln ( "[" + focusLayerSet.name + "]" );

        // レイヤーセット名に該当する拡張子を捜索する
        if( checkNameExtensions( focusLayerSet.name ) ){

            var focusLayerSetArea = focusLayerSet.bounds;  // フォーカスエリアを取得
            var x1 = parseInt(focusLayerSetArea[0]);  // 左上 x座標
            var y1 = parseInt(focusLayerSetArea[1]);  // 左上 y座標

            var addStr = focusLayerSet.name + "," + x1 + "," + y1;  // テキスト生成

            // デバック出力
            $.writeln ( "    " + addStr );

            strData += addStr + "\n";  // CSV用テキストへ連結
        }
        // 再帰的に処理
        allLayerSetsBounds( focusLayerSet );
    }
}

余談や応用例

自分の手元ではこれをどういうふうに拡張しているか

こちらのCSVのコードを書き換えると色々なものに応用できます。

私の場合は、FLASH内での配置に置き換えられるような処理群にさらに変換して、PSDの位置を移植できるような仕組みにしています。

ちょっと試してみたものとしては、Edge AnimateのプロジェクトはJSONでできているので、それに合わせた出力もなんとなく上手く行って、今後はもっとたくさんのオブジェクトで試してみようと画策中です。

対象のレイヤーでboundsの情報が深い階層になると重い

レイヤーセットのことが多い気がしますが、対象のレイヤーで中に含まれているレイヤー数が多い場合に、全部捜索して位置情報を拾うためエラい重くなるようです。

今回のスクリプトでは入れていませんが、その場合は一度レイヤーセットを複製し結合してしまってから、boundsを発動させるとかなり負荷軽減しました。

var focusLayerSetArea = focusLayerSet.bounds;  // フォーカスエリアを取得
// ↑ レイヤーセット内に仮に50レイヤー+20レイヤーフォルダあったりすると激重になる

var x1 = parseInt(focusLayerSetArea[0]);  // 左上 x座標
var y1 = parseInt(focusLayerSetArea[1]);  // 左上 y座標

これに処理を加えるなら

var dupLayerSet = focusLayerSet.duplicate().merge();  // 複製してレイヤー結合してしまう(なるべく非破壊にしたいので複製)
var focusLayerSetArea = dupLayerSet.bounds;  // フォーカスエリアを取得
dupLayerSet.remove(); // 用済みで削除
            
var x1 = parseInt(focusLayerSetArea[0]);  // 左上 x座標
var y1 = parseInt(focusLayerSetArea[1]);  // 左上 y座標

こうすると、処理が軽くなります。

高解像度データが入っているスマートオブジェクトが激重

スマートオブジェクトは試行錯誤にとても向いているツールだとは思うのですが、高解像度データが入っているものは捜索が重くなります。

なので「すべてのレイヤーをラスタライズ」をかけてから行うほうがいいでしょう。

シェイプやパスなどの作りによっては、位置がしっかり取れない場合がある。

シェイプやパスで画面外にはみ出る形で配置されていたりすると、位置がマイナスから始まるケースがあります。

ある意味正しい動作なのですが、それだと困ることも多いので、なるべくラスタライズしてから、画面ピッタリに切り抜きを行ってから行うほうがいいでしょう。

ラスタライズ後の切り抜きも自動化に組み込むことも可能です。以下が例です。

    // 切り抜き
    preferences.rulerUnits = Units.PIXELS;
    activeDocument.crop([0,0,activeDocument.width.value,activeDocument.height.value]);

上記のようなこともあり、ある程度できている自動処理で公開した

上記のような処理を内包してしまうと、より使い勝手は良くなるのですが、PSDデータがラスタライズや切り抜き、あるいは複製レイヤーとはいえPSDが変更されるため、ファイルが破壊されてしまうのです。そのため、そういった、破壊の可能性があるものは抜いた、ある程度できている自動処理で公開した次第です。

おわりに

いかがでしたでしょうか?

このままだとCSVに位置情報が拾えるだけですが、PhotoshopのGenerator機能便利だけど位置が飛んで嫌だなあメモを取りたいと思う方、もし位置情報が取れるならこんな拡張も行けそうじゃないかと思う方はぜひ使ってみてください。

それではよきPhotoshop JSX Lifeを!