├── script ├── engine │ ├── title.js │ ├── engine.js │ ├── game.js │ ├── scene.js │ ├── assets.js │ ├── application.js │ ├── collection.js │ └── loader.js ├── main.js ├── game.js ├── ant.js └── vendor │ ├── underscore-min.js │ ├── eveline.js │ └── canvasquery.js └── index.html /script/engine/title.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /script/engine/engine.js: -------------------------------------------------------------------------------- 1 | var ENGINE = {}; -------------------------------------------------------------------------------- /script/engine/game.js: -------------------------------------------------------------------------------- 1 | app.game = new ENGINE.Scene({ 2 | 3 | onstep: function(delta) { 4 | 5 | }, 6 | 7 | onrender: function(delta) { 8 | 9 | app.layer.clear("#111"); 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /script/main.js: -------------------------------------------------------------------------------- 1 | /* global ENGINE */ 2 | 3 | var app; 4 | window.app = app = new ENGINE.Application({ 5 | width: window.innerWidth, 6 | height: window.innerHeight, 7 | 8 | oncreate: function() { 9 | console.log("oncreate"); 10 | this.loader.foo(500); 11 | }, 12 | 13 | onready: function() { 14 | this.selectScene(this.game); 15 | } 16 | }); -------------------------------------------------------------------------------- /script/engine/scene.js: -------------------------------------------------------------------------------- 1 | /* global ENGINE, _ */ 2 | ENGINE.Scene = function(args) { 3 | 4 | _.extend(this, args); 5 | 6 | if (this.oncreate) { 7 | this.oncreate(); 8 | } 9 | }; 10 | 11 | ENGINE.Scene.prototype = { 12 | 13 | onenter: function() { }, 14 | onleave: function() { }, 15 | onrender: function() { }, 16 | onstep: function() { } 17 | 18 | }; 19 | -------------------------------------------------------------------------------- /script/game.js: -------------------------------------------------------------------------------- 1 | /* global ENGINE,app */ 2 | 3 | app.game = new ENGINE.Scene({ 4 | oncreate: function() { 5 | this.entities = new ENGINE.Collection(this); 6 | 7 | for (var i = 0; i < 100; i++) { 8 | this.spawnAnt(); 9 | } 10 | }, 11 | spawnAnt: function() { 12 | this.entities.add(ENGINE.Ant, { 13 | x: app.width / 2, 14 | y: app.height / 2 15 | }); 16 | }, 17 | onstep: function(delta) { 18 | this.entities.step(delta); 19 | 20 | this.entities.call("step", delta); 21 | }, 22 | onrender: function(delta) { 23 | app.layer.clear("#000"); 24 | 25 | this.entities.call("render", delta); 26 | } 27 | }); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Whole lotta ants 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /script/engine/assets.js: -------------------------------------------------------------------------------- 1 | /* global ENGINE */ 2 | 3 | ENGINE.Assets = function(loader, paths) { 4 | this.loader = loader; 5 | 6 | this.paths = paths || { 7 | images: "assets/images/" 8 | }; 9 | 10 | this.data = { 11 | images: [] 12 | }; 13 | }; 14 | 15 | ENGINE.Assets.prototype = { 16 | /* get image by key - key is created by removing extension from filename for example 17 | units/tank.png 18 | becomes units/tank 19 | */ 20 | image: function(key) { 21 | return this.data.images[key]; 22 | }, 23 | 24 | /* Add multiple images */ 25 | addImages: function(filenames) { 26 | for(var i = 0; i < filenames.length; i++) { 27 | this.addImage(filenames[i]); 28 | } 29 | }, 30 | 31 | /* Add single image */ 32 | addImage: function(filename) { 33 | var image = new Image(); 34 | 35 | // Pass image to loader 36 | this.loader.image(image); 37 | 38 | // Rip off extension 39 | var key = filename.match(/(.*)\..*/)[1]; 40 | 41 | // Add image to assets 42 | this.data.images[key] = image; 43 | 44 | // Search for image in defined path 45 | image.src = this.paths.images + filename; 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /script/engine/application.js: -------------------------------------------------------------------------------- 1 | /* global ENGINE,cq,eveline, _ */ 2 | 3 | ENGINE.Application = function(args) { 4 | var app = this; 5 | 6 | _.extend(this, args); 7 | 8 | /* create a fullscreen canvas wrapper - we will draw on it */ 9 | this.layer = cq(); 10 | 11 | this.layer.appendTo("body"); 12 | 13 | eveline(this); 14 | 15 | this.loader = new ENGINE.Loader(); 16 | this.assets = new ENGINE.Assets(this.loader); 17 | 18 | this.oncreate(); 19 | 20 | this.loader.ready(function() { 21 | app.onready(); 22 | }); 23 | }; 24 | 25 | ENGINE.Application.prototype = { 26 | /* calls the method in current scene with given arguments 27 | for example 28 | this.dispatch("onmousemove", 32, 64); 29 | will trigger onmousemove method in current scene (if it has one) 30 | */ 31 | dispatch: function(method) { 32 | 33 | if (this.scene && this.scene[arguments[0]]) { 34 | this.scene[arguments[0]].apply(this.scene, Array.prototype.slice.call(arguments, 1)); 35 | } 36 | }, 37 | 38 | selectScene: function(scene) { 39 | this.dispatch("onleave"); 40 | 41 | this.scene = scene; 42 | 43 | this.dispatch("onenter"); 44 | }, 45 | 46 | /* game logic step (setInterval) */ 47 | onstep: function(delta) { 48 | this.dispatch("onstep", delta); 49 | }, 50 | 51 | /* rendering loop (requestAnimationFrame) */ 52 | onrender: function(delta) { 53 | this.dispatch("onrender", delta); 54 | }, 55 | 56 | /* the key gets translated to a string like shift, escape, a, b, c */ 57 | onkeydown: function(key) { 58 | this.dispatch("onkeydown", key); 59 | } 60 | 61 | // TODO: fill out rest 62 | }; 63 | -------------------------------------------------------------------------------- /script/ant.js: -------------------------------------------------------------------------------- 1 | /* global ENGINE,app,_ */ 2 | 3 | ENGINE.Ant = function(args) { 4 | /* extend the instancewith defaults and user data */ 5 | _.extend(this, { 6 | // Direction ant is facing in radians 7 | direction: 0, 8 | // px's per sec 9 | speed: 6, 10 | // AI cooldown so movement is natural 11 | brainDelta: 0 12 | }, args); 13 | }; 14 | 15 | ENGINE.Ant.prototype = { 16 | step: function(delta) { 17 | // Decrease brain cooldown 18 | this.brainDelta -= delta; 19 | 20 | //if cooldown goes below zero do some thinking 21 | if (this.brainDelta < 0) { 22 | this.direction = Math.random() * Math.PI * 2; 23 | this.brainDelta = Math.random() * 2000; 24 | } 25 | 26 | // increase speed with age 27 | this.speed += 8 * delta / 1000; 28 | 29 | //move ant 30 | this.x += Math.cos(this.direction) * this.speed * delta / 1000; 31 | this.y += Math.sin(this.direction) * this.speed * delta / 1000; 32 | 33 | //if off screen kill it 34 | if (this.x < 0 || this.y < 0 35 | || this.x > app.width || this.y > app.height) { 36 | 37 | this.remove(); 38 | app.game.spawnAnt(); 39 | 40 | } 41 | }, 42 | 43 | render: function(delta) { 44 | app.layer 45 | .fillStyle("#ff0000") 46 | .fillRect( 47 | 5 * (this.x / 5 | 0), 48 | 5 * (this.y / 5 | 0), 49 | 2, 2 50 | ); 51 | 52 | }, 53 | remove: function() { 54 | // Mark for removal 55 | this._remove = true; 56 | 57 | // Tell collection it is dirty 58 | this.collection.dirty = true; 59 | } 60 | }; -------------------------------------------------------------------------------- /script/engine/collection.js: -------------------------------------------------------------------------------- 1 | /* global ENGINE, _ */ 2 | 3 | ENGINE.Collection = function(parent) { 4 | // Object that manages the collection 5 | this.parent = parent; 6 | 7 | // Unique id for every entitiy 8 | this.index = 0; 9 | 10 | // If something inside dies, remove it 11 | this.dirty = false; 12 | }; 13 | 14 | ENGINE.Collection.prototype = new Array; 15 | 16 | _.extend(ENGINE.Collection.prototype, { 17 | 18 | /* creates new object instance with given args and pushes it to the collection 19 | example: 20 | var entities = new ENGINE.Collection; 21 | entities.add(ENGINE.Soldier, { x: 32, y: 64}); 22 | */ 23 | add: function(constructor, args) { 24 | var entity = new constructor(_.extend({ 25 | collection: this, 26 | index: this.index++ 27 | }, args)); 28 | 29 | // Use native Array prototype to push 30 | this.push(entity); 31 | 32 | return entity; 33 | }, 34 | 35 | /* Remove dead bodies so they don't drain resources */ 36 | clean: function() { 37 | for (var i = 0, len = this.length; i < len; i++) { 38 | if (this[i]._remove) { 39 | this.splice(i--, 1); 40 | len--; 41 | } 42 | } 43 | }, 44 | 45 | /* Needs to be called in order to keep track of collections garbage */ 46 | step: function(delta) { 47 | if (this.dirty) { 48 | // Collection has been cleaned 49 | this.dirty = false; 50 | 51 | this.clean(); 52 | 53 | // Sort by z-index 54 | this.sort(function(a,b) { 55 | return (a.zIndex | 0) - (b.zIndex | 0); 56 | }); 57 | } 58 | }, 59 | 60 | /* Call some method on every entity */ 61 | call: function(method) { 62 | var args = Array.prototype.slice.call(arguments, 1); 63 | 64 | for (var i = 0, len = this.length; i < len; i++) { 65 | if(this[i][method]) { 66 | this[i][method].apply(this[i], args); 67 | } 68 | } 69 | }, 70 | 71 | /* Call some method on every entity **with args as array** */ 72 | apply: function(method, args) { 73 | for (var i = 0, len = this.length; i < len; i++) { 74 | if(this[i][method]) { 75 | this[i][method].apply(this[i], args); 76 | } 77 | } 78 | } 79 | }); -------------------------------------------------------------------------------- /script/engine/loader.js: -------------------------------------------------------------------------------- 1 | /* global ENGINE */ 2 | 3 | ENGINE.Loader = function() { 4 | 5 | /* all items to load */ 6 | this.total = 0; 7 | 8 | /* items in queue */ 9 | this.count = 0; 10 | 11 | /* convenient progress from 0 to 1 */ 12 | this.progress = 0; 13 | 14 | /* all callbacks that should be fired when the loading is over */ 15 | this.callbacks = []; 16 | 17 | this.loading = false; 18 | }; 19 | 20 | ENGINE.Loader.prototype = { 21 | add: function() { 22 | this.loading = true; 23 | this.count++; 24 | this.total++; 25 | }, 26 | 27 | image: function(image) { 28 | var loader = this; 29 | 30 | /* Listen to when the image is ready */ 31 | image.addEventListener("load", function() { 32 | loader.onItemReady(); 33 | }); 34 | 35 | image.addEventListener("error", function() { 36 | loader.onItemError(this.src); 37 | }); 38 | 39 | this.add(); 40 | }, 41 | 42 | /* sometimes it is convinient to simulate loading by using timeout 43 | usage: 44 | loader.foo(1000); 45 | this will simulate that some asset is being loaded for 1 second 46 | */ 47 | foo: function(duration) { 48 | var loader = this; 49 | 50 | /* simulate loading using timeout */ 51 | setTimeout(function() { 52 | loader.onItemReady(); 53 | }, duration); 54 | 55 | /* increase items count */ 56 | this.add(); 57 | }, 58 | 59 | /* Ready callback caller */ 60 | ready: function(callback) { 61 | if (!this.loading) { 62 | callback(); 63 | } else { 64 | this.callbacks.push(callback); 65 | } 66 | }, 67 | 68 | /* Called when one item finished loading */ 69 | onItemReady: function() { 70 | /* Reduce queue count */ 71 | this.count--; 72 | 73 | /* Update progress - Can be used for progress bars */ 74 | this.progress = (this.total - this.count) / this.total; 75 | 76 | if (this.count <= 0) { 77 | this.loading = false; 78 | 79 | // Run all callbacks 80 | for (var i = 0, len = this.callbacks.length; i < len; i++) { 81 | this.callbacks[i](); 82 | } 83 | 84 | // Cleanup callbacks 85 | this.callbacks = []; 86 | this.total = 0; 87 | this.count = 0; 88 | } 89 | }, 90 | 91 | /* Called on error */ 92 | onItemError: function(source) { 93 | console.log("unable to load ", source); 94 | } 95 | }; 96 | -------------------------------------------------------------------------------- /script/vendor/underscore-min.js: -------------------------------------------------------------------------------- 1 | (function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,d=e.filter,g=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,_=Object.keys,j=i.bind,w=function(n){return n instanceof w?n:this instanceof w?(this._wrapped=n,void 0):new w(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=w),exports._=w):n._=w,w.VERSION="1.4.4";var A=w.each=w.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(w.has(n,a)&&t.call(e,n[a],a,n)===r)return};w.map=w.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e[e.length]=t.call(r,n,u,i)}),e)};var O="Reduce of empty array with no initial value";w.reduce=w.foldl=w.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=w.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},w.reduceRight=w.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=w.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=w.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},w.find=w.detect=function(n,t,r){var e;return E(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},w.filter=w.select=function(n,t,r){var e=[];return null==n?e:d&&n.filter===d?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&(e[e.length]=n)}),e)},w.reject=function(n,t,r){return w.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},w.every=w.all=function(n,t,e){t||(t=w.identity);var u=!0;return null==n?u:g&&n.every===g?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var E=w.some=w.any=function(n,t,e){t||(t=w.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};w.contains=w.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:E(n,function(n){return n===t})},w.invoke=function(n,t){var r=o.call(arguments,2),e=w.isFunction(t);return w.map(n,function(n){return(e?t:n[t]).apply(n,r)})},w.pluck=function(n,t){return w.map(n,function(n){return n[t]})},w.where=function(n,t,r){return w.isEmpty(t)?r?null:[]:w[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},w.findWhere=function(n,t){return w.where(n,t,!0)},w.max=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.max.apply(Math,n);if(!t&&w.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>=e.computed&&(e={value:n,computed:a})}),e.value},w.min=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.min.apply(Math,n);if(!t&&w.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;e.computed>a&&(e={value:n,computed:a})}),e.value},w.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=w.random(r++),e[r-1]=e[t],e[t]=n}),e};var k=function(n){return w.isFunction(n)?n:function(t){return t[n]}};w.sortBy=function(n,t,r){var e=k(t);return w.pluck(w.map(n,function(n,t,u){return{value:n,index:t,criteria:e.call(r,n,t,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.indexi;){var o=i+a>>>1;u>r.call(e,n[o])?i=o+1:a=o}return i},w.toArray=function(n){return n?w.isArray(n)?o.call(n):n.length===+n.length?w.map(n,w.identity):w.values(n):[]},w.size=function(n){return null==n?0:n.length===+n.length?n.length:w.keys(n).length},w.first=w.head=w.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:o.call(n,0,t)},w.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},w.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},w.rest=w.tail=w.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},w.compact=function(n){return w.filter(n,w.identity)};var R=function(n,t,r){return A(n,function(n){w.isArray(n)?t?a.apply(r,n):R(n,t,r):r.push(n)}),r};w.flatten=function(n,t){return R(n,t,[])},w.without=function(n){return w.difference(n,o.call(arguments,1))},w.uniq=w.unique=function(n,t,r,e){w.isFunction(t)&&(e=r,r=t,t=!1);var u=r?w.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:w.contains(a,r))||(a.push(r),i.push(n[e]))}),i},w.union=function(){return w.uniq(c.apply(e,arguments))},w.intersection=function(n){var t=o.call(arguments,1);return w.filter(w.uniq(n),function(n){return w.every(t,function(t){return w.indexOf(t,n)>=0})})},w.difference=function(n){var t=c.apply(e,o.call(arguments,1));return w.filter(n,function(n){return!w.contains(t,n)})},w.zip=function(){for(var n=o.call(arguments),t=w.max(w.pluck(n,"length")),r=Array(t),e=0;t>e;e++)r[e]=w.pluck(n,""+e);return r},w.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},w.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=w.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},w.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},w.range=function(n,t,r){1>=arguments.length&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=Array(e);e>u;)i[u++]=n,n+=r;return i},w.bind=function(n,t){if(n.bind===j&&j)return j.apply(n,o.call(arguments,1));var r=o.call(arguments,2);return function(){return n.apply(t,r.concat(o.call(arguments)))}},w.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},w.bindAll=function(n){var t=o.call(arguments,1);return 0===t.length&&(t=w.functions(n)),A(t,function(t){n[t]=w.bind(n[t],n)}),n},w.memoize=function(n,t){var r={};return t||(t=w.identity),function(){var e=t.apply(this,arguments);return w.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},w.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},w.defer=function(n){return w.delay.apply(w,[n,1].concat(o.call(arguments,1)))},w.throttle=function(n,t){var r,e,u,i,a=0,o=function(){a=new Date,u=null,i=n.apply(r,e)};return function(){var c=new Date,l=t-(c-a);return r=this,e=arguments,0>=l?(clearTimeout(u),u=null,a=c,i=n.apply(r,e)):u||(u=setTimeout(o,l)),i}},w.debounce=function(n,t,r){var e,u;return function(){var i=this,a=arguments,o=function(){e=null,r||(u=n.apply(i,a))},c=r&&!e;return clearTimeout(e),e=setTimeout(o,t),c&&(u=n.apply(i,a)),u}},w.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},w.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},w.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},w.after=function(n,t){return 0>=n?t():function(){return 1>--n?t.apply(this,arguments):void 0}},w.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)w.has(n,r)&&(t[t.length]=r);return t},w.values=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push(n[r]);return t},w.pairs=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push([r,n[r]]);return t},w.invert=function(n){var t={};for(var r in n)w.has(n,r)&&(t[n[r]]=r);return t},w.functions=w.methods=function(n){var t=[];for(var r in n)w.isFunction(n[r])&&t.push(r);return t.sort()},w.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},w.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},w.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)w.contains(r,u)||(t[u]=n[u]);return t},w.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)null==n[r]&&(n[r]=t[r])}),n},w.clone=function(n){return w.isObject(n)?w.isArray(n)?n.slice():w.extend({},n):n},w.tap=function(n,t){return t(n),n};var I=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof w&&(n=n._wrapped),t instanceof w&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==t+"";case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;r.push(n),e.push(t);var a=0,o=!0;if("[object Array]"==u){if(a=n.length,o=a==t.length)for(;a--&&(o=I(n[a],t[a],r,e)););}else{var c=n.constructor,f=t.constructor;if(c!==f&&!(w.isFunction(c)&&c instanceof c&&w.isFunction(f)&&f instanceof f))return!1;for(var s in n)if(w.has(n,s)&&(a++,!(o=w.has(t,s)&&I(n[s],t[s],r,e))))break;if(o){for(s in t)if(w.has(t,s)&&!a--)break;o=!a}}return r.pop(),e.pop(),o};w.isEqual=function(n,t){return I(n,t,[],[])},w.isEmpty=function(n){if(null==n)return!0;if(w.isArray(n)||w.isString(n))return 0===n.length;for(var t in n)if(w.has(n,t))return!1;return!0},w.isElement=function(n){return!(!n||1!==n.nodeType)},w.isArray=x||function(n){return"[object Array]"==l.call(n)},w.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){w["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),w.isArguments(arguments)||(w.isArguments=function(n){return!(!n||!w.has(n,"callee"))}),"function"!=typeof/./&&(w.isFunction=function(n){return"function"==typeof n}),w.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},w.isNaN=function(n){return w.isNumber(n)&&n!=+n},w.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},w.isNull=function(n){return null===n},w.isUndefined=function(n){return n===void 0},w.has=function(n,t){return f.call(n,t)},w.noConflict=function(){return n._=t,this},w.identity=function(n){return n},w.times=function(n,t,r){for(var e=Array(n),u=0;n>u;u++)e[u]=t.call(r,u);return e},w.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var M={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};M.unescape=w.invert(M.escape);var S={escape:RegExp("["+w.keys(M.escape).join("")+"]","g"),unescape:RegExp("("+w.keys(M.unescape).join("|")+")","g")};w.each(["escape","unescape"],function(n){w[n]=function(t){return null==t?"":(""+t).replace(S[n],function(t){return M[n][t]})}}),w.result=function(n,t){if(null==n)return null;var r=n[t];return w.isFunction(r)?r.call(n):r},w.mixin=function(n){A(w.functions(n),function(t){var r=w[t]=n[t];w.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),D.call(this,r.apply(w,n))}})};var N=0;w.uniqueId=function(n){var t=++N+"";return n?n+t:t},w.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var T=/(.)^/,q={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},B=/\\|'|\r|\n|\t|\u2028|\u2029/g;w.template=function(n,t,r){var e;r=w.defaults({},r,w.templateSettings);var u=RegExp([(r.escape||T).source,(r.interpolate||T).source,(r.evaluate||T).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(B,function(n){return"\\"+q[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,w);var c=function(n){return e.call(this,n,w)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},w.chain=function(n){return w(n).chain()};var D=function(n){return this._chain?w(n).chain():n};w.mixin(w),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];w.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],D.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];w.prototype[n]=function(){return D.call(this,t.apply(this._wrapped,arguments))}}),w.extend(w.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this); -------------------------------------------------------------------------------- /script/vendor/eveline.js: -------------------------------------------------------------------------------- 1 | /* rezoner */ 2 | 3 | (function(window, undefined) { 4 | 5 | var MOBILE = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent); 6 | 7 | 8 | window.requestAnimationFrame = (function() { 9 | return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { 10 | window.setTimeout(callback, 1000 / 60); 11 | }; 12 | })(); 13 | 14 | eveline = function(events, context, element) { 15 | 16 | if (element === undefined) { 17 | element = window; 18 | } else if (typeof element === "string") { 19 | element = document.querySelector(element); 20 | } 21 | 22 | var tempContext = context || events; 23 | 24 | for (var name in events) { 25 | if (typeof eveline[name] === "function") eveline[name](element, events[name], tempContext); 26 | } 27 | 28 | /* apply scaling */ 29 | 30 | tempContext.scale = 1; 31 | tempContext.iscale = 1; 32 | 33 | tempContext.preventMultitouch = tempContext.preventMultitouch || false; 34 | 35 | if (tempContext.autoscale) { 36 | 37 | window.addEventListener("resize", function() { 38 | eveline.fitToScreen(element, tempContext); 39 | }); 40 | 41 | eveline.fitToScreen(element, tempContext); 42 | } 43 | 44 | requestAnimationFrame(eveline.step); 45 | 46 | } 47 | 48 | eveline.extend = function() { 49 | for (var i = 1; i < arguments.length; i++) { 50 | for (var j in arguments[i]) { 51 | arguments[0][j] = arguments[i][j]; 52 | } 53 | } 54 | 55 | return arguments[0]; 56 | }; 57 | 58 | eveline.gamepadButtons = { 59 | 0: "1", 60 | 1: "2", 61 | 2: "3", 62 | 3: "4", 63 | 4: "l1", 64 | 5: "r1", 65 | 6: "l2", 66 | 7: "r2", 67 | 8: "select", 68 | 9: "start", 69 | 70 | 12: "up", 71 | 13: "down", 72 | 14: "left", 73 | 15: "right" 74 | }; 75 | 76 | eveline.keycodes = { 77 | 37: "left", 78 | 38: "up", 79 | 39: "right", 80 | 40: "down", 81 | 45: "insert", 82 | 46: "delete", 83 | 8: "backspace", 84 | 9: "tab", 85 | 13: "enter", 86 | 16: "shift", 87 | 17: "ctrl", 88 | 18: "alt", 89 | 19: "pause", 90 | 20: "capslock", 91 | 27: "escape", 92 | 32: "space", 93 | 33: "pageup", 94 | 34: "pagedown", 95 | 35: "end", 96 | 112: "f1", 97 | 113: "f2", 98 | 114: "f3", 99 | 115: "f4", 100 | 116: "f5", 101 | 117: "f6", 102 | 118: "f7", 103 | 119: "f8", 104 | 120: "f9", 105 | 121: "f10", 106 | 122: "f11", 107 | 123: "f12", 108 | 144: "numlock", 109 | 145: "scrolllock", 110 | 186: "semicolon", 111 | 187: "equal", 112 | 188: "comma", 113 | 189: "dash", 114 | 190: "period", 115 | 191: "slash", 116 | 192: "graveaccent", 117 | 219: "openbracket", 118 | 220: "backslash", 119 | 221: "closebraket", 120 | 222: "singlequote" 121 | }; 122 | 123 | eveline.extend(eveline, { 124 | 125 | gamepadState: [], 126 | 127 | eventsListeners: {}, 128 | 129 | addEventListener: function(event, callback) { 130 | if (!this.eventsListeners[event]) this.eventsListeners[event] = []; 131 | this.eventsListeners[event].push(callback); 132 | }, 133 | 134 | trigger: function(event) { 135 | var args = []; 136 | 137 | for (var i = 1; i < arguments.length; i++) args.push(arguments[i]); 138 | 139 | var listeners = this.eventsListeners[event]; 140 | 141 | if (!listeners) return; 142 | 143 | for (var i = 0; i < listeners.length; i++) { 144 | listeners[i].apply(this, args); 145 | } 146 | }, 147 | 148 | distance: function(x1, y1, x2, y2) { 149 | return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y2)); 150 | }, 151 | 152 | step: function() { 153 | 154 | if (navigator.webkitGetGamepads) { 155 | 156 | var gamepads = navigator.webkitGetGamepads(); 157 | 158 | 159 | for (var i = 0; i < gamepads.length; i++) { 160 | var current = gamepads[i]; 161 | 162 | if (!current) continue; 163 | 164 | if (!eveline.gamepadState[i]) eveline.gamepadState[i] = current; 165 | 166 | var previous = eveline.gamepadState[i]; 167 | 168 | var buttons = []; 169 | 170 | if (previous.axes[0] !== current.axes[0] || previous.axes[1] !== current.axes[1]) { 171 | eveline.trigger("gamepadmove", current.axes[0], current.axes[1], i); 172 | } 173 | 174 | buttons[0] = current.axes[1] < 0 ? 1 : 0; 175 | buttons[1] = current.axes[1] > 0 ? 1 : 0; 176 | buttons[2] = current.axes[0] < 0 ? 1 : 0; 177 | buttons[3] = current.axes[0] > 0 ? 1 : 0; 178 | 179 | buttons = current.buttons.concat(buttons); 180 | 181 | for (var j = 0; j < buttons.length; j++) { 182 | if (previous.buttons[j] === 0 && buttons[j] === 1) eveline.trigger("gamepaddown", eveline.gamepadButtons[j], i); 183 | if (previous.buttons[j] === 1 && buttons[j] === 0) eveline.trigger("gamepadup", eveline.gamepadButtons[j], i); 184 | } 185 | 186 | eveline.gamepadState[i] = { 187 | buttons: buttons, 188 | axes: current.axes 189 | }; 190 | } 191 | 192 | } 193 | 194 | requestAnimationFrame(eveline.step); 195 | }, 196 | 197 | mousePosition: function(event, element) { 198 | var totalOffsetX = 0, 199 | totalOffsetY = 0, 200 | coordX = 0, 201 | coordY = 0, 202 | currentElement = element || event.target || event.srcElement, 203 | mouseX = 0, 204 | mouseY = 0; 205 | 206 | // Traversing the parents to get the total offset 207 | do { 208 | totalOffsetX += currentElement.offsetLeft; 209 | totalOffsetY += currentElement.offsetTop; 210 | } 211 | while ((currentElement = currentElement.offsetParent)); 212 | // Set the event to first touch if using touch-input 213 | if (event.changedTouches && event.changedTouches[0] !== undefined) { 214 | event = event.changedTouches[0]; 215 | } 216 | // Use pageX to get the mouse coordinates 217 | if (event.pageX || event.pageY) { 218 | mouseX = event.pageX; 219 | mouseY = event.pageY; 220 | } 221 | // IE8 and below doesn't support event.pageX 222 | else if (event.clientX || event.clientY) { 223 | mouseX = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; 224 | mouseY = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; 225 | } 226 | // Subtract the offset from the mouse coordinates 227 | coordX = mouseX - totalOffsetX; 228 | coordY = mouseY - totalOffsetY; 229 | 230 | return { 231 | x: coordX, 232 | y: coordY 233 | }; 234 | }, 235 | 236 | onstep: function(element, callback, context) { 237 | var lastTick = Date.now(); 238 | 239 | this.timer = setInterval(function() { 240 | var delta = Date.now() - lastTick; 241 | lastTick = Date.now(); 242 | callback.call(context, delta, lastTick); 243 | }, 25); 244 | }, 245 | 246 | onrender: function(element, callback, context) { 247 | var lastTick = Date.now(); 248 | 249 | function step() { 250 | var delta = Date.now() - lastTick; 251 | lastTick = Date.now(); 252 | requestAnimationFrame(step) 253 | callback.call(context, delta, lastTick); 254 | }; 255 | 256 | requestAnimationFrame(step); 257 | }, 258 | 259 | onmousemove: function(element, callback, context) { 260 | element.addEventListener("mousemove", function(e) { 261 | var pos = eveline.mousePosition(e, element); 262 | callback.call(context, pos.x * context.iscale | 0, pos.y * context.iscale | 0); 263 | }); 264 | 265 | return this; 266 | }, 267 | 268 | onmousedown: function(element, callback, context) { 269 | 270 | element.addEventListener("mousedown", function(e) { 271 | var pos = eveline.mousePosition(e, element); 272 | context.mouseStart = { 273 | x: pos.x * context.iscale | 0, 274 | y: pos.y * context.iscale | 0 275 | }; 276 | callback.call(context, pos.x * context.iscale | 0, pos.y * context.iscale | 0, e.button); 277 | }); 278 | 279 | return this; 280 | }, 281 | 282 | onmouseup: function(element, callback, context) { 283 | 284 | element.addEventListener("mouseup", function(e) { 285 | var pos = eveline.mousePosition(e, element); 286 | callback.call(context, pos.x * context.iscale | 0, pos.y * context.iscale | 0, e.button); 287 | }); 288 | 289 | return this; 290 | }, 291 | 292 | ontouchmove: function(element, callback, context) { 293 | element.addEventListener("touchmove", function(e) { 294 | if (context.preventMultitouch && e.targetTouches.length > 1) return; 295 | e.preventDefault(); 296 | var pos = eveline.mousePosition(e, element); 297 | callback.call(context, pos.x * context.iscale | 0, pos.y * context.iscale | 0); 298 | }); 299 | }, 300 | 301 | ontouchstart: function(element, callback, context) { 302 | element.addEventListener("touchstart", function(e) { 303 | if (context.preventMultitouch && e.targetTouches.length > 0) return; 304 | 305 | var pos = eveline.mousePosition(e, element); 306 | context.mouseStart = { 307 | x: pos.x * context.iscale | 0, 308 | y: pos.y * context.iscale | 0 309 | }; 310 | callback.call(context, pos.x * context.iscale | 0, pos.y * context.iscale | 0, e.button); 311 | }); 312 | }, 313 | 314 | ontouchend: function(element, callback, context) { 315 | element.addEventListener("touchend", function(e) { 316 | if (context.preventMultitouch && e.targetTouches.length > 0) return; 317 | var pos = eveline.mousePosition(e, element); 318 | callback.call(context, pos.x * context.iscale | 0, pos.y * context.iscale | 0, e.button); 319 | }); 320 | }, 321 | 322 | onclick: function(element, callback, context) { 323 | this.onmouseup(element, function(x, y) { 324 | if (eveline.distance(x, y, context.mouseStart.x, context.mouseStart.y) < 5) { 325 | callback.call(context, x * context.iscale | 0, y * context.iscale | 0); 326 | } 327 | }, context); 328 | }, 329 | 330 | onkeydown: function(element, callback, context) { 331 | 332 | document.addEventListener("keydown", function(e) { 333 | if (e.which >= 48 && e.which <= 90) var keyName = String.fromCharCode(e.which).toLowerCase(); 334 | else var keyName = eveline.keycodes[e.which]; 335 | callback.call(context, keyName); 336 | }); 337 | return this; 338 | }, 339 | 340 | onkeyup: function(element, callback, context) { 341 | 342 | document.addEventListener("keyup", function(e) { 343 | if (e.which >= 48 && e.which <= 90) var keyName = String.fromCharCode(e.which).toLowerCase(); 344 | else var keyName = eveline.keycodes[e.which]; 345 | callback.call(context, keyName); 346 | }); 347 | return this; 348 | }, 349 | 350 | ongamepaddown: function(element, callback, context) { 351 | 352 | eveline.addEventListener("gamepaddown", function(button, gamepad) { 353 | callback.call(context, button, gamepad); 354 | }); 355 | 356 | return this; 357 | }, 358 | 359 | ongamepadup: function(element, callback, context) { 360 | 361 | eveline.addEventListener("gamepadup", function(button, gamepad) { 362 | callback.call(context, button, gamepad); 363 | }); 364 | 365 | return this; 366 | }, 367 | 368 | ongamepadmove: function(element, callback, context) { 369 | 370 | eveline.addEventListener("gamepadmove", function(x, y, gamepad) { 371 | callback.call(context, x, y, gamepad); 372 | }); 373 | 374 | return this; 375 | }, 376 | 377 | 378 | onresize: function(element, callback, context) { 379 | window.addEventListener("resize", function() { 380 | callback.call(context, window.innerWidth, window.innerHeight); 381 | }); 382 | 383 | // callback.call(context, window.innerWidth, window.innerHeight); 384 | 385 | return this; 386 | }, 387 | 388 | ondropimage: function(element, callback, context) { 389 | 390 | document.addEventListener('drop', function(e) { 391 | e.stopPropagation(); 392 | e.preventDefault(); 393 | 394 | var file = e.dataTransfer.files[0]; 395 | 396 | if (!(/image/i).test(file.type)) return false; 397 | var reader = new FileReader(); 398 | 399 | reader.onload = function(e) { 400 | var image = new Image; 401 | 402 | image.onload = function() { 403 | callback.call(context, this); 404 | }; 405 | 406 | image.src = e.target.result; 407 | }; 408 | 409 | reader.readAsDataURL(file); 410 | 411 | }); 412 | 413 | document.addEventListener("dragover", function(e) { 414 | e.preventDefault(); 415 | }); 416 | 417 | return this; 418 | }, 419 | 420 | fitToScreen: function(element, context) { 421 | var scale = window.innerWidth / context.width; 422 | 423 | if (context.height * scale > window.innerHeight) { 424 | scale = window.innerHeight / context.height; 425 | } 426 | 427 | // scale = Math.min(1, scale); 428 | 429 | context.scale = scale; 430 | context.iscale = 1 / scale; 431 | 432 | var scaleCSSText = "scale(" + scale + "," + scale + ")"; 433 | 434 | eveline.extend(element.style, { 435 | "-webkit-transform-origin": "top left", 436 | "-webkit-transform": scaleCSSText, 437 | "-moz-transform-origin": "top left", 438 | "-moz-transform": scaleCSSText, 439 | "-ms-transform-origin": "top left", 440 | "-ms-transform": scaleCSSText, 441 | "transformOrigin": "top left", 442 | "transform": scaleCSSText, 443 | "left": ((window.innerWidth / 2 - (context.width * context.scale) / 2) | 0) + "px", 444 | "top": ((window.innerHeight / 2 - (context.height * context.scale) / 2) | 0) + "px" 445 | }); 446 | }, 447 | 448 | }); 449 | 450 | if (typeof define === "function" && define.amd) { 451 | define([], function() { 452 | return eveline; 453 | }); 454 | } 455 | 456 | })(window); -------------------------------------------------------------------------------- /script/vendor/canvasquery.js: -------------------------------------------------------------------------------- 1 | /* 2 | Canvas Query 0.9.0 3 | http://canvasquery.com 4 | (c) 2012-2013 http://rezoner.net 5 | Canvas Query may be freely distributed under the MIT license. 6 | */ 7 | 8 | (function(window, undefined) { 9 | 10 | var MOBILE = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent); 11 | 12 | 13 | window.requestAnimationFrame = (function() { 14 | return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { 15 | window.setTimeout(callback, 1000 / 60); 16 | }; 17 | })(); 18 | 19 | 20 | var $ = function(selector) { 21 | if (arguments.length === 0) { 22 | var canvas = $.createCanvas(window.innerWidth, window.innerHeight); 23 | window.addEventListener("resize", function() { 24 | // canvas.width = window.innerWidth; 25 | // canvas.height = window.innerHeight; 26 | }); 27 | } else if (typeof selector === "string") { 28 | var canvas = document.querySelector(selector); 29 | } else if (typeof selector === "number") { 30 | var canvas = $.createCanvas(arguments[0], arguments[1]); 31 | } else if (selector instanceof Image || selector instanceof HTMLImageElement) { 32 | var canvas = $.createCanvas(selector); 33 | } else if (selector instanceof $.Wrapper) { 34 | return selector; 35 | } else { 36 | var canvas = selector; 37 | } 38 | 39 | return new $.Wrapper(canvas); 40 | } 41 | 42 | $.extend = function() { 43 | for (var i = 1; i < arguments.length; i++) { 44 | for (var j in arguments[i]) { 45 | arguments[0][j] = arguments[i][j]; 46 | } 47 | } 48 | 49 | return arguments[0]; 50 | }; 51 | 52 | $.augment = function() { 53 | for (var i = 1; i < arguments.length; i++) { 54 | _.extend(arguments[0], arguments[i]); 55 | arguments[i](arguments[0]); 56 | } 57 | }; 58 | 59 | $.distance = function(x1, y1, x2, y2) { 60 | if (arguments.length > 2) { 61 | var dx = x1 - x2; 62 | var dy = y1 - y2; 63 | 64 | return Math.sqrt(dx * dx + dy * dy); 65 | } else { 66 | return Math.abs(x1 - y1); 67 | } 68 | }; 69 | 70 | $.extend($, { 71 | 72 | keycodes: { 73 | 37: "left", 74 | 38: "up", 75 | 39: "right", 76 | 40: "down", 77 | 45: "insert", 78 | 46: "delete", 79 | 8: "backspace", 80 | 9: "tab", 81 | 13: "enter", 82 | 16: "shift", 83 | 17: "ctrl", 84 | 18: "alt", 85 | 19: "pause", 86 | 20: "capslock", 87 | 27: "escape", 88 | 32: "space", 89 | 33: "pageup", 90 | 34: "pagedown", 91 | 35: "end", 92 | 112: "f1", 93 | 113: "f2", 94 | 114: "f3", 95 | 115: "f4", 96 | 116: "f5", 97 | 117: "f6", 98 | 118: "f7", 99 | 119: "f8", 100 | 120: "f9", 101 | 121: "f10", 102 | 122: "f11", 103 | 123: "f12", 104 | 144: "numlock", 105 | 145: "scrolllock", 106 | 186: "semicolon", 107 | 187: "equal", 108 | 188: "comma", 109 | 189: "dash", 110 | 190: "period", 111 | 191: "slash", 112 | 192: "graveaccent", 113 | 219: "openbracket", 114 | 220: "backslash", 115 | 221: "closebraket", 116 | 222: "singlequote" 117 | }, 118 | 119 | cleanArray: function(array, property) { 120 | 121 | var lastArgument = arguments[arguments.length - 1]; 122 | var isLastArgumentFunction = typeof lastArgument === "function"; 123 | 124 | for (var i = 0, len = array.length; i < len; i++) { 125 | if (array[i] === null || (property && array[i][property])) { 126 | if (isLastArgumentFunction) { 127 | lastArgument(array[i]); 128 | } 129 | array.splice(i--, 1); 130 | len--; 131 | } 132 | } 133 | }, 134 | 135 | specialBlendFunctions: ["color", "value", "hue", "saturation"], 136 | 137 | blendFunctions: { 138 | normal: function(a, b) { 139 | return b; 140 | }, 141 | 142 | overlay: function(a, b) { 143 | a /= 255; 144 | b /= 255; 145 | var result = 0; 146 | 147 | if (a < 0.5) result = 2 * a * b; 148 | else result = 1 - 2 * (1 - a) * (1 - b); 149 | 150 | return Math.min(255, Math.max(0, result * 255 | 0)); 151 | }, 152 | 153 | hardLight: function(a, b) { 154 | return $.blendFunctions.overlay(b, a); 155 | }, 156 | 157 | softLight: function(a, b) { 158 | a /= 255; 159 | b /= 255; 160 | 161 | var v = (1 - 2 * b) * (a * a) + 2 * b * a; 162 | return $.limitValue(v * 255, 0, 255); 163 | }, 164 | 165 | dodge: function(a, b) { 166 | return Math.min(256 * a / (255 - b + 1), 255); 167 | }, 168 | 169 | burn: function(a, b) { 170 | return 255 - Math.min(256 * (255 - a) / (b + 1), 255); 171 | }, 172 | 173 | multiply: function(a, b) { 174 | return b * a / 255; 175 | }, 176 | 177 | divide: function(a, b) { 178 | return Math.min(256 * a / (b + 1), 255); 179 | }, 180 | 181 | screen: function(a, b) { 182 | return 255 - (255 - b) * (255 - a) / 255; 183 | }, 184 | 185 | grainExtract: function(a, b) { 186 | return $.limitValue(a - b + 128, 0, 255); 187 | }, 188 | 189 | grainMerge: function(a, b) { 190 | return $.limitValue(a + b - 128, 0, 255); 191 | }, 192 | 193 | difference: function(a, b) { 194 | return Math.abs(a - b); 195 | }, 196 | 197 | addition: function(a, b) { 198 | return Math.min(a + b, 255); 199 | }, 200 | 201 | substract: function(a, b) { 202 | return Math.max(a - b, 0); 203 | }, 204 | 205 | darkenOnly: function(a, b) { 206 | return Math.min(a, b); 207 | }, 208 | 209 | lightenOnly: function(a, b) { 210 | return Math.max(a, b); 211 | }, 212 | 213 | color: function(a, b) { 214 | var aHSL = $.rgbToHsl(a); 215 | var bHSL = $.rgbToHsl(b); 216 | 217 | return $.hslToRgb(bHSL[0], bHSL[1], aHSL[2]); 218 | }, 219 | 220 | hue: function(a, b) { 221 | var aHSV = $.rgbToHsv(a); 222 | var bHSV = $.rgbToHsv(b); 223 | 224 | if (!bHSV[1]) return $.hsvToRgb(aHSV[0], aHSV[1], aHSV[2]); 225 | else return $.hsvToRgb(bHSV[0], aHSV[1], aHSV[2]); 226 | }, 227 | 228 | value: function(a, b) { 229 | var aHSV = $.rgbToHsv(a); 230 | var bHSV = $.rgbToHsv(b); 231 | 232 | return $.hsvToRgb(aHSV[0], aHSV[1], bHSV[2]); 233 | }, 234 | 235 | saturation: function(a, b) { 236 | var aHSV = $.rgbToHsv(a); 237 | var bHSV = $.rgbToHsv(b); 238 | 239 | return $.hsvToRgb(aHSV[0], bHSV[1], aHSV[2]); 240 | } 241 | }, 242 | 243 | blend: function(below, above, mode, mix) { 244 | if (typeof mix === "undefined") mix = 1; 245 | 246 | var below = $(below); 247 | var above = $(above); 248 | 249 | var belowCtx = below.context; 250 | var aboveCtx = above.context; 251 | 252 | var belowData = belowCtx.getImageData(0, 0, below.canvas.width, below.canvas.height); 253 | var aboveData = aboveCtx.getImageData(0, 0, above.canvas.width, above.canvas.height); 254 | 255 | var belowPixels = belowData.data; 256 | var abovePixels = aboveData.data; 257 | 258 | var imageData = this.createImageData(below.canvas.width, below.canvas.height); 259 | var pixels = imageData.data; 260 | 261 | var blendingFunction = $.blendFunctions[mode]; 262 | 263 | if ($.specialBlendFunctions.indexOf(mode) !== -1) { 264 | for (var i = 0, len = belowPixels.length; i < len; i += 4) { 265 | var rgb = blendingFunction([belowPixels[i + 0], belowPixels[i + 1], belowPixels[i + 2]], [abovePixels[i + 0], abovePixels[i + 1], abovePixels[i + 2]]); 266 | 267 | pixels[i + 0] = belowPixels[i + 0] + (rgb[0] - belowPixels[i + 0]) * mix; 268 | pixels[i + 1] = belowPixels[i + 1] + (rgb[1] - belowPixels[i + 1]) * mix; 269 | pixels[i + 2] = belowPixels[i + 2] + (rgb[2] - belowPixels[i + 2]) * mix; 270 | 271 | pixels[i + 3] = belowPixels[i + 3]; 272 | } 273 | } else { 274 | 275 | for (var i = 0, len = belowPixels.length; i < len; i += 4) { 276 | var r = blendingFunction(belowPixels[i + 0], abovePixels[i + 0]); 277 | var g = blendingFunction(belowPixels[i + 1], abovePixels[i + 1]); 278 | var b = blendingFunction(belowPixels[i + 2], abovePixels[i + 2]); 279 | 280 | pixels[i + 0] = belowPixels[i + 0] + (r - belowPixels[i + 0]) * mix; 281 | pixels[i + 1] = belowPixels[i + 1] + (g - belowPixels[i + 1]) * mix; 282 | pixels[i + 2] = belowPixels[i + 2] + (b - belowPixels[i + 2]) * mix; 283 | 284 | pixels[i + 3] = belowPixels[i + 3]; 285 | } 286 | } 287 | 288 | below.context.putImageData(imageData, 0, 0); 289 | 290 | return below; 291 | }, 292 | 293 | wrapValue: function(value, min, max) { 294 | var d = Math.abs(max - min); 295 | return min + (value - min) % d; 296 | }, 297 | 298 | limitValue: function(value, min, max) { 299 | return value < min ? min : value > max ? max : value; 300 | }, 301 | 302 | mix: function(a, b, ammount) { 303 | return a + (b - a) * ammount; 304 | }, 305 | 306 | hexToRgb: function(hex) { 307 | if (hex.length === 7) return ['0x' + hex[1] + hex[2] | 0, '0x' + hex[3] + hex[4] | 0, '0x' + hex[5] + hex[6] | 0]; 308 | else return ['0x' + hex[1] | 0, '0x' + hex[2], '0x' + hex[3] | 0]; 309 | }, 310 | 311 | rgbToHex: function(r, g, b) { 312 | return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1, 7); 313 | }, 314 | 315 | /* author: http://mjijackson.com/ */ 316 | 317 | rgbToHsl: function(r, g, b) { 318 | 319 | if (r instanceof Array) { 320 | b = r[2]; 321 | g = r[1]; 322 | r = r[0]; 323 | } 324 | 325 | r /= 255, g /= 255, b /= 255; 326 | var max = Math.max(r, g, b), 327 | min = Math.min(r, g, b); 328 | var h, s, l = (max + min) / 2; 329 | 330 | if (max == min) { 331 | h = s = 0; // achromatic 332 | } else { 333 | var d = max - min; 334 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 335 | switch (max) { 336 | case r: 337 | h = (g - b) / d + (g < b ? 6 : 0); 338 | break; 339 | case g: 340 | h = (b - r) / d + 2; 341 | break; 342 | case b: 343 | h = (r - g) / d + 4; 344 | break; 345 | } 346 | h /= 6; 347 | } 348 | 349 | return [h, s, l]; 350 | }, 351 | 352 | /* author: http://mjijackson.com/ */ 353 | 354 | hslToRgb: function(h, s, l) { 355 | var r, g, b; 356 | 357 | if (s == 0) { 358 | r = g = b = l; // achromatic 359 | } else { 360 | function hue2rgb(p, q, t) { 361 | if (t < 0) t += 1; 362 | if (t > 1) t -= 1; 363 | if (t < 1 / 6) return p + (q - p) * 6 * t; 364 | if (t < 1 / 2) return q; 365 | if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; 366 | return p; 367 | } 368 | 369 | var q = l < 0.5 ? l * (1 + s) : l + s - l * s; 370 | var p = 2 * l - q; 371 | r = hue2rgb(p, q, h + 1 / 3); 372 | g = hue2rgb(p, q, h); 373 | b = hue2rgb(p, q, h - 1 / 3); 374 | } 375 | 376 | return [r * 255 | 0, g * 255 | 0, b * 255 | 0]; 377 | }, 378 | 379 | rgbToHsv: function(r, g, b) { 380 | if (r instanceof Array) { 381 | b = r[2]; 382 | g = r[1]; 383 | r = r[0]; 384 | } 385 | 386 | r = r / 255, g = g / 255, b = b / 255; 387 | var max = Math.max(r, g, b), 388 | min = Math.min(r, g, b); 389 | var h, s, v = max; 390 | 391 | var d = max - min; 392 | s = max == 0 ? 0 : d / max; 393 | 394 | if (max == min) { 395 | h = 0; // achromatic 396 | } else { 397 | switch (max) { 398 | case r: 399 | h = (g - b) / d + (g < b ? 6 : 0); 400 | break; 401 | case g: 402 | h = (b - r) / d + 2; 403 | break; 404 | case b: 405 | h = (r - g) / d + 4; 406 | break; 407 | } 408 | h /= 6; 409 | } 410 | 411 | return [h, s, v]; 412 | }, 413 | 414 | hsvToRgb: function(h, s, v) { 415 | var r, g, b; 416 | 417 | var i = Math.floor(h * 6); 418 | var f = h * 6 - i; 419 | var p = v * (1 - s); 420 | var q = v * (1 - f * s); 421 | var t = v * (1 - (1 - f) * s); 422 | 423 | switch (i % 6) { 424 | case 0: 425 | r = v, g = t, b = p; 426 | break; 427 | case 1: 428 | r = q, g = v, b = p; 429 | break; 430 | case 2: 431 | r = p, g = v, b = t; 432 | break; 433 | case 3: 434 | r = p, g = q, b = v; 435 | break; 436 | case 4: 437 | r = t, g = p, b = v; 438 | break; 439 | case 5: 440 | r = v, g = p, b = q; 441 | break; 442 | } 443 | 444 | return [r * 255 | 0, g * 255 | 0, b * 255 | 0]; 445 | }, 446 | 447 | color: function() { 448 | var result = new $.Color(); 449 | result.parse(arguments[0], arguments[1]); 450 | return result; 451 | }, 452 | 453 | createCanvas: function(width, height) { 454 | var result = document.createElement("canvas"); 455 | 456 | if (arguments[0] instanceof Image || arguments[0] instanceof HTMLImageElement) { 457 | var image = arguments[0]; 458 | result.width = image.width; 459 | result.height = image.height; 460 | result.getContext("2d").drawImage(image, 0, 0); 461 | } else { 462 | result.width = width; 463 | result.height = height; 464 | } 465 | 466 | return result; 467 | }, 468 | 469 | createImageData: function(width, height) { 470 | return document.createElement("Canvas").getContext("2d").createImageData(width, height); 471 | }, 472 | 473 | 474 | /* https://gist.github.com/3781251 */ 475 | 476 | mousePosition: function(event) { 477 | var totalOffsetX = 0, 478 | totalOffsetY = 0, 479 | coordX = 0, 480 | coordY = 0, 481 | currentElement = event.target || event.srcElement, 482 | mouseX = 0, 483 | mouseY = 0; 484 | 485 | // Traversing the parents to get the total offset 486 | do { 487 | totalOffsetX += currentElement.offsetLeft; 488 | totalOffsetY += currentElement.offsetTop; 489 | } 490 | while ((currentElement = currentElement.offsetParent)); 491 | // Set the event to first touch if using touch-input 492 | if (event.changedTouches && event.changedTouches[0] !== undefined) { 493 | event = event.changedTouches[0]; 494 | } 495 | // Use pageX to get the mouse coordinates 496 | if (event.pageX || event.pageY) { 497 | mouseX = event.pageX; 498 | mouseY = event.pageY; 499 | } 500 | // IE8 and below doesn't support event.pageX 501 | else if (event.clientX || event.clientY) { 502 | mouseX = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; 503 | mouseY = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; 504 | } 505 | // Subtract the offset from the mouse coordinates 506 | coordX = mouseX - totalOffsetX; 507 | coordY = mouseY - totalOffsetY; 508 | 509 | return { 510 | x: coordX, 511 | y: coordY 512 | }; 513 | } 514 | }); 515 | 516 | $.Wrapper = function(canvas) { 517 | this.context = canvas.getContext("2d"); 518 | this.canvas = canvas; 519 | } 520 | 521 | $.Wrapper.prototype = { 522 | appendTo: function(selector) { 523 | if (typeof selector === "object") { 524 | var element = selector; 525 | } else { 526 | var element = document.querySelector(selector); 527 | } 528 | 529 | element.appendChild(this.canvas); 530 | 531 | return this; 532 | }, 533 | 534 | blendOn: function(what, mode, mix) { 535 | $.blend(what, this, mode, mix); 536 | 537 | return this; 538 | }, 539 | 540 | blend: function(what, mode, mix) { 541 | if (typeof what === "string") { 542 | var color = what; 543 | what = $($.createCanvas(this.canvas.width, this.canvas.height)); 544 | what.fillStyle(color).fillRect(0, 0, this.canvas.width, this.canvas.height); 545 | } 546 | 547 | $.blend(this, what, mode, mix); 548 | 549 | return this; 550 | }, 551 | 552 | circle: function(x, y, r) { 553 | this.context.arc(x, y, r, 0, Math.PI * 2); 554 | return this; 555 | }, 556 | 557 | crop: function(x, y, w, h) { 558 | 559 | var canvas = $.createCanvas(w, h); 560 | var context = canvas.getContext("2d"); 561 | 562 | context.drawImage(this.canvas, x, y, w, h, 0, 0, w, h); 563 | this.canvas.width = w; 564 | this.canvas.height = h; 565 | this.clear(); 566 | this.context.drawImage(canvas, 0, 0); 567 | 568 | return this; 569 | }, 570 | 571 | set: function(properties) { 572 | $.extend(this.context, properties); 573 | 574 | /* for(var key in properties) { 575 | this[key](properties[key]); 576 | } 577 | */ 578 | 579 | }, 580 | 581 | resize: function(width, height) { 582 | var w = width, 583 | h = height; 584 | 585 | if (arguments.length === 1) { 586 | w = arguments[0] * this.canvas.width | 0; 587 | h = arguments[0] * this.canvas.height | 0; 588 | } else { 589 | 590 | if (height === null) { 591 | if (this.canvas.width > width) { 592 | h = this.canvas.height * (width / this.canvas.width) | 0; 593 | w = width; 594 | } else { 595 | w = this.canvas.width; 596 | h = this.canvas.height; 597 | } 598 | } else if (width === null) { 599 | if (this.canvas.width > width) { 600 | w = this.canvas.width * (height / this.canvas.height) | 0; 601 | h = height; 602 | } else { 603 | w = this.canvas.width; 604 | h = this.canvas.height; 605 | } 606 | } 607 | } 608 | 609 | var $resized = $(w, h).drawImage(this.canvas, 0, 0, this.canvas.width, this.canvas.height, 0, 0, w, h); 610 | this.canvas = $resized.canvas; 611 | this.context = $resized.context; 612 | 613 | return this; 614 | }, 615 | 616 | 617 | trim: function(color, changes) { 618 | var transparent; 619 | 620 | if (color) { 621 | color = $.color(color).toArray(); 622 | transparent = !color[3]; 623 | } else transparent = true; 624 | 625 | var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 626 | var sourcePixels = sourceData.data; 627 | 628 | var bound = [this.canvas.width, this.canvas.height, 0, 0]; 629 | 630 | for (var i = 0, len = sourcePixels.length; i < len; i += 4) { 631 | if (transparent) { 632 | if (!sourcePixels[i + 3]) continue; 633 | } else if (sourcePixels[i + 0] === color[0] && sourcePixels[i + 1] === color[1] && sourcePixels[i + 2] === color[2]) continue; 634 | var x = (i / 4 | 0) % this.canvas.width | 0; 635 | var y = (i / 4 | 0) / this.canvas.width | 0; 636 | 637 | if (x < bound[0]) bound[0] = x; 638 | if (x > bound[2]) bound[2] = x; 639 | 640 | if (y < bound[1]) bound[1] = y; 641 | if (y > bound[3]) bound[3] = y; 642 | } 643 | 644 | if (bound[2] === 0 || bound[3] === 0) { 645 | 646 | } else { 647 | if (changes) { 648 | changes.left = bound[0]; 649 | changes.top = bound[1]; 650 | changes.width = bound[2] - bound[0]; 651 | changes.height = bound[3] - bound[1]; 652 | } 653 | 654 | this.crop(bound[0], bound[1], bound[2] - bound[0] + 1, bound[3] - bound[1] + 1); 655 | } 656 | 657 | return this; 658 | }, 659 | 660 | resizePixel: function(pixelSize) { 661 | 662 | var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 663 | var sourcePixels = sourceData.data; 664 | var canvas = document.createElement("canvas"); 665 | var context = canvas.context = canvas.getContext("2d"); 666 | 667 | canvas.width = this.canvas.width * pixelSize | 0; 668 | canvas.height = this.canvas.height * pixelSize | 0; 669 | 670 | for (var i = 0, len = sourcePixels.length; i < len; i += 4) { 671 | if (!sourcePixels[i + 3]) continue; 672 | context.fillStyle = $.rgbToHex(sourcePixels[i + 0], sourcePixels[i + 1], sourcePixels[i + 2]); 673 | 674 | var x = (i / 4) % this.canvas.width; 675 | var y = (i / 4) / this.canvas.width | 0; 676 | 677 | context.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize); 678 | } 679 | 680 | this.canvas.width = canvas.width; 681 | this.canvas.height = canvas.height; 682 | this.clear().drawImage(canvas, 0, 0); 683 | 684 | return this; 685 | 686 | /* this very clever method is working only under Chrome */ 687 | 688 | var x = 0, 689 | y = 0; 690 | 691 | var canvas = document.createElement("canvas"); 692 | var context = canvas.context = canvas.getContext("2d"); 693 | 694 | canvas.width = this.canvas.width * pixelSize | 0; 695 | canvas.height = this.canvas.height * pixelSize | 0; 696 | 697 | while (x < this.canvas.width) { 698 | y = 0; 699 | while (y < this.canvas.height) { 700 | context.drawImage(this.canvas, x, y, 1, 1, x * pixelSize, y * pixelSize, pixelSize, pixelSize); 701 | y++; 702 | } 703 | x++; 704 | } 705 | 706 | this.canvas = canvas; 707 | this.context = context; 708 | 709 | return this; 710 | }, 711 | 712 | 713 | matchPalette: function(palette) { 714 | var imgData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 715 | 716 | var rgbPalette = []; 717 | for (var i = 0; i < palette.length; i++) { 718 | rgbPalette.push($.color(palette[i])); 719 | } 720 | 721 | 722 | for (var i = 0; i < imgData.data.length; i += 4) { 723 | var difList = []; 724 | for (var j = 0; j < rgbPalette.length; j++) { 725 | var rgbVal = rgbPalette[j]; 726 | var rDif = Math.abs(imgData.data[i] - rgbVal[0]), 727 | gDif = Math.abs(imgData.data[i + 1] - rgbVal[1]), 728 | bDif = Math.abs(imgData.data[i + 2] - rgbVal[2]); 729 | difList.push(rDif + gDif + bDif); 730 | } 731 | 732 | var closestMatch = 0; 733 | for (var j = 0; j < palette.length; j++) { 734 | if (difList[j] < difList[closestMatch]) { 735 | closestMatch = j; 736 | } 737 | } 738 | 739 | var paletteRgb = cq.hexToRgb(palette[closestMatch]); 740 | imgData.data[i] = paletteRgb[0]; 741 | imgData.data[i + 1] = paletteRgb[1]; 742 | imgData.data[i + 2] = paletteRgb[2]; 743 | } 744 | 745 | this.context.putImageData(imgData, 0, 0); 746 | 747 | return this; 748 | }, 749 | 750 | getPalette: function() { 751 | var palette = []; 752 | var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 753 | var sourcePixels = sourceData.data; 754 | 755 | for (var i = 0, len = sourcePixels.length; i < len; i += 4) { 756 | if (sourcePixels[i + 3]) { 757 | var hex = $.rgbToHex(sourcePixels[i + 0], sourcePixels[i + 1], sourcePixels[i + 2]); 758 | if (palette.indexOf(hex) === -1) palette.push(hex); 759 | } 760 | } 761 | 762 | return palette; 763 | }, 764 | 765 | pixelize: function(size) { 766 | if (!size) return this; 767 | size = size || 4; 768 | 769 | var mozImageSmoothingEnabled = this.context.mozImageSmoothingEnabled; 770 | var webkitImageSmoothingEnabled = this.context.webkitImageSmoothingEnabled; 771 | 772 | this.context.mozImageSmoothingEnabled = false; 773 | this.context.webkitImageSmoothingEnabled = false; 774 | 775 | var scale = (this.canvas.width / size) / this.canvas.width; 776 | 777 | var temp = cq(this.canvas.width, this.canvas.height); 778 | 779 | temp.drawImage(this.canvas, 0, 0, this.canvas.width, this.canvas.height, 0, 0, this.canvas.width * scale | 0, this.canvas.height * scale | 0); 780 | this.clear().drawImage(temp.canvas, 0, 0, this.canvas.width * scale | 0, this.canvas.height * scale | 0, 0, 0, this.canvas.width, this.canvas.height); 781 | 782 | this.context.mozImageSmoothingEnabled = mozImageSmoothingEnabled; 783 | this.context.webkitImageSmoothingEnabled = webkitImageSmoothingEnabled; 784 | 785 | return this; 786 | }, 787 | 788 | colorToMask: function(color, inverted) { 789 | color = $.color(color).toArray(); 790 | var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 791 | var sourcePixels = sourceData.data; 792 | 793 | var mask = []; 794 | 795 | for (var i = 0, len = sourcePixels.length; i < len; i += 4) { 796 | if (sourcePixels[i + 0] == color[0] && sourcePixels[i + 1] == color[1] && sourcePixels[i + 2] == color[2]) mask.push(inverted || false); 797 | else mask.push(!inverted); 798 | } 799 | 800 | return mask; 801 | }, 802 | 803 | grayscaleToMask: function(color) { 804 | color = $.color(color).toArray(); 805 | var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 806 | var sourcePixels = sourceData.data; 807 | 808 | var mask = []; 809 | 810 | for (var i = 0, len = sourcePixels.length; i < len; i += 4) { 811 | mask.push((sourcePixels[i + 0] + sourcePixels[i + 1] + sourcePixels[i + 2]) / 3 | 0); 812 | } 813 | 814 | return mask; 815 | }, 816 | 817 | grayscaleToAlpha: function() { 818 | var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 819 | var sourcePixels = sourceData.data; 820 | 821 | var mask = []; 822 | 823 | for (var i = 0, len = sourcePixels.length; i < len; i += 4) { 824 | sourcePixels[i + 3] = (sourcePixels[i + 0] + sourcePixels[i + 1] + sourcePixels[i + 2]) / 3 | 0; 825 | 826 | sourcePixels[i + 0] = sourcePixels[i + 1] = sourcePixels[i + 2] = 255; 827 | } 828 | 829 | this.context.putImageData(sourceData, 0, 0); 830 | 831 | return this; 832 | }, 833 | 834 | applyMask: function(mask) { 835 | var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 836 | var sourcePixels = sourceData.data; 837 | 838 | var mode = typeof mask[0] === "boolean" ? "bool" : "byte"; 839 | 840 | for (var i = 0, len = sourcePixels.length; i < len; i += 4) { 841 | var value = mask[i / 4]; 842 | 843 | if (mode === "bool") sourcePixels[i + 3] = 255 * value | 0; 844 | else { 845 | sourcePixels[i + 3] = value | 0; 846 | } 847 | } 848 | 849 | 850 | this.context.putImageData(sourceData, 0, 0); 851 | return this; 852 | }, 853 | 854 | fillMask: function(mask) { 855 | 856 | var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 857 | var sourcePixels = sourceData.data; 858 | 859 | var maskType = typeof mask[0] === "boolean" ? "bool" : "byte"; 860 | var colorMode = arguments.length === 2 ? "normal" : "gradient"; 861 | 862 | var color = $.color(arguments[1]); 863 | if (colorMode === "gradient") colorB = $.color(arguments[2]); 864 | 865 | for (var i = 0, len = sourcePixels.length; i < len; i += 4) { 866 | var value = mask[i / 4]; 867 | 868 | if (maskType === "byte") value /= 255; 869 | 870 | if (colorMode === "normal") { 871 | if (value) { 872 | sourcePixels[i + 0] = color[0] | 0; 873 | sourcePixels[i + 1] = color[1] | 0; 874 | sourcePixels[i + 2] = color[2] | 0; 875 | sourcePixels[i + 3] = value * 255 | 0; 876 | } 877 | } else { 878 | sourcePixels[i + 0] = color[0] + (colorB[0] - color[0]) * value | 0; 879 | sourcePixels[i + 1] = color[1] + (colorB[1] - color[1]) * value | 0; 880 | sourcePixels[i + 2] = color[2] + (colorB[2] - color[2]) * value | 0; 881 | sourcePixels[i + 3] = 255; 882 | } 883 | } 884 | 885 | this.context.putImageData(sourceData, 0, 0); 886 | return this; 887 | }, 888 | 889 | clear: function(color) { 890 | if (color) { 891 | this.context.fillStyle = color; 892 | this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); 893 | } else { 894 | this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); 895 | } 896 | 897 | return this; 898 | }, 899 | 900 | clone: function() { 901 | var result = $.createCanvas(this.canvas.width, this.canvas.height); 902 | result.getContext("2d").drawImage(this.canvas, 0, 0); 903 | return $(result); 904 | }, 905 | 906 | fillStyle: function(fillStyle) { 907 | this.context.fillStyle = fillStyle; 908 | return this; 909 | }, 910 | 911 | strokeStyle: function(strokeStyle) { 912 | this.context.strokeStyle = strokeStyle; 913 | return this; 914 | }, 915 | 916 | gradientText: function(text, x, y, maxWidth, gradient) { 917 | 918 | var words = text.split(" "); 919 | 920 | var h = this.font().match(/\d+/g)[0] * 2; 921 | 922 | var ox = 0; 923 | var oy = 0; 924 | 925 | if (maxWidth) { 926 | var line = 0; 927 | var lines = [""]; 928 | 929 | for (var i = 0; i < words.length; i++) { 930 | var word = words[i] + " "; 931 | var wordWidth = this.context.measureText(word).width; 932 | 933 | if (ox + wordWidth > maxWidth) { 934 | lines[++line] = ""; 935 | ox = 0; 936 | } 937 | 938 | lines[line] += word; 939 | 940 | ox += wordWidth; 941 | } 942 | } else var lines = [text]; 943 | 944 | for (var i = 0; i < lines.length; i++) { 945 | var oy = y + i * h * 0.6 | 0; 946 | var lingrad = this.context.createLinearGradient(0, oy, 0, oy + h * 0.6 | 0); 947 | 948 | for (var j = 0; j < gradient.length; j += 2) { 949 | lingrad.addColorStop(gradient[j], gradient[j + 1]); 950 | } 951 | 952 | var text = lines[i]; 953 | 954 | this.fillStyle(lingrad).fillText(text, x, oy); 955 | } 956 | 957 | return this; 958 | }, 959 | 960 | setHsl: function() { 961 | 962 | if (arguments.length === 1) { 963 | var args = arguments[0]; 964 | } else { 965 | var args = arguments; 966 | } 967 | 968 | var data = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 969 | var pixels = data.data; 970 | var r, g, b, a, h, s, l, hsl = [], 971 | newPixel = []; 972 | 973 | for (var i = 0, len = pixels.length; i < len; i += 4) { 974 | hsl = $.rgbToHsl(pixels[i + 0], pixels[i + 1], pixels[i + 2]); 975 | 976 | h = args[0] === null ? hsl[0] : $.limitValue(args[0], 0, 1); 977 | s = args[1] === null ? hsl[1] : $.limitValue(args[1], 0, 1); 978 | l = args[2] === null ? hsl[2] : $.limitValue(args[2], 0, 1); 979 | 980 | newPixel = $.hslToRgb(h, s, l); 981 | 982 | pixels[i + 0] = newPixel[0]; 983 | pixels[i + 1] = newPixel[1]; 984 | pixels[i + 2] = newPixel[2]; 985 | } 986 | 987 | this.context.putImageData(data, 0, 0); 988 | 989 | return this; 990 | }, 991 | 992 | shiftHsl: function() { 993 | 994 | if (arguments.length === 1) { 995 | var args = arguments[0]; 996 | } else { 997 | var args = arguments; 998 | } 999 | 1000 | var data = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 1001 | var pixels = data.data; 1002 | var r, g, b, a, h, s, l, hsl = [], 1003 | newPixel = []; 1004 | 1005 | for (var i = 0, len = pixels.length; i < len; i += 4) { 1006 | hsl = $.rgbToHsl(pixels[i + 0], pixels[i + 1], pixels[i + 2]); 1007 | 1008 | h = args[0] === null ? hsl[0] : $.wrapValue(hsl[0] + args[0], 0, 1); 1009 | s = args[1] === null ? hsl[1] : $.limitValue(hsl[1] + args[1], 0, 1); 1010 | l = args[2] === null ? hsl[2] : $.limitValue(hsl[2] + args[2], 0, 1); 1011 | 1012 | newPixel = $.hslToRgb(h, s, l); 1013 | 1014 | pixels[i + 0] = newPixel[0]; 1015 | pixels[i + 1] = newPixel[1]; 1016 | pixels[i + 2] = newPixel[2]; 1017 | } 1018 | 1019 | 1020 | this.context.putImageData(data, 0, 0); 1021 | 1022 | return this; 1023 | }, 1024 | 1025 | replaceHue: function(src, dst) { 1026 | 1027 | var data = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 1028 | var pixels = data.data; 1029 | var r, g, b, a, h, s, l, hsl = [], 1030 | newPixel = []; 1031 | 1032 | for (var i = 0, len = pixels.length; i < len; i += 4) { 1033 | hsl = $.rgbToHsl(pixels[i + 0], pixels[i + 1], pixels[i + 2]); 1034 | 1035 | if (Math.abs(hsl[0] - src) < 0.05) h = $.wrapValue(dst, 0, 1); 1036 | else h = hsl[0]; 1037 | 1038 | newPixel = $.hslToRgb(h, hsl[1], hsl[2]); 1039 | 1040 | pixels[i + 0] = newPixel[0]; 1041 | pixels[i + 1] = newPixel[1]; 1042 | pixels[i + 2] = newPixel[2]; 1043 | } 1044 | 1045 | this.context.putImageData(data, 0, 0); 1046 | 1047 | return this; 1048 | }, 1049 | 1050 | invert: function(src, dst) { 1051 | 1052 | var data = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); 1053 | var pixels = data.data; 1054 | var r, g, b, a, h, s, l, hsl = [], 1055 | newPixel = []; 1056 | 1057 | for (var i = 0, len = pixels.length; i < len; i += 4) { 1058 | pixels[i + 0] = 255 - pixels[i + 0]; 1059 | pixels[i + 1] = 255 - pixels[i + 1]; 1060 | pixels[i + 2] = 255 - pixels[i + 2]; 1061 | } 1062 | 1063 | this.context.putImageData(data, 0, 0); 1064 | 1065 | return this; 1066 | }, 1067 | 1068 | roundRect: function(x, y, width, height, radius) { 1069 | 1070 | this.beginPath(); 1071 | this.moveTo(x + radius, y); 1072 | this.lineTo(x + width - radius, y); 1073 | this.quadraticCurveTo(x + width, y, x + width, y + radius); 1074 | this.lineTo(x + width, y + height - radius); 1075 | this.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); 1076 | this.lineTo(x + radius, y + height); 1077 | this.quadraticCurveTo(x, y + height, x, y + height - radius); 1078 | this.lineTo(x, y + radius); 1079 | this.quadraticCurveTo(x, y, x + radius, y); 1080 | this.closePath(); 1081 | 1082 | return this; 1083 | }, 1084 | 1085 | wrappedText: function(text, x, y, maxWidth, newlineCallback) { 1086 | 1087 | var words = text.split(" "); 1088 | 1089 | var h = this.font().match(/\d+/g)[0] * 2; 1090 | 1091 | var ox = 0; 1092 | var oy = 0; 1093 | 1094 | if (maxWidth) { 1095 | var line = 0; 1096 | var lines = [""]; 1097 | 1098 | for (var i = 0; i < words.length; i++) { 1099 | var word = words[i] + " "; 1100 | var wordWidth = this.context.measureText(word).width; 1101 | 1102 | if (ox + wordWidth > maxWidth) { 1103 | lines[++line] = ""; 1104 | ox = 0; 1105 | } 1106 | 1107 | lines[line] += word; 1108 | 1109 | ox += wordWidth; 1110 | } 1111 | } else { 1112 | var lines = [text]; 1113 | } 1114 | 1115 | for (var i = 0; i < lines.length; i++) { 1116 | var oy = y + i * h * 0.6 | 0; 1117 | 1118 | var text = lines[i]; 1119 | 1120 | if (newlineCallback) newlineCallback.call(this, x, y + oy); 1121 | 1122 | this.fillText(text, x, oy); 1123 | } 1124 | 1125 | return this; 1126 | }, 1127 | 1128 | textBoundaries: function(text, maxWidth) { 1129 | var words = text.split(" "); 1130 | 1131 | var h = this.font().match(/\d+/g)[0] * 2; 1132 | 1133 | var ox = 0; 1134 | var oy = 0; 1135 | 1136 | if (maxWidth) { 1137 | var line = 0; 1138 | var lines = [""]; 1139 | 1140 | for (var i = 0; i < words.length; i++) { 1141 | var word = words[i] + " "; 1142 | var wordWidth = this.context.measureText(word).width; 1143 | 1144 | if (ox + wordWidth > maxWidth) { 1145 | lines[++line] = ""; 1146 | ox = 0; 1147 | } 1148 | 1149 | lines[line] += word; 1150 | 1151 | ox += wordWidth; 1152 | } 1153 | } else { 1154 | var lines = [text]; 1155 | maxWidth = this.measureText(text).width; 1156 | } 1157 | 1158 | return { 1159 | height: lines.length * h * 0.6 | 0, 1160 | width: maxWidth 1161 | } 1162 | }, 1163 | 1164 | paperBag: function(x, y, width, height, blowX, blowY) { 1165 | var lx, ly; 1166 | this.beginPath(); 1167 | this.moveTo(x, y); 1168 | this.quadraticCurveTo(x + width / 2 | 0, y + height * blowY | 0, x + width, y); 1169 | this.quadraticCurveTo(x + width - width * blowX | 0, y + height / 2 | 0, x + width, y + height); 1170 | this.quadraticCurveTo(x + width / 2 | 0, y + height - height * blowY | 0, x, y + height); 1171 | this.quadraticCurveTo(x + width * blowX | 0, y + height / 2 | 0, x, y); 1172 | }, 1173 | 1174 | borderImage: function(image, x, y, w, h, t, r, b, l, fill) { 1175 | 1176 | /* top */ 1177 | this.drawImage(image, l, 0, image.width - l - r, t, x + l, y, w - l - r, t); 1178 | 1179 | /* bottom */ 1180 | this.drawImage(image, l, image.height - b, image.width - l - r, b, x + l, y + h - b, w - l - r, b); 1181 | 1182 | /* left */ 1183 | this.drawImage(image, 0, t, l, image.height - b - t, x, y + t, l, h - b - t); 1184 | 1185 | /* right */ 1186 | this.drawImage(image, image.width - r, t, r, image.height - b - t, x + w - r, y + t, r, h - b - t); 1187 | 1188 | /* top-left */ 1189 | this.drawImage(image, 0, 0, l, t, x, y, l, t); 1190 | 1191 | /* top-right */ 1192 | this.drawImage(image, image.width - r, 0, r, t, x + w - r, y, r, t); 1193 | 1194 | /* bottom-right */ 1195 | this.drawImage(image, image.width - r, image.height - b, r, b, x + w - r, y + h - b, r, b); 1196 | 1197 | /* bottom-left */ 1198 | this.drawImage(image, 0, image.height - b, l, b, x, y + h - b, l, b); 1199 | 1200 | if (fill) { 1201 | if (typeof fill === "string") { 1202 | this.fillStyle(fill).fillRect(x + l, y + t, w - l - r, h - t - b); 1203 | } else { 1204 | this.drawImage(image, l, t, image.width - r - l, image.height - b - t, x + l, y + t, w - l - r, h - t - b); 1205 | } 1206 | } 1207 | }, 1208 | 1209 | measureText: function() { 1210 | return this.context.measureText.apply(this.context, arguments); 1211 | }, 1212 | 1213 | createRadialGradient: function() { 1214 | return this.context.createRadialGradient.apply(this.context, arguments); 1215 | }, 1216 | 1217 | createLinearGradient: function() { 1218 | return this.context.createLinearGradient.apply(this.context, arguments); 1219 | }, 1220 | 1221 | getImageData: function() { 1222 | return this.context.getImageData.apply(this.context, arguments); 1223 | } 1224 | 1225 | }; 1226 | 1227 | /* extend wrapper with drawing context methods */ 1228 | 1229 | var methods = ["arc", "arcTo", "beginPath", "bezierCurveTo", "clearRect", "clip", "closePath", "createImageData", "createLinearGradient", "createRadialGradient", "createPattern", "drawFocusRing", "drawImage", "fill", "fillRect", "fillText", "getImageData", "isPointInPath", "lineTo", "measureText", "moveTo", "putImageData", "quadraticCurveTo", "rect", "restore", "rotate", "save", "scale", "setTransform", "stroke", "strokeRect", "strokeText", "transform", "translate"]; 1230 | for (var i = 0; i < methods.length; i++) { 1231 | var name = methods[i]; 1232 | if (!$.Wrapper.prototype[name]) $.Wrapper.prototype[name] = Function("this.context." + name + ".apply(this.context, arguments); return this;"); 1233 | }; 1234 | 1235 | /* create setters and getters */ 1236 | 1237 | var properties = ["canvas", "fillStyle", "font", "globalAlpha", "globalCompositeOperation", "lineCap", "lineJoin", "lineWidth", "miterLimit", "shadowOffsetX", "shadowOffsetY", "shadowBlur", "shadowColor", "strokeStyle", "textAlign", "textBaseline"]; 1238 | for (var i = 0; i < properties.length; i++) { 1239 | var name = properties[i]; 1240 | if (!$.Wrapper.prototype[name]) $.Wrapper.prototype[name] = Function("if(arguments.length) { this.context." + name + " = arguments[0]; return this; } else { return this.context." + name + "; }"); 1241 | }; 1242 | 1243 | /* color */ 1244 | 1245 | $.Color = function(data, type) { 1246 | if (arguments.length) this.parse(data); 1247 | } 1248 | 1249 | $.Color.prototype = { 1250 | parse: function(args, type) { 1251 | if (args[0] instanceof $.Color) { 1252 | this[0] = args[0][0]; 1253 | this[1] = args[0][1]; 1254 | this[2] = args[0][2]; 1255 | this[3] = args[0][3]; 1256 | return; 1257 | } 1258 | 1259 | if (typeof args === "string") { 1260 | var match = null; 1261 | 1262 | if (args[0] === "#") { 1263 | var rgb = $.hexToRgb(args); 1264 | this[0] = rgb[0]; 1265 | this[1] = rgb[1]; 1266 | this[2] = rgb[2]; 1267 | this[3] = 1.0; 1268 | } else if (match = args.match(/rgb\((.*),(.*),(.*)\)/)) { 1269 | this[0] = match[1] | 0; 1270 | this[1] = match[2] | 0; 1271 | this[2] = match[3] | 0; 1272 | this[3] = 1.0; 1273 | } else if (match = args.match(/rgba\((.*),(.*),(.*)\)/)) { 1274 | this[0] = match[1] | 0; 1275 | this[1] = match[2] | 0; 1276 | this[2] = match[3] | 0; 1277 | this[3] = match[4] | 0; 1278 | } else if (match = args.match(/hsl\((.*),(.*),(.*)\)/)) { 1279 | this.fromHsl(match[1], match[2], match[3]); 1280 | } else if (match = args.match(/hsv\((.*),(.*),(.*)\)/)) { 1281 | this.fromHsv(match[1], match[2], match[3]); 1282 | } 1283 | } else { 1284 | switch (type) { 1285 | case "hsl": 1286 | case "hsla": 1287 | 1288 | this.fromHsl(args[0], args[1], args[2], args[3]); 1289 | break; 1290 | 1291 | case "hsv": 1292 | case "hsva": 1293 | 1294 | this.fromHsv(args[0], args[1], args[2], args[3]); 1295 | break; 1296 | 1297 | default: 1298 | this[0] = args[0]; 1299 | this[1] = args[1]; 1300 | this[2] = args[2]; 1301 | this[3] = typeof args[3] === "undefined" ? 1.0 : args[3]; 1302 | break; 1303 | } 1304 | } 1305 | }, 1306 | 1307 | fromHsl: function() { 1308 | var components = arguments[0] instanceof Array ? arguments[0] : arguments; 1309 | var color = $.hslToRgb(components[0], components[1], components[2]); 1310 | 1311 | this[0] = color[0]; 1312 | this[1] = color[1]; 1313 | this[2] = color[2]; 1314 | this[3] = typeof arguments[3] === "undefined" ? 1.0 : arguments[3]; 1315 | }, 1316 | 1317 | fromHsv: function() { 1318 | var components = arguments[0] instanceof Array ? arguments[0] : arguments; 1319 | var color = $.hsvToRgb(components[0], components[1], components[2]); 1320 | 1321 | this[0] = color[0]; 1322 | this[1] = color[1]; 1323 | this[2] = color[2]; 1324 | this[3] = typeof arguments[3] === "undefined" ? 1.0 : arguments[3]; 1325 | }, 1326 | 1327 | toArray: function() { 1328 | return [this[0], this[1], this[2], this[3]]; 1329 | }, 1330 | 1331 | toRgb: function() { 1332 | return "rgb(" + this[0] + ", " + this[1] + ", " + this[2] + ")"; 1333 | }, 1334 | 1335 | toRgba: function() { 1336 | return "rgba(" + this[0] + ", " + this[1] + ", " + this[2] + ", " + this[3] + ")"; 1337 | }, 1338 | 1339 | toHex: function() { 1340 | return $.rgbToHex(this[0], this[1], this[2]); 1341 | }, 1342 | 1343 | toHsl: function() { 1344 | var c = $.rgbToHsl(this[0], this[1], this[2]); 1345 | c[3] = this[3]; 1346 | return c; 1347 | }, 1348 | 1349 | toHsv: function() { 1350 | var c = $.rgbToHsv(this[0], this[1], this[2]); 1351 | c[3] = this[3]; 1352 | return c; 1353 | }, 1354 | 1355 | gradient: function(target, steps) { 1356 | var targetColor = $.color(target); 1357 | }, 1358 | 1359 | shiftHsl: function() { 1360 | var hsl = this.toHsl(); 1361 | 1362 | var h = arguments[0] === null ? hsl[0] : $.wrapValue(hsl[0] + arguments[0], 0, 1); 1363 | var s = arguments[1] === null ? hsl[1] : $.limitValue(hsl[1] + arguments[1], 0, 1); 1364 | var l = arguments[2] === null ? hsl[2] : $.limitValue(hsl[2] + arguments[2], 0, 1); 1365 | 1366 | this.fromHsl(h, s, l); 1367 | 1368 | return this; 1369 | }, 1370 | 1371 | setHsl: function() { 1372 | var hsl = this.toHsl(); 1373 | 1374 | var h = arguments[0] === null ? hsl[0] : $.limitValue(arguments[0], 0, 1); 1375 | var s = arguments[1] === null ? hsl[1] : $.limitValue(arguments[1], 0, 1); 1376 | var l = arguments[2] === null ? hsl[2] : $.limitValue(arguments[2], 0, 1); 1377 | 1378 | this.fromHsl(h, s, l); 1379 | 1380 | return this; 1381 | } 1382 | 1383 | }; 1384 | 1385 | window["cq"] = window["CanvasQuery"] = $; 1386 | 1387 | if (typeof define === "function" && define.amd) { 1388 | define([], function() { 1389 | return $; 1390 | }); 1391 | } 1392 | 1393 | })(window); --------------------------------------------------------------------------------