diff --git a/doc/webcamjs/LICENSE b/doc/webcamjs/LICENSE
new file mode 100644
index 0000000..872b93c
--- /dev/null
+++ b/doc/webcamjs/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2012 - 2014 Joseph Huckaby
+
+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.
diff --git a/doc/webcamjs/README.md b/doc/webcamjs/README.md
new file mode 100644
index 0000000..95f1459
--- /dev/null
+++ b/doc/webcamjs/README.md
@@ -0,0 +1,453 @@
+# WebcamJS
+
+WebcamJS is a small (~3K minified and gzipped) standalone JavaScript library for capturing still images from your computer's camera, and delivering them to you as JPEG or PNG [Data URIs](http://en.wikipedia.org/wiki/Data_URI_scheme). The images can then be displayed in your web page, rendered into a canvas, or submitted to your server. WebcamJS uses [HTML5 getUserMedia](http://dev.w3.org/2011/webrtc/editor/getusermedia.html), but provides an automatic and invisible Adobe Flash fallback.
+
+WebcamJS is based on my old [JPEGCam](https://code.google.com/p/jpegcam/) project, but has been redesigned for the modern web. Instead of relying solely on Flash and only being able to submit images directly to a server, WebcamJS delivers your images as client-side Data URIs in JavaScript, and it uses HTML5 getUserMedia where available. Flash is only used if your browser doesn't support getUserMedia, and the fallback is handled automatically using the same API (so your code doesn't have to care).
+
+Looking for a good alternative to WebcamJS? Please check out [JpegCamera](https://github.com/amw/jpeg_camera) by [Adam Wróbel](https://github.com/amw). It has many advanced features that WebcamJS is lacking (for example, upload multiple photos at once, retry failed uploads, CSRF tokens, make sure camera is ready), and has a very clean and object-oriented design.
+
+## Important Note for Chrome 47+
+
+Google Chrome has made it a strict requirement that your website be secure (HTTPS) if you want to access the camera. This change is live in Chrome version 47 and up. So basically, if you want to use WebcamJS, you will need to host your website with SSL / HTTPS. The only alternative is to force Flash fallback mode on Chrome, which is probably not desirable.
+
+See the Chromium docs for details: [https://www.chromium.org/Home/chromium-security/prefer-secure-origins-for-powerful-new-features]
+
+Note that you do not need HTTPS for localhost / 127.0.0.1. Here is the list of rules for Chrome which unlock the camera:
+
+| Scheme | Host | Port |
+|--------|------|------|
+| `https://` | `*` | `*` |
+| `wss://` | `*` | `*` |
+| `*` | `localhost` | `*` |
+| `*` | `*.localhost` | `*` |
+| `*` | `127/8` | `*` |
+| `*` | `::1/128` | `*` |
+| `file://` | `*` | `-` |
+| `chrome-extension://` | `*` | `-` |
+
+## Browser Support
+
+WebcamJS has been tested on the following browsers / operating systems:
+
+| OS | Browser | Notes |
+|----|---------|-------|
+| Mac OS X | Chrome 30+ | Works |
+| Mac OS X | Firefox 20+ | Works |
+| Mac OS X | Safari 6+ | **Requires Adobe Flash Player** |
+| Windows | Chrome 30+ | Works -- **Chrome 47+ requires HTTPS** |
+| Windows | Firefox 20+ | Works |
+| Windows | IE 9 | **Requires Adobe Flash Player** |
+| Windows | IE 10 | **Requires Adobe Flash Player** |
+| Windows | IE 11 | **Requires Adobe Flash Player** |
+
+## Demos
+
+Here are some live demos showcasing various features of the library:
+
+| Demo Link | Description |
+|------|-------|
+| [Basic Demo](https://pixlcore.com/demos/webcamjs/demos/basic.html) | Demonstrates a basic 320x240 image capture. |
+| [Large Demo](https://pixlcore.com/demos/webcamjs/demos/large.html) | Demonstrates capturing a large 640x480 image, but showing a live preview at 320x240. |
+| [Crop Demo](https://pixlcore.com/demos/webcamjs/demos/crop.html) | Demonstrates cropping a 240x240 square out of the center of a 320x240 webcam image. |
+| [Large Crop Demo](https://pixlcore.com/demos/webcamjs/demos/crop-large.html) | Demonstrates a large 480x480 square crop, from a 640x480 image capture, with a 240x240 live preview. |
+| [HD Demo](https://pixlcore.com/demos/webcamjs/demos/hd.html) | Demonstrates a 720p HD (1280x720) image capture (only supported by some webcams). |
+| [SFX Demo](https://pixlcore.com/demos/webcamjs/demos/sfx.html) | Demonstrates a camera shutter sound effect at capture time. |
+| [Flash Demo](https://pixlcore.com/demos/webcamjs/demos/flash.html) | Demonstrates forcing Adobe Flash fallback mode. |
+| [Freeze Demo](https://pixlcore.com/demos/webcamjs/demos/preview.html) | Demonstrates freezing / previewing a snapshot before saving it. |
+| [Mirror Demo](https://pixlcore.com/demos/webcamjs/demos/flip.html) | Demonstrates flipping the image horizontally (mirror mode). |
+| **[Full Combo Demo](https://pixlcore.com/demos/webcamjs/demos/combo.html)** | A full combination demo showcasing all features. |
+
+## Open Source
+
+WebcamJS is open source, MIT licensed, and available on GitHub:
+
+https://github.com/jhuckaby/webcamjs
+
+## QuickStart Guide
+
+Host the `webcam.js` and `webcam.swf` files on your web server (both in the same directory), and drop in this HTML snippet:
+
+```html
+
+
+
+
+
+
+
+ Take Snapshot
+```
+
+This will create a live camera view in the `my_camera` DIV, and when the **Take Snapshot** link is clicked it will take a still snapshot, convert it to a JPEG, and deliver a Data URI which is inserted into the `my_result` DIV as a standard `` tag.
+
+Data URIs may be passed around like any URL, and can be submitted to your server as well (see below for example of this).
+
+## Configuration
+
+If you want to override the default settings, just call `Webcam.set()` and pass in a hash with any of the following keys:
+
+| Param Name | Default Value | Notes |
+|------------|---------------|-------|
+| `width` | (Auto) | Width of the live camera viewer in pixels, defaults to the actual size of the DOM element. |
+| `height` | (Auto) | Height of the live camera viewer in pixels, defaults to the actual size of the DOM element. |
+| `dest_width` | (Auto) | Width of the captured camera image in pixels, defaults to the live viewer size. |
+| `dest_height` | (Auto) | Height of the captured camera image in pixels, defaults to the live viewer size. |
+| `crop_width` | (Disabled) | Width of the final cropped image in pixels, defaults to `dest_width`. |
+| `crop_height` | (Disabled) | Height of the final cropped image in pixels, defaults to `dest_height`. |
+| `image_format` | jpeg | Desired image format of captured image, may be "jpeg" or "png". |
+| `jpeg_quality` | 90 | For JPEG images, this is the desired quality, from 0 (worst) to 100 (best). |
+| `force_flash` | false | Setting this to true will always run in Adobe Flash fallback mode. |
+| `flip_horiz` | false | Setting this to true will flip the image horizontally (mirror mode). |
+| `fps` | 30 | Set the desired fps (frames per second) capture rate. |
+
+Here is an example of overriding some parameters. Remember to call this *before* you attach the viewer.
+
+```javascript
+ Webcam.set({
+ width: 320,
+ height: 240,
+ dest_width: 640,
+ dest_height: 480,
+ image_format: 'jpeg',
+ jpeg_quality: 90,
+ force_flash: false,
+ flip_horiz: true,
+ fps: 45
+ });
+
+ // Attach camera here
+```
+
+## Initialization
+
+WebcamJS is initialized and activated by "attaching" a live camera viewer to a DOM element. The DOM element must already be created and empty. Pass in an ID or CSS selector to the `Webcam.attach()` function. Example:
+
+```javascript
+ Webcam.attach( '#my_camera' );
+```
+
+This will activate the user's webcam, ask for the appropriate permission, and begin showing a live camera image in the specified DOM element.
+
+Note that the browser itself handles asking the user for permission to use their camera. WebcamJS has no control over this, so there is no way to style the UI. Each browser does it a little differently, typically a bar at the top of the page, and Flash does it inside the view area.
+
+## Snapping a Picture
+
+To snap a picture, just call the `Webcam.snap()` function, passing in a callback function. The image data will be passed to your function as a Data URI, which you can then display in your web page, or submit to a server. Example:
+
+```javascript
+ Webcam.snap( function(data_uri) {
+ document.getElementById('my_result').innerHTML = '';
+ } );
+```
+
+[See a live demo of this here](https://pixlcore.com/demos/webcamjs/demos/basic.html)
+
+Your function is also passed a HTML5 Canvas and a 2D Context object, so you can gain access to the raw pixels instead of a compressed image Data URI. These are passed as the 2nd and 3rd arguments to your callback function. Example:
+
+```javascript
+ Webcam.snap( function(data_uri, canvas, context) {
+ // copy image to my own canvas
+ myContext.drawImage( context, 0, 0 );
+ } );
+```
+
+If you would prefer that WebcamJS simply copy the image into your own canvas, it can do that instead of generating a Data URI (which can be an expensive operation). To do this, simply pass your canvas object to the `Webcam.snap()` method, as the 2nd argument, right after your callback function. Example:
+
+```javascript
+ // assumes 'myCanvas' is a reference to your own canvas object, at the correct size
+
+ Webcam.snap( function() {
+ // the webcam image is now in your own canvas
+ }, myCanvas );
+```
+
+## Customizing Image Size
+
+WebcamJS will automatically size the live camera viewer based on the DOM element it is attached to. However, you can override this by setting the `width` and/or `height` parameters:
+
+```javascript
+ Webcam.set({
+ width: 320,
+ height: 240
+ });
+
+ // Attach camera here
+```
+
+The size of the captured JPEG / PNG image is set to match the live camera viewer by default. However, you can override this by setting the `dest_width` and/or `dest_height`. Note that you can set the destination image size different than the viewer size. So you can have a small live viewer, but capture a large image. Example:
+
+```javascript
+ Webcam.set({
+ width: 320,
+ height: 240,
+ dest_width: 640,
+ dest_height: 480,
+ });
+
+ // Attach camera here
+```
+
+[See a live demo of this feature here](https://pixlcore.com/demos/webcamjs/demos/large.html)
+
+## Cropping The Image
+
+WebcamJS can also crop the final image for you, to any dimensions you like. This is useful for when you want a square image (perhaps for a website profile pic), but you want to capture the image from the user's webcam at 4:3 ratio to be fully compatible (some cameras require 4:3 and cannot capture square images). To do this, include `crop_width` and `crop_height` params, specifying the area to crop out of the center of the final image:
+
+```javascript
+ Webcam.set({
+ width: 320,
+ height: 240,
+ crop_width: 240,
+ crop_height: 240
+ });
+
+ // Attach camera here
+```
+
+This would crop a 240x240 square out of the center of the 320x240 webcam image. The crop is also reflected in the live preview area. In this case the live preview would also be cropped to 240x240, so the user can see exactly what portion of the image will be captured.
+
+[See a live demo of this feature here](https://pixlcore.com/demos/webcamjs/demos/crop.html)
+
+## Flipping The Image (Mirror Mode)
+
+If you want, WebcamJS can "flip" (mirror) both the live preview and captured image horizontally. This will produce a reversed image, as if you were looking in a mirror. To enable this optional feature, include the `flip_horiz` param, and set it to `true`. Example:
+
+```javascript
+ Webcam.set({
+ width: 320,
+ height: 240,
+ flip_horiz: true
+ });
+
+ // Attach camera here
+```
+
+[See a live demo of this feature here](https://pixlcore.com/demos/webcamjs/demos/flip.html)
+
+## Freezing / Previewing The Image
+
+Want to provide your users with the ability to "freeze" (i.e. preview) the image before actually saving a snapshot? Just call `Webcam.freeze()` to freeze the current camera image. Then call `Webcam.snap()` to save the frozen image, or call `Webcam.unfreeze()` to cancel and resume the live camera feed.
+
+The idea here is to provide a photo-booth-like experience, where the user can take a snapshot, then choose to keep or discard it, before actually calling `Webcam.snap()` to save the image.
+
+[See a live demo of this feature here](https://pixlcore.com/demos/webcamjs/demos/preview.html)
+
+## Setting an Alternate SWF Location
+
+By default WebcamJS looks for the SWF file in the same directory as the JS file. If you are hosting the SWF in a different location, please set it using the `Webcam.setSWFLocation()` function. It should be on the same domain as your page. Example:
+
+```javascript
+ Webcam.setSWFLocation("/path/to/the/webcam.swf");
+```
+
+Note that this is only used if the user's browser doesn't support HTML5 getUserMedia, and WebcamJS has to fallback to using an Adobe Flash movie to capture the camera.
+
+## Reset (Shutdown)
+
+To shut down the live camera preview and reset the system, call `Webcam.reset()`. This removes any DOM elements we added, including a Flash movie if applicable, and resets everything in the library to the initial state. Example:
+
+```javascript
+ Webcam.reset();
+```
+
+To use the library again after resetting, you must call `Webcam.attach()` and pass it your DOM element.
+
+## API Reference
+
+Here is a list of all the API function calls available in the WebcamJS library.
+
+| Method Name | Notes |
+|-------------|-------|
+| `Webcam.set()` | Set configuration parameters. Pass a key + value, or a hash with multiple keys/values. |
+| `Webcam.on()` | Register an event listener for a given event. Pass in the event name, and a callback function. |
+| `Webcam.off()` | Remove an event listener for a given event. Pass in the event name, and the callback function to remove. Omit the callback reference to remove *all* listeners. |
+| `Webcam.setSWFLocation()` | Set an alternate location for the Adobe Flash fallback SWF file (defaults to JS location). |
+| `Webcam.attach()` | Initialize library and attach live camera to specified DOM object. |
+| `Webcam.reset()` | Shut down library and reset everything. Must call `attach()` to use it again. Does not remove event listeners. |
+| `Webcam.freeze()` | Freeze the current live camera frame, allowing the user to preview before saving. |
+| `Webcam.unfreeze()` | Cancel the preview (discard image) and resume the live camera view. |
+| `Webcam.snap()` | Take a snapshot from the camera (or frozen preview image). Pass callback function to receive data. |
+| `Webcam.upload()` | Upload a saved image to your server via binary AJAX. Fires progress events (see below). |
+
+## Custom Events
+
+WebcamJS fires a number of events you can intercept using a simple JavaScript hook system. Events are fired when: the library is fully loaded, when the camera is live (after user allows access), when an error occurs, and during upload. To register an event listener, call the `Webcam.on()` function, passing an event name and callback function. Here is a table of the available event types:
+
+| Event Name | Notes |
+|------------|-------|
+| `load` | Fires when the library finishes loading. |
+| `live` | Fires when the user's camera goes live (i.e. showing a live preview). This will only happen after the user allows access to their camera. |
+| `error` | Fires when an error occurs (your callback function is passed an error string). |
+| `uploadProgress` | Fires repeatedly while an upload is in progress (see below). |
+| `uploadComplete` | Fires once when the upload completes (see below). |
+
+Example:
+
+```javascript
+ Webcam.on( 'load', function() {
+ // library is loaded
+ } );
+
+ Webcam.on( 'live', function() {
+ // camera is live, showing preview image
+ // (and user has allowed access)
+ } );
+
+ Webcam.on( 'error', function(err) {
+ // an error occurred (see 'err')
+ } );
+```
+
+By default the `error` event shows a JavaScript alert dialog, but if you register your own event handler this action is suppressed, and your function is called instead.
+
+Please note that WebcamJS allows multiple listeners on the same event. So if you call `Webcam.on()` multiple times, your callback functions are all added to an array for the event, and *all* of them will be called when the event fires. So only call `Webcam.on()` once for each listener function. You can use `Webcam.off()` to remove listeners from an event.
+
+## Submitting Images to a Server
+
+The `Webcam.snap()` function delivers your image by way of a client-side JavaScript Data URI. The binary image data is encoded with Base64 and stuffed into the URI. You can use this image in JavaScript and display it on your page. However, the library also provides a way to decode and submit this image data to a server API endpoint, via binary AJAX. Example:
+
+```javascript
+ Webcam.snap( function(data_uri) {
+ // snap complete, image data is in 'data_uri'
+
+ Webcam.upload( data_uri, 'myscript.php', function(code, text) {
+ // Upload complete!
+ // 'code' will be the HTTP response code from the server, e.g. 200
+ // 'text' will be the raw response content
+ } );
+
+ } );
+```
+
+The `Webcam.upload()` function accepts three arguments: the Data URI containing the Base64 encoded image data as returned from `snap()`, a URL to your server API endpoint (PHP script, etc.), and a callback function to execute when the upload is complete. You can alternatively specify the callback using `Webcam.on('uploadComplete', YOUR_FUNC)`.
+
+The image data is uploaded as part of a standard multipart form post, and included as a form element named `webcam`. To gain access to this data, write some server-side code like this (PHP shown):
+
+```php
+ // be aware of file / directory permissions on your server
+ move_uploaded_file($_FILES['webcam']['tmp_name'], 'webcam.jpg');
+```
+
+Treat the uploaded data as if you were receiving a standard form submission with a `` element. The data is sent in the same exact way.
+
+If you need to pass any additional information along with your image to the server, please add a query string to your script URL. For example:
+
+```javascript
+ var username = 'jhuckaby';
+ var image_fmt = 'jpeg';
+ var url = 'myscript.php?username=' + username + '&format=' + image_fmt;
+ Webcam.upload( data_uri, url, function(code, text) {...} );
+```
+
+Those variables will then be available to your server-side code however you would normally access the query string, e.g. `$_GET['username']` in PHP.
+
+### Tracking Upload Progress
+
+If you want to track progress while your image is uploading, you can register an event listener for the `uploadProgress` event. This event is called very frequently while an upload is in progress, and passes the function a floating point number between 0.0 and 1.0 representing the upload progress. Here is how to use:
+
+```javascript
+ Webcam.snap( function(data_uri) {
+
+ Webcam.on( 'uploadProgress', function(progress) {
+ // Upload in progress
+ // 'progress' will be between 0.0 and 1.0
+ } );
+
+ Webcam.on( 'uploadComplete', function(code, text) {
+ // Upload complete!
+ // 'code' will be the HTTP response code from the server, e.g. 200
+ // 'text' will be the raw response content
+ } );
+
+ Webcam.upload( data_uri, 'myscript.php' );
+
+ } );
+```
+
+### Including in an Existing Form
+
+If you are already submitting a form on your page, and simply want to include the image data in your form, you can do this. However, note that the data will be Base64 encoded until it gets to the server, so you will need to decode it on the server-side, and the file size in transit will be about 30% larger than normal.
+
+This alternate upload technique is also shown here because it's probably the only way it'll ever work in IE 7, 8, and 9. Those older IE versions do not support binary AJAX and blobs, so the standard `Webcam.upload()` function will not work, and you'll have to use a form trick like this:
+
+First, add a hidden text element to your form:
+
+```html
+
+```
+
+Then, when you snap your picture, stuff the Data URI into the form field value (minus the header), and submit the form:
+
+```javascript
+ Webcam.snap( function(data_uri) {
+ var raw_image_data = data_uri.replace(/^data\:image\/\w+\;base64\,/, '');
+
+ document.getElementById('mydata').value = raw_image_data;
+ document.getElementById('myform').submit();
+ } );
+```
+
+Finally, in your server-side script, grab the form data as if it were a plain form text field, decode the Base64, and you have your binary image file! Example here in PHP, which assumes JPEG format:
+
+```php
+ $encoded_data = $_POST['mydata'];
+ $binary_data = base64_decode( $encoded_data );
+
+ // save to server (beware of permissions)
+ $result = file_put_contents( 'webcam.jpg', $binary_data );
+ if (!$result) die("Could not save image! Check file permissions.");
+```
+
+## Custom User Media Constraints (Advanced)
+
+The HTML5 getUserMedia API has a constraints system by which you can specify optional or mandatory requirements for the video stream. These include things such a minimum or maximum resolution and/or framerate. By default, WebcamJS will specify a mandatory minimum width and height, matching your `dest_width` and `dest_height` parameters. However, if you want to customize this, you can set a `constraints` parameter using `Webcam.set()`, and pass in an object containing all the custom constraints you want. Example:
+
+```javascript
+ Webcam.set( 'constraints', {
+ mandatory: {
+ minWidth: 1280,
+ minHeight: 720,
+ minFrameRate: 30
+ },
+ optional: [
+ { minFrameRate: 60 }
+ ]
+ } );
+```
+
+To remove the mandatory constraints and instead just specify the resolution you would prefer, you can set simple `width` and `height` properties like this:
+
+```javascript
+ Webcam.set( 'constraints', {
+ width: 1280,
+ height: 720
+ } );
+```
+
+Please call this this before calling `Webcam.attach()`.
+
+Note that some browsers may not support every possible constraint, so consult your browser's documentation and test in all your supported browsers before using this advanced feature. For example, as of this writing Chrome 44 doesn't support framerate constraints.
+
+For more information see the [Media Capture Spec](http://w3c.github.io/mediacapture-main/getusermedia.html#idl-def-Constraints) and the [WebRTC Constraints Spec](http://tools.ietf.org/id/draft-alvestrand-constraints-resolution-00.html).
+
+## License
+
+The MIT License (MIT)
+
+Copyright (c) 2012 - 2015 Joseph Huckaby
+
+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.
diff --git a/doc/webcamjs/bower.json b/doc/webcamjs/bower.json
new file mode 100644
index 0000000..2cab3a6
--- /dev/null
+++ b/doc/webcamjs/bower.json
@@ -0,0 +1,24 @@
+{
+ "name": "webcamjs",
+ "version": "1.0.6",
+ "homepage": "https://github.com/jhuckaby/webcamjs",
+ "authors": [
+ "Joseph Huckaby "
+ ],
+ "description": "HTML5 Webcam Image Capture Library with Flash Fallback",
+ "main": "webcam.js",
+ "keywords": [
+ "webcam"
+ ],
+ "license": "MIT",
+ "ignore": [
+ "**/.*",
+ "node_modules",
+ "bower_components",
+ "test",
+ "tests",
+ "build.sh",
+ "demos",
+ "flash"
+ ]
+}
diff --git a/doc/webcamjs/build.sh b/doc/webcamjs/build.sh
new file mode 100644
index 0000000..af495a8
--- /dev/null
+++ b/doc/webcamjs/build.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+# Build Script for WebcamJS
+# Install uglifyjs first: sudo npm install uglify-js -g
+
+uglifyjs webcam.js -o webcam.min.js --mangle --reserved "Webcam" --preamble "// WebcamJS v1.0.6 - http://github.com/jhuckaby/webcamjs - MIT Licensed"
diff --git a/doc/webcamjs/demos/basic.html b/doc/webcamjs/demos/basic.html
new file mode 100644
index 0000000..038a968
--- /dev/null
+++ b/doc/webcamjs/demos/basic.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+ WebcamJS Test Page
+
+
+
+
Demonstrates simple capture & display with a shutter sound effect
+
(Note: Sound implemented using HTML5 Audio, not part of WebcamJS)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/webcamjs/demos/shutter.mp3 b/doc/webcamjs/demos/shutter.mp3
new file mode 100644
index 0000000..16df2b3
Binary files /dev/null and b/doc/webcamjs/demos/shutter.mp3 differ
diff --git a/doc/webcamjs/demos/shutter.ogg b/doc/webcamjs/demos/shutter.ogg
new file mode 100644
index 0000000..5daf287
Binary files /dev/null and b/doc/webcamjs/demos/shutter.ogg differ
diff --git a/doc/webcamjs/flash/README.txt b/doc/webcamjs/flash/README.txt
new file mode 100644
index 0000000..02adef9
--- /dev/null
+++ b/doc/webcamjs/flash/README.txt
@@ -0,0 +1,15 @@
+NOTE: You don't need anything in the "flash" directory unless you are trying to edit the ActionScript code and rebuild the Flash movie from source. WebcamJS works out of the box in your browser, just grab "webcam.js" and "webcam.swf" for everything you need. The "flash" directory is the raw Flash ActionScript source only, which is not needed at runtime.
+
+BUILDING INSTRUCTIONS
+
+This library requires the AS3 Core Library (as3corelib) available from Google Code:
+ https://github.com/mikechambers/as3corelib
+
+As well as the "Base64Encoder.as" file from the Adobe Flex SDK, available free from Adobe:
+ svn co http://opensource.adobe.com/svn/opensource/flex/sdk/trunk/frameworks/projects/framework
+
+After downloading and extracting the package, place the "com" and "mx" directories right here, alongside the "Webcam.fla" and "Webcam.as" files.
+
+You should then be able to compile the FLA into a SWF in Adobe Flash CS3, CS4 or CS5. This requires at least Adobe Flash CS3 (this is a Flash 9 movie). ActionScript 3.0 is required.
+
+- Joe
diff --git a/doc/webcamjs/flash/Webcam.as b/doc/webcamjs/flash/Webcam.as
new file mode 100644
index 0000000..ab592a1
--- /dev/null
+++ b/doc/webcamjs/flash/Webcam.as
@@ -0,0 +1,179 @@
+package {
+ /* Webcam.js v1.0 */
+ /* Webcam library for capturing JPEG/PNG images and sending them to JavaScript */
+ /* Author: Joseph Huckaby */
+ /* Based on JPEGCam: http://code.google.com/p/jpegcam/ */
+ /* Copyright (c) 2012 Joseph Huckaby */
+ /* Licensed under the MIT License */
+
+ import flash.display.LoaderInfo;
+ import flash.display.Sprite;
+ import flash.display.StageAlign;
+ import flash.display.StageScaleMode;
+ import flash.display.Bitmap;
+ import flash.display.BitmapData;
+ import flash.events.*;
+ import flash.utils.*;
+ import flash.media.Camera;
+ import flash.media.Video;
+ import flash.external.ExternalInterface;
+ import flash.net.*;
+ import flash.system.Security;
+ import flash.system.SecurityPanel;
+ import flash.media.Sound;
+ import flash.media.SoundChannel;
+ import flash.geom.Matrix;
+ import mx.utils.Base64Encoder;
+ import com.adobe.images.BitString;
+ import com.adobe.images.PNGEncoder;
+ import com.adobe.images.JPGEncoder;
+
+ public class Webcam extends Sprite {
+ private var video:Video;
+ private var video_width:int;
+ private var video_height:int;
+ private var dest_width:int;
+ private var dest_height:int;
+ private var camera:Camera;
+ private var bmpdata:BitmapData;
+ private var jpeg_quality:int;
+ private var image_format:String;
+ private var fps:int;
+
+ public function Webcam() {
+ // class constructor
+ flash.system.Security.allowDomain("*");
+ var flashvars:Object = LoaderInfo(this.root.loaderInfo).parameters;
+
+ video_width = Math.floor( flashvars.width );
+ video_height = Math.floor( flashvars.height );
+ dest_width = Math.floor( flashvars.dest_width );
+ dest_height = Math.floor( flashvars.dest_height );
+ jpeg_quality = Math.floor( flashvars.jpeg_quality );
+ image_format = flashvars.image_format;
+ fps = Math.floor( flashvars.fps );
+
+ stage.scaleMode = StageScaleMode.NO_SCALE;
+ // stage.scaleMode = StageScaleMode.EXACT_FIT; // Note: This breaks HD capture
+
+ stage.align = StageAlign.TOP_LEFT;
+ stage.stageWidth = Math.max(video_width, dest_width);
+ stage.stageHeight = Math.max(video_height, dest_height);
+
+ if (flashvars.new_user) {
+ Security.showSettings( SecurityPanel.PRIVACY );
+ }
+
+ // Hack to auto-select iSight camera on Mac (JPEGCam Issue #5, submitted by manuel.gonzalez.noriega)
+ // From: http://www.squidder.com/2009/03/09/trick-auto-select-mac-isight-in-flash/
+ var cameraIdx:int = -1;
+ for (var idx = 0, len = Camera.names.length; idx < len; idx++) {
+ if (Camera.names[idx] == "USB Video Class Video") {
+ cameraIdx = idx;
+ idx = len;
+ }
+ }
+ if (cameraIdx > -1) camera = Camera.getCamera( String(cameraIdx) );
+ else camera = Camera.getCamera();
+
+ if (camera != null) {
+ camera.addEventListener(ActivityEvent.ACTIVITY, activityHandler);
+ camera.addEventListener(StatusEvent.STATUS, handleCameraStatus, false, 0, true);
+ video = new Video( Math.max(video_width, dest_width), Math.max(video_height, dest_height) );
+ video.attachCamera(camera);
+ addChild(video);
+
+ if ((video_width < dest_width) && (video_height < dest_height)) {
+ video.scaleX = video_width / dest_width;
+ video.scaleY = video_height / dest_height;
+ }
+
+ camera.setQuality(0, 100);
+ camera.setKeyFrameInterval(10);
+ camera.setMode( Math.max(video_width, dest_width), Math.max(video_height, dest_height), fps );
+
+ // only detect motion once, to determine when camera is "live"
+ camera.setMotionLevel( 1 );
+
+ ExternalInterface.addCallback('_snap', snap);
+ ExternalInterface.addCallback('_configure', configure);
+
+ ExternalInterface.call('Webcam.flashNotify', 'flashLoadComplete', true);
+ }
+ else {
+ trace("You need a camera.");
+ ExternalInterface.call('Webcam.flashNotify', "error", "No camera was detected.");
+ }
+ }
+
+ public function configure(panel:String = SecurityPanel.CAMERA) {
+ // show configure dialog inside flash movie
+ Security.showSettings(panel);
+ }
+
+ private function activityHandler(event:ActivityEvent):void {
+ trace("activityHandler: " + event);
+ ExternalInterface.call('Webcam.flashNotify', 'cameraLive', true);
+
+ // now disable motion detection (may help reduce CPU usage)
+ camera.setMotionLevel( 100 );
+ }
+
+ private function handleCameraStatus(e:StatusEvent):void {
+ switch (e.code) {
+ case 'Camera.Muted': {
+ trace("Camera not allowed");
+ ExternalInterface.call('Webcam.flashNotify', "error", "Access to camera denied");
+ break;
+ }
+ case 'Camera.Unmuted': {
+ trace("Camera allowed");
+ break;
+ }
+ }
+ }
+
+ public function snap() {
+ // take snapshot from camera, and upload if URL was provided
+ trace("in snap(), drawing to bitmap");
+
+ // take snapshot, convert to jpeg, submit to server
+ bmpdata = new BitmapData( Math.max(video_width, dest_width), Math.max(video_height, dest_height) );
+ bmpdata.draw( video );
+
+ if ((video_width > dest_width) && (video_height > dest_height)) {
+ // resize image downward before submitting
+ var tmpdata = new BitmapData(dest_width, dest_height);
+
+ var matrix = new Matrix();
+ matrix.scale( dest_width / video_width, dest_height / video_height );
+
+ tmpdata.draw( bmpdata, matrix, null, null, null, true ); // smoothing
+ bmpdata = tmpdata;
+ } // need resize
+
+ trace("converting to " + image_format);
+
+ var bytes:ByteArray;
+
+ if (image_format == 'png') {
+ bytes = PNGEncoder.encode( bmpdata );
+ }
+ else {
+ var encoder:JPGEncoder;
+ encoder = new JPGEncoder( jpeg_quality );
+ bytes = encoder.encode( bmpdata );
+ }
+
+ trace("raw image length: " + bytes.length);
+
+ var be = new Base64Encoder();
+ be.encodeBytes( bytes );
+
+ var bstr = be.toString();
+ trace("b64 string length: " + bstr.length);
+
+ return bstr;
+ }
+ }
+}
\ No newline at end of file
diff --git a/doc/webcamjs/flash/Webcam.fla b/doc/webcamjs/flash/Webcam.fla
new file mode 100644
index 0000000..6f17dd1
Binary files /dev/null and b/doc/webcamjs/flash/Webcam.fla differ
diff --git a/doc/webcamjs/flash/com/adobe/images/BitString.as b/doc/webcamjs/flash/com/adobe/images/BitString.as
new file mode 100644
index 0000000..5d89c93
--- /dev/null
+++ b/doc/webcamjs/flash/com/adobe/images/BitString.as
@@ -0,0 +1,42 @@
+/*
+ Adobe Systems Incorporated(r) Source Code License Agreement
+ Copyright(c) 2005 Adobe Systems Incorporated. All rights reserved.
+
+ Please read this Source Code License Agreement carefully before using
+ the source code.
+
+ Adobe Systems Incorporated grants to you a perpetual, worldwide, non-exclusive,
+ no-charge, royalty-free, irrevocable copyright license, to reproduce,
+ prepare derivative works of, publicly display, publicly perform, and
+ distribute this source code and such derivative works in source or
+ object code form without any attribution requirements.
+
+ The name "Adobe Systems Incorporated" must not be used to endorse or promote products
+ derived from the source code without prior written permission.
+
+ You agree to indemnify, hold harmless and defend Adobe Systems Incorporated from and
+ against any loss, damage, claims or lawsuits, including attorney's
+ fees that arise or result from your use or distribution of the source
+ code.
+
+ THIS SOURCE CODE IS PROVIDED "AS IS" AND "WITH ALL FAULTS", WITHOUT
+ ANY TECHNICAL SUPPORT OR ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
+ BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ALSO, THERE IS NO WARRANTY OF
+ NON-INFRINGEMENT, TITLE OR QUIET ENJOYMENT. IN NO EVENT SHALL MACROMEDIA
+ OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOURCE CODE, EVEN IF
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+package com.adobe.images
+{
+ public class BitString
+ {
+ public var len:int = 0;
+ public var val:int = 0;
+ }
+}
\ No newline at end of file
diff --git a/doc/webcamjs/flash/com/adobe/images/JPGEncoder.as b/doc/webcamjs/flash/com/adobe/images/JPGEncoder.as
new file mode 100644
index 0000000..1679976
--- /dev/null
+++ b/doc/webcamjs/flash/com/adobe/images/JPGEncoder.as
@@ -0,0 +1,651 @@
+/*
+ Adobe Systems Incorporated(r) Source Code License Agreement
+ Copyright(c) 2005 Adobe Systems Incorporated. All rights reserved.
+
+ Please read this Source Code License Agreement carefully before using
+ the source code.
+
+ Adobe Systems Incorporated grants to you a perpetual, worldwide, non-exclusive,
+ no-charge, royalty-free, irrevocable copyright license, to reproduce,
+ prepare derivative works of, publicly display, publicly perform, and
+ distribute this source code and such derivative works in source or
+ object code form without any attribution requirements.
+
+ The name "Adobe Systems Incorporated" must not be used to endorse or promote products
+ derived from the source code without prior written permission.
+
+ You agree to indemnify, hold harmless and defend Adobe Systems Incorporated from and
+ against any loss, damage, claims or lawsuits, including attorney's
+ fees that arise or result from your use or distribution of the source
+ code.
+
+ THIS SOURCE CODE IS PROVIDED "AS IS" AND "WITH ALL FAULTS", WITHOUT
+ ANY TECHNICAL SUPPORT OR ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
+ BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ALSO, THERE IS NO WARRANTY OF
+ NON-INFRINGEMENT, TITLE OR QUIET ENJOYMENT. IN NO EVENT SHALL MACROMEDIA
+ OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOURCE CODE, EVEN IF
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+package com.adobe.images
+{
+ import flash.geom.*;
+ import flash.display.*;
+ import flash.utils.*;
+
+ /**
+ * Class that converts BitmapData into a valid JPEG
+ */
+ public class JPGEncoder
+ {
+
+ // Static table initialization
+
+ private var ZigZag:Array = [
+ 0, 1, 5, 6,14,15,27,28,
+ 2, 4, 7,13,16,26,29,42,
+ 3, 8,12,17,25,30,41,43,
+ 9,11,18,24,31,40,44,53,
+ 10,19,23,32,39,45,52,54,
+ 20,22,33,38,46,51,55,60,
+ 21,34,37,47,50,56,59,61,
+ 35,36,48,49,57,58,62,63
+ ];
+
+ private var YTable:Array = new Array(64);
+ private var UVTable:Array = new Array(64);
+ private var fdtbl_Y:Array = new Array(64);
+ private var fdtbl_UV:Array = new Array(64);
+
+ private function initQuantTables(sf:int):void
+ {
+ var i:int;
+ var t:Number;
+ var YQT:Array = [
+ 16, 11, 10, 16, 24, 40, 51, 61,
+ 12, 12, 14, 19, 26, 58, 60, 55,
+ 14, 13, 16, 24, 40, 57, 69, 56,
+ 14, 17, 22, 29, 51, 87, 80, 62,
+ 18, 22, 37, 56, 68,109,103, 77,
+ 24, 35, 55, 64, 81,104,113, 92,
+ 49, 64, 78, 87,103,121,120,101,
+ 72, 92, 95, 98,112,100,103, 99
+ ];
+ for (i = 0; i < 64; i++) {
+ t = Math.floor((YQT[i]*sf+50)/100);
+ if (t < 1) {
+ t = 1;
+ } else if (t > 255) {
+ t = 255;
+ }
+ YTable[ZigZag[i]] = t;
+ }
+ var UVQT:Array = [
+ 17, 18, 24, 47, 99, 99, 99, 99,
+ 18, 21, 26, 66, 99, 99, 99, 99,
+ 24, 26, 56, 99, 99, 99, 99, 99,
+ 47, 66, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99
+ ];
+ for (i = 0; i < 64; i++) {
+ t = Math.floor((UVQT[i]*sf+50)/100);
+ if (t < 1) {
+ t = 1;
+ } else if (t > 255) {
+ t = 255;
+ }
+ UVTable[ZigZag[i]] = t;
+ }
+ var aasf:Array = [
+ 1.0, 1.387039845, 1.306562965, 1.175875602,
+ 1.0, 0.785694958, 0.541196100, 0.275899379
+ ];
+ i = 0;
+ for (var row:int = 0; row < 8; row++)
+ {
+ for (var col:int = 0; col < 8; col++)
+ {
+ fdtbl_Y[i] = (1.0 / (YTable [ZigZag[i]] * aasf[row] * aasf[col] * 8.0));
+ fdtbl_UV[i] = (1.0 / (UVTable[ZigZag[i]] * aasf[row] * aasf[col] * 8.0));
+ i++;
+ }
+ }
+ }
+
+ private var YDC_HT:Array;
+ private var UVDC_HT:Array;
+ private var YAC_HT:Array;
+ private var UVAC_HT:Array;
+
+ private function computeHuffmanTbl(nrcodes:Array, std_table:Array):Array
+ {
+ var codevalue:int = 0;
+ var pos_in_table:int = 0;
+ var HT:Array = new Array();
+ for (var k:int=1; k<=16; k++) {
+ for (var j:int=1; j<=nrcodes[k]; j++) {
+ HT[std_table[pos_in_table]] = new BitString();
+ HT[std_table[pos_in_table]].val = codevalue;
+ HT[std_table[pos_in_table]].len = k;
+ pos_in_table++;
+ codevalue++;
+ }
+ codevalue*=2;
+ }
+ return HT;
+ }
+
+ private var std_dc_luminance_nrcodes:Array = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0];
+ private var std_dc_luminance_values:Array = [0,1,2,3,4,5,6,7,8,9,10,11];
+ private var std_ac_luminance_nrcodes:Array = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d];
+ private var std_ac_luminance_values:Array = [
+ 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,
+ 0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,
+ 0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,
+ 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,
+ 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,
+ 0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,
+ 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,
+ 0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,
+ 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,
+ 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,
+ 0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,
+ 0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,
+ 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,
+ 0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,
+ 0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,
+ 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,
+ 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,
+ 0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,
+ 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,
+ 0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
+ 0xf9,0xfa
+ ];
+
+ private var std_dc_chrominance_nrcodes:Array = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0];
+ private var std_dc_chrominance_values:Array = [0,1,2,3,4,5,6,7,8,9,10,11];
+ private var std_ac_chrominance_nrcodes:Array = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77];
+ private var std_ac_chrominance_values:Array = [
+ 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,
+ 0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,
+ 0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,
+ 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,
+ 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,
+ 0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,
+ 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,
+ 0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,
+ 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,
+ 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,
+ 0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,
+ 0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,
+ 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,
+ 0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,
+ 0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,
+ 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,
+ 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,
+ 0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,
+ 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,
+ 0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
+ 0xf9,0xfa
+ ];
+
+ private function initHuffmanTbl():void
+ {
+ YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values);
+ UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values);
+ YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values);
+ UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values);
+ }
+
+ private var bitcode:Array = new Array(65535);
+ private var category:Array = new Array(65535);
+
+ private function initCategoryNumber():void
+ {
+ var nrlower:int = 1;
+ var nrupper:int = 2;
+ var nr:int;
+ for (var cat:int=1; cat<=15; cat++) {
+ //Positive numbers
+ for (nr=nrlower; nr= 0 ) {
+ if (value & uint(1 << posval) ) {
+ bytenew |= uint(1 << bytepos);
+ }
+ posval--;
+ bytepos--;
+ if (bytepos < 0) {
+ if (bytenew == 0xFF) {
+ writeByte(0xFF);
+ writeByte(0);
+ }
+ else {
+ writeByte(bytenew);
+ }
+ bytepos=7;
+ bytenew=0;
+ }
+ }
+ }
+
+ private function writeByte(value:int):void
+ {
+ byteout.writeByte(value);
+ }
+
+ private function writeWord(value:int):void
+ {
+ writeByte((value>>8)&0xFF);
+ writeByte((value )&0xFF);
+ }
+
+ // DCT & quantization core
+
+ private function fDCTQuant(data:Array, fdtbl:Array):Array
+ {
+ var tmp0:Number, tmp1:Number, tmp2:Number, tmp3:Number, tmp4:Number, tmp5:Number, tmp6:Number, tmp7:Number;
+ var tmp10:Number, tmp11:Number, tmp12:Number, tmp13:Number;
+ var z1:Number, z2:Number, z3:Number, z4:Number, z5:Number, z11:Number, z13:Number;
+ var i:int;
+ /* Pass 1: process rows. */
+ var dataOff:int=0;
+ for (i=0; i<8; i++) {
+ tmp0 = data[dataOff+0] + data[dataOff+7];
+ tmp7 = data[dataOff+0] - data[dataOff+7];
+ tmp1 = data[dataOff+1] + data[dataOff+6];
+ tmp6 = data[dataOff+1] - data[dataOff+6];
+ tmp2 = data[dataOff+2] + data[dataOff+5];
+ tmp5 = data[dataOff+2] - data[dataOff+5];
+ tmp3 = data[dataOff+3] + data[dataOff+4];
+ tmp4 = data[dataOff+3] - data[dataOff+4];
+
+ /* Even part */
+ tmp10 = tmp0 + tmp3; /* phase 2 */
+ tmp13 = tmp0 - tmp3;
+ tmp11 = tmp1 + tmp2;
+ tmp12 = tmp1 - tmp2;
+
+ data[dataOff+0] = tmp10 + tmp11; /* phase 3 */
+ data[dataOff+4] = tmp10 - tmp11;
+
+ z1 = (tmp12 + tmp13) * 0.707106781; /* c4 */
+ data[dataOff+2] = tmp13 + z1; /* phase 5 */
+ data[dataOff+6] = tmp13 - z1;
+
+ /* Odd part */
+ tmp10 = tmp4 + tmp5; /* phase 2 */
+ tmp11 = tmp5 + tmp6;
+ tmp12 = tmp6 + tmp7;
+
+ /* The rotator is modified from fig 4-8 to avoid extra negations. */
+ z5 = (tmp10 - tmp12) * 0.382683433; /* c6 */
+ z2 = 0.541196100 * tmp10 + z5; /* c2-c6 */
+ z4 = 1.306562965 * tmp12 + z5; /* c2+c6 */
+ z3 = tmp11 * 0.707106781; /* c4 */
+
+ z11 = tmp7 + z3; /* phase 5 */
+ z13 = tmp7 - z3;
+
+ data[dataOff+5] = z13 + z2; /* phase 6 */
+ data[dataOff+3] = z13 - z2;
+ data[dataOff+1] = z11 + z4;
+ data[dataOff+7] = z11 - z4;
+
+ dataOff += 8; /* advance pointer to next row */
+ }
+
+ /* Pass 2: process columns. */
+ dataOff = 0;
+ for (i=0; i<8; i++) {
+ tmp0 = data[dataOff+ 0] + data[dataOff+56];
+ tmp7 = data[dataOff+ 0] - data[dataOff+56];
+ tmp1 = data[dataOff+ 8] + data[dataOff+48];
+ tmp6 = data[dataOff+ 8] - data[dataOff+48];
+ tmp2 = data[dataOff+16] + data[dataOff+40];
+ tmp5 = data[dataOff+16] - data[dataOff+40];
+ tmp3 = data[dataOff+24] + data[dataOff+32];
+ tmp4 = data[dataOff+24] - data[dataOff+32];
+
+ /* Even part */
+ tmp10 = tmp0 + tmp3; /* phase 2 */
+ tmp13 = tmp0 - tmp3;
+ tmp11 = tmp1 + tmp2;
+ tmp12 = tmp1 - tmp2;
+
+ data[dataOff+ 0] = tmp10 + tmp11; /* phase 3 */
+ data[dataOff+32] = tmp10 - tmp11;
+
+ z1 = (tmp12 + tmp13) * 0.707106781; /* c4 */
+ data[dataOff+16] = tmp13 + z1; /* phase 5 */
+ data[dataOff+48] = tmp13 - z1;
+
+ /* Odd part */
+ tmp10 = tmp4 + tmp5; /* phase 2 */
+ tmp11 = tmp5 + tmp6;
+ tmp12 = tmp6 + tmp7;
+
+ /* The rotator is modified from fig 4-8 to avoid extra negations. */
+ z5 = (tmp10 - tmp12) * 0.382683433; /* c6 */
+ z2 = 0.541196100 * tmp10 + z5; /* c2-c6 */
+ z4 = 1.306562965 * tmp12 + z5; /* c2+c6 */
+ z3 = tmp11 * 0.707106781; /* c4 */
+
+ z11 = tmp7 + z3; /* phase 5 */
+ z13 = tmp7 - z3;
+
+ data[dataOff+40] = z13 + z2; /* phase 6 */
+ data[dataOff+24] = z13 - z2;
+ data[dataOff+ 8] = z11 + z4;
+ data[dataOff+56] = z11 - z4;
+
+ dataOff++; /* advance pointer to next column */
+ }
+
+ // Quantize/descale the coefficients
+ for (i=0; i<64; i++) {
+ // Apply the quantization and scaling factor & Round to nearest integer
+ data[i] = Math.round((data[i]*fdtbl[i]));
+ }
+ return data;
+ }
+
+ // Chunk writing
+
+ private function writeAPP0():void
+ {
+ writeWord(0xFFE0); // marker
+ writeWord(16); // length
+ writeByte(0x4A); // J
+ writeByte(0x46); // F
+ writeByte(0x49); // I
+ writeByte(0x46); // F
+ writeByte(0); // = "JFIF",'\0'
+ writeByte(1); // versionhi
+ writeByte(1); // versionlo
+ writeByte(0); // xyunits
+ writeWord(1); // xdensity
+ writeWord(1); // ydensity
+ writeByte(0); // thumbnwidth
+ writeByte(0); // thumbnheight
+ }
+
+ private function writeSOF0(width:int, height:int):void
+ {
+ writeWord(0xFFC0); // marker
+ writeWord(17); // length, truecolor YUV JPG
+ writeByte(8); // precision
+ writeWord(height);
+ writeWord(width);
+ writeByte(3); // nrofcomponents
+ writeByte(1); // IdY
+ writeByte(0x11); // HVY
+ writeByte(0); // QTY
+ writeByte(2); // IdU
+ writeByte(0x11); // HVU
+ writeByte(1); // QTU
+ writeByte(3); // IdV
+ writeByte(0x11); // HVV
+ writeByte(1); // QTV
+ }
+
+ private function writeDQT():void
+ {
+ writeWord(0xFFDB); // marker
+ writeWord(132); // length
+ writeByte(0);
+ var i:int;
+ for (i=0; i<64; i++) {
+ writeByte(YTable[i]);
+ }
+ writeByte(1);
+ for (i=0; i<64; i++) {
+ writeByte(UVTable[i]);
+ }
+ }
+
+ private function writeDHT():void
+ {
+ writeWord(0xFFC4); // marker
+ writeWord(0x01A2); // length
+ var i:int;
+
+ writeByte(0); // HTYDCinfo
+ for (i=0; i<16; i++) {
+ writeByte(std_dc_luminance_nrcodes[i+1]);
+ }
+ for (i=0; i<=11; i++) {
+ writeByte(std_dc_luminance_values[i]);
+ }
+
+ writeByte(0x10); // HTYACinfo
+ for (i=0; i<16; i++) {
+ writeByte(std_ac_luminance_nrcodes[i+1]);
+ }
+ for (i=0; i<=161; i++) {
+ writeByte(std_ac_luminance_values[i]);
+ }
+
+ writeByte(1); // HTUDCinfo
+ for (i=0; i<16; i++) {
+ writeByte(std_dc_chrominance_nrcodes[i+1]);
+ }
+ for (i=0; i<=11; i++) {
+ writeByte(std_dc_chrominance_values[i]);
+ }
+
+ writeByte(0x11); // HTUACinfo
+ for (i=0; i<16; i++) {
+ writeByte(std_ac_chrominance_nrcodes[i+1]);
+ }
+ for (i=0; i<=161; i++) {
+ writeByte(std_ac_chrominance_values[i]);
+ }
+ }
+
+ private function writeSOS():void
+ {
+ writeWord(0xFFDA); // marker
+ writeWord(12); // length
+ writeByte(3); // nrofcomponents
+ writeByte(1); // IdY
+ writeByte(0); // HTY
+ writeByte(2); // IdU
+ writeByte(0x11); // HTU
+ writeByte(3); // IdV
+ writeByte(0x11); // HTV
+ writeByte(0); // Ss
+ writeByte(0x3f); // Se
+ writeByte(0); // Bf
+ }
+
+ // Core processing
+ private var DU:Array = new Array(64);
+
+ private function processDU(CDU:Array, fdtbl:Array, DC:Number, HTDC:Array, HTAC:Array):Number
+ {
+ var EOB:BitString = HTAC[0x00];
+ var M16zeroes:BitString = HTAC[0xF0];
+ var i:int;
+
+ var DU_DCT:Array = fDCTQuant(CDU, fdtbl);
+ //ZigZag reorder
+ for (i=0;i<64;i++) {
+ DU[ZigZag[i]]=DU_DCT[i];
+ }
+ var Diff:int = DU[0] - DC; DC = DU[0];
+ //Encode DC
+ if (Diff==0) {
+ writeBits(HTDC[0]); // Diff might be 0
+ } else {
+ writeBits(HTDC[category[32767+Diff]]);
+ writeBits(bitcode[32767+Diff]);
+ }
+ //Encode ACs
+ var end0pos:int = 63;
+ for (; (end0pos>0)&&(DU[end0pos]==0); end0pos--) {
+ };
+ //end0pos = first element in reverse order !=0
+ if ( end0pos == 0) {
+ writeBits(EOB);
+ return DC;
+ }
+ i = 1;
+ while ( i <= end0pos ) {
+ var startpos:int = i;
+ for (; (DU[i]==0) && (i<=end0pos); i++) {
+ }
+ var nrzeroes:int = i-startpos;
+ if ( nrzeroes >= 16 ) {
+ for (var nrmarker:int=1; nrmarker <= nrzeroes/16; nrmarker++) {
+ writeBits(M16zeroes);
+ }
+ nrzeroes = int(nrzeroes&0xF);
+ }
+ writeBits(HTAC[nrzeroes*16+category[32767+DU[i]]]);
+ writeBits(bitcode[32767+DU[i]]);
+ i++;
+ }
+ if ( end0pos != 63 ) {
+ writeBits(EOB);
+ }
+ return DC;
+ }
+
+ private var YDU:Array = new Array(64);
+ private var UDU:Array = new Array(64);
+ private var VDU:Array = new Array(64);
+
+ private function RGB2YUV(img:BitmapData, xpos:int, ypos:int):void
+ {
+ var pos:int=0;
+ for (var y:int=0; y<8; y++) {
+ for (var x:int=0; x<8; x++) {
+ var P:uint = img.getPixel32(xpos+x,ypos+y);
+ var R:Number = Number((P>>16)&0xFF);
+ var G:Number = Number((P>> 8)&0xFF);
+ var B:Number = Number((P )&0xFF);
+ YDU[pos]=((( 0.29900)*R+( 0.58700)*G+( 0.11400)*B))-128;
+ UDU[pos]=(((-0.16874)*R+(-0.33126)*G+( 0.50000)*B));
+ VDU[pos]=((( 0.50000)*R+(-0.41869)*G+(-0.08131)*B));
+ pos++;
+ }
+ }
+ }
+
+ /**
+ * Constructor for JPEGEncoder class
+ *
+ * @param quality The quality level between 1 and 100 that detrmines the
+ * level of compression used in the generated JPEG
+ * @langversion ActionScript 3.0
+ * @playerversion Flash 9.0
+ * @tiptext
+ */
+ public function JPGEncoder(quality:Number = 50)
+ {
+ if (quality <= 0) {
+ quality = 1;
+ }
+ if (quality > 100) {
+ quality = 100;
+ }
+ var sf:int = 0;
+ if (quality < 50) {
+ sf = int(5000 / quality);
+ } else {
+ sf = int(200 - quality*2);
+ }
+ // Create tables
+ initHuffmanTbl();
+ initCategoryNumber();
+ initQuantTables(sf);
+ }
+
+ /**
+ * Created a JPEG image from the specified BitmapData
+ *
+ * @param image The BitmapData that will be converted into the JPEG format.
+ * @return a ByteArray representing the JPEG encoded image data.
+ * @langversion ActionScript 3.0
+ * @playerversion Flash 9.0
+ * @tiptext
+ */
+ public function encode(image:BitmapData):ByteArray
+ {
+ // Initialize bit writer
+ byteout = new ByteArray();
+ bytenew=0;
+ bytepos=7;
+
+ // Add JPEG headers
+ writeWord(0xFFD8); // SOI
+ writeAPP0();
+ writeDQT();
+ writeSOF0(image.width,image.height);
+ writeDHT();
+ writeSOS();
+
+
+ // Encode 8x8 macroblocks
+ var DCY:Number=0;
+ var DCU:Number=0;
+ var DCV:Number=0;
+ bytenew=0;
+ bytepos=7;
+ for (var ypos:int=0; ypos= 0 ) {
+ var fillbits:BitString = new BitString();
+ fillbits.len = bytepos+1;
+ fillbits.val = (1<<(bytepos+1))-1;
+ writeBits(fillbits);
+ }
+
+ writeWord(0xFFD9); //EOI
+ return byteout;
+ }
+ }
+}
diff --git a/doc/webcamjs/flash/com/adobe/images/PNGEncoder.as b/doc/webcamjs/flash/com/adobe/images/PNGEncoder.as
new file mode 100644
index 0000000..bb86444
--- /dev/null
+++ b/doc/webcamjs/flash/com/adobe/images/PNGEncoder.as
@@ -0,0 +1,144 @@
+/*
+ Adobe Systems Incorporated(r) Source Code License Agreement
+ Copyright(c) 2005 Adobe Systems Incorporated. All rights reserved.
+
+ Please read this Source Code License Agreement carefully before using
+ the source code.
+
+ Adobe Systems Incorporated grants to you a perpetual, worldwide, non-exclusive,
+ no-charge, royalty-free, irrevocable copyright license, to reproduce,
+ prepare derivative works of, publicly display, publicly perform, and
+ distribute this source code and such derivative works in source or
+ object code form without any attribution requirements.
+
+ The name "Adobe Systems Incorporated" must not be used to endorse or promote products
+ derived from the source code without prior written permission.
+
+ You agree to indemnify, hold harmless and defend Adobe Systems Incorporated from and
+ against any loss, damage, claims or lawsuits, including attorney's
+ fees that arise or result from your use or distribution of the source
+ code.
+
+ THIS SOURCE CODE IS PROVIDED "AS IS" AND "WITH ALL FAULTS", WITHOUT
+ ANY TECHNICAL SUPPORT OR ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
+ BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ALSO, THERE IS NO WARRANTY OF
+ NON-INFRINGEMENT, TITLE OR QUIET ENJOYMENT. IN NO EVENT SHALL MACROMEDIA
+ OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOURCE CODE, EVEN IF
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+package com.adobe.images
+{
+ import flash.geom.*;
+ import flash.display.Bitmap;
+ import flash.display.BitmapData;
+ import flash.utils.ByteArray;
+
+ /**
+ * Class that converts BitmapData into a valid PNG
+ */
+ public class PNGEncoder
+ {
+ /**
+ * Created a PNG image from the specified BitmapData
+ *
+ * @param image The BitmapData that will be converted into the PNG format.
+ * @return a ByteArray representing the PNG encoded image data.
+ * @langversion ActionScript 3.0
+ * @playerversion Flash 9.0
+ * @tiptext
+ */
+ public static function encode(img:BitmapData):ByteArray {
+ // Create output byte array
+ var png:ByteArray = new ByteArray();
+ // Write PNG signature
+ png.writeUnsignedInt(0x89504e47);
+ png.writeUnsignedInt(0x0D0A1A0A);
+ // Build IHDR chunk
+ var IHDR:ByteArray = new ByteArray();
+ IHDR.writeInt(img.width);
+ IHDR.writeInt(img.height);
+ IHDR.writeUnsignedInt(0x08060000); // 32bit RGBA
+ IHDR.writeByte(0);
+ writeChunk(png,0x49484452,IHDR);
+ // Build IDAT chunk
+ var IDAT:ByteArray= new ByteArray();
+ for(var i:int=0;i < img.height;i++) {
+ // no filter
+ IDAT.writeByte(0);
+ var p:uint;
+ var j:int;
+ if ( !img.transparent ) {
+ for(j=0;j < img.width;j++) {
+ p = img.getPixel(j,i);
+ IDAT.writeUnsignedInt(
+ uint(((p&0xFFFFFF) << 8)|0xFF));
+ }
+ } else {
+ for(j=0;j < img.width;j++) {
+ p = img.getPixel32(j,i);
+ IDAT.writeUnsignedInt(
+ uint(((p&0xFFFFFF) << 8)|
+ (p>>>24)));
+ }
+ }
+ }
+ IDAT.compress();
+ writeChunk(png,0x49444154,IDAT);
+ // Build IEND chunk
+ writeChunk(png,0x49454E44,null);
+ // return PNG
+ return png;
+ }
+
+ private static var crcTable:Array;
+ private static var crcTableComputed:Boolean = false;
+
+ private static function writeChunk(png:ByteArray,
+ type:uint, data:ByteArray):void {
+ if (!crcTableComputed) {
+ crcTableComputed = true;
+ crcTable = [];
+ var c:uint;
+ for (var n:uint = 0; n < 256; n++) {
+ c = n;
+ for (var k:uint = 0; k < 8; k++) {
+ if (c & 1) {
+ c = uint(uint(0xedb88320) ^
+ uint(c >>> 1));
+ } else {
+ c = uint(c >>> 1);
+ }
+ }
+ crcTable[n] = c;
+ }
+ }
+ var len:uint = 0;
+ if (data != null) {
+ len = data.length;
+ }
+ png.writeUnsignedInt(len);
+ var p:uint = png.position;
+ png.writeUnsignedInt(type);
+ if ( data != null ) {
+ png.writeBytes(data);
+ }
+ var e:uint = png.position;
+ png.position = p;
+ c = 0xffffffff;
+ for (var i:int = 0; i < (e-p); i++) {
+ c = uint(crcTable[
+ (c ^ png.readUnsignedByte()) &
+ uint(0xff)] ^ uint(c >>> 8));
+ }
+ c = uint(c^uint(0xffffffff));
+ png.position = e;
+ png.writeUnsignedInt(c);
+ }
+ }
+}
\ No newline at end of file
diff --git a/doc/webcamjs/flash/mx/utils/Base64Encoder.as b/doc/webcamjs/flash/mx/utils/Base64Encoder.as
new file mode 100644
index 0000000..860484f
--- /dev/null
+++ b/doc/webcamjs/flash/mx/utils/Base64Encoder.as
@@ -0,0 +1,377 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2004-2007 Adobe Systems Incorporated
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file
+// in accordance with the terms of the license agreement accompanying it.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package mx.utils
+{
+
+import flash.utils.ByteArray;
+
+/**
+ * A utility class to encode a String or ByteArray as a Base64 encoded String.
+ *
+ * @langversion 3.0
+ * @playerversion Flash 9
+ * @playerversion AIR 1.1
+ * @productversion Flex 3
+ */
+public class Base64Encoder
+{
+ //--------------------------------------------------------------------------
+ //
+ // Static Class Variables
+ //
+ //--------------------------------------------------------------------------
+
+ /**
+ * Constant definition for the string "UTF-8".
+ *
+ * @langversion 3.0
+ * @playerversion Flash 9
+ * @playerversion AIR 1.1
+ * @productversion Flex 3
+ */
+ public static const CHARSET_UTF_8:String = "UTF-8";
+
+ /**
+ * The character codepoint to be inserted into the encoded output to
+ * denote a new line if insertNewLines is true.
+ *
+ * The default is 10 to represent the line feed \n.
+ *
+ * @langversion 3.0
+ * @playerversion Flash 9
+ * @playerversion AIR 1.1
+ * @productversion Flex 3
+ */
+ public static var newLine:int = 10;
+
+ //--------------------------------------------------------------------------
+ //
+ // Constructor
+ //
+ //--------------------------------------------------------------------------
+
+ /**
+ * Constructor.
+ *
+ * @langversion 3.0
+ * @playerversion Flash 9
+ * @playerversion AIR 1.1
+ * @productversion Flex 3
+ */
+ public function Base64Encoder()
+ {
+ super();
+ reset();
+ }
+
+ //--------------------------------------------------------------------------
+ //
+ // Variables
+ //
+ //--------------------------------------------------------------------------
+
+ /**
+ * A Boolean flag to control whether the sequence of characters specified
+ * for Base64Encoder.newLine are inserted every 76 characters
+ * to wrap the encoded output.
+ *
+ * The default is true.
+ *
+ * @langversion 3.0
+ * @playerversion Flash 9
+ * @playerversion AIR 1.1
+ * @productversion Flex 3
+ */
+ public var insertNewLines:Boolean = true;
+
+ //--------------------------------------------------------------------------
+ //
+ // Public Methods
+ //
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ public function drain():String
+ {
+ var result:String = "";
+
+ for (var i:uint = 0; i < _buffers.length; i++)
+ {
+ var buffer:Array = _buffers[i] as Array;
+ result += String.fromCharCode.apply(null, buffer);
+ }
+
+ _buffers = [];
+ _buffers.push([]);
+
+ return result;
+ }
+
+ /**
+ * Encodes the characters of a String in Base64 and adds the result to
+ * an internal buffer. Subsequent calls to this method add on to the
+ * internal buffer. After all data have been encoded, call
+ * toString() to obtain a Base64 encoded String.
+ *
+ * @param data The String to encode.
+ * @param offset The character position from which to start encoding.
+ * @param length The number of characters to encode from the offset.
+ *
+ * @langversion 3.0
+ * @playerversion Flash 9
+ * @playerversion AIR 1.1
+ * @productversion Flex 3
+ */
+ public function encode(data:String, offset:uint=0, length:uint=0):void
+ {
+ if (length == 0)
+ length = data.length;
+
+ var currentIndex:uint = offset;
+
+ var endIndex:uint = offset + length;
+ if (endIndex > data.length)
+ endIndex = data.length;
+
+ while (currentIndex < endIndex)
+ {
+ _work[_count] = data.charCodeAt(currentIndex);
+ _count++;
+
+ if (_count == _work.length || endIndex - currentIndex == 1)
+ {
+ encodeBlock();
+ _count = 0;
+ _work[0] = 0;
+ _work[1] = 0;
+ _work[2] = 0;
+ }
+ currentIndex++;
+ }
+ }
+
+ /**
+ * Encodes the UTF-8 bytes of a String in Base64 and adds the result to an
+ * internal buffer. The UTF-8 information does not contain a length prefix.
+ * Subsequent calls to this method add on to the internal buffer. After all
+ * data have been encoded, call toString() to obtain a Base64
+ * encoded String.
+ *
+ * @param data The String to encode.
+ *
+ * @langversion 3.0
+ * @playerversion Flash 9
+ * @playerversion AIR 1.1
+ * @productversion Flex 3
+ */
+ public function encodeUTFBytes(data:String):void
+ {
+ var bytes:ByteArray = new ByteArray();
+ bytes.writeUTFBytes(data);
+ bytes.position = 0;
+ encodeBytes(bytes);
+ }
+
+ /**
+ * Encodes a ByteArray in Base64 and adds the result to an internal buffer.
+ * Subsequent calls to this method add on to the internal buffer. After all
+ * data have been encoded, call toString() to obtain a
+ * Base64 encoded String.
+ *
+ * @param data The ByteArray to encode.
+ * @param offset The index from which to start encoding.
+ * @param length The number of bytes to encode from the offset.
+ *
+ * @langversion 3.0
+ * @playerversion Flash 9
+ * @playerversion AIR 1.1
+ * @productversion Flex 3
+ */
+ public function encodeBytes(data:ByteArray, offset:uint=0, length:uint=0):void
+ {
+ if (length == 0)
+ length = data.length;
+
+ var oldPosition:uint = data.position;
+ data.position = offset;
+ var currentIndex:uint = offset;
+
+ var endIndex:uint = offset + length;
+ if (endIndex > data.length)
+ endIndex = data.length;
+
+ while (currentIndex < endIndex)
+ {
+ _work[_count] = data[currentIndex];
+ _count++;
+
+ if (_count == _work.length || endIndex - currentIndex == 1)
+ {
+ encodeBlock();
+ _count = 0;
+ _work[0] = 0;
+ _work[1] = 0;
+ _work[2] = 0;
+ }
+ currentIndex++;
+ }
+
+ data.position = oldPosition;
+ }
+
+ /**
+ * @private
+ */
+ public function flush():String
+ {
+ if (_count > 0)
+ encodeBlock();
+
+ var result:String = drain();
+ reset();
+ return result;
+ }
+
+ /**
+ * Clears all buffers and resets the encoder to its initial state.
+ *
+ * @langversion 3.0
+ * @playerversion Flash 9
+ * @playerversion AIR 1.1
+ * @productversion Flex 3
+ */
+ public function reset():void
+ {
+ _buffers = [];
+ _buffers.push([]);
+ _count = 0;
+ _line = 0;
+ _work[0] = 0;
+ _work[1] = 0;
+ _work[2] = 0;
+ }
+
+ /**
+ * Returns the current buffer as a Base64 encoded String. Note that
+ * calling this method also clears the buffer and resets the
+ * encoder to its initial state.
+ *
+ * @return The Base64 encoded String.
+ *
+ * @langversion 3.0
+ * @playerversion Flash 9
+ * @playerversion AIR 1.1
+ * @productversion Flex 3
+ */
+ public function toString():String
+ {
+ return flush();
+ }
+
+ //--------------------------------------------------------------------------
+ //
+ // Private Methods
+ //
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ private function encodeBlock():void
+ {
+ var currentBuffer:Array = _buffers[_buffers.length - 1] as Array;
+ if (currentBuffer.length >= MAX_BUFFER_SIZE)
+ {
+ currentBuffer = [];
+ _buffers.push(currentBuffer);
+ }
+
+ currentBuffer.push(ALPHABET_CHAR_CODES[(_work[0] & 0xFF) >> 2]);
+ currentBuffer.push(ALPHABET_CHAR_CODES[((_work[0] & 0x03) << 4) | ((_work[1] & 0xF0) >> 4)]);
+
+ if (_count > 1)
+ currentBuffer.push(ALPHABET_CHAR_CODES[((_work[1] & 0x0F) << 2) | ((_work[2] & 0xC0) >> 6) ]);
+ else
+ currentBuffer.push(ESCAPE_CHAR_CODE);
+
+ if (_count > 2)
+ currentBuffer.push(ALPHABET_CHAR_CODES[_work[2] & 0x3F]);
+ else
+ currentBuffer.push(ESCAPE_CHAR_CODE);
+
+ if (insertNewLines)
+ {
+ if ((_line += 4) == 76)
+ {
+ currentBuffer.push(newLine);
+ _line = 0;
+ }
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ //
+ // Private Variables
+ //
+ //--------------------------------------------------------------------------
+
+ /**
+ * An Array of buffer Arrays.
+ *
+ * @langversion 3.0
+ * @playerversion Flash 9
+ * @playerversion AIR 1.1
+ * @productversion Flex 3
+ */
+ private var _buffers:Array;
+ private var _count:uint;
+ private var _line:uint;
+ private var _work:Array = [ 0, 0, 0 ];
+
+ /**
+ * This value represents a safe number of characters (i.e. arguments) that
+ * can be passed to String.fromCharCode.apply() without exceeding the AVM+
+ * stack limit.
+ *
+ * @private
+ */
+ public static const MAX_BUFFER_SIZE:uint = 32767;
+
+ private static const ESCAPE_CHAR_CODE:Number = 61; // The '=' char
+
+ /*
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+ 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3',
+ '4', '5', '6', '7', '8', '9', '+', '/'
+ */
+ private static const ALPHABET_CHAR_CODES:Array =
+ [
+ 65, 66, 67, 68, 69, 70, 71, 72,
+ 73, 74, 75, 76, 77, 78, 79, 80,
+ 81, 82, 83, 84, 85, 86, 87, 88,
+ 89, 90, 97, 98, 99, 100, 101, 102,
+ 103, 104, 105, 106, 107, 108, 109, 110,
+ 111, 112, 113, 114, 115, 116, 117, 118,
+ 119, 120, 121, 122, 48, 49, 50, 51,
+ 52, 53, 54, 55, 56, 57, 43, 47
+ ];
+
+}
+
+}
diff --git a/doc/webcamjs/package.json b/doc/webcamjs/package.json
new file mode 100644
index 0000000..cf8c193
--- /dev/null
+++ b/doc/webcamjs/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "webcamjs",
+ "version": "1.0.6",
+ "description": "HTML5 Webcam Image Capture Library with Flash Fallback",
+ "author": "Joseph Huckaby ",
+ "homepage": "https://github.com/jhuckaby/webcamjs",
+ "license": "MIT",
+ "main": "webcam.js",
+ "scripts": {
+ "build": "./build.sh"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/jhuckaby/webcamjs"
+ },
+ "bugs": {
+ "url": "https://github.com/jhuckaby/webcamjs/issues"
+ },
+ "keywords": [
+ "webcam",
+ "camera",
+ "getusermedia",
+ "flash",
+ "jpegcam"
+ ],
+ "dependencies": {
+
+ },
+ "devDependencies": {
+
+ }
+}
\ No newline at end of file
diff --git a/doc/webcamjs/webcam.js b/doc/webcamjs/webcam.js
new file mode 100644
index 0000000..3edd325
--- /dev/null
+++ b/doc/webcamjs/webcam.js
@@ -0,0 +1,718 @@
+// WebcamJS v1.0.6
+// Webcam library for capturing JPEG/PNG images in JavaScript
+// Attempts getUserMedia, falls back to Flash
+// Author: Joseph Huckaby: http://github.com/jhuckaby
+// Based on JPEGCam: http://code.google.com/p/jpegcam/
+// Copyright (c) 2012 - 2015 Joseph Huckaby
+// Licensed under the MIT License
+
+(function(window) {
+
+var Webcam = {
+ version: '1.0.6',
+
+ // globals
+ protocol: location.protocol.match(/https/i) ? 'https' : 'http',
+ swfURL: '', // URI to webcam.swf movie (defaults to the js location)
+ loaded: false, // true when webcam movie finishes loading
+ live: false, // true when webcam is initialized and ready to snap
+ userMedia: true, // true when getUserMedia is supported natively
+
+ params: {
+ width: 0,
+ height: 0,
+ dest_width: 0, // size of captured image
+ dest_height: 0, // these default to width/height
+ image_format: 'jpeg', // image format (may be jpeg or png)
+ jpeg_quality: 90, // jpeg image quality from 0 (worst) to 100 (best)
+ force_flash: false, // force flash mode,
+ flip_horiz: false, // flip image horiz (mirror mode)
+ fps: 30, // camera frames per second
+ upload_name: 'webcam', // name of file in upload post data
+ constraints: null // custom user media constraints
+ },
+
+ hooks: {}, // callback hook functions
+
+ init: function() {
+ // initialize, check for getUserMedia support
+ var self = this;
+
+ // Setup getUserMedia, with polyfill for older browsers
+ // Adapted from: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
+ this.mediaDevices = (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) ?
+ navigator.mediaDevices : ((navigator.mozGetUserMedia || navigator.webkitGetUserMedia) ? {
+ getUserMedia: function(c) {
+ return new Promise(function(y, n) {
+ (navigator.mozGetUserMedia ||
+ navigator.webkitGetUserMedia).call(navigator, c, y, n);
+ });
+ }
+ } : null);
+
+ window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
+ this.userMedia = this.userMedia && !!this.mediaDevices && !!window.URL;
+
+ // Older versions of firefox (< 21) apparently claim support but user media does not actually work
+ if (navigator.userAgent.match(/Firefox\D+(\d+)/)) {
+ if (parseInt(RegExp.$1, 10) < 21) this.userMedia = null;
+ }
+
+ // Make sure media stream is closed when navigating away from page
+ if (this.userMedia) {
+ window.addEventListener( 'beforeunload', function(event) {
+ self.reset();
+ } );
+ }
+ },
+
+ attach: function(elem) {
+ // create webcam preview and attach to DOM element
+ // pass in actual DOM reference, ID, or CSS selector
+ if (typeof(elem) == 'string') {
+ elem = document.getElementById(elem) || document.querySelector(elem);
+ }
+ if (!elem) {
+ return this.dispatch('error', "Could not locate DOM element to attach to.");
+ }
+ this.container = elem;
+ elem.innerHTML = ''; // start with empty element
+
+ // insert "peg" so we can insert our preview canvas adjacent to it later on
+ var peg = document.createElement('div');
+ elem.appendChild( peg );
+ this.peg = peg;
+
+ // set width/height if not already set
+ if (!this.params.width) this.params.width = elem.offsetWidth;
+ if (!this.params.height) this.params.height = elem.offsetHeight;
+
+ // set defaults for dest_width / dest_height if not set
+ if (!this.params.dest_width) this.params.dest_width = this.params.width;
+ if (!this.params.dest_height) this.params.dest_height = this.params.height;
+
+ // if force_flash is set, disable userMedia
+ if (this.params.force_flash) this.userMedia = null;
+
+ // check for default fps
+ if (typeof this.params.fps !== "number") this.params.fps = 30;
+
+ // adjust scale if dest_width or dest_height is different
+ var scaleX = this.params.width / this.params.dest_width;
+ var scaleY = this.params.height / this.params.dest_height;
+
+ if (this.userMedia) {
+ // setup webcam video container
+ var video = document.createElement('video');
+ video.setAttribute('autoplay', 'autoplay');
+ video.style.width = '' + this.params.dest_width + 'px';
+ video.style.height = '' + this.params.dest_height + 'px';
+
+ if ((scaleX != 1.0) || (scaleY != 1.0)) {
+ elem.style.overflow = 'hidden';
+ video.style.webkitTransformOrigin = '0px 0px';
+ video.style.mozTransformOrigin = '0px 0px';
+ video.style.msTransformOrigin = '0px 0px';
+ video.style.oTransformOrigin = '0px 0px';
+ video.style.transformOrigin = '0px 0px';
+ video.style.webkitTransform = 'scaleX('+scaleX+') scaleY('+scaleY+')';
+ video.style.mozTransform = 'scaleX('+scaleX+') scaleY('+scaleY+')';
+ video.style.msTransform = 'scaleX('+scaleX+') scaleY('+scaleY+')';
+ video.style.oTransform = 'scaleX('+scaleX+') scaleY('+scaleY+')';
+ video.style.transform = 'scaleX('+scaleX+') scaleY('+scaleY+')';
+ }
+
+ // add video element to dom
+ elem.appendChild( video );
+ this.video = video;
+
+ // ask user for access to their camera
+ var self = this;
+ this.mediaDevices.getUserMedia({
+ "audio": false,
+ "video": this.params.constraints || {
+ mandatory: {
+ minWidth: this.params.dest_width,
+ minHeight: this.params.dest_height
+ }
+ }
+ })
+ .then( function(stream) {
+ // got access, attach stream to video
+ video.src = window.URL.createObjectURL( stream ) || stream;
+ self.stream = stream;
+ self.loaded = true;
+ self.live = true;
+ self.dispatch('load');
+ self.dispatch('live');
+ self.flip();
+ })
+ .catch( function(err) {
+ return self.dispatch('error', "Could not access webcam: " + err.name + ": " + err.message, err);
+ });
+ }
+ else {
+ // flash fallback
+ window.Webcam = Webcam; // needed for flash-to-js interface
+ var div = document.createElement('div');
+ div.innerHTML = this.getSWFHTML();
+ elem.appendChild( div );
+ }
+
+ // setup final crop for live preview
+ if (this.params.crop_width && this.params.crop_height) {
+ var scaled_crop_width = Math.floor( this.params.crop_width * scaleX );
+ var scaled_crop_height = Math.floor( this.params.crop_height * scaleY );
+
+ elem.style.width = '' + scaled_crop_width + 'px';
+ elem.style.height = '' + scaled_crop_height + 'px';
+ elem.style.overflow = 'hidden';
+
+ elem.scrollLeft = Math.floor( (this.params.width / 2) - (scaled_crop_width / 2) );
+ elem.scrollTop = Math.floor( (this.params.height / 2) - (scaled_crop_height / 2) );
+ }
+ else {
+ // no crop, set size to desired
+ elem.style.width = '' + this.params.width + 'px';
+ elem.style.height = '' + this.params.height + 'px';
+ }
+ },
+
+ reset: function() {
+ // shutdown camera, reset to potentially attach again
+ if (this.preview_active) this.unfreeze();
+
+ // attempt to fix issue #64
+ this.unflip();
+
+ if (this.userMedia) {
+ if (this.stream) {
+ if (this.stream.getVideoTracks) {
+ // get video track to call stop on it
+ var tracks = this.stream.getVideoTracks();
+ if (tracks && tracks[0] && tracks[0].stop) tracks[0].stop();
+ }
+ else if (this.stream.stop) {
+ // deprecated, may be removed in future
+ this.stream.stop();
+ }
+ }
+ delete this.stream;
+ delete this.video;
+ }
+
+ if (this.container) {
+ this.container.innerHTML = '';
+ delete this.container;
+ }
+
+ this.loaded = false;
+ this.live = false;
+ },
+
+ set: function() {
+ // set one or more params
+ // variable argument list: 1 param = hash, 2 params = key, value
+ if (arguments.length == 1) {
+ for (var key in arguments[0]) {
+ this.params[key] = arguments[0][key];
+ }
+ }
+ else {
+ this.params[ arguments[0] ] = arguments[1];
+ }
+ },
+
+ on: function(name, callback) {
+ // set callback hook
+ name = name.replace(/^on/i, '').toLowerCase();
+ if (!this.hooks[name]) this.hooks[name] = [];
+ this.hooks[name].push( callback );
+ },
+
+ off: function(name, callback) {
+ // remove callback hook
+ name = name.replace(/^on/i, '').toLowerCase();
+ if (this.hooks[name]) {
+ if (callback) {
+ // remove one selected callback from list
+ var idx = this.hooks[name].indexOf(callback);
+ if (idx > -1) this.hooks[name].splice(idx, 1);
+ }
+ else {
+ // no callback specified, so clear all
+ this.hooks[name] = [];
+ }
+ }
+ },
+
+ dispatch: function() {
+ // fire hook callback, passing optional value to it
+ var name = arguments[0].replace(/^on/i, '').toLowerCase();
+ var args = Array.prototype.slice.call(arguments, 1);
+
+ if (this.hooks[name] && this.hooks[name].length) {
+ for (var idx = 0, len = this.hooks[name].length; idx < len; idx++) {
+ var hook = this.hooks[name][idx];
+
+ if (typeof(hook) == 'function') {
+ // callback is function reference, call directly
+ hook.apply(this, args);
+ }
+ else if ((typeof(hook) == 'object') && (hook.length == 2)) {
+ // callback is PHP-style object instance method
+ hook[0][hook[1]].apply(hook[0], args);
+ }
+ else if (window[hook]) {
+ // callback is global function name
+ window[ hook ].apply(window, args);
+ }
+ } // loop
+ return true;
+ }
+ else if (name == 'error') {
+ // default error handler if no custom one specified
+ alert("Webcam.js Error: " + args[0]);
+ }
+
+ return false; // no hook defined
+ },
+
+ setSWFLocation: function(url) {
+ // set location of SWF movie (defaults to webcam.swf in cwd)
+ this.swfURL = url;
+ },
+
+ detectFlash: function() {
+ // return true if browser supports flash, false otherwise
+ // Code snippet borrowed from: https://github.com/swfobject/swfobject
+ var SHOCKWAVE_FLASH = "Shockwave Flash",
+ SHOCKWAVE_FLASH_AX = "ShockwaveFlash.ShockwaveFlash",
+ FLASH_MIME_TYPE = "application/x-shockwave-flash",
+ win = window,
+ nav = navigator,
+ hasFlash = false;
+
+ if (typeof nav.plugins !== "undefined" && typeof nav.plugins[SHOCKWAVE_FLASH] === "object") {
+ var desc = nav.plugins[SHOCKWAVE_FLASH].description;
+ if (desc && (typeof nav.mimeTypes !== "undefined" && nav.mimeTypes[FLASH_MIME_TYPE] && nav.mimeTypes[FLASH_MIME_TYPE].enabledPlugin)) {
+ hasFlash = true;
+ }
+ }
+ else if (typeof win.ActiveXObject !== "undefined") {
+ try {
+ var ax = new ActiveXObject(SHOCKWAVE_FLASH_AX);
+ if (ax) {
+ var ver = ax.GetVariable("$version");
+ if (ver) hasFlash = true;
+ }
+ }
+ catch (e) {;}
+ }
+
+ return hasFlash;
+ },
+
+ getSWFHTML: function() {
+ // Return HTML for embedding flash based webcam capture movie
+ var html = '';
+
+ // make sure we aren't running locally (flash doesn't work)
+ if (location.protocol.match(/file/)) {
+ this.dispatch('error', "Flash does not work from local disk. Please run from a web server.");
+ return '
ERROR: the Webcam.js Flash fallback does not work from local disk. Please run it from a web server.
';
+ }
+
+ // make sure we have flash
+ if (!this.detectFlash()) {
+ this.dispatch('error', "Adobe Flash Player not found. Please install from get.adobe.com/flashplayer and try again.");
+ return '
ERROR: No Adobe Flash Player detected. Webcam.js relies on Flash for browsers that do not support getUserMedia (like yours).
';
+ }
+
+ // set default swfURL if not explicitly set
+ if (!this.swfURL) {
+ // find our script tag, and use that base URL
+ var base_url = '';
+ var scpts = document.getElementsByTagName('script');
+ for (var idx = 0, len = scpts.length; idx < len; idx++) {
+ var src = scpts[idx].getAttribute('src');
+ if (src && src.match(/\/webcam(\.min)?\.js/)) {
+ base_url = src.replace(/\/webcam(\.min)?\.js.*$/, '');
+ idx = len;
+ }
+ }
+ if (base_url) this.swfURL = base_url + '/webcam.swf';
+ else this.swfURL = 'webcam.swf';
+ }
+
+ // if this is the user's first visit, set flashvar so flash privacy settings panel is shown first
+ if (window.localStorage && !localStorage.getItem('visited')) {
+ this.params.new_user = 1;
+ localStorage.setItem('visited', 1);
+ }
+
+ // construct flashvars string
+ var flashvars = '';
+ for (var key in this.params) {
+ if (flashvars) flashvars += '&';
+ flashvars += key + '=' + escape(this.params[key]);
+ }
+
+ // construct object/embed tag
+ html += '';
+
+ return html;
+ },
+
+ getMovie: function() {
+ // get reference to movie object/embed in DOM
+ if (!this.loaded) return this.dispatch('error', "Flash Movie is not loaded yet");
+ var movie = document.getElementById('webcam_movie_obj');
+ if (!movie || !movie._snap) movie = document.getElementById('webcam_movie_embed');
+ if (!movie) this.dispatch('error', "Cannot locate Flash movie in DOM");
+ return movie;
+ },
+
+ freeze: function() {
+ // show preview, freeze camera
+ var self = this;
+ var params = this.params;
+
+ // kill preview if already active
+ if (this.preview_active) this.unfreeze();
+
+ // determine scale factor
+ var scaleX = this.params.width / this.params.dest_width;
+ var scaleY = this.params.height / this.params.dest_height;
+
+ // must unflip container as preview canvas will be pre-flipped
+ this.unflip();
+
+ // calc final size of image
+ var final_width = params.crop_width || params.dest_width;
+ var final_height = params.crop_height || params.dest_height;
+
+ // create canvas for holding preview
+ var preview_canvas = document.createElement('canvas');
+ preview_canvas.width = final_width;
+ preview_canvas.height = final_height;
+ var preview_context = preview_canvas.getContext('2d');
+
+ // save for later use
+ this.preview_canvas = preview_canvas;
+ this.preview_context = preview_context;
+
+ // scale for preview size
+ if ((scaleX != 1.0) || (scaleY != 1.0)) {
+ preview_canvas.style.webkitTransformOrigin = '0px 0px';
+ preview_canvas.style.mozTransformOrigin = '0px 0px';
+ preview_canvas.style.msTransformOrigin = '0px 0px';
+ preview_canvas.style.oTransformOrigin = '0px 0px';
+ preview_canvas.style.transformOrigin = '0px 0px';
+ preview_canvas.style.webkitTransform = 'scaleX('+scaleX+') scaleY('+scaleY+')';
+ preview_canvas.style.mozTransform = 'scaleX('+scaleX+') scaleY('+scaleY+')';
+ preview_canvas.style.msTransform = 'scaleX('+scaleX+') scaleY('+scaleY+')';
+ preview_canvas.style.oTransform = 'scaleX('+scaleX+') scaleY('+scaleY+')';
+ preview_canvas.style.transform = 'scaleX('+scaleX+') scaleY('+scaleY+')';
+ }
+
+ // take snapshot, but fire our own callback
+ this.snap( function() {
+ // add preview image to dom, adjust for crop
+ preview_canvas.style.position = 'relative';
+ preview_canvas.style.left = '' + self.container.scrollLeft + 'px';
+ preview_canvas.style.top = '' + self.container.scrollTop + 'px';
+
+ self.container.insertBefore( preview_canvas, self.peg );
+ self.container.style.overflow = 'hidden';
+
+ // set flag for user capture (use preview)
+ self.preview_active = true;
+
+ }, preview_canvas );
+ },
+
+ unfreeze: function() {
+ // cancel preview and resume live video feed
+ if (this.preview_active) {
+ // remove preview canvas
+ this.container.removeChild( this.preview_canvas );
+ delete this.preview_context;
+ delete this.preview_canvas;
+
+ // unflag
+ this.preview_active = false;
+
+ // re-flip if we unflipped before
+ this.flip();
+ }
+ },
+
+ flip: function() {
+ // flip container horiz (mirror mode) if desired
+ if (this.params.flip_horiz) {
+ var sty = this.container.style;
+ sty.webkitTransform = 'scaleX(-1)';
+ sty.mozTransform = 'scaleX(-1)';
+ sty.msTransform = 'scaleX(-1)';
+ sty.oTransform = 'scaleX(-1)';
+ sty.transform = 'scaleX(-1)';
+ sty.filter = 'FlipH';
+ sty.msFilter = 'FlipH';
+ }
+ },
+
+ unflip: function() {
+ // unflip container horiz (mirror mode) if desired
+ if (this.params.flip_horiz) {
+ var sty = this.container.style;
+ sty.webkitTransform = 'scaleX(1)';
+ sty.mozTransform = 'scaleX(1)';
+ sty.msTransform = 'scaleX(1)';
+ sty.oTransform = 'scaleX(1)';
+ sty.transform = 'scaleX(1)';
+ sty.filter = '';
+ sty.msFilter = '';
+ }
+ },
+
+ savePreview: function(user_callback, user_canvas) {
+ // save preview freeze and fire user callback
+ var params = this.params;
+ var canvas = this.preview_canvas;
+ var context = this.preview_context;
+
+ // render to user canvas if desired
+ if (user_canvas) {
+ var user_context = user_canvas.getContext('2d');
+ user_context.drawImage( canvas, 0, 0 );
+ }
+
+ // fire user callback if desired
+ user_callback(
+ user_canvas ? null : canvas.toDataURL('image/' + params.image_format, params.jpeg_quality / 100 ),
+ canvas,
+ context
+ );
+
+ // remove preview
+ this.unfreeze();
+ },
+
+ snap: function(user_callback, user_canvas) {
+ // take snapshot and return image data uri
+ var self = this;
+ var params = this.params;
+
+ if (!this.loaded) return this.dispatch('error', "Webcam is not loaded yet");
+ // if (!this.live) return this.dispatch('error', "Webcam is not live yet");
+ if (!user_callback) return this.dispatch('error', "Please provide a callback function or canvas to snap()");
+
+ // if we have an active preview freeze, use that
+ if (this.preview_active) {
+ this.savePreview( user_callback, user_canvas );
+ return null;
+ }
+
+ // create offscreen canvas element to hold pixels
+ var canvas = document.createElement('canvas');
+ canvas.width = this.params.dest_width;
+ canvas.height = this.params.dest_height;
+ var context = canvas.getContext('2d');
+
+ // flip canvas horizontally if desired
+ if (this.params.flip_horiz) {
+ context.translate( params.dest_width, 0 );
+ context.scale( -1, 1 );
+ }
+
+ // create inline function, called after image load (flash) or immediately (native)
+ var func = function() {
+ // render image if needed (flash)
+ if (this.src && this.width && this.height) {
+ context.drawImage(this, 0, 0, params.dest_width, params.dest_height);
+ }
+
+ // crop if desired
+ if (params.crop_width && params.crop_height) {
+ var crop_canvas = document.createElement('canvas');
+ crop_canvas.width = params.crop_width;
+ crop_canvas.height = params.crop_height;
+ var crop_context = crop_canvas.getContext('2d');
+
+ crop_context.drawImage( canvas,
+ Math.floor( (params.dest_width / 2) - (params.crop_width / 2) ),
+ Math.floor( (params.dest_height / 2) - (params.crop_height / 2) ),
+ params.crop_width,
+ params.crop_height,
+ 0,
+ 0,
+ params.crop_width,
+ params.crop_height
+ );
+
+ // swap canvases
+ context = crop_context;
+ canvas = crop_canvas;
+ }
+
+ // render to user canvas if desired
+ if (user_canvas) {
+ var user_context = user_canvas.getContext('2d');
+ user_context.drawImage( canvas, 0, 0 );
+ }
+
+ // fire user callback if desired
+ user_callback(
+ user_canvas ? null : canvas.toDataURL('image/' + params.image_format, params.jpeg_quality / 100 ),
+ canvas,
+ context
+ );
+ };
+
+ // grab image frame from userMedia or flash movie
+ if (this.userMedia) {
+ // native implementation
+ context.drawImage(this.video, 0, 0, this.params.dest_width, this.params.dest_height);
+
+ // fire callback right away
+ func();
+ }
+ else {
+ // flash fallback
+ var raw_data = this.getMovie()._snap();
+
+ // render to image, fire callback when complete
+ var img = new Image();
+ img.onload = func;
+ img.src = 'data:image/'+this.params.image_format+';base64,' + raw_data;
+ }
+
+ return null;
+ },
+
+ configure: function(panel) {
+ // open flash configuration panel -- specify tab name:
+ // "camera", "privacy", "default", "localStorage", "microphone", "settingsManager"
+ if (!panel) panel = "camera";
+ this.getMovie()._configure(panel);
+ },
+
+ flashNotify: function(type, msg) {
+ // receive notification from flash about event
+ switch (type) {
+ case 'flashLoadComplete':
+ // movie loaded successfully
+ this.loaded = true;
+ this.dispatch('load');
+ break;
+
+ case 'cameraLive':
+ // camera is live and ready to snap
+ this.live = true;
+ this.dispatch('live');
+ this.flip();
+ break;
+
+ case 'error':
+ // Flash error
+ this.dispatch('error', msg);
+ break;
+
+ default:
+ // catch-all event, just in case
+ // console.log("webcam flash_notify: " + type + ": " + msg);
+ break;
+ }
+ },
+
+ b64ToUint6: function(nChr) {
+ // convert base64 encoded character to 6-bit integer
+ // from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
+ return nChr > 64 && nChr < 91 ? nChr - 65
+ : nChr > 96 && nChr < 123 ? nChr - 71
+ : nChr > 47 && nChr < 58 ? nChr + 4
+ : nChr === 43 ? 62 : nChr === 47 ? 63 : 0;
+ },
+
+ base64DecToArr: function(sBase64, nBlocksSize) {
+ // convert base64 encoded string to Uintarray
+ // from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
+ var sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length,
+ nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2,
+ taBytes = new Uint8Array(nOutLen);
+
+ for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
+ nMod4 = nInIdx & 3;
+ nUint24 |= this.b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4;
+ if (nMod4 === 3 || nInLen - nInIdx === 1) {
+ for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
+ taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
+ }
+ nUint24 = 0;
+ }
+ }
+ return taBytes;
+ },
+
+ upload: function(image_data_uri, target_url, callback) {
+ // submit image data to server using binary AJAX
+ var form_elem_name = this.params.upload_name || 'webcam';
+
+ // detect image format from within image_data_uri
+ var image_fmt = '';
+ if (image_data_uri.match(/^data\:image\/(\w+)/))
+ image_fmt = RegExp.$1;
+ else
+ throw "Cannot locate image format in Data URI";
+
+ // extract raw base64 data from Data URI
+ var raw_image_data = image_data_uri.replace(/^data\:image\/\w+\;base64\,/, '');
+
+ // contruct use AJAX object
+ var http = new XMLHttpRequest();
+ http.open("POST", target_url, true);
+
+ // setup progress events
+ if (http.upload && http.upload.addEventListener) {
+ http.upload.addEventListener( 'progress', function(e) {
+ if (e.lengthComputable) {
+ var progress = e.loaded / e.total;
+ Webcam.dispatch('uploadProgress', progress, e);
+ }
+ }, false );
+ }
+
+ // completion handler
+ var self = this;
+ http.onload = function() {
+ if (callback) callback.apply( self, [http.status, http.responseText, http.statusText] );
+ Webcam.dispatch('uploadComplete', http.status, http.responseText, http.statusText);
+ };
+
+ // create a blob and decode our base64 to binary
+ var blob = new Blob( [ this.base64DecToArr(raw_image_data) ], {type: 'image/'+image_fmt} );
+
+ // stuff into a form, so servers can easily receive it as a standard file upload
+ var form = new FormData();
+ form.append( form_elem_name, blob, form_elem_name+"."+image_fmt.replace(/e/, '') );
+
+ // send data to server
+ http.send(form);
+ }
+
+};
+
+Webcam.init();
+
+if (typeof define === 'function' && define.amd) {
+ define( function() { return Webcam; } );
+}
+else if (typeof module === 'object' && module.exports) {
+ module.exports = Webcam;
+}
+else {
+ window.Webcam = Webcam;
+}
+
+}(window));
diff --git a/doc/webcamjs/webcam.min.js b/doc/webcamjs/webcam.min.js
new file mode 100644
index 0000000..fd8d05c
--- /dev/null
+++ b/doc/webcamjs/webcam.min.js
@@ -0,0 +1,2 @@
+// WebcamJS v1.0.6 - http://github.com/jhuckaby/webcamjs - MIT Licensed
+(function(e){var Webcam={version:"1.0.6",protocol:location.protocol.match(/https/i)?"https":"http",swfURL:"",loaded:false,live:false,userMedia:true,params:{width:0,height:0,dest_width:0,dest_height:0,image_format:"jpeg",jpeg_quality:90,force_flash:false,flip_horiz:false,fps:30,upload_name:"webcam",constraints:null},hooks:{},init:function(){var t=this;this.mediaDevices=navigator.mediaDevices&&navigator.mediaDevices.getUserMedia?navigator.mediaDevices:navigator.mozGetUserMedia||navigator.webkitGetUserMedia?{getUserMedia:function(e){return new Promise(function(t,a){(navigator.mozGetUserMedia||navigator.webkitGetUserMedia).call(navigator,e,t,a)})}}:null;e.URL=e.URL||e.webkitURL||e.mozURL||e.msURL;this.userMedia=this.userMedia&&!!this.mediaDevices&&!!e.URL;if(navigator.userAgent.match(/Firefox\D+(\d+)/)){if(parseInt(RegExp.$1,10)<21)this.userMedia=null}if(this.userMedia){e.addEventListener("beforeunload",function(e){t.reset()})}},attach:function(t){if(typeof t=="string"){t=document.getElementById(t)||document.querySelector(t)}if(!t){return this.dispatch("error","Could not locate DOM element to attach to.")}this.container=t;t.innerHTML="";var a=document.createElement("div");t.appendChild(a);this.peg=a;if(!this.params.width)this.params.width=t.offsetWidth;if(!this.params.height)this.params.height=t.offsetHeight;if(!this.params.dest_width)this.params.dest_width=this.params.width;if(!this.params.dest_height)this.params.dest_height=this.params.height;if(this.params.force_flash)this.userMedia=null;if(typeof this.params.fps!=="number")this.params.fps=30;var i=this.params.width/this.params.dest_width;var s=this.params.height/this.params.dest_height;if(this.userMedia){var r=document.createElement("video");r.setAttribute("autoplay","autoplay");r.style.width=""+this.params.dest_width+"px";r.style.height=""+this.params.dest_height+"px";if(i!=1||s!=1){t.style.overflow="hidden";r.style.webkitTransformOrigin="0px 0px";r.style.mozTransformOrigin="0px 0px";r.style.msTransformOrigin="0px 0px";r.style.oTransformOrigin="0px 0px";r.style.transformOrigin="0px 0px";r.style.webkitTransform="scaleX("+i+") scaleY("+s+")";r.style.mozTransform="scaleX("+i+") scaleY("+s+")";r.style.msTransform="scaleX("+i+") scaleY("+s+")";r.style.oTransform="scaleX("+i+") scaleY("+s+")";r.style.transform="scaleX("+i+") scaleY("+s+")"}t.appendChild(r);this.video=r;var o=this;this.mediaDevices.getUserMedia({audio:false,video:this.params.constraints||{mandatory:{minWidth:this.params.dest_width,minHeight:this.params.dest_height}}}).then(function(t){r.src=e.URL.createObjectURL(t)||t;o.stream=t;o.loaded=true;o.live=true;o.dispatch("load");o.dispatch("live");o.flip()}).catch(function(e){return o.dispatch("error","Could not access webcam: "+e.name+": "+e.message,e)})}else{e.Webcam=Webcam;var h=document.createElement("div");h.innerHTML=this.getSWFHTML();t.appendChild(h)}if(this.params.crop_width&&this.params.crop_height){var n=Math.floor(this.params.crop_width*i);var l=Math.floor(this.params.crop_height*s);t.style.width=""+n+"px";t.style.height=""+l+"px";t.style.overflow="hidden";t.scrollLeft=Math.floor(this.params.width/2-n/2);t.scrollTop=Math.floor(this.params.height/2-l/2)}else{t.style.width=""+this.params.width+"px";t.style.height=""+this.params.height+"px"}},reset:function(){if(this.preview_active)this.unfreeze();this.unflip();if(this.userMedia){if(this.stream){if(this.stream.getVideoTracks){var e=this.stream.getVideoTracks();if(e&&e[0]&&e[0].stop)e[0].stop()}else if(this.stream.stop){this.stream.stop()}}delete this.stream;delete this.video}if(this.container){this.container.innerHTML="";delete this.container}this.loaded=false;this.live=false},set:function(){if(arguments.length==1){for(var e in arguments[0]){this.params[e]=arguments[0][e]}}else{this.params[arguments[0]]=arguments[1]}},on:function(e,t){e=e.replace(/^on/i,"").toLowerCase();if(!this.hooks[e])this.hooks[e]=[];this.hooks[e].push(t)},off:function(e,t){e=e.replace(/^on/i,"").toLowerCase();if(this.hooks[e]){if(t){var a=this.hooks[e].indexOf(t);if(a>-1)this.hooks[e].splice(a,1)}else{this.hooks[e]=[]}}},dispatch:function(){var t=arguments[0].replace(/^on/i,"").toLowerCase();var a=Array.prototype.slice.call(arguments,1);if(this.hooks[t]&&this.hooks[t].length){for(var i=0,s=this.hooks[t].length;iERROR: the Webcam.js Flash fallback does not work from local disk. Please run it from a web server.'}if(!this.detectFlash()){this.dispatch("error","Adobe Flash Player not found. Please install from get.adobe.com/flashplayer and try again.");return'
ERROR: No Adobe Flash Player detected. Webcam.js relies on Flash for browsers that do not support getUserMedia (like yours).
'}if(!this.swfURL){var a="";var i=document.getElementsByTagName("script");for(var s=0,r=i.length;s';return t},getMovie:function(){if(!this.loaded)return this.dispatch("error","Flash Movie is not loaded yet");var e=document.getElementById("webcam_movie_obj");if(!e||!e._snap)e=document.getElementById("webcam_movie_embed");if(!e)this.dispatch("error","Cannot locate Flash movie in DOM");return e},freeze:function(){var e=this;var t=this.params;if(this.preview_active)this.unfreeze();var a=this.params.width/this.params.dest_width;var i=this.params.height/this.params.dest_height;this.unflip();var s=t.crop_width||t.dest_width;var r=t.crop_height||t.dest_height;var o=document.createElement("canvas");o.width=s;o.height=r;var h=o.getContext("2d");this.preview_canvas=o;this.preview_context=h;if(a!=1||i!=1){o.style.webkitTransformOrigin="0px 0px";o.style.mozTransformOrigin="0px 0px";o.style.msTransformOrigin="0px 0px";o.style.oTransformOrigin="0px 0px";o.style.transformOrigin="0px 0px";o.style.webkitTransform="scaleX("+a+") scaleY("+i+")";o.style.mozTransform="scaleX("+a+") scaleY("+i+")";o.style.msTransform="scaleX("+a+") scaleY("+i+")";o.style.oTransform="scaleX("+a+") scaleY("+i+")";o.style.transform="scaleX("+a+") scaleY("+i+")"}this.snap(function(){o.style.position="relative";o.style.left=""+e.container.scrollLeft+"px";o.style.top=""+e.container.scrollTop+"px";e.container.insertBefore(o,e.peg);e.container.style.overflow="hidden";e.preview_active=true},o)},unfreeze:function(){if(this.preview_active){this.container.removeChild(this.preview_canvas);delete this.preview_context;delete this.preview_canvas;this.preview_active=false;this.flip()}},flip:function(){if(this.params.flip_horiz){var e=this.container.style;e.webkitTransform="scaleX(-1)";e.mozTransform="scaleX(-1)";e.msTransform="scaleX(-1)";e.oTransform="scaleX(-1)";e.transform="scaleX(-1)";e.filter="FlipH";e.msFilter="FlipH"}},unflip:function(){if(this.params.flip_horiz){var e=this.container.style;e.webkitTransform="scaleX(1)";e.mozTransform="scaleX(1)";e.msTransform="scaleX(1)";e.oTransform="scaleX(1)";e.transform="scaleX(1)";e.filter="";e.msFilter=""}},savePreview:function(e,t){var a=this.params;var i=this.preview_canvas;var s=this.preview_context;if(t){var r=t.getContext("2d");r.drawImage(i,0,0)}e(t?null:i.toDataURL("image/"+a.image_format,a.jpeg_quality/100),i,s);this.unfreeze()},snap:function(e,t){var a=this;var i=this.params;if(!this.loaded)return this.dispatch("error","Webcam is not loaded yet");if(!e)return this.dispatch("error","Please provide a callback function or canvas to snap()");if(this.preview_active){this.savePreview(e,t);return null}var s=document.createElement("canvas");s.width=this.params.dest_width;s.height=this.params.dest_height;var r=s.getContext("2d");if(this.params.flip_horiz){r.translate(i.dest_width,0);r.scale(-1,1)}var o=function(){if(this.src&&this.width&&this.height){r.drawImage(this,0,0,i.dest_width,i.dest_height)}if(i.crop_width&&i.crop_height){var a=document.createElement("canvas");a.width=i.crop_width;a.height=i.crop_height;var o=a.getContext("2d");o.drawImage(s,Math.floor(i.dest_width/2-i.crop_width/2),Math.floor(i.dest_height/2-i.crop_height/2),i.crop_width,i.crop_height,0,0,i.crop_width,i.crop_height);r=o;s=a}if(t){var h=t.getContext("2d");h.drawImage(s,0,0)}e(t?null:s.toDataURL("image/"+i.image_format,i.jpeg_quality/100),s,r)};if(this.userMedia){r.drawImage(this.video,0,0,this.params.dest_width,this.params.dest_height);o()}else{var h=this.getMovie()._snap();var n=new Image;n.onload=o;n.src="data:image/"+this.params.image_format+";base64,"+h}return null},configure:function(e){if(!e)e="camera";this.getMovie()._configure(e)},flashNotify:function(e,t){switch(e){case"flashLoadComplete":this.loaded=true;this.dispatch("load");break;case"cameraLive":this.live=true;this.dispatch("live");this.flip();break;case"error":this.dispatch("error",t);break;default:break}},b64ToUint6:function(e){return e>64&&e<91?e-65:e>96&&e<123?e-71:e>47&&e<58?e+4:e===43?62:e===47?63:0},base64DecToArr:function(e,t){var a=e.replace(/[^A-Za-z0-9\+\/]/g,""),i=a.length,s=t?Math.ceil((i*3+1>>2)/t)*t:i*3+1>>2,r=new Uint8Array(s);for(var o,h,n=0,l=0,c=0;c>>(16>>>o&24)&255}n=0}}return r},upload:function(e,t,a){var i=this.params.upload_name||"webcam";var s="";if(e.match(/^data\:image\/(\w+)/))s=RegExp.$1;else throw"Cannot locate image format in Data URI";var r=e.replace(/^data\:image\/\w+\;base64\,/,"");var o=new XMLHttpRequest;o.open("POST",t,true);if(o.upload&&o.upload.addEventListener){o.upload.addEventListener("progress",function(e){if(e.lengthComputable){var t=e.loaded/e.total;Webcam.dispatch("uploadProgress",t,e)}},false)}var h=this;o.onload=function(){if(a)a.apply(h,[o.status,o.responseText,o.statusText]);Webcam.dispatch("uploadComplete",o.status,o.responseText,o.statusText)};var n=new Blob([this.base64DecToArr(r)],{type:"image/"+s});var l=new FormData;l.append(i,n,i+"."+s.replace(/e/,""));o.send(l)}};Webcam.init();if(typeof define==="function"&&define.amd){define(function(){return Webcam})}else if(typeof module==="object"&&module.exports){module.exports=Webcam}else{e.Webcam=Webcam}})(window);
\ No newline at end of file
diff --git a/doc/webcamjs/webcam.swf b/doc/webcamjs/webcam.swf
new file mode 100644
index 0000000..1e19c9d
Binary files /dev/null and b/doc/webcamjs/webcam.swf differ