キケッツ掲示板のリポジトリです. https://bbs.kekec.wiki
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

MedianCut.js 8.6 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. /**************************************************/
  2. /* */
  3. /* MedianCut.js */
  4. /* v0.88 */
  5. /* */
  6. /* Copyright 2016 Takeshi Okamoto (Japan) */
  7. /* */
  8. /* Released under the MIT license */
  9. /* https://github.com/TakeshiOkamoto/ */
  10. /* */
  11. /* Date: 2016-12-14 */
  12. /**************************************************/
  13. ////////////////////////////////////////////////////////////////////////////////
  14. // Generic Function
  15. ////////////////////////////////////////////////////////////////////////////////
  16. // 画像のカラー情報の取得
  17. function getColorInfo(imagedata){
  18. var height = imagedata.height;
  19. var width = imagedata.width;
  20. var raw = imagedata.data;
  21. // 使用色/使用回数(面積)を取得
  22. var cnt = 0;
  23. var uses_colors = new Object;
  24. for(var i = 0; i< height;i++){
  25. for(var j = 0; j< width;j++){
  26. var key = raw[cnt] + ',' +
  27. raw[cnt+1] + ',' +
  28. raw[cnt+2] ;
  29. if (!uses_colors[key])
  30. uses_colors[key] = 1;
  31. else
  32. uses_colors[key] += 1;
  33. cnt = cnt + 4;
  34. }
  35. }
  36. // 連想配列を配列へ設定
  37. var rgb;
  38. var colors = new Array();
  39. for (var key in uses_colors) {
  40. rgb = key.split(",");
  41. colors[colors.length] = {'r':parseInt(rgb[0],10),
  42. 'g':parseInt(rgb[1],10),
  43. 'b':parseInt(rgb[2],10),
  44. 'uses':uses_colors[key]}; // 使用数
  45. }
  46. return colors;
  47. }
  48. ////////////////////////////////////////////////////////////////////////////////
  49. // Generic Class
  50. ////////////////////////////////////////////////////////////////////////////////
  51. // ---------------------
  52. // TMedianCut
  53. // ---------------------
  54. // imagedata : 減色するImageDataオブジェクト
  55. // colors : getColorInfo()で取得したカラー情報
  56. function TMedianCut(imagedata,colors) {
  57. this.raw = imagedata.data;
  58. this.width = imagedata.width;
  59. this.height = imagedata.height;
  60. this.msg = '';
  61. this.colors = colors;
  62. }
  63. // ---------------------
  64. // TMedianCut.Method
  65. // ---------------------
  66. TMedianCut.prototype = {
  67. // プロパティの設定
  68. _setProperty : function (color){
  69. var total = 0;
  70. var maxR = 0, maxG = 0, maxB = 0;
  71. var minR = 255, minG = 255, minB = 255;
  72. // 立方体の1辺の長さ
  73. for(var i = 0; i < color.length;i++){
  74. if (color[i].rgb.r > maxR) maxR = color[i].rgb.r ;
  75. if (color[i].rgb.g > maxG) maxG = color[i].rgb.g ;
  76. if (color[i].rgb.b > maxB) maxB = color[i].rgb.b ;
  77. if (color[i].rgb.r < minR) minR = color[i].rgb.r ;
  78. if (color[i].rgb.g < minG) minG = color[i].rgb.g ;
  79. if (color[i].rgb.b < minB) minB = color[i].rgb.b ;
  80. // キューブで使用している面積
  81. total += color[i].rgb.uses;
  82. }
  83. var dr = (maxR - minR)*1.2;
  84. var dg = (maxG - minG)*1.2;
  85. var db = (maxB - minB);
  86. // 同一の場合はrを優先する
  87. var colortype = 'r';
  88. // r
  89. if (dr > dg && dr > db){
  90. colortype = 'r';
  91. }
  92. // g
  93. if (dg > dr && dg > db){
  94. colortype = 'g';
  95. }
  96. // b
  97. if (db > dr && db > dg){
  98. colortype = 'b';
  99. }
  100. return { 'color' : color, // キューブの各色情報
  101. 'total' : total, // キューブの総面積(総色数)
  102. 'type' : colortype,// キューブの種類(R/G/B)
  103. // キューブの体積用 'volume': dr * dg * db
  104. };
  105. },
  106. // メディアンカット
  107. _MedianCut : function(cubes,colorsize){
  108. var count = 0;
  109. var index = 0;
  110. // 面積(色数)が最大のキューブを選択
  111. for(var i = 0; i < cubes.length;i++){
  112. if(cubes[i].total > count){
  113. // 1点は除く
  114. if (cubes[i].color.length != 1){
  115. index = i;
  116. count = cubes[i].total;
  117. }
  118. }
  119. }
  120. // 体積が最大のキューブを選択
  121. //if(cubes[index].color.length == 1){
  122. //
  123. // count =0; index =0;
  124. //
  125. // for(var i = 0; i < cubes.length;i++){
  126. // if(cubes[i].volume > count){
  127. // index = i;
  128. // count = cubes[i].volume;
  129. // }
  130. // }
  131. //}
  132. if (cubes[index].total == 1){
  133. // Cube could not be split.
  134. this.msg += colorsize + '色までキューブを分割できませんでした。\n';
  135. return cubes;
  136. }
  137. if(cubes[index].color.length == 1){
  138. // Cube could not be split.
  139. this.msg += colorsize + '色までキューブを分割できませんでした。\n';
  140. return cubes;
  141. }
  142. // メディアン由来の中央値を算出する
  143. var colortype = cubes[index].type;
  144. cubes[index].color.sort(function(a,b){
  145. if(a.rgb[colortype] < b.rgb[colortype] ) return -1;
  146. if(a.rgb[colortype] > b.rgb[colortype] ) return 1;
  147. return 0;
  148. });
  149. split_border = Math.floor((cubes[index].color.length+1)/2);
  150. // 分割の開始
  151. var split1 = new Array;
  152. var split2 = new Array;
  153. for(var i = 0; i < cubes[index].color.length;i++){
  154. if (i < split_border){
  155. split1[split1.length] = cubes[index].color[i];
  156. }else{
  157. split2[split2.length] = cubes[index].color[i];
  158. }
  159. }
  160. // プロパティの設定
  161. split1 = this._setProperty(split1);
  162. split2 = this._setProperty(split2);
  163. // キューブ配列の再編成
  164. var result = new Array();
  165. for(var i = 0; i < cubes.length;i++){
  166. if (i != index){
  167. result[result.length] = cubes[i];
  168. }
  169. }
  170. result[result.length] = split1;
  171. result[result.length] = split2;
  172. if (result.length < colorsize){
  173. return this._MedianCut(result,colorsize);
  174. }else{
  175. return result;
  176. }
  177. },
  178. // 減色の実行
  179. // colorsize : 最大何色まで減色するかの色数(2- 256)
  180. // update : true ピクセルデータを更新 false 更新しない
  181. run : function(colorsize,update){
  182. if (this.colors.length <= colorsize){
  183. // It has already been reduced color.
  184. this.msg = '既に'+ this.colors.length +'色に減色されています。\n';
  185. //return;
  186. }
  187. // 1個目のキューブの作成
  188. var plane = new Array;
  189. for(var i = 0; i < this.colors.length;i++){
  190. plane[plane.length] = {'rgb': this.colors[i]};
  191. }
  192. var dummy = new Array();
  193. dummy[0] = this._setProperty(plane);
  194. // キューブの分割
  195. var cubes = this._MedianCut(dummy,colorsize);
  196. // キューブ毎に代表色(重み係数による平均)を算出する
  197. var rep_color = new Array();
  198. for(var i = 0; i < cubes.length;i++){
  199. var count = 0;
  200. var r =0,g=0,b=0;
  201. for(var j = 0; j < cubes[i].color.length;j++){
  202. r += cubes[i].color[j].rgb.r * cubes[i].color[j].rgb.uses;
  203. g += cubes[i].color[j].rgb.g * cubes[i].color[j].rgb.uses;
  204. b += cubes[i].color[j].rgb.b * cubes[i].color[j].rgb.uses;
  205. count += cubes[i].color[j].rgb.uses;
  206. }
  207. rep_color[i] = {'r': Math.round(r/count),
  208. 'g': Math.round(g/count),
  209. 'b': Math.round(b/count)};
  210. }
  211. // 代表色の保存
  212. this.rep_color = rep_color;
  213. // ピクセルデータの更新
  214. if (update) {
  215. // ピクセルデータ設定用の連想配列(高速化用)
  216. var pixels = new Object;
  217. for(var i = 0; i < cubes.length;i++){
  218. for(var j = 0; j < cubes[i].color.length;j++){
  219. pixels[cubes[i].color[j].rgb.r + ',' +
  220. cubes[i].color[j].rgb.g + ',' +
  221. cubes[i].color[j].rgb.b] = {'r': rep_color[i].r,
  222. 'g': rep_color[i].g,
  223. 'b': rep_color[i].b};
  224. }
  225. }
  226. // データの設定
  227. var key,cnt =0;
  228. for(var i = 0; i< this.height;i++){
  229. for(var j = 0; j< this.width;j++){
  230. key = this.raw[cnt] + ',' +
  231. this.raw[cnt+1] + ',' +
  232. this.raw[cnt+2];
  233. this.raw[cnt] = pixels[key].r;
  234. this.raw[cnt+1] = pixels[key].g;
  235. this.raw[cnt+2] = pixels[key].b;
  236. cnt = cnt + 4;
  237. }
  238. }
  239. }
  240. }
  241. }