職業プログラマの休日出勤

職業プログラマによる日曜自宅プログラミングや思考実験の成果たち。リアル休日出勤が発生すると更新が滞りがちになる。記事の内容は個人の意見であり、所属している(いた)組織の意見ではない。

clip と transform との組み合わせ

この記事は SVG Advent Calendar 2014 の1日目の記事です。

SVG とは?

SVG Advent Calendar の初日ということで、簡単に SVG そのものの紹介をしておきましょう。

WikiPediaの記事 → http://ja.wikipedia.org/wiki/Scalable_Vector_Graphics
SVG 1.1 Second Edition の厳密な定義 → http://www.w3.org/TR/SVG/(英語)
手書きする人向けの日本語での解説 → http://www.h2.dion.ne.jp/~defghi/svgMemo/svgMemo_01.htm

なお、この記事には SVG画像 を直接記述しています。ブラウザによっては正しく表示されないことがあるかもしれません。特に古いやつとか。予めご了承下さいませ。

実演に使う画像

この記事では、以前の記事

で掲載した写真を 160px * 120px にリサイズした、
f:id:t_motooka:20141130232505j:plain
を題材にして、いろいろと実演していきたいと思います。

clip とは?

画像を切り抜くやつです。
ここで言う「画像」とは、いわゆる写真(<image>要素)だけでなく、SVGの図形や文字なども含みます。

例えば、こんな SVG を書くと

<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="320" height="240" viewBox="0 0 320 240">
	<rect stroke="none" fill="#ffffc0" x="0" y="0" width="320" height="240"/>
	<image xlink:href="http://img.f.hatena.ne.jp/images/fotolife/t/t_motooka/20141130/20141130232505.jpg" x="80" y="60" width="160" height="120" clip-path="url(#clip1)"/>
	<clipPath id="clip1">
		<path d="M80,60 v120 h30 v-120 z M120,60 v30 h30 l-10,20 l30,-20 v-30 z M120,150 v30 h50 v-30 z M240,60 h-40 l-30,60 l30,60 h40 l-30,-60 z" clip-rule="even-odd"/>
	</clipPath>
</svg>

こんな図形が出来上がります。

<image>要素に添えられたclip-path属性が、<clipPath>要素のid属性の値を指し示しているからこそ、このclipが実現するのです。

SVG における clip の詳細は、こちら http://www.w3.org/TR/SVG/masking.html をご覧下さい。

transform とは?

画像を変形させる機能です。(ここで言う画像とは…clipのところと同じ)
2次元のアフィン変換で処理できることであれば、(その動作原理さえ知っていれば)簡単に記述することができて非常に便利です。

例えばこんな SVG を書くと

<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="320" height="240" viewBox="0 0 320 240">
	<rect stroke="none" fill="#ffffc0" x="0" y="0" width="320" height="240"/>
	<image xlink:href="http://img.f.hatena.ne.jp/images/fotolife/t/t_motooka/20141130/20141130232505.jpg" x="80" y="60" width="160" height="120" transform="rotate(30, 160, 120)"/>
</svg>

こんなのができあがります。

<image>要素のtransform属性の値に着目して下さい。rotate(30, 160, 120)は、座標(160,120)を中心として、30°だけ時計回りに回転するという意味です。

SVG における transform の詳細は、こちら http://www.w3.org/TR/SVG/coords.html をご覧下さい。


clip した結果を transform してみる

ここでは、clip した結果、即ち「にく」の字形も、肉の画像も、どちらも 30°だけ時計回りさせてみましょう。
これは簡単です。

<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="320" height="240" viewBox="0 0 320 240">
	<rect stroke="none" fill="#ffffc0" x="0" y="0" width="320" height="240"/>
	<image xlink:href="http://img.f.hatena.ne.jp/images/fotolife/t/t_motooka/20141130/20141130232505.jpg" x="80" y="60" width="160" height="120" clip-path="url(#clip1)" transform="rotate(30, 160, 120)"/>
	<clipPath id="clip1">
		<path d="M80,60 v120 h30 v-120 z M120,60 v30 h30 l-10,20 l30,-20 v-30 z M120,150 v30 h50 v-30 z M240,60 h-40 l-30,60 l30,60 h40 l-30,-60 z" clip-rule="even-odd"/>
	</clipPath>
</svg>

この SVG を与えてあげれば

このように表示されます。
<image>要素に付加されている属性に着目して下さい。clip-path属性とtransform属性の両方が指定されていますね。

transform で変形したものを clip してみる

それでは、回転させた画像を clip するには、どうしたら良いのでしょうか? そう、ここでは貴方は「にく」の字形は回転させたくないのものとします。
直感的には、上記の「clip した結果を transform してみる」と同じ書き方で良さそうですが、これだと上記のような結果になります。
これこそが、多くの SVG 手書き初心者を悩ませるポイントの一つです。

ある程度 SVG に慣れている方であればもう想像ついているかと思いますが、transformをかけた<image>要素の外側で clip してあげればOKです。具体的には<g>を使います。

<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="320" height="240" viewBox="0 0 320 240">
	<rect stroke="none" fill="#ffffc0" x="0" y="0" width="320" height="240"/>
	<g clip-path="url(#clip1)">
		<image xlink:href="http://img.f.hatena.ne.jp/images/fotolife/t/t_motooka/20141130/20141130232505.jpg" x="80" y="60" width="160" height="120" transform="rotate(30, 160, 120) translate(160, 120) scale(1.5, 1.5) translate(-160, -120)"/>
		<clipPath id="clip1">
			<path d="M80,60 v120 h30 v-120 z M120,60 v30 h30 l-10,20 l30,-20 v-30 z M120,150 v30 h50 v-30 z M240,60 h-40 l-30,60 l30,60 h40 l-30,-60 z" clip-rule="even-odd"/>
		</clipPath>
	</g>
</svg>

このようにしてあげれば

きちんと、回転した結果を clip してくれるようになります。
なお、ここでは、そのまま回転するだけだと文字の形が変わってしまうので、一緒に1.5倍の拡大をかけています。transform属性の中で scale(拡大縮小) を translate(平行移動) で挟んでいるのは、SVGに限らず、二次元のアフィン変換を使うときの必須テクニックです。拡大縮小の起点を一旦座標平面の原点に戻してあげている訳です。内部的には rotate も似たようなことをやってるはずなんですけどね。

※本来、<clipPath>要素は<defs>要素の中に記述するのが良いのですが、この記事ではコード量削減のため、それを実践しておりません。ゴメンなさい。。

次の日

次の2日目は、 memoca_ さんによる「filter を使ったマウスオーバー効果」の予定です。
公開され(たのを検知し)次第、リンク追記します。

☆追記 on 2014.12.02 AM8時頃☆
2日目の記事が公開されました!
[SVG] filter 要素の基本的な使い方と filter 要素をつかったモノクロからカラーへ変化するマウスオーバー効果を作る方法 | memocarilog
(追記ここまで)

関連書籍

Webで使える!SVGファーストガイド

Webで使える!SVGファーストガイド

2014年に発行された書籍だそうです。自分では読んだことないけど、初心者の人に勧めるのには良いのかも。

Svg Essentials

Svg Essentials

これはちょっと古いかも?

☆追記 on 2014.12.02 AM8時頃☆

Svg Essentials

Svg Essentials

上記のものの更新版(2nd Edition)です。
ブコメで教えてくれた id:rikuo さん、ありがとうございました!!
(追記ここまで)