Created
March 12, 2022 02:10
-
-
Save yokoishioka/8c4d084e0811bd4e685fabc57599685e to your computer and use it in GitHub Desktop.
Color Converter Service
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Injectable } from '@angular/core'; | |
import { ColorHsl, ColorRgb, ColorRgbCalculations } from './color'; | |
@Injectable({ | |
providedIn: 'root' | |
}) | |
export class ColorService { | |
hexToRgb(hex: string): ColorRgb { | |
hex.trim(); | |
hex = hex.replace('#', ''); | |
if (hex.length === 3) { | |
return { | |
red: this._hexToDecimal(hex[0]), | |
green: this._hexToDecimal(hex[1]), | |
blue: this._hexToDecimal(hex[2]) | |
} | |
} | |
return { | |
red: this._hexToDecimal(hex[0], hex[1]), | |
green: this._hexToDecimal(hex[2], hex[3]), | |
blue: this._hexToDecimal(hex[4], hex[5]) | |
}; | |
} | |
hexToHsl(hex: string): ColorHsl { | |
const rgb: ColorRgb = this.hexToRgb(hex); | |
return this.rgbToHsl(rgb.red, rgb.green, rgb.blue); | |
} | |
hslToRgb(hue: number, saturation: number, lightness: number): ColorRgb { | |
const sFraction: number = saturation / 100; | |
const lFraction: number = lightness / 100; | |
const chroma: number = (1 - Math.abs(2 * lFraction - 1)) * sFraction; | |
const second: number = chroma * (1 - Math.abs((hue / 60) % 2 - 1)); | |
const amount: number = lFraction - (chroma / 2); | |
let red: number = 0; | |
let green: number = 0; | |
let blue: number = 0; | |
if (0 <= hue && hue < 60) { | |
red = chroma; | |
green = second; | |
blue = 0; | |
} else if (60 <= hue && hue < 120) { | |
red = second; | |
green = chroma; | |
blue = 0; | |
} else if (120 <= hue && hue < 180) { | |
red = 0; | |
green = chroma; | |
blue = second; | |
} else if (180 <= hue && hue < 240) { | |
red = 0; | |
green = second; | |
blue = chroma; | |
} else if (240 <= hue && hue < 300) { | |
red = second; | |
green = 0; | |
blue = chroma; | |
} else if (300 <= hue && hue < 360) { | |
red = chroma; | |
green = 0; | |
blue = second; | |
} | |
return { | |
red: Math.round((red + amount) * 255), | |
green: Math.round((green + amount) * 255), | |
blue: Math.round((blue + amount) * 255) | |
}; | |
} | |
hslToHex(hue: number, saturation: number, lightness: number): string { | |
const rgb: ColorRgb = this.hslToRgb(hue, saturation, lightness); | |
return this.rgbToHex(rgb.red, rgb.green, rgb.blue); | |
} | |
hslValuesToString(hue: number, saturation: number, lightness: number, alpha?: number): string { | |
if (!alpha) return `hsl(${hue}, ${saturation}%, ${lightness}%)`; | |
return `hsl(${hue}, ${saturation}%, ${lightness}%, ${alpha})`; | |
} | |
hslStringToValues(hslString: string): ColorHsl | undefined { | |
const values: number[] | undefined = this.stringToValues(hslString); | |
if (!values) return; | |
return { | |
hue: values[0], | |
saturation: values[1], | |
lightness: values[2], | |
alpha: values[3] || undefined | |
} | |
} | |
rgbToHex(red: number, green: number, blue: number): string { | |
return `#${this._decimalToHex(red)}${this._decimalToHex(green)}${this._decimalToHex(blue)}`; | |
} | |
rgbToHsl(red: number, green: number, blue: number): ColorHsl { | |
const rgb: ColorRgbCalculations = this._buildRgbCalculations(red, green, blue); | |
const lightness: number = this._lightnessFromRgb(rgb); | |
return { | |
hue: this._hueFromRgb(rgb), | |
saturation: this._saturationFromRgb(rgb, lightness) * 100, | |
lightness: Math.round(lightness * 100), | |
}; | |
} | |
rgbValuesToString(red: number, green: number, blue: number, alpha?: number): string { | |
if (!alpha) return `rgb(${red}, ${green}, ${blue})`; | |
return `rgb(${red}, ${green}, ${blue}, ${alpha})`; | |
} | |
rgbStringToValues(rgbString: string): ColorRgb | undefined { | |
const values: number[] | undefined = this.stringToValues(rgbString); | |
if (!values) return; | |
return { | |
red: values[0], | |
green: values[1], | |
blue: values[2], | |
alpha: values[3] || undefined | |
} | |
} | |
stringToValues(colorString: string): number[] | undefined { | |
const pattern: RegExp = /(\d+)/g; | |
return colorString.match(pattern)?.map(value => +value); | |
} | |
private _hueFromRgb(rgb: ColorRgbCalculations): number { | |
if (rgb.delta === 0) return 0; | |
let hue: number; | |
switch (rgb.max) { | |
case rgb.red: | |
hue = ((rgb.green - rgb.blue) / rgb.delta) % 6; | |
break; | |
case rgb.green: | |
hue = ((rgb.blue - rgb.red) / rgb.delta) + 2; | |
break; | |
default: | |
hue = ((rgb.red - rgb.green) / rgb.delta) + 4; | |
break; | |
} | |
hue = Math.round(hue * 60); | |
return hue >= 0 ? hue : hue + 360 | |
} | |
private _lightnessFromRgb(rgb: ColorRgbCalculations): number { | |
return ((rgb.max + rgb.min) / 2); | |
} | |
private _saturationFromRgb(rgb: ColorRgbCalculations, lightness: number): number { | |
return rgb.delta === 0 ? rgb.delta : rgb.delta / (1 - Math.abs(2 * lightness - 1)); | |
} | |
private _buildRgbCalculations(red: number, green: number, blue: number): ColorRgbCalculations { | |
const rgbFractions: number[] = [red / 255, green / 255, blue / 255]; | |
const min: number = Math.min(...rgbFractions); | |
const max: number = Math.max(...rgbFractions); | |
const delta: number = max - min; | |
return { | |
red: rgbFractions[0], | |
green: rgbFractions[1], | |
blue: rgbFractions[2], | |
min: min, | |
max: max, | |
delta: delta | |
}; | |
} | |
private _decimalToHex(decimal: number): string { | |
const hex: string = decimal.toString(16); | |
return hex.length === 1 ? `0${hex}` : hex; | |
} | |
private _hexToDecimal(hex1: string, hex2?: string): number { | |
return !hex2 ? +(`0x${hex1}${hex1}`) : +(`0x${hex1}${hex2}`); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment