Source: webcam-screenshot.js

/**
* @project js-webcam-screenshot <http://github-com/mlocati/js-webcam-screenshot>
* @author Michele Locati <mlocati@gmail.com>
* @license
* 	in English: do what you want with this code, but don't sue me for problems
* 	in Legal: MIT license (http://opensource.org/licenses/MIT)
*
* This code is based on https://github.com/codepo8/interaction-cam
* by Christian Heilmann
*/

/* jshint unused:vars, undef:true, browser:true */
/* global jQuery */

(function(w, $) {
'use strict';

var
	getUserMedia = w.navigator.getUserMedia || w.navigator.webkitGetUserMedia || w.navigator.mozGetUserMedia || w.navigator.msGetUserMedia || null,
	isSupported = (getUserMedia && w.FormData) ? true : false;

if(isSupported) {
	w.navigator._webcamScreenshotgetUserMedia = getUserMedia;
}

/** The namespace of js-webcam-screenshot.
* @global
* @namespace
*/
var WebcamScreenshot = {
	/** The current version of WebcamScreenshot.
	* @static
	* @constant
	* @default
	* @type {string}
	*/
	VERSION: '0.1.0',
	/** The default name of the image field to be posted.
	* @static
	* @default
	* @type {string}
	*/
	DEFAULT_POST_FIELDNAME: 'image',
	/** Returns <code>true</code> if the browser is supported, <code>false</code> otherwise.
	* @return {boolean} 
	*/
	getIsSpported: function() {
		return isSupported;
	},
	/** Possible code values passed to the [<code>callback</code>]{@link WebcamScreenshot.goBeforePostCallback} of the [<code>go()</code> method]{@link WebcamScreenshot.go}.
	* @static
	* @readonly
	* @enum {number}
	*/
	RC: {
		/** All good. */
		OK: 0,
		/** User cancelled. */
		USER_CANCELLED: 1,
		/** Browser is not supported. */
		UNSUPPORTED_BROWSER: 2,
		/** Error accessing the webcam. */
		CANT_ACCESS_WEBCAM: 3,
		/** Error thrown by the callback called just before posting data. */
		SAVE_CALLBACKERROR: 4,
		/** Error while posting the image. */
		SAVE_FAILED: 5
	},
	/** Take a shot with the webcam.
	* @param {WebcamScreenshot.goOptions} [options] - Options.
	* @param {WebcamScreenshot.goCallback} [callback] - A function that will be called at the end.
	* @static
	*/
	go: function(options, callback) {
		if(!callback) {
			callback = function() {};
		}
		if(!isSupported) {
			callback(WebcamScreenshot.RC.UNSUPPORTED_BROWSER, 'Browser is not supported.');
			return;
		}
		options = $.extend(true, {parent: w.document.body, width: 300, cancelText: 'Cancel', takeText: 'Take it', dialogFramework: '', dialogTitle: 'Screenshot from webcam'}, options || {});
		switch(options.dialogFramework) {
			case 'bs2':
			case 'bs3':
			case 'jQueryUI':
				break;
			default:
				options.dialogFramework = '';
				break;
		}
		var
			video = w.document.createElement('video'),
			$shadow = null,
			$dialog = null,
			height = null,
			currentStream = null,
			resize = function() {
				if($shadow) {
					$shadow.css({width: $(w).width(), height: $(w).height()});
				}
				if($dialog && (!options.dialogFramework)) {
					$dialog.css('left', ($(w).width() - options.width) / 2 + 'px');
				}
			},
			dispose = function() {
				$(w).off('resize', resize);
				if($shadow) {
					$shadow.remove();
					$shadow = null;
				}
				if($dialog) {
					switch(options.dialogFramework) {
						case 'bs2':
						case 'bs3':
							$dialog.modal('hide').remove();
							break;
						case 'jQueryUI':
							$dialog.dialog('destroy');
							break;
						default:
							$dialog.remove();
							break;
					}
					$dialog = null;
				}
				if(currentStream) {
					try {
						currentStream.stop();
					}
					catch(e) {
					}
					currentStream = null;
				}
			}
		;
		var doCancel = function() {
			dispose();
			callback(WebcamScreenshot.RC.USER_CANCELLED, 'User cancelled.');
		};
		var doTake = function() {
			var i;
			if(options.canvas) {
				var canvasList = [];
				if(options.canvas instanceof $) {
					canvasList = options.canvas;
				}
				else if (options.canvas instanceof Array) {
					canvasList = options.canvas;
				}
				else {
					canvasList = [options.canvas];
				}
				for(i = 0; i < canvasList.length; i++) {
					canvasList[i].setAttribute('width', options.width);
					canvasList[i].setAttribute('height', height);
					canvasList[i].getContext('2d').drawImage(video, 0, 0, options.width, height);
				}
			}
			if(options.postTo) {
				var canvas = w.document.createElement('canvas'), formData = new w.FormData();
				canvas.setAttribute('width', options.width);
				canvas.setAttribute('height', height);
				canvas.getContext('2d').drawImage(video, 0, 0, options.width, height);
				WebcamScreenshot.canvasToFormData(formData, options.postFieldname || WebcamScreenshot.DEFAULT_POST_FIELDNAME, canvas, options.postImageFormat);
				if(options.onBeforePost) {
					try {
						options.onBeforePost(formData);
					}
					catch(err) {
						dispose();
						callback(WebcamScreenshot.RC.SAVE_CALLBACKERROR, err.message || err.name || err.toString());
						return;
					}
				}
				var ajaxData = {
				    url: options.postTo,
				    data: formData,
				    cache: false,
				    contentType: false,
				    processData: false,
				    type: 'POST'
				};
				if(options.postReturnDataType) {
					ajaxData.dataType = options.postReturnDataType;
				}
				$.ajax(ajaxData)
					.fail(function(jqXHR, textStatus, errorThrown) {
						w.alert(errorThrown);
					})
					.success(function(data) {
						dispose();
						callback(WebcamScreenshot.RC.OK, data);
					})
				;
				return;
			}
			dispose();
			callback(WebcamScreenshot.RC.OK);
		};
		$shadow = $('<div class="webcamscreenshot-shadow" />');
		$(w).on('resize', resize);
		resize();
		$(options.parent).append($shadow);
		switch(options.dialogFramework) {
			case 'bs2':
				$dialog = $([
					'<div class="modal hide" role="dialog">',
						'<div class="modal-header">',
							'<button type="button" class="close">×</button>',
							'<h3></h3>',
						'</div>',
						'<div class="modal-body"></div>',
						'<div class="modal-footer"></div>',
					'</div>',
				''].join(''));
				$dialog.find('button.close').on('click', function() { doCancel(); });
				$dialog.find('.modal-header h3').text(options.dialogTitle);
				$dialog.find('.modal-body').append(video);
				$dialog.find('.modal-footer')
					.append($('<button class="btn" />')
						.html(options.cancelText)
						.on('click', function() {
							doCancel();
						})
					)
					.append($('<button class="btn btn-primary" />')
						.html(options.takeText)
						.on('click', function() {
							doTake();
						})
					)
				;
				$(options.parent).append($dialog);
				break;
			case 'bs3':
				$dialog = $([
					'<div class="modal">',
						'<div class="modal-dialog">',
							'<div class="modal-content">',
								'<div class="modal-header">',
									'<button type="button" class="close">×</button>',
									'<h4 class="modal-title"></h4>',
								'</div>',
								'<div class="modal-body"></div>',
								'<div class="modal-footer"></div>',
							'</div>',
						'</div>',
					'</div>',
				''].join(''));
				$dialog.find('button.close').on('click', function() { doCancel(); });
				$dialog.find('.modal-title').text(options.dialogTitle);
				$dialog.find('.modal-body').append(video);
				$dialog.find('.modal-footer')
					.append($('<button type="button" class="btn btn-default" />')
						.html(options.cancelText)
						.on('click', function() {
							doCancel();
						})
					)
					.append($('<button type="button" class="btn btn-primary" />')
						.html(options.takeText)
						.on('click', function() {
							doTake();
						})
					)
				;
				$(options.parent).append($dialog);
				break;
			case 'jQueryUI':
				$dialog = $('<div />');
				$dialog.append(video);
				$dialog.dialog({
					appendTo: options.parent,
					autoOpen: false,
					closeText: options.cancelText,
					modal: true,
					resizable: false,
					title: options.dialogTitle,
					width: options.width + 40,
					close: function() {
						dispose();
					},
					buttons: [
						{
							text: options.cancelText,
							click: function() {
								doCancel();
							}
						},
						{
							text: options.takeText,
							click: function() {
								doTake();
							}
						}
					]
				});
				break;
			default:
				$dialog = $('<div class="webcamscreenshot-dialog" />');
				$(options.parent).append($dialog);
				$dialog
					.append(video)
					.append($('<div class="webcamscreenshot-buttons" />')
						.append($('<button />')
							.html(options.takeText)
							.on('click', function() {
								doTake();
							})
						)
						.append($('<button />')
							.html(options.cancelText)
							.on('click', function() {
								doCancel();
							})
						)
					)
				;
				break;
		}
		video.addEventListener(
			'canplay',
			function(e) {
				height = Math.ceil(options.width * video.videoHeight / video.videoWidth);
				video.setAttribute('width', options.width);
				video.setAttribute('height', height);
			},
			false
		);
		w.navigator._webcamScreenshotgetUserMedia(
			{
				video: true,
				audio: false
			},
			function(stream) {
				currentStream = stream;
				if(w.navigator.mozGetUserMedia) {
					video.mozSrcObject = currentStream;
				}
				else {
					var url = w.URL || w.webkitURL;
					video.src = (url && url.createObjectURL) ? url.createObjectURL(currentStream) : currentStream;
				}
				video.play();
				switch(options.dialogFramework) {
					case 'bs2':
					case 'bs3':
						$shadow.remove();
						$shadow = null;
						$dialog.modal('show');
						break;
					case 'jQueryUI':
						$shadow.remove();
						$shadow = null;
						$dialog.dialog('open');
						break;
				}
			},
			function(err) {
				dispose();
				callback(WebcamScreenshot.RC.CANT_ACCESS_WEBCAM, err.message || err.name || err.toString());
			}
		);
	},
	/** Helper function to add the image represented in a <code><canvas></code> to a <code>FormData</code> instance for posting.
	* @param {FormData} formData - The <code>FormData</code> instance to add the image to.
	* @param {string} fieldName - The name of the field that will contain the image data.
	* @param {HTMLCanvasElement} canvas - A <code><canvas></code> DOM element.
	* @param {string} [imageFormat='png'] The format of the image (accepted values: <code>'jpg'</code>, <code>'png'</code>).
	*/
	canvasToFormData: function(formData, fieldName, canvas, imageFormat) {
		var mimeType, filename;
		switch(imageFormat) {
			case 'jpg':
				mimeType = 'image/jpeg';
				filename = 'image.jpg';
				break;
			case 'png':
				/* falls through */
			default:
				mimeType = 'image/png';
				filename = 'image.png';
				break;
		}
		var done = function(blob) {
			formData.append(fieldName, blob, filename);
		};
		var i, L;
		for(L = ['getAsFile', 'getAsBlob', 'webkitGetAsFile', 'webkitGetAsBlob', 'mozGetAsFile', 'mozGetAsBlob', 'msGetAsFile', 'msGetAsBlob'], i = 0; i < L.length; i++) {
			if(canvas[L[i]]) {
				done(canvas[L[i]](filename));
				return;
			}
		}
		var chunks = canvas.toDataURL(mimeType).split(',');
		var binaryString;
		if (chunks[0].indexOf('base64') >= 0) {
			binaryString = w.atob(chunks[1]);
		}
		else {
			binaryString = w.unescape(chunks[1]);
		}
		var length = binaryString.length;
		if(w.Blob) {
			var array = new Array(length);
			for(i = 0; i < length; i++) {
				array[i] = binaryString.charCodeAt(i);
			}
			done(new w.Blob([new w.Uint8Array(array)], {type: mimeType}));
		}
		else {
			var arrayBuffer = new w.ArrayBuffer(length), byteArray = new w.Uint8Array(arrayBuffer);
			for (i = 0; i < length; i++) {
				byteArray[i] = binaryString.charCodeAt(i);
			}
			var bb = new w.BlobBuilder();
			bb.append(arrayBuffer);
			done(bb.getBlob(mimeType));
		}
	}
};


/** The options for [<code>go()</code> method]{@link WebcamScreenshot.go}
* @typedef {Object} WebcamScreenshot.goOptions
* @property {number} [width=300] - The width of the image to be taken (in pixels).
* @property {string} [cancelText='Cancel'] - The text (html is accepted) to show for the "Cancel" button.
* @property {string} [takeText='Take it'] - The text (html is accepted) to show for the "Take it" button.
* @property {HTMLElement} [parent=window.document.body] - The DOM node to which we'll append our DOM nodes.
* @property {HTMLCanvasElement|HTMLCanvasElement[]|jQuery} [canvas=null] - The <canvas> DOM node to which we'll render the taken shot (it can also be a jQuery object or an array of DOM nodes).
* @property {string} [postTo] - If specified, we'll post the taken shot to this URL (as a form POST).
* @property {string} [postFieldname] - If postTo, this will be the name of the field containing the image (defaults to WebcamScreenshot.DEFAULT_POST_FIELDNAME).
* @property {string} [postImageFormat='png'] - The file format of the data to send (accepted values: <code>'jpg'</code>, <code>'png'</code>).
* @property {string} [postReturnDataType] - The type of data that you're expecting back from the server. For possible values see thee description of the <code>dataType</code> options of [<code>jQuery.ajax()</code>]{@link http://api.jquery.com/jquery.ajax/}. 
* @property {WebcamScreenshot.goBeforePostCallback} [onBeforePost] - A function that will be called before posting the image.
* @property {string} [dialogFramework] - The optional framework to use to show the dialog. Allowed values:
* <ul>
* 	<li><code>'bs2'</code> for Bootstrap 2</li>
* 	<li><code>'bs3'</code> for Bootstrap 3</li>
* 	<li><code>'jQueryUI'</code> for jQuery UI</li>
* </ul>
* @property {string} [dialogTitle='Screenshot from webcam'] - The title of the dialog (used only if <code>dialogFramework</code> is specified).
*/

/** This callback is called just before POSTing the image.
* @callback WebcamScreenshot.goBeforePostCallback
* @param {FormData} formData - The <code>FormData</code> instance that's going to be posted. Useful to add other values to the POST (example: <code>formData.append('myfield', 'myvalue');</code>).
*/

/** Callback called after the [<code>go()</code> method]{@link WebcamScreenshot.go} ends.
* @callback WebcamScreenshot.goCallback
* @param {WebcamScreenshot.RC} code - One of the <code>WebcamScreenshot.RC</code> values.
* @param {(string|Object)} [result] - This value may be:
* <ul>
* 	<li>If <code>code</code> is not <code>WebcamScreenshot.RC.OK</code> → <code>result</code> will contain error description ({string}).</li>
* 	<li>If <code>code</code> is <code>WebcamScreenshot.RC.OK</code>:
*		<ul>
*			<li>If the <code>postTo</code> option has been specified → <code>result</code> will contain the response from the server.</li>
*			<li>If the <code>postTo</code> optionw wasn't set → <code>result</code> will be <code>undefined</code>.</li>
*		</ul>
*	</li>
* </ul>
*/

w.WebcamScreenshot = WebcamScreenshot;
})(window, jQuery);