ポケモンの画像でモザイクアートを作ってみた

# はじめに
　この記事では入力画像から18匹のポケモンの画像を用いてモザイクアートを自動作成する方法を紹介します。
<img src="https://qiita-image-store.s3.amazonaws.com/0/284589/021b20b9-39bb-ec22-49cd-c63a4d224987.png" width="50%">

# 事前準備
1. [ポケモン図鑑](https://www.pokemon.jp/zukan/)から18匹(aqua,black,blue,fuchsia,silver,green,lime,maroon,navy,olive,purple,red,gray,teal, white,yellow,beige,orangeに色が近いポケモン)を選ぶ。
2. photoshopなどを使って背景色をつけたら40×40にサイズを調整する。
<div>
<img src="https://qiita-image-store.s3.amazonaws.com/0/284589/dcf33cbe-a96e-42a1-9b9c-e93a0b27aa6f.png">
<img src="https://qiita-image-store.s3.amazonaws.com/0/284589/82f01f3b-62ff-0478-b516-fa5859748904.png">
<img src="https://qiita-image-store.s3.amazonaws.com/0/284589/e42e4e99-71ed-b3b1-2630-09ff44f4decf.png">
<img src="https://qiita-image-store.s3.amazonaws.com/0/284589/5c77df75-3fb3-6067-78ca-80f7b877e3cc.png">
<img src="https://qiita-image-store.s3.amazonaws.com/0/284589/13c738f5-e2bd-7f44-3c01-0419145a8d2a.png">
<img src="https://qiita-image-store.s3.amazonaws.com/0/284589/3a485876-15b1-844c-980d-ecd4c7891865.png">
<img src="https://qiita-image-store.s3.amazonaws.com/0/284589/b6e3fb6b-6a62-748e-3525-fe21e563e70a.png">
</div>
3. image/とか適当なディレクトリにポケモンの画像を置く。


# 処理の大まかな流れ
もっと賢い方法あるかもしれませんが、、

1. 入力画像を40×40に分割     ``` PhotoMosaic.java ```
2. 1でできた画像の色平均をとる  ``` GetAverage.java ```
3. 2の色平均をもとに減色   ``` ReduceColor.java ```
4. 3で選ばれた色のポケモンを配置``` PhotoMosaic.java ```

という感じで実装してみます。

# 実装
- 画像の読み込み/書き出し部分は省略します。

``` java:PhotoMosaic.java
import java.awt.image.*;
import java.awt.Graphics;

public class PhotoMosaic {

	static BufferedImage execute(BufferedImage before_image) {
		int width = before_image.getWidth();
		int height = before_image.getHeight();
		BufferedImage after_image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
		Graphics g = after_image.getGraphics();
		BufferedImage image[] = new BufferedImage[18];
        image[0] = JpegFileReader.read("image/white.png");
        image[1] = JpegFileReader.read("image/fuchsia.png");
        image[2] = JpegFileReader.read("image/lime.png");
        image[3] = JpegFileReader.read("image/blue.png");
        image[4] = JpegFileReader.read("image/black.png");
        image[5] = JpegFileReader.read("image/maroon.png");
        image[6] = JpegFileReader.read("image/olive.png");
		image[7] = JpegFileReader.read("image/silver.png");
		image[8] = JpegFileReader.read("image/red.png");
		image[9] = JpegFileReader.read("image/green.png");
		image[10] = JpegFileReader.read("image/navy.png");
		image[11] = JpegFileReader.read("image/yellow.png");
		image[12] = JpegFileReader.read("image/aqua.png");
		image[13] = JpegFileReader.read("image/gray.png");
		image[14] = JpegFileReader.read("image/purple.png");
		image[15] = JpegFileReader.read("image/teal.png");
		image[16] = JpegFileReader.read("image/beige.png");
		image[17] = JpegFileReader.read("image/orange.png");

		// 切り取った画像を入れておく配列を宣言
		BufferedImage[][] split_image = new BufferedImage[width/40][height/40];

		for ( int i=0; i*40<width; i++ ) {
			for ( int j=0; j*40<height; j++ ) {
				// 元の画像を40*40に切り取る
				split_image[i][j] = before_image.getSubimage( i*40, j*40, 40, 40 );
				// 切り取った画像の色平均をとる
				int color_num = ReduceColor.detectColor(GetAverage.execute(split_image[i][j]));
				g.drawImage(image[color_num], i*40, j*40, null);
			}
		}
		g.dispose();
		return after_image;
  }

}

```
``` java:GetAverage.java
import java.awt.image.*;
import java.awt.Color;

public class GetAverage{
  static int[] execute(BufferedImage image){
    int R = 0, G = 0, B = 0;
    // Average[0]->Red, Average[1]->Green, Average[2]->Blue
    int Average[] = new int[3];

    // 画素のRGB値をとって合計を求める
    for (int i=0; i<40; i++ ) {
      for (int j=0; j<40; j++ ) {
        int tmp_color = image.getRGB(i,j);
        Color color = new Color(tmp_color);
        R += color.getRed();
        G += color.getGreen();
        B += color.getBlue();
      }
    }

    // 画素の平均値を求める
    Average[0] = (int)R/1600;
    Average[1] = (int)G/1600;
    Average[2] = (int)B/1600;
    return Average;
  }
}

```
``` java:ReduceColor.java
public class ReduceColor{

  static int detectColor(int[] Average){
    int R = getNearestValue(Average[0]);
    int G = getNearestValue(Average[1]);
    int B = getNearestValue(Average[2]);
    // 変換する画像の配列の添え字を入れる配列
    int[][][] v = new int[256][256][256];
    
    v[64][64][64] = 4;    // black
    v[64][64][128] = 10;  // navy
    v[64][64][192] = 3;   // blue
    v[64][64][255] = 3;   // blue

    v[64][128][64] = 9;   // green
    v[64][128][128] = 15; // teal
    v[64][128][192] = 15; // teal
    v[64][128][255] = 3;  // blue

    v[64][192][64] = 2;   // lime
    v[64][192][128] = 15; // teal
    v[64][192][192] = 12; // aqua
    v[64][192][255] = 3;  // blue

    v[64][255][64] = 2;   // lime
    v[64][255][128] = 2;  // lime
    v[64][255][192] = 12; // aqua
    v[64][255][255] = 12; // aqua

    v[128][64][64] = 5;   // maroon
    v[128][64][128] = 14; // purple
    v[128][64][192] = 14; // purple
    v[128][64][255] = 3;  // blue

    v[128][128][64] = 6;  // olive
    v[128][128][128] = 13;// gray
    v[128][128][192] = 3; // blue
    v[128][128][255] = 3; // blue

    v[128][192][64] = 9;  // green
    v[128][192][128] = 2; // lime
    v[128][192][192] = 12;// aqua
    v[128][192][255] = 12;// aqua

    v[128][255][64] = 2;  // lime
    v[128][255][128] = 2; // lime
    v[128][255][192] = 2; // lime
    v[128][255][255] = 12;// aqua

    v[192][64][64] = 8;   // red
    v[192][64][128] = 1;  // fuchsia
    v[192][64][192] = 1;  // fuchsia
    v[192][64][255] = 14; // purple

    v[192][128][64] = 17; // orange
    v[192][128][128] = 16;// beige
    v[192][128][192] = 1; // fuchsia
    v[192][128][255] = 1; // fuchsia

    v[192][192][64] = 11;  // yellow
    v[192][192][128] = 11; // yellow
    v[192][192][192] = 0; // white
    v[192][192][255] = 12;// aqua

    v[192][255][64] = 12; // aqua
    v[192][255][128] = 2; // lime
    v[192][255][192] = 0; // white
    v[192][255][255] = 12;// aqua

    v[255][64][64] = 8;   // red
    v[255][64][128] = 8;  // red
    v[255][64][192] = 1;  // fuchsia
    v[255][64][255] = 1;  // fuchsia

    v[255][128][64] = 17; // orange
    v[255][128][128] = 16;// beige
    v[255][128][192] = 1; // fuchsia
    v[255][128][255] = 1; // fuchsia

    v[255][192][64] = 11; // yellow
    v[255][192][128] = 11;// yellow
    v[255][192][192] = 16;// beige
    v[255][192][255] = 16;// beige

    v[255][255][64] = 11; // yellow
    v[255][255][128] = 11;// yellow
    v[255][255][192] = 0; // white
    v[255][255][255] = 0; // white

    return v[R][G][B];
  }

  public static int getNearestValue(int v){
		int num=0;	// 配列の添え字
		int difference;	// 配列値-RGB値vの絶対値
    int[] list = {64,128,192,192};
    // System.out.println("vの値は"+v);
		difference = Math.abs( list[0] - v );
		for ( int i = 1; i < list.length; i++ ) {
			if ( Math.abs( list[i] - v ) < difference ) {
				num = i;
				difference = Math.abs( list[i] - v );
			}
		}
 		return list[num];
  }

}

```


# 結果
 (クリックして拡大)
<img src="https://qiita-image-store.s3.amazonaws.com/0/284589/a0ec79fc-5110-49b7-5512-9956717ca9be.jpeg" width="45%"><img src="https://qiita-image-store.s3.amazonaws.com/0/284589/adc0b37e-e79f-f7e5-7f1e-31b17dc5d297.jpeg" width="45%">
<img src="https://qiita-image-store.s3.amazonaws.com/0/284589/df3fc43c-1858-6d64-67e2-cf3428663809.jpeg" width="45%"><img src="https://qiita-image-store.s3.amazonaws.com/0/284589/151dbecd-1eef-789a-5f1b-cd8138b3f3a4.jpeg" width="45%">

- 減色するのが結構難しかった。特に水色だと白だと判断されてうまくいかなかった。
- 赤系は割といい感じにできた。
- 18色しかないので風景写真は厳しい。

# 参考になった記事
- [BufferedImage_getSubimageを使って、画像の一部を切り取る。](
http://aki-miya.hatenablog.com/entry/2015/02/14/BufferedImage_getSubimage%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%80%81%E7%94%BB%E5%83%8F%E3%81%AE%E4%B8%80%E9%83%A8%E3%82%92%E5%88%87%E3%82%8A%E5%8F%96%E3%82%8B%E3%80%82)

- [WEBカラー一覧](https://note.cman.jp/color/base_color.cgi)