diff --git a/demoassets/main.js b/demoassets/main.js
index 0d7a5fc..7463927 100644
--- a/demoassets/main.js
+++ b/demoassets/main.js
@@ -58,6 +58,8 @@ $(function(){
window.open(diffImage.src, '_blank');
});
+ $('#button').show();
+
if(data.misMatchPercentage == 0){
$('#thesame').show();
$('#diff-results').hide();
@@ -75,16 +77,39 @@ $(function(){
var file1;
var file2;
+ var resembleControl;
dropZone($('#dropzone1'), function(file){
file1 = file;
if(file2){
- resemble(file).compareTo(file2).onComplete(onComplete);
+ resembleControl = resemble(file).compareTo(file2).onComplete(onComplete);
}
});
dropZone($('#dropzone2'), function(file){
file2 = file;
if(file1){
- resemble(file).compareTo(file1).onComplete(onComplete);
+ resembleControl = resemble(file).compareTo(file1).onComplete(onComplete);
+ }
+ });
+
+
+ var buttons = $('#raw, #colors, #antialising');
+
+ buttons.click(function(){
+ var $this = $(this);
+
+ buttons.removeClass('active');
+ $this.addClass('active');
+
+ if($this.is('#raw')){
+ resembleControl.ignoreNothing();
+ }
+ else
+ if($this.is('#colors')){
+ resembleControl.ignoreColors();
+ }
+ else
+ if($this.is('#antialising')){
+ resembleControl.ignoreAntialiasing();
}
});
diff --git a/index.html b/index.html
index 6547c52..4c27892 100644
--- a/index.html
+++ b/index.html
@@ -81,6 +81,13 @@
Diff will appear here.
+
The second image is % different compared to the first. And they have different dimensions. diff --git a/resemble.js b/resemble.js index 5ee2cdc..8700d3d 100644 --- a/resemble.js +++ b/resemble.js @@ -6,13 +6,17 @@ var images = []; var updateCallbackArray = []; - var tolerance = [ // between 0 and 255 - 255, // red - 255, // green 16 - 255, // blue 24 - 255, // alpha - 255 // brightness - ]; + var tolerance = { // between 0 and 255 + red: 16, + green: 16, + blue: 16, + minBrightness: 16, + maxBrightness: 240 + }; + + var ignoreAntialiasing = false; + var ignoreColors = false; + var skip = false; function triggerDataUpdate(){ var len = updateCallbackArray.length; @@ -46,8 +50,7 @@ var red = sourceImageData[offset]; var green = sourceImageData[offset + 1]; var blue = sourceImageData[offset + 2]; - var alpha = sourceImageData[offset + 3]; - var brightness = (0.3*red + 0.59*green + 0.11*blue); + var brightness = getBrightness(red,green,blue); pixleCount++; @@ -93,9 +96,7 @@ fileReader.readAsDataURL(fileData); } - function isPixelDifferent(d1, d2, off, plus){ - var a = d1[off + plus]; - var b = d2[off + plus]; + function isColorSimilar(a, b, color){ var absDiff = Math.abs(a - b); @@ -108,34 +109,175 @@ if(a === b){ return true; - } else if ( absDiff < tolerance[plus] ) { + } else if ( absDiff < tolerance[color] ) { return true; } else { return false; } } - function compareBrightness(data1, data2, offset){ - var red1 = data1[offset + 0]; - var green1 = data1[offset + 1]; - var blue1 = data1[offset + 2]; + 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'); - var red2 = data2[offset + 0]; - var green2 = data2[offset + 1]; - var blue2 = data2[offset + 2]; + return red && green && blue; + } + + function isContrasting(d1, d2){ + return Math.abs(d1.brightness - d2.brightness) > tolerance.maxBrightness; + } + + function getHsl(data, offset){ + + var r = data[offset] / 255; + var g = data[offset+1] / 255; + var b = data[offset+2] / 255; + var max = Math.max(r, g, b), min = Math.min(r, g, b); + var h; + var s; + var l = (max + min) / 2; + var d; + + if (max == min){ + h = s = 0; // achromatic + } else{ + d = max - min; + s = l > 0.5 ? d / (2 - max - min) : 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; + } - var brightness1; - var brightness2; + return { // values between 0 and 1 + hue: h, + saturation: s, + lightness: l + }; + } - if(red1 && green1 && blue1 && red2 && green2 && blue2){ + 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; - brightness1 = (0.3*red1 + 0.59*green1 + 0.11*blue1); - brightness2 = (0.3*red2 + 0.59*green2 + 0.11*blue2); + for (i = distance*-1; i <= distance; i++){ + for (j = distance*-1; j <= distance; j++){ - return Math.abs(brightness1 - brightness2) < tolerance[4]; + if(i===0 && j===0){ + // ignore source pixel + } else { + offset = ((verticalPos+j)*width + (horizontalPos+i)) * 4; + targetPix = getRGBAndHSL(data, offset, cacheSet); + + if(targetPix === null){ + continue; + } + + 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 || hasEquivilantSibling === 0){ + 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 + } + + var cache = []; + function getRGBAndHSL(data, offset, cacheSet){ + var hsl; + var r; + var g; + var b; + var d; + + if(cache[cacheSet] && cache[cacheSet][offset]){ + return cache[cacheSet][offset]; } else { - return false; + if(typeof data[offset] !== 'undefined'){ + hsl = getHsl(data, offset); + r = data[offset]; + g = data[offset+1]; + b = data[offset+2]; + d = { + r: r, + g: g, + b: b, + h: hsl.hue, + s: hsl.saturation, + l: hsl.lightness, + brightness: getBrightness(r,g,b) // 'corrected' lightness + }; + if(!cache[cacheSet]){ + cache[cacheSet] = []; + } + cache[cacheSet][offset] = d; + return d; + } else { + return null; + } } } @@ -151,39 +293,67 @@ var context = hiddenCanvas.getContext('2d'); var imgd = context.createImageData(width,height); - var pix = imgd.data; + var targetPix = imgd.data; var mismatchCount = 0; + var isAntialiasedCount = 0; + var isBrightnessCount = 0; loop(height, width, function(verticalPos, horizontalPos){ var offset = (verticalPos*width + horizontalPos) * 4; - var red = isPixelDifferent(data1, data2, offset, 0); - var green = isPixelDifferent(data1, data2, offset, 1); - var blue = isPixelDifferent(data1, data2, offset, 2); - var alpha = isPixelDifferent(data1, data2, offset, 3); + var pixel1 = getRGBAndHSL(data1, offset, 1); - var brightness = compareBrightness(data1, data2, offset); + if(skip){ + if(verticalPos % skip === 0 || horizontalPos % skip === 0){ + copyGrayScalePixel(targetPix, offset, pixel1); + return; + } + } - if(brightness && red && green && blue){ + var pixel2 = getRGBAndHSL(data2, offset, 2); - pix[offset] = data1[offset + 0]; - pix[offset + 1] = data1[offset + 1]; - pix[offset + 2] = data1[offset + 2]; - pix[offset + 3] = data1[offset + 3]; + if(pixel1 === null || pixel2 === null){ + return; + } - } else { - pix[offset] = 255; - pix[offset + 1] = 0; - pix[offset + 2] = 255; - pix[offset + 3] = 255; + if (ignoreColors){ + 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 && + (isAntialiased(pixel1, data1, 1, verticalPos, horizontalPos, width) || + isAntialiased(pixel2, data2, 2, verticalPos, horizontalPos, width)) ){ + + isAntialiasedCount++; + if( isPixelBrightnessSimilar(pixel1, pixel2) ){ + isBrightnessCount++; + copyGrayScalePixel(targetPix, offset, pixel2); + } else { + errorPixel(targetPix, offset); + mismatchCount++; + } + } else { + errorPixel(targetPix, offset); mismatchCount++; } + }); context.putImageData(imgd, 0,0); + cache = []; + data.misMatchPercentage = (mismatchCount / (height*width) * 100).toFixed(2); data.imageDiffFileData = hiddenCanvas.toDataURL("image/png"); } @@ -209,10 +379,82 @@ } } + images = []; loadImageData(one, onceWeHaveBoth); loadImageData(two, onceWeHaveBoth); } + function getCompareApi(param){ + + var 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; + + skip = false; + + if(hasMethod) { param(); } + return self; + }, + ignoreAntialiasing: function(){ + + tolerance.red = 24; + tolerance.green = 24; + tolerance.blue = 24; + tolerance.minBrightness = 64; + tolerance.maxBrightness = 96; + + ignoreAntialiasing = true; + ignoreColors = false; + skip = 4; + + if(hasMethod) { param(); } + return self; + }, + ignoreColors: function(){ + + tolerance.minBrightness = 16; + tolerance.maxBrightness = 240; + + ignoreAntialiasing = false; + ignoreColors = true; + + skip = false; + + 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); @@ -221,12 +463,7 @@ }); }, compareTo: function(secondFileData){ - return { - onComplete: function( callback ){ - updateCallbackArray.push(callback); - compare(fileData, secondFileData); - } - }; + return getCompareApi(secondFileData); } };