Javascript: Using Canvas to cut an area of an image

Over the few weeks I’ve been working on a Canvas based side project (more on that soon) that involved cutting a mask out of a source image and placing it on a Canvas. In Photoshop parlance, this would be similar to creating a clipping mask and then using it to extract a path from the image into a new layer. So visually, we’re looking to achieve something similar to:

At face value, it looks like doing this with Canvas is pretty straightforward using the getImageData function. Unfortunately, if you look at the parameters that function accepts it’ll only support slicing out rectangular areas which isn’t what we’re looking to do. Luckily, if you look a bit further in the docs it turns out Canvas supports setting globalCompositeOperation which allows you to control how image data is drawn onto the canvas. The idea is to draw the mask on a canvas, turn on the “source-in” setting, and then draw on the image that you want to generate the slice off. The big thing to note here is that putImageData isn’t effected by the globalCompositeOperation setting so you have to use drawImage to draw the mask and image data.

So concretely how do you do this? Well check it out:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title></title>

    <link href='http://fonts.googleapis.com/css?family=Open+Sans:400,700,800' rel='stylesheet' type='text/css'>
    <link href='http://fonts.googleapis.com/css?family=Lato:400,700,900' rel='stylesheet' type='text/css'>
    
    <link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
        
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
    <script src="bootstrap/js/bootstrap.min.js"></script>
    <script src="underscore-min.js"></script>    
    <script src="fabric.min.js"></script>
    
    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->    
  </head>
  <body>
  
  <canvas id="target" width="800px" height="400px" style="margin: auto"></canvas>
  
  <script>
  
  $(document).ready(function(){
      
      var mask = new Image(), bg = new Image();
      var mDf = $.Deferred(), bgDf = $.Deferred();
            
      mask.src = "grass_overlay.png";
      bg.src = "grass_texture.png";
      
      mask.addEventListener("load", function(){ mDf.resolve(this); });
      bg.addEventListener("load", function(){ bgDf.resolve(this); });
      
      var canvas = document.createElement("canvas"), ctx = canvas.getContext('2d');
      var target = document.getElementById("target"), targetCtx = target.getContext('2d');
      
      
      $.when(mDf, bgDf).done(function(){
          var l = (bg.width / 2) - (mask.width / 2), t = (bg.height / 2) - (mask.height / 2);
          
          canvas.width = bg.width;
          canvas.height = bg.height;
          
          ctx.drawImage(mask, l, t);
          ctx.globalCompositeOperation = "source-in";
          ctx.drawImage(bg, 0, 0);
                 
          var imageData = ctx.getImageData(l, t, mask.width, mask.height);                                       
          targetCtx.putImageData(imageData, 50, 50);                         
      });
      
  });
  
  </script>
  
  </body>
</html>

The code is running over at http://symf.setfive.com/canvas_puzzle/grass.html if you want to see it in action.

Anyway, happy canvasing!

JavaScript: Using ChipmunkJS to build ping pong

A couple of weeks ago, we picked up a RaspberryPi for the office and started brainstorming ideas for cool hacks for it. One of the ideas that was floated was using the Pi as a server for some sort of multiplayer JavaScript game. I’ve actually never written a game before so I figured it would be an interesting project. We ended up trying to use node-qt for graphics along with ws to receive input via websockets but the refresh rate of Qt was just to low. A story for another time.

Anyway, so back to ping pong. One of the things we wanted to avoid was writing our own code to manage the game objects, we really wanted to use a game engine for this. The only caveat we had was that the engine had to be UI agnostic since we were planning to output graphics on Qt instead of a HTML5 Canvas. After looking around, most of the JavaScript game engines have strong dependencies on Canvas so we started looking at other options. The two strongest options were Box2JS and ChipmunkJS. We decided to go with ChipmunkJS in part because it was handwritten while Box2JS was automatically converted from ActionScript so the resulting code is harder to follow.

Getting ChipmunkJS setup is relatively painless, just include the file and you’re off to the races. Since we were looking to eventually use Qt, we tried our best to cleanly separate the Chipmunk code from what we’d be using to draw the objects on the screen. Because of that, it’s pretty easy to follow what’s going on with the Chipmunk code. If you take a look at cpPong.js it basically initializes the physics space and then starts the game.

