1. /*
  2.  * -- grayscale.js --
  3.  * Copyright (C) James Padolsey (http://james.padolsey.com)
  4.  *
  5.  */
  6.  
  7. var grayscale = (function(){
  8.  
  9. var config = {
  10. colorProps: ['color','backgroundColor','borderBottomColor','borderTopColor','borderLeftColor','borderRightColor','backgroundImage'],
  11. externalImageHandler : {
  12. /* Grayscaling externally hosted images does not work
  13.   - Use these functions to handle those images as you so desire */
  14. /* Out of convenience these functions are also used for browsers
  15.   like Chrome that do not support CanvasContext.getImageData */
  16. init : function(el, src) {
  17. if (el.nodeName.toLowerCase() === 'img') {
  18. // Is IMG element...
  19. } else {
  20. // Is background-image element:
  21. // Default - remove background images
  22. data(el).backgroundImageSRC = src;
  23. el.style.backgroundImage = '';
  24. }
  25. },
  26. reset : function(el) {
  27. if (el.nodeName.toLowerCase() === 'img') {
  28. // Is IMG element...
  29. } else {
  30. // Is background-image element:
  31. el.style.backgroundImage = 'url(' + (data(el).backgroundImageSRC || '') + ')';
  32. }
  33. }
  34. }
  35. },
  36. log = function(){
  37. try { window.console.log.apply(console, arguments); }
  38. catch(e) {};
  39. },
  40. isExternal = function(url) {
  41. // Checks whether URL is external: 'CanvasContext.getImageData'
  42. // only works if the image is on the current domain.
  43. return (new RegExp('https?://(?!' + window.location.hostname + ')')).test(url);
  44. },
  45. data = (function(){
  46.  
  47. var cache = [0],
  48. expando = 'data' + (+new Date());
  49.  
  50. return function(elem) {
  51. var cacheIndex = elem[expando],
  52. nextCacheIndex = cache.length;
  53. if(!cacheIndex) {
  54. cacheIndex = elem[expando] = nextCacheIndex;
  55. cache[cacheIndex] = {};
  56. }
  57. return cache[cacheIndex];
  58. };
  59.  
  60. })(),
  61. desatIMG = function(img, prepare, realEl) {
  62.  
  63. // realEl is only set when img is temp (for BG images)
  64.  
  65. var canvas = document.createElement('canvas'),
  66. context = canvas.getContext('2d'),
  67. height = img.naturalHeight || img.offsetHeight || img.height,
  68. width = img.naturalWidth || img.offsetWidth || img.width,
  69. imgData;
  70.  
  71. canvas.height = height;
  72. canvas.width = width;
  73. context.drawImage(img, 0, 0);
  74. try {
  75. imgData = context.getImageData(0, 0, width, height);
  76. } catch(e) {}
  77.  
  78. if (prepare) {
  79. desatIMG.preparing = true;
  80. // Slowly recurse through pixels for prep,
  81. // :: only occurs on grayscale.prepare()
  82. var y = 0;
  83. (function(){
  84.  
  85. if (!desatIMG.preparing) { return; }
  86.  
  87. if (y === height) {
  88. // Finished!
  89. context.putImageData(imgData, 0, 0, 0, 0, width, height);
  90. realEl ? (data(realEl).BGdataURL = canvas.toDataURL())
  91. : (data(img).dataURL = canvas.toDataURL())
  92. }
  93.  
  94. for (var x = 0; x < width; x++) {
  95. var i = (y * width + x) * 4;
  96. // Apply Monoschrome level across all channels:
  97. imgData.data[i] = imgData.data[i+1] = imgData.data[i+2] =
  98. RGBtoGRAYSCALE(imgData.data[i], imgData.data[i+1], imgData.data[i+2]);
  99. }
  100.  
  101. y++;
  102. setTimeout(arguments.callee, 0);
  103.  
  104. })();
  105. return;
  106. } else {
  107. // If desatIMG was called without 'prepare' flag
  108. // then cancel recursion and proceed with force! (below)
  109. desatIMG.preparing = false;
  110. }
  111.  
  112. for (var y = 0; y < height; y++) {
  113. for (var x = 0; x < width; x++) {
  114. var i = (y * width + x) * 4;
  115. // Apply Monoschrome level across all channels:
  116. imgData.data[i] = imgData.data[i+1] = imgData.data[i+2] =
  117. RGBtoGRAYSCALE(imgData.data[i], imgData.data[i+1], imgData.data[i+2]);
  118. }
  119. }
  120.  
  121. context.putImageData(imgData, 0, 0, 0, 0, width, height);
  122. return canvas;
  123.  
  124. },
  125. getStyle = function(el, prop) {
  126. var style = document.defaultView && document.defaultView.getComputedStyle ?
  127. document.defaultView.getComputedStyle(el, null)[prop]
  128. : el.currentStyle[prop];
  129. // If format is #FFFFFF: (convert to RGB)
  130. if (style && /^#[A-F0-9]/i.test(style)) {
  131. var hex = style.match(/[A-F0-9]{2}/ig);
  132. style = 'rgb(' + parseInt(hex[0], 16) + ','
  133. + parseInt(hex[1], 16) + ','
  134. + parseInt(hex[2], 16) + ')';
  135. }
  136. return style;
  137. },
  138. RGBtoGRAYSCALE = function(r,g,b) {
  139. // Returns single monochrome figure:
  140. return parseInt( (0.2125 * r) + (0.7154 * g) + (0.0721 * b), 10 );
  141. },
  142. getAllNodes = function(context) {
  143. var all = Array.prototype.slice.call(context.getElementsByTagName('*'));
  144. all.unshift(context);
  145. return all;
  146. };
  147.  
  148. var init = function(context) {
  149.  
  150. // Handle if a DOM collection is passed instead of a single el:
  151. if (context && context[0] && context.length && context[0].nodeName) {
  152. // Is a DOM collection:
  153. var allContexts = Array.prototype.slice.call(context),
  154. cIndex = -1, cLen = allContexts.length;
  155. while (++cIndex<cLen) { init.call(this, allContexts[cIndex]); }
  156. return;
  157. }
  158.  
  159. context = context || document.documentElement;
  160.  
  161. if (!document.createElement('canvas').getContext) {
  162. context.style.filter = 'progid:DXImageTransform.Microsoft.BasicImage(grayscale=1)';
  163. context.style.zoom = 1;
  164. return;
  165. }
  166.  
  167. var all = getAllNodes(context),
  168. i = -1, len = all.length;
  169.  
  170. while (++i<len) {
  171. var cur = all[i];
  172.  
  173. if (cur.nodeName.toLowerCase() === 'img') {
  174. var src = cur.getAttribute('src');
  175. if(!src) { continue; }
  176. if (isExternal(src)) {
  177. config.externalImageHandler.init(cur, src);
  178. } else {
  179. data(cur).realSRC = src;
  180. try {
  181. // Within try statement just encase there's no support....
  182. cur.src = data(cur).dataURL || desatIMG(cur).toDataURL();
  183. } catch(e) { config.externalImageHandler.init(cur, src); }
  184. }
  185.  
  186. } else {
  187. for (var pIndex = 0, pLen = config.colorProps.length; pIndex < pLen; pIndex++) {
  188. var prop = config.colorProps[pIndex],
  189. style = getStyle(cur, prop);
  190. if (!style) {continue;}
  191. if (cur.style[prop]) {
  192. data(cur)[prop] = style;
  193. }
  194. // RGB color:
  195. if (style.substring(0,4) === 'rgb(') {
  196. var monoRGB = RGBtoGRAYSCALE.apply(null, style.match(/\d+/g));
  197. cur.style[prop] = style = 'rgb(' + monoRGB + ',' + monoRGB + ',' + monoRGB + ')';
  198. continue;
  199. }
  200. // Background Image:
  201. if (style.indexOf('url(') > -1) {
  202. var urlPatt = /\(['"]?(.+?)['"]?\)/,
  203. url = style.match(urlPatt)[1];
  204. if (isExternal(url)) {
  205. config.externalImageHandler.init(cur, url);
  206. data(cur).externalBG = true;
  207. continue;
  208. }
  209. // data(cur).BGdataURL refers to caches URL (from preparation)
  210. try {
  211. var imgSRC = data(cur).BGdataURL || (function(){
  212. var temp = document.createElement('img');
  213. temp.src = url;
  214. return desatIMG(temp).toDataURL();
  215. })();
  216.  
  217. cur.style[prop] = style.replace(urlPatt, function(_, url){
  218. return '(' + imgSRC + ')';
  219. });
  220. } catch(e) { config.externalImageHandler.init(cur, url); }
  221. }
  222. }
  223. }
  224. }
  225.  
  226. };
  227.  
  228. init.reset = function(context) {
  229. // Handle if a DOM collection is passed instead of a single el:
  230. if (context && context[0] && context.length && context[0].nodeName) {
  231. // Is a DOM collection:
  232. var allContexts = Array.prototype.slice.call(context),
  233. cIndex = -1, cLen = allContexts.length;
  234. while (++cIndex<cLen) { init.reset.call(this, allContexts[cIndex]); }
  235. return;
  236. }
  237. context = context || document.documentElement;
  238. if (!document.createElement('canvas').getContext) {
  239. context.style.filter = 'progid:DXImageTransform.Microsoft.BasicImage(grayscale=0)';
  240. return;
  241. }
  242. var all = getAllNodes(context),
  243. i = -1, len = all.length;
  244. while (++i<len) {
  245. var cur = all[i];
  246. if (cur.nodeName.toLowerCase() === 'img') {
  247. var src = cur.getAttribute('src');
  248. if (isExternal(src)) {
  249. config.externalImageHandler.reset(cur, src);
  250. }
  251. cur.src = data(cur).realSRC || src;
  252. } else {
  253. for (var pIndex = 0, pLen = config.colorProps.length; pIndex < pLen; pIndex++) {
  254. if (data(cur).externalBG) {
  255. config.externalImageHandler.reset(cur);
  256. }
  257. var prop = config.colorProps[pIndex];
  258. cur.style[prop] = data(cur)[prop] || '';
  259. }
  260. }
  261. }
  262. };
  263.  
  264. init.prepare = function(context) {
  265.  
  266. // Handle if a DOM collection is passed instead of a single el:
  267. if (context && context[0] && context.length && context[0].nodeName) {
  268. // Is a DOM collection:
  269. var allContexts = Array.prototype.slice.call(context),
  270. cIndex = -1, cLen = allContexts.length;
  271. while (++cIndex<cLen) { init.prepare.call(null, allContexts[cIndex]); }
  272. return;
  273. }
  274.  
  275. // Slowly recurses through all elements
  276. // so as not to lock up on the user.
  277.  
  278. context = context || document.documentElement;
  279.  
  280. if (!document.createElement('canvas').getContext) { return; }
  281.  
  282. var all = getAllNodes(context),
  283. i = -1, len = all.length;
  284.  
  285. while (++i<len) {
  286. var cur = all[i];
  287. if (data(cur).skip) { return; }
  288. if (cur.nodeName.toLowerCase() === 'img') {
  289. if (cur.getAttribute('src') && !isExternal(cur.src)) {
  290. desatIMG(cur, true);
  291. }
  292.  
  293. } else {
  294. var style = getStyle(cur, 'backgroundImage');
  295. if (style.indexOf('url(') > -1) {
  296. var urlPatt = /\(['"]?(.+?)['"]?\)/,
  297. url = style.match(urlPatt)[1];
  298. if (!isExternal(url)) {
  299. var temp = document.createElement('img');
  300. temp.src = url;
  301. desatIMG(temp, true, cur);
  302. }
  303. }
  304. }
  305. }
  306. };
  307.  
  308. return init;
  309.  
  310. })();

ALL COPYRIGHT © James Padolsey unless otherwise specified
Go back to the top