-
Notifications
You must be signed in to change notification settings - Fork 30.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Derive default color if not provided
- Loading branch information
Showing
4 changed files
with
441 additions
and
60 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
|
||
import * as Object from 'vs/base/common/objects'; | ||
|
||
export interface RGBA { r: number; g: number; b: number; a: number; } | ||
export interface HSLA { h: number; s: number; l: number; a: number; } | ||
|
||
/** | ||
* Converts an Hex color value to RGB. | ||
* returns r, g, and b are contained in the set [0, 255] | ||
*/ | ||
function hex2rgba(hex: string): RGBA { | ||
function parseHex(str: string) { | ||
return parseInt('0x' + str); | ||
} | ||
if (hex.charAt(0) === '#' && hex.length >= 7) { | ||
let r = parseHex(hex.substr(1, 2)); | ||
let g = parseHex(hex.substr(3, 2)); | ||
let b = parseHex(hex.substr(5, 2)); | ||
let a = hex.length === 9 ? parseHex(hex.substr(7, 2)) / 0xff : 1; | ||
return { r, g, b, a }; | ||
} | ||
return { r: 255, g: 0, b: 0, a: 1 }; | ||
} | ||
|
||
/** | ||
* Converts an RGB color value to HSL. Conversion formula | ||
* adapted from http://en.wikipedia.org/wiki/HSL_color_space. | ||
* Assumes r, g, and b are contained in the set [0, 255] and | ||
* returns h in the set [0, 360], s, and l in the set [0, 1]. | ||
*/ | ||
function rgba2hsla(rgba: RGBA): HSLA { | ||
let r = rgba.r / 255; | ||
let g = rgba.g / 255; | ||
let b = rgba.b / 255; | ||
let a = rgba.a === void 0 ? rgba.a : 1; | ||
|
||
let max = Math.max(r, g, b), min = Math.min(r, g, b); | ||
let h = 0, s = 0, l = Math.round(((min + max) / 2) * 1000) / 1000, chroma = max - min; | ||
|
||
if (chroma > 0) { | ||
s = Math.min(Math.round((l <= 0.5 ? chroma / (2 * l) : chroma / (2 - (2 * l))) * 1000) / 1000, 1); | ||
switch (max) { | ||
case r: h = (g - b) / chroma + (g < b ? 6 : 0); break; | ||
case g: h = (b - r) / chroma + 2; break; | ||
case b: h = (r - g) / chroma + 4; break; | ||
} | ||
h *= 60; | ||
h = Math.round(h); | ||
} | ||
return { h, s, l, a }; | ||
} | ||
|
||
/** | ||
* Converts an HSL color value to RGB. Conversion formula | ||
* adapted from http://en.wikipedia.org/wiki/HSL_color_space. | ||
* Assumes h in the set [0, 360] s, and l are contained in the set [0, 1] and | ||
* returns r, g, and b in the set [0, 255]. | ||
*/ | ||
function hsla2rgba(hsla: HSLA): RGBA { | ||
let h = hsla.h / 360; | ||
let s = Math.min(hsla.s, 1); | ||
let l = Math.min(hsla.l, 1); | ||
let a = hsla.a === void 0 ? hsla.a : 1; | ||
let r, g, b; | ||
|
||
if (s === 0) { | ||
r = g = b = l; // achromatic | ||
} else { | ||
let hue2rgb = function hue2rgb(p, q, t) { | ||
if (t < 0) { | ||
t += 1; | ||
} | ||
if (t > 1) { | ||
t -= 1; | ||
} | ||
if (t < 1 / 6) { | ||
return p + (q - p) * 6 * t; | ||
} | ||
if (t < 1 / 2) { | ||
return q; | ||
} | ||
if (t < 2 / 3) { | ||
return p + (q - p) * (2 / 3 - t) * 6; | ||
} | ||
return p; | ||
}; | ||
|
||
let q = l < 0.5 ? l * (1 + s) : l + s - l * s; | ||
let p = 2 * l - q; | ||
r = hue2rgb(p, q, h + 1 / 3); | ||
g = hue2rgb(p, q, h); | ||
b = hue2rgb(p, q, h - 1 / 3); | ||
} | ||
|
||
return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255), a }; | ||
} | ||
|
||
export class Color { | ||
|
||
private rgba: RGBA; | ||
private hsla: HSLA; | ||
private str: string; | ||
|
||
constructor(arg: string | RGBA) { | ||
this.rgba = typeof arg === 'string' ? hex2rgba(arg) : <RGBA>arg; | ||
this.str = null; | ||
} | ||
|
||
/** | ||
* http://www.w3.org/TR/WCAG20/#relativeluminancedef | ||
* Returns the number in the set [0, 1]. O => Darkest Black. 1 => Lightest white. | ||
*/ | ||
public getLuminosity(): number { | ||
let luminosityFor = function (color): number { | ||
let c = color / 255; | ||
return (c <= 0.03928) ? c / 12.92 : Math.pow(((c + 0.055) / 1.055), 2.4); | ||
}; | ||
let R = luminosityFor(this.rgba.r); | ||
let G = luminosityFor(this.rgba.g); | ||
let B = luminosityFor(this.rgba.b); | ||
let luminosity = 0.2126 * R + 0.7152 * G + 0.0722 * B; | ||
return Math.round(luminosity * 10000) / 10000; | ||
} | ||
|
||
/** | ||
* http://www.w3.org/TR/WCAG20/#contrast-ratiodef | ||
* Returns the contrast ration number in the set [1, 21]. | ||
*/ | ||
public getContrast(another: Color): number { | ||
let lum1 = this.getLuminosity(); | ||
let lum2 = another.getLuminosity(); | ||
return lum1 > lum2 ? (lum1 + 0.05) / (lum2 + 0.05) : (lum2 + 0.05) / (lum1 + 0.05); | ||
} | ||
|
||
/** | ||
* http://24ways.org/2010/calculating-color-contrast | ||
* Return 'true' if darker color otherwise 'false' | ||
*/ | ||
public isDarker(): boolean { | ||
var yiq = (this.rgba.r * 299 + this.rgba.g * 587 + this.rgba.b * 114) / 1000; | ||
return yiq < 128; | ||
} | ||
|
||
/** | ||
* http://24ways.org/2010/calculating-color-contrast | ||
* Return 'true' if lighter color otherwise 'false' | ||
*/ | ||
public isLighter(): boolean { | ||
var yiq = (this.rgba.r * 299 + this.rgba.g * 587 + this.rgba.b * 114) / 1000; | ||
return yiq >= 128; | ||
} | ||
|
||
public isLighterThan(another: Color): boolean { | ||
let lum1 = this.getLuminosity(); | ||
let lum2 = another.getLuminosity(); | ||
return lum1 > lum2; | ||
} | ||
|
||
public isDarkerThan(another: Color): boolean { | ||
let lum1 = this.getLuminosity(); | ||
let lum2 = another.getLuminosity(); | ||
return lum1 < lum2; | ||
} | ||
|
||
public lighten(factor: number): Color { | ||
let hsl = this.toHSLA(); | ||
hsl.l += hsl.l * factor; | ||
return new Color(hsla2rgba(hsl)); | ||
} | ||
|
||
public darken(factor: number): Color { | ||
let hsl = this.toHSLA(); | ||
hsl.l -= hsl.l * factor; | ||
return new Color(hsla2rgba(hsl)); | ||
} | ||
|
||
public transparent(factor: number): Color { | ||
let p = this.rgba; | ||
return new Color({ r: p.r, g: p.g, b: p.b, a: p.a * factor }); | ||
} | ||
|
||
public opposite(): Color { | ||
return new Color({ | ||
r: 255 - this.rgba.r, | ||
g: 255 - this.rgba.g, | ||
b: 255 - this.rgba.b, | ||
a: this.rgba.a | ||
}); | ||
} | ||
|
||
public toString(): string { | ||
if (!this.str) { | ||
let p = this.rgba; | ||
this.str = `rgba(${p.r}, ${p.g}, ${p.b}, ${+p.a.toFixed(2)})`; | ||
} | ||
return this.str; | ||
} | ||
|
||
public toHSLA(): HSLA { | ||
if (!this.hsla) { | ||
this.hsla = rgba2hsla(this.rgba); | ||
} | ||
return Object.clone(this.hsla); | ||
} | ||
|
||
public toRGBA(): RGBA { | ||
return Object.clone(this.rgba); | ||
} | ||
|
||
public static fromRGBA(rgba: RGBA): Color { | ||
return new Color(rgba); | ||
} | ||
|
||
public static fromHex(hex: string): Color { | ||
return new Color(hex); | ||
} | ||
|
||
public static fromHSLA(hsla: HSLA): Color { | ||
return new Color(hsla2rgba(hsla)); | ||
} | ||
|
||
public static getLighterColor(of: Color, relative: Color, factor?: number): Color { | ||
if (of.isLighterThan(relative)) { | ||
return of; | ||
} | ||
factor = factor ? factor : 0.5; | ||
let lum1 = of.getLuminosity(), lum2 = relative.getLuminosity(); | ||
factor = factor * (lum2 - lum1) / lum2; | ||
return of.lighten(factor); | ||
} | ||
|
||
public static getDarkerColor(of: Color, relative: Color, factor?: number): Color { | ||
if (of.isDarkerThan(relative)) { | ||
return of; | ||
} | ||
factor = factor ? factor : 0.5; | ||
let lum1 = of.getLuminosity(), lum2 = relative.getLuminosity(); | ||
factor = factor * (lum1 - lum2) / lum1; | ||
return of.darken(factor); | ||
} | ||
} |
Oops, something went wrong.
5c70d8d
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ref #1636