UNPKG

blink-diff

Version:

A lightweight image comparison tool

260 lines (211 loc) 6.12 kB
// Copyright 2015 Yahoo! Inc. // Copyrights licensed under the Mit License. See the accompanying LICENSE file for terms. var PNGImage = require('pngjs-image'), Config = require('./lib/configuration/config'), Image = require('./lib/image'), PixelComparator = require('./lib/pixelComparator'), constants = require('./lib/constants'), Base = require('preceptor-core').Base; /** * @class BlinkDiff * @extends Base * @module Compare * * @property {Config} _configuration * @property {PNGImage} _outputImage * @property {PNGImage} _highlightImage */ var BlinkDiff = Base.extend( /** * Constructor * * @constructor * @param {object} options */ function (options) { options = options || {}; options.blinkDiff = this; this._configuration = new Config(options); }, { /** * Gets the configuration * * @method getConfig * @return {Config} */ getConfig: function () { return this._configuration; }, /** * Gets the output image * * @method getOutputImage * @return {PNGImage} */ getOutputImage: function () { return this._outputImage; }, /** * Gets the highlight image * * @method getHighlightImage * @return {PNGImage} */ getHighlightImage: function () { return this._highlightImage; }, /** * Logs events to the console * * @method log * @param {string} text */ log: function (text) { if (this._configuration.isVerboseMode()) { console.log(text); } }, /** * Clips the images to the lower resolution of both, if needed * * @private * @method _clip * @param {PNGImage} imageA Source image * @param {PNGImage} imageB Destination image */ _clip: function (imageA, imageB) { var minWidth, minHeight; if ((imageA.getWidth() != imageB.getWidth()) || (imageA.getHeight() != imageB.getHeight())) { minWidth = Math.min(imageA.getWidth(), imageB.getWidth()); minHeight = Math.min(imageA.getHeight(), imageB.getHeight()); this.log("Clipping to " + minWidth + " x " + minHeight); imageA.clip(0, 0, minWidth, minHeight); imageB.clip(0, 0, minWidth, minHeight); } }, /** * Has comparison passed? * * @method hasPassed * @param {int} result Comparison result-code * @return {boolean} */ hasPassed: function (result) { return ((result !== constants.RESULT_DIFFERENT) && (result !== constants.RESULT_UNKNOWN)); }, /** * Runs the comparison synchronously * * @method process * @return {Object} Result of comparison { code, differences, dimension, width, height } */ process: function () { // Catch all image errors PNGImage.log = function (text) { this.log('ERROR: ' + text); throw new Error('ERROR: ' + text); }.bind(this); var config = this.getConfig(), dimension, flagField, imageA = config.getImageA().getProcessedImage(), imageB = config.getImageB().getProcessedImage(), compareImageA, compareImageB, highlightImage, outputImage, differences = 0, shifts = 0, i, index, color, exported = false, code = constants.RESULT_UNKNOWN; this._highlightImage = null; this._outputImage = null; this._clip(imageA, imageB); dimension = imageA.getWidth() * imageB.getWidth(); flagField = new Buffer(dimension); flagField.fill(0); config.getComparisons().forEach(function (comparison, index) { this.log('Apply comparison #' + index); compareImageA = Image.processImage(PNGImage.copyImage(imageA), comparison); compareImageB = Image.processImage(PNGImage.copyImage(imageB), comparison); var pixelCompare = new PixelComparator(compareImageA, compareImageB, config); pixelCompare.compare(comparison, flagField); }.bind(this)); if (config.isDebugMode()) { // In debug-mode? Export comparison image this.log('In debug-mode'); imageA = compareImageA || imageA; imageB = compareImageB || imageB; } highlightImage = PNGImage.createImage(imageA.getWidth(), imageA.getHeight()); outputImage = PNGImage.createImage(imageA.getWidth(), imageA.getHeight()); config.getOutput().copyImage(imageA, imageB, outputImage); // Draw and count flag-field for(i = 0; i < dimension; i++) { index = i * 4; // Count if (flagField[i] & 1 == 1) { differences++; } if (flagField[i] & 2 == 2) { shifts++; } // Draw if (flagField[i] & 1 == 1) { color = config.getDiffColor(); } else if (flagField[i] & 2 == 2) { color = config.getIgnoreColor(); } else { color = config.getBackgroundColor(); } outputImage.setAtIndex(index, color.getColor(true, true)); highlightImage.setAtIndex(index, color.getColor(false, false)); } // Create composition if requested this._highlightImage = highlightImage; this._outputImage = config.getOutput().createComposition(imageA, imageB, outputImage); // Result if (differences == 0) { this.log("Images are identical or near identical"); code = constants.RESULT_IDENTICAL; } else if (config.getThreshold().isAboveThreshold(differences, dimension)) { this.log("Images are visibly different"); this.log(differences + " pixels are different"); code = constants.RESULT_DIFFERENT; } else { this.log("Images are similar"); this.log(differences + " pixels are different"); code = constants.RESULT_SIMILAR; } // Need to write to the filesystem? if (config.getOutput().withinOutputLimit(code)) { if (config.getOutput().writeImage(this._outputImage)) { this.log("Wrote differences to " + config.getOutput().getImagePath()); exported = true; } } return { code: code, differences: differences, shifts: shifts, dimension: dimension, width: imageA.getWidth(), height: imageA.getWidth(), highlightImage: highlightImage, outputImage: outputImage, exported: exported }; } }, { /** * Version * * @static * @property version * @type {string} */ version: require('./package.json').version } ); module.exports = BlinkDiff;