Minecraft Pi Editionで画像をブロック化して描画する

[前回の記事](http://qiita.com/jyuko/items/cd39d93146fd392c8be3)でラズパイを購入して、まだBluetoothしか使っていない訳ですが、私は気付いていました。
そう、Raspbianにマインクラフトがインストールされていることを！

マイクラやってみたかったので、少し遊んでみます。


## Minecraft Pi Edition
まとめサイトを見つけました。
https://matome.naver.jp/m/odai/2142545327953860701

Pi Editionの特徴は、以下のようです。

- 無料
- プログラミング機能あり
- クリエイティブモードのみ
- ワールドの広さに制限
- クラフト不可

サバイバルモードを有効にする非公式のパッチはあるみたいですね。
平面の広さが256×256しかないので、プログラミングして遊んで、本格的にやりたくなったらPC版を買う感じかな。

[公式のドキュメント](
https://www.raspberrypi.org/learning/getting-started-with-minecraft-pi/worksheet/)もプログラミングすること前提で書かれているみたい。

## 作るもの
256×256と聞いて思い浮かぶのは、地図タイルですね！（職業病）
https://en.m.wikipedia.org/wiki/Tiled_web_map

試しに、地図タイルを描画してみましょう。


## 使ったもの
- Minecraft Pi Edition v0.1.1
- Python 2.7.9
- python-opencv 2.4.9

Python3系が使えるかわからなかったので、Python2系にしました。


## 実装
OpenCVで画像を読み込んで各ピクセルのRGB値を調べ、その色に近いブロックを置いていきます。


### OpenCVのインストール
以下の記事にある手順でインストールできました。
http://qiita.com/suppy193/items/91609e75789e9f458c39

```shell-session
$ sudo apt-get update
$ sudo apt-get install libopencv-dev
$ sudo apt-get install python-opencv
```

### 画像の読み込み&ピクセルのRGB値を取得
OpenCVを使えば簡単です。

```python
import cv2

img = cv2.imread('image.png')

column = 10
row = 20
pixel = img[column, row]
print(pixel)
```

上記を実行すると、以下のような出力になります。
RGBではなく、BGRの順になっています。

```shell-session
>>>
[ 57  70 255]
```

### 近い色のブロックを置く
16色に塗り分けられるwool（羊毛）を使います。
各色のRGB値は、以下に記載されていました。
http://minecraft.gamepedia.com/Wool

ブロックを置くためのコードは以下のような感じです。
実行すると、プレイヤーの足元にあるブロックがオレンジになります。

```python
from mcpi import minecraft
mc = minecraft.Minecraft.create()
x, y, z = mc.player.getPos()

wool = 35
selectedColor = 1 # Orange
mc.setBlock(x, y-1, z, wool, selectedColor)
```

各ピクセルにどの色を使うかですが、RGB値の差を`numpy.linalg.norm`で求めて、最も差が小さい色を使うことにします。

```python
dist = numpy.linalg.norm(woolColor - pixel)
```

### ループさせて画像を描画する
RGB値に近い色のブロックを置く処理をループさせ、画像の各ピクセルをブロックに変換します。

最終的なコード全文は、以下です。

```minecraft.py
from mcpi import minecraft
import cv2
import numpy

mc = minecraft.Minecraft.create()

wool = 35

# wool color BGR
woolColors = [
    [221,221,221],  # 0: White
    [62,125,219], # 1: Orange
    [188,80,179], # 2: Magenta
    [201,138,107], # 3: Light blue
    [39,166,177], # 4: Yellow
    [56,174,65], # 5: Lime
    [153,132,208], # 6: Pink
    [64,64,64], # 7: Gray
    [161,161,154], # 8: Light gray
    [137,110,46], # 9: Cyan
    [181,61,126], # 10: Purple
    [141,56,46], # 11: Blue
    [31,50,79], # 12: Brown
    [27,70,53], # 13: Green
    [48,52,150], # 14: Red
    [22,22,25] # 15:Black
]

img = cv2.imread('image.png')
img = cv2.resize(img, (256, 256))

height, width = img.shape[:2]
x, y, z = mc.player.getPos()

for column in range(width):
    for row in range(height):
        pixel = img[column, row]

        selectedColor = 0
        minDist = None
        for i, woolColor in enumerate(woolColors):
            dist = numpy.linalg.norm(woolColor - pixel)
            if  minDist == None or minDist > dist:
                minDist = dist
                selectedColor = i

        mc.setBlock(-153+row, y-1, -116+column, wool, selectedColor)

mc.postToChat("Create Image")
```
x,z座標はワールド端を基点にしているのですが、何故か[-153,-116]でした。
y座標はプレイヤーの位置から-1して、プレイヤーが立っている平面にブロックを並べています。


## 実行結果
Minecraft Pi Editionを起動した状態で、Pythonのスクリプトを実行します。
「F5」キーを押すか、"Run" > "Run Module"で実行できます。

65,536ピクセルを16色と比較しているため、結構時間が掛かります。
気長に数分待ってください。

元画像は、OpenStreetMapの地図タイル（渋谷駅周辺）を使ってみました。

![IMG_1436.jpg](https://qiita-image-store.s3.amazonaws.com/0/153460/f3a46f68-9935-68d2-854d-83da763f221f.jpeg)

![IMG_1438.jpg](https://qiita-image-store.s3.amazonaws.com/0/153460/102c73ab-173a-2cc9-13bc-47a1f406e854.jpeg)

© OpenStreetMap contributors
http://www.openstreetmap.org/copyright


カメラワークの都合上、全体を一度に見渡すことはできません。
アイコンの形はしっかり出てますが、注記の文字はがんばってなんとか読める程度まで崩れています。
また、淡いグレーや淡いブルーが白のブロックになってしまい、建物や敷地の境界が消えてしまったので、地図としてはちょっと微妙な結果になりました。


## まとめ
シンプルなロジックで思ったよりも描画できていますが、いかんせん色数が足りない印象です。
wool以外のブロックも使って、不足している色を割り当てれば画質はよくなりそうですが、さらに処理時間が増えるので考えものです。


## オマケ（本編）
256×256のサイズありきで地図タイルを試したんですが、縦横が同じピクセル数の画像であれば、OpenCVでresizeしてから処理すればいいんですよね。（コード例にも仕込んでます）

で、Twitterアイコン用に配布されている画像って、正方形で解像度も小さいじゃないですか。

適当に読み込んでみましょう。

![IMG_1441.jpg](https://qiita-image-store.s3.amazonaws.com/0/153460/4455c627-df4f-5677-2ebb-b48f75aacd1a.jpeg)

![IMG_1442.jpg](https://qiita-image-store.s3.amazonaws.com/0/153460/f776f877-9b85-7215-198b-33c8dfe1617b.jpeg)

![IMG_1443.jpg](https://qiita-image-store.s3.amazonaws.com/0/153460/8de036bc-5f8c-fafe-6d15-9bbf6371e117.jpeg)

こっちの方が全然上手く描けてるじゃん！

髪や肌など、色の淡い部分が白ブロックになってしまう傾向は変わりません。肌色や茶色に近いブロック（SandstoneやStaind Clay）を足すといいかもしれないですね。