サイトトップ

Director Flash 書籍 業務内容 プロフィール

HTML5テクニカルノート

EaselJSのMatrix2D.scale()メソッドを他の変換の後に呼出すと正しく伸縮されない

ID: FN1305002 Technique: HTML5 and JavaScript Library: EaselJS 0.6.0

EaselJS 0.6.0のMatrix2Dクラスは、2次元平面における座標を行列により変換します。座標変換には、回転と伸縮、および平行移動があります。Matrix2D.scale()は、伸縮を担うメソッドです。ところが、他の変換の後にこのメソッドを呼出すと、正しく伸縮されないことがあります。

[追記: 2013年5月16日] 2013年5月15日付GitHubでこの問題は修正されました。


01 まったく伸縮されない例

デフォルトのMatrix2Dオブジェクト(単位行列)をMatrix2D.rotate()メソッドで90度(π/2ラジアン)回転すると、Matrix2D.scale()メソッドで伸縮することはできなくなります。

たとえばつぎのコードは、新たなMatrix2DオブジェクトにMatrix2D.rotate()メソッドで90度(Math.PI / 2ラジアン)の回転を加えたうえ、Matrix2D.scale()メソッドによりxyともに2倍に拡大します。ところが、そのMatrix2DオブジェクトをMatrix2D.decompose()メソッドでインスタンス(myBitmap)に適用しても、90度回るだけで大きさが変わりせん(図001)。

var matrix = new createjs.Matrix2D();
matrix.rotate(Math.PI / 2);
matrix.scale(2, 2);
matrix.decompose(myBitmap);

図001■インスタンスは90度回るだけで拡大しない
図001左 図001右

jsdo.itにテストコードを掲げました。[Play]ボタンで実行して画像をクリックすると、前掲コードと同じ変換のスクリプト(リスナー関数testTransform())が適用されます。インスタンスはMatrix2D.rotate()メソッドで90度回転しても、Matrix2D.scale()メソッドによる大きさの変化は見られません。

前掲コードで、Matrix2D.rotate()Matrix2D.scale()メソッドを呼出した後のMatrix2Dオブジェクトをそれぞれ文字列に表す(Matrix2D.toString())と、変換行列の成分値がわかります。

Matrix2D.rotate()メソッドで90度回転:
[Matrix2D (a=6.123233995736766e-17 b=1 c=-1 d=6.123233995736766e-17 tx=0 ty=0)]
Matrix2D.scale()メソッドでxyを2倍に拡大:
[Matrix2D (a=1.2246467991473532e-16 b=1 c=-1 d=1.2246467991473532e-16 tx=0 ty=0)]

わずかな誤差を丸めれば、ふたつの変換行列の成分はともにつぎの値になります。したがって、Matrix2D.scale()メソッドによる伸縮の変換は、変換行列の成分値に表れていないということです。

a=0 b=1 c=-1 d=0 tx=0 ty=0

02 Matrix2D.scale()メソッドの実装

Matrix2D.scale()メソッドで伸縮の行列変換を行った結果、なぜ前述のように成分値が変わらないのかは、メソッドの実装を見れば確かめられます。Matrix2D.scale()メソッドは、つぎのコード001のように、引数のxy伸縮率を成分のa、d、tx、tyに乗じています。ところが、デフォルトのMatrix2Dオブジェクト(単位行列)を90度回転すると、前述のとおりこれら4成分値はすべて0になります。ですから、演算結果が変わらないのです。

コード001■Matrix2D.scale()メソッドの実装
  1. createjs.Matrix2D.prototype.scale = function(x, y) {
  2.   this.a *= x;
  3.   this.d *= y;
  4.   this.tx *= x;
  5.   this.ty *= y;
  6.   return this;
  7. }

逆にいえば、90度回転したMatrix2Dオブジェクトを伸縮するには、変換行列の成分bとcについても計算しなければならないということです。したがって、EaselJS 0.6.0のMatrix2D.scale()メソッドの実装が誤っています。正しい実装は、変換行列の計算をしなければなりませんので、次項で論じます。当面の対処法としては、Matrix2D.prependTransform()メソッドを用いることが考えられます。

