RubyでRubyを描く // Speaker Deck
RubyKaigi2016最終日の次の日に開催された第74回 Ruby関西 勉強会で発表した内容とその補足になります。
RubyをRubyで描く とは
一ヶ月ほど前、RubyKaigiを目前にしてテンションが高まりRubyのロゴ
(Copyright © 2006, Yukihiro Matsumoto)
をRubyで描いて信仰心を深めたいという衝動に駆られました。
画像を数値化
とりあえずこのRubyのロゴを数値化します。
ImageMagickのwrapperであるRMagick使いました。
require 'RMagick'
include Magick
# 画像の読み込み
image = ImageList.new('ruby-kit/ruby.jpg')
# ピクセル毎に赤色要素だけを数値化して2次元配列に
z = image.rows.downto(0).map do |y|
image.columns.times.map do |x|
image.pixel_color(x, y).red
end
end
元の画像が1000x1000pxぐらいだったのでz
の配列のサイズも1000x1000になっています。
Heatmap化
とりあえず簡単に画像っぽいものを描くならヒートマップですね。
簡単に説明すると、数値を色で表現できる可視化手法で上の例だと値が小さいほど青く、大きいほど赤く表示されます。 Githubのアレです。今回は僕がSciRuby.jpの活動の一環として書いているrbplotlyというgemで描画しています。
require 'rbplotly'
data = [{
z: z,
type: :heatmap,
colorscale: [
[0.0, 'rgb( 0, 0, 0)'],
[0.7, 'rgb(256, 0, 0)'],
[0.95, 'rgb(256, 256, 256)'],
[1.0, 'rgb(256, 256, 256)']
]
}]
layout = { width: 700, height: 700 }
plot = Plotly::Plot.new(data: data, layout: layout)
plot.show # IRuby上以外では#generate_htmlや#download_imageを。
# 詳しくは https://github.com/ash1day/rbplotly 参照
colorscaleを0〜1で指定するところがポイントです。デフォルトでは上の例のように青〜赤なのですが、今回は画像を再現したいので黒〜赤〜白としています。
結果と比較
結果
colorscaleを手動でチューニングしたこともあり、結構似ました。
比較
左が元の画像です。やっぱりちょっと深みがありますね。
カラーバリエーション
色の指定を黒〜緑〜白や、黒〜青〜白とすることにより色を変化させることができます。
Heatmapでの問題点
- 色の指定が面倒
- 「それ、画像貼った方がいいんじゃない?」という痛いところを突かれやすい。
Scatter Plot
というわけで、もっとグラフ描いてる感が出る可視化手法Scatter Plotで描いてみます。何のことはない点プロットです。
好きな色を指定できるように今度は先ほど配列化した数値をマーカーの濃度に適用します。
# opacityの設定が0〜1なので正規化
max = z.flatten.max
min = z.flatten.min
denom = max - min
z.map! { |row| row.map { |red| 1 - (red - min).to_f / denom } }
trace = {
x: z.length.times.map { z.first.length.times.to_a }.flatten,
y: z.length.times.map { |y| Array.new(z.first.length - 1, y) }.flatten,
type: :scatter,
mode: :markers,
marker: { size: 1, color: 'rgb(255, 65, 54)', opacity: z.flatten }
}
layout = { width: 700, height: 700 }
plot = Plotly::Plot.new(data: [trace], layout: layout)
plot.show
Heatmap版と比べて色の指定が簡単になっているのが分かるでしょうか。これなら何色のRubyでも書けそうですね。
結果
ナウなフラットデザインっぽい色のRubyが描けました。(白い部分も点は打たれていますが、opacityがほぼ0なため見えなくなっています。)