convert.ts 6.6 KB

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