/* Copyright © 2009-2010 Adam Harvey
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */


/**
 * @namespace Global namespace for all CineJS related objects.
 */
var cinejs = {};


/**
 * @namespace Encapsulates the various filters provided with CineJS.
 */
cinejs.filters = {};


// JSLint specific settings follow.

/*jslint browser: true, devel: true, onevar: false, newcap: true, nomen: true,
  plusplus: false, sub: false, white: true */

/* Elements we use that are defined by HTML5 compliant browsers that JSLint
 * doesn't currently know about. */

/*global HTMLCanvasElement: false, HTMLImageElement: false, 
  HTMLVideoElement: false, window: false */


// vim: set cin noet ts=8 sw=8:
/* Copyright © 2009-2010 Adam Harvey
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */


/**
 * @namespace Namespace for utility functions.
 */
cinejs.util = {internal: {}};


/**
 * Apply an arbitrarily sized convolution filter.
 *
 * This is (a very loose) port of the libgd gdImageConvolution() function as
 * written by Pierre Joye.
 *
 * @param {ImageData} frame The image data to filter.
 * @param {number} width The width of the image data.
 * @param {number} height The height of the image data.
 * @param {array} filter A 2D array describing the filter. This is not checked
 *                       at all for speed reasons, so make sure this is a 
 *                       square 2D array before passing it in; preferably upon 
 *                       initial calculation. Things are likely to work better
 *                       with matrices of odd size: ie 3x3, 5x5, et cetera, but
 *                       even sized matrices should also sort of work.
 * @param {number} divisor The amount to divide the resultant pixel values by.
 *                         This is typically the sum of the filter elements,
 *                         and can (and should) be pre-calculated.
 * @param {number} offset An offset to apply to the resulting subpixel values.
 * @return void
 */
cinejs.util.applyConvolution = function (frame, width, height, filter, divisor, offset) {
	/* This isn't brilliantly named. It's basically the distance from the
	 * middle of the convolution matrix to the edge. */
	var filterSize = Math.floor(filter.length / 2);

	var i = 0;

	/* We have to keep the previous image data around for the entire
	 * convolution. */
	var newImage = [];

	for (var y = 0; y < height; y++) {
		for (var x = 0; x < width; x++) {
			var red = 0;
			var green = 0;
			var blue = 0;

			for (var j = 0; j < filter.length; j++) {
				var sourceRow = Math.min(Math.max(y + j - filterSize, 0), height - 1);
				for (i = 0; i < filter.length; i++) {
					var sourceCol = Math.min(Math.max(x + i - filterSize, 0), width - 1);
					var frameIndex = 4 * ((width * sourceRow) + sourceCol);

					red += filter[j][i] * frame.data[frameIndex + 0];
					green += filter[j][i] * frame.data[frameIndex + 1];
					blue += filter[j][i] * frame.data[frameIndex + 2];
				}
			}

			red /= divisor;
			green /= divisor;
			blue /= divisor;

			newImage.push(Math.min(Math.max(0, red + offset), 255));
			newImage.push(Math.min(Math.max(0, green + offset), 255));
			newImage.push(Math.min(Math.max(0, blue + offset), 255));
			newImage.push(255);
		}
	}

	// Copy the new image into the ImageData array.
	for (i = 0; i < newImage.length; i++) {
		frame.data[i] = newImage[i];
	}
};


/**
 * Converts a HSV colour value to RGB.
 *
 * @param {number} hue The hue in degrees. Expected to be in the range 0-360,
 *                     or -1 if the colour is greyscale.
 * @param {number} saturation The saturation as a number from 0-1.
 * @param {number} value The colour value. Between 0 and 255.
 * @return {array} An array of colour values: [red, green, blue].
 */
cinejs.util.hsvToRgb = function (hue, saturation, value) {
	// Check for the simple greyscale case.
	if (saturation === 0.0) {
		return [value, value, value];
	}

	hue %= 360.0;
	hue /= 60.0;

	/* This particular piece of unclear goobledigook is brought to you by
	 * Blender and Peter Schlaile. */
	var i = Math.floor(hue);
	var f = hue - i;
	var w = value * (1.0 - saturation);
	var q = value * (1.0 - (saturation * f));
	var t = value * (1.0 - (saturation * (1.0 - f)));

	return cinejs.util.internal.rgbType[i](value, w, q, t);
};


