スポンサーサイト

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

Twitterで『あの人』が自分をfollowしているかどうかを調べるAppleScript

ここんとこ仕事が立て込んでたので放置してしまいました。ちょっと軽めのネタを。

Twitterやってると突然、『follow整理します!』みたいな感じのPOSTが流れて来て、そんな時に自分のfollowerが減ってたりすると、『えっ?…オレ?』とか思ったりして、特に相手がお気に入りの人だったりするとちょっと気になったりするもんです。また、結構前にfollowしたけどfollow返されてたっけ?みたいに思ってもtwitterからの『followされました』メールをいちいちチェックするのも面倒です。

相手にremoveされたかを知るには、『りむったー』とか『Qwitter』等のサービスを利用すればremoveされた際に通知を受け取る事が出来ますが、そもそも一度もfollowされてない相手には関係がありません。

そこで、Twitter APIに用意されている『ユーザ間のfriend関係』を調べる機能を利用して『相手にfollowされてるかどうかを調べる』AppleScriptを書きました。この手のサービスやuser.jsも有りそうですが、探すヒマがあったら自分で書く、みたいな感じで。あと、高機能なTwitterクライアント使ってる人には必要無いかも知れません。

スクリプト中の『あなたのユーザ名』『パスワード』の部分を書き換えて実行し、気になる『あの人』のユーザ名を入力するとfollowされてるかどうかを表示します。





■動作確認
MacOSX 10.5.8
property user : "あなたのユーザ名"
property pass : "パスワード"

tell application "Finder"
	set friend to text returned of (display dialog "『あの人』のユーザ名を入力してください。" default answer "") as string
	try
		--set scpt to "curl --user \"" & user & ":" & pass & "\" -L \"http://twitter.com/friendships/exists.json?user_a=" & friend & "&user_b=" & user & "\""
		set scpt to "curl -L \"http://api.twitter.com/1/friendships/exists.json?user_a=" & friend & "&user_b=" & user & "\""
		set ret to do shell script scpt
	on error
		my myError()
	end try
	
	set mes to "『" & friend & "』さんは、あなたをfollowして"
	if ret is "true" then
		display dialog mes & "います。" buttons "OK" default button 1
	else if ret is "false" then
		display dialog mes & "いません。" buttons "OK" default button 1 with icon 2
	else
		my myError()
	end if
end tell

on myError()
	activate me
	display dialog "ユーザ名を正しく入力してください。" buttons "OK" default button 1
	error number -128
end myError

3DCGソフト『Blender』のDTPer的な利用法



DTPの制作現場に携わってると時々「この文字を立体的に表現できる?」なんてリクエストを受ける事がありますが、最近ではAdobeのアプリに3D表現の機能が充実してきたので、昔みたいにサードパーティーのプラグインを使ったりしなくてもお手軽に立体表現ができる様になってきました。ただ、どうしても「いかにも」な感じになったり、質感が乏しかったりで、先方が首を縦に振ってくれる物を作るのが難しい場合も有ります。

そこで、フリーの3DCGソフトを使ってIllustrator等で表現するよりもリアルに文字や図形を立体化する方法を紹介したいと思います。3DCGを制作する際にスキルを要する「モデリング」の部分はIllustratorで作ったパスを使うので、あとは表面の質感(マテリアルやテクスチャ)、光源を設定してやる部分さえ何とか出来れば形になると思います。

フリーで使える3DCGソフトとして、自分はいつも『Blender』を使用しています。このソフトはその気になれば本格的な3DCGアニメーションが作れるくらい多機能なんですが、始めは独特なUIに面食らってしまって使い方を覚えるのにかなり苦労しました(慣れると手が勝手に動く様になるんですが)。今回の例では一部の基本的な機能だけを使っているので、『使い方さえ解れば』それほど難しくはないと思います。

BlenderはMac、Windows、Linux等各OS用が用意されていて、UIも全く同じなので違うプラットフォームでも違和感無く使う事が出来ますが、操作は3ボタンマウス必須なのでMacの場合は用意する必要が有るかも知れません。

