ExamplesPlaygroundReference Source

coral-spectrum/coral-component-colorinput/src/scripts/Color.js

  1. /**
  2. * Copyright 2019 Adobe. All rights reserved.
  3. * This file is licensed to you under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License. You may obtain a copy
  5. * of the License at http://www.apache.org/licenses/LICENSE-2.0
  6. *
  7. * Unless required by applicable law or agreed to in writing, software distributed under
  8. * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
  9. * OF ANY KIND, either express or implied. See the License for the specific language
  10. * governing permissions and limitations under the License.
  11. */
  12.  
  13. import {transform} from '../../../coral-utils';
  14.  
  15. // try to stay in same color space as long as possible (because of conversions being not 100% accurate ...)
  16. /** @ignore */
  17. const colorSpace = {
  18. RGB: 'rgb',
  19. HEX: 'hex',
  20. CMYK: 'cmyk',
  21. HSB: 'hsb',
  22. HSL: 'hsl'
  23. };
  24.  
  25. /**
  26. Transforms part of a color (r,g,b) into a hex value.
  27.  
  28. @static
  29. @param {Number} x
  30. value between 0-255
  31.  
  32. @return {String} Hex representation
  33. @ignore
  34. */
  35. function _hex(x) {
  36. return `0${x.toString(16)}`.slice(-2);
  37. }
  38.  
  39. /** @ignore */
  40. function _slice(str, startStr) {
  41. let sliced = [];
  42.  
  43. str = transform.string(str).toLowerCase();
  44. startStr = transform.string(startStr).toLowerCase();
  45.  
  46. if (str.indexOf(startStr) !== -1) {
  47. sliced = str.substring(str.indexOf(startStr) + startStr.length, str.lastIndexOf(')')).split(/,\s*/);
  48. }
  49.  
  50. return sliced;
  51. }
  52.  
  53. /**
  54. Parse an rgb value into an object.
  55. e.g.: 'rgb(0,0,0)' => {r:0, g:0, b:0}
  56.  
  57. @static
  58. @param {String} rgbStr
  59. The string representing the rgb value
  60.  
  61. @return {Object} {r, g, b} Returns null if string could not be parsed
  62. @ignore
  63. */
  64. function _parseRGB(rgbStr) {
  65. const sliced = _slice(rgbStr, 'rgb(');
  66.  
  67. if (sliced.length !== 3) {
  68. return null;
  69. }
  70.  
  71. const r = parseInt(sliced[0], 10);
  72. const g = parseInt(sliced[1], 10);
  73. const b = parseInt(sliced[2], 10);
  74.  
  75. if (isNaN(r) || isNaN(g) || isNaN(b)) {
  76. return null;
  77. }
  78.  
  79. if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) {
  80. return null;
  81. }
  82.  
  83. return {r, g, b};
  84. }
  85.  
  86. /**
  87. Serialize an rgb object into a string.
  88. e.g.: {r:0, g:0, b:0} => 'rgb(0,0,0)'
  89.  
  90. @static
  91. @param {Object} rgb
  92. @return {String} rgbStr The string representing the rgb value
  93. @ignore
  94. */
  95. function _serializeRGB(rgb) {
  96. if (rgb) {
  97. return `rgb(${rgb.r},${rgb.g},${rgb.b})`;
  98. }
  99.  
  100. return '';
  101. }
  102.  
  103. /**
  104. Parse an rgba value into an object.
  105. e.g.: 'rgba(0,0,0,0.5)' => {r:0, g:0, b:0, a:0.5}
  106.  
  107. @static
  108. @param {String} rgbaStr
  109. The string representing the rgba value.
  110.  
  111. @return {Object} {r, g, b, a} Returns null if string could not be parsed
  112. @ignore
  113. */
  114. function _parseRGBA(rgbaStr) {
  115. const sliced = _slice(rgbaStr, 'rgba(');
  116.  
  117. if (sliced.length !== 4) {
  118. return null;
  119. }
  120.  
  121. const r = parseInt(sliced[0], 10);
  122. const g = parseInt(sliced[1], 10);
  123. const b = parseInt(sliced[2], 10);
  124. const a = parseFloat(sliced[3]);
  125.  
  126. if (isNaN(r) || isNaN(g) || isNaN(b) || isNaN(a)) {
  127. return null;
  128. }
  129.  
  130. if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 || a < 0 || a > 1) {
  131. return null;
  132. }
  133.  
  134. return {r, g, b, a};
  135. }
  136.  
  137. /**
  138. Serialize an rgba object into a string.
  139. e.g.: {r:0, g:0, b:0, a:0.5} => 'rgb(0,0,0,0.5)'
  140.  
  141. @static
  142. @param {Object} rgba
  143. @return {String} rgbaStr The string representing the rgba value
  144. @ignore
  145. */
  146. function _serializeRGBA(rgba) {
  147. if (rgba) {
  148. return `rgba(${rgba.r},${rgba.g},${rgba.b},${rgba.a})`;
  149. }
  150. return '';
  151. }
  152.  
  153. /**
  154. Parse an cmyk value into an object.
  155. e.g.: 'cmyk(0, 100, 50, 0)' => {c:0, m:100, y:50, k:0}
  156.  
  157. @static
  158. @param {String} cmykStr
  159. The string representing the cmyk value.
  160.  
  161. @return {Object} {c, m, y, k} Returns null if string could not be parsed
  162. @ignore
  163. */
  164. function _parseCMYK(cmykStr) {
  165. const sliced = _slice(cmykStr, 'cmyk(');
  166.  
  167. if (sliced.length !== 4) {
  168. return null;
  169. }
  170.  
  171. const c = parseFloat(sliced[0]);
  172. const m = parseFloat(sliced[1]);
  173. const y = parseFloat(sliced[2]);
  174. const k = parseFloat(sliced[3]);
  175.  
  176. if (isNaN(c) || isNaN(m) || isNaN(y) || isNaN(k)) {
  177. return null;
  178. }
  179.  
  180. if (c < 0 || c > 100 || m < 0 || m > 100 || y < 0 || y > 100 || k < 0 || k > 100) {
  181. return null;
  182. }
  183.  
  184. return {c, m, y, k};
  185. }
  186.  
  187. /**
  188. Serialize an cmyk object into a string.
  189. e.g.: {c:0, m:100, y:50, k:0} => 'cmyk(0, 100, 50, 0)'
  190.  
  191. @static
  192. @param {Object} cmyk
  193. @return {String} cmykStr The string representing the cmyk value
  194. @ignore
  195. */
  196. function _serializeCMYK(cmyk) {
  197. if (cmyk) {
  198. // make sure there are not more than 2 digits after dot
  199. const c = parseFloat(cmyk.c.toFixed(2));
  200. const m = parseFloat(cmyk.m.toFixed(2));
  201. const y = parseFloat(cmyk.y.toFixed(2));
  202. const k = parseFloat(cmyk.k.toFixed(2));
  203.  
  204. return `cmyk(${c},${m},${y},${k})`;
  205. }
  206. return '';
  207. }
  208.  
  209. /**
  210. Parse an hex value into a number. Corrects a hex value, if it is represented by 3 or 6 characters with or without
  211. '#'.
  212.  
  213. @static
  214. @param {String} hexStr The string representing the hex value
  215. @return {Number} Returns a number representing the parsed hex value
  216. @ignore
  217. */
  218. function _parseHex(hexStr) {
  219. hexStr = transform.string(hexStr).replace('#', '');
  220.  
  221. if (hexStr.length === 3) {
  222. hexStr = hexStr.charAt(0) + hexStr.charAt(0) +
  223. hexStr.charAt(1) + hexStr.charAt(1) +
  224. hexStr.charAt(2) + hexStr.charAt(2);
  225. }
  226.  
  227. // test if this could be a hex value
  228. const isOk = (/^[0-9A-F]{6}$/i).test(hexStr);
  229. if (!isOk) {
  230. return null;
  231. }
  232.  
  233. return parseInt(hexStr, 16);
  234. }
  235.  
  236. /**
  237. Transforms a hex color into RGB representation.
  238.  
  239. @static
  240. @param {Number} hex
  241. The color hex representation.
  242.  
  243. @return {Object} {r, g, b}
  244. @ignore
  245. */
  246. function _hexToRgb(hex) {
  247. // explicitly test null (0 is valid)
  248. if (hex !== null) {
  249. return {
  250. r: hex >> 16,
  251. // eslint-disable-next-line no-extra-parens
  252. g: (hex & 0x00FF00) >> 8,
  253. // eslint-disable-next-line no-extra-parens
  254. b: (hex & 0x0000FF)
  255. };
  256. }
  257. return null;
  258. }
  259.  
  260. /**
  261. Serialize a hex number into a string.
  262.  
  263. @static
  264. @param {Number} hex
  265. @return {String}
  266. @ignore
  267. */
  268. function _serializeHex(hex) {
  269. // explicitly test null (0 is valid)
  270. if (hex !== null) {
  271. const rgb = _hexToRgb(hex);
  272. return `#${_hex(rgb.r) + _hex(rgb.g) + _hex(rgb.b)}`;
  273. }
  274.  
  275. return '';
  276. }
  277.  
  278. /**
  279. Transforms a RGB color into HEX representation.
  280.  
  281. @static
  282. @param {Object} rgb
  283. @return {Number} hex The color hex representation
  284. @ignore
  285. */
  286. function _rgbToHex(rgb) {
  287. if (rgb) {
  288. return _parseHex(_hex(rgb.r) + _hex(rgb.g) + _hex(rgb.b));
  289. }
  290. return null;
  291. }
  292.  
  293. /**
  294. Transforms a cmyk color into RGB representation. Converting CMYK to RGB will incur slight loss because both color
  295. spaces are not absolute and there will be some round-off error in the conversion process.
  296.  
  297. @static
  298. @param {Object} cmyk
  299. @return {Object} {r, g, b}
  300. @ignore
  301. */
  302. function _cmykToRgb(cmyk) {
  303. if (!cmyk) {
  304. return null;
  305. }
  306.  
  307. const result = {
  308. r: 0,
  309. g: 0,
  310. b: 0
  311. };
  312.  
  313. const c = parseFloat(cmyk.c) / 100;
  314. const m = parseFloat(cmyk.m) / 100;
  315. const y = parseFloat(cmyk.y) / 100;
  316. const k = parseFloat(cmyk.k) / 100;
  317.  
  318. result.r = 1 - Math.min(1, c * (1 - k) + k);
  319. result.g = 1 - Math.min(1, m * (1 - k) + k);
  320. result.b = 1 - Math.min(1, y * (1 - k) + k);
  321.  
  322. result.r = Math.round(result.r * 255);
  323. result.g = Math.round(result.g * 255);
  324. result.b = Math.round(result.b * 255);
  325.  
  326. return result;
  327. }
  328.  
  329. /**
  330. Transforms a rgb color into cmyk representation. Converting CMYK to RGB will incur slight loss because both color
  331. spaces are not absolute and there will be some round-off error in the conversion process.
  332.  
  333. @static
  334. @param {Object} rgb
  335. @return {Object} {c, m, y, k}
  336. @ignore
  337. */
  338. function _rgbToCmyk(rgb) {
  339. if (!rgb) {
  340. return null;
  341. }
  342.  
  343. const result = {
  344. c: 0,
  345. m: 0,
  346. y: 0,
  347. k: 0
  348. };
  349.  
  350. if (rgb.r === 0 && rgb.g === 0 && rgb.b === 0) {
  351. result.k = 100;
  352. return result;
  353. }
  354.  
  355. const r = rgb.r / 255;
  356. const g = rgb.g / 255;
  357. const b = rgb.b / 255;
  358.  
  359. result.k = Math.min(1 - r, 1 - g, 1 - b);
  360. result.c = (1 - r - result.k) / (1 - result.k);
  361. result.m = (1 - g - result.k) / (1 - result.k);
  362. result.y = (1 - b - result.k) / (1 - result.k);
  363.  
  364. result.c = Math.round(result.c * 100);
  365. result.m = Math.round(result.m * 100);
  366. result.y = Math.round(result.y * 100);
  367. result.k = Math.round(result.k * 100);
  368.  
  369. return result;
  370. }
  371.  
  372. /**
  373. Parse an hsb value into an object.
  374. e.g.: 'hsb(360,100,0)' => {h:360, s:100, b:0}
  375.  
  376. @static
  377. @param {String} hsbStr
  378. The string representing the hsb value.
  379.  
  380. @return {Object} {h, s, b} Returns null if string could not be parsed
  381. @ignore
  382. */
  383. function _parseHSB(hsbStr) {
  384. const sliced = _slice(hsbStr, 'hsb(');
  385.  
  386. if (sliced.length !== 3) {
  387. return null;
  388. }
  389.  
  390. // make sure there are not more than 2 digits after dot
  391. const h = parseFloat(sliced[0]);
  392. const s = parseFloat(sliced[1]);
  393. const b = parseFloat(sliced[2]);
  394.  
  395. if (isNaN(h) || isNaN(s) || isNaN(b)) {
  396. return null;
  397. }
  398.  
  399. if (h < 0 || h > 360 || s < 0 || s > 100 || b < 0 || b > 100) {
  400. return null;
  401. }
  402.  
  403. return {h, s, b};
  404. }
  405.  
  406. /**
  407. Serialize an hsb object into a string.
  408. e.g.: {h:0, s:0, b:0} => 'hsb(0,0,0)'
  409.  
  410. @static
  411. @param {Object} hsb
  412. @return {String} hsb The string representing the hsb value
  413. @ignore
  414. */
  415. function _serializeHSB(hsb) {
  416. if (hsb) {
  417. // make sure there are not more than 2 digits after dot
  418. const h = parseFloat(hsb.h.toFixed(2));
  419. const s = parseFloat(hsb.s.toFixed(2));
  420. const b = parseFloat(hsb.b.toFixed(2));
  421.  
  422. return `hsb(${h},${s},${b})`;
  423. }
  424.  
  425. return '';
  426. }
  427.  
  428. /**
  429. Transforms a HSB (same as HSV) color into RGB representation.
  430. h (hue has value between 0-360 degree)
  431. s (saturation has a value between 0-100 percent)
  432. b (brightness has a value between 0-100 percent)
  433.  
  434. @static
  435. @param {Object} hsb
  436. @return {Object} {r, g, b}
  437. @ignore
  438. */
  439. function _hsbToRgb(hsb) {
  440. if (!hsb) {
  441. return null;
  442. }
  443.  
  444. const h = hsb.h / 360;
  445. const s = hsb.s / 100;
  446. const v = hsb.b / 100;
  447.  
  448. let r, g, b;
  449. const i = Math.floor(h * 6);
  450. const f = h * 6 - i;
  451. const p = v * (1 - s);
  452. const q = v * (1 - f * s);
  453. const t = v * (1 - (1 - f) * s);
  454. switch (i % 6) {
  455. case 0:
  456. r = v;
  457. g = t;
  458. b = p;
  459. break;
  460. case 1:
  461. r = q;
  462. g = v;
  463. b = p;
  464. break;
  465. case 2:
  466. r = p;
  467. g = v;
  468. b = t;
  469. break;
  470. case 3:
  471. r = p;
  472. g = q;
  473. b = v;
  474. break;
  475. case 4:
  476. r = t;
  477. g = p;
  478. b = v;
  479. break;
  480. case 5:
  481. r = v;
  482. g = p;
  483. b = q;
  484. break;
  485. }
  486. return {
  487. r: Math.round(r * 255),
  488. g: Math.round(g * 255),
  489. b: Math.round(b * 255)
  490. };
  491. }
  492.  
  493. /**
  494. Transforms a RGB color into HSB (same as HSV) representation.
  495.  
  496. @static
  497. @param {Object} rgb
  498. @return {Object} {h, s, b}
  499. @ignore
  500. */
  501. function _rgbToHsb(rgb) {
  502. if (!rgb) {
  503. return null;
  504. }
  505.  
  506. const r = rgb.r;
  507. const g = rgb.g;
  508. const b = rgb.b;
  509.  
  510. const max = Math.max(r, g, b);
  511. const min = Math.min(r, g, b);
  512. const d = max - min;
  513. let h;
  514. const s = max === 0 ? 0 : d / max;
  515. const v = max / 255;
  516.  
  517. switch (max) {
  518. case min:
  519. h = 0;
  520. break;
  521. case r:
  522. h = g - b + d * (g < b ? 6 : 0);
  523. h /= 6 * d;
  524. break;
  525. case g:
  526. h = b - r + d * 2;
  527. h /= 6 * d;
  528. break;
  529. case b:
  530. h = r - g + d * 4;
  531. h /= 6 * d;
  532. break;
  533. }
  534.  
  535. return {
  536. h: h * 360,
  537. s: s * 100,
  538. b: v * 100
  539. };
  540. }
  541.  
  542. /**
  543. Parse an hsl value into an object.
  544. e.g.: 'hsl(360,100,0)' => {h:360, s:100, b:0}
  545.  
  546. @static
  547. @param {String} hslStr
  548. The string representing the hsl value.
  549.  
  550. @return {Object} {h, s, l} Returns null if string could not be parsed
  551. @ignore
  552. */
  553. function _parseHSL(hslStr) {
  554. const sliced = _slice(hslStr, 'hsl(');
  555.  
  556. if (sliced.length !== 3) {
  557. return null;
  558. }
  559.  
  560. // make sure there are not more than 2 digits after dot
  561. const h = parseFloat(sliced[0]);
  562. const s = parseFloat(sliced[1]);
  563. const l = parseFloat(sliced[2]);
  564.  
  565. if (isNaN(h) || isNaN(s) || isNaN(l)) {
  566. return null;
  567. }
  568.  
  569. if (h < 0 || h > 360 || s < 0 || s > 100 || l < 0 || l > 100) {
  570. return null;
  571. }
  572.  
  573. return {h, s, l};
  574. }
  575.  
  576. /**
  577. Serialize an hsl object into a string.
  578. e.g.: {h:0, s:0, l:0} => 'hsl(0,0%,0%)'
  579.  
  580. @static
  581. @param {Object} hsl
  582. @return {String} hsb The string representing the hsb value
  583. @ignore
  584. */
  585. function _serializeHSL(hsl) {
  586. if (hsl) {
  587. // make sure there are not more than 2 digits after dot
  588. const h = parseFloat(hsl.h.toFixed(2));
  589. const s = parseFloat(hsl.s.toFixed(2));
  590. const l = parseFloat(hsl.l.toFixed(2));
  591.  
  592. return `hsl(${h},${s}%,${l}%)`;
  593. }
  594.  
  595. return '';
  596. }
  597.  
  598. /**
  599. Transforms a HSL color into RGB representation.
  600. h (hue has value between 0-360 degree)
  601. s (saturation has a value between 0-100 percent)
  602. l (lightness has a value between 0-100 percent)
  603.  
  604. @static
  605. @param {Object} hsl
  606. @return {Object} {r, g, b}
  607. @ignore
  608. */
  609. function _hslToRgb(hsl) {
  610. if (!hsl) {
  611. return null;
  612. }
  613.  
  614. const h = hsl.h / 360;
  615. const s = hsl.s / 100;
  616. const l = hsl.l / 100;
  617.  
  618. let r;
  619. let g;
  620. let b;
  621.  
  622. if (s === 0) {
  623. // achromatic
  624. r = g = b = l;
  625. } else {
  626. const hue2rgb = (p, q, t) => {
  627. if (t < 0) {
  628. t += 1;
  629. }
  630. if (t > 1) {
  631. t -= 1;
  632. }
  633. if (t < 1 / 6) {
  634. return p + (q - p) * 6 * t;
  635. }
  636. if (t < 1 / 2) {
  637. return q;
  638. }
  639. if (t < 2 / 3) {
  640. return p + (q - p) * (2 / 3 - t) * 6;
  641. }
  642. return p;
  643. };
  644.  
  645. const qValue = l < 0.5 ? l * (1 + s) : l + s - l * s;
  646. const pValue = 2 * l - qValue;
  647. r = hue2rgb(pValue, qValue, h + 1 / 3);
  648. g = hue2rgb(pValue, qValue, h);
  649. b = hue2rgb(pValue, qValue, h - 1 / 3);
  650. }
  651.  
  652. return {
  653. r: Math.round(r * 255),
  654. g: Math.round(g * 255),
  655. b: Math.round(b * 255)
  656. };
  657. }
  658.  
  659. /**
  660. Transforms an RGB color into HSL representation.
  661.  
  662. @static
  663. @param {Object} rgb
  664. @return {Object} {h, s, l}
  665. @ignore
  666. */
  667. function _rgbToHsl(rgb) {
  668. if (!rgb) {
  669. return null;
  670. }
  671.  
  672. const r = rgb.r / 255;
  673. const g = rgb.g / 255;
  674. const b = rgb.b / 255;
  675.  
  676. const max = Math.max(r, g, b);
  677. const min = Math.min(r, g, b);
  678. let h;
  679. let s;
  680. const l = (max + min) / 2;
  681.  
  682. if (max === min) {
  683. // achromatic
  684. h = s = 0;
  685. } else {
  686. const d = max - min;
  687. s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
  688. switch (max) {
  689. case r:
  690. h = (g - b) / d + (g < b ? 6 : 0);
  691. break;
  692. case g:
  693. h = (b - r) / d + 2;
  694. break;
  695. case b:
  696. h = (r - g) / d + 4;
  697. break;
  698. }
  699. h /= 6;
  700. }
  701.  
  702. return {
  703. h: h * 360,
  704. s: s * 100,
  705. l: l * 100
  706. };
  707. }
  708.  
  709. /**
  710. Parse an hsla value into an object.
  711. e.g.: 'hsla(360,100%,0%,0.5)' => {h:360, s:100, l:0, 0.5}
  712.  
  713. @static
  714. @param {String} hslaStr
  715. The string representing the hsl value.
  716.  
  717. @return {Object} {h, s, l, a} Returns null if string could not be parsed
  718. @ignore
  719. */
  720. function _parseHSLA(hslaStr) {
  721. const sliced = _slice(hslaStr, 'hsla(');
  722.  
  723. if (sliced.length !== 4) {
  724. return null;
  725. }
  726.  
  727. // make sure there are not more than 2 digits after dot
  728. const h = parseFloat(sliced[0]);
  729. const s = parseFloat(sliced[1]);
  730. const l = parseFloat(sliced[2]);
  731. const a = parseFloat(sliced[3]);
  732.  
  733. if (isNaN(h) || isNaN(s) || isNaN(l)) {
  734. return null;
  735. }
  736.  
  737. if (h < 0 || h > 360 || s < 0 || s > 100 || l < 0 || l > 100 || a < 0 || a > 1) {
  738. return null;
  739. }
  740.  
  741. return {h, s, l, a};
  742. }
  743.  
  744. /**
  745. Serialize an hsla object into a string.
  746. e.g.: {h:0, s:0, l:0, a:0.5} => 'hsl(0,0%,0%,0.5)'
  747.  
  748. @static
  749. @param {Object} hsla
  750. @return {String} hsb The string representing the hsb value
  751. @ignore
  752. */
  753. function _serializeHSLA(hsla) {
  754. if (hsla) {
  755. // make sure there are not more than 2 digits after dot
  756. const h = parseFloat(hsla.h.toFixed(2));
  757. const s = parseFloat(hsla.s.toFixed(2));
  758. const l = parseFloat(hsla.l.toFixed(2));
  759. const a = parseFloat(hsla.a.toFixed(2));
  760.  
  761. return `hsla(${h},${s}%,${l}%,${a})`;
  762. }
  763.  
  764. return '';
  765. }
  766.  
  767. /**
  768. Color is used to get a color in different color spaces, calculate tints and shades etc.
  769. */
  770. class Color {
  771. /** @ignore */
  772. constructor() {
  773. // Set defaults
  774. this._colorSpace = colorSpace.HEX;
  775. this._value = '';
  776. this._alpha = 1;
  777. }
  778.  
  779. /**
  780. The value of the color. This value can be set in different formats (HEX, RGB, RGBA, HSB, HSL, HSLA and CMYK).
  781. Corrects a hex value, if it is represented by 3 or 6 characters with or without '#'.
  782.  
  783. e.g:
  784. HEX: #FFFFFF
  785. RGB: rgb(16,16,16)
  786. RGBA: rgba(215,40,40,0.9)
  787. HSB: hsb(360,100,100)
  788. HSL: hsl(360,100%,100%)
  789. HSLA: hsla(360,100%,100%,0.9)
  790. CMYK: cmyk(0,100,50,0)
  791.  
  792. @type {String}
  793. @default ""
  794. */
  795. get value() {
  796. return this._value;
  797. }
  798.  
  799. set value(value) {
  800. // Two color formats with alpha values
  801. const rgba = _parseRGBA(value);
  802. const hsla = _parseHSLA(value);
  803.  
  804. const rgb = _parseRGB(value);
  805. const cmyk = _parseCMYK(value);
  806. const hsb = _parseHSB(value);
  807. const hsl = _parseHSL(value);
  808. const hex = _parseHex(value);
  809.  
  810. if (rgba !== null) {
  811. this._colorSpace = colorSpace.RGB;
  812. this.alpha = rgba.a;
  813. value = _serializeRGB({
  814. r: rgba.r,
  815. g: rgba.g,
  816. b: rgba.b
  817. });
  818. } else if (hsla !== null) {
  819. this._colorSpace = colorSpace.HSL;
  820. this.alpha = hsla.a;
  821. value = _serializeHSL({
  822. h: hsla.h,
  823. s: hsla.s,
  824. l: hsla.l
  825. });
  826. } else if (rgb !== null) {
  827. this._colorSpace = colorSpace.RGB;
  828. } else if (cmyk !== null) {
  829. this._colorSpace = colorSpace.CMYK;
  830. } else if (hsb !== null) {
  831. this._colorSpace = colorSpace.HSB;
  832. } else if (hsl !== null) {
  833. this._colorSpace = colorSpace.HSL;
  834. } else if (hex !== null) {
  835. this._colorSpace = colorSpace.HEX;
  836. } else {
  837. // restore defaults
  838. this._colorSpace = colorSpace.HEX;
  839. value = '';
  840. }
  841.  
  842. this._value = value;
  843. }
  844.  
  845. /**
  846. The alpha value of the color (value between 0-1).
  847.  
  848. @type {Number}
  849. @default 1
  850. */
  851. get alpha() {
  852. return this._alpha;
  853. }
  854.  
  855. set alpha(value) {
  856. if (isNaN(value) || value < 0 || value > 1) {
  857. return;
  858. }
  859. this._alpha = transform.number(value);
  860. }
  861.  
  862. /**
  863. The rgb values of the color (value between 0-255).
  864. e.g.: {r:0, g:0, b:0}
  865.  
  866. @type {Object}
  867. @default null
  868. */
  869. get rgb() {
  870. let rgb = null;
  871. if (this._colorSpace === colorSpace.RGB) {
  872. rgb = _parseRGB(this.value);
  873. } else if (this._colorSpace === colorSpace.HEX) {
  874. const hex = _parseHex(this.value);
  875. rgb = _hexToRgb(hex);
  876. } else if (this._colorSpace === colorSpace.CMYK) {
  877. const cmyk = _parseCMYK(this.value);
  878. rgb = _cmykToRgb(cmyk);
  879. } else if (this._colorSpace === colorSpace.HSB) {
  880. const hsb = _parseHSB(this.value);
  881. rgb = _hsbToRgb(hsb);
  882. } else if (this._colorSpace === colorSpace.HSL) {
  883. const hsl = _parseHSL(this.value);
  884. rgb = _hslToRgb(hsl);
  885. }
  886. return rgb;
  887. }
  888.  
  889. set rgb(value) {
  890. this.value = _serializeRGB(value);
  891. }
  892.  
  893. /**
  894. The serialized rgb values of the color (r,g,b values between 0-255).
  895. e.g: 'rgb(0,0,0)'
  896.  
  897. @type {String}
  898. @default ""
  899. */
  900. get rgbValue() {
  901. return _serializeRGB(this.rgb);
  902. }
  903.  
  904. set rgbValue(value) {
  905. this.value = value;
  906. }
  907.  
  908. /**
  909. The rgba values of the color (r,g,b values between 0-255 and a between 0-1).
  910. e.g: {r:0, g:0, b:0, a:1}
  911.  
  912. @type {Object}
  913. @default null
  914. */
  915. get rgba() {
  916. const rgb = this.rgb;
  917. if (rgb) {
  918. return {
  919. r: rgb.r,
  920. g: rgb.g,
  921. b: rgb.b,
  922. a: this.alpha
  923. };
  924. }
  925.  
  926. return null;
  927. }
  928.  
  929. set rgba(value) {
  930. this.value = _serializeRGBA(value);
  931. }
  932.  
  933. /**
  934. The serialized rgba values of the color (r,g,b values between 0-255 and alpha between 0-1).
  935. e.g: 'rgba(0,0,0,1)'
  936.  
  937. @type {String}
  938. @default ""
  939. */
  940. get rgbaValue() {
  941. return _serializeRGBA(this.rgba);
  942. }
  943.  
  944. set rgbaValue(value) {
  945. this.value = value;
  946. }
  947.  
  948. /**
  949. The hex value of the color.
  950.  
  951. @type {Number}
  952. @default null
  953. */
  954. get hex() {
  955. // as hex color space is essentially just the same as rgb and there is no loss in conversion, we can do it this way
  956. return _rgbToHex(this.rgb);
  957. }
  958.  
  959. set hex(value) {
  960. this.value = _serializeHex(value);
  961. }
  962.  
  963. /**
  964. The serialized hex value of the color.
  965. e.g: '#94CD4B'
  966.  
  967. @type {String}
  968. @default ""
  969. */
  970. get hexValue() {
  971. return _serializeHex(this.hex);
  972. }
  973.  
  974. set hexValue(value) {
  975. this.value = value;
  976. }
  977.  
  978. /**
  979. The cmyk values of the color (all values between 0-100).
  980. e.g: {c:0, m:100, y:0, k:100}
  981.  
  982. @type {Object}
  983. @default null
  984. */
  985. get cmyk() {
  986. let cmyk = null;
  987. let rgb = null;
  988. if (this._colorSpace === colorSpace.RGB) {
  989. rgb = _parseRGB(this.value);
  990. cmyk = _rgbToCmyk(rgb);
  991. } else if (this._colorSpace === colorSpace.HEX) {
  992. const hex = _parseHex(this.value);
  993. rgb = _hexToRgb(hex);
  994. cmyk = _rgbToCmyk(rgb);
  995. } else if (this._colorSpace === colorSpace.CMYK) {
  996. cmyk = _parseCMYK(this.value);
  997. } else if (this._colorSpace === colorSpace.HSB) {
  998. const hsb = _parseHSB(this.value);
  999. rgb = _hsbToRgb(hsb);
  1000. cmyk = _rgbToCmyk(rgb);
  1001. } else if (this._colorSpace === colorSpace.HSL) {
  1002. const hsl = _parseHSL(this.value);
  1003. rgb = _hslToRgb(hsl);
  1004. cmyk = _rgbToCmyk(rgb);
  1005. }
  1006. return cmyk;
  1007. }
  1008.  
  1009. set cmyk(value) {
  1010. this.value = _serializeCMYK(value);
  1011. }
  1012.  
  1013. /**
  1014. The serialized cmyk values of the color (all values between 0-100).
  1015. e.g: 'cmyk(100,100,100,100)'
  1016.  
  1017. @type {String}
  1018. @default ""
  1019. */
  1020. get cmykValue() {
  1021. return _serializeCMYK(this.cmyk);
  1022. }
  1023.  
  1024. set cmykValue(value) {
  1025. this.value = value;
  1026. }
  1027.  
  1028. /**
  1029. The hsb values of the color.
  1030. h (hue has value between 0-360 degree)
  1031. s (saturation has a value between 0-100 percent)
  1032. b (brightness has a value between 0-100 percent)
  1033.  
  1034. @type {Object}
  1035. @default null
  1036. */
  1037. get hsb() {
  1038. let hsb = null;
  1039. let rgb = null;
  1040. if (this._colorSpace === colorSpace.RGB) {
  1041. rgb = _parseRGB(this.value);
  1042. hsb = _rgbToHsb(rgb);
  1043. } else if (this._colorSpace === colorSpace.HEX) {
  1044. const hex = _parseHex(this.value);
  1045. rgb = _hexToRgb(hex);
  1046. hsb = _rgbToHsb(rgb);
  1047. } else if (this._colorSpace === colorSpace.CMYK) {
  1048. const cmyk = _parseCMYK(this.value);
  1049. rgb = _cmykToRgb(cmyk);
  1050. hsb = _rgbToHsb(rgb);
  1051. } else if (this._colorSpace === colorSpace.HSB) {
  1052. hsb = _parseHSB(this.value);
  1053. } else if (this._colorSpace === colorSpace.HSL) {
  1054. const hsl = _parseHSL(this.value);
  1055. rgb = _hslToRgb(hsl);
  1056. hsb = _rgbToHsb(rgb);
  1057. }
  1058. return hsb;
  1059. }
  1060.  
  1061. set hsb(value) {
  1062. this.value = _serializeHSB(value);
  1063. }
  1064.  
  1065. /**
  1066. The serialized hsb values of the color (s and b values between 0-100, h between 0-360).
  1067. e.g: 'hsb(360,100,100)'
  1068.  
  1069. @type {String}
  1070. @default ""
  1071. */
  1072. get hsbValue() {
  1073. return _serializeHSB(this.hsb);
  1074. }
  1075.  
  1076. set hsbValue(value) {
  1077. this.value = value;
  1078. }
  1079.  
  1080. /*
  1081. The hsl values of the color.
  1082. h (hue has value between 0-360 degree)
  1083. s (saturation has a value between 0-100 percent)
  1084. l (lightness has a value between 0-100 percent)
  1085.  
  1086. @type {Object}
  1087. @default null
  1088. */
  1089. get hsl() {
  1090. let hsl = null;
  1091. let rgb = null;
  1092. if (this._colorSpace === colorSpace.RGB) {
  1093. rgb = _parseRGB(this.value);
  1094. hsl = _rgbToHsl(rgb);
  1095. } else if (this._colorSpace === colorSpace.HEX) {
  1096. const hex = _parseHex(this.value);
  1097. rgb = _hexToRgb(hex);
  1098. hsl = _rgbToHsl(rgb);
  1099. } else if (this._colorSpace === colorSpace.CMYK) {
  1100. const cmyk = _parseCMYK(this.value);
  1101. rgb = _cmykToRgb(cmyk);
  1102. hsl = _rgbToHsl(rgb);
  1103. } else if (this._colorSpace === colorSpace.HSB) {
  1104. const hsb = _parseHSB(this.value);
  1105. rgb = _hsbToRgb(hsb);
  1106. hsl = _rgbToHsl(rgb);
  1107. } else if (this._colorSpace === colorSpace.HSL) {
  1108. hsl = _parseHSL(this.value);
  1109. }
  1110. return hsl;
  1111. }
  1112.  
  1113. set hsl(value) {
  1114. this.value = _serializeHSL(value);
  1115. }
  1116.  
  1117. /**
  1118. The serialized hsl values of the color (s and l values between 0-100 in percent, h between 0-360).
  1119. e.g: 'hsl(360,100%,100%)'
  1120.  
  1121. @type {String}
  1122. @default ""
  1123. */
  1124. get hslValue() {
  1125. return _serializeHSL(this.hsl);
  1126. }
  1127.  
  1128. set hslValue(value) {
  1129. this.value = value;
  1130. }
  1131.  
  1132. /**
  1133. The hsla values of the color.
  1134. h (hue has value between 0-360 degree)
  1135. s (saturation has a value between 0-100 percent)
  1136. l (lightness has a value between 0-100 percent)
  1137. a (alpha has a value between 0-1)
  1138.  
  1139. @type {Object}
  1140. @default null
  1141. */
  1142. get hsla() {
  1143. const hsl = this.hsl;
  1144. if (hsl) {
  1145. return {
  1146. h: hsl.h,
  1147. s: hsl.s,
  1148. l: hsl.l,
  1149. a: this.alpha
  1150. };
  1151. }
  1152.  
  1153. return null;
  1154. }
  1155.  
  1156. set hsla(value) {
  1157. this.value = _serializeHSLA(value);
  1158. }
  1159.  
  1160. /**
  1161. The serialized hsla values of the color.
  1162. h (hue has value between 0-360 degree)
  1163. s (saturation has a value between 0-100 percent)
  1164. l (lightness has a value between 0-100 percent)
  1165. a (alpha has a value between 0-1)
  1166. e.g: 'hsla(360,50%,50%,0.9)'
  1167.  
  1168. @type {String}
  1169. @default ""
  1170. */
  1171. get hslaValue() {
  1172. return _serializeHSLA(this.hsla);
  1173. }
  1174.  
  1175. set hslaValue(value) {
  1176. this.value = value;
  1177. }
  1178.  
  1179. /**
  1180. Clone this color.
  1181.  
  1182. @type {Color}
  1183. */
  1184. clone() {
  1185. const clone = new this.constructor();
  1186. clone.value = this.value;
  1187. clone.alpha = this.alpha;
  1188. return clone;
  1189. }
  1190.  
  1191. /**
  1192. Test if this Color is similar to another color.
  1193.  
  1194. @type {Boolean}
  1195. @param {Color} compareColor
  1196. The color to compare this color too.
  1197.  
  1198. @param {Boolean} [allowSlightColorDifference=true]
  1199. While converting between color spaces slight loses might happen => we should normally consider colors equal,
  1200. even if they are minimally different.
  1201.  
  1202. */
  1203. isSimilarTo(compareColor, allowSlightColorDifference) {
  1204. if (this.rgb === null && (!compareColor || compareColor.rgb === null)) {
  1205. // Consider an rgb of null equal to a null object (or another value of null)
  1206. return true;
  1207. }
  1208.  
  1209. if (!compareColor || compareColor.rgb === null || this.rgb === null) {
  1210. return false;
  1211. }
  1212.  
  1213. let allowedRgbDifference = 1;
  1214. let allowedAlphaDifference = 0.01;
  1215.  
  1216. if (allowSlightColorDifference === false) {
  1217. allowedRgbDifference = 0;
  1218. allowedAlphaDifference = 0;
  1219. }
  1220.  
  1221. const rgb = this.rgb;
  1222. const rgb2 = compareColor.rgb;
  1223.  
  1224. const rDiff = Math.abs(rgb2.r - rgb.r);
  1225. const gDiff = Math.abs(rgb2.g - rgb.g);
  1226. const bDiff = Math.abs(rgb2.b - rgb.b);
  1227. const aDiff = Math.abs(this.alpha - compareColor.alpha);
  1228.  
  1229. if (rDiff <= allowedRgbDifference && gDiff <= allowedRgbDifference && bDiff <= allowedRgbDifference && aDiff <= allowedAlphaDifference) {
  1230. return true;
  1231. }
  1232.  
  1233. return false;
  1234. }
  1235.  
  1236. /**
  1237. Calculates an array of lighter colors.
  1238.  
  1239. @type {Array<Coral.Color>}
  1240. @param {Number} amount
  1241. Amount of tint colors to generate.
  1242.  
  1243. */
  1244. calculateTintColors(amount) {
  1245. const tintColors = [];
  1246. let tintColor = null;
  1247.  
  1248. const rgb = this.rgb;
  1249. if (rgb) {
  1250. const r = rgb.r;
  1251. const g = rgb.g;
  1252. const b = rgb.b;
  1253.  
  1254. let tintFactor = 1;
  1255.  
  1256. for (let i = 1 ; i <= amount ; i++) {
  1257. tintFactor = i / (amount + 1);
  1258. tintColor = this.clone();
  1259. // alpha value kept from original
  1260. tintColor.rgb = {
  1261. r: r + (255 - r) * tintFactor,
  1262. g: g + (255 - g) * tintFactor,
  1263. b: b + (255 - b) * tintFactor
  1264. };
  1265.  
  1266. tintColors.push(tintColor);
  1267. }
  1268. }
  1269.  
  1270. return tintColors;
  1271. }
  1272.  
  1273. /**
  1274. Calculates an array of darker colors.
  1275.  
  1276. @type {Array<Coral.Color>}
  1277. @param {Number} amount
  1278. Amount of shade colors to generate.
  1279.  
  1280. */
  1281. calculateShadeColors(amount) {
  1282. const shadeColors = [];
  1283. let shadeColor = null;
  1284.  
  1285. const rgb = this.rgb;
  1286. if (rgb) {
  1287. const r = rgb.r;
  1288. const g = rgb.g;
  1289. const b = rgb.b;
  1290.  
  1291. let shadeFactor = 1;
  1292.  
  1293. for (let i = 1 ; i <= amount ; i++) {
  1294. shadeFactor = i / (amount + 1);
  1295. shadeColor = this.clone();
  1296. // alpha value kept from original
  1297. shadeColor.rgb = {
  1298. r: r * (1 - shadeFactor),
  1299. g: g * (1 - shadeFactor),
  1300. b: b * (1 - shadeFactor)
  1301. };
  1302.  
  1303. shadeColors.push(shadeColor);
  1304. }
  1305. }
  1306.  
  1307. return shadeColors;
  1308. }
  1309. }
  1310.  
  1311. export default Color;