/**
 * Converts an RGB colour value to HSV.
 *
 * @param {number} red The red colour value.
 * @param {number} green The green colour value.
 * @param {number} blue The blue colour value.
 * @return {array} An array of colour values: [hue, saturation, value].
 */
cinejs.util.rgbToHsv = function (red, green, blue) {
	var max = red;
	var min = red;
	var maxChannel = "red";

	// Find the maximum colour value.
	if (green > max) {
		max = green;
		maxChannel = "green";
	}
	if (blue > max) {
		max = blue;
		maxChannel = "blue";
	}

	// Do the same for the minimum.
	if (green < min) {
		min = green;
	}
	if (blue < min) {
		min = blue;
	}

	// Calculate saturation.
	var saturation = 0.0;
	if (max !== 0.0) {
		saturation = (max - min) / max;
	}

	/* And now the hue. It doesn't overly matter what we return for
	 * greyscale images, for obvious reasons. */
	var hue = 0.0;
	if (saturation !== 0.0) {
		var delta = max - min;

		if (maxChannel === "red") {
			hue = (green - blue) / delta;
		}
		else if (maxChannel === "green") {
			hue = 2.0 + (blue - red) / delta;
		}
		else {
			hue = 4.0 + (red - green) / delta;
		}

		hue *= 60.0;

		if (hue < 0.0) {
			hue += 360.0;
		}
	}

	return [hue, saturation, max];
};


/**
 * Internal helper functions for HSV. We'll define these here rather than in
 * hsvToRgb() to save redeclaring them on each invocation.
 */
cinejs.util.internal.rgbType = [
	function (value, w, q, t) { return [value, t, w]; },
	function (value, w, q, t) { return [q, value, w]; },
	function (value, w, q, t) { return [w, value, t]; },
	function (value, w, q, t) { return [w, q, value]; },
	function (value, w, q, t) { return [t, w, value]; },
	function (value, w, q, t) { return [value, w, q]; }
];


// vim: set cin noet ts=8 sw=8:
/* Copyright © 2009-2010 Adam Harvey
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */


/**
 * Creates a new Player object.
 *
 * @class Represents a chain of elements that result in a video being played.
 * @param {object} options Options to be set within the player.
 */
cinejs.Player = function (options) {
	// Initialise the filter chain.
	this.filters = [];

	// Set default options.
	this.options = {
		frameDelay: 25
	};

	// Copy in our provided options.
	for (var option in options) {
		if (options.hasOwnProperty(option)) {
			this.options[option] = options[option];
		}
	}
};

/**
 * Pre-flight check for the player to make sure that all required options are
 * set correctly.
 *
 * @throws {string} A string describing the problem found.
 */
cinejs.Player.prototype.check = function () {
	// Check for a valid source.
	if (!(this.options.source instanceof HTMLVideoElement || this.options.source instanceof HTMLImageElement)) {
		throw "The source option must be a video or image element";
	}

	// Check for a valid destination element.
	if (!this.options.destination instanceof HTMLCanvasElement) {
		throw "The destination option must be a canvas element";
	}

	// Simpler checks for other options.
	if (typeof this.options.frameDelay !== "number" || this.options.frameDelay < 0) {
		throw "The frame delay must be a non-negative number";
	}

	/* Temporary WebKit bug workaround: because WebKit only correctly
	 * implements the first version of the context.drawImage() API, which
	 * is the least useful one. (Of course.) This means we can't alter
	 * image dimensions from the source to the destination. */
	if (this.options.source.width != this.options.destination.width || this.options.source.height != this.options.destination.height) {
		throw "The source and destination dimensions must be specified and identical";
	}

	// Check any and all defined filters.
	for (var i in this.filters) {
		if (this.filters.hasOwnProperty(i)) {
			var filter = this.filters[i];

			if (typeof filter.processFrame !== "function") {
				var name = "unknown";

				if (filter.name) {
					name = filter.name;
				}

				throw "Filter " + name + " does not have a processFrame method";
			}
		}
	}
};

