16

I have two canvases, and I want to pass the content of canvas1, serialize it to an ArrayBuffer, and then load it in canvas2. In the future I will send the canvas1 content to the server, process it, and return it to canvas2, but right now I just want to serialize and deserialize it.

I found this way of getting the canvas info in bytes:

var img1 = context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img1.data.length);
for (var i = 0; i < img1.data.length; i++) {
    binary[i] = img1.data[i];
}

And also found this way of set the information to a Image object:

var blob = new Blob( [binary], { type: "image/png" } );
var urlCreator = window.URL || window.webkitURL;
var imageUrl = urlCreator.createObjectURL( blob );
var img = new Image();
img.src = imageUrl;

But unfortunately it doesn't seem to work.

Which would be the right way of doing this?

ggorlen
  • 44,755
  • 7
  • 76
  • 106
vtortola
  • 34,709
  • 29
  • 161
  • 263

3 Answers3

27

The ImageData you get from getImageData() is already using an ArrayBuffer (used by the Uint8ClampedArray view). Just grab it and send it:

var imageData = context.getImageData(x, y, w, h);
var buffer = imageData.data.buffer;  // ArrayBuffer

To set it again:

var imageData = context.createImageData(w, h);
imageData.data.set(incomingBuffer);

You probably want to consider some form of byte encoding though (such as f.ex base-64) as any byte value above 127 (ASCII) is subject to character encoding used on a system. Or make sure all steps on the trip uses the same (f.ex. UTF8).

  • Doesn’t work in IE because IE still uses CanvasPixelArray instead of Uint8ClampedArray (CanvasPixelArray's imageData.data has no .buffer). – markE Mar 06 '14 at 19:35
  • @markE true, but in any other browser... (as always) :) –  Mar 06 '14 at 19:41
  • That darned IE! It has gotten much better lately, though. :) – markE Mar 06 '14 at 19:42
  • 1
    @markE sure, it's getting there.. eventually (?) :-P The good ol' Mosaic :-] –  Mar 06 '14 at 19:48
  • 2
    I made it work, I had to use a Uint8Array on the "buffer" variable to make it work in "imageData". Thanks! – vtortola Mar 07 '14 at 13:48
4

Create an ArrayBuffer and send it into to the Uint8Array constructor, then send the buffer using websockets:

var img1 = context.getImageData(0, 0, 400, 320);
var data=img1.data;
var buffer = new ArrayBuffer(data.length);
var binary = new Uint8Array(buffer);
for (var i=0; i<binary.length; i++) {
    binary[i] = data[i];
}
websocket.send(buffer);
Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
markE
  • 102,905
  • 11
  • 164
  • 176
  • I need to do it in binary. – vtortola Mar 06 '14 at 18:01
  • Why? You can always convert image-to-binary on either the client-side or server-side. The trouble is binary transport--conversion to binary is simple. – markE Mar 06 '14 at 18:03
  • Because the connection is a websocket that accepts data in binary format. That is why I am asking to do it in binary. – vtortola Mar 06 '14 at 18:55
  • 1
    Fair enough--you're constrained to a specific kind of connection on the server-side. In your top code, you need to create an ArrayBuffer and send that through the websocket. See my updated answer. Be sure the websocket.binaryType="arrayBuffer" because the default type is blob. – markE Mar 06 '14 at 19:11
2

Consider using canvas.toBlob() instead of context.getImageData() if you want compact data rather than a raw ImageData object.

Example:

const imageIn = document.querySelector('#image-in');
const imageOut = document.querySelector('#image-out');
const canvas = document.querySelector('#canvas');
const imageDataByteLen = document.querySelector('#imagedata-byte-length');
const bufferByteLen = document.querySelector('#arraybuffer-byte-length');

const mimeType = 'image/png';

imageIn.addEventListener('load', () => {

  // Draw image to canvas.
  canvas.width = imageIn.width;
  canvas.height = imageIn.height;
  const ctx = canvas.getContext('2d');
  ctx.drawImage(imageIn, 0, 0);

  // Convert canvas to ImageData.
  const imageData = ctx.getImageData(0, 0, imageIn.width, imageIn.height);
  imageDataByteLen.textContent = imageData.data.byteLength + ' bytes.';

  // Convert canvas to Blob, then Blob to ArrayBuffer.
  canvas.toBlob((blob) => {
    const reader = new FileReader();
    reader.addEventListener('loadend', () => {
      const arrayBuffer = reader.result;
      bufferByteLen.textContent = arrayBuffer.byteLength + ' bytes.';

      // Dispay Blob content in an Image.
      const blob = new Blob([arrayBuffer], {type: mimeType});
      imageOut.src = URL.createObjectURL(blob);
    });
    reader.readAsArrayBuffer(blob);
  }, mimeType);

});
<h1>Canvas ↔ ArrayBuffer</h1>

<h2>1. Source <code>&lt;img&gt;</code></h2>
<img id="image-in" src="https://ucarecdn.com/a0338bfa-9f88-4ce7-b53f-e6b61000df89/" crossorigin="">

<h2>2. Canvas</h2>
<canvas id="canvas"></canvas>

<h2>3. ImageData</h2>
<p id="imagedata-byte-length"></p>

<h2>4. ArrayBuffer</h2>
<p id="arraybuffer-byte-length"></p>

<h2>5. Final <code>&lt;img&gt;</code></h2>
<img id="image-out">

JSFiddle: https://jsfiddle.net/donmccurdy/jugzk15b/

Also note that images must be hosted on a service that provides CORS headers, or you'll see errors like "The canvas has been tainted by cross-origin data."

Don McCurdy
  • 10,975
  • 2
  • 37
  • 75