Skip to content

Commit

Permalink
Use eslint and dockerized mocha for testing.
Browse files Browse the repository at this point in the history
  • Loading branch information
blueimp committed Feb 19, 2019
1 parent dd53939 commit 8290484
Show file tree
Hide file tree
Showing 15 changed files with 1,612 additions and 96 deletions.
1 change: 1 addition & 0 deletions .dockerignore
@@ -0,0 +1 @@
*
3 changes: 3 additions & 0 deletions .eslintrc.js
@@ -0,0 +1,3 @@
module.exports = {
extends: 'standard'
}
2 changes: 0 additions & 2 deletions .gitignore
@@ -1,3 +1 @@
.DS_Store
node_modules
test/out*.png
23 changes: 23 additions & 0 deletions Dockerfile
@@ -0,0 +1,23 @@
FROM alpine:3.9

RUN apk --no-cache add \
nodejs \
npm \
ffmpeg \
&& npm install -g \
npm@latest \
mocha@5.2.0 \
# Clean up obsolete files:
&& rm -rf \
/tmp/* \
/root/.npm

# Avoid permission issues with host mounts by assigning a user/group with
# uid/gid 1000 (usually the ID of the first user account on GNU/Linux):
RUN adduser -D -u 1000 mocha

USER mocha

WORKDIR /opt

ENTRYPOINT ["mocha"]
9 changes: 4 additions & 5 deletions README.md
Expand Up @@ -6,8 +6,7 @@ Creates an image showing perceptual differences and returns

## Requirements
This is a thin wrapper around [FFmpeg](https://www.ffmpeg.org/) and has no other
dependencies.
Tested with FFmpeg versions 3.0 (Linux) and 3.2 (OSX).
dependencies.

## Install

Expand Down Expand Up @@ -91,13 +90,13 @@ An empty string displays the default differential pixels.
## Samples

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

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

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

## License
Released under the [MIT license](https://opensource.org/licenses/MIT).
Expand Down
10 changes: 10 additions & 0 deletions docker-compose.yml
@@ -0,0 +1,10 @@
version: '3.7'
services:
test:
build: .
init: true
command: index.test.js
read_only: true
volumes:
- .:/opt:ro
- ./output:/opt/output
76 changes: 34 additions & 42 deletions index.js
Expand Up @@ -4,8 +4,6 @@
* 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
*
Expand All @@ -19,39 +17,31 @@
* https://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)
const execFile = require('util').promisify(require('child_process').execFile)

function ffmpeg (refImg, cmpImg, args) {
return execFile(
'ffmpeg',
['-loglevel', 'error', '-i', refImg, '-i', cmpImg].concat(args)
).then(function (result) {
// 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:
return result.stdout
.split(' ')
.slice(1, -1)
.reduce(function (obj, val) {
const tupel = val.split(':')
obj[tupel[0]] = parseFloat(tupel[1])
return obj
}, {})
})
}

module.exports = function (refImg, cmpImg, outImg, opts) {
function imageDiff (refImg, cmpImg, outImg, opts) {
if (!outImg) {
// Resolve with the SSIM stats without creating a differential image:
return ffmpegImageDiff(refImg, cmpImg, [
return ffmpeg(refImg, cmpImg, [
'-filter_complex',
'ssim=stats_file=-',
'-f',
Expand All @@ -60,17 +50,16 @@ module.exports = function (refImg, cmpImg, outImg, opts) {
])
}
const options = {
ssim: true, // true or false
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: 'magenta' // magenta, yellow, cyan, red green, blue or ''
blend: 1.0, // 1.0 - 0.0
opacity: 0.1, // 1.0 - 0.0
color: 'magenta' // magenta, yellow, cyan, red green, blue or ''
}
if (opts) Object.assign(options, opts)
const ssim = options.ssim
? 'ssim=stats_file=-[ssim];[ssim][1]'
: ''
const colorkey = 'format=rgba,colorkey=white' +
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) {
Expand Down Expand Up @@ -99,16 +88,19 @@ module.exports = function (refImg, cmpImg, outImg, opts) {
// 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]`
: ''
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, [
return ffmpeg(refImg, cmpImg, [
'-filter_complex',
`${ssim}${diff}${overlay}`,
'-y',
outImg
])
}

module.exports = imageDiff
175 changes: 175 additions & 0 deletions index.test.js
@@ -0,0 +1,175 @@
'use strict'

/* global describe, it */

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

const referenceImage = 'samples/lenna.png'
const comparisonImage = 'samples/lenna-edit.png'