// The physics space size is 640x480, with the origin in the bottom left.
// Its really an arbitrary number except for the ratio - everything is done
// in floating point maths anyway.

var GRABABLE_MASK_BIT = 1<<31;
var NOT_GRABABLE_MASK = ~GRABABLE_MASK_BIT;

if( typeof cp == 'undefined') {
	var cp = require("./cp.js");
}
    
var cpPong = function(){

    this.addDrawables();
    
    var ball = null, paddleOne = null, paddleTwo = null;    
    
    this.v = cp.v;
    this.space = new cp.Space();
        
    this.space.iterations = 10;
    this.space.gravity = this.v(0, 0);
    this.space.sleepTimeThreshold = 0.5;
    this.space.collisionSlop = 0.1; 
    
    this.addFloor();
    this.addWalls();
    
    var mass = 10;    
    var radius = 20;
        
    this.body = this.space.addBody( new cp.Body(mass, cp.momentForCircle(mass, 0, radius, this.v(0, 0))) );            
    this.ball = this.space.addShape( new cp.CircleShape(this.body, radius, this.v(0, 0)) );
    this.ball.setElasticity(1);    
    this.ball.setFriction(0.0);   
    this.ball.setCollisionType(1);
        
    this.paddleOneBody = this.space.addBody( new cp.Body(100000, cp.momentForBox(Infinity, 75, 10)) );        
    this.paddleOne = this.space.addShape( new cp.BoxShape(this.paddleOneBody, 75, 10) ); 
    this.paddleOne.setElasticity(1);
    this.paddleOne.setFriction(0);         

    this.paddleTwoBody = this.space.addBody( new cp.Body(100000, cp.momentForBox(Infinity, 75, 10)) );        
    this.paddleTwo = this.space.addShape( new cp.BoxShape(this.paddleTwoBody, 75, 10) ); 
    this.paddleTwo.setElasticity(1);
    this.paddleTwo.setFriction(0);        
    
    this.scoreboard = {s1: 0, s2: 0};
    
    var self = this;    
    
    this.resetGameObjects();
    this.renderScoreBoard();
    this.isRunning = true;
    this.run();
        
    this.space.setDefaultCollisionHandler(null, null, function(el){     	    	
        if( el.a.isGameEnder || el.b.isGameEnder ){        	        	
        	var pointFor = el.a.pointFor ? el.a.pointFor : el.b.pointFor;
        	
        	self.isRunning = false;
        	
        	self.scoreboard[ pointFor ] += 1;            	
        	self.renderScoreBoard();
        	
        	self.alertRoundOver();
        	self.resetGame();        	        	        	
        }        
    });          
    
};

cpPong.prototype.resetGameObjects = function(){
    this.body.setPos( this.v(20, 320) );
    this.body.setVel( this.v(200, -200) );
    
    this.paddleOneBody.setPos( this.v(320, 60) );
    this.paddleTwoBody.setPos( this.v(320, 420) );
    
    this.paddleOneBody.setVel( this.v(0, 0) );
    this.paddleTwoBody.setVel( this.v(0, 0) );		
};

cpPong.prototype.addFloor = function() { 	
    this.floor = this.space.addShape( new cp.SegmentShape(this.space.staticBody, this.v(0, 0), this.v(640, 0), 0) );
    this.floor.setElasticity(1);
    this.floor.setFriction(0.0);    
    this.floor.setCollisionType(1);
    
    this.floor.isGameEnder = true;
    this.floor.pointFor = "s1";
    this.floor.lineWidth = 5;
    
    this.ceiling = this.space.addShape( new cp.SegmentShape(this.space.staticBody, this.v(0, 480), this.v(640, 480), 0) );
    this.ceiling.setElasticity(1);
    this.ceiling.setFriction(0.0);    
    this.ceiling.setCollisionType(1);
    
    this.ceiling.isGameEnder = true;
    this.ceiling.pointFor = "s2";
    this.ceiling.lineWidth = 5;
};

