Skip to content

Commit

Permalink
feat: support create Rgb color from Hsl value, issue#40
Browse files Browse the repository at this point in the history
  • Loading branch information
inhere committed Oct 17, 2021
1 parent b29d2b2 commit dfe4c47
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 57 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,16 @@ rgb.Println("rgb color")
rgb.C256().Println("256 color")
```

**More converts functions**:

convert to rgb:

- `func RGBFromSlice(rgb []uint8, isBg ...bool) RGBColor`
- `func RGBFromString(rgb string, isBg ...bool) RGBColor`
- `func HEX(hex string, isBg ...bool) RGBColor`
- `func HSL(h, s, l float32, isBg ...bool) RGBColor`
- `func HSLInt(h, s, l int, isBg ...bool) RGBColor`

## Func refer

There are some useful functions reference
Expand Down
44 changes: 37 additions & 7 deletions color_rgb.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ const (
// RGBColor{30,144,255, 0}
// RGBColor{30,144,255, 1}
//
// NOTICE: now support RGB color on windows CMD, PowerShell
// NOTICE: now support RGB color on Windows CMD, PowerShell
type RGBColor [4]uint8

// create a empty RGBColor
// create an empty RGBColor
var emptyRGBColor = RGBColor{3: 99}

// RGB color create.
Expand All @@ -97,6 +97,16 @@ func Rgb(r, g, b uint8, isBg ...bool) RGBColor { return RGB(r, g, b, isBg...) }
// Bit24 alias of the RGB()
func Bit24(r, g, b uint8, isBg ...bool) RGBColor { return RGB(r, g, b, isBg...) }

// RgbFromInt create instance from int r,g,b value
func RgbFromInt(r, g, b int, isBg ...bool) RGBColor {
return RGB(uint8(r), uint8(g), uint8(b), isBg...)
}

// RgbFromInts create instance from []int r,g,b value
func RgbFromInts(rgb []int, isBg ...bool) RGBColor {
return RGB(uint8(rgb[0]), uint8(rgb[1]), uint8(rgb[2]), isBg...)
}

// HEX create RGB color from a HEX color string.
//
// Usage:
Expand All @@ -118,7 +128,8 @@ func HEX(hex string, isBg ...bool) RGBColor {
func Hex(hex string, isBg ...bool) RGBColor { return HEX(hex, isBg...) }

// HSL create RGB color from a hsl value.
func HSL(h, s, l float32, isBg ...bool) RGBColor {
// more see HslToRgb()
func HSL(h, s, l float64, isBg ...bool) RGBColor {
if rgb := HslToRgb(h, s, l); len(rgb) > 0 {
return RGB(rgb[0], rgb[1], rgb[2], isBg...)
}
Expand All @@ -128,15 +139,29 @@ func HSL(h, s, l float32, isBg ...bool) RGBColor {
}

// Hsl alias of the HSL()
func Hsl(h, s, l float32, isBg ...bool) RGBColor { return HSL(h, s, l, isBg...) }
func Hsl(h, s, l float64, isBg ...bool) RGBColor { return HSL(h, s, l, isBg...) }

// HSLInt create RGB color from a hsl int value.
// more see HslIntToRgb()
func HSLInt(h, s, l int, isBg ...bool) RGBColor {
if rgb := HslIntToRgb(h, s, l); len(rgb) > 0 {
return RGB(rgb[0], rgb[1], rgb[2], isBg...)
}

// mark is empty
return emptyRGBColor
}

// HslInt alias of the HSLInt()
func HslInt(h, s, l int, isBg ...bool) RGBColor { return HSLInt(h, s, l, isBg...) }

// RGBFromSlice quick RGBColor from slice
func RGBFromSlice(rgb []uint8, isBg ...bool) RGBColor {
return RGB(rgb[0], rgb[1], rgb[2], isBg...)
}

// RGBFromString create RGB color from a string.
// support use color name in the {namedRgbColor}
// support use color name in the {namedRgbMap}
//
// Usage:
// c := RGBFromString("170,187,204")
Expand All @@ -145,8 +170,8 @@ func RGBFromSlice(rgb []uint8, isBg ...bool) RGBColor {
// c := RGBFromString("brown")
// c.Print("message with color brown")
func RGBFromString(rgb string, isBg ...bool) RGBColor {
// use color name in the {namedRgbColor}
if rgbVal, ok := namedRgbColor[rgb]; ok {
// use color name in the {namedRgbMap}
if rgbVal, ok := namedRgbMap[rgb]; ok {
rgb = rgbVal
}

Expand Down Expand Up @@ -219,6 +244,11 @@ func (c RGBColor) Hex() string {
return fmt.Sprintf("%02x%02x%02x", c[0], c[1], c[2])
}

// RgbString to color code string without prefix. eg: "204,123,56"
func (c RGBColor) RgbString() string {
return fmt.Sprintf("%d,%d,%d", c[0], c[1], c[2])
}

// FullCode to color code string with prefix
func (c RGBColor) FullCode() string {
return c.String()
Expand Down
40 changes: 27 additions & 13 deletions color_rgb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,14 @@ func TestPrintRGBColor(t *testing.T) {
HEXStyle("eee", "D50000").Println("deep-purple color")
}

func TestRGBStyle_SetOpts(t *testing.T) {
s := NewRGBStyle(RGB(234, 78, 23), RGB(20, 144, 234))
s.Println("rgb style message")

s.SetOpts(Opts{OpItalic, OpBold, OpUnderscore})
s.Println("RGB style message with options")
}

func testRgbToC256Color(t *testing.T, name string, c RGBColor, expected uint8) {
t.Log("RGB Color:", c.Sprint(name))
t.Log("256 Color:", c.C256().Sprint(name))
Expand All @@ -215,21 +223,13 @@ func testRgbToC256Color(t *testing.T, name string, c RGBColor, expected uint8) {
}
}

func TestRGBStyle_SetOpts(t *testing.T) {
s := NewRGBStyle(RGB(234, 78, 23), RGB(20, 144, 234))
s.Println("rgb style message")

s.SetOpts(Opts{OpItalic, OpBold, OpUnderscore})
s.Println("RGB style message with options")
}

func TestRgbToC256(t *testing.T) {
testRgbToC256Color(t, "white", RGB(255, 255, 255), 15)
testRgbToC256Color(t, "red", RGB(255, 0, 0), 9)
testRgbToC256Color(t, "yellow", RGB(255, 255, 0), 11)
testRgbToC256Color(t, "greenBg", RGB(0, 255, 0, true), 10)
testRgbToC256Color(t, "greenBg", RgbFromInt(0, 255, 0, true), 10)
testRgbToC256Color(t, "blueBg", RGB(0, 0, 255, true), 12)
testRgbToC256Color(t, "light blue", RGB(57, 187, 226), 74)
testRgbToC256Color(t, "light blue", RgbFromInts([]int{57, 187, 226}), 74)
}

func TestRgbToC256Background(t *testing.T) {
Expand Down Expand Up @@ -259,7 +259,21 @@ func TestRGBColor_C16(t *testing.T) {
}

func TestHSL(t *testing.T) {
hsl := HSL(0.33, 1, 0.5)
// red #ff0000 255, 0, 0
rgb := HSL(0, 1, 0.5)
rgb.Println("rgb color create by HSL; hex:", rgb.Hex(), "rgb:", rgb.RgbString())

rgb = Hsl(0, 1, 0.5)
rgb.Println("rgb color create by HSL; hex:", rgb.Hex(), "rgb:", rgb.RgbString())

rgb = HslInt(0, 100, 50)
rgb.Println("rgb color create by HSL int; hex:", rgb.Hex(), "rgb:", rgb.RgbString())

hsl.Println("rgb color create by HSL")
}
// maroon #800000 128,0,0 0,100%,25%
rgb = HslInt(0, 100, 25)
rgb.Println("rgb color create by HSL int; hex:", rgb.Hex(), "rgb:", rgb.RgbString())

// darkgray #a9a9a9 169,169,169 0,0%,66%
rgb = HslInt(0, 0, 66)
rgb.Println("rgb color create by HSL int; hex:", rgb.Hex(), "rgb:", rgb.RgbString())
}
112 changes: 88 additions & 24 deletions convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package color
import (
"fmt"
"math"
"sort"
"strconv"
"strings"
)
Expand Down Expand Up @@ -628,15 +629,17 @@ func C256ToRgbV1(val uint8) (rgb []uint8) {
* color: hsl(120, 75%, 75%) // pastel green, and so on
*/

// HslToRgbByInt Converts an HSL color value to RGB
// HslIntToRgb Converts an HSL color value to RGB
// Assumes h: 0-360, s: 0-100, l: 0-100
// returns r, g, and b in the set [0, 255].
//
// Usage:
// HslToRgbByInt(120, 100, 50)
// HslToRgbByInt(120, 100, 25)
func HslToRgbByInt(h, s, l int) (rgb []uint8) {
return HslToRgb(float32(h)/360, float32(s)/100, float32(l)/100)
// HslIntToRgb(0, 100, 50) // red
// HslIntToRgb(120, 100, 50) // lime
// HslIntToRgb(120, 100, 25) // dark green
// HslIntToRgb(120, 100, 75) // light green
func HslIntToRgb(h, s, l int) (rgb []uint8) {
return HslToRgb(float64(h)/360, float64(s)/100, float64(l)/100)
}

// HslToRgb Converts an HSL color value to RGB. Conversion formula
Expand All @@ -645,50 +648,110 @@ func HslToRgbByInt(h, s, l int) (rgb []uint8) {
// returns r, g, and b in the set [0, 255].
//
// Usage:
// rgbVals := HslToRgb(0.33, 1, 0.5)
func HslToRgb(h, s, l float32) (rgb []uint8) {
var r, g, b float32
// rgbVals := HslToRgb(0, 1, 0.5) // red
func HslToRgb(h, s, l float64) (rgb []uint8) {
var r, g, b float64

if s == 0 { // achromatic
r, g, b = l, l, l
} else {
var hue2rgb = func(p, q, t float32) float32 {
if t < 0 {
var hue2rgb = func(p, q, t float64) float64 {
if t < 0.0 {
t += 1
}
if t > 1 {
if t > 1.0 {
t -= 1
}
if t < 1/6 {
return p + (q-p)*6*t

if t < 1.0/6.0 {
return p + (q-p)*6.0*t
}

if t < 1/2 {
if t < 1.0/2.0 {
return q
}
if t < 2/3 {
return p + (q-p)*(2/3-t)*6

if t < 2.0/3.0 {
return p + (q-p)*(2.0/3.0-t)*6.0
}
return p
}

// q = l < 0.5 ? l * (1 + s) : l + s - l*s
var q float32
var q float64
if l < 0.5 {
q = l * (1 + s)
q = l * (1.0 + s)
} else {
q = l + s - l*s
}

var p = 2*l - q
var p = 2.0*l - q

r = hue2rgb(p, q, h+1/3)
r = hue2rgb(p, q, h+1.0/3.0)
g = hue2rgb(p, q, h)
b = hue2rgb(p, q, h-1/3)
b = hue2rgb(p, q, h-1.0/3.0)
}

// return []uint8{uint8(r * 255), uint8(g * 255), uint8(b * 255)}
return []uint8{
uint8(math.Round(r * 255)),
uint8(math.Round(g * 255)),
uint8(math.Round(b * 255)),
}
}

// RgbToHslInt Converts an RGB color value to HSL. Conversion formula
// Assumes r, g, and b are contained in the set [0, 255] and
// returns [h,s,l] h: 0-360, s: 0-100, l: 0-100.
func RgbToHslInt(r, g, b uint8) []int {
f64s := RgbToHsl(r, g, b)

return []int{int(f64s[0] * 360), int(f64s[1] * 100), int(f64s[2] * 100)}
}

// RgbToHsl Converts an RGB color value to HSL. Conversion formula
// adapted from http://en.wikipedia.org/wiki/HSL_color_space.
// Assumes r, g, and b are contained in the set [0, 255] and
// returns h, s, and l in the set [0, 1].
func RgbToHsl(r, g, b uint8) []float64 {
// to float64
fr, fg, fb := float64(r), float64(g), float64(b)
// percentage
pr, pg, pb := float64(r)/255.0, float64(g)/255.0, float64(b)/255.0

ps := []float64{pr, pg, pb}
sort.Float64s(ps)

min, max := ps[0], ps[2]
// max := math.Max(math.Max(pr, pg), pb)
// min := math.Min(math.Min(pr, pg), pb)

mid := (max + min) / 2

h, s, l := mid, mid, mid

if max == min {
h, s = 0, 0 // achromatic
} else {
var d = max - min
// s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
s = compareF64Val(l > 0.5, d/(2-max-min), d/(max+min))

switch max {
case fr:
// h = (g - b) / d + (g < b ? 6 : 0)
h = (fg - fb) / d
h += compareF64Val(g < b, 6, 0)
case fg:
h = (fb-fr)/d + 2
case fb:
h = (fr-fg)/d + 4
}

h /= 6
}

// return [math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
return []uint8{uint8(r * 255 / 100), uint8(g * 255 / 100), uint8(b * 255 / 100)}
return []float64{h, s, l}
}

/**************************************************************
Expand All @@ -707,12 +770,13 @@ func HslToRgb(h, s, l float32) (rgb []uint8) {
// Assumes h: 0-360, s: 0-100, l: 0-100
// returns r, g, and b in the set [0, 255].
func HsvToRgb(h, s, v int) (rgb []uint8) {
// TODO ...
return
}

// Named rgb colors
// https://www.w3.org/TR/css-color-3/#svg-color
var namedRgbColor = map[string]string{
var namedRgbMap = map[string]string{
"aliceblue": "240,248,255", // #F0F8FF
"antiquewhite": "250,235,215", // #FAEBD7
"aqua": "0,255,255", // #00FFFF
Expand Down
45 changes: 45 additions & 0 deletions convert_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package color

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func TestRgb2basic(t *testing.T) {
assert.Equal(t, uint8(31), Rgb2basic(134, 56, 56, false))
assert.Equal(t, uint8(41), Rgb2basic(134, 56, 56, true))
assert.Equal(t, uint8(46), Rgb2basic(57, 187, 226, true))
}

func TestHslToRgb(t *testing.T) {
// red #ff0000 255,0,0 0,100%,50%
rgbVal := HslToRgb(0, 1, 0.5)
// fmt.Println(rgbVal)
assert.Equal(t, []uint8{255, 0, 0}, rgbVal)

rgbVal = HslIntToRgb(0, 100, 50)
// fmt.Println(rgbVal)
assert.Equal(t, []uint8{255, 0, 0}, rgbVal)

rgbVal = HslIntToRgb(0, 100, 25)
// fmt.Println(rgbVal)
assert.Equal(t, []uint8{128, 0, 0}, rgbVal)

// darkgray #a9a9a9 169,169,169 0,0%,66%
rgbVal = HslIntToRgb(0, 0, 66)
fmt.Println(rgbVal)
assert.Equal(t, []uint8{168, 168, 168}, rgbVal)

rgbVal = HslToRgb(0, 0, 0.6627)
fmt.Println(rgbVal)
assert.Equal(t, []uint8{169, 169, 169}, rgbVal)

hslVal := RgbToHslInt(rgbVal[0], rgbVal[1], rgbVal[2])
fmt.Println(hslVal)
assert.Equal(t, []int{0, 0, 66}, hslVal)

hslFVal := RgbToHsl(rgbVal[0], rgbVal[1], rgbVal[2])
fmt.Println(hslFVal)
}
Loading

0 comments on commit dfe4c47

Please sign in to comment.