Skip to content

Instantly share code, notes, and snippets.

@davidrenne
Created December 2, 2015 20:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davidrenne/46482f3d14f4b9409d5f to your computer and use it in GitHub Desktop.
Save davidrenne/46482f3d14f4b9409d5f to your computer and use it in GitHub Desktop.
Snorpey Hacks
var jsdom = require("jsdom").jsdom;
var document = jsdom('<!doctype html>' +
'<html>' +
' <head>' +
' <meta charset="utf-8" />' +
'<title>image triangulation experiment</title>' +
'<link rel="stylesheet" href="styles/main.css" />' +
'</head>' +
'<body data-defaultimage="lincoln.jpg">' +
' <div class="nav-wrapper dark-bg">' +
' <div class="export-wrapper center">' +
' <h1 class="headline">triangulate images</h1>' +
' <button id="cam-button" class="button" title="take photo with you web cam">take photo</button>' +
' <button id="import-button" class="button" title="open image from your computer">open image</button>' +
' <input type="file" id="import-input" accept="image/*" />' +
' <button id="export-button" class="button">download image</button>' +
' <button id="imgur-button" class="button" title="share image via imgur.com"><span>share image</span></button>' +
' <a id="png-link" download="triangulated-image.png" target="_blank" class="download-link">download bitmap<span> (.png)</span></a>' +
' <a id="svg-link" download="triangulated-image.svg" target="_blank" class="download-link">download vector<span> (.svg)</span></a>' +
' <div id="imgur-url-container">' +
' <input id="imgur-url-input" type="text" readonly="readonly" />' +
' <a id="imgur-url-link" class="button social-link" href="https://imgur.com/" target="_blank">open</a>' +
' <a id="twitter-link" class="button social-link" href="https://twitter.com/" target="_blank" title="post your image on twitter">twitter</a>' +
' <a id="facebook-link" class="button social-link" href="https://www.facebook.com/" target="_blank" title="post your image on facebook">facebook</a>' +
' <a id="reddit-link" class="button social-link" href="https://www.reddit.com/" target="_blank" title="post your image on reddit">reddit</a>' +
' <span id="imgur-url-error">sorry, something went wrong. maybe try again?</span>' +
' </div>' +
' <button class="intro-button button is-active">?</button>' +
' </div>' +
'</div>' +
'<div class="nav-wrapper light-bg">' +
' <div class="content center" id="controls">' +
' <div class="control-wrapper">' +
' <label class="control-label" for="blur-number">blur</label>' +
' <input class="control-input control-number" id="blur-number" type="number" min="0" max="99" value="50" maxlength="2" />' +
' <input class="control-input control-slider" id="blur-slider" type="range" min="0" max="99" value="50" step="1" maxlength="2" />' +
' </div>' +
' <div class="control-wrapper">' +
' <label class="control-label" for="accuracy-number">accuracy</label>' +
' <input class="control-input control-number" id="accuracy-number" type="number" min="0" max="99" value="50" maxlength="2" />' +
' <input class="control-input control-slider" id="accuracy-slider" type="range" min="0" max="99" value="50" step="1" maxlength="2" />' +
' </div>' +
' <div class="control-wrapper">' +
' <label class="control-label" for="point-rate-number">point rate</label>' +
' <input class="control-input control-number" id="point-rate-number" type="number" min="0" max="99" value="50" maxlength="2" />' +
' <input class="control-input control-slider" id="point-rate-slider" type="range" min="0" max="99" value="50" step="1" maxlength="2" />' +
' </div>' +
' <div class="control-wrapper">' +
' <label class="control-label" for="point-count">point count</label>' +
' <input class="control-input control-number" id="point-count-number" type="number" min="0" max="99" value="50" maxlength="2" />' +
' <input class="control-input control-slider" id="point-count-slider" type="range" min="0" max="99" value="50" step="1" maxlength="2" />' +
' </div>' +
' <button id="random-button" class="button is-hidden">randomise</button>' +
' </div>' +
'</div>' +
'<div class="canvas-wrapper">' +
' <canvas id="canvas"></canvas>' +
' <article class="content intro is-active">' +
' <div class="center">' +
' <p>drop an image in the browser to triangulate it.</p>' +
' <p>this script uses the <a href="https://en.wikipedia.org/wiki/Delaunay_triangulation" title="Wikipedia article on Delaunay Triangulation">delaunay triangulation</a> algorithm. it is based on the <a href="http://jsdo.it/akm2/xoYx" title="Triangulation Image Generator on jsdo.it">triangulation image generator</a> and includes some speed improvements. this experiment was created by <a href="http://fishnation.de">georg</a>. you can follow him on <a href="https://twitter.com/snorpey">twitter</a> or explore the source code on <a href="https://github.com/snorpey/triangulation">github</a>.</p>' +
' <p>if you like this one, you can check out some of his other javascript experiments on <a href="http://snorpey.github.io/experiments/">github</a>.</p>' +
' <button class="close">✕</button>' +
' </div>' +
' </article>' +
'</div>' +
' </body>' +
'</html>');
var window = document.defaultView;
(function () {/**
* almond 0.2.6 Copyright (c) 2011-2012, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license.
* see: http://github.com/jrburke/almond for details
*/
//Going sloppy to avoid 'use strict' string cost, but strict practices should
//be followed.
/*jslint sloppy: true */
/*global setTimeout: false */
var requirejs, require, define;
(function (undef) {
var main, req, makeMap, handlers,
defined = {},
waiting = {},
config = {},
defining = {},
hasOwn = Object.prototype.hasOwnProperty,
aps = [].slice;
function hasProp(obj, prop) {
return hasOwn.call(obj, prop);
}
/**
* Given a relative module name, like ./something, normalize it to
* a real name that can be mapped to a path.
* @param {String} name the relative name
* @param {String} baseName a real name that the name arg is relative
* to.
* @returns {String} normalized name
*/
function normalize(name, baseName) {
var nameParts, nameSegment, mapValue, foundMap,
foundI, foundStarMap, starI, i, j, part,
baseParts = baseName && baseName.split("/"),
map = config.map,
starMap = (map && map['*']) || {};
//Adjust any relative paths.
if (name && name.charAt(0) === ".") {
//If have a base name, try to normalize against it,
//otherwise, assume it is a top-level require that will
//be relative to baseUrl in the end.
if (baseName) {
//Convert baseName to array, and lop off the last part,
//so that . matches that "directory" and not name of the baseName's
//module. For instance, baseName of "one/two/three", maps to
//"one/two/three.js", but we want the directory, "one/two" for
//this normalization.
baseParts = baseParts.slice(0, baseParts.length - 1);
name = baseParts.concat(name.split("/"));
//start trimDots
for (i = 0; i < name.length; i += 1) {
part = name[i];
if (part === ".") {
name.splice(i, 1);
i -= 1;
} else if (part === "..") {
if (i === 1 && (name[2] === '..' || name[0] === '..')) {
//End of the line. Keep at least one non-dot
//path segment at the front so it can be mapped
//correctly to disk. Otherwise, there is likely
//no path mapping for a path starting with '..'.
//This can still fail, but catches the most reasonable
//uses of ..
break;
} else if (i > 0) {
name.splice(i - 1, 2);
i -= 2;
}
}
}
//end trimDots
name = name.join("/");
} else if (name.indexOf('./') === 0) {
// No baseName, so this is ID is resolved relative
// to baseUrl, pull off the leading dot.
name = name.substring(2);
}
}
//Apply map config if available.
if ((baseParts || starMap) && map) {
nameParts = name.split('/');
for (i = nameParts.length; i > 0; i -= 1) {
nameSegment = nameParts.slice(0, i).join("/");
if (baseParts) {
//Find the longest baseName segment match in the config.
//So, do joins on the biggest to smallest lengths of baseParts.
for (j = baseParts.length; j > 0; j -= 1) {
mapValue = map[baseParts.slice(0, j).join('/')];
//baseName segment has config, find if it has one for
//this name.
if (mapValue) {
mapValue = mapValue[nameSegment];
if (mapValue) {
//Match, update name to the new value.
foundMap = mapValue;
foundI = i;
break;
}
}
}
}
if (foundMap) {
break;
}
//Check for a star map match, but just hold on to it,
//if there is a shorter segment match later in a matching
//config, then favor over this star map.
if (!foundStarMap && starMap && starMap[nameSegment]) {
foundStarMap = starMap[nameSegment];
starI = i;
}
}
if (!foundMap && foundStarMap) {
foundMap = foundStarMap;
foundI = starI;
}
if (foundMap) {
nameParts.splice(0, foundI, foundMap);
name = nameParts.join('/');
}
}
return name;
}
function makeRequire(relName, forceSync) {
return function () {
//A version of a require function that passes a moduleName
//value for items that may need to
//look up paths relative to the moduleName
return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync]));
};
}
function makeNormalize(relName) {
return function (name) {
return normalize(name, relName);
};
}
function makeLoad(depName) {
return function (value) {
defined[depName] = value;
};
}
function callDep(name) {
if (hasProp(waiting, name)) {
var args = waiting[name];
delete waiting[name];
defining[name] = true;
main.apply(undef, args);
}
if (!hasProp(defined, name) && !hasProp(defining, name)) {
throw new Error('No ' + name);
}
return defined[name];
}
//Turns a plugin!resource to [plugin, resource]
//with the plugin being undefined if the name
//did not have a plugin prefix.
function splitPrefix(name) {
var prefix,
index = name ? name.indexOf('!') : -1;
if (index > -1) {
prefix = name.substring(0, index);
name = name.substring(index + 1, name.length);
}
return [prefix, name];
}
/**
* Makes a name map, normalizing the name, and using a plugin
* for normalization if necessary. Grabs a ref to plugin
* too, as an optimization.
*/
makeMap = function (name, relName) {
var plugin,
parts = splitPrefix(name),
prefix = parts[0];
name = parts[1];
if (prefix) {
prefix = normalize(prefix, relName);
plugin = callDep(prefix);
}
//Normalize according
if (prefix) {
if (plugin && plugin.normalize) {
name = plugin.normalize(name, makeNormalize(relName));
} else {
name = normalize(name, relName);
}
} else {
name = normalize(name, relName);
parts = splitPrefix(name);
prefix = parts[0];
name = parts[1];
if (prefix) {
plugin = callDep(prefix);
}
}
//Using ridiculous property names for space reasons
return {
f: prefix ? prefix + '!' + name : name, //fullName
n: name,
pr: prefix,
p: plugin
};
};
function makeConfig(name) {
return function () {
return (config && config.config && config.config[name]) || {};
};
}
handlers = {
require: function (name) {
return makeRequire(name);
},
exports: function (name) {
var e = defined[name];
if (typeof e !== 'undefined') {
return e;
} else {
return (defined[name] = {});
}
},
module: function (name) {
return {
id: name,
uri: '',
exports: defined[name],
config: makeConfig(name)
};
}
};
main = function (name, deps, callback, relName) {
var cjsModule, depName, ret, map, i,
args = [],
usingExports;
//Use name if no relName
relName = relName || name;
//Call the callback to define the module, if necessary.
if (typeof callback === 'function') {
//Pull out the defined dependencies and pass the ordered
//values to the callback.
//Default to [require, exports, module] if no deps
deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
for (i = 0; i < deps.length; i += 1) {
map = makeMap(deps[i], relName);
depName = map.f;
//Fast path CommonJS standard dependencies.
if (depName === "require") {
args[i] = handlers.require(name);
} else if (depName === "exports") {
//CommonJS module spec 1.1
args[i] = handlers.exports(name);
usingExports = true;
} else if (depName === "module") {
//CommonJS module spec 1.1
cjsModule = args[i] = handlers.module(name);
} else if (hasProp(defined, depName) ||
hasProp(waiting, depName) ||
hasProp(defining, depName)) {
args[i] = callDep(depName);
} else if (map.p) {
map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
args[i] = defined[depName];
} else {
throw new Error(name + ' missing ' + depName);
}
}
ret = callback.apply(defined[name], args);
if (name) {
//If setting exports via "module" is in play,
//favor that over return value and exports. After that,
//favor a non-undefined return value over exports use.
if (cjsModule && cjsModule.exports !== undef &&
cjsModule.exports !== defined[name]) {
defined[name] = cjsModule.exports;
} else if (ret !== undef || !usingExports) {
//Use the return value from the function.
defined[name] = ret;
}
}
} else if (name) {
//May just be an object definition for the module. Only
//worry about defining if have a module name.
defined[name] = callback;
}
};
requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
if (typeof deps === "string") {
if (handlers[deps]) {
//callback in this case is really relName
return handlers[deps](callback);
}
//Just return the module wanted. In this scenario, the
//deps arg is the module name, and second arg (if passed)
//is just the relName.
//Normalize module name, if it contains . or ..
return callDep(makeMap(deps, callback).f);
} else if (!deps.splice) {
//deps is a config object, not an array.
config = deps;
if (callback.splice) {
//callback is an array, which means it is a dependency list.
//Adjust args if there are dependencies
deps = callback;
callback = relName;
relName = null;
} else {
deps = undef;
}
}
//Support require(['a'])
callback = callback || function () {};
//If relName is a function, it is an errback handler,
//so remove it.
if (typeof relName === 'function') {
relName = forceSync;
forceSync = alt;
}
//Simulate async callback;
if (forceSync) {
main(undef, deps, callback, relName);
} else {
//Using a non-zero value because of concern for what old browsers
//do, and latest browsers "upgrade" to 4 if lower value is used:
//http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
//If want a value immediately, use require('id') instead -- something
//that works in almond on the global level, but not guaranteed and
//unlikely to work in other AMD implementations.
setTimeout(function () {
main(undef, deps, callback, relName);
}, 4);
}
return req;
};
/**
* Just drops the config on the floor, but returns req in case
* the config return value is used.
*/
req.config = function (cfg) {
config = cfg;
if (config.deps) {
req(config.deps, config.callback);
}
return req;
};
/**
* Expose module registry for debugging and tooling
*/
requirejs._defined = defined;
define = function (name, deps, callback) {
//This module may not have dependencies
if (!deps.splice) {
//deps is not an array, so probably means
//an object literal or factory function for
//the value. Adjust args.
callback = deps;
deps = [];
}
if (!hasProp(defined, name) && !hasProp(waiting, name)) {
waiting[name] = [name, deps, callback];
}
};
define.amd = {
jQuery: true
};
}());
define("lib/almond-0.2.6", function(){});
/*global define*/
/*
Superfast Blur - a fast Box Blur For Canvas
Version: 0.5
Author: Mario Klingemann
Contact: mario@quasimondo.com
Website: http://www.quasimondo.com/BoxBlurForCanvas
Twitter: @quasimondo
In case you find this class useful - especially in commercial projects -
I am not totally unhappy for a small donation to my PayPal account
mario@quasimondo.de
Or support me on flattr:
https://flattr.com/thing/140066/Superfast-Blur-a-pretty-fast-Box-Blur-Effect-for-CanvasJavascript
Copyright (c) 2011 Mario Klingemann
Note by Georg Fischer (snorpey@gmail.com / @snorpey):
While much of the original algorithm is still the same,
I modified some parts of the script to fit my needs:
- removed the iterations argument
- modified the functions to accept an imageData object
instead of element ids to remove dependency on the
document object.
- added AMD / requirejs wrapper
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.
*/
define(
'lib/superfast-blur.0.5',[],function()
{
var mul_table = [
1,57,41,21,203,34,97,73,227,91,149,62,105,45,39,137,241,107,3,173,39,71,65,238,219,101,
187,87,81,151,141,133,249,117,221,209,197,187,177,169,5,153,73,139,133,127,243,233,223,
107,103,99,191,23,177,171,165,159,77,149,9,139,135,131,253,245,119,231,224,109,211,103,
25,195,189,23,45,175,171,83,81,79,155,151,147,9,141,137,67,131,129,251,123,30,235,115,
113,221,217,53,13,51,50,49,193,189,185,91,179,175,43,169,83,163,5,79,155,19,75,147,145,
143,35,69,17,67,33,65,255,251,247,243,239,59,29,229,113,111,219,27,213,105,207,51,201,
199,49,193,191,47,93,183,181,179,11,87,43,85,167,165,163,161,159,157,155,77,19,75,37,
73,145,143,141,35,138,137,135,67,33,131,129,255,63,250,247,61,121,239,237,117,29,229,
227,225,111,55,109,216,213,211,209,207,205,203,201,199,197,195,193,48,190,47,93,185,
183,181,179,178,176,175,173,171,85,21,167,165,41,163,161,5,79,157,78,154,153,19,75,
149,74,147,73,144,143,71,141,140,139,137,17,135,134,133,66,131,65,129,1
];
var shg_table = [
0,9,10,10,14,12,14,14,16,15,16,15,16,15,15,17,18,17,12,18,16,17,17,19,19,18,19,18,18,
19,19,19,20,19,20,20,20,20,20,20,15,20,19,20,20,20,21,21,21,20,20,20,21,18,21,21,21,
21,20,21,17,21,21,21,22,22,21,22,22,21,22,21,19,22,22,19,20,22,22,21,21,21,22,22,22,
18,22,22,21,22,22,23,22,20,23,22,22,23,23,21,19,21,21,21,23,23,23,22,23,23,21,23,22,
23,18,22,23,20,22,23,23,23,21,22,20,22,21,22,24,24,24,24,24,22,21,24,23,23,24,21,24,
23,24,22,24,24,22,24,24,22,23,24,24,24,20,23,22,23,24,24,24,24,24,24,24,23,21,23,22,
23,24,24,24,22,24,24,24,23,22,24,24,25,23,25,25,23,24,25,25,24,22,25,25,25,24,23,24,
25,25,25,25,25,25,25,25,25,25,25,25,23,25,23,24,25,25,25,25,25,25,25,25,25,24,22,25,
25,23,25,25,20,24,25,24,25,25,22,24,25,24,25,24,25,25,24,25,25,25,25,22,25,25,25,24,
25,24,25,18
];
function boxBlurCanvas( image_data, radius, blur_alpha_channel )
{
var result = image_data;
if ( ! ( isNaN( radius ) || radius < 1 ) )
{
if ( blur_alpha_channel )
{
result = boxBlurCanvasRGBA( image_data, radius );
}
else
{
result = boxBlurCanvasRGB( image_data, radius );
}
}
return result;
}
function boxBlurCanvasRGBA( image_data, radius )
{
radius |= 0;
var pixels = image_data.data;
var width = image_data.width;
var height = image_data.height;
var rsum, gsum, bsum, asum, x, y, i, p, p1, p2, yp, yi, yw, idx, pa;
var wm = width - 1;
var hm = height - 1;
var wh = width * height;
var rad1 = radius + 1;
var mul_sum = mul_table[radius];
var shg_sum = shg_table[radius];
var r = [ ];
var g = [ ];
var b = [ ];
var a = [ ];
var vmin = [ ];
var vmax = [ ];
yw = yi = 0;
for ( y = 0; y < height; y++ )
{
rsum = pixels[yw] * rad1;
gsum = pixels[yw + 1] * rad1;
bsum = pixels[yw + 2] * rad1;
asum = pixels[yw + 3] * rad1;
for ( i = 1; i <= radius; i++ )
{
p = yw + ( ( ( i > wm ? wm : i ) ) << 2 );
rsum += pixels[p++];
gsum += pixels[p++];
bsum += pixels[p++];
asum += pixels[p];
}
for ( x = 0; x < width; x++ )
{
r[yi] = rsum;
g[yi] = gsum;
b[yi] = bsum;
a[yi] = asum;
if ( y === 0 )
{
vmin[x] = ( ( p = x + rad1) < wm ? p : wm ) << 2;
vmax[x] = ( ( p = x - radius) > 0 ? p << 2 : 0 );
}
p1 = yw + vmin[x];
p2 = yw + vmax[x];
rsum += pixels[p1++] - pixels[p2++];
gsum += pixels[p1++] - pixels[p2++];
bsum += pixels[p1++] - pixels[p2++];
asum += pixels[p1] - pixels[p2];
yi++;
}
yw += ( width << 2 );
}
for ( x = 0; x < width; x++ )
{
yp = x;
rsum = r[yp] * rad1;
gsum = g[yp] * rad1;
bsum = b[yp] * rad1;
asum = a[yp] * rad1;
for ( i = 1; i <= radius; i++ )
{
yp += ( i > hm ? 0 : width );
rsum += r[yp];
gsum += g[yp];
bsum += b[yp];
asum += a[yp];
}
yi = x << 2;
for ( y = 0; y < height; y++ )
{
pixels[yi + 3] = pa = ( asum * mul_sum ) >>> shg_sum;
if ( pa > 0 )
{
pa = 255 / pa;
pixels[yi] = ( ( rsum * mul_sum ) >>> shg_sum ) * pa;
pixels[yi+1] = ( ( gsum * mul_sum ) >>> shg_sum ) * pa;
pixels[yi+2] = ( ( bsum * mul_sum ) >>> shg_sum ) * pa;
}
else
{
pixels[yi] = pixels[yi + 1] = pixels[yi + 2] = 0;
}
if ( x === 0 )
{
vmin[y] = ( ( p = y + rad1) < hm ? p : hm ) * width;
vmax[y] = ( ( p = y - radius) > 0 ? p * width : 0 );
}
p1 = x + vmin[y];
p2 = x + vmax[y];
rsum += r[p1] - r[p2];
gsum += g[p1] - g[p2];
bsum += b[p1] - b[p2];
asum += a[p1] - a[p2];
yi += width << 2;
}
}
return image_data;
}
function boxBlurCanvasRGB( image_data, radius )
{
radius |= 0;
var pixels = image_data.data;
var width = image_data.width;
var height = image_data.height;
var rsum, gsum, bsum, asum, x, y, i, p, p1, p2, yp, yi, yw, idx;
var wm = width - 1;
var hm = height - 1;
var wh = width * height;
var rad1 = radius + 1;
var r = [ ];
var g = [ ];
var b = [ ];
var mul_sum = mul_table[radius];
var shg_sum = shg_table[radius];
var vmin = [ ];
var vmax = [ ];
yw = yi = 0;
for ( y = 0; y < height; y++ )
{
rsum = pixels[yw] * rad1;
gsum = pixels[yw + 1] * rad1;
bsum = pixels[yw + 2] * rad1;
for ( i = 1; i <= radius; i++ )
{
p = yw + ( ( ( i > wm ? wm : i ) ) << 2 );
rsum += pixels[p++];
gsum += pixels[p++];
bsum += pixels[p++];
}
for ( x = 0; x < width; x++ )
{
r[yi] = rsum;
g[yi] = gsum;
b[yi] = bsum;
if ( y === 0 )
{
vmin[x] = ( ( p = x + rad1) < wm ? p : wm ) << 2;
vmax[x] = ( ( p = x - radius) > 0 ? p << 2 : 0 );
}
p1 = yw + vmin[x];
p2 = yw + vmax[x];
rsum += pixels[p1++] - pixels[p2++];
gsum += pixels[p1++] - pixels[p2++];
bsum += pixels[p1++] - pixels[p2++];
yi++;
}
yw += ( width << 2 );
}
for ( x = 0; x < width; x++ )
{
yp = x;
rsum = r[yp] * rad1;
gsum = g[yp] * rad1;
bsum = b[yp] * rad1;
for ( i = 1; i <= radius; i++ )
{
yp += ( i > hm ? 0 : width );
rsum += r[yp];
gsum += g[yp];
bsum += b[yp];
}
yi = x << 2;
for ( y = 0; y < height; y++ )
{
pixels[yi] = (rsum * mul_sum) >>> shg_sum;
pixels[yi+1] = (gsum * mul_sum) >>> shg_sum;
pixels[yi+2] = (bsum * mul_sum) >>> shg_sum;
if ( x === 0 )
{
vmin[y] = ( ( p = y + rad1) < hm ? p : hm ) * width;
vmax[y] = ( ( p = y - radius) > 0 ? p * width : 0 );
}
p1 = x + vmin[y];
p2 = x + vmax[y];
rsum += r[p1] - r[p2];
gsum += g[p1] - g[p2];
bsum += b[p1] - b[p2];
yi += width << 2;
}
}
return image_data;
}
return boxBlurCanvas;
}
);
/*global define*/
define(
'lib/delaunay',[],function()
{
// https://github.com/ironwallaby/delaunay/blob/master/delaunay.js
function Triangle(a, b, c) {
this.a = a
this.b = b
this.c = c
var A = b.x - a.x,
B = b.y - a.y,
C = c.x - a.x,
D = c.y - a.y,
E = A * (a.x + b.x) + B * (a.y + b.y),
F = C * (a.x + c.x) + D * (a.y + c.y),
G = 2 * (A * (c.y - b.y) - B * (c.x - b.x)),
minx, miny, dx, dy
/* If the points of the triangle are collinear, then just find the
* extremes and use the midpoint as the center of the circumcircle. */
if(Math.abs(G) < 0.000001) {
minx = Math.min(a.x, b.x, c.x)
miny = Math.min(a.y, b.y, c.y)
dx = (Math.max(a.x, b.x, c.x) - minx) * 0.5
dy = (Math.max(a.y, b.y, c.y) - miny) * 0.5
this.x = minx + dx
this.y = miny + dy
this.r = dx * dx + dy * dy
}
else {
this.x = (D*E - B*F) / G
this.y = (A*F - C*E) / G
dx = this.x - a.x
dy = this.y - a.y
this.r = dx * dx + dy * dy
}
}
Triangle.prototype.draw = function(ctx) {
ctx.beginPath()
ctx.moveTo(this.a.x, this.a.y)
ctx.lineTo(this.b.x, this.b.y)
ctx.lineTo(this.c.x, this.c.y)
ctx.closePath()
ctx.stroke()
}
function byX(a, b) {
return b.x - a.x
}
function dedup(edges) {
var j = edges.length,
a, b, i, m, n
outer: while(j) {
b = edges[--j]
a = edges[--j]
i = j
while(i) {
n = edges[--i]
m = edges[--i]
if((a === m && b === n) || (a === n && b === m)) {
edges.splice(j, 2)
edges.splice(i, 2)
j -= 2
continue outer
}
}
}
}
function triangulate(vertices) {
/* Bail if there aren't enough vertices to form any triangles. */
if(vertices.length < 3)
return []
/* Ensure the vertex array is in order of descending X coordinate
* (which is needed to ensure a subquadratic runtime), and then find
* the bounding box around the points. */
vertices.sort(byX)
var i = vertices.length - 1,
xmin = vertices[i].x,
xmax = vertices[0].x,
ymin = vertices[i].y,
ymax = ymin
while(i--) {
if(vertices[i].y < ymin) ymin = vertices[i].y
if(vertices[i].y > ymax) ymax = vertices[i].y
}
/* Find a supertriangle, which is a triangle that surrounds all the
* vertices. This is used like something of a sentinel value to remove
* cases in the main algorithm, and is removed before we return any
* results.
*
* Once found, put it in the "open" list. (The "open" list is for
* triangles who may still need to be considered; the "closed" list is
* for triangles which do not.) */
var dx = xmax - xmin,
dy = ymax - ymin,
dmax = (dx > dy) ? dx : dy,
xmid = (xmax + xmin) * 0.5,
ymid = (ymax + ymin) * 0.5,
open = [
new Triangle(
{x: xmid - 20 * dmax, y: ymid - dmax, __sentinel: true},
{x: xmid , y: ymid + 20 * dmax, __sentinel: true},
{x: xmid + 20 * dmax, y: ymid - dmax, __sentinel: true}
)
],
closed = [],
edges = [],
j, a, b
/* Incrementally add each vertex to the mesh. */
i = vertices.length
while(i--) {
/* For each open triangle, check to see if the current point is
* inside it's circumcircle. If it is, remove the triangle and add
* it's edges to an edge list. */
edges.length = 0
j = open.length
while(j--) {
/* If this point is to the right of this triangle's circumcircle,
* then this triangle should never get checked again. Remove it
* from the open list, add it to the closed list, and skip. */
dx = vertices[i].x - open[j].x
if(dx > 0 && dx * dx > open[j].r) {
closed.push(open[j])
open.splice(j, 1)
continue
}
/* If not, skip this triangle. */
dy = vertices[i].y - open[j].y
if(dx * dx + dy * dy > open[j].r)
continue
/* Remove the triangle and add it's edges to the edge list. */
edges.push(
open[j].a, open[j].b,
open[j].b, open[j].c,
open[j].c, open[j].a
)
open.splice(j, 1)
}
/* Remove any doubled edges. */
dedup(edges)
/* Add a new triangle for each edge. */
j = edges.length
while(j) {
b = edges[--j]
a = edges[--j]
open.push(new Triangle(a, b, vertices[i]))
}
}
/* Copy any remaining open triangles to the closed list, and then
* remove any triangles that share a vertex with the supertriangle. */
Array.prototype.push.apply(closed, open)
i = closed.length
while(i--)
if(closed[i].a.__sentinel ||
closed[i].b.__sentinel ||
closed[i].c.__sentinel)
closed.splice(i, 1)
/* Yay, we're done! */
return closed
}
if (typeof module !== 'undefined') {
module.exports = {
Triangle: Triangle,
triangulate: triangulate
}
}
return triangulate;
}
);
// most parts taken from http://jsdo.it/akm2/xoYx
// (starting line 366++)
/*global define*/
define(
'util/detect-edges',[],function()
{
/**
* @see http://jsdo.it/akm2/iMsL
*/
function detectEdges( image_data, accuracy, edge_size, divisor )
{
var matrix = getEdgeMatrix( edge_size ).slice();
var multiplier = parseInt( ( accuracy || 0.5 ) * 10, 10 ) || 1;
divisor = divisor || 1;
var divscalar = divisor ? 1 / divisor : 0;
var k, len;
if ( divscalar !== 1 )
{
for ( k = 0, len = matrix.length; k < matrix.length; k++ )
{
matrix[k] *= divscalar;
}
}
var data = image_data.data;
len = data.length >> 2;
var copy = new Uint8Array( len );
for (i = 0; i < len; i++)
{
copy[i] = data[i << 2];
}
var width = image_data.width | 0;
var height = image_data.height | 0;
var size = Math.sqrt( matrix.length );
var range = size * 0.5 | 0;
var x, y;
var r, g, b, v;
var col, row, sx, sy;
var i, istep, jstep, kstep;
for ( y = 0; y < height; y += multiplier )
{
istep = y * width;
for ( x = 0; x < width; x += multiplier )
{
r = g = b = 0;
for ( row = -range; row <= range; row++ )
{
sy = y + row;
jstep = sy * width;
kstep = (row + range) * size;
if ( sy >= 0 && sy < height )
{
for ( col = -range; col <= range; col++ )
{
sx = x + col;
if (
sx >= 0 && sx < width &&
( v = matrix[( col + range ) + kstep] )
)
{
r += copy[sx + jstep] * v;
}
}
}
}
if ( r < 0 )
{
r = 0;
}
else
{
if ( r > 255 )
{
r = 255;
}
}
data[( x + istep ) << 2] = r & 0xFF;
}
}
return image_data;
}
function getEdgeMatrix( size )
{
var matrix = [ ];
var side = size * 2 + 1;
var i, len = side * side;
var center = len * 0.5 | 0;
for ( i = 0; i < len; i++ )
{
matrix[i] = i === center ? -len + 1 : 1;
}
return matrix;
}
return detectEdges;
}
);
// most parts taken from http://jsdo.it/akm2/xoYx
// (starting line 293++)
/*global define*/
define(
'util/get-edge-points',[],function()
{
function getEdgePoints( image_data, sensitivity, accuracy )
{
var multiplier = parseInt( ( accuracy || 0.1 ) * 10, 10 ) || 1;
var edge_detect_value = sensitivity;
var width = image_data.width;
var height = image_data.height;
var data = image_data.data;
var points = [ ];
var x, y, row, col, sx, sy, step, sum, total;
for ( y = 0; y < height; y += multiplier )
{
for ( x = 0; x < width; x += multiplier )
{
sum = total = 0;
for ( row = -1; row <= 1; row++ )
{
sy = y + row;
step = sy * width;
if ( sy >= 0 && sy < height )
{
for ( col = -1; col <= 1; col++ )
{
sx = x + col;
if ( sx >= 0 && sx < width )
{
sum += data[( sx + step ) << 2];
total++;
}
}
}
}
if ( total )
{
sum /= total;
}
if ( sum > edge_detect_value )
{
points.push( { x: x, y: y } );
}
}
}
return points;
}
return getEdgePoints;
}
);
// most parts taken from akm2's script:
// http://jsdo.it/akm2/xoYx (line 230++)
/*global define*/
define(
'util/get-random-vertices',[],function()
{
function getRandomVertices( points, rate, max_num, accuracy, width, height )
{
var j;
var result = [ ];
var i = 0;
var i_len = points.length;
var t_len = i_len;
var limit = Math.round( i_len * rate );
points = points.slice();
if ( limit > max_num )
{
limit = max_num;
}
while ( i < limit && i < i_len )
{
j = t_len * Math.random() | 0;
result.push( { x: points[j].x, y: points[j].y } );
// this seems to be extremely time
// intensive.
// points.splice( j, 1 );
t_len--;
i++;
}
var x, y;
// gf: add more points along the edges so we always use the full canvas,
for ( x = 0; x < width; x += (100 - accuracy) )
{
result.push( { x: ~~x, y: 0 } );
result.push( { x: ~~x, y: height } );
}
for ( y = 0; y < height; y += (100 - accuracy) )
{
result.push( { x: 0, y: ~~y } );
result.push( { x: width, y: ~~y } );
}
result.push( { x: 0, y: height } );
result.push( { x: width, y: height } );
return result;
}
return getRandomVertices;
}
);
/*global define*/
define(
'util/greyscale',[],function()
{
function greyscale( image_data )
{
var len = image_data.data.length;
var data = image_data.data;
for ( var i = 0; i < len; i += 4 )
{
var brightness = 0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2];
data[i] = brightness;
data[i + 1] = brightness;
data[i + 2] = brightness;
}
image_data.data = data;
return image_data;
}
return greyscale;
}
);
/*global define*/
define(
'src/process',[
'lib/superfast-blur.0.5',
'lib/delaunay',
'util/detect-edges',
'util/get-edge-points',
'util/get-random-vertices',
'util/greyscale'
],
function( blur, triangulate, detectEdges, getEdgePoints, getRandomVertices, greyscale )
{
var tmp_canvas = document.createElement( 'canvas' );
var tmp_ctx = tmp_canvas.getContext( '2d' );
var canvas = document.getElementById( 'canvas' );
var ctx = canvas.getContext( '2d' );
var is_processing = false;
var values;
var image;
var signals;
var triangles;
var triangle;
var image_data;
var color_data;
var blurred_image_data;
var greyscale_data;
var edge_image_data;
var edge_points;
var edge_vertices;
var polygons;
var len;
var i;
var triangle_center_x, triangle_center_y, pixel;
var pxratio = ( window.devicePixelRatio && window.devicePixelRatio > 1 ) ? window.devicePixelRatio : 1;
function init( shared )
{
signals = shared.signals;
signals['image-loaded'].add( generate );
signals['control-updated'].add( controlsUpdated );
signals['export-requested'].add( exportData );
}
function controlsUpdated( new_values )
{
values = getAdjustedValues( new_values );
update();
}
function generate( img )
{
if ( ! is_processing )
{
image = img;
processImage( image );
}
}
function update()
{
if ( ! is_processing && image )
{
processImage( image );
}
}
function processImage( img )
{
is_processing = true;
clearCanvas( tmp_canvas, tmp_ctx );
clearCanvas( canvas, ctx );
resizeCanvas( tmp_canvas, img, pxratio );
resizeCanvas( canvas, img, pxratio );
tmp_ctx.drawImage( img, 0, 0 );
// get the image data
image_data = tmp_ctx.getImageData( 0, 0, tmp_canvas.width, tmp_canvas.height );
// since the image data is blurred and greyscaled later on,
// we need another copy of the image data with preserved colors
color_data = tmp_ctx.getImageData( 0, 0, tmp_canvas.width, tmp_canvas.height );
// blur the imagedata using superfast blur by @quasimondo
// not very accurate, but fast
blurred_image_data = blur( image_data, values.blur, false );
greyscale_data = greyscale( image_data );
edge_image_data = detectEdges( greyscale_data, values.accuracy, 5 );
// gets some of the edge points to construct triangles
edge_points = getEdgePoints( edge_image_data, 50, values.accuracy );
edge_vertices = getRandomVertices( edge_points, values['point-rate'], values['point-count'], values.accuracy, tmp_canvas.width, tmp_canvas.height );
// makes triangles out of points
polygons = triangulate( edge_vertices );
// get the color for every triangle
triangles = getColorfulTriangles( polygons, color_data );
drawTriangles( ctx, triangles );
is_processing = false;
}
function drawTriangles( ctx, triangles )
{
len = triangles.length;
for ( i = 0; i < len; i++ )
{
triangle = triangles[i];
ctx.beginPath();
ctx.moveTo( triangle.a.x * pxratio, triangle.a.y * pxratio );
ctx.lineTo( triangle.b.x * pxratio, triangle.b.y * pxratio );
ctx.lineTo( triangle.c.x * pxratio, triangle.c.y * pxratio );
ctx.lineTo( triangle.a.x * pxratio, triangle.a.y * pxratio );
ctx.fillStyle = triangle.color;
ctx.fill();
ctx.closePath();
}
}
function resizeCanvas( canvas, img, ratio )
{
canvas.width = img.width * ratio;
canvas.height = img.height * ratio;
canvas.style.width = ( canvas.width / ratio ) + 'px';
canvas.style.height = ( canvas.height / ratio ) + 'px';
}
function clearCanvas( canvas, ctx )
{
ctx.clearRect( ctx, 0, 0, canvas.width, canvas.height );
}
function exportData( callback )
{
if ( typeof callback === 'function' )
{
var export_data = {
png: canvas.toDataURL( 'image/png' ),
svg: {
triangles: triangles,
size : { width: canvas.width / pxratio, height: canvas.height / pxratio }
}
};
callback( export_data );
}
}
function getColorfulTriangles( triangles, color_data )
{
len = triangles.length;
for ( i = 0; i < len; i++ )
{
triangle = triangles[i];
// triangle color = color at center of triangle
triangle_center_x = ( triangle.a.x + triangle.b.x + triangle.c.x ) * 0.33333;
triangle_center_y = ( triangle.a.y + triangle.b.y + triangle.c.y ) * 0.33333;
pixel = ( ( triangle_center_x | 0 ) + ( triangle_center_y | 0 ) * color_data.width ) << 2;
triangle.color = 'rgb(' + color_data.data[pixel] + ', ' + color_data.data[pixel + 1] + ', ' + color_data.data[pixel + 2] + ')';
}
return triangles;
}
function getAdjustedValues( new_values )
{
var result = { };
for ( var key in new_values )
{
switch ( key )
{
case 'blur' :
result[key] = parseInt( scaleRange( new_values[key], 0, 100, 0, 50 ), 10 );
break;
case 'accuracy' :
result[key] = scaleRange( new_values[key], 0, 100, 1, 0.1 );
break;
case 'point-rate' :
result[key] = scaleRange( new_values[key], 0, 100, 0.001, 0.1 );
break;
case 'point-count' :
result[key] = parseInt( scaleRange( new_values[key], 0, 100, 100, 5000 ), 10 );
break;
}
}
return result;
}
function scaleRange( value, low_1, high_1, low_2, high_2 )
{
return low_2 + ( high_2 - low_2) * ( value - low_1 ) / (high_1 - low_1 );
}
return { init: init };
}
);
/*global define*/
define(
'src/image',[],function()
{
var signals;
var image;
var initialized = false;
var defaultimage = document.body.getAttribute( 'data-defaultimage' );
function init( shared )
{
signals = shared.signals;
image = new Image();
signals['set-new-src'].add( setSrc );
image.addEventListener( 'load', imageLoaded, false );
// the image "Abraham Lincoln November 1863" is public domain:
// https://en.wikipedia.org/wiki/File:Abraham_Lincoln_November_1863.jpg
setSrc( defaultimage );
}
function imageLoaded()
{
signals['image-loaded'].dispatch( image );
if ( initialized ) {
signals['close-intro'].dispatch();
}
initialized = true;
}
function setSrc( src )
{
image.src = src;
if (
initialized &&
image.naturalWidth !== undefined &&
image.naturalWidth !== 0
)
{
setTimeout( imageLoaded, 10 );
}
}
return { init: init };
}
);
/*global define*/
define(
'src/file',[],function()
{
var signals;
var reader;
var feature;
var allowed_file_types = [ 'image/png', 'image/jpg', 'image/jpeg' ];
function init( shared )
{
signals = shared.signals;
feature = shared.feature;
if ( feature['file-api' ] )
{
signals['load-file'].add( loadFile );
reader = new FileReader();
reader.addEventListener( 'load', fileLoaded, false );
}
}
function loadFile( file )
{
if (
file &&
file.type &&
allowed_file_types.indexOf( file.type ) !== -1
)
{
reader.readAsDataURL( file );
}
}
function fileLoaded( event )
{
signals['set-new-src'].dispatch( event.target.result );
}
return { init: init };
}
);
/*global define*/
define(
'src/dragdrop',[],function()
{
var signals;
var feature;
function init( shared )
{
feature = shared.feature;
signals = shared.signals;
if ( feature['drag-drop' ] )
{
document.addEventListener( 'drop', dropped, false );
document.addEventListener( 'dragover', preventDefault, false );
document.addEventListener( 'dragleave', preventDefault, false );
}
}
function preventDefault( event )
{
event.preventDefault();
}
function dropped( event )
{
event.preventDefault();
if (
event.dataTransfer &&
event.dataTransfer.files &&
event.dataTransfer.files[0]
)
{
signals['load-file'].dispatch( event.dataTransfer.files[0] );
signals['close-intro'].dispatch();
}
}
return { init: init };
}
);
/*global define*/
define(
'src/controls',[],function()
{
var values = { };
var is_initialized = false;
var signals;
var controls;
var timeout_id;
var is_blocked = false;
function init( shared )
{
signals = shared.signals;
if ( shared.feature['query-selector-all'] )
{
var wrapper = document.getElementById( 'controls' );
controls = wrapper.querySelectorAll( '.control-input' );
wrapper.className += ' is-active';
for ( var i = 0; i < controls.length; i++ )
{
var control = controls[i];
control.addEventListener( 'change', controlUpdated, false );
updateValue( getInputKey( control.id ), control.value );
updateInput( getCorrespondingInput( control.id ), control.value );
}
is_initialized = true;
signals['control-set'].add( setControlValues );
signals['control-updated'].dispatch( values );
}
}
function controlUpdated( element )
{
clearTimeout( timeout_id );
timeout_id = setTimeout(
function()
{
if ( element.target )
{
element = element.target;
}
updateValue( getInputKey( element.id ), element.value );
updateInput( getCorrespondingInput( element.id ), element.value );
},
100
);
}
function setControlValues( new_values )
{
var control;
var updated_values = { };
for ( var id in new_values )
{
control = getCorrespondingInput( id );
control.value = new_values[id];
controlUpdated( control );
updated_values[ getInputKey( id ) ] = new_values[id];
}
values = updated_values;
signals['control-updated'].dispatch( values );
}
function updateValue( key, value )
{
values[key] = value;
if ( is_initialized )
{
signals['control-updated'].dispatch( values );
}
}
function updateInput( input, value )
{
if ( input.value !== value )
{
input.value = value;
}
}
function getCorrespondingInput( id )
{
var result;
var key = getInputKey( id );
var element_id;
for ( var i = 0, len = controls.length; i < len; i++ )
{
element_id = controls[i].id;
if (
element_id !== id &&
element_id.indexOf( key ) !== -1
)
{
result = controls[i];
break;
}
}
return result;
}
function getInputKey( id )
{
return id.replace( '-slider', '' ).replace( '-number', '' );
}
return { init: init };
}
);
/*global define*/
define(
'src/export-button',[],function()
{
var signals;
var export_button;
var png_link;
var svg_link;
function init( shared )
{
signals = shared.signals;
export_button = document.getElementById( 'export-button' );
png_link = document.getElementById( 'png-link' );
svg_link = document.getElementById( 'svg-link' );
export_button.addEventListener( 'click', exportButtonClicked, false );
png_link.addEventListener( 'click', hidePNGLink, false );
svg_link.addEventListener( 'click', hideSVGLink, false );
}
function exportButtonClicked( event )
{
event.preventDefault();
console.log("adsfasfd");
signals['export-requested'].dispatch( upldateLinkAddresses );
}
function upldateLinkAddresses( data )
{
png_link.href = data.png;
png_link.classList.add( 'is-active' );
var svg_string = getSVG( data.svg.triangles, data.svg.size );
var blob = new Blob( [ svg_string ], { type: 'image/svg+xml' } );
var svg_url = window.URL.createObjectURL( blob );
svg_link.href = svg_url;
svg_link.classList.add( 'is-active' );
}
function hidePNGLink()
{
png_link.classList.remove( 'is-active' );
}
function hideSVGLink()
{
svg_link.classList.remove( 'is-active' );
}
// http://stackoverflow.com/questions/6918597/convert-canvas-or-control-points-to-svg
// https://developer.mozilla.org/en-US/docs/SVG/Element/polygon
function getSVG( triangles, size )
{
var triangle_keys = [ 'a', 'b', 'c' ];
var svg = '';
svg += '<?xml version="1.0" standalone="yes"?>';
svg += '<svg ';
svg += 'width="' + size.width + 'px" ';
svg += 'height="' + size.height + 'px" ';
svg += 'xmlns="http://www.w3.org/2000/svg" version="1.1">';
for ( var i = 0; i < triangles.length; i++ )
{
var triangle = triangles[i];
var points = [ ];
for ( var j = 0; j < triangle_keys.length; j++ )
{
var key = triangle_keys[j];
points[j] = triangle[key].x + ',' + triangle[key].y;
}
svg += '<polygon ';
svg += 'points="' + points.join( ' ' ) + '" ';
svg += 'fill="' + triangle.color + '" ';
svg += '/>';
}
svg += '</svg>';
return svg;
}
return { init: init };
}
);
/*global define*/
define(
'util/trigger-event',[],function()
{
// http://stackoverflow.com/a/2381862/229189
function triggerEvent( node, event_name )
{
var doc;
if ( node.ownerDocument )
{
doc = node.ownerDocument;
}
else if ( node.nodeType === 9 )
{
doc = node;
}
else
{
throw new Error('Invalid node passed to fireEvent: ' + node.id);
}
if ( node.fireEvent )
{
// IE-style
var event = doc.createEventObject();
event.synthetic = true;
node.fireEvent( 'on' + event_name, event );
}
else if ( node.dispatchEvent )
{
var event_class = '';
switch ( event_name )
{
case 'click':
case 'mousedown':
case 'mouseup':
event_class = 'MouseEvents';
break;
case 'focus':
case 'change':
case 'blur':
case 'select':
event_class = 'HTMLEvents';
break;
default:
throw 'triggerEvent: Couldn’t find an event class for event ' + event_name + '.';
break;
}
var event = doc.createEvent( event_class );
var bubbles = event_name == 'change' ? false : true;
event.initEvent( event_name, bubbles, true );
event.synthetic = true;
node.dispatchEvent( event );
}
}
return triggerEvent;
}
);
/*global define*/
define(
'src/import-button',[ 'util/trigger-event' ],
function( triggerEvent )
{
var feature;
var signals;
var import_button;
var import_input;
var file_reader;
var image;
var file_loading = false;
function init( shared )
{
signals = shared.signals;
feature = shared.feature;
// http://www.html5rocks.com/en/tutorials/file/dndfiles/
if ( feature['file-api' ] )
{
file_reader = new FileReader();
import_button = document.getElementById( 'import-button' );
import_input = document.getElementById( 'import-input' );
import_button.addEventListener( 'click', buttonClicked, false );
import_input.addEventListener( 'change', fileSelected, false );
}
}
function buttonClicked( event )
{
event.preventDefault();
if ( ! file_loading )
{
triggerEvent( import_input, 'click' );
}
}
function fileSelected( event )
{
var files = event.target.files;
if (
event.target &&
event.target.files &&
event.target.files[0]
)
{
signals['load-file'].dispatch( event.target.files[0] );
signals['close-intro'].dispatch();
}
}
return { init: init };
}
);
/*global define*/
define(
'src/random-button',[],function()
{
var signals;
var controls;
var random_button;
var constraints = { };
function init( shared )
{
signals = shared.signals;
if ( shared.feature['query-selector-all'] )
{
controls = document.querySelectorAll( '.control-slider' );
constraints = getConstraints( controls );
random_button = document.getElementById( 'random-button' );
random_button.addEventListener( 'click', buttonClicked, false );
random_button.classList.remove( 'is-hidden' );
}
}
function buttonClicked( event )
{
event.preventDefault();
randomize();
}
function randomize()
{
var new_values = { };
var constraint;
for ( var id in constraints )
{
constraint = constraints[id];
new_values[id] = getRandomInt( constraint.min, constraint.max );
}
signals['control-set'].dispatch( new_values );
}
function getConstraints( controls )
{
var result = { };
var control;
for ( var i = 0, len = controls.length; i < len; i++ )
{
control = controls[i];
result[control.id] = { min: parseInt( control.min, 10 ), max: parseInt( control.max, 10 ) };
}
return result;
}
function getRandomInt( min, max )
{
return Math.floor( Math.random() * ( max - min + 1 ) ) + min;
}
return { init: init };
}
);
!function (name, context, definition) {
if (typeof module != 'undefined' && module.exports) module.exports = definition()
else if (typeof define == 'function' && define.amd) define('lib/reqwest',definition)
else context[name] = definition()
}('reqwest', this, function () {
var win = window
, doc = document
, twoHundo = /^20\d$/
, byTag = 'getElementsByTagName'
, readyState = 'readyState'
, contentType = 'Content-Type'
, requestedWith = 'X-Requested-With'
, head = doc[byTag]('head')[0]
, uniqid = 0
, callbackPrefix = 'reqwest_' + (+new Date())
, lastValue // data stored by the most recent JSONP callback
, xmlHttpRequest = 'XMLHttpRequest'
, xDomainRequest = 'XDomainRequest'
, noop = function () {}
, isArray = typeof Array.isArray == 'function'
? Array.isArray
: function (a) {
return a instanceof Array
}
, defaultHeaders = {
contentType: 'application/x-www-form-urlencoded'
, requestedWith: xmlHttpRequest
, accept: {
'*': 'text/javascript, text/html, application/xml, text/xml, */*'
, xml: 'application/xml, text/xml'
, html: 'text/html'
, text: 'text/plain'
, json: 'application/json, text/javascript'
, js: 'application/javascript, text/javascript'
}
}
, xhr = function(o) {
// is it x-domain
if (o.crossOrigin === true) {
var xhr = win[xmlHttpRequest] ? new XMLHttpRequest() : null
if (xhr && 'withCredentials' in xhr) {
return xhr
} else if (win[xDomainRequest]) {
return new XDomainRequest()
} else {
throw new Error('Browser does not support cross-origin requests')
}
} else if (win[xmlHttpRequest]) {
return new XMLHttpRequest()
} else {
return new ActiveXObject('Microsoft.XMLHTTP')
}
}
, globalSetupOptions = {
dataFilter: function (data) {
return data
}
}
function handleReadyState(r, success, error) {
return function () {
// use _aborted to mitigate against IE err c00c023f
// (can't read props on aborted request objects)
if (r._aborted) return error(r.request)
if (r.request && r.request[readyState] == 4) {
r.request.onreadystatechange = noop
if (twoHundo.test(r.request.status))
success(r.request)
else
error(r.request)
}
}
}
function setHeaders(http, o) {
var headers = o.headers || {}
, h
headers.Accept = headers.Accept
|| defaultHeaders.accept[o.type]
|| defaultHeaders.accept['*']
// breaks cross-origin requests with legacy browsers
if (!o.crossOrigin && !headers[requestedWith]) headers[requestedWith] = defaultHeaders.requestedWith
if (!headers[contentType]) headers[contentType] = o.contentType || defaultHeaders.contentType
for (h in headers)
headers.hasOwnProperty(h) && 'setRequestHeader' in http && http.setRequestHeader(h, headers[h])
}
function setCredentials(http, o) {
if (typeof o.withCredentials !== 'undefined' && typeof http.withCredentials !== 'undefined') {
http.withCredentials = !!o.withCredentials
}
}
function generalCallback(data) {
lastValue = data
}
function urlappend (url, s) {
return url + (/\?/.test(url) ? '&' : '?') + s
}
function handleJsonp(o, fn, err, url) {
var reqId = uniqid++
, cbkey = o.jsonpCallback || 'callback' // the 'callback' key
, cbval = o.jsonpCallbackName || reqwest.getcallbackPrefix(reqId)
// , cbval = o.jsonpCallbackName || ('reqwest_' + reqId) // the 'callback' value
, cbreg = new RegExp('((^|\\?|&)' + cbkey + ')=([^&]+)')
, match = url.match(cbreg)
, script = doc.createElement('script')
, loaded = 0
, isIE10 = navigator.userAgent.indexOf('MSIE 10.0') !== -1
if (match) {
if (match[3] === '?') {
url = url.replace(cbreg, '$1=' + cbval) // wildcard callback func name
} else {
cbval = match[3] // provided callback func name
}
} else {
url = urlappend(url, cbkey + '=' + cbval) // no callback details, add 'em
}
win[cbval] = generalCallback
script.type = 'text/javascript'
script.src = url
script.async = true
if (typeof script.onreadystatechange !== 'undefined' && !isIE10) {
// need this for IE due to out-of-order onreadystatechange(), binding script
// execution to an event listener gives us control over when the script
// is executed. See http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html
//
// if this hack is used in IE10 jsonp callback are never called
script.event = 'onclick'
script.htmlFor = script.id = '_reqwest_' + reqId
}
script.onload = script.onreadystatechange = function () {
if ((script[readyState] && script[readyState] !== 'complete' && script[readyState] !== 'loaded') || loaded) {
return false
}
script.onload = script.onreadystatechange = null
script.onclick && script.onclick()
// Call the user callback with the last value stored and clean up values and scripts.
fn(lastValue)
lastValue = undefined
head.removeChild(script)
loaded = 1
}
// Add the script to the DOM head
head.appendChild(script)
// Enable JSONP timeout
return {
abort: function () {
script.onload = script.onreadystatechange = null
err({}, 'Request is aborted: timeout', {})
lastValue = undefined
head.removeChild(script)
loaded = 1
}
}
}
function getRequest(fn, err) {
var o = this.o
, method = (o.method || 'GET').toUpperCase()
, url = typeof o === 'string' ? o : o.url
// convert non-string objects to query-string form unless o.processData is false
, data = (o.processData !== false && o.data && typeof o.data !== 'string')
? reqwest.toQueryString(o.data)
: (o.data || null)
, http
, sendWait = false
// if we're working on a GET request and we have data then we should append
// query string to end of URL and not post data
if ((o.type == 'jsonp' || method == 'GET') && data) {
url = urlappend(url, data)
data = null
}
if (o.type == 'jsonp') return handleJsonp(o, fn, err, url)
http = xhr(o)
http.open(method, url, o.async === false ? false : true)
setHeaders(http, o)
setCredentials(http, o)
if (win[xDomainRequest] && http instanceof win[xDomainRequest]) {
http.onload = fn
http.onerror = err
// NOTE: see
// http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/30ef3add-767c-4436-b8a9-f1ca19b4812e
http.onprogress = function() {}
sendWait = true
} else {
http.onreadystatechange = handleReadyState(this, fn, err)
}
o.before && o.before(http)
if (sendWait) {
setTimeout(function () {
http.send(data)
}, 200)
} else {
http.send(data)
}
return http
}
function Reqwest(o, fn) {
this.o = o
this.fn = fn
init.apply(this, arguments)
}
function setType(url) {
var m = url.match(/\.(json|jsonp|html|xml)(\?|$)/)
return m ? m[1] : 'js'
}
function init(o, fn) {
this.url = typeof o == 'string' ? o : o.url
this.timeout = null
// whether request has been fulfilled for purpose
// of tracking the Promises
this._fulfilled = false
// success handlers
this._successHandler = function(){}
this._fulfillmentHandlers = []
// error handlers
this._errorHandlers = []
// complete (both success and fail) handlers
this._completeHandlers = []
this._erred = false
this._responseArgs = {}
var self = this
, type = o.type || setType(this.url)
fn = fn || function () {}
if (o.timeout) {
this.timeout = setTimeout(function () {
self.abort()
}, o.timeout)
}
if (o.success) {
this._successHandler = function () {
o.success.apply(o, arguments)
}
}
if (o.error) {
this._errorHandlers.push(function () {
o.error.apply(o, arguments)
})
}
if (o.complete) {
this._completeHandlers.push(function () {
o.complete.apply(o, arguments)
})
}
function complete (resp) {
o.timeout && clearTimeout(self.timeout)
self.timeout = null
while (self._completeHandlers.length > 0) {
self._completeHandlers.shift()(resp)
}
}
function success (resp) {
resp = (type !== 'jsonp') ? self.request : resp
// use global data filter on response text
var filteredResponse = globalSetupOptions.dataFilter(resp.responseText, type)
, r = filteredResponse
try {
resp.responseText = r
} catch (e) {
// can't assign this in IE<=8, just ignore
}
if (r) {
switch (type) {
case 'json':
try {
resp = win.JSON ? win.JSON.parse(r) : eval('(' + r + ')')
} catch (err) {
return error(resp, 'Could not parse JSON in response', err)
}
break
case 'js':
resp = eval(r)
break
case 'html':
resp = r
break
case 'xml':
resp = resp.responseXML
&& resp.responseXML.parseError // IE trololo
&& resp.responseXML.parseError.errorCode
&& resp.responseXML.parseError.reason
? null
: resp.responseXML
break
}
}
self._responseArgs.resp = resp
self._fulfilled = true
fn(resp)
self._successHandler(resp)
while (self._fulfillmentHandlers.length > 0) {
resp = self._fulfillmentHandlers.shift()(resp)
}
complete(resp)
}
function error(resp, msg, t) {
resp = self.request
self._responseArgs.resp = resp
self._responseArgs.msg = msg
self._responseArgs.t = t
self._erred = true
while (self._errorHandlers.length > 0) {
self._errorHandlers.shift()(resp, msg, t)
}
complete(resp)
}
this.request = getRequest.call(this, success, error)
}
Reqwest.prototype = {
abort: function () {
this._aborted = true
this.request.abort()
}
, retry: function () {
init.call(this, this.o, this.fn)
}
/**
* Small deviation from the Promises A CommonJs specification
* http://wiki.commonjs.org/wiki/Promises/A
*/
/**
* `then` will execute upon successful requests
*/
, then: function (success, fail) {
success = success || function () {}
fail = fail || function () {}
if (this._fulfilled) {
this._responseArgs.resp = success(this._responseArgs.resp)
} else if (this._erred) {
fail(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t)
} else {
this._fulfillmentHandlers.push(success)
this._errorHandlers.push(fail)
}
return this
}
/**
* `always` will execute whether the request succeeds or fails
*/
, always: function (fn) {
if (this._fulfilled || this._erred) {
fn(this._responseArgs.resp)
} else {
this._completeHandlers.push(fn)
}
return this
}
/**
* `fail` will execute when the request fails
*/
, fail: function (fn) {
if (this._erred) {
fn(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t)
} else {
this._errorHandlers.push(fn)
}
return this
}
}
function reqwest(o, fn) {
return new Reqwest(o, fn)
}
// normalize newline variants according to spec -> CRLF
function normalize(s) {
return s ? s.replace(/\r?\n/g, '\r\n') : ''
}
function serial(el, cb) {
var n = el.name
, t = el.tagName.toLowerCase()
, optCb = function (o) {
// IE gives value="" even where there is no value attribute
// 'specified' ref: http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-862529273
if (o && !o.disabled)
cb(n, normalize(o.attributes.value && o.attributes.value.specified ? o.value : o.text))
}
, ch, ra, val, i
// don't serialize elements that are disabled or without a name
if (el.disabled || !n) return
switch (t) {
case 'input':
if (!/reset|button|image|file/i.test(el.type)) {
ch = /checkbox/i.test(el.type)
ra = /radio/i.test(el.type)
val = el.value
// WebKit gives us "" instead of "on" if a checkbox has no value, so correct it here
;(!(ch || ra) || el.checked) && cb(n, normalize(ch && val === '' ? 'on' : val))
}
break
case 'textarea':
cb(n, normalize(el.value))
break
case 'select':
if (el.type.toLowerCase() === 'select-one') {
optCb(el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null)
} else {
for (i = 0; el.length && i < el.length; i++) {
el.options[i].selected && optCb(el.options[i])
}
}
break
}
}
// collect up all form elements found from the passed argument elements all
// the way down to child elements; pass a '<form>' or form fields.
// called with 'this'=callback to use for serial() on each element
function eachFormElement() {
var cb = this
, e, i
, serializeSubtags = function (e, tags) {
var i, j, fa
for (i = 0; i < tags.length; i++) {
fa = e[byTag](tags[i])
for (j = 0; j < fa.length; j++) serial(fa[j], cb)
}
}
for (i = 0; i < arguments.length; i++) {
e = arguments[i]
if (/input|select|textarea/i.test(e.tagName)) serial(e, cb)
serializeSubtags(e, [ 'input', 'select', 'textarea' ])
}
}
// standard query string style serialization
function serializeQueryString() {
return reqwest.toQueryString(reqwest.serializeArray.apply(null, arguments))
}
// { 'name': 'value', ... } style serialization
function serializeHash() {
var hash = {}
eachFormElement.apply(function (name, value) {
if (name in hash) {
hash[name] && !isArray(hash[name]) && (hash[name] = [hash[name]])
hash[name].push(value)
} else hash[name] = value
}, arguments)
return hash
}
// [ { name: 'name', value: 'value' }, ... ] style serialization
reqwest.serializeArray = function () {
var arr = []
eachFormElement.apply(function (name, value) {
arr.push({name: name, value: value})
}, arguments)
return arr
}
reqwest.serialize = function () {
if (arguments.length === 0) return ''
var opt, fn
, args = Array.prototype.slice.call(arguments, 0)
opt = args.pop()
opt && opt.nodeType && args.push(opt) && (opt = null)
opt && (opt = opt.type)
if (opt == 'map') fn = serializeHash
else if (opt == 'array') fn = reqwest.serializeArray
else fn = serializeQueryString
return fn.apply(null, args)
}
reqwest.toQueryString = function (o, trad) {
var prefix, i
, traditional = trad || false
, s = []
, enc = encodeURIComponent
, add = function (key, value) {
// If value is a function, invoke it and return its value
value = ('function' === typeof value) ? value() : (value == null ? '' : value)
s[s.length] = enc(key) + '=' + enc(value)
}
// If an array was passed in, assume that it is an array of form elements.
if (isArray(o)) {
for (i = 0; o && i < o.length; i++) add(o[i].name, o[i].value)
} else {
// If traditional, encode the "old" way (the way 1.3.2 or older
// did it), otherwise encode params recursively.
for (prefix in o) {
buildParams(prefix, o[prefix], traditional, add)
}
}
// spaces should be + according to spec
return s.join('&').replace(/%20/g, '+')
}
function buildParams(prefix, obj, traditional, add) {
var name, i, v
, rbracket = /\[\]$/
if (isArray(obj)) {
// Serialize array item.
for (i = 0; obj && i < obj.length; i++) {
v = obj[i]
if (traditional || rbracket.test(prefix)) {
// Treat each array item as a scalar.
add(prefix, v)
} else {
buildParams(prefix + '[' + (typeof v === 'object' ? i : '') + ']', v, traditional, add)
}
}
} else if (obj && obj.toString() === '[object Object]') {
// Serialize object item.
for (name in obj) {
buildParams(prefix + '[' + name + ']', obj[name], traditional, add)
}
} else {
// Serialize scalar item.
add(prefix, obj)
}
}
reqwest.getcallbackPrefix = function () {
return callbackPrefix
}
// jQuery and Zepto compatibility, differences can be remapped here so you can call
// .ajax.compat(options, callback)
reqwest.compat = function (o, fn) {
if (o) {
o.type && (o.method = o.type) && delete o.type
o.dataType && (o.type = o.dataType)
o.jsonpCallback && (o.jsonpCallbackName = o.jsonpCallback) && delete o.jsonpCallback
o.jsonp && (o.jsonpCallback = o.jsonp)
}
return new Reqwest(o, fn)
}
reqwest.ajaxSetup = function (options) {
options = options || {}
for (var k in options) {
globalSetupOptions[k] = options[k]
}
}
return reqwest
});
/*global define*/
define(
'src/intro',[],function()
{
var signals;
var is_open = true;
var element = document.querySelectorAll( '.intro' )[0];
var button = document.querySelectorAll( '.intro-button' )[0];
var close_button = element.querySelectorAll( '.close' )[0];
function init( shared )
{
signals = shared.signals;
button.addEventListener( 'click', buttonClicked );
close_button.addEventListener( 'click', close );
signals['close-intro'].add( close );
}
function buttonClicked( event )
{
if ( is_open )
{
close();
}
else
{
open();
}
}
function open()
{
button.classList.add( 'is-active' );
element.classList.add( 'is-active' );
is_open = true;
}
function close()
{
button.classList.remove( 'is-active' );
element.classList.remove( 'is-active' );
is_open = false;
}
return { init: init };
}
);
define("lib/getusermedia", function(){});
/*global define*/
define(
'src/cam',[ 'lib/getusermedia' ],
function ()
{
var cam_button_el = document.getElementById( 'cam-button' );
var video_wrapper_el;
var video_el;
var stream;
var canvas_el;
var ctx;
var signals;
function init ( shared ) {
signals = shared.signals;
if ( navigator.getUserMedia ) {
cam_button_el.classList.add( 'is-supported' );
cam_button_el.addEventListener( 'click', camButtonClicked );
video_wrapper_el = document.createElement( 'div' );
video_wrapper_el.classList.add( 'cam-wrapper' );
var take_picture_el = document.createElement( 'div' );
take_picture_el.textContent = 'Take Picture';
take_picture_el.classList.add( 'take-picture' );
take_picture_el.classList.add( 'button' );
take_picture_el.addEventListener( 'click', videoClicked );
video_wrapper_el.appendChild( take_picture_el );
video_el = document.createElement( 'video' );
video_el.classList.add( 'cam' );
video_el.addEventListener( 'click', videoClicked );
video_wrapper_el.appendChild( video_el );
document.body.appendChild( video_wrapper_el );
canvas_el = document.createElement( 'canvas' );
}
}
function camButtonClicked ( event ) {
var cam_options = { video: true };
navigator.getUserMedia( cam_options, gotCamData, failed );
}
function gotCamData ( media_stream ) {
var source;
if ( window.webkitURL )
{
source = window.URL.createObjectURL( media_stream );
}
else
{
source = media_stream;
}
if ( video_el.mozSrcObject !== undefined )
{
video_el.mozSrcObject = source;
}
else
{
video_el.src = source;
}
stream = media_stream;
video_el.play();
video_el.style.display = 'block';
video_wrapper_el.classList.add( 'is-active' );
canvas_el.width = 640;
canvas_el.height = 480;
ctx = canvas_el.getContext( '2d' );
}
function failed () {
console.log( 'sorry, but there was an error accessing you camera...' );
}
function videoClicked ( event ) {
ctx.translate( canvas_el.width, 0 );
ctx.scale( -1, 1 );
ctx.drawImage( video_el, 0, 0 );
var data = ctx.getImageData( 0, 0, canvas_el.width, canvas_el.height );
var image_src = canvas_el.toDataURL( 'image/png' );
video_wrapper_el.classList.remove( 'is-active' );
signals['set-new-src'].dispatch( image_src );
setTimeout( function() { stream.stop(); }, 500 );
}
return {
init: init
};
}
);
/*global define*/
define(
'util/feature-test',[],function()
{
var tests = {
'canvas': { required: true, test: function(){ return !! document.createElement('canvas').getContext; } },
'query-selector-all': { required: false, test: function(){ return !! document.querySelectorAll; } },
'drag-drop': { required: false, test: function(){ return 'draggable' in document.createElement('span'); } },
'file-api': { required: false, test: function(){ return typeof FileReader !== 'undefined'; } }
};
function test( success, error )
{
var required_supported = true;
var results = { };
var required_features_missing = [ ];
for ( var key in tests )
{
var result = tests[key].test();
if ( ! result )
{
if ( tests[key].required )
{
required_supported = false;
required_features_missing.push( key );
}
}
results[key] = result;
}
if ( required_supported )
{
success( results );
}
else
{
error( required_features_missing, results );
}
}
return test;
}
);
/*jslint onevar:true, undef:true, newcap:true, regexp:true, bitwise:true, maxerr:50, indent:4, white:false, nomen:false, plusplus:false */
/*global define:false, require:false, exports:false, module:false, signals:false */
/** @license
* JS Signals <http://millermedeiros.github.com/js-signals/>
* Released under the MIT license
* Author: Miller Medeiros
* Version: 1.0.0 - Build: 268 (2012/11/29 05:48 PM)
*/
(function(global){
// SignalBinding -------------------------------------------------
//================================================================
/**
* Object that represents a binding between a Signal and a listener function.
* <br />- <strong>This is an internal constructor and shouldn't be called by regular users.</strong>
* <br />- inspired by Joa Ebert AS3 SignalBinding and Robert Penner's Slot classes.
* @author Miller Medeiros
* @constructor
* @internal
* @name SignalBinding
* @param {Signal} signal Reference to Signal object that listener is currently bound to.
* @param {Function} listener Handler function bound to the signal.
* @param {boolean} isOnce If binding should be executed just once.
* @param {Object} [listenerContext] Context on which listener will be executed (object that should represent the `this` variable inside listener function).
* @param {Number} [priority] The priority level of the event listener. (default = 0).
*/
function SignalBinding(signal, listener, isOnce, listenerContext, priority) {
/**
* Handler function bound to the signal.
* @type Function
* @private
*/
this._listener = listener;
/**
* If binding should be executed just once.
* @type boolean
* @private
*/
this._isOnce = isOnce;
/**
* Context on which listener will be executed (object that should represent the `this` variable inside listener function).
* @memberOf SignalBinding.prototype
* @name context
* @type Object|undefined|null
*/
this.context = listenerContext;
/**
* Reference to Signal object that listener is currently bound to.
* @type Signal
* @private
*/
this._signal = signal;
/**
* Listener priority
* @type Number
* @private
*/
this._priority = priority || 0;
}
SignalBinding.prototype = {
/**
* If binding is active and should be executed.
* @type boolean
*/
active : true,
/**
* Default parameters passed to listener during `Signal.dispatch` and `SignalBinding.execute`. (curried parameters)
* @type Array|null
*/
params : null,
/**
* Call listener passing arbitrary parameters.
* <p>If binding was added using `Signal.addOnce()` it will be automatically removed from signal dispatch queue, this method is used internally for the signal dispatch.</p>
* @param {Array} [paramsArr] Array of parameters that should be passed to the listener
* @return {*} Value returned by the listener.
*/
execute : function (paramsArr) {
var handlerReturn, params;
if (this.active && !!this._listener) {
params = this.params? this.params.concat(paramsArr) : paramsArr;
handlerReturn = this._listener.apply(this.context, params);
if (this._isOnce) {
this.detach();
}
}
return handlerReturn;
},
/**
* Detach binding from signal.
* - alias to: mySignal.remove(myBinding.getListener());
* @return {Function|null} Handler function bound to the signal or `null` if binding was previously detached.
*/
detach : function () {
return this.isBound()? this._signal.remove(this._listener, this.context) : null;
},
/**
* @return {Boolean} `true` if binding is still bound to the signal and have a listener.
*/
isBound : function () {
return (!!this._signal && !!this._listener);
},
/**
* @return {boolean} If SignalBinding will only be executed once.
*/
isOnce : function () {
return this._isOnce;
},
/**
* @return {Function} Handler function bound to the signal.
*/
getListener : function () {
return this._listener;
},
/**
* @return {Signal} Signal that listener is currently bound to.
*/
getSignal : function () {
return this._signal;
},
/**
* Delete instance properties
* @private
*/
_destroy : function () {
delete this._signal;
delete this._listener;
delete this.context;
},
/**
* @return {string} String representation of the object.
*/
toString : function () {
return '[SignalBinding isOnce:' + this._isOnce +', isBound:'+ this.isBound() +', active:' + this.active + ']';
}
};
/*global SignalBinding:false*/
// Signal --------------------------------------------------------
//================================================================
function validateListener(listener, fnName) {
if (typeof listener !== 'function') {
throw new Error( 'listener is a required param of {fn}() and should be a Function.'.replace('{fn}', fnName) );
}
}
/**
* Custom event broadcaster
* <br />- inspired by Robert Penner's AS3 Signals.
* @name Signal
* @author Miller Medeiros
* @constructor
*/
function Signal() {
/**
* @type Array.<SignalBinding>
* @private
*/
this._bindings = [];
this._prevParams = null;
// enforce dispatch to aways work on same context (#47)
var self = this;
this.dispatch = function(){
Signal.prototype.dispatch.apply(self, arguments);
};
}
Signal.prototype = {
/**
* Signals Version Number
* @type String
* @const
*/
VERSION : '1.0.0',
/**
* If Signal should keep record of previously dispatched parameters and
* automatically execute listener during `add()`/`addOnce()` if Signal was
* already dispatched before.
* @type boolean
*/
memorize : false,
/**
* @type boolean
* @private
*/
_shouldPropagate : true,
/**
* If Signal is active and should broadcast events.
* <p><strong>IMPORTANT:</strong> Setting this property during a dispatch will only affect the next dispatch, if you want to stop the propagation of a signal use `halt()` instead.</p>
* @type boolean
*/
active : true,
/**
* @param {Function} listener
* @param {boolean} isOnce
* @param {Object} [listenerContext]
* @param {Number} [priority]
* @return {SignalBinding}
* @private
*/
_registerListener : function (listener, isOnce, listenerContext, priority) {
var prevIndex = this._indexOfListener(listener, listenerContext),
binding;
if (prevIndex !== -1) {
binding = this._bindings[prevIndex];
if (binding.isOnce() !== isOnce) {
throw new Error('You cannot add'+ (isOnce? '' : 'Once') +'() then add'+ (!isOnce? '' : 'Once') +'() the same listener without removing the relationship first.');
}
} else {
binding = new SignalBinding(this, listener, isOnce, listenerContext, priority);
this._addBinding(binding);
}
if(this.memorize && this._prevParams){
binding.execute(this._prevParams);
}
return binding;
},
/**
* @param {SignalBinding} binding
* @private
*/
_addBinding : function (binding) {
//simplified insertion sort
var n = this._bindings.length;
do { --n; } while (this._bindings[n] && binding._priority <= this._bindings[n]._priority);
this._bindings.splice(n + 1, 0, binding);
},
/**
* @param {Function} listener
* @return {number}
* @private
*/
_indexOfListener : function (listener, context) {
var n = this._bindings.length,
cur;
while (n--) {
cur = this._bindings[n];
if (cur._listener === listener && cur.context === context) {
return n;
}
}
return -1;
},
/**
* Check if listener was attached to Signal.
* @param {Function} listener
* @param {Object} [context]
* @return {boolean} if Signal has the specified listener.
*/
has : function (listener, context) {
return this._indexOfListener(listener, context) !== -1;
},
/**
* Add a listener to the signal.
* @param {Function} listener Signal handler function.
* @param {Object} [listenerContext] Context on which listener will be executed (object that should represent the `this` variable inside listener function).
* @param {Number} [priority] The priority level of the event listener. Listeners with higher priority will be executed before listeners with lower priority. Listeners with same priority level will be executed at the same order as they were added. (default = 0)
* @return {SignalBinding} An Object representing the binding between the Signal and listener.
*/
add : function (listener, listenerContext, priority) {
validateListener(listener, 'add');
return this._registerListener(listener, false, listenerContext, priority);
},
/**
* Add listener to the signal that should be removed after first execution (will be executed only once).
* @param {Function} listener Signal handler function.
* @param {Object} [listenerContext] Context on which listener will be executed (object that should represent the `this` variable inside listener function).
* @param {Number} [priority] The priority level of the event listener. Listeners with higher priority will be executed before listeners with lower priority. Listeners with same priority level will be executed at the same order as they were added. (default = 0)
* @return {SignalBinding} An Object representing the binding between the Signal and listener.
*/
addOnce : function (listener, listenerContext, priority) {
validateListener(listener, 'addOnce');
return this._registerListener(listener, true, listenerContext, priority);
},
/**
* Remove a single listener from the dispatch queue.
* @param {Function} listener Handler function that should be removed.
* @param {Object} [context] Execution context (since you can add the same handler multiple times if executing in a different context).
* @return {Function} Listener handler function.
*/
remove : function (listener, context) {
validateListener(listener, 'remove');
var i = this._indexOfListener(listener, context);
if (i !== -1) {
this._bindings[i]._destroy(); //no reason to a SignalBinding exist if it isn't attached to a signal
this._bindings.splice(i, 1);
}
return listener;
},
/**
* Remove all listeners from the Signal.
*/
removeAll : function () {
var n = this._bindings.length;
while (n--) {
this._bindings[n]._destroy();
}
this._bindings.length = 0;
},
/**
* @return {number} Number of listeners attached to the Signal.
*/
getNumListeners : function () {
return this._bindings.length;
},
/**
* Stop propagation of the event, blocking the dispatch to next listeners on the queue.
* <p><strong>IMPORTANT:</strong> should be called only during signal dispatch, calling it before/after dispatch won't affect signal broadcast.</p>
* @see Signal.prototype.disable
*/
halt : function () {
this._shouldPropagate = false;
},
/**
* Dispatch/Broadcast Signal to all listeners added to the queue.
* @param {...*} [params] Parameters that should be passed to each handler.
*/
dispatch : function (params) {
if (! this.active) {
return;
}
var paramsArr = Array.prototype.slice.call(arguments),
n = this._bindings.length,
bindings;
if (this.memorize) {
this._prevParams = paramsArr;
}
if (! n) {
//should come after memorize
return;
}
bindings = this._bindings.slice(); //clone array in case add/remove items during dispatch
this._shouldPropagate = true; //in case `halt` was called before dispatch or during the previous dispatch.
//execute all callbacks until end of the list or until a callback returns `false` or stops propagation
//reverse loop since listeners with higher priority will be added at the end of the list
do { n--; } while (bindings[n] && this._shouldPropagate && bindings[n].execute(paramsArr) !== false);
},
/**
* Forget memorized arguments.
* @see Signal.memorize
*/
forget : function(){
this._prevParams = null;
},
/**
* Remove all bindings from signal and destroy any reference to external objects (destroy Signal object).
* <p><strong>IMPORTANT:</strong> calling any method on the signal instance after calling dispose will throw errors.</p>
*/
dispose : function () {
this.removeAll();
delete this._bindings;
delete this._prevParams;
},
/**
* @return {string} String representation of the object.
*/
toString : function () {
return '[Signal active:'+ this.active +' numListeners:'+ this.getNumListeners() +']';
}
};
// Namespace -----------------------------------------------------
//================================================================
/**
* Signals namespace
* @namespace
* @name signals
*/
var signals = Signal;
/**
* Custom event broadcaster
* @see Signal
*/
// alias for backwards compatibility (see #gh-44)
signals.Signal = Signal;
//exports to multiple environments
if(typeof define === 'function' && define.amd){ //AMD
define('lib/signals-1.0.0',[],function () { return signals; });
} else if (typeof module !== 'undefined' && module.exports){ //node
module.exports = signals;
} else { //browser
//use string because of Google closure compiler ADVANCED_MODE
/*jslint sub:true */
global['signals'] = signals;
}
}(this));
/*global require, requirejs, define, Modernizr, _basepath_ */
// http://requirejs.org/docs/api.html#config
var path = typeof _basepath_ === 'string' ? _basepath_ + '/' : '';
requirejs.config(
{
baseUrl: path + 'scripts/',
waitSeconds: 50,
urlArgs: 'bust=' + ( new Date() ).getTime()
}
);
require(
[
'src/process',
'src/image',
'src/file',
'src/dragdrop',
'src/controls',
'src/export-button',
'src/import-button',
'src/random-button',
'src/intro',
'src/cam',
'util/feature-test',
'lib/signals-1.0.0'
],
function(
process,
image,
file,
dragdrop,
controls,
export_button,
import_button,
random_button,
imgur,
intro,
cam,
testFeatures,
Signal
)
{
testFeatures( init, showError );
function init( supported_features )
{
var shared = {
feature: supported_features,
signals: {
'load-file' : new Signal(),
'image-loaded' : new Signal(),
'set-new-src' : new Signal(),
'control-set' : new Signal(),
'control-updated' : new Signal(),
'close-intro' : new Signal(),
'export-requested' : new Signal()
}
};
process.init( shared );
dragdrop.init( shared );
controls.init( shared );
export_button.init( shared );
import_button.init( shared );
random_button.init( shared );
image.init( shared );
file.init( shared );
imgur.init( shared );
intro.init( shared );
cam.init( shared );
}
function showError( required_features )
{
var message = document.createElement( 'div' );
var message_text = 'sorry. it looks like your browser is missing some of the features ';
message_text += '(' + required_features.join( ', ' ) + ') that are required to run this ';
message_text += 'experiment.';
message.innerText = message_text;
message.className = 'missing-feature';
document.getElementsByTagName( 'body' )[0].appendChild( message );
}
}
);
define("main", function(){});
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment