#はじめに
※ この記事は「画像処理:プログラムが読み込むイメージの基本構造」をベースに進めていきます。お読みでない方は先にこちらの記事を読んで下さい。
前回はプログラムがイメージをどうやって保存し、それを表示するのかを実際のコードと共に見てみました。今回はそれをベースにして実際にイメージを色々イジメて(?)みましょう。
基本となる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°)の回転のみとします。
(自由に回転させるには三角関数がどうしても必要になるため)
※ 変更されたところだけコメントを残します
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();
}
}
}
座標の割り出し式は前回の記事にあったのと同じです。
今回の記事で一番の肝は座標計算です。元画像のどの色をどこに置くのかが重要です。
直角での回転の場合はどこから初めて、どの方向に読んでいくのかが重要です。
実際の画像を見てみましょう。
元の画像と、90°回転させた画像ですが。
ここで重要になるのは一番右上、画像の0番目の色がどこから持ってきた色かです。
90°の場合は右下から右上までの色がそのまま、直線上に並べられています。
他の角度も同じですが、このように角度が変わった時の色の座標を割り出して、
それを変更される座標に合わせて入れればOKです。
#イメージ反転
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にすれば180°回転と動作は一緒です。
#イメージの一部を切り取る
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です。
ただ、このままだと画像のサイズを超える範囲までループが回る可能性があるので、それは直してみてください。
#パズルみたいに分割する
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()を利用したイメージ編集
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();
}
}
}
JAVAで支援するGraphicsライブラリーを使って
ライブラリー内のメソットを利用して画像を修正するコードです。
これはRGBコードとか座標とかそう難しく考える必要はないですね。
ただ、複雑な画像処理の機能はついていないので
Graphicsだけではどうしても足りないです。
なので、前のメソットのようにRGBコードや座標を切り替えるだけで色々作れる機能は多いので、
ぜひ、直接作ってみてください。
#終わりに
今回は前の記事で書いた中で一番理解しにくい、
RGBコードの座標の割り出しを使ったコードを幾つか紹介してみましたが、どうでしたか?
これは画像処理でも基本中の基本ではありますが、何事も基本が一番大事です。
しっかりマスターして、皆さんも画像処理の世界に入ってみましょう!