From 079365ccbb1555d5fc503588bbf4bcc2e4ae2201 Mon Sep 17 00:00:00 2001 From: inhere Date: Thu, 3 Dec 2020 00:41:07 +0800 Subject: [PATCH] feat: support convert rgb to 16-color code. close: #31 --- README.md | 4 +-- _examples/ref/cmd_color.go | 2 ++ color_16.go | 6 +++++ color_rgb.go | 7 ++--- color_rgb_test.go | 10 +++++++ go.mod | 4 ++- utils.go | 55 +++++++++++++++++++++++++++++++++++++- utils_test.go | 35 ++++++++++++++++++++++++ 8 files changed, 116 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 49d7767..981c12d 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,9 @@ Now, 256 colors and RGB colors have also been supported to work in Windows CMD a ## Features - Simple to use, zero dependencies - - Supports rich color output: 16-color, 256-color, true color (24-bit, RGB) + - Supports rich color output: 16-color (4-bit), 256-color (8-bit), true color (24-bit, RGB) - 16-color output is the most commonly used and most widely supported, working on any Windows version - - Since `v1.2.4` **the 256-color, true color (24-bit) support windows CMD and PowerShell** + - Since `v1.2.4` **the 256-color (8-bit), true color (24-bit) support windows CMD and PowerShell** - See [this gist](https://gist.github.com/XVilka/8346728) for information on true color support - Generic API methods: `Print`, `Printf`, `Println`, `Sprint`, `Sprintf` - Supports HTML tag-style color rendering, such as `message`. Support working on windows `cmd` `powerShell` diff --git a/_examples/ref/cmd_color.go b/_examples/ref/cmd_color.go index 55f0e1e..4eddcce 100644 --- a/_examples/ref/cmd_color.go +++ b/_examples/ref/cmd_color.go @@ -1,3 +1,5 @@ +//+build windows + package main import ( diff --git a/color_16.go b/color_16.go index a064f7d..1f54aa3 100644 --- a/color_16.go +++ b/color_16.go @@ -40,6 +40,12 @@ func (o Opts) String() string { * Basic 16 color definition *************************************************************/ +// Base value for foreground/background color +const ( + FgBase uint8 = 30 + BgBase uint8 = 40 +) + // Foreground colors. basic foreground colors 30 - 37 const ( FgBlack Color = iota + 30 diff --git a/color_rgb.go b/color_rgb.go index 78e4e4f..fa912c6 100644 --- a/color_rgb.go +++ b/color_rgb.go @@ -173,9 +173,10 @@ func (c RGBColor) C256() Color256 { } // C16 returns the closest approximate 256 (8 bit) color -// func (c RGBColor) C16() Color { -// return Color() -// } +// refer https://github.com/radareorg/radare2/blob/master/libr/cons/rgb.c#L249-L271 +func (c RGBColor) C16() Color { + return Color(RgbToAnsi(c[0], c[1], c[2], c[3] == AsBg)) +} /************************************************************* * RGB Style diff --git a/color_rgb_test.go b/color_rgb_test.go index 4778259..3a49b55 100644 --- a/color_rgb_test.go +++ b/color_rgb_test.go @@ -2,6 +2,8 @@ package color import ( "testing" + + "github.com/stretchr/testify/assert" ) func testRgbToC256Color(t *testing.T, name string, c RGBColor, expected uint8) { @@ -45,3 +47,11 @@ func TestRgbToC256Background(t *testing.T) { t.Errorf("background color didn't have background prefix: %v", prefix) } } + +func TestRGBColor_C16(t *testing.T) { + rgb := RGB(57, 187, 226) + assert.Equal(t, "36", rgb.C16().String()) + + rgb = RGB(57, 187, 226, true) + assert.Equal(t, "46", rgb.C16().String()) +} diff --git a/go.mod b/go.mod index f81dabd..3af875d 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,6 @@ module github.com/gookit/color go 1.12 -require github.com/stretchr/testify v1.3.0 +require ( + github.com/stretchr/testify v1.3.0 +) diff --git a/utils.go b/utils.go index 8572a42..e86d765 100644 --- a/utils.go +++ b/utils.go @@ -171,7 +171,7 @@ func HexToRgb(hex string) (rgb []int) { // Rgb2hex alias of the RgbToHex() func Rgb2hex(rgb []int) string { return RgbToHex(rgb) } -// RgbToHex convert RGB to hex code +// RgbToHex convert RGB-code to hex-code // Usage: // hex := RgbToHex([]int{170, 187, 204}) // hex: "aabbcc" func RgbToHex(rgb []int) string { @@ -183,6 +183,51 @@ func RgbToHex(rgb []int) string { return strings.Join(hexNodes, "") } +// Rgb2ansi alias of the RgbToAnsi() +func Rgb2ansi(r, g, b uint8, isBg bool) uint8 { + return RgbToAnsi(r, g, b, isBg) +} + +// RgbToAnsi convert RGB-code to 16-code +func RgbToAnsi(r, g, b uint8, isBg bool) uint8 { + var bright, c, k uint8 + + base := compareVal(isBg, BgBase, FgBase) + + // eco bright-specific + if r == 0x80 && g == 0x80 && b == 0x80 { // 0x80=128 + bright = 53 + } else if r == 0xff || g == 0xff || b == 0xff { // 0xff=255 + bright = 60 + } // else bright = 0 + + if r == g && g == b { + // 0x7f=127 + // r = (r > 0x7f) ? 1 : 0; + r = compareVal(r > 0x7f, 1, 0) + g = compareVal(g > 0x7f, 1, 0) + b = compareVal(b > 0x7f, 1, 0) + } else { + k = (r + g + b) / 3; + + // r = (r >= k) ? 1 : 0; + r = compareVal(r >= k, 1, 0) + g = compareVal(g >= k, 1, 0) + b = compareVal(b >= k, 1, 0) + } + + // c = (r ? 1 : 0) + (g ? (b ? 6 : 2) : (b ? 4 : 0)) + c = compareVal(r > 0, 1, 0) + + if g > 0 { + c += compareVal(b > 0, 6, 2) + } else { + c += compareVal(b > 0, 4, 0) + } + + return base + bright + c +} + /************************************************************* * print methods(will auto parse color tags) *************************************************************/ @@ -308,6 +353,14 @@ func Text(s string) string { // return runtime.GOOS == "windows" // } +// equals: return ok ? val1 : val2 +func compareVal(ok bool, val1, val2 uint8) uint8 { + if ok { + return val1 + } + return val2 +} + func saveInternalError(err error) { if err != nil { errors = append(errors, err) diff --git a/utils_test.go b/utils_test.go index 578f03a..f6d5c90 100644 --- a/utils_test.go +++ b/utils_test.go @@ -1,6 +1,7 @@ package color import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -43,3 +44,37 @@ func TestRgbToHex(t *testing.T) { assert.Equal(t, Rgb2hex(item.given), item.want) } } + +func TestRgbToAnsi(t *testing.T) { + tests := []struct { + want uint8 + rgb []uint8 + isBg bool + }{ + {40, []uint8{102, 102, 102}, true}, + {37, []uint8{204, 204, 204}, false}, + {47, []uint8{170, 78, 204}, true}, + {37, []uint8{170, 153, 245}, false}, + {30, []uint8{127, 127, 127}, false}, + {40, []uint8{127, 127, 127}, true}, + {90, []uint8{128, 128, 128}, false}, + {97, []uint8{34, 56, 255}, false}, + {31, []uint8{134, 56, 56}, false}, + {30, []uint8{0, 0, 0}, false}, + {40, []uint8{0, 0, 0}, true}, + {97, []uint8{255, 255, 255}, false}, + {107, []uint8{255, 255, 255}, true}, + } + + for _, item := range tests { + r, g, b := item.rgb[0], item.rgb[1], item.rgb[2] + + assert.Equal( + t, + item.want, + RgbToAnsi(r, g, b, item.isBg), + fmt.Sprint("rgb=", item.rgb, ", is bg? ", item.isBg), + ) + assert.Equal(t, item.want, Rgb2ansi(r, g, b, item.isBg)) + } +}