スポンサーサイト

Tags :
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Flashで画像の色の分布を3Dグラフにプロットしてみる

同じネタで引っ張ります。

以前のエントリで、画像の色(『L*a*b*』値)の分布をPhotoshop&JavaScriptで書き出して、それをMacOSX付属の『Grapher』に読み込ませて3D空間にプロットするってのをやりましたが、今回はActionScriptの勉強も兼ねて、Flashで3Dの分布図を作成してみました。3Dの表示には定番のライブラリ『Papervision3D』を利用しています。

PV3Dのコードを書くに当たり、『ClockMaker』さんの一連のエントリ、『フレームアクションで覚える Papervision3D チュートリアル』が非常に参考になりました。

以下が今回作ったFlash。マウスでぐりぐり動かせます。(ホントは『wonderfl』のアカウント取ったんでそっちを貼りたかったんですが、現在ログイン出来なくなってる様なので生のswf貼ります。)


画像の『L*a*b*値』のデータは、以前のエントリのJavaScriptで書き出したものを外部ファイルとしてFlashから読み込んで表示する様になっているので、それを変える事によって色々な画像の色の分布が表示出来ます。

例えば、デフォルトで読み込まれるデータは、AbobeRGBの色域のグラデーションの『L*a*b*』値なんですが、実際に読み込んでるデータはこれになります。

次の画像の『L*a*b*値』を書き出したファイルはこれなんですが、このファイルをダウンロードして、『Load L*a*b* coordinates』ボタンで読み込ませると下の画像の様な表示になります。



一連のエントリの出発点が、『Photoshopでのカラーマネジメントが効いた状態の色をプロットする』という所から始まっているので、画像を直接読み込むのではなく(Flashでカラープロファイルが扱えれば良いんですが)わざわざ座標を書き出してから読み込んで、それを元のRGBに戻すなんて事をしています。他のエントリのJavaScript等もそうなんですが、実用というよりは、『思い付いたものを形に出来るか?』といったアプローチで、訓練としてやってる様なものが多いですね。

