Ad

Comparing Buffers

I would like to compare two images in memory as opposed to relying on reading and writing from the file system.

I might be going about this the wrong way and if so I'd appreciate some sort of hint/help.

I've created a repo showcasing what I'm seeing: https://github.com/philmirez/comparing-screenshots

const puppeteer = require('puppeteer')
const pixelmatch = require('pixelmatch')
const PNG = require('pngjs').PNG
const fs = require('fs')

function getTime(startTime) {
  return Date.now() - startTime
}

async function takeScreenshot(page, options) {
  const startTime = Date.now()
  await page.goto(
    options.url,
    {
      waitUntil: 'networkidle2'
    }
  )
  const totalTime = getTime(startTime)

  await page.setViewport({
    height: options.height,
    width: options.width
  })
  const data = await page.screenshot({
    path: options.path || null,
    fullPage: true
  })

  return {
    data: data,
    time: totalTime
  }
}

async function takeScreenshots(stagingOptions, prodOptions) {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()
  const staging = await takeScreenshot(page, stagingOptions)
  const prod = await takeScreenshot(page, prodOptions)

  await browser.close();

  return [
    staging,
    prod
  ]
}

const run = async function () {
  try {
    const height = 900
    const width = 640
    const diff = new PNG({width, height})
    const stagingOptions = {
      path: 'stagingScreenshot.png',
      url: 'https://www.google.com/search?q=cat',
      height,
      width
    }
    const prodOptions = {
      path: 'prodScreenshot.png',
      url: 'https://www.google.com/search?q=cat',
      height,
      width
    }
    const [stagingScreenshot, prodScreenshot] = await takeScreenshots(stagingOptions, prodOptions)
    const allocationSize = height * width * 4
    const stagingBuff = Buffer.alloc(allocationSize)
    const prodBuff = Buffer.alloc(allocationSize)
    stagingBuff.fill(stagingScreenshot.data)
    prodBuff.fill(prodScreenshot.data)

    const numDiffPixels = pixelmatch(stagingBuff, prodBuff, diff.data, width, height, { threshold: 0.1 })
    fs.writeFileSync('diff.png', PNG.sync.write(diff))
    console.log('Results', JSON.stringify({
      diffScore: numDiffPixels / (height * width)
    }))
  } catch (error) {
    console.error(error)
  }
}

run()

The result diff image looks like red static. enter image description here

Both images have the same dimensions 1101 x 2614.

stagingScreenshot.png: enter image description here

prodScreenshot.png: enter image description here

Side note: One thing that threw me off was when I was reading the pixelmatch/index.js file, it says...

if (img1.length !== width * height * 4) throw new Error('Image data size does not match width/height.')

I don't understand why width and height are being multiplied by 4.

Ad

Answer

A few things to fix here:

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

This is not right because when you send the width and the height to takeScreenshot, you are using them to set the viewPort. But as you are taking a fullscreen screnshot, the result will be and image bigger than that.

So you should create the diff based on one of the screenshots. Something like this:

const [stagingScreenshot, prodScreenshot] = await takeScreenshots(stagingOptions, prodOptions)
const prodPng = PNG.sync.read(prodScreenshot.data)
const stgPng = PNG.sync.read(stagingScreenshot.data)
const diff = new PNG({width: prodPng.width, height: prodPng.height})

const numDiffPixels = pixelmatch(prodPng.data, stgPng.data, diff.data, prodPng.width, prodPng.height, { threshold: 0.1 })
Ad
source: stackoverflow.com
Ad