Figured I may as well take care of that other caveat from the original post. I need to publicly state that I simply translated this code from Grant Skinner’s ActionScript 2 ColorMatrix class. It is with Grant’s kind permission that I can post this for anyone else to use.

This took a while to complete, and I apologize for being MIA recently. The work of this script didn’t take very long at all, but gathering my wits to blog about it just kept taking a backseat. We’ve been rather busy, and hopefully we’ll have some cool new things to share soon.

At any rate, a complete code listing is available here, and below:



function ColorMatrix(m) {

	this._matrix = m?m.concat():ColorMatrix.IDENTITY_MATRIX.concat();

	/**
	 * Copies the supplied matrix to the internal matrix.
	 * @param	m	Array
	**/
	this.copyMatrix = function(m) {
		this._matrix = m.concat();
	}
	/**
	 * Ensures that the supplied matrix Array is of the correct length.  It slices off extraneous elements, or
	 * fills in missing elements with IDENTITY elements.
	 * @param	m	Array
	 * @return	Fixed Array
	**/
	this.fixMatrix = function(m) {
		if (m.length < ColorMatrix.LENGTH) {
			m = m.slice(0, m.length).concat(ColorMatrix.IDENTITY_MATRIX.slice(m.length,ColorMatrix.LENGTH));
		} else if (m.length > ColorMatrix.LENGTH) {
			m = m.slice(0, ColorMatrix.LENGTH);
		}
		return m;
	}

	/**
	 * Makes sure the incoming value in between a negative and positive limit value.
	 * @param	val		The value to clean
	 * @param	limit	The absolute value of the range.  If the limit is 100, then the value is ensured to be between -100 and 100.
	 * @return	Number; the cleaned value.
	**/
	this.cleanValue = function(val, limit) {
		return Math.min(limit, Math.max(-limit, val));
	}

	/**
	 * Core function for translating color adjustments to matrix arrays.  The internal matrix is affected.
	 * @param	m	Array
	**/
	this.multiplyMatrix = function(m) {
		var col = [];

		for (var i=0; i<5; i++) {
			for (j=0; j<5; j++) {
				col[j] = this._matrix[j+i*5];
			}
			for (var j=0; j<5; j++) {
				var val=0;
				for (var k=0; k<5; k++) {
					val += m[j+k*5]*col[k];
				}
				this._matrix[j+i*5] = val;
			}
		}
	}

	/**
	 * Adjusts the internal matrix array to reflect a brightness adjustment.
	 * @param	val		The brightness, from -100 to 100.
	**/
	this.adjustBrightness = function(val) {
		val = this.cleanValue(val,100);
		if (val == 0 || isNaN(val)) { return; }
		this.multiplyMatrix([
			1,0,0,0,val,
			0,1,0,0,val,
			0,0,1,0,val,
			0,0,0,1,0,
			0,0,0,0,1
		]);
	}

	/**
	 * @param	val	The Contrast, from -100 to 100
	**/
	this.adjustContrast = function(val) {
		val = this.cleanValue(val,100);
		if (val == 0 || isNaN(val)) { return; }
		var x;
		if (val < 0) {
			x = 127 + (val / 100) * 127
		} else {
			x = val % 1;
			if (x == 0) {
				x = ColorMatrix.DELTA_INDEX[val];
			} else {
				//x = ColorMatrix.DELTA_INDEX[(val<<0)]; // this is how the IDE does it.
				x = ColorMatrix.DELTA_INDEX[(val<<0)]*(1-x)+ColorMatrix.DELTA_INDEX[(val<<0)+1]*x; // use linear interpolation for more granularity.
			}
			x = x*127+127;
		}
		this.multiplyMatrix([
			x/127,0,0,0,0.5*(127-x),
			0,x/127,0,0,0.5*(127-x),
			0,0,x/127,0,0.5*(127-x),
			0,0,0,1,0,
			0,0,0,0,1
		]);
	}

	/**
	 * @param	val	The Saturation, from -100 to 100
	**/
	this.adjustSaturation = function(val) {
		val = this.cleanValue(val,100);
		if (val == 0 || isNaN(val)) { return; }
		var x    = 1+((val > 0) ? 3*val/100 : val/100);
		var lumR = 0.3086;
		var lumG = 0.6094;
		var lumB = 0.0820;
		this.multiplyMatrix([
			lumR*(1-x)+x,lumG*(1-x),lumB*(1-x),0,0,
			lumR*(1-x),lumG*(1-x)+x,lumB*(1-x),0,0,
			lumR*(1-x),lumG*(1-x),lumB*(1-x)+x,0,0,
			0,0,0,1,0,
			0,0,0,0,1
		]);
	}

	/**
	 * @param	val	The Hue, from -180 to 180
	**/
	this.adjustHue = function(val) {
		val = this.cleanValue(val,180)/180*Math.PI;
		if (val == 0 || isNaN(val)) { return; }
		var cosVal = Math.cos(val);
		var sinVal = Math.sin(val);
		var lumR = 0.213;
		var lumG = 0.715;
		var lumB = 0.072;
		this.multiplyMatrix([
			lumR+cosVal*(1-lumR)+sinVal*(-lumR),lumG+cosVal*(-lumG)+sinVal*(-lumG),lumB+cosVal*(-lumB)+sinVal*(1-lumB),0,0,
			lumR+cosVal*(-lumR)+sinVal*(0.143),lumG+cosVal*(1-lumG)+sinVal*(0.140),lumB+cosVal*(-lumB)+sinVal*(-0.283),0,0,
			lumR+cosVal*(-lumR)+sinVal*(-(1-lumR)),lumG+cosVal*(-lumG)+sinVal*(lumG),lumB+cosVal*(1-lumB)+sinVal*(lumB),0,0,
			0,0,0,1,0,
			0,0,0,0,1
		]);
	}

	/**
	 * Adjust all four properties at once
	 * @param	brightness (-100 - 100)
	 * @param	contrast (-100 - 100)
	 * @param	saturation (-100 - 100)
	 * @param	hue (-180 - 180)
	**/
	this.adjustColor = function(brightness, contrast, saturation, hue) {
		this.adjustHue(hue);
		this.adjustContrast(contrast);
		this.adjustBrightness(brightness);
		this.adjustSaturation(saturation);
	}

	this.__defineGetter__("matrix", function() {return this._matrix.concat(); });
	this.toString = function() {
		//return "ColorMatrix: " + this._matrix.toString();
		var out = "ColorMatrix:\n";
		var v;
		var iLen = this._matrix.length;
		var l = 4;
		for (var i = 0; i < iLen; i++) {
			v = this._matrix[i].toString();
			if (v.length > l) {
				v = v.substr(0, l);
			} else {
				while (v.length < l) {
					v += " ";
				}
			}
			out += v;
			if (i % 5 == 4) {
				out += "\n";
			} else {
				out += " ";
			}
		}
		return out;
	}

}

