imagediff.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. // js-imagediff 1.0.3
  2. // (c) 2011-2012 Carl Sutherland, Humble Software
  3. // Distributed under the MIT License
  4. // For original source and documentation visit:
  5. // http://www.github.com/HumbleSoftware/js-imagediff
  6. (function (name, definition) {
  7. var root = this;
  8. if (typeof module !== 'undefined') {
  9. try {
  10. var Canvas = require('canvas');
  11. } catch (e) {
  12. throw new Error(
  13. e.message + '\n' +
  14. 'Please see https://github.com/HumbleSoftware/js-imagediff#cannot-find-module-canvas\n'
  15. );
  16. }
  17. module.exports = definition(root, name, Canvas);
  18. } else if (typeof define === 'function' && typeof define.amd === 'object') {
  19. define(definition);
  20. } else {
  21. root[name] = definition(root, name);
  22. }
  23. })('imagediff', function (root, name, Canvas) {
  24. var
  25. TYPE_ARRAY = /\[object Array\]/i,
  26. TYPE_CANVAS = /\[object (Canvas|HTMLCanvasElement)\]/i,
  27. TYPE_CONTEXT = /\[object CanvasRenderingContext2D\]/i,
  28. TYPE_IMAGE = /\[object (Image|HTMLImageElement)\]/i,
  29. TYPE_IMAGE_DATA = /\[object ImageData\]/i,
  30. UNDEFINED = 'undefined',
  31. canvas = getCanvas(),
  32. context = canvas.getContext('2d'),
  33. previous = root[name],
  34. imagediff, jasmine;
  35. // Creation
  36. function getCanvas(width, height) {
  37. var
  38. canvas = Canvas ?
  39. new Canvas() :
  40. document.createElement('canvas');
  41. if (width) canvas.width = width;
  42. if (height) canvas.height = height;
  43. return canvas;
  44. }
  45. function getImageData(width, height) {
  46. canvas.width = width;
  47. canvas.height = height;
  48. context.clearRect(0, 0, width, height);
  49. return context.createImageData(width, height);
  50. }
  51. // Type Checking
  52. function isImage(object) {
  53. return isType(object, TYPE_IMAGE);
  54. }
  55. function isCanvas(object) {
  56. return isType(object, TYPE_CANVAS);
  57. }
  58. function isContext(object) {
  59. return isType(object, TYPE_CONTEXT);
  60. }
  61. function isImageData(object) {
  62. return !!(object &&
  63. isType(object, TYPE_IMAGE_DATA) &&
  64. typeof (object.width) !== UNDEFINED &&
  65. typeof (object.height) !== UNDEFINED &&
  66. typeof (object.data) !== UNDEFINED);
  67. }
  68. function isImageType(object) {
  69. return (
  70. isImage(object) ||
  71. isCanvas(object) ||
  72. isContext(object) ||
  73. isImageData(object)
  74. );
  75. }
  76. function isType(object, type) {
  77. return typeof (object) === 'object' && !! Object.prototype.toString.apply(object).match(type);
  78. }
  79. // Type Conversion
  80. function copyImageData(imageData) {
  81. var
  82. height = imageData.height,
  83. width = imageData.width,
  84. data = imageData.data,
  85. newImageData, newData, i;
  86. canvas.width = width;
  87. canvas.height = height;
  88. newImageData = context.getImageData(0, 0, width, height);
  89. newData = newImageData.data;
  90. for (i = imageData.data.length; i--;) {
  91. newData[i] = data[i];
  92. }
  93. return newImageData;
  94. }
  95. function toImageData(object) {
  96. if (isImage(object)) {
  97. return toImageDataFromImage(object);
  98. }
  99. if (isCanvas(object)) {
  100. return toImageDataFromCanvas(object);
  101. }
  102. if (isContext(object)) {
  103. return toImageDataFromContext(object);
  104. }
  105. if (isImageData(object)) {
  106. return object;
  107. }
  108. }
  109. function toImageDataFromImage(image) {
  110. var
  111. height = image.height,
  112. width = image.width;
  113. canvas.width = width;
  114. canvas.height = height;
  115. context.clearRect(0, 0, width, height);
  116. context.drawImage(image, 0, 0);
  117. return context.getImageData(0, 0, width, height);
  118. }
  119. function toImageDataFromCanvas(canvas) {
  120. var
  121. height = canvas.height,
  122. width = canvas.width,
  123. context = canvas.getContext('2d');
  124. return context.getImageData(0, 0, width, height);
  125. }
  126. function toImageDataFromContext(context) {
  127. var
  128. canvas = context.canvas,
  129. height = canvas.height,
  130. width = canvas.width;
  131. return context.getImageData(0, 0, width, height);
  132. }
  133. function toCanvas(object) {
  134. var
  135. data = toImageData(object),
  136. canvas = getCanvas(data.width, data.height),
  137. context = canvas.getContext('2d');
  138. context.putImageData(data, 0, 0);
  139. return canvas;
  140. }
  141. // ImageData Equality Operators
  142. function equalWidth(a, b) {
  143. return a.width === b.width;
  144. }
  145. function equalHeight(a, b) {
  146. return a.height === b.height;
  147. }
  148. function equalDimensions(a, b) {
  149. return equalHeight(a, b) && equalWidth(a, b);
  150. }
  151. function equal(a, b, tolerance) {
  152. var
  153. aData = a.data,
  154. bData = b.data,
  155. length = aData.length,
  156. i;
  157. tolerance = tolerance || 0;
  158. if (!equalDimensions(a, b)) return false;
  159. e = true;
  160. for (i = length; i--;)
  161. if (aData[i] !== bData[i] && Math.abs(aData[i] - bData[i]) > tolerance) {
  162. console.log(i + "|" + aData[i] + "|" + bData[i]);
  163. e = false;
  164. }
  165. return e;
  166. }
  167. // Diff
  168. function diff(a, b, options) {
  169. return (equalDimensions(a, b) ? diffEqual : diffUnequal)(a, b, options);
  170. }
  171. function diffEqual(a, b, options) {
  172. var
  173. height = a.height,
  174. width = a.width,
  175. c = getImageData(width, height), // c = a - b
  176. aData = a.data,
  177. bData = b.data,
  178. cData = c.data,
  179. length = cData.length,
  180. row, column,
  181. i, j, k, v;
  182. for (i = 0; i < length; i += 4) {
  183. cData[i] = Math.abs(aData[i] - bData[i]);
  184. cData[i + 1] = Math.abs(aData[i + 1] - bData[i + 1]);
  185. cData[i + 2] = Math.abs(aData[i + 2] - bData[i + 2]);
  186. cData[i + 3] = Math.abs(255 - Math.abs(aData[i + 3] - bData[i + 3]));
  187. }
  188. return c;
  189. }
  190. function diffUnequal(a, b, options) {
  191. var
  192. height = Math.max(a.height, b.height),
  193. width = Math.max(a.width, b.width),
  194. c = getImageData(width, height), // c = a - b
  195. aData = a.data,
  196. bData = b.data,
  197. cData = c.data,
  198. align = options && options.align,
  199. rowOffset,
  200. columnOffset,
  201. row, column,
  202. i, j, k, v;
  203. for (i = cData.length - 1; i > 0; i = i - 4) {
  204. cData[i] = 255;
  205. }
  206. // Add First Image
  207. offsets(a);
  208. for (row = a.height; row--;) {
  209. for (column = a.width; column--;) {
  210. i = 4 * ((row + rowOffset) * width + (column + columnOffset));
  211. j = 4 * (row * a.width + column);
  212. cData[i + 0] = aData[j + 0]; // r
  213. cData[i + 1] = aData[j + 1]; // g
  214. cData[i + 2] = aData[j + 2]; // b
  215. // cData[i+3] = aData[j+3]; // a
  216. }
  217. }
  218. // Subtract Second Image
  219. offsets(b);
  220. for (row = b.height; row--;) {
  221. for (column = b.width; column--;) {
  222. i = 4 * ((row + rowOffset) * width + (column + columnOffset));
  223. j = 4 * (row * b.width + column);
  224. cData[i + 0] = Math.abs(cData[i + 0] - bData[j + 0]); // r
  225. cData[i + 1] = Math.abs(cData[i + 1] - bData[j + 1]); // g
  226. cData[i + 2] = Math.abs(cData[i + 2] - bData[j + 2]); // b
  227. }
  228. }
  229. // Helpers
  230. function offsets(imageData) {
  231. if (align === 'top') {
  232. rowOffset = 0;
  233. columnOffset = 0;
  234. } else {
  235. rowOffset = Math.floor((height - imageData.height) / 2);
  236. columnOffset = Math.floor((width - imageData.width) / 2);
  237. }
  238. }
  239. return c;
  240. }
  241. // Validation
  242. function checkType() {
  243. var i;
  244. for (i = 0; i < arguments.length; i++) {
  245. if (!isImageType(arguments[i])) {
  246. throw {
  247. name: 'ImageTypeError',
  248. message: 'Submitted object was not an image.'
  249. };
  250. }
  251. }
  252. }
  253. // Jasmine Matchers
  254. function get(element, content) {
  255. element = document.createElement(element);
  256. if (element && content) {
  257. element.innerHTML = content;
  258. }
  259. return element;
  260. }
  261. jasmine = {
  262. toBeImageData: function (util, customEqualityTesters) {
  263. return {
  264. compare: function (actual) {
  265. return imagediff.isImageData(actual);
  266. }
  267. }
  268. },
  269. toImageDiffEqual: function (util, customEqualityTesters) {
  270. return {
  271. compare: function (actual, expected, tolerance, maxDiffCount) {
  272. var message;
  273. if (typeof (document) !== UNDEFINED) {
  274. message = function () {
  275. var
  276. div = get('div'),
  277. a = get('div', '<div>Actual:</div>'),
  278. b = get('div', '<div>Expected:</div>'),
  279. c = get('div', '<div>Diff:</div>'),
  280. diff = imagediff.diff(actual, expected),
  281. canvas = getCanvas(),
  282. context;
  283. canvas.height = diff.height;
  284. canvas.width = diff.width;
  285. div.style.overflow = 'hidden';
  286. a.style.float = 'left';
  287. b.style.float = 'left';
  288. c.style.float = 'left';
  289. context = canvas.getContext('2d');
  290. context.putImageData(diff, 0, 0);
  291. a.appendChild(toCanvas(actual));
  292. b.appendChild(toCanvas(expected));
  293. c.appendChild(canvas);
  294. div.appendChild(a);
  295. div.appendChild(b);
  296. div.appendChild(c);
  297. return div;
  298. };
  299. }
  300. return { pass: imagediff.equal(actual, expected, tolerance, maxDiffCount), message: message () };
  301. }
  302. }
  303. }
  304. };
  305. // Image Output
  306. function imageDataToPNG(imageData, outputFile, callback) {
  307. var
  308. canvas = toCanvas(imageData),
  309. base64Data,
  310. decodedImage;
  311. callback = callback || Function;
  312. base64Data = canvas.toDataURL().replace(/^data:image\/\w+;base64,/, "");
  313. decodedImage = new Buffer(base64Data, 'base64');
  314. require('fs').writeFile(outputFile, decodedImage, callback);
  315. }
  316. // Definition
  317. imagediff = {
  318. createCanvas: getCanvas,
  319. createImageData: getImageData,
  320. isImage: isImage,
  321. isCanvas: isCanvas,
  322. isContext: isContext,
  323. isImageData: isImageData,
  324. isImageType: isImageType,
  325. toImageData: function (object) {
  326. checkType(object);
  327. if (isImageData(object)) {
  328. return copyImageData(object);
  329. }
  330. return toImageData(object);
  331. },
  332. equal: function (a, b, tolerance) {
  333. checkType(a, b);
  334. a = toImageData(a);
  335. b = toImageData(b);
  336. return equal(a, b, tolerance);
  337. },
  338. diff: function (a, b, options) {
  339. checkType(a, b);
  340. a = toImageData(a);
  341. b = toImageData(b);
  342. return diff(a, b, options);
  343. },
  344. jasmine: jasmine,
  345. // Compatibility
  346. noConflict: function () {
  347. root[name] = previous;
  348. return imagediff;
  349. }
  350. };
  351. if (typeof module !== 'undefined') {
  352. imagediff.imageDataToPNG = imageDataToPNG;
  353. }
  354. return imagediff;
  355. });