cpPong.prototype.addWalls = function() {
    var space = this.space;
    
    var wall1 = space.addShape(new cp.SegmentShape(space.staticBody, this.v(0, 0), this.v(0, 480), 0));
    wall1.setElasticity(1);
    wall1.setFriction(0.0);
    wall1.setLayers(NOT_GRABABLE_MASK);
    wall1.setCollisionType(1);
    wall1.lineWidth = 5;
    
    var wall2 = space.addShape(new cp.SegmentShape(space.staticBody, this.v(640, 0), this.v(640, 480), 0));
    wall2.setElasticity(1);
    wall2.setFriction(0.0);
    wall2.setLayers(NOT_GRABABLE_MASK);
    wall2.setCollisionType(1);
    wall2.lineWidth = 5;
};

cpPong.prototype.movePaddle = function(paddle, direction){	
	var target = paddle == "one" ? this.paddleOneBody : this.paddleTwoBody;
	var velocity = direction == "right" ? 500 : -500;
	
    var pt = target.getVel().add( this.v(velocity, 0) ); 
    target.setVel( this.v(velocity, 0) );
    // target.setVel( pt );
};

//Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in
// the browser, add `_` as a global object via a string identifier,
// for Closure Compiler "advanced" mode.
if (typeof exports !== 'undefined') {
  if (typeof module !== 'undefined' && module.exports) {
    exports = module.exports = cpPong;
  }
  exports.cpPong = cpPong;
} else {
  window.cpPong = cpPong;
}

With the physics space setup, the next step is setting up the drawing. Following the Chipmunk demo’s lead, I added specific “draw” methods to the Prototypes of each shape. Doing this, allows you to just iterate over each shape in the scene and call “draw” to have it appear on Canvas or Qt. Check out pong.html to see how we added the draw methods to the shapes. And that’s about it! The only other interesting part is using “requestAnimationFrame” to avoid using setTimeout to update the scene.

var scoreboardTemplate = _.template( $("#scoreboardTemplate").html() );

var ctx;
var canvas = $("canvas")[0];
 
var canvasBackend = {
        
    scale: 0,
    
    run: function(){ 
    	
    	if( this.isRunning == false ){
    		return false;
    	}
    	
        this.drawScene();
        this.space.step(1/60);                
        
        var raf = window.requestAnimationFrame
        || window.webkitRequestAnimationFrame
        || window.mozRequestAnimationFrame
        || window.oRequestAnimationFrame
        || window.msRequestAnimationFrame
        || function(callback) {
            return window.setTimeout(callback, 1000 / 60);
        };      
        
        var draw = _.bind(this.run, this);
        raf(draw);             
    },
    
    drawScene: function(){
        var self = this;
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.space.eachShape(function(shape) {          
            shape.draw(self.ctx);           
        });        
    },
    
    resetGame: function(){
    	var self = this;
        window.setTimeout(function(){
            self.resetGameObjects();
            self.isRunning = true;
            self.run();
        }, 3000);    	
    },
    
    renderScoreBoard: function(){
    	$("#scoreboard").html( scoreboardTemplate(this.scoreboard) );
    },
    
    alertRoundOver: function(){
    	alert("Round over!");
    },
    
    addDrawables: function(){
        
        this.canvas = $("canvas")[0];
        this.ctx = $("canvas")[0].getContext('2d');
                                  
        var width = Math.ceil( (window.innerHeight * 640) / 480 ) + 25;
        
        $("canvas").attr("height", window.innerHeight);
        $("canvas").attr("width", width);        
        
        // var width = this.canvas.width;
        var height = this.canvas.height;
                
        window.scale = 0;          
        
        if (width / height > 640 / 480) {
            window.scale = height / 480;
        } else {
            window.scale = width / 640;
        }                       
        
        var self = this;
        $(window).keydown(function(e){                    
            if( e.keyCode == 39 ){
                self.movePaddle("one", "right");
            }
            
            if( e.keyCode == 37 ){         
            	self.movePaddle("one", "left");
            }
                    
            if( e.keyCode == 87 ){
            	self.movePaddle("two", "right");
            }
            
            if( e.keyCode == 81 ){         
            	self.movePaddle("two", "left");
            }
        });         
        
        cp.Shape.prototype.point2canvas = function(point){          
            return cp.v( point.x * window.scale, (480 - point.y) * window.scale );        	
        };
        
        cp.Shape.prototype.drawLine = function(ctx, a, b){        
            a = this.point2canvas(a); 
            b = this.point2canvas(b);
            ctx.beginPath();
            ctx.moveTo(a.x + .5, a.y);
            ctx.lineTo(b.x + .5, b.y);
            ctx.stroke();
        };
        
        cp.Shape.prototype.drawCircle = function(ctx, c, radius){
            var c = this.point2canvas(c);
            ctx.beginPath();
            ctx.arc(c.x, c.y, window.scale * radius, 0, 2 * Math.PI, false);
            ctx.fill();
            ctx.stroke();           
        }
        
        cp.SegmentShape.prototype.draw = function(ctx) {                        
            ctx.strokeStyle = "green";                      
            ctx.fillStyle = "green";
            ctx.lineCap = 'round';
            ctx.lineWidth = this.lineWidth ? this.lineWidth : Math.max(1, this.r * window.scale * 2);
            this.drawLine(ctx, this.ta, this.tb);
        };

        cp.CircleShape.prototype.draw = function(ctx) { 
            ctx.lineWidth = 1;
            ctx.strokeStyle = "black";                      
            ctx.fillStyle = "red";  
            this.drawCircle(ctx, this.tc, this.r);
            
            ctx.strokeStyle = "white";                      
            ctx.fillStyle = "white";
            this.drawLine(ctx, this.tc, cp.v.mult(this.body.rot, this.r).add(this.tc));
        };
        
        cp.PolyShape.prototype.draw = function(ctx){
            
            ctx.strokeStyle = "blue";                      
            ctx.fillStyle = "blue";
            ctx.lineWidth = 1;
            
            ctx.beginPath();

            var verts = this.tVerts;
            var len = verts.length;
            var lastPoint = this.point2canvas( new cp.Vect(verts[len - 2], verts[len - 1]) );
            
            ctx.moveTo(lastPoint.x, lastPoint.y);

            for(var i = 0; i < len; i+=2){
                var p = this.point2canvas(new cp.Vect(verts[i], verts[i+1]));
                ctx.lineTo(p.x, p.y);
            }
            
            ctx.fill();
            ctx.stroke();
        };      
        
    },
};

