Hatena::ブログ(Diary)

聴く耳を持たない(片方しか)

2013-12-03 Advent Calendar 3日目:SVG画像を1バイトでも削るためのコードゴルフ

このエントリーはGraphical Web Advent Calendar 2013 への参加記事です。

今日は12月3日、ということで Advent Calendar の3日目は私id:rikuoid:rikuoが担当します。今回はSVG画像ファイルを1バイトでも削るためのコードゴルフのTipsを紹介していきます。


コードゴルフって?

まずコードゴルフとはなにか?の説明から。

ゴルフをしているイラスト

コードゴルフとは任意のソースコードを出来るだけ短く書くことを目的にしたものです。そのコードの動作を損なわなず、かつ可能な限り短縮して記述する手法を試行錯誤していく様子が、ゴルフのように少ない打法で競うところに似ていることからこのように呼ばれています。

またそれを競技としたコンテストも催されていますね。


画像のファイルサイズ低減策

JPEG、GIF、PNGといったビットマップ画像であればファイルサイズを低減する方法やソフトウェア・ツールは既にいくつかあり、そうした手法についての情報というのは世の中に共有されているのですが、

ベクター画像ファイルであるSVGはいかんせん不遇の時代が長かったためか、いまいちそうした情報がまだまだ知られていないようなんですよね。例えば Google や Apple のような巨大企業であってもそうで、ビットマップ画像ならばファイルサイズ低減策を行っているのに、SVGだとこの2社でも無駄にファイルサイズが大きいままだったりします。


コードゴルフを実際やってみるよ

じゃあどれくらい削減できるのか、まずは具体的にやってみると分かりやすいでしょうから、Google と Apple で使われているSVG画像ファイルをコードゴルフ的な観点から削るとどうなるか?を実際にやってみましょう。

GoogleのSVGを1/3以下にしてやりましたよ(ドヤ顔で)

Googleであれば、

天気で検索した際の

風向きの矢印にSVGを利用していますがこのファイルには無駄が多いです(ドヤ顔で)

https://ssl.gstatic.com/m/images/weather/wind_unselected.svg

元のファイルサイズ:481バイト

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="42px" height="42px" viewBox="0 0 42 42" enable-background="new 0 0 42 42" xml:space="preserve">
<polygon fill="#AEBFCF" points="27,37.5 42,20 27,4.5 18,4.5 30,16.5 0,16.5 0,23.5 30,23.5 18,37.5 "/>
</svg>

これを無駄な記述を減らして最適化を施すと

コードゴルフ後:148バイト

<svg xmlns="http://www.w3.org/2000/svg" width="42" height="42">
<path d="m27 37.5 15-17.5-15-15.5h-9l12 12h-30v7h30l-12 14z" fill="#aebfcf"/>
</svg>

と1/3以下にファイルサイズを低減できました。



AppleのSVGは約半分になったわー(ドヤ顔で)

Appleだとこのフッター部分のロゴに使われているのがSVGなんですが

http://images.apple.com/global/elements/breadory/breadcrumb_home.svg

元のファイルサイズ:1679バイト

<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
	 width="30px" height="93px" viewBox="0 0 30 93" enable-background="new 0 0 30 93" xml:space="preserve">
<g id="Layer_1_1_">
</g>
<g id="Layer_2">
	<g>
		<path fill="#777777" d="M11.971,43.694c-1.068,0.806-1.6,1.774-1.597,2.905c0.004,1.352,0.668,2.387,1.993,3.107
			c-0.353,1.091-0.864,2.042-1.536,2.851c-0.671,0.812-1.285,1.217-1.841,1.219c-0.263,0-0.62-0.09-1.073-0.272l-0.218-0.087
			C7.254,53.23,6.86,53.143,6.52,53.143c-0.323,0.002-0.676,0.074-1.059,0.219l-0.273,0.104l-0.344,0.149
			c-0.27,0.116-0.544,0.174-0.821,0.175c-0.65,0.002-1.369-0.562-2.158-1.696c-1.137-1.622-1.707-3.393-1.712-5.31
			c-0.004-1.363,0.346-2.461,1.05-3.296c0.704-0.834,1.638-1.253,2.802-1.257c0.436-0.001,0.842,0.083,1.222,0.25l0.26,0.111
			l0.273,0.118c0.243,0.108,0.44,0.162,0.589,0.162c0.192,0,0.405-0.048,0.638-0.144l0.358-0.149l0.266-0.104
			c0.425-0.165,0.895-0.248,1.409-0.25C10.242,42.22,11.225,42.71,11.971,43.694z M9.067,38.782
			c0.014,0.173,0.022,0.307,0.023,0.401c0.002,0.857-0.292,1.61-0.879,2.259s-1.271,0.973-2.052,0.975
			c-0.023-0.193-0.036-0.332-0.036-0.416c-0.002-0.729,0.27-1.414,0.816-2.054c0.544-0.641,1.177-1.016,1.897-1.127
			C8.888,38.81,8.965,38.797,9.067,38.782z"/>
	</g>
	<polygon fill="#DDDDDD" points="5.656,93 4.516,93 28.875,46.418 5.297,0 6.406,0 30,46.425 	"/>
