PhotoShop jsxでスマートオブジェクトのangle・scaleを取得する


Photoshopのスクリプト上ではスマートオブジェクトのレイヤーも通常のArtLayerとして認識されています。
しかし、スマートオブジェクトがどのように配置されているかスクリプト上で取得する際、ArtLayerにある情報だけでは配置情報をうまく生成できません。そのため、ArtLayerの情報ではなく、ActionDescriptorから収集した情報で配置情報をうまく解析する方法を模索してみました。

ちなみにこの記事で扱うのは以下のスマートオブジェクトの情報です。

  • 回転角度(angle)
  • 拡大縮小率(scale)
  • 水平反転・垂直反転(Flip)

事前準備 : 4角の座標を取得する

通常、レイヤーの4角を取得するのは基本layer.boundsを使いますが、回転している場合、回転している状態の全てを覆うサイズのboundsが取得されてしまうため、正しい4角の情報が来ません。

これで得られる情報では角度などの計算が難しいため、本当の4角の情報を取得したいと思います。

この情報はうまいことAPIから取れないので、ActionDescriptorから取得しました。

getRectPoints.jsx
/**
 * 配置されたスマートオブジェクトの4角の点を取得する
 **/
var getRectPoints = function(layer)
{
  if(layer.kind != LayerKind.SMARTOBJECT) return;
  app.activeDocument.activeLayer = layer;

  // 現在のアクティブレイヤーをリファレンスに登録
  var ref = new ActionReference();
  ref.putEnumerated(charIDToTypeID("Lyr "),charIDToTypeID("Ordn"),charIDToTypeID("Trgt"));
  var d = executeActionGet(ref);

  // スマートオブジェクト以外のレイヤーに行うとエラーになるので注意
  var obj = d.getObjectValue(stringIDToTypeID('smartObjectMore'));

  var points = [];
  if (obj.hasKey(stringIDToTypeID('transform')))
  {
    var t_list = obj.getList(stringIDToTypeID('transform'));
    // 端四点の位置を取得する
    for(var i=0;i<t_list.count;i=i+2)
    {
      points.push({
        x : t_list.getDouble(i),
        y : t_list.getDouble(i+1)
      });
    }
  }
  return points;
}

4点の情報は以下の順番で配列に入ってきます

重要なのは、この点の順番は回転・反転などしたとしても、リンク元のPSDの左上から時計回りの順番でくる、ということです。
この4角の情報が取れてしまえばあとは計算するだけです!

角度(angle)の取得

ベクトルの角度計算で求めます。

getAngle.js
var getAngle = function()
{
  // 先ほど作った関数から4角の座標を取得
  var p = getRectPoints();

  // 端4点の位置から回転量を計算
  var x = p[1].x - p[0].x;
  var y = p[1].y - p[0].y;
  var angle = Math.atan2(y, x) * (180/Math.PI);

  return angle;
}

拡大率(scale)の取得

scaleの計算では、4角の点に加えてリンク元のドキュメントのサイズも取得します。

getScale.js
var getScale = function()
{
  // 現在のアクティブレイヤーをリファレンスに登録
  var ref = new ActionReference();
  ref.putEnumerated(charIDToTypeID("Lyr "),charIDToTypeID("Ordn"),charIDToTypeID("Trgt"));
  var d = executeActionGet(ref);

  // スマートオブジェクト以外のレイヤーに行うとエラーになるので注意
  var obj = d.getObjectValue(stringIDToTypeID('smartObjectMore'));

  // 先ほど作った関数から4角の座標を取得
  var p = getRectPoints();

  if (obj.hasKey(stringIDToTypeID('size')))
  {
    var t = obj.getObjectValue(stringIDToTypeID('size'));
    var width = t.getDouble(stringIDToTypeID("width"));
    var height = t.getDouble(stringIDToTypeID("height"));

    // 端4点からレイヤーのサイズを取得する
    var l_width = Math.sqrt(
        Math.pow(p[1].x-p[0].x, 2)
      + Math.pow(p[1].y-p[0].y, 2));
    var l_height = Math.sqrt(
        Math.pow(p[2].x-p[1].x, 2)
      + Math.pow(p[2].y-p[1].y, 2));

    // レイヤーのサイズと見比べてscaleを計算する
    var scale = {
      x : l_width / width,  // 横scale
      y : l_height / height, // 縦scale
    };
  }
  return scale;
}

反転(flip)の取得

反転のチェックでは、取得した点同士の位置関係からflipの状態を取得します。

getFlip.jsx
var getFlip = function()
{
  // 先ほど作った関数から4角の座標を取得
  var p = getRectPoints();

  // 垂直反転(左上[0]が右上[1]より右にきている && 上下状態が正しい)
  var vertical = p[0].x > p[1].x && p[0].y < p[3].y;

  // 水平反転(左上[0]が左下[3]より下にきている && 左右状態が正しい)
  var horizontal = p[0].y > p[3].y && p[0].x < p[1].x;

  var flip = {
    vertical : vertical,
    horizontal : horizontal
  };
  return flip;
}

結構みなさん当たり前に取得できているのか、知見が調べてみてもなかなか出てこなかったので、取得できるようになるまで時間がかかりました。
ActionDescriptor周りは情報が少なくて難しいですね....。もっと使いこなせるようになりたいところ。