にせねこメモ

はてなダイアリーがUTF-8じゃないので移ってきました。

SVGでフレームアニメーション?

  1. SVGにはラスタ画像を埋め込める
  2. SVGではアニメーションが可能

なら、GIFアニメみたいなアニメーションも可能では?と思ってやってみた。

作成したもの

https://nixeneko.sakura.ne.jp/hatenablog/20170330-svganim/animate.svg
アニメーションは使いまわし: 【けものフレンズ】「ネコ科のフレンズ」イラスト/にせねこ [pixiv]


はてなブログsvgタグをコピペしようとしたら、文字数が多すぎると怒られた(data URI schemeを利用して200万文字を超えていた)。

ソース

<svg xmlns="http://www.w3.org/2000/svg" 
     xmlns:xlink="http://www.w3.org/1999/xlink"
     width="768" height="432" viewBox="0 0 764 432">
  <image width="768" height="432" xlink:href="img/00015.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00014.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00013.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;0;0;0;0;0;0;0;0;1;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00012.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;0;0;0;0;0;0;0;1;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00011.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;0;0;0;0;0;0;1;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00010.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;0;0;0;0;0;1;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00009.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;0;0;0;0;1;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00008.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;0;0;0;1;0;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00007.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00006.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00005.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;0;1;0;0;0;0;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00004.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;0;1;0;0;0;0;0;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00003.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;0;1;0;0;0;0;0;0;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00002.jpg">
    <animate 
      attributeName="opacity"
      values="0;0;1;0;0;0;0;0;0;0;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00001.jpg">
    <animate 
      attributeName="opacity"
      values="0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
  <image width="768" height="432" xlink:href="img/00000.jpg">
    <animate 
      attributeName="opacity"
      values="1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>
</svg>

もちろん<image>要素のxlink:href属性にdata URI schemeを突っ込めば画像ファイルを埋め込むことができる。

なお、HTMLから<img>タグなどで画像としてSVGを読み込む場合には、セキュリティ上の理由から外部参照が無視されるため、xlink:hrefにはdata URI schemeによって画像を埋め込まないといけない。(参考: SVG as an Image - SVG | MDN)

何をやってるのか?

<image>要素に1フレームの画像を読み込んでいる。これをアニメーションの全フレーム分用意する。
今回は8fpsで16フレームなので、全体の時間は2秒となる。
一つのフレームに注目して見ていく。

  <image width="768" height="432" xlink:href="img/00000.jpg">
    <animate
      attributeName="opacity"
      values="1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0" dur="2s" calcMode="discrete"
      begin="0s" repeatCount="indefinite" />
  </image>

これは16枚中の1番目に表示されるフレームに対応している。

  • まず、アニメーションさせる対象として不透明度をattributeName="opacity"として指定している。
  • アニメーションの時間は2秒なのでdur="2s"と指定している。
  • valuesは、durで指定された時間を等分し、attributeNameで指定された対象の値とする。今回は16フレーム中の1番目のフレームということで、16個の数値がセミコロンで区切られ、1番目のみ1、その他が0になっている("1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0")。1は表示、0が非表示に対応する。
  • さらに、calcMode="discrete"を指定することで透明度が中間値にならずvaluesで指定した1か0、つまり表示・非表示の切り替えになる。
  • begin="0s"ですぐに再生されるようにしておき、repeatCount="indefinite"で何度もループされるようにしておく。

フレームごとに異なるのは、valuesの値で、1がどの位置に来るかということである。2番目に表示されるフレームなら"0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0"、などなど。
後は同じ。

感想

  • できるだけシンプルにしようとしたらこうなった。
    • ほかにもっと良い方法があるかもしれない。
  • この実装では各画像を切り替えているだけであるため、<image>タグの順番は関係ない。
  • GIFでいいじゃん!

20170331追記

同様のことを考えた先人が居た。

XNGファイルはSVGファイルに、データURIスキームでBase64エンコードされたJPEG画像をならべ、それをSVGアニメーションで送っているだけということがわかりました。

ついにGIFに代わる新しいアニメーション画像フォーマットが誕生か - GIGAZINE

これも同様にSVG SMIL animationを利用しているが、アニメーションの実現の方法が異なる。
フレームの若い順に幅0の<image>要素を重ねておき、<set>で各<image>要素について、前フレームのアニメーション終了時に開始するようにbegin属性を設定し(e.g. begin="A000310.end")、33msでwidthを100%にしているという実装だった。

SMILにはInternet ExplolerやEdgeは対応していない(し、する予定もなさそう)ため、画像は表示されないこととなる。
最初の一フレームは常に表示される設定にしておいた方が、アニメーションに対応していないビューアで見ても何らかの画像が表示されるため、よいかもしれない。

SVGでアニメーションをつくる利点?

時間軸方向の圧縮をしないため(Motion JPEGみたいな…)圧縮効率が悪いが、

  • 複数要素を独立してアニメーションできる
  • エフェクト(フィルタ)がかけられる
  • クリッピングができる

など、SVGの機能をそのまま生かせるので、表現力は高いはず。簡単なアニメーション撮影もできそうである(ブラウザが対応してるか知らないが)。

20170331さらに追記

内の「画像の参照先を変える」にて、画像をつかってパラパラアニメーションを作る方法が紹介されている。

これでは、<def>内で画像を読み込み、<use>内で<animate>のvalues属性にidの参照をセミコロン;で区切って指定している。これが一番シンプルで分かりやすいかもしれない。