ComicR, online speech bubbles using HTML5/NodeJS

The other day I wanted to quickly put a speech bubble on an image but surprisingly I found no online tool that would enable me to do that apart from some Flash based ones that I didn’t find particularly usable and one HTML5 one which is cool but I didn’t like the bubble style.

So I decided to make one as part of my let’s learn javascript and nodejs journey. There are so many very cool nodejs projects and blog entries out there that I don’t see much point of going into too many technical details of how the project was done, the source code is on GitHub anyway, you can take look if you are interested. But again, I’m only learning both JavaScript and NodeJS so I’m not in any way suggesting that the methods I use are the way to go, but they do the job which I can prove to you if you care to visit the website and put some speech bubbles: http://comicr.co.uk.

There were a few gotchas in the project which I had to do fair amount of googling around to sort out, so in the hope that I can make that process less painful for someone I will summarize here what I’ve learned.

  • Forcing the browser to download a file
    The use case is that after you put the speech bubble on your image you maybe wanna download it to your computer. This is not really nodejs specific task, but this is how it can be done in node:

    app.get('/downloadImage', function(req, res) {
    	var fn = 'speechBubble.png';
    	res.header('Content-Type', 'image/png');
    	res.header('Content-Disposition', 'attachment; filename="'+fn+'"');
            // ...
            // write the png stream to res
            // ...
    }
    

    On the client side you do a window.location = “/downloadImage” + …; or <a href …> and the browser will either pop-up a dialog where you can change the destination filename (firefox) or just go ahead and download the file using the given name (chrome).

  • Shared code between the server and the client
    One of the main appeals of NodeJS is that in enables sharing code between the backend (node) and the frontend (browser). In this project I could actually take advantage of this approach as I need to draw the bubbles in the browser as well as in node when rendering the image to png. I used the node-canvas which is an HTML5 canvas implementation for NodeJS using the cairo library. The way I did the code sharing is the following:
    - I created a file called drawApi.js which has a drawBubble method.
    - On the client I call this method whenever the user places a bubble somewhere, or resizes it, or changes the text.
    - On the server I wrap it with drawApi = require(‘drawApi’) then call it when I’m rendering the image.
    - The two images are not _exactly_ the same but they are close enough (I would say 99% perceived similarity).

    This is not a too sophisticated use of code sharing, but still it’s quite useful. For instance if I want add new bubble types or effects I won’t have to replicate that code just stuff it into drawApi.

  • C++ wrappers
    Quite a few node libraries are actually written in C++ with a very thin js wrapper around them so you can call the methods from javascript. This is partly cool, cause they are obviously very performant, partly uncool when you try to deploy your application somewhere. The aforementioned node-canvas module uses the cairo library for rendering and libjpeg and giflib for the drawImage method of the canvas which let’s you ‘draw’ an image (the source image has to be decoded). This was not a problem when I was developing on my mac, I just used brew to compile these libraries and then npm took care of linking the node module nicely with them and all was working fine. I was much less happy when I tried to deploy the app to a number of PaaSes that allow NodeJS. To name a couple I tried Nodester, Cloudfoundry. These platforms are probably great for hosting Node apps that don’t hook too deeply in with the host operating system, but I did not succeed to get the node-canvas module to compile on them, as I could not set up the cairo library dependency. Finally I ended up hosting the app on a dedicated centos machine where I could install the libraries manually. This is just something to consider if you plan to host your application on cloud platforms.

  • Upload the image to Imgur using the Imgur API
    The upload image to imgur functionality required me to do an HTTP POST to imgur. I didn’t find any example in the NodeJS documentation to do it so but found an answer on stackoverflow. In case someone doesn’t find it I’ll include the example:

    var post_data = querystring.stringify({
    	'key' : 'imgur_api_key',
    	'image': canvas.toBuffer().toString("base64") // base64 encode the png stream
    });
    var post_options = {
    	host: 'api.imgur.com',
    	port: '80',
    	path: '/2/upload.json',
    	method: 'POST',
    	headers: {
    		'Content-Type': 'application/x-www-form-urlencoded',
    		'Content-Length': post_data.length
    	}
    };
    var post_req = http.request(post_options, function(pres) {
    	pres.setEncoding('utf8');
    	var response = "";  // this will be the response to the POST
    	pres.on('data', function (chunk) {
    		response += chunk;
    	});
    	pres.on('end', function() {
    		console.log('Response: ' + response);
    		var obj = JSON.parse(response); // imgur answers with json
    		var result = obj.upload.links;
    		console.dir(result); // this will contain the imgur links (image, imgur page, delete, etc.)
    	});
    });
    // post the data
    post_req.write(post_data);
    post_req.end();
    
  • Uploading a file (form-based, using ajax and connect-form)
    The problem I had with uploading a file was that the upload process occurs in chunks of Buffers but I needed only one buffer that contains the whole image. I read through the documentation of Buffer but couldn’t find a method that would grow the buffer as new chunks arrive so I did the naive solution and just appended the buffers when the data input stream ended:

    app.post('/doUpload', function(req, res, next) {
    	var buffers = [];
    	var sumLength = 0;
    	req.on("data", function(chunk) {
    		buffers.push(chunk);
    		sumLength += chunk.length;
    	});
    	req.on('end', function(chunk) {
    		console.log("Total Length: ", sumLength);
    		var pos = 0;
    		var bigBuffer = new Buffer(sumLength + 1)
    		for (var i = 0; i < buffers.length; i++) {
    			buffers[i].copy(bigBuffer, pos, 0);
    			pos += buffers[i].length;
    		}
    
    		// ...
    		// bigBuffer now holds the full image as a Buffer
    		// ...
    	});
    });
    

    That’s pretty much it the rest is mostly trivial but I’m happy to answer any questions concerning to code you may have.

  • From apache to nginx and more nodeJS

    Whilst I was looking for a solution to serve more nodeJS applications on the same box I’ve found that many hands were pointing towards nginx which is a light-weight webserver. This was the first time I heard about it. Nevertheless I gave it a try and was quite happy with it.

    I did not manage to achieve my original goal, which was to serve a socket-io app (sociowriter mentioned in a previous post) and a normal web app on the same IP even though I recompiled nginx with the tcp_proxy with which the socket-io app was working fine but it took full ownership of the IP address and did not allow me to set up virtual hosts.

    So I gave up on that and decided to host all grails / nodejs apps on one IP and have a dedicated IP for each app that needs websockets (so these are not going through nginx). That solution seems to be working fine however I understand that it’s not always possible to get hold of an additional IP so if anyone knows a better solution I’d be glad to see it.

    The nginx setup for those who are interested:

    http {
      include       mime.types;
      default_type  application/octet-stream;
      index index.html;
    
      server {
        listen IP:80;
        server_name host1.com;
        location / {
            proxy_pass        http://localhost:8080;
            proxy_set_header  X-Real-IP  $remote_addr;
         }
      }
    
      server {
        listen IP:80;
        server_name host2.com;
        location / {
            proxy_pass        http://localhost:3000;
            proxy_set_header  X-Real-IP  $remote_addr;
         }
      }
    
      server {
        listen IP:80;
        server_name host3.com;
        root /var/www/web
        location / {
            index index.html index.htm;
        }
      }
    }                                                                            8,0-1         21%
    

    Instead of IP you write the actual IP address of course. So host1.com is a grails webapp running on port 8080, host2.com is a nodejs app running on port 3000 and host3.com is simple site served by nginx. All of this I could have done in apache as well but with a considerably bigger and more complex configuration file.

    SkinnableComponent vs SkinnableContainer

    I was wondering what the point was for having SkinnableContainer when you could do any kind of skinning with SkinnableComponent. Then the other day I came across a problem which showed a real-life use case for it.

    Let’s say you want to have a skinnable text editor and you want allow the user to change the skin in runtime. You can use SkinnableComponent and have the text area in the skin class but whenever you change the skin the framework will remove all the skin parts including the text editor and then re-add them using the new skin. So the content of the textarea (whatever text the user entered) will be lost as well, which is not the greatest user experience.

    The solution (one of them at least) is to use SkinnableContainer. So it would look something like:

    
    <s:SkinnableContainer xmlns:fx="http://ns.adobe.com/mxml/2009"
    					  xmlns:s="library://ns.adobe.com/flex/spark"
    					  xmlns:mx="library://ns.adobe.com/flex/mx">
    	<fx:Script>
    			import spark.components.Label;
    			[SkinPart]
    			public var l1:Label;
    			[SkinPart]
    			public var l2:Label;
    
    	</fx:Script>
    
    	<s:TextArea width="30" height="30" />
    
    </s:SkinnableContainer>
    

    And the skin would look like:

    <s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
    		xmlns:s="library://ns.adobe.com/flex/spark"
    		xmlns:mx="library://ns.adobe.com/flex/mx">
    	<fx:Metadata>
    		[HostComponent("MyComp")]
    	</fx:Metadata>
    
    	<s:states>
    		<s:State name="disabled" />
    		<s:State name="normal" />
    	</s:states>
    
    	<s:Label id="l1"  text="l1"/>
    	<s:Label id="l2" x="50" text="l2"/>
    
    	<s:Group id="contentGroup"
    			 left="5" right="5" top="5" bottom="5">
    		<s:layout>
    			<s:VerticalLayout/>
    		</s:layout>
    	</s:Group>
    </s:Skin>
    

    In this case the TextArea doesn’t belong to the skin but it’s a persistent part of the component and won’t be altered by the skin change.

    A glance at NodeJS (and SocketIO)

    So after 3-4 years of pure frontend (Flash, Flex) development I’ve gotten bored of it and decided it was time explore something else.

    The first stack I looked into was the increasingly popular NodeJS platform. I’m not a huge fan of Javascript but the idea of a fully asynchronous platform both on the server and the client sounded interesting enough to give it a go.

    According to my experience the best way to learn a language is to write something in it so I decided to write a very simple application that can be looked at as a much simplified version of google docs collaborative text editor with a little twist that the text can only grow (no correction / deletion) and users have to ‘agree’ on the next word by suggesting words and/or voting on suggestions. If 3 users agree on a word it becomes the continuation of the story. The idea is pretty much the extrapolation of the game I used to play as a kid with a friend where we would start telling a story and each of us would suggest how it should go on. Some really cool stuff came out of that game so I was (still am) curious what it would look like with many more people.

    You can check out the result here: http://sociowriter.com . There’s not too much great content there at the moment, people getting there usually just type some random words to see if the thing works then get bored and leave. Which is understandable cause the only appeal of it would be seeing what other people think the next word should be; without that it’s merely a very bad text editor.

    The de facto push technology for NodeJS seemed to be socket.io which is an abstraction for duplex client/server communication and includes methods like Websockets, Flash transport, long polling. Since I didn’t really want to bother with the communication layer too much I was quite happy for this library. It turned out to be quite good except for some issues like channels not working properly after socket reconnection and the whole thing not working at all on some free cloud hosting services. But apart from that it’s really very convenient. I’m somewhat reluctant to put code here as their API seem to be ever changing but I’ll do anyway and if you are lucky it still works by the time you try it.

    On the server side:

    var sio = require('socket.io');
    var io = sio.listen(8080);
    //Sending a message to all connected sockets:
    io.sockets.emit("someMessage", someData);

    And on the client:

    <script type="text/javascript" src="/socket.io/socket.io.js" />
    var socket = io.connect();
    socket.on('connect', function(socket) {
    	console.log('socket connect '+socket);
    });
    socket.on('someMessage', function(data) {
    	console.log('data arrived '+data);
    });

    So as you see it’s really high level and you don’t need to get your hands dirty with the networking stuff. I didn’t entirely rely on SocketIO for the communication: the clients don’t send messages to the server through it but instead they do HTTP requests and the server broadcasts the words to the clients through SocketIO.

    It was surprisingly easy to get the thing do what I wanted. Because I wanted to immerse a bit deeper into NodeJS I decided to use ExpressJS and MongoDB but I will write about that in an other post maybe. If anyone’s interested in the source code please let me know and I’ll put it on github.

    Edit: https://github.com/amiklosi/SocioWriter

    Hello world!

    Hi,

    Just a quick note, that the site is up and running and you’ll be able to find content here about Flex and Grails programming and any other topic that interests me.