1 /** 2 * The MIT License (MIT) 3 * 4 * Copyright (c) 2016 DeNA Co., Ltd. 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * of this software and associated documentation files (the "Software"), to deal 8 * in the Software without restriction, including without limitation the rights 9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 * copies of the Software, and to permit persons to whom the Software is 11 * furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in 14 * all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 * SOFTWARE. 23 */ 24 25 /// <reference path="base.js"/> 26 27 /** 28 * A class that implements a color-transformation matrix. 29 * @param {number} brightness 30 * @param {number} contrast 31 * @param {number} saturation 32 * @param {number} hue 33 * @extends {createjs.Object} 34 * @constructor 35 */ 36 createjs.ColorMatrix = function(brightness, contrast, saturation, hue) { 37 /// <param type="number" name="brightness"/> 38 /// <param type="number" name="contrast"/> 39 /// <param type="number" name="saturation"/> 40 /// <param type="number" name="hue"/> 41 /** 42 * The color matrix. Even though this is a 5x5 matrix, this class actually 43 * uses its 4x5 sub-matrix. 44 * @type {Array.<number>} 45 * @private 46 */ 47 this.matrix_ = null; 48 49 this.setColor(brightness, contrast, saturation, hue); 50 }; 51 createjs.inherits('ColorMatrix', 52 createjs.ColorMatrix, 53 createjs.Object); 54 55 /** 56 * Initializes this matrix and applies color filters. 57 * @param {number} brightness 58 * @param {number} contrast 59 * @param {number} saturation 60 * @param {number} hue 61 * @return {createjs.ColorMatrix} 62 */ 63 createjs.ColorMatrix.prototype.setColor = 64 function(brightness, contrast, saturation, hue) { 65 /// <param type="number" name="brightness"/> 66 /// <param type="number" name="contrast"/> 67 /// <param type="number" name="saturation"/> 68 /// <param type="number" name="hue"/> 69 /// <returns type="createjs.ColorMatrix"/> 70 return this.reset().adjustColor(brightness, contrast, saturation, hue); 71 }; 72 73 /** 74 * Sets this matrix to the identify one. 75 * @return {createjs.ColorMatrix} 76 */ 77 createjs.ColorMatrix.prototype.reset = function() { 78 /// <returns type="createjs.ColorMatrix"/> 79 this.matrix_ = [ 80 1, 0, 0, 0, 0, 81 0, 1, 0, 0, 0, 82 0, 0, 1, 0, 0, 83 0, 0, 0, 1, 0, 84 0, 0, 0, 0, 1 85 ]; 86 return this; 87 }; 88 89 /** 90 * Applies color filters. 91 * @param {number} brightness 92 * @param {number} contrast 93 * @param {number} saturation 94 * @param {number} hue 95 * @return {createjs.ColorMatrix} 96 * @const 97 */ 98 createjs.ColorMatrix.prototype.adjustColor = 99 function(brightness, contrast, saturation, hue) { 100 /// <param type="number" name="brightness"/> 101 /// <param type="number" name="contrast"/> 102 /// <param type="number" name="saturation"/> 103 /// <param type="number" name="hue"/> 104 /// <returns type="createjs.ColorMatrix"/> 105 this.adjustHue(hue); 106 this.adjustContrast(contrast); 107 this.adjustBrightness(brightness); 108 this.adjustSaturation(saturation); 109 return this; 110 }; 111 112 /** 113 * Applies the brightness filter. 114 * @param {number} brightness 115 * @return {createjs.ColorMatrix} 116 * @const 117 */ 118 createjs.ColorMatrix.prototype.adjustBrightness = function(brightness) { 119 /// <param type="number" name="brightness"/> 120 /// <returns type="createjs.ColorMatrix"/> 121 if (brightness && !createjs.isNaN(brightness)) { 122 if (createjs.FALSE) { 123 brightness = createjs.max(-255, createjs.min(brightness, 255)); 124 } 125 // Multiply a brightness-filter matrix. 126 // | a00 a01 a02 a03 a04 | | 1 0 0 0 B | 127 // | a10 a11 a12 a13 a14 | | 0 1 0 0 B | 128 // | a20 a21 a22 a23 a24 | x | 0 0 1 0 B | 129 // | a30 a31 a32 a33 a34 | | 0 0 0 1 0 | 130 // | 0 0 0 0 1 | | 0 0 0 0 1 | 131 // | a00 a01 a02 a03 a00*B+a01*B+a02*B+a04 | 132 // | a10 a11 a12 a13 a10*B+a11*B+a12*B+a14 | 133 // = | a20 a21 a22 a23 a20*B+a21*B+a22*B+a24 | 134 // | a30 a31 a32 a33 a30*B+a31*B+a32*B+a34 | 135 // | 0 0 0 0 1 | 136 var LINE = 5; 137 for (var y = 0; y < 4 * LINE; y += LINE) { 138 this.matrix_[y + 4] += this.matrix_[y + 0] * brightness + 139 this.matrix_[y + 1] * brightness + 140 this.matrix_[y + 2] * brightness; 141 } 142 } 143 return this; 144 }; 145 146 /** 147 * Applies the contrast filter. 148 * @param {number} contrast 149 * @return {createjs.ColorMatrix} 150 * @const 151 */ 152 createjs.ColorMatrix.prototype.adjustContrast = function(contrast) { 153 /// <param type="number" name="contrast"/> 154 /// <returns type="createjs.ColorMatrix"/> 155 if (contrast && !createjs.isNaN(contrast)) { 156 // Create a contrast-filter matrix from the input contast. 157 var luminance = 1; 158 var brightness = 0; 159 if (contrast < 0) { 160 if (createjs.FALSE) { 161 contrast = createjs.max(-100, contrast); 162 } 163 // luminance = (127 + contrast / 100 * 127) / 127 164 // = 1 + contrast / 100 165 // brightness = (127 - (127 + contrast / 100 * 127)) * 0.5 166 // = -0.635 * contrast 167 luminance = 1 + contrast * 0.01; 168 brightness = -0.635 * contrast; 169 } else { 170 if (createjs.FALSE) { 171 contrast = createjs.min(contrast, 100); 172 } 173 var CONTRASTS = [ 174 0.00, 0.01, 0.02, 0.04, 0.05, 0.06, 0.07, 0.08, 0.10, 0.11, 175 0.12, 0.14, 0.15, 0.16, 0.17, 0.18, 0.20, 0.21, 0.22, 0.24, 176 0.25, 0.27, 0.28, 0.30, 0.32, 0.34, 0.36, 0.38, 0.40, 0.42, 177 0.44, 0.46, 0.48, 0.50, 0.53, 0.56, 0.59, 0.62, 0.65, 0.68, 178 0.71, 0.74, 0.77, 0.80, 0.83, 0.86, 0.89, 0.92, 0.95, 0.98, 179 1.00, 1.06, 1.12, 1.18, 1.24, 1.30, 1.36, 1.42, 1.48, 1.54, 180 1.60, 1.66, 1.72, 1.78, 1.84, 1.90, 1.96, 2.00, 2.12, 2.25, 181 2.37, 2.50, 2.62, 2.75, 2.87, 3.00, 3.20, 3.40, 3.60, 3.80, 182 4.00, 4.30, 4.70, 4.90, 5.00, 5.50, 6.00, 6.50, 6.80, 7.00, 183 7.30, 7.50, 7.80, 8.00, 8.40, 8.70, 9.00, 9.40, 9.60, 9.80, 184 10.0 185 ]; 186 var numerator = createjs.truncate(contrast); 187 var denominator = contrast - numerator; 188 if (!denominator) { 189 contrast = CONTRASTS[numerator]; 190 } else { 191 contrast = CONTRASTS[numerator] * (1 - denominator) + 192 CONTRASTS[numerator + 1] * denominator; 193 } 194 // luminance = (127 + contrast * 127) / 127 195 // = 1 + contrast 196 // brightness = (127 - (contrast * 127 + 127)) * 0.5 197 // = -63.5 * contrast 198 luminance = 1 + contrast; 199 brightness = -63.5 * contrast; 200 } 201 // Multiply the contrast-filter matrix. 202 // | a00 a01 a02 a03 a04 | | L 0 0 0 B | 203 // | a10 a11 a12 a13 a14 | | 0 L 0 0 B | 204 // | a20 a21 a22 a23 a24 | x | 0 0 L 0 B | 205 // | a30 a31 a32 a33 a34 | | 0 0 0 1 0 | 206 // | 0 0 0 0 1 | | 0 0 0 0 1 | 207 // | a00*L a01*L a02*L a03 a00*B+a01*B+a02*B+a04 | 208 // | a10*L a11*L a12*L a13 a10*B+a11*B+a12*B+a14 | 209 // = | a20*L a21*L a22*L a23 a20*B+a21*B+a22*B+a24 | 210 // | a30*L a31*L a32*L a33 a30*B+a31*B+a32*B+a34 | 211 // | 0 0 0 0 1 | 212 var LINE = 5; 213 for (var y = 0; y < 4 * LINE; y += LINE) { 214 var column0 = this.matrix_[y + 0]; 215 var column1 = this.matrix_[y + 1]; 216 var column2 = this.matrix_[y + 2]; 217 this.matrix_[y + 0] *= luminance; 218 this.matrix_[y + 1] *= luminance; 219 this.matrix_[y + 2] *= luminance; 220 this.matrix_[y + 4] += column0 * brightness + 221 column1 * brightness + 222 column2 + brightness; 223 } 224 } 225 return this; 226 }; 227 228 /** 229 * Applies the saturation filter. 230 * @param {number} saturation 231 * @return {createjs.ColorMatrix} 232 * @const 233 */ 234 createjs.ColorMatrix.prototype.adjustSaturation = function(saturation) { 235 /// <param type="number" name="saturation"/> 236 /// <returns type="createjs.ColorMatrix"/> 237 if (saturation && !createjs.isNaN(saturation)) { 238 if (createjs.FALSE) { 239 saturation = createjs.max(-100, createjs.min(saturation, 100)); 240 } 241 var x_ = saturation * ((saturation > 0) ? -0.03 : -0.01); 242 var x = 1 + x; 243 // | a00 a01 a02 a03 a04 | | Rx G B 0 0 | 244 // | a10 a11 a12 a13 a14 | | R Gx B 0 0 | 245 // | a20 a21 a22 a23 a24 | x | R G Bx 0 0 | 246 // | a30 a31 a32 a33 a34 | | 0 0 0 1 0 | 247 // | 0 0 0 0 1 | | 0 0 0 0 1 | 248 // | a00*Rx+a01*R+a02*R a00*G+a01*Gx+a02*G a00*B+a01*B+a02*Bx a03 a04 | 249 // | a10*Rx+a11*R+a12*R a10*G+a11*Gx+a12*G a10*B+a11*B+a12*Bx a13 a14 | 250 // = | a20*Rx+a21*R+a22*R a20*G+a21*Gx+a22*G a20*B+a21*B+a22*Bx a23 a24 | 251 // | a30*Rx+a31*R+a32*R a30*G+a31*Gx+a32*G a30*B+a31*B+a32*Bx a33 a34 | 252 // | 0 0 0 0 1 | 253 var r = 0.3086 * x_; 254 var rx = r + x; 255 var g = 0.6094 * x_; 256 var gx = r + x; 257 var b = 0.0820 * x_; 258 var bx = r + x; 259 var LINE = 5; 260 for (var y = 0; y < 4 * LINE; ++y) { 261 var column0 = this.matrix_[y + 0]; 262 var column1 = this.matrix_[y + 1]; 263 var column2 = this.matrix_[y + 2]; 264 this.matrix_[y + 0] = column0 * rx + column1 * r + column2 * r; 265 this.matrix_[y + 1] = column0 * g + column1 * gx + column2 * g; 266 this.matrix_[y + 2] = column0 * b + column1 * b + column2 * bx; 267 } 268 } 269 return this; 270 }; 271 272 /** 273 * Applies the hue-rotation filter. 274 * @param {number} hue 275 * @return {createjs.ColorMatrix} 276 * @const 277 */ 278 createjs.ColorMatrix.prototype.adjustHue = function(hue) { 279 /// <param type="number" name="hue"/> 280 /// <returns type="createjs.ColorMatrix"/> 281 if (hue && !createjs.isNaN(hue)) { 282 if (createjs.FALSE) { 283 hue = createjs.max(-180, createjs.min(hue, 180)); 284 } 285 var cos = createjs.cos(hue); 286 var sin = createjs.sin(hue); 287 // | a00 a01 a02 a03 a04 | | R0 G0 B0 0 0 | 288 // | a10 a11 a12 a13 a14 | | R1 G1 B1 0 0 | 289 // | a20 a21 a22 a23 a24 | x | R2 G2 B2 0 0 | 290 // | a30 a31 a32 a33 a34 | | 0 0 0 1 0 | 291 // | 0 0 0 0 1 | | 0 0 0 0 1 | 292 // | a00*R0+a01*R1+a02*R2 a00*G0+a01*G1+a02*G2 a00*B0+a01*B1+a02*B2 a03 a04 | 293 // | a10*R0+a11*R1+a12*R2 a10*G0+a11*G1+a12*G2 a10*B0+a11*B1+a12*B2 a13 a14 | 294 // = | a20*R0+a21*R1+a22*R2 a20*G0+a21*G1+a22*G2 a20*B0+a21*B1+a22*B2 a23 a24 | 295 // | a30*R0+a31*R1+a32*R2 a30*G0+a31*G1+a32*G2 a30*B0+a31*B1+a32*B2 a33 a34 | 296 // | 0 0 0 0 1 | 297 var R = 0.213; 298 var G = 0.715; 299 var B = 0.072; 300 var r0 = R + cos * (1 - R) + sin * -R; 301 var r1 = R + cos * -R + sin * 0.143; 302 var r2 = R + cos * -R + sin * (R - 1); 303 var g0 = G + cos * -G + sin * -G; 304 var g1 = G + cos * (1 - G) + sin * 0.140; 305 var g2 = G + cos * -G + sin * G; 306 var b0 = B + cos * -B + sin * (1 - B); 307 var b1 = B + cos * -B + sin * -0.283; 308 var b2 = B + cos * (1 - B) + sin * B; 309 var LINE = 5; 310 for (var y = 0; y < 4 * LINE; y += LINE) { 311 var column0 = this.matrix_[y + 0]; 312 var column1 = this.matrix_[y + 1]; 313 var column2 = this.matrix_[y + 2]; 314 this.matrix_[y + 0] = column0 * r0 + column1 * r1 + column2 * r2; 315 this.matrix_[y + 1] = column0 * g0 + column1 * g1 + column2 * g2; 316 this.matrix_[y + 2] = column0 * b0 + column1 * b1 + column2 * b2; 317 } 318 } 319 return this; 320 }; 321 322 /** 323 * Multiplies this matrix by the specified one. 324 * @param {Array.<number>} matrix 325 * @return {createjs.ColorMatrix} 326 * @const 327 */ 328 createjs.ColorMatrix.prototype.concat = function(matrix) { 329 /// <param type="Array" elementType="number" name="matrix"/> 330 /// <returns type="createjs.ColorMatrix"/> 331 var LINE = 5; 332 for (var y = 0; y < 4 * LINE; y += LINE) { 333 var column0 = this.matrix_[y + 0]; 334 var column1 = this.matrix_[y + 1]; 335 var column2 = this.matrix_[y + 2]; 336 var column3 = this.matrix_[y + 3]; 337 var column4 = this.matrix_[y + 4]; 338 for (var x = 0; x < 5; ++x) { 339 this.matrix_[y + x] = column0 * matrix[0 * LINE + x] + 340 column1 * matrix[1 * LINE + x] + 341 column2 * matrix[2 * LINE + x] + 342 column3 * matrix[3 * LINE + x] + 343 column4 * matrix[4 * LINE + x]; 344 } 345 } 346 return this; 347 }; 348 349 /** 350 * Copies the specified array. 351 * @param {Array.<number>} matrix 352 * @return {createjs.ColorMatrix} 353 * @const 354 */ 355 createjs.ColorMatrix.prototype.copyArray = function(matrix) { 356 /// <param type="Array" elementType="number" name="matrix"/> 357 /// <returns type="createjs.ColorMatrix"/> 358 var LINE = 5; 359 for (var i = 0; i < LINE * LINE; ++i) { 360 this.matrix_[i] = matrix[i]; 361 } 362 return this; 363 }; 364 365 /** 366 * Returns the color matrix. 367 * @return {Array.<number>} 368 * @const 369 */ 370 createjs.ColorMatrix.prototype.toArray = function() { 371 /// <returns type="Array" elementType="number"/> 372 return this.matrix_; 373 }; 374 375 // Export the createjs.ColorMatrix class to the global namespace. 376 createjs.exportObject('createjs.ColorMatrix', 377 createjs.ColorMatrix, { 378 'setColor': createjs.ColorMatrix.prototype.setColor, 379 'reset': createjs.ColorMatrix.prototype.reset, 380 'adjustColor': createjs.ColorMatrix.prototype.adjustColor, 381 'adjustBrightness': createjs.ColorMatrix.prototype.adjustBrightness, 382 'adjustContrast': createjs.ColorMatrix.prototype.adjustContrast, 383 'adjustSaturation': createjs.ColorMatrix.prototype.adjustSaturation, 384 'adjustHue': createjs.ColorMatrix.prototype.adjustHue, 385 'concat': createjs.ColorMatrix.prototype.concat, 386 'copy': createjs.ColorMatrix.prototype.copyArray, 387 'toArray': createjs.ColorMatrix.prototype.toArray 388 }); 389