Visit Mozilla.org

Canvas tutorial:Transformations

出典: MDC

目次


[編集] 状態の保存と復帰

変形のメソッドを見る前に、私は一度より複雑な図形を作り始めるために欠かすことのできない他の二つのメソッドを紹介しましょう。

save()
restore()

canvas の saverestore メソッドは canvas の状態を保存および回復するために使われるメソッドです。canvas の描画状態は基本的には今まで適用されてきた全てのスタイルと変形のスナップショットです。両方のメソッドはともにパラメタを取りません。

Canvas の状態はスタックに保存されます。save メソッドが呼ばれる毎に、現在の描画状態はスタックの上にプッシュされます。描画状態は以下によって構成されます

  • 適用されてきた変形 (すなわち 移動、回転、伸縮 - 下記参照)。
  • strokeStylefillStyleglobalAlphalineWidthlineCaplineJoinmiterLimitshadowOffsetXshadowOffsetYshadowBlurshadowColorglobalCompositeOperation プロパティの値
  • 現在の切り抜きパス。次の節でみるでしょう。

あなたは好きなだけ save メソッドを呼ぶことができます。

restore メソッドが呼ばれるごとに、最後に保存された状態がスタックから戻り全ての保存された状態が復帰します。

[編集] canvas 状態の保存と復帰の例

この例は連続した矩形を描くことによって描画状態のスタックがどのように機能するかを説明しようとしています。

最初の作業はデフォルトの設定でおおきな矩形を描くことです。次にこの状態を保存し塗りの色を変更します。ついで二番目のより小さい青い矩形を描き状態を保存します。もう一度いくつかの描画設定を変更し三番目の半透明の白い矩形を描きます。

今までのところこれは私たちが前の節でしたこととほとんど同じです。しかし一度最初の restore 文を呼ぶと、一番上の描画状態がスタックから削除され、設定が復帰されます。もし save を使って状態を保存していないと、以前の状態に戻すために塗りの色や透明度を手動で設定する必要があるでしょう。これは二つのプロパティなら簡単でしょうが、これより多くのプロパティを持っていたら、コードはとても長く、早くなります。

二番目の restore 文が呼ばれると原始状態(最初に save を呼ぶ前に設定されたもの)が復帰され、最後の矩形がもう一度黒で描かれます。

この例を見る

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  ctx.fillRect(0,0,150,150);   // デフォルトの設定で矩形を描く
  ctx.save();                  // デフォルトの状態を保存する

  ctx.fillStyle = '#09F'       // 設定を変更する
  ctx.fillRect(15,15,120,120); // 新しい設定で矩形を描く

  ctx.save();                  // 現在の設定を保存する
  ctx.fillStyle = '#FFF'       // 設定を保存する
  ctx.globalAlpha = 0.5;
  ctx.fillRect(30,30,90,90);   // 新しい設定で矩形を描く

  ctx.restore();               // 直前の状態に復帰する
  ctx.fillRect(45,45,60,60);   // 復帰した設定で矩形を描く

  ctx.restore();               // 原始状態で復帰する
  ctx.fillRect(60,60,30,30);   // 復帰した設定で矩形を描く
}

[編集] 移動

最初の変形メソッドは translate です。このメソッドは canvas とその原点をグリッドの異なる点に移動します。

translate(x, y)

このメソッドは二つの引数を取ります。x は canvas が左か右に動かされた量、そして y は上または下に動かされた量です(右の画像で図説されています)。 どんな変形をする前に canvas 状態を保存することはよい考えです。ほとんどの場合、restore を呼ぶことは原始状態に戻るために逆の移動をするよりも簡単です。もしあなたがループの中で移動し canvas 状態を保存したり復帰しないなら、canvas の端の外に描かれるため、あなたは描画の一部を失うかもしれません。

[編集] translate の例

この例はいくつかの canvas の原点を移動したことによる利益を実演します。私は スパイログラフのパターンを描く drawSpirograph 関数を書きました。それれは原点の周りに描かれます。もし私が translate 関数を使わないと、私はパターンの 4 分の 1 しか見ないでしょう。translate メソッドは私にスパイログラフ関数を手動で座標を調整することなく canvas 上のどこにでもそれを置く自由をも与えます。このことはそれを理解し使用することを少し簡単にします。

