compaa 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Rakefile +10 -2
- data/bin/compaa +6 -0
- data/compaa.gemspec +3 -0
- data/lib/assets/blender.js +59 -0
- data/lib/assets/compaa.js +138 -140
- data/lib/assets/context_blender.js +51 -0
- data/lib/assets/index.haml +4 -4
- data/lib/compaa/rack_app.rb +6 -3
- data/lib/compaa/version.rb +1 -1
- data/mock_app.rb +24 -15
- data/spec/generated_image_spec.rb +1 -1
- data/spec/integration/integration_spec.rb +1 -1
- data/spec/javascripts/BlenderSpec.js +26 -0
- data/spec/javascripts/support/expected.png +0 -0
- data/spec/javascripts/support/generated.png +0 -0
- data/spec/javascripts/support/imagediff.js +382 -0
- data/spec/javascripts/support/jasmine.yml +4 -4
- data/spec/javascripts/support/one.jpg +0 -0
- data/spec/javascripts/support/reference.png +0 -0
- data/spec/javascripts/support/three.png +0 -0
- data/spec/javascripts/support/two.jpg +0 -0
- metadata +48 -8
- data/lib/assets/jquery-2.0.0.js +0 -8755
- data/lib/assets/underscore.js +0 -1227
- data/spec/javascripts/CompaaSpec.js +0 -81
- data/spec/javascripts/support/jasmine-jquery.js +0 -658
data/lib/assets/index.haml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
!!!
|
2
2
|
%head
|
3
|
-
%script{src: '/assets/
|
4
|
-
%script{src: '/assets/
|
3
|
+
%script{src: '/assets/context_blender.js'}
|
4
|
+
%script{src: '/assets/blender.js'}
|
5
5
|
%script{src: '/assets/compaa.js'}
|
6
6
|
%link{rel: 'stylesheet', href: '/assets/bootstrap.css'}
|
7
7
|
:javascript
|
8
8
|
var compaa;
|
9
|
-
|
9
|
+
document.addEventListener('DOMContentLoaded', function() {
|
10
10
|
compaa = Object.create(Compaa);
|
11
11
|
compaa.init();
|
12
|
-
});
|
12
|
+
}, false);
|
13
13
|
|
14
14
|
%body
|
15
15
|
%canvas#difference
|
data/lib/compaa/rack_app.rb
CHANGED
@@ -37,9 +37,12 @@ module Compaa
|
|
37
37
|
|
38
38
|
def get
|
39
39
|
case @request.path
|
40
|
-
when '/'
|
41
|
-
when '/artifacts.json'
|
42
|
-
|
40
|
+
when '/' then index
|
41
|
+
when '/artifacts.json' then artifacts_json
|
42
|
+
when '/compaa.js' then compaa_js
|
43
|
+
when '/blender.js' then blender_js
|
44
|
+
when '/context_blender.js' then context_blender_js
|
45
|
+
else four_oh_four
|
43
46
|
end
|
44
47
|
end
|
45
48
|
|
data/lib/compaa/version.rb
CHANGED
data/mock_app.rb
CHANGED
@@ -1,22 +1,31 @@
|
|
1
|
-
require 'sinatra'
|
1
|
+
require 'sinatra/base'
|
2
2
|
require 'json'
|
3
3
|
require 'rack/cors'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
class MockCompaa < Sinatra::Base
|
6
|
+
set :port, 4567
|
7
|
+
disable :logging
|
8
|
+
|
9
|
+
use Rack::Cors do
|
10
|
+
allow do
|
11
|
+
origins '*'
|
12
|
+
resource '/*', :headers => :any, :methods => [:get, :post]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
get '/artifacts.json' do
|
17
|
+
content_type 'application/json'
|
18
|
+
{
|
19
|
+
artifacts: %w(
|
20
|
+
artifacts/reference_screenshots/one.png
|
21
|
+
artifacts/reference_screenshots/two.png
|
22
|
+
artifacts/reference_screenshots/three.png
|
23
|
+
artifacts/reference_screenshots/four.png
|
24
|
+
)
|
25
|
+
}.to_json
|
9
26
|
end
|
10
27
|
end
|
11
28
|
|
12
|
-
|
13
|
-
|
14
|
-
{
|
15
|
-
artifacts: %w(
|
16
|
-
artifacts/reference_screenshots/one.png
|
17
|
-
artifacts/reference_screenshots/two.png
|
18
|
-
artifacts/reference_screenshots/three.png
|
19
|
-
artifacts/reference_screenshots/four.png
|
20
|
-
)
|
21
|
-
}.to_json
|
29
|
+
Thread.new do
|
30
|
+
MockCompaa.run!
|
22
31
|
end
|
@@ -71,7 +71,7 @@ describe Compaa::GeneratedImage do
|
|
71
71
|
difference_path =
|
72
72
|
File.join %w[artifacts differences_in_screenshots_this_run dir file.png_difference.gif]
|
73
73
|
|
74
|
-
mock_file_manager.expect :rm, true, [difference_path]
|
74
|
+
mock_file_manager.expect :rm, true, [difference_path, { force: true }]
|
75
75
|
|
76
76
|
subject.delete_difference_image
|
77
77
|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
describe("blender", function() {
|
2
|
+
describe("makeItBlend()", function() {
|
3
|
+
it("produces a difference image", function() {
|
4
|
+
var imageOne = new Image(),
|
5
|
+
imageTwo = new Image(),
|
6
|
+
expectedImage = new Image(),
|
7
|
+
actual;
|
8
|
+
|
9
|
+
this.addMatchers(imagediff.jasmine);
|
10
|
+
|
11
|
+
expectedImage.src = '/spec/javascripts/support/expected.png';
|
12
|
+
imageOne.src = '/spec/javascripts/support/generated.png';
|
13
|
+
imageTwo.src = '/spec/javascripts/support/reference.png';
|
14
|
+
|
15
|
+
waitsFor(function() {
|
16
|
+
return imageOne.complete && imageTwo.complete && expectedImage.complete;
|
17
|
+
});
|
18
|
+
|
19
|
+
runs(function() {
|
20
|
+
window.blender.makeItBlend(imageOne, imageTwo, function(blended) {
|
21
|
+
expect(blended).toImageDiffEqual(expectedImage);
|
22
|
+
});
|
23
|
+
});
|
24
|
+
});
|
25
|
+
});
|
26
|
+
});
|
Binary file
|
Binary file
|
@@ -0,0 +1,382 @@
|
|
1
|
+
// js-imagediff 1.0.3
|
2
|
+
// (c) 2011-2012 Carl Sutherland, Humble Software
|
3
|
+
// Distributed under the MIT License
|
4
|
+
// For original source and documentation visit:
|
5
|
+
// http://www.github.com/HumbleSoftware/js-imagediff
|
6
|
+
|
7
|
+
(function (name, definition) {
|
8
|
+
var root = this;
|
9
|
+
if (typeof module !== 'undefined') {
|
10
|
+
var Canvas = require('canvas');
|
11
|
+
module.exports = definition(root, name, Canvas);
|
12
|
+
} else if (typeof define === 'function' && typeof define.amd === 'object') {
|
13
|
+
define(definition);
|
14
|
+
} else {
|
15
|
+
root[name] = definition(root, name);
|
16
|
+
}
|
17
|
+
})('imagediff', function (root, name, Canvas) {
|
18
|
+
|
19
|
+
var
|
20
|
+
TYPE_ARRAY = /\[object Array\]/i,
|
21
|
+
TYPE_CANVAS = /\[object (Canvas|HTMLCanvasElement)\]/i,
|
22
|
+
TYPE_CONTEXT = /\[object CanvasRenderingContext2D\]/i,
|
23
|
+
TYPE_IMAGE = /\[object (Image|HTMLImageElement)\]/i,
|
24
|
+
TYPE_IMAGE_DATA = /\[object ImageData\]/i,
|
25
|
+
|
26
|
+
UNDEFINED = 'undefined',
|
27
|
+
|
28
|
+
canvas = getCanvas(),
|
29
|
+
context = canvas.getContext('2d'),
|
30
|
+
previous = root[name],
|
31
|
+
imagediff, jasmine;
|
32
|
+
|
33
|
+
// Creation
|
34
|
+
function getCanvas (width, height) {
|
35
|
+
var
|
36
|
+
canvas = Canvas ?
|
37
|
+
new Canvas() :
|
38
|
+
document.createElement('canvas');
|
39
|
+
if (width) canvas.width = width;
|
40
|
+
if (height) canvas.height = height;
|
41
|
+
return canvas;
|
42
|
+
}
|
43
|
+
function getImageData (width, height) {
|
44
|
+
canvas.width = width;
|
45
|
+
canvas.height = height;
|
46
|
+
context.clearRect(0, 0, width, height);
|
47
|
+
return context.createImageData(width, height);
|
48
|
+
}
|
49
|
+
|
50
|
+
|
51
|
+
// Type Checking
|
52
|
+
function isImage (object) {
|
53
|
+
return isType(object, TYPE_IMAGE);
|
54
|
+
}
|
55
|
+
function isCanvas (object) {
|
56
|
+
return isType(object, TYPE_CANVAS);
|
57
|
+
}
|
58
|
+
function isContext (object) {
|
59
|
+
return isType(object, TYPE_CONTEXT);
|
60
|
+
}
|
61
|
+
function isImageData (object) {
|
62
|
+
return !!(object &&
|
63
|
+
isType(object, TYPE_IMAGE_DATA) &&
|
64
|
+
typeof(object.width) !== UNDEFINED &&
|
65
|
+
typeof(object.height) !== UNDEFINED &&
|
66
|
+
typeof(object.data) !== UNDEFINED);
|
67
|
+
}
|
68
|
+
function isImageType (object) {
|
69
|
+
return (
|
70
|
+
isImage(object) ||
|
71
|
+
isCanvas(object) ||
|
72
|
+
isContext(object) ||
|
73
|
+
isImageData(object)
|
74
|
+
);
|
75
|
+
}
|
76
|
+
function isType (object, type) {
|
77
|
+
return typeof (object) === 'object' && !!Object.prototype.toString.apply(object).match(type);
|
78
|
+
}
|
79
|
+
|
80
|
+
|
81
|
+
// Type Conversion
|
82
|
+
function copyImageData (imageData) {
|
83
|
+
var
|
84
|
+
height = imageData.height,
|
85
|
+
width = imageData.width,
|
86
|
+
data = imageData.data,
|
87
|
+
newImageData, newData, i;
|
88
|
+
|
89
|
+
canvas.width = width;
|
90
|
+
canvas.height = height;
|
91
|
+
newImageData = context.getImageData(0, 0, width, height);
|
92
|
+
newData = newImageData.data;
|
93
|
+
|
94
|
+
for (i = imageData.data.length; i--;) {
|
95
|
+
newData[i] = data[i];
|
96
|
+
}
|
97
|
+
|
98
|
+
return newImageData;
|
99
|
+
}
|
100
|
+
function toImageData (object) {
|
101
|
+
if (isImage(object)) { return toImageDataFromImage(object); }
|
102
|
+
if (isCanvas(object)) { return toImageDataFromCanvas(object); }
|
103
|
+
if (isContext(object)) { return toImageDataFromContext(object); }
|
104
|
+
if (isImageData(object)) { return object; }
|
105
|
+
}
|
106
|
+
function toImageDataFromImage (image) {
|
107
|
+
var
|
108
|
+
height = image.height,
|
109
|
+
width = image.width;
|
110
|
+
canvas.width = width;
|
111
|
+
canvas.height = height;
|
112
|
+
context.clearRect(0, 0, width, height);
|
113
|
+
context.drawImage(image, 0, 0);
|
114
|
+
return context.getImageData(0, 0, width, height);
|
115
|
+
}
|
116
|
+
function toImageDataFromCanvas (canvas) {
|
117
|
+
var
|
118
|
+
height = canvas.height,
|
119
|
+
width = canvas.width,
|
120
|
+
context = canvas.getContext('2d');
|
121
|
+
return context.getImageData(0, 0, width, height);
|
122
|
+
}
|
123
|
+
function toImageDataFromContext (context) {
|
124
|
+
var
|
125
|
+
canvas = context.canvas,
|
126
|
+
height = canvas.height,
|
127
|
+
width = canvas.width;
|
128
|
+
return context.getImageData(0, 0, width, height);
|
129
|
+
}
|
130
|
+
function toCanvas (object) {
|
131
|
+
var
|
132
|
+
data = toImageData(object),
|
133
|
+
canvas = getCanvas(data.width, data.height),
|
134
|
+
context = canvas.getContext('2d');
|
135
|
+
|
136
|
+
context.putImageData(data, 0, 0);
|
137
|
+
return canvas;
|
138
|
+
}
|
139
|
+
|
140
|
+
|
141
|
+
// ImageData Equality Operators
|
142
|
+
function equalWidth (a, b) {
|
143
|
+
return a.width === b.width;
|
144
|
+
}
|
145
|
+
function equalHeight (a, b) {
|
146
|
+
return a.height === b.height;
|
147
|
+
}
|
148
|
+
function equalDimensions (a, b) {
|
149
|
+
return equalHeight(a, b) && equalWidth(a, b);
|
150
|
+
}
|
151
|
+
function equal (a, b, tolerance) {
|
152
|
+
|
153
|
+
var
|
154
|
+
aData = a.data,
|
155
|
+
bData = b.data,
|
156
|
+
length = aData.length,
|
157
|
+
i;
|
158
|
+
|
159
|
+
tolerance = tolerance || 0;
|
160
|
+
|
161
|
+
if (!equalDimensions(a, b)) return false;
|
162
|
+
for (i = length; i--;) if (aData[i] !== bData[i] && Math.abs(aData[i] - bData[i]) > tolerance) return false;
|
163
|
+
|
164
|
+
return true;
|
165
|
+
}
|
166
|
+
|
167
|
+
|
168
|
+
// Diff
|
169
|
+
function diff (a, b) {
|
170
|
+
return (equalDimensions(a, b) ? diffEqual : diffUnequal)(a, b);
|
171
|
+
}
|
172
|
+
function diffEqual (a, b) {
|
173
|
+
|
174
|
+
var
|
175
|
+
height = a.height,
|
176
|
+
width = a.width,
|
177
|
+
c = getImageData(width, height), // c = a - b
|
178
|
+
aData = a.data,
|
179
|
+
bData = b.data,
|
180
|
+
cData = c.data,
|
181
|
+
length = cData.length,
|
182
|
+
row, column,
|
183
|
+
i, j, k, v;
|
184
|
+
|
185
|
+
for (i = 0; i < length; i += 4) {
|
186
|
+
cData[i] = Math.abs(aData[i] - bData[i]);
|
187
|
+
cData[i+1] = Math.abs(aData[i+1] - bData[i+1]);
|
188
|
+
cData[i+2] = Math.abs(aData[i+2] - bData[i+2]);
|
189
|
+
cData[i+3] = Math.abs(255 - aData[i+3] - bData[i+3]);
|
190
|
+
}
|
191
|
+
|
192
|
+
return c;
|
193
|
+
}
|
194
|
+
function diffUnequal (a, b) {
|
195
|
+
|
196
|
+
var
|
197
|
+
height = Math.max(a.height, b.height),
|
198
|
+
width = Math.max(a.width, b.width),
|
199
|
+
c = getImageData(width, height), // c = a - b
|
200
|
+
aData = a.data,
|
201
|
+
bData = b.data,
|
202
|
+
cData = c.data,
|
203
|
+
rowOffset,
|
204
|
+
columnOffset,
|
205
|
+
row, column,
|
206
|
+
i, j, k, v;
|
207
|
+
|
208
|
+
|
209
|
+
for (i = cData.length - 1; i > 0; i = i - 4) {
|
210
|
+
cData[i] = 255;
|
211
|
+
}
|
212
|
+
|
213
|
+
// Add First Image
|
214
|
+
offsets(a);
|
215
|
+
for (row = a.height; row--;){
|
216
|
+
for (column = a.width; column--;) {
|
217
|
+
i = 4 * ((row + rowOffset) * width + (column + columnOffset));
|
218
|
+
j = 4 * (row * a.width + column);
|
219
|
+
cData[i+0] = aData[j+0]; // r
|
220
|
+
cData[i+1] = aData[j+1]; // g
|
221
|
+
cData[i+2] = aData[j+2]; // b
|
222
|
+
// cData[i+3] = aData[j+3]; // a
|
223
|
+
}
|
224
|
+
}
|
225
|
+
|
226
|
+
// Subtract Second Image
|
227
|
+
offsets(b);
|
228
|
+
for (row = b.height; row--;){
|
229
|
+
for (column = b.width; column--;) {
|
230
|
+
i = 4 * ((row + rowOffset) * width + (column + columnOffset));
|
231
|
+
j = 4 * (row * b.width + column);
|
232
|
+
cData[i+0] = Math.abs(cData[i+0] - bData[j+0]); // r
|
233
|
+
cData[i+1] = Math.abs(cData[i+1] - bData[j+1]); // g
|
234
|
+
cData[i+2] = Math.abs(cData[i+2] - bData[j+2]); // b
|
235
|
+
}
|
236
|
+
}
|
237
|
+
|
238
|
+
// Helpers
|
239
|
+
function offsets (imageData) {
|
240
|
+
rowOffset = Math.floor((height - imageData.height) / 2);
|
241
|
+
columnOffset = Math.floor((width - imageData.width) / 2);
|
242
|
+
}
|
243
|
+
|
244
|
+
return c;
|
245
|
+
}
|
246
|
+
|
247
|
+
|
248
|
+
// Validation
|
249
|
+
function checkType () {
|
250
|
+
var i;
|
251
|
+
for (i = 0; i < arguments.length; i++) {
|
252
|
+
if (!isImageType(arguments[i])) {
|
253
|
+
throw {
|
254
|
+
name : 'ImageTypeError',
|
255
|
+
message : 'Submitted object was not an image.'
|
256
|
+
};
|
257
|
+
}
|
258
|
+
}
|
259
|
+
}
|
260
|
+
|
261
|
+
|
262
|
+
// Jasmine Matchers
|
263
|
+
function get (element, content) {
|
264
|
+
element = document.createElement(element);
|
265
|
+
if (element && content) {
|
266
|
+
element.innerHTML = content;
|
267
|
+
}
|
268
|
+
return element;
|
269
|
+
}
|
270
|
+
|
271
|
+
jasmine = {
|
272
|
+
|
273
|
+
toBeImageData : function () {
|
274
|
+
return imagediff.isImageData(this.actual);
|
275
|
+
},
|
276
|
+
|
277
|
+
toImageDiffEqual : function (expected, tolerance) {
|
278
|
+
|
279
|
+
if (typeof (document) !== UNDEFINED) {
|
280
|
+
this.message = function () {
|
281
|
+
var
|
282
|
+
div = get('div'),
|
283
|
+
a = get('div', '<div>Actual:</div>'),
|
284
|
+
b = get('div', '<div>Expected:</div>'),
|
285
|
+
c = get('div', '<div>Diff:</div>'),
|
286
|
+
diff = imagediff.diff(this.actual, expected),
|
287
|
+
canvas = getCanvas(),
|
288
|
+
context;
|
289
|
+
|
290
|
+
canvas.height = diff.height;
|
291
|
+
canvas.width = diff.width;
|
292
|
+
|
293
|
+
div.style.overflow = 'hidden';
|
294
|
+
a.style.float = 'left';
|
295
|
+
b.style.float = 'left';
|
296
|
+
c.style.float = 'left';
|
297
|
+
|
298
|
+
context = canvas.getContext('2d');
|
299
|
+
context.putImageData(diff, 0, 0);
|
300
|
+
|
301
|
+
a.appendChild(toCanvas(this.actual));
|
302
|
+
b.appendChild(toCanvas(expected));
|
303
|
+
c.appendChild(canvas);
|
304
|
+
|
305
|
+
div.appendChild(a);
|
306
|
+
div.appendChild(b);
|
307
|
+
div.appendChild(c);
|
308
|
+
|
309
|
+
return [
|
310
|
+
div,
|
311
|
+
"Expected not to be equal."
|
312
|
+
];
|
313
|
+
};
|
314
|
+
}
|
315
|
+
|
316
|
+
return imagediff.equal(this.actual, expected, tolerance);
|
317
|
+
}
|
318
|
+
};
|
319
|
+
|
320
|
+
|
321
|
+
// Image Output
|
322
|
+
function imageDataToPNG (imageData, outputFile, callback) {
|
323
|
+
|
324
|
+
var
|
325
|
+
canvas = toCanvas(imageData),
|
326
|
+
base64Data,
|
327
|
+
decodedImage;
|
328
|
+
|
329
|
+
callback = callback || Function;
|
330
|
+
|
331
|
+
base64Data = canvas.toDataURL().replace(/^data:image\/\w+;base64,/,"");
|
332
|
+
decodedImage = new Buffer(base64Data, 'base64');
|
333
|
+
require('fs').writeFile(outputFile, decodedImage, callback);
|
334
|
+
}
|
335
|
+
|
336
|
+
|
337
|
+
// Definition
|
338
|
+
imagediff = {
|
339
|
+
|
340
|
+
createCanvas : getCanvas,
|
341
|
+
createImageData : getImageData,
|
342
|
+
|
343
|
+
isImage : isImage,
|
344
|
+
isCanvas : isCanvas,
|
345
|
+
isContext : isContext,
|
346
|
+
isImageData : isImageData,
|
347
|
+
isImageType : isImageType,
|
348
|
+
|
349
|
+
toImageData : function (object) {
|
350
|
+
checkType(object);
|
351
|
+
if (isImageData(object)) { return copyImageData(object); }
|
352
|
+
return toImageData(object);
|
353
|
+
},
|
354
|
+
|
355
|
+
equal : function (a, b, tolerance) {
|
356
|
+
checkType(a, b);
|
357
|
+
a = toImageData(a);
|
358
|
+
b = toImageData(b);
|
359
|
+
return equal(a, b, tolerance);
|
360
|
+
},
|
361
|
+
diff : function (a, b) {
|
362
|
+
checkType(a, b);
|
363
|
+
a = toImageData(a);
|
364
|
+
b = toImageData(b);
|
365
|
+
return diff(a, b);
|
366
|
+
},
|
367
|
+
|
368
|
+
jasmine : jasmine,
|
369
|
+
|
370
|
+
// Compatibility
|
371
|
+
noConflict : function () {
|
372
|
+
root[name] = previous;
|
373
|
+
return imagediff;
|
374
|
+
}
|
375
|
+
};
|
376
|
+
|
377
|
+
if (typeof module !== 'undefined') {
|
378
|
+
imagediff.imageDataToPNG = imageDataToPNG;
|
379
|
+
}
|
380
|
+
|
381
|
+
return imagediff;
|
382
|
+
});
|