initial commit
commit
cca32d920e
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
#Mac OS files
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear on external disk
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# SublimeText project files
|
||||
*.sublime-workspace
|
||||
25
README.md
Normal file
25
README.md
Normal file
@ -0,0 +1,25 @@
|
||||
image triangulation experiment
|
||||
===
|
||||
|
||||
this is an experiment for the web browser. it uses the [delaunay triangulation](https://en.wikipedia.org/wiki/Delaunay_triangulation) algorithm to alter an image.
|
||||
|
||||
[](http://snorpey.github.io/triangulation/)
|
||||
|
||||
[online demo](http://snorpey.github.io/triangulation/)
|
||||
|
||||
this experiment is very much based on the [triangulation image generator](http://jsdo.it/akm2/xoYx) script. it includes several speed enhancements. it is my goal to make it fast enough for use with real time streaming input, e.g. from a [web cam](https://github.com/snorpey/photobooth).
|
||||
|
||||
you can fine another experiment that applies the the triangulation to text input here: [http://snorpey.github.io/text-triangulation/](http://snorpey.github.io/text-triangulation/)
|
||||
|
||||
third party code used in this experiment
|
||||
---
|
||||
* some parts of the code code from [triangulation image generator](http://jsdo.it/akm2/xoYx) by [akm2](http://codepen.io/akm2), MIT license
|
||||
* [delaunay js](https://github.com/ironwallaby/delaunay) by [ironwallaby](https://github.com/ironwallaby), public domain
|
||||
* [html5slider](http://frankyan.com/labs/html5slider/) by [fryn](https://github.com/fryn), MIT license
|
||||
* [js signals](http://millermedeiros.github.io/js-signals/) by [millermedeiros](https://github.com/millermedeiros), MIT license
|
||||
* [superfast boxblur for canvas](http://quasimondo.com/BoxBlurForCanvas/FastBlurDemo.html) by [quasimondo](https://github.com/quasimondo), MIT license
|
||||
* [require js](http://requirejs.org/), by [jrburke](jrburke), BSD & MIT license
|
||||
|
||||
license
|
||||
---
|
||||
[MIT License](http://www.opensource.org/licenses/mit-license.php)
|
||||
38
index.html
Normal file
38
index.html
Normal file
@ -0,0 +1,38 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>image glitch experiment</title>
|
||||
<link rel="stylesheet" href="styles/global.css" />
|
||||
</head>
|
||||
<body>
|
||||
<article class="content">
|
||||
<h1 class="headline">glitch images</h1>
|
||||
<p>drop an image in the browser.</p>
|
||||
</article>
|
||||
<div class="content" id="controls">
|
||||
<div class="control-wrapper">
|
||||
<label class="control-label" for="seed">seed</label>
|
||||
<input class="control-input" type="range" id="seed" min="0" max="100" value="50" />
|
||||
</div>
|
||||
<div class="control-wrapper">
|
||||
<label class="control-label" for="quality">quality</label>
|
||||
<input class="control-input" type="range" id="quality" min="0" max="100" value="50" />
|
||||
</div>
|
||||
<div class="control-wrapper">
|
||||
<label class="control-label" for="offset">px offset</label>
|
||||
<input class="control-input" type="range" id="offset" min="0" max="100" value="50" />
|
||||
</div>
|
||||
<div class="control-wrapper">
|
||||
<label class="control-label" for="iterations">iterations</label>
|
||||
<input class="control-input" type="range" id="iterations" min="1" max="20" value="5" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="export-wrapper">
|
||||
<button id="save-button" class="button">export image</button>
|
||||
<a id="png-button" download="triangulated-image.png" target="_blank" class="download-link">download bitmap file<span> (.png)</span></a>
|
||||
</div>
|
||||
<canvas id="canvas"></canvas>
|
||||
<script src="scripts/lib/require-2.1.4.js" data-main="scripts/src/main"></script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
lincoln.jpg
Normal file
BIN
lincoln.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 188 KiB |
48
scripts/aux/feature-test.js
Normal file
48
scripts/aux/feature-test.js
Normal file
@ -0,0 +1,48 @@
|
||||
/*global define*/
|
||||
define(
|
||||
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;
|
||||
}
|
||||
);
|
||||
206
scripts/aux/glitch.js
Normal file
206
scripts/aux/glitch.js
Normal file
@ -0,0 +1,206 @@
|
||||
/*global define*/
|
||||
define(
|
||||
function()
|
||||
{
|
||||
var canvas = document.createElement( 'canvas' );
|
||||
var ctx = canvas.getContext( '2d' );
|
||||
|
||||
var tmp_canvas = document.createElement( 'canvas' );
|
||||
var tmp_ctx = tmp_canvas.getContext( '2d' );
|
||||
|
||||
var canvas_size = { width: 10, height: 10 };
|
||||
|
||||
var base64_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
var base64_map = base64_chars.split( '' );
|
||||
var reverse_base64_map = { };
|
||||
|
||||
var iterations;
|
||||
var quality;
|
||||
var seed;
|
||||
var offset;
|
||||
var base64;
|
||||
var byte_array;
|
||||
var jpg_header_length;
|
||||
var img;
|
||||
var new_image_data;
|
||||
|
||||
var i;
|
||||
var len;
|
||||
|
||||
base64_map.forEach( function( val, key ) { reverse_base64_map[val] = key; } );
|
||||
|
||||
function getGlitchedImageSrc( image_data, input, callback )
|
||||
{
|
||||
seed = input.seed / 100;
|
||||
quality = input.quality / 100;
|
||||
offset = input.offset / 100;
|
||||
iterations = input.iterations;
|
||||
|
||||
updateCanvasSize( image_data.width, image_data.height );
|
||||
|
||||
base64 = getBase64FromImageData( image_data, quality );
|
||||
byte_array = base64ToByteArray( base64 );
|
||||
jpg_header_length = getJpegHeaderSize( byte_array );
|
||||
|
||||
for ( i = 0; i < iterations; i++ )
|
||||
{
|
||||
glitchJpegBytes( byte_array, jpg_header_length, seed, offset, i );
|
||||
}
|
||||
|
||||
img = new Image();
|
||||
img.onload = function() {
|
||||
ctx.drawImage( img, 0, 0 );
|
||||
new_image_data = ctx.getImageData( 0, 0, image_data.width, image_data.height );
|
||||
callback( new_image_data );
|
||||
};
|
||||
|
||||
img.src = byteArrayToBase64( byte_array );
|
||||
}
|
||||
|
||||
function updateCanvasSize( width, height )
|
||||
{
|
||||
var updated = false;
|
||||
|
||||
if ( canvas_size.width !== width ) { canvas_size.width = width; updated = true; }
|
||||
if ( canvas_size.height !== height ) { canvas_size.height = height; updated = true; }
|
||||
|
||||
if ( updated )
|
||||
{
|
||||
resizeCanvas( tmp_canvas, canvas_size );
|
||||
resizeCanvas( canvas, canvas_size );
|
||||
}
|
||||
}
|
||||
|
||||
function resizeCanvas( canvas, img )
|
||||
{
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
}
|
||||
|
||||
function glitchJpegBytes( byte_array, jpg_header_length, seed, offset, iteration )
|
||||
{
|
||||
var it = ( iteration + 1 ) / 10;
|
||||
var max_index = byte_array.length - jpg_header_length - 4;
|
||||
var px_index = it * max_index + offset * 10;
|
||||
|
||||
if ( px_index > max_index )
|
||||
{
|
||||
px_index = px_index;
|
||||
}
|
||||
|
||||
var index = Math.floor( jpg_header_length + px_index );
|
||||
byte_array[index] = Math.floor( seed * 256 );
|
||||
}
|
||||
|
||||
function getBase64FromImageData( image_data, quality )
|
||||
{
|
||||
var q = typeof quality === 'number' && quality < 1 && quality > 0 ? quality : 0.1;
|
||||
tmp_ctx.putImageData( image_data, 0, 0 );
|
||||
return tmp_canvas.toDataURL( 'image/jpeg', q );
|
||||
}
|
||||
|
||||
function getJpegHeaderSize( data )
|
||||
{
|
||||
var result = 417;
|
||||
|
||||
for ( var i = 0, l = data.length; i < l; i++ )
|
||||
{
|
||||
if ( data[i] === 0xFF && data[i + 1] === 0xDA )
|
||||
{
|
||||
result = i + 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// https://github.com/mutaphysis/smackmyglitchupjs/blob/master/glitch.html
|
||||
// base64 is 2^6, byte is 2^8, every 4 base64 values create three bytes
|
||||
function base64ToByteArray( str )
|
||||
{
|
||||
var result = [ ];
|
||||
var digit_num;
|
||||
var cur;
|
||||
var prev;
|
||||
|
||||
for ( var i = 23, l = str.length; i < l; i++ )
|
||||
{
|
||||
cur = reverse_base64_map[ str.charAt( i ) ];
|
||||
digit_num = ( i - 23 ) % 4;
|
||||
|
||||
switch ( digit_num )
|
||||
{
|
||||
// case 0: first digit - do nothing, not enough info to work with
|
||||
case 1: // second digit
|
||||
result.push( prev << 2 | cur >> 4 );
|
||||
break;
|
||||
case 2: // third digit
|
||||
result.push( ( prev & 0x0f ) << 4 | cur >> 2 );
|
||||
break;
|
||||
case 3: // fourth digit
|
||||
result.push( ( prev & 3 ) << 6 | cur );
|
||||
break;
|
||||
}
|
||||
|
||||
prev = cur;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function byteArrayToBase64( arr )
|
||||
{
|
||||
var result = [ 'data:image/jpeg;base64,' ];
|
||||
var byte_num;
|
||||
var cur;
|
||||
var prev;
|
||||
var i;
|
||||
|
||||
for ( var i = 0, l = arr.length; i < l; i++ )
|
||||
{
|
||||
cur = arr[i];
|
||||
byte_num = i % 3;
|
||||
|
||||
switch ( byte_num )
|
||||
{
|
||||
case 0: // first byte
|
||||
result.push( base64_map[ cur >> 2 ] );
|
||||
break;
|
||||
case 1: // second byte
|
||||
result.push( base64_map[( prev & 3 ) << 4 | ( cur >> 4 )] );
|
||||
break;
|
||||
case 2: // third byte
|
||||
result.push( base64_map[( prev & 0x0f ) << 2 | ( cur >> 6 )] );
|
||||
result.push( base64_map[cur & 0x3f] );
|
||||
break;
|
||||
}
|
||||
|
||||
prev = cur;
|
||||
}
|
||||
|
||||
if ( byte_num === 0 )
|
||||
{
|
||||
result.push( base64_map[( prev & 3 ) << 4] );
|
||||
result.push( '==' );
|
||||
}
|
||||
|
||||
else if ( byte_num === 1 )
|
||||
{
|
||||
result.push( base64_map[( prev & 0x0f ) << 2] );
|
||||
result.push( '=' );
|
||||
}
|
||||
|
||||
return result.join( '' );
|
||||
}
|
||||
|
||||
function getImageDataCopy( image_data )
|
||||
{
|
||||
var copy = tmp_ctx.createImageData( image_data.width, image_data.height );
|
||||
copy.data.set( image_data.data );
|
||||
return copy;
|
||||
}
|
||||
|
||||
return getGlitchedImageSrc;
|
||||
}
|
||||
);
|
||||
182
scripts/lib/delaunay.js
Normal file
182
scripts/lib/delaunay.js
Normal file
@ -0,0 +1,182 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
285
scripts/lib/html5slider.js
Normal file
285
scripts/lib/html5slider.js
Normal file
@ -0,0 +1,285 @@
|
||||
/*
|
||||
html5slider - a JS implementation of <input type=range> for Firefox 16 and up
|
||||
https://github.com/fryn/html5slider
|
||||
|
||||
Copyright (c) 2010-2013 Frank Yan, <http://frankyan.com>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
// test for native support
|
||||
var test = document.createElement('input');
|
||||
try {
|
||||
test.type = 'range';
|
||||
if (test.type == 'range')
|
||||
return;
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
// test for required property support
|
||||
test.style.background = 'linear-gradient(red, red)';
|
||||
if (!test.style.backgroundImage || !('MozAppearance' in test.style) ||
|
||||
!document.mozSetImageElement || !this.MutationObserver)
|
||||
return;
|
||||
|
||||
var scale;
|
||||
var isMac = navigator.platform == 'MacIntel';
|
||||
var thumb = {
|
||||
radius: isMac ? 9 : 6,
|
||||
width: isMac ? 22 : 12,
|
||||
height: isMac ? 16 : 20
|
||||
};
|
||||
var track = 'linear-gradient(transparent ' + (isMac ?
|
||||
'6px, #999 6px, #999 7px, #ccc 8px, #bbb 9px, #bbb 10px, transparent 10px' :
|
||||
'9px, #999 9px, #bbb 10px, #fff 11px, transparent 11px') +
|
||||
', transparent)';
|
||||
var styles = {
|
||||
'min-width': thumb.width + 'px',
|
||||
'min-height': thumb.height + 'px',
|
||||
'max-height': thumb.height + 'px',
|
||||
padding: '0 0 ' + (isMac ? '2px' : '1px'),
|
||||
border: 0,
|
||||
'border-radius': 0,
|
||||
cursor: 'default',
|
||||
'text-indent': '-999999px' // -moz-user-select: none; breaks mouse capture
|
||||
};
|
||||
var options = {
|
||||
attributes: true,
|
||||
attributeFilter: ['min', 'max', 'step', 'value']
|
||||
};
|
||||
var forEach = Array.prototype.forEach;
|
||||
var onInput = document.createEvent('HTMLEvents');
|
||||
onInput.initEvent('input', true, false);
|
||||
var onChange = document.createEvent('HTMLEvents');
|
||||
onChange.initEvent('change', true, false);
|
||||
|
||||
if (document.readyState == 'loading')
|
||||
document.addEventListener('DOMContentLoaded', initialize, true);
|
||||
else
|
||||
initialize();
|
||||
|
||||
function initialize() {
|
||||
// create initial sliders
|
||||
forEach.call(document.querySelectorAll('input[type=range]'), transform);
|
||||
// create sliders on-the-fly
|
||||
new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function(mutation) {
|
||||
if (mutation.addedNodes)
|
||||
forEach.call(mutation.addedNodes, function(node) {
|
||||
check(node);
|
||||
if (node.childElementCount)
|
||||
forEach.call(node.querySelectorAll('input'), check);
|
||||
});
|
||||
});
|
||||
}).observe(document, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
function check(input) {
|
||||
if (input.localName == 'input' && input.type != 'range' &&
|
||||
input.getAttribute('type') == 'range')
|
||||
transform(input);
|
||||
}
|
||||
|
||||
function transform(slider) {
|
||||
|
||||
var isValueSet, areAttrsSet, isChanged, isClick, prevValue, rawValue, prevX;
|
||||
var min, max, step, range, value = slider.value;
|
||||
|
||||
// lazily create shared slider affordance
|
||||
if (!scale) {
|
||||
scale = document.body.appendChild(document.createElement('hr'));
|
||||
style(scale, {
|
||||
'-moz-appearance': isMac ? 'scale-horizontal' : 'scalethumb-horizontal',
|
||||
display: 'block',
|
||||
visibility: 'visible',
|
||||
opacity: 1,
|
||||
position: 'fixed',
|
||||
top: '-999999px'
|
||||
});
|
||||
document.mozSetImageElement('__sliderthumb__', scale);
|
||||
}
|
||||
|
||||
// reimplement value and type properties
|
||||
var getValue = function() { return '' + value; };
|
||||
var setValue = function setValue(val) {
|
||||
value = '' + val;
|
||||
isValueSet = true;
|
||||
draw();
|
||||
delete slider.value;
|
||||
slider.value = value;
|
||||
Object.defineProperty(slider, 'value', {
|
||||
get: getValue,
|
||||
set: setValue
|
||||
});
|
||||
};
|
||||
Object.defineProperty(slider, 'value', {
|
||||
get: getValue,
|
||||
set: setValue
|
||||
});
|
||||
Object.defineProperty(slider, 'type', {
|
||||
get: function() { return 'range'; }
|
||||
});
|
||||
|
||||
// sync properties with attributes
|
||||
['min', 'max', 'step'].forEach(function(prop) {
|
||||
if (slider.hasAttribute(prop))
|
||||
areAttrsSet = true;
|
||||
Object.defineProperty(slider, prop, {
|
||||
get: function() { return this.hasAttribute(prop) ? this.getAttribute(prop) : ''; },
|
||||
set: function(val) { val === null ? this.removeAttribute(prop) : this.setAttribute(prop, val); }
|
||||
});
|
||||
});
|
||||
|
||||
// initialize slider
|
||||
slider.readOnly = true;
|
||||
style(slider, styles);
|
||||
update();
|
||||
|
||||
new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function(mutation) {
|
||||
if (mutation.attributeName != 'value') {
|
||||
update();
|
||||
areAttrsSet = true;
|
||||
}
|
||||
// note that value attribute only sets initial value
|
||||
else if (!isValueSet) {
|
||||
value = slider.getAttribute('value');
|
||||
draw();
|
||||
}
|
||||
});
|
||||
}).observe(slider, options);
|
||||
|
||||
slider.addEventListener('mousedown', onDragStart, true);
|
||||
slider.addEventListener('keydown', onKeyDown, true);
|
||||
slider.addEventListener('focus', onFocus, true);
|
||||
slider.addEventListener('blur', onBlur, true);
|
||||
|
||||
function onDragStart(e) {
|
||||
isClick = true;
|
||||
setTimeout(function() { isClick = false; }, 0);
|
||||
if (e.button || !range)
|
||||
return;
|
||||
var width = parseFloat(getComputedStyle(this, 0).width);
|
||||
var multiplier = (width - thumb.width) / range;
|
||||
if (!multiplier)
|
||||
return;
|
||||
// distance between click and center of thumb
|
||||
var dev = e.clientX - this.getBoundingClientRect().left - thumb.width / 2 -
|
||||
(value - min) * multiplier;
|
||||
// if click was not on thumb, move thumb to click location
|
||||
if (Math.abs(dev) > thumb.radius) {
|
||||
isChanged = true;
|
||||
this.value -= -dev / multiplier;
|
||||
}
|
||||
rawValue = value;
|
||||
prevX = e.clientX;
|
||||
this.addEventListener('mousemove', onDrag, true);
|
||||
this.addEventListener('mouseup', onDragEnd, true);
|
||||
}
|
||||
|
||||
function onDrag(e) {
|
||||
var width = parseFloat(getComputedStyle(this, 0).width);
|
||||
var multiplier = (width - thumb.width) / range;
|
||||
if (!multiplier)
|
||||
return;
|
||||
rawValue += (e.clientX - prevX) / multiplier;
|
||||
prevX = e.clientX;
|
||||
isChanged = true;
|
||||
this.value = rawValue;
|
||||
}
|
||||
|
||||
function onDragEnd() {
|
||||
this.removeEventListener('mousemove', onDrag, true);
|
||||
this.removeEventListener('mouseup', onDragEnd, true);
|
||||
slider.dispatchEvent(onChange);
|
||||
}
|
||||
|
||||
function onKeyDown(e) {
|
||||
if (e.keyCode > 36 && e.keyCode < 41) { // 37-40: left, up, right, down
|
||||
onFocus.call(this);
|
||||
isChanged = true;
|
||||
this.value = value + (e.keyCode == 38 || e.keyCode == 39 ? step : -step);
|
||||
}
|
||||
}
|
||||
|
||||
function onFocus() {
|
||||
if (!isClick)
|
||||
this.style.boxShadow = !isMac ? '0 0 0 2px #fb0' :
|
||||
'inset 0 0 20px rgba(0,127,255,.1), 0 0 1px rgba(0,127,255,.4)';
|
||||
}
|
||||
|
||||
function onBlur() {
|
||||
this.style.boxShadow = '';
|
||||
}
|
||||
|
||||
// determines whether value is valid number in attribute form
|
||||
function isAttrNum(value) {
|
||||
return !isNaN(value) && +value == parseFloat(value);
|
||||
}
|
||||
|
||||
// validates min, max, and step attributes and redraws
|
||||
function update() {
|
||||
min = isAttrNum(slider.min) ? +slider.min : 0;
|
||||
max = isAttrNum(slider.max) ? +slider.max : 100;
|
||||
if (max < min)
|
||||
max = min > 100 ? min : 100;
|
||||
step = isAttrNum(slider.step) && slider.step > 0 ? +slider.step : 1;
|
||||
range = max - min;
|
||||
draw(true);
|
||||
}
|
||||
|
||||
// recalculates value property
|
||||
function calc() {
|
||||
if (!isValueSet && !areAttrsSet)
|
||||
value = slider.getAttribute('value');
|
||||
if (!isAttrNum(value))
|
||||
value = (min + max) / 2;;
|
||||
// snap to step intervals (WebKit sometimes does not - bug?)
|
||||
value = Math.round((value - min) / step) * step + min;
|
||||
if (value < min)
|
||||
value = min;
|
||||
else if (value > max)
|
||||
value = min + ~~(range / step) * step;
|
||||
}
|
||||
|
||||
// renders slider using CSS background ;)
|
||||
function draw(attrsModified) {
|
||||
calc();
|
||||
if (isChanged && value != prevValue)
|
||||
slider.dispatchEvent(onInput);
|
||||
isChanged = false;
|
||||
if (!attrsModified && value == prevValue)
|
||||
return;
|
||||
prevValue = value;
|
||||
var position = range ? (value - min) / range * 100 : 0;
|
||||
var bg = '-moz-element(#__sliderthumb__) ' + position + '% no-repeat, ';
|
||||
style(slider, { background: bg + track });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function style(element, styles) {
|
||||
for (var prop in styles)
|
||||
element.style.setProperty(prop, styles[prop], 'important');
|
||||
}
|
||||
|
||||
})();
|
||||
2000
scripts/lib/require-2.1.4.js
Normal file
2000
scripts/lib/require-2.1.4.js
Normal file
File diff suppressed because it is too large
Load Diff
445
scripts/lib/signals-1.0.0.js
Executable file
445
scripts/lib/signals-1.0.0.js
Executable file
@ -0,0 +1,445 @@
|
||||
/*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(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));
|
||||
338
scripts/lib/superfast-blur.0.5.js
Normal file
338
scripts/lib/superfast-blur.0.5.js
Normal file
@ -0,0 +1,338 @@
|
||||
/*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(
|
||||
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;
|
||||
}
|
||||
);
|
||||
53
scripts/src/controls.js
vendored
Normal file
53
scripts/src/controls.js
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
/*global define*/
|
||||
define(
|
||||
function()
|
||||
{
|
||||
var values = { };
|
||||
var is_initialized = false;
|
||||
var signals;
|
||||
|
||||
function init( shared )
|
||||
{
|
||||
signals = shared.signals;
|
||||
|
||||
if ( shared.feature['query-selector-all'] )
|
||||
{
|
||||
var wrapper = document.getElementById( 'controls' );
|
||||
var controls = document.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( control.id, control.value );
|
||||
}
|
||||
|
||||
is_initialized = true;
|
||||
|
||||
signals['control-updated'].dispatch( values );
|
||||
}
|
||||
}
|
||||
|
||||
function controlUpdated( event )
|
||||
{
|
||||
var target = event.target;
|
||||
|
||||
updateValue( target.id, target.value );
|
||||
}
|
||||
|
||||
function updateValue( key, value )
|
||||
{
|
||||
values[key] = value;
|
||||
|
||||
if ( is_initialized )
|
||||
{
|
||||
signals['control-updated'].dispatch( values );
|
||||
}
|
||||
}
|
||||
|
||||
return { init: init };
|
||||
}
|
||||
);
|
||||
43
scripts/src/dragdrop.js
vendored
Normal file
43
scripts/src/dragdrop.js
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
/*global define*/
|
||||
define(
|
||||
function()
|
||||
{
|
||||
var signals;
|
||||
var reader;
|
||||
var feature;
|
||||
|
||||
function init( shared )
|
||||
{
|
||||
feature = shared.feature;
|
||||
signals = shared.signals;
|
||||
|
||||
if ( feature['drag-drop' ] && feature['file-api' ] )
|
||||
{
|
||||
document.addEventListener( 'drop', dropped, false );
|
||||
document.addEventListener( 'dragover', preventDefault, false );
|
||||
document.addEventListener( 'dragleave', preventDefault, false );
|
||||
|
||||
reader = new FileReader();
|
||||
reader.addEventListener( 'load', fileLoaded, false );
|
||||
}
|
||||
}
|
||||
|
||||
function preventDefault( event )
|
||||
{
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
function dropped( event )
|
||||
{
|
||||
event.preventDefault();
|
||||
reader.readAsDataURL( event.dataTransfer.files[0] );
|
||||
}
|
||||
|
||||
function fileLoaded( event )
|
||||
{
|
||||
signals['set-new-src'].dispatch( event.target.result );
|
||||
}
|
||||
|
||||
return { init: init };
|
||||
}
|
||||
);
|
||||
31
scripts/src/export-png.js
Normal file
31
scripts/src/export-png.js
Normal file
@ -0,0 +1,31 @@
|
||||
/*global define*/
|
||||
define(
|
||||
function()
|
||||
{
|
||||
var signals;
|
||||
var png_button;
|
||||
|
||||
function init( shared )
|
||||
{
|
||||
signals = shared.signals;
|
||||
png_button = document.getElementById( 'png-button' );
|
||||
|
||||
signals['export-png'].add( generatePNG );
|
||||
signals['control-updated'].add( hideLink );
|
||||
png_button.addEventListener( 'click', hideLink, false );
|
||||
}
|
||||
|
||||
function generatePNG( data_url )
|
||||
{
|
||||
png_button.href = data_url;
|
||||
png_button.classList.add( 'is-active' );
|
||||
}
|
||||
|
||||
function hideLink()
|
||||
{
|
||||
png_button.classList.remove( 'is-active' );
|
||||
}
|
||||
|
||||
return { init: init };
|
||||
}
|
||||
);
|
||||
45
scripts/src/image.js
Normal file
45
scripts/src/image.js
Normal file
@ -0,0 +1,45 @@
|
||||
/*global define*/
|
||||
define(
|
||||
function()
|
||||
{
|
||||
var signals;
|
||||
var image;
|
||||
var initialized = false;
|
||||
|
||||
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( 'lincoln.jpg' );
|
||||
}
|
||||
|
||||
function imageLoaded()
|
||||
{
|
||||
signals['image-loaded'].dispatch( image );
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
function setSrc( src )
|
||||
{
|
||||
image.src = src;
|
||||
|
||||
if (
|
||||
initialized &&
|
||||
image.naturalWidth !== undefined &&
|
||||
image.naturalWidth !== 0
|
||||
)
|
||||
{
|
||||
signals['image-loaded'].dispatch( image );
|
||||
}
|
||||
}
|
||||
|
||||
return { init: init };
|
||||
}
|
||||
);
|
||||
75
scripts/src/main.js
Normal file
75
scripts/src/main.js
Normal file
@ -0,0 +1,75 @@
|
||||
/*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: 5,
|
||||
urlArgs: 'bust=' + ( new Date() ).getTime(),
|
||||
shim: {
|
||||
'lib/delaunay': { exports: 'triangulate' }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
require(
|
||||
[
|
||||
'src/process',
|
||||
'src/image',
|
||||
'src/dragdrop',
|
||||
'src/controls',
|
||||
'src/export-png',
|
||||
'src/save-button',
|
||||
'aux/feature-test',
|
||||
'lib/signals-1.0.0',
|
||||
'lib/html5slider'
|
||||
],
|
||||
function(
|
||||
process,
|
||||
image,
|
||||
dragdrop,
|
||||
controls,
|
||||
png,
|
||||
save_button,
|
||||
testFeatures,
|
||||
Signal
|
||||
)
|
||||
{
|
||||
testFeatures( init, showError );
|
||||
|
||||
function init( supported_features )
|
||||
{
|
||||
var shared = {
|
||||
feature: supported_features,
|
||||
signals: {
|
||||
'image-loaded' : new Signal(),
|
||||
'set-new-src' : new Signal(),
|
||||
'control-updated' : new Signal(),
|
||||
'export-png' : new Signal(),
|
||||
'saved' : new Signal()
|
||||
}
|
||||
};
|
||||
|
||||
process.init( shared );
|
||||
dragdrop.init( shared );
|
||||
controls.init( shared );
|
||||
png.init( shared );
|
||||
save_button.init( shared );
|
||||
image.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 );
|
||||
}
|
||||
}
|
||||
);
|
||||
109
scripts/src/process.js
Normal file
109
scripts/src/process.js
Normal file
@ -0,0 +1,109 @@
|
||||
/*global define*/
|
||||
define(
|
||||
[ 'aux/glitch' ],
|
||||
function( glitch )
|
||||
{
|
||||
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 image_data;
|
||||
|
||||
function init( shared )
|
||||
{
|
||||
signals = shared.signals;
|
||||
|
||||
signals['image-loaded'].add( generate );
|
||||
signals['control-updated'].add( controlsUpdated );
|
||||
signals['saved'].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 );
|
||||
resizeCanvas( canvas, img );
|
||||
|
||||
tmp_ctx.drawImage( img, 0, 0 );
|
||||
|
||||
image_data = tmp_ctx.getImageData( 0, 0, tmp_canvas.width, tmp_canvas.height );
|
||||
|
||||
glitch( image_data, values, draw );
|
||||
}
|
||||
|
||||
function draw( image_data )
|
||||
{
|
||||
resizeCanvas( canvas, image_data );
|
||||
ctx.putImageData( image_data, 0, 0 );
|
||||
|
||||
is_processing = false;
|
||||
image_data = null;
|
||||
}
|
||||
|
||||
function resizeCanvas( canvas, img )
|
||||
{
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
}
|
||||
|
||||
function clearCanvas( canvas, ctx )
|
||||
{
|
||||
ctx.clearRect( ctx, 0, 0, canvas.width, canvas.height );
|
||||
}
|
||||
|
||||
function exportData()
|
||||
{
|
||||
signals['export-png'].dispatch( canvas.toDataURL( 'image/png' ) );
|
||||
}
|
||||
|
||||
function getAdjustedValues( new_values )
|
||||
{
|
||||
var result = { };
|
||||
|
||||
for ( var key in new_values )
|
||||
{
|
||||
result[key] = parseInt( new_values[key], 10 );
|
||||
}
|
||||
|
||||
key = null;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return { init: init };
|
||||
}
|
||||
);
|
||||
25
scripts/src/save-button.js
Normal file
25
scripts/src/save-button.js
Normal file
@ -0,0 +1,25 @@
|
||||
/*global define*/
|
||||
define(
|
||||
function()
|
||||
{
|
||||
var signals;
|
||||
var save_button;
|
||||
|
||||
function init( shared )
|
||||
{
|
||||
signals = shared.signals;
|
||||
save_button = document.getElementById( 'save-button' );
|
||||
|
||||
save_button.addEventListener( 'click', buttonClicked, false );
|
||||
}
|
||||
|
||||
function buttonClicked( event )
|
||||
{
|
||||
event.preventDefault();
|
||||
|
||||
signals['saved'].dispatch();
|
||||
}
|
||||
|
||||
return { init: init };
|
||||
}
|
||||
);
|
||||
130
styles/global.css
Normal file
130
styles/global.css
Normal file
@ -0,0 +1,130 @@
|
||||
*
|
||||
{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
padding: 50px;
|
||||
font-family: sans-serif;
|
||||
color: #666;
|
||||
line-height: 18px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
a
|
||||
{
|
||||
color: #06f;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover
|
||||
{
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.button
|
||||
{
|
||||
background-color: #eaeaea;
|
||||
padding: 5px 9px;
|
||||
display: inline-block;
|
||||
color: #06f;
|
||||
font-weight: normal;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
font-family: sans-serif;
|
||||
font-size: 12px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button:hover
|
||||
{
|
||||
background-color: #06f;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.headline
|
||||
{
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.content,
|
||||
.missing-feature
|
||||
{
|
||||
max-width: 650px;
|
||||
}
|
||||
|
||||
#controls
|
||||
{
|
||||
float: left;
|
||||
margin-top: 30px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#controls.is-active
|
||||
{
|
||||
display: block;
|
||||
}
|
||||
|
||||
.control-wrapper
|
||||
{
|
||||
float: left;
|
||||
width: 100px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.control-label
|
||||
{
|
||||
display: block;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.control-input
|
||||
{
|
||||
display: block;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
#canvas
|
||||
{
|
||||
clear: both;
|
||||
float: left;
|
||||
margin-top: 30px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.export-wrapper
|
||||
{
|
||||
clear: both;
|
||||
float: left;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.download-link
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.download-link.is-active
|
||||
{
|
||||
display: inline-block;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.download-link span
|
||||
{
|
||||
color: #999;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
|
||||
.missing-feature
|
||||
{
|
||||
clear: both;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user