</g>
</svg>

この画像はAdobe Illustratorから出力したまま使っているので特に無駄な要素が多いんですよね。これもコードゴルファーな観点から見直すと

コードゴルフ後:875バイト

<svg viewBox="0 0 3e4 93e3" width="30" xmlns="http://www.w3.org/2000/svg" height="93">
<path d="m11971 43694c-1068 806-1600 1774-1597 2905 4 1352 668 2387 1993 3107-353 1091-864 2042-1536 2851-671 812-1285 1217-1841 1219-263 0-620-90-1073-272l-218-87c-445-187-839-274-1179-274-323 2-676 74-1059 219l-273 104-344 149c-270 116-544 174-821 175-650 2-1369-562-2158-1696-1137-1622-1707-3393-1712-5310-4-1363 346-2461 1050-3296 704-834 1638-1253 2802-1257 436-1 842 83 1222 250l260 111 273 118c243 108 440 162 589 162 192 0 405-48 638-144l358-149 266-104c425-165 895-248 1409-250 1222-5 2205 485 2951 1469zm-2904-4912c14 173 22 307 23 401 2 857-292 1610-879 2259s-1271 973-2052 975c-23-193-36-332-36-416-2-729 270-1414 816-2054 544-641 1177-1016 1897-1127 52-10 129-23 231-38z" fill="#777"/>
<path d="m5656 93e3h-1140l24359-46582-23578-46418h1109l23594 46425z" fill="#ddd"/>
</svg>

元のファイルサイズから比較すると52%まで削減できました。


このような感じでファイルサイズを削減する手法の紹介と、そのテクニックの原理や仕様ではどう定義されているかなど解説をしてゆきます。


Adobe Illustratorの設定を見直す

実はそうしたコンセプトで、昨年のAdvent Calendarでも記事を書いていました。

SVG画像を1キロバイトでも削るダイエット術!

特にSVG画像制作でよく使われるAdobe Illustratorの設定を見直すことで、無駄に大きくなるファイルサイズを低減する、という趣旨でした。

今回はSVGの仕様を参考にしつつ、それよりもさらに無駄な部分を1バイトでも見逃さずに鬼のように細かく削っていく技の紹介という上級者向けの内容になっています。

なので、初心者には難しい記事ですから、その点はご了承ください。

そもそもSVGってなに?という人向けの記事も以前 書いたのでそちらもよければ是非。

SVGってなに?の説明と、SVGの学習に役立つサイト紹介


……というわけで長くなりましたがここまでが前置きで、ここからが本題。


簡易目次

かなり分量が多くなってしまったので、簡易目次を作りました。

ま、目次だけでも多いんですが……。



Adobe Illustratorの出力設定で気をつけるべき点

昨年の記事にも書いたのですが、Adobe IllustratorからSVGを書き出す際にそのままだと無駄にファイルサイズが大きくなってしまうので、設定を変更するといいでしょう。

重要なポイントをピックアップすると

  • 小数点以下の桁数は「1」
  • CSSプロパティは原則「スタイル要素」*1

とこの二つが挙げられます。なぜそうなのかは、上述の記事を参考に。


作画時に気をつける点

続いてはAdobe Illustratorでの作画作業時に気をつけておくと、SVG画像ファイルにしたときにファイルサイズ量を減らせるポイントの紹介です。

シンボル・パターンを使う

Adobe Illustratorにはシンボル機能と

パターン機能がありますが、

SVGにも、それに相当するsymbol要素(とuse要素)、pattern要素があります。Adobe Illustratorのシンボル・パターン機能と完全互換とはいきませんが、上手く活用できれば無駄な要素をカットしてファイルサイズを減らせるでしょう。


