Commit 54eb255a authored by Zéfling's avatar Zéfling 🎨

Make it possible to change color by parameters

- Update interface to test this.
- Best support of HSL/HSV
parent c1263c22
......@@ -7,8 +7,19 @@ const pattern = {
hsva: /^hsla?\(([\d]*(\.[\d]+)?)(,?\s*|\s+)([\d]*(\.[\d]+)?)\%(,?\s*|\s+)([\d]*(\.[\d]+)?)\%((,\s*)([\d]*(\.[\d]+)?))?\)$/i
};
export interface RGB { r: number; g: number; b: number; }
export interface HSV { h: number; s: number; v: number; }
export interface ColorData {
r?: number; g?: number; b?: number;
h?: number; s?: number; v?: number;
color?: string; luminosity?: number;
}
export class Coloration {
/**
* CSS color list
*/
static colorsName: any = {
// CSS 1
black: '#000000', silver: '#c0c0c0', gray: '#808080', white: '#ffffff', maroon: '#800000', red: '#ff0000',
......@@ -51,48 +62,121 @@ export class Coloration {
};
private intColor: number;
private rgb: RGB = { r: 0, g: 0, b: 0 };
private hsv: HSV = { h: 0, s: 0, v: 0 };
constructor(public color: string) {
this.intColor = this.parseColor(color);
}
/**
* HEX color
*/
getColor() {
return this.intRbgToString(this.intColor);
this.updateColor();
}
/**
* change the luminosity of a color
* @param lum value between -1 and 1
*/
changeLuminosity(lum: number): string {
lum = Math.min(Math.max(lum || 0, -1), 1);
return this.maskColor(lum < 0 ? '#000' : '#FFF', Math.abs(lum));
changeLuminosity(lum: number): Coloration {
lum = this.minmax(lum, -1, 1);
this.maskColor(lum < 0 ? '#000' : '#FFF', Math.abs(lum));
return this;
}
/**
* add color with a mark
* @param color additional color
* @param opacity value of opacity between 0 and 1 for the additional color
* @returns hexa color
*/
maskColor(color: string, opacity: number = 1): string {
maskColor(color: string, opacity: number = 1): Coloration {
const baseColor = this.parseColor(this.color);
const baseColor = this.intColor;
const additionalColor = this.parseColor(color);
const lum = Math.min(Math.max(opacity || 0, 0), 1);
const lum = this.minmax(opacity, 0, 1);
const R = baseColor >> 16;
const G = baseColor >> 8 & 0x00FF;
const B = baseColor & 0x0000FF;
return this.intRbgToString(this.rgbToInt(
this.intColor = this.rgbToInt(
Math.round(((additionalColor >> 16) - R) * lum) + R,
Math.round(((additionalColor >> 8 & 0x00FF) - G) * lum) + G,
Math.round(((additionalColor & 0x0000FF) - B) * lum) + B
));
);
this.updateColor();
return this;
}
/**
* change color with color parameters
* @param colorData additionnal parameters
*/
addColor(colorData: ColorData) {
if (colorData.luminosity) {
this.changeLuminosity(colorData.luminosity);
}
if (colorData.r || colorData.g || colorData.b) {
if (colorData.r) {
console.log(this.rgb.r, colorData.r, this.rgb.r + colorData.r);
this.rgb.r = this.minmax(this.rgb.r + (+colorData.r), 0, 255);
console.log(this.rgb.r);
}
if (colorData.g) {
this.rgb.g = this.minmax(this.rgb.g + (+colorData.g), 0, 255);
}
if (colorData.b) {
this.rgb.r = this.minmax(this.rgb.b + (+colorData.b), 0, 255);
}
this.intColor = this.rgbToInt(this.rgb.r, this.rgb.g, this.rgb.b);
this.updateColor();
}
if (colorData.h || colorData.s || colorData.v) {
if (colorData.r) {
this.hsv.h = (this.hsv.h + (+colorData.h) + 360) % 360;
}
if (colorData.s) {
this.hsv.s = this.minmax(this.hsv.s + (+colorData.s), 0, 100);
}
if (colorData.v) {
this.hsv.v = this.minmax(this.hsv.v + (+colorData.v), 0, 100);
}
this.intColor = this.hsvToInt(this.hsv.h, this.hsv.s, this.hsv.v);
this.updateColor();
}
return this;
}
/**
* color in #HEX format
*/
toHEX(): string {
return '#' + (0x1000000 + this.intColor).toString(16).slice(1);
}
/**
* color in HVL() format
*/
toHSL(): string {
return `hsl(${this.hsv.h}, ${this.hsv.s}%, ${this.hsv.v}%)`;
}
/**
* color in RGB() format
*/
toRGB(): string {
return `rgb(${this.rgb.r}, ${this.rgb.g}, ${this.rgb.b})`;
}
private updateColor() {
// update RGB
this.rgb.r = this.intColor >> 16;
this.rgb.g = this.intColor >> 8 & 0x00FF;
this.rgb.b = this.intColor & 0x0000FF;
// update HSL
this.hsv = this.rgb2hsv(this.rgb.r, this.rgb.g, this.rgb.b);
}
/**
......@@ -130,7 +214,7 @@ export class Coloration {
// validate hsv() / hsva()
const matchHsv = String(color).match(pattern.hsva);
if (matchHsv) {
intColor = this.hsv(parseInt(matchHsv[1], 10), parseInt(matchHsv[4], 10), parseInt(matchHsv[7], 10));
intColor = this.hsvToInt(parseInt(matchHsv[1], 10), parseInt(matchHsv[4], 10), parseInt(matchHsv[7], 10));
}
}
......@@ -146,7 +230,7 @@ export class Coloration {
* @returns int RGB
* @see https://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
*/
private hsv(hue: number, saturation: number, value: number): number {
private hsvToInt(hue: number, saturation: number, value: number): number {
saturation = Math.max(0, saturation);
const s = saturation / 100;
......@@ -174,12 +258,64 @@ export class Coloration {
return p;
}
/**
* Convert RGB to HSV
* @param r [0, 255]
* @param g [0, 255]
* @param b [0, 255]
* @see https://stackoverflow.com/questions/39118528/rgb-to-hsl-conversion
* @see https://en.wikipedia.org/wiki/HSL_and_HSV#Formal_derivation
*/
private rgb2hsv(r: number, g: number, b: number): HSV {
// convert r,g,b [0,255] range to [0,1]
r = r / 255;
g = g / 255;
b = b / 255;
// get the min and max of r,g,b
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
// lightness is the average of the largest and smallest color components
const val = (max + min) / 2;
let hue: number;
let sat: number;
if (max === min) { // no saturation
hue = 0;
sat = 0;
} else {
const c = max - min; // chroma
// saturation is simply the chroma scaled to fill
// the interval [0, 1] for every combination of hue and lightness
sat = c / (1 - Math.abs(2 * val - 1));
switch (max) {
case r:
hue = (g - b) / c + (g < b ? 6 : 0);
break;
case g:
hue = (b - r) / c + 2;
break;
case b:
hue = (r - g) / c + 4;
break;
}
}
return {
h: Math.round(hue * 60), // °
s: Math.round(sat * 100), // %
v: Math.round(val * 100) // %
};
}
private rgbToInt(r: number, g: number, b: number): number {
return r * 0x10000 + g * 0x100 + b;
}
private intRbgToString(int: number) {
return '#' + (0x1000000 + int).toString(16).slice(1);
private minmax(value: number, min: number, max: number, defaultValue = 0) {
return Math.min(Math.max(value || defaultValue, min), max);
}
}
<!--The content below is only a placeholder and can be replaced.-->
<div>
<h1>
Welcome to {{ title }}!
</h1>
</div>
<h1>{{ title }}</h1>
<h2>Test to change color: </h2>
<div>
Color : <input type="color"
[value]="color"
(change)="changeColor($event)">
<main>
<form>
<fieldset>
<legend>Base</legend>
<label>Color : <input type="text"
[(value)]="baseColor"
(change)="baseColor = $event.target.value"></label>
<div [style.background-color]="baseColor">&nbsp;</div>
Luminosity : <input type="number"
max="1"
min="-1"
step="0.01"
[value]="luminosity"
(keyup)="changeLuminosity($event)"
(change)="changeLuminosity($event)">
</div>
<hr>
<div>Color : {{color}}</div>
<div>ColorFinal : {{colorFinal}}</div>
<div [style.background-color]="colorFinal">&nbsp;</div>
<hr>
<div>ColorRgb : {{colorRgb}} -> {{colorRgbHex}}</div>
<div [style.background-color]="colorRgb">&nbsp;</div>
<div [style.background-color]="colorRgbHex">&nbsp;</div>
<hr>
<div>ColorHsv : {{colorHsv}} -> {{colorHsvHex}}</div>
<div [style.background-color]="colorHsv">&nbsp;</div>
<div [style.background-color]="colorHsvHex">&nbsp;</div>
\ No newline at end of file
</fieldset>
<fieldset>
<legend>Additionnal</legend>
<div class="additionnal">
<div class="luminosity">
<label> <span>Luminosity:</span><input type="number"
min="-1.00"
max="1.00"
step="0.01"
[value]="luminosity"
(change)="luminosity = $event.target.value"></label>
</div>
<div class="red">
<label><span>Red:</span><input type="number"
min="-255"
max="256"
step="1"
[value]="red"
(change)="red = $event.target.value"></label>
</div>
<div class="green">
<label><span>Green:</span><input type="number"
min="-255"
max="256"
step="1"
[value]="green"
(change)="green = $event.target.value"></label>
</div>
<div class="blue">
<label><span>Blue:</span><input type="number"
min="-255"
max="256"
step="1"
[value]="blue"
(change)="blue = $event.target.value"></label>
</div>
<div class="hue">
<label><span>Hue (°):</span><input type="number"
max="-360"
max="360"
step="1"
[value]="hue"
(change)="hue = $event.target.value"></label>
</div>
<div class="saturation">
<label><span>Saluration (%):</span><input type="number"
min="-255"
max="256"
step="1"
[value]="saturation"
(change)="saturation = $event.target.value"></label>
</div>
<div class="value">
<label><span>Value (%):</span><input type="number"
min="-100"
max="100"
step="1"
[value]="value"
(change)="value = $event.target.value"></label>
</div>
</div>
<div>
<button type="button"
(click)="change()">Change</button>
</div>
</fieldset>
</form>
<aside>
<div>
HEX : {{colorHex}}
<div [style.background-color]="colorHex">&nbsp;</div>
</div>
<div>
RGB : {{colorRgb}}
<div [style.background-color]="colorRgb">&nbsp;</div>
</div>
<div>
HSL : {{colorHsl}}
<div [style.background-color]="colorHsl">&nbsp;</div>
</div>
</aside>
</main>
\ No newline at end of file
main {
display: flex;
}
form {
flex: 2;
}
aside {
flex: 1;
padding: 0 15px
}
.additionnal {
display: grid;
width: 100%;
grid-template:
"lum . . " 35px
"red green blue " 35px
"hue sat value" 35px / 1fr 1fr 1fr;
label {
display:flex;
padding: 0 15px;
align-items: center;
}
span {
flex: 1;
}
}
.luminosity {
grid-area: lum;
}
.red { grid-area: red;}
.green { grid-area: green;}
.blue { grid-area: blue;}
.hue {grid-area: hue;}
.saturation { grid-area: sat;}
.value{ grid-area: value;}
\ No newline at end of file
......@@ -9,40 +9,40 @@ import { Coloration } from 'projects/coloration/src/public_api';
export class AppComponent {
title = 'coloration-demo';
color = '#FF0000';
colorRgb = 'rgb(50, 75, 255)';
colorRgbHex: string;
colorHsv = 'hsl(300, 50%, 70%)';
colorHsvHex: string;
baseColor = '';
luminosity = 0;
luminosity = 0.00;
red = 0;
green = 0;
blue = 0;
hue = 0;
saturation = 0;
value = 0;
colorFinal: string;
colorHex: string;
colorRgb: string;
colorHsl: string;
constructor() {
this.calculColor();
const colorRgb = new Coloration(this.colorRgb);
this.colorRgbHex = colorRgb.getColor();
const colorHsv = new Coloration(this.colorHsv);
this.colorHsvHex = colorHsv.getColor();
}
changeColor(event: Event) {
this.color = (event.target as any).value;
this.calculColor();
}
changeLuminosity(event: Event) {
this.luminosity = parseFloat((event.target as any).value);
this.calculColor();
}
calculColor() {
const color = new Coloration(this.color);
this.colorFinal = color.changeLuminosity(this.luminosity);
change() {
const color = new Coloration(this.baseColor);
color.addColor({
r: this.red,
g: this.green,
b: this.blue,
h: this.hue,
s: this.saturation,
v: this.value,
luminosity: this.luminosity
});
console.log(color);
this.colorHex = color.toHEX();
this.colorHsl = color.toHSL();
this.colorRgb = color.toRGB();
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment