Files
engine/packages/math/src/Color.ts
zhuxudong ae3dc4a655 Color api rename (#2607)
* refactor: color api change
2025-04-09 17:35:59 +08:00

388 lines
9.6 KiB
TypeScript

import { IClone } from "./IClone";
import { ICopy } from "./ICopy";
import { MathUtil } from "./MathUtil";
/**
* Describes a color in the from of RGBA (in order: R, G, B, A).
*/
export class Color implements IClone<Color>, ICopy<ColorLike, Color> {
/**
* Modify a value from the sRGB space to the linear space.
* @param value - The value in sRGB space
* @returns The value in linear space
*/
static sRGBToLinearSpace(value: number): number {
// https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_framebuffer_sRGB.txt
// https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_sRGB_decode.txt
if (value <= 0.0) return 0.0;
else if (value <= 0.04045) return value / 12.92;
else if (value < 1.0) return Math.pow((value + 0.055) / 1.055, 2.4);
else return Math.pow(value, 2.4);
}
/**
* Modify a value from the linear space to the sRGB space.
* @param value - The value in linear space
* @returns The value in sRGB space
*/
static linearToSRGBSpace(value: number): number {
// https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_framebuffer_sRGB.txt
// https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_sRGB_decode.txt
if (value <= 0.0) return 0.0;
else if (value < 0.0031308) return 12.92 * value;
else if (value < 1.0) return 1.055 * Math.pow(value, 0.41666) - 0.055;
else return Math.pow(value, 0.41666);
}
/**
* Determines whether the specified colors are equals.
* @param left - The first color to compare
* @param right - The second color to compare
* @returns True if the specified colors are equals, false otherwise
*/
static equals(left: Color, right: Color): boolean {
return (
MathUtil.equals(left._r, right._r) &&
MathUtil.equals(left._g, right._g) &&
MathUtil.equals(left._b, right._b) &&
MathUtil.equals(left._a, right._a)
);
}
/**
* Determines the sum of two colors.
* @param left - The first color to add
* @param right - The second color to add
* @param out - The sum of two colors
* @returns The added color
*/
static add(left: Color, right: Color, out: Color): Color {
out._r = left._r + right._r;
out._g = left._g + right._g;
out._b = left._b + right._b;
out._a = left._a + right._a;
out._onValueChanged?.();
return out;
}
/**
* Determines the difference between two colors.
* @param left - The first color to subtract
* @param right - The second color to subtract
* @param out - The difference between two colors
*/
static subtract(left: Color, right: Color, out: Color): void {
out._r = left._r - right._r;
out._g = left._g - right._g;
out._b = left._b - right._b;
out._a = left._a - right._a;
out._onValueChanged?.();
}
/**
* Scale a color by the given value.
* @param left - The color to scale
* @param s - The amount by which to scale the color
* @param out - The scaled color
* @returns The scaled color
*/
static scale(left: Color, s: number, out: Color): Color {
out._r = left._r * s;
out._g = left._g * s;
out._b = left._b * s;
out._a = left._a * s;
out._onValueChanged?.();
return out;
}
/**
* Performs a linear interpolation between two color.
* @param start - The first color
* @param end - The second color
* @param t - The blend amount where 0 returns start and 1 end
* @param out - The result of linear blending between two color
*/
static lerp(start: Color, end: Color, t: number, out: Color): Color {
const { _r, _g, _b, _a } = start;
out._r = _r + (end._r - _r) * t;
out._g = _g + (end._g - _g) * t;
out._b = _b + (end._b - _b) * t;
out._a = _a + (end._a - _a) * t;
out._onValueChanged?.();
return out;
}
/** @internal */
_r: number;
/** @internal */
_g: number;
/** @internal */
_b: number;
/** @internal */
_a: number;
/** @internal */
_onValueChanged: () => void = null;
/**
* The red component of the color, 0~1.
*/
public get r(): number {
return this._r;
}
public set r(value: number) {
this._r = value;
this._onValueChanged?.();
}
/**
* The green component of the color, 0~1.
*/
public get g(): number {
return this._g;
}
public set g(value: number) {
this._g = value;
this._onValueChanged?.();
}
/**
* The blue component of the color, 0~1.
*/
public get b(): number {
return this._b;
}
public set b(value: number) {
this._b = value;
this._onValueChanged?.();
}
/**
* The alpha component of the color, 0~1.
*/
public get a(): number {
return this._a;
}
public set a(value: number) {
this._a = value;
this._onValueChanged?.();
}
/**
* Constructor of Color.
* @param r - The red component of the color
* @param g - The green component of the color
* @param b - The blue component of the color
* @param a - The alpha component of the color
*/
constructor(r: number = 1, g: number = 1, b: number = 1, a: number = 1) {
this._r = r;
this._g = g;
this._b = b;
this._a = a;
}
/**
* Set the value of this color.
* @param r - The red component of the color
* @param g - The green component of the color
* @param b - The blue component of the color
* @param a - The alpha component of the color
* @returns This color.
*/
set(r: number, g: number, b: number, a: number): Color {
this._r = r;
this._g = g;
this._b = b;
this._a = a;
this._onValueChanged?.();
return this;
}
/**
* Determines the sum of this color and the specified color.
* @param color - The specified color
* @returns The added color
*/
add(color: Color): Color {
this._r += color._r;
this._g += color._g;
this._b += color._b;
this._a += color._a;
this._onValueChanged?.();
return this;
}
/**
* Scale this color by the given value.
* @param s - The amount by which to scale the color
* @returns The scaled color
*/
scale(s: number): Color {
this._r *= s;
this._g *= s;
this._b *= s;
this._a *= s;
this._onValueChanged?.();
return this;
}
/**
* Creates a clone of this color.
* @returns A clone of this color
*/
clone(): Color {
const ret = new Color(this._r, this._g, this._b, this._a);
return ret;
}
/**
* Copy from color like object.
* @param source - Color like object.
* @returns This vector
*/
copyFrom(source: ColorLike): Color {
this._r = source.r;
this._g = source.g;
this._b = source.b;
this._a = source.a;
this._onValueChanged?.();
return this;
}
/**
* Copy to color like object.
* @param target - Color like object.
* @returns This Color like object
*/
copyTo(target: ColorLike): ColorLike {
target.r = this._r;
target.g = this._g;
target.b = this._b;
target.a = this._a;
return target;
}
/**
* Copy from array like object.
* @param source - Array like object
* @param offset - The start offset
* @returns This color
*/
copyFromArray(source: ArrayLike<number>, offset: number = 0): Color {
this._r = source[offset];
this._g = source[offset + 1];
this._b = source[offset + 2];
this._a = source[offset + 3];
this._onValueChanged?.();
return this;
}
/**
* Copy the value of this color to an array.
* @param out - The color
* @param outOffset - The start offset
*/
copyToArray(out: number[] | Float32Array | Float64Array, outOffset: number = 0): void {
out[outOffset] = this._r;
out[outOffset + 1] = this._g;
out[outOffset + 2] = this._b;
out[outOffset + 3] = this._a;
}
/**
* Modify components (r, g, b) of this color from gamma space to linear space.
* @param out - The color in linear space
* @returns The color in linear space
*/
toLinear(out: Color): Color {
out._r = Color.sRGBToLinearSpace(this._r);
out._g = Color.sRGBToLinearSpace(this._g);
out._b = Color.sRGBToLinearSpace(this._b);
out._a = this._a;
out._onValueChanged?.();
return out;
}
/**
* Modify components (r, g, b) of this color from linear space to sRGB space.
* @param out - The color in sRGB space
* @returns The color in sRGB space
*/
toSRGB(out: Color): Color {
out._r = Color.linearToSRGBSpace(this._r);
out._g = Color.linearToSRGBSpace(this._g);
out._b = Color.linearToSRGBSpace(this._b);
out._a = this._a;
out._onValueChanged?.();
return out;
}
/**
* Gets the brightness.
* @returns The Hue-Saturation-Brightness (HSB) saturation for this
*/
getBrightness(): number {
const r = this.r;
const g = this.g;
const b = this.b;
let max = r;
let min = r;
if (g > max) max = g;
if (b > max) max = b;
if (g < min) min = g;
if (b < min) min = b;
return (max + min) / 2;
}
/**
* Serialize this color to a JSON representation.
* @return A JSON representation of this color
*/
toJSON(): ColorLike {
return {
r: this._r,
g: this._g,
b: this._b,
a: this._a
};
}
/** @deprecated Please use `sRGBToLinearSpace` instead. */
static gammaToLinearSpace(value: number): number {
return Color.sRGBToLinearSpace(value);
}
/** @deprecated Please use `linearToSRGBSpace` instead. */
static linearToGammaSpace(value: number): number {
return Color.linearToSRGBSpace(value);
}
/** @deprecated Please use `toSRGB` instead. */
toGamma(out: Color): Color {
return this.toSRGB(out);
}
}
interface ColorLike {
/** {@inheritDoc Color._r} */
r: number;
/** {@inheritDoc Color._g} */
g: number;
/** {@inheritDoc Color._b} */
b: number;
/** {@inheritDoc Color._a} */
a: number;
}