絵文字で絵文字を描くプログラムを作った話。
時間があったのでなんとなく絵文字で絵文字を描くプログラムを Ruby で書いてみました。
出力された絵文字はこんな感じです。
アプローチとしては、まず各絵文字の色情報を 2px × 2px に単純化して、どの絵文字がどんな色を持つかを辞書構造で持つ JSON データを生成します。入力した画像の各画素値に一番近い色と思われる絵文字を割り出し、そのような絵文字を並べていき出力とします。
プロジェクトディレクトリに上記リポジトリを clone しておきます。
git clone https://github.com/googlefonts/noto-emoji.git
続いて Gemfile を作ります。
source "https://rubygems.org"
gem "mini_magick"
gem "numo-narray"
そして、gem をインストールします。
bundle install
次に以下のようなコードを書きます。
# convert.rb
#
# 各絵文字の色情報を 2px × 2px に単純化して、
# どの絵文字がどんな色を持つかを辞書構造で持つ json データを生成する
require "mini_magick"
# 絵文字画像があるディレクトリの指定
# clone したリポジトリのなかにある
IMAGE_PATH = "noto-emoji/png/512".freeze
image_pathes = []
# 絵文字画像のファイルパスを配列 image_pathes に格納する
Dir.glob("#{IMAGE_PATH}/emoji_u?????.png") do |item|
image_pathes.push(item)
end
num_all = image_pathes.size
num_current = 0
# 絵文字画像は透過されているので背景を白くするために使うベース画像を生成する
MiniMagick::Tool::Convert.new do |convert|
convert.size "512x512"
convert.xc "#FFFFFF"
convert << "base.png"
end
emoji_table = {}
base_image = MiniMagick::Image.open("base.png")
# 各絵文字ごとに処理する
image_pathes.each do |path|
code = /.+\/emoji_u(.{5})/.match(path)[1]
# 絵文字画像を読み込む
emoji_image = MiniMagick::Image.open(path)
# 白ベース画像を合成する
image = base_image.composite(emoji_image) do |config|
config.compose "Over"
config.gravity "Center"
config.colorspace "rgb" # これが無いとグレースケールになる
end
pixels = image.get_pixels
image_2x2 = [[[0, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 0]]]
# 絵文字画像の各画素値を 2x2 のピクセル画像に平均化する
pixels.each.with_index do |col, y|
col.each.with_index do |px, x|
image_2x2[y / 256][x / 256][0] += px[0]
image_2x2[y / 256][x / 256][1] += px[1]
image_2x2[y / 256][x / 256][2] += px[2]
end
end
image_2x2.each.with_index do |col, y|
col.each.with_index do |p, x|
p.each.with_index do |total, c|
image_2x2[y][x][c] = total / (256 * 256)
end
end
end
emoji_table[code] = image_2x2
num_current += 1
# 進捗を簡単に表示する
num_percent = num_current * 100 / num_all
str_percent = "*" * (num_percent / 4) + " " * ((100 - num_percent) / 4)
puts "#{num_percent}%\t[#{str_percent}]"
end
# 計算結果を JSON で書き出す
File.open("emoji.json","w") do |f|
f.write(emoji_table.to_json)
end
puts "Done!!"
そして、実行します。
ruby convert.rb
するとプロジェクトディレクトリに emoji.json が生成されます。
続いて以下のようなコードを書きます。
# main.rb
#
# 絵文字から絵文字を描くプログラム
require "mini_magick"
require "numo/narray"
require "json"
# 入力する画像のパス
PATH = "noto-emoji/png/512/emoji_u1f642.png"
# 分割数 小さいほど高精細になる
DIV = 24
# 入力画像を読み込む
image = MiniMagick::Image.open(PATH)
width = image[:width]
height = image[:height]
# サイズが小さすぎる場合は終了
if width < DIV * 2
w = DIV * 2
puts "Image shoud be larger than #{w}x#{w}."
exit
end
# 縦横サイズが DIV の倍数になるように調整
width = (width.to_f / DIV.to_f / 2.0).ceil.to_i * DIV * 2
height = (height.to_f / DIV.to_f / 2.0).ceil.to_i * DIV * 2
# 入力画像が透過されている場合のために白のベース画像を生成する
MiniMagick::Tool::Convert.new do |convert|
convert.size "#{width}x#{height}"
convert.xc "#FFFFFF"
convert << "base.png"
end
base_image = MiniMagick::Image.open("base.png")
# 入力画像をベース画像に合成
image = base_image.composite(image) do |config|
config.compose "Over"
config.gravity "Center"
config.colorspace "rgb"
end
pixels = image.get_pixels
image_2x2 = []
# DIV の 1/2 サイズのピクセルで入力画像の画素値を平均化するための準備を行う
(0...(height / DIV / 2)).each do |h|
image_2x2.push([])
(0...(width / DIV / 2)).each do |w|
image_2x2[h].push([])
(0...3).each do |p|
image_2x2[h][w].push(0)
end
end
end
# 入力画像の画素値を平均化する
pixels.each.with_index do |col, y|
col.each.with_index do |px, x|
image_2x2[y / DIV / 2][x / DIV / 2][0] += px[0]
image_2x2[y / DIV / 2][x / DIV / 2][1] += px[1]
image_2x2[y / DIV / 2][x / DIV / 2][2] += px[2]
end
end
image_2x2.each.with_index do |col, y|
col.each.with_index do |p, x|
p.each.with_index do |total, c|
image_2x2[y][x][c] = total / (2 * DIV * 2 * DIV)
end
end
end
emoji_table = {}
# 絵文字の色情報を格納した emoji.json を読み込む
File.open("emoji.json") do |j|
emoji_table = JSON.load(j)
end
result = ""
# メイン処理
# 画素ごとに最も画素値に近い絵文字を探す
image_2x2.each.with_index do |col, y|
col.each.with_index do |p, x|
array1 = Numo::DFloat.cast(p)
sum = 1000
sim_code = nil
emoji_table.each do |code, q|
# 画素値の配列 arrray1, array2 を差の絶対値で比較する
array2 = Numo::DFloat.cast(q)
tmp = (array1 - array2).abs.sum.to_i
# より画素値が似ていれば sim_code に絵文字のコードを代入する
if tmp <= sum
sum = tmp
sim_code = code
end
end
# 絵文字コード値(文字列)から絵文字に変換
e = sim_code.hex.chr("UTF-8")
# 絵文字を並べる
result = "#{result}#{e}"
end
# 改行を入れる
result = "#{result}\n"
end
# 完成した絵文字の文字列を出力する
puts result
このコードを実行すると…
ruby main.rb
できましたー😇
🥄🥄🥄🥄🔹🌨💠💠🥶🔘🔘🔘🔘🥶💠💠🌨🔹🥄🥄🥄🥄
🥄🔹💠🔘🈁🔘💠💨🔋📧🚸🚸📧🌫🌧💠🔘🈁🔘💠🔹🥄
🥄🔘🈁💠🔹🔸🌞🟨🟨🟨🟨🟨🟨🟨🟨😳🔸🔹💠🈁🔘🥄
🥄🔘🈁🈁🥶🧔🤓📒🟨🟨🟨🟨🟨🟨📒🤓🤵🥶🈁🈁🔘🥄
🥄🔹💠🈁🈁🈁🈁🈁🈁🈁🈁🈁🈁🈁🈁🈁🈁🈁🈁💠🔹🥄
🥄🥄🧜🟧🟨🤮🈯🈯🏛🏛🏛🏛🏛🏛🈯🟢🤮🟨🟧🕯🥄🥄
🥄🥄🔼🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🤗🥄🥄
🥄🧜🟧🟨🟨🟨🟨🥘🟨🟨🟨🟨🟨🟨🥘🟨🟨🟨🟨🟧🔸🥄
🥄🔶🟨🟨🟨🟨🏿🏿🏿🥘🟨🟨🥘🏿🏿🏿🟨🟨🟨🟧👐🥄
🥄🟡🟨🟨🟨🟧🏿🥘🏿🏿🟨🟨🏾🏿🥘🏿🟧🟨🟨🟨😾🥄
🥄🟠🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🤗🥄
🥄🟠🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🤗🥄
🥄🤫🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨😾🥄
🥄👐🟧🟨🟨🟨🟨🏾🟨🟨🟨🟨🟨🟨🏾🟨🟨🟨🟨🟧🍁🥄
🥄🔸🟧🟨🟨🟨🟨🏾🏿🏿🏾🏿🏿🏿🙆🟨🟨🟨🟨🟧🔸🥄
🥄🥄🤫🟨🟨🟨🟨🟨🟨🏾🏾🏾🥘🟨🟨🟨🟨🟨🟧😳🥄🥄
🥄🥄🔸🟧🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟧🔸🥄🥄
🥄🥄🥄🔅🟧🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟧🟧🔸🥄🥄🥄
🥄🥄🥄🥄🔸🟠🟧🟨🟨🟨🟨🟨🟨🟨🟨🟧🤗🔸🥄🥄🥄🥄
🥄🥄🥄🥄🥄🥄🧜🤗🟧🟧🟧🟧🟧🟧😍🔅🥄🥄🥄🥄🥄🥄
🥄🥄🥄🥄🥄🥄🥄🥄🥄🔸🍹🍹🔸🥄🥄🥄🥄🥄🥄🥄🥄🥄
🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄
というわけで、今日はこのへんで。それではまたー。
(終)
この記事が気に入ったらサポートをしてみませんか?