Dealing with Binary Data from a Canvas Object using JavaScript TypedArrays

I’m not sure how helpful this will be for anyone, but during the process of doing a binary WebSocket demo I found myself learning a lot about JavaScript typed arrays and how that translates into binary data. The demo I wrote took Canvas image data and sent it over a binary WebSocket connection. The WebSocket server took that data and sent it out to all of the connected clients, who would then render the Canvas data as a PNG. It’s kind of a niche use case but I wanted to specifically create a binary WebSocket demo. It also was a more efficient way to send image data than doing something like base64 encoding it. First off, the Mozilla documentation was REALLY helpful. Major props to them.

Translating Canvas Data Into a Binary Format

Canvas has a getImageData() method that gives you an ImageData object. Within that ImageData object is a data property, which has the actual array data of the image. Normally I would have been able to stop right there because that would (in theory) have the information I needed. But what I had to get at was the ArrayBuffer. The way the spec has been implemented you can’t do anything with the ArrayBuffer. Instead you have to use an ArrayBufferView, which takes the form of TypedArrays in JavaScript. Luckily, to get the actual buffer you can just call the buffer property on any of the typed arrays and do what you want with it. But Canvas (at least in Chrome) is slightly different. The object you get from the ArrayBuffer of ImageData.data is something called CanvasPixelArray. Currently CanvasPixelArray doesn’t behave like a regular typed array, it looks like it will become a Uint8ClampedArray but the way it works in Chrome right now the CanvasPixelArray doesn’t provide access to a buffer property so you can’t send/access the ArrayBuffer data. Luckily getting that data into a Uint8Array, which you can get the buffer data from is pretty easy:

     imagedata = context.getImageData(0, 0, imagewidth,imageheight);
 
     var canvaspixelarray = imagedata.data;
 
 
     var canvaspixellen = canvaspixelarray.length;
     var bytearray = new Uint8Array(canvaspixellen);
 
     for (var i=0;i<canvaspixellen;++i) {
          bytearray[i] = canvaspixelarray[i];
     }

With that new Uint8Array all that we have to do is grab the buffer property and we can send it across the wire (beyond this post, but I’m planning on writing up the binary WebSocket info).

Reassembling the pieces

Now what I wanted to do was take that binary data from my Canvas and render it as a PNG file on the screen. The first step is to use a Canvas object to render out a PNG. But before that we have to get the data into a Canvas. In theory, you should be able to just do what happened above in reverse. But it’s not quite that simple. Once you get back the binary data from somewhere (WebSocket say), you’ve got an ArrayBuffer that you have to deal with. There is a putImageData() that takes an ImageData object, and we can create an ImageData object a few different ways, but you can’t set the data property of it. It’s read only. So we can’t take our data from the ArrayBuffer and just put it into our Canvas. We have to manually loop through the data property and line-by-line change the data.

          var bytearray = new Uint8Array(event.data);
 
 
          var tempcanvas = document.createElement('canvas');
               tempcanvas.height = imageheight;
               tempcanvas.width = imagewidth;
          var tempcontext = tempcanvas.getContext('2d');
 
          var imgdata = tempcontext.getImageData(0,0,imagewidth,imageheight);
 
          var imgdatalen = imgdata.data.length;
 
          for(var i=8;i<imgdatalen;i++)
          {
               imgdata.data[i] = bytearray[i];
          }
 
          tempcontext.putImageData(imgdata,0,0);

If the above isn’t clear, basically I’m just creating a new Uint8Array with the data from the server, then creating the temporary Canvas so I can get image data from it, and when I have that, I’m looping through the data property and inserting my own data from the Uint8Array.

Rendering it as a PNG

So now we have a Canvas (not being displayed) that has all of the data from our server, so it’s an exact graphical copy of the info we received. Turning that into a PNG is actually pretty easy because HTML is awesome. Canvas has a toDataURL() method that will take whatever is in the Canvas and create a string that can be put into the src property of an image. Then putting that image somewhere on the DOM will display the data as an image.

          var img = document.createElement('img');
               img.height = imageheight;
               img.width = imagewidth;
               img.src = tempcanvas.toDataURL();

Fin