/**
 * Creates a hidden canvas for our intermediate video processing. While there's
 * no technical reason we can't just work off the destination canvas, this is
 * likely to result in unsightly video artifacts as processing occurs. This may
 * be overridden should the defaults not suffice (for example, if you want to
 * write a demonstration page that shows the intermediate canvas).
 *
 * @return {HTMLCanvasElement}
 */
cinejs.Player.prototype.createIntermediateCanvas = function () {
	var canvas = document.createElement("canvas");

	canvas.style.display = "none";

	// Grab the destination size as our intermediate size.
	canvas.width = this.options.source.width;
	canvas.height = this.options.source.height;

	document.body.appendChild(canvas);

	return canvas;
};

/**
 * Starts playback of the processed video.
 *
 * @throws {string} Throws anything check() can throw.
 */
cinejs.Player.prototype.play = function () {
	this.check();

	// We need an intermediate canvas to do our processing on.
	var intermediate = this.createIntermediateCanvas();
	if (!intermediate instanceof HTMLCanvasElement) {
		throw "Intermediate canvas is not a canvas";
	}

	var self = this;
	if (this.options.source instanceof HTMLVideoElement) {
		this.options.source.addEventListener("play", function () {
			self.renderFrame(self.options.source, intermediate, self.options.destination);
		}, false);

		this.options.source.play();
	}
	else {
		self.renderFrame(self.options.source, intermediate, self.options.destination);
	}
};

/**
 * Actually renders a frame of the video by rendering it and calling any
 * filters required.
 *
 * @param {HTMLImageElement|HTMLVideoElement} source The source video element.
 * @param {HTMLCanvasElement} intermediate An intermediate canvas to be used
 *                                         for filtering and processing.
 * @param {HTMLCanvasElement} destination The destination canvas.
 */
cinejs.Player.prototype.renderFrame = function (source, intermediate, destination) {
	if (this.filters.length === 0) {
		/* Shortcut if we have no filters defined: just splat the data
		 * straight to the destination. */
		var context = destination.getContext("2d");
		context.drawImage(source, 0, 0, destination.width, destination.height);
	}
	else {
		var intermediateContext = intermediate.getContext("2d");
		var destinationContext = destination.getContext("2d");

		/* Put the current source frame onto the intermediate canvas so
		 * we can get its raw image data. */
		intermediateContext.drawImage(source, 0, 0);
		var imageData = intermediateContext.getImageData(0, 0, intermediate.width, intermediate.height);

		// Now apply each filter in turn.
		for (var filter in this.filters) {
			if (this.filters.hasOwnProperty(filter)) {
				this.filters[filter].processFrame(imageData, intermediate);
			}
		}

		// Finally, put the munged frame to the destination canvas.
		destinationContext.putImageData(imageData, 0, 0);

		imageData = null;
	}

	/* Make sure we're called again after the right delay, unless the source
	 * has already been stopped. */
	if (source instanceof HTMLVideoElement && !(source.paused || source.ended)) {
		var self = this;
		window.setTimeout(function () {
			self.renderFrame(source, intermediate, destination);
		}, this.options.frameDelay);
	}
};

// vim: set cin noet ts=8 sw=8:
/* Copyright © 2009-2010 Adam Harvey
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */


/**
 * Constructs a brightness and contrast filter.
 *
 * @class A brightness and contrast filter.
 * @param {number} brightness A multiplier for the brightness between -1 and 1.
 * @param {number} contrast Contrast multiplier. Low values (0 to 1) will
 *                          result in low contrast, high values (1 to infinity)
 *                          higher contrast. It's probably easiest just to play
 *                          with it.
 */
cinejs.filters.BrightnessContrast = function (brightness, contrast) {
	this.brightness = brightness;
	this.contrast = contrast;
};

/**
 * The main processing function for the brightness and contrast filter.
 *
 * @param {ImageData} frame
 * @param {HTMLCanvasElement} canvas
 */
cinejs.filters.BrightnessContrast.prototype.processFrame = function (frame, canvas) {
	for (var i = 0; i < frame.data.length; i += 4) {
		frame.data[i + 0] = this.processSubpixel(frame.data[i + 0]);
		frame.data[i + 1] = this.processSubpixel(frame.data[i + 1]);
		frame.data[i + 2] = this.processSubpixel(frame.data[i + 2]);
	}
};

/**
 * The processing function applied to the individual red, green and blue
 * subpixels within a frame.
 *
 * @param {number} subpixel
 * @return {number}
 */
