blink-diff
Advanced tools
Comparing version 1.0.7 to 1.0.8
@@ -1,4 +0,8 @@ | ||
Changelog | ||
CHANGELOG | ||
========= | ||
v1.0.8 - 03/29/15 | ||
* Cleanup | ||
* Added code-Climate, coveralls, and others | ||
v1.0.7 - 12/06/14 | ||
@@ -5,0 +9,0 @@ * Block-out areas + color definition |
1440
index.js
@@ -1,7 +0,7 @@ | ||
// Copyright 2014 Yahoo! Inc. | ||
// Copyright 2014-2015 Yahoo! Inc. | ||
// Copyrights licensed under the Mit License. See the accompanying LICENSE file for terms. | ||
var assert = require('assert'), | ||
PNGImage = require('pngjs-image'), | ||
Promise = require('promise'); | ||
PNGImage = require('pngjs-image'), | ||
Promise = require('promise'); | ||
@@ -131,86 +131,86 @@ /** | ||
this._imageA = options.imageA; | ||
this._imageAPath = options.imageAPath; | ||
assert.ok(options.imageAPath || options.imageA, "Path to image A not given."); | ||
this._imageA = options.imageA; | ||
this._imageAPath = options.imageAPath; | ||
assert.ok(options.imageAPath || options.imageA, "Image A not given."); | ||
this._imageB = options.imageB; | ||
this._imageBPath = options.imageBPath; | ||
assert.ok(options.imageBPath || options.imageB, "Path to image B not given."); | ||
this._imageB = options.imageB; | ||
this._imageBPath = options.imageBPath; | ||
assert.ok(options.imageBPath || options.imageB, "Image B not given."); | ||
this._imageOutput = null; | ||
this._imageOutputPath = options.imageOutputPath; | ||
this._imageOutputLimit = options.imageOutputLimit || BlinkDiff.OUTPUT_ALL; | ||
this._imageOutput = null; | ||
this._imageOutputPath = options.imageOutputPath; | ||
this._imageOutputLimit = options.imageOutputLimit || BlinkDiff.OUTPUT_ALL; | ||
// Pixel or Percent | ||
this._thresholdType = options.thresholdType || BlinkDiff.THRESHOLD_PIXEL; | ||
// Pixel or Percent | ||
this._thresholdType = options.thresholdType || BlinkDiff.THRESHOLD_PIXEL; | ||
// How many pixels different to ignore. | ||
this._threshold = options.threshold || 500; | ||
// How many pixels different to ignore. | ||
this._threshold = options.threshold || 500; | ||
this._delta = options.delta || 20; | ||
this._delta = options.delta || 20; | ||
this._outputMaskRed = options.outputMaskRed || 255; | ||
this._outputMaskGreen = options.outputMaskGreen || 0; | ||
this._outputMaskBlue = options.outputMaskBlue || 0; | ||
this._outputMaskAlpha = options.outputMaskAlpha || 255; | ||
this._outputMaskOpacity = options.outputMaskOpacity || 0.7; | ||
this._outputMaskRed = options.outputMaskRed || 255; | ||
this._outputMaskGreen = options.outputMaskGreen || 0; | ||
this._outputMaskBlue = options.outputMaskBlue || 0; | ||
this._outputMaskAlpha = options.outputMaskAlpha || 255; | ||
this._outputMaskOpacity = options.outputMaskOpacity || 0.7; | ||
this._outputBackgroundRed = options.outputBackgroundRed || 0; | ||
this._outputBackgroundGreen = options.outputBackgroundGreen || 0; | ||
this._outputBackgroundBlue = options.outputBackgroundBlue || 0; | ||
this._outputBackgroundAlpha = options.outputBackgroundAlpha; | ||
this._outputBackgroundOpacity = options.outputBackgroundOpacity || 0.6; | ||
this._outputBackgroundRed = options.outputBackgroundRed || 0; | ||
this._outputBackgroundGreen = options.outputBackgroundGreen || 0; | ||
this._outputBackgroundBlue = options.outputBackgroundBlue || 0; | ||
this._outputBackgroundAlpha = options.outputBackgroundAlpha; | ||
this._outputBackgroundOpacity = options.outputBackgroundOpacity || 0.6; | ||
if (options.hideShift) { | ||
this._outputShiftRed = this._outputBackgroundRed; | ||
this._outputShiftGreen = this._outputBackgroundGreen; | ||
this._outputShiftBlue = this._outputBackgroundBlue; | ||
this._outputShiftAlpha = this._outputBackgroundAlpha; | ||
this._outputShiftOpacity = this._outputBackgroundOpacity; | ||
if (options.hideShift) { | ||
this._outputShiftRed = this._outputBackgroundRed; | ||
this._outputShiftGreen = this._outputBackgroundGreen; | ||
this._outputShiftBlue = this._outputBackgroundBlue; | ||
this._outputShiftAlpha = this._outputBackgroundAlpha; | ||
this._outputShiftOpacity = this._outputBackgroundOpacity; | ||
} else { | ||
this._outputShiftRed = options.outputShiftRed || 200; | ||
this._outputShiftGreen = options.outputShiftGreen || 100; | ||
this._outputShiftBlue = options.outputShiftBlue || 0; | ||
this._outputShiftAlpha = options.outputShiftAlpha || 255; | ||
this._outputShiftOpacity = options.outputShiftOpacity || 0.7; | ||
} | ||
} else { | ||
this._outputShiftRed = options.outputShiftRed || 200; | ||
this._outputShiftGreen = options.outputShiftGreen || 100; | ||
this._outputShiftBlue = options.outputShiftBlue || 0; | ||
this._outputShiftAlpha = options.outputShiftAlpha || 255; | ||
this._outputShiftOpacity = options.outputShiftOpacity || 0.7; | ||
} | ||
this._blockOut = options.blockOut || []; | ||
if (typeof this._blockOut != 'object' && (this._blockOut.length !== undefined)) { | ||
this._blockOut = [this._blockOut]; | ||
} | ||
this._blockOut = options.blockOut || []; | ||
if (typeof this._blockOut != 'object' && (this._blockOut.length !== undefined)) { | ||
this._blockOut = [this._blockOut]; | ||
} | ||
this._blockOutRed = options.blockOutRed || 0; | ||
this._blockOutGreen = options.blockOutGreen || 0; | ||
this._blockOutBlue = options.blockOutBlue || 0; | ||
this._blockOutAlpha = options.blockOutAlpha || 255; | ||
this._blockOutOpacity = options.blockOutOpacity || 1.0; | ||
this._blockOutRed = options.blockOutRed || 0; | ||
this._blockOutGreen = options.blockOutGreen || 0; | ||
this._blockOutBlue = options.blockOutBlue || 0; | ||
this._blockOutAlpha = options.blockOutAlpha || 255; | ||
this._blockOutOpacity = options.blockOutOpacity || 1.0; | ||
this._copyImageAToOutput = options.copyImageAToOutput || true; | ||
this._copyImageBToOutput = options.copyImageBToOutput || false; | ||
this._copyImageAToOutput = options.copyImageAToOutput || true; | ||
this._copyImageBToOutput = options.copyImageBToOutput || false; | ||
this._filter = options.filter || []; | ||
this._filter = options.filter || []; | ||
this._debug = options.debug || false; | ||
this._debug = options.debug || false; | ||
this._composition = options.composition || true; | ||
this._composeLeftToRight = options.composeLeftToRight || false; | ||
this._composeTopToBottom = options.composeTopToBottom || false; | ||
this._composition = options.composition || true; | ||
this._composeLeftToRight = options.composeLeftToRight || false; | ||
this._composeTopToBottom = options.composeTopToBottom || false; | ||
this._hShift = options.hShift || 2; | ||
this._vShift = options.vShift || 2; | ||
this._hShift = options.hShift || 2; | ||
this._vShift = options.vShift || 2; | ||
this._cropImageA = options.cropImageA; | ||
this._cropImageB = options.cropImageB; | ||
this._cropImageA = options.cropImageA; | ||
this._cropImageB = options.cropImageB; | ||
// Prepare reference white | ||
this._refWhite = this._convertRgbToXyz({ c1:1, c2:1, c3:1, c4:1 }); | ||
// Prepare reference white | ||
this._refWhite = this._convertRgbToXyz({c1: 1, c2: 1, c3: 1, c4: 1}); | ||
this._perceptual = options.perceptual || false; | ||
this._perceptual = options.perceptual || false; | ||
this._gamma = options.gamma; | ||
this._gammaR = options.gammaR; | ||
this._gammaG = options.gammaG; | ||
this._gammaB = options.gammaB; | ||
this._gamma = options.gamma; | ||
this._gammaR = options.gammaR; | ||
this._gammaG = options.gammaG; | ||
this._gammaB = options.gammaB; | ||
} | ||
@@ -315,24 +315,24 @@ | ||
/** | ||
* Runs the comparison with a promise | ||
* | ||
* @method runWithPromise | ||
* @example | ||
* var blinkDiff = BlinkDiff(...); | ||
* blinkDiff.runWithPromise().then(function (result) { | ||
/** | ||
* Runs the comparison with a promise | ||
* | ||
* @method runWithPromise | ||
* @example | ||
* var blinkDiff = BlinkDiff(...); | ||
* blinkDiff.runWithPromise().then(function (result) { | ||
* ... | ||
* }); | ||
* @return {Promise} | ||
*/ | ||
runWithPromise: function () { | ||
return Promise.denodeify(this.run).call(this); | ||
}, | ||
* @return {Promise} | ||
*/ | ||
runWithPromise: function () { | ||
return Promise.denodeify(this.run).call(this); | ||
}, | ||
/** | ||
* Runs the comparison in node-style | ||
* | ||
* @method run | ||
* @example | ||
* var blinkDiff = BlinkDiff(...); | ||
* blinkDiff.run(function (err, result) { | ||
/** | ||
* Runs the comparison in node-style | ||
* | ||
* @method run | ||
* @example | ||
* var blinkDiff = BlinkDiff(...); | ||
* blinkDiff.run(function (err, result) { | ||
* if (err) { | ||
@@ -344,776 +344,722 @@ * throw err; | ||
* }); | ||
* | ||
* @param {function} fn | ||
*/ | ||
run: function (fn) { | ||
* | ||
* @param {function} fn | ||
*/ | ||
run: function (fn) { | ||
var promise = Promise.resolve(), | ||
result; | ||
var promise = Promise.resolve(), result; | ||
PNGImage.log = function (text) { | ||
this.log('ERROR: ' + text); | ||
throw new Error('ERROR: ' + text); | ||
}.bind(this); | ||
PNGImage.log = function (text) { | ||
this.log('ERROR: ' + text); | ||
throw new Error('ERROR: ' + text); | ||
}.bind(this); | ||
promise.then(function () { | ||
return this._loadImage(this._imageAPath, this._imageA); | ||
promise.then(function () { | ||
return this._loadImage(this._imageAPath, this._imageA); | ||
}.bind(this)).then(function (imageA) { | ||
this._imageA = imageA; | ||
return this._loadImage(this._imageBPath, this._imageB); | ||
}.bind(this)).then(function (imageA) { | ||
this._imageA = imageA; | ||
return this._loadImage(this._imageBPath, this._imageB); | ||
}.bind(this)).then(function (imageB) { | ||
}.bind(this)).then(function (imageB) { | ||
var gamma, | ||
i, len, | ||
rect, | ||
color; | ||
var gamma, i, len, rect, color; | ||
this._imageB = imageB; | ||
this._imageB = imageB; | ||
// Crop images if requested | ||
if (this._cropImageA) { | ||
this._correctDimensions(this._imageA.getWidth(), this._imageA.getHeight(), this._cropImageA); | ||
this._crop("Image-A", this._imageA, this._cropImageA); | ||
} | ||
if (this._cropImageB) { | ||
this._correctDimensions(this._imageB.getWidth(), this._imageB.getHeight(), this._cropImageB); | ||
this._crop("Image-B", this._imageB, this._cropImageB); | ||
} | ||
// Crop images if requested | ||
if (this._cropImageA) { | ||
this._correctDimensions(this._imageA.getWidth(), this._imageA.getHeight(), this._cropImageA); | ||
this._crop("Image-A", this._imageA, this._cropImageA); | ||
} | ||
if (this._cropImageB) { | ||
this._correctDimensions(this._imageB.getWidth(), this._imageB.getHeight(), this._cropImageB); | ||
this._crop("Image-B", this._imageB, this._cropImageB); | ||
} | ||
// Always clip | ||
this._clip(this._imageA, this._imageB); | ||
// Always clip | ||
this._clip(this._imageA, this._imageB); | ||
this._imageOutput = PNGImage.createImage(this._imageA.getWidth(), this._imageA.getHeight()); | ||
this._imageOutput = PNGImage.createImage(this._imageA.getWidth(), this._imageA.getHeight()); | ||
// Make a copy when not in debug mode | ||
if (this._debug) { | ||
this._imageACompare = this._imageA; | ||
this._imageBCompare = this._imageB; | ||
} else { | ||
this._imageACompare = PNGImage.copyImage(this._imageA); | ||
this._imageBCompare = PNGImage.copyImage(this._imageB); | ||
} | ||
// Make a copy when not in debug mode | ||
if (this._debug) { | ||
this._imageACompare = this._imageA; | ||
this._imageBCompare = this._imageB; | ||
} else { | ||
this._imageACompare = PNGImage.copyImage(this._imageA); | ||
this._imageBCompare = PNGImage.copyImage(this._imageB); | ||
} | ||
// Block-out | ||
color = { | ||
red: this._blockOutRed, | ||
green: this._blockOutGreen, | ||
blue: this._blockOutBlue, | ||
alpha: this._blockOutAlpha, | ||
opacity: this._blockOutOpacity | ||
}; | ||
for (i = 0, len = this._blockOut.length; i < len; i++) { | ||
rect = this._blockOut[i]; | ||
// Block-out | ||
color = { | ||
red: this._blockOutRed, | ||
green: this._blockOutGreen, | ||
blue: this._blockOutBlue, | ||
alpha: this._blockOutAlpha, | ||
opacity: this._blockOutOpacity | ||
}; | ||
for (i = 0, len = this._blockOut.length; i < len; i++) { | ||
rect = this._blockOut[i]; | ||
// Make sure the block-out parameters fit | ||
this._correctDimensions(this._imageACompare.getWidth(), this._imageACompare.getHeight(), rect); | ||
// Make sure the block-out parameters fit | ||
this._correctDimensions(this._imageACompare.getWidth(), this._imageACompare.getHeight(), rect); | ||
this._imageACompare.fillRect(rect.x, rect.y, rect.width, rect.height, color); | ||
this._imageBCompare.fillRect(rect.x, rect.y, rect.width, rect.height, color); | ||
} | ||
this._imageACompare.fillRect(rect.x, rect.y, rect.width, rect.height, color); | ||
this._imageBCompare.fillRect(rect.x, rect.y, rect.width, rect.height, color); | ||
} | ||
// Copy image to composition | ||
if (this._copyImageAToOutput) { | ||
this._copyImage(this._debug ? this._imageACompare : this._imageA, this._imageOutput); | ||
} else if (this._copyImageBToOutput) { | ||
this._copyImage(this._debug ? this._imageBCompare : this._imageB, this._imageOutput); | ||
} | ||
// Copy image to composition | ||
if (this._copyImageAToOutput) { | ||
this._copyImage(this._debug ? this._imageACompare : this._imageA, this._imageOutput); | ||
} else if (this._copyImageBToOutput) { | ||
this._copyImage(this._debug ? this._imageBCompare : this._imageB, this._imageOutput); | ||
} | ||
// Apply all filters | ||
this._imageACompare.applyFilters(this._filter); | ||
this._imageBCompare.applyFilters(this._filter); | ||
// Apply all filters | ||
this._imageACompare.applyFilters(this._filter); | ||
this._imageBCompare.applyFilters(this._filter); | ||
// Gamma correction | ||
if (this._gamma || this._gammaR || this._gammaG || this._gammaB) { | ||
gamma = { | ||
r: this._gammaR || this._gamma, | ||
g: this._gammaG || this._gamma, | ||
b: this._gammaB || this._gamma | ||
}; | ||
} | ||
// Gamma correction | ||
if (this._gamma || this._gammaR || this._gammaG || this._gammaB) { | ||
gamma = { | ||
r: this._gammaR || this._gamma, g: this._gammaG || this._gamma, b: this._gammaB || this._gamma | ||
}; | ||
} | ||
// Comparison | ||
result = this._compare(this._imageACompare, this._imageBCompare, this._imageOutput, this._delta, | ||
{ // Output-Mask color | ||
red: this._outputMaskRed, | ||
green: this._outputMaskGreen, | ||
blue: this._outputMaskBlue, | ||
alpha: this._outputMaskAlpha, | ||
opacity: this._outputMaskOpacity | ||
}, | ||
{ // Output-Shift color | ||
red: this._outputShiftRed, | ||
green: this._outputShiftGreen, | ||
blue: this._outputShiftBlue, | ||
alpha: this._outputShiftAlpha, | ||
opacity: this._outputShiftOpacity | ||
}, | ||
{ // Background color | ||
red: this._outputBackgroundRed, | ||
green: this._outputBackgroundGreen, | ||
blue: this._outputBackgroundBlue, | ||
alpha: this._outputBackgroundAlpha, | ||
opacity: this._outputBackgroundOpacity | ||
}, | ||
this._hShift, | ||
this._vShift, | ||
this._perceptual, | ||
gamma | ||
); | ||
// Comparison | ||
result = this._compare(this._imageACompare, this._imageBCompare, this._imageOutput, this._delta, { // Output-Mask color | ||
red: this._outputMaskRed, | ||
green: this._outputMaskGreen, | ||
blue: this._outputMaskBlue, | ||
alpha: this._outputMaskAlpha, | ||
opacity: this._outputMaskOpacity | ||
}, { // Output-Shift color | ||
red: this._outputShiftRed, | ||
green: this._outputShiftGreen, | ||
blue: this._outputShiftBlue, | ||
alpha: this._outputShiftAlpha, | ||
opacity: this._outputShiftOpacity | ||
}, { // Background color | ||
red: this._outputBackgroundRed, | ||
green: this._outputBackgroundGreen, | ||
blue: this._outputBackgroundBlue, | ||
alpha: this._outputBackgroundAlpha, | ||
opacity: this._outputBackgroundOpacity | ||
}, this._hShift, this._vShift, this._perceptual, gamma); | ||
// Create composition if requested | ||
if (this._debug) { | ||
this._imageOutput = this._createComposition(this._imageACompare, this._imageBCompare, this._imageOutput); | ||
} else { | ||
this._imageOutput = this._createComposition(this._imageA, this._imageB, this._imageOutput); | ||
} | ||
// Create composition if requested | ||
if (this._debug) { | ||
this._imageOutput = this._createComposition(this._imageACompare, this._imageBCompare, this._imageOutput); | ||
} else { | ||
this._imageOutput = this._createComposition(this._imageA, this._imageB, this._imageOutput); | ||
} | ||
// Need to write to the filesystem? | ||
if (this._imageOutputPath && this._withinOutputLimit(result.code, this._imageOutputLimit)) { | ||
this._imageOutput.writeImage(this._imageOutputPath, function (err) { | ||
if (err) { | ||
fn(err); | ||
} else { | ||
this.log("Wrote differences to " + this._imageOutputPath); | ||
fn(undefined, result); | ||
} | ||
}.bind(this)); | ||
} else { | ||
fn(undefined, result); | ||
} | ||
// Need to write to the filesystem? | ||
if (this._imageOutputPath && this._withinOutputLimit(result.code, this._imageOutputLimit)) { | ||
this._imageOutput.writeImage(this._imageOutputPath, function (err) { | ||
if (err) { | ||
fn(err); | ||
} else { | ||
this.log("Wrote differences to " + this._imageOutputPath); | ||
fn(undefined, result); | ||
} | ||
}.bind(this)); | ||
} else { | ||
fn(undefined, result); | ||
} | ||
}.bind(this)).then(null, function (err) { | ||
console.error(err.stack); | ||
fn(err); | ||
}); | ||
}, | ||
}.bind(this)).then(null, function (err) { | ||
console.error(err.stack); | ||
fn(err); | ||
}); | ||
}, | ||
/** | ||
* Determines if result is within the output limit | ||
* | ||
* @method _withinOutputLimit | ||
* @param {int} resultCode | ||
* @param {int} outputLimit | ||
* @return {boolean} | ||
* @private | ||
*/ | ||
_withinOutputLimit: function (resultCode, outputLimit) { | ||
return this._convertResultCodeToRelativeValue(resultCode) <= outputLimit; | ||
}, | ||
/** | ||
* Determines if result is within the output limit | ||
* | ||
* @method _withinOutputLimit | ||
* @param {int} resultCode | ||
* @param {int} outputLimit | ||
* @return {boolean} | ||
* @private | ||
*/ | ||
_withinOutputLimit: function (resultCode, outputLimit) { | ||
return this._convertResultCodeToRelativeValue(resultCode) <= outputLimit; | ||
}, | ||
/** | ||
* Converts the result-code to a relative value | ||
* | ||
* @method _convertResultCodeToRelativeValue | ||
* @param {int} resultCode | ||
* @return {int} | ||
* @private | ||
*/ | ||
_convertResultCodeToRelativeValue: function (resultCode) { | ||
/** | ||
* Converts the result-code to a relative value | ||
* | ||
* @method _convertResultCodeToRelativeValue | ||
* @param {int} resultCode | ||
* @return {int} | ||
* @private | ||
*/ | ||
_convertResultCodeToRelativeValue: function (resultCode) { | ||
var valueMap = { | ||
0: 0, | ||
1: 10, | ||
7: 20, | ||
5: 30 | ||
}; | ||
var valueMap = { | ||
0: 0, 1: 10, 7: 20, 5: 30 | ||
}; | ||
return valueMap[resultCode] !== undefined ? valueMap[resultCode] : 0; | ||
}, | ||
return valueMap[resultCode] !== undefined ? valueMap[resultCode] : 0; | ||
}, | ||
/** | ||
* Creates a comparison image | ||
* | ||
* @method _createComposition | ||
* @param {PNGImage} imageA | ||
* @param {PNGImage} imageB | ||
* @param {PNGImage} imageOutput | ||
* @return {PNGImage} | ||
* @private | ||
*/ | ||
_createComposition: function (imageA, imageB, imageOutput) { | ||
/** | ||
* Creates a comparison image | ||
* | ||
* @method _createComposition | ||
* @param {PNGImage} imageA | ||
* @param {PNGImage} imageB | ||
* @param {PNGImage} imageOutput | ||
* @return {PNGImage} | ||
* @private | ||
*/ | ||
_createComposition: function (imageA, imageB, imageOutput) { | ||
var width, | ||
height, | ||
image = imageOutput; | ||
var width, height, image = imageOutput; | ||
if (this._composition) { | ||
width = Math.max(imageA.getWidth(), imageB.getWidth()); | ||
height = Math.max(imageA.getHeight(), imageB.getHeight()); | ||
if (this._composition) { | ||
width = Math.max(imageA.getWidth(), imageB.getWidth()); | ||
height = Math.max(imageA.getHeight(), imageB.getHeight()); | ||
if (((width > height) && !this._composeLeftToRight) || this._composeTopToBottom) { | ||
image = PNGImage.createImage(width, height * 3); | ||
if (((width > height) && !this._composeLeftToRight) || this._composeTopToBottom) { | ||
image = PNGImage.createImage(width, height * 3); | ||
imageA.getImage().bitblt(image.getImage(), 0, 0, imageA.getWidth(), imageA.getHeight(), 0, 0); | ||
imageOutput.getImage().bitblt(image.getImage(), 0, 0, imageOutput.getWidth(), imageOutput.getHeight(), 0, height); | ||
imageB.getImage().bitblt(image.getImage(), 0, 0, imageB.getWidth(), imageB.getHeight(), 0, height * 2); | ||
} else { | ||
image = PNGImage.createImage(width * 3, height); | ||
imageA.getImage().bitblt(image.getImage(), 0, 0, imageA.getWidth(), imageA.getHeight(), 0, 0); | ||
imageOutput.getImage().bitblt(image.getImage(), 0, 0, imageOutput.getWidth(), imageOutput.getHeight(), 0, height); | ||
imageB.getImage().bitblt(image.getImage(), 0, 0, imageB.getWidth(), imageB.getHeight(), 0, height * 2); | ||
} else { | ||
image = PNGImage.createImage(width * 3, height); | ||
imageA.getImage().bitblt(image.getImage(), 0, 0, imageA.getWidth(), imageA.getHeight(), 0, 0); | ||
imageOutput.getImage().bitblt(image.getImage(), 0, 0, imageOutput.getWidth(), imageOutput.getHeight(), width, 0); | ||
imageB.getImage().bitblt(image.getImage(), 0, 0, imageB.getWidth(), imageB.getHeight(), width * 2, 0); | ||
} | ||
} | ||
imageA.getImage().bitblt(image.getImage(), 0, 0, imageA.getWidth(), imageA.getHeight(), 0, 0); | ||
imageOutput.getImage().bitblt(image.getImage(), 0, 0, imageOutput.getWidth(), imageOutput.getHeight(), width, 0); | ||
imageB.getImage().bitblt(image.getImage(), 0, 0, imageB.getWidth(), imageB.getHeight(), width * 2, 0); | ||
} | ||
} | ||
return image; | ||
}, | ||
return image; | ||
}, | ||
/** | ||
* Loads the image or uses the already available image | ||
* | ||
* @method _loadImage | ||
* @param {string} path | ||
* @param {PNGImage} image | ||
* @return {PNGImage|Promise} | ||
* @private | ||
*/ | ||
_loadImage: function (path, image) { | ||
/** | ||
* Loads the image or uses the already available image | ||
* | ||
* @method _loadImage | ||
* @param {string} path | ||
* @param {PNGImage} image | ||
* @return {PNGImage|Promise} | ||
* @private | ||
*/ | ||
_loadImage: function (path, image) { | ||
if (image instanceof Buffer) { | ||
return Promise.denodeify(PNGImage.loadImage)(image); | ||
if (image instanceof Buffer) { | ||
return Promise.denodeify(PNGImage.loadImage)(image); | ||
} else if ((typeof path === 'string') && !image) { | ||
return Promise.denodeify(PNGImage.readImage)(path); | ||
} else if ((typeof path === 'string') && !image) { | ||
return Promise.denodeify(PNGImage.readImage)(path); | ||
} else { | ||
return image; | ||
} | ||
}, | ||
} else { | ||
return image; | ||
} | ||
}, | ||
/** | ||
* Copies one image into another image | ||
* | ||
* @method _copyImage | ||
* @param {PNGImage} imageSrc | ||
* @param {PNGImage} imageDst | ||
* @private | ||
*/ | ||
_copyImage: function (imageSrc, imageDst) { | ||
imageSrc.getImage().bitblt( | ||
imageDst.getImage(), | ||
0, 0, | ||
imageSrc.getWidth(), | ||
imageSrc.getHeight(), | ||
0, 0 | ||
); | ||
}, | ||
/** | ||
* Copies one image into another image | ||
* | ||
* @method _copyImage | ||
* @param {PNGImage} imageSrc | ||
* @param {PNGImage} imageDst | ||
* @private | ||
*/ | ||
_copyImage: function (imageSrc, imageDst) { | ||
imageSrc.getImage().bitblt(imageDst.getImage(), 0, 0, imageSrc.getWidth(), imageSrc.getHeight(), 0, 0); | ||
}, | ||
/** | ||
* Is the difference above the set threshold? | ||
* | ||
* @method isAboveThreshold | ||
* @param {int} items | ||
* @param {int} [total] | ||
* @return {boolean} | ||
*/ | ||
isAboveThreshold: function (items, total) { | ||
/** | ||
* Is the difference above the set threshold? | ||
* | ||
* @method isAboveThreshold | ||
* @param {int} items | ||
* @param {int} [total] | ||
* @return {boolean} | ||
*/ | ||
isAboveThreshold: function (items, total) { | ||
if ((this._thresholdType === BlinkDiff.THRESHOLD_PIXEL) && (this._threshold <= items)) { | ||
return true; | ||
if ((this._thresholdType === BlinkDiff.THRESHOLD_PIXEL) && (this._threshold <= items)) { | ||
return true; | ||
} else if (this._threshold <= (items / total)) { | ||
return true; | ||
} | ||
} else if (this._threshold <= (items / total)) { | ||
return true; | ||
} | ||
return false; | ||
}, | ||
return false; | ||
}, | ||
/** | ||
* Log method that can be overwritten to modify the logging behavior. | ||
* | ||
* @method log | ||
* @param {string} text | ||
*/ | ||
log: function (text) { | ||
// Nothing here; Overwrite this to add some functionality | ||
}, | ||
/** | ||
* Log method that can be overwritten to modify the logging behavior. | ||
* | ||
* @method log | ||
* @param {string} text | ||
*/ | ||
log: function (text) { | ||
// Nothing here; Overwrite this to add some functionality | ||
}, | ||
/** | ||
* Has comparison passed? | ||
* | ||
* @method hasPassed | ||
* @param {int} result Comparison result-code | ||
* @return {boolean} | ||
*/ | ||
hasPassed: function (result) { | ||
return ((result !== BlinkDiff.RESULT_DIFFERENT) && (result !== BlinkDiff.RESULT_UNKNOWN)); | ||
}, | ||
/** | ||
* Has comparison passed? | ||
* | ||
* @method hasPassed | ||
* @param {int} result Comparison result-code | ||
* @return {boolean} | ||
*/ | ||
hasPassed: function (result) { | ||
return ((result !== BlinkDiff.RESULT_DIFFERENT) && (result !== BlinkDiff.RESULT_UNKNOWN)); | ||
}, | ||
/** | ||
* Clips the images to the lower resolution of both | ||
* | ||
* @private | ||
* @method _clip | ||
* @param {PNGImage} imageA Source image | ||
* @param {PNGImage} imageB Destination image | ||
*/ | ||
_clip: function (imageA, imageB) { | ||
/** | ||
* Clips the images to the lower resolution of both | ||
* | ||
* @private | ||
* @method _clip | ||
* @param {PNGImage} imageA Source image | ||
* @param {PNGImage} imageB Destination image | ||
*/ | ||
_clip: function (imageA, imageB) { | ||
var minWidth, | ||
minHeight; | ||
var minWidth, minHeight; | ||
if ((imageA.getWidth() != imageB.getWidth()) || (imageA.getHeight() != imageB.getHeight())) { | ||
if ((imageA.getWidth() != imageB.getWidth()) || (imageA.getHeight() != imageB.getHeight())) { | ||
minWidth = imageA.getWidth(); | ||
if (imageB.getWidth() < minWidth) { | ||
minWidth = imageB.getWidth(); | ||
} | ||
minWidth = imageA.getWidth(); | ||
if (imageB.getWidth() < minWidth) { | ||
minWidth = imageB.getWidth(); | ||
} | ||
minHeight = imageA.getHeight(); | ||
if (imageB.getHeight() < minHeight) { | ||
minHeight = imageB.getHeight(); | ||
} | ||
minHeight = imageA.getHeight(); | ||
if (imageB.getHeight() < minHeight) { | ||
minHeight = imageB.getHeight(); | ||
} | ||
this.log("Clipping to " + minWidth + " x " + minHeight); | ||
this.log("Clipping to " + minWidth + " x " + minHeight); | ||
imageA.clip(0, 0, minWidth, minHeight); | ||
imageB.clip(0, 0, minWidth, minHeight); | ||
} | ||
}, | ||
imageA.clip(0, 0, minWidth, minHeight); | ||
imageB.clip(0, 0, minWidth, minHeight); | ||
} | ||
}, | ||
/** | ||
* Crops the source image to the bounds of rect | ||
* | ||
* @method _crop | ||
* @param {string} which Title of image to crop | ||
* @param {PNGImage} image Source image | ||
* @param {object} rect Values for rect | ||
* @param {int} rect.x X value of rect | ||
* @param {int} rect.y Y value of rect | ||
* @param {int} rect.width Width value of rect | ||
* @param {int} rect.height Height value of rect | ||
* @private | ||
*/ | ||
_crop: function (which, image, rect) { | ||
/** | ||
* Crops the source image to the bounds of rect | ||
* | ||
* @method _crop | ||
* @param {string} which Title of image to crop | ||
* @param {PNGImage} image Source image | ||
* @param {object} rect Values for rect | ||
* @param {int} rect.x X value of rect | ||
* @param {int} rect.y Y value of rect | ||
* @param {int} rect.width Width value of rect | ||
* @param {int} rect.height Height value of rect | ||
* @private | ||
*/ | ||
_crop: function (which, image, rect) { | ||
this.log("Cropping " + which + " from " + rect.x + "," + rect.y + " by " + rect.width + " x " + rect.height); | ||
this.log("Cropping " + which + " from " + rect.x + "," + rect.y + " by " + rect.width + " x " + rect.height); | ||
image.clip(rect.x, rect.y, rect.width, rect.height); | ||
}, | ||
image.clip(rect.x, rect.y, rect.width, rect.height); | ||
}, | ||
/** | ||
* Correcting area dimensions if necessary | ||
* | ||
* Note: | ||
* Priority is on the x/y coordinates, and not on the size since the size will then be removed anyways. | ||
* | ||
* @method _correctDimensions | ||
* @param {int} width | ||
* @param {int} height | ||
* @param {object} rect Values for rect | ||
* @param {int} rect.x X value of rect | ||
* @param {int} rect.y Y value of rect | ||
* @param {int} rect.width Width value of rect | ||
* @param {int} rect.height Height value of rect | ||
* @private | ||
*/ | ||
_correctDimensions: function (width, height, rect) { | ||
/** | ||
* Correcting area dimensions if necessary | ||
* | ||
* Note: | ||
* Priority is on the x/y coordinates, and not on the size since the size will then be removed anyways. | ||
* | ||
* @method _correctDimensions | ||
* @param {int} width | ||
* @param {int} height | ||
* @param {object} rect Values for rect | ||
* @param {int} rect.x X value of rect | ||
* @param {int} rect.y Y value of rect | ||
* @param {int} rect.width Width value of rect | ||
* @param {int} rect.height Height value of rect | ||
* @private | ||
*/ | ||
_correctDimensions: function (width, height, rect) { | ||
// Set values if none given | ||
rect.x = rect.x || 0; | ||
rect.y = rect.y || 0; | ||
rect.width = rect.width || width; | ||
rect.height = rect.height || height; | ||
// Set values if none given | ||
rect.x = rect.x || 0; | ||
rect.y = rect.y || 0; | ||
rect.width = rect.width || width; | ||
rect.height = rect.height || height; | ||
// Check negative values | ||
rect.x = Math.max(0, rect.x); | ||
rect.y = Math.max(0, rect.y); | ||
rect.width = Math.max(0, rect.width); | ||
rect.height = Math.max(0, rect.height); | ||
// Check negative values | ||
rect.x = Math.max(0, rect.x); | ||
rect.y = Math.max(0, rect.y); | ||
rect.width = Math.max(0, rect.width); | ||
rect.height = Math.max(0, rect.height); | ||
// Check dimensions | ||
rect.x = Math.min(rect.x, width - 1); // -1 to make sure that there is an image | ||
rect.y = Math.min(rect.y, height - 1); | ||
rect.width = Math.min(rect.width, width - rect.x); | ||
rect.height = Math.min(rect.height, height - rect.y); | ||
}, | ||
// Check dimensions | ||
rect.x = Math.min(rect.x, width - 1); // -1 to make sure that there is an image | ||
rect.y = Math.min(rect.y, height - 1); | ||
rect.width = Math.min(rect.width, width - rect.x); | ||
rect.height = Math.min(rect.height, height - rect.y); | ||
}, | ||
/** | ||
* Calculates the distance of colors in the 4 dimensional color space | ||
* | ||
* @method _colorDelta | ||
* @param {object} color1 Values for color 1 | ||
* @param {int} color1.c1 First value of color 1 | ||
* @param {int} color1.c2 Second value of color 1 | ||
* @param {int} color1.c3 Third value of color 1 | ||
* @param {int} color1.c4 Fourth value of color 1 | ||
* @param {object} color2 Values for color 2 | ||
* @param {int} color2.c1 First value of color 2 | ||
* @param {int} color2.c2 Second value of color 2 | ||
* @param {int} color2.c3 Third value of color 2 | ||
* @param {int} color2.c4 Fourth value of color 2 | ||
* @return {number} Distance | ||
* @private | ||
*/ | ||
_colorDelta: function (color1, color2) { | ||
var c1, c2, c3, c4; | ||
/** | ||
* Calculates the distance of colors in the 4 dimensional color space | ||
* | ||
* @method _colorDelta | ||
* @param {object} color1 Values for color 1 | ||
* @param {int} color1.c1 First value of color 1 | ||
* @param {int} color1.c2 Second value of color 1 | ||
* @param {int} color1.c3 Third value of color 1 | ||
* @param {int} color1.c4 Fourth value of color 1 | ||
* @param {object} color2 Values for color 2 | ||
* @param {int} color2.c1 First value of color 2 | ||
* @param {int} color2.c2 Second value of color 2 | ||
* @param {int} color2.c3 Third value of color 2 | ||
* @param {int} color2.c4 Fourth value of color 2 | ||
* @return {number} Distance | ||
* @private | ||
*/ | ||
_colorDelta: function (color1, color2) { | ||
var c1, c2, c3, c4; | ||
c1 = Math.pow(color1.c1 - color2.c1, 2); | ||
c2 = Math.pow(color1.c2 - color2.c2, 2); | ||
c3 = Math.pow(color1.c3 - color2.c3, 2); | ||
c4 = Math.pow(color1.c4 - color2.c4, 2); | ||
c1 = Math.pow(color1.c1 - color2.c1, 2); | ||
c2 = Math.pow(color1.c2 - color2.c2, 2); | ||
c3 = Math.pow(color1.c3 - color2.c3, 2); | ||
c4 = Math.pow(color1.c4 - color2.c4, 2); | ||
return Math.sqrt(c1 + c2 + c3 + c4); | ||
}, | ||
return Math.sqrt(c1 + c2 + c3 + c4); | ||
}, | ||
/** | ||
* Gets the color of an image by the index | ||
* | ||
* @method _getColor | ||
* @param {PNGImage} image Image | ||
* @param {int} idx Index of pixel in image | ||
* @param {boolean} [perceptual=false] | ||
* @param {object} [gamma] | ||
* @return {object} Color | ||
* @private | ||
*/ | ||
_getColor: function (image, idx, perceptual, gamma) { | ||
/** | ||
* Gets the color of an image by the index | ||
* | ||
* @method _getColor | ||
* @param {PNGImage} image Image | ||
* @param {int} idx Index of pixel in image | ||
* @param {boolean} [perceptual=false] | ||
* @param {object} [gamma] | ||
* @return {object} Color | ||
* @private | ||
*/ | ||
_getColor: function (image, idx, perceptual, gamma) { | ||
var color; | ||
var color; | ||
color = { | ||
c1: image.getRed(idx), | ||
c2: image.getGreen(idx), | ||
c3: image.getBlue(idx), | ||
c4: image.getAlpha(idx) | ||
}; | ||
color = { | ||
c1: image.getRed(idx), c2: image.getGreen(idx), c3: image.getBlue(idx), c4: image.getAlpha(idx) | ||
}; | ||
if (perceptual || gamma) { | ||
color = this._correctGamma(color, gamma); | ||
color = this._convertRgbToXyz(color); | ||
color = this._convertXyzToCieLab(color); | ||
} | ||
if (perceptual || gamma) { | ||
color = this._correctGamma(color, gamma); | ||
color = this._convertRgbToXyz(color); | ||
color = this._convertXyzToCieLab(color); | ||
} | ||
return color; | ||
}, | ||
return color; | ||
}, | ||
/** | ||
* Correct gamma and return color in [0, 1] range | ||
* | ||
* @method _correctGamma | ||
* @param {object} color | ||
* @param {object} [gamma] | ||
* @return {{c1: number, c2: number, c3: number, c4: number}} | ||
* @private | ||
*/ | ||
_correctGamma: function (color, gamma) { | ||
/** | ||
* Correct gamma and return color in [0, 1] range | ||
* | ||
* @method _correctGamma | ||
* @param {object} color | ||
* @param {object} [gamma] | ||
* @return {{c1: number, c2: number, c3: number, c4: number}} | ||
* @private | ||
*/ | ||
_correctGamma: function (color, gamma) { | ||
// Convert to range [0, 1] | ||
var result = { | ||
c1: color.c1 / 255, | ||
c2: color.c2 / 255, | ||
c3: color.c3 / 255, | ||
c4: color.c4 | ||
}; | ||
// Convert to range [0, 1] | ||
var result = { | ||
c1: color.c1 / 255, c2: color.c2 / 255, c3: color.c3 / 255, c4: color.c4 | ||
}; | ||
if (gamma || gamma.R !== undefined || gamma.G !== undefined || gamma.B !== undefined) { | ||
if (gamma.R !== undefined) { | ||
result.c1 = Math.pow(result.c1, gamma.R); | ||
} | ||
if (gamma.G !== undefined) { | ||
result.c2 = Math.pow(result.c2, gamma.G); | ||
} | ||
if (gamma.B !== undefined) { | ||
result.c3 = Math.pow(result.c3, gamma.B); | ||
} | ||
} | ||
if (gamma || gamma.R !== undefined || gamma.G !== undefined || gamma.B !== undefined) { | ||
if (gamma.R !== undefined) { | ||
result.c1 = Math.pow(result.c1, gamma.R); | ||
} | ||
if (gamma.G !== undefined) { | ||
result.c2 = Math.pow(result.c2, gamma.G); | ||
} | ||
if (gamma.B !== undefined) { | ||
result.c3 = Math.pow(result.c3, gamma.B); | ||
} | ||
} | ||
return result; | ||
}, | ||
return result; | ||
}, | ||
/** | ||
* Converts the color from RGB to XYZ | ||
* | ||
* @method _convertRgbToXyz | ||
* @param {object} color | ||
* @return {object} | ||
* @private | ||
*/ | ||
_convertRgbToXyz: function (color) { | ||
var result = {}; | ||
/** | ||
* Converts the color from RGB to XYZ | ||
* | ||
* @method _convertRgbToXyz | ||
* @param {object} color | ||
* @return {object} | ||
* @private | ||
*/ | ||
_convertRgbToXyz: function (color) { | ||
var result = {}; | ||
result.c1 = color.c1 * 0.4887180 + color.c2 * 0.3106803 + color.c3 * 0.2006017; | ||
result.c2 = color.c1 * 0.1762044 + color.c2 * 0.8129847 + color.c3 * 0.0108109; | ||
result.c3 = color.c2 * 0.0102048 + color.c3 * 0.9897952; | ||
result.c4 = color.c4; | ||
result.c1 = color.c1 * 0.4887180 + color.c2 * 0.3106803 + color.c3 * 0.2006017; | ||
result.c2 = color.c1 * 0.1762044 + color.c2 * 0.8129847 + color.c3 * 0.0108109; | ||
result.c3 = color.c2 * 0.0102048 + color.c3 * 0.9897952; | ||
result.c4 = color.c4; | ||
return result; | ||
}, | ||
return result; | ||
}, | ||
/** | ||
* Converts the color from XYZ to CieLab | ||
* | ||
* @method _convertXyzToCieLab | ||
* @param {object} color | ||
* @return {object} | ||
* @private | ||
*/ | ||
_convertXyzToCieLab: function (color) { | ||
var result = {}, | ||
c1, c2, c3; | ||
/** | ||
* Converts the color from XYZ to CieLab | ||
* | ||
* @method _convertXyzToCieLab | ||
* @param {object} color | ||
* @return {object} | ||
* @private | ||
*/ | ||
_convertXyzToCieLab: function (color) { | ||
var result = {}, c1, c2, c3; | ||
function f (t) { | ||
return (t > 0.00885645167904) ? Math.pow(t, 1/3) : 70.08333333333263 * t + 0.13793103448276; | ||
} | ||
function f (t) { | ||
return (t > 0.00885645167904) ? Math.pow(t, 1 / 3) : 70.08333333333263 * t + 0.13793103448276; | ||
} | ||
c1 = f(color.c1 / this._refWhite.c1); | ||
c2 = f(color.c2 / this._refWhite.c2); | ||
c3 = f(color.c3 / this._refWhite.c3); | ||
c1 = f(color.c1 / this._refWhite.c1); | ||
c2 = f(color.c2 / this._refWhite.c2); | ||
c3 = f(color.c3 / this._refWhite.c3); | ||
result.c1 = (116 * c2) - 16; | ||
result.c2 = 500 * (c1 - c2); | ||
result.c3 = 200 * (c2 - c3); | ||
result.c4 = color.c4; | ||
result.c1 = (116 * c2) - 16; | ||
result.c2 = 500 * (c1 - c2); | ||
result.c3 = 200 * (c2 - c3); | ||
result.c4 = color.c4; | ||
return result; | ||
}, | ||
return result; | ||
}, | ||
/** | ||
* Calculates the lower limit | ||
* | ||
* @param {int} value | ||
* @param {int} min | ||
* @param {int} shift | ||
* @return {int} | ||
* @private | ||
*/ | ||
_calculateLowerLimit: function (value, min, shift) { | ||
return (value - shift) < min ? -(shift + (value - shift)) : -shift; | ||
}, | ||
/** | ||
* Calculates the lower limit | ||
* | ||
* @method _calculateLowerLimit | ||
* @param {int} value | ||
* @param {int} min | ||
* @param {int} shift | ||
* @return {int} | ||
* @private | ||
*/ | ||
_calculateLowerLimit: function (value, min, shift) { | ||
return (value - shift) < min ? -(shift + (value - shift)) : -shift; | ||
}, | ||
/** | ||
* Calculates the upper limit | ||
* | ||
* @param {int} value | ||
* @param {int} max | ||
* @param {int} shift | ||
* @return {int} | ||
* @private | ||
*/ | ||
_calculateUpperLimit: function (value, max, shift) { | ||
return (value + shift) > max ? (max - value) : shift; | ||
}, | ||
/** | ||
* Calculates the upper limit | ||
* | ||
* @method _calculateUpperLimit | ||
* @param {int} value | ||
* @param {int} max | ||
* @param {int} shift | ||
* @return {int} | ||
* @private | ||
*/ | ||
_calculateUpperLimit: function (value, max, shift) { | ||
return (value + shift) > max ? (max - value) : shift; | ||
}, | ||
/** | ||
* Checks if any pixel in the shift surrounding has a comparable color | ||
* | ||
* @param {int} x | ||
* @param {int} y | ||
* @param {object} color | ||
* @param {number} deltaThreshold | ||
* @param {PNGImage} imageA | ||
* @param {PNGImage} imageB | ||
* @param {int} width | ||
* @param {int} height | ||
* @param {int} hShift | ||
* @param {int} vShift | ||
* @param {boolean} [perceptual=false] | ||
* @param {object} [gamma] | ||
* @return {boolean} Is pixel within delta found in surrounding? | ||
* @private | ||
*/ | ||
_shiftCompare: function (x, y, color, deltaThreshold, imageA, imageB, width, height, hShift, vShift, perceptual, gamma) { | ||
/** | ||
* Checks if any pixel in the shift surrounding has a comparable color | ||
* | ||
* @method _shiftCompare | ||
* @param {int} x | ||
* @param {int} y | ||
* @param {object} color | ||
* @param {number} deltaThreshold | ||
* @param {PNGImage} imageA | ||
* @param {PNGImage} imageB | ||
* @param {int} width | ||
* @param {int} height | ||
* @param {int} hShift | ||
* @param {int} vShift | ||
* @param {boolean} [perceptual=false] | ||
* @param {object} [gamma] | ||
* @return {boolean} Is pixel within delta found in surrounding? | ||
* @private | ||
*/ | ||
_shiftCompare: function (x, y, color, deltaThreshold, imageA, imageB, width, height, hShift, vShift, perceptual, gamma) { | ||
var i, | ||
xOffset, xLow, xHigh, | ||
yOffset, yLow, yHigh, | ||
delta, | ||
color1, | ||
color2, | ||
localDeltaThreshold; | ||
var i, xOffset, xLow, xHigh, yOffset, yLow, yHigh, delta, color1, color2, localDeltaThreshold; | ||
if ((hShift > 0) || (vShift > 0)) { | ||
if ((hShift > 0) || (vShift > 0)) { | ||
xLow = this._calculateLowerLimit(x, 0, hShift); | ||
xHigh = this._calculateUpperLimit(x, width - 1, hShift); | ||
xLow = this._calculateLowerLimit(x, 0, hShift); | ||
xHigh = this._calculateUpperLimit(x, width - 1, hShift); | ||
yLow = this._calculateLowerLimit(y, 0, vShift); | ||
yHigh = this._calculateUpperLimit(y, height - 1, vShift); | ||
yLow = this._calculateLowerLimit(y, 0, vShift); | ||
yHigh = this._calculateUpperLimit(y, height - 1, vShift); | ||
for (xOffset = xLow; xOffset <= xHigh; xOffset++) { | ||
for (yOffset = yLow; yOffset <= yHigh; yOffset++) { | ||
for (xOffset = xLow; xOffset <= xHigh; xOffset++) { | ||
for (yOffset = yLow; yOffset <= yHigh; yOffset++) { | ||
if ((xOffset != 0) || (yOffset != 0)) { | ||
if ((xOffset != 0) || (yOffset != 0)) { | ||
i = imageB.getIndex(x + xOffset, y + yOffset); | ||
i = imageB.getIndex(x + xOffset, y + yOffset); | ||
color1 = this._getColor(imageA, i, perceptual, gamma); | ||
localDeltaThreshold = this._colorDelta(color, color1); | ||
color1 = this._getColor(imageA, i, perceptual, gamma); | ||
localDeltaThreshold = this._colorDelta(color, color1); | ||
color2 = this._getColor(imageB, i, perceptual, gamma); | ||
delta = this._colorDelta(color, color2); | ||
color2 = this._getColor(imageB, i, perceptual, gamma); | ||
delta = this._colorDelta(color, color2); | ||
if ((Math.abs(delta - localDeltaThreshold) < deltaThreshold) && (localDeltaThreshold > deltaThreshold)) { | ||
return true; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
if ((Math.abs(delta - localDeltaThreshold) < deltaThreshold) && (localDeltaThreshold > deltaThreshold)) { | ||
return true; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
return false; | ||
}, | ||
return false; | ||
}, | ||
/** | ||
* Does a quick comparison between the supplied images | ||
* | ||
* @method _pixelCompare | ||
* @param {PNGImage} imageA | ||
* @param {PNGImage} imageB | ||
* @param {PNGImage} imageOutput | ||
* @param {number} deltaThreshold | ||
* @param {int} width Width of image | ||
* @param {int} height Height of image | ||
* @param {object} outputMaskColor | ||
* @param {int} [outputMaskColor.red] | ||
* @param {int} [outputMaskColor.green] | ||
* @param {int} [outputMaskColor.blue] | ||
* @param {int} [outputMaskColor.alpha] | ||
* @param {float} [outputMaskColor.opacity] | ||
* @param {object} outputShiftColor | ||
* @param {int} [outputShiftColor.red] | ||
* @param {int} [outputShiftColor.green] | ||
* @param {int} [outputShiftColor.blue] | ||
* @param {int} [outputShiftColor.alpha] | ||
* @param {float} [outputShiftColor.opacity] | ||
* @param {object} backgroundColor | ||
* @param {int} [backgroundColor.red] | ||
* @param {int} [backgroundColor.green] | ||
* @param {int} [backgroundColor.blue] | ||
* @param {int} [backgroundColor.alpha] | ||
* @param {float} [backgroundColor.opacity] | ||
* @param {int} [hShift=0] Horizontal shift | ||
* @param {int} [vShift=0] Vertical shift | ||
* @param {boolean} [perceptual=false] | ||
* @param {object} [gamma] | ||
* @return {int} Number of pixel differences | ||
* @private | ||
*/ | ||
_pixelCompare: function (imageA, imageB, imageOutput, deltaThreshold, width, height, outputMaskColor, outputShiftColor, backgroundColor, hShift, vShift, perceptual, gamma) { | ||
var difference = 0, | ||
i, | ||
x, y, | ||
delta, | ||
color1, color2; | ||
/** | ||
* Does a quick comparison between the supplied images | ||
* | ||
* @method _pixelCompare | ||
* @param {PNGImage} imageA | ||
* @param {PNGImage} imageB | ||
* @param {PNGImage} imageOutput | ||
* @param {number} deltaThreshold | ||
* @param {int} width Width of image | ||
* @param {int} height Height of image | ||
* @param {object} outputMaskColor | ||
* @param {int} [outputMaskColor.red] | ||
* @param {int} [outputMaskColor.green] | ||
* @param {int} [outputMaskColor.blue] | ||
* @param {int} [outputMaskColor.alpha] | ||
* @param {float} [outputMaskColor.opacity] | ||
* @param {object} outputShiftColor | ||
* @param {int} [outputShiftColor.red] | ||
* @param {int} [outputShiftColor.green] | ||
* @param {int} [outputShiftColor.blue] | ||
* @param {int} [outputShiftColor.alpha] | ||
* @param {float} [outputShiftColor.opacity] | ||
* @param {object} backgroundColor | ||
* @param {int} [backgroundColor.red] | ||
* @param {int} [backgroundColor.green] | ||
* @param {int} [backgroundColor.blue] | ||
* @param {int} [backgroundColor.alpha] | ||
* @param {float} [backgroundColor.opacity] | ||
* @param {int} [hShift=0] Horizontal shift | ||
* @param {int} [vShift=0] Vertical shift | ||
* @param {boolean} [perceptual=false] | ||
* @param {object} [gamma] | ||
* @return {int} Number of pixel differences | ||
* @private | ||
*/ | ||
_pixelCompare: function (imageA, imageB, imageOutput, deltaThreshold, width, height, outputMaskColor, outputShiftColor, backgroundColor, hShift, vShift, perceptual, gamma) { | ||
var difference = 0, i, x, y, delta, color1, color2; | ||
for (x = 0; x < width; x++) { | ||
for (y = 0; y < height; y++) { | ||
i = imageA.getIndex(x, y); | ||
for (x = 0; x < width; x++) { | ||
for (y = 0; y < height; y++) { | ||
i = imageA.getIndex(x, y); | ||
color1 = this._getColor(imageA, i, perceptual, gamma); | ||
color2 = this._getColor(imageB, i, perceptual, gamma); | ||
color1 = this._getColor(imageA, i, perceptual, gamma); | ||
color2 = this._getColor(imageB, i, perceptual, gamma); | ||
delta = this._colorDelta(color1, color2); | ||
delta = this._colorDelta(color1, color2); | ||
if (delta > deltaThreshold) { | ||
if (delta > deltaThreshold) { | ||
if (this._shiftCompare(x, y, color1, deltaThreshold, imageA, imageB, width, height, hShift, vShift, perceptual, gamma) && | ||
this._shiftCompare(x, y, color2, deltaThreshold, imageB, imageA, width, height, hShift, vShift, perceptual, gamma)) { | ||
imageOutput.setAtIndex(i, outputShiftColor); | ||
} else { | ||
difference++; | ||
imageOutput.setAtIndex(i, outputMaskColor); | ||
} | ||
if (this._shiftCompare(x, y, color1, deltaThreshold, imageA, imageB, width, height, hShift, vShift, perceptual, gamma) && this._shiftCompare(x, y, color2, deltaThreshold, imageB, imageA, width, height, hShift, vShift, perceptual, gamma)) { | ||
imageOutput.setAtIndex(i, outputShiftColor); | ||
} else { | ||
difference++; | ||
imageOutput.setAtIndex(i, outputMaskColor); | ||
} | ||
} else { | ||
imageOutput.setAtIndex(i, backgroundColor); | ||
} | ||
} | ||
} | ||
} else { | ||
imageOutput.setAtIndex(i, backgroundColor); | ||
} | ||
} | ||
} | ||
return difference; | ||
}, | ||
return difference; | ||
}, | ||
/** | ||
* Compares the two images supplied | ||
* | ||
* @method _compare | ||
* @param {PNGImage} imageA | ||
* @param {PNGImage} imageB | ||
* @param {PNGImage} imageOutput | ||
* @param {number} deltaThreshold | ||
* @param {object} outputMaskColor | ||
* @param {int} [outputMaskColor.red] | ||
* @param {int} [outputMaskColor.green] | ||
* @param {int} [outputMaskColor.blue] | ||
* @param {int} [outputMaskColor.alpha] | ||
* @param {float} [outputMaskColor.opacity] | ||
* @param {object} outputShiftColor | ||
* @param {int} [outputShiftColor.red] | ||
* @param {int} [outputShiftColor.green] | ||
* @param {int} [outputShiftColor.blue] | ||
* @param {int} [outputShiftColor.alpha] | ||
* @param {float} [outputShiftColor.opacity] | ||
* @param {object} backgroundColor | ||
* @param {int} [backgroundColor.red] | ||
* @param {int} [backgroundColor.green] | ||
* @param {int} [backgroundColor.blue] | ||
* @param {int} [backgroundColor.alpha] | ||
* @param {float} [backgroundColor.opacity] | ||
* @param {int} [hShift=0] Horizontal shift | ||
* @param {int} [vShift=0] Vertical shift | ||
* @param {boolean} [perceptual=false] | ||
* @param {object} [gamma] | ||
* @return {object} | ||
* @private | ||
*/ | ||
_compare: function (imageA, imageB, imageOutput, deltaThreshold, outputMaskColor, outputShiftColor, backgroundColor, hShift, vShift, perceptual, gamma) { | ||
/** | ||
* Compares the two images supplied | ||
* | ||
* @method _compare | ||
* @param {PNGImage} imageA | ||
* @param {PNGImage} imageB | ||
* @param {PNGImage} imageOutput | ||
* @param {number} deltaThreshold | ||
* @param {object} outputMaskColor | ||
* @param {int} [outputMaskColor.red] | ||
* @param {int} [outputMaskColor.green] | ||
* @param {int} [outputMaskColor.blue] | ||
* @param {int} [outputMaskColor.alpha] | ||
* @param {float} [outputMaskColor.opacity] | ||
* @param {object} outputShiftColor | ||
* @param {int} [outputShiftColor.red] | ||
* @param {int} [outputShiftColor.green] | ||
* @param {int} [outputShiftColor.blue] | ||
* @param {int} [outputShiftColor.alpha] | ||
* @param {float} [outputShiftColor.opacity] | ||
* @param {object} backgroundColor | ||
* @param {int} [backgroundColor.red] | ||
* @param {int} [backgroundColor.green] | ||
* @param {int} [backgroundColor.blue] | ||
* @param {int} [backgroundColor.alpha] | ||
* @param {float} [backgroundColor.opacity] | ||
* @param {int} [hShift=0] Horizontal shift | ||
* @param {int} [vShift=0] Vertical shift | ||
* @param {boolean} [perceptual=false] | ||
* @param {object} [gamma] | ||
* @return {object} | ||
* @private | ||
*/ | ||
_compare: function (imageA, imageB, imageOutput, deltaThreshold, outputMaskColor, outputShiftColor, backgroundColor, hShift, vShift, perceptual, gamma) { | ||
var result = { | ||
code: BlinkDiff.RESULT_UNKNOWN, | ||
differences: undefined, | ||
dimension: undefined, | ||
width: undefined, | ||
height: undefined | ||
}; | ||
var result = { | ||
code: BlinkDiff.RESULT_UNKNOWN, | ||
differences: undefined, | ||
dimension: undefined, | ||
width: undefined, | ||
height: undefined | ||
}; | ||
// Get some data needed for comparison | ||
result.width = imageA.getWidth(); | ||
result.height = imageA.getHeight(); | ||
result.dimension = result.width * result.height; | ||
// Get some data needed for comparison | ||
result.width = imageA.getWidth(); | ||
result.height = imageA.getHeight(); | ||
result.dimension = result.width * result.height; | ||
// Check if identical | ||
result.differences = this._pixelCompare( | ||
imageA, | ||
imageB, | ||
imageOutput, | ||
deltaThreshold, | ||
result.width, | ||
result.height, | ||
outputMaskColor, | ||
outputShiftColor, | ||
backgroundColor, | ||
hShift, vShift, | ||
perceptual, | ||
gamma | ||
); | ||
// Check if identical | ||
result.differences = this._pixelCompare(imageA, imageB, imageOutput, deltaThreshold, result.width, result.height, outputMaskColor, outputShiftColor, backgroundColor, hShift, vShift, perceptual, gamma); | ||
// Result | ||
if (result.differences == 0) { | ||
this.log("Images are identical or near identical"); | ||
result.code = BlinkDiff.RESULT_IDENTICAL; | ||
return result; | ||
// Result | ||
if (result.differences == 0) { | ||
this.log("Images are identical or near identical"); | ||
result.code = BlinkDiff.RESULT_IDENTICAL; | ||
return result; | ||
} else if (this.isAboveThreshold(result.differences, result.dimension)) { | ||
this.log("Images are visibly different"); | ||
this.log(result.differences + " pixels are different"); | ||
result.code = BlinkDiff.RESULT_DIFFERENT; | ||
return result; | ||
} else if (this.isAboveThreshold(result.differences, result.dimension)) { | ||
this.log("Images are visibly different"); | ||
this.log(result.differences + " pixels are different"); | ||
result.code = BlinkDiff.RESULT_DIFFERENT; | ||
return result; | ||
} else { | ||
this.log("Images are similar"); | ||
this.log(result.differences + " pixels are different"); | ||
result.code = BlinkDiff.RESULT_SIMILAR; | ||
return result; | ||
} | ||
} | ||
} else { | ||
this.log("Images are similar"); | ||
this.log(result.differences + " pixels are different"); | ||
result.code = BlinkDiff.RESULT_SIMILAR; | ||
return result; | ||
} | ||
} | ||
}; | ||
module.exports = BlinkDiff; |
{ | ||
"name": "blink-diff", | ||
"version": "1.0.7", | ||
"description": "A lightweight image comparison tool", | ||
"license": "MIT", | ||
"main": "index.js", | ||
"bugs": "https://github.com/yahoo/blink-diff/issues", | ||
"homepage": "https://github.com/yahoo/blink-diff", | ||
"bin": { | ||
"blink-diff": "bin/blink-diff" | ||
}, | ||
"author": { | ||
"name": "Marcel Erz", | ||
"email": "erz@yahoo-inc.com" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/yahoo/blink-diff.git" | ||
}, | ||
"scripts": { | ||
"test": "istanbul cover -- _mocha --reporter spec", | ||
"docs": "yuidoc ." | ||
}, | ||
"keywords": [ | ||
"image-diff", | ||
"blink", | ||
"image", | ||
"difference", | ||
"compare" | ||
], | ||
"dependencies": { | ||
"promise": "6.0.0", | ||
"pngjs-image": "0.9.3" | ||
}, | ||
"devDependencies": { | ||
"chai": "1.9.1", | ||
"coveralls": "2.11.2", | ||
"istanbul": "0.3.2", | ||
"mocha": "1.21.4", | ||
"yuidocjs": "0.3.50" | ||
} | ||
"name": "blink-diff", | ||
"version": "1.0.8", | ||
"description": "A lightweight image comparison tool", | ||
"license": "MIT", | ||
"main": "index.js", | ||
"bugs": "https://github.com/yahoo/blink-diff/issues", | ||
"homepage": "https://github.com/yahoo/blink-diff", | ||
"bin": { | ||
"blink-diff": "bin/blink-diff" | ||
}, | ||
"author": { | ||
"name": "Marcel Erz", | ||
"email": "erz@yahoo-inc.com", | ||
"url": "http://www.marcelerz.com" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/yahoo/blink-diff.git" | ||
}, | ||
"keywords": [ | ||
"image-diff", | ||
"visual-diff", | ||
"diff", | ||
"testing", | ||
"blink", | ||
"image", | ||
"difference", | ||
"compare" | ||
], | ||
"scripts": { | ||
"test": "istanbul cover -- _mocha --reporter spec", | ||
"docs": "yuidoc ." | ||
}, | ||
"dependencies": { | ||
"promise": "6.0.0", | ||
"pngjs-image": "0.9.3" | ||
}, | ||
"devDependencies": { | ||
"chai": "1.9.2", | ||
"coveralls": "2.11.2", | ||
"codeclimate-test-reporter": "0.0.4", | ||
"istanbul": "0.3.2", | ||
"mocha": "1.21.4", | ||
"sinon": "1.12.2", | ||
"sinon-chai": "2.7.0", | ||
"yuidocjs": "0.3.50" | ||
} | ||
} |
@@ -6,11 +6,16 @@ Blink-Diff | ||
[![Build Status](https://secure.travis-ci.org/yahoo/blink-diff.png)](http://travis-ci.org/yahoo/blink-diff) | ||
[![npm version](https://badge.fury.io/js/blink-diff.svg)](http://badge.fury.io/js/blink-diff) | ||
[![Build Status](https://img.shields.io/travis/yahoo/blink-diff.svg)](http://travis-ci.org/yahoo/blink-diff) | ||
[![Coveralls Coverage](https://img.shields.io/coveralls/yahoo/blink-diff.svg)](https://coveralls.io/r/yahoo/blink-diff) | ||
[![Code Climate Grade](https://img.shields.io/codeclimate/github/yahoo/blink-diff.svg)](https://codeclimate.com/github/yahoo/blink-diff) | ||
[![NPM](https://nodei.co/npm/blink-diff.png?downloads=true)](https://nodei.co/npm/blink-diff/) | ||
[![NPM version](https://badge.fury.io/js/blink-diff.svg)](https://www.npmjs.com/package/blink-diff) | ||
[![NPM License](https://img.shields.io/npm/l/blink-diff.svg)](https://www.npmjs.com/package/blink-diff) | ||
[![NPM](https://nodei.co/npm/blink-diff.png?downloads=true&stars=true)](https://www.npmjs.com/package/blink-diff) | ||
[![NPM](https://nodei.co/npm-dl/blink-diff.png?months=3&height=2)](https://www.npmjs.com/package/blink-diff) | ||
[API-Documentation](http://yahoo.github.io/blink-diff/docs/) | ||
[![Coverage Report](https://img.shields.io/badge/Coverage_Report-Available-blue.svg)](http://yahoo.github.io/blink-diff/coverage/lcov-report/) | ||
[![API Documentation](https://img.shields.io/badge/API_Documentation-Available-blue.svg)](http://yahoo.github.io/blink-diff/docs/) | ||
[Coverage Report](http://yahoo.github.io/blink-diff/coverage/lcov-report/) | ||
[![Gitter Support](https://img.shields.io/badge/Support-Gitter_IM-yellow.svg)](https://gitter.im/preceptorjs/support) | ||
@@ -26,2 +31,3 @@ | ||
* [Logging](#logging) | ||
* [Block-Out](#block-out) | ||
* [Examples](#examples) | ||
@@ -109,7 +115,12 @@ * [Results](#results) | ||
outputPath: 'path/to/output/image' | ||
imageOutputPath: 'path/to/output/image' | ||
}); | ||
diff.run(function (error) { | ||
console.log(error ? 'Failed' : 'Passed'); | ||
diff.run(function (error, result) { | ||
if (error) { | ||
throw error; | ||
} else { | ||
console.log(diff.hasPassed(result.code) ? 'Passed' : 'Failed'); | ||
console.log('Found ' + result.differences + ' differences.'); | ||
} | ||
}); | ||
@@ -190,4 +201,9 @@ ``` | ||
diff.run(function (error) { | ||
console.log(error ? 'Failed' : 'Passed'); | ||
diff.run(function (error, result) { | ||
if (error) { | ||
throw error; | ||
} else { | ||
console.log(diff.hasPassed(result.code) ? 'Passed' : 'Failed'); | ||
console.log('Found ' + result.differences + ' differences.'); | ||
} | ||
}); | ||
@@ -284,2 +300,6 @@ }); | ||
* Block-out areas | ||
* [jeffposnick](https://github.com/jeffposnick) | ||
* Documentation | ||
* [a-nwhitmont](https://github.com/a-nwhitmont) | ||
* Documentation | ||
@@ -296,4 +316,8 @@ ##Third-party libraries | ||
* chai: http://chaijs.com | ||
* coveralls: https://github.com/cainus/node-coveralls | ||
* codeclimate-test-reporter: https://github.com/codeclimate/javascript-test-reporter | ||
* istanbul: https://github.com/gotwarlost/istanbul | ||
* mocha: https://github.com/visionmedia/mocha | ||
* sinon: http://sinonjs.org | ||
* sinon-chai: https://github.com/domenic/sinon-chai | ||
* yuidocjs: https://github.com/yui/yuidoc | ||
@@ -305,2 +329,2 @@ | ||
Copyright 2014 Yahoo Inc. | ||
Copyright 2014-2015 Yahoo Inc. |
{ | ||
"options": { | ||
"outdir": "docs", | ||
"linkNatives": true, | ||
"exclude": "test,bin" | ||
} | ||
} | ||
"options": { | ||
"outdir": "docs", | ||
"linkNatives": true, | ||
"exclude": "test,bin" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
11
324
473076
8
916