PageRenderTime 59ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/ajax/libs/camanjs/2.0.0/caman.js

https://gitlab.com/Mirros/cdnjs
JavaScript | 1904 lines | 1208 code | 349 blank | 347 comment | 194 complexity | 5d2aa62f671ebd0df18c35f0b86885bd MD5 | raw file
  1. /*!
  2. * CamanJS - Pure HTML5 Javascript (Ca)nvas (Man)ipulation
  3. * http://camanjs.com/
  4. *
  5. * Version 2.2
  6. *
  7. * Copyright 2011, Ryan LeFevre
  8. * Licensed under the new BSD License.
  9. * See LICENSE for more info.
  10. *
  11. * Project Contributors:
  12. * Ryan LeFevre - Lead Developer and Project Maintainer
  13. * Twitter: @meltingice
  14. * GitHUb: http://github.com/meltingice
  15. *
  16. * Rick Waldron - Plugin Architect and Developer
  17. * Twitter: @rwaldron
  18. * GitHub: http://github.com/rwldrn
  19. *
  20. * Cezar Sa Espinola - Developer
  21. * Twitter: @cezarsa
  22. * GitHub: http://github.com/cezarsa
  23. */
  24. /*global Caman: true, require: true, exports: true */
  25. var fs = require('fs'),
  26. Canvas = require('canvas'),
  27. Image = Canvas.Image;
  28. /*
  29. * Responsible for loading CamanJS and setting everything up.
  30. * The Caman() function is defined here.
  31. */
  32. var Caman = function ( file, ready ) {
  33. return new Caman.manip.load(file, ready);
  34. };
  35. Caman.manip = Caman.prototype = {
  36. /*
  37. * Sets up everything that needs to be done before the filter
  38. * functions can be run. This includes loading the image into
  39. * the canvas element and saving lots of different data about
  40. * the image.
  41. */
  42. load: function(file, ready) {
  43. var self = this,
  44. img = new Image();
  45. file = fs.realpathSync(file);
  46. img.onload = function () {
  47. var canvas = new Canvas(img.width, img.height);
  48. self.canvas = canvas;
  49. self.context = canvas.getContext('2d');
  50. self.context.drawImage(img, 0, 0);
  51. self.image_data = self.context.getImageData(0, 0, img.width, img.height);
  52. self.pixel_data = self.image_data.data;
  53. self.dimensions = {
  54. width: img.width,
  55. height: img.height
  56. };
  57. self.renderQueue = [];
  58. self.pixelStack = [];
  59. self.layerStack = [];
  60. if(typeof ready === "function") { ready.call(self, self); }
  61. Caman.store[file] = self;
  62. return self;
  63. };
  64. img.onerror = function (err) {
  65. throw err;
  66. };
  67. img.src = file;
  68. }
  69. };
  70. Caman.manip.load.prototype = Caman.manip;
  71. // Helper function since document.getElementById()
  72. // is a mouthful. Note that this will not conflict
  73. // with jQuery since this Caman.$ variable does not exist
  74. // in the global window scope.
  75. Caman.$ = function (id) {
  76. if (id[0] == '#') {
  77. id = id.substr(1);
  78. }
  79. return document.getElementById(id);
  80. };
  81. Caman.store = {};
  82. Caman.isRemote = function (url) {
  83. var domain_regex = /(?:(?:http|https):\/\/)((?:\w+)\.(?:(?:\w|\.)+))/,
  84. test_domain;
  85. if (!url || !url.length) {
  86. return;
  87. }
  88. var matches = url.match(domain_regex);
  89. if (matches) {
  90. test_domain = matches[1];
  91. return test_domain != document.domain;
  92. } else {
  93. return false;
  94. }
  95. };
  96. Caman.remoteCheck = function (src) {
  97. // Check to see if image is remote or not
  98. if (Caman.isRemote(src)) {
  99. if (!Caman.remoteProxy.length) {
  100. console.info("Attempting to load remote image without a configured proxy, URL: " + src);
  101. return;
  102. } else {
  103. if (Caman.isRemote(Caman.remoteProxy)) {
  104. console.info("Cannot use a remote proxy for loading remote images due to same-origin policy");
  105. return;
  106. }
  107. // We have a remote proxy setup properly, so lets alter the image src
  108. return Caman.remoteProxy + "?url=" + encodeURIComponent(src);
  109. }
  110. }
  111. };
  112. exports.Caman = Caman;
  113. /*
  114. * Utility functions that help out in various areas of CamanJS.
  115. */
  116. (function (Caman) {
  117. var forEach = Array.prototype.forEach,
  118. hasOwn = Object.prototype.hasOwnProperty,
  119. slice = Array.prototype.slice;
  120. Caman.plugin = {};
  121. /*
  122. * Utility forEach function for iterating over
  123. * objects/arrays.
  124. */
  125. Caman.forEach = function( obj, fn, context ) {
  126. if ( !obj || !fn ) {
  127. return {};
  128. }
  129. context = context || this;
  130. // Use native whenever possible
  131. if ( forEach && obj.forEach === forEach ) {
  132. return obj.forEach(fn, context);
  133. }
  134. for ( var key in obj ) {
  135. if ( hasOwn.call(obj, key) ) {
  136. fn.call(context, obj[key], key, obj);
  137. }
  138. }
  139. return obj;
  140. };
  141. /*
  142. * Used for extending the Caman object, primarily to
  143. * add new functionality to the base library.
  144. */
  145. Caman.extend = function( obj ) {
  146. var dest = obj, src = slice.call(arguments, 1);
  147. Caman.forEach( src, function( copy ) {
  148. for ( var prop in copy ) {
  149. dest[prop] = copy[prop];
  150. }
  151. });
  152. return dest;
  153. };
  154. Caman.clampRGB = function (value) {
  155. if (value > 255) return 255;
  156. else if (value < 0) return 0;
  157. return value;
  158. };
  159. /*
  160. * Here we define the proxies that ship with CamanJS for easy
  161. * usage.
  162. */
  163. Caman.useProxy = function (lang) {
  164. // define cases where file extensions don't match the language name
  165. var langToExt = {
  166. ruby: 'rb',
  167. python: 'py',
  168. perl: 'pl'
  169. };
  170. lang = langToExt[lang.toLowerCase()] || lang.toLowerCase();
  171. return "proxies/caman_proxy." + lang;
  172. };
  173. /*
  174. * Unique ID generator. Guaranteed to always generate a new ID.
  175. */
  176. Caman.uniqid = (function () {
  177. var id = 0;
  178. return {
  179. get: function () {
  180. return id++;
  181. },
  182. reset: function () {
  183. id = 0;
  184. }
  185. };
  186. }());
  187. Caman.extend(Caman, {
  188. /*
  189. * Returns the size of an object (the number of properties
  190. * the object has)
  191. */
  192. sizeOf: function ( obj ) {
  193. var size = 0,
  194. prop;
  195. for ( prop in obj ) {
  196. size++;
  197. }
  198. return size;
  199. },
  200. /*
  201. * Determines whether two given objects are the same based
  202. * on their properties and values.
  203. */
  204. sameAs: function ( base, test ) {
  205. // only tests arrays
  206. // TODO: extend to object tests
  207. if ( base.length !== test.length ) {
  208. return false;
  209. }
  210. for ( var i = base.length; i >= 0; i-- ) {
  211. if ( base[i] !== test[i] ) {
  212. return false;
  213. }
  214. }
  215. return true;
  216. },
  217. /*
  218. * Removes items with the given value from an array if they
  219. * are present.
  220. */
  221. remove: function ( arr, item ) {
  222. var ret = [];
  223. for ( var i = 0, len = arr.length; i < len; i++ ) {
  224. if ( arr[i] !== item ) {
  225. ret.push(arr[i]);
  226. }
  227. }
  228. arr = ret;
  229. return ret;
  230. },
  231. randomRange: function (min, max, float) {
  232. var rand = min + (Math.random() * (max - min));
  233. return typeof float == 'undefined' ? Math.round(rand) : rand.toFixed(float);
  234. },
  235. /**
  236. * Converts an RGB color to HSL.
  237. * Assumes r, g, and b are in the set [0, 255] and
  238. * returns h, s, and l in the set [0, 1].
  239. *
  240. * @param Number r Red channel
  241. * @param Number g Green channel
  242. * @param Number b Blue channel
  243. * @return The HSL representation
  244. */
  245. rgb_to_hsl: function(r, g, b) {
  246. r /= 255;
  247. g /= 255;
  248. b /= 255;
  249. var max = Math.max(r, g, b), min = Math.min(r, g, b),
  250. h, s, l = (max + min) / 2;
  251. if(max == min){
  252. h = s = 0; // achromatic
  253. } else {
  254. var d = max - min;
  255. s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
  256. switch(max){
  257. case r: h = (g - b) / d + (g < b ? 6 : 0); break;
  258. case g: h = (b - r) / d + 2; break;
  259. case b: h = (r - g) / d + 4; break;
  260. }
  261. h /= 6;
  262. }
  263. return {h: h, s: s, l: l};
  264. },
  265. hue_to_rgb: function (p, q, t) {
  266. if(t < 0) t += 1;
  267. if(t > 1) t -= 1;
  268. if(t < 1/6) return p + (q - p) * 6 * t;
  269. if(t < 1/2) return q;
  270. if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
  271. return p;
  272. },
  273. /**
  274. * Converts an HSL color value to RGB. Conversion formula
  275. * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
  276. * Assumes h, s, and l are contained in the set [0, 1] and
  277. * returns r, g, and b in the set [0, 255].
  278. *
  279. * @param Number h The hue
  280. * @param Number s The saturation
  281. * @param Number l The lightness
  282. * @return Array The RGB representation
  283. */
  284. hsl_to_rgb: function(h, s, l){
  285. var r, g, b;
  286. if(s === 0){
  287. r = g = b = l; // achromatic
  288. } else {
  289. var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
  290. var p = 2 * l - q;
  291. r = this.hue_to_rgb(p, q, h + 1/3);
  292. g = this.hue_to_rgb(p, q, h);
  293. b = this.hue_to_rgb(p, q, h - 1/3);
  294. }
  295. return {r: r * 255, g: g * 255, b: b * 255};
  296. },
  297. /**
  298. * Converts an RGB color value to HSV. Conversion formula
  299. * adapted from http://en.wikipedia.org/wiki/HSV_color_space.
  300. * Assumes r, g, and b are contained in the set [0, 255] and
  301. * returns h, s, and v in the set [0, 1].
  302. *
  303. * @param Number r The red color value
  304. * @param Number g The green color value
  305. * @param Number b The blue color value
  306. * @return Array The HSV representation
  307. */
  308. rgb_to_hsv: function(r, g, b){
  309. r = r/255;
  310. g = g/255;
  311. b = b/255;
  312. var max = Math.max(r, g, b), min = Math.min(r, g, b),
  313. h, s, v = max,
  314. d = max - min;
  315. s = max === 0 ? 0 : d / max;
  316. if(max == min){
  317. h = 0; // achromatic
  318. } else {
  319. switch(max){
  320. case r: h = (g - b) / d + (g < b ? 6 : 0); break;
  321. case g: h = (b - r) / d + 2; break;
  322. case b: h = (r - g) / d + 4; break;
  323. }
  324. h /= 6;
  325. }
  326. return {h: h, s: s, v: v};
  327. },
  328. /**
  329. * Converts an HSV color value to RGB. Conversion formula
  330. * adapted from http://en.wikipedia.org/wiki/HSV_color_space.
  331. * Assumes h, s, and v are contained in the set [0, 1] and
  332. * returns r, g, and b in the set [0, 255].
  333. *
  334. * @param Number h The hue
  335. * @param Number s The saturation
  336. * @param Number v The value
  337. * @return Array The RGB representation
  338. */
  339. hsv_to_rgb: function(h, s, v){
  340. var r, g, b,
  341. i = Math.floor(h * 6),
  342. f = h * 6 - i,
  343. p = v * (1 - s),
  344. q = v * (1 - f * s),
  345. t = v * (1 - (1 - f) * s);
  346. switch(i % 6){
  347. case 0:
  348. r = v;
  349. g = t;
  350. b = p;
  351. break;
  352. case 1:
  353. r = q;
  354. g = v;
  355. b = p;
  356. break;
  357. case 2:
  358. r = p;
  359. g = v;
  360. b = t;
  361. break;
  362. case 3:
  363. r = p;
  364. g = q;
  365. b = v;
  366. break;
  367. case 4:
  368. r = t;
  369. g = p;
  370. b = v;
  371. break;
  372. case 5:
  373. r = v;
  374. g = p;
  375. b = q;
  376. break;
  377. }
  378. return {r: r * 255, g: g * 255, b: b * 255};
  379. },
  380. /**
  381. * Converts a RGB color value to the XYZ color space. Formulas
  382. * are based on http://en.wikipedia.org/wiki/SRGB assuming that
  383. * RGB values are sRGB.
  384. * Assumes r, g, and b are contained in the set [0, 255] and
  385. * returns x, y, and z.
  386. *
  387. * @param Number r The red color value
  388. * @param Number g The green color value
  389. * @param Number b The blue color value
  390. * @return Array The XYZ representation
  391. */
  392. rgb_to_xyz: function (r, g, b) {
  393. r = r / 255; g = g / 255; b = b / 255;
  394. if (r > 0.04045) {
  395. r = Math.pow((r + 0.055) / 1.055, 2.4);
  396. } else {
  397. r = r / 12.92;
  398. }
  399. if (g > 0.04045) {
  400. g = Math.pow((g + 0.055) / 1.055, 2.4);
  401. } else {
  402. g = g / 12.92;
  403. }
  404. if (b > 0.04045) {
  405. b = Math.pow((b + 0.055) / 1.055, 2.4);
  406. } else {
  407. b = b / 12.92;
  408. }
  409. var x = r * 0.4124 + g * 0.3576 + b * 0.1805;
  410. var y = r * 0.2126 + g * 0.7152 + b * 0.0722;
  411. var z = r * 0.0193 + g * 0.1192 + b * 0.9505;
  412. return {x: x * 100, y: y * 100, z: z * 100};
  413. },
  414. /**
  415. * Converts a XYZ color value to the sRGB color space. Formulas
  416. * are based on http://en.wikipedia.org/wiki/SRGB and the resulting
  417. * RGB value will be in the sRGB color space.
  418. * Assumes x, y and z values are whatever they are and returns
  419. * r, g and b in the set [0, 255].
  420. *
  421. * @param Number x The X value
  422. * @param Number y The Y value
  423. * @param Number z The Z value
  424. * @return Array The RGB representation
  425. */
  426. xyz_to_rgb: function (x, y, z) {
  427. x = x / 100; y = y / 100; z = z / 100;
  428. var r, g, b;
  429. r = (3.2406 * x) + (-1.5372 * y) + (-0.4986 * z);
  430. g = (-0.9689 * x) + (1.8758 * y) + (0.0415 * z);
  431. b = (0.0557 * x) + (-0.2040 * y) + (1.0570 * z);
  432. if(r > 0.0031308) {
  433. r = (1.055 * Math.pow(r, 0.4166666667)) - 0.055;
  434. } else {
  435. r = 12.92 * r;
  436. }
  437. if(g > 0.0031308) {
  438. g = (1.055 * Math.pow(g, 0.4166666667)) - 0.055;
  439. } else {
  440. g = 12.92 * g;
  441. }
  442. if(b > 0.0031308) {
  443. b = (1.055 * Math.pow(b, 0.4166666667)) - 0.055;
  444. } else {
  445. b = 12.92 * b;
  446. }
  447. return {r: r * 255, g: g * 255, b: b * 255};
  448. },
  449. /**
  450. * Converts a XYZ color value to the CIELAB color space. Formulas
  451. * are based on http://en.wikipedia.org/wiki/Lab_color_space
  452. * The reference white point used in the conversion is D65.
  453. * Assumes x, y and z values are whatever they are and returns
  454. * L*, a* and b* values
  455. *
  456. * @param Number x The X value
  457. * @param Number y The Y value
  458. * @param Number z The Z value
  459. * @return Array The Lab representation
  460. */
  461. xyz_to_lab: function(x, y, z) {
  462. // D65 reference white point
  463. var whiteX = 95.047, whiteY = 100.0, whiteZ = 108.883;
  464. x = x / whiteX; y = y / whiteY; z = z / whiteZ;
  465. if (x > 0.008856451679) { // (6/29) ^ 3
  466. x = Math.pow(x, 0.3333333333);
  467. } else {
  468. x = (7.787037037 * x) + 0.1379310345; // (1/3) * ((29/6) ^ 2)c + (4/29)
  469. }
  470. if (y > 0.008856451679) {
  471. y = Math.pow(y, 0.3333333333);
  472. } else {
  473. y = (7.787037037 * y) + 0.1379310345;
  474. }
  475. if (z > 0.008856451679) {
  476. z = Math.pow(z, 0.3333333333);
  477. } else {
  478. z = (7.787037037 * z) + 0.1379310345;
  479. }
  480. var l = 116 * y - 16;
  481. var a = 500 * (x - y);
  482. var b = 200 * (y - z);
  483. return {l: l, a: a, b: b};
  484. },
  485. /**
  486. * Converts a L*, a*, b* color values from the CIELAB color space
  487. * to the XYZ color space. Formulas are based on
  488. * http://en.wikipedia.org/wiki/Lab_color_space
  489. * The reference white point used in the conversion is D65.
  490. * Assumes L*, a* and b* values are whatever they are and returns
  491. * x, y and z values.
  492. *
  493. * @param Number l The L* value
  494. * @param Number a The a* value
  495. * @param Number b The b* value
  496. * @return Array The XYZ representation
  497. */
  498. lab_to_xyz: function(l, a, b) {
  499. var y = (l + 16) / 116;
  500. var x = y + (a / 500);
  501. var z = y - (b / 200);
  502. if (x > 0.2068965517) { // 6 / 29
  503. x = x * x * x;
  504. } else {
  505. x = 0.1284185493 * (x - 0.1379310345); // 3 * ((6 / 29) ^ 2) * (c - (4 / 29))
  506. }
  507. if (y > 0.2068965517) {
  508. y = y * y * y;
  509. } else {
  510. y = 0.1284185493 * (y - 0.1379310345);
  511. }
  512. if (z > 0.2068965517) {
  513. z = z * z * z;
  514. } else {
  515. z = 0.1284185493 * (z - 0.1379310345);
  516. }
  517. // D65 reference white point
  518. return {x: x * 95.047, y: y * 100.0, z: z * 108.883};
  519. },
  520. /*
  521. * Converts the hex representation of a color to RGB values.
  522. * Hex value can optionally start with the hash (#).
  523. *
  524. * @param String hex The colors hex value
  525. * @return Array The RGB representation
  526. */
  527. hex_to_rgb: function(hex) {
  528. var r, g, b;
  529. if (hex.charAt(0) === "#") {
  530. hex = hex.substr(1);
  531. }
  532. r = parseInt(hex.substr(0, 2), 16);
  533. g = parseInt(hex.substr(2, 2), 16);
  534. b = parseInt(hex.substr(4, 2), 16);
  535. return {r: r, g: g, b: b};
  536. },
  537. bezier: function (start, ctrl1, ctrl2, end, lowBound, highBound) {
  538. var Ax, Bx, Cx, Ay, By, Cy,
  539. x0 = start[0], y0 = start[1],
  540. x1 = ctrl1[0], y1 = ctrl1[1],
  541. x2 = ctrl2[0], y2 = ctrl2[1],
  542. x3 = end[0], y3 = end[1],
  543. t, curveX, curveY,
  544. bezier = {};
  545. // Calculate our X and Y coefficients
  546. Cx = 3 * (x1 - x0);
  547. Bx = 3 * (x2 - x1) - Cx;
  548. Ax = x3 - x0 - Cx - Bx;
  549. Cy = 3 * (y1 - y0);
  550. By = 3 * (y2 - y1) - Cy;
  551. Ay = y3 - y0 - Cy - By;
  552. for (var i = 0; i < 1000; i++) {
  553. t = i / 1000;
  554. curveX = Math.round((Ax * Math.pow(t, 3)) + (Bx * Math.pow(t, 2)) + (Cx * t) + x0);
  555. curveY = Math.round((Ay * Math.pow(t, 3)) + (By * Math.pow(t, 2)) + (Cy * t) + y0);
  556. if (lowBound && curveY < lowBound) {
  557. curveY = lowBound;
  558. } else if (highBound && curveY > highBound) {
  559. curveY = highBound;
  560. }
  561. bezier[curveX] = curveY;
  562. }
  563. // Do a search for missing values in the bezier array and use linear interpolation
  564. // to approximate their values.
  565. var leftCoord, rightCoord, j, slope, bint;
  566. if (bezier.length < end[0] + 1) {
  567. for (i = 0; i <= end[0]; i++) {
  568. if (typeof bezier[i] === "undefined") {
  569. // The value to the left will always be defined. We don't have to worry about
  570. // when i = 0 because the starting point is guaranteed (I think...)
  571. leftCoord = [i-1, bezier[i-1]];
  572. // Find the first value to the right that was found. Ideally this loop will break
  573. // very quickly.
  574. for (j = i; j <= end[0]; j++) {
  575. if (typeof bezier[j] !== "undefined") {
  576. rightCoord = [j, bezier[j]];
  577. break;
  578. }
  579. }
  580. bezier[i] = leftCoord[1] + ((rightCoord[1] - leftCoord[1]) / (rightCoord[0] - leftCoord[0])) * (i - leftCoord[0]);
  581. }
  582. }
  583. }
  584. // Edge case
  585. if (typeof bezier[end[0]] === "undefined") {
  586. bezier[end[0]] = bezier[254];
  587. }
  588. return bezier;
  589. },
  590. distance: function (x1, y1, x2, y2) {
  591. return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
  592. }
  593. });
  594. }(Caman));
  595. /*
  596. * Input/output functions for CamanJS. Mostly deal with
  597. * saving images, converting them to base64, and so on.
  598. */
  599. (function (Caman) {
  600. Caman.remoteProxy = "";
  601. Caman.extend(Caman.manip, {
  602. /*
  603. * Grabs the canvas data, encodes it to Base64, then
  604. * sets the browser location to the encoded data so that
  605. * the user will be prompted to download it.
  606. */
  607. save: function (file, overwrite) {
  608. var out = fs.createWriteStream(file),
  609. stream = this.canvas.createPNGStream();
  610. var stats = fs.statSync(file);
  611. if (stats.isFile() && !overwrite) {
  612. return false;
  613. }
  614. stream.on('data', function (chunk) {
  615. out.write(chunk);
  616. });
  617. },
  618. /*
  619. * Grabs the current canvas data and Base64 encodes it.
  620. */
  621. toBase64: function (type) {
  622. if (type) {
  623. type = type.toLowerCase();
  624. }
  625. if (!type || (type !== 'png' && type !== 'jpg')) {
  626. type = 'png';
  627. }
  628. return this.canvas.toDataURL("image/" + type);
  629. }
  630. });
  631. }(Caman));
  632. /*
  633. * CamanJS event system
  634. * Events can be subscribed to using Caman.listen() and events
  635. * can be triggered using Caman.trigger().
  636. */
  637. (function (Caman) {
  638. Caman.events = {
  639. types: [ "processStart", "processComplete", "renderFinished" ],
  640. fn: {
  641. /*
  642. * Triggers an event with the given target name.
  643. */
  644. trigger: function ( target, type, data ) {
  645. var _target = target, _type = type, _data = data;
  646. if ( Caman.events.types.indexOf(target) !== -1 ) {
  647. _target = this;
  648. _type = target;
  649. _data = type;
  650. }
  651. if ( Caman.events.fn[_type] && Caman.sizeOf(Caman.events.fn[_type]) ) {
  652. Caman.forEach(Caman.events.fn[_type], function ( obj, key ) {
  653. obj.call(_target, _data);
  654. });
  655. }
  656. },
  657. /*
  658. * Registers a callback function to be fired when a certain
  659. * event occurs.
  660. */
  661. listen: function ( target, type, fn ) {
  662. var _target = target, _type = type, _fn = fn;
  663. if ( Caman.events.types.indexOf(target) !== -1 ) {
  664. _target = this;
  665. _type = target;
  666. _fn = type;
  667. }
  668. if ( !Caman.events.fn[_type] ) {
  669. Caman.events.fn[_type] = [];
  670. }
  671. Caman.events.fn[_type].push(_fn);
  672. return true;
  673. }
  674. },
  675. cache: {}
  676. };
  677. Caman.forEach( ["trigger", "listen"], function ( key ) {
  678. Caman[key] = Caman.events.fn[key];
  679. });
  680. })(Caman);
  681. /*
  682. * The pixelInfo object. This object is available inside of the
  683. * process() loop, and it lets filter developers have simple access
  684. * to any arbitrary pixel in the image, as well as information about
  685. * the current pixel in the loop.
  686. */
  687. (function (Caman) {
  688. /*
  689. * Allows the currently rendering filter to get data about
  690. * surrounding pixels relative to the pixel currently being
  691. * processed. The data returned is identical in format to the
  692. * rgba object provided in the process function.
  693. *
  694. * Example: to get data about the pixel to the top-right
  695. * of the currently processing pixel, you can call (within the process
  696. * function):
  697. * this.getPixelRelative(1, -1);
  698. */
  699. Caman.manip.pixelInfo = function (self) {
  700. this.loc = 0;
  701. this.manip = self;
  702. };
  703. Caman.manip.pixelInfo.prototype.locationXY = function () {
  704. var x, y;
  705. y = this.manip.dimensions.height - Math.floor(this.loc / (this.manip.dimensions.width * 4));
  706. x = ((this.loc % (this.manip.dimensions.width * 4)) / 4);
  707. return {x: x, y: y};
  708. };
  709. Caman.manip.pixelInfo.prototype.getPixelRelative = function (horiz_offset, vert_offset) {
  710. // We invert the vert_offset in order to make the coordinate system non-inverted. In laymans
  711. // terms: -1 means down and +1 means up.
  712. var newLoc = this.loc + (this.manip.dimensions.width * 4 * (vert_offset * -1)) + (4 * horiz_offset);
  713. // error handling
  714. if (newLoc > this.manip.pixel_data.length || newLoc < 0) {
  715. return {r: 0, g: 0, b: 0, a: 0};
  716. }
  717. return {
  718. r: this.manip.pixel_data[newLoc],
  719. g: this.manip.pixel_data[newLoc+1],
  720. b: this.manip.pixel_data[newLoc+2],
  721. a: this.manip.pixel_data[newLoc+3]
  722. };
  723. };
  724. Caman.manip.pixelInfo.prototype.putPixelRelative = function (horiz_offset, vert_offset, rgba) {
  725. var newLoc = this.loc + (this.manip.dimensions.width * 4 * (vert_offset * -1)) + (4 * horiz_offset);
  726. // error handling
  727. if (newLoc > this.manip.pixel_data.length || newLoc < 0) {
  728. return false;
  729. }
  730. this.manip.pixel_data[newLoc] = rgba.r;
  731. this.manip.pixel_data[newLoc+1] = rgba.g;
  732. this.manip.pixel_data[newLoc+2] = rgba.b;
  733. this.manip.pixel_data[newLoc+3] = rgba.a;
  734. };
  735. Caman.manip.pixelInfo.prototype.getPixel = function (x, y) {
  736. var newLoc = (y * this.manip.dimensions.width + x) * 4;
  737. return {
  738. r: this.manip.pixel_data[newLoc],
  739. g: this.manip.pixel_data[newLoc+1],
  740. b: this.manip.pixel_data[newLoc+2],
  741. a: this.manip.pixel_data[newLoc+3]
  742. };
  743. };
  744. Caman.manip.pixelInfo.prototype.putPixel = function (x, y, rgba) {
  745. var newLoc = (y * this.manip.dimensions.width + x) * 4;
  746. this.manip.pixel_data[newLoc] = rgba.r;
  747. this.manip.pixel_data[newLoc+1] = rgba.g;
  748. this.manip.pixel_data[newLoc+2] = rgba.b;
  749. this.manip.pixel_data[newLoc+3] = rgba.a;
  750. };
  751. }(Caman));
  752. /*
  753. * CamanJS's rendering system. This covers convolution kernels,
  754. * pixel-wise filters, and plugins. All of the actual pixel/image
  755. * manipulation is executed here when render() is called.
  756. */
  757. (function (Caman) {
  758. Caman.renderBlocks = 4;
  759. /*
  760. * SINGLE = traverse the image 1 pixel at a time
  761. * KERNEL = traverse the image using convolution kernels
  762. * LAYER_DEQUEUE = shift a layer off the canvasQueue
  763. * LAYER_FINISHED = finished processing a layer
  764. * LOAD_OVERLAY = load a local/remote image into the layer canvas
  765. * PLUGIN = executes a plugin function that isn't pixelwise or kernel
  766. */
  767. Caman.ProcessType = {
  768. SINGLE: 1,
  769. KERNEL: 2,
  770. LAYER_DEQUEUE: 3,
  771. LAYER_FINISHED: 4,
  772. LOAD_OVERLAY: 5,
  773. PLUGIN: 6
  774. };
  775. Caman.manip.process = function (adjust, processFn) {
  776. // Since the block-based renderer is asynchronous, we simply build
  777. // up a render queue and execute the filters in order once
  778. // render() is called instead of executing them as they're called
  779. // synchronously.
  780. this.renderQueue.push({adjust: adjust, processFn: processFn, type: Caman.ProcessType.SINGLE});
  781. return this;
  782. };
  783. Caman.manip.processKernel = function (name, adjust, divisor, bias) {
  784. if (!divisor) {
  785. divisor = 0;
  786. for (var i = 0, len = adjust.length; i < len; i++) {
  787. divisor += adjust[i];
  788. }
  789. }
  790. var data = {
  791. name: name,
  792. adjust: adjust,
  793. divisor: divisor,
  794. bias: bias || 0
  795. };
  796. this.renderQueue.push({adjust: data, processFn: Caman.processKernel, type: Caman.ProcessType.KERNEL});
  797. return this;
  798. };
  799. Caman.manip.processPlugin = function (plugin, args) {
  800. this.renderQueue.push({type: Caman.ProcessType.PLUGIN, plugin: plugin, args: args});
  801. return this;
  802. };
  803. Caman.manip.executePlugin = function (plugin, args) {
  804. console.log("Executing plugin: " + plugin);
  805. Caman.plugin[plugin].apply(this, args);
  806. console.log("Plugin " + plugin + " finished!");
  807. this.processNext();
  808. };
  809. /*
  810. * Begins the render process if it's not started, or moves to the next
  811. * filter in the queue and processes it. Calls the finishedFn callback
  812. * when the render queue is empty.
  813. */
  814. Caman.manip.processNext = function (finishedFn) {
  815. if (typeof finishedFn === "function") {
  816. this.finishedFn = finishedFn;
  817. }
  818. if (this.renderQueue.length === 0) {
  819. Caman.trigger("renderFinished", {id: this.canvas_id});
  820. if (typeof this.finishedFn === "function") {
  821. this.finishedFn.call(this);
  822. }
  823. return;
  824. }
  825. var next = this.renderQueue.shift();
  826. if (next.type == Caman.ProcessType.LAYER_DEQUEUE) {
  827. var layer = this.canvasQueue.shift();
  828. this.executeLayer(layer);
  829. } else if (next.type == Caman.ProcessType.LAYER_FINISHED) {
  830. this.applyCurrentLayer();
  831. this.popContext();
  832. this.processNext();
  833. } else if (next.type == Caman.ProcessType.LOAD_OVERLAY) {
  834. this.loadOverlay(next.layer, next.src);
  835. } else if (next.type == Caman.ProcessType.PLUGIN) {
  836. this.executePlugin(next.plugin, next.args);
  837. } else {
  838. this.executeFilter(next.adjust, next.processFn, next.type);
  839. }
  840. };
  841. /*
  842. * Convolution kernel processing
  843. */
  844. Caman.extend( Caman, {
  845. processKernel: function (adjust, kernel, divisor, bias) {
  846. var val = {r: 0, g: 0, b: 0};
  847. for (var i = 0, len = adjust.length; i < len; i++) {
  848. val.r += adjust[i] * kernel[i * 3];
  849. val.g += adjust[i] * kernel[i * 3 + 1];
  850. val.b += adjust[i] * kernel[i * 3 + 2];
  851. }
  852. val.r = (val.r / divisor) + bias;
  853. val.g = (val.g / divisor) + bias;
  854. val.b = (val.b / divisor) + bias;
  855. return val;
  856. }
  857. });
  858. /*
  859. * The core of the image rendering, this function executes
  860. * the provided filter and updates the canvas pixel data
  861. * accordingly. NOTE: this does not write the updated pixel
  862. * data to the canvas. That happens when all filters are finished
  863. * rendering in order to be as fast as possible.
  864. */
  865. Caman.manip.executeFilter = function (adjust, processFn, type) {
  866. var n = this.pixel_data.length,
  867. res = null,
  868. // (n/4) == # of pixels in image
  869. // Give remaining pixels to last block in case it doesn't
  870. // divide evenly.
  871. blockPixelLength = Math.floor((n / 4) / Caman.renderBlocks),
  872. // expand it again to make the loop easier.
  873. blockN = blockPixelLength * 4,
  874. // add the remainder pixels to the last block.
  875. lastBlockN = blockN + ((n / 4) % Caman.renderBlocks) * 4,
  876. self = this,
  877. blocks_done = 0,
  878. // Called whenever a block finishes. It's used to determine when all blocks
  879. // finish rendering.
  880. block_finished = function (bnum) {
  881. if (bnum >= 0) {
  882. console.log("Block #" + bnum + " finished! Filter: " + processFn.name);
  883. }
  884. blocks_done++;
  885. if (blocks_done == Caman.renderBlocks || bnum == -1) {
  886. if (bnum >= 0) {
  887. console.log("Filter " + processFn.name + " finished!");
  888. } else {
  889. console.log("Kernel filter finished!");
  890. }
  891. Caman.trigger("processComplete", {id: self.canvas_id, completed: processFn.name});
  892. self.processNext();
  893. }
  894. },
  895. /*
  896. * Renders a block of the image bounded by the start and end
  897. * parameters.
  898. */
  899. render_block = function (bnum, start, end) {
  900. console.log("BLOCK #" + bnum + " - Filter: " + processFn.name + ", Start: " + start + ", End: " + end);
  901. setTimeout(function () {
  902. var data = {r: 0, g: 0, b: 0, a: 0};
  903. var pixelInfo = new self.pixelInfo(self);
  904. for (var i = start; i < end; i += 4) {
  905. pixelInfo.loc = i;
  906. data.r = self.pixel_data[i];
  907. data.g = self.pixel_data[i+1];
  908. data.b = self.pixel_data[i+2];
  909. data.a = self.pixel_data[i+3];
  910. res = processFn.call(pixelInfo, adjust, data);
  911. self.pixel_data[i] = res.r;
  912. self.pixel_data[i+1] = res.g;
  913. self.pixel_data[i+2] = res.b;
  914. self.pixel_data[i+3] = res.a;
  915. }
  916. block_finished(bnum);
  917. }, 0);
  918. },
  919. render_kernel = function () {
  920. setTimeout(function () {
  921. var kernel = [],
  922. pixelInfo, pixel,
  923. start, end,
  924. mod_pixel_data,
  925. name = adjust.name,
  926. bias = adjust.bias,
  927. divisor = adjust.divisor,
  928. adjustSize,
  929. builder, builder_index,
  930. i, j, k;
  931. adjust = adjust.adjust;
  932. adjustSize = Math.sqrt(adjust.length);
  933. mod_pixel_data = [];
  934. console.log("Rendering kernel - Filter: " + name);
  935. start = self.dimensions.width * 4 * ((adjustSize - 1) / 2);
  936. end = n - (self.dimensions.width * 4 * ((adjustSize - 1) / 2));
  937. builder = (adjustSize - 1) / 2;
  938. pixelInfo = new self.pixelInfo(self);
  939. for (i = start; i < end; i += 4) {
  940. pixelInfo.loc = i;
  941. builder_index = 0;
  942. for (j = -builder; j <= builder; j++) {
  943. for (k = builder; k >= -builder; k--) {
  944. pixel = pixelInfo.getPixelRelative(j, k);
  945. kernel[builder_index * 3] = pixel.r;
  946. kernel[builder_index * 3 + 1] = pixel.g;
  947. kernel[builder_index * 3 + 2] = pixel.b;
  948. builder_index++;
  949. }
  950. }
  951. // Execute the kernel processing function
  952. res = processFn.call(pixelInfo, adjust, kernel, divisor, bias);
  953. // Update the new pixel array since we can't modify the original
  954. // until the convolutions are finished on the entire image.
  955. mod_pixel_data[i] = res.r;
  956. mod_pixel_data[i+1] = res.g;
  957. mod_pixel_data[i+2] = res.b;
  958. mod_pixel_data[i+3] = 255;
  959. }
  960. // Update the actual canvas pixel data. Unfortunately we have to set
  961. // this one by one.
  962. for (i = start; i < end; i++) {
  963. self.pixel_data[i] = mod_pixel_data[i];
  964. }
  965. block_finished(-1);
  966. }, 0);
  967. };
  968. Caman.trigger("processStart", {id: this.canvas_id, start: processFn.name});
  969. if (type === Caman.ProcessType.SINGLE) {
  970. // Split the image into its blocks.
  971. for (var j = 0; j < Caman.renderBlocks; j++) {
  972. var start = j * blockN,
  973. end = start + ((j == Caman.renderBlocks - 1) ? lastBlockN : blockN);
  974. render_block(j, start, end);
  975. }
  976. } else {
  977. render_kernel();
  978. }
  979. };
  980. Caman.extend(Caman.manip, {
  981. revert: function (ready) {
  982. this.loadCanvas(this.options.image, this.options.canvas, ready);
  983. },
  984. render: function (callback) {
  985. this.processNext(function () {
  986. this.context.putImageData(this.image_data, 0, 0);
  987. if (typeof callback === 'function') {
  988. callback.call(this);
  989. }
  990. });
  991. }
  992. });
  993. }(Caman));
  994. /*
  995. * CamanJS layering system. Supports layer grouping and layer
  996. * ordering. Layers are blended into the parent layer using a variety
  997. * of blending functions, similar to what you would find in Photoshop
  998. * or GIMP.
  999. */
  1000. (function (Caman) {
  1001. Caman.manip.loadOverlay = function (layer, src) {
  1002. var proxyUrl = Caman.remoteCheck(src),
  1003. self = this;
  1004. if (proxyUrl) {
  1005. src = proxyUrl;
  1006. }
  1007. var img = document.createElement('img');
  1008. img.onload = function () {
  1009. layer.context.drawImage(img, 0, 0, self.dimensions.width, self.dimensions.height);
  1010. layer.image_data = layer.context.getImageData(0, 0, self.dimensions.width, self.dimensions.height);
  1011. layer.pixel_data = layer.image_data.data;
  1012. self.pixel_data = layer.pixel_data;
  1013. self.processNext();
  1014. };
  1015. img.src = src;
  1016. };
  1017. Caman.manip.canvasLayer = function (manip) {
  1018. // Default options
  1019. this.options = {
  1020. blendingMode: 'normal',
  1021. opacity: 1.0
  1022. };
  1023. // Create a blank and invisible canvas and append it to the document
  1024. this.layerID = Caman.uniqid.get();
  1025. this.canvas = document.createElement('canvas');
  1026. this.canvas.width = manip.dimensions.width;
  1027. this.canvas.height = manip.dimensions.height;
  1028. this.canvas.style.display = 'none';
  1029. this.context = this.canvas.getContext("2d");
  1030. this.context.createImageData(this.canvas.width, this.canvas.height);
  1031. this.image_data = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
  1032. this.pixel_data = this.image_data.data;
  1033. this.__defineGetter__("filter", function () {
  1034. return manip;
  1035. });
  1036. return this;
  1037. };
  1038. Caman.manip.canvasLayer.prototype.newLayer = function (callback) {
  1039. return this.filter.newLayer.call(this.filter, callback);
  1040. };
  1041. Caman.manip.canvasLayer.prototype.setBlendingMode = function (mode) {
  1042. this.options.blendingMode = mode;
  1043. return this;
  1044. };
  1045. Caman.manip.canvasLayer.prototype.opacity = function (opacity) {
  1046. this.options.opacity = (opacity / 100);
  1047. return this;
  1048. };
  1049. Caman.manip.canvasLayer.prototype.copyParent = function () {
  1050. var parentData = this.filter.pixel_data;
  1051. for (var i = 0; i < this.pixel_data.length; i += 4) {
  1052. this.pixel_data[i] = parentData[i];
  1053. this.pixel_data[i+1] = parentData[i+1];
  1054. this.pixel_data[i+2] = parentData[i+2];
  1055. this.pixel_data[i+3] = parentData[i+3];
  1056. }
  1057. return this;
  1058. };
  1059. Caman.manip.canvasLayer.prototype.fillColor = function () {
  1060. this.filter.fillColor.apply(this.filter, arguments);
  1061. return this;
  1062. };
  1063. Caman.manip.canvasLayer.prototype.overlayImage = function (image) {
  1064. if (image[0] == '#') {
  1065. image = Caman.Caman.$(image).src;
  1066. } else if (typeof image === "object") {
  1067. image = image.src;
  1068. }
  1069. // Some problem, skip to prevent errors
  1070. if (!image) return;
  1071. this.filter.renderQueue.push({type: Caman.ProcessType.LOAD_OVERLAY, src: image, layer: this});
  1072. return this;
  1073. };
  1074. // Leaving this here for compatibility reasons. It is NO
  1075. // LONGER REQUIRED at the end of the layer.
  1076. Caman.manip.canvasLayer.prototype.render = function () {};
  1077. Caman.manip.canvasLayer.prototype.applyToParent = function () {
  1078. var parentData = this.filter.pixelStack[this.filter.pixelStack.length - 1],
  1079. layerData = this.filter.pixel_data,
  1080. rgbaParent = {},
  1081. rgbaLayer = {},
  1082. result = {};
  1083. for (var i = 0; i < layerData.length; i += 4) {
  1084. rgbaParent = {
  1085. r: parentData[i],
  1086. g: parentData[i+1],
  1087. b: parentData[i+2],
  1088. a: parentData[i+3]
  1089. };
  1090. rgbaLayer = {
  1091. r: layerData[i],
  1092. g: layerData[i+1],
  1093. b: layerData[i+2],
  1094. a: layerData[i+3]
  1095. };
  1096. result = this.blenders[this.options.blendingMode](rgbaLayer, rgbaParent);
  1097. result.r = Caman.clampRGB(result.r);
  1098. result.g = Caman.clampRGB(result.g);
  1099. result.b = Caman.clampRGB(result.b);
  1100. parentData[i] = rgbaParent.r - ((rgbaParent.r - result.r) * (this.options.opacity * (result.a / 255)));
  1101. parentData[i+1] = rgbaParent.g - ((rgbaParent.g - result.g) * (this.options.opacity * (result.a / 255)));
  1102. parentData[i+2] = rgbaParent.b - ((rgbaParent.b - result.b) * (this.options.opacity * (result.a / 255)));
  1103. parentData[i+3] = 255;
  1104. }
  1105. };
  1106. // Blending functions
  1107. Caman.manip.canvasLayer.prototype.blenders = {
  1108. normal: function (rgbaLayer, rgbaParent) {
  1109. return {
  1110. r: rgbaLayer.r,
  1111. g: rgbaLayer.g,
  1112. b: rgbaLayer.b,
  1113. a: 255
  1114. };
  1115. },
  1116. multiply: function (rgbaLayer, rgbaParent) {
  1117. return {
  1118. r: (rgbaLayer.r * rgbaParent.r) / 255,
  1119. g: (rgbaLayer.g * rgbaParent.g) / 255,
  1120. b: (rgbaLayer.b * rgbaParent.b) / 255,
  1121. a: 255
  1122. };
  1123. },
  1124. screen: function (rgbaLayer, rgbaParent) {
  1125. return {
  1126. r: 255 - (((255 - rgbaLayer.r) * (255 - rgbaParent.r)) / 255),
  1127. g: 255 - (((255 - rgbaLayer.g) * (255 - rgbaParent.g)) / 255),
  1128. b: 255 - (((255 - rgbaLayer.b) * (255 - rgbaParent.b)) / 255),
  1129. a: 255
  1130. };
  1131. },
  1132. overlay: function (rgbaLayer, rgbaParent) {
  1133. var result = {};
  1134. result.r =
  1135. (rgbaParent.r > 128) ?
  1136. 255 - 2 * (255 - rgbaLayer.r) * (255 - rgbaParent.r) / 255:
  1137. (rgbaParent.r * rgbaLayer.r * 2) / 255;
  1138. result.g =
  1139. (rgbaParent.g > 128) ?
  1140. 255 - 2 * (255 - rgbaLayer.g) * (255 - rgbaParent.g) / 255:
  1141. (rgbaParent.g * rgbaLayer.g * 2) / 255;
  1142. result.b =
  1143. (rgbaParent.b > 128) ?
  1144. 255 - 2 * (255 - rgbaLayer.b) * (255 - rgbaParent.b) / 255:
  1145. (rgbaParent.b * rgbaLayer.b * 2) / 255;
  1146. result.a = 255;
  1147. return result;
  1148. },
  1149. difference: function (rgbaLayer, rgbaParent) {
  1150. return {
  1151. r: rgbaLayer.r - rgbaParent.r,
  1152. g: rgbaLayer.g - rgbaParent.g,
  1153. b: rgbaLayer.b - rgbaParent.b,
  1154. a: 255
  1155. };
  1156. },
  1157. addition: function (rgbaLayer, rgbaParent) {
  1158. return {
  1159. r: rgbaParent.r + rgbaLayer.r,
  1160. g: rgbaParent.g + rgbaLayer.g,
  1161. b: rgbaParent.b + rgbaLayer.b,
  1162. a: 255
  1163. };
  1164. },
  1165. exclusion: function (rgbaLayer, rgbaParent) {
  1166. return {
  1167. r: 128 - 2 * (rgbaParent.r - 128) * (rgbaLayer.r - 128) / 255,
  1168. g: 128 - 2 * (rgbaParent.g - 128) * (rgbaLayer.g - 128) / 255,
  1169. b: 128 - 2 * (rgbaParent.b - 128) * (rgbaLayer.b - 128) / 255,
  1170. a: 255
  1171. };
  1172. },
  1173. softLight: function (rgbaLayer, rgbaParent) {
  1174. var result = {};
  1175. result.r =
  1176. (rgbaParent.r > 128) ?
  1177. 255 - ((255 - rgbaParent.r) * (255 - (rgbaLayer.r - 128))) / 255 :
  1178. (rgbaParent.r * (rgbaLayer.r + 128)) / 255;
  1179. result.g =
  1180. (rgbaParent.g > 128) ?
  1181. 255 - ((255 - rgbaParent.g) * (255 - (rgbaLayer.g - 128))) / 255 :
  1182. (rgbaParent.g * (rgbaLayer.g + 128)) / 255;
  1183. result.b = (rgbaParent.b > 128) ?
  1184. 255 - ((255 - rgbaParent.b) * (255 - (rgbaLayer.b - 128))) / 255 :
  1185. (rgbaParent.b * (rgbaLayer.b + 128)) / 255;
  1186. result.a = 255;
  1187. return result;
  1188. }
  1189. };
  1190. Caman.manip.blenders = Caman.manip.canvasLayer.prototype.blenders;
  1191. Caman.manip.canvasQueue = [];
  1192. Caman.manip.newLayer = function (callback) {
  1193. var layer = new Caman.manip.canvasLayer(this);
  1194. this.canvasQueue.push(layer);
  1195. this.renderQueue.push({type: Caman.ProcessType.LAYER_DEQUEUE});
  1196. callback.call(layer);
  1197. this.renderQueue.push({type: Caman.ProcessType.LAYER_FINISHED});
  1198. return this;
  1199. };
  1200. Caman.manip.executeLayer = function (layer) {
  1201. this.pushContext(layer);
  1202. this.processNext();
  1203. };
  1204. Caman.manip.pushContext = function (layer) {
  1205. console.log("PUSH LAYER!");
  1206. this.layerStack.push(this.currentLayer);
  1207. this.pixelStack.push(this.pixel_data);
  1208. this.currentLayer = layer;
  1209. this.pixel_data = layer.pixel_data;
  1210. };
  1211. Caman.manip.popContext = function () {
  1212. console.log("POP LAYER!");
  1213. this.pixel_data = this.pixelStack.pop();
  1214. this.currentLayer = this.layerStack.pop();
  1215. };
  1216. Caman.manip.applyCurrentLayer = function () {
  1217. this.currentLayer.applyToParent();
  1218. };
  1219. }(Caman));
  1220. /*!
  1221. * Below are all of the built-in filters that are a part
  1222. * of the CamanJS core library.
  1223. */
  1224. (function(Caman) {
  1225. Caman.manip.fillColor = function () {
  1226. var color;
  1227. if (arguments.length == 1) {
  1228. color = Caman.hex_to_rgb(arguments[0]);
  1229. } else {
  1230. color = {
  1231. r: arguments[0],
  1232. g: arguments[1],
  1233. b: arguments[2]
  1234. };
  1235. }
  1236. return this.process( color, function fillColor(color, rgba) {
  1237. rgba.r = color.r;
  1238. rgba.g = color.g;
  1239. rgba.b = color.b;
  1240. rgba.a = 255;
  1241. return rgba;
  1242. });
  1243. };
  1244. Caman.manip.brightness = function(adjust) {
  1245. adjust = Math.floor(255 * (adjust / 100));
  1246. return this.process( adjust, function brightness(adjust, rgba) {
  1247. rgba.r += adjust;
  1248. rgba.g += adjust;
  1249. rgba.b += adjust;
  1250. return rgba;
  1251. });
  1252. };
  1253. Caman.manip.saturation = function(adjust) {
  1254. var max, diff;
  1255. adjust *= -0.01;
  1256. return this.process( adjust, function saturation(adjust, rgba) {
  1257. var chan;
  1258. max = Math.max(rgba.r, rgba.g, rgba.b);
  1259. if (rgba.r !== max) {
  1260. diff = max - rgba.r;
  1261. rgba.r += diff * adjust;
  1262. }
  1263. if (rgba.g !== max) {
  1264. diff = max - rgba.g;
  1265. rgba.g += diff * adjust;
  1266. }
  1267. if (rgba.b !== max) {
  1268. diff = max - rgba.b;
  1269. rgba.b += diff * adjust;
  1270. }
  1271. return rgba;
  1272. });
  1273. };
  1274. Caman.manip.vibrance = function (adjust) {
  1275. var max, avg, amt, diff;
  1276. adjust *= -1;
  1277. return this.process( adjust, function vibrance(adjust, rgba) {
  1278. var chan;
  1279. max = Math.max(rgba.r, rgba.g, rgba.b);
  1280. // Calculate difference between max color and other colors
  1281. avg = (rgba.r + rgba.g + rgba.b) / 3;
  1282. amt = ((Math.abs(max - avg) * 2 / 255) * adjust) / 100;
  1283. if (rgba.r !== max) {
  1284. diff = max - rgba.r;
  1285. rgba.r += diff * amt;
  1286. }
  1287. if (rgba.g !== max) {
  1288. diff = max - rgba.g;
  1289. rgba.g += diff * amt;
  1290. }
  1291. if (rgba.b !== max) {
  1292. diff = max - rgba.b;
  1293. rgba.b += diff * amt;
  1294. }
  1295. return rgba;
  1296. });
  1297. };
  1298. /*
  1299. * An improved greyscale function that should make prettier results
  1300. * than simply using the saturation filter to remove color. There are
  1301. * no arguments, it simply makes the image greyscale with no in-between.
  1302. *
  1303. * Algorithm adopted from http://www.phpied.com/image-fun/
  1304. */
  1305. Caman.manip.greyscale = function () {
  1306. return this.process({}, function greyscale(adjust, rgba) {
  1307. var avg = 0.3 * rgba.r + 0.59 * rgba.g + 0.11 * rgba.b;
  1308. rgba.r = avg;
  1309. rgba.g = avg;
  1310. rgba.b = avg;
  1311. return rgba;
  1312. });
  1313. };
  1314. Caman.manip.contrast = function(adjust) {
  1315. adjust = (adjust + 100) / 100;
  1316. adjust = Math.pow(adjust, 2);
  1317. return this.process( adjust, function contrast(adjust, rgba) {
  1318. /* Red channel */
  1319. rgba.r /= 255;
  1320. rgba.r -= 0.5;
  1321. rgba.r *= adjust;
  1322. rgba.r += 0.5;
  1323. rgba.r *= 255;
  1324. /* Green channel */
  1325. rgba.g /= 255;
  1326. rgba.g -= 0.5;
  1327. rgba.g *= adjust;
  1328. rgba.g += 0.5;
  1329. rgba.g *= 255;
  1330. /* Blue channel */
  1331. rgba.b /= 255;
  1332. rgba.b -= 0.5;
  1333. rgba.b *= adjust;
  1334. rgba.b += 0.5;
  1335. rgba.b *= 255;
  1336. // While uglier, I found that using if statements are
  1337. // faster than calling Math.max() and Math.min() to bound
  1338. // the numbers.
  1339. if (rgba.r > 255) {
  1340. rgba.r = 255;
  1341. } else if (rgba.r < 0) {
  1342. rgba.r = 0;
  1343. }
  1344. if (rgba.g > 255) {
  1345. rgba.g = 255;
  1346. } else if (rgba.g < 0) {
  1347. rgba.g = 0;
  1348. }
  1349. if (rgba.b > 255) {
  1350. rgba.b = 255;
  1351. } else if (rgba.b < 0) {
  1352. rgba.b = 0;
  1353. }
  1354. return rgba;
  1355. });
  1356. };
  1357. Caman.manip.hue = function(adjust) {
  1358. var hsv, h;
  1359. return this.process( adjust, function hue(adjust, rgba) {
  1360. var rgb;
  1361. hsv = Caman.rgb_to_hsv(rgba.r, rgba.g, rgba.b);
  1362. h = hsv.h * 100;
  1363. h += Math.abs(adjust);
  1364. h = h % 100;
  1365. h /= 100;
  1366. hsv.h = h;
  1367. rgb = Caman.hsv_to_rgb(hsv.h, hsv.s, hsv.v);
  1368. return {r: rgb.r, g: rgb.g, b: rgb.b, a: rgba.a};
  1369. });
  1370. };
  1371. Caman.manip.colorize = function() {
  1372. var diff, rgb, level;
  1373. if (arguments.length === 2) {
  1374. rgb = Caman.hex_to_rgb(arguments[0]);
  1375. level = arguments[1];
  1376. } else if (arguments.length === 4) {
  1377. rgb = {
  1378. r: arguments[0],
  1379. g: arguments[1],
  1380. b: arguments[2]
  1381. };
  1382. level = arguments[3];
  1383. }
  1384. return this.process( [ level, rgb ], function colorize( adjust, rgba) {
  1385. // adjust[0] == level; adjust[1] == rgb;
  1386. rgba.r -= (rgba.r - adjust[1].r) * (adjust[0] / 100);
  1387. rgba.g -= (rgba.g - adjust[1].g) * (adjust[0] / 100);
  1388. rgba.b -= (rgba.b - adjust[1].b) * (adjust[0] / 100);
  1389. return rgba;
  1390. });
  1391. };
  1392. Caman.manip.invert = function () {
  1393. return this.process({}, function invert (adjust, rgba) {
  1394. rgba.r = 255 - rgba.r;
  1395. rgba.g = 255 - rgba.g;
  1396. rgba.b = 255 - rgba.b;
  1397. return rgba;
  1398. });
  1399. };
  1400. /*
  1401. * Applies a sepia filter to the image. Assumes adjustment is between 0 and 100,
  1402. * which represents how much the sepia filter is applied.
  1403. */
  1404. Caman.manip.sepia = function (adjust) {
  1405. if (adjust === undefined) {
  1406. adjust = 100;
  1407. }
  1408. adjust = (adjust / 100);
  1409. return this.process(adjust, function sepia (adjust, rgba) {
  1410. rgba.r = Math.min(255, (rgba.r * (1 - (0.607 * adjust))) + (rgba.g * (0.769 * adjust)) + (rgba.b * (0.189 * adjust)));
  1411. rgba.g = Math.min(255, (rgba.r * (0.349 * adjust)) + (rgba.g * (1 - (0.314 * adjust))) + (rgba.b * (0.168 * adjust)));
  1412. rgba.b = Math.min(255, (rgba.r * (0.272 * adjust)) + (rgba.g * (0.534 * adjust)) + (rgba.b * (1- (0.869 * adjust))));
  1413. return rgba;
  1414. });
  1415. };
  1416. /*
  1417. * Adjusts the gamma of the image. I would stick with low values to be safe.
  1418. */
  1419. Caman.manip.gamma = function (adjust) {
  1420. return this.process(adjust, function gamma(adjust, rgba) {
  1421. rgba.r = Math.pow(rgba.r / 255, adjust) * 255;
  1422. rgba.g = Math.pow(rgba.g / 255, adjust) * 255;
  1423. rgba.b = Math.pow(rgba.b / 255, adjust) * 255;
  1424. return rgba;
  1425. });
  1426. };
  1427. /*
  1428. * Adds noise to the image on a scale from 1 - 100
  1429. * However, the scale isn't constrained, so you can specify
  1430. * a value > 100 if you want a LOT of noise.
  1431. */
  1432. Caman.manip.noise = function (adjust) {
  1433. adjust = Math.abs(adjust) * 2.55;
  1434. return this.process(adjust, function noise(adjust, rgba) {
  1435. var rand = Caman.randomRange(adjust*-1, adjust);
  1436. rgba.r += rand;
  1437. rgba.g += rand;
  1438. rgba.b += rand;
  1439. return rgba;
  1440. });
  1441. };
  1442. /*
  1443. * Clips a color to max values when it falls outside of the specified range.
  1444. * User supplied value should be between 0 and 100.
  1445. */
  1446. Caman.manip.clip = function (adjust) {
  1447. adjust = Math.abs(adjust) * 2.55;
  1448. return this.process(adjust, function clip(adjust, rgba) {
  1449. if (rgba.r > 255 - adjust) {
  1450. rgba.r = 255;
  1451. } else if (rgba.r < adjust) {
  1452. rgba.r = 0;
  1453. }
  1454. if (rgba.g > 255 - adjust) {
  1455. rgba.g = 255;
  1456. } else if (rgba.g < adjust) {
  1457. rgba.g = 0;
  1458. }
  1459. if (rgba.b > 255 - adjust) {
  1460. rgba.b = 255;
  1461. } else if (rgba.b < adjust) {
  1462. rgba.b = 0;
  1463. }
  1464. return rgba;
  1465. });
  1466. };
  1467. /*
  1468. * Lets you modify the intensity of any combination of red, green, or blue channels.
  1469. * Options format (must specify 1 - 3 colors):
  1470. * {
  1471. * red: 20,
  1472. * green: -5,
  1473. * blue: -40
  1474. * }
  1475. */
  1476. Caman.manip.channels = function (options) {
  1477. if (typeof(options) !== 'object') {
  1478. return;
  1479. }
  1480. for (var chan in options) {
  1481. if (options.hasOwnProperty(chan)) {
  1482. if (options[chan] === 0) {
  1483. delete options[chan];
  1484. continue;
  1485. }
  1486. options[chan] = options[chan] / 100;
  1487. }
  1488. }
  1489. if (options.length === 0) {
  1490. return;
  1491. }
  1492. return this.process(options, function channels(options, rgba) {
  1493. if (options.red) {
  1494. if (options.red > 0) {
  1495. // fraction of the distance between current color and 255
  1496. rgba.r = rgba.r + ((255 - rgba.r) * options.red);
  1497. } else {
  1498. rgba.r = rgba.r - (rgba.r * Math.abs(options.red));
  1499. }
  1500. }
  1501. if (options.green) {
  1502. if (options.green > 0) {
  1503. rgba.g = rgba.g + ((255 - rgba.g) * options.green);
  1504. } else {
  1505. rgba.g = rgba.g - (rgba.g * Math.abs(options.green));
  1506. }
  1507. }
  1508. if (options.blue) {
  1509. if (options.blue > 0) {
  1510. rgba.b = rgba.b + ((255 - rgba.b) * options.blue);
  1511. } else {
  1512. rgba.b = rgba.b - (rgba.b * Math.abs(options.blue));
  1513. }
  1514. }
  1515. return rgba;
  1516. });
  1517. };
  1518. /*
  1519. * Curves implementation using Bezier curve equation.
  1520. *
  1521. * Params:
  1522. * chan - [r, g, b, rgb]
  1523. * start - [x, y] (start of curve; 0 - 255)
  1524. * ctrl1 - [x, y] (control point 1; 0 - 255)
  1525. * ctrl2 - [x, y] (control point 2; 0 - 255)
  1526. * end - [x, y] (end of curve; 0 - 255)
  1527. */
  1528. Caman.manip.curves = function (chan, start, ctrl1, ctrl2, end) {
  1529. var bezier, i;
  1530. if (typeof chan === 'string') {
  1531. if (chan == 'rgb') {
  1532. chan = ['r', 'g', 'b'];
  1533. } else {
  1534. chan = [chan];
  1535. }
  1536. }
  1537. bezier = Caman.bezier(start, ctrl1, ctrl2, end, 0, 255);
  1538. // If our curve starts after x = 0, initialize it with a flat line until
  1539. // the curve begins.
  1540. if (start[0] > 0) {
  1541. for (i = 0; i < start[0]; i++) {
  1542. bezier[i] = start[1];
  1543. }
  1544. }
  1545. // ... and the same with the end point
  1546. if (end[0] < 255) {
  1547. for (i = end[0]; i <= 255; i++) {
  1548. bezier[i] = end[1];
  1549. }
  1550. }
  1551. return this.process({bezier: bezier, chans: chan}, function curves(opts, rgba) {
  1552. for (var i = 0; i < opts.chans.length; i++) {
  1553. rgba[opts.chans[i]] = opts.bezier[rgba[opts.chans[i]]];
  1554. }
  1555. return