Julia / Plots によるプロット画像化の過程を追う


知りたいこと

using Plots

plt = plot([sin, cos])
savefig(plt, "sincos.png")

上の例において、プロットを画像化する際の処理を詳しく調べたい。

plt の内部構造も気になるところだが、その点については機会を改めたい。

output.jl

savefig

savefig(plt, "sincos.png")

ファイル名の拡張子に応じて、対応する関数 (この場合は png) に処理を受け渡す。

png

png(plt, "sincos.png")

PNG の MIME を持たせて Base.show に処理を受け渡す。

Base.show

https://github.com/JuliaPlots/Plots.jl/blob/master/src/output.jl#L210
(様々な MIME に対してメタプログラミングで定義されている)

io = open("sincos.png", "w")
show(io, MIME("image/png"), plt)

本来は、名前の通りに表示を目的とした関数である。
https://docs.julialang.org/en/v1/base/io-network/#Base.show-Tuple{IO,Any,Any}

ファイルへの書き込みは IO にデータを流し込むという処理であり、表示と共通化できるので、 Base.show でまとめて請け負っているのだろう。

prepare_output (ビューポイントの設定?)の後、内部関数 _show に処理を受け渡す。

--
【補足】
例えば、

plt = plot([sin, cos])

などで直接プロットが表示される場合も、暗黙的に呼ばれる display(plt) からこの show に処理が移譲される。

backends/gr.jl

内部関数 _show について説明していく。
https://github.com/JuliaPlots/Plots.jl/blob/master/src/backends/gr.jl#L1968

バックエンドに応じた分岐

この _show で、バックエンドごとに処理が分岐する。バックエンドの情報を plt 変数の型パラメータで保持し、多重ディスパッチしている。

typeof(plt)
# Result: Plots.Plot{Plots.GRBackend}

_show(io, MIME("image/png"), plt)

# backends/gr.jl
function _show(io::IO, ::MIME{Symbol("image/png")}, plt::Plot{GRBackend})
    # 省略
end

本記事ではバックエンドを GR として進めていくが、他のバックエンドでもそれぞれ _show が定義されている。プロット時には、バックエンドに対応した _show メソッドが呼ばれることになる。各バックエンドでの _show の定義は backends ディレクトリに含まれている。

_show の定義を得る

そのままでは読みにくいので、 quote で囲んで補間しよう。
https://docs.julialang.org/en/v1/manual/metaprogramming/#Interpolation-1

mime, fmt = "image/png" => "png"

quote
    function _show(io::IO, ::MIME{Symbol($mime)}, plt::Plot{GRBackend})
        GR.emergencyclosegks()
        filepath = tempname() * "." * $fmt
        env = get(ENV, "GKSwstype", "0")
        ENV["GKSwstype"] = $fmt
        ENV["GKS_FILEPATH"] = filepath
        gr_display(plt, $fmt)
        GR.emergencyclosegks()
        write(io, read(filepath, String))
        rm(filepath)
        if env != "0"
            ENV["GKSwstype"] = env
        else
            pop!(ENV,"GKSwstype")
        end
    end
end

出力の quote を外して、適当にコメントを追加した。

function _show(io::IO, ::MIME{Symbol("image/png")}, plt::Plot{GRBackend})
    GR.emergencyclosegks()
    filepath = tempname() * "." * "png"  # データのやり取りに使用する一時ファイル名
    env = get(ENV, "GKSwstype", "0")     # 設定の退避
    ENV["GKSwstype"] = "png"             # フォーマットを環境変数で設定
    ENV["GKS_FILEPATH"] = filepath       # 出力ファイル名も同様
    gr_display(plt, "png")               # プロットを画像化して一時ファイルに保存
    GR.emergencyclosegks()
    write(io, read(filepath, String))    # 画像を読み出して IO に出力
    rm(filepath)                         # 一時ファイルを削除
    if env != "0"
        ENV["GKSwstype"] = env           # 設定を元に戻す
    else
        pop!(ENV, "GKSwstype")
    end
end

GR の環境変数を用いた設定一覧
https://gr-framework.org/environment_variables.html