From 54fe17a1f4266b8ca51540bfb9f11f899a1b1757 Mon Sep 17 00:00:00 2001 From: B2F Date: Sat, 18 Apr 2015 12:22:11 +0200 Subject: [PATCH] Fixes #34 (Diff swallows black color in added areas): adds options.ligthness, options.rgb and options.stack to customize the diff image. Updates Jasmine ImageDiffSpec with new diff options --- imagediff.js | 54 ++++++++++++++++++++++++++++++++++++------- js/imagediff.js | 54 ++++++++++++++++++++++++++++++++++++------- spec/ImageDiffSpec.js | 51 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 140 insertions(+), 19 deletions(-) diff --git a/imagediff.js b/imagediff.js index 279ade0..4149ceb 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 diffPixel = diffPixels(pixelA, pixelB, options); + for (var rgbIndex = 0; rgbIndex < 4; rgbIndex++) { + cData[i+rgbIndex] = diffPixel[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 diffPixel = diffPixels(pixelA, pixelB, options); + for (var rgbIndex = 0; rgbIndex < 4; rgbIndex++) { + cData[i+rgbIndex] = diffPixel[rgbIndex]; + } } } @@ -261,6 +265,40 @@ return c; } + /** + * Differentiates two rgb pixels by subtracting color values. + * The light value for each color marks the difference gap. + * + * @see https://github.com/HumbleSoftware/js-imagediff/issues/34 + * @param {Object} options + * options.lightness: light added to color value, increasing differences visibility + * options.rgb : array used to specify rgb balance instead of lightness + * options.stack: stack 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 lightness = options && options.lightness || 25; + if (options && options.lightness === 0) lightness = 0; + var diffColor = options && options.rgb || [lightness,lightness,lightness]; + var stack = options && options.stack; + // diffPixel = [r,g,b,a] + var diffPixel = [0,0,0,255]; + for (var rgbIndex = 0; rgbIndex < 3 ; rgbIndex++) { + diffPixel[rgbIndex] = Math.abs(pixelA[rgbIndex] - pixelB[rgbIndex]); + if (diffPixel[rgbIndex] > 0) { + // Optionally colors area of difference, adds visibility with lightness. + diffPixel[rgbIndex] += diffColor[rgbIndex]; + diffPixel[rgbIndex] = Math.min(diffPixel[rgbIndex], 255); + } + else if (stack) { + // If options.stack and no pixel differences are found, + // print original pixel instead of a black (0) pixel. + diffPixel[rgbIndex] = pixelA[rgbIndex]; + } + } + return diffPixel; + } // Validation function checkType () { diff --git a/js/imagediff.js b/js/imagediff.js index 275d6f8..7c35206 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 diffPixel = diffPixels(pixelA, pixelB, options); + for (var rgbIndex = 0; rgbIndex < 4; rgbIndex++) { + cData[i+rgbIndex] = diffPixel[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 diffPixel = diffPixels(pixelA, pixelB, options); + for (var rgbIndex = 0; rgbIndex < 4; rgbIndex++) { + cData[i+rgbIndex] = diffPixel[rgbIndex]; + } } } @@ -254,6 +258,40 @@ return c; } + /** + * Differentiates two rgb pixels by subtracting color values. + * The light value for each color marks the difference gap. + * + * @see https://github.com/HumbleSoftware/js-imagediff/issues/34 + * @param {Object} options + * options.lightness: light added to color value, increasing differences visibility + * options.rgb : array used to specify rgb balance instead of lightness + * options.stack: stack 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 lightness = options && options.lightness || 25; + if (options && options.lightness === 0) lightness = 0; + var diffColor = options && options.rgb || [lightness,lightness,lightness]; + var stack = options && options.stack; + // diffPixel = [r,g,b,a] + var diffPixel = [0,0,0,255]; + for (var rgbIndex = 0; rgbIndex < 3 ; rgbIndex++) { + diffPixel[rgbIndex] = Math.abs(pixelA[rgbIndex] - pixelB[rgbIndex]); + if (diffPixel[rgbIndex] > 0) { + // Optionally colors area of difference, adds visibility with lightness. + diffPixel[rgbIndex] += diffColor[rgbIndex]; + diffPixel[rgbIndex] = Math.min(diffPixel[rgbIndex], 255); + } + else if (stack) { + // If options.stack and no pixel differences are found, + // print original pixel instead of a black (0) pixel. + diffPixel[rgbIndex] = pixelA[rgbIndex]; + } + } + return diffPixel; + } // Validation function checkType () { diff --git a/spec/ImageDiffSpec.js b/spec/ImageDiffSpec.js index 67299b4..56f9b2d 100644 --- a/spec/ImageDiffSpec.js +++ b/spec/ImageDiffSpec.js @@ -312,6 +312,20 @@ 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, {lightness: 0}), + + d = imagediff.createImageData(1, 1); + d.data[1] = 42; + d.data[3] = 255; + + expect(c).toImageDiffEqual(d); + }); + + it('should calculate difference with adjusted lightness', function () { a = imagediff.createImageData(1, 1), a.data[1] = 200; b = imagediff.createImageData(1, 1), @@ -319,7 +333,8 @@ describe('ImageUtils', function() { c = imagediff.diff(a, b); d = imagediff.createImageData(1, 1); - d.data[1] = 42; + // 42 + 25 (default increased lightness) + d.data[1] = 67; d.data[3] = 255; expect(c).toImageDiffEqual(d); @@ -329,7 +344,7 @@ describe('ImageUtils', function() { a = imagediff.createImageData(3, 3), b = imagediff.createImageData(1, 1), b.data[1] = 21; - c = imagediff.diff(a, b); + c = imagediff.diff(a, b, {lightness: 0}); d = imagediff.createImageData(3, 3); // 4 * (rowPos * imageWidth + columnPos) + rgbPos @@ -348,7 +363,7 @@ describe('ImageUtils', function() { a = imagediff.createImageData(3, 3), b = imagediff.createImageData(1, 1), b.data[1] = 21; - c = imagediff.diff(a, b, {align: 'top'}); + c = imagediff.diff(a, b, {lightness: 0, align: 'top'}); d = imagediff.createImageData(3, 3); d.data[1] = 21; @@ -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, {rgb: [255,0,255]}); + + d = imagediff.createImageData(1, 1); + d.data[0] = 255; + d.data[1] = 125; + d.data[2] = 255; + 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); + }); }); /*