Blenderの基本的な操作については以下のリンク先が参考になります。
JBDP Blender Documentation 日本語版
チュートリアル(本当に初めての方のために作成したBlenderチュートリアル) - WBS+(Web/Blender Studio+)
BlenderWiki

作業の流れとしては、
・立体化したい文字や図形などをIllustratorで作成し、アウトライン化したパスをファイルとして保存する。
・Illustratorで保存したファイル(パス)をBlenderでインポートする。
・Blender上で、パスの図形に厚みを与え、表面の材質(マテリアルやテクスチャ)を設定する。
・実際に出力するイメージに合わせたカメラアングルや光源の設定をする。
・必要な解像度でレンダリングし、画像ファイルとして出力する。
といった感じになります。ただBlender上での作業は狙った通りのイメージになるまでパラメータを弄りながらある程度試行錯誤する事になると思います。

今回はあくまで「こんな方法もある」といった事を紹介するのが主目的なので具体的な操作については割愛しますが、興味のある方は上記のリンク先等を参照してみてください。作業する上でハマりそうな部分や数値などが参考になりそうな部分はスクリーンショットを載せておきます。

では実際にウチのBlogのタイトル文字を立体化してみます。

まず、立体化したい文字等のアウトラインデータをIllustratorで作成し、『SVG形式』で保存します。A3のアートボード一杯くらいの大きめのサイズで作った方がBlenderでの作業が楽になります。


BlenderにはEPSやAIフォーマットのデータをインポートする機能も用意されてるんですが、いろいろ試した結果SVGで保存したものが一番安定してインポート出来る様でした。


Blenderで、保存したIllustratorのファイルをインポートします。


次に表示されるメニューではInkSpace(.svg)を選択、『Inport Options』は何も選択しない状態でOKすれば問題無くインポート出来ました。




で、インポートされたパスに厚みを付けるんですが、『Curve and Surface』パネル中の『Extrude』で文字の厚み(奥行き)、『Bevel Depth』で角を落とすサイズの値を設定します。Illustratorで作成したパスのサイズが小さいとBlender上で拡大しても小さな数値(0.001とか)で大きく変化するので注意が必要です。


文字のオブジェクトにマテリアルを設定し、光源やカメラを配置してレンダリングします。マテリアルは鏡面加工の金属ぽい質感になるよう設定しました。光源の配置が結構キモになったりするので、いろいろ試行錯誤していきます。


レンダリング結果。ターミネーター風。


今度は別のパスをインポートして下の様な画像をテクスチャとして設定し、レンダリングしてみます。




トランスフォーマー風になりました。


もちろんアングルを自由に変えられますし、このデータをそのまま使ってアニメーションにする事もできます。


実際の制作物ではレンダリングした画像だけで完結するのではなく、別の画像と合成して使う事になる場合もあると思いますが、そんな時は後々作業がやり易い様に、文字のマテリアルを完全に白く飛んでしまう設定にしてレンダリングし、文字部分のマスクとして使える物も作っておきます。

■2009.7.21追記 コメントにて教えていただきましたが、レンダリングの設定でFormatパネルのRGBAをオンにしてPNGで書き出せば、背景が透明に抜けた状態の画像が得られます。

レンダリングした画像を印刷物に使う場合、そのままではネムい仕上がりになってしまう事があるので(テクスチャの解像度が低い場合は特に)、適切なシャープネス処理が必要になります。


以上、非常に大雑把な説明でしたが、スキルを要するモデリングの部分をIllustratorのパスを使う事によって簡略化できるので、3DCGに興味はあるけど敷居が高いと感じてる人もこの辺りから手を付けると良いんではないでしょうか。画像を見ても分かる通りBlender標準のレンダラでも品質の高いレンダリングを行う事が出来ます。自分もほんのさわりの部分しか使ってませんが、Photoshopでのちょっとした合成用素材なんかを作るのにも重宝しています。

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ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。