danielenick89
danielenick89

Reputation: 101

Javascript canvas: how to efficiently compute distance of two canvases

I want to compute the distance between two figures drawn in two canvases, actually i'm doing the following, iterating through the data of the canvases (canvases have the same size):

var computeDifference = function() {

    var imgd1 = bufferCtx.getImageData(0, 0, w, h).data;
    var imgd2 = targetCtx.getImageData(0, 0, w, h).data;

    var diff = 0;

    for(var i=0; i<imgd1.length; i+=4) {
        var d = (imgd1[i]-imgd2[i]);
        var tot = d > 0 ? d : -d;
        diff += tot
    }

    return diff;
}

this is not very efficient.

Is there a better method? I read about composite operations, but I'm not sure if that could help in this case.

I've purposely considered only the R channel because for now I'm operating with black and white images, but I'm probably going to consider the other channels later.

Upvotes: 3

Views: 105

Answers (1)

user1693593
user1693593

Reputation:

You can use the new difference blending method on a single canvas, draw both images in with mode set before the last draw, then extract the bitmap data to get the total sum.

You would use the same property, globalCompositeOperation, to set blending mode with.

This way you are letting the browser do the initial work calculating the difference on each component leaving you only to sum them up. You are also saving one canvas, one call to getImageData() which is relative expensive on an hardware accelerated system:

ctx.drawImage(image1, x, y);
ctx.globalCompositeOperation = "difference";  // use composite to set blending...
ctx.drawImage(image2, x, y);

// extract data, and sum -

Note: IE11 does not support the new blending modes. For IE you would need to do the difference calculations manually as initially.

You can feature detect this by providing the fast method when supported, manual when not:

ctx.globalCompositeOperation = "difference";
if (ctx.globalCompositeOperation === "difference") {
    // fast
}
else {
    // manual
}

Live performance test

Test1 will do manual difference calclation, test2 will use browser difference blending mode. On my setup FireFox wins with more than a 4x factor (slightly less difference in Chrome).

var canvas1 = document.createElement("canvas"),
    canvas2 = document.createElement("canvas"),
    ctx1 = canvas1.getContext("2d"),
    ctx2 = canvas2.getContext("2d"),
    img1 = new Image, img2 = new Image,
    count = 2,
    startTime1, startTime2, endTime1, endTime2, sum1, sum2;

performance = performance || Date;               // "polyfill" the performance object
img1.crossOrigin = img2.crossOrigin = "";        // we need to extract pixels
img1.onload = img2.onload = loader;
img1.src = "http://i.imgur.com/TJiD5GM.jpg";
img2.src = "http://i.imgur.com/s9ksOb1.jpg";

function loader() {if(!--count) test1()}         // handle async load
function test1(){
  startTime1 = performance.now();
  
  ctx1.drawImage(img1, 0, 0);
  ctx2.drawImage(img2, 0, 0);
  
  var data1 = ctx1.getImageData(0, 0, 500, 500).data,
      data2 = ctx2.getImageData(0, 0, 500, 500).data,
      i = 0, len = data1.length, sum = 0;
  
  // we do all channels except alpha channel (not used in difference calcs.)
  while(i < len) {
    sum += Math.abs(data2[i] - data1[i++]) + 
           Math.abs(data2[i] - data1[i++]) + 
           Math.abs(data2[i] - data1[i++]); 
    i++
  }
  
  sum1 = sum;
  endTime1 = performance.now();
  test2();
}

function test2(){
  startTime2 = performance.now();
  
  ctx1.drawImage(img1, 0, 0);
  ctx1.globalCompositeOperation = "difference";
  if (ctx1.globalCompositeOperation !== "difference")
    alert("Sorry, use Firefox or Chrome");
  ctx1.drawImage(img2, 0, 0);
  
  var data = ctx1.getImageData(0, 0, 500, 500).data,
      i = 0, len = data.length, sum = 0;
  
  // we do all channels except alpha channel
  while(i < len) {
    sum += data[i++];
    sum += data[i++];
    sum += data[i++];
    i++;
  }
  sum2 = sum;      
  endTime2 = performance.now();
  result();
}

function result() {
  var time1 = endTime1 - startTime1,
      time2 = endTime2 - startTime2,
      factor = time1 / time2,
      res = "Manual method: " + time1.toFixed(3) + "ms<br>";
  res += "Blending mode: " + time2.toFixed(3) + "ms<br>";
  res += "Factor: " + factor.toFixed(2) + "x<br>";
  res += "Sum 1 = " + sum1;
  res += "<br>Sum 2 = " + sum2;
  document.querySelector("output").innerHTML = res;
}
<output>Loading images and calculating...</output>

Upvotes: 1

Related Questions