ゆるゆるプログラミング

・ステンドグラス処理

ステンドグラス風画像は、幾何学模様のカラフルなガラスを張って作られたような画像のことです。

元の画像元の画像      ステンドグラス処理後の画像ステンドグラス処理後の画像

変換前の画像から複数のピクセルを選択し、そのピクセルの勢力圏を作っていきます。ここでは、選択したピクセルをコア(核)と呼びます。

具体的には、画像の全ての座標(x,y)から、選択した複数のピクセル(コア)の中で一番近いものを見つけ、そのピクセルの色を座標(x,y)に格納していきます。この処理により、コアを中心とした幾何学模様の色の塗り潰しができ、それがステンドグラスのパーツのように見えます。

これを、Javaソースコードにすると以下のようになります。

StainedGlass.java
001:    import java.awt.image.BufferedImage;
002:    import java.io.File;
003:    import javax.imageio.ImageIO;
004:    import java.io.IOException;
005:    
006:    
007:    // コアの座標と色を格納するクラス
008:    class CoreColor {
009:    	public int x, y;
010:    	public int color;
011:    }
012:    
013:    
014:    public class StainedGlass {
015:    	public static void main( String[] args ) {
016:    		boolean result;	// 結果格納フラグ
017:    		int     corenum;	// コアの数	
018:    
019:    		// ファイル名
020:    		String inname, outname;
021:    		// 画像格納クラス
022:    		BufferedImage img = null;
023:    
024:    		// 入力した引数が3つ以上かを調べる
025:    		if ( 3 > args.length ) {
026:    			// 入力した引数が3つ未満の場合、使用方法を表示する
027:    			System.out.println(
028:    				 "StainedGlass [入力JPEG名]  [出力JPEG名] [コアの数]" );
029:    			return;
030:    		}
031:    
032:    		// 入力JPEG名をinnameに代入(拡張子".jpg"省略なし)
033:    		inname  = args[ 0 ];
034:    		// 出力JPEG名をoutnameに代入(拡張子".jpg"省略なし)
035:    		outname = args[ 1 ];
036:    
037:    		// 引数を変換し、コア数に代入
038:    		try {
039:    			corenum = Integer.valueOf( args[ 2 ] );
040:    			if ( 2 > corenum ) {
041:    				System.out.println( "コア数に2以上を指定してください" );
042:    				return;
043:    			}
044:    		}
045:    		catch( NumberFormatException ne )
046:    		{
047:    			System.out.println( "引数が不正です" );
048:    			return;
049:    		}
050:    
051:    		// JPEGの読み込み
052:    		try {
053:    			// inname(入力JPEG)を読み込んでimgにセット
054:    			img = ImageIO.read( new File( inname ) );
055:    		} catch (Exception e) {
056:    			// inname(入力JPEG)の読み込みに失敗したときの処理
057:    			 e.printStackTrace();
058:    			return;
059:    		}
060:    
061:    		// 画像の色の持ち方をチェック
062:    		if ( BufferedImage.TYPE_3BYTE_BGR != img.getType() )
063:    		{
064:    			System.out.println( "対応していないカラーモデルです!("
065:    									 + inname +")" );
066:    			return;
067:    		}
068:    
069:    		// ステンドグラス画像の作成
070:    		int x, y;
071:    		int width, height;
072:    
073:    		// 画像サイズの取得
074:    		width = img.getWidth();
075:    		height= img.getHeight();
076:    
077:    		// コアを乱数で決める
078:    		CoreColor core[] = new CoreColor[ corenum ];		
079:    		for ( int i = 0; i < corenum; ++ i ) {
080:    			// 乱数を発生し、適当に座標を求める
081:    			core[ i ] = new CoreColor();
082:    			core[ i ].x = (int)( Math.random() * (double)width );
083:    			core[ i ].y = (int)( Math.random() * (double)height );
084:    			core[ i ].color = img.getRGB( core[ i ].x, core[ i ].y );
085:    		}
086:    
087:    	
088:    		// 全ての画素の座標(x,y)から最も近いコアを求め
089:    		//           そのコアの色を(x,y)に置く
090:    		for ( y = 0; y < height; ++ y ) {
091:    			for ( x = 0; x < width; ++ x ) {
092:    				int minl = 10000000;
093:    				int newcolor = 0;
094:    
095:    				// 一番近いコアを求める
096:    				for ( int i = 0; i < corenum; ++ i ) {
097:    					// 距離の2乗を計算
098:    					int dx = core[ i ].x - x;
099:    					int dy = core[ i ].y - y;
100:    					int l  = dx * dx + dy * dy;
101:    
102:    					if ( l < minl ) {
103:    						minl = l;
104:    						newcolor = core[ i ].color;
105:    					}
106:    				}
107:    				img.setRGB( x, y, newcolor );
108:    			}
109:    		}
110:    
111:    		try {
112:    			// imgをoutname(出力JPEG)に保存
113:    			result = ImageIO.write( img, "jpeg", new File( outname ) );
114:    		} catch ( Exception e ) {
115:    			// outname(出力JPEG)の保存に失敗したときの処理
116:    			e.printStackTrace();
117:    			return;
118:    		}
119:    
120:    		// 正常に終了
121:    		System.out.println( "正常に終了しました" );
122:    	}
123:    }

