colors.ts 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import { clamp } from "./math.js";
  2. /** Converts a hex color string to a tuple of RGB values */
  3. export function hexToRgb(hex: string): [red: number, green: number, blue: number] {
  4. hex = hex.trim();
  5. const bigint = parseInt(hex.startsWith("#") ? hex.slice(1) : hex, 16);
  6. const r = (bigint >> 16) & 255;
  7. const g = (bigint >> 8) & 255;
  8. const b = (bigint & 255);
  9. return [clamp(r, 0, 255), clamp(g, 0, 255), clamp(b, 0, 255)];
  10. }
  11. /** Converts RGB values to a hex color string */
  12. export function rgbToHex(red: number, green: number, blue: number, withHash = true, upperCase = false): string {
  13. const toHexVal = (n: number) =>
  14. clamp(Math.round(n), 0, 255).toString(16).padStart(2, "0")[(upperCase ? "toUpperCase" : "toLowerCase")]();
  15. return `${withHash ? "#" : ""}${toHexVal(red)}${toHexVal(green)}${toHexVal(blue)}`;
  16. }
  17. /**
  18. * Lightens a CSS color value (in hex, RGB or RGBA format) by a given percentage.
  19. * Will not exceed the maximum range (00-FF or 0-255).
  20. * @returns Returns the new color value in the same format as the input
  21. * @throws Throws if the color format is invalid or not supported
  22. */
  23. export function lightenColor(color: string, percent: number): string {
  24. return darkenColor(color, percent * -1);
  25. }
  26. /**
  27. * Darkens a CSS color value (in hex, RGB or RGBA format) by a given percentage.
  28. * Will not exceed the maximum range (00-FF or 0-255).
  29. * @returns Returns the new color value in the same format as the input
  30. * @throws Throws if the color format is invalid or not supported
  31. */
  32. export function darkenColor(color: string, percent: number): string {
  33. color = color.trim();
  34. const darkenRgb = (r: number, g: number, b: number, percent: number): [number, number, number] => {
  35. r = Math.max(0, Math.min(255, r - (r * percent / 100)));
  36. g = Math.max(0, Math.min(255, g - (g * percent / 100)));
  37. b = Math.max(0, Math.min(255, b - (b * percent / 100)));
  38. return [r, g, b];
  39. };
  40. let r: number, g: number, b: number, a: number | undefined;
  41. const isHexCol = color.match(/^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/);
  42. if(isHexCol)
  43. [r, g, b] = hexToRgb(color);
  44. else if(color.startsWith("rgb")) {
  45. const rgbValues = color.match(/\d+(\.\d+)?/g)?.map(Number);
  46. if (rgbValues)
  47. [r, g, b, a] = rgbValues as [number, number, number, number?];
  48. else
  49. throw new Error("Invalid RGB/RGBA color format");
  50. }
  51. else
  52. throw new Error("Unsupported color format");
  53. [r, g, b] = darkenRgb(r, g, b, percent);
  54. const upperCase = color.match(/[A-F]/) !== null;
  55. if(isHexCol)
  56. return rgbToHex(r, g, b, color.startsWith("#"), upperCase);
  57. else if(color.startsWith("rgba"))
  58. return `rgba(${r}, ${g}, ${b}, ${a})`;
  59. else if(color.startsWith("rgb"))
  60. return `rgb(${r}, ${g}, ${b})`;
  61. else
  62. throw new Error("Unsupported color format");
  63. }