-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
197 lines (147 loc) · 6.38 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
var fs = require('fs-extra');
var path = require('path');
var Promise = require('bluebird');
var outputFile = Promise.promisify(fs.outputFile);
var through = require('through2');
var extend = require('extend');
var gutil = require('gulp-util');
var jsonReporter = require('./lib/json-reporter.js');
var compareImages = require('./lib/compare-images.js');
var convertToPng = require('./lib/convert-to-png.js');
// consts
const PLUGIN_NAME = 'gulp-image-diff';
var differ = function(options) {
var defaults = {
// String path+filename, buffer, or vinyl file of where the reference image
referenceImage: null,
// 0-1 representing the allowed color difference between pixels
// 0 means no tolerance. Pixels need to be exactly the same
// This allows for slight differences in aliasing
pixelColorTolerance: 0.01,
// Pass a string path+filename or a function that returns string path+filename of where to save the difference image
// function(referencePath, compareImagePath): Return string path+filename
differenceMapImage: null,
// The color for each pixel that is different
differenceMapColor: {
r: 255,
g: 0,
b: 0,
a: 200
},
// Log to the console
// You can also hook onto `.on('log', ...)` events which are emitted no matter what
logProgress: false
};
var settings = extend({}, defaults, options);
var whenReferenceImageReadyPromise = convertToPng(settings.referenceImage);
var stream = through.obj(function(chunk, enc, cb) {
// http://nodejs.org/docs/latest/api/stream.html#stream_transform_transform_chunk_encoding_callback
//console.log('transform');
// Each `chunk` is a vinyl file: https://www.npmjs.com/package/vinyl
// chunk.cwd
// chunk.base
// chunk.path
// chunk.contents
var self = this;
if (chunk.isStream()) {
self.emit('error', new gutil.PluginError(PLUGIN_NAME, 'Cannot operate on stream'));
}
else if (chunk.isBuffer()) {
whenReferenceImageReadyPromise.then(function(referenceImage) {
convertToPng(chunk).then(function(compareImage) {
var totalPixels = compareImage.width*compareImage.height;
var compareResult = compareImages(referenceImage, compareImage, settings.pixelColorTolerance, settings.differenceMapColor);
var analysis = {
differences: compareResult.numDifferences,
total: totalPixels,
disparity: compareResult.numDifferences/totalPixels,
referenceImage: path.normalize(settings.referenceImage),
compareImage: path.relative(chunk.cwd, chunk.path)
};
compareResult.differenceMapImagePromise.then(function(differenceMapImageBuffer) {
var whenImageDealtWithPromise = new Promise(function(resolve, reject) {
if(settings.differenceMapImage) {
// You can pass a string or function to generate the diff save path
// We give you the path of the reference and compare image to construct a path
var differenceMapSavePath = settings.differenceMapImage;
if(typeof(settings.differenceMapImage) === "function") {
differenceMapSavePath = settings.differenceMapImage(analysis.referenceImage, analysis.compareImage);
}
// Save out the difference map
if(differenceMapSavePath) {
outputFile(differenceMapSavePath, differenceMapImageBuffer).then(function() {
// Add to the analysis if we saved the image
analysis.differenceMap = differenceMapSavePath;
resolve();
}).catch(function(err) {
err.message = 'Error saving difference image:\n' + err.message;
self.emit('error', new gutil.PluginError(PLUGIN_NAME, err));
//reject(err);
// The error was handled by emitting
resolve();
});
}
else {
var err = new Error('`options.differenceMapImage` defined but no string path was passed in or returned');
self.emit('error', new gutil.PluginError(PLUGIN_NAME, err));
//reject(err);
// The error was handled by emitting
resolve();
}
}
else {
resolve();
}
});
whenImageDealtWithPromise.finally(function() {
// Attach some extra data to what we emit in case something else wants to consume down the line
// Since we can chain the diffs, we need to maintain all of the analysis's
// If array, add to the array
if(chunk.analysis instanceof Array) {
chunk.analysis.push(analysis);
}
// If it already exists, make it into an array
else if(chunk.analysis) {
chunk.analysis = [chunk.analysis, analysis];
}
// Else, just set it to itself
else {
chunk.analysis = analysis;
}
// We don't maintain multiple difference images through chains
// We could, but this is just a design decision
chunk.differenceMap = differenceMapImageBuffer;
var logMessage = 'Diff complete: ' + analysis.differences + '/' + analysis.total + ' = ' + gutil.colors.cyan(analysis.differences / analysis.total) + ' -- ' + gutil.colors.magenta(analysis.compareImage) + ' compared to ' + gutil.colors.magenta(analysis.referenceImage);
// Emit some log events for anyone to catch
self.emit('log', logMessage);
// We also have a setting to log to the console
if(settings.logProgress) {
gutil.log(logMessage);
}
// Push out the original image
// So you can pipe it multiple times into the diff plugin against different references
self.push(chunk);
// "call callback when the transform operation is complete."
return cb();
});
}, function(err) {
err.message = 'Error making difference image:\n' + err.message;
self.emit('error', new gutil.PluginError(PLUGIN_NAME, err));
});
});
}, function(err) {
err.message = 'Error getting reference image\n:' + err.message;
self.emit('error', new gutil.PluginError(PLUGIN_NAME, err));
});
}
}, function(cb) {
// http://nodejs.org/docs/latest/api/stream.html#stream_transform_flush_callback
//console.log('flush');
// "call callback when the flush operation is complete."
cb();
});
// returning the file stream
return stream;
};
module.exports = differ;
module.exports.jsonReporter = jsonReporter;