JMH
JMH

Reputation: 461

Compare two Images in JavaScript

I am trying to determine if two images are the same in JavaScript (even if the source URLs are different).

My specific use case is within a Chrome extension (though this being a chrome extension doesn't really factor into the question). I can get the image of a favicon png stored within Chrome's internal database by setting the img src to: 'chrome://favicon/'+url where 'url' is the actual URL of the website. However, I now want to find all the unique favicons. Given that they all will have a different URL to the internal database, is there an easy way to compare images in JavaScript?

Upvotes: 37

Views: 71157

Answers (8)

ggorlen
ggorlen

Reputation: 56895

Here's a complete, runnable example of using pixelmatch in the browser without bundling.

const prepareImgCtx = img => {
  const canvas = document.createElement("canvas");
  canvas.width = img.width;
  canvas.height = img.height;
  const ctx = canvas.getContext("2d");
  ctx.drawImage(img, 0, 0);
  return ctx;
};

const imgDiff = (imgA, imgB, canvasDiff) => {
  const ctxA = prepareImgCtx(imgA);
  const ctxB = prepareImgCtx(imgB);
  canvasDiff.width = imgA.width;
  canvasDiff.height = imgA.height;
  const diffCtx = canvasDiff.getContext("2d");
  const diff = diffCtx.createImageData(imgA.width, imgA.height);
  const mismatchedPixels = pixelmatch(
    ctxA.getImageData(0, 0, imgA.width, imgA.height).data,
    ctxB.getImageData(0, 0, imgB.width, imgB.height).data,
    diff.data,
    imgA.width,
    imgA.height,
    {threshold: 0.1}
  );
  diffCtx.putImageData(diff, 0, 0);
  return mismatchedPixels;
};

const imageLoaded = async img => {  
  img.crossOrigin = "anonymous";
  img.setAttribute("crossOrigin", "");
  await img.decode();
  return img;
};

(async () => {
  const imgs = [...document.querySelectorAll("img")];
  await Promise.all(imgs.map(imageLoaded));
  const canvasDiff = document.createElement("canvas");
  document.body.prepend(canvasDiff);
  console.log("mismatched pixels:", imgDiff(...imgs, canvasDiff));
})();
<script src="https://bundle.run/[email protected]"></script>
<img src="https://picsum.photos/id/3/200/200">
<img src="https://picsum.photos/id/5/200/200">

Upvotes: 2

Igor Milla
Igor Milla

Reputation: 2786

I think you may be interested in this JavaScript library called resemble.js which:

Analyses and compares images with HTML5 canvas and JavaScript.

Resemble.js can be used for any image analysis and comparison requirement you might have in the browser. However, it has been designed and built for use by the https://github.com/Huddle/PhantomCSS powered visual regression library PhantomCSS. PhantomCSS needs to be able to ignore antialiasing as this would cause differences between screenshots derived from different machines.

Resemble.js uses the HTML5 File API to parse image data, and canvas for rendering image diffs.

Upvotes: 17

traktor
traktor

Reputation: 19301

Browser based file comparison

This answer addresses a closed as duplicate question about how to compare two images in a browser, say opened or dropped onto the page, without using canvas or base64.

The high level answer is to get array buffers for each of the files, use them to create a data view or actual typed array and check them byte by byte.

This bare bones example illustrates the principles, making some choices that could have been done differently by using

"use strict";
function checkFiles( event) {
    //console.log(event);
    //console.log(this);
    
    const files = Array.from(event.target.files);
    if( files.length !=2) {
        console.log( "Expected 2 files to compare");
        return;
    }
    Promise.all(files.map(file=>file.arrayBuffer()))
    .then( buffers =>  {          
         const [f1, f2] = buffers.map( buffer => new Uint8Array( buffer));
         if( f1.length!=f2.length) {
              return false;
         }
         if( f1.length == 0) {
              console.warn("Files have zero length");
              return true;
         }
         let i = 0;
         for(; i<f1.length; ++i) {
              if( f1[i] != f2[i]) return false;
         }
         return true;
    })
    .then( same=> {
        console.log( same ? "same" : "different");
    })
    .catch(err=>console.error(err));
}
<input type=file multiple onchange="checkFiles.call(this, event)">

For efficiency, a production version of the snippet could use file objects' size attributes to check the files have the same, non-zero length before reading them in order to compare their contents byte by byte.

Upvotes: 1

Jason Lydon
Jason Lydon

Reputation: 7180

If someone else ended up here like I did because they were trying to compare two images in Node javascript, I was able to utilize Node's Buffer to convert a gif as an AWS S3 Object into a Base64 and test against it.

This was used within CypressJS in order to test an image sent to S3 and then in the test using s3.getObject() to return the image

const imageToBase64 = Buffer.from(image.Body).toString('base64');

Upvotes: 0

us_david
us_david

Reputation: 4917

This is probably a very old thread and I ran into it when I needed to achieve the same goal of comparing 2 images pixel by pixel instead of similarity comparison. I found a very fast and easy to use tool for PNG image pixel by pixel comparison js library. Here is a simple example provided by the repo:

const fs = require('fs');
const PNG = require('pngjs').PNG;

const pixelmatch = require('pixelmatch');

const img1 = PNG.sync.read(fs.readFileSync('img1.png'));
const img2 = PNG.sync.read(fs.readFileSync('img2.png'));

const {width, height} = img1;
const diff = new PNG({width, height});

pixelmatch(img1.data, img2.data, diff.data, width, height, {threshold: 0.1});

fs.writeFileSync('diff.png', PNG.sync.write(diff));

It even generates a diff image for you. Please note this is NOT perceptual comparison. There are other tools for such purpose.
Here is the link on GitHub for pixelmatch: pixelmatch on github

Upvotes: 3

Jan Bussieck
Jan Bussieck

Reputation: 1059

We just released a lightweight library RembrandtJS, that does exactly that and it works both in the browser using HTML5 Canvas2D API as well as on the server via the drop-in Node.JS replacement node-canvas. It accepts both blobs and urls as image sources so you could simply do this:

const rembrandt = new Rembrandt({
  // `imageA` and `imageB` can be either Strings (file path on node.js,
  // public url on Browsers) or Buffers
  imageA: 'chrome://favicon/' + url_a,
  imageB: 'chrome://favicon/' + url_b,

  thresholdType: Rembrandt.THRESHOLD_PERCENT,

  // The maximum threshold (0...1 for THRESHOLD_PERCENT, pixel count for THRESHOLD_PIXELS
  maxThreshold: 0,

  // Maximum color delta (0...255):
  maxDelta: 0,

  // Maximum surrounding pixel offset
  maxOffset: 0,

})

// Run the comparison
rembrandt.compare()
  .then(function (result) {

    if(result.passed){
      // do what you want
    }
  })

As you can see Rembrandt also allows you to introduce threshold values, if you domain requires some leeway with respect to color or pixel difference. Since it works in both the browser and on the server (node), it makes it easy to integrate into your test suite.

Upvotes: 13

vitopn
vitopn

Reputation: 72

Maybe this tool will help: https://github.com/HumbleSoftware/js-imagediff/

Upvotes: 5

Matt Ball
Matt Ball

Reputation: 359786

No, there is no especially easy way to do this. JavaScript was not made for handling low-level operations such as working directly with binary data for, say, image processing.

You could use a <canvas> element to base64 encode each image, and then compare the resulting base64 strings, but this will only tell you whether or not the images are identical.

To use the getBase64Image function (defined in the answer I linked) to compare two images:

var a = new Image(),
    b = new Image();
a.src = 'chrome://favicon/' + url_a;
b.src = 'chrome://favicon/' + url_b;

// might need to wait until a and b have actually loaded, ignoring this for now
var a_base64 = getBase64Image(a),
    b_base64 = getBase64Image(b);

if (a_base64 === b_base64)
{
    // they are identical
}
else
{
    // you can probably guess what this means
}

Upvotes: 43

Related Questions