diff --git a/scripts/lib/glitch-canvas.js b/scripts/lib/glitch-canvas.js new file mode 100644 index 0000000..83bd6cd --- /dev/null +++ b/scripts/lib/glitch-canvas.js @@ -0,0 +1,184 @@ +//! glitch-canvas by snorpey, MIT License +(function(window, factory) { + if (typeof define === "function" && define.amd) { + define(factory); + } else if (typeof exports === "object") { + module.exports = factory(); + } else { + window.glitch = factory(); + } +})(this, function() { + var canvas_1 = document.createElement("canvas"); + var canvas_2 = document.createElement("canvas"); + var ctx_1 = canvas_1.getContext("2d"); + var ctx_2 = canvas_2.getContext("2d"); + var base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var base64_map = base64_chars.split(""); + var reversed_base64_map = {}; + var params; + 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) { + reversed_base64_map[val] = key; + }); + function glitchImageData(image_data, parameters, callback) { + if (isValidImageData(image_data) && checkType(parameters, "parameters", "object") && checkType(callback, "callback", "function")) { + params = getNormalizedParameters(parameters); + resizeCanvas(canvas_1, image_data); + resizeCanvas(canvas_2, image_data); + base64 = getBase64FromImageData(image_data, params.quality); + byte_array = base64ToByteArray(base64); + jpg_header_length = getJpegHeaderSize(byte_array); + for (i = 0, len = params.iterations; i < len; i++) { + glitchJpegBytes(byte_array, jpg_header_length, params.seed, params.amount, i, params.iterations); + } + img = new Image(); + img.onload = function() { + ctx_1.drawImage(img, 0, 0); + new_image_data = ctx_1.getImageData(0, 0, image_data.width, image_data.height); + callback(new_image_data); + }; + img.src = byteArrayToBase64(byte_array); + } + } + function resizeCanvas(canvas, size) { + if (canvas.width !== size.width) { + canvas.width = size.width; + } + if (canvas.height !== size.height) { + canvas.height = size.height; + } + } + function glitchJpegBytes(byte_array, jpg_header_length, seed, amount, i, len) { + var max_index = byte_array.length - jpg_header_length - 4; + var px_min = parseInt(max_index / len * i, 10); + var px_max = parseInt(max_index / len * (i + 1), 10); + var delta = px_max - px_min; + var px_i = parseInt(px_min + delta * seed, 10); + if (px_i > max_index) { + px_i = max_index; + } + var index = Math.floor(jpg_header_length + px_i); + byte_array[index] = Math.floor(amount * 256); + } + function getBase64FromImageData(image_data, quality) { + var q = typeof quality === "number" && quality < 1 && quality > 0 ? quality : .1; + ctx_2.putImageData(image_data, 0, 0); + return canvas_2.toDataURL("image/jpeg", q); + } + function getJpegHeaderSize(data) { + var result = 417; + for (i = 0, len = data.length; i < len; i++) { + if (data[i] === 255 && data[i + 1] === 218) { + result = i + 2; + break; + } + } + return result; + } + function base64ToByteArray(str) { + var result = []; + var digit_num; + var cur; + var prev; + for (i = 23, len = str.length; i < len; i++) { + cur = reversed_base64_map[str.charAt(i)]; + digit_num = (i - 23) % 4; + switch (digit_num) { + case 1: + result.push(prev << 2 | cur >> 4); + break; + + case 2: + result.push((prev & 15) << 4 | cur >> 2); + break; + + case 3: + 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; + for (i = 0, len = arr.length; i < len; i++) { + cur = arr[i]; + byte_num = i % 3; + switch (byte_num) { + case 0: + result.push(base64_map[cur >> 2]); + break; + + case 1: + result.push(base64_map[(prev & 3) << 4 | cur >> 4]); + break; + + case 2: + result.push(base64_map[(prev & 15) << 2 | cur >> 6]); + result.push(base64_map[cur & 63]); + 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 & 15) << 2]); + result.push("="); + } + return result.join(""); + } + function getImageDataCopy(image_data) { + var copy = ctx_2.createImageData(image_data.width, image_data.height); + copy.data.set(image_data.data); + return copy; + } + function getNormalizedParameters(parameters) { + return { + seed: (parameters.seed || 0) / 100, + quality: (parameters.quality || 0) / 100, + amount: (parameters.amount || 0) / 100, + iterations: parameters.iterations || 0 + }; + } + function isValidImageData(image_data) { + if (checkType(image_data, "image_data", "object") && checkType(image_data.width, "image_data.width", "number") && checkType(image_data.height, "image_data.height", "number") && checkType(image_data.data, "image_data.data", "object") && checkType(image_data.data.length, "image_data.data.length", "number") && checkNumber(image_data.data.length, "image_data.data.length", isPositive, "> 0")) { + return true; + } else { + return false; + } + } + function checkType(it, name, expected_type) { + if (typeof it === expected_type) { + return true; + } else { + error(it, "typeof " + name, '"' + expected_type + '"', '"' + typeof it + '"'); + return false; + } + } + function checkNumber(it, name, condition, condition_name) { + if (condition(it) === true) { + return true; + } else { + error(it, name, condition_name, "not"); + } + } + function isPositive(nr) { + return nr > 0; + } + function error(it, name, expected, result) { + throw new Error("glitch(): Expected " + name + " to be " + expected + ", but it was " + result + "."); + } + return glitchImageData; +}); \ No newline at end of file diff --git a/scripts/src/glitch.js b/scripts/src/glitch.js deleted file mode 100644 index ac2fe4f..0000000 --- a/scripts/src/glitch.js +++ /dev/null @@ -1,195 +0,0 @@ -/*global define*/ -define( - [ 'util/canvas' ], - function( canvas_helper ) - { - 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 amount; - 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; - amount = input.amount / 100; - iterations = input.iterations; - - canvas_helper.resize( canvas, image_data ); - canvas_helper.resize( tmp_canvas, image_data ); - - 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, amount, i, iterations ); - } - - 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 glitchJpegBytes( byte_array, jpg_header_length, seed, amount, i, len ) - { - var max_index = byte_array.length - jpg_header_length - 4; - var px_min = parseInt( max_index / len * i, 10 ); - var px_max = parseInt( max_index / len * ( i + 1 ), 10 ); - - var delta = px_max - px_min; - var px_i = parseInt( px_min + delta * seed, 10 ); - - if ( px_i > max_index ) - { - px_i = max_index; - } - - var index = Math.floor( jpg_header_length + px_i ); - - byte_array[index] = Math.floor( amount * 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; - } -); \ No newline at end of file diff --git a/scripts/src/process.js b/scripts/src/process.js index 55523cc..76a8e75 100644 --- a/scripts/src/process.js +++ b/scripts/src/process.js @@ -1,6 +1,6 @@ /*global define, requestAnimationFrame*/ define( - [ 'src/glitch', 'util/canvas', 'lib/raf' ], + [ 'lib/glitch-canvas', 'util/canvas', 'lib/raf' ], function( glitch, canvas_helper ) { var tmp_canvas = document.createElement( 'canvas' );