@vole-engine/core
Version:
Tiny 2D game engine
459 lines • 13.1 kB
JavaScript
/**
* BSD 2-Clause License
*
* Copyright (c) 2013, Erik Onarheim
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* Provides standard colors (e.g. [[Color.Black]])
* but you can also create custom colors using RGB, HSL, or Hex. Also provides
* useful color operations like [[Color.lighten]], [[Color.darken]], and more.
*/
export class Color {
/**
* Creates a new instance of Color from an r, g, b, a
*
* @param r The red component of color (0-255)
* @param g The green component of color (0-255)
* @param b The blue component of color (0-255)
* @param a The alpha component of color (0-1.0)
*/
constructor(r, g, b, a) {
this.r = r;
this.g = g;
this.b = b;
this.a = a != null ? a : 1;
}
/**
* Creates a new instance of Color from an r, g, b, a
*
* @param r The red component of color (0-255)
* @param g The green component of color (0-255)
* @param b The blue component of color (0-255)
* @param a The alpha component of color (0-1.0)
*/
static fromRGB(r, g, b, a) {
return new Color(r, g, b, a);
}
/**
* Creates a new instance of Color from a rgb string
*
* @param string CSS color string of the form rgba(255, 255, 255, 1) or rgb(255, 255, 255)
*/
static fromRGBString(string) {
const rgbaRegEx = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)/i;
let match;
if ((match = string.match(rgbaRegEx))) {
const r = parseInt(match[1], 10);
const g = parseInt(match[2], 10);
const b = parseInt(match[3], 10);
let a = 1;
if (match[4]) {
a = parseFloat(match[4]);
}
return new Color(r, g, b, a);
}
else {
throw new Error('Invalid rgb/a string: ' + string);
}
}
/**
* Creates a new instance of Color from a hex string
*
* @param hex CSS color string of the form #ffffff, the alpha component is optional
*/
static fromHex(hex) {
const hexRegEx = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/i;
let match;
if ((match = hex.match(hexRegEx))) {
const r = parseInt(match[1], 16);
const g = parseInt(match[2], 16);
const b = parseInt(match[3], 16);
let a = 1;
if (match[4]) {
a = parseInt(match[4], 16) / 255;
}
return new Color(r, g, b, a);
}
else {
throw new Error('Invalid hex string: ' + hex);
}
}
/**
* Creates a new instance of Color from hsla values
*
* @param h Hue is represented [0-1]
* @param s Saturation is represented [0-1]
* @param l Luminance is represented [0-1]
* @param a Alpha is represented [0-1]
*/
static fromHSL(h, s, l, a = 1.0) {
const temp = new HSLColor(h, s, l, a);
return temp.toRGBA();
}
/**
* Lightens the current color by a specified amount
*
* @param factor The amount to lighten by [0-1]
*/
lighten(factor = 0.1) {
const temp = HSLColor.fromRGBA(this.r, this.g, this.b, this.a);
temp.l += (1 - temp.l) * factor;
return temp.toRGBA();
}
/**
* Darkens the current color by a specified amount
*
* @param factor The amount to darken by [0-1]
*/
darken(factor = 0.1) {
const temp = HSLColor.fromRGBA(this.r, this.g, this.b, this.a);
temp.l -= temp.l * factor;
return temp.toRGBA();
}
/**
* Saturates the current color by a specified amount
*
* @param factor The amount to saturate by [0-1]
*/
saturate(factor = 0.1) {
const temp = HSLColor.fromRGBA(this.r, this.g, this.b, this.a);
temp.s += temp.s * factor;
return temp.toRGBA();
}
/**
* Desaturates the current color by a specified amount
*
* @param factor The amount to desaturate by [0-1]
*/
desaturate(factor = 0.1) {
const temp = HSLColor.fromRGBA(this.r, this.g, this.b, this.a);
temp.s -= temp.s * factor;
return temp.toRGBA();
}
/**
* Multiplies a color by another, results in a darker color
*
* @param color The other color
*/
multiply(color) {
const newR = (((color.r / 255) * this.r) / 255) * 255;
const newG = (((color.g / 255) * this.g) / 255) * 255;
const newB = (((color.b / 255) * this.b) / 255) * 255;
const newA = color.a * this.a;
return new Color(newR, newG, newB, newA);
}
/**
* Screens a color by another, results in a lighter color
*
* @param color The other color
*/
screen(color) {
const color1 = color.invert();
const color2 = color.invert();
return color1.multiply(color2).invert();
}
/**
* Inverts the current color
*/
invert() {
return new Color(255 - this.r, 255 - this.g, 255 - this.b, 1.0 - this.a);
}
/**
* Averages the current color with another
*
* @param color The other color
*/
average(color) {
const newR = (color.r + this.r) / 2;
const newG = (color.g + this.g) / 2;
const newB = (color.b + this.b) / 2;
const newA = (color.a + this.a) / 2;
return new Color(newR, newG, newB, newA);
}
equal(color) {
return this.toString() === color.toString();
}
/**
* Returns a CSS string representation of a color.
*
* @param format Color representation, accepts: rgb, hsl, or hex
*/
toString(format = 'rgb') {
switch (format) {
case 'rgb':
return this.toRGBA();
case 'hsl':
return this.toHSLA();
case 'hex':
return this.toHex();
default:
throw new Error('Invalid Color format');
}
}
/**
* Returns Hex Value of a color component
* @param c color component
* @see https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
*/
_componentToHex(c) {
const hex = c.toString(16);
return hex.length === 1 ? '0' + hex : hex;
}
/**
* Return Hex representation of a color.
*/
toHex() {
return '#' + this._componentToHex(this.r) + this._componentToHex(this.g) + this._componentToHex(this.b);
}
/**
* Return RGBA representation of a color.
*/
toRGBA() {
const result = String(this.r.toFixed(0)) + ', ' + String(this.g.toFixed(0)) + ', ' + String(this.b.toFixed(0));
if (this.a !== undefined || this.a !== null) {
return 'rgba(' + result + ', ' + String(this.a) + ')';
}
return 'rgb(' + result + ')';
}
/**
* Return HSLA representation of a color.
*/
toHSLA() {
return HSLColor.fromRGBA(this.r, this.g, this.b, this.a).toString();
}
/**
* Returns a CSS string representation of a color.
*/
fillStyle() {
return this.toString();
}
/**
* Returns a clone of the current color.
*/
clone() {
return new Color(this.r, this.g, this.b, this.a);
}
/**
* Black (#000000)
*/
static get Black() {
return Color.fromHex('#000000');
}
/**
* White (#FFFFFF)
*/
static get White() {
return Color.fromHex('#FFFFFF');
}
/**
* Gray (#808080)
*/
static get Gray() {
return Color.fromHex('#808080');
}
/**
* Light gray (#D3D3D3)
*/
static get LightGray() {
return Color.fromHex('#D3D3D3');
}
/**
* Dark gray (#A9A9A9)
*/
static get DarkGray() {
return Color.fromHex('#A9A9A9');
}
/**
* Yellow (#FFFF00)
*/
static get Yellow() {
return Color.fromHex('#FFFF00');
}
/**
* Orange (#FFA500)
*/
static get Orange() {
return Color.fromHex('#FFA500');
}
/**
* Red (#FF0000)
*/
static get Red() {
return Color.fromHex('#FF0000');
}
/**
* Vermilion (#FF5B31)
*/
static get Vermilion() {
return Color.fromHex('#FF5B31');
}
/**
* Rose (#FF007F)
*/
static get Rose() {
return Color.fromHex('#FF007F');
}
/**
* Magenta (#FF00FF)
*/
static get Magenta() {
return Color.fromHex('#FF00FF');
}
/**
* Violet (#7F00FF)
*/
static get Violet() {
return Color.fromHex('#7F00FF');
}
/**
* Blue (#0000FF)
*/
static get Blue() {
return Color.fromHex('#0000FF');
}
/**
* Azure (#007FFF)
*/
static get Azure() {
return Color.fromHex('#007FFF');
}
/**
* Cyan (#00FFFF)
*/
static get Cyan() {
return Color.fromHex('#00FFFF');
}
/**
* Viridian (#59978F)
*/
static get Viridian() {
return Color.fromHex('#59978F');
}
/**
* Green (#00FF00)
*/
static get Green() {
return Color.fromHex('#00FF00');
}
/**
* Chartreuse (#7FFF00)
*/
static get Chartreuse() {
return Color.fromHex('#7FFF00');
}
/**
* Transparent (#FFFFFF00)
*/
static get Transparent() {
return Color.fromHex('#FFFFFF00');
}
/**
* ExcaliburBlue (#176BAA)
*/
static get ExcaliburBlue() {
return Color.fromHex('#176BAA');
}
}
/**
* Internal HSL Color representation
*
* http://en.wikipedia.org/wiki/HSL_and_HSV
* http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
*/
class HSLColor {
constructor(h, s, l, a) {
this.h = h;
this.s = s;
this.l = l;
this.a = a;
}
static 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;
}
static fromRGBA(r, g, b, a) {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h;
let s;
const l = (max + min) / 2;
if (max === min) {
h = 0;
s = 0;
}
else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
}
h = h / 6;
}
return new HSLColor(h, s, l, a);
}
toRGBA() {
let r, g, b;
if (this.s === 0) {
r = g = b = this.l; // achromatic
}
else {
const q = this.l < 0.5 ? this.l * (1 + this.s) : this.l + this.s - this.l * this.s;
const p = 2 * this.l - q;
r = HSLColor.hue2rgb(p, q, this.h + 1 / 3);
g = HSLColor.hue2rgb(p, q, this.h);
b = HSLColor.hue2rgb(p, q, this.h - 1 / 3);
}
return new Color(r * 255, g * 255, b * 255, this.a);
}
toString() {
const h = this.h.toFixed(0), s = this.s.toFixed(0), l = this.l.toFixed(0), a = this.a.toFixed(0);
return `hsla(${h}, ${s}, ${l}, ${a})`;
}
}
//# sourceMappingURL=color.js.map