draw 関数の中で私は 2 つのfor ループを使って drawSpirograph を 9 回呼び出しています。それぞれのループごとに canvas は移動され、スパイログラフが描かれ、canvas は原始状態に戻っています。

この例を見る

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  ctx.fillRect(0,0,300,300);
  for (var i=0;i<3;i++) {
    for (var j=0;j<3;j++) {
      ctx.save();
      ctx.strokeStyle = "#9CFF00";
      ctx.translate(50+j*100,50+i*100);
      drawSpirograph(ctx,20*(j+2)/(j+1),-8*(i+3)/(i+1),10);
      ctx.restore();
    }
  }
}
function drawSpirograph(ctx,R,r,O){
  var x1 = R-O;
  var y1 = 0;
  var i  = 1;
  ctx.beginPath();
  ctx.moveTo(x1,y1);
  do {
    if (i>20000) break;
    var x2 = (R+r)*Math.cos(i*Math.PI/72) - (r+O)*Math.cos(((R+r)/r)*(i*Math.PI/72))
    var y2 = (R+r)*Math.sin(i*Math.PI/72) - (r+O)*Math.sin(((R+r)/r)*(i*Math.PI/72))
    ctx.lineTo(x2,y2);
    x1 = x2;
    y1 = y2;
    i++;
  } while (x2 != R-O && y2 != 0 );
  ctx.stroke();
}

[編集] 回転

二つ目の変形メソッドは rotate です。現在の原点を中心に canvas を回転するのに使われます。

rotate(angle)

このメソッドは 1 つのパラメタを取り、それは canvas を回転させる角度です。これはラジアンで計算された時計周りの回転です(右の画像で図説されています)。

回転の中心点は常に canvas の原点です。中心点を変えるには、translateを使って canvas を移動する必要があります。

[編集] rotate

右にみえる例の中で、私は円形のパターンを描くために rotate を使いました。あなたは個々の xy 座標を計算することもできます(x = r*Math.cos(a); y = r*Math.sin(a))。円を描いているので、この件ではあなたがどのメソッドを選ぶかは重要ではありません。座標の計算することは円の中心点を回転することのみでそれらを回転させることを引き起こしませんが、rotate を使うことは両方を引き起こしますが、もちろん、円の中心がどんなに回転しても円は同じように見えます。

再び二つのループをもっています。最初は輪の数を定義し、二番目はそれぞれの輪の点の数を定義します。それぞれの輪を描く前に、より簡単にそれを回復しやすくするために、私はそれぞれの canvas 状態を保存しました。描かれたドットごとに、私は canvas の座標空間を輪の中の点の数によって定義された角度によって回転させました。もっとも深い縁は 6 個の点があるので、各ステップでは、私は 360/6 = 60度の角度ずつ回転します。それぞれの追加すれた輪するともに点の数は 2 倍になり回転の角度は半分になります。

この例を見る

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  ctx.translate(75,75);

  for (i=1;i<6;i++){ // Loop through rings (from inside to out)
    ctx.save();
    ctx.fillStyle = 'rgb('+(51*i)+','+(255-51*i)+',255)';

    for (j=0;j<i*6;j++){ // draw individual dots
      ctx.rotate(Math.PI*2/(i*6));
      ctx.beginPath();
      ctx.arc(0,i*12.5,5,0,Math.PI*2,true);
      ctx.fill();
    }

    ctx.restore();
  }
}

[編集] 伸縮

次の変形メソッドは伸縮です。canvas グリッドの単位を増加したり減少させるのにそれを使います。これは図形やビットマップを縮小したり拡大するのに使われます。

scale(x, y)

このメソッドは二つのパラメタを取ります。x は水平方向の伸縮の係数で y は垂直方向の伸縮の係数です。パラメタは両方とも正の数でなくてなりません。 1.0 より小さい値は単位の大きさを減少させ、1.0 より大きい値は単位の大きさを増加させます。伸縮の係数を性格に 1.0 に設定することは単位の大きさには影響を与えません。

初期の canvas の 1 単位は正確に 1 ピクセルです。例えば、もし 0.5 を伸縮の係数に適用すると、結果の単位は 0.5 ピクセルになり半分の大きさで図形が描かれます。似たように伸縮の係数を 2.0 に設定することは単位の大きさが増加して 1 単位は 2 ピクセルになります。これは 2 倍大きく描画された図形をもたらします。