不要なレイヤーを統合する / 不要なグループ化は解除する

レイヤー、グループ化はSVGでもg要素として保持されます。

このg要素は、HTMLでいうところのdiv要素のような感じで直接描画はされないものの要素をまとめるのに使われています。g要素でまとめておくとSVGをJavaScriptで操作するといったときに、複数の図形をまとめてコントロールできてとても便利なのですが、そういった活用をしないのであれば不要なレイヤーの統合、グループ化の解除を行うと良いでしょう。


アピアランスを減らす

GIF画像やPNG画像では減色を行うとファイルサイズが小さくなりますが、CSSプロパティの設定でスタイル要素を指定したSVGでも同様で、Adobe Illustratorでのアピアランスをまとめるとファイルサイズが小さくなります。

少し極端な例ですが、このような感じ

アピアランス 30種アピアランス 6種
2830バイト2262バイト

この図には30個の円がありますが、左は30個それぞれが別の色で塗られており、30種類のアピアランスが設定されています。

右は5個ずつ同じ色で、アピアランスの種類は6種です。そのため後者の方がファイルサイズが小さくなります。

デザインや図によっては変更できないことも多いでしょうが、可能であれば共通のアピアランスをまとめておくといいでしょう。


無駄なアンカーポイントを減らす

これは基本ですね。

不要なアンカーポイントを減らせばその分、座標を指定する情報も少なくて済みますからファイルサイズ削減に役立ちます。


コードゴルフ的なベジェ曲線の引き方

Adobe Illustratorやベクター系画像の特徴であるベジェ曲線。

ベジェ曲線の仕組み自体についてはこちらの記事がとても分かりやすいです。

中学生でもわかるベジェ曲線 - Rui’s Blog

このベジェ曲線もコードゴルフ的な観点から見ると、ファイルサイズを抑えた曲線の描き方というのがあるので、それを解説していきましょう。

まずベジェ曲線の話の前に、こうした直線があったとします。


この際、SVGに変換したときにこの図形を示すデータとしては3個のアンカーポイントの座標が記述されます。

(「アンカーポイント」だと長いので、図では「点A」としました)

この図形は点Aから点B、続いて点Bから点Cを結ぶことで作画できます。


ここまでは分かりやすいですね。

続いてこうしたベジェ曲線だとどうでしょうか。


この図形はこのようになります。

Adobe Illustratorの用語で言えばアンカーポイントから出てくる方向ハンドルの先の点

方向点(SVGでは制御点と呼ばれています)がこの図形では2個ありますね。この場合は点Aから点Bにかけて制御点M1で示されるベジェ曲線が引かれ、さらにもう一つ点Bから点Cにかけて制御点M2で示されるベジェ曲線が描かれているので、2つのベジェ曲線が使われています。

そのためこの図形を示すには点A〜Cと制御点M1・M2の5個の座標が必要になります。


それを踏まえての話ですが、

Adobe Illustratorで「アンカーポイントの切り替えツール」を使ってこのような操作*2を行った場合の図形を見てみましょう。


この図形をSVGに変換した際は


点A〜Cと制御点M1の4個の座標のみで書けます

先ほどの図形よりも、座標が一つ少なくて済むんですよね。

これはなぜか?というと、

制御点M1と制御点M2は点Bを中心として点対称の位置関係にあるため、制御点M2の座標を明記せずとも自明のこととして記述を省略できるshorthand curvetoコマンドというものがSVGでは定義されているためです。


このようにSVGの仕様とベジェ曲線の仕組みを知ることで、コードゴルフ的な観点でベストな図形を描くやり方は他にもいくつかあるのですが……説明がすごく長くなってしまうので、この項はこれくらいで。


不要な方向点は減らす

他のオブジェクトの下に隠れてしまう部分などで可能であれば、ベジェ曲線でなく直線で結んだ方が不要な方向点の座標が削減できます。


さらに水平線・垂直線にする

さらに可能であれば、単なる直線でなく水平線・垂直線にすると良いです。

これはSVGでは水平線・垂直線なら普通の直線よりも省略して記述が行えるためです。


組み合わせる

☆のような星型を素直に書いてしまうと10個の頂点座標が必要になりますが、2つの図形に分けてしまえばそれぞれ4個と3個、あわせて7個の頂点の座標だけで済みます。

こうした組み合わせでファイルサイズを抑えて描ける図形は他にもあり、こちらのエントリーでまとまっています。

