convert.ts 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. import { round } from "./round";
  2. import { RgbaColor, RgbColor, HslaColor, HslColor, HsvaColor, HsvColor } from "../interface";
  3. /**
  4. * Valid CSS <angle> units.
  5. * https://developer.mozilla.org/en-US/docs/Web/CSS/angle
  6. */
  7. /**
  8. * Referrer from https://github.com/web-padawan/vanilla-colorful/blob/master/src/lib/utils/convert.ts
  9. */
  10. const angleUnits: Record<string, number> = {
  11. grad: 360 / 400,
  12. turn: 360,
  13. rad: 360 / (Math.PI * 2),
  14. };
  15. export const hexToHsva = (hex: string): HsvaColor => rgbaToHsva(hexToRgba(hex));
  16. export const hexToRgba = (hex: string): RgbaColor => {
  17. if (hex[0] === "#") hex = hex.substring(1);
  18. const hexToPercent = (str: string) => {
  19. const decimal = parseInt(str, 16);
  20. if (!isNaN(decimal)) {
  21. const percent = decimal / 255;
  22. return percent;
  23. }
  24. return 1;
  25. };
  26. return {
  27. r: parseInt(hex.substring(0, 2), 16),
  28. g: parseInt(hex.substring(2, 4), 16),
  29. b: parseInt(hex.substring(4, 6), 16),
  30. a: hexToPercent(hex.substring(6, 8)),
  31. };
  32. };
  33. export const parseHue = (value: string, unit = "deg"): number => {
  34. return Number(value) * (angleUnits[unit] || 1);
  35. };
  36. export const hslaStringToHsva = (hslString: string): HsvaColor => {
  37. const matcher = /hsla?\(?\s*(-?\d*\.?\d+)(deg|rad|grad|turn)?[,\s]+(-?\d*\.?\d+)%?[,\s]+(-?\d*\.?\d+)%?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i;
  38. const match = matcher.exec(hslString);
  39. if (!match) return { h: 0, s: 0, v: 0, a: 1 };
  40. return hslaToHsva({
  41. h: parseHue(match[1], match[2]),
  42. s: Number(match[3]),
  43. l: Number(match[4]),
  44. a: match[5] === undefined ? 1 : Number(match[5]) / (match[6] ? 100 : 1),
  45. });
  46. };
  47. export const hslStringToHsva = hslaStringToHsva;
  48. export const hslaToHsva = ({ h, s, l, a }: HslaColor): HsvaColor => {
  49. s *= (l < 50 ? l : 100 - l) / 100;
  50. return {
  51. h: h,
  52. s: s > 0 ? ((2 * s) / (l + s)) * 100 : 0,
  53. v: l + s,
  54. a,
  55. };
  56. };
  57. export const hsvaToHex = (hsva: HsvaColor): string => rgbaToHex(hsvaToRgba(hsva));
  58. export const hsvaToHsla = ({ h, s, v, a }: HsvaColor): HslaColor => {
  59. const hh = ((200 - s) * v) / 100;
  60. return {
  61. h: round(h),
  62. s: round(hh > 0 && hh < 200 ? ((s * v) / 100 / (hh <= 100 ? hh : 200 - hh)) * 100 : 0),
  63. l: round(hh / 2),
  64. a: round(a, 2),
  65. };
  66. };
  67. export const hsvaToHslString = (hsva: HsvaColor): string => {
  68. const { h, s, l } = hsvaToHsla(hsva);
  69. return `hsl(${h}, ${s}%, ${l}%)`;
  70. };
  71. export const hsvaToHsvString = (hsva: HsvaColor): string => {
  72. const { h, s, v } = roundHsva(hsva);
  73. return `hsv(${h}, ${s}%, ${v}%)`;
  74. };
  75. export const hsvaToHsvaString = (hsva: HsvaColor): string => {
  76. const { h, s, v, a } = roundHsva(hsva);
  77. return `hsva(${h}, ${s}%, ${v}%, ${a})`;
  78. };
  79. export const hsvaToHslaString = (hsva: HsvaColor): string => {
  80. const { h, s, l, a } = hsvaToHsla(hsva);
  81. return `hsla(${h}, ${s}%, ${l}%, ${a})`;
  82. };
  83. export const hsvaToRgba = ({ h, s, v, a }: HsvaColor): RgbaColor => {
  84. h = (h / 360) * 6;
  85. s = s / 100;
  86. v = v / 100;
  87. const hh = Math.floor(h),
  88. b = v * (1 - s),
  89. c = v * (1 - (h - hh) * s),
  90. d = v * (1 - (1 - h + hh) * s),
  91. module = hh % 6;
  92. return {
  93. r: round([v, c, b, b, d, v][module] * 255),
  94. g: round([d, v, v, c, b, b][module] * 255),
  95. b: round([b, b, d, v, v, c][module] * 255),
  96. a: round(a, 2),
  97. };
  98. };
  99. export const hsvaToRgbString = (hsva: HsvaColor): string => {
  100. const { r, g, b } = hsvaToRgba(hsva);
  101. return `rgb(${r}, ${g}, ${b})`;
  102. };
  103. export const hsvaToRgbaString = (hsva: HsvaColor): string => {
  104. const { r, g, b, a } = hsvaToRgba(hsva);
  105. return `rgba(${r}, ${g}, ${b}, ${a})`;
  106. };
  107. export const hsvaStringToHsva = (hsvString: string): HsvaColor => {
  108. const matcher = /hsva?\(?\s*(-?\d*\.?\d+)(deg|rad|grad|turn)?[,\s]+(-?\d*\.?\d+)%?[,\s]+(-?\d*\.?\d+)%?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i;
  109. const match = matcher.exec(hsvString);
  110. if (!match) return { h: 0, s: 0, v: 0, a: 1 };
  111. return roundHsva({
  112. h: parseHue(match[1], match[2]),
  113. s: Number(match[3]),
  114. v: Number(match[4]),
  115. a: match[5] === undefined ? 1 : Number(match[5]) / (match[6] ? 100 : 1),
  116. });
  117. };
  118. export const hsvStringToHsva = hsvaStringToHsva;
  119. export const rgbaStringToHsva = (rgbaString: string): HsvaColor => {
  120. return rgbaToHsva(rgbaStringToRgba(rgbaString));
  121. };
  122. export const rgbaStringToRgba = (rgbaString: string): RgbaColor => {
  123. const matcher = /rgba?\(?\s*(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i;
  124. const match = matcher.exec(rgbaString);
  125. if (!match) return { r: 0, g: 0, b: 0, a: 1 };
  126. return {
  127. r: Number(match[1]) / (match[2] ? 100 / 255 : 1),
  128. g: Number(match[3]) / (match[4] ? 100 / 255 : 1),
  129. b: Number(match[5]) / (match[6] ? 100 / 255 : 1),
  130. a: match[7] === undefined ? 1 : Number(match[7]) / (match[8] ? 100 : 1),
  131. };
  132. };
  133. export const rgbStringToRgba = rgbaStringToRgba;
  134. export const rgbStringToHsva = rgbaStringToHsva;
  135. const format = (number: number) => {
  136. const hex = number.toString(16);
  137. return hex.length < 2 ? "0" + hex : hex;
  138. };
  139. export const rgbaToHex = ({ r, g, b, a }: RgbaColor): string => {
  140. const percentToHex = (p) => {
  141. //const percent = Math.max(0, Math.min(100, p)); // bound percent from 0 to 100
  142. const intValue = Math.round(p / 100 * 255); // map percent to nearest integer (0 - 255)
  143. const hexValue = intValue.toString(16); // get hexadecimal representation
  144. return hexValue.padStart(2, '0').toLowerCase(); // format with leading 0 and upper case characters
  145. };
  146. if (a === undefined || a === 1) {
  147. return "#" + format(r) + format(g) + format(b);
  148. } else {
  149. return "#" + format(r) + format(g) + format(b) + percentToHex(a * 100);
  150. }
  151. };
  152. export const rgbaToHsva = ({ r, g, b, a }: RgbaColor): HsvaColor => {
  153. const max = Math.max(r, g, b);
  154. const delta = max - Math.min(r, g, b);
  155. // prettier-ignore
  156. const hh = delta
  157. ? max === r
  158. ? (g - b) / delta
  159. : max === g
  160. ? 2 + (b - r) / delta
  161. : 4 + (r - g) / delta
  162. : 0;
  163. return {
  164. h: round(60 * (hh < 0 ? hh + 6 : hh)),
  165. s: round(max ? (delta / max) * 100 : 0),
  166. v: round((max / 255) * 100),
  167. a,
  168. };
  169. };
  170. export const roundHsva = (hsva: HsvaColor): HsvaColor => ({
  171. h: round(hsva.h),
  172. s: round(hsva.s),
  173. v: round(hsva.v),
  174. a: round(hsva.a, 2),
  175. });
  176. export const rgbaToRgb = ({ r, g, b }: RgbaColor): RgbColor => ({ r, g, b });
  177. export const hslaToHsl = ({ h, s, l }: HslaColor): HslColor => ({ h, s, l });
  178. export const hsvaToHsv = (hsva: HsvaColor): HsvColor => {
  179. const { h, s, v } = roundHsva(hsva);
  180. return { h, s, v };
  181. };