StainedGlassを実行

>java StainedGlass sampleimage004_400x300.jpg stainedglass-100.jpg 100

1つ目の引数で元のJPEGファイル名、2つ目の引数に作成するJPEG画像ファイル名、3つ目にコアの数を指定します。

実行結果

・元の画像(sampleimage004_400x300.jpg)

元画像

・コア数=100で作成した画像(stainedglass-100.jpg)

ステンドグラス画像(core=100)ステンドグラス画像 コア数:100

・コア数=500で作成した画像(stainedglass-500.jpg)

ステンドグラス画像(core=500)ステンドグラス画像 コア数:500

・コア数=1000で作成した画像(stainedglass-1000.jpg)

ステンドグラス画像(core=1000)ステンドグラス画像 コア数:1000

・コア数=5000で作成した画像(stainedglass-5000.jpg)

ステンドグラス画像(core=5000)ステンドグラス画像 コア数:5000

・コア数=10000で作成した画像(stainedglass-10000.jpg)

ステンドグラス画像(core=10000)ステンドグラス画像 コア数:10000

・コア数=50000で作成した画像(stainedglass-50000.jpg)

ステンドグラス画像(core=50000)ステンドグラス画像 コア数:50000

コア数が多いほど、見た目が元の画像に近づきます。

ここからは、このJavaソースコードを上から順番に解説していきます。

001:    import java.awt.image.BufferedImage;
002:    import java.io.File;
003:    import javax.imageio.ImageIO;
004:    import java.io.IOException;

Javaのクラスライブラリの中から「java.awt.image.BufferedImage」と「java.io.File」と「javax.imageio.ImageIO」と「java.io.IOException」と「java.awt.image.RasterFormatException」というパッケージにあるクラスを、このプログラム内で使うために記述します。 この記述により、ImageIOクラスBufferedImageクラスRasterFormatExceptionが利用できるようになります。

007:    // コアの座標と色を格納するクラス
008:    class CoreColor {
009:    	public int x, y;
010:    	public int color;
011:    }

ステンドグラスの色を決めるコア(核)となる情報を格納するクラスを定義しています。

