296 строки
9.3 KiB
TypeScript
296 строки
9.3 KiB
TypeScript
///<reference path='refs.ts'/>
|
|
module TDev.RT {
|
|
//? A argb color (alpha, red, green, blue)
|
|
//@ stem("c") icon("color") immutable ctx(general,indexkey,walltap,cloudfield,json) serializable
|
|
//@ robust
|
|
export class Color
|
|
extends RTValue
|
|
{
|
|
// 0-255
|
|
public r: number;
|
|
public g: number;
|
|
public b: number;
|
|
public a: number;
|
|
|
|
constructor() {
|
|
super()
|
|
}
|
|
|
|
static fromArtUrl(url: string) { return Promise.wrap(Color.fromHtml(url)); }
|
|
|
|
static fromArgb(a: number, r: number, g: number, b: number)
|
|
{
|
|
var c = new Color();
|
|
c.a = a;
|
|
c.r = r;
|
|
c.g = g;
|
|
c.b = b;
|
|
return c;
|
|
}
|
|
|
|
static capByte(n: number): number
|
|
{
|
|
var c: number = Math.floor(n);
|
|
if (c < 0) return 0;
|
|
else if (c > 255) return 255;
|
|
return c;
|
|
}
|
|
|
|
static normalizeToByte(n: number): number
|
|
{
|
|
if (n < 0) n = 0;
|
|
else if (n > 1) n = 1;
|
|
return Math.floor(n * 255 + 0.499999);
|
|
}
|
|
|
|
static fromArgbF(a: number, r: number, g: number, b: number)
|
|
{
|
|
var c = new Color();
|
|
c.a = Color.normalizeToByte(a);
|
|
c.r = Color.normalizeToByte(r);
|
|
c.g = Color.normalizeToByte(g);
|
|
c.b = Color.normalizeToByte(b);
|
|
return c;
|
|
}
|
|
|
|
static fromHtml(c: string)
|
|
{
|
|
if (c[0] == "#") c = c.substr(1);
|
|
var n = parseInt(c, 16);
|
|
var r = new Color();
|
|
if (c.length == 3 || c.length == 4) {
|
|
r.b = (n & 0xf) * 0x11;
|
|
r.g = ((n >> 4) & 0xf) * 0x11;
|
|
r.r = ((n >> 8) & 0xf) * 0x11;
|
|
r.a = ((n >> 12) & 0xf) * 0x11;
|
|
} else if (c.length == 6 || c.length == 8) {
|
|
r.b = (n & 0xff)
|
|
r.g = ((n >> 8) & 0xff);
|
|
r.r = ((n >> 16) & 0xff);
|
|
r.a = ((n >> 24) & 0xff);
|
|
}
|
|
|
|
if (c.length == 3 || c.length == 6) r.a = 255;
|
|
|
|
return r;
|
|
}
|
|
|
|
static fromInt32(n: number)
|
|
{
|
|
n = Math.round(n);
|
|
var r = new Color();
|
|
r.b = (n & 0xff)
|
|
r.g = ((n >> 8) & 0xff);
|
|
r.r = ((n >> 16) & 0xff);
|
|
r.a = ((n >> 24) & 0xff);
|
|
return r;
|
|
}
|
|
|
|
//? Gets the normalized alpha value (0.0-1.0)
|
|
public A(): number { return this.a / 255.0; }
|
|
|
|
//? Gets the normalized red value (0.0-1.0)
|
|
public R(): number { return this.r / 255.0; }
|
|
|
|
//? Gets the normalized green value (0.0-1.0)
|
|
public G(): number { return this.g / 255.0; }
|
|
|
|
//? Gets the normalized blue value (0.0-1.0)
|
|
public B(): number { return this.b / 255.0; }
|
|
|
|
//? Checks if the color is equal to the other
|
|
public equals(other: Color): boolean { return this.r == other.r && this.g == other.g && this.b == other.b && this.a == other.a; }
|
|
|
|
//? Composes a new color using alpha blending
|
|
public blend(other: Color): Color
|
|
{
|
|
var caAlpha = other.A();
|
|
var cbAlpha = this.A() * (1 - caAlpha);
|
|
return Color.fromArgbF(caAlpha + cbAlpha,
|
|
other.R() * caAlpha + this.R() * cbAlpha,
|
|
other.G() * caAlpha + this.G() * cbAlpha,
|
|
other.B() * caAlpha + this.B() * cbAlpha);
|
|
}
|
|
|
|
//? Creates a new color by changing the alpha channel from 0 (transparent) to 1 (opaque).
|
|
public make_transparent(alpha: number): Color
|
|
{
|
|
if (alpha == 1 && this.a == 255) return this;
|
|
var a = Color.normalizeToByte(alpha)
|
|
if (a == this.a) return this;
|
|
return Color.fromArgb(a, this.r, this.g, this.b);
|
|
}
|
|
|
|
//? Makes a darker color by a delta between 0 and 1.
|
|
//@ [delta].defl(0.1)
|
|
public darken(delta: number): Color
|
|
{
|
|
return Color.fromArgbF(this.A(), this.R() - delta, this.G() - delta, this.B() - delta);
|
|
}
|
|
|
|
//? Makes a lighter color by a delta between 0 and 1.
|
|
//@ [delta].defl(0.1)
|
|
public lighten(delta: number): Color
|
|
{
|
|
return Color.fromArgbF(this.A(), this.R() + delta, this.G() + delta, this.B() + delta);
|
|
}
|
|
|
|
public toInt32()
|
|
{
|
|
return ((this.a << 24) | (this.r << 16) | (this.g << 8) | this.b) >>> 0;
|
|
}
|
|
|
|
public toJsonKey(): any { return this.toInt32(); }
|
|
|
|
public keyCompareTo(other: any): number
|
|
{
|
|
var o: Color = other;
|
|
var diff = this.r - o.r;
|
|
if (diff) return diff;
|
|
diff = this.b - o.b;
|
|
if (diff) return diff;
|
|
diff = this.g - o.g;
|
|
if (diff) return diff;
|
|
diff = this.a - o.a;
|
|
return diff;
|
|
}
|
|
|
|
public toString() : string
|
|
{
|
|
var h = this.toInt32().toString(16);
|
|
return "#" + "00000000".substr(0, 8 - h.length) + h;
|
|
}
|
|
|
|
public getViewCore(s: IStackFrame, b: BoxBase): HTMLElement
|
|
{
|
|
var d = div("item");
|
|
d.style.backgroundColor = this.toHtml();
|
|
var t = div("item-subtitle", Util.fmt('alpha:{0:f1.3}, red:{1:f1.3}, green:{2:f1.3}, blue:{3:f1.3}',
|
|
this.A(), this.R(), this.G(), this.B()));
|
|
t.style.textAlign = 'center';
|
|
t.style.color = ((this.r + this.g + this.b) * this.A()) > 300 ? 'black' : 'white';
|
|
d.appendChild(t);
|
|
return d;
|
|
}
|
|
|
|
private htmlCache:string;
|
|
|
|
public toHtml() : string
|
|
{
|
|
if (this.htmlCache) return this.htmlCache;
|
|
|
|
if (this.a == 0xff) {
|
|
var h = (this.toInt32() & 0xffffff).toString(16);
|
|
this.htmlCache = "#" + "000000".substr(0, 6 - h.length) + h;
|
|
}
|
|
else
|
|
this.htmlCache = Util.fmt("rgba({0}, {1}, {2}, {3})", this.r, this.g, this.b, Math_.round_with_precision(this.a / 0xff, 6));
|
|
return this.htmlCache;
|
|
}
|
|
|
|
public getShortStringRepresentation(): string {
|
|
return this.toHtml();
|
|
}
|
|
|
|
public exportJson(ctx: JsonExportCtx): any {
|
|
// return a string
|
|
return this.toHtml();
|
|
}
|
|
public importJson(ctx: JsonImportCtx, json: any): RT.RTValue {
|
|
Util.oops("should not call immutable instance for importing");
|
|
return undefined;
|
|
}
|
|
static mkFromJson(ctx: JsonImportCtx, json: any): RT.RTValue {
|
|
if (typeof (json) === "string") {
|
|
var c = Color.fromHtml(json);
|
|
return c;
|
|
} else {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
public debuggerDisplay(clickHandler: () => any) {
|
|
// this is the old code:
|
|
// return span(null, this.toHtml()).withClick(clickHandler);
|
|
|
|
var tempSpan: HTMLElement;
|
|
tempSpan = span(null, "R: " + this.r + " G: " + this.g + " B: " + this.b).withClick(clickHandler);
|
|
|
|
var s: string;
|
|
for (s = (this.toInt32() & 0xffffff).toString(16); s.length < 6; s = "0" + s) { }
|
|
tempSpan.style.backgroundColor = "#" + s;
|
|
|
|
if ((this.r + this.g + this.b) / 3 < 100) {
|
|
tempSpan.style.color = "white";
|
|
}
|
|
|
|
return tempSpan;
|
|
}
|
|
|
|
//? Convert color to HTML syntax (either #FF002A or rgba(255, 0, 42, 0.5) when A is non-1)
|
|
public to_html() : string
|
|
{
|
|
return this.toHtml();
|
|
}
|
|
|
|
//? Gets the hue component of the color.
|
|
public hue(): number
|
|
{
|
|
return this.toHsb().x() / 255;
|
|
}
|
|
|
|
//? Gets the saturation component of the color.
|
|
public saturation(): number
|
|
{
|
|
return this.toHsb().y() / 255;
|
|
}
|
|
|
|
//? Gets the brightness component of the color.
|
|
public brightness(): number
|
|
{
|
|
return this.toHsb().z() / 255;
|
|
}
|
|
|
|
private toHsb() : Vector3
|
|
{
|
|
var max = Math.max(this.r, Math.max(this.g, this.b));
|
|
if (max <= 0)
|
|
return Vector3.mk(0, 0, 0);
|
|
|
|
var min = Math.min(this.r, Math.min(this.g, this.b));
|
|
var dif = max - min;
|
|
var hue = 0;
|
|
|
|
if (max > min) {
|
|
if (this.g === max) {
|
|
hue = (this.b - this.r) / dif * 60 + 120;
|
|
}
|
|
else if (this.b === max) {
|
|
hue = (this.r - this.g) / dif * 60 + 240;
|
|
}
|
|
else if (this.b > this.g) {
|
|
hue = (this.g - this.b) / dif * 60 + 360;
|
|
}
|
|
else {
|
|
hue = (this.g - this.b) / dif * 60;
|
|
}
|
|
if (hue < 0) {
|
|
hue = hue + 360;
|
|
}
|
|
}
|
|
else {
|
|
hue = 0;
|
|
}
|
|
|
|
hue *= 255 / 360;
|
|
var saturation = (dif / max) * 255;
|
|
var brightness = max;
|
|
|
|
return Vector3.mk(hue, saturation, brightness);
|
|
}
|
|
|
|
//? Prints the value to the wall
|
|
public post_to_wall(s:IStackFrame) : void { super.post_to_wall(s) }
|
|
}
|
|
}
|