fraphael.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. /**
  2. * FRaphael
  3. * An extension for Raphael.js to make it easier to work with Filter Effects
  4. *
  5. * Copyright © 2013 Chris Scott <chris.scott@factmint.com>
  6. * Delivered with and licensed under the MIT licence
  7. *
  8. */
  9. // Create the global FRaphael object
  10. (function(scope) {
  11. var version = "0.0.1",
  12. license = "MIT";
  13. var ns = "http://www.w3.org/2000/svg",
  14. idCounter = 0;
  15. var FR = {
  16. // Object prototype for a filter
  17. Filter: function(id) {
  18. if (id == undefined) {
  19. id = "filter-" + idCounter++;
  20. while(FR.filters[id] != undefined) {
  21. id = "filter-" + idCounter++;
  22. }
  23. }
  24. if (FR.filters[id] != undefined) {
  25. throw "A filter with id " + id + " already exists";
  26. }
  27. this.element = document.createElementNS(ns, "filter");
  28. this.element.setAttribute("id", id);
  29. this.element.setAttribute("x", "-25%");
  30. this.element.setAttribute("y", "-25%");
  31. this.element.setAttribute("width", "150%");
  32. this.element.setAttribute("height", "150%");
  33. this.lastFEResult = null;
  34. FR.filters[id] = this;
  35. this.id = id;
  36. },
  37. // Object prototype for an effect
  38. FilterEffect: function(type, attributes) {
  39. this.element = document.createElementNS(ns, type);
  40. for (var key in attributes) {
  41. this.element.setAttribute(key, attributes[key]);
  42. }
  43. },
  44. // Return the filter applied to an element or a new filter if none are currently applied
  45. getFilter: function(element) {
  46. var filterId = element.data("filterId");
  47. var filter = null;
  48. if (filterId == undefined) {
  49. filterId = "element-filter-" + element.id;
  50. filter = element.paper.createFilter(filterId);
  51. element.filter(filterId);
  52. } else {
  53. filter = FR.filters[filterId];
  54. }
  55. return filter;
  56. },
  57. // maintain a list of filters by id
  58. filters: {}
  59. };
  60. FR.Filter.prototype = {
  61. addEffect: function(type, attributes, children) {
  62. var effect = new FR.FilterEffect(type, attributes);
  63. if (children) {
  64. if (children instanceof Array) {
  65. for (var x in children) {
  66. if (!children.hasOwnProperty(x)) continue;
  67. effect.element.appendChild(children[x].element);
  68. }
  69. } else {
  70. effect.element.appendChild(children.element);
  71. }
  72. }
  73. this.element.appendChild(effect.element);
  74. return this;
  75. },
  76. chainEffect: function(type, attributes, children) {
  77. if (attributes == undefined) {
  78. attributes = {};
  79. }
  80. var inId;
  81. var outId;
  82. if (attributes.in == undefined) {
  83. inId = this.getLastResult();
  84. } else {
  85. inId = attributes.in;
  86. }
  87. if (attributes.result == undefined) {
  88. outId = idCounter++;
  89. } else {
  90. outId = attributes.result;
  91. }
  92. this.lastFEResult = outId;
  93. attributes.in = inId;
  94. attributes.result = outId;
  95. this.addEffect(type, attributes, children);
  96. return this;
  97. },
  98. getLastResult: function() {
  99. return (this.lastFEResult == undefined) ? "SourceGraphic" : this.lastFEResult;
  100. },
  101. merge: function(in1, in2, attributes) {
  102. var mergeNode1 = new FR.FilterEffect("feMergeNode", {
  103. in: in1
  104. });
  105. var mergeNode2 = new FR.FilterEffect("feMergeNode", {
  106. in: in2
  107. });
  108. this.chainEffect("feMerge", attributes, [mergeNode1, mergeNode2]);
  109. return this;
  110. },
  111. compose: function(in1, in2, operator, attributes) {
  112. if (attributes == undefined) {
  113. attributes = {};
  114. }
  115. if (operator == undefined) {
  116. operator = "over";
  117. }
  118. attributes.in = in1;
  119. attributes.in2 = in2;
  120. attributes.operator = operator;
  121. this.chainEffect("feComposite", attributes);
  122. return this;
  123. },
  124. arithmeticCompose: function(in1, in2, k1, k2, k3, k4) {
  125. if (k1 == undefined) {
  126. k1 = 0;
  127. }
  128. if (k2 == undefined) {
  129. k2 = 0;
  130. }
  131. if (k3 == undefined) {
  132. k3 = 0;
  133. }
  134. if (k4 == undefined) {
  135. k4 = 0;
  136. }
  137. this.compose(in1, in2, "arithmetic", {
  138. k1: k1,
  139. k2: k2,
  140. k3: k3,
  141. k4: k4
  142. });
  143. return this;
  144. },
  145. addBlur: function(stdDeviation, attributes) {
  146. if (!stdDeviation) {
  147. throw "Standard deviation is required to perform a blur filter";
  148. }
  149. if (attributes == undefined) {
  150. attributes = {};
  151. }
  152. attributes.stdDeviation = stdDeviation;
  153. this.chainEffect("feGaussianBlur", attributes);
  154. return this;
  155. },
  156. addOffset: function(dx, dy, attributes) {
  157. if (dx == undefined | dy == undefined) {
  158. throw "dx and dy values are required to perform an offset FE";
  159. }
  160. if (attributes == undefined) {
  161. attributes = {};
  162. }
  163. attributes.dx = dx;
  164. attributes.dy = dy;
  165. this.chainEffect("feOffset", attributes);
  166. return this;
  167. },
  168. addLighting: function(x, y, z, color, type, attributes) {
  169. if (x == undefined | y == undefined | z == undefined) {
  170. throw "Three co-ordinates are required to create a light source";
  171. }
  172. var previousResult = this.getLastResult();
  173. var id = idCounter++;
  174. if (attributes == undefined) {
  175. attributes = {};
  176. }
  177. attributes.result = id;
  178. if (color != undefined) {
  179. attributes["lighting-color"] = color;
  180. }
  181. if (type == undefined || type == "diffuse") {
  182. type = "feDiffuseLighting";
  183. } else if (type == "specular") {
  184. type = "feSpecularLighting";
  185. }
  186. var lightSource = new FR.FilterEffect("fePointLight", {
  187. x: x,
  188. y: y,
  189. z: z
  190. });
  191. this.chainEffect(type, attributes, lightSource).arithmeticCompose(previousResult, id, 3, 0.2, 0, 0);
  192. return this;
  193. },
  194. addShiftToColor: function(color, moveBy, attributes) {
  195. if (color == undefined) {
  196. throw "A colour string is a required argument to create a colorMatrix";
  197. }
  198. if (moveBy == undefined) {
  199. moveBy = 0.5;
  200. }
  201. var remainingColor = 1 - moveBy, x = remainingColor;
  202. if (attributes == undefined) {
  203. attributes = {};
  204. }
  205. var colorObject = Raphael.color(color);
  206. var r = colorObject.r * moveBy / 255,
  207. g = colorObject.g * moveBy / 255,
  208. b = colorObject.b * moveBy / 255;
  209. /**
  210. * r' x 0 0 0 r r
  211. * g' 0 x 0 0 g g
  212. * b' = 0 0 x 0 b . b
  213. * a' 0 0 0 1 0 o
  214. * 1 1
  215. */
  216. attributes.values = x + " 0 0 0 " + r + " 0 " + x + " 0 0 " + g + " 0 0 " + x + " 0 " + b + " 0 0 0 1 0 ";
  217. this.chainEffect("feColorMatrix", attributes);
  218. return this;
  219. },
  220. addRecolor: function(color, opacity, attributes) {
  221. if (color == undefined) {
  222. throw "A colour string is a required argument to create a colorMatrix";
  223. }
  224. if (opacity == undefined) {
  225. opacity = 1;
  226. }
  227. if (attributes == undefined) {
  228. attributes = {};
  229. }
  230. var colorObject = Raphael.color(color);
  231. var r = colorObject.r / 255,
  232. g = colorObject.g / 255,
  233. b = colorObject.b / 255;
  234. /**
  235. * r' 0 0 0 0 r r
  236. * g' 0 0 0 0 g g
  237. * b' = 0 0 0 0 b . b
  238. * a' 0 0 0 a 0 a
  239. * 1 1
  240. */
  241. attributes.values = "0 0 0 0 " + r + " 0 0 0 0 " + g + " 0 0 0 0 " + b + " 0 0 0 " + opacity + " 0 ";
  242. this.chainEffect("feColorMatrix", attributes);
  243. return this;
  244. },
  245. addDesaturate: function(saturation, attributes) {
  246. if (saturation == undefined) {
  247. saturnation = 0;
  248. }
  249. if (attributes == undefined) {
  250. attributes = {};
  251. }
  252. attributes.values = saturation;
  253. attributes.type = "saturate";
  254. this.chainEffect("feColorMatrix", attributes);
  255. return this;
  256. },
  257. addConvolveMatrix: function(matrix, attributes) {
  258. if (matrix == undefined) {
  259. throw "A matrix (usually 9 numbers) must be provided to apply a convolve matrix transform";
  260. }
  261. if (attributes == undefined) {
  262. attributes = {};
  263. }
  264. attributes.kernelMatrix = matrix;
  265. this.chainEffect("feConvolveMatrix", attributes);
  266. return this;
  267. },
  268. createShadow: function(dx, dy, blur, opacity, color) {
  269. if (dx == undefined) {
  270. throw "dx is required for the shadow effect";
  271. }
  272. if (dy == undefined) {
  273. throw "dy is required for the shadow effect";
  274. }
  275. if (blur == undefined) {
  276. throw "blur (stdDeviation) is required for the shadow effect";
  277. }
  278. if (opacity == undefined) {
  279. opacity = 0.6;
  280. }
  281. var previousResult = this.getLastResult();
  282. if (color == undefined) {
  283. color = "#000000";
  284. }
  285. this.addOffset(dx, dy, {
  286. in: "SourceAlpha"
  287. });
  288. this.addRecolor(color, opacity);
  289. this.addBlur(blur);
  290. this.merge(this.getLastResult(), previousResult);
  291. return this;
  292. },
  293. createEmboss: function(height, x, y, z) {
  294. if (height == undefined) {
  295. height = 2;
  296. }
  297. if (x == undefined) {
  298. x = -1000;
  299. }
  300. if (y == undefined) {
  301. y = -5000;
  302. }
  303. if (z == undefined) {
  304. z = 300;
  305. }
  306. // Create the highlight
  307. this.addOffset(height * x / (x + y), height * y / (x + y), {
  308. in: "SourceAlpha"
  309. });
  310. this.addBlur(height * 0.5);
  311. var whiteLightSource = new FR.FilterEffect("fePointLight", {
  312. x: x,
  313. y: y,
  314. z: z
  315. });
  316. this.chainEffect("feSpecularLighting", {
  317. surfaceScale: height,
  318. specularConstant: 0.8,
  319. specularExponent: 15
  320. }, whiteLightSource);
  321. this.compose(this.getLastResult(), "SourceAlpha", "in");
  322. var whiteLight = this.getLastResult();
  323. // Create the lowlight
  324. this.addOffset(height * -1 * x / (x + y), height * -1 * y / (x + y), {
  325. in: "SourceAlpha"
  326. });
  327. this.addBlur(height * 0.5);
  328. var darkLightSource = new FR.FilterEffect("fePointLight", {
  329. x: -1 * x,
  330. y: -1 * y,
  331. z: z
  332. });
  333. this.chainEffect("feSpecularLighting", {
  334. surfaceScale: height,
  335. specularConstant: 1.8,
  336. specularExponent: 6
  337. }, darkLightSource);
  338. this.compose(this.getLastResult(), "SourceAlpha", "in");
  339. this.chainEffect("feColorMatrix", {
  340. values: "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"
  341. });
  342. var darkLight = this.getLastResult();
  343. this.arithmeticCompose(whiteLight, darkLight, 0, 0.8, 0.5, 0);
  344. this.merge("SourceGraphic", this.getLastResult());
  345. return this;
  346. }
  347. };
  348. scope.FRaphael = FR;
  349. })(this);
  350. /**
  351. * add a filter to the paper by id
  352. */
  353. Raphael.fn.createFilter = function(id) {
  354. var paper = this;
  355. var filter = new FRaphael.Filter(id);
  356. paper.defs.appendChild(filter.element);
  357. return filter;
  358. };
  359. /**
  360. * Apply a filter to an element by id
  361. */
  362. Raphael.el.filter = function(filter) {
  363. var id = (filter instanceof FRaphael.Filter) ? filter.id : filter;
  364. this.node.setAttribute("filter", "url(#" + id + ")");
  365. this.data("filterId", id);
  366. return this;
  367. };
  368. /**
  369. * Get the current filter for an element or a new one if not
  370. */
  371. Raphael.el.getFilter = function() {
  372. return FRaphael.getFilter(this);
  373. };
  374. /**
  375. * A shorthand method for applying blur
  376. */
  377. Raphael.el.blur = function(stdDeviation) {
  378. if (stdDeviation == undefined) {
  379. stdDeviation = 3;
  380. }
  381. this.getFilter().addBlur(stdDeviation);
  382. return this;
  383. };
  384. /**
  385. * A shorthand method for applying a drop shadow
  386. */
  387. Raphael.el.shadow = function(dx, dy, blur, opacity, color) {
  388. if (dx == undefined) {
  389. dx = 3;
  390. }
  391. if (dy == undefined) {
  392. dy = 3;
  393. }
  394. if (blur == undefined) {
  395. blur = 3;
  396. }
  397. this.getFilter().createShadow(dx, dy, blur, opacity, color);
  398. return this;
  399. };
  400. /**
  401. * A shorthand method for applying lighting
  402. */
  403. Raphael.el.light = function(x, y, z, color, type) {
  404. if (x == undefined) {
  405. x = this.paper.width;
  406. }
  407. if (y == undefined) {
  408. y = 0;
  409. }
  410. if (z == undefined) {
  411. z = 20;
  412. }
  413. this.getFilter().addLighting(x, y, z, color, type);
  414. return this;
  415. };
  416. /**
  417. * A shorthand method for applying a colour shift
  418. */
  419. Raphael.el.colorShift = function(color, shift) {
  420. if (color == undefined) {
  421. color = "black";
  422. }
  423. if (shift == undefined) {
  424. shift = 0.5;
  425. }
  426. this.getFilter().addShiftToColor(color, shift);
  427. return this;
  428. };
  429. /**
  430. * A shorthand method for embossing
  431. */
  432. Raphael.el.emboss = function(height) {
  433. this.getFilter().createEmboss(height);
  434. return this;
  435. };
  436. /**
  437. * A shorthand method for desaturating
  438. */
  439. Raphael.el.desaturate = function(saturation) {
  440. this.getFilter().addDesaturate(saturation);
  441. return this;
  442. };
  443. /**
  444. * A shorthand method for complete desaturation
  445. */
  446. Raphael.el.greyScale = function() {
  447. this.getFilter().addDesaturate(0);
  448. return this;
  449. };