【実験】GIFアニメーション(GIF89a)をSVGで実現してみた
実験意図
つい先日、TwitterがGIFアニメーション(以下GIF)に対応した。
ショック! Twitterがサポートを始めたGIFアニメはGIFではなかった―しかしうまい考え – Techcrunch
Google+もすでに対応済みで、あと主要なSNSとしてはFacebookだが、おそらく時間の問題だろう。
GIF対応の理由は分かる。それは動画はクリックしないと再生されないからだ。特にごく短い動画を再生させるためにいちいちユーザーに対してクリックを強要するのか?ということだろう。実際、おもしろいGIF動画があることも事実だ。
しかし、GIFは25年も前に作られた「インターネットの化石」とでも言うべき画像フォーマットだ。
Graphics Interchange Format - Wikipedia
しかも最大で256色しか使えないので基本的に画質が悪い。それでも…
「WEBの世界はこれからも永遠にGIFを使い続けるのか?本当にこれがWEBの未来なのか?」
と強い疑問を感じて色々と考えていたら、GIFと同じことをSVGで実現できるのではないか?と思いついた。
SVGにはGIFと比べて様々なメリットがある。
- 基本的にベクター形式の画像ファイルフォーマットだが、ビットマップ形式の画像を埋め込むとができる(ベクターも使える)
- フルカラーの画像を使える(256色に制限されない)
- スクリプトにより複雑なアニメーションをさせることができる
- 強力な画像フィルターを使うことができる
- GIFはバイナリーファイルだが、SVGは基本的にテキストファイルなので画像作成ツールがより作りやすい
などなど。そして、いわゆるモダンなブラウザは皆SVGに対応していて環境も揃ってきている。
しかし、本当にできるのか?実験してみることにした。
デモ(実験結果)
実験は成功。
GIFとSVGを両方表示して比較できるようにした。ページが重いのでモバイルでは注意が必要だ。
GIF vs SVG (別タブで表示します。約1.3MB。)
アニメーション速度的にも画質的にもオリジナルと見分けがつかないと思うが、どうだろうか?GIFとSVGのアニメーションが少しずれるのは、SVGのアニメーションの開始が少し遅れて始まるためだ。
全成果物はGitHubにアップした。
junkoro/gifvssvg
メイキング
アニメーションは上のWikiに張ってあった地球のアニメーションを使うことにした。
ImageMagickでコマ画像に分解
まずGIFをImageMagickを使ってPNGのコマ画像に分解した。総コマ数は44コマで、総ファイル容量は1,577,611バイト。元のGIFは316,029バイトなので5倍程度に増加したことになる。
# mkdir pre_optim
# convert -coalesce rotating_earth.gif pre_optim/rotating_earth%02d.png
# cp -r pre_optim post_optim
ImageOptimで各コマを最適化
次にImageOptimを使ってpost_optimにコピーした各コマを最適化した。この時点で936,276バイトに減少。
node.jsでコマ画像をbase64化>imageタグ化>xmlファイル書き出し
次にnodeを使って最適化した各コマをbase64にエンコードしつつ、imageタグとしてxmlファイルに書き出す。この時点で1,252,564バイトに増加(ノ∀`)アチャ
var fs = require('fs'); var path = "./post_optim/"; var fileNames = fs.readdirSync(path); fileNames = fileNames.filter(function(element, index, array) { return element.match(/\.png$/); }); var xml = ""; for (var i = 0; i < fileNames.length; i++) { var fileName = fileNames[i]; console.log("encoding:" + fileName); var file = fs.readFileSync(path + fileName); var base64 = new Buffer(file).toString('base64'); var imgTag = "<image width='200' height='200' style='display: none;' xlink:href='data:image/png;base64,"; imgTag += base64; imgTag += "' />\n"; xml += imgTag; } console.log("writing imgTags.xml"); fs.writeFileSync("imgTags.xml", xml);
# node encode2xml.js
SVGにimageタグとアニメーションスクリプトを埋め込み
こんな感じで、書き出したimageタグをSVGに埋め込みつつ、各コマを90msecごとに表示したり非表示にしたりするJavaScriptも埋め込む。
<?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" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" width="200" height="200"> <image width='200' height='200' style='display: none;' xlink:href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMg・・・' /> <image width='200' height='200' style='display: none;' xlink:href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMg・・・' /> ・・・・ <script type="text/javascript"> <![CDATA[ var imgs = document.getElementsByTagName("image"); var currImgIdx = 0; setInterval(function() { var prevImgIdx = -1; if (currImgIdx == 0) { //console.log("start"); } else if (currImgIdx > imgs.length - 1) { //console.log("end > loop"); prevImgIdx = currImgIdx - 1; currImgIdx = 0; } else if (currImgIdx != 0) { //console.log("normal"); prevImgIdx = currImgIdx - 1; } if (prevImgIdx != -1) { var imgPrev = imgs[prevImgIdx]; imgPrev.style.display = "none"; } var imgCurr = imgs[currImgIdx]; imgCurr.style.display = "block"; currImgIdx++; }, 90); ]]> </script> </svg>
SVGZに圧縮
完成したSVGをgzipでSVGZに圧縮する。
# gzip -9 --stdout svganim.svg > svganim.svgz
これで完成。最終的には943,22バイトになった。元のアニメーションGIFが316,029バイトなので、ファイル容量的には3倍近く増えてしまったことになる(ノ∀`)アチャー
結論:採用を検討する価値あり
実験は成功したものの、いくつか問題点が見つかった。
ファイル容量の増加
GIFに較べてファイル容量が増加する可能性があるが、背景が変更されない単純なスプライトアニメーションなどの場合は数十分の一になる可能性もある。さらに、WebPなどのより圧縮率の高い画像形式を使えば効率が高くなる可能性もある。
サーバーがSVGZのmime-typeに対応していない場合が多い
デモサイトはGoogleDriveを使ったが、残念ながらSVGZに対応していないためにやむなく非圧縮のSVGを使わざるを得なかった。これはサーバーの設定次第なので、独自サイトの場合は問題にはならないだろう。
変換に手間がかかる
もっと簡単に変換できるツールを開発する必要がある。
いくつか問題はあるが、SVGのメリットを考えれば十分に採用を検討する価値があるのではないだろうか。
コメント
コメントを投稿