Now that I’ve gotten my head around binary data a lot more, I’m kind of excited about JavaScript typed arrays. Looking through the list it looks like the typed arrays will help a lot with byte manipulation because of the different types. It also looks like they’re pretty fast (at least the fastest option at the time of that post).

  • http://twitter.com/kichinsky Constantin Kichinsky

    Ryan, it would be better if you cache .data property on local variable similar to what you do wneh translating data from canvas to arraybuffer: var pixels = imgdata.data; … for(var i=0;i<imgdatalen;i++) { pixels[i] = bytearray[i]; }

  • http://blog.digitalbackcountry.com Ryan Stewart

    Good call, I’ll fix that in the final version. Thanks!

  • http://blog.digitalbackcountry.com Ryan Stewart

    Whoops meant to fix that this weekend. Thanks!

  • penk

    great post, can’t wait for the binary WebSocket!

  • penk

    great post, can’t wait for the binary WebSocket!

  • http://twitter.com/enzenhofer franz enzenhofer

    thx, this is exactly the right info at the right time. 

  • http://twitter.com/enzenhofer franz enzenhofer

    thx, this is exactly the right info at the right time. 

  • http://kout.com/#/arkanciscan Jesse

    Very interesting post. I’m trying to generate images in canvas, and store them on a server via JSON over XHR. Base64 DataURLs are big, and can contain characters that don’t work over Ajax so I’m looking for ways to get binary data from a canvas. Your method is interesting as it results in a typed array, but I wonder if you nave heard of window.atob() which converts Base64 Asci to binary strings. I’m not sure how the resulting string would perform over WebSockets, but it seems like it might be simpler to get the result into a canvas using img.src = ‘data:image/png;base64,’+window.btoa(string). Do you think this would be a better approach if you weren’t planning on sending the data over a socket?

  • http://kout.com/#/arkanciscan Jesse

    Very interesting post. I’m trying to generate images in canvas, and store them on a server via JSON over XHR. Base64 DataURLs are big, and can contain characters that don’t work over Ajax so I’m looking for ways to get binary data from a canvas. Your method is interesting as it results in a typed array, but I wonder if you nave heard of window.atob() which converts Base64 Asci to binary strings. I’m not sure how the resulting string would perform over WebSockets, but it seems like it might be simpler to get the result into a canvas using img.src = ‘data:image/png;base64,’+window.btoa(string). Do you think this would be a better approach if you weren’t planning on sending the data over a socket?

  • http://kout.com/#/arkanciscan Jesse

    Very interesting post. I’m trying to generate images in canvas, and store them on a server via JSON over XHR. Base64 DataURLs are big, and can contain characters that don’t work over Ajax so I’m looking for ways to get binary data from a canvas. Your method is interesting as it results in a typed array, but I wonder if you nave heard of window.atob() which converts Base64 Asci to binary strings. I’m not sure how the resulting string would perform over WebSockets, but it seems like it might be simpler to get the result into a canvas using img.src = ‘data:image/png;base64,’+window.btoa(string). Do you think this would be a better approach if you weren’t planning on sending the data over a socket?

  • http://kout.com/#/arkanciscan Jesse

    Very interesting post. I’m trying to generate images in canvas, and store them on a server via JSON over XHR. Base64 DataURLs are big, and can contain characters that don’t work over Ajax so I’m looking for ways to get binary data from a canvas. Your method is interesting as it results in a typed array, but I wonder if you nave heard of window.atob() which converts Base64 Asci to binary strings. I’m not sure how the resulting string would perform over WebSockets, but it seems like it might be simpler to get the result into a canvas using img.src = ‘data:image/png;base64,’+window.btoa(string). Do you think this would be a better approach if you weren’t planning on sending the data over a socket?

  • http://kout.com/#/arkanciscan Jesse

    Very interesting post. I’m trying to generate images in canvas, and store them on a server via JSON over XHR. Base64 DataURLs are big, and can contain characters that don’t work over Ajax so I’m looking for ways to get binary data from a canvas. Your method is interesting as it results in a typed array, but I wonder if you nave heard of window.atob() which converts Base64 Asci to binary strings. I’m not sure how the resulting string would perform over WebSockets, but it seems like it might be simpler to get the result into a canvas using img.src = ‘data:image/png;base64,’+window.btoa(string). Do you think this would be a better approach if you weren’t planning on sending the data over a socket?

  • http://kout.com/#/arkanciscan Jesse

    Very interesting post. I’m trying to generate images in canvas, and store them on a server via JSON over XHR. Base64 DataURLs are big, and can contain characters that don’t work over Ajax so I’m looking for ways to get binary data from a canvas. Your method is interesting as it results in a typed array, but I wonder if you nave heard of window.atob() which converts Base64 Asci to binary strings. I’m not sure how the resulting string would perform over WebSockets, but it seems like it might be simpler to get the result into a canvas using img.src = ‘data:image/png;base64,’+window.btoa(string). Do you think this would be a better approach if you weren’t planning on sending the data over a socket?

  • http://twitter.com/danhouldsworth Dan Houldsworth

    Great post. Can you include a link to your WebSocket Binary demo that you refer to as this is exactly what I’m trying to learn at the moment which brought me here.
    Thanks!

  • Ketting00

    Please write the part of how to send binary data via websocket. I search for the method for months, and find only how to render receiving binary data.  Today I still send base64 data via websocket, it’s not practical and very slow.

    Thanks

  • Andrew Dodson

    Canvas toDataURL() does the job of compressing the canvas bitmap into an efficient PNG format. Whilst getImageData doesn’t provide any compression. I’m surprised you’ve found this to be a better solution. I’m tempted to say it should be thusly Canvas to base64 DataURL, atob DataURL to get BinaryString, loop BinaryString with charCodeAt to build up a much smaller Array Buffer.