[編集] scale の例

この最後の例で私は異なる伸縮の係数をもった9つの図形を描くために、以前の例の一つからスパイログラフ関数を使いました 左上の図形は伸縮なしで描かれています。右の黄色い図形は両方とも均一な伸縮係数をもっています (xy パラメタは同じ値)。もしあなたが下のコードを見るとあなたは私が 2番目と 3番目のスパイログラフのために scale メソッドを 2 度等しいパラメタの値を使っていることに気がつくでしょう。canvas 状態の復帰を行っていないため、三番目の図形は 0.75 × 0.75 = 0.5625 の伸縮係数を使って描かれています。

青い図形の 2 番目のは 不均一な伸縮が垂直方向に適用されています。それぞれの図形は x 伸縮係数は伸縮なしを意味する 1.0 に設定されています。y 伸縮係数は 0.75 に設定されています。これは潰れる3つの図形をもたらします。オリジナルの円は今楕円になりますしっかり見るとあなたは線の幅が縦方向で減少したのもわかるでしょう。

3番目の列の緑色の形は上のものと同様ですが、今私は水平方向で伸縮を適用しました。

この例を見る

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  ctx.strokeStyle = "#fc0";
  ctx.lineWidth = 1.5;
  ctx.fillRect(0,0,300,300);

  // 一律の伸縮
  ctx.save()
  ctx.translate(50,50);
  drawSpirograph(ctx,22,6,5);  // 伸縮されない

  ctx.translate(100,0);
  ctx.scale(0.75,0.75);
  drawSpirograph(ctx,22,6,5);

  ctx.translate(133.333,0);
  ctx.scale(0.75,0.75);
  drawSpirograph(ctx,22,6,5);
  ctx.restore();

  // 均一でない伸縮 (y 方向)
  ctx.strokeStyle = "#0cf";
  ctx.save()
  ctx.translate(50,150);
  ctx.scale(1,0.75);
  drawSpirograph(ctx,22,6,5);

  ctx.translate(100,0);
  ctx.scale(1,0.75);
  drawSpirograph(ctx,22,6,5);

  ctx.translate(100,0);
  ctx.scale(1,0.75);
  drawSpirograph(ctx,22,6,5);
  ctx.restore();

  // 均一でない伸縮 (x 方向)
  ctx.strokeStyle = "#cf0";
  ctx.save()
  ctx.translate(50,250);
  ctx.scale(0.75,1);
  drawSpirograph(ctx,22,6,5);

  ctx.translate(133.333,0);
  ctx.scale(0.75,1);
  drawSpirograph(ctx,22,6,5);

  ctx.translate(177.777,0);
  ctx.scale(0.75,1);
  drawSpirograph(ctx,22,6,5);
  ctx.restore();

}

[編集] 変形

最後の変形メソッドは、変形マトリックスを直接変更します。

transform(m11, m12, m21, m22, dx, dy)

このメソッドは、現在の変形マトリックスに次のように書かれたマトリックスを掛けます:

m11 	m21 	dx
m12 	m22 	dy
0 	0 	1

引数のいずれかが無限の場合は、メソッドが例外を発生させる代わりに、変形マトリックスに無限としてマークされます。

setTransform(m11, m12, m21, m22, dx, dy)

このメソッドは、現在の変形を指定のマトリックスにリセットし、同じ引数の transform メソッドを呼び出します。引数のいずれかが無限の場合は、メソッドが例外を発生させる代わりに、変形マトリックスに無限としてマークされます。

[編集] transform / setTransform の例

function draw() {
  var canvas = document.getElementById("canvas");
  var ctx = canvas.getContext("2d");

  var i = 0;
  var sin = Math.sin(Math.PI/6);
  var cos = Math.cos(Math.PI/6);
  ctx.translate(200, 200);
  var c = 0;
  for (i; i <= 12; i++) {
    c = Math.floor(255 / 12 * i);
    ctx.fillStyle = "rgb(" + c + "," + c + "," + c + ")";
    ctx.fillRect(0, 0, 100, 10);
    ctx.transform(cos, sin, -sin, cos, 0, 0);
  }
  
  ctx.setTransform(-1, 0, 0, 1, 200, 200);
  ctx.fillStyle = "rgba(255, 128, 255, 0.5)";
  ctx.fillRect(0, 50, 100, 100);
}


« »