1  /**
     2   * properties.ColorShortcuts
     3   * List of default special color properties (normal and splitter properties) for the Tweener class
     4   * The function names are strange/inverted because it makes for easier debugging (alphabetic order). They're only for internal use (on this class) anyways.
     5   *
     6   * @author		Zeh Fernando, Nate Chatellier, Arthur Debert
     7   * @version		1.0.0
     8   */
     9  
    10  import flash.geom.ColorTransform;
    11  import flash.filters.ColorMatrixFilter;
    12  
    13  import caurina.transitions.Tweener;
    14  import caurina.transitions.AuxFunctions;
    15  
    16  class caurina.transitions.properties.ColorShortcuts {
    17  
    18  	// Sources:
    19  	// http://www.graficaobscura.com/matrix/index.html
    20  	// And mario Klingemann's ColorMatrix class as mentioned on the credits:
    21  	// http://www.quasimondo.com/archives/000565.php
    22  	
    23  	// Defines luminance using sRGB luminance
    24  	private static var LUMINANCE_R:Number = 0.212671;
    25  	private static var LUMINANCE_G:Number = 0.715160;
    26  	private static var LUMINANCE_B:Number = 0.072169;
    27  	
    28  	/**
    29  	 * There's no constructor.
    30  	 */
    31  	public function ColorShortcuts () {
    32  		trace ("This is an static class and should not be instantiated.")
    33  	}
    34  
    35  	/**
    36  	 * Registers all the special properties to the Tweener class, so the Tweener knows what to do with them.
    37  	 */
    38  	public static function init():Void {
    39  
    40  		// Normal properties
    41  		Tweener.registerSpecialProperty("_color_ra", _oldColor_property_get, _oldColor_property_set, ["ra"]);
    42  		Tweener.registerSpecialProperty("_color_rb", _oldColor_property_get, _oldColor_property_set, ["rb"]);
    43  		Tweener.registerSpecialProperty("_color_ga", _oldColor_property_get, _oldColor_property_set, ["ga"]);
    44  		Tweener.registerSpecialProperty("_color_gb", _oldColor_property_get, _oldColor_property_set, ["gb"]);
    45  		Tweener.registerSpecialProperty("_color_ba", _oldColor_property_get, _oldColor_property_set, ["ba"]);
    46  		Tweener.registerSpecialProperty("_color_bb", _oldColor_property_get, _oldColor_property_set, ["bb"]);
    47  		Tweener.registerSpecialProperty("_color_aa", _oldColor_property_get, _oldColor_property_set, ["aa"]);
    48  		Tweener.registerSpecialProperty("_color_ab", _oldColor_property_get, _oldColor_property_set, ["ab"]);
    49  
    50  		Tweener.registerSpecialProperty("_color_redMultiplier", 	_color_property_get, _color_property_set, ["redMultiplier"]);
    51  		Tweener.registerSpecialProperty("_color_redOffset",			_color_property_get, _color_property_set, ["redOffset"]);
    52  		Tweener.registerSpecialProperty("_color_greenMultiplier",	_color_property_get, _color_property_set, ["greenMultiplier"]);
    53  		Tweener.registerSpecialProperty("_color_greenOffset",		_color_property_get, _color_property_set, ["greenOffset"]);
    54  		Tweener.registerSpecialProperty("_color_blueMultiplier",	_color_property_get, _color_property_set, ["blueMultiplier"]);
    55  		Tweener.registerSpecialProperty("_color_blueOffset",		_color_property_get, _color_property_set, ["blueOffset"]);
    56  		Tweener.registerSpecialProperty("_color_alphaMultiplier",	_color_property_get, _color_property_set, ["alphaMultiplier"]);
    57  		Tweener.registerSpecialProperty("_color_alphaOffset",		_color_property_get, _color_property_set, ["alphaOffset"]);
    58  
    59  		// Normal splitter properties
    60  		Tweener.registerSpecialPropertySplitter("_color", _color_splitter);
    61  		Tweener.registerSpecialPropertySplitter("_colorTransform", _colorTransform_splitter);
    62  
    63  		// Color changes that depend on the ColorMatrixFilter
    64  		Tweener.registerSpecialProperty("_brightness",		_brightness_get,	_brightness_set, [false]);
    65  		Tweener.registerSpecialProperty("_tintBrightness",	_brightness_get,	_brightness_set, [true]);
    66  		Tweener.registerSpecialProperty("_contrast",		_contrast_get,		_contrast_set);
    67  		Tweener.registerSpecialProperty("_hue",				_hue_get,			_hue_set);
    68  		Tweener.registerSpecialProperty("_saturation",		_saturation_get,	_saturation_set, [false]);
    69  		Tweener.registerSpecialProperty("_dumbSaturation",	_saturation_get,	_saturation_set, [true]);
    70  
    71  	}
    72  
    73  
    74  	// ==================================================================================================================================
    75  	// PROPERTY GROUPING/SPLITTING functions --------------------------------------------------------------------------------------------
    76  
    77  	// ----------------------------------------------------------------------------------------------------------------------------------
    78  	// _color
    79  
    80  	/**
    81  	 * Splits the _color parameter into specific color variables
    82  	 *
    83  	 * @param		p_value				Number		The original _color value
    84  	 * @return							Array		An array containing the .name and .value of all new properties
    85  	 */
    86  	public static function _color_splitter (p_value:Number, p_parameters:Array):Array {
    87  		var nArray:Array = new Array();
    88  		if (p_value == null) {
    89  			// No parameter passed, so just resets the color
    90  			nArray.push({name:"_color_redMultiplier",	value:1});
    91  			nArray.push({name:"_color_redOffset",		value:0});
    92  			nArray.push({name:"_color_greenMultiplier",	value:1});
    93  			nArray.push({name:"_color_greenOffset",		value:0});
    94  			nArray.push({name:"_color_blueMultiplier",	value:1});
    95  			nArray.push({name:"_color_blueOffset",		value:0});
    96  		} else {
    97  			// A color tinting is passed, so converts it to the object values
    98  			nArray.push({name:"_color_redMultiplier",	value:0});
    99  			nArray.push({name:"_color_redOffset",		value:AuxFunctions.numberToR(p_value)});
   100  			nArray.push({name:"_color_greenMultiplier",	value:0});
   101  			nArray.push({name:"_color_greenOffset",		value:AuxFunctions.numberToG(p_value)});
   102  			nArray.push({name:"_color_blueMultiplier",	value:0});
   103  			nArray.push({name:"_color_blueOffset",		value:AuxFunctions.numberToB(p_value)});
   104  		}
   105  		return nArray;
   106  	}
   107  
   108  
   109  	// ----------------------------------------------------------------------------------------------------------------------------------
   110  	// _colorTransform
   111  
   112  	/**
   113  	 * Splits the _colorTransform parameter into specific color variables
   114  	 *
   115  	 * @param		p_value				Number		The original _colorTransform value
   116  	 * @return							Array		An array containing the .name and .value of all new properties
   117  	 */
   118  	public static function _colorTransform_splitter (p_value:Object, p_parameters:Array):Array {
   119  		var nArray:Array = new Array();
   120  		if (p_value == null) {
   121  			// No parameter passed, so just resets the color
   122  			nArray.push({name:"_color_redMultiplier",	value:1});
   123  			nArray.push({name:"_color_redOffset",		value:0});
   124  			nArray.push({name:"_color_greenMultiplier",	value:1});
   125  			nArray.push({name:"_color_greenOffset",		value:0});
   126  			nArray.push({name:"_color_blueMultiplier",	value:1});
   127  			nArray.push({name:"_color_blueOffset",		value:0});
   128  		} else {
   129  			// A color tinting is passed, so converts it to the object values
   130  			if (p_value.ra != undefined) nArray.push({name:"_color_ra", value:p_value.ra});
   131  			if (p_value.rb != undefined) nArray.push({name:"_color_rb", value:p_value.rb});
   132  			if (p_value.ga != undefined) nArray.push({name:"_color_ba", value:p_value.ba});
   133  			if (p_value.gb != undefined) nArray.push({name:"_color_bb", value:p_value.bb});
   134  			if (p_value.ba != undefined) nArray.push({name:"_color_ga", value:p_value.ga});
   135  			if (p_value.bb != undefined) nArray.push({name:"_color_gb", value:p_value.gb});
   136  			if (p_value.aa != undefined) nArray.push({name:"_color_aa", value:p_value.aa});
   137  			if (p_value.ab != undefined) nArray.push({name:"_color_ab", value:p_value.ab});
   138  			if (p_value.redMultiplier != undefined)		nArray.push({name:"_color_redMultiplier", value:p_value.redMultiplier});
   139  			if (p_value.redOffset != undefined)			nArray.push({name:"_color_redOffset", value:p_value.redOffset});
   140  			if (p_value.blueMultiplier != undefined)	nArray.push({name:"_color_blueMultiplier", value:p_value.blueMultiplier});
   141  			if (p_value.blueOffset != undefined)		nArray.push({name:"_color_blueOffset", value:p_value.blueOffset});
   142  			if (p_value.greenMultiplier != undefined)	nArray.push({name:"_color_greenMultiplier", value:p_value.greenMultiplier});
   143  			if (p_value.greenOffset != undefined)		nArray.push({name:"_color_greenOffset", value:p_value.greenOffset});
   144  			if (p_value.alphaMultiplier != undefined)	nArray.push({name:"_color_alphaMultiplier", value:p_value.alphaMultiplier});
   145  			if (p_value.alphaOffset != undefined)		nArray.push({name:"_color_alphaOffset", value:p_value.alphaOffset});
   146  		}
   147  		return nArray;
   148  	}
   149  
   150  
   151  	// ==================================================================================================================================
   152  	// NORMAL SPECIAL PROPERTY functions ------------------------------------------------------------------------------------------------
   153  
   154  	// ----------------------------------------------------------------------------------------------------------------------------------
   155  	// _color_*
   156  
   157  	/**
   158  	 * _color_*
   159  	 * Generic function for the ra/rb/etc components of the deprecated colorTransform object
   160  	 */
   161  	public static function _oldColor_property_get (p_obj:Object, p_parameters:Array):Number {
   162  		return (new Color(p_obj)).getTransform()[p_parameters[0]];
   163  	}
   164  	public static function _oldColor_property_set (p_obj:Object, p_value:Number, p_parameters:Array):Void {
   165  		var cfObj:Object = new Object();
   166  		cfObj[p_parameters[0]] = p_value; // Math.round(p_value);
   167  		(new Color(p_obj)).setTransform(cfObj);
   168  	}
   169  
   170  	/**
   171  	 * _color_*
   172  	 * Generic function for the redMultiplier/redOffset/etc components of the new colorTransform
   173  	 */
   174  	public static function _color_property_get (p_obj:Object, p_parameters:Array):Number {
   175  		return p_obj.transform.colorTransform[p_parameters[0]];
   176  	}
   177  	public static function _color_property_set (p_obj:Object, p_value:Number, p_parameters:Array):Void {
   178  		var cfm:ColorTransform = p_obj.transform.colorTransform;
   179  		cfm[p_parameters[0]] = p_value;
   180  		p_obj.transform.colorTransform = cfm;
   181  	}
   182  
   183  	// ----------------------------------------------------------------------------------------------------------------------------------
   184  	// Special coloring
   185  
   186  	/**
   187  	 * _brightness
   188  	 * Brightness of an object: -1 -> [0] -> +1
   189  	 */
   190  	public static function _brightness_get (p_obj:Object, p_parameters:Array):Number {
   191  
   192  		var isTint:Boolean = p_parameters[0];
   193  
   194  		/*
   195  		// Using ColorMatrix:
   196  		
   197  		var mtx:Array = getObjectMatrix(p_obj);
   198  		
   199  		var mc:Number = 1 - ((mtx[0] + mtx[6] + mtx[12]) / 3); // Brightness as determined by the main channels
   200  		var co:Number = (mtx[4] + mtx[9] + mtx[14]) / 3; // Brightness as determined by the offset channels
   201  		*/
   202  
   203  		var cfm:Object = (new Color(p_obj)).getTransform();
   204  		var mc:Number = 1 - ((cfm.ra + cfm.ga + cfm.ba) / 300); // Brightness as determined by the main channels
   205  		var co:Number = (cfm.rb + cfm.gb + cfm.bb) / 3;
   206  
   207  		if (isTint) {
   208  			// Tint style
   209  			return co > 0 ? co / 255 : -mc;
   210  		} else {
   211  			// Native, Flash "Adjust Color" and Photoshop style
   212  			return co / 100;
   213  		}
   214  	}
   215  	public static function _brightness_set (p_obj:Object, p_value:Number, p_parameters:Array):Void {
   216  		//var mtx:Array = getObjectMatrix(p_obj);
   217  
   218  		var isTint:Boolean = p_parameters[0];
   219  
   220  		var mc:Number; // Main channel
   221  		var co:Number; // Channel offset
   222  
   223  		if (isTint) {
   224  			// Tint style
   225  			mc = 1 - Math.abs(p_value);
   226  			co = p_value > 0 ? Math.round(p_value*255) : 0;
   227  		} else {
   228  			// Native, Flash "Adjust Color" and Photoshop style
   229  			mc = 1;
   230  			co = Math.round(p_value*100);
   231  		}
   232  
   233  		/*
   234  		// Using ColorMatrix:
   235  		var mtx:Array = [
   236  			mc, cc, cc, cc, co,
   237  			cc, mc, cc, cc, co,
   238  			cc, cc, mc, cc, co,
   239  			0,  0,  0,  1,  0
   240  		];
   241  		setObjectMatrix(p_obj, mtx);
   242  		*/
   243  		var cfm:Object = {ra:mc * 100, rb:co, ga:mc * 100, gb:co, ba:mc * 100, bb:co};
   244  		(new Color(p_obj)).setTransform(cfm);
   245  	}
   246  
   247  	/**
   248  	 * _saturation
   249  	 * Saturation of an object: 0 -> [1] -> 2
   250  	 */
   251  	public static function _saturation_get (p_obj:Object, p_parameters:Array):Number {
   252  
   253  		var mtx:Array = getObjectMatrix(p_obj);
   254  
   255  		var isDumb:Boolean = p_parameters[0];
   256  		var rl:Number = isDumb ? 1/3 : LUMINANCE_R;
   257  		var gl:Number = isDumb ? 1/3 : LUMINANCE_G;
   258  		var bl:Number = isDumb ? 1/3 : LUMINANCE_B;
   259  
   260  		var mc:Number = ((mtx[0]-rl)/(1-rl) + (mtx[6]-gl)/(1-gl) + (mtx[12]-bl)/(1-bl)) / 3;					// Color saturation as determined by the main channels
   261  		var cc:Number = 1 - ((mtx[1]/gl + mtx[2]/bl + mtx[5]/rl + mtx[7]/bl + mtx[10]/rl + mtx[11]/gl) / 6);	// Color saturation as determined by the other channels
   262  		return (mc + cc) / 2;
   263  	}
   264  	public static function _saturation_set (p_obj:Object, p_value:Number, p_parameters:Array):Void {
   265  		
   266  		var isDumb:Boolean = p_parameters[0];
   267  		var rl:Number = isDumb ? 1/3 : LUMINANCE_R;
   268  		var gl:Number = isDumb ? 1/3 : LUMINANCE_G;
   269  		var bl:Number = isDumb ? 1/3 : LUMINANCE_B;
   270  
   271  		var sf:Number = p_value;
   272  		var nf:Number = 1-sf;
   273  		var nr:Number = rl * nf;
   274  		var ng:Number = gl * nf;
   275  		var nb:Number = bl * nf;
   276  
   277  		var mtx:Array = [
   278  			nr+sf,	ng,		nb,		0,	0,
   279  			nr,		ng+sf,	nb,		0,	0,
   280  			nr,		ng,		nb+sf,	0,	0,
   281  			0,  	0, 		0,  	1,  0
   282  		];
   283  		setObjectMatrix(p_obj, mtx);
   284  	}
   285  
   286  	/**
   287  	 * _contrast
   288  	 * Contrast of an object: -1 -> [0] -> +1
   289  	 */
   290  	public static function _contrast_get (p_obj:Object, p_parameters:Array):Number {
   291  
   292  		/*
   293  		// Using ColorMatrix:
   294  		var mtx:Array = getObjectMatrix(p_obj);
   295  
   296  		var mc:Number = ((mtx[0] + mtx[6] + mtx[12]) / 3) - 1;		// Contrast as determined by the main channels
   297  		var co:Number = (mtx[4] + mtx[9] + mtx[14]) / 3 / -128;		// Contrast as determined by the offset channel
   298  		*/
   299  		var cfm:Object = (new Color(p_obj)).getTransform();
   300  		var mc:Number;	// Contrast as determined by the main channels
   301  		var co:Number;	// Contrast as determined by the offset channel
   302  		mc = ((cfm.ra + cfm.ga + cfm.ba) / 300) - 1;
   303  		co = (cfm.rb + cfm.gb + cfm.bb) / 3 / -128;
   304  		/*
   305  		if (cfm.ra < 100) {
   306  			// Low contrast
   307  			mc = ((cfm.ra + cfm.ga + cfm.ba) / 300) - 1;
   308  			co = (cfm.rb + cfm.gb + cfm.bb) / 3 / -128;
   309  		} else {
   310  			// High contrast
   311  			mc = (((cfm.ra + cfm.ga + cfm.ba) / 300) - 1) / 37;
   312  			co = (cfm.rb + cfm.gb + cfm.bb) / 3 / -3840;
   313  		}
   314  		*/
   315  
   316  		return (mc+co)/2;
   317  	}
   318  	public static function _contrast_set (p_obj:Object, p_value:Number, p_parameters:Array):Void {
   319  		
   320  		var mc:Number;	// Main channel
   321  		var co:Number;	// Channel offset
   322  		mc = p_value + 1;
   323  		co = Math.round(p_value*-128);
   324  
   325  		/*
   326  		if (p_value < 0) {
   327  			// Low contrast
   328  			mc = p_value + 1;
   329  			co = Math.round(p_value*-128);
   330  		} else {
   331  			// High contrast
   332  			mc = (p_value * 37) + 1;
   333  			co = Math.round(p_value*-3840);
   334  		}
   335  		*/
   336  		
   337  		// Flash: * 8, * -512
   338  
   339  		/*
   340  		// Using ColorMatrix:
   341  		var mtx:Array = [
   342  			mc,	0,	0, 	0, co,
   343  			0,	mc,	0, 	0, co,
   344  			0,	0,	mc,	0, co,
   345  			0,  0, 	0, 	1,  0
   346  		];
   347  		setObjectMatrix(p_obj, mtx);
   348  		*/
   349  		var cfm:Object = {ra:mc * 100, rb:co, ga:mc * 100, gb:co, ba:mc * 100, bb:co};
   350  		(new Color(p_obj)).setTransform(cfm);
   351  	}
   352  
   353  	/**
   354  	 * _hue
   355  	 * Hue of an object: -180 -> [0] -> 180
   356  	 */
   357  	public static function _hue_get (p_obj:Object, p_parameters:Array):Number {
   358  
   359  		var mtx:Array = getObjectMatrix(p_obj);
   360  
   361  		// Find the current Hue based on a given matrix.
   362  		// This is a kind of a brute force method by sucessive division until a close enough angle is found.
   363  		// Reverse-engineering the hue equation would be is a better choice, but it's hard to find material
   364  		// on the correct calculation employed by Flash.
   365  		// This code has to run only once (before the tween starts), so it's good enough.
   366  
   367  		var hues:Array = [];
   368  		hues[0] = {angle:-179.9, matrix:getHueMatrix(-179.9)};
   369  		hues[1] = {angle:180, matrix:getHueMatrix(180)};
   370  	
   371  		for (var i:Number = 0; i < hues.length; i++) {
   372  			hues[i].distance = getHueDistance(mtx, hues[i].matrix);
   373  		}
   374  
   375  		var maxTries:Number = 15;	// Number o maximum divisions until the hue is found
   376  		var angleToSplit:Number;
   377  
   378  		for (var i:Number = 0; i < maxTries; i++) {
   379  			// Find the nearest angle
   380  			if (hues[0].distance < hues[1].distance) {
   381  				// First is closer
   382  				angleToSplit = 1;
   383  			} else {
   384  				// Second is closer
   385  				angleToSplit = 0;
   386  			}
   387  			hues[angleToSplit].angle = (hues[0].angle + hues[1].angle)/2;
   388  			hues[angleToSplit].matrix = getHueMatrix(hues[angleToSplit].angle)
   389  			hues[angleToSplit].distance = getHueDistance(mtx, hues[angleToSplit].matrix);
   390  		}
   391  
   392  		return hues[angleToSplit].angle;
   393  	}
   394  
   395  	public static function _hue_set (p_obj:Object, p_value:Number, p_parameters:Array):Void {
   396  		setObjectMatrix(p_obj, getHueMatrix(p_value));
   397  	}
   398  
   399  	public static function getHueDistance (mtx1:Array, mtx2:Array): Number {
   400  		return (Math.abs(mtx1[0] - mtx2[0]) + Math.abs(mtx1[1] - mtx2[1]) + Math.abs(mtx1[2] - mtx2[2]));
   401  	}
   402  
   403  	public static function getHueMatrix (hue:Number): Array {
   404  		var ha:Number = hue * Math.PI/180;		// Hue angle, to radians
   405  
   406   		var rl:Number = LUMINANCE_R;
   407  		var gl:Number = LUMINANCE_G;
   408  		var bl:Number = LUMINANCE_B;
   409  
   410  		var c:Number = Math.cos(ha);
   411  		var s:Number = Math.sin(ha);
   412  
   413  		var mtx:Array = [
   414  			(rl + (c * (1 - rl))) + (s * (-rl)),
   415  			(gl + (c * (-gl))) + (s * (-gl)),
   416  			(bl + (c * (-bl))) + (s * (1 - bl)),
   417  			0, 0,
   418  
   419  			(rl + (c * (-rl))) + (s * 0.143),
   420  			(gl + (c * (1 - gl))) + (s * 0.14),
   421  			(bl + (c * (-bl))) + (s * -0.283),
   422  			0, 0,
   423  
   424  			(rl + (c * (-rl))) + (s * (-(1 - rl))),
   425  			(gl + (c * (-gl))) + (s * gl),
   426  			(bl + (c * (1 - bl))) + (s * bl),
   427  			0, 0,
   428  
   429  			0, 0, 0, 1, 0
   430  		];
   431  		
   432  		return mtx;
   433  	}
   434  
   435  
   436  	// ==================================================================================================================================
   437  	// AUXILIARY functions --------------------------------------------------------------------------------------------------------------
   438  
   439  	private static function getObjectMatrix(p_obj:Object): Array {
   440  		// Get the current color matrix of an object
   441  		for (var i:Number = 0; i < p_obj.filters.length; i++) {
   442  			if (p_obj.filters[i] instanceof ColorMatrixFilter) {
   443  				return p_obj.filters[i].matrix.concat();
   444  			}
   445  		}
   446  		return [
   447  			1, 0, 0, 0, 0,
   448  			0, 1, 0, 0, 0,
   449  			0, 0, 1, 0, 0,
   450  			0, 0, 0, 1, 0
   451  		];
   452  	}
   453  
   454  	private static function setObjectMatrix(p_obj:Object, p_matrix:Array): Void {
   455  		// Set the current color matrix of an object
   456  		var objFilters:Array = p_obj.filters.concat();
   457  		var found:Boolean = false;
   458  		for (var i:Number = 0; i < objFilters.length; i++) {
   459  			if (objFilters[i] instanceof ColorMatrixFilter) {
   460  				objFilters[i].matrix = p_matrix.concat();
   461  				found = true;
   462  			}
   463  		}
   464  		if (!found) {
   465  			// Has to create a new color matrix filter
   466  			var cmtx:ColorMatrixFilter = new ColorMatrixFilter(p_matrix);
   467  			objFilters[objFilters.length] = cmtx;
   468  		}
   469  		p_obj.filters = objFilters;
   470  	}
   471  }
   472