Squashed commit of the following (unittest-refactor):
commit 9887e45976fc5211a4393e588d278a5e10baf06c Author: Dr. Kibitz <info@drkibitz.com> Date: Tue Sep 3 10:54:13 2013 -0700 FIX: Ellipse getBounds method is not static commit b3b359ad1c0fae412209705b01081dd231383656 Author: Dr. Kibitz <info@drkibitz.com> Date: Tue Sep 3 10:53:41 2013 -0700 More unit testing commit dafbd1dbb03364b6d54db0cad26c4f02a8b9efe1 Author: Dr. Kibitz <info@drkibitz.com> Date: Sun Sep 1 16:04:29 2013 -0700 Remove unused karma plugin commit 43209bfb59263278d2dc3561d06926ad515c4147 Author: Dr. Kibitz <info@drkibitz.com> Date: Sun Sep 1 15:28:21 2013 -0700 Add Travis CI status image to README commit 2e38ba370bc05c239736155dcb06e34b47abf938 Author: Dr. Kibitz <info@drkibitz.com> Date: Sun Sep 1 15:16:54 2013 -0700 typo commit b8898030da4d3e7b446ebb4ddeb30e8fc0adcf39 Author: Dr. Kibitz <info@drkibitz.com> Date: Sun Sep 1 15:13:33 2013 -0700 Lint test files, jsthint:test is now part of the test task commit ea414dd1d734b39523174bd86d0cc58e920a89d2 Author: Dr. Kibitz <info@drkibitz.com> Date: Sun Sep 1 15:11:50 2013 -0700 Make all boilerplate tests, and an actual start to writing some real tests commit 9e09c87997ae924989da4e1685f3a94636ab64de Author: Dr. Kibitz <info@drkibitz.com> Date: Sun Sep 1 14:37:51 2013 -0700 Add back serving png files, convert to spaces commit b45b1650c2e812a9cdb1165d96fba7b14bbdfab7 Author: Dr. Kibitz <info@drkibitz.com> Date: Sun Sep 1 00:55:57 2013 -0700 Comment out functional tests for now commit 3a350bf9e4d9fea470ad8e734dfc3542db74ccc8 Author: Dr. Kibitz <info@drkibitz.com> Date: Sat Aug 31 23:53:35 2013 -0700 Removed quint, added karma, added resemble.js, refactored unit tests for mocha, added first example visual test run with: grunt travis You should see a brewer open and eventually see the spinning bunny. Then in the terminal, you should see the tests running where it matches frames 30, 60, and 90. Conflicts: package.json commit ac4c6de388e7a37786c9c4a6b2c95e22ec26b05d Author: Dr. Kibitz <info@drkibitz.com> Date: Sat Aug 31 23:33:23 2013 -0700 Remove trailing whitespace
This commit is contained in:
parent
56df185471
commit
da4c67d38b
98 changed files with 1829 additions and 2585 deletions
535
test/lib/resemble.js
Normal file
535
test/lib/resemble.js
Normal file
|
@ -0,0 +1,535 @@
|
|||
/*
|
||||
Author: James Cryer
|
||||
Company: Huddle
|
||||
Last updated date: 21 Feb 2013
|
||||
URL: https://github.com/Huddle/Resemble.js
|
||||
*/
|
||||
|
||||
(function(_this){
|
||||
'use strict';
|
||||
|
||||
_this['resemble'] = function( fileData ){
|
||||
|
||||
var data = {};
|
||||
var images = [];
|
||||
var updateCallbackArray = [];
|
||||
|
||||
var tolerance = { // between 0 and 255
|
||||
red: 16,
|
||||
green: 16,
|
||||
blue: 16,
|
||||
minBrightness: 16,
|
||||
maxBrightness: 240
|
||||
};
|
||||
|
||||
var ignoreAntialiasing = false;
|
||||
var ignoreColors = false;
|
||||
|
||||
function triggerDataUpdate(){
|
||||
var len = updateCallbackArray.length;
|
||||
var i;
|
||||
for(i=0;i<len;i++){
|
||||
if (typeof updateCallbackArray[i] === 'function'){
|
||||
updateCallbackArray[i](data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loop(x, y, callback){
|
||||
var i,j;
|
||||
|
||||
for (i=0;i<x;i++){
|
||||
for (j=0;j<y;j++){
|
||||
callback(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseImage(sourceImageData, width, height){
|
||||
|
||||
var pixleCount = 0;
|
||||
var redTotal = 0;
|
||||
var greenTotal = 0;
|
||||
var blueTotal = 0;
|
||||
var brightnessTotal = 0;
|
||||
|
||||
loop(height, width, function(verticalPos, horizontalPos){
|
||||
var offset = (verticalPos*width + horizontalPos) * 4;
|
||||
var red = sourceImageData[offset];
|
||||
var green = sourceImageData[offset + 1];
|
||||
var blue = sourceImageData[offset + 2];
|
||||
var brightness = getBrightness(red,green,blue);
|
||||
|
||||
pixleCount++;
|
||||
|
||||
redTotal += red / 255 * 100;
|
||||
greenTotal += green / 255 * 100;
|
||||
blueTotal += blue / 255 * 100;
|
||||
brightnessTotal += brightness / 255 * 100;
|
||||
});
|
||||
|
||||
data.red = Math.floor(redTotal / pixleCount);
|
||||
data.green = Math.floor(greenTotal / pixleCount);
|
||||
data.blue = Math.floor(blueTotal / pixleCount);
|
||||
data.brightness = Math.floor(brightnessTotal / pixleCount);
|
||||
|
||||
triggerDataUpdate();
|
||||
}
|
||||
|
||||
function loadImageData( fileData, callback ){
|
||||
var fileReader,
|
||||
hiddenImage = new Image();
|
||||
|
||||
hiddenImage.onload = function() {
|
||||
|
||||
var hiddenCanvas = document.createElement('canvas');
|
||||
var imageData;
|
||||
var width = hiddenImage.width;
|
||||
var height = hiddenImage.height;
|
||||
|
||||
hiddenCanvas.width = width;
|
||||
hiddenCanvas.height = height;
|
||||
hiddenCanvas.getContext('2d').drawImage(hiddenImage, 0, 0, width, height);
|
||||
imageData = hiddenCanvas.getContext('2d').getImageData(0, 0, width, height);
|
||||
|
||||
images.push(imageData);
|
||||
|
||||
callback(imageData, width, height);
|
||||
};
|
||||
|
||||
if (typeof fileData === 'string') {
|
||||
hiddenImage.src = fileData;
|
||||
} else {
|
||||
fileReader = new FileReader();
|
||||
fileReader.onload = function (event) {
|
||||
hiddenImage.src = event.target.result;
|
||||
};
|
||||
fileReader.readAsDataURL(fileData);
|
||||
}
|
||||
}
|
||||
|
||||
function isColorSimilar(a, b, color){
|
||||
|
||||
var absDiff = Math.abs(a - b);
|
||||
|
||||
if(typeof a === 'undefined'){
|
||||
return false;
|
||||
}
|
||||
if(typeof b === 'undefined'){
|
||||
return false;
|
||||
}
|
||||
|
||||
if(a === b){
|
||||
return true;
|
||||
} else if ( absDiff < tolerance[color] ) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isNumber(n) {
|
||||
return !isNaN(parseFloat(n));
|
||||
}
|
||||
|
||||
function isPixelBrightnessSimilar(d1, d2){
|
||||
return Math.abs(d1.brightness - d2.brightness) < tolerance.minBrightness;
|
||||
}
|
||||
|
||||
function getBrightness(r,g,b){
|
||||
return 0.3*r + 0.59*g + 0.11*b;
|
||||
}
|
||||
|
||||
function isRGBSame(d1,d2){
|
||||
var red = d1.r === d2.r;
|
||||
var green = d1.g === d2.g;
|
||||
var blue = d1.b === d2.b;
|
||||
return red && green && blue;
|
||||
}
|
||||
|
||||
function isRGBSimilar(d1, d2){
|
||||
var red = isColorSimilar(d1.r,d2.r,'red');
|
||||
var green = isColorSimilar(d1.g,d2.g,'green');
|
||||
var blue = isColorSimilar(d1.b,d2.b,'blue');
|
||||
|
||||
return red && green && blue;
|
||||
}
|
||||
|
||||
function isContrasting(d1, d2){
|
||||
return Math.abs(d1.brightness - d2.brightness) > tolerance.maxBrightness;
|
||||
}
|
||||
|
||||
function getHue(r,g,b){
|
||||
|
||||
r = r / 255;
|
||||
g = g / 255;
|
||||
b = b / 255;
|
||||
var max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||||
var h;
|
||||
var d;
|
||||
|
||||
if (max == min){
|
||||
h = 0; // achromatic
|
||||
} else{
|
||||
d = max - min;
|
||||
switch(max){
|
||||
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||||
case g: h = (b - r) / d + 2; break;
|
||||
case b: h = (r - g) / d + 4; break;
|
||||
}
|
||||
h /= 6;
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
function isAntialiased(sourcePix, data, cacheSet, verticalPos, horizontalPos, width){
|
||||
var offset;
|
||||
var targetPix;
|
||||
var distance = 1;
|
||||
var i;
|
||||
var j;
|
||||
var hasHighContrastSibling = 0;
|
||||
var hasSiblingWithDifferentHue = 0;
|
||||
var hasEquivilantSibling = 0;
|
||||
|
||||
addHueInfo(sourcePix);
|
||||
|
||||
for (i = distance*-1; i <= distance; i++){
|
||||
for (j = distance*-1; j <= distance; j++){
|
||||
|
||||
if(i===0 && j===0){
|
||||
// ignore source pixel
|
||||
} else {
|
||||
|
||||
offset = ((verticalPos+j)*width + (horizontalPos+i)) * 4;
|
||||
targetPix = getPixelInfo(data, offset, cacheSet);
|
||||
|
||||
if(targetPix === null){
|
||||
continue;
|
||||
}
|
||||
|
||||
addBrightnessInfo(targetPix);
|
||||
addHueInfo(targetPix);
|
||||
|
||||
if( isContrasting(sourcePix, targetPix) ){
|
||||
hasHighContrastSibling++;
|
||||
}
|
||||
|
||||
if( isRGBSame(sourcePix,targetPix) ){
|
||||
hasEquivilantSibling++;
|
||||
}
|
||||
|
||||
if( Math.abs(targetPix.h - sourcePix.h) > 0.3 ){
|
||||
hasSiblingWithDifferentHue++;
|
||||
}
|
||||
|
||||
if( hasSiblingWithDifferentHue > 1 || hasHighContrastSibling > 1){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(hasEquivilantSibling < 2){
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function errorPixel(px, offset){
|
||||
px[offset] = 255; //r
|
||||
px[offset + 1] = 0; //g
|
||||
px[offset + 2] = 255; //b
|
||||
px[offset + 3] = 255; //a
|
||||
}
|
||||
|
||||
function copyPixel(px, offset, data){
|
||||
px[offset] = data.r; //r
|
||||
px[offset + 1] = data.g; //g
|
||||
px[offset + 2] = data.b; //b
|
||||
px[offset + 3] = 255; //a
|
||||
}
|
||||
|
||||
function copyGrayScalePixel(px, offset, data){
|
||||
px[offset] = data.brightness; //r
|
||||
px[offset + 1] = data.brightness; //g
|
||||
px[offset + 2] = data.brightness; //b
|
||||
px[offset + 3] = 255; //a
|
||||
}
|
||||
|
||||
|
||||
function getPixelInfo(data, offset, cacheSet){
|
||||
var r;
|
||||
var g;
|
||||
var b;
|
||||
var d;
|
||||
|
||||
if(typeof data[offset] !== 'undefined'){
|
||||
r = data[offset];
|
||||
g = data[offset+1];
|
||||
b = data[offset+2];
|
||||
d = {
|
||||
r: r,
|
||||
g: g,
|
||||
b: b
|
||||
};
|
||||
|
||||
return d;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function addBrightnessInfo(data){
|
||||
data.brightness = getBrightness(data.r,data.g,data.b); // 'corrected' lightness
|
||||
}
|
||||
|
||||
function addHueInfo(data){
|
||||
data.h = getHue(data.r,data.g,data.b);
|
||||
}
|
||||
|
||||
function analyseImages(img1, img2, width, height){
|
||||
|
||||
var hiddenCanvas = document.createElement('canvas');
|
||||
|
||||
var data1 = img1.data;
|
||||
var data2 = img2.data;
|
||||
|
||||
hiddenCanvas.width = width;
|
||||
hiddenCanvas.height = height;
|
||||
|
||||
var context = hiddenCanvas.getContext('2d');
|
||||
var imgd = context.createImageData(width,height);
|
||||
var targetPix = imgd.data;
|
||||
|
||||
var mismatchCount = 0;
|
||||
|
||||
var time = Date.now();
|
||||
|
||||
var skip;
|
||||
|
||||
if( (width > 1200 || height > 1200) && ignoreAntialiasing){
|
||||
skip = 6;
|
||||
}
|
||||
|
||||
loop(height, width, function(verticalPos, horizontalPos){
|
||||
|
||||
if(skip){ // only skip if the image isn't small
|
||||
if(verticalPos % skip === 0 || horizontalPos % skip === 0){
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var offset = (verticalPos*width + horizontalPos) * 4;
|
||||
var pixel1 = getPixelInfo(data1, offset, 1);
|
||||
var pixel2 = getPixelInfo(data2, offset, 2);
|
||||
|
||||
if(pixel1 === null || pixel2 === null){
|
||||
return;
|
||||
}
|
||||
|
||||
if (ignoreColors){
|
||||
|
||||
addBrightnessInfo(pixel1);
|
||||
addBrightnessInfo(pixel2);
|
||||
|
||||
if( isPixelBrightnessSimilar(pixel1, pixel2) ){
|
||||
copyGrayScalePixel(targetPix, offset, pixel2);
|
||||
} else {
|
||||
errorPixel(targetPix, offset);
|
||||
mismatchCount++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if( isRGBSimilar(pixel1, pixel2) ){
|
||||
copyPixel(targetPix, offset, pixel2);
|
||||
|
||||
} else if( ignoreAntialiasing && (
|
||||
addBrightnessInfo(pixel1), // jit pixel info augmentation looks a little weird, sorry.
|
||||
addBrightnessInfo(pixel2),
|
||||
isAntialiased(pixel1, data1, 1, verticalPos, horizontalPos, width) ||
|
||||
isAntialiased(pixel2, data2, 2, verticalPos, horizontalPos, width)
|
||||
)){
|
||||
|
||||
if( isPixelBrightnessSimilar(pixel1, pixel2) ){
|
||||
copyGrayScalePixel(targetPix, offset, pixel2);
|
||||
} else {
|
||||
errorPixel(targetPix, offset);
|
||||
mismatchCount++;
|
||||
}
|
||||
} else {
|
||||
errorPixel(targetPix, offset);
|
||||
mismatchCount++;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
data.misMatchPercentage = (mismatchCount / (height*width) * 100).toFixed(2);
|
||||
data.analysisTime = Date.now() - time;
|
||||
|
||||
data.getImageDataUrl = function(text){
|
||||
var barHeight = 0;
|
||||
|
||||
if(text){
|
||||
barHeight = addLabel(text,context,hiddenCanvas);
|
||||
}
|
||||
|
||||
context.putImageData(imgd, 0, barHeight);
|
||||
|
||||
return hiddenCanvas.toDataURL("image/png");
|
||||
};
|
||||
}
|
||||
|
||||
function addLabel(text, context, hiddenCanvas){
|
||||
var textPadding = 2;
|
||||
|
||||
context.font = '12px sans-serif';
|
||||
|
||||
var textWidth = context.measureText(text).width + textPadding*2;
|
||||
var barHeight = 22;
|
||||
|
||||
if(textWidth > hiddenCanvas.width){
|
||||
hiddenCanvas.width = textWidth;
|
||||
}
|
||||
|
||||
hiddenCanvas.height += barHeight;
|
||||
|
||||
context.fillStyle = "#666";
|
||||
context.fillRect(0,0,hiddenCanvas.width,barHeight -4);
|
||||
context.fillStyle = "#fff";
|
||||
context.fillRect(0,barHeight -4,hiddenCanvas.width, 4);
|
||||
|
||||
context.fillStyle = "#fff";
|
||||
context.textBaseline = "top";
|
||||
context.font = '12px sans-serif';
|
||||
context.fillText(text, textPadding, 1);
|
||||
|
||||
return barHeight;
|
||||
}
|
||||
|
||||
function normalise(img, w, h){
|
||||
var c;
|
||||
var context;
|
||||
|
||||
if(img.height < h || img.width < w){
|
||||
c = document.createElement('canvas');
|
||||
c.width = w;
|
||||
c.height = h;
|
||||
context = c.getContext('2d');
|
||||
context.putImageData(img, 0, 0);
|
||||
return context.getImageData(0, 0, w, h);
|
||||
}
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
function compare(one, two){
|
||||
|
||||
function onceWeHaveBoth(){
|
||||
var width;
|
||||
var height;
|
||||
if(images.length === 2){
|
||||
width = images[0].width > images[1].width ? images[0].width : images[1].width;
|
||||
height = images[0].height > images[1].height ? images[0].height : images[1].height;
|
||||
|
||||
if( (images[0].width === images[1].width) && (images[0].height === images[1].height) ){
|
||||
data.isSameDimensions = true;
|
||||
} else {
|
||||
data.isSameDimensions = false;
|
||||
}
|
||||
|
||||
analyseImages( normalise(images[0],width, height), normalise(images[1],width, height), width, height);
|
||||
|
||||
triggerDataUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
images = [];
|
||||
loadImageData(one, onceWeHaveBoth);
|
||||
loadImageData(two, onceWeHaveBoth);
|
||||
}
|
||||
|
||||
function getCompareApi(param){
|
||||
|
||||
var secondFileData,
|
||||
hasMethod = typeof param === 'function';
|
||||
|
||||
if( !hasMethod ){
|
||||
// assume it's file data
|
||||
secondFileData = param;
|
||||
}
|
||||
|
||||
var self = {
|
||||
ignoreNothing: function(){
|
||||
|
||||
tolerance.red = 16;
|
||||
tolerance.green = 16;
|
||||
tolerance.blue = 16;
|
||||
tolerance.minBrightness = 16;
|
||||
tolerance.maxBrightness = 240;
|
||||
|
||||
ignoreAntialiasing = false;
|
||||
ignoreColors = false;
|
||||
|
||||
if(hasMethod) { param(); }
|
||||
return self;
|
||||
},
|
||||
ignoreAntialiasing: function(){
|
||||
|
||||
tolerance.red = 32;
|
||||
tolerance.green = 32;
|
||||
tolerance.blue = 32;
|
||||
tolerance.minBrightness = 64;
|
||||
tolerance.maxBrightness = 96;
|
||||
|
||||
ignoreAntialiasing = true;
|
||||
ignoreColors = false;
|
||||
|
||||
if(hasMethod) { param(); }
|
||||
return self;
|
||||
},
|
||||
ignoreColors: function(){
|
||||
|
||||
tolerance.minBrightness = 16;
|
||||
tolerance.maxBrightness = 240;
|
||||
|
||||
ignoreAntialiasing = false;
|
||||
ignoreColors = true;
|
||||
|
||||
if(hasMethod) { param(); }
|
||||
return self;
|
||||
},
|
||||
onComplete: function( callback ){
|
||||
|
||||
updateCallbackArray.push(callback);
|
||||
|
||||
var wrapper = function(){
|
||||
compare(fileData, secondFileData);
|
||||
};
|
||||
|
||||
wrapper();
|
||||
|
||||
return getCompareApi(wrapper);
|
||||
}
|
||||
};
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
return {
|
||||
onComplete: function( callback ){
|
||||
updateCallbackArray.push(callback);
|
||||
loadImageData(fileData, function(imageData, width, height){
|
||||
parseImage(imageData.data, width, height);
|
||||
});
|
||||
},
|
||||
compareTo: function(secondFileData){
|
||||
return getCompareApi(secondFileData);
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
}(this));
|
Loading…
Add table
Add a link
Reference in a new issue