Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion README.md
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,39 @@ API

* `createCanvas()` create a new Canvas element.
* `createImageData(width, height)` create a new ImageData object.
* `displayMask(width, height, regions)` create a image representation of regions mask.
* `isImage(object)` tests for Image object.
* `isCanvas(object)` tests for Canvas object.
* `isContext(object)` tests for CanvasRenderingContext2D object.
* `isImageData(object)` tests for ImageData object.
* `isImageType(object)` tests for any of the above.
* `toImageData(object)` converts image type object to a new ImageData object.
* `equal(a, b, tolerance)` tests image type objects for equality; accepts tolerance in pixels.
* `equal(a, b, tolerance, options)` tests image type objects for equality; accepts tolerance in pixels.
* `diff(a, b, options)` performs an image diff on a and b, returning a - b.
* `options.align` set to `'top'` to top-align the images when diffing different sizes.
* `noConflict()` removes imagediff from the global space for compatibility, returning imagediff.
* `imageDataToPNG(imageData, outputFile, [callback])` (node only) renders the imageData to png in outputFile with optional callback.

Regions
_______

Sometimes there's only part of the image that you want to test.
`equal` method accepts optional `regions` array that allows you to specify mask for image comparison.
Each region in the array is a five element array in the form `[x0, y0, x1, y1, include]`, where

* `(x0, y0)` is the smallest coordinate corner of the region (inclusive)
* `(x1, y1)` is the largest coordinate corner of the region (non-inclusive)
* `include` is a boolean value that decides whether the rectangle should be checked for equality.

Setting `include` to `true` will add the region to the mask, setting it to `false` will substract it.
Initially the mask is empty, therefore passing a single rectangle, e.g.:

`equal(a, b, 0, {regions: [[20, 20, 240, 120, true]]})`

will yield `true` if the images are equal within the single passed rectangle (`[20, 20, 240, 120]`).

You can use `displayMask` method to generate visual representation of created mask.

NodeJS
------

Expand Down Expand Up @@ -73,6 +94,9 @@ If you are using js-imagediff pelase drop us a line and let us know what you are

Changelog
---------
<h3>NEXT</h3>
* Accept regions for masking image comparison

<h3>1.0.8</h3>
* Update canvas dependency.
* Expose internal Canvas.
Expand Down
Binary file added examples/1_normal_c.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
92 changes: 69 additions & 23 deletions examples/index.html
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -4,61 +4,107 @@
<title>JS-ImageDiff Example</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="description" content="JavaScript imagediff utility using canvas for doing image diff in the browser." />
<script type="text/javascript" src="../imagediff.js"></script>
<script type="text/javascript" src="../js/imagediff.js"></script>
<link rel="stylesheet" href="styles.css" type="text/css" />
</head>
<body>
<div id="content">
<h1>JS-ImageDiff Example:</h1>
<p>See more examples at <a href="http://humblesoftware.github.com/js-imagediff/">humblesoftware.github.com/js-imagediff</a>.
Sample images from the <a href="https://github.com/cameronmcefee/Image-Diff-View-Modes/commit/8e95f70c9c47168305970e91021072673d7cdad8">github imagediff demo</a>.</p>
<div id="demo">
<div id="demo-1">
<img id="1-a" alt="example image one" src="1_normal_a.jpg" />
<img id="1-b" alt="example image two" src="1_normal_b.jpg" />
</div>
<div id="demo-2" style = "display: none;">
<img id="2-a" alt="example image one" src="1_normal_a.jpg" />
<img id="2-b" alt="example image two" src="1_normal_c.jpg" />
</div>
<div id="mask" style = "display: none;">
</div>
<script type="text/javascript">

//
// Example:
//

var
a = document.getElementById('1-a'), // Original image
b = document.getElementById('1-b'), // Altered image
demo = document.getElementById('demo'); // Container div

function difference () {
function difference (a, b, demo, regions) {

var
options = {},
diff, canvas, context;

if (!a.complete || !b.complete) {

// Images are loaded asynchronously.
// Let's wait until they are ready.
setTimeout(difference, 10);
return setTimeout(function() {
difference(a, b, demo, regions)
}, 10);

}

if(regions) {
options.regions = regions;
options.mask = true;
}

} else {
// Once they are ready, create a diff.
// This returns an ImageData object.
diff = imagediff.diff(a, b, options);

// Once they are ready, create a diff.
// This returns an ImageData object.
diff = imagediff.diff(a, b);
// Now create a canvas,
canvas = imagediff.createCanvas(diff.width, diff.height);

// Now create a canvas,
canvas = imagediff.createCanvas(diff.width, diff.height);
// get its context
context = canvas.getContext('2d');

// get its context
context = canvas.getContext('2d');
// and finally draw the ImageData diff.
context.putImageData(diff, 0, 0);

// and finally draw the ImageData diff.
context.putImageData(diff, 0, 0);
// Add the canvas element to the container.
demo.appendChild(canvas);

// Add the canvas element to the container.
demo.appendChild(canvas);
}
console.log("EQUAL?", imagediff.equal(a, b, 0, options));

}