describe('image diff', function () {
this.slow(300)

it('no output image', async function () {
this.slow(150)
const result = await imgDiff(referenceImage, comparisonImage)
assert.strictEqual(typeof result.R, 'number')
assert.strictEqual(typeof result.G, 'number')
assert.strictEqual(typeof result.B, 'number')
assert.strictEqual(typeof result.All, 'number')
})

it('default', async function () {
const result = await imgDiff(
referenceImage,
comparisonImage,
'output/default.png'
)
assert.strictEqual(typeof result.R, 'number')
assert.strictEqual(typeof result.G, 'number')
assert.strictEqual(typeof result.B, 'number')
assert.strictEqual(typeof result.All, 'number')
})

it('color yellow', async function () {
const result = await imgDiff(
referenceImage,
comparisonImage,
'output/color-yellow.png',
{ color: 'yellow' }
)
assert.strictEqual(typeof result.R, 'number')
assert.strictEqual(typeof result.G, 'number')
assert.strictEqual(typeof result.B, 'number')
assert.strictEqual(typeof result.All, 'number')
})

it('color cyan', async function () {
const result = await imgDiff(
referenceImage,
comparisonImage,
'output/color-cyan.png',
{ color: 'cyan' }
)
assert.strictEqual(typeof result.R, 'number')
assert.strictEqual(typeof result.G, 'number')
assert.strictEqual(typeof result.B, 'number')
assert.strictEqual(typeof result.All, 'number')
})

it('color red', async function () {
const result = await imgDiff(
referenceImage,
comparisonImage,
'output/color-red.png',
{ color: 'red' }
)
assert.strictEqual(typeof result.R, 'number')
assert.strictEqual(typeof result.G, 'number')
assert.strictEqual(typeof result.B, 'number')
assert.strictEqual(typeof result.All, 'number')
})

it('color green', async function () {
const result = await imgDiff(
referenceImage,
comparisonImage,
'output/color-green.png',
{ color: 'green' }
)
assert.strictEqual(typeof result.R, 'number')
assert.strictEqual(typeof result.G, 'number')
assert.strictEqual(typeof result.B, 'number')
assert.strictEqual(typeof result.All, 'number')
})

it('color blue', async function () {
const result = await imgDiff(
referenceImage,
comparisonImage,
'output/color-blue.png',
{ color: 'blue' }
)
assert.strictEqual(typeof result.R, 'number')
assert.strictEqual(typeof result.G, 'number')
assert.strictEqual(typeof result.B, 'number')
assert.strictEqual(typeof result.All, 'number')
})

it('color empty', async function () {
const result = await imgDiff(
referenceImage,
comparisonImage,
'output/color-empty.png',
{ color: '' }
)
assert.strictEqual(typeof result.R, 'number')
assert.strictEqual(typeof result.G, 'number')
assert.strictEqual(typeof result.B, 'number')
assert.strictEqual(typeof result.All, 'number')
})

it('similarity 1', async function () {
const result = await imgDiff(
referenceImage,
comparisonImage,
'output/similarity-1.png',
{ similarity: 1 }
)
assert.strictEqual(typeof result.R, 'number')
assert.strictEqual(typeof result.G, 'number')
assert.strictEqual(typeof result.B, 'number')
assert.strictEqual(typeof result.All, 'number')
})

it('blend 0', async function () {
const result = await imgDiff(
referenceImage,
comparisonImage,
'output/blend-0.png',
{ blend: 0 }
)
assert.strictEqual(typeof result.R, 'number')
assert.strictEqual(typeof result.G, 'number')
assert.strictEqual(typeof result.B, 'number')
assert.strictEqual(typeof result.All, 'number')
})

it('opacity 0', async function () {
const result = await imgDiff(
referenceImage,
comparisonImage,
'output/opacity-0.png',
{ opacity: 0 }
)
assert.strictEqual(typeof result.R, 'number')
assert.strictEqual(typeof result.G, 'number')
assert.strictEqual(typeof result.B, 'number')
assert.strictEqual(typeof result.All, 'number')
})

it('opacity 1', async function () {
const result = await imgDiff(
referenceImage,
comparisonImage,
'output/opacity-1.png',
{ opacity: 1 }
)
assert.strictEqual(typeof result.R, 'number')
assert.strictEqual(typeof result.G, 'number')
assert.strictEqual(typeof result.B, 'number')
assert.strictEqual(typeof result.All, 'number')
})

it('ssim false', async function () {
const result = await imgDiff(
referenceImage,
comparisonImage,
'output/ssim-false.png',
{ ssim: false }
)
assert.strictEqual(result.constructor, Object)
assert.strictEqual(Object.entries(result).length, 0)
})
})
1 change: 1 addition & 0 deletions output/.gitignore
@@ -0,0 +1 @@
*.png

0 comments on commit 8290484

Please sign in to comment.