模様生成

昔見かけた記事でプレコの模様を作るプログラムが紹介されてた。
http://www.geekpage.jp/blog/?id=2007/11/7
それをほんのちょっと自分なりに変えて作ってみた。

模様生成の手順
 1. 2次元画像にランダムに色を付ける
 2. 各ピクセルで、そのピクセルを中心とした小円と大円の内側でそれぞれ色の平均値を計算し、
   その差によってそのピクセルの色を新たに決める
 3. 2の操作を何度か繰り返していくと模様が出来ていく
という事らしい。

黒が広範囲にあると、反復操作の結果その中央部分が白くなる。
逆に、白が広範囲にあると、その中央部が黒くなる。
その結果、白と黒がある程度のスケールで偏在した模様が出来る、という理屈。

皮膚の細胞が状態に応じて物質を2種類生成して、拡散&減衰(それぞれの物質の到達距離が大円と小円の半径に相当)して、
さらに周囲から拡散してきた物質の濃度に応じて状態が変化する・・・みたいな状況を考えているんだろう。



プログラム
当然、円の半径が大きくなるほど計算は重くなる。
そこで、今回は円内部の平均値のかわりに全領域でテキトーに重み付け平均した値を使う。
あと、2値の代わりに0〜1の実数を使用。

import java.io.*;
import java.awt.image.*;
import javax.imageio.*;
class GenTex{
	public static void main(String args[])throws Exception{
		int S=512;
		double map[][]=new double[S][S];
		double tmp[][]=new double[S][S];
		double out1[][]=new double[S][S];
		double out2[][]=new double[S][S];
		double param1[][]=genParam(0.8,0.7);//range parameter(bigger)
		double param2[][]=genParam(0.75,0.65);//range parameter(smaller)
		double area1=areaParam(param1);
		double area2=areaParam(param2);
		double patternParam=1.00;//0.99~1.01
		
		if(args.length==1){
			BufferedImage img=ImageIO.read(new File(args[0]));
			int w=img.getWidth(),h=img.getHeight();
			for(int x=0;x<S;x++)for(int y=0;y<S;y++){
				int c=img.getRGB(x*w/S,y*h/S);
				map[x][y]=(0.8*(((c>>16)&0xff)+((c>>8)&0xff)+(c&0xff))/3/0xff+0.2*Math.random());
			}
		}else{
			for(int x=0;x<S;x++)for(int y=0;y<S;y++)map[x][y]=Math.random();
		}
		
		for(int i=0;i<10;i++){
			smooth(map,tmp,out1,S,param1);
			smooth(map,tmp,out2,S,param2);
			for(int x=0;x<S;x++)for(int y=0;y<S;y++){
				double d=patternParam*out2[x][y]/area2-out1[x][y]/area1;
				map[x][y]=(Math.tanh(100*d)+1)/2;
			}
			System.out.println(i);
		}
		
		BufferedImage img=new BufferedImage(S,S,BufferedImage.TYPE_INT_RGB);
		for(int x=0;x<S;x++)for(int y=0;y<S;y++)img.setRGB(x,y,0x10101*(int)(0xff*map[x][y]));
		ImageIO.write(img,"png",new File("out.png"));
	}
	static double areaParam(double[][]p){
		int n=p.length;
		double area=0;
		for(int i=0;i<n;i++)for(int j=0;j<n;j++){
			area+=p[i][1]*p[j][1]/Math.log(p[i][0])/Math.log(p[j][0]);
		}
		return 4*area;
	}
	//force weight(0,0)=1 and d/dx{weight(0,0)}=d/dy{weight(0,0)}=0
	static double[][]genParam(double pa,double pb){
		double pp=(pa*pa-4*pa+3)/(pa-pb)/(4-pa-pb);
		double param[][]={{pa,1+pp},{pb,-pp}};
		return param;
	}
	//average with weight(x,y)=sigma{p[i][1]*pow(p[i][0],|x|)}*sigma{p[i][1]*pow(p[i][0],|y|)}
	static void smooth(double[][]map,double[][]tmp,double[][]out,int S,double p[][]){
		sum(map,tmp,S,p);
		for(int x=0;x<S;x++)for(int y=0;y<S;y++)out[x][y]=tmp[y][x];
		sum(out,tmp,S,p);
		for(int x=0;x<S;x++)for(int y=0;y<S;y++)out[x][y]=tmp[y][x];
	}
	static void sum(double[][]map,double[][]out,int S,double p[][]){
		int n=p.length;
		double tmp[]=new double[n];
		for(int y=0;y<S;y++){
			for(int x=0;x<S;x++)out[x][y]=0;
			for(int i=0;i<n;i++){
				for(int x=0;x<S;x++){
					tmp[i]=tmp[i]*p[i][0]+map[x][y];
				}
				for(int x=0;x<S;x++){
					tmp[i]=tmp[i]*p[i][0]+map[x][y];
					out[x][y]+=tmp[i]*p[i][1];
				}
			}
			for(int i=0;i<n;i++){
				for(int x=S-1;x>=0;x--){
					tmp[i]=tmp[i]*p[i][0]+map[(x+1)%S][y];
				}
				for(int x=S-1;x>=0;x--){
					tmp[i]=tmp[i]*p[i][0]+map[(x+1)%S][y];
					out[x][y]+=tmp[i]*p[i][0]*p[i][1];
				}
			}
		}
	}
}

反復していくと・・・

とまぁこんな感じで収束していく。
処理は結構重い。



プログラムが無駄に長くなったのは技術不足か?
生成した模様とフラクタルノイズを組み合わせてやると、本物っぽい毛皮のテクスチャを自動で作れそうな気がする。