difference();
function mask (div, w, h, regions) {
var canvas;
var image = imagediff.displayMask(w, h, regions);

canvas = imagediff.createCanvas(image.width, image.height);
context = canvas.getContext('2d');
context.putImageData(image, 0, 0);
div.appendChild(canvas);
}

difference(
document.getElementById('1-a'), // Original image
document.getElementById('1-b'), // Altered image
document.getElementById('demo-1') // Container div
);

difference(
document.getElementById('2-a'),
document.getElementById('2-b'),
document.getElementById('demo-2'),
[
[10, 10, 30, 30, true],
[270, 10, 290, 30, true],
[270, 270, 290, 290, true],
[10, 270, 30, 290, true],
]
);

mask(document.getElementById('mask'), 480, 320, [
[40, 40, 280, 280, true],
[320, 40, 440, 80, true],
[80, 80, 160, 160, false],
[160, 160, 200, 200, false],
]);


</script>
</div>
<div id="github-ribbon">
Expand Down
108 changes: 96 additions & 12 deletions js/imagediff.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,62 @@
return canvas;
}

// Image mask generation


function fillArray(array, value, start, end) {
var i,
i0 = (start !== undefined) ? start : 0,
i1 = (end !== undefined) ? end : array.length;
if(Array.prototype.fill) {
return array.fill(value, start, end);
}
for(i = i0; i < i1; i += 1) {
array[i] = value;
}
return array;
}
/*
region format: [x0, y0, x1, y1, include]
*/
function createMask (width, height, regions) {
var
array = fillArray(Array(width * height), false),
i, y, region;

for(i = 0; i < regions.length; i += 1) {
region = regions[i];
for(y = region[1]; y < region[3]; y += 1) {
fillArray(array, region[4], y * width + region[0], y * width + region[2]);
}
}
return array;
}
function displayMask(width, height, regions) {
var
mask = createMask(width, height, regions),
image = getImageData(width, height),
iData = image.data,
length = mask.length,
i, ii, color;

for (i = 0, ii = 0; i < length; i += 1, ii += 4) {
color = mask[i] ? 255 : 0;
iData[ii] = color;
iData[ii+1] = color;
iData[ii+2] = color;
iData[ii+3] = 255;

if(!(mask[i] === true || mask[i] === false)) {
iData[ii] = 255;
iData[ii+1] = 0;
iData[ii+2] = 0;
}
}

return image;
}


// ImageData Equality Operators
function equalWidth (a, b) {
Expand All @@ -152,18 +208,32 @@
function equalDimensions (a, b) {
return equalHeight(a, b) && equalWidth(a, b);
}
function equal (a, b, tolerance) {
function equal (a, b, tolerance, options) {

var
aData = a.data,
bData = b.data,
length = aData.length,
i;
length = aData.length / 4,
mask = null,
i, ii, j;

tolerance = tolerance || 0;
options = options || {};

if (!equalDimensions(a, b)) return false;
for (i = length; i--;) if (aData[i] !== bData[i] && Math.abs(aData[i] - bData[i]) > tolerance) return false;

if(options.regions) {
mask = createMask(a.width, a.height, options.regions);
}

for (i = 0; i < length; i += 1) {
if (mask && !mask[i]) continue;
for(j = 0, ii = i * 4; j < 4; j += 1, ii += 1)
if (aData[ii] !== bData[ii] &&
Math.abs(aData[ii] - bData[ii]) > tolerance) {
return false;
}
}

return true;
}
Expand All @@ -183,14 +253,26 @@
bData = b.data,
cData = c.data,
length = cData.length,
mask = null,
row, column,
i, j, k, v;
i, ii, j, k, v;

if(options.regions && options.mask) {
mask = createMask(width, height, options.regions);
}

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]));
for (i = 0, ii = 0; ii < length; i += 1, ii += 4) {
if(mask && !mask[i]) {
cData[ii] = 0;
cData[ii+1] = 0;
cData[ii+2] = 92;
cData[ii+3] = 255;
} else {
cData[ii] = Math.abs(aData[ii] - bData[ii]);
cData[ii+1] = Math.abs(aData[ii+1] - bData[ii+1]);
cData[ii+2] = Math.abs(aData[ii+2] - bData[ii+2]);
cData[ii+3] = Math.abs(255 - Math.abs(aData[ii+3] - bData[ii+3]));
}
}

return c;
Expand Down Expand Up @@ -350,6 +432,8 @@
createCanvas : getCanvas,
createImageData : getImageData,

displayMask : displayMask,

isImage : isImage,
isCanvas : isCanvas,
isContext : isContext,
Expand All @@ -362,11 +446,11 @@
return toImageData(object);
},

equal : function (a, b, tolerance) {
equal : function (a, b, tolerance, options) {
checkType(a, b);
a = toImageData(a);
b = toImageData(b);
return equal(a, b, tolerance);
return equal(a, b, tolerance, options);
},
diff : function (a, b, options) {
checkType(a, b);
Expand Down