Reputation: 461
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
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
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
Reputation: 19301
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
Blob.arrayBuffer
to create promises for buffers of file data, andnew Uint8Array
to construct a typed array as opposed to creating a DataView object."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
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
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
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
Reputation: 72
Maybe this tool will help: https://github.com/HumbleSoftware/js-imagediff/
Upvotes: 5
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