diff --git a/imagediff.js b/imagediff.js index 279ade0..abd3088 100644 --- a/imagediff.js +++ b/imagediff.js @@ -194,10 +194,12 @@ i, j, k, v; for (i = 0; i < length; i += 4) { - cData[i] = Math.abs(aData[i] - bData[i]); - cData[i+1] = Math.abs(aData[i+1] - bData[i+1]); - cData[i+2] = Math.abs(aData[i+2] - bData[i+2]); - cData[i+3] = Math.abs(255 - Math.abs(aData[i+3] - bData[i+3])); + var pixelA = Array.prototype.slice.call(aData, i, i+3); + var pixelB = Array.prototype.slice.call(bData, i, i+3); + var pixelC = diffPixels(pixelA, pixelB, options); + for (var rgbIndex = 0; rgbIndex < 4; rgbIndex++) { + cData[i+rgbIndex] = pixelC[rgbIndex]; + } } return c; @@ -231,7 +233,6 @@ cData[i+0] = aData[j+0]; // r cData[i+1] = aData[j+1]; // g cData[i+2] = aData[j+2]; // b - // cData[i+3] = aData[j+3]; // a } } @@ -241,9 +242,12 @@ for (column = b.width; column--;) { i = 4 * ((row + rowOffset) * width + (column + columnOffset)); j = 4 * (row * b.width + column); - cData[i+0] = Math.abs(cData[i+0] - bData[j+0]); // r - cData[i+1] = Math.abs(cData[i+1] - bData[j+1]); // g - cData[i+2] = Math.abs(cData[i+2] - bData[j+2]); // b + var pixelA = Array.prototype.slice.call(cData, i, i+3); + var pixelB = Array.prototype.slice.call(bData, j, j+3); + var pixelC = diffPixels(pixelA, pixelB, options); + for (var rgbIndex = 0; rgbIndex < 4; rgbIndex++) { + cData[i+rgbIndex] = pixelC[rgbIndex]; + } } } @@ -261,6 +265,35 @@ return c; } + /** + * Differentiates two rgb pixels by subtracting color values. + * @see https://github.com/HumbleSoftware/js-imagediff/pull/52 + * + * @param {Object} options + * options.lightboost: increases differences visibility with a light boost. + * options.diffColor: a rgb array used to specify differences color instead of light gap. + * options.stack: stacks differences on top of the original image, preserving common pixels. + * + * @returns {Array} pixel rgba values between 0 and 255. + */ + function diffPixels(pixelA, pixelB, options) { + var lightboost = options && options.lightboost || 0; + var diffColor = options && options.diffColor || false; + var stack = options && options.stack || false; + // pixel = [r,g,b,a] + var pixelC = [0,0,0,255]; + for (var rgbIndex = 0; rgbIndex < 3 ; rgbIndex++) { + pixelC[rgbIndex] = Math.abs(pixelA[rgbIndex] - pixelB[rgbIndex]); + if (pixelC[rgbIndex] > 0) { + if (diffColor) pixelC[rgbIndex] = diffColor[rgbIndex]; + pixelC[rgbIndex] = Math.min(pixelC[rgbIndex] + lightboost, 255); + } + else if (stack) { + pixelC[rgbIndex] = pixelA[rgbIndex]; + } + } + return pixelC; + } // Validation function checkType () { diff --git a/js/imagediff.js b/js/imagediff.js index 275d6f8..9b5dbcd 100644 --- a/js/imagediff.js +++ b/js/imagediff.js @@ -187,10 +187,12 @@ i, j, k, v; for (i = 0; i < length; i += 4) { - cData[i] = Math.abs(aData[i] - bData[i]); - cData[i+1] = Math.abs(aData[i+1] - bData[i+1]); - cData[i+2] = Math.abs(aData[i+2] - bData[i+2]); - cData[i+3] = Math.abs(255 - Math.abs(aData[i+3] - bData[i+3])); + var pixelA = Array.prototype.slice.call(aData, i, i+3); + var pixelB = Array.prototype.slice.call(bData, i, i+3); + var pixelC = diffPixels(pixelA, pixelB, options); + for (var rgbIndex = 0; rgbIndex < 4; rgbIndex++) { + cData[i+rgbIndex] = pixelC[rgbIndex]; + } } return c; @@ -224,7 +226,6 @@ cData[i+0] = aData[j+0]; // r cData[i+1] = aData[j+1]; // g cData[i+2] = aData[j+2]; // b - // cData[i+3] = aData[j+3]; // a } } @@ -234,9 +235,12 @@ for (column = b.width; column--;) { i = 4 * ((row + rowOffset) * width + (column + columnOffset)); j = 4 * (row * b.width + column); - cData[i+0] = Math.abs(cData[i+0] - bData[j+0]); // r - cData[i+1] = Math.abs(cData[i+1] - bData[j+1]); // g - cData[i+2] = Math.abs(cData[i+2] - bData[j+2]); // b + var pixelA = Array.prototype.slice.call(cData, i, i+3); + var pixelB = Array.prototype.slice.call(bData, j, j+3); + var pixelC = diffPixels(pixelA, pixelB, options); + for (var rgbIndex = 0; rgbIndex < 4; rgbIndex++) { + cData[i+rgbIndex] = pixelC[rgbIndex]; + } } } @@ -254,6 +258,35 @@ return c; } + /** + * Differentiates two rgb pixels by subtracting color values. + * @see https://github.com/HumbleSoftware/js-imagediff/pull/52 + * + * @param {Object} options + * options.lightboost: increases differences visibility with a light boost. + * options.diffColor: a rgb array used to specify differences color instead of light gap. + * options.stack: stacks differences on top of the original image, preserving common pixels. + * + * @returns {Array} pixel rgba values between 0 and 255. + */ + function diffPixels(pixelA, pixelB, options) { + var lightboost = options && options.lightboost || 0; + var diffColor = options && options.diffColor || false; + var stack = options && options.stack || false; + // pixel = [r,g,b,a] + var pixelC = [0,0,0,255]; + for (var rgbIndex = 0; rgbIndex < 3 ; rgbIndex++) { + pixelC[rgbIndex] = Math.abs(pixelA[rgbIndex] - pixelB[rgbIndex]); + if (pixelC[rgbIndex] > 0) { + if (diffColor) pixelC[rgbIndex] = diffColor[rgbIndex]; + pixelC[rgbIndex] = Math.min(pixelC[rgbIndex] + lightboost, 255); + } + else if (stack) { + pixelC[rgbIndex] = pixelA[rgbIndex]; + } + } + return pixelC; + } // Validation function checkType () { diff --git a/spec/ImageDiffSpec.js b/spec/ImageDiffSpec.js index 67299b4..ad8b88a 100644 --- a/spec/ImageDiffSpec.js +++ b/spec/ImageDiffSpec.js @@ -312,14 +312,29 @@ describe('ImageUtils', function() { }); it('should calculate difference', function () { + a = imagediff.createImageData(1, 1), + a.data[1] = 200, + b = imagediff.createImageData(1, 1), + b.data[1] = 158, + c = imagediff.diff(a, b), + + d = imagediff.createImageData(1, 1); + d.data[1] = 42; + d.data[3] = 255; + + expect(c).toImageDiffEqual(d); + }); + + it('should calculate color difference with adjusted lightness', function () { a = imagediff.createImageData(1, 1), a.data[1] = 200; b = imagediff.createImageData(1, 1), b.data[1] = 158; - c = imagediff.diff(a, b); + c = imagediff.diff(a, b, {lightboost: 155}); d = imagediff.createImageData(1, 1); - d.data[1] = 42; + // a-b + 155 + d.data[1] = 197; d.data[3] = 255; expect(c).toImageDiffEqual(d); @@ -361,6 +376,36 @@ describe('ImageUtils', function() { expect(c).toImageDiffEqual(d); }); + + it('should optionally color differences', function () { + a = imagediff.createImageData(1, 1), + b = imagediff.createImageData(1, 1); + // Fills a grey pixel and expects c to be tinted in light pink + Array.prototype.forEach.call(b.data, function (value, i) { + b.data[i] = 125; + }); + c = imagediff.diff(a, b, {diffColor: [255,0,44], lightboost: 191}); + + d = imagediff.createImageData(1, 1); + d.data[0] = 255; + d.data[1] = 191; + d.data[2] = 235; + d.data[3] = 255; + + expect(c).toImageDiffEqual(d); + }); + + it('should optionally show common pixels', function () { + a = imagediff.createImageData(1, 1); + // Fills white pixels + Array.prototype.forEach.call(a.data, function (value, i) { + a.data[i] = 255; + }); + b = a; + c = imagediff.diff(a, b, {stack: true}); + + expect(c).toImageDiffEqual(a); + }); }); /*