Skip to content

Instantly share code, notes, and snippets.

@plonk
Created January 29, 2018 14:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save plonk/04f854311f4744f4ca723c4cb6800c48 to your computer and use it in GitHub Desktop.
Save plonk/04f854311f4744f4ca723c4cb6800c48 to your computer and use it in GitHub Desktop.
PNM 形式の画像をフルカラー対応の端末で表示するプログラム
#!/usr/bin/env ruby
=begin
sgp - Semigraphic Print
PNM 形式の画像をフルカラー対応の端末(xterm、gnome-terminalなど)で表
示するプログラムです。Unicode の Block Elements 文字を使って表示します。
動かすには pnm ジェムが必要です。
$ gem install pnm
使い方:
$ sgp 画像ファイル名.ppm
ファイル名に '-' を指定すると画像データを標準入力から読み込みます。
ImageMagick の convert コマンドと併用すると多種の画像フォーマットが
表示できます。
$ convert 入力ファイル名 pnm:- | sgp -
文字セルのアスペクト比の関係で、たいてい縦長に表示されるので、-scale
オプションで幅を狭くすると良いと思います。
$ convert -scale x50% 入力ファイル名 pnm:- | sgp -
=end
require 'pnm'
# 返り値: {
# assigned_clusters: データ点に割り当てられたクラスターの番号[0,k),
# cluster_means: 個々のクラスターの平均値(nilあり)
# }
def clustering(data_points, k, niterations = 1)
fail "number of data points < number of clusters" if data_points.size < k
clusters = (0...k).cycle.first(data_points.size)
calculate_mean = lambda do |cluster|
points = data_points.zip(clusters)
.select { |pt, c| c == cluster }.map { |pt, _| pt }
if points.empty?
return nil
else
return points.transpose.map { |col| col.inject(:+).div(col.size) }
end
end
distance = lambda do |p1, p2|
da = p1[0] - p2[0]
db = p1[1] - p2[1]
dc = p1[2] - p2[2]
return Math.sqrt(da*da + db*db + dc*dc)
end
reassign = lambda do |centers|
data_points.each.with_index do |pt, i|
new_cluster = (0...centers.size).min_by { |j|
if centers[j].nil?
Float::INFINITY
else
distance.(pt, centers[j])
end
}
clusters[i] = new_cluster
end
end
means = (0...k).map { |i| calculate_mean.(i) }
niterations.times do
reassign.(means)
means = (0...k).map { |i| calculate_mean.(i) }
end
return { assigned_clusters: clusters, cluster_means: means }
end
def pixel_distance(p1, p2)
dr = p1[0] - p2[0]
dg = p1[1] - p2[1]
db = p1[2] - p2[2]
return Math.sqrt(dr*dr + dg*dg + db*db)
end
BLOCK_ELEMENT_TABLE = {
[0, 0, 0, 0] => " ",
[0, 0, 1, 0] => "▖",
[0, 0, 0, 1] => "▗",
[1, 0, 0, 0] => "▘",
[1, 0, 1, 1] => "▙",
[1, 0, 0, 1] => "▚",
[1, 1, 1, 0] => "▛",
[1, 1, 0, 1] => "▜",
[0, 1, 0, 0] => "▝",
[0, 1, 1, 0] => "▞",
[0, 1, 1, 1] => "▟",
[1, 0, 1, 0] => "▌",
[0, 1, 0, 1] => "▐",
[0, 0, 1, 1] => "▄",
[1, 1, 0, 0] => "▀",
[1, 1, 1, 1] => "█",
}
def block_element(cell)
return BLOCK_ELEMENT_TABLE[cell] || fail
end
def render_cell(data)
bg = data[:cluster_means][0] || [255,0,255]
fg = data[:cluster_means][1] || [255,0,255]
return "\e[38;2;#{fg.join(';')}m" +
"\e[48;2;#{bg.join(';')}" + "m" +
block_element(data[:assigned_clusters]) + "\e[0m"
end
ALL_PATTERNS = ["*", " "].repeated_permutation(4).to_a
def print_image(pnm)
bm = pnm.pixels
(pnm.height/2).times do |y|
buf = ""
(pnm.width/2).times do |x|
pixels = [bm[y*2][x*2], bm[y*2][x*2+1], bm[y*2+1][x*2], bm[y*2+1][x*2+1]]
result = clustering(pixels, 2)
buf.concat render_cell(result)
end
puts buf
end
end
def main
if ARGV.empty?
STDERR.puts "Usage: sgp [FILENAME.. (specify '-' for stdin)]\n"
exit 1
else
ARGV.each do |arg|
if arg == '-'
print_image PNM.read(STDIN)
else
print_image PNM.read(arg)
end
end
end
end
if __FILE__ == $0
main
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment