1 /**
  2  * @namespace JawsJS core functions.
  3  *
  4  * Jaws, a HTML5 canvas/javascript 2D game development framework
  5  *
  6  * Homepage:      http://jawsjs.com/
  7  * Source:        http://github.com/ippa/jaws/
  8  * Documentation: http://jawsjs.com/docs/
  9  *
 10  * Works with:  Chrome 6.0+, Firefox 3.6+, 4+, IE 9+
 11  * License: LGPL - http://www.gnu.org/licenses/lgpl.html
 12  *
 13  * Jaws uses the "module pattern". 
 14  * Adds 1 global, <b>jaws</b>, so plays nice with all other JS libs.
 15  *  
 16  * Formating guide:
 17  *   jaws.oneFunction()
 18  *   jaws.one_variable = 1
 19  *   new jaws.OneConstructor
 20  *
 21  * @property {int}      mouse_x     Mouse X position with respect to the canvas-element
 22  * @property {int}      mouse_y     Mouse Y position with respect to the canvas-element
 23  * @property {canvas}   canvas      The detected/created canvas-element used for the game
 24  * @property {context}  context     The detected/created canvas 2D-context, used for all draw-operations
 25  * @property {int}      width       Width of the canvas-element
 26  * @property {int}      height      Height of the canvas-element
 27  */
 28 var jaws = (function(jaws) {
 29 
 30   var title;
 31   var log_tag;
 32 
 33   /*
 34   * Placeholders for constructors in extras-dir. We define the constructors here to be able to give ppl better error-msgs.
 35   * When the correct from extras-dir is included, these will be overwritten.
 36   */
 37   jaws.Parallax = function() { jaws.log.error("To use jaws.Parallax() you need to include src/extras/parallax.js") }
 38   jaws.SpriteList = function() { jaws.log.error("To use SpriteList() you need to include src/extras/sprite_list.js") }
 39   jaws.TileMap = function() { jaws.log.error("To use TileMap() you need to include src/extras/tile_map.js") }
 40   jaws.PixelMap = function() { jaws.log.error("To use PixelMap() you need to include src/extras/pixel_map.js") }
 41   jaws.QuadTree = function() { jaws.log.error("To use QuadTree() you need to include src/extras/quadtree.js") }
 42   jaws.Audio = function() { jaws.log.error("To use jaws.Audio() you need to include src/extras/audio.js") }
 43 
 44   /**
 45    * Returns or sets contents of title's innerHTML
 46    * @private
 47    * @param   {type}   value  The new value to set the innerHTML of title
 48    * @returns {string}        The innerHTML of title
 49    */
 50   jaws.title = function(value) {
 51 
 52     if (!jaws.isString(value)) {
 53       jaws.log.error("jaws.title: Passed in value is not a String.");
 54       return;
 55     }
 56 
 57     if (value) {
 58       return (title.innerHTML = value);
 59     }
 60     return title.innerHTML;
 61   };
 62 
 63   /**
 64    * Unpacks Jaws core-constructors into the global namespace.
 65    * If a global property is already taken, a warning will be written to jaws log.
 66    */
 67   jaws.unpack = function() {
 68     var make_global = ["Sprite", "SpriteList", "Animation", "Viewport", "SpriteSheet", "Parallax", "TileMap", "pressed", "QuadTree"];
 69 
 70     make_global.forEach(function(item) {
 71       if (window[item]) {
 72         jaws.log.warn("jaws.unpack: " + item + " already exists in global namespace.");
 73       }
 74       else {
 75         window[item] = jaws[item];
 76       }
 77     });
 78   };
 79 
 80   /**
 81    * Writes messages to either log_tag (if set) or console.log (if available)
 82    * @param   {string}  msg     The string to write
 83    * @param   {boolean} append  If messages should be appended or not
 84    */
 85   jaws.log = function(msg, append) {
 86     if (!jaws.isString(msg)) {
 87       msg = JSON.stringify(msg);
 88     }
 89 
 90     if (jaws.log.on) {
 91       if (log_tag && jaws.log.use_log_element) {
 92         if (append) {
 93           log_tag.innerHTML += msg + "<br />";
 94         }
 95         else {
 96           log_tag.innerHTML = msg;
 97         }
 98       }
 99       if (console.log && jaws.log.use_console) {
100         console.log("JawsJS: ", msg);
101       }
102     }
103   };
104 
105   /**
106    * If logging should take place or not
107    * @type {boolean}
108    */
109   jaws.log.on = true;
110 
111   /**
112    * If console.log should be used during log writing 
113    * @type {boolean}
114    */
115   jaws.log.use_console = false;
116 
117   /**
118    * If log_tag should be used during log writing
119    * @type {boolean}
120    */
121   jaws.log.use_log_element = true;
122 
123   /**
124    * Write messages to console.warn (if it exists) or append current log
125    * @param {string|object} msg String or object to record
126    * @see jaws.log
127    */
128   jaws.log.warn = function(msg) {
129     if (console.warn && jaws.log.use_console && jaws.log.on) {
130       console.warn(msg);
131     } else {
132       jaws.log("[WARNING]: " + JSON.stringify(msg), true);
133     }
134   };
135 
136   /**
137    * Write messages to console.error (if it exists) or append current log
138    * @param {string|object} msg String or object to record
139    * @see jaws.log
140    */
141   jaws.log.error = function(msg) {
142     if (console.error && jaws.log.use_console && jaws.log.on) {
143       console.error(msg);
144     } else {
145       jaws.log("[ERROR]: " + JSON.stringify(msg), true);
146     }
147   };
148 
149   /**
150    * Write messages to console.info (if it exists) or append current log
151    * @param {string|object} msg String or object to record
152    * @see jaws.log
153    */
154   jaws.log.info = function(msg) {
155     if (console.info && jaws.log.use_console && jaws.log.on) {
156       console.info(msg);
157     } else {
158       jaws.log("[INFO]: " + JSON.stringify(msg), true);
159     }
160   };
161 
162   /**
163    * Write messages to console.debug (if it exists) or append current log
164    * @param {string|object} msg String or object to record
165    * @see jaws.log
166    */
167   jaws.log.debug = function(msg) {
168     if (console.debug && jaws.log.use_console && jaws.log.on) {
169       console.debug(msg);
170     } else {
171       jaws.log("[DEBUG]: " + JSON.stringify(msg), true);
172     }
173   };
174 
175   /**
176    * Clears the contents of log_tag element (if set) and console.log (if set)
177    */
178   jaws.log.clear = function() {
179     if (log_tag) {
180       log_tag.innerHTML = "";
181     } 
182     if (console.clear) {
183       console.clear();
184     }
185   };
186 
187   /**
188    * Initalizes jaws{canvas, context, dom, width, height}
189    * @private
190    * @param    {object}    options     Object-literal of constructor properties
191    * @see jaws.url_parameters()
192    */
193   jaws.init = function(options) {
194 
195     /* Find <title> tag */
196     title = document.getElementsByTagName('title')[0];
197     jaws.url_parameters = jaws.getUrlParameters();
198 
199     jaws.canvas = document.getElementsByTagName('canvas')[0];
200     if (!jaws.canvas) {
201       jaws.dom = document.getElementById("canvas");
202     }
203 
204     // Ordinary <canvas>, get context
205     if (jaws.canvas) {
206       jaws.context = jaws.canvas.getContext('2d');
207     } 
208     else if (jaws.dom) {
209       jaws.dom.style.position = "relative";
210     } 
211     else {
212       jaws.canvas = document.createElement("canvas");
213       jaws.canvas.width = options.width;
214       jaws.canvas.height = options.height;
215       jaws.context = jaws.canvas.getContext('2d');
216       document.body.appendChild(jaws.canvas);
217     }
218 
219     /*
220      * If debug=1 parameter is present in the URL, let's either find <div id="jaws-log"> or create the tag.
221      * jaws.log(message) will use this div for debug/info output to the gamer or developer
222      *
223      */
224     log_tag = document.getElementById('jaws-log');
225     if (jaws.url_parameters["debug"]) {
226       if (!log_tag) {
227         log_tag = document.createElement("div");
228         log_tag.id = "jaws-log";
229         log_tag.style.cssText = "overflow: auto; color: #aaaaaa; width: 300px; height: 150px; margin: 40px auto 0px auto; padding: 5px; border: #444444 1px solid; clear: both; font: 10px verdana; text-align: left;";
230         document.body.appendChild(log_tag);
231       }
232     }
233 
234 
235     if(jaws.url_parameters["bust_cache"]) {
236       jaws.log.info("Busting cache when loading assets")
237       jaws.assets.bust_cache = true;
238     }
239 
240     /* Let's scale sprites retro-style by default */
241     if (jaws.context)
242       jaws.useCrispScaling();
243 
244     jaws.width = jaws.canvas ? jaws.canvas.width : jaws.dom.offsetWidth;
245     jaws.height = jaws.canvas ? jaws.canvas.height : jaws.dom.offsetHeight;
246 
247     jaws.mouse_x = 0;
248     jaws.mouse_y = 0;
249     window.addEventListener("mousemove", saveMousePosition);
250   };
251 
252   /**
253    * Use 'retro' crisp scaling when drawing sprites through the canvas API, this is the default
254    */
255   jaws.useCrispScaling = function() {
256     jaws.context.imageSmoothingEnabled = false;
257     jaws.context.webkitImageSmoothingEnabled = false;
258     jaws.context.mozImageSmoothingEnabled = false;
259   };
260 
261   /**
262    * Use smooth antialiased scaling when drawing sprites through the canvas API
263    */
264   jaws.useSmoothScaling = function() {
265     jaws.context.imageSmoothingEnabled = true;
266     jaws.context.webkitImageSmoothingEnabled = true;
267     jaws.context.mozImageSmoothingEnabled = true;
268   };
269 
270   /**
271    * Keeps updated mouse coordinates in jaws.mouse_x and jaws.mouse_y
272    * This is called each time event "mousemove" triggers.
273    * @private
274    * @param {EventObject} e The EventObject populated by the calling event
275    */
276   function saveMousePosition(e) {
277     jaws.mouse_x = (e.pageX || e.clientX);
278     jaws.mouse_y = (e.pageY || e.clientY);
279 
280     var game_area = jaws.canvas ? jaws.canvas : jaws.dom;
281     jaws.mouse_x -= game_area.offsetLeft;
282     jaws.mouse_y -= game_area.offsetTop;
283   }
284 
285   /**
286    * 1) Calls jaws.init(), detects or creats a canvas, and sets up the 2D context (jaws.canvas and jaws.context).
287    * 2) Pre-loads all defined assets with jaws.assets.loadAll().
288    * 3) Creates an instance of game_state and calls setup() on that instance.
289    * 4) Loops calls to update() and draw() with given FPS until game ends or another game state is activated.
290    * @param   {function}   game_state                The game state function to be started
291    * @param   {object}     options                   Object-literal of game loop properties
292    * @param   {object}     game_state_setup_options  Object-literal of game state properties and values
293    * @see jaws.init()
294    * @see jaws.setupInput()
295    * @see jaws.assets.loadAll()
296    * @see jaws.switchGameState()
297    * @example
298    *
299    *  jaws.start(MyGame)            // Start game state Game() with default options
300    *  jaws.start(MyGame, {fps: 30}) // Start game state Game() with options, in this case jaws will run your game with 30 frames per second.
301    *  jaws.start(window)            // Use global functions setup(), update() and draw() if available. Not the recommended way but useful for testing and mini-games.
302    *
303    */
304   jaws.start = function(game_state, options, game_state_setup_options) {
305     if (!options) options = {};
306 
307     var fps = options.fps || 60;
308     if (options.loading_screen === undefined) options.loading_screen = true;
309     if (!options.width)                       options.width = 500;
310     if (!options.height)                      options.height = 300;
311     
312     /* Takes care of finding/creating canvas-element and debug-div */
313     jaws.init(options);
314 
315     if (!jaws.isFunction(game_state) && !jaws.isObject(game_state)) {
316       jaws.log.error("jaws.start: Passed in GameState is niether function or object");
317       return;
318     }
319     if (!jaws.isObject(game_state_setup_options) && game_state_setup_options !== undefined) {
320       jaws.log.error("jaws.start: The setup options for the game state is not an object.");
321       return;
322     }
323 
324     if (options.loading_screen) {
325       jaws.assets.displayProgress(0);
326     }
327 
328     jaws.log.info("setupInput()", true);
329     jaws.setupInput();
330 
331     /* Callback for when one single asset has been loaded */
332     function assetProgress(src, percent_done) {
333       jaws.log.info(percent_done + "%: " + src, true);
334       if (options.loading_screen) {
335         jaws.assets.displayProgress(percent_done);
336       }
337     }
338 
339     /* Callback for when an asset can't be loaded*/
340     function assetError(src, percent_done) {
341       jaws.log.info(percent_done + "%: Error loading asset " + src, true);
342     }
343 
344     /* Callback for when all assets are loaded */
345     function assetsLoaded() {
346       jaws.log.info("all assets loaded", true);
347       jaws.switchGameState(game_state || window, {fps: fps}, game_state_setup_options);
348     }
349 
350     jaws.log.info("assets.loadAll()", true);
351     if (jaws.assets.length() > 0) {
352       jaws.assets.loadAll({onprogress: assetProgress, onerror: assetError, onload: assetsLoaded});
353     }
354     else {
355       assetsLoaded();
356     }
357   };
358 
359   /**
360    * Switchs to a new active game state and saves previous game state in jaws.previous_game_state
361    * @param   {function}  game_state                The game state function to start
362    * @param   {object}    options                   The object-literal properties to pass to the new game loop
363    * @param   {object}    game_state_setup_options  The object-literal properties to pass to starting game state
364    * @example
365    * 
366    * function MenuState() {
367    *   this.setup = function() { ... }
368    *   this.draw = function() { ... }
369    *   this.update = function() {
370    *     if(pressed("enter")) jaws.switchGameState(GameState); // Start game when Enter is pressed
371    *   }
372    * }
373    *
374    * function GameState() {
375    *   this.setup = function() { ... }
376    *   this.update = function() { ... }
377    *   this.draw = function() { ... }
378    * }
379    *
380    * jaws.start(MenuState)
381    *
382    */
383   jaws.switchGameState = function(game_state, options, game_state_setup_options) {
384     if(options === undefined) options = {};
385 
386     if(jaws.isFunction(game_state)) {
387       game_state = new game_state;
388     }
389     if(!jaws.isObject(game_state)) {
390       jaws.log.error("jaws.switchGameState: Passed in GameState should be a Function or an Object.");
391       return;
392     }
393 
394     var fps = (options && options.fps) || (jaws.game_loop && jaws.game_loop.fps) || 60;
395     var setup = options.setup
396 
397     jaws.game_loop && jaws.game_loop.stop();
398     jaws.clearKeyCallbacks();
399 
400     jaws.previous_game_state = jaws.game_state;
401     jaws.game_state = game_state;
402     jaws.game_loop = new jaws.GameLoop(game_state, {fps: fps, setup: setup}, game_state_setup_options);
403     jaws.game_loop.start();
404   };
405 
406   /**
407    * Creates a new HTMLCanvasElement from a HTMLImageElement
408    * @param   {HTMLImageElement}  image   The HTMLImageElement to convert to a HTMLCanvasElement
409    * @returns {HTMLCanvasElement}         A HTMLCanvasElement with drawn HTMLImageElement content
410    */
411   jaws.imageToCanvas = function(image) {
412     if (jaws.isCanvas(image)) return image;
413 
414     if (!jaws.isImage(image)) {
415       jaws.log.error("jaws.imageToCanvas: Passed in object is not an Image.");
416       return;
417     }
418 
419     var canvas = document.createElement("canvas");
420     canvas.src = image.src;
421     canvas.width = image.width;
422     canvas.height = image.height;
423 
424     var context = canvas.getContext("2d");
425     context.drawImage(image, 0, 0, image.width, image.height);
426     return canvas;
427   };
428 
429   /**
430    * Returns object as an array
431    * @param   {object}  obj   An array or object
432    * @returns {array}         Either an array or the object as an array 
433    * @example
434    *
435    *   jaws.forceArray(1)       // --> [1]
436    *   jaws.forceArray([1,2])   // --> [1,2]
437    */
438   jaws.forceArray = function(obj) {
439     return Array.isArray(obj) ? obj : [obj];
440   };
441 
442   /**
443    * Clears screen (the canvas-element) through context.clearRect()
444    */
445   jaws.clear = function() {
446     jaws.context.clearRect(0, 0, jaws.width, jaws.height);
447   };
448 
449   /** Fills the screen with given fill_style */
450   jaws.fill = function(fill_style) {
451     jaws.context.fillStyle = fill_style;
452     jaws.context.fillRect(0, 0, jaws.width, jaws.height);
453   };
454 
455 
456   /**
457    * calls draw() on everything you throw on it. Give it arrays, argumentlists, arrays of arrays.
458    *
459    */
460   jaws.draw = function() {
461     var list = arguments;
462     if(list.length == 1 && jaws.isArray(list[0])) list = list[0];
463     for(var i=0; i < list.length; i++) {
464       if(jaws.isArray(list[i])) jaws.draw(list[i]);  
465       else                      if(list[i].draw) list[i].draw();
466     }
467   }
468 
469   /**
470    * calls update() on everything you throw on it. Give it arrays, argumentlists, arrays of arrays.
471    *
472    */
473   jaws.update = function() {
474     var list = arguments;
475     if(list.length == 1 && jaws.isArray(list[0])) list = list[0];
476     for(var i=0; i < list.length; i++) {
477       if(jaws.isArray(list[i])) jaws.update(list[i]);  
478       else                      if(list[i].update) list[i].update();
479     }
480   }
481 
482   /**
483    * Tests if object is an image or not
484    * @param   {object}  obj   An Image or image-like object
485    * @returns {boolean}       If object's prototype is "HTMLImageElement"
486    */
487   jaws.isImage = function(obj) {
488     return Object.prototype.toString.call(obj) === "[object HTMLImageElement]";
489   };
490 
491   /**
492    * Tests if object is a Canvas object
493    * @param   {type}  obj   A canvas or canvas-like object
494    * @returns {boolean}     If object's prototype is "HTMLCanvasElement"
495    */
496   jaws.isCanvas = function(obj) {
497     return Object.prototype.toString.call(obj) === "[object HTMLCanvasElement]";
498   };
499 
500   /**
501    * Tests if an object is either a canvas or an image object
502    * @param   {object}  obj   A canvas or canva-like object
503    * @returns {boolean}       If object isImage or isCanvas
504    */
505   jaws.isDrawable = function(obj) {
506     return jaws.isImage(obj) || jaws.isCanvas(obj);
507   };
508 
509   /**
510    * Tests if an object is a string or not
511    * @param   {object}  obj   A string or string-like object
512    * @returns {boolean}       The result of typeof and constructor testing
513    */
514   jaws.isString = function(obj) {
515     return typeof obj === "string" || (typeof obj === "object" && obj.constructor === String);
516   };
517 
518   /**
519    * Tests if an object is a number or not
520    * @param   {number}  n   A number or number-like value
521    * @returns {boolean}     If n passed isNaN() and isFinite()
522    */
523   jaws.isNumber = function(n) {
524     return !isNaN(parseFloat(n)) && isFinite(n);
525   };
526 
527   /**
528    * Tests if an object is an Array or not
529    * @param   {object}  obj   An array or array-like object
530    * @returns {boolean}       If object's constructor is "Array"
531    */
532   jaws.isArray = function(obj) {
533     if (!obj)
534       return false;
535     return !(obj.constructor.toString().indexOf("Array") === -1);
536   };
537 
538   /**
539    * Tests if an object is an Object or not
540    * @param   {object}  value   An object or object-like enitity
541    * @returns {boolean}         If object is not null and typeof 'object'
542    */
543   jaws.isObject = function(value) {
544     return value !== null && typeof value === 'object';
545   };
546 
547   /**
548    * Tests if an object is a function or not
549    * @param   {object}  obj   A function or function-like object
550    * @returns {boolean}       If the prototype of the object is "Function"
551    */
552   jaws.isFunction = function(obj) {
553     return (Object.prototype.toString.call(obj) === "[object Function]");
554   };
555 
556   /**
557    * Tests if an object is a regular expression or not
558    * @param   {object}  obj   A /regexp/-object
559    * @returns {boolean}       If the object is an instance of RegExp
560    */
561   jaws.isRegExp = function(obj) {
562     return (obj instanceof RegExp);
563   };
564 
565 
566   /**
567    * Tests if an object is within drawing canvas (jaws.width and jaws.height) 
568    * @param   {object}  item  An object with both x and y properties
569    * @returns {boolean}       If the item's x and y are less than 0 or more than jaws.width or jaws.height
570    */
571   jaws.isOutsideCanvas = function(item) {
572     if (item.x && item.y) {
573       return (item.x < 0 || item.y < 0 || item.x > jaws.width || item.y > jaws.height);
574     }
575   };
576 
577   /**
578    * Sets x and y properties to 0 (if less than), or jaws.width or jaws.height (if greater than)
579    * @param   {object}  item  An object with x and y properties
580    */
581   jaws.forceInsideCanvas = function(item) {
582     if (item.x && item.y) {
583       if (item.x < 0) {
584         item.x = 0;
585       }
586       if (item.x > jaws.width) {
587         item.x = jaws.width;
588       }
589       if (item.y < 0) {
590         item.y = 0;
591       }
592       if (item.y > jaws.height) {
593         item.y = jaws.height;
594       }
595     }
596   };
597 
598   /**
599    * Parses current window.location for URL parameters and values
600    * @returns   {array}   Hash of url-parameters and their values
601    * @example
602    *   // Given the current URL is <b>http://test.com/?debug=1&foo=bar</b>
603    *   jaws.getUrlParameters() // --> {debug: 1, foo: bar}
604    */
605   jaws.getUrlParameters = function() {
606     var vars = [], hash;
607     var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
608     for (var i = 0; i < hashes.length; i++) {
609       hash = hashes[i].split('=');
610       vars.push(hash[0]);
611       vars[hash[0]] = hash[1];
612     }
613     return vars;
614   };
615 
616   /**
617    * Compares an object's default properties against those sent to its constructor
618    * @param   {object}  object    The object to compare and assign new values
619    * @param   {object}  options   Object-literal of constructor properties and new values
620    * @param   {object}  defaults  Object-literal of properties and their default values
621    */
622   jaws.parseOptions = function(object, options, defaults) {
623     object["options"] = options;
624 
625     for (var option in options) {
626       if (defaults[option] === undefined) {
627         jaws.log.warn("jaws.parseOptions: Unsupported property " + option + "for " + object.constructor);
628       }
629     }
630     for (var option in defaults) {
631       if( jaws.isFunction(defaults[option]) ) defaults[option] = defaults[option](); 
632       object[option] = (options[option] !== undefined) ? options[option] : jaws.clone(defaults[option]);
633     }
634   };
635 
636   /**
637    * Returns a shallow copy of an array or object
638    * @param   {array|object}  value   The array or object to clone
639    * @returns {array|object}          A copy of an array of object
640    */
641   jaws.clone = function(value) {
642     if (jaws.isArray(value))
643       return value.slice(0);
644     if (jaws.isObject(value))
645       return JSON.parse(JSON.stringify(value));
646     return value;
647   };
648 
649   /*
650   * Converts image to canvas 2D context. Then you can draw on it :).
651   */
652   jaws.imageToCanvasContext = function(image) {
653     var canvas = document.createElement("canvas")
654     canvas.width = image.width
655     canvas.height = image.height
656   
657     var context = canvas.getContext("2d")
658     if(jaws.context) {
659       context.imageSmoothingEnabled = jaws.context.mozImageSmoothingEnabled;
660       context.webkitImageSmoothingEnabled = jaws.context.mozImageSmoothingEnabled;
661       context.mozImageSmoothingEnabled = jaws.context.mozImageSmoothingEnabled;
662     } 
663 
664     context.drawImage(image, 0, 0, canvas.width, canvas.height)
665     return context
666   }
667 
668   /**
669    * scale 'image' by factor 'factor'.
670    * Scaling is done using nearest-neighbor ( retro-blocky-style ).
671    * Returns a canvas.
672    */
673   jaws.retroScaleImage = function(image, factor) {
674     var canvas = jaws.isImage(image) ? jaws.imageToCanvas(image) : image
675     var context = canvas.getContext("2d")
676     var data = context.getImageData(0,0,canvas.width,canvas.height).data
677 
678     // Create new canvas to return
679     var canvas2 = document.createElement("canvas")
680     canvas2.width = image.width * factor
681     canvas2.height = image.height * factor
682     var context2 = canvas2.getContext("2d")
683     var to_data = context2.createImageData(canvas2.width, canvas2.height)
684 
685     var w2 = to_data.width
686     var h2 = to_data.height
687     for (var y=0; y < h2; y += 1) {
688       var y2 = Math.floor(y / factor)
689       var y_as_x = y * to_data.width
690       var y2_as_x = y2 * image.width
691 
692       for (var x=0; x < w2; x += 1) {
693         var x2 = Math.floor(x / factor)
694         var y_dst = (y_as_x + x) * 4
695         var y_src = (y2_as_x + x2) * 4
696         
697         to_data.data[y_dst] = data[y_src];
698         to_data.data[y_dst+1] = data[y_src+1];
699         to_data.data[y_dst+2] = data[y_src+2];
700         to_data.data[y_dst+3] = data[y_src+3];
701       }
702     }
703 
704     context2.putImageData(to_data, 0, 0)
705 
706     return canvas2
707   }
708 
709   return jaws;
710 })(jaws || {});
711 
712 // Support CommonJS require()
713 if(typeof module !== "undefined" && ('exports' in module)) { module.exports = jaws }
714