Rubyで画像を行列にしたり、行列を画像にしたりする

実は非常に沢山の方法があります。

* chunky_png および oily_png
* mini_magick および rmagick
* magro

などがあります。その他opencvバインディングを使ったり、Ruby/Tkを使ったり、pnmを経由したりする方法もあります。

ここではRuby-Gnomeの一部である、GdkPixbufを使います。
https://github.com/ruby-gnome2/ruby-gnome2

```bash
gem install gtk3
```
Gtkは↓みたいなGUIを作るやつだけど、今回はそれに関しては最後の方にちょこっとだけ出てきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/ba27782f-fd80-3642-7ce0-3a889064d246.png)

画像素材には、かわいいQiitanを使います[^1]。

![qiitan-icon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/6cf5b5bc-de57-5c7a-bd76-120d01eea637.png)

そうそう、私の落書きしたぶさかわのQiitanっぽい動物の画像もありますので欲しい人はどうぞ（一体何の記事なんだ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/97c0d6b7-42a7-a19e-4e88-811b4c08f1a5.png)


## 画像→ピクセル
```ruby
require 'gdk_pixbuf2'

image  = GdkPixbuf::Pixbuf.new(file: "qiitan-icon.png")
width  = image.width
height = image.height
data   = image.pixels
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/cef8d985-610b-fadd-13a3-e2b5225256d7.png)


はい、Qiitanが、数字の切れ端になってしまいましたね。

## 画像 → ピクセル（行列）
このまま、Rubyの配列のまま頑張ってもいいのですが、それだと辛いので、ここでは専用の行列計算ライブラリを使いしょう。Numo::NArrayです。
https://github.com/ruby-numo/numo-narray
わざわざRubyの配列を経由しなくても、バイナリ文字列を経由すれば大丈夫ですね。

```ruby
require 'gdk_pixbuf2'
require 'numo/narray'

image = GdkPixbuf::Pixbuf.new(file: "qiitan-icon.png")
width = image.width
height = image.height

data = image.pixel_bytes.to_str

narray = Numo::UInt8.from_binary data
narray.reshape!(height, width, 4)
```

さいごの`reshape` の4に注目してください。Qiitanは透過性の情報が入っているので、チャンネルが４つあります。

## ピクセル（行列） → 画像
逆に行列からQiitan画像を復元してみましょう。

```ruby
require 'gdk_pixbuf2'
data2 = narray.to_string

image2 = GdkPixbuf::Pixbuf.new(data: data2, width: width, height: height, has_alpha: true)
image2.save("qiitan-reloaded.png")
```

無事、Qiitanが復活しました！

![qiitan-reloaded.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/bb88c9fc-c2fa-0b92-8336-4276f6a948f4.png)

このQiitanの画像は4チャンネルなので、`has_alpha` に気をつけてください。

## フィルターでもかけてみるか
さて、これでこの記事は終わりでもいいのですが、せっかくなので適当にフィルタをかけてみましょう。

```ruby
require 'gdk_pixbuf2'
require 'numo/narray'

# フィルターをかける関数
def filter(na, filter)
  height, width, _c = na.shape
  f_height, f_width = filter.shape
  na2 = Numo::UInt8.zeros(height + f_height - 1, width + f_width - 1)
  x = (f_height - 1) / 2
  y = (f_width - 1) / 2
  na2[x...-x, y...-y] = na
  na3 = na.clone.fill 0

  f_height.times do |i|
    f_width.times do |j|
      temp = (na2[i..(i - f_height), j..(j - f_width)] * filter[i, j])
      na3 += temp
    end
  end

  Numo::UInt8.cast(na3.ceil)
end

image = GdkPixbuf::Pixbuf.new(file: "qiitan-icon.png")
width = image.width
height = image.height

data = image.pixel_bytes.to_str

narray = Numo::UInt8.from_binary data
narray.reshape!(height, width, 4)

# 横ブレを引き起こすフィルタ
myfilter = Numo::SFloat.ones(3,51)
myfilter /= myfilter.sum

# アルファチャンネル以外にフィルターをかける
3.times do |i|
  narray[true, true, i] = filter(narray[true, true, i], myfilter)
end

data2 = narray.to_string

image2 = GdkPixbuf::Pixbuf.new(data: data2, width: width, height: height, has_alpha: true)
image2.save("qiitan-vibrated.png")
```
![qiitan-vibrated.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/30952867-567f-6682-af3b-42c0fccf5a61.png)

フィルタを変えてみましょう

```ruby
myfilter = Numo::SFloat[[0,-1,0],[-1,0,1],[0,1,0]]
```

![qiitan-reloaded.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/def672a0-7283-eb13-4ade-302f86fcb9a3.png)

なかなかいい感じですね。
ほかにもチャンネルの順番を入れ替えるだけで、簡単にQiitanの色違いができたりします。

![qiitan-[0, 1, 2, 3].png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/bde425d5-9e78-4b32-4e78-d6d6c5e31bc3.png)![qiitan-[0, 2, 1, 3].png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/60f0f0c0-8adc-6e48-1aca-f3053627d5fa.png)![qiitan-[1, 0, 2, 3].png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/c586cb49-b5d4-2650-40d7-4eba7b365f4b.png)![qiitan-[2, 1, 0, 3].png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/59e51e73-c9a6-0689-6be4-d6edd68b43d4.png)

## ウィンドウに表示してみよう
さらにおまけで、これらの画像をWindowに表示することを考えます。GdkPixbufを使っているのでGTKを使えば簡単ですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/ba27782f-fd80-3642-7ce0-3a889064d246.png)

ソースコードは適当ですが、こんな感じでしょうか

```ruby
require 'gtk3'

win = Gtk::Window.new
win.signal_connect(:destroy) { Gtk.main_quit }
hbox = Gtk::Box.new(:horizontal)
win.add hbox

Dir.glob('*.png').each do |file|
  pixbuf = GdkPixbuf::Pixbuf.new file: file
  pixbuf = pixbuf.scale(100, 100)
  image = Gtk::Image.new(pixbuf: pixbuf)
  hbox.add image
end

win.show_all

Gtk.main
```

この記事は以上です。

![qiitan.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/eaf24fae-33aa-34fd-f65b-8b1c20bf335d.png)


[^1]: 著作権ありそうだけど、たぶんこのサイト内で使うぶんには怒られないであろう…