Matrix2Dオブジェクト.prependTransform(x座標, y座標, x伸縮率, y伸縮率, 回転角, x傾斜角, y傾斜角, x基準点, y基準点)

Matrix2D.scale()メソッドのふたつの引数と同じxyの伸縮率が、第3および第4引数で定められます。座標の移動はありませんので、第1および第2引数は0とします。そして、第5引数以降は省いて構いません。なお、前掲jsdo.itのテストコードにも、Matrix2D.prependTransform()メソッドはコメントアウトで添えてあります。

var matrix = new createjs.Matrix2D();
matrix.rotate(Math.PI / 2);
// matrix.scale(2, 2);
matrix.prependTransform(0, 0, 2, 2);
matrix.decompose(myBitmap);


03 EaselJS 0.6.1で修正されたMatrix2D.scale()メソッドの実装

2013年5月11日にGitHubで公開されたEaselJS 0.6.1では、Matrix2D.scale()メソッドの実装がつぎのコード002のように修正されました。「CreateJS Support」への5月5日の投稿「Calculation problem of Matrix2D.scale method」にもとづくものです。

コード002■EaselJS 0.6.1におけるMatrix2D.scale()メソッドの実装
  1. createjs.Matrix2D.prototype.scale = function(x, y) {
  2.   this.a *= x;
  3.   this.b *= y;
  4.   this.c *= x;
  5.   this.d *= y;
  6.   return this;
  7. }

成分bとcの計算が加えられました(第4〜5行目)。したがって、90度回転した後伸縮できないという問題はなくなります。他方で、成分txとtyの計算が除かれました。そのため、前述のMatrix2D.prependTransform()メソッドを用いた場合の結果とは、成分txとtyの値が異なります。同じ結果を得るには、次項04「Matrix2D.scale()メソッドを正しく実装する」のように実装しなければなりません。

[追記: 2013年5月16日] 2013年5月15日付のGitHubで以下の実装のとおり修正されました。


04 Matrix2D.scale()メソッドを正しく実装する

では、Matrix2D.scale()メソッドは、どう実装すれば正しい伸縮の変換ができるでしょう。Matrix2Dクラスが扱う2次元平面の変換行列は、つぎのような3行×3列の正方行列で表されます。ただし、第3行目の成分は定数([0 0 1])なので、実質の計算は6成分で行われます。

  a     c     tx  
  b     d     ty  
  0     0     1  

変換行列の演算は乗算です。xとyで伸縮するには、伸縮率を対角成分としたつぎの変換行列を掛合わせます。

伸縮率x   0     0  
  0   伸縮率y   0  
  0     0     1  

Matrix2D.scale()メソッドの伸縮は、この行列を左から乗じます。水平と垂直の伸縮率をそれぞれxおよびyとすれば、つぎのような行列の掛け算になります。なお、行列の乗算については「変換行列を数学的に捉える」2.「行列の乗算」をお読みください。

  x     0     0     a     c     tx  
  0     y     0     b     d     ty  
  0     0     1     0     0     1  
=     ax     cx     txx  
  by     dy     tyy  
  0     0     1  

この計算にもとづいてMatrix2D.scale()メソッドを正しく実装すると、つぎのコード003のようになります。前掲コード001に、成分bとcの演算が加わっています(第3〜4行目)。EaselJSライブラリの読込み後にこのコードを書けば、Matrix2D.scale()メソッドは伸縮の演算を正しく行います。なお、前掲jsdo.itのテストコードにもコメントアウトで加えてあります。

コード003■Matrix2D.scale()メソッドの正しい実装
  1. createjs.Matrix2D.prototype.scale = function(x, y) {
  2.   this.a *= x;
  3.   this.b *= y;
  4.   this.c *= x;
  5.   this.d *= y;
  6.   this.tx *= x;
  7.   this.ty *= y;
  8.   return this;
  9. }


作成者: 野中文雄
更新日; 2013年5月16日 GitHubの0.6.1が再修正された旨を追記。
更新日: 2013年5月13日 03「EaselJS 0.6.1で修正されたMatrix2D.scale()メソッドの実装」を追加。
作成日: 2013年5月7日


Copyright © 2001-2013 Fumio Nonaka.  All rights reserved.