_.extend( cpPong.prototype, canvasBackend );

There’s a live demo running here and of course the code on GitHub.

Any questions or comments welcome!

Phonegap: Fixing black bars on iOS7/iPhone5

Last week, we were using Phonegap Build to build an iOS IPA for a project an ran into an odd issue. When we launched the app on an iPhone 5 running iOS7 black bars appeared at the top and bottom of the screen. On top of that, when launching the app we were observing the same issue with the splash screen.

In typical Phonegap fashion, Googling for people suffering from similar issues brought back dozens of results across several versions each with a different root cause and solution. One of the first promising leads we noticed was this comment in the top of the default Phonegap template:

<!-- WARNING: for iOS 7, remove the width=device-width and height=device-height attributes. See https://issues.apache.org/jira/browse/CB-4323 -->
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />

Unfortunately, that comment seems either be invalid or the issue has since been resolved since removing those meta attributes had no effect.

As it turns out, the issue is that the “config.xml” file created by the default Phonegap “Hello world” project is missing an entry for the iPhone5’s screen size. Oddly enough, there’s actually a splash screen image of the correct height in the demo project but its not referenced in the config file. To resolve this issue, you just need to add this line to your config.xml:

<gap:splash gap:platform="ios" height="1136" src="res/screen/ios/screen-iphone-portrait-568h-2x.png" width="640" />

Just make sure that you have a file named “res/screen/ios/screen-iphone-portrait-568h-2x.png” where the rest of your splash screens are and you should be good to go.

Javascript: Phonegap with Backbone and Marionette

We’ve worked with our clients to execute a couple of Phonegap apps lately and in doing so used Backbone and Marionette to structure the apps. For some background, Phonegap, now Apache Cordova, is a project that allows developers to build native iOS, Android, and WP apps using HTML, CSS, and of course Javascript. As a developer, you write some HTML and Javascript, pass it to Phonegap, and Phonegap returns a native app that displays your code inside a WebView without any surrounding chrome. On top of this, Phonegap provides a set of Javascript APIs that allow you to leverage some of the device’s native functions, like the accelerometer or camera.