ColorMatrix._DELTA_INDEX = [
	0,    0.01, 0.02, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1,  0.11,
	0.12, 0.14, 0.15, 0.16, 0.17, 0.18, 0.20, 0.21, 0.22, 0.24,
	0.25, 0.27, 0.28, 0.30, 0.32, 0.34, 0.36, 0.38, 0.40, 0.42,
	0.44, 0.46, 0.48, 0.5,  0.53, 0.56, 0.59, 0.62, 0.65, 0.68,
	0.71, 0.74, 0.77, 0.80, 0.83, 0.86, 0.89, 0.92, 0.95, 0.98,
	1.0,  1.06, 1.12, 1.18, 1.24, 1.30, 1.36, 1.42, 1.48, 1.54,
	1.60, 1.66, 1.72, 1.78, 1.84, 1.90, 1.96, 2.0,  2.12, 2.25,
	2.37, 2.50, 2.62, 2.75, 2.87, 3.0,  3.2,  3.4,  3.6,  3.8,
	4.0,  4.3,  4.7,  4.9,  5.0,  5.5,  6.0,  6.5,  6.8,  7.0,
	7.3,  7.5,  7.8,  8.0,  8.4,  8.7,  9.0,  9.4,  9.6,  9.8,
	10.0
];
ColorMatrix._IDENTITY_MATRIX = [
	1,0,0,0,0,
	0,1,0,0,0,
	0,0,1,0,0,
	0,0,0,1,0,
	0,0,0,0,1
]
ColorMatrix._LENGTH = ColorMatrix._IDENTITY_MATRIX.length;

ColorMatrix.__defineGetter__("DELTA_INDEX",     function() { return ColorMatrix._DELTA_INDEX; } );
ColorMatrix.__defineGetter__("IDENTITY_MATRIX", function() { return ColorMatrix._IDENTITY_MATRIX; } );
ColorMatrix.__defineGetter__("LENGTH",          function() { return ColorMatrix._LENGTH; } );

