※ 前の記事(自動カラー選択ボタン(β版)のテストを開始しました)のつづき。
canvasに表示した画像から、カラーパターンを作成するスクリプトを書いてみました。処理にweb workersを利用しています。
Auto ColorPicker β – rokuro fire
下記2つの処理はUIスレッド側に書きます。
// canvasで画像を読み込み、全ピクセルデータを取得 // canvasにはdrawImage済とする var pixels = canvas.getContext('2d').getImageData(0,0,canvas.width,canvas.height) // workerを作成し、ピクセルデータをworkerに渡す // worker.jsにweb workerでの処理が書かれているとする var worker = new Worker('/js/worker.js'); // workerに渡すメッセージ var msgData = { width:canvas.width, height:canvas.height, pixdata:pixels }; // workerからのメッセージ取得 worker.onmessage = function(e){ // HTMLを出力するための何らかの処理をここに記述 // ワーカーを終了 worker.terminate(); return false; }; // workerにメッセージを渡す worker.postMessage(msgData);
workerには msgData オブジェクトを渡しています。
workerではDOMやwindowオブジェクトを使った操作が一切出来ないため、必要な変数は事前に取得しておき、一緒に渡してしまいます。
下記2つの処理は、worker側に書きます。
色抽出の処理は非常に重い処理のため、worker側で行います。そのため、PCのスペックによっては、処理に時間がかかることがあります。
またweb workersをサポートしていないブラウザではこの機能は使えません。
本処理では、ベースカラー/サブカラー/キーカラー、それぞれの面積の比率(%)を設定し、各レンジの中で占有率の多い色を表示します。
通常、ベースカラー 70% サブカラー 25% アクセントカラー 5%くらいの比率ですが、これだとうまく取得できなかったため、試験的に ベースカラー 85% サブカラー 10% アクセントカラー 1%に設定しています。
ただ色を取得しただけではRGBのどれかの値が1違うだけで別の色になってしまうため、近似色の計算もしています。計算式は下記です。
var r = r1 - r2; // R値の差 var g = g1 - g2; // G値の差 var b = b1 - b2; // B値の差 var d = Math.sqrt(r*r + g*g + b*b); // 2つの色の距離 if(d < 60) return true; // 閾値(60)以下なら、その色は近似色
var worker_getAllColor = {}; // ピクセル走査処理 worker_getAllColor.search = function(e){ // e.data に、メッセージの値が入ってくる var pixdata = e.data.pixdata.data, colorNum = 5, // ベース/サブ/キーカラーそれぞれ5色を出力 tmplabel = "", colorObj = {}, colorAry = { base:[], sub:[], key:[] }; // ピクセルの総数 var pixNum = pixdata.length; // 全ピクセルデータからオブジェクトを作成する処理 // colorObjのkeyを "Rの値,Gの値,Bの値"とし、valueには同色のピクセルの個数 // 1件め追加 colorObj[pixdata[0] + ',' + pixdata[1] + ',' + pixdata[2]] = 1; var flag; // 2件目以降 // ピクセルデータは RGBaの順番で取得されるので、4番目のalpha(透明度)は今回は取得せずに飛ばす for(var i=4; i<pixNum; i++){ if(i % 4 === 3) continue; if(i % 4 === 2) { tmplabel += pixdata[i]; // 近似色を比較し、近似色なら元オブジェクトの数値をプラス for(var label in colorObj){ if(!worker_getAllColor.checkNum(label,tmplabel)) { colorObj[label] = colorObj[label]+1; flag = true; break; } } // 近似色でない場合、新たにオブジェクトを追加 if(!flag) { colorObj[tmplabel] = 1; } else { flag = false; } tmplabel = ""; } else { tmplabel += pixdata[i] + ','; } } // 返り値データの成型 var count = 0; for(var label in colorObj) { // 数値が全ピクセルの15%以上ならベースカラー // 1%〜10%ならサブカラー // 1%以下ならキーカラー var lineNum = (colorObj[label]/pixNum * 100); if(lineNum > 15) { // ベースカラー var type = {name:"base"}; } else if(lineNum <= 1) { // キーカラー var type = {name:"key"}; } else if(lineNum <= 10) { // サブカラー var type = {name:"sub"}; } else { continue; } // 配列の個数が規定数以下の時は配列に追加 if(colorAry[type.name].length < colorNum) { colorAry[type.name].push({name:label, num:colorObj[label]}); } else { // 各オブジェクトを比較し、valueが多い方を配列に残す for(var i=0,len=colorAry[type.name].length; i<len; i++){ if(colorAry[type.name][i].num < colorObj[label]) { colorAry[type.name][i] = {name:label, num:colorObj[label]}; break; } } } count++; } // それぞれvalueの多い順にソート colorAry.base.sort( function( a, b ) { return b.num - a.num; } ); colorAry.sub.sort( function( a, b ) { return b.num - a.num; } ); colorAry.key.sort( function( a, b ) { return b.num - a.num; } ); // UIスレッドにオブジェクトを返す postMessage({ary:colorAry}); }; // 近似色かチェック(近似色ならfalse、別色ならtrueを返す) worker_getAllColor.checkNum = function(obj1,obj2){ var col1 = obj1.split(","); var col2 = obj2.split(","); var r = col1[0] - col2[0], g = col1[1] - col2[1], b = col1[2] - col2[2]; var d = Math.sqrt(r*r + g*g + b*b); // postMessage({log:d}); if(60 < d) return true; // 閾値 return false; }; // onmessageイベント // ここに設定したオブジェクトから処理を開始 onmessage = worker_getAllColor.search;
ざざーっと書いてしまいましたが、文章にすると下記処理を行っています。
注意というか戸惑った点は下記です。
私はネイティブJSが好きなので特に問題ありませんが、普段jQueryを使っている場合、ちょっと大変かもしれません。
書き方が悪いのかもですが、処理が重すぎてブラウザが止まってしまいました・・・処理時間の計測もちゃんとできず。
並列処理なら、動作もほとんど重くなりません。が、処理時間はPCのスペックによって大きく変わってきます。