今回のActionScriptのソースは以下の通りです。FlashPlayer10で動作確認しています。
package
{	
	import flash.net.*;
	import flash.events.*;
	import flash.text.*;
	import flash.ui.Mouse;
	import flash.system.Security;
	import org.papervision3d.view.*
	import org.papervision3d.objects.*;
	import org.papervision3d.materials.*
	import org.papervision3d.objects.primitives.*
	import org.papervision3d.core.geom.*;
	import org.papervision3d.core.geom.renderables.*;
	import org.papervision3d.materials.special.ParticleMaterial;
	import org.papervision3d.objects.special.ParticleField;
	import org.papervision3d.materials.utils.MaterialsList;
	import net.hires.debug.Stats;
	
	
	[SWF(width = "500", height = "400", frameRate = "30", backgroundColor = "0x000000")]
	
	public class PlotLab extends BasicView
	{

		private var world:BasicView = new BasicView();
		private var file:FileReference = new FileReference();
		private var pChips:Particles = new Particles("pChips");
		private var isMouseDown:Boolean = false;
		private var oldX:Number = 0;
		private var oldY:Number = 0;
		private var nowX:Number = 0;
		private var nowY:Number = 0;
		private var targetRot:Number = 180;
		private var targetPitch:Number = 0;	
		private var rot:Number = 0;
		private var pitch:Number = 0;
		private var text1:TextField = new TextField();
		private var lab_colors:Array = [];
		private var mes1:String = 'Load L*a*b* coordinates';
		private var mes2:String = '...Loading';
		
		public function PlotLab():void {
			
			// via http://5ivestar.org/blog/2008/12/wonderfl-webproxy/ 
			// Thanks!!
			Security.loadPolicyFile("http://5ivestar.org/proxy/crossdomain.xml");
			
			addChild(new Stats({bg: 0x000000, fps: 0xC0C0C0, ms: 0x505050, mem: 0x707070, memmax: 0xA0A0A0}));
			world.startRendering();
			addChild(world);
		
			var loader:URLLoader = new URLLoader();
			loader.addEventListener(Event.COMPLETE, completeHandler);
			
			//by default, loading the coordinates of a fullcolor image on AdobeRGB   
			loader.load(new URLRequest("http://5ivestar.org/proxy/http://files.getdropbox.com/u/271700/gamut_AdobeRGB.txt"));	
			
			file.addEventListener(Event.SELECT, selectHandler);
			file.addEventListener(Event.COMPLETE, onFileLoad);
			
			stage.addEventListener(Event.ENTER_FRAME, enterFarme);
			stage.addEventListener(MouseEvent.MOUSE_DOWN, downHandler);
			stage.addEventListener(MouseEvent.MOUSE_UP, upHandler);
			stage.addEventListener(MouseEvent.MOUSE_MOVE, moveHandler);
			
			makeWorld();
		}
		
		
		private function completeHandler(event:Event):void {
			lab_colors = event.target.data.split('\r');
			makeParticles();
		}
		
		
		private function makeParticles():void {
			
			pChips.removeAllParticles();
			
			text1.text = mes1;
			var coordinates:Array = [];
			var rgb:Object = {};
			var xyz:Object = {};
			var plot_color:String;
			
			for (var i:Number = 0; i < lab_colors.length-1; i++) {
				coordinates = lab_colors[i].split('\t');
				xyz = lab2xyz(coordinates[2], coordinates[0], coordinates[1]);
				rgb = xyz2rgb(xyz.x, xyz.y, xyz.z);
				plot_color = toHexRgb(rgb);
				var particleMat:ParticleMaterial = new ParticleMaterial(parseInt(plot_color), 1)
				var pt:Particle = new Particle(particleMat, 5, coordinates[0]*5, (coordinates[2]-50)*5, coordinates[1]*5);
				pChips.addParticle(pt);
			}
			
		}
		
		
		private function enterFarme(e:Event):void {						  
				// easing: (target - current) * deceleration
				rot += (targetRot - rot) * 0.05;
				pitch += (targetPitch - pitch) * 0.05;
				
				pitch = Math.max(pitch, -90);
				pitch = Math.min(pitch, 90);
				
				world.camera.x = 1000 * Math.sin(rot * Math.PI / 180);
				world.camera.z = 1000 * Math.cos(rot * Math.PI / 180);
				world.camera.y = 1000 * Math.sin(pitch * Math.PI / 180);
				//rot += 1.5;
			}
	
	
		private function downHandler(e:MouseEvent):void {
			isMouseDown = true;
			oldX = mouseX;
			oldY = mouseY;
		}
		
	
		private function upHandler(e:MouseEvent):void {
			isMouseDown = false;
		}
		
	
		private function moveHandler(e:MouseEvent):void {
			if(isMouseDown){
				var dx:Number = e.stageX - oldX;
				var dy:Number = e.stageY - oldY;
				
				targetRot += dx * 0.5;
				targetPitch += dy * 0.5;
				
				oldX = e.stageX;
				oldY = e.stageY;
			}
		}
		
		
		private function makeWorld():void {
			
			text1.addEventListener(MouseEvent.CLICK, onClickLoadButton);
			text1.addEventListener(MouseEvent.MOUSE_OVER, onOverLoadButton);
			text1.addEventListener(MouseEvent.MOUSE_OUT, onOutLoadButton);
			text1.type = TextFieldType.DYNAMIC;
			text1.width = 150;
			text1.height = 30;
			text1.x = 350;
			text1.y = 350;
			text1.textColor = 0xFFFFFF;
			text1.autoSize = TextFieldAutoSize.CENTER;
			text1.border = true;
			text1.borderColor = 0xFFFFFF;
			text1.text = mes2;
			addChild(text1);
		
			var line_mat = new WireframeMaterial(0xAAAAAA);
			line_mat.doubleSided = true;	
			var line_a = new Plane(line_mat, 1, 1000, 1, 1);
			world.scene.addChild(line_a);
			line_a.y = -250;
			line_a.rotationZ = 90;
			var line_b = new Plane(line_mat, 1, 1000, 1, 1);
			world.scene.addChild(line_b);
			line_b.y = -250;
			line_b.rotationX = 90;
			world.scene.addChild(pChips);
		
		}
		
		
		private function onOverLoadButton(e:Event):void {
			Mouse.cursor = flash.ui.MouseCursor.BUTTON;
		}
		
		private function onOutLoadButton(e:Event):void {
			Mouse.cursor = flash.ui.MouseCursor.ARROW;
		}
		
		
		private function onClickLoadButton(e:Event):void {
			file.browse();
		}
		
		
		private function selectHandler(e:Event):void {
			file.load();
			text1.text = mes2;
		}
		
		private function onFileLoad(e:Event):void {
			targetRot = 180;
			targetPitch = 0;
			lab_colors = file.data.toString().split('\r');
			text1.text = mes1;
			makeParticles();
		}
		
		private function toHexRgb(rgb:Object):String {
			var r:String = rgb.r.toString(16);
			var g:String = rgb.g.toString(16);
			var b:String = rgb.b.toString(16);
			if (r.length == 1) r = '0' + r;
			if (g.length == 1) g = '0' + g;
			if (b.length == 1) b = '0' + b;
			var ret:String = '0x' + r + g + b;
			return ret;
		}
		
		
		private function lab2xyz( l:Number, a:Number, b:Number ):Object {
			const REF_X:Number = 95.047; // Observer= 2digrees Illuminant= D65
			const REF_Y:Number = 100.000; 
			const REF_Z:Number = 108.883; 
			var y:Number = (l + 16) / 116;
			var x:Number = a / 500 + y;
			var z:Number = y - b / 200;
			if ( Math.pow( y , 3 ) > 0.008856 ) { y = Math.pow( y , 3 ); }
			else { y = ( y - 16 / 116 ) / 7.787; }
			if ( Math.pow( x , 3 ) > 0.008856 ) { x = Math.pow( x , 3 ); }
			else { x = ( x - 16 / 116 ) / 7.787; }
			if ( Math.pow( z , 3 ) > 0.008856 ) { z = Math.pow( z , 3 ); }
			else { z = ( z - 16 / 116 ) / 7.787; }
			var xyz:Object = {x:0, y:0, z:0};
			xyz.x = REF_X * x;  
			xyz.y = REF_Y * y;
			xyz.z = REF_Z * z;
		 
			return xyz;
		}
		
		
		private function xyz2rgb(X:Number, Y:Number, Z:Number):Object {
			var x:Number = X / 100;        
			var y:Number = Y / 100;        
			var z:Number = Z / 100;        
			var r:Number = x * 3.2406 + y * -1.5372 + z * -0.4986;
			var g:Number = x * -0.9689 + y * 1.8758 + z * 0.0415;
			var b:Number = x * 0.0557 + y * -0.2040 + z * 1.0570;
		 
			if ( r > 0.0031308 ) { r = 1.055 * Math.pow( r , ( 1 / 2.4 ) ) - 0.055; }
			else { r = 12.92 * r; }
			if ( g > 0.0031308 ) { g = 1.055 * Math.pow( g , ( 1 / 2.4 ) ) - 0.055; }
			else { g = 12.92 * g; }
			if ( b > 0.0031308 ) { b = 1.055 * Math.pow( b , ( 1 / 2.4 ) ) - 0.055; }
			else { b = 12.92 * b; }
			var rgb:Object = {r:0, g:0, b:0}
			var tmp_r = Math.min(r*255, 255);
			var tmp_g = Math.min(g*255, 255);
			var tmp_b = Math.min(b*255, 255);
			rgb.r = Math.max(tmp_r, 0);
			rgb.g = Math.max(tmp_g, 0);
			rgb.b = Math.max(tmp_b, 0);
			return rgb;
		}
	
	}
}
スポンサーサイト

