diff --git a/test/imagediff.js b/test/imagediff.js new file mode 100644 index 0000000..835169d --- /dev/null +++ b/test/imagediff.js @@ -0,0 +1,409 @@ +// js-imagediff 1.0.3 +// (c) 2011-2012 Carl Sutherland, Humble Software +// Distributed under the MIT License +// For original source and documentation visit: +// http://www.github.com/HumbleSoftware/js-imagediff + +(function (name, definition) { + var root = this; + if (typeof module !== 'undefined') { + try { + var Canvas = require('canvas'); + } catch (e) { + throw new Error( + e.message + '\n' + + 'Please see https://github.com/HumbleSoftware/js-imagediff#cannot-find-module-canvas\n' + ); + } + module.exports = definition(root, name, Canvas); + } else if (typeof define === 'function' && typeof define.amd === 'object') { + define(definition); + } else { + root[name] = definition(root, name); + } +})('imagediff', function (root, name, Canvas) { + + var + TYPE_ARRAY = /\[object Array\]/i, + TYPE_CANVAS = /\[object (Canvas|HTMLCanvasElement)\]/i, + TYPE_CONTEXT = /\[object CanvasRenderingContext2D\]/i, + TYPE_IMAGE = /\[object (Image|HTMLImageElement)\]/i, + TYPE_IMAGE_DATA = /\[object ImageData\]/i, + + UNDEFINED = 'undefined', + + canvas = getCanvas(), + context = canvas.getContext('2d'), + previous = root[name], + imagediff, jasmine; + + // Creation + function getCanvas (width, height) { + var + canvas = Canvas ? + new Canvas() : + document.createElement('canvas'); + if (width) canvas.width = width; + if (height) canvas.height = height; + return canvas; + } + function getImageData (width, height) { + canvas.width = width; + canvas.height = height; + context.clearRect(0, 0, width, height); + return context.createImageData(width, height); + } + // expost canvas module + function getCanvasRef() { + return Canvas; + } + + + // Type Checking + function isImage (object) { + return isType(object, TYPE_IMAGE); + } + function isCanvas (object) { + return isType(object, TYPE_CANVAS); + } + function isContext (object) { + return isType(object, TYPE_CONTEXT); + } + function isImageData (object) { + return !!(object && + isType(object, TYPE_IMAGE_DATA) && + typeof(object.width) !== UNDEFINED && + typeof(object.height) !== UNDEFINED && + typeof(object.data) !== UNDEFINED); + } + function isImageType (object) { + return ( + isImage(object) || + isCanvas(object) || + isContext(object) || + isImageData(object) + ); + } + function isType (object, type) { + return typeof (object) === 'object' && !!Object.prototype.toString.apply(object).match(type); + } + + + // Type Conversion + function copyImageData (imageData) { + var + height = imageData.height, + width = imageData.width, + data = imageData.data, + newImageData, newData, i; + + canvas.width = width; + canvas.height = height; + newImageData = context.getImageData(0, 0, width, height); + newData = newImageData.data; + + for (i = imageData.data.length; i--;) { + newData[i] = data[i]; + } + + return newImageData; + } + function toImageData (object) { + if (isImage(object)) { return toImageDataFromImage(object); } + if (isCanvas(object)) { return toImageDataFromCanvas(object); } + if (isContext(object)) { return toImageDataFromContext(object); } + if (isImageData(object)) { return object; } + } + function toImageDataFromImage (image) { + var + height = image.height, + width = image.width; + canvas.width = width; + canvas.height = height; + context.clearRect(0, 0, width, height); + context.drawImage(image, 0, 0); + return context.getImageData(0, 0, width, height); + } + function toImageDataFromCanvas (canvas) { + var + height = canvas.height, + width = canvas.width, + context = canvas.getContext('2d'); + return context.getImageData(0, 0, width, height); + } + function toImageDataFromContext (context) { + var + canvas = context.canvas, + height = canvas.height, + width = canvas.width; + return context.getImageData(0, 0, width, height); + } + function toCanvas (object) { + var + data = toImageData(object), + canvas = getCanvas(data.width, data.height), + context = canvas.getContext('2d'); + + context.putImageData(data, 0, 0); + return canvas; + } + + + // ImageData Equality Operators + function equalWidth (a, b) { + return a.width === b.width; + } + function equalHeight (a, b) { + return a.height === b.height; + } + function equalDimensions (a, b) { + return equalHeight(a, b) && equalWidth(a, b); + } + function equal (a, b, tolerance) { + + var + aData = a.data, + bData = b.data, + length = aData.length, + i; + + tolerance = tolerance || 0; + + if (!equalDimensions(a, b)) return false; + for (i = length; i--;) { + if (aData[i] !== bData[i] && Math.abs(aData[i] - bData[i]) > tolerance) { + var x = i % (a.width * 4); + var y = (i - x) / (a.width * 4); + var color = x % 4; + x = (x - color) / 4; + console.log("Difference x", x, "y", y, ["R", "G", "B", "A"][color], " - ", aData[i], " !== ", bData[i]); + return false; + } + } + + return true; + } + + + // Diff + function diff (a, b, options) { + return (equalDimensions(a, b) ? diffEqual : diffUnequal)(a, b, options); + } + function diffEqual (a, b, options) { + + var + height = a.height, + width = a.width, + c = getImageData(width, height), // c = a - b + aData = a.data, + bData = b.data, + cData = c.data, + length = cData.length, + row, column, + 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])); + } + + return c; + } + function diffUnequal (a, b, options) { + + var + height = Math.max(a.height, b.height), + width = Math.max(a.width, b.width), + c = getImageData(width, height), // c = a - b + aData = a.data, + bData = b.data, + cData = c.data, + align = options && options.align, + rowOffset, + columnOffset, + row, column, + i, j, k, v; + + + for (i = cData.length - 1; i > 0; i = i - 4) { + cData[i] = 255; + } + + // Add First Image + offsets(a); + for (row = a.height; row--;){ + for (column = a.width; column--;) { + i = 4 * ((row + rowOffset) * width + (column + columnOffset)); + j = 4 * (row * a.width + column); + 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 + } + } + + // Subtract Second Image + offsets(b); + for (row = b.height; row--;){ + 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 + } + } + + // Helpers + function offsets (imageData) { + if (align === 'top') { + rowOffset = 0; + columnOffset = 0; + } else { + rowOffset = Math.floor((height - imageData.height) / 2); + columnOffset = Math.floor((width - imageData.width) / 2); + } + } + + return c; + } + + + // Validation + function checkType () { + var i; + for (i = 0; i < arguments.length; i++) { + if (!isImageType(arguments[i])) { + throw { + name : 'ImageTypeError', + message : 'Submitted object was not an image.' + }; + } + } + } + + + // Jasmine Matchers + function get (element, content) { + element = document.createElement(element); + if (element && content) { + element.innerHTML = content; + } + return element; + } + + jasmine = { + + toBeImageData : function () { + return imagediff.isImageData(this.actual); + }, + + toImageDiffEqual : function (expected, tolerance) { + + if (typeof (document) !== UNDEFINED) { + this.message = function () { + var + div = get('div'), + a = get('div', '
filter changing per scanline, grayscale, 4 bit
no filtering, colour, 8 bit
no filtering, grayscale, 8 bit
filter 3, colour, 8 bit
filter 3, grayscale, 8 bit
filter 2, colour, 8 bit
filter 2, grayscale, 8 bit
filter 1, colour, 8 bit
filter 1, grayscale, 8 bit
filter 0, colour, 8 bit
filter 0, grayscale, 8 bit
filter changing per scanline, grayscale, 4 bit
no filtering, colour, 8 bit
no filtering, grayscale, 8 bit
filter 3, colour, 8 bit
filter 3, grayscale, 8 bit
filter 2, colour, 8 bit
filter 2, grayscale, 8 bit
filter 1, colour, 8 bit
filter 1, grayscale, 8 bit
filter 0, colour, 8 bit
filter 0, grayscale, 8 bit
basn0g01 - black & white
basn0g02 - 2 bit (4 level) grayscale
basn0g04 - 4 bit (16 level) grayscale
basn0g08 - 8 bit (256 level) grayscale
basn0g16 - 16 bit (64k level) grayscale
basn2c08 - 3x8 bits rgb color
basn2c16 - 3x16 bits rgb color
basn3p01 - 1 bit (2 color) paletted
basn3p02 - 2 bit (4 color) paletted
basn3p04 - 4 bit (16 color) paletted
basn3p08 - 8 bit (256 color) paletted
basn4a08 - 8 bit grayscale + 8 bit alpha-channel
basn4a16 - 16 bit grayscale + 16 bit alpha-channel
basn6a08 - 3x8 bits rgb color + 8 bit alpha-channel
basn6a16 - 3x16 bits rgb color + 16 bit alpha-channel
basi0g01 - black & white
basi0g02 - 2 bit (4 level) grayscale
basi0g04 - 4 bit (16 level) grayscale
basi0g08 - 8 bit (256 level) grayscale
basi0g16 - 16 bit (64k level) grayscale
basi2c08 - 3x8 bits rgb color
basi2c16 - 3x16 bits rgb color
basi3p01 - 1 bit (2 color) paletted
basi3p02 - 2 bit (4 color) paletted
basi3p04 - 4 bit (16 color) paletted
basi3p08 - 8 bit (256 color) paletted
basi4a08 - 8 bit grayscale + 8 bit alpha-channel
basi4a16 - 16 bit grayscale + 16 bit alpha-channel
basi6a08 - 3x8 bits rgb color + 8 bit alpha-channel
basi6a16 - 3x16 bits rgb color + 16 bit alpha-channel
bgyn6a16 - 3x16 bits rgb color, alpha, yellow background chunk
bgwn6a08 - 3x8 bits rgb color, alpha, white background chunk
bggn4a16 - 16 bit grayscale, alpha, gray background chunk
bgbn4a08 - 8 bit grayscale, alpha, black background chunk
bgan6a16 - 3x16 bits rgb color, alpha, no background chunk
bgan6a08 - 3x8 bits rgb color, alpha, no background chunk
bgai4a16 - 16 bit grayscale, alpha, no background chunk, interlaced
bgai4a08 - 8 bit grayscale, alpha, no background chunk, interlaced