var sel = fl.getDocumentDOM().selection;
fl.outputPanel.clear();

var iLen = sel.length;
var qualities = {
	low:1,
	medium:2,
	high:3
}
for (var i = 0; i < iLen; i++) {
	var s = sel[i];
	if (s.instanceType != "symbol") continue;
	// fl.trace("FILTERS FOR " + (s.name == "" ? "UNNAMED INSTANCE" : s.name) + " -------------------------------------");
	var filters = s.filters;
	var jLen = filters.length;
	for (var j = 0; j < jLen; j++) {
		var f = filters[j];

		// for (var p in f) {
		// 	fl.trace(p + " :: " + f[p]);
		// }

		var en = f.enabled ? "" : "//";
		var q = qualities[f.quality];
		if (f.color) {
			var colorInfo = getColorAndAlpha(f.color);
		}
		if (f.highlightColor) {
			var highlightColorInfo = getColorAndAlpha(f.highlightColor);
		}
		if (f.shadowColor) {
			var shadowColorInfo = getColorAndAlpha(f.shadowColor);
		}
		if (f.colorArray) {
			var colorArray = [];
			var alphaArray = [];
			var iLen = f.colorArray.length;
			for (var i = 0; i < iLen; i++) {
				var info = getColorAndAlpha(f.colorArray[i]);
				colorArray.push(info.color);
				alphaArray.push (info.alpha);
			}
		}
		var s = f.strength / 100;

		switch (f.name) {
			case "glowFilter":
				fl.trace(en + "new GlowFilter("+colorInfo.color+", "+colorInfo.alpha+", "+f.blurX+", "+f.blurY+", "+s+", "+q+", "+f.inner+", "+f.knockout+");");
				break;
			case "dropShadowFilter":
				fl.trace(en + "new DropShadowFilter("+f.distance+", "+f.angle+", "+colorInfo.color+", "+colorInfo.alpha+", "+f.blurX+", "+f.blurY+", "+s+", "+q+", "+f.inner+", "+f.knockout+", "+f.hideObject+");");
				break;
			case "blurFilter":
				fl.trace(en + "new BlurFilter("+f.blurX+", "+f.blurY+", "+q+");");
				break;
			case "bevelFilter":
				fl.trace(en + "new BevelFilter("+f.distance+", "+f.angle+", "+highlightColorInfo.color+", "+highlightColorInfo.alpha+", "+shadowColorInfo.color+", "+shadowColorInfo.alpha+", "+f.blurX+", "+f.blurY+", "+f.strength+", "+q+", \""+f.type+"\", "+f.knockout+");");
				break;
			case "gradientBevelFilter":
				fl.trace(en + "new GradientBevelFilter("+f.distance+", "+f.angle+", ["+colorArray+"], ["+alphaArray+"], ["+f.posArray+"], "+f.blurX+", "+f.blurY+", "+f.strength+", "+q+", \""+f.type+"\", "+f.knockout+");");
				break;
			case "gradientGlowFilter":
				fl.trace(en + "new GradientGlowFilter("+f.distance+", "+f.angle+", ["+colorArray+"], ["+alphaArray+"], ["+f.posArray+"], "+f.blurX+", "+f.blurY+", "+f.strength+", "+q+", \""+f.type+"\", "+f.knockout+");");
				break;
			case "adjustColorFilter":
				var m = new ColorMatrix();
				m.adjustColor(f.brightness, f.contrast, f.saturation, f.hue);
				fl.trace(en + "new ColorMatrixFilter(["+m.matrix+"]);");
				break;
		}

	}
	fl.trace("\n");
}

/**
 * Returns an Object with two properties, color and alpha, as strings, based on the parsing of the color string coming in.  The color string
 * will be something like "#FF9933" or "#FF993366".  If it's a 32-bit color, the alpha channel is contained in the last two bytes
 * (66 in this case).
**/
function getColorAndAlpha(color) {
	var colorInfo = {};
	if (color.length == 9) {
		colorInfo.alpha = Math.round(parseInt(color.substr(7, 2), 16) / 0xFF * 100) / 100;
		colorInfo.color = color.substr(0, 7).replace(/^#/, "0x")
	} else if (color.length == 7) {
		colorInfo.alpha = 1;
		colorInfo.color = color.replace(/^#/, "0x")
	} else {
		fl.trace("Problem parsing color.")
	}
	return colorInfo;
}

Thank you, Grant, for doing all of the hard work on this one!

Advertisement