UIで色を選択させたい時、SliderやEditTextなどでRGBそれぞれの数値を入力させてもいいのだが、使う側からしたら直感的に出来ないので扱いづらい。
カラーピッカーを使用して色の選択をしやすくする。
説明書であるJavaScriptToolsGuide.pdfなど、ちょっと調べれば $.colorPicker() というメソッドが用意されているのがわかる。
読むと「16進数」という言葉が出てきて帰りたくなる。何の知識もなしに使ってみると辛い。まぁまずは一旦見てみる。
$.colorPicker(name)
プラットフォーム固有の色選択ダイアログを呼び出し、選択した色を16進数のRGB値0xRRGGBBとして返します。
name:ダイアログで事前選択される色。16進数のRGB値(0xRRGGBB)、またはプラットフォームのデフォルトの場合は-1。
(JavascriptToolsGuideより)
「プラットフォーム固有の色選択ダイアログ」とあるがこれは、OSのカラーピッカーを呼び出すよってこと。AEのカラーピッカーは呼び出せない(多分)。個人的にはAEのカラーピッカーのほうが使いやすいのでそうしたいが、仕方ない。
とにかく、このメソッドを実行すればカラーピッカーは表示される。そこは難しくない。
嫌な感じがするのは「選択した色を16進数のRGB値0xRRGGBBとして返します。」という部分。
これを見て「意味がわからん、さっぱりだ...」という人は、解読の仕方は後ほど説明するので、
まず重要なのは、R、G、B、3つの値から生成された、
たった「1つの数値」が返ってくる!
それだけ理解して次に進む。
$.colorPicker()が返してくる値って、RGBの値から生成された「1つ」の数値。対してAfterEffectsが求めてるのって[R,G, B]の形、つまり「要素数が3の配列」なんですよね。エフェクトのカラープロパティ設定したり、平面の色、コンポの背景色、いろいろあると思うけど、全部配列で渡さないといけない。
「でも手元にはたった1つの数値のみ...。これをどうしろと...!?」
と、なってしまうのでやり方を知っていないと、$.colorPicker()を使うことが出来ない。その説明をする。
選択した色を16進数のRGB値0xRRGGBBとして返します。
とあった。0x の部分は「これは16進数表記ですよ」のマーク。「16進数なんだね!」と把握だけできれば無視して良い。
それに続くRRGGBBというのは、赤(Red)、緑(Green)、青(Blue)それぞれの値がRR、GG、BBの部分に2桁ずつで入るってこと。
なぜ2桁か。RGBはそれぞれ「0-255」で表されるが、これを16進数表記にすると「0-ff」。最大値は「ff」であり2桁で事足りるから。
で、それらが足し算などではなく、単純に連結されている。イメージしやすく10進数で例えると、RGBが[255, 128, 0]の場合、Rの「255」、Gの「128」、Bの「000」(他と桁数を合わせるために3桁にした)をそのままつなげて「255128000」になるみたいなこと。※連結のイメージの例えであってこの数値は正しくない。
正しい例)
[255, 255, 255] = ffffff //白
[255, 0, 255] = ff00ff //マゼンタ
[0, 0, 0] = 0(=000000) //黒
[255, 128, 0] = ff8000 //さっきの例
まとめると、
カラーピッカーから16進数表記の6桁の数値が返ってくる。その左2桁がRed、中2桁がGreen、右2桁がBlueを示してっぞ。
ってこと。
とにかく使ってみて返ってくる値を確認してみる。次のコードを実行する。
var pick = $.colorPicker();
alert(pick);
Windowsだったら右図のようなカラーピッカーが出る。
適当な色を選択して、戻り値を確認する。
なんかよくわからない数字が返ってきている?これをどうすればいいのかって途方に暮れる中、青[0, 0, 255] の返り値だけ馴染みのある数字である。というか16進数が返ってくるはずでは?これ10進数じゃね?なんか間違えた?
間違えていない。これはalert()(か、Windowsか、はたまたその他なのかは知らないが、何かしら)が勝手に10進数表記に直して表示してくれているだけ。
あくまで10進数、16進数てのは表記上の問題。「りんご」も「林檎」も「apple」も同じ「りんご」を指すのと同じ。
とにかく、数値として返ってきていることはわかった。
だが、このままではRGBのそれぞれの数値を抜き出すことは...出来なくはないが難易度高すぎ。「15711805」という数字からどうやって「239, 190, 61」を導き出すか。ここで16進数が出てくるわけ。10進数だと謎の数字だったものが、16進数にすれば説明書にあった「RRGGBB」の形になるはずなので、RGBそれぞれの要素が取り出しやすくなる。
もはや暗号解読よね。コナンくんか、金田一か...何でもいいです。僕は銀狼怪奇ファイルが好きです。
先程の戻り値の数値を16進数表記にしてみる。もちろんスクリプト内でやるのだが、今はとりあえず確認をしたいのでここは電卓を使ってみる。Windowsについてる電卓、結構パワーアップしているのをご存知だろうか。ここでプログラマー電卓に切り替える。
「DEC」の部分をクリックして、「自分が入力するのは10進数ですよ」と指定する。この状態で、先程の戻り値を入れてみる。
「HEX」の部分、つまり16進数の値を確認してみる。
なるほど、それぞれFF0000、FF00、FFとなっている。なんかそれっぽい!でも桁数がバラバラ…? 16進数の6桁ってゆったぢゃん!!
いや、待て。君は「100」を表記する時、「000100」などと書くのか? 頭の0は普通表記しないだろう。そういうこと。
「FF」は「0000FF」として見ればいい。
この状態で左から2桁ずつ見ていくと...、あ!なんかできそうっぽい!
あとは、スクリプト内で16進数表記にしてから、2桁ずつ10進数に直していけば出来そう。という「16進数コース」と、実はもう1つ、別のコースがある。「2進数コース」である。さっきの電卓の画像で「BIN」の部分が2進数表記。どっちが楽か、という問題であるが...。
多分16進数のほうが理解しやすいと思う。でもソースコード的には2進数のほうがスッキリする。
両方説明するので、わかりやすい方法でいいと思う。
個人的には、最初は16進数コースで書いていたが、理解出来るようになってから2進数コースに書き換えた。
なぜって?そのほうがかっこいいから。(確かにソースコード短くなるけど誤差レベル。処理速度も体感できない)
$.colorPickerの戻り値に数値が入っているのは確認したが、どうやって16進数の表記として取得するか。実際のコード上で電卓は使えない。
いきなり答えだが、NumberオブジェクトにtoString()メソッドがあるのでそれを使う。
【参考】Number.prototype.toString() - JavaScript | MDN
↑を読めばだいたい分かる。Number.toString()は引数に数値を取って、それを基数(何進数かということ)で表した「文字列」を返す。雑に言うと、数値を「○進数」に変換してくれるってこと。
試せばすぐわかる。
冒頭で戻り値確認したコードを少し修正する。
var pick = $.colorPicker();
alert(pick.toString(16)); //16進数表記にしてほしいので16を渡す
これを実行してみると、右図のような結果に。さっきまではすごい桁数の数字になっていたが、6桁になった。
これを2文字ずつ取って、10進数に変換すればRGBそれぞれの要素を0-255の範囲で取得できる。
すなわち、やっとこさAfterEffectsにデータを渡せる。やったね!あと2歩。
そんなに難しくない。
String.slice();
を使えばいいだろう。
【参考】String.prototype.slice() - JavaScript | MDN
左から何文字の間とか右から何文字とか指定できる。文字数の指定ではなく、カーソルの位置をイメージする。メモ帳などで適当に打って、カーソルが一番左にあるその場所が0、1つ右に行くとその場所が1、といった具合。カーソルの位置番号みたいなイメージで引数を指定する。コピーしたい文字列をドラッグして反転した時、何番から何番までの位置をドラッグしたのか。そういうイメージ。
先程も書いたが、6桁になっていないものは先頭が0というだけなので、実際は0埋めして桁数を合わせてから行う。
すると以下の感じ。
//カラーピッカーから得た数値を変数へ
var pick = $.colorPicker();
//16進数表記の文字列に変換
pick = pick.toString(16);
//6桁に揃える
while (pick.length < 6) pick = "0" + pick;
//2文字ずつ抽出
var R = pick.slice(0, 2);
var G = pick.slice(2, 4);
var B = pick.slice(4, 6);
変数R,G,Bにはそれぞれ、16進数の00-ff(10進数で言えば0-255)の範囲内の値が文字列として格納されている状態。
あとは、それぞれ文字のままでは使えないので数値化して、AfterEffectsが欲しているように配列化して、という流れ。
「10進数に戻す」というか「数値に戻す」が正確。
16進数表記の文字列から実際に数値を生成するには、parseInt()を使う。今回は16進数だが、16進数以外からでも可能。
【参考】parseInt() - JavaScript | MDN
例えば、右の画像でいう[0, 0, 255]の時に返ってきた「ff」を使うと、
var a = parseInt("ff", 16);
alert(a);
とすればaには数値「255」が入っている。第2引数には「これは○進数ですよ」と明示する必要があるので、16を入れる。
するとこんな感じ。
//カラーピッカーから得た数値を変数へ
var pick = $.colorPicker();
//16進数表記の文字列に変換
pick = pick.toString(16);
//6桁に揃える
while (pick.length < 6) pick = "0" + pick;
//2文字ずつ抽出
var R = pick.slice(0, 2);
var G = pick.slice(2, 4);
var B = pick.slice(4, 6);
//16進から10進に変換
var color = [];
color[0] = parseInt(R, 16);
color[1] = parseInt(G, 16);
color[2] = parseInt(B, 16);
alert(color);
変数colorにはAfterEffectsご所望の「要素数が3の配列」が入っている。このままAfterEffectsに渡すことが出来る。ふぅ...。
ただしまだ終わらない。2進数コースが不要なら、すっ飛ばしてその下へ...。
2進数でもやることは似ているが、文字列と数値の変換が必要ない。なぜなら2進数の計算用のコマンドがあるから。「パソコンは2進数だ」みたいな言葉は聞いたことがあるでしょう。得意だし楽なんですよ、そのほうが。その分コードは簡単になるが、2進数のイメージが出来ないと何してるかさっぱり。でもここでやることはさほど難しくはない。
かくいう私も2進数を使いこなしているわけではない。理解すればもっと効率よく条件判定とかできそうやな、くらいには思っているその程度。
戻り値確認で使った、適当な色を例にする。入力前に必ず「プログラマー」モードで「DEC」を選択していることを確認してから、戻り値「15711805」を電卓に入れる。
たまに電卓アプリのUIをいちいちクリックしている人を見かけるが、テンキーあるならそれを使ったほうが早いでしょう。電卓なんだから電卓っぽく入力したほうが早いですよ。
右の画像で、入力した状態を確認。
HEX: EF BE 3D
DEC: 15,711,805
OCT: 73 737 075
BIN: 1110 1111 1011 1110 0011 1101
先程の16進数コースならば「HEX」欄の「EF BE 3D」、これを文字列として取得した後、3分割、つまり2文字ずつ数値に変換することで[239, 190, 61]というデータを得た。
2進数コースも同じく「BIN」欄の「1110 1111 1011 1110 0011 1101」を3分割して見ていけばいい。
EF = 1110 1111
BE = 1011 1110
3D = 0011 1101
と、対応していると見ていい。2進数の場合は8桁ずつを見ていく。
|1110 1111|1011 1110|0011 1101|
| R | G | B |
こんなイメージ。これ超大事。
うまく言語化できない。もっと賢い方々がたくさん情報を出しているので「ビット演算」とかで検索してみてください。2進数でデータを扱う時の便利ないろいろなやり方があります。
【参考】ビット演算入門 - Qiita
ゲームのステータス状態とか進行フラグとか、そういうのにも使います。いろいろ読んでみるとおもしろい。最近のゲームはビジュアルがどんどんすごくなるけれど、こういう根底のプログラムはファミコン時代から一緒(と思ってる。確証はない。でも多分そう)。よく「フラグが立った」とか言うけど語源はビット演算界隈よ。
本題に戻って、ここで必要な情報は「ビットマスク」というテク。「&」演算子を使って、欲しいビット以外をOFFにする。
|1110 1111|1011 1110|0011 1101|
| R | G | B |
元の色データはこんなだった。例えば、このデータからRGBのBの値だけ取り出したいとする。右8桁分がBの値になっている。R,G部分のビットは邪魔なので全てOFF、B部分のみ抽出したいのでそのためのマスクは、右8桁だけを全て1にした「0000 0000 0000 0000 1111 1111」。これは10進数に直すと「255」。
このマスクを「&」演算子でつなぐ。ifの条件でよく使う「&&」と使う文字は同じだが、1つしか書かないと今回のビット演算になる。
カラーピッカー呼び出すのめんどくさいので、直接返り値を使って書くと、
var pick = 15711805; //色のデータ
var a = pick & 255; //ビットマスクで右8桁のみ抽出
alert(a); //61
「61」が表示されるはず。これはすなわちRGB[239, 190, 61]のBの部分。右8桁だけ抽出できたということ。
ビットマスクの計算イメージはこう。
//それぞれのビットに於いて、両方とも1の場合は1、そうでない場合は0が結果に入る
1110 1111 1011 1110 0011 1101 (例の適当な色の返り値の2進数表記)
0000 0000 0000 0000 1111 1111 (マスク。10進数にすると255)
---- ---- ---- ---- ---- ----
0000 0000 0000 0000 0011 1101 (計算結果。10進数にすると61)
なんか、余裕じゃん。文字変換なども行っていないので、そのまま数値として利用できる。
では同様にGも見てみる。Gの部分のマスクは中8桁だけ1にした「0000 0000 1111 1111 0000 0000」。10進数にすると65280。どこかで見た数字?冒頭の「戻り値を確認」の部分で出てきたやつですね。
var pick = 15711805; //色のデータ
var a = pick & 65280; //ビットマスクで中8桁のみ抽出
alert(a); //48640
「48640」が表示された...。「190」が出てくるんじゃないのか...。とお思いだろうか?ビットマスクの計算イメージを確認する。
//それぞれのビットに於いて、両方とも1の場合は1、そうでない場合は0が結果に入る
1110 1111 1011 1110 0011 1101 (例の適当な色の返り値の2進数表記)
0000 0000 1111 1111 0000 0000 (10進数にすると65280)
---- ---- ---- ---- ---- ----
0000 0000 1011 1110 0000 0000 (10進数にすると48640)
試しに、1011 1110 0000 0000を電卓で入力してみる。「プログラマー」モードで「BIN」をクリックして「2進数を入力します」宣言。で先程の結果を入力する。すると、「DEC」の部分に48640と表示される。ふむぅ...、何かの間違いではないようだ。
と、実際に電卓アプリに入力してみた人、お気づきになっただろうか...。入力時、最初の8桁 1011 1110 を入力した時点で10進数表記である「DEC」の欄が「190」になっていた事を...。「これ欲しかった数字じゃね?」と思っているうちに0000 0000を打ち込んでしまい、さよならしてしまう運命。
事は単純。Bの部分の0000 0000が余計、つまり桁数が無駄に多いってこと。10進数で「190」と「190,000」は見た目は似てても全く違う数字だろう。そういうこと。なので8桁ずらせばいい。
で、桁数をかんたんに変更できるのが「ビットシフト」というもの。これも検索してもらえれば、初心者向けから上級者向けまでいくらでも記事は見つかる。
>> ビットを右に移動
<< ビットを左に移動
例えば、0100>>2のイメージは、右に2桁移動するので、
0100 //もとの状態
----
0001 //右に2つシフトした
という感じ。右側に溢れたものは破棄される。イメージは簡単だと思う。場所を左右に移動するだけ。
これを使って邪魔だったBの8桁を消してやればよい。
var pick = 15711805; //色のデータ
var a = pick & 65280; //ビットマスクで中8桁のみ抽出
var a = a>>8; //ビットシフト
alert(a); //190
「190」がアラート表示されたのでは?欲しかった数字が手に入ったね!ビットシフト部分のイメージはこう。
0000 0000 1011 1110 0000 0000 (10進数にすると48640)
右に8桁移動
0000 0000 0000 0000 1011 1110 (10進数にすると190)
Rに対しても同様にすればいい。
var pick = 15711805; //色のデータ
var a = pick & 16711680; //ビットマスクで左8桁のみ抽出
var a = a>>16; //ビットシフト:16桁右に移動
alert(a); //239
ちなみにマスク部分はとっつきやすいかなと思って10進数にしてきたが、ここまで桁数がくるとむしろわかりにくい。数字が何を意味するのかがわかりにくいし。
プログラム的には16進数表記でも問題ない、というかそっちの方がわかりやすい。
var a = pick & 16711680; //10進数表記。この数字からソースコードの意図を汲むのは難しい。
var a = pick & 0xff0000; //16進数表記。こっちのほうが赤のマスクだ!とわかりやすい。
ついでにGも見ておくと、
var a = pick & 65280; //10進数表記。
var a = pick & 0xff00; //16進数表記。
文章的には長くなってしまったが、2進数コースはビットマスクとビットシフト、これらを行うだけで欲しい数値が簡単に取れる。
合わせ技で次のようになる。ビット演算子(&)よりもシフト演算子(>>)の方が優先順が上なので、( )で括るのを忘れない。
//カラーピッカーから得た数値を変数へ
var pick = $.colorPicker();
//ビットシフトして各要素を取得
var color = [];
color[0] = (pick & 0xff0000) >> 16; //Red
color[1] = (pick & 0xff00) >> 8; //Green
color[2] = (pick & 0xff); //Blue
alert(color);
それか、先にビットシフトしたければ、マスクは全部「右8桁」になるのでこうも出来る。
//カラーピッカーから得た数値を変数へ
var pick = $.colorPicker();
//ビットシフトして各要素を取得
var color = [];
color[0] = pick >> 16 & 0xff; //Red
color[1] = pick >> 8 & 0xff; //Green
color[2] = pick >> 0 & 0xff; //Blue
alert(color);
Blueの行は、他と雰囲気合わせたくて >> 0 を書いたが、書かなくてもよい。意味はない。何も起きない。単純に pick & 0xff でも同じ。
2進数で計算すると16進数で考えるよりも手順が少ない。
これでAfterEffectsお誂え向きの状態でデータが用意できた。
今までのコードだとカラーピッカーが開くたびに初期値が黒だったと思う。(Macは知らない)
スクリプトの設定項目としてユーザーに色を選択させている場合、現在の設定色がデフォルトで選択されている状態でカラーピッカーを開きたい。そのほうがユーザーにとっても微調整がしやすい。
「やっぱりもうちょっと明度あげよかな」と思って再度カラーピッカーを開いた時に、黒を表示されると「さっきの値なんかいちいち覚えてないわ!」ってなる。そんなのはユーザーフレンドリーじゃない。なので最後に、初期値の設定方法を書いて終わり。
長くなりすぎてもう覚えていないと思うので、最初に書いたのと同じものを。
$.colorPicker(name)
プラットフォーム固有の色選択ダイアログを呼び出し、選択した色を16進数のRGB値0xRRGGBBとして返します。
name:ダイアログで事前選択される色。16進数のRGB値(0xRRGGBB)、またはプラットフォームのデフォルトの場合は-1。
(JavascriptToolsGuideより)
引数nameに色データを渡せば、初期状態の色を変更できそうな旨が書いてあるのは読み取れる。
データ形式も戻り値と同じ形式、つまり1つの数値、ただし16進数表記にした時に、RRGGBBとなるような値。
以下の例はどれでも一緒。
$.colorPicker(0xEFBE3D);
16進数で渡してもいいし
$.colorPicker(15711805);
10進数で渡してもいいし
$.colorPicker(073737075);
8進数で渡してもいいし
$.colorPicker(0b111011111011111000111101);
2進数で渡してもいいし
(古いAfterEffectsでは無理)ただ実際、引数に渡せる手元に持ってるデータって、[R, G, B]の配列の状態がほとんど。だって先にも書いた通り、AfterEffects内で色取得したり設定したりはその形式だから。てことはそれを0xRRGGBBの形に変換して渡さないといけないということ。16進数じゃなくて10進数でも$.colorPicker()の引数として問題ないのは確認したが、長々と見てきたように、配列状態のデータとの変換がしやすいのが16進数の形式か2進数の形式。じゃあもうそれらのどっちがで渡せばええよね。
見てきたことの逆なので細かくは説明する必要はないと思う。
配列内部をそれぞれ16進数化、whileループで桁数を2桁に統一して、join()でつなげるとRRGGBBの状態の文字列が出来るので、それの頭に16進数である証である0xをつけて、$.colorPicker()に渡す。
//適当な色
var color = [239, 190, 61];
//仮変数に格納
var temp = [];
//RGBをそれぞれ16進数表記に変換
for (var i = 0; i < 3; i++) {
//16進数表記化(文字列化)
temp[i] = color[i].toString(16);
//桁数不足分を0埋め
while (temp[i].length < 2) temp[i] = "0" + temp[i];
}
//色が選択された状態で起動
$.colorPicker("0x" + temp.join(""));
こっちも同じ。相変わらず手数は少なく出来る。
R, G, Bの順で、左に8個ビットシフトして追加...を繰り返すだけ。RRGGBBならぬRRRRRRRRGGGGGGGGBBBBBBBB状態に出来るので、そのまま$.colorPicker()に渡す。
//適当な色
var color = [239, 190, 61];
//仮変数に格納
var temp = 0;
//左シフトしながら追加していくだけ
for (var i = 0; i < 3; i++) {
temp = (temp << 8) + color[i]; // ビットシフトより加算のほうが優先順位上やから()つけるのだけ注意
}
//色が選択された状態で起動
var pick = $.colorPicker(temp);
一応、丁寧にビットシフトのイメージを書いておく。
[temp = 0]
0000 0000 0000 0000 0000 0000
[1周目:R追加]
ビットシフト後
0000 0000 0000 0000 0000 0000 //左にズラしたが0には変わりない
+color[0]
0000 0000 0000 0000 1110 1111 //+239
[2周目:G追加]
ビットシフト後
0000 0000 1110 1111 0000 0000
+color[1]
0000 0000 1110 1111 1011 1110 //+190
[3周目:B追加]
ビットシフト後
1110 1111 1011 1110 0000 0000
+color[2]
1110 1111 1011 1110 0011 1101 //+61
長い旅もここで終わり。まぁなんかいろいろめんどくさそうってのがわかったので、使いやすいように関数化しておく。
初期選択の色を[R, G, B]で渡して、選択した色も[R, G, B]で返ってくる、そんな仕様にしよう。
中身の書き方が16進数か2進数かで違うだけで、どちらも使い方は一緒。
//--------------------------------------------------------------------
// カラーピッカーで色配列を返す
//--------------------------------------------------------------------
//0-255でRGB値返します
//initColor: [r,g,b](0-255)の配列
function getColor(initColor) {
//上書きしないように仮変数に格納
var temp = [];
//引数が配列かチェック
if (initColor instanceof Array && initColor.length > 2){
//初期選択用に引数を16進に変換
for (var i = 0; i < 3; i++) {
temp[i] = initColor[i].toString(16);
while (temp[i].length < 2) temp[i] = "0" + temp[i];
}
}
var pick = $.colorPicker("0x" + temp.join(""));
//-1が返ってきたらおしまい
if (pick < 0) return pick;
//16進数に変換
pick = pick.toString(16);
//6桁に揃える
while (pick.length < 6) pick = "0" + pick;
//16進から10進に変換
var color = [];
color[0] = parseInt(pick.slice(0, 2), 16);
color[1] = parseInt(pick.slice(2, 4), 16);
color[2] = parseInt(pick.slice(4, 6), 16);
return color;
}
//--------------------------------------------------------------------
// カラーピッカーで色配列を返す
//--------------------------------------------------------------------
//0-255でRGB値返します
//initColor : [r,g,b](0-255)の配列
function getColor(initColor) {
//上書きしないように仮変数に格納
var temp = 0;
//引数が配列かチェック
if (initColor instanceof Array && initColor.length > 2){
for (var i = 0; i < 3; i++) {
// ビットシフトより加算のほうが優先順位上やから()つけないと
temp = (temp << 8) + initColor[i];
}
}
var pick = $.colorPicker(temp);
//-1が返ってきたらおしまい
if (pick < 0) return pick;
//ビットシフトして各要素を取得
var color = [];
color[0] = pick >> 16 & 0xff;
color[1] = pick >> 8 & 0xff;
color[2] = pick >> 0 & 0xff;
return color;
}
引数のチェックしてないので、配列以外が渡されるとエラーになりますね。余計なコードで長くなっちゃうので、そのあたりは各々で。
もっと短くなると思ってました。でも要点だけ書くと、本質を理解できないまま、コピペで済ませたりする人多そうだし。それ嫌いだし...。
スクリプトパネルはいずれCEPに取って代わられるのでしょうかね。でもAfterEffectsに於いては、まだそこまで急な話ではない感じがするので、この記事もまだ少しは意味あるかなと信じたい。