PSDファイルのレイヤー構造とレイアウト情報をxml化する


PSDファイルで作ったモックをcocos2dでレイアウトしようと思ったのだけど、冷静に考えて無理だという結論に至ったのでPSDファイル上のレイアウト情報を構造化しようと思った。

JSXのリファレンスが少ないわ公式のドキュメントは膨大だわなぜかpdfだわで散々な目にあった。
とはいえjsxは意外と高機能でファイルシステムへのアクセスとかも普通にできた。

だけどjsxはjsにあらずというかecmaにまったく準拠していないのでjsだと思ってかかると大変なことになる。
Objectはkeysとか持ってないしinstanceofとかも機能しないしもうじゃあ逆に何が使えるんだ的な。

## 環境
- OSX 10.9
- Adobe Photoshop CC
- Adobe Extendscript Toolkit CC

## PSD上でのレイアウト

たとえばこんなpsdファイルがあったとして

![スクリーンショット 2014-04-01 5.05.44 1.png](https://qiita-image-store.s3.amazonaws.com/0/3209/4933bcd9-eecd-42a4-93d7-0241f47309ff.png "スクリーンショット 2014-04-01 5.05.44 1.png")

こんなjsxを

```javascript:psd2xml.jsx
(function(){
    var doc, depth, log, tabs, xmlize, isLayerSet, isCollection, dumpCollection, dumpLayers;
    // 現在の階層に応じてログを出力する
    log = function(msg){
        $.writeln(tabs(depth)+msg);
    };
    // タブを取得
    tabs = function(){
      var i, tabs;
      for (i = 0, tabs = ""; i < depth; i++){
          tabs += "\t";
      }
      return tabs;
    };
    // オブジェクトがレイヤーのコレクションかどうか
    isCollection = function(obj){
        return (obj.typename === "LayerSets" || obj.typename === "Layers" || obj.typename === "ArtLayers");
    };
    // オブジェクトがレイヤーセットか
    isLayerSet = function(obj){
      return (obj.typename === "LayerSet" || obj.typename === "Document");
    };
    // コレクションをレイヤーごとに出力する
    dumpCollection = function(collection){
         var i, max, memo, layer, keys;
         memo = "";
         keys = ["name","bounds","opacity","visible","kind","isBackgroundLayer","blendMode"];
         if (isCollection(collection)){
            for (i = 0, max = collection.length; i < max; i++){
                layer = collection[i];
                memo += dumpLayers(layer,keys);
            }
        }
        return memo;
    };
    // オブジェクトをxml化する
    xmlize = function(obj,keys){
      var i,max,key,xml;
      xml = "";
      xml += tabs() + "<"+obj.typename;
      for (i = 0, max = keys.length; i < max; i++){
         key = keys[i];
         val = obj[key];
         xml += " "+key+"="+"\""+val+"\"";
      }
      xml += isLayerSet(obj) ? " >\n" : " />\n";
      if (isLayerSet(obj)){
         depth++;
         xml += dumpCollection(obj.layers);
         depth--;
         xml += tabs() + "</"+obj.typename+">\n";
      }
      return xml;
    };
    // オブジェクトをKeyValueでXML化して出力
    dumpLayers = function(obj,keys,memo){
      if (!obj) return;
      if (!keys) keys = obj.keys;
      if (!memo) memo = "";
      if (isCollection(obj)) {
          return dumpCollection(obj) + memo;
      }else{
          return xmlize(obj,keys) + memo;
      }
    };
    // Photoshopをアクティブにする
    app.bringToFront();
    doc = app.activeDocument;
    depth = 0;
    (function(){
      // ファイルに保存
      var xml, file, path;
      xml = dumpLayers(doc,["name","width","height"],"");
      $.writeln(xml);
      path = File.saveDialog ("psdデータを保存");
      file = new File(path);
      if (file.open("w")) {
        file.write(xml);
        file.close();
      }else{
          alert("保存に失敗しました");
      }
    })();
})();

```

Extendscript Toolkit上で実行すると、ファイル保存ダイアログが表示されるので、

![スクリーンショット 2014-04-01 5.04.04.png](https://qiita-image-store.s3.amazonaws.com/0/3209/3b6e4619-dcfa-96e9-9995-45a425d75cae.png "スクリーンショット 2014-04-01 5.04.04.png")

適当に名前を付けて保存するとこんなxmlファイルができる。

```xml:Test.psd.xml
<Document name="Test.psd" width="640 px" height="960 px" >	<ArtLayer name="Text" bounds="24 px,792 px,107 px,821 px" opacity="100" visible="true" kind="LayerKind.TEXT" isBackgroundLayer="false" blendMode="BlendMode.NORMAL" />	<LayerSet name="グループ 1" bounds="110 px,340 px,216 px,369 px" opacity="100" visible="true" kind="undefined" isBackgroundLayer="undefined" blendMode="BlendMode.PASSTHROUGH" >		<ArtLayer name="Text2" bounds="110 px,340 px,216 px,369 px" opacity="100" visible="true" kind="LayerKind.TEXT" isBackgroundLayer="false" blendMode="BlendMode.NORMAL" />	</LayerSet>	<ArtLayer name="Bottom" bounds="0 px,765 px,640 px,960 px" opacity="100" visible="true" kind="LayerKind.SOLIDFILL" isBackgroundLayer="false" blendMode="BlendMode.NORMAL" />	<ArtLayer name="Top" bounds="0 px,0 px,640 px,100 px" opacity="100" visible="true" kind="LayerKind.SOLIDFILL" isBackgroundLayer="false" blendMode="BlendMode.NORMAL" />	<ArtLayer name="背景" bounds="0 px,0 px,640 px,960 px" opacity="100" visible="true" kind="LayerKind.NORMAL" isBackgroundLayer="true" blendMode="BlendMode.NORMAL" /></Document>
```

xmlのattributeはjsx中で指定しているので変えることもできる。

## 構造化ができたら？

煮るなり焼くなり。

- レイヤー構造が分かっている	→	cocos2dやらUIViewやらでリビルドができる
- 絶対位置のboundsが分かっている	→	レイアウトができる
- opacity/bleneMode/visibleなどのメタデータがある	→	コンポジションができる

ということに加えて、jsxはレイヤーを画像として切り出すことが可能なので、アプリ側でビルダーを作ってあげれば

**PSDファイルからViewを構築できるということですね、ハイ。**

あと、分かんないですけど多分jsxでスプライト化とかもできるんじゃないでしょうか？
どちらにせよ素晴らしい作業効率のupが期待できると思います。
というわけで次回はxmlをパースしてスプライトとかを配置したいと思います。