|
|
|
@ -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); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|