diff --git a/.travis.yml b/.travis.yml
index acde58d1..262cf0c3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -30,6 +30,8 @@ jobs:
fast_finish: true
allow_failures:
- env: LAUNCHER=Node CANFAIL=TRUE
+ - env: LAUNCHER=Firefox CANFAIL=TRUE
+ - env: LAUNCHER=Chrome CANFAIL=TRUE
include:
- stage: Linting and Building
env: STEP=LINT
@@ -70,8 +72,15 @@ jobs:
node_js: "4"
- stage: Visual Tests
node_js: "8"
- env: LAUNCHER=Node CANFAIL=TRUE
script: npm run test:visual
+ - stage: Visual Tests
+ env: LAUNCHER=Chrome CANFAIL=TRUE
+ install: npm install testem@1.18.4 qunit@2.4.1
+ script: npm run build:fast && testem ci --port 8080 -f testem-visual.json -l $LAUNCHER
+ - stage: Visual Tests
+ env: LAUNCHER=Firefox CANFAIL=TRUE
+ install: npm install testem@1.18.4 qunit@2.4.1
+ script: npm run build:fast && testem ci --port 8080 -f testem-visual.json -l $LAUNCHER
script: 'npm run build:fast && testem ci --port 8080 -f testem.json -l $LAUNCHER'
diff --git a/package.json b/package.json
index 58b4ca60..0cb9b659 100644
--- a/package.json
+++ b/package.json
@@ -54,6 +54,7 @@
"export_tests_to_site": "cp test/unit/*.js ../fabricjs.com/test/unit",
"all": "npm run build && npm run test && npm run lint && npm run lint_tests && npm run export_dist_to_site && npm run export_tests_to_site",
"testem": "testem .",
+ "testem:visual": "testem --file testem-visual.json",
"testem:ci": "testem ci"
},
"optionalDependencies": {
diff --git a/test-visual.js b/test-visual.js
deleted file mode 100644
index b381d942..00000000
--- a/test-visual.js
+++ /dev/null
@@ -1,28 +0,0 @@
-var testrunner = require('node-qunit');
-
-testrunner.options.log.summary = true;
-testrunner.options.log.tests = false;
-testrunner.options.log.assertions = false;
-testrunner.options.log.coverage = false;
-
-testrunner.options.coverage = true;
-testrunner.options.maxBlockDuration = 120000;
-
-testrunner.run({
- deps: './test/fixtures/test_script.js',
- code: './dist/fabric.js',
- tests: [
- './test/visual/svg_import.js',
- ],
- // tests: ['./test/unit/object_clipPath.js',],
-}, function(err, report) {
- if (err) {
- console.log(err);
- process.exit(1);
- }
- if(report.failed > 0){
- process.on('exit', function() {
- process.exit(1);
- });
- }
-});
diff --git a/test/lib/pixelmatch.js b/test/lib/pixelmatch.js
new file mode 100644
index 00000000..73706c90
--- /dev/null
+++ b/test/lib/pixelmatch.js
@@ -0,0 +1,156 @@
+'use strict';
+
+function pixelmatch(img1, img2, output, width, height, options) {
+
+ if (!options) options = {};
+
+ var threshold = options.threshold === undefined ? 0.1 : options.threshold;
+
+ // maximum acceptable square distance between two colors;
+ // 35215 is the maximum possible value for the YIQ difference metric
+ var maxDelta = 35215 * threshold * threshold,
+ diff = 0;
+
+ // compare each pixel of one image against the other one
+ for (var y = 0; y < height; y++) {
+ for (var x = 0; x < width; x++) {
+
+ var pos = (y * width + x) * 4;
+
+ // squared YUV distance between colors at this pixel position
+ var delta = colorDelta(img1, img2, pos, pos);
+
+ // the color difference is above the threshold
+ if (delta > maxDelta) {
+ // check it's a real rendering difference or just anti-aliasing
+ if (!options.includeAA && (antialiased(img1, x, y, width, height, img2) ||
+ antialiased(img2, x, y, width, height, img1))) {
+ // one of the pixels is anti-aliasing; draw as yellow and do not count as difference
+ if (output) drawPixel(output, pos, 255, 255, 0);
+
+ } else {
+ // found substantial difference not caused by anti-aliasing; draw it as red
+ if (output) drawPixel(output, pos, 255, 0, 0);
+ diff++;
+ }
+
+ } else if (output) {
+ // pixels are similar; draw background as grayscale image blended with white
+ var val = blend(grayPixel(img1, pos), 0.1);
+ drawPixel(output, pos, val, val, val);
+ }
+ }
+ }
+
+ // return the number of different pixels
+ return diff;
+}
+
+// check if a pixel is likely a part of anti-aliasing;
+// based on "Anti-aliased Pixel and Intensity Slope Detector" paper by V. Vysniauskas, 2009
+
+function antialiased(img, x1, y1, width, height, img2) {
+ var x0 = Math.max(x1 - 1, 0),
+ y0 = Math.max(y1 - 1, 0),
+ x2 = Math.min(x1 + 1, width - 1),
+ y2 = Math.min(y1 + 1, height - 1),
+ pos = (y1 * width + x1) * 4,
+ zeroes = 0,
+ positives = 0,
+ negatives = 0,
+ min = 0,
+ max = 0,
+ minX, minY, maxX, maxY;
+
+ // go through 8 adjacent pixels
+ for (var x = x0; x <= x2; x++) {
+ for (var y = y0; y <= y2; y++) {
+ if (x === x1 && y === y1) continue;
+
+ // brightness delta between the center pixel and adjacent one
+ var delta = colorDelta(img, img, pos, (y * width + x) * 4, true);
+
+ // count the number of equal, darker and brighter adjacent pixels
+ if (delta === 0) zeroes++;
+ else if (delta < 0) negatives++;
+ else if (delta > 0) positives++;
+
+ // if found more than 2 equal siblings, it's definitely not anti-aliasing
+ if (zeroes > 2) return false;
+
+ if (!img2) continue;
+
+ // remember the darkest pixel
+ if (delta < min) {
+ min = delta;
+ minX = x;
+ minY = y;
+ }
+ // remember the brightest pixel
+ if (delta > max) {
+ max = delta;
+ maxX = x;
+ maxY = y;
+ }
+ }
+ }
+
+ if (!img2) return true;
+
+ // if there are no both darker and brighter pixels among siblings, it's not anti-aliasing
+ if (negatives === 0 || positives === 0) return false;
+
+ // if either the darkest or the brightest pixel has more than 2 equal siblings in both images
+ // (definitely not anti-aliased), this pixel is anti-aliased
+ return (!antialiased(img, minX, minY, width, height) && !antialiased(img2, minX, minY, width, height)) ||
+ (!antialiased(img, maxX, maxY, width, height) && !antialiased(img2, maxX, maxY, width, height));
+}
+
+// calculate color difference according to the paper "Measuring perceived color difference
+// using YIQ NTSC transmission color space in mobile applications" by Y. Kotsarenko and F. Ramos
+
+function colorDelta(img1, img2, k, m, yOnly) {
+ var a1 = img1[k + 3] / 255,
+ a2 = img2[m + 3] / 255,
+
+ r1 = blend(img1[k + 0], a1),
+ g1 = blend(img1[k + 1], a1),
+ b1 = blend(img1[k + 2], a1),
+
+ r2 = blend(img2[m + 0], a2),
+ g2 = blend(img2[m + 1], a2),
+ b2 = blend(img2[m + 2], a2),
+
+ y = rgb2y(r1, g1, b1) - rgb2y(r2, g2, b2);
+
+ if (yOnly) return y; // brightness difference only
+
+ var i = rgb2i(r1, g1, b1) - rgb2i(r2, g2, b2),
+ q = rgb2q(r1, g1, b1) - rgb2q(r2, g2, b2);
+
+ return 0.5053 * y * y + 0.299 * i * i + 0.1957 * q * q;
+}
+
+function rgb2y(r, g, b) { return r * 0.29889531 + g * 0.58662247 + b * 0.11448223; }
+function rgb2i(r, g, b) { return r * 0.59597799 - g * 0.27417610 - b * 0.32180189; }
+function rgb2q(r, g, b) { return r * 0.21147017 - g * 0.52261711 + b * 0.31114694; }
+
+// blend semi-transparent color with white
+function blend(c, a) {
+ return 255 + (c - 255) * a;
+}
+
+function drawPixel(output, pos, r, g, b) {
+ output[pos + 0] = r;
+ output[pos + 1] = g;
+ output[pos + 2] = b;
+ output[pos + 3] = 255;
+}
+
+function grayPixel(img, i) {
+ var a = img[i + 3] / 255,
+ r = blend(img[i + 0], a),
+ g = blend(img[i + 1], a),
+ b = blend(img[i + 2], a);
+ return rgb2y(r, g, b);
+}
diff --git a/test/visual/assets/svg_linear_1.svg b/test/visual/assets/svg_linear_1.svg
new file mode 100644
index 00000000..a05f7482
--- /dev/null
+++ b/test/visual/assets/svg_linear_1.svg
@@ -0,0 +1,13 @@
+
+
+
\ No newline at end of file
diff --git a/test/visual/assets/svg_linear_2.svg b/test/visual/assets/svg_linear_2.svg
new file mode 100644
index 00000000..593255da
--- /dev/null
+++ b/test/visual/assets/svg_linear_2.svg
@@ -0,0 +1,11 @@
+
+
+
\ No newline at end of file
diff --git a/test/visual/assets/svg_linear_3.svg b/test/visual/assets/svg_linear_3.svg
new file mode 100644
index 00000000..909e3a62
--- /dev/null
+++ b/test/visual/assets/svg_linear_3.svg
@@ -0,0 +1,11 @@
+
+
+
\ No newline at end of file
diff --git a/test/visual/assets/svg_linear_4.svg b/test/visual/assets/svg_linear_4.svg
new file mode 100644
index 00000000..5f5c263c
--- /dev/null
+++ b/test/visual/assets/svg_linear_4.svg
@@ -0,0 +1,20 @@
+
+
+
\ No newline at end of file
diff --git a/test/visual/assets/svg_linear_5.svg b/test/visual/assets/svg_linear_5.svg
new file mode 100644
index 00000000..cf6adc40
--- /dev/null
+++ b/test/visual/assets/svg_linear_5.svg
@@ -0,0 +1,13 @@
+
+
+
\ No newline at end of file
diff --git a/test/visual/assets/svg_linear_6.svg b/test/visual/assets/svg_linear_6.svg
new file mode 100644
index 00000000..59ba6af7
--- /dev/null
+++ b/test/visual/assets/svg_linear_6.svg
@@ -0,0 +1,13 @@
+
+
+
\ No newline at end of file
diff --git a/test/visual/assets/svg_linear_7.svg b/test/visual/assets/svg_linear_7.svg
new file mode 100644
index 00000000..e54be1f9
--- /dev/null
+++ b/test/visual/assets/svg_linear_7.svg
@@ -0,0 +1,13 @@
+
+
+
\ No newline at end of file
diff --git a/test/visual/assets/svg_linear_8.svg b/test/visual/assets/svg_linear_8.svg
new file mode 100644
index 00000000..fe21f47f
--- /dev/null
+++ b/test/visual/assets/svg_linear_8.svg
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/test/visual/assets/svg_radial_1.svg b/test/visual/assets/svg_radial_1.svg
new file mode 100644
index 00000000..0d9990d5
--- /dev/null
+++ b/test/visual/assets/svg_radial_1.svg
@@ -0,0 +1,11 @@
+
+
+
\ No newline at end of file
diff --git a/test/visual/assets/svg_radial_10.svg b/test/visual/assets/svg_radial_10.svg
new file mode 100644
index 00000000..479e6c04
--- /dev/null
+++ b/test/visual/assets/svg_radial_10.svg
@@ -0,0 +1,16 @@
+
\ No newline at end of file
diff --git a/test/visual/assets/svg_radial_11.svg b/test/visual/assets/svg_radial_11.svg
new file mode 100644
index 00000000..08d92f6f
--- /dev/null
+++ b/test/visual/assets/svg_radial_11.svg
@@ -0,0 +1,13 @@
+
\ No newline at end of file
diff --git a/test/visual/assets/svg_radial_12.svg b/test/visual/assets/svg_radial_12.svg
new file mode 100644
index 00000000..9f6db29f
--- /dev/null
+++ b/test/visual/assets/svg_radial_12.svg
@@ -0,0 +1,14 @@
+
\ No newline at end of file
diff --git a/test/visual/assets/svg_radial_13.svg b/test/visual/assets/svg_radial_13.svg
new file mode 100644
index 00000000..86d16a78
--- /dev/null
+++ b/test/visual/assets/svg_radial_13.svg
@@ -0,0 +1,14 @@
+
\ No newline at end of file
diff --git a/test/visual/assets/svg_radial_2.svg b/test/visual/assets/svg_radial_2.svg
new file mode 100644
index 00000000..07169461
--- /dev/null
+++ b/test/visual/assets/svg_radial_2.svg
@@ -0,0 +1,12 @@
+
+
+
+
\ No newline at end of file
diff --git a/test/visual/assets/svg_radial_3.svg b/test/visual/assets/svg_radial_3.svg
new file mode 100644
index 00000000..e08ae361
--- /dev/null
+++ b/test/visual/assets/svg_radial_3.svg
@@ -0,0 +1,13 @@
+
+
+
\ No newline at end of file
diff --git a/test/visual/assets/svg_radial_4.svg b/test/visual/assets/svg_radial_4.svg
new file mode 100644
index 00000000..880b24e2
--- /dev/null
+++ b/test/visual/assets/svg_radial_4.svg
@@ -0,0 +1,11 @@
+
+
+
\ No newline at end of file
diff --git a/test/visual/assets/svg_radial_5.svg b/test/visual/assets/svg_radial_5.svg
new file mode 100644
index 00000000..1d00f815
--- /dev/null
+++ b/test/visual/assets/svg_radial_5.svg
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/test/visual/assets/svg_radial_6.svg b/test/visual/assets/svg_radial_6.svg
new file mode 100644
index 00000000..5de412b5
--- /dev/null
+++ b/test/visual/assets/svg_radial_6.svg
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/test/visual/assets/svg_radial_8.svg b/test/visual/assets/svg_radial_8.svg
new file mode 100644
index 00000000..89b43388
--- /dev/null
+++ b/test/visual/assets/svg_radial_8.svg
@@ -0,0 +1,11 @@
+
+
+
\ No newline at end of file
diff --git a/test/visual/assets/svg_radial_9.svg b/test/visual/assets/svg_radial_9.svg
new file mode 100644
index 00000000..5220c20e
--- /dev/null
+++ b/test/visual/assets/svg_radial_9.svg
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/test/visual/assets/svg_stroke_1.svg b/test/visual/assets/svg_stroke_1.svg
index d8dfd763..97a17fd5 100644
--- a/test/visual/assets/svg_stroke_1.svg
+++ b/test/visual/assets/svg_stroke_1.svg
@@ -7,9 +7,11 @@