UNPKG

6.71 kBJavaScriptView Raw
1'use strict';
2
3const _ = require('lodash');
4const parseColor = require('parse-color');
5const colorDiff = require('color-diff');
6const png = require('./lib/png');
7const areColorsSame = require('./lib/same-colors');
8const AntialiasingComparator = require('./lib/antialiasing-comparator');
9const IgnoreCaretComparator = require('./lib/ignore-caret-comparator');
10const utils = require('./lib/utils');
11const readPair = utils.readPair;
12const getDiffPixelsCoords = utils.getDiffPixelsCoords;
13
14const JND = 2.3; // Just noticeable difference if ciede2000 >= JND then colors difference is noticeable by human eye
15
16const makeAntialiasingComparator = (comparator, png1, png2, opts) => {
17 const antialiasingComparator = new AntialiasingComparator(comparator, png1, png2, opts);
18 return (data) => antialiasingComparator.compare(data);
19};
20
21const makeNoCaretColorComparator = (comparator, pixelRatio) => {
22 const caretComparator = new IgnoreCaretComparator(comparator, pixelRatio);
23 return (data) => caretComparator.compare(data);
24};
25
26function makeCIEDE2000Comparator(tolerance) {
27 return function doColorsLookSame(data) {
28 if (areColorsSame(data)) {
29 return true;
30 }
31 /*jshint camelcase:false*/
32 const lab1 = colorDiff.rgb_to_lab(data.color1);
33 const lab2 = colorDiff.rgb_to_lab(data.color2);
34
35 return colorDiff.diff(lab1, lab2) < tolerance;
36 };
37}
38
39const createComparator = (png1, png2, opts) => {
40 let comparator = opts.strict ? areColorsSame : makeCIEDE2000Comparator(opts.tolerance);
41
42 if (opts.ignoreAntialiasing) {
43 comparator = makeAntialiasingComparator(comparator, png1, png2, opts);
44 }
45
46 if (opts.ignoreCaret) {
47 comparator = makeNoCaretColorComparator(comparator, opts.pixelRatio);
48 }
49
50 return comparator;
51};
52
53const iterateRect = (width, height, callback, endCallback) => {
54 const processRow = (y) => {
55 setImmediate(() => {
56 for (let x = 0; x < width; x++) {
57 callback(x, y);
58 }
59
60 y++;
61
62 if (y < height) {
63 processRow(y);
64 } else {
65 endCallback();
66 }
67 });
68 };
69
70 processRow(0);
71};
72
73const buildDiffImage = (png1, png2, options, callback) => {
74 const width = Math.max(png1.width, png2.width);
75 const height = Math.max(png1.height, png2.height);
76 const minWidth = Math.min(png1.width, png2.width);
77 const minHeight = Math.min(png1.height, png2.height);
78 const highlightColor = options.highlightColor;
79 const result = png.empty(width, height);
80
81 iterateRect(width, height, (x, y) => {
82 if (x >= minWidth || y >= minHeight) {
83 result.setPixel(x, y, highlightColor);
84 return;
85 }
86
87 const color1 = png1.getPixel(x, y);
88 const color2 = png2.getPixel(x, y);
89
90 if (!options.comparator({color1, color2, png1, png2, x, y, width, height})) {
91 result.setPixel(x, y, highlightColor);
92 } else {
93 result.setPixel(x, y, color1);
94 }
95 }, () => callback(result));
96};
97
98const parseColorString = (str) => {
99 const parsed = parseColor(str);
100
101 return {
102 R: parsed.rgb[0],
103 G: parsed.rgb[1],
104 B: parsed.rgb[2]
105 };
106};
107
108const getToleranceFromOpts = (opts) => {
109 if (!_.hasIn(opts, 'tolerance')) {
110 return JND;
111 }
112
113 if (opts.strict) {
114 throw new TypeError('Unable to use "strict" and "tolerance" options together');
115 }
116
117 return opts.tolerance;
118};
119
120const prepareOpts = (opts) => {
121 opts.tolerance = getToleranceFromOpts(opts);
122
123 _.defaults(opts, {
124 ignoreAntialiasing: true,
125 antialiasingTolerance: 0
126 });
127};
128
129const getMaxDiffBounds = (first, second) => ({
130 left: 0,
131 top: 0,
132 right: Math.max(first.width, second.width) - 1,
133 bottom: Math.max(first.height, second.height) - 1
134});
135
136module.exports = exports = function looksSame(reference, image, opts, callback) {
137 if (!callback) {
138 callback = opts;
139 opts = {};
140 }
141
142 prepareOpts(opts);
143
144 readPair(reference, image, (error, pair) => {
145 if (error) {
146 return callback(error);
147 }
148
149 const first = pair.first;
150 const second = pair.second;
151 const refImg = {size: {width: pair.first.width, height: pair.first.height}};
152 const metaInfo = {refImg};
153
154 if (first.width !== second.width || first.height !== second.height) {
155 return process.nextTick(() => callback(null, {equal: false, metaInfo, diffBounds: getMaxDiffBounds(first, second)}));
156 }
157
158 const comparator = createComparator(first, second, opts);
159 const {stopOnFirstFail} = opts;
160
161 getDiffPixelsCoords(first, second, comparator, {stopOnFirstFail}, (result) => {
162 const diffBounds = result.area;
163
164 callback(null, {equal: result.isEmpty(), metaInfo, diffBounds});
165 });
166 });
167};
168
169exports.getDiffArea = function(reference, image, opts, callback) {
170 if (!callback) {
171 callback = opts;
172 opts = {};
173 }
174
175 prepareOpts(opts);
176
177 readPair(reference, image, (error, pair) => {
178 if (error) {
179 return callback(error);
180 }
181
182 const first = pair.first;
183 const second = pair.second;
184
185 if (first.width !== second.width || first.height !== second.height) {
186 return process.nextTick(() => callback(null, getMaxDiffBounds(first, second)));
187 }
188
189 const comparator = createComparator(first, second, opts);
190
191 getDiffPixelsCoords(first, second, comparator, opts, (result) => {
192 if (result.isEmpty()) {
193 return callback(null, null);
194 }
195
196 callback(null, result.area);
197 });
198 });
199};
200
201exports.createDiff = function saveDiff(opts, callback) {
202 opts.tolerance = getToleranceFromOpts(opts);
203
204 readPair(opts.reference, opts.current, (error, {first, second}) => {
205 if (error) {
206 return callback(error);
207 }
208
209 const diffOptions = {
210 highlightColor: parseColorString(opts.highlightColor),
211 comparator: createComparator(first, second, opts)
212 };
213
214 buildDiffImage(first, second, diffOptions, (result) => {
215 if (opts.diff === undefined) {
216 result.createBuffer(callback);
217 } else {
218 result.save(opts.diff, callback);
219 }
220 });
221 });
222};
223
224exports.colors = (color1, color2, opts) => {
225 opts = opts || {};
226
227 if (opts.tolerance === undefined) {
228 opts.tolerance = JND;
229 }
230
231 const comparator = makeCIEDE2000Comparator(opts.tolerance);
232
233 return comparator({color1, color2});
234};