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