cinejs.filters.BrightnessContrast.prototype.processSubpixel = function (subpixel) {
	// Contrast adjustment.
	if (this.contrast !== 1) {
		subpixel -= 128;
		subpixel *= this.contrast;
		subpixel += 128;
	}

	// Brightness adjustment.
	subpixel += (255 * this.brightness);

	// Constrain to valid values.
	return Math.min(255, Math.max(0, subpixel));
};


// vim: set cin noet ts=8 sw=8:
/* Copyright © 2009-2010 Adam Harvey
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */


/**
 * Constructs a colour level filter.
 *
 * @class A very basic colour level filter.
 * @param {number} red The multiplier for the red channel.
 * @param {number} green The multiplier for the green channel.
 * @param {number} blue The multiplier for the blue channel.
 */
cinejs.filters.ColourLevel = function (red, green, blue) {
	this.red = red;
	this.green = green;
	this.blue = blue;
};

/**
 * The processing function for the colour level filter.
 *
 * @param {ImageData} frame
 * @param {HTMLCanvasElement} canvas
 */
cinejs.filters.ColourLevel.prototype.processFrame = function (frame, canvas) {
	for (var i = 0; i < frame.data.length; i += 4) {
		frame.data[i + 0] = Math.min(frame.data[i + 0] * this.red, 255);
		frame.data[i + 1] = Math.min(frame.data[i + 1] * this.green, 255);
		frame.data[i + 2] = Math.min(frame.data[i + 2] * this.blue, 255);
	}
};

// This one's for my vowel deprived American friends.
cinejs.filters.ColorLevel = cinejs.filters.ColourLevel;


// vim: set cin noet ts=8 sw=8:
/* Copyright © 2009-2010 Adam Harvey
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */


/**
 * Constructs a quick and dirty edge detection filter.
 *
 * @class An edge detection filter.
 */
cinejs.filters.EdgeDetect = function () {
	this.filter = [
		[-1.0, 0.0, -1.0],
		[0.0, 4.0, 0.0],
		[-1.0, 0.0, -1.0]
	];
};

/**
 * The processing function for the edge detection filter.
 *
 * This is based on gdImageEdgeDetectQuick() by Pierre Joye.
 *
 * @param {ImageData} frame
 * @param {HTMLCanvasElement} canvas
 */
cinejs.filters.EdgeDetect.prototype.processFrame = function (frame, canvas) {
	cinejs.util.applyConvolution(frame, canvas.width, canvas.height, this.filter, 1, 127);
};


// vim: set cin noet ts=8 sw=8:
/* Copyright © 2009-2010 Adam Harvey
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */


/**
 * Constructs an embossing filter.
 *
 * @class An embossing filter.
 */
cinejs.filters.Emboss = function () {
	this.filter = [
		[1.5, 0.0, 0.0],
		[0.0, 0.0, 0.0],
		[0.0, 0.0, -1.5]
	];
};

/**
 * The processing function for the embossing filter.
 *
 * This is based on gdImageEmboss() by Pierre Joye.
 *
 * @param {ImageData} frame
 * @param {HTMLCanvasElement} canvas
 */
cinejs.filters.Emboss.prototype.processFrame = function (frame, canvas) {
	cinejs.util.applyConvolution(frame, canvas.width, canvas.height, this.filter, 1, 127);
};


// vim: set cin noet ts=8 sw=8:
/* Copyright © 2009-2010 Adam Harvey
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */


/**
 * Constructs a Gaussian blur filter.
 *
 * @class A Gaussian blur filter.
 * @param {number} radius The radius for the blur, in pixels.
 * @param {number} sigma The sigma for the Gaussian kernel; 0.8 if omitted.
 */
cinejs.filters.GaussianBlur = function (radius, sigma) {
	if (!sigma) {
		/* Arbitrarily chosen default σ value based upon Wikipedia and
		 * guesswork. (My favourite kind of maths: the type that
		 * doesn't involve me thinking for myself.) */
		sigma = 0.8;
	}

	this.radius = radius;
	this.kernel = this.calculateKernel(radius, sigma);
};

/**
 * The main processing function for the Gaussian blur filter.
 *
 * @param {ImageData} frame
 * @param {HTMLCanvasElement} canvas
 */
