Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 1fbe8d7
Showing
9 changed files
with
288 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.DS_Store | ||
node_modules | ||
test/out*.png |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
* | ||
!index.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
} |
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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})) |