//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 12 Jun 08 Brian Frank Creation
//
**
** Models an ARGB color (alpha, red, green, blue).
** Color is also a solid `Brush`.
**
@Js
@Serializable { simple = true }
const class Color : Brush
{
//////////////////////////////////////////////////////////////////////////
// Constants
//////////////////////////////////////////////////////////////////////////
** Constant for 0x00_00_00
const static Color black := make(0x00_00_00)
** Constant for 0xff_ff_ff
const static Color white := make(0xff_ff_ff)
** Constant for 0xff_00_00
const static Color red := make(0xff_00_00)
** Constant for 0x00_ff_00
const static Color green := make(0x00_ff_00)
** Constant for 0x00_00_ff
const static Color blue := make(0x00_00_ff)
** Constant for 0x80_80_80
const static Color gray := make(0x80_80_80)
** Constant for 0xa9_a9_a9
const static Color darkGray := make(0xa9_a9_a9)
** Constant for 0xff_ff_00
const static Color yellow := make(0xff_ff_00)
** Constant for 0xff_a5_00
const static Color orange := make(0xff_a5_00)
** Constant for 0x80_00_80
const static Color purple := make(0x80_00_80)
//////////////////////////////////////////////////////////////////////////
// Construction
//////////////////////////////////////////////////////////////////////////
**
** Make a new instance with the ARGB components masked
** together: bits 31-24 alpha; bits 16-23 red; bits 8-15 green;
** bits 0-7 blue. If hasAlpha is false, then we assume the
** alpha bits are 0xFF.
**
new make(Int argb := 0, Bool hasAlpha := false)
{
if (!hasAlpha) argb = argb.or(0xff00_0000)
this.argb = argb
// 26 Feb 2013 - Chrome 25 appears to sometimes optimize
// out our Int.shiftx methods - which can cause our sign
// bit to be set - which we need to fix before argb value
// is configured - and for some reason - doesn't always
// take the firt time...
while (this.argb < 0)
{
this.argb += 0xffff_ffff+1
echo("# fixing: $argb -> $this.argb")
}
}
**
** Make a new instance with the ARGB individual
** components as integers between 0 and 255.
**
static Color makeArgb(Int a, Int r, Int g, Int b)
{
return make((a.and(0xff).shiftl(24))
.or(r.and(0xff).shiftl(16))
.or(g.and(0xff).shiftl(8))
.or(b.and(0xff)), true)
}
**
** Make a new instance with the RGB individual
** components as integers between 0 and 255.
**
static Color makeRgb(Int r, Int g, Int b)
{
return make((r.and(0xff).shiftl(16))
.or(g.and(0xff).shiftl(8))
.or(b.and(0xff)), false)
}
**
** Construct a color using HSV model (hue, saturation, value),
** also known as HSB (hue, saturation, brightness):
** - hue as 0.0 to 360.0
** - saturation as 0.0 to 1.0
** - value (or brightness) as 0.0 to 1.0
** Also see `h`, `s`, `v`.
**
static Color makeHsv(Float h, Float s, Float v)
{
r := v; g := v; b := v
if (s != 0f)
{
if (h == 360f) h = 0f
h /= 60f
i := h.floor
f := h - i
p := v * (1f - s)
q := v * (1f - s * f)
t := v * (1f - (s*(1f-f)))
switch (i.toInt)
{
case 0: r=v; g=t; b=p
case 1: r=q; g=v; b=p
case 2: r=p; g=v; b=t
case 3: r=p; g=q; b=v
case 4: r=t; g=p; b=v
case 5: r=v; g=p; b=q
}
}
return make((r * 255f).toInt.shiftl(16)
.or((g * 255f).toInt.shiftl(8))
.or((b * 255f).toInt),
false)
}
**
** Parse color from string (see `toStr`). If invalid
** and checked is true then throw ParseErr otherwise
** return null. The following formats are supported:
** - #AARRGGBB
** - #RRGGBB
** - #RGB
**
** Examples:
** Color.fromStr("#8A0")
** Color.fromStr("#88AA00")
** Color.fromStr("#d088aa00")
**
static new fromStr(Str s, Bool checked := true)
{
try
{
if (!s.startsWith("#")) throw Err()
sub := s[1..-1]
hex := sub.toInt(16)
switch (sub.size)
{
case 3:
r := hex.shiftr(8).and(0xf); r = r.shiftl(4).or(r)
g := hex.shiftr(4).and(0xf); g = g.shiftl(4).or(g)
b := hex.shiftr(0).and(0xf); b = b.shiftl(4).or(b)
return make(r.shiftl(16).or(g.shiftl(8)).or(b))
case 6:
return make(hex, false)
case 8:
return make(hex, true)
default: throw Err()
}
}
catch {}
if (checked) throw ParseErr("Invalid Color: $s")
return null
}
//////////////////////////////////////////////////////////////////////////
// Color Model
//////////////////////////////////////////////////////////////////////////
**
** The ARGB components masked together: bits 31-24 alpha;
** bits 16-23 red; bits 8-15 green; bits 0-7 blue.
**
const Int argb
**
** Get the RGB bitmask without the alpha bits.
**
Int rgb() { argb.and(0x00ff_ffff) }
**
** The alpha component from 0 to 255, where 255 is opaque
** and 0 is transparent.
**
Int a() { argb.shiftr(24).and(0xff) }
**
** The red component from 0 to 255.
**
Int r() { argb.shiftr(16).and(0xff) }
**
** The green component from 0 to 255.
**
Int g() { argb.shiftr(8).and(0xff) }
**
** The blue component from 0 to 255.
**
Int b() { argb.and(0xff) }
**
** Hue as a float between 0.0 and 360.0 of the HSV model (hue,
** saturation, value), also known as HSB (hue, saturation, brightness).
** Also see `makeHsv`, `s`, `v`.
**
Float h()
{
r := this.r.toFloat
b := this.b.toFloat
g := this.g.toFloat
min := r.min(b.min(g))
max := r.max(b.max(g))
delta := max - min
s := max == 0f ? 0f : delta / max
h := 0f
if (s != 0f)
{
if (r == max) h = (g - b) / delta
else if (g == max) h = 2f + (b - r) / delta
else if (b == max) h = 4f + (r - g) / delta
h *= 60f
if (h < 0f) h += 360f
}
return h
}
**
** Saturation as a float between 0.0 and 1.0 of the HSV model (hue,
** saturation, value), also known as HSB (hue, saturation, brightness).
** Also see `makeHsv`, `h`, `v`.
**
Float s()
{
min := r.min(b.min(g)).toFloat
max := r.max(b.max(g)).toFloat
return max == 0f ? 0f : (max-min) / max
}
**
** Value or brightness as a float between 0.0 and 1.0 of the HSV
** model (hue, saturation, value), also known as HSB (hue, saturation,
** brightness). Also see `makeHsv`, `h`, `s`.
**
Float v()
{
return r.max(b.max(g)).toFloat / 255f
}
//////////////////////////////////////////////////////////////////////////
// Identity
//////////////////////////////////////////////////////////////////////////
**
** Free any operating system resources used by this color.
** Dispose is required if this color has been used in an operation
** such as FWT onPaint which allocated a system resource to
** represent this instance.
**
Void dispose() { GfxEnv.cur(false)?.colorDispose(this) }
**
** Return `argb` as the hash code.
**
override Int hash() { return argb }
**
** Equality is based on `argb`.
**
override Bool equals(Obj? that)
{
x := that as Color
return x == null ? false : x.argb == argb
}
**
** If the alpha component is 255, then format as '"#RRGGBB"' hex
** string, otherwise format as '"#AARRGGBB"' hex string.
**
override Str toStr()
{
if (a == 0xff)
return "#" + rgb.toHex(6)
else
return "#" + argb.toHex(8)
}
**
** To a valid CSS color string.
**
Str toCss()
{
if (a == 0xff) return "#" + rgb.toHex(6)
alphaVal := a * 100 / 255
return "rgba($r,$g,$b,0.${alphaVal})"
}
**
** Get a color which is a lighter shade of this color.
** This increases the brightness by the given percentage
** which is a float between 0.0 and 1.0.
**
Color lighter(Float percentage := 0.2f)
{
// adjust value (brighness)
v := (this.v + percentage).max(0f).min(1f)
return makeHsv(h, s, v)
}
**
** Get a color which is a dark shade of this color.
** This decreases the brightness by the given percentage
** which is a float between 0.0 and 1.0.
**
Color darker(Float percentage := 0.2f)
{
return lighter(-percentage)
}
}