cinejs.filters.GaussianBlur.prototype.processFrame = function (frame, canvas) {
	var i = 0;
	var sourceIndex;
	var red;
	var green;
	var blue;
	var ki;
	var fi;
	var fcol;
	var frow;
	var row;
	var col;

	/* JFTR: This function predates the existence of
	 * cinejs.util.applyConvolution(), but I'm going to keep this
	 * implementation anyway, simply because we're not doing a generic 2D
	 * convolution; we're doing a pair of 1D convolutions instead for
	 * performance reasons -- namely that, with a radius of size 4 (which
	 * is pretty common), you'd end up with a 9x9 convolution matrix, which
	 * is going to perform about as well as Half-Life 2 on an asthmatic
	 * 486SX. */

	// Column-wise blurring.
	for (row = 0; row < canvas.height; row++) {
		for (col = 0; col < canvas.width; col++) {
			i = 4 * (row * canvas.width + col);
			ki = 0;
			red = green = blue = 0.0;
			for (fi = -this.radius; fi <= this.radius; fi++) {
				fcol = col + fi;

				/* End of row handling. We'll use the naïve
				 * approach and just reuse the first or last
				 * pixel for pixels beyond the first and last
				 * pixel in the row, respectively. I presume
				 * this might mean the blur isn't quite as
				 * blurry at the edges, but it's the easiest
				 * approach to implement here, and should be
				 * speedier than any other option we
				 * realistically have. */
				if (fcol < 0) {
					fcol = 0;
				}
				else if (fcol > canvas.width) {
					fcol = canvas.width - 1;
				}

				sourceIndex = 4 * (row * canvas.width + fcol);

				red += this.kernel[ki] * frame.data[sourceIndex + 0];
				green += this.kernel[ki] * frame.data[sourceIndex + 1];
				blue += this.kernel[ki] * frame.data[sourceIndex + 2];

				++ki;
			}

			frame.data[i + 0] = red;
			frame.data[i + 1] = green;
			frame.data[i + 2] = blue;
		}
	}

	// Row-wise blurring.
	for (row = 0; row < canvas.height; row++) {
		for (col = 0; col < canvas.width; col++) {
			i = 4 * (row * canvas.width + col);
			ki = 0;
			red = green = blue = 0.0;
			for (fi = -this.radius; fi <= this.radius; fi++) {
				frow = row + fi;

				/* Same out-of-bounds handling as in the
				 * column-wise case, except we check for the
				 * constraint in a row-wise manner, for fairly
				 * obvious reasons. */
				if (frow < 0) {
					frow = 0;
				}
				else if (frow >= canvas.height) {
					frow = canvas.height - 1;
				}

				sourceIndex = 4 * (frow * canvas.width + col);

				red += this.kernel[ki] * frame.data[sourceIndex + 0];
				green += this.kernel[ki] * frame.data[sourceIndex + 1];
				blue += this.kernel[ki] * frame.data[sourceIndex + 2];

				++ki;
			}

			frame.data[i + 0] = red;
			frame.data[i + 1] = green;
			frame.data[i + 2] = blue;
		}
	}
};

/**
 * Calculates a one-dimensional convolution kernel for a Gaussian blur.
 *
 * @param {number} radius The radius for the blur, in pixels.
 * @param {number} sigma The sigma for the Gaussian kernel.
 * @return {Array} The one-dimensional kernel as a set of multiplicands.
 */
cinejs.filters.GaussianBlur.prototype.calculateKernel = function (radius, sigma) {
	var kernel = [];
	var i = 0;

	/* Calculate one side of the kernel. The formula is from Wikipedia
	 * <http://en.wikipedia.org/wiki/Gaussian_blur>, which sources it on
	 * back to Shapiro & Stockman's "Computer Vision" book. I choose to
	 * believe it's correct. */
	for (i = 0; i <= radius; i++) {
		kernel.push(Math.pow((1.0 / (sigma * Math.sqrt(2.0 * Math.PI))) * Math.E, -(Math.pow(radius - i, 2) / (2.0 * sigma * sigma))));
	}

	// Repeat the values on the other side of the centre point.
	for (i = radius - 1; i >= 0; i--) {
		kernel.push(kernel[i]);
	}

	/* Scale the kernel down so values can be cleanly multiplied without
	 * affecting the overall brightness or saturation. */
	var sum = 0.0;
	for (i = 0; i < kernel.length; i++) {
		sum += kernel[i];
	}
	for (i = 0; i < kernel.length; i++) {
		kernel[i] /= sum;
	}

	return kernel;
};


