Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
blueimp committed Dec 7, 2016
0 parents commit 1fbe8d7
Show file tree
Hide file tree
Showing 9 changed files with 288 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -0,0 +1,3 @@
.DS_Store
node_modules
test/out*.png
2 changes: 2 additions & 0 deletions .npmignore
@@ -0,0 +1,2 @@
*
!index.js
98 changes: 98 additions & 0 deletions README.md
@@ -0,0 +1,98 @@
# FFmpeg Image Diff
An image diffing library using [FFmpeg](https://www.ffmpeg.org/).
Creates an image showing perceptual differences and returns
[SSIM](https://en.wikipedia.org/wiki/Structural_similarity) data.

## Install

```sh
npm install ffmpeg-image-diff
```

## Usage

Displaying
[SSIM](https://en.wikipedia.org/wiki/Structural_similarity) data only:
```js
const imgDiff = require('ffmpeg-image-diff')

imgDiff(
referenceImage, // e.g. 'test/lenna.png'
comparisonImage // e.g. 'test/lenna-edit.png'
).then((ssim) => {
console.log(ssim)
}).catch((error) => {
console.error(error)
})
```

Creating a differential image with default options:
```js
const imgDiff = require('ffmpeg-image-diff')

imgDiff(
referenceImage, // e.g. 'test/lenna.png'
comparisonImage, // e.g. 'test/lenna-edit.png'
outputImage, // e.g. 'test/out.png'
{
ssim: true, // true or false
similarity: 0.01, // 1.0 - 0.01
blend: 1.0, // 1.0 - 0.0
opacity: 0.1, // 1.0 - 0.0
color: 'pink' // pink, yellow, green, blue or ''
}
).then((ssim) => {
console.log(ssim)
}).catch((error) => {
console.error(error)
})
```

## Options

### ssim
If set to `false`, disables
[SSIM](https://en.wikipedia.org/wiki/Structural_similarity) calculation.
*Default:* `true`

### similarity
Defines the threshold to identify image differences.
`0.01` matches slight differences, while `1.0` only matches stark contrast.
*Range:* `1.0 - 0.01`
*Default:* `0.01`

### blend
Blend percentage for the differential pixels.
Higher values result in semi-transparent pixels, with a higher transparency the
more similar the pixels color is to the original color.
*Range:* `1.0 - 0.0`
*Default:* `1.0`

### opacity
Opacity of the reference image as backdrop for the differential image.
Higher values result in a more opaque background image.
*Range:* `1.0 - 0.0`
*Default:* `0.1`

### color
The color balance of the differential pixels.
An empty string displays the default differential pixels.
*Set:* `['pink', 'yellow', 'green', 'blue', '']`
*Default:* `'pink'`

## Samples

### Reference image
![Lenna](test/lenna.png)

### Comparison image
![Lenna Edit](test/lenna-edit.png)

### Output image
![Lenna Diff](test/lenna-diff.png)

## License
Released under the [MIT license](https://opensource.org/licenses/MIT).

## Author
[Sebastian Tschan](https://blueimp.net/)
108 changes: 108 additions & 0 deletions index.js
@@ -0,0 +1,108 @@
'use strict'

/*
* An image diffing library using FFmpeg.
* Creates an image showing perceptual differences and returns SSIM data.
*
* Tested with FFmpeg versions 3.0 (Linux) and 3.2 (OSX).
*
* For documentation on the image filters used, please see
* https://ffmpeg.org/ffmpeg-filters.html
*
* On the definition of SSIM, please see
* https://en.wikipedia.org/wiki/Structural_similarity
*
* Copyright 2016, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* https://www.opensource.org/licenses/MIT
*/

function ffmpegImageDiff (refImg, cmpImg, args) {
return new Promise((resolve, reject) => {
require('child_process').execFile(
'ffmpeg',
[
'-loglevel',
'error',
'-i',
refImg,
'-i',
cmpImg
].concat(args),
function (error, stdout, stderr) {
if (error) {
return reject(error)
}
// SSIM log output has the following format:
// n:1 R:x.xxxxxx G:x.xxxxxx B:x.xxxxxx All:x.xxxxxx (xx.xxxxxx)
// We only keep the R, G, B and All values and turn them into an object:
resolve(stdout.split(' ').slice(1, -1).reduce((obj, val) => {
const tupel = val.split(':')
obj[tupel[0]] = parseFloat(tupel[1])
return obj
}, {}))
}
).on('error', reject)
})
}

module.exports = function (refImg, cmpImg, outImg, opts) {
if (!outImg) {
// Resolve with the SSIM stats without creating a differential image:
return ffmpegImageDiff(refImg, cmpImg, [
'-filter_complex',
'ssim=stats_file=-',
'-f',
'null',
'-'
])
}
const options = {
ssim: true, // true or false
similarity: 0.01, // 1.0 - 0.01
blend: 1.0, // 1.0 - 0.0
opacity: 0.1, // 1.0 - 0.0
color: 'pink' // pink, yellow, green, blue, '', or FFmpeg colorbalance
}
if (opts) Object.assign(options, opts)
const ssim = options.ssim
? 'ssim=stats_file=-[ssim];[ssim][1]'
: ''
const colorkey = 'format=rgba,colorkey=white' +
`:similarity=${options.similarity}:blend=${options.blend}`
let colorbalance = 'colorbalance='
switch (options.color) {
case 'pink':
colorbalance += `rs=1:gs=-1:bs=1:rm=1:gm=-1:bm=1:rh=1:gh=-1:bh=1`
break
case 'yellow':
colorbalance += `rs=1:gs=1:bs=-1:rm=1:gm=1:bm=-1:rh=1:gh=1:bh=-1`
break
case 'green':
colorbalance += `rs=-1:gs=1:bs=-1:rm=-1:gm=1:bm=-1:rh=-1:gh=1:bh=-1`
break
case 'blue':
colorbalance += `rs=-1:gs=-1:bs=1:rm=-1:gm=-1:bm=1:rh=-1:gh=-1:bh=1`
break
default:
colorbalance += options.color
}
// Create a differential image with transparent background.
// The differences are highlighted with the given colorbalance:
const diff = `blend=all_mode=phoenix,${colorkey},${colorbalance}[diff];`
// Use the reference file as background with the given opacity:
const bg = options.opacity < 1
? `format=rgba,colorchannelmixer=aa=${options.opacity}[bg];[bg]`
: ''
// Overlay the background with the diff:
const overlay = `[0]${bg}[diff]overlay=format=rgb`
// Create a differential image with the given arguments:
return ffmpegImageDiff(refImg, cmpImg, [
'-filter_complex',
`${ssim}${diff}${overlay}`,
'-y',
outImg
])
}
36 changes: 36 additions & 0 deletions package.json
@@ -0,0 +1,36 @@
{
"name": "ffmpeg-image-diff",
"version": "0.0.0",
"title": "FFmpeg Image Diff",
"description": "An image diffing library using FFmpeg. Creates an image showing perceptual differences and returns SSIM data.",
"keywords": [
"ffmpeg",
"image",
"diff",
"ssim"
],
"homepage": "https://github.com/blueimp/node-ffmpeg-image-diff",
"author": {
"name": "Sebastian Tschan",
"url": "https://blueimp.net"
},
"repository": {
"type": "git",
"url": "git://github.com/blueimp/node-ffmpeg-image-diff.git"
},
"license": "MIT",
"engines": {
"node": ">=6.0.0"
},
"devDependencies": {
"standard": "8.6.0"
},
"scripts": {
"lint": "standard *.js test/*.js",
"unit": "./test/run.js",
"test": "npm run lint && npm run unit",
"preversion": "npm test",
"postversion": "git push --tags origin HEAD && npm publish"
},
"main": "index.js"
}
Binary file added test/lenna-diff.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/lenna-edit.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/lenna.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 41 additions & 0 deletions test/run.js
@@ -0,0 +1,41 @@
#!/usr/bin/env node

'use strict'

process.chdir(__dirname)

// Throw for any unhandled rejections in Promise chains:
process.on('unhandledRejection', function (reason) { throw reason })

const assert = require('assert')
const imgDiff = require('../index')

const refImg = 'lenna.png'
const cmpImg = 'lenna-edit.png'

function test (outImg, options) {
return imgDiff(refImg, cmpImg, outImg, options)
.then((result) => {
assert.ok(result)
if (options && options.ssim === false) {
assert.deepEqual(result, {})
} else {
assert.strictEqual(typeof result.R, 'number')
assert.strictEqual(typeof result.G, 'number')
assert.strictEqual(typeof result.B, 'number')
assert.strictEqual(typeof result.All, 'number')
}
})
}

test() // SSIM only
.then(() => test('out.png')) // Default options (color: 'pink')
.then(() => test('out-color-yellow.png', {color: 'yellow'}))
.then(() => test('out-color-green.png', {color: 'green'}))
.then(() => test('out-color-blue.png', {color: 'blue'}))
.then(() => test('out-color-empty.png', {color: ''}))
.then(() => test('out-similarity-1.png', {similarity: 1}))
.then(() => test('out-blend-0.png', {blend: 0}))
.then(() => test('out-opacity-0.png', {opacity: 0}))
.then(() => test('out-opacity-1.png', {opacity: 1}))
.then(() => test('out-ssim-false.png', {ssim: false}))

0 comments on commit 1fbe8d7

Please sign in to comment.