画像の色の分布を『xy色度図』にプロットしてみる(あと、CMYKプロファイルの事も少し)

MacOSX付属の『Grapher』を使った画像の色の分布図の作成その2。

今回は、カラーマネジメント関係の書籍等には必ず出てくる、馬蹄形の『xy色度図』上の座標に、画像の色の分布をプロットしてみます。

前のエントリではPhotoshopで画像を開き、JavaScriptを使ってサンプリングしたピクセルの『L*a*b*値』をファイルに書き出して、そのファイルをGrapherに読み込ませて表示させましたが、今回はピクセルの色を『xyY』の値で書き出すので、『L*a*b*』から『xyY』の値に変換する部分をJavaScriptに新たに書き加えました。

今回は色度を表す『xy』の二次元のグラフなので、明度を示す『Y』の値は使いません。

『L*a*b*』から『xyY』ヘの変換式は、
Color Conversion Algorithms 』(http://www.cs.rit.edu/~ncs/color/t_convert.html)や、
Color Conversion Library in ANSI C』 (http://www.tecgraf.puc-rio.br/~mgattass/color/ColorIndex.html)
あたりをを参考にしています。

PhotoshopのAdobeRGB環境で下のグラデーデョン画像を開き、JavaScriptを実行して書き出されたファイルをGrapherで読み込んで、色度図にプロットしてみます。


赤と緑の三角形の部分はそれぞれ『AdobeRGB』と『sRGB』の色域を表していますが、それらは色度図上の値をあらかじめ調べておいてグラフに数値を入力しました。また、グレーの馬蹄形の部分、360nm~830nmの各波長の単色光の色度を示す『スペクトル軌跡』も別途計算して求めた数値を読み込ませてプロットしてあります。


上のグラデーション画像を『JapanColor2001corted』のプロファイル、レンダリングインテントが『知覚的』の設定でCMYKに変換した場合の色の分布を見てみると、色域が以下の様に圧縮されているのが解ります。



次に、下の画像は始めからCMYKモードで作成した、CMYK各色100%と、その二次色のレッド、グリーン、ブルーをグラデーションにしたもので、


これを『JapanColor2001corted』で開いた状態でプロットしてみると、非常になめらかな色の分布を持つグラフがプロットされました。


同じ画像を、『某印刷所の本機(枚葉)の印刷物を測色して作成したプロファイル』を適用した状態でプロットすると、

ややいびつな輪郭でプロットされました。グリーン(上の頂点の部分)の色域がやや狭く、イエロー方向に転んでいるのが見て取れます。

ただ、これはいびつなプロファイルが悪いと言うのではなく、あくまで『プロファイル』として実際の印刷物の状態を表したものなので、特に、JapanColorをターゲットとした『印刷の標準化』が行われていない印刷会社では、おそらく色が合わない等の問題も出てくる筈なので、必ずしも『JapanColor2001corted』の方が良い結果になるとは言い切れないと思います。

今回プロットした画像を見て思ったんですが、『JapanColor2001corted』の不自然なほど整ったプロファイルは、『理想的なJapanColor』をターゲットにして最適化の処理がされた、『架空のプロファイル』に近いものの様な気がします。

カラーマネジメントの書籍等では『CMYKのカラー設定はJapanColor2001corted推奨』というのが殆どで、もちろん出鱈目なプロファイルで運用する事に比べればベストに近い選択という事で異論は無いんですが、画像をCMYK分解する時点ではトーンジャンプも無くキレイに分解されていても、後の工程でRIPにデバイスリンクプロファイルを噛ましたり、刷版や印刷で色を調整する必要が出るとすれば、『画像が劣化する要因が他の工程に移っただけ』とも考えられるので、その辺りもツッ込んで解説してあればなぁとか思った次第です。(画像の段階で劣化しないのが望ましいとは思いますが)

画像を『xyY』の数値で書き出すのに使ったJavaScriptは以下の通り。

■動作確認
MacOSX 10.5.7
Photoshop CS4
#target Photoshop

var samplingPixelsAsYxy = {
	
	'tristimulus_values': {
		/* 2 degrees */
		'ccTA_2': [109.850, 100.000, 35.585],
		'ccTC_2': [98.074,  100.000, 118.232],
		'D50_2': [96.422,  100.000, 82.521],
		'D55_2': [95.682,  100.000, 92.149],
		'D65_2': [95.047,  100.000, 108.883],
		'D75_2': [94.972,  100.000, 122.638],
		'F2_2': [99.187,  100.000, 67.395],
		'F7_2': [95.044,  100.000, 108.755],
		'F11_2': [100.966, 100.000, 64.370],

		/* 10 degrees */
		'A_10': [111.144, 100.000, 35.200],
		'C_10': [97.285,  100.000, 116.145],
		'D50_10': [96.720,  100.000, 81.427],
		'D55_10': [95.799,  100.000, 90.926],
		'D65_10': [94.811,  100.000, 107.304],
		'D75_10': [94.416,  100.000, 120.641],
		'F2_10': [103.280, 100.000, 69.026],
		'F7_10': [95.792,  100.000, 107.687],
		'F11_10': [103.866, 100.000, 65.627],
	
		/* PCS illuminant of AdobeRGB & sRGB profiles */
		'PCS_illuminant': [96.420, 100.000, 82.491],
		
		/* reference white in xyY coordinates */
		'chromaD65': [0.3127, 0.3290, 100.0]
	},


	'convertCIELabToXYZ': function(L, a, b) {
		var ref = this.tristimulus_values['PCS_illuminant']; //tristimulus of reference white
		var X, Y, Z;
		
		/* via http://www.cs.rit.edu/~ncs/color/API_JAVA/XYZSet.java
		var frac = (L + 16) / 116;
		if ( L < 7.9996 ) {
			Y = L / 903.3;
			X = a / 3893.5 + Y;
			Z = Y - b / 1557.4;
		} else {
			var tmp = frac + a / 500;
			X = tmp * tmp * tmp * ref[0];
			Y = frac * frac * frac * ref[1];
			tmp = frac - b / 200;
			Z = tmp * tmp * tmp * ref[2];
		}
		*/
		
		/* via http://www.tecgraf.puc-rio.br/~mgattass/color/CIELabtoXYZ.htm */
		var var_Y = ( L + 16 ) / 116;
		var var_X = a / 500 + var_Y;
		var var_Z = var_Y - b / 200;
		var_Y = Math.pow(var_Y, 3) > 0.008856 ? Math.pow(var_Y, 3) : ( var_Y - 16 / 116 ) / 7.787;
		var_X = Math.pow(var_X, 3) > 0.008856 ? Math.pow(var_X, 3) : ( var_X - 16 / 116 ) / 7.787;
		var_Z = Math.pow(var_Z, 3) > 0.008856 ? Math.pow(var_Z, 3) : ( var_Z - 16 / 116 ) / 7.787;
		X = var_X * ref[0];
		Y = var_Y * ref[1];
		Z = var_Z * ref[2];
		//
		return [X, Y, Z];
	},

	'main': function() {
		var t = new Date();
		var outputFile = '~/Desktop/xy_colors_64x64.txt';
		var sampling_size = 64;
		//var threshold = 10;
		var sampling_data = '';
		var ru = app.preferences.rulerUnits;
		app.preferences.rulerUnits = Units.PIXELS;
		var doc = app.activeDocument;
		doc.flatten();
		doc.resizeImage(sampling_size, sampling_size, 72, ResampleMethod.BICUBIC);
		//doc.resizeImage(sampling_size*2, sampling_size*2, 72, ResampleMethod.NEARESTNEIGHBOR);
		
		var smpl = doc.colorSamplers.add([0, 0]);
		var col = new SolidColor();
		var Yxy_x, Yxy_y;
		var XYZ = [];
		var X, Y, Z;
		for (var i=0; i<sampling_size; i++) {
			for (var j=0; j<sampling_size; j++) {
				smpl.move([j+0.5, i+0.5]);
				col = smpl.color;
				XYZ = this.convertCIELabToXYZ(col.lab.l, col.lab.a, col.lab.b);
				X = XYZ[0]; Y = XYZ[1]; Z = XYZ[2];
				Yxy_x = X / ( X + Y + Z );
				Yxy_y = Y / ( X + Y + Z );
				if (Y > 1) { sampling_data += Yxy_x + '\t' + Yxy_y + '\r'; }
			}
		}
		
		var f = new File(outputFile);  
		if (f.open("w")) {  
			f.encoding = 'BINARY';  
			f.write(sampling_data);
			f.close();
		}
		app.preferences.rulerUnits = ru;
		var now = new Date();
		alert('Finished!   time: ' + (now - t));
	}
}

if (app.documents.length > 0) {
	samplingPixelsAsYxy.main();
}

画像の色の分布を3Dグラフにプロットしてみる

MacOSXに付属しているグラフ作成アプリ『Grapher』を使って、画像の色(L*a*b*値)の分布を3Dのグラフにプロットしてみました。

この手の情報の表示は、高価なカラーマネジメントツールを買うとか、年間18万9千円の会費を払って研究会に参加すれば実現できると思いますが、手持ちのツールを使えばまあそこそこ近い事は出来るのではないかと思いやってみました。先日のエントリ、『PhotoshopでのCMYK分解カーブをJavaScriptを使ってプロットしてみる』を書いたのも、そんなふうに思ったからです。

まず、JavaScriptを使ってPhotoshopで開いている画像を64 x 64ピクセルに縮小し、その4096のポイントをサンプリングしてそれぞれのL*a*b*値をタブ区切りのテキストでファイルに書き出します。そしてそれをGrapherで読み込んで、3Dのグラフにプロットする様にしました。

Photoshopのカラー設定でAdobeRGBが設定された状態で、下のグラデーション画像のL*a*b*値を書き出してプロットすると、



AdobeRGBの色域一杯に色が均等に分布したグラフが現れました。

このグラフは横軸にa*チャンネル(-128~128)、縦軸がb*チャンネル(-128~128)、垂直のZ軸がL*チャンネル(0~100)になっています。

同じ画像がsRGBの色空間だと、

こんな感じになります。(比較の為にAdobeRGBのプロットを表示しています)

そして下の画像はAdobeRGBのカラースペースで開いてプロットしましたが、色の分布はsRGBの空間に収まっているようです。画像を保存するまでの過程でプロファイル変換されているのかも知れません。




Photoshop&JavaScriptでピクセルの色を取得してファイルに書き出す所で、CMYK分解カーブのプロットの時と同じく、ピクセル単位での操作が今ひとつ解らず、色を取得するのに1ピクセルの選択範囲を作ってその中のヒストグラムが立った数値を調べるという回りくどい事をしています。(なので処理は劇遅。カラーサンプラーを置いて取得するのも試みましたが、さらに遅かったです)

あと、Photpshopで色を扱う、『SolidColor』オブジェクトは、RGBなど1つのカラーモードの値を与えてやれば、後はカラー設定の作業用スペースに設定されたプロファイルに応じて、CMYKやL*a*b*の値が一意に決まるので簡単に他のカラーモードでの値を取り出せるみたいです。ただ、ドキュメントのカラープロファイルは無視される様なので注意が必要です。

■2009.06.06追記
ちょっとやっつけだったコードを直しました。ピクセルの色の取得にカラーサンプラを使う様に変更してあります。ほんの少しだけですが速くなりました。


■2010.03.08追記
このエントリで使用している『Grapher』のファイルをアップロードしました。以下のリンクから落とせます。
http://dl.dropbox.com/u/271700/cielab.gcx.zip


■画像のL*a*b*値を書き出すJavaScriptのソース(遅いです)
#target Photoshop

/*
function getPixelColor(x, y) {
	var hg;
	doc.selection.select([[x, y], [x + 1, y], [x + 1, y + 1], [x, y + 1]], SelectionType.REPLACE, 0, false);
	hg = doc.channels[0].histogram;
	for (var i=0; i<256; i++) if (hg[i] != 0) { colObj.rgb.red = i; break; }
	hg = doc.channels[1].histogram;
	for (var i=0; i<256; i++) if (hg[i] != 0) { colObj.rgb.green = i; break; }
	hg = doc.channels[2].histogram;
	for (var i=0; i<256; i++) if (hg[i] != 0) { colObj.rgb.blue = i; break; }
}
*/

var t = new Date();
var sampling_size = 64;
var ru = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.PIXELS;
var doc = app.activeDocument;
doc.flatten();
doc.resizeImage(sampling_size, sampling_size, 72, ResampleMethod.BICUBIC);
doc.resizeImage(sampling_size*2, sampling_size*2, 72, ResampleMethod.NEARESTNEIGHBOR);

var smpl = doc.colorSamplers.add([0, 0]);
var sampling_data = '';

for (var i=0; i<sampling_size*2; i+=2) {
	for (var j=0; j<sampling_size*2; j+=2) {
		smpl.move([j+1, i+1]);
		sampling_data += smpl.color.lab.a + '\t' + smpl.color.lab.b + '\t' + smpl.color.lab.l + '\r';
	}
}

var f = new File('~/Desktop/clelab_colors_64x64.txt');  
if (f.open("w")) {  
	f.encoding = 'BINARY';  
	f.write(sampling_data);
	f.close();
}

app.preferences.rulerUnits = ru;
alert('Finished.   time: ' + (new Date() - t));
Profile
choco
Author : choco

印刷・製版の現場を経て、広告制作会社でPhotoshopを使ったビジュアル制作を担当。

→現在は車載機器開発ベンダにて、組み込み3Dデータ作成やUIデザインなどを行っています。

Categories
Favorites


Search
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。