画像処理：イメージを色々弄ってみよう

#はじめに

　※ この記事は「[画像処理：プログラムが読み込むイメージの基本構造](http://qiita.com/pocoman/items/6e65f457069f4c17a425)」をベースに進めていきます。お読みでない方は先にこちらの記事を読んで下さい。

　前回はプログラムがイメージをどうやって保存し、それを表示するのかを実際のコードと共に見てみました。今回はそれをベースにして実際にイメージを色々イジメて(？)みましょう。

#事前準備
**今回使用する画像**
![image.png](https://qiita-image-store.s3.amazonaws.com/0/188095/a1f0926c-ff63-5ed3-96eb-27dc165c5538.png)

**基本となるJAVAコード**

```java:ImageProcessing02.java
package ImageProcessing;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class ImageProcessing02 {
	public static void main(String[] args) {
		BufferedImage original = null;
		try {
			original = ImageIO.read(new File("image.png")); // 編集する画像を持ってくる
		} catch (IOException e) {
			e.printStackTrace();
		}
		int iWidth = original.getWidth();				// 画像の横幅
		int iHeight = original.getHeight();				// 画像の縦幅
		int[] iColor = new int[iWidth * iHeight];		// 横幅と縦幅を元に色情報を保存する配列作成
		int[] iChangeColor = new int[iWidth * iHeight]; // 同じサイズで修正したデータを保存するための配列
		
		// 修正するBufferedImage変数を予め作成する。
		BufferedImage img = new BufferedImage(iWidth, iHeight, BufferedImage.TYPE_4BYTE_ABGR);
		
		// 画像の色情報をgetRGBで持ってくる
		original.getRGB(0, 0, iWidth, iHeight, iColor, 0, iWidth);
		
		/**********************
		 * ここにコードを書きます。  *
		 **********************/

		// 修正した色情報を修正用のBufferedImageにセット
		img.setRGB(0, 0, iWidth, iHeight, iChangeColor, 0, iWidth);
		
		try {
			ImageIO.write(img, "png", new File("保存する画像ファイルの経路")); // 画像を保存
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
```
途中の宣言文の方を変更するケースもありますが、基本はこんな感じ。

#イメージ回転
今回は360°自由な回転ではなく、直角（90°）の回転のみとします。
（自由に回転させるには三角関数がどうしても必要になるため）

**※ 変更されたところだけコメントを残します**

```java:ImageProcessing02_1.java
package ImageProcessing;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class ImageProcessing02_1 {
	public static void main(String[] args) {
		BufferedImage original = null;
		try {
			original = ImageIO.read(new File("image.png"));
		} catch (IOException e) {
			e.printStackTrace();
		}
		int iWidth = original.getWidth();
		int iHeight = original.getHeight();
		int[] iColor = new int[iWidth * iHeight];
		int[] iChangeColor = new int[iWidth * iHeight];
		
		original.getRGB(0, 0, iWidth, iHeight, iColor, 0, iWidth);

		// 状況によって横幅と縦幅が変わるので、とりあえず宣言だけ
		BufferedImage img = null;
		
		/**Image Rotate(Rotate 90)**/
		int iRotate = 90;　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　// 回す角度を設定
		int iRotateS = iRotate / 90;
		int iChangeWidth = 0, iChangeHeight = 0;

		//　横幅と縦幅の設定 (0, 180, 360度 = 0 / 90, 270度 = 1)
		switch(iRotateS % 2) {
		case 0:
			iChangeWidth = iWidth;
			iChangeHeight = iHeight;
			break;
		case 1:
			iChangeWidth = iHeight;
			iChangeHeight = iWidth;
			break;
		}
		img = new BufferedImage(iChangeWidth, iChangeHeight, BufferedImage.TYPE_4BYTE_ABGR);
		int iTargetRow = 0, iTargetCol = 0;
		for(int i = 0; i < iChangeHeight; i++) {
			for(int j = 0; j < iChangeWidth; j++) {
				// 360°以上も対応出来るように残りで確認する
				switch(iRotateS % 4) {
				case 0: // そのまま
					iTargetRow = i;
					iTargetCol = j;
					break;
				case 1: // 90°、画像の左下から上に
					iTargetRow = iChangeWidth - (j + 1);
					iTargetCol = i;
					break;
				case 2: // 180°、画像の右下から左に
					iTargetRow = iChangeHeight - (i + 1);
					iTargetCol = iChangeWidth - (j + 1);
					break;
				case 3: // 270°、画像の右上から下に
					iTargetRow = j;
					iTargetCol = iChangeHeight - (i + 1);
					break;
				}
				
				int iTargetIndex = (iTargetRow * iWidth) + iTargetCol;
				int iChangeIndex = (i * iChangeWidth) + j;
				iChangeColor[iChangeIndex] = iColor[iTargetIndex]; 
			}
		}

		img.setRGB(0, 0, iWidth, iHeight, iChangeColor, 0, iWidth);
		
		try {
			ImageIO.write(img, "png", new File("ImageRotate.png"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
```

座標の割り出し式は前回の記事にあったのと同じです。
今回の記事で一番の肝は座標計算です。元画像のどの色をどこに置くのかが重要です。
直角での回転の場合はどこから初めて、どの方向に読んでいくのかが重要です。
実際の画像を見てみましょう。

**元の画像**
![image.png](https://qiita-image-store.s3.amazonaws.com/0/188095/a1f0926c-ff63-5ed3-96eb-27dc165c5538.png)

**90°回転**
![ImageRotate.png](https://qiita-image-store.s3.amazonaws.com/0/188095/bed67271-9114-5be0-bac7-232712d9d8d4.png)

元の画像と、90°回転させた画像ですが。
ここで重要になるのは一番右上、画像の０番目の色がどこから持ってきた色かです。
90°の場合は右下から右上までの色がそのまま、直線上に並べられています。

他の角度も同じですが、このように角度が変わった時の色の座標を割り出して、
それを変更される座標に合わせて入れればOKです。

#イメージ反転

```java:ImageProcessing02_2.java
package ImageProcessing;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class ImageProcessing02_2 {
	public static void main(String[] args) {
		BufferedImage original = null;
		try {
			original = ImageIO.read(new File("image.png"));
		} catch (IOException e) {
			e.printStackTrace();
		}
		int iWidth = original.getWidth();		
		int iHeight = original.getHeight();		
		int[] iColor = new int[iWidth * iHeight];	
		int[] iChangeColor = new int[iWidth * iHeight]; 
		
		BufferedImage img = new BufferedImage(iWidth, iHeight, BufferedImage.TYPE_4BYTE_ABGR);
		original.getRGB(0, 0, iWidth, iHeight, iColor, 0, iWidth);
		
		boolean isVerticalChange = true; // 上下反転
		boolean isHorizonChange = true; // 左右反転
		for(int i = 0; i < iHeight; i++) {
			for(int j = 0; j < iWidth; j++) {
				int iChangeRow = (isVerticalChange) ? iHeight - ( i + 1 ) : i; // 上下反転なら一番下から上に
				int iChangeCol = (isHorizonChange) ? iWidth - ( j + 1 ) : j;  // 左右反転なら一番右から左に
				int iChangeIndex = (iChangeRow * iWidth) + iChangeCol;
				
				int iTargetIndex = (i * iWidth) + j;
				
				iChangeColor[iChangeIndex] = iColor[iTargetIndex];
			}
		}
		img.setRGB(0, 0, iWidth, iHeight, iChangeColor, 0, iWidth);
		
		try {
			ImageIO.write(img, "png", new File("ImageReverse.png"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
```

先程とほぼ同じです。Booleanで上下反転か左右反転かをチェックして、それによって座標を変えてます。
両方trueにすれば１８０°回転と動作は一緒です。
#イメージの一部を切り取る

```java:ImageProcessing02_3.java
package ImageProcessing;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class ImageProcessing02_3 {
	public static void main(String[] args) {
		BufferedImage original = null;
		try {
			original = ImageIO.read(new File("image.png"));
		} catch (IOException e) {
			e.printStackTrace();
		}
		int iWidth = original.getWidth();		
		int iHeight = original.getHeight();		
		int[] iColor = new int[iWidth * iHeight];	
		original.getRGB(0, 0, iWidth, iHeight, iColor, 0, iWidth);
		
		// 切り取り始める座標
		int iChangeY = 50;
		int iChangeX = 50;
		// 切り取るサイズ
		int iChangeWidth = 100;
		int iChangeHeight = 100;

		// 切り取るサイズを元に配列とBufferedImage変数を宣言する。
		int[] iChangeColor = new int[iChangeWidth * iChangeHeight]; 
		BufferedImage img = new BufferedImage(iChangeWidth, iChangeHeight, BufferedImage.TYPE_4BYTE_ABGR);

		// 切り取るサイズを元にループ
		for(int i = 0; i < iChangeHeight; i++) {
			for(int j = 0; j < iChangeWidth; j++) {
				// 切り取り始める座標から色情報の座標を割り出す。
				int iTargetRow = iChangeY + i;
				int iTargetCol = iChangeX + j;
				int iTargetIndex = (iTargetRow * iWidth) + iTargetCol;
				
				int iChangeIndex = (i * iChangeWidth) + j;
				
				iChangeColor[iChangeIndex] = iColor[iTargetIndex];
			}
		}

		img.setRGB(0, 0, iWidth, iHeight, iChangeColor, 0, iWidth);
		
		try {
			ImageIO.write(img, "png", new File("ImageSlice.png"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
```

画像の一部を切り取りたい場合には、始まる座標を切り取りたい場所の一番左上から
切り取るサイズ分まわせばOKです。
ただ、このままだと画像のサイズを超える範囲までループが回る可能性があるので、それは直してみてください。

#パズルみたいに分割する

```java:ImageProcessing02_4.java
package ImageProcessing;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class ImageProcessing02_4 {
	public static void main(String[] args) {
		BufferedImage original = null;
		try {
			original = ImageIO.read(new File("image.png"));
		} catch (IOException e) {
			e.printStackTrace();
		}
		int iWidth = original.getWidth();		
		int iHeight = original.getHeight();		
		int[] iColor = new int[iWidth * iHeight];	
		original.getRGB(0, 0, iWidth, iHeight, iColor, 0, iWidth);

		int iPieceW = 3; // 横ピース数
		int iPieceH = 4; // 縦ピース数

		// ピース数を元にサイズ設定
		int iChangeWidth = iWidth / iPieceW;
		int iChangeHeight = iHeight / iPieceH;

		// サイズとピース数を元に二次元配列で色情報用配列を宣言
		int[][] iChangeColor = new int[iPieceW * iPieceH][iChangeWidth * iChangeHeight];

		// BufferedImageも配列で宣言
		BufferedImage[] img = new BufferedImage[iPieceW * iPieceH];
		for(int i = 0; i < img.length; i++) {
			img[i] = new BufferedImage(iChangeWidth, iChangeHeight, BufferedImage.TYPE_4BYTE_ABGR);
		}

		for(int i = 0; i < iHeight; i++) {
			int iNowY = i / iChangeHeight; // ピースが何行目か
			if(iNowY >= iPieceH) break;　　　　　// 残りがあって、設定した数以上の座標になった時は抜け出す
			for(int j = 0; j < iWidth; j++) {
				int iNowX = j / iChangeWidth; // ピースが何列目か
				if(iNowX >= iPieceW) continue;　　　　　// 残りがあって、設定した数以上の座標になった時は無視する

				int iChangeRow = i % iChangeHeight;
				int iChangeCol = j % iChangeWidth;
				
				int iTargetIndex = (i * iWidth) + j;
				int iChangeIndex = (iChangeRow * iChangeWidth) + iChangeCol;
				int iChangeKey = iNowY * iPieceW + iNowX;
				
				iChangeColor[iChangeKey][iChangeIndex] = iColor[iTargetIndex];
			}
		}

		img.setRGB(0, 0, iWidth, iHeight, iChangeColor, 0, iWidth);
		
		try {
			for(int i = 0; i < img.length; i++) {
				ImageIO.write(img[i], "png", new File("ImageSlice" + String.format("%03d", i) + ".png"));
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
```

イメージパズルのように画像を均等に分割するコードです。
BufferedImageを配列で宣言して座標をもう一回分けて、分割するサイズに合わせて
Keyを変更するようにできればあとは簡単ですね。

#getGraphics()を利用したイメージ編集

```java:ImageProcessing02_5.java
package ImageProcessing;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class ImageProcessing02_5 {
	public static void main(String[] args) {
		BufferedImage original = null;
		try {
			original = ImageIO.read(new File("image.png"));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		// イメージのGraphicsを持ってくる
		Graphics g = original.getGraphics();
		
		// イメージに色んな色の円を描く。
		g.setColor(Color.RED);
		g.fillOval(70, 70, 100, 100);
		g.setColor(Color.GREEN);
		g.fillOval(20, 120, 100, 100);
		g.setColor(Color.BLUE);
		g.fillOval(70, 170, 100, 100);
		g.setColor(Color.BLACK);
		g.fillOval(120, 120, 100, 100);
		
		try {
			ImageIO.write(original, "png", new File("ImageEdit.png"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
```

**結果**
![ImageEdit.png](https://qiita-image-store.s3.amazonaws.com/0/188095/e08cdd3b-47bd-0df5-bdfc-c01062bece0b.png)

JAVAで支援するGraphicsライブラリーを使って
ライブラリー内のメソットを利用して画像を修正するコードです。
これはRGBコードとか座標とかそう難しく考える必要はないですね。

ただ、複雑な画像処理の機能はついていないので
Graphicsだけではどうしても足りないです。
なので、前のメソットのようにRGBコードや座標を切り替えるだけで色々作れる機能は多いので、
ぜひ、直接作ってみてください。

#終わりに

今回は前の記事で書いた中で一番理解しにくい、
RGBコードの座標の割り出しを使ったコードを幾つか紹介してみましたが、どうでしたか？
これは画像処理でも基本中の基本ではありますが、何事も基本が一番大事です。
しっかりマスターして、皆さんも画像処理の世界に入ってみましょう！