ソーシャルゲームデザインで使える!パスを減らして軽量化するアイデア | KAYAC DESIGNER’S BLOG - デザインやマークアップの話


複合パスを生成する

これも昨年の記事に書いたのですが、状況は選ぶものの複合パスにすると要素が一つにまとまりファイルサイズを小さくできます。


Adobe Illustratorでの作業上で行えるファイルサイズ削減策はこれくらいですね。

続いてはSVG画像ファイルの最適化ツールの紹介です。


SVGファイル最適化ツール

いずれも自動的にSVG画像ファイルサイズを削減して最適化してくれるツールです。

Adobe Illustratorから出力後にこれらのツールを使うとかなりファイルサイズはすっきりするでしょう。普段の利用であれば十分なのですが、コードゴルフ的にはその後もまだまだ削れる部分があるので、さらに手を加えていきます。



以下、W3Cの仕様を参照する部分が増えてくるので、そちらも紹介。

SVG 1.1 仕様 (第2版) 日本語訳


スタイルシートを短く

上述の通り、SVGでもスタイルシートは使われています。SVG独自のプロパティも多数あるものの、基本的には(X)HTMLのCSSと変わらないので、それらで活用されているコードゴルフ的な技術はそのまま使えます。


末尾のセミコロンの省略
.hoge{fill-rule:nonzero;fill:none;}

宣言ブロック(declaration block)内の最も後ろにあるセミコロンは省略できます。

.hoge{fill-rule:nonzero;fill:none}

……とは言え見辛いので、以下の例示では省略せずにそのまま載せておきます。


ゼロの省略
.piyo{opacity:0.45;}

0よりも大きく1未満の小数の場合は、このように省略して書けます。

.piyo{opacity:.45;}