014:    public class StainedGlass {

クラス名を、StainedGlassとしています。

015:    	public static void main( String[] args ) {

このmainメソッドからプログラムを実行します。

016:    		boolean result;	// 結果格納フラグ
017:    		int     corenum;	// コアの数	
018:    
019:    		// ファイル名
020:    		String inname, outname;
021:    		// 画像格納クラス
022:    		BufferedImage img = null;

このプログラムで使う変数を宣言しています。

024:    		// 入力した引数が3つ以上かを調べる
025:    		if ( 3 > args.length ) {
026:    			// 入力した引数が3つ未満の場合、使用方法を表示する
027:    			System.out.println(
028:    				 "StainedGlass [入力JPEG名]  [出力JPEG名] [コアの数]" );
029:    			return;
030:    		}

3つ以上の引数が与えられたかをチェックし、3つ未満の場合に、使い方のメッセージを表示し、returnによってmainメソッドを抜けています。

032:    		// 入力JPEG名をinnameに代入(拡張子".jpg"省略なし)
033:    		inname  = args[ 0 ];
034:    		// 出力JPEG名をoutnameに代入(拡張子".jpg"省略なし)
035:    		outname = args[ 1 ];
036:    
037:    		// 引数を変換し、コア数に代入
038:    		try {
039:    			corenum = Integer.valueOf( args[ 2 ] );
040:    			if ( 2 > corenum ) {
041:    				System.out.println( "コア数に2以上を指定してください" );
042:    				return;
043:    			}
044:    		}
045:    		catch( NumberFormatException ne )
046:    		{
047:    			System.out.println( "引数が不正です" );
048:    			return;
049:    		}

与えられた引数をそれぞれ、入力JPEGファイル名、出力JPEGファイル名、コアの数を格納する変数に代入しています。指定したコアの数が2未満の場合、returnによってmainメソッドを抜けています。

051:    		// JPEGの読み込み
052:    		try {
053:    			// inname(入力JPEG)を読み込んでimgにセット
054:    			img = ImageIO.read( new File( inname ) );
055:    		} catch (Exception e) {
056:    			// inname(入力JPEG)の読み込みに失敗したときの処理
057:    			 e.printStackTrace();
058:    			return;
059:    		}

入力JPEG名の変数(inname)を読み込んで、BufferedImageクラスのimgに格納しています。この処理には、ImageIOクラスreadメソッドを使います。

ImageIO.readメソッド

public static BufferedImage read( File input ) throws IOException
■Fileオブジェクトを復元した結果をBufferedImageに格納します。

  パラメータ input : Fileオブジェクト

  戻り値     inputを復元したBufferedImageaを返します。

try { ~ } catchは、失敗する可能性がある処理を波括弧で囲み、その処理に失敗したときにcatch { ~ }の波括弧で囲まれた処理を実行するということです。この場合は、JPEGファイル名が不正であったり、存在していなかったり、フォーマットが違っているなどが原因で処理が失敗する可能性があります。処理が失敗するとreturnによってmainメソッドを抜けるようにしています。

061:    		// 画像の色の持ち方をチェック
062:    		if ( BufferedImage.TYPE_3BYTE_BGR != img.getType() )
063:    		{
064:    			System.out.println( "対応していないカラーモデルです!("
065:    									 + inname +")" );
066:    			return;
067:    		}

BufferedImageクラスgetTypeメソッドで画像のイメージ型を取得しています。

BufferedImage.getTypeメソッド

public static int getType()
■イメージ型を返します。
  パラメータ なし

  戻り値     BufferedImage のイメージ型を返します。

069:    		// ステンドグラス画像の作成
070:    		int x, y;
071:    		int width, height;

ステンドグラス作成処理で使う変数を宣言しています。

073:    		// 画像サイズの取得
074:    		width = img.getWidth();
075:    		height= img.getHeight();

widthに画像の幅(ピクセル)、heightに画像の高さ(ピクセル)を代入しています。

077:    		// コアを乱数で決める
078:    		CoreColor core[] = new CoreColor[ corenum ];		
079:    		for ( int i = 0; i < corenum; ++ i ) {
080:    			// 乱数を発生し、適当に座標を求める
081:    			core[ i ] = new CoreColor();
082:    			core[ i ].x = (int)( Math.random() * (double)width );
083:    			core[ i ].y = (int)( Math.random() * (double)height );
084:    			core[ i ].color = img.getRGB( core[ i ].x, core[ i ].y );
085:    		}

CoreColorクラス配列coreを用意し、その中に乱数で作成した画像の座標(x,y)とその位置の色を格納しています。コア数が多くなるほど、同じ座標をcore配列に入れる確率が高くなりますが、本プログラムでは同じ座標でもそのまま格納しています。例えば、指定したコア数が3で、そのうち2つが同じ座標であった場合には、作成された画像のコアが2つしかないように見えます。

090:    		for ( y = 0; y < height; ++ y ) {
091:    			for ( x = 0; x < width; ++ x ) {

画像の中の全てのピクセルの座標を参照するループをつくっています。具体的には、変数yを0~height-1、変数xを0~width-1に変化させています。

092:    				int minl = 10000000;
093:    				int newcolor = 0;
094:    
095:    				// 一番近いコアを求める
096:    				for ( int i = 0; i < corenum; ++ i ) {
097:    					// 距離の2乗を計算
098:    					int dx = core[ i ].x - x;
099:    					int dy = core[ i ].y - y;
100:    					int l  = dx * dx + dy * dy;
101:    
102:    					if ( l < minl ) {
103:    						minl = l;
104:    						newcolor = core[ i ].color;
105:    					}
106:    				}

ピクセル座標(x,y)から一番近いコアの座標を求めています。求めた結果、そのコアの色をnewcolorに格納しています。本プログラムでは(x,y)からコアの座標までの距離をピタゴラスの定理を使って計算していますが、距離の比較には、距離の2乗を使っています。2点間の距離を計算するには以下の図のように、xとyの変化量dxとdyの2乗の和をとって、その平方根を計算する必要があります。ただし、この処理の場合は実際の距離は使用する必要がなく、距離の比較が出来ればよいので平方根の計算は省略して距離の2乗で比較しています。これにより、少しでも処理時間を短くしようとしています。

2点間の距離計算

107:    				img.setRGB( x, y, newcolor );

最近傍のコアの色(変数newcolor)をnewimgの(x,y)に代入しています。

111:    		try {
112:    			// imgをoutname(出力JPEG)に保存
113:    			result = ImageIO.write( img, "jpeg", new File( outname ) );
114:    		} catch ( Exception e ) {
115:    			// outname(出力JPEG)の保存に失敗したときの処理
116:    			e.printStackTrace();
117:    			return;
118:    		}

BufferedImageクラスのnewimgのメモリ内のデータを、出力JPEG名の変数(outname)に格納されているファイル名で保存します。この場合は、JPEGファイル名が不正であったり、保存先のHDDなどが存在していなかったり、空き容量が少ないなどが原因で処理が失敗する可能性があります。

ImageIO.writeメソッド

public static boolean write( RenderedImage im, String formatName, File output ) throws IOException
■BufferedImageを画像ファイルに保存します。

  パラメータ RenderedImage : 保存するRenderedImage
                  formatName     : 画像ファイルのフォーマット(png/jpeg/bmp/gifなど)
                  output             : Fileオブジェクト

  戻り値     保存に成功するとtrue、失敗するとfalseを返します。

120:    		// 正常に終了
121:    		System.out.println( "正常に終了しました" );

全ての処理が正常終了すると、ここまで処理が実行されます。

■関連コンテンツ

2点間の距離 2点間の距離計算
光と色の3原色 光と色の3原色の考え方を解説
画像の色 画像の色について解説
画像ファイル形式 画像ファイル形式について解説
for文 繰り返し処理に使用するfor文について解説
コマンドライン引数 外部からの値を受け取る方法について解説

■新着情報

2017.11.17 N値化 カラー画像をN値化する方法について解説
2017.11.16 最も近い値の取得 指定値に最も近い配列の値を取得する方法を解説
2017.10.02 アルファ値(透過) アルファ値(透過)について

■広告

法人向けのETC専用カード

~約8,000名の受講生と80社以上の導入実績~ 企業向けプログラミング研修ならCodeCamp

日本最大級ショッピングサイト!お買い物なら楽天市場

Topへ