Posted at

Julia におけるアニメーションのしくみ

More than 1 year has passed since last update.


はじめに

アニメーションでやってみたいことがあるので、既存のコードでどのような実装がなされているのかを調査する。

具体的には、Plots.jl1 の animation.jl を読んでいく。

https://github.com/JuliaPlots/Plots.jl/blob/master/src/animation.jl


まずは概要から


Animation の使われ方


一般的な使われ方

anim = @animate for i=1:100

plot(...)
end

mp4(anim, "movie.mp4", fps=15)


マクロを展開すると?

マクロはちゃんと読めていないが、流れから解釈するとこんな感じかと思われる。

anim = Animation()

for i=1:100
plt = plot(...)
frame(anim, plt)
end

mp4(anim, "movie.mp4", fps=15)


処理の流れ


  1. 連番 PNG のパスを記憶するために anim を作成する

  2. フレームごとに plot から PNG を出力する

  3. ffmpeg を呼び出して、連番 PNG から mp4 に変換する


主な関数をもっと詳しく


Animation

連番 PNG を管理する


animation.jl

"Represents an animation object"

structAnimation
dir::String
frames::Vector{String}
end

function Animation()
tmpdir = convert(String, mktempdir())
Animation(tmpdir, String[])
end



  1. 一時ディレクトリを作成する

  2. 一時ディレクトリのパスとフレーム数を記憶する構造体を返す

frames::Vector{String} がフレーム数を記憶するわけなのだが、なぜこれが単なる整数ではなく、文字列の配列になっているかは疑問に思った。試してみると、(デフォルトで immutable となる)構造体だと内部変数を変更できないが、内部配列ならその要素を変更できるようなのである。immutable の制限を回避するためにこのような方法をとっているのだろうか。dir は変更されないほうがいいのは理解できるし、まあそういうものかととりあえずは納得した。


frame

PNG を1フレーム出力する


animation.jl

"""

frame(animation[, plot])
Add a plot (the current plot if not specified) to an existing animation
"""

function frame(anim::Animation, plt::P=current()) where P<:AbstractPlot
i = length(anim.frames) + 1
filename = @sprintf("%06d.png", i)
png(plt, joinpath(anim.dir, filename))
push!(anim.frames, filename)
end


  1. anim と plt を受け取る

  2. anim から現在のフレーム数を取得する

  3. 現在のフレーム数を6桁まで0埋めして PNG のファイル名にする(000001.png など)

  4. Plots.png で plt を PNG に出力する

  5. anim のフレーム数を加算する

ここで見ればわかるように、 Animation.frames は連番 PNG のファイル名の配列になっている。


mp4

連番 PNG から mp4 を作成する

ごちゃごちゃしているが、本質的にはこの2行に集約される。fn は動画のファイル名。


animation.jl

run(`ffmpeg -v 0 -framerate $fps -loop $loop -i $(animdir)/%06d.png -pix_fmt yuv420p -y $fn`)

AnimatedGif(fn)


ffmpeg に PNG 連番ファイルを渡して mp4 を作成している。

その後で返ってくる AnimatedGif は後述。


Base.show


animation.jl

"Wraps the location of an animated gif so that it can be displayed"

structAnimatedGif
filename::String
end

# write out html to view the gif... note the rand call which is a hack so the image doesn't get cached
function Base.show(io::IO, ::MIME"text/html", agif::AnimatedGif)
ext = file_extension(agif.filename)
write(io, if ext == "gif"
"<img src=\"$(relpath(agif.filename))?$(rand())>\" />"
elseif ext in ("mov", "mp4")
"<video controls><source src=\"$(relpath(agif.filename))?$(rand())>\" type=\"video/$ext\"></video>"
else
error("Cannot show animation with extension $ext: $agif")
end)
end


mp4 作成の後に返される AnimatedGif は、作成した動画のパスを保持している。

Jupyter などでオブジェクトが呼ばれると、その型に合った Base.show が呼ばれる。上のコードはその時の動作を記述している。要するに、 mp4 関数が呼ばれると作成した動画を Jupyter 上に表示するような HTML タグを出力する。

アドレスに乱数を付加しているのは、キャッシュにアクセスさせず、毎回直に動画ファイルを読み込みにいかせるためのテクニックのようだ。





  1. Copyright (c) 2015: Thomas Breloff.