642 lines
16 KiB
Go
642 lines
16 KiB
Go
package noire
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
)
|
|
|
|
// Color represents a manipulable color.
|
|
type Color struct {
|
|
Red float64
|
|
Green float64
|
|
Blue float64
|
|
Alpha float64
|
|
}
|
|
|
|
// newColor returns a new color.
|
|
func newColor(r float64, g float64, b float64, a float64) Color {
|
|
if r > 255 {
|
|
r = 255
|
|
} else if r < 0 {
|
|
r = 0
|
|
}
|
|
if g > 255 {
|
|
g = 255
|
|
} else if g < 0 {
|
|
g = 0
|
|
}
|
|
if b > 255 {
|
|
b = 255
|
|
} else if b < 0 {
|
|
b = 0
|
|
}
|
|
if a > 1 {
|
|
a = 1
|
|
} else if a < 0 {
|
|
a = 0
|
|
}
|
|
return Color{
|
|
Red: r,
|
|
Green: g,
|
|
Blue: b,
|
|
Alpha: a,
|
|
}
|
|
}
|
|
|
|
// CMYKToRGB converts the color from CMYK to RGB with a lossy algorithm.
|
|
//
|
|
// reference: https://www.ginifab.com.tw/tools/colors/js/colorconverter.js
|
|
func CMYKToRGB(c float64, m float64, y float64, k float64) (r float64, g float64, b float64) {
|
|
c = c / 100
|
|
m = m / 100
|
|
y = y / 100
|
|
k = k / 100
|
|
|
|
r = 1 - math.Min(1, c*(1-k)+k)
|
|
g = 1 - math.Min(1, m*(1-k)+k)
|
|
b = 1 - math.Min(1, y*(1-k)+k)
|
|
|
|
r = math.Round(r * 255)
|
|
g = math.Round(g * 255)
|
|
b = math.Round(b * 255)
|
|
|
|
return
|
|
}
|
|
|
|
// RGBToCMYK converts the color from RGB to CMYK with a lossy algorithm.
|
|
//
|
|
// reference: https://www.ginifab.com.tw/tools/colors/js/colorconverter.js
|
|
func RGBToCMYK(r float64, g float64, b float64) (c float64, m float64, y float64, k float64) {
|
|
r = r / 255
|
|
g = g / 255
|
|
b = b / 255
|
|
|
|
k = math.Min(1-r, 1-g)
|
|
k = math.Min(k, 1-b)
|
|
if (1 - k) == 0 {
|
|
c = 0
|
|
m = 0
|
|
y = 0
|
|
} else {
|
|
c = (1 - r - k) / (1 - k)
|
|
m = (1 - g - k) / (1 - k)
|
|
y = (1 - b - k) / (1 - k)
|
|
}
|
|
c = math.Round(c * 100)
|
|
m = math.Round(m * 100)
|
|
y = math.Round(y * 100)
|
|
k = math.Round(k * 100)
|
|
return
|
|
}
|
|
|
|
// HueToRGB converts the color from Hue to RGB.
|
|
//
|
|
// reference: https://www.ginifab.com.tw/tools/colors/js/colorconverter.js
|
|
func HueToRGB(p float64, q float64, t float64) float64 {
|
|
|
|
if t < 0 {
|
|
t++
|
|
}
|
|
if t > 1 {
|
|
t--
|
|
}
|
|
if t < float64(1)/float64(6) {
|
|
return p + (q-p)*6*t
|
|
}
|
|
if t < float64(1)/float64(2) {
|
|
return q
|
|
}
|
|
if t < float64(2)/float64(3) {
|
|
return p + (q-p)*(float64(2)/float64(3)-t)*6
|
|
}
|
|
return p
|
|
}
|
|
|
|
// RGBToHSL converts the color from RGB to HSL with a lossy algorithm.
|
|
//
|
|
// reference: https://www.ginifab.com.tw/tools/colors/js/colorconverter.js
|
|
func RGBToHSL(r float64, g float64, b float64) (h float64, s float64, l float64) {
|
|
r = r / 255
|
|
g = g / 255
|
|
b = b / 255
|
|
max := math.Max(r, g)
|
|
max = math.Max(max, b)
|
|
min := math.Min(r, g)
|
|
min = math.Min(min, b)
|
|
l = (max + min) / 2
|
|
if max == min {
|
|
h = 0
|
|
s = 0
|
|
} else {
|
|
d := max - min
|
|
if l > 0.5 {
|
|
s = d / (2 - max - min)
|
|
} else {
|
|
s = d / (max + min)
|
|
}
|
|
switch max {
|
|
case r:
|
|
if g < b {
|
|
h = (g-b)/d + 6
|
|
} else {
|
|
h = (g-b)/d + 0
|
|
}
|
|
break
|
|
case g:
|
|
h = (b-r)/d + 2
|
|
break
|
|
case b:
|
|
h = (r-g)/d + 4
|
|
break
|
|
}
|
|
}
|
|
h = math.Round(h * 60)
|
|
s = math.Round(s*1000) / 10
|
|
l = math.Round(l*1000) / 10
|
|
return
|
|
}
|
|
|
|
// HSLToRGB converts the color from HSL to RGB with a lossy algorithm.
|
|
//
|
|
// reference: https://www.ginifab.com.tw/tools/colors/js/colorconverter.js
|
|
func HSLToRGB(h float64, s float64, l float64) (r float64, g float64, b float64) {
|
|
h = h / 360
|
|
s = s / 100
|
|
l = l / 100
|
|
|
|
if s == 0 {
|
|
r = l
|
|
g = l
|
|
b = l
|
|
} else {
|
|
var q float64
|
|
if l < 0.5 {
|
|
q = l * (1 + s)
|
|
} else {
|
|
q = l + s - l*s
|
|
}
|
|
p := 2*l - q
|
|
r = HueToRGB(p, q, h+float64(float64(1)/float64(3)))
|
|
g = HueToRGB(p, q, h)
|
|
b = HueToRGB(p, q, h-float64(float64(1)/float64(3)))
|
|
}
|
|
r = math.Round(r * 255)
|
|
g = math.Round(g * 255)
|
|
b = math.Round(b * 255)
|
|
return
|
|
}
|
|
|
|
// HSVToRGB converts the color from HSV to RGB with a lossy algorithm.
|
|
//
|
|
// reference: https://www.rapidtables.com/convert/color/hsv-to-rgb.html
|
|
func HSVToRGB(h float64, s float64, v float64) (r float64, g float64, b float64) {
|
|
s = s / 100
|
|
v = v / 100
|
|
c := v * s
|
|
hh := h / 60
|
|
x := c * (1 - math.Abs(math.Mod(hh, 2)-1))
|
|
if hh >= 0 && hh < 1 {
|
|
r = c
|
|
g = x
|
|
} else if hh >= 1 && hh < 2 {
|
|
r = x
|
|
g = c
|
|
} else if hh >= 2 && hh < 3 {
|
|
g = c
|
|
b = x
|
|
} else if hh >= 3 && hh < 4 {
|
|
g = x
|
|
b = c
|
|
} else if hh >= 4 && hh < 5 {
|
|
r = x
|
|
b = c
|
|
} else {
|
|
r = c
|
|
b = x
|
|
}
|
|
m := v - c
|
|
r += m
|
|
g += m
|
|
b += m
|
|
r = math.Round(r * 255)
|
|
g = math.Round(g * 255)
|
|
b = math.Round(b * 255)
|
|
return
|
|
}
|
|
|
|
// RGBToHSV converts the color from RGB to HSV with a lossy algorithm.
|
|
//
|
|
// reference: https://www.ginifab.com.tw/tools/colors/js/colorconverter.js
|
|
func RGBToHSV(r float64, g float64, b float64) (h float64, s float64, v float64) {
|
|
r = r / 255
|
|
g = g / 255
|
|
b = b / 255
|
|
|
|
minValue := math.Min(math.Min(r, g), b)
|
|
maxValue := math.Max(math.Max(r, g), b)
|
|
|
|
delta := maxValue - minValue
|
|
v = maxValue
|
|
|
|
if delta == 0 {
|
|
h = 0
|
|
s = 0
|
|
} else {
|
|
s = delta / maxValue
|
|
deltaR := (((maxValue - r) / 6) + (delta / 2)) / delta
|
|
deltaG := (((maxValue - g) / 6) + (delta / 2)) / delta
|
|
deltaB := (((maxValue - b) / 6) + (delta / 2)) / delta
|
|
switch maxValue {
|
|
case r:
|
|
h = deltaB - deltaG
|
|
break
|
|
case g:
|
|
h = (float64(1) / float64(3)) + deltaR - deltaB
|
|
break
|
|
case b:
|
|
h = (float64(2) / float64(3)) + deltaG - deltaR
|
|
break
|
|
}
|
|
if h < 0 {
|
|
h++
|
|
}
|
|
if h > 1 {
|
|
h--
|
|
}
|
|
}
|
|
|
|
h = math.Round(h * 360)
|
|
s = math.Round(s*1000) / 10
|
|
v = math.Round(v*1000) / 10
|
|
return
|
|
}
|
|
|
|
// RGBToHex converts the color from RGB to a uppercased Hex string (without the `#` prefix).
|
|
func RGBToHex(r float64, g float64, b float64) string {
|
|
h := []byte{uint8(math.Round(r)), uint8(math.Round(g)), uint8(math.Round(b))}
|
|
return strings.ToUpper(hex.EncodeToString(h))
|
|
}
|
|
|
|
// HexToRGB converts the Hex string (can be `#` prefixed or either a 3 characters shorthand) to RGB.
|
|
func HexToRGB(h string) (r float64, g float64, b float64) {
|
|
if string(h[0]) == "#" {
|
|
h = h[1:]
|
|
}
|
|
if len(h) == 3 {
|
|
h = string(h[0]) + string(h[0]) + string(h[1]) + string(h[1]) + string(h[2]) + string(h[2])
|
|
}
|
|
byteArray, _ := hex.DecodeString(h)
|
|
r = float64(byteArray[0])
|
|
g = float64(byteArray[1])
|
|
b = float64(byteArray[2])
|
|
return
|
|
}
|
|
|
|
// HTMLToRGB converts the color from HTML color name or a Hex string (can be `#` prefixed or either a 3 characters shorthand) to RGB.
|
|
func HTMLToRGB(h string) (r float64, g float64, b float64) {
|
|
if string(h[0]) == "#" {
|
|
h = h[1:]
|
|
r, g, b = HexToRGB(h)
|
|
} else {
|
|
v, ok := colorNames[strings.ToUpper(h)]
|
|
if !ok {
|
|
r = 0
|
|
g = 0
|
|
b = 0
|
|
return
|
|
}
|
|
r, g, b = HexToRGB(v)
|
|
}
|
|
return
|
|
}
|
|
|
|
// RGBToHTML converts the color from RGB to a `#` prefixed Hex string if it doesn't have a HTML color name.
|
|
func RGBToHTML(r float64, g float64, b float64) (h string) {
|
|
h = RGBToHex(r, g, b)
|
|
v, ok := hexNames[h]
|
|
if !ok {
|
|
h = "#" + h
|
|
return
|
|
}
|
|
h = v
|
|
return
|
|
}
|
|
|
|
// NewHTML initializes a color based on the HTML color name.
|
|
func NewHTML(color string) Color {
|
|
r, g, b := HTMLToRGB(color)
|
|
return newColor(r, g, b, 1)
|
|
}
|
|
|
|
// NewHTMLA initializes a color based on the HTML color name with an alpha channel.
|
|
func NewHTMLA(color string, a float64) Color {
|
|
r, g, b := HTMLToRGB(color)
|
|
return newColor(r, g, b, a)
|
|
}
|
|
|
|
// NewHex initializes a color based on a Hex string.
|
|
func NewHex(color string) Color {
|
|
r, g, b := HexToRGB(color)
|
|
return newColor(r, g, b, 1)
|
|
}
|
|
|
|
// NewHexA initializes a color based on a Hex string with an alpha channel.
|
|
func NewHexA(color string, a float64) Color {
|
|
r, g, b := HexToRGB(color)
|
|
return newColor(r, g, b, a)
|
|
}
|
|
|
|
// NewHSL initializes a color based on HSL.
|
|
func NewHSL(h float64, s float64, l float64) Color {
|
|
r, g, b := HSLToRGB(h, s, l)
|
|
return newColor(r, g, b, 1)
|
|
}
|
|
|
|
// NewHSLA initializes a color based on HSL with an alpha channel.
|
|
func NewHSLA(h float64, s float64, l float64, a float64) Color {
|
|
r, g, b := HSLToRGB(h, s, l)
|
|
return newColor(r, g, b, a)
|
|
}
|
|
|
|
// NewHSV initializes a color based on HSV.
|
|
func NewHSV(h float64, s float64, v float64) Color {
|
|
r, g, b := HSVToRGB(h, s, v)
|
|
return newColor(r, g, b, 1)
|
|
}
|
|
|
|
// NewHSVA initializes a color based on HSV with an alpha channel.
|
|
func NewHSVA(h float64, s float64, v float64, a float64) Color {
|
|
r, g, b := HSVToRGB(h, s, v)
|
|
return newColor(r, g, b, a)
|
|
}
|
|
|
|
// NewRGB initializes a color based on RGB.
|
|
func NewRGB(r float64, g float64, b float64) Color {
|
|
return newColor(r, g, b, 1)
|
|
}
|
|
|
|
// NewRGBA initializes a color based on RGB with an alpha channel.
|
|
func NewRGBA(r float64, g float64, b float64, a float64) Color {
|
|
return newColor(r, g, b, a)
|
|
}
|
|
|
|
// NewCMYK initializes a color based on CMYK.
|
|
func NewCMYK(c float64, m float64, y float64, k float64) Color {
|
|
r, g, b := CMYKToRGB(c, m, y, k)
|
|
return newColor(r, g, b, 1)
|
|
}
|
|
|
|
// NewCMYKA initializes a color based on CMYK with an alpha channel.
|
|
func NewCMYKA(c float64, m float64, y float64, k float64, a float64) Color {
|
|
r, g, b := CMYKToRGB(c, m, y, k)
|
|
return newColor(r, g, b, a)
|
|
}
|
|
|
|
// Mix mixs both color with the specified weight of the second color. (`0.5` as `50%`)
|
|
func (c Color) Mix(color Color, weight float64) Color {
|
|
oWeight := 1 - weight
|
|
r := math.Round(oWeight*c.Red + weight*color.Red)
|
|
g := math.Round(oWeight*c.Green + weight*color.Green)
|
|
b := math.Round(oWeight*c.Blue + weight*color.Blue)
|
|
a := math.Round(oWeight*c.Alpha + weight*color.Alpha)
|
|
return newColor(r, g, b, a)
|
|
}
|
|
|
|
// Hue returns the Hue angle of the current color based on the HSL algorithm.
|
|
func (c Color) Hue() float64 {
|
|
h, _, _ := c.HSL()
|
|
return h
|
|
}
|
|
|
|
// Saturation returns the percentage of the current color saturation based on the HSL algorithm.
|
|
func (c Color) Saturation() float64 {
|
|
_, s, _ := c.HSL()
|
|
return s
|
|
}
|
|
|
|
// Lightness returns the Lightness of the current color based on the HSL algorithm.
|
|
func (c Color) Lightness() float64 {
|
|
_, _, l := c.HSL()
|
|
return l
|
|
}
|
|
|
|
// AdjustHue rotates the Hue angle of the color based on HSL mode, it still goes clockwise if the value was set over than 360 degree.
|
|
func (c Color) AdjustHue(degrees float64) Color {
|
|
h, s, l := c.HSL()
|
|
h += degrees
|
|
for {
|
|
if h >= 0 && h <= 360 {
|
|
break
|
|
}
|
|
if h < 0 {
|
|
h += 360
|
|
} else {
|
|
h += -360
|
|
}
|
|
}
|
|
r, g, b := HSLToRGB(h, s, l)
|
|
return newColor(r, g, b, c.Alpha)
|
|
}
|
|
|
|
// Lighten increases the brightness of the color based on HSL mode. (`0.5` as `50%`)
|
|
func (c Color) Lighten(percent float64) Color {
|
|
percent = percent * 100
|
|
h, s, l := c.HSL()
|
|
l += percent
|
|
if l > 100 {
|
|
l = 100
|
|
}
|
|
r, g, b := HSLToRGB(h, s, l)
|
|
return newColor(r, g, b, c.Alpha)
|
|
}
|
|
|
|
// Darken decreases the brightness of the color based on HSL mode. (`0.5` as `50%`)
|
|
func (c Color) Darken(percent float64) Color {
|
|
percent = percent * 100
|
|
h, s, l := c.HSL()
|
|
l -= percent
|
|
if l < 0 {
|
|
l = 0
|
|
}
|
|
r, g, b := HSLToRGB(h, s, l)
|
|
return newColor(r, g, b, c.Alpha)
|
|
}
|
|
|
|
// Saturate increases the saturation of the color based on HSL mode. (`0.5` as `50%`)
|
|
func (c Color) Saturate(percent float64) Color {
|
|
percent = percent * 100
|
|
h, s, l := c.HSL()
|
|
s += percent
|
|
if s > 100 {
|
|
s = 100
|
|
}
|
|
r, g, b := HSLToRGB(h, s, l)
|
|
return newColor(r, g, b, c.Alpha)
|
|
}
|
|
|
|
// Desaturate decreases the saturation of the color based on HSL mode. (`0.5` as `50%`)
|
|
func (c Color) Desaturate(percent float64) Color {
|
|
percent = percent * 100
|
|
h, s, l := c.HSL()
|
|
s -= percent
|
|
if s < 0 {
|
|
s = 0
|
|
}
|
|
r, g, b := HSLToRGB(h, s, l)
|
|
return newColor(r, g, b, c.Alpha)
|
|
}
|
|
|
|
// Grayscale converts the color to grayscale, same as `Desaturate(1)`.
|
|
func (c Color) Grayscale() Color {
|
|
return c.Desaturate(1)
|
|
}
|
|
|
|
// Complement returns the complementary color of the current color, same as `AdjustHue(180)`.
|
|
func (c Color) Complement() Color {
|
|
return c.AdjustHue(180)
|
|
}
|
|
|
|
// Tint increases the brightness of the color while keeping the color tone, same as `Mix` with a white color. (`0.5` as `50%`)
|
|
func (c Color) Tint(percent float64) Color {
|
|
return c.Mix(newColor(255, 255, 255, c.Alpha), percent)
|
|
}
|
|
|
|
// Shade decreases the brightness of the color while keeping the color tone, same as `Mix` with a black color. (`0.5` as `50%`)
|
|
func (c Color) Shade(percent float64) Color {
|
|
return c.Mix(newColor(0, 0, 0, c.Alpha), percent)
|
|
}
|
|
|
|
// Invert returns the opposite color that based on the RGB color map (it's not a complementary color).
|
|
func (c Color) Invert() Color {
|
|
r, g, b := c.RGB()
|
|
r = 255 - r
|
|
g = 255 - g
|
|
b = 255 - b
|
|
return newColor(r, g, b, c.Alpha)
|
|
}
|
|
|
|
// LuminanaceWCAG returns the Luminance of the the current color based on the WCAG 2.0 algorithm.
|
|
//
|
|
// reference: https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests
|
|
//
|
|
// reference: https://medium.com/dev-channel/using-sass-to-automatically-pick-text-colors-4ba7645d2796
|
|
func (c Color) LuminanaceWCAG() float64 {
|
|
rgb := []float64{c.Red, c.Green, c.Blue}
|
|
for k, v := range rgb {
|
|
v /= 255
|
|
if v <= 0.03928 {
|
|
v = v / 12.92
|
|
} else {
|
|
v = math.Pow((v+0.055)/1.055, 2.4)
|
|
}
|
|
rgb[k] = v
|
|
}
|
|
v := rgb[0]*0.2126 + rgb[1]*0.7152 + rgb[2]*0.0722
|
|
return math.Round(v*100) / 100
|
|
}
|
|
|
|
// Luminanace returns the Luminance of the current color.
|
|
//
|
|
// reference: https://en.wikipedia.org/wiki/Relative_luminance
|
|
//
|
|
// reference: https://stackoverflow.com/questions/596216/formula-to-determine-brightness-of-rgb-color
|
|
//
|
|
// reference: https://stackoverflow.com/questions/3116260/given-a-background-color-how-to-get-a-foreground-color-that-makes-it-readable-o
|
|
func (c Color) Luminanace() float64 {
|
|
v := 0.2126*c.Red + 0.7152*c.Green + 0.0722*c.Blue
|
|
return math.Round(v*100) / 100
|
|
}
|
|
|
|
// Foreground returns suggested foreground color by calculating the color luminance, it returns a white color when the color is dark, vise versa.
|
|
func (c Color) Foreground() Color {
|
|
white := NewRGB(255, 255, 255)
|
|
black := NewRGB(0, 0, 0)
|
|
if c.Luminanace() < 140 {
|
|
return white
|
|
}
|
|
return black
|
|
}
|
|
|
|
// Brighten increases the brightness of the color. (`0.5` as `50%`)
|
|
//
|
|
// reference: https://github.com/ozdemirburak/iris
|
|
func (c Color) Brighten(percent float64) Color {
|
|
percent *= -100
|
|
r := math.Max(0, math.Min(255, c.Red-math.Round(255*(percent/100))))
|
|
g := math.Max(0, math.Min(255, c.Green-math.Round(255*(percent/100))))
|
|
b := math.Max(0, math.Min(255, c.Blue-math.Round(255*(percent/100))))
|
|
return newColor(r, g, b, c.Alpha)
|
|
}
|
|
|
|
// Contrast returns the Contrast of the current color based on the WCAG Luminance algorithm.
|
|
func (c Color) Contrast(color Color) float64 {
|
|
c1 := c.LuminanaceWCAG() + 0.05
|
|
c2 := color.LuminanaceWCAG() + 0.05
|
|
v := math.Max(c1, c2) / math.Min(c1, c2)
|
|
return math.Round(v*100) / 100
|
|
}
|
|
|
|
// IsLight returns true if the color is a light scheme, it might not be the same as what human eyes can see.
|
|
func (c Color) IsLight() bool {
|
|
darkness := 1 - (0.299*c.Red+0.587*c.Green+0.114*c.Blue)/255
|
|
return darkness < 0.5
|
|
}
|
|
|
|
// IsDark returns true if the color is a dark scheme, it might not be the same as what human eyes can see.
|
|
func (c Color) IsDark() bool {
|
|
return !c.IsLight()
|
|
}
|
|
|
|
// HSV returns the HSV value of the current color.
|
|
func (c Color) HSV() (float64, float64, float64) {
|
|
return RGBToHSV(c.Red, c.Green, c.Blue)
|
|
}
|
|
|
|
// HSVA returns the HSVA value of the current color.
|
|
func (c Color) HSVA() (float64, float64, float64, float64) {
|
|
h, s, v := RGBToHSV(c.Red, c.Green, c.Blue)
|
|
return h, s, v, c.Alpha
|
|
}
|
|
|
|
// HSL returns the HSL value of the current color.
|
|
func (c Color) HSL() (float64, float64, float64) {
|
|
return RGBToHSL(c.Red, c.Green, c.Blue)
|
|
}
|
|
|
|
// HSLA returns the HSLA value of the current color.
|
|
func (c Color) HSLA() (float64, float64, float64, float64) {
|
|
h, s, l := RGBToHSL(c.Red, c.Green, c.Blue)
|
|
return h, s, l, c.Alpha
|
|
}
|
|
|
|
// RGB returns the RGB value of the current color.
|
|
func (c Color) RGB() (float64, float64, float64) {
|
|
return c.Red, c.Green, c.Blue
|
|
}
|
|
|
|
// RGBA returns the RGBA value of the current color.
|
|
func (c Color) RGBA() (float64, float64, float64, float64) {
|
|
return c.Red, c.Green, c.Blue, c.Alpha
|
|
}
|
|
|
|
// CMYK returns the CMYK value of the current color.
|
|
func (c Color) CMYK() (float64, float64, float64, float64) {
|
|
return RGBToCMYK(c.Red, c.Green, c.Blue)
|
|
}
|
|
|
|
// Hex returns a Hex string of the current color. (Without the `#` prefix)
|
|
func (c Color) Hex() string {
|
|
return RGBToHex(c.Red, c.Green, c.Blue)
|
|
}
|
|
|
|
// HTML returns a `#` prefixed Hex string or the HTML color name (like: `red`, `yellow`) if it had one.
|
|
// It returns a format with `rgba()` if the color comes with a alpha channel.
|
|
func (c Color) HTML() string {
|
|
if c.Alpha != 1 {
|
|
return fmt.Sprintf("rgba(%f, %f, %f, %f)", c.Red, c.Green, c.Blue, c.Alpha)
|
|
}
|
|
return RGBToHTML(c.Red, c.Green, c.Blue)
|
|
}
|