Developer's Blog

CSS3 の trasform による画像処理入門

WebDevBlogTitle

こんにちは。Web 開発担当の梶野です。
最近はやっと IE8 対応も減ってきて、 CSS3 の transform を安心して使用できる機会が増えてきました。
そこで今回は、今更ではありますが、CSS3 の transform を使って基本的な画像変換である「任意座標を中心とした回転、拡大、移動」をやってみたいと思います。

 

transformの2つの使い方

 

CSS3 の transform プロパティの設定には大きくわけて以下の2つの方法が存在します。

  • ・ rotate()、scale()、 translate() でそれぞれの値を指定する
  • ・ matrix() を使って全ての変換を1つの行列で指定する

 

今回は両方のアプローチで「任意座標を中心とした回転、拡大、移動」をやってみて同じ結果になることを確認しようと思います。
それぞれの場合で 100px × 100px の画像に対して以下の変換を試みます。

  • ・90度回転
  • ・1.5倍に拡大
  • ・ベース( 200px × 200px のグレーの領域)の中心まで移動する

 

変換前、変換後の画像は以下のようになる予定です。

 

sample_s

rotate()、scale()、 translate() で指定する場合

この方法で座標変換を指定する一番のメリットは画像処理に関する知識が無くても直感的に記述できる点です。
1つずつ変換の実例を見ていきましょう。

回転:rotate ()

 

  transform: rotate(90deg);
title_icon

問題なく90度回転していることが確認できますが、1つ注意点があります。
transform には座標変換の中心を指定する機能があります。
この変換の中心はデフォルトでは要素の左上座標 (0, 0) ではなく要素の中心座標が設定されています。
今回の画像は 100px × 100px なので変換の中心 (x, y) = (50px, 50px) となります。

 

・変換の中心を明示的に (x, y) = (0, 0) にした場合

  transform-origin: 0 0;
  transform: rotate(90deg);
title_icon

要素の原点 (x, y) = (0, 0) を中心に回転していることがわかります。

・変換の中心を明示的に (x, y) = (50px, 50px) にした場合

 

  transform-origin: 50px 50px;
  transform: rotate(90deg);
title_icon

変換の中心を (x, y) = (50px, 50px) に指定した際は未指定の場合と同じ結果が得られます。

拡大:scale ()

 

  transform: scale(1.5);
title_icon

scale() の場合もデフォルトでは要素の中心を変換の中心として拡大されるためベース(グレー領域)を飛び出してしまっています。
このような場合、明示的に変換の中心を (x, y) = (0, 0) に指定すると以下のように拡大できます。

 

  transform-origin: 0 0;
  transform: scale(1.5);
title_icon

移動:translate()

 

  transform: translate(50px, 50px);
title_icon

translate を単独で使用する場合は変換の中心をどこに置いても見た目に変化はありません。

変換処理を組み合わせる

変換処理を組み合わせる場合、変換の順序が記述順と逆になる点に注意が必要です。
例えば以下のように記述した場合、移動 → 拡大 → 回転 の順で変換されます。

 

  transform-origin: 50px 50px;
  transform:rotate(90deg) scale(1.5) translate(50px, 50px);
title_icon

この順序ではなんだかおかしなことになりました。変換の順番を変えてみましょう。

 

  transform-origin: 50px 50px;
  transform: translate(50px, 50px) scale(1.5) rotate(90deg);
title_icon

うまく変換できました。拡大 → 回転 → 移動 または 回転 → 拡大 → 移動 の順で変換すれば目的の結果が得られます。
では何故こんな現象が起きるのでしょうか。
それを知るにはまず transform-origin の正体を調べてみる必要があります。

 

  transform-origin: 0 0;
  transform: translate(50px, 50px) 
             translate(50px, 50px)
             scale(1.5)
             rotate(90deg)
             translate(-50px, -50px);
title_icon

おわかりいただけたでしょうか。変換の中心を (0, 0) にしているにもかかわらず同じ結果が表示されています。代わりに以下の処理が追加されています。

  • ・変換の中心となる座標までの距離を最初にマイナス方向に移動
  • ・変換の中心となる座標までの距離を最後にプラス方向へ移動


つまり変換前に中心となる位置まで移動させて、終わったらその分の位置を元に戻している訳です。これが transform-origin の正体です。
最初の変換の組み合わせで同じ結果が得られなかったのは拡大、回転の変換の前に移動処理を入れたことで変換の中心がズレてしまったことが原因です。

matrix() で指定する場合

matrix() を使って変換するにはアフィン変換の知識が少なからず必要になるでしょう。
アフィン変換についての詳細は調べればいくらでも出てくるのでここでは割愛します。
要するに今回でいうところの回転、拡大、移動を実現するための座標変換であり、以下の行列で表します。

affine

(x, y) が変換前の座標、(x’, y’) が変換後の座標です。
a から f までの6つの値によって回転、拡大、移動の全ての変換を表現します。

  transform: matrix(a, b, c, d, e, f)

affine2

上記の行列 R、S、T は rotate()、scale()、translate() に相当する変換行列です。
これらの行列は (x, y) に乗算していくことで変換結果の座標を得られますが、ここでも順番が重要になってきます。
回転 → 拡大 → 移動 の順で変換するならば以下の式になります。

affine3

最終的な変換行列を M とするなら以下の式が成り立ちます。

affine4

並び順が rotate()、scale()、translate() の関数を使ったときと同じになっています。
記述順が逆になっていたのは行列の順に合わせたのでしょう。

transform-origin に (0, 0) を指定して、変換の中心の指定を含めた行列を作る場合は前後に移動の変換行列を加えることになるので M は
以下の式になります。

affine5

各行列ごとに matrix() を複数に分けて書くこともできますが、それでは rotate()、scale()、translate() を使用するのと変わらないので1つの matrix() で一括指定してみます。
式を展開してみましょう。

abcdef

実際の値を入れてみます。
a = 0
b = 1.5
c = -1.5
d = 0
e = 175
f = 25

やっと matrix() を使う準備が整いました。実際に使ってみましょう。

  transform-origin: 0 0;
  transform: matrix(0, 1.5, -1.5, 0, 175, 25);
title_icon

めでたく目標の結果と一致しました。

matrix() に慣れない方はこの機能に必要性を感じないかもしれませんが、
CSS3 だけでなく画像処理であれば必ずと言っていいほど同じような処理が出てきます。
1度覚えれば使い回しが効くので便利です。
HTML5 の canvas では transform(a, b, c, d, e, f) 関数が用意されており、
今回算出した値をそのまま使用できます。

まとめ

さて、長くなってしまいましたが、今回は CSS3 の transform の使い方を見てきました。
2通りのやり方はどちらも一長一短ありますので用途にあった方を適切に選んで使用してみましょう。

 

フェンリルのオフィシャル Twitter アカウントでは、フェンリルプロダクトの最新情報などをつぶやいています。よろしければフォローしてください!

 

Copyright © 2019 Fenrir Inc. All rights reserved.