Code Monkey home page Code Monkey logo

Comments (8)

Abdull avatar Abdull commented on August 31, 2024 8

Here is my solution to compare (possibly) differently-dimensioned images: first place each image on an empty canvas having the maximum width and height among both images. Then compare these identically sized versions. I use sharp for the image dimension harmonization.

#!/usr/bin/env node
// filename: pixelmatch-different-size.mjs
'use strict';

import fs from 'fs';
import { PNG } from 'pngjs'; // npm install pngjs ... https://www.npmjs.com/package/pngjs/v/6.0.0
import pixelmatch from 'pixelmatch'; // npm install pixelmatch ... https://www.npmjs.com/package/pixelmatch/v/5.3.0
import sharp from 'sharp'; // npm install sharp ... https://www.npmjs.com/package/sharp/v/0.31.1

const buffer1 = fs.readFileSync('img1.png'); // https://nodejs.org/api/fs.html#fsreadfilesyncpath-options
const sharp1 = sharp(buffer1); // https://sharp.pixelplumbing.com/api-constructor
const sharp1Metadata = await sharp1.metadata(); // https://sharp.pixelplumbing.com/api-input#metadata

const buffer2 = fs.readFileSync('img2.png');
const sharp2 = sharp(buffer2);
const sharp2Metadata = await sharp2.metadata();

// choose maximum width and height among both images, providing a bounding box canvas which can contain either image
const maximumWidth = Math.max(sharp1Metadata.width, sharp2Metadata.width);
const maximumHeight = Math.max(sharp1Metadata.height, sharp2Metadata.height);
    
// derive a new version "boxedBuffer1" of "img1.png", conceptually placing "img1.png" in the upper-left corner of a bounding box canvas
const boxedBuffer1 = await sharp1
  .resize({ // https://sharp.pixelplumbing.com/api-resize#resize
    width: maximumWidth,
    height: maximumHeight,
    fit: 'contain', // instead of cropping or stretching, use "(letter/pillar/window)-boxing" (black space) to fill any excess space
    position: 'left top' // arrange the original image in the upper-left corner
  })
  .raw()
  .toBuffer();

const boxedBuffer2 = await sharp2
  .resize({
    width: maximumWidth,
    height: maximumHeight,
    fit: 'contain',
    position: 'left top'
  })
  .raw()
  .toBuffer();

// boxedBuffer1 and boxedBuffer2 will have the same dimensions

const diff = new PNG({ // create a new PNG for holding the pixelmatch difference
  width: maximumWidth,
  height: maximumHeight
});

const numDiffPixels = pixelmatch(
  boxedBuffer1,
  boxedBuffer2,
  diff.data,
  maximumWidth,
  maximumHeight,
  {threshold: 0.05} // my favorite threshold to also consider lighter grays as differences
);

fs.writeFileSync('diff-boxed.png', PNG.sync.write(diff));
await sharp1.png().toFile('img1-boxed.png');
await sharp2.png().toFile('img2-boxed.png');

Example

img1.png: img1

img2.png: img2

$ node pixelmatch-different-size.mjs    
numDiffPixels:  69302

img1-boxed.png: img1-boxed

img2-boxed.png: img2-boxed

diff-boxed.png: diff-boxed

from pixelmatch.

mourner avatar mourner commented on August 31, 2024 4

Currently no easy way to compare differently sized images, but it's possible to prepare for it manually if you slice out the relevant pixels from a bigger image before feeding to pixelmatch. Something like this:

var croppedImage = new Uint8Array(4 * smallerHeight * smallerWidth);

for (var y = 0; y < smallerHeight; y++) {
    for (var x = 0; x < smallerWidth; x++) {
        var i = (y * biggerWidth + x) * 4;
        var j = (y * smallerWidth + x) * 4;
        croppedImage[j + 0] = biggerImage[i + 0];
        croppedImage[j + 1] = biggerImage[i + 1];
        croppedImage[j + 2] = biggerImage[i + 2];
        croppedImage[j + 3] = biggerImage[i + 3];
    }    
}

from pixelmatch.

htho avatar htho commented on August 31, 2024 2

here is my minimal-dependency solution to the problem:

/** Creates a diff of the given input images, which may have different sizes. 
 * @param {PNG} img1
 * @param {PNG} img2
 * @param {pixelmatch.PixelmatchOptions} options
 * @returns {{diff: PNG, numOfDiffPixels: number}}
 */
function createDiff(img1, img2, options) {
	const diffDimensions = {
		width: Math.max(img1.width, img2.width),
		height: Math.max(img1.height, img2.height),
	};

	const resizedImg1 = createResized(img1, diffDimensions);
	const resizedImg2 = createResized(img2, diffDimensions);

	const diff = new PNG(diffDimensions);

	const numOfDiffPixels = pixelmatch(
		resizedImg1.data,
		resizedImg2.data,
		diff.data,
		diffDimensions.width,
		diffDimensions.height,
		options,
	);

	return {diff, numOfDiffPixels};
}

/** Cretes a copy of {@link img}, with the {@link dimensions}.
 * @param {PNG} img 
 * @param {{width: number, height: number}} dimensions 
 * @returns {PNG} 
 */
function createResized(img, dimensions) {
	if(img.width > dimensions.width || img.height > dimensions.height) {
		throw new Error(`New dimensions expected to be greater than or equal to the original dimensions!`);
	}
	const resized = new PNG(dimensions);
	PNG.bitblt(img, resized, 0, 0, img.width, img.height);

	return resized;
}

from pixelmatch.

bronius avatar bronius commented on August 31, 2024 2

here is my minimal-dependency solution to the problem:

This works really well, @htho and @Abdull -- thank you for posting your implementations. Setting an automatically sized, common-dimensions canvas as a starting point and comparing from 0,0 is a great solution.

from pixelmatch.

zinen avatar zinen commented on August 31, 2024 1

I have had luck with subimage-match. It allows different sizes of images and is basically developed around this repo's method and efficiency.

Not to steal credits though... just for the sake of helping on this issue.

from pixelmatch.

htho avatar htho commented on August 31, 2024 1

Looking at the arguments of Pixelmatch, it is very unlikely to ever support different image sizes out-of-the box.

Pixelmatch operates on Buffers. It does not care where these buffers come from, it does not create new ones.

Pixelmatch receives two Buffers of input-image data.
In these Buffers, 4 bytes of data make a pixel. Buffers are 1-Dimensional. There is no information about the width and the height of the images stored in these buffers. Width and height are meta-data.

We provide a Buffer for the output diff.

We need to provide the dimensions Pixelmatch uses to interpret these Buffers.

Pixelmatch does one thing. I like that.

from pixelmatch.

cqtangsong avatar cqtangsong commented on August 31, 2024

Excuse me,Does it support the comparison of two images of different sizes, Now? if so how to set it

from pixelmatch.

ansonliao avatar ansonliao commented on August 31, 2024

Hi @zinen , look like subimage-match can't handle different sizes of images by some scenarios, for example, my case is the capture the image from different iPhone model iOS simulator, subimage-match can't handle this case, for example, capture the same image from iPhone 11 pro iOS simulator and iPhone 14 Pro Max iOS simulator.

from pixelmatch.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.