|
- /**************************************************/
- /* */
- /* MedianCut.js */
- /* v0.88 */
- /* */
- /* Copyright 2016 Takeshi Okamoto (Japan) */
- /* */
- /* Released under the MIT license */
- /* https://github.com/TakeshiOkamoto/ */
- /* */
- /* Date: 2016-12-14 */
- /**************************************************/
-
- ////////////////////////////////////////////////////////////////////////////////
- // Generic Function
- ////////////////////////////////////////////////////////////////////////////////
-
- // 画像のカラー情報の取得
- function getColorInfo(imagedata){
- var height = imagedata.height;
- var width = imagedata.width;
- var raw = imagedata.data;
-
- // 使用色/使用回数(面積)を取得
- var cnt = 0;
- var uses_colors = new Object;
-
- for(var i = 0; i< height;i++){
- for(var j = 0; j< width;j++){
- var key = raw[cnt] + ',' +
- raw[cnt+1] + ',' +
- raw[cnt+2] ;
- if (!uses_colors[key])
- uses_colors[key] = 1;
- else
- uses_colors[key] += 1;
-
- cnt = cnt + 4;
- }
- }
-
- // 連想配列を配列へ設定
- var rgb;
- var colors = new Array();
- for (var key in uses_colors) {
- rgb = key.split(",");
- colors[colors.length] = {'r':parseInt(rgb[0],10),
- 'g':parseInt(rgb[1],10),
- 'b':parseInt(rgb[2],10),
- 'uses':uses_colors[key]}; // 使用数
- }
- return colors;
- }
-
- ////////////////////////////////////////////////////////////////////////////////
- // Generic Class
- ////////////////////////////////////////////////////////////////////////////////
-
- // ---------------------
- // TMedianCut
- // ---------------------
- // imagedata : 減色するImageDataオブジェクト
- // colors : getColorInfo()で取得したカラー情報
- function TMedianCut(imagedata,colors) {
-
- this.raw = imagedata.data;
- this.width = imagedata.width;
- this.height = imagedata.height;
- this.msg = '';
- this.colors = colors;
- }
-
- // ---------------------
- // TMedianCut.Method
- // ---------------------
- TMedianCut.prototype = {
-
- // プロパティの設定
- _setProperty : function (color){
- var total = 0;
- var maxR = 0, maxG = 0, maxB = 0;
- var minR = 255, minG = 255, minB = 255;
-
- // 立方体の1辺の長さ
- for(var i = 0; i < color.length;i++){
-
- if (color[i].rgb.r > maxR) maxR = color[i].rgb.r ;
- if (color[i].rgb.g > maxG) maxG = color[i].rgb.g ;
- if (color[i].rgb.b > maxB) maxB = color[i].rgb.b ;
-
- if (color[i].rgb.r < minR) minR = color[i].rgb.r ;
- if (color[i].rgb.g < minG) minG = color[i].rgb.g ;
- if (color[i].rgb.b < minB) minB = color[i].rgb.b ;
-
- // キューブで使用している面積
- total += color[i].rgb.uses;
- }
-
- var dr = (maxR - minR)*1.2;
- var dg = (maxG - minG)*1.2;
- var db = (maxB - minB);
-
- // 同一の場合はrを優先する
- var colortype = 'r';
-
- // r
- if (dr > dg && dr > db){
- colortype = 'r';
- }
-
- // g
- if (dg > dr && dg > db){
- colortype = 'g';
- }
-
- // b
- if (db > dr && db > dg){
- colortype = 'b';
- }
-
- return { 'color' : color, // キューブの各色情報
- 'total' : total, // キューブの総面積(総色数)
- 'type' : colortype,// キューブの種類(R/G/B)
- // キューブの体積用 'volume': dr * dg * db
- };
- },
-
- // メディアンカット
- _MedianCut : function(cubes,colorsize){
- var count = 0;
- var index = 0;
-
- // 面積(色数)が最大のキューブを選択
- for(var i = 0; i < cubes.length;i++){
- if(cubes[i].total > count){
- // 1点は除く
- if (cubes[i].color.length != 1){
- index = i;
- count = cubes[i].total;
- }
- }
- }
-
- // 体積が最大のキューブを選択
- //if(cubes[index].color.length == 1){
- //
- // count =0; index =0;
- //
- // for(var i = 0; i < cubes.length;i++){
- // if(cubes[i].volume > count){
- // index = i;
- // count = cubes[i].volume;
- // }
- // }
- //}
-
-
- if (cubes[index].total == 1){
- // Cube could not be split.
- this.msg += colorsize + '色までキューブを分割できませんでした。\n';
- return cubes;
- }
-
- if(cubes[index].color.length == 1){
- // Cube could not be split.
- this.msg += colorsize + '色までキューブを分割できませんでした。\n';
- return cubes;
- }
-
- // メディアン由来の中央値を算出する
- var colortype = cubes[index].type;
- cubes[index].color.sort(function(a,b){
- if(a.rgb[colortype] < b.rgb[colortype] ) return -1;
- if(a.rgb[colortype] > b.rgb[colortype] ) return 1;
- return 0;
- });
- split_border = Math.floor((cubes[index].color.length+1)/2);
-
- // 分割の開始
- var split1 = new Array;
- var split2 = new Array;
- for(var i = 0; i < cubes[index].color.length;i++){
- if (i < split_border){
- split1[split1.length] = cubes[index].color[i];
- }else{
- split2[split2.length] = cubes[index].color[i];
- }
- }
-
- // プロパティの設定
- split1 = this._setProperty(split1);
- split2 = this._setProperty(split2);
-
- // キューブ配列の再編成
- var result = new Array();
- for(var i = 0; i < cubes.length;i++){
- if (i != index){
- result[result.length] = cubes[i];
- }
- }
- result[result.length] = split1;
- result[result.length] = split2;
-
- if (result.length < colorsize){
- return this._MedianCut(result,colorsize);
- }else{
- return result;
- }
- },
-
- // 減色の実行
- // colorsize : 最大何色まで減色するかの色数(2- 256)
- // update : true ピクセルデータを更新 false 更新しない
- run : function(colorsize,update){
-
- if (this.colors.length <= colorsize){
- // It has already been reduced color.
- this.msg = '既に'+ this.colors.length +'色に減色されています。\n';
- //return;
- }
-
- // 1個目のキューブの作成
- var plane = new Array;
- for(var i = 0; i < this.colors.length;i++){
- plane[plane.length] = {'rgb': this.colors[i]};
- }
-
- var dummy = new Array();
- dummy[0] = this._setProperty(plane);
-
- // キューブの分割
- var cubes = this._MedianCut(dummy,colorsize);
-
- // キューブ毎に代表色(重み係数による平均)を算出する
- var rep_color = new Array();
- for(var i = 0; i < cubes.length;i++){
- var count = 0;
- var r =0,g=0,b=0;
- for(var j = 0; j < cubes[i].color.length;j++){
- r += cubes[i].color[j].rgb.r * cubes[i].color[j].rgb.uses;
- g += cubes[i].color[j].rgb.g * cubes[i].color[j].rgb.uses;
- b += cubes[i].color[j].rgb.b * cubes[i].color[j].rgb.uses;
- count += cubes[i].color[j].rgb.uses;
- }
- rep_color[i] = {'r': Math.round(r/count),
- 'g': Math.round(g/count),
- 'b': Math.round(b/count)};
- }
-
- // 代表色の保存
- this.rep_color = rep_color;
-
- // ピクセルデータの更新
- if (update) {
-
- // ピクセルデータ設定用の連想配列(高速化用)
- var pixels = new Object;
- for(var i = 0; i < cubes.length;i++){
- for(var j = 0; j < cubes[i].color.length;j++){
- pixels[cubes[i].color[j].rgb.r + ',' +
- cubes[i].color[j].rgb.g + ',' +
- cubes[i].color[j].rgb.b] = {'r': rep_color[i].r,
- 'g': rep_color[i].g,
- 'b': rep_color[i].b};
- }
- }
-
- // データの設定
- var key,cnt =0;
- for(var i = 0; i< this.height;i++){
- for(var j = 0; j< this.width;j++){
-
- key = this.raw[cnt] + ',' +
- this.raw[cnt+1] + ',' +
- this.raw[cnt+2];
-
- this.raw[cnt] = pixels[key].r;
- this.raw[cnt+1] = pixels[key].g;
- this.raw[cnt+2] = pixels[key].b;
-
- cnt = cnt + 4;
- }
- }
- }
- }
- }
|