読者です 読者をやめる 読者になる 読者になる

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

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

マスター・オブ・円弧 #SVG

この記事は SVG Advent Calendar 2015 の25日目の記事です。
www.adventar.org
Advent Calendar の最後を締めくくるのに相応しい内容かどうかはわかりませんが、皆さんきっとSVGを描きたくなるはずです。

SVGとは?

SVGとは画像ファイルの形式の一つで、基本的にはベクター画像(引き伸ばしても荒れないアレ)です。画像なんだけど中身はXML、すなわちテキストファイルになっている、不思議で便利な画像です。いまここで初めてSVGの名前を聞いたという皆さんは、上記の Advent Calendar に掲載されている記事を読んだり、Wikipediaの記事 を読むと良いでしょう。

SVG手描きとは?

前述の通りSVGXMLであり、テキストです。
テキストであるということは、テキストエディタで作成したり編集したりすることが理論上可能であるということが言えます。
理論上可能なんだったら実際にやろうぜ!と考えるのが人間というものです。そういう背景から近年盛り上がってきているのが「SVG手描き」という趣味になります。テキストエディタで描きましょう。

(注意)一般的には、Adobe Illustrator などのツールで描いた画像をSVG形式で出力することが多いはずです。

こんな画像だって、SVGならテキストエディタでサクッと描けます。

ソースはこんな感じです。
これをコピーしてテキストエディタに貼り付け、拡張子「.svg」で保存させてからブラウザに読み込ませると同じように描画されるはずです。

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="480px" height="360px" viewBox="0 0 480 360" xmlns="http://www.w3.org/2000/svg" version="1.1">
	<rect fill="white" stroke="black" stroke-width="2" x="0" y="0" width="480" height="360"/>
	<polygon fill="red" stroke="none" points="40,40 40,320 440,320 440,40 400,40 400,280 80,280 80,40"/>
</svg>

rect 要素が外枠の黒い細い線を、 polygon 要素が「凹」の字を描いています。
※別に筆者が凹んでいる訳ではないですからね?

慣れてくると、公式の SVG 1.1 の仕様 を見ながらであれば5分以内に上記の図形を描くことができるようになります。テキストエディタで。

path要素

SVGでは、さまざまな図形がサポートされており、簡単に図形を描くことができます。
例えば以下のような図形があります。

  • rect : 長方形。左上座標と幅と高さを指定して描く。
  • circle : 円(楕円ではない真円)。中心座標と半径を指定して描く。
  • ellipse : 楕円。中心座標とx方向半径、y方向半径とを指定して描く。(回転はアフィン変換で実現)
  • line : 線分。始点と終点を指定して描く。
  • polyline : 線分をつなぎあわせたもの。カクカク曲がる。
  • polygon : 多角形。各頂点の座標を指定して描く。

これらは非常に便利であり、かつ、後からSVGのソースを見たときに意図が分かりやすいため、非常に重宝します。これらの要素を使って描くことができるのであれば、ぜひそうするべきです。
ところが、これらの図形では表現できないものというのは数多く存在します。
そういった図形は、path要素 を使って描くことになります。
画像を描くプログラムを書いたことがある人であればわかるかもしれませんが、path要素で線を描くときは「ペン」の考え方が根底にあります。ペンは現在の座標という状態を持ちます。現在地点から目的地までの経路をどのように線を引くのか、というのをコマンドで指定していきます。便利ですよ〜。

曲線あれこれ

path 要素の d 属性で使うことができるコマンドには、以下のようなものがあります。
※ここでは大文字のコマンドを紹介していますが、それぞれのコマンドには小文字バージョンがあり、それは現在のペンの位置からの相対座標を指定することで描画させるものになります。

  • M : 線を引かずにペンを指定した座標のところへ移動させる
  • Z : 開始地点までの線分を引く(閉じた領域を作る)
  • L : 指定した座標までの線分を引く
  • H : 指定したx座標までの水平線を引く
  • V : 指定したy座標までの垂直な線を引く
  • C : 3次のベジェ曲線を引く
  • S : 3次のベジェ曲線を引くが、1個目の制御点の座標は直前に引いたベジェ曲線のものに応じる(本当はきちんとした定義があるので、公式ドキュメントをご覧ください)。
  • Q : 2次のベジェ曲線を引く
  • T : 2次のベジェ曲線を引くが、制御点の座標は直前に引いたベジェ曲線のものに応じる。
  • A : 円弧を描く

線分を描くコマンドは単純明快ですし、2種類のベジェ曲線も制御点が線に与える影響のことを知っていれば描くことが可能です。注意するのは目的地の座標よりも制御点の座標を先に書くということくらいです。
一方で、円弧はそうは行きません。

円弧を描く

円弧を描くときにどのようなパラメータが必要なのか挙げてみましょう。

  • rx : x方向の半径
  • ry : y方向の半径
  • x-axis-rotation : x軸からの回転角。0だと回転しない。
  • large-arc-flag : 長い方の円弧を描くなら1。短い方なら0。
  • sweep-flag : 時計回りに円弧を描くなら1。反時計回りにするなら0。
  • x : 目的地のx座標
  • y : 目的地のy座標

恐らく、「なにそれ」という感想を抱いた方も多いことでしょう。
large-arc-flag と sweep-flag の詳細については、言葉でいろいろ見るよりも図示されたものを見る方がずっと効率が良さそうなので、図を貼っておきます。


Example arcs02 - arc options in paths Pictures showing the result of setting large-arc-flag and sweep-flag to the four possible combinations of 0 and 1. Arc start Arc end large-arc-flag=0 sweep-flag=0 large-arc-flag=0 sweep-flag=1 large-arc-flag=1 sweep-flag=0 large-arc-flag=1 sweep-flag=1
origin : Paths – SVG 1.1 (Second Edition)
Copyright © 2011 W3C® (MIT, ERCIM, Keio), All Rights Reserved.


一方、普段私たちが円弧を描くときは、次のようなものを指定することを考えると思います。

  • 中心の座標
  • x, y それぞれの方向の半径
  • 開始角度と終了角度

確かにこちらの組み合わせの方が円弧を描く上で便利そうに見えますが、前述の通り、path要素はペンの概念のもとで描画させる仕組みになっていますので、開始座標は初めから指定されているのと、終点座標はズバリその値を明示的に指定した方が後続の線を引く上で有利なのです。だから、 rx, ry, x-axis-rotation, などの指定をする方が良いのです。

一通り背景を理解したところで、path 要素を使って円弧を描いてみましょう!
えっ、描けない?
描けない人の大多数は、パラメータの順番を暗記できていません。再度掲載しますので覚えてください!

  • rx : x方向の半径
  • ry : y方向の半径
  • x-axis-rotation : x軸からの回転角。0だと回転しない。
  • large-arc-flag : 長い方の円弧を描くなら1。短い方なら0。
  • sweep-flag : 時計回りに円弧を描くなら1。反時計回りにするなら0。
  • x : 目的地のx座標
  • y : 目的地のy座標


タイトルとして「マスター・オブ・円弧」を掲げましたが、マスターするには結局は単純な記憶が必要でした。
でも、こういう指定をすることができれば、円弧が通る道筋は一意に定まる、というイメージを抱くことができたのではないでしょうか?
あとは練習あるのみです。皆さんもテキストエディタで円弧のSVG図形を描き、人生を豊かにしていって下さい。

それではSVGファンの皆様、よいお年を!!