Compare two Images in JavaScript
Asked Answered
S

8

37

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?

Saied answered 19/5, 2011 at 23:53 Comment(1)
It probably does factor in to the equation, as you have far more facilities and permissions within an extension, than in general javascript within the browser.Braid
D
43

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
}
Dett answered 19/5, 2011 at 23:57 Comment(3)
Or write them to 2 canvases and examine the pixels from each and compare them with some sort of snazzy algorithm. But honestly that sounds pretty hard.Jehanna
I think you nailed it - "tell you whether or not the images are identical" - isn't that what they're after ;)Kelcy
+1 for this answer. something similar to nude.js is as close as you can get on client side/JavaScript davidwalsh.name/nudejsPyrexia
S
16

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.

Subotica answered 3/4, 2014 at 11:54 Comment(0)
L
13

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.

Lheureux answered 19/11, 2016 at 1:59 Comment(3)
I can not get this to work. I just want to test, if this image looks exactly the same or not. You can look at my test here: cdn.axiell.com/~dev/gota/image-test/html-template.html. It is quite straight forward and simple code doing the test. But even if these images are the same, the result is that they are not.Brewer
wouldnt it be more easy of both the images are grey scaled?Knepper
I am getting "ReferenceError: window is not defined" for compare methodEmpathy
A
5

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

Appling answered 26/6, 2012 at 18:19 Comment(0)
S
3

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

Skiest answered 27/9, 2021 at 17:48 Comment(2)
Hmm... This tool doesn't seem to provide a way of knowing whether two images is the same in JavaScript. As far as I can till it produces an image output that can only be determined by humans. All it really needed to do is return true or false.Scrutineer
@RasmusPuls pixelmatch() returns the difference in pixels, so if it's 0, then the images are identical, otherwise they aren't. Does this fulfill your requirements?Merrythought
M
2

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">
Merrythought answered 24/4, 2022 at 2:55 Comment(0)
K
1

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.

Khalif answered 11/8, 2022 at 3:39 Comment(0)
S
0

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');

Sphalerite answered 8/12, 2021 at 20:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.