色の指定を省略
.foo{fill:#777777;}
.bar{fill:#FAF0E6;}
.baz{fill:#FF0000;}

これもCSSの基本ですね。色の指定を省略して書けます。

.foo{fill:#777;}
.bar{fill:linen;}
.baz{fill:red;}

特に #FF0000 は色キーワード名がredと3字なんですが、7文字未満の色は他にもありSVGで使える色キーワードはこちらで定義されています。

参考:基本データ型と基本インタフェース - 識別され得る色キーワード名 - SVG 1.1 (第2版)


class名の短縮化
<path class="foobar" d="m40,30l50,30"/>

class名の命名に問題がなければ、コードゴルフの面ではできるだけ少ない文字数の方が良いでしょう*3

<path class="a" d="m40,30l50,30"/>


class から id指定に
<g class="hoge">
<path d="m40,30l50,30"/>
</g>

これはケースバイケースで状況によるのですが、単純に「class」は5文字に対し「id」は2文字と、3文字の差があるので使えるのであれば置き換えるのもありでしょう。

<g id="hoge">
<path d="m40,30l50,30"/>
</g>


同一のスタイルをまとめる

g要素は基本的には削除していくのがファイルサイズ低減策につながるのですが、状況によってはg要素でまとめてスタイルを指定することで無駄な記述を減らすことができる状況もあります。


単位の省略

[CSS2] で定義されるプロパティの値では、(非ゼロ値に対しては)長さ単位識別子が指定されなければならない。 SVG 固有の プロパティ における長さ値では、長さ単位識別子を省略できる。 省略された場合の長さ値は、現在の利用座標系における距離を表すものとみなされる。

http://www.hcn.zaq.ne.jp/___/SVG11-2nd/types.html#BasicDataTypes

SVG固有のプロパティでは単位を省略できることが多いです。

ただプロパティ毎に説明や解説は面倒なので割愛。


外部スタイルシート化

SVGはXMLを基盤にしているわけですが、XMLも(X)HTMLと同様にスタイルシートを外部ファイルから参照できます。とは言えSVGは図やグラフ、イラストが主な用途でしょうからなかなかスタイルシートを共有できるケースは少ないと思いますが、状況によっては活用できる手段です。

またXSLTを使えばさらに大胆な運用も可能なんですが、奥が深すぎるのでこれも割愛。




スタイルシートに関するものはこれくらいでしょうか、続いて文書構造をコードゴルフ的な観点から見直していきます。


SVGの文書構造から見直す

SVGはXMLを基盤に構成されています。

ですから記述がHTMLとは異なる部分も多く、そのためかAdobe Illustratorなどのオーサリングツールから出力されたそのままで皆さん手をつけていないケースが多いのですが、仕様を理解していけば削れる部分は結構あります。

SVGの構造は大まかにこのように3つに分けられます

  • XML宣言
  • 文書型宣言
  • ルート要素(SVG文書片)
<?xml version="1.0" encoding="UTF-8" standalone="no"?>		<!-- XML宣言 -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
	 "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">	<!-- 文書型宣言 -->
<svg version="1.1" 
	xmlns="http://www.w3.org/2000/svg" 
	xmlns:xlink="http://www.w3.org/1999/xlink"
	width="400px" height="300px" viewBox="0 0 400 300">	<!-- ルート要素 -->
</svg>

このそれぞれについて、見直していきます。


XML宣言は省略できる(条件を満たせば)

XML宣言は

  • 文字コードがUTF-8かUTF-16である
  • XMLのバージョンが1.0である
  • スタンドアロン宣言がnoである

の3つの条件を満たしていれば、省略できます。

それぞれの項目について詳しい説明は長くなるので省きますが、スタンドアロン宣言は記述が無い場合 noとなりますし条件に合致するのであれば削ってしまいましょう。


文書型宣言はSVG 1.1 (Second Edition)では非推奨

文書型宣言はその文書がどのような定義で記述されているかを示します。

ただし、

概要 - SVG の名前空間, 公開識別子, システム識別子 | SVG 1.1 (第2版)

この仕様では DTD が与えられているが、 XML 文書の妥当性検証には問題がある事も知られている。 特に、 DTD では名前空間をスマートに扱えない。 SVG 文書に DOCTYPE 宣言を含める事は、推奨されない。

http://www.hcn.zaq.ne.jp/___/SVG11-2nd/intro.html#NamespaceAndDTDIdentifiers

SVG 1.1 (Second Edition)ではこのように、文書型宣言は非推奨となっていますからこの記述も省きましょう。


ルート要素

SVGではルート要素はSVG要素(SVG文書片)になります。

続いてSVG文書片の各属性を吟味していきます。

SVG名前空間宣言
xmlns="http://www.w3.org/2000/svg" 

これは必須。


XLink名前空間宣言
xmlns:xlink="http://www.w3.org/1999/xlink"

これはXML文書間でリンクを行うための仕様です。

(X)HTMLでのハイパーリンクのようなもの、と捉えると分かりやすいでしょうか。SVGでは単純にリンクだけでなくパターンやシンボル生成、フィルタなどにも使われています。ただ文書内にそうした要素が無いのであればコードゴルフでは省略しても良いでしょう。

ただし後からJavaScriptなどで操作する状況もありますから通常の利用ではきちんと記述した方が良いと思います。


version属性

文字通りSVGのバージョンを示すものです。

正直これは省略するかは悩むところです。現在 SVG2が策定中 なこと、また将来的にはさらにバージョンが増える可能性も否定できないため、普通に使うのであれば念の為 一応バージョンは記述しておいた方が良いかなとは個人的には考えています。

ただ省略しても現時点ではそう問題もないので、コードゴルフなら削るのもありかなと。


width / height属性

これは単位がpxであればそれを省略して数値指定だけでも大丈夫です。

最も外側の svg 要素 の width もしくは height 属性が 利用単位 で指定されている場合(すなわち、単位識別子が与えられていない)、その値の単位は "px" であるものと見なされる

http://www.hcn.zaq.ne.jp/___/SVG11-2nd/coords.html#ViewportSpace


viewBox属性

viewBox属性を省略した場合

<svg xmlns="http://www.w3.org/2000/svg" 
	width="400" height="300">
</svg>

width / heightの数値がそのまま適用され

viewBox="0 0 widthの値 heightの値"

と指定したと同じことになります。特にAdobe Illustratorから出力したままだとアートワークの原点は0 0で、かつ width / heightの値とviewBoxのwidth/heightの値は同じですから、それであれば省略してもコードゴルフでは問題ないでしょう。

ですが通常であればきちんと記述しておいた方がいいですし、viewBoxの値・利用空間の設定がwidth / heightの値とは異なる場合はもちろん記述は必須です。



なので文書構造の部分をまとめると

<?xml version="1.0" encoding="UTF-8" standalone="no"?>		<!-- XML宣言 -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
	 "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">	<!-- 文書型宣言 -->
<svg version="1.1" 
	xmlns="http://www.w3.org/2000/svg" 
	xmlns:xlink="http://www.w3.org/1999/xlink"
	width="400px" height="300px" viewBox="0 0 400 300">	<!-- ルート要素 -->
</svg>

このような記述を

<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300">
</svg>

ここまで簡潔にカットできました。



さらなる削減へ

まだまだ削減できる技はあるので、紹介していきます。

line要素 / polyline要素 / polygon要素は path要素に置き換える

これらは単純に置き換えができます。

<line x1="15.2" y1="41.8" x2="62.7" y2="153.3"/>
<polyline points="108,31 72.4,74.2 139.3,97.9 105.9,148.3 148.7,156.9"/>
<polygon points="193.6,22.8 154.8,70.3 191.8,108.4 173.9,162 240.1,103.7 186.1,61.6"/>

「path」「d」属性など、字数が少ないので分かりやすいかと。

<path d="M15.2,41.8L62.7,153.3"/>
<path d="M108,31L72.4,74.2L139.3,97.9L105.9,148.3L148.7,156.9"/>
<path d="M193.6,22.8L154.8,70.3L191.8,108.4L173.9,162L240.1,103.7L186.1,61.6Z"/>


絶対座標と相対座標

path要素では図形の座標を示す手段が2つ用意されており、それぞれ絶対座標・相対座標と呼ばれています。

同じ図形を描くのにも、絶対座標と相対座標どちらを使っても良いのですが、コードゴルフ的な観点からだとこの違いは大きいのでどういう仕組みなのか詳しく解説していきます。

こうした直線があるとします。

path要素で記述するとこうなります。

<path d="M165,88L215,141"/>

詳しく説明していくと d属性の最初のMはmovetoコマンドで、始点への移動。

これが点Aの座標です。

SVGは原則、左上が原点0ですから点Aは X軸が165でY軸が88の座標になるわけです。

そしてLは直線を書くlinetoコマンド、ここでは点Bを示しそこまでの直線を引きます。

点BはX軸が215でY軸は141

これが絶対座標での表記の例です、点A、点Bのいずれも左上の原点0からの差が座標となるわけです。


続いて相対座標での例。

<path d="m165 88l48 53"/>

このd属性でもmovetoコマンドとlinetoコマンドが使われていますが、mとl、と小文字ですよね。このように相対座標のときは小文字のコマンド、絶対座標の際は大文字で表記するのがルールになっています。

そして座標の数値を見てみましょう。

点Aは直前に参照する点が他にないため原点0からになっていますが、点Bは点Aを基準としてその差が座標になっています。

点Bは点Aからの距離がX軸で48、Y軸は53の座標です。

このように直前に参照する点からの相対的な座標で示すのが、相対座標での記述方法になります。


違いの説明はさておき、どちらがファイルサイズの削減につながるでしょうか?これは図形や状況によるので一概には断言できないものの、多くの場合で相対座標の方が小さくなります。なので、コードゴルフ的には見逃せないポイントです。

例えばこの図形であれば

<path d="M15.2,41.8L62.7,153.3"/>
<path d="M108,31L72.4,74.2L139.3,97.9L105.9,148.3L148.7,156.9"/>
<path d="M193.6,22.8L154.8,70.3L191.8,108.4L173.9,162L240.1,103.7L186.1,61.6Z"/>

相対座標に書き直すと

<path d="m15.2 41.8l47.5 111.5"/>
<path d="m108 31l-35.6 43.2l66.9 23.7l-33.4 50.4l42.8 8.6"/>
<path d="m193.6 22.8l-38.8 47.5l37 38.1l-17.9 53.6l66.2-58.3l-54-42.1z"/>

このようになりました。

とは言えこれを手で計算・変換するのは大変です。相対座標への変換は上記で紹介した最適化ツール「svgo (GUI版)」や「scour」であれば自動的に行ってくれるので、そちらに任せてしまうのが楽ですね。


path要素での連続したコマンドの省略

上の項目通り、path要素は曲線だけでなく直線も書けます。

またこのとき連続するコマンドは省略もできます。例えば直線を書く linetoコマンドを素直に書くとこのようになりますが

<path d="m193.6 22.8l-38.8 47.5l37 38.1l-17.9 53.6l66.2-58.3l-54-42.1z"/>


2個目以降のlコマンドは連続していますから

<path d="m193.6 22.8l-38.8 47.5 37 38.1 -17.9 53.6 66.2-58.3-54-42.1z"/>

このように省略が可能です。

これはlinetoだけでなく、ベジェ曲線を書くコマンドでも同様に省略ができます。

パス ? SVG 1.1 (第2版)

同じ命令が続いている場合、後続の命令では命令文字を省いてよい

http://www.hcn.zaq.ne.jp/___/SVG11-2nd/paths.html


movetoコマンド直後のlinetoコマンドは省略できる

まず説明の前に具体的にやってみると

<path d="m193.6 22.8l-38.8 47.5 37 38.1 -17.9 53.6 66.2-58.3-54-42.1z"/>


先ほどの図形の最初の直線を描くlinetoコマンドを

<path d="m193.6 22.8-38.8 47.5 37 38.1 -17.9 53.6 66.2-58.3-54-42.1z"/>

このように省くことができます。

これはやはり仕様から

moveto に続けて複数の座標成分ペアが与えられた場合、2番目以降のペアは暗黙の lineto 命令として扱われる。

http://www.hcn.zaq.ne.jp/___/SVG11-2nd/paths.html#PathDataMovetoCommands

と定義されているためです。


小数点を取る

冒頭で紹介したAppleのSVG画像ファイルですが、あれをこれまで説明してきたテクニックで整理するとこのようになりますね。

<svg viewBox="0 0 30 93" width="30" xmlns="http://www.w3.org/2000/svg" height="93">
<path d="m11.971 43.694c-1.068 0.806-1.6 1.774-1.597 2.905 0.004 1.352 0.668 2.387 1.993 3.107-0.353 1.091-0.864 2.042-1.536 2.851-0.671 0.812-1.285 1.217-1.841 1.219-0.263 0-0.62-0.09-1.073-0.272l-0.218-0.087c-0.445-0.187-0.839-0.274-1.179-0.274-0.323 0.002-0.676 0.074-1.059 0.219l-0.273 0.104-0.344 0.149c-0.27 0.116-0.544 0.174-0.821 0.175-0.65 0.002-1.369-0.562-2.158-1.696-1.137-1.622-1.707-3.393-1.712-5.31-0.004-1.363 0.346-2.461 1.05-3.296 0.704-0.834 1.638-1.253 2.802-1.257 0.436-0.001 0.842 0.083 1.222 0.25l0.26 0.111 0.273 0.118c0.243 0.108 0.44 0.162 0.589 0.162 0.192 0 0.405-0.048 0.638-0.144l0.358-0.149 0.266-0.104c0.425-0.165 0.895-0.248 1.409-0.25 1.222-0.005 2.205 0.485 2.951 1.469zm-2.904-4.912c0.014 0.173 0.022 0.307 0.023 0.401 0.002 0.857-0.292 1.61-0.879 2.259s-1.271 0.973-2.052 0.975c-0.023-0.193-0.036-0.332-0.036-0.416-0.002-0.729 0.27-1.414 0.816-2.054 0.544-0.641 1.177-1.016 1.897-1.127 0.052-0.01 0.129-0.023 0.231-0.038z" fill="#777"/>
<path d="m5.656 93h-1.14l24.359-46.582-23.578-46.418h1.109l23.594 46.425z" fill="#ddd"/>
</svg>

この時点では1153バイト

このファイルでは頂点座標の精度が細かく、小数点以下3桁まで記述されています。

しかし座標毎に小数点が入っているのは無駄なので、viewBoxの数値を1000倍にすれば精度を保ったまま小数点の分削減が可能です。

<svg viewBox="0 0 30000 93000" width="30" xmlns="http://www.w3.org/2000/svg" height="93">
<path d="m11971 43694c-1068 806-1600 1774-1597 2905 4 1352 668 2387 1993 3107-353 1091-864 2042-1536 2851-671 812-1285 1217-1841 1219-263 0-620-90-1073-272l-218-87c-445-187-839-274-1179-274-323 2-676 74-1059 219l-273 104-344 149c-270 116-544 174-821 175-650 2-1369-562-2158-1696-1137-1622-1707-3393-1712-5310-4-1363 346-2461 1050-3296 704-834 1638-1253 2802-1257 436-1 842 83 1222 250l260 111 273 118c243 108 440 162 589 162 192 0 405-48 638-144l358-149 266-104c425-165 895-248 1409-250 1222-5 2205 485 2951 1469zm-2904-4912c14 173 22 307 23 401 2 857-292 1610-879 2259s-1271 973-2052 975c-23-193-36-332-36-416-2-729 270-1414 816-2054 544-641 1177-1016 1897-1127 52-10 129-23 231-38z" fill="#777"/>
<path d="m5656 93000h-1140l24359-46582-23578-46418h1109l23594 46425z" fill="#ddd"/>
</svg>

これで879バイト

viewBox属性の仕組みと見た目の幅や高さとの関係はSVGの仕様の中でもちょっとややこしいので詳しい説明は仕様を紹介するだけにとどめますが

参考:座標系, 変換, 単位 - viewBox属性 | SVG 1.1 (第2版)

理解していくとこうした応用技につながるので、面白いですよ。座標だけでなくストロークなどの幅も変更する必要があるのでちょっと難しいですが。

(※尚この手法は @DEFGHI1977さんによるアイデアを参考にさせていただきました。)


さてこの時点では879バイトで、まだ無駄があるのでさらに削っていきましょう。


大きな数値は指数表記で

SVG固有のプロパティでは指数表記が使えます。

どういう事かというと、例えば30000は

3 ¥times 10^{4} = 30000

3かける10の4乗とも言えますね。これを指数表記で書くと

3e+4

となります。

参考:指数表記 - Wikipedia

さらにSVGの仕様上、+は省略ができるので「30000」は「3e4」と3文字で表現できます。

参考:基本データ型と基本インタフェース ? SVG 1.1 (第2版)


というわけで、このファイルでは30000と93000を、3e4や93e3と指数表記に置き換えることで、4バイト削減して875バイトにできました。

<svg viewBox="0 0 3e4 93e3" width="30" xmlns="http://www.w3.org/2000/svg" height="93">
<path d="m11971 43694c-1068 806-1600 1774-1597 2905 4 1352 668 2387 1993 3107-353 1091-864 2042-1536 2851-671 812-1285 1217-1841 1219-263 0-620-90-1073-272l-218-87c-445-187-839-274-1179-274-323 2-676 74-1059 219l-273 104-344 149c-270 116-544 174-821 175-650 2-1369-562-2158-1696-1137-1622-1707-3393-1712-5310-4-1363 346-2461 1050-3296 704-834 1638-1253 2802-1257 436-1 842 83 1222 250l260 111 273 118c243 108 440 162 589 162 192 0 405-48 638-144l358-149 266-104c425-165 895-248 1409-250 1222-5 2205 485 2951 1469zm-2904-4912c14 173 22 307 23 401 2 857-292 1610-879 2259s-1271 973-2052 975c-23-193-36-332-36-416-2-729 270-1414 816-2054 544-641 1177-1016 1897-1127 52-10 129-23 231-38z" fill="#777"/>
<path d="m5656 93e3h-1140l24359-46582-23578-46418h1109l23594 46425z" fill="#ddd"/>
</svg>



さて、コードゴルフのテクニックの紹介は以上です。

今回この機会に知っている技術を書き出してしまおう、と考えて思いつくままにペンを走らせてみたらとても長文になってしまいました。最後まで読んでくれる人はいるのかしら?

いらっしゃったらありがとうございます。参考になれば何よりです。


冒頭でも書いた通り、Google や Apple であってもSVG画像ファイルはあまり最適化されておらず、この分野の山に登って技術を磨いても誰も後ろについて来ないんじゃ……と感じることもあります。でもブラウザ環境も整ってきてそろそろSVGの不遇の時代も終わる……かな?


またコードゴルフはあくまで知的なお遊びのようなもので、実際の運用ではトリッキーな記述で第三者がなかなか再編集しづらかったり、ブラウザは対応してもオーサリングツールでは読み込めなくなったり、コードが読み辛くなったりとデメリットもあります。

まぁ実制作の際はコードゴルフをやりすぎず、あくまでお遊びはお遊びと割り切って楽しむのがいいでしょう。


またせっかくのSVGなので無駄な部分を削った分、title要素、desc要素またmetadata要素などを活用してアクセシビリティに配慮した書き方もしてほしいですね。



*1:ただし画像の内容によってはプレゼンテーション属性の方がファイルサイズが抑えられるケースもあります。例にしたGoogle/AppleのSVG画像ファイルのように要素が少ない場合もそうですね。

*2:アニメーションGif画像でカーソル付近に丸がでているのは、マウス操作を分かりやすくするためのものです

*3:ただし、最終的にSVGZにする場合にはgzip圧縮を鑑みると命名方法は1字よりも、a, aa, aaa, と連続したものの方が効果が高くなります。参考:最小ファイルサイズSVGのOperaロゴに挑戦 - もし高校野球の女子マネージャーがOpera Browserを使ったら - チーム俺等