-
Notifications
You must be signed in to change notification settings - Fork 783
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(color): add color channel values and luminosity, saturation, clip functions #4366
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -13,6 +13,10 @@ const hslRegex = /hsl\(\s*([\d.]+)(rad|turn)/; | |||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||
export default class Color { | ||||||||||||||||||||||||||
constructor(red, green, blue, alpha = 1) { | ||||||||||||||||||||||||||
if (red instanceof Color) { | ||||||||||||||||||||||||||
({ red, green, blue, alpha } = red); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** @type {number} */ | ||||||||||||||||||||||||||
this.red = red; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
@@ -34,9 +38,9 @@ export default class Color { | |||||||||||||||||||||||||
* @return {string} | ||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||
toHexString() { | ||||||||||||||||||||||||||
var redString = Math.round(this.red).toString(16); | ||||||||||||||||||||||||||
var greenString = Math.round(this.green).toString(16); | ||||||||||||||||||||||||||
var blueString = Math.round(this.blue).toString(16); | ||||||||||||||||||||||||||
const redString = Math.round(this.red).toString(16); | ||||||||||||||||||||||||||
const greenString = Math.round(this.green).toString(16); | ||||||||||||||||||||||||||
const blueString = Math.round(this.blue).toString(16); | ||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||
'#' + | ||||||||||||||||||||||||||
(this.red > 15.5 ? redString : '0' + redString) + | ||||||||||||||||||||||||||
|
@@ -137,19 +141,174 @@ export default class Color { | |||||||||||||||||||||||||
* @return {number} The luminance value, ranges from 0 to 1 | ||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||
getRelativeLuminance() { | ||||||||||||||||||||||||||
var rSRGB = this.red / 255; | ||||||||||||||||||||||||||
var gSRGB = this.green / 255; | ||||||||||||||||||||||||||
var bSRGB = this.blue / 255; | ||||||||||||||||||||||||||
const { red: rSRGB, green: gSRGB, blue: bSRGB } = this.normalize(); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
var r = | ||||||||||||||||||||||||||
const r = | ||||||||||||||||||||||||||
rSRGB <= 0.03928 ? rSRGB / 12.92 : Math.pow((rSRGB + 0.055) / 1.055, 2.4); | ||||||||||||||||||||||||||
var g = | ||||||||||||||||||||||||||
const g = | ||||||||||||||||||||||||||
gSRGB <= 0.03928 ? gSRGB / 12.92 : Math.pow((gSRGB + 0.055) / 1.055, 2.4); | ||||||||||||||||||||||||||
var b = | ||||||||||||||||||||||||||
const b = | ||||||||||||||||||||||||||
bSRGB <= 0.03928 ? bSRGB / 12.92 : Math.pow((bSRGB + 0.055) / 1.055, 2.4); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return 0.2126 * r + 0.7152 * g + 0.0722 * b; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
* Normalize the color to RGB values between 0-1 | ||||||||||||||||||||||||||
* @method normalize | ||||||||||||||||||||||||||
* @memberof axe.commons.color.Color | ||||||||||||||||||||||||||
* @instance | ||||||||||||||||||||||||||
* @return {Color} A new color instance with RGB values between 0-1 | ||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||
normalize() { | ||||||||||||||||||||||||||
straker marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
return new Color( | ||||||||||||||||||||||||||
this.red / 255, | ||||||||||||||||||||||||||
this.green / 255, | ||||||||||||||||||||||||||
this.blue / 255, | ||||||||||||||||||||||||||
this.alpha | ||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This object is broken. That's the issue I mentioned in the other PR too. Color is built to accept 0-255. A bunch of methods on this class don't work correctly if you pass it 0-1. When we're in 0-1 land we need different properties. We can do that with getter/setters on this class, or we could create a separate object or class to represent that.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Decided to mimic how Colorjs.io does this by using color coordinates (mapped to |
||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
* Scale the color by a value | ||||||||||||||||||||||||||
* @method scale | ||||||||||||||||||||||||||
* @memberof axe.commons.color.Color | ||||||||||||||||||||||||||
* @instance | ||||||||||||||||||||||||||
* @param {number} value The value to scale by | ||||||||||||||||||||||||||
* @return {Color} A new color instance | ||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||
scale(value) { | ||||||||||||||||||||||||||
return new Color( | ||||||||||||||||||||||||||
this.red * value, | ||||||||||||||||||||||||||
this.green * value, | ||||||||||||||||||||||||||
this.blue * value, | ||||||||||||||||||||||||||
this.alpha | ||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
* Add a value to the color | ||||||||||||||||||||||||||
* @method add | ||||||||||||||||||||||||||
* @memberof axe.commons.color.Color | ||||||||||||||||||||||||||
* @instance | ||||||||||||||||||||||||||
* @param {number} value The value to add | ||||||||||||||||||||||||||
* @return {Color} A new color instance | ||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||
add(value) { | ||||||||||||||||||||||||||
return new Color( | ||||||||||||||||||||||||||
this.red + value, | ||||||||||||||||||||||||||
this.green + value, | ||||||||||||||||||||||||||
this.blue + value, | ||||||||||||||||||||||||||
this.alpha | ||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
* Get the luminosity of a color. Color should be normalized before calling. | ||||||||||||||||||||||||||
* using algorithm from https://www.w3.org/TR/compositing-1/#blendingnonseparable | ||||||||||||||||||||||||||
* @method getLuminosity | ||||||||||||||||||||||||||
* @memberof axe.commons.color.Color | ||||||||||||||||||||||||||
* @instance | ||||||||||||||||||||||||||
* @return {number} The luminosity of the color | ||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||
getLuminosity() { | ||||||||||||||||||||||||||
return 0.3 * this.red + 0.59 * this.green + 0.11 * this.blue; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
* Set the luminosity of a color. Color should be normalized before calling. | ||||||||||||||||||||||||||
* using algorithm from https://www.w3.org/TR/compositing-1/#blendingnonseparable | ||||||||||||||||||||||||||
* @method setLuminosity | ||||||||||||||||||||||||||
* @memberof axe.commons.color.Color | ||||||||||||||||||||||||||
* @instance | ||||||||||||||||||||||||||
* @param {number} L The luminosity | ||||||||||||||||||||||||||
* @return {Color} A new color instance | ||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||
setLuminosity(L) { | ||||||||||||||||||||||||||
const d = L - this.getLuminosity(); | ||||||||||||||||||||||||||
return this.add(d).clip(); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
* Get the saturation of a color. Color should be normalized before calling. | ||||||||||||||||||||||||||
* using algorithm from https://www.w3.org/TR/compositing-1/#blendingnonseparable | ||||||||||||||||||||||||||
* @method getSaturation | ||||||||||||||||||||||||||
* @memberof axe.commons.color.Color | ||||||||||||||||||||||||||
* @instance | ||||||||||||||||||||||||||
* @return {number} The saturation of the color | ||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||
getSaturation() { | ||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||
Math.max(this.red, this.green, this.blue) - | ||||||||||||||||||||||||||
Math.min(this.red, this.green, this.blue) | ||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
* Set the saturation of a color. Color should be normalized before calling. | ||||||||||||||||||||||||||
* using algorithm from https://www.w3.org/TR/compositing-1/#blendingnonseparable | ||||||||||||||||||||||||||
* @method setSaturation | ||||||||||||||||||||||||||
* @memberof axe.commons.color.Color | ||||||||||||||||||||||||||
* @instance | ||||||||||||||||||||||||||
* @param {number} s The saturation | ||||||||||||||||||||||||||
* @return {Color} A new color instance | ||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||
setSaturation(s) { | ||||||||||||||||||||||||||
const C = new Color(this); | ||||||||||||||||||||||||||
const colorEntires = [ | ||||||||||||||||||||||||||
{ name: 'red', value: C.red }, | ||||||||||||||||||||||||||
{ name: 'green', value: C.green }, | ||||||||||||||||||||||||||
{ name: 'blue', value: C.blue } | ||||||||||||||||||||||||||
]; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// find the min, mid, and max values of the color components | ||||||||||||||||||||||||||
const [Cmin, Cmid, Cmax] = colorEntires.sort((a, b) => { | ||||||||||||||||||||||||||
return a.value - b.value; | ||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
if (Cmax.value > Cmin.value) { | ||||||||||||||||||||||||||
Cmid.value = ((Cmid.value - Cmin.value) * s) / (Cmax.value - Cmin.value); | ||||||||||||||||||||||||||
Cmax.value = s; | ||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||
Cmid.value = Cmax.value = 0; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
Cmin.value = 0; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
C[Cmax.name] = Cmax.value; | ||||||||||||||||||||||||||
C[Cmin.name] = Cmin.value; | ||||||||||||||||||||||||||
C[Cmid.name] = Cmid.value; | ||||||||||||||||||||||||||
return C; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||
* Clip the color between RGB 0-1 accounting for the luminosity of the color. Color must be normalized before calling. | ||||||||||||||||||||||||||
* using algorithm from https://www.w3.org/TR/compositing-1/#blendingnonseparable | ||||||||||||||||||||||||||
* @method clip | ||||||||||||||||||||||||||
* @memberof axe.commons.color.Color | ||||||||||||||||||||||||||
* @instance | ||||||||||||||||||||||||||
* @return {Color} A new color instance clipped between 0-1 | ||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||
clip() { | ||||||||||||||||||||||||||
const C = new Color(this); | ||||||||||||||||||||||||||
const L = C.getLuminosity(); | ||||||||||||||||||||||||||
const n = Math.min(C.red, C.green, C.blue); | ||||||||||||||||||||||||||
const x = Math.max(C.red, C.green, C.blue); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
if (n < 0) { | ||||||||||||||||||||||||||
C.red = L + ((C.red - L) * L) / (L - n); | ||||||||||||||||||||||||||
C.green = L + ((C.green - L) * L) / (L - n); | ||||||||||||||||||||||||||
C.blue = L + ((C.blue - L) * L) / (L - n); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
if (x > 1) { | ||||||||||||||||||||||||||
C.red = L + ((C.red - L) * (1 - L)) / (x - L); | ||||||||||||||||||||||||||
C.green = L + ((C.green - L) * (1 - L)) / (x - L); | ||||||||||||||||||||||||||
C.blue = L + ((C.blue - L) * (1 - L)) / (x - L); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return C; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// clamp a value between two numbers (inclusive) | ||||||||||||||||||||||||||
|
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.
Now we can create a new color from another one. Makes the new functions that create new colors much easier.