// vim: set cin noet ts=8 sw=8:
/* Copyright © 2009-2010 Adam Harvey
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */


/**
 * Constructs a greyscale filter.
 *
 * @class A greyscale filter.
 */
cinejs.filters.Greyscale = function () {
};

/**
 * The processing function for the greyscale filter.
 *
 * @param {ImageData} frame
 * @param {HTMLCanvasElement} canvas
 */
cinejs.filters.Greyscale.prototype.processFrame = function (frame, canvas) {
	for (var i = 0; i < frame.data.length; i += 4) {
		// Using the algorithm used in libgd.
		var value = (0.299 * frame.data[i + 0]) + (0.587 * frame.data[i + 1]) + (0.114 * frame.data[i + 2]);
		frame.data[i + 0] = value;
		frame.data[i + 1] = value;
		frame.data[i + 2] = value;
	}
};


cinejs.filters.Grayscale = cinejs.filters.Greyscale;


// vim: set cin noet ts=8 sw=8:
/* Copyright © 2009-2010 Adam Harvey
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */


/**
 * Constructs a colour inversion filter.
 *
 * @class A very basic colour inversion filter.
 */
cinejs.filters.Invert = function () {
};

/**
 * The processing function for the colour level filter.
 *
 * @param {ImageData} frame
 * @param {HTMLCanvasElement} canvas
 */
cinejs.filters.Invert.prototype.processFrame = function (frame, canvas) {
	for (var i = 0; i < frame.data.length; i += 4) {
		frame.data[i + 0] = 255 - frame.data[i + 0];
		frame.data[i + 1] = 255 - frame.data[i + 1];
		frame.data[i + 2] = 255 - frame.data[i + 2];
	}
};


// vim: set cin noet ts=8 sw=8:
/* Copyright © 2009-2010 Adam Harvey
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */


/**
 * Constructs a posterisation filter.
 *
 * @class A simple posterisation filter.
 * @param {number} levels The number of levels to output.
 */
cinejs.filters.Posterise = function (levels) {
	this.divisor = 256 / Math.floor(levels);
};

/**
 * The processing function for the posterisation filter.
 *
 * @param {ImageData} frame
 * @param {HTMLCanvasElement} canvas
 */
cinejs.filters.Posterise.prototype.processFrame = function (frame, canvas) {
	for (var i = 0; i < frame.data.length; i += 4) {
		/* We'll use round rather than floor to try to keep the overall
		 * image brightness roughly the same. */
		frame.data[i + 0] = this.divisor * Math.round(frame.data[i + 0] / this.divisor);
		frame.data[i + 1] = this.divisor * Math.round(frame.data[i + 1] / this.divisor);
		frame.data[i + 2] = this.divisor * Math.round(frame.data[i + 2] / this.divisor);
	}
};

// See, Americans, I think of you.
cinejs.filters.Posterize = cinejs.filters.Posterise;


// vim: set cin noet ts=8 sw=8:
/* Copyright © 2009-2010 Adam Harvey
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */


/**
 * Constructs a smoothing filter.
 *
 * @param {number} weight The weight of the smoothing to apply. Lower values
 *                        (toward 0) imply more smoothing.
 *
 * @class A smoothing filter.
 */
cinejs.filters.Smooth = function (weight) {
	this.filter = [
		[1.0, 1.0, 1.0],
		[1.0, weight, 1.0],
		[1.0, 1.0, 1.0]
	];

	this.divisor = weight + 8;
};

/**
 * The processing function for the embossing filter.
 *
 * This is based on gdImageSmooth() by Pierre Joye.
 *
 * @param {ImageData} frame
 * @param {HTMLCanvasElement} canvas
 */
cinejs.filters.Smooth.prototype.processFrame = function (frame, canvas) {
	cinejs.util.applyConvolution(frame, canvas.width, canvas.height, this.filter, this.divisor, 0);
};


// vim: set cin noet ts=8 sw=8:

