3 回答

TA贡献1998条经验 获得超6个赞
前言:我在 中发布了此实用程序(2.Fast 解决方案)github.com/icza/gox
,请参阅colorx.ParseHexColor()
。
1.优雅的解决方案
这是另一个使用fmt.Sscanf()
. 它当然不是最快的解决方案,但它很优雅。它直接扫描到结构的字段中color.RGBA
:
func ParseHexColor(s string) (c color.RGBA, err error) {
c.A = 0xff
switch len(s) {
case 7:
_, err = fmt.Sscanf(s, "#%02x%02x%02x", &c.R, &c.G, &c.B)
case 4:
_, err = fmt.Sscanf(s, "#%1x%1x%1x", &c.R, &c.G, &c.B)
// Double the hex digits:
c.R *= 17
c.G *= 17
c.B *= 17
default:
err = fmt.Errorf("invalid length, must be 7 or 4")
}
return
}
测试它:
hexCols := []string{
"#112233",
"#123",
"#000233",
"#023",
"invalid",
"#abcd",
"#-12",
}
for _, hc := range hexCols {
c, err := ParseHexColor(hc)
fmt.Printf("%-7s = %3v, %v\n", hc, c, err)
}
输出(在Go Playground上尝试):
#112233 = { 17 34 51 255}, <nil>
#123 = { 17 34 51 255}, <nil>
#000233 = { 0 2 51 255}, <nil>
#023 = { 0 34 51 255}, <nil>
invalid = { 0 0 0 255}, input does not match format
#abcd = { 0 0 0 255}, invalid length, must be 7 or 4
#-12 = { 0 0 0 255}, expected integer
2.快速解决
如果性能确实很重要,fmt.Sscanf()
那是一个非常糟糕的选择。它需要一个实现必须解析的格式字符串,并根据它解析输入,并使用反射将结果存储到指向的值中。
由于任务基本上只是“解析”一个十六进制值,我们可以做得更好。我们甚至不必调用通用的十六进制解析库(例如encoding/hex
),我们可以自己完成。我们甚至不必将输入视为 a string
,甚至不必将其视为一系列rune
s,我们可能会降低到将其视为一系列字节的级别。是的,Go 在内存中将值存储为 UTF-8 字节序列,但如果输入是有效的颜色字符串,则其所有字节必须在1 对 1 映射到字节string
的范围内。0..127
如果不是这种情况,输入将已经无效,我们将检测到这一点,但在这种情况下我们返回什么颜色应该无关紧要(无关紧要)。
现在让我们看一个快速的实现:
var errInvalidFormat = errors.New("invalid format")
func ParseHexColorFast(s string) (c color.RGBA, err error) {
c.A = 0xff
if s[0] != '#' {
return c, errInvalidFormat
}
hexToByte := func(b byte) byte {
switch {
case b >= '0' && b <= '9':
return b - '0'
case b >= 'a' && b <= 'f':
return b - 'a' + 10
case b >= 'A' && b <= 'F':
return b - 'A' + 10
}
err = errInvalidFormat
return 0
}
switch len(s) {
case 7:
c.R = hexToByte(s[1])<<4 + hexToByte(s[2])
c.G = hexToByte(s[3])<<4 + hexToByte(s[4])
c.B = hexToByte(s[5])<<4 + hexToByte(s[6])
case 4:
c.R = hexToByte(s[1]) * 17
c.G = hexToByte(s[2]) * 17
c.B = hexToByte(s[3]) * 17
default:
err = errInvalidFormat
}
return
}
使用与第一个示例相同的输入对其进行测试,输出为(在Go Playground上尝试):
#112233 = { 17 34 51 255}, <nil>
#123 = { 17 34 51 255}, <nil>
#000233 = { 0 2 51 255}, <nil>
#023 = { 0 34 51 255}, <nil>
invalid = { 0 0 0 255}, invalid format
#abcd = { 0 0 0 255}, invalid format
#-12 = { 0 17 34 255}, invalid format
3. 基准
让我们对这两个解决方案进行基准测试。基准测试代码将包括使用长格式和短格式调用它们。排除错误情况。
func BenchmarkParseHexColor(b *testing.B) {
for i := 0; i < b.N; i++ {
ParseHexColor("#112233")
ParseHexColor("#123")
}
}
func BenchmarkParseHexColorFast(b *testing.B) {
for i := 0; i < b.N; i++ {
ParseHexColorFast("#112233")
ParseHexColorFast("#123")
}
}
以下是基准测试结果:
go test -bench . -benchmem
BenchmarkParseHexColor-4 500000 2557 ns/op 144 B/op 9 allocs/op
BenchmarkParseHexColorFast-4 100000000 10.3 ns/op 0 B/op 0 allocs/op
正如我们所见,“快速”解决方案大约快250 倍并且不使用分配(与“优雅”解决方案不同)。

TA贡献1808条经验 获得超4个赞
RGBA 颜色只有 4 个字节,红色、绿色、蓝色和 alpha 通道各一个。对于三个或六个十六进制数字,字母字节通常隐含为 0xFF(AABBCC被认为与 一样AABBCCFF,按原样ABC)。
因此解析颜色字符串就像对其进行规范化一样简单,使其具有“RRGGBBAA”(4 个十六进制编码字节)的形式,然后对其进行解码:
package main
import (
"encoding/hex"
"fmt"
"image/color"
"log"
)
func main() {
colorStr := "102030FF"
colorStr, err := normalize(colorStr)
if err != nil {
log.Fatal(err)
}
b, err := hex.DecodeString(colorStr)
if err != nil {
log.Fatal(err)
}
color := color.RGBA{b[0], b[1], b[2], b[3]}
fmt.Println(color) // Output: {16 32 48 255}
}
func normalize(colorStr string) (string, error) {
// left as an exercise for the reader
return colorStr, nil
}
在操场上试试:https ://play.golang.org/p/aCX-vyfMG4G

TA贡献1834条经验 获得超8个赞
您可以使用将任意 2 个十六进制数字转换为整数strconv.ParseUint
strconv.ParseUint(str, 16, 8)
指示16
基数 16(十六进制),8 指示位数,在本例中为一个字节。
您可以使用它来将每 2 个字符解析为它们的组件
https://play.golang.org/p/B56B8_NvnVR
func ParseHexColor(v string) (out color.RGBA, err error) {
if len(v) != 7 {
return out, errors.New("hex color must be 7 characters")
}
if v[0] != '#' {
return out, errors.New("hex color must start with '#'")
}
var red, redError = strconv.ParseUint(v[1:3], 16, 8)
if redError != nil {
return out, errors.New("red component invalid")
}
out.R = uint8(red)
var green, greenError = strconv.ParseUint(v[3:5], 16, 8)
if greenError != nil {
return out, errors.New("green component invalid")
}
out.G = uint8(green)
var blue, blueError = strconv.ParseUint(v[5:7], 16, 8)
if blueError != nil {
return out, errors.New("blue component invalid")
}
out.B = uint8(blue)
return
}
- 3 回答
- 0 关注
- 201 浏览
添加回答
举报