|
|
@@ -0,0 +1,287 @@ |
|
|
|
/**************************************************/ |
|
|
|
/* */ |
|
|
|
/* 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; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|