見出し画像

絵文字で絵文字を描くプログラムを作った話。

時間があったのでなんとなく絵文字で絵文字を描くプログラムを 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

できましたー😇

🥄🥄🥄🥄🔹🌨💠💠🥶🔘🔘🔘🔘🥶💠💠🌨🔹🥄🥄🥄🥄
🥄🔹💠🔘🈁🔘💠💨🔋📧🚸🚸📧🌫🌧💠🔘🈁🔘💠🔹🥄
🥄🔘🈁💠🔹🔸🌞🟨🟨🟨🟨🟨🟨🟨🟨😳🔸🔹💠🈁🔘🥄
🥄🔘🈁🈁🥶🧔🤓📒🟨🟨🟨🟨🟨🟨📒🤓🤵🥶🈁🈁🔘🥄
🥄🔹💠🈁🈁🈁🈁🈁🈁🈁🈁🈁🈁🈁🈁🈁🈁🈁🈁💠🔹🥄
🥄🥄🧜🟧🟨🤮🈯🈯🏛🏛🏛🏛🏛🏛🈯🟢🤮🟨🟧🕯🥄🥄
🥄🥄🔼🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🤗🥄🥄
🥄🧜🟧🟨🟨🟨🟨🥘🟨🟨🟨🟨🟨🟨🥘🟨🟨🟨🟨🟧🔸🥄
🥄🔶🟨🟨🟨🟨🏿🏿🏿🥘🟨🟨🥘🏿🏿🏿🟨🟨🟨🟧👐🥄
🥄🟡🟨🟨🟨🟧🏿🥘🏿🏿🟨🟨🏾🏿🥘🏿🟧🟨🟨🟨😾🥄
🥄🟠🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🤗🥄
🥄🟠🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🤗🥄
🥄🤫🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨😾🥄
🥄👐🟧🟨🟨🟨🟨🏾🟨🟨🟨🟨🟨🟨🏾🟨🟨🟨🟨🟧🍁🥄
🥄🔸🟧🟨🟨🟨🟨🏾🏿🏿🏾🏿🏿🏿🙆🟨🟨🟨🟨🟧🔸🥄
🥄🥄🤫🟨🟨🟨🟨🟨🟨🏾🏾🏾🥘🟨🟨🟨🟨🟨🟧😳🥄🥄
🥄🥄🔸🟧🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟧🔸🥄🥄
🥄🥄🥄🔅🟧🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟧🟧🔸🥄🥄🥄
🥄🥄🥄🥄🔸🟠🟧🟨🟨🟨🟨🟨🟨🟨🟨🟧🤗🔸🥄🥄🥄🥄
🥄🥄🥄🥄🥄🥄🧜🤗🟧🟧🟧🟧🟧🟧😍🔅🥄🥄🥄🥄🥄🥄
🥄🥄🥄🥄🥄🥄🥄🥄🥄🔸🍹🍹🔸🥄🥄🥄🥄🥄🥄🥄🥄🥄
🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄🥄

というわけで、今日はこのへんで。それではまたー。

(終)

この記事が気に入ったらサポートをしてみませんか?