Writing apps with HTML/JS is great, but it presents some issues particularly that triggering a full page reload for navigation appears “non-native” on mobile. On technique to combat this issue is developing single page Javascript apps. In a single page app, the entire page is never reloaded, instead portions of the DOM are dynamically re-rendered using Javascript. Because of the complexity of managing this process with straight Javascript, several libraries, including Backbone, have been developed to simplify this process. Marionette is a companion library to Backbone which provides a set of features to make managing complex applications easier. So, what were the pros and cons of using Backbone with Marionette to build a Phonegap app?

The Good

Structure: Using a library like Backbone guides you to structuring your code in loosely a MVC design pattern. Coupled with a templating framework, this ends up producing code that’s much easier to follow and maintain. Before Phonegap, I’d already started using Backbone in traditional Symfony2 projects just to get the benefit of better structured code.

It’s familiar: This is a personal preference, but compared to declarative frameworks like AngularJS, Backbone/Marionette apps “look” like regular HTML/JS. Primarily because of the regular HTML templates and use of jQuery, the learning curve for Backbone isn’t very steep. A team member without Backbone experience can quickly grok how things work and make changes quickly.

The Not So Good

“There’s more than one way to do it”: Although flexibility is good, having a generally “well recommended” way usually helps decrease confusion and frustration. With Backbone/Marionette, there doesn’t seem to be much consensus on how to do “standard” things like message passing or even structuring the app as a whole. There’s dozens of StackOverflow answers debating the “best” way to approach things, often with outright conflicting viewpoints. In contrast, Symfony2 and Rails typically have “best practices” for approaching common tasks, even if they’re not appropriate in every circumstance.

Documentation: The documentation for Marionette, and to a lesser degree Backbone, is pretty lacking. The Marionette documentation explains how the individual components work but they didn’t provide much insight to the “big picture”. The docs were also missing some explanation into the “why”, which of course lead to StackOverflow answers and then differing viewpoints. Marionette is also short example apps which Backbone does have. The Backbone documentation is thorough, its just a bit hard to navigate and purposefully introduces the “There’s more than one way to do it” mantra.

Anyway, on the whole using Backbone with Marionette to build a Phonegap app was a positive experience. Unfortunately, our clients’ own the code so we can’t release it. That said, we’ll do our best to build something in-house that we can share.

Phonegap: Recording audio with Phonegap

I was working on a Phonegap app recently and one of the goals was to allow the user to record some feedback as an audio file which would later be shared. If you hit Google, there’s plenty of StackOverflow threads which claim to have working sample code. Almost all of the SO code is using the Phonegap Media API with the “startRecording”/“stopRecording” functions. Despite it looking simple, I could never get the Media based code working on iOS. For whatever reason, I kept hitting iOS permission errors trying to create the recording file. On Android, the code works but I kept ending up with an “AMR” file instead of the expected 3gp recording.

So what’s the alternative? Turns out there’s another Phonegap API called Capture which has a captureAudio method which lets you record audio. As opposed to Media, captureAudio uses the device’s native audio recording UI to let you to record audio. Because of this, captureAudio won’t work for all use cases but the implementation is straightforward and consistent across Android and iOS.

Anyway, here’s the code to record some audio and upload it to a server:

navigator.device.capture.captureAudio(function(mediaFiles){

    // mediaFiles will be an array of MediaFile objects

    var options = new FileUploadOptions();
    var ft = new FileTransfer();
    var fileURI = mediaFiles[0].fullPath;

    options.params = _.extend({}, {id: 5);
    options.fileKey = "file";
    options.fileName = fileURI.substr(fileURI.lastIndexOf('/') + 1);
    options.headers = {Connection: "close"};

    // upload the file to the server
    ft.upload(fileURI, encodeURI("http://www.setfive.com/upload.php"), function(resp){
        var data = JSON.parse(resp.response);
        alert( data.successMsg );        
    }, function(error){
        alert("Sorry! Something went wrong. Please try again.");
    }, options);

}, {limit: 1});

Then, on the server you can just grab the file from say the $_FILES[“file”] variable.