Go语言 将十六进制字符串解析为image/color

b5buobof  于 12个月前  发布在  Go
关注(0)|答案(4)|浏览(190)

如何解析web color的RGB颜色从image/color转换为Color?go有内置的解析器吗?我希望能够解析#XXXXXX#XXX的颜色格式。color文档对此没有说明:https://golang.org/pkg/image/color/但是这个任务很常见,所以我相信go有一些函数(我只是没有找到)。
更新:我创建了一个基于accepted的小型Go库回答:github.com/g4s8/hexcolor

iecba09b

iecba09b1#

**前言:**我在github.com/icza/gox中发布了这个实用程序(2.快速解决方案),请参见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),我们可以自己做。我们甚至不必将输入视为string,甚至视为一系列rune s,我们可以降低到将其视为一系列字节的水平。是的,Go语言将string的值以UTF-8字节序列的形式存储在内存中,但是如果输入是一个有效的颜色字符串,那么它的所有字节都必须在0..127的范围内,这些字节Map到字节1到1。如果不是这样,那么输入已经是无效的,我们将检测到,但是在这种情况下我们返回什么颜色不重要(不重要)。
现在让我们看看一个快速的实现:

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倍,并且不使用分配(不像“优雅”解决方案)。

wpx232ag

wpx232ag2#

RGBA颜色只有4个字节,分别用于红色,绿色,蓝色和alpha通道。对于三个或六个十六进制数字,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

uhry853o

uhry853o3#

您可以使用strconv.ParseUint将任何2个十六进制数字转换为整数

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
}


编辑:感谢Peter的更正

4bbkushb

4bbkushb4#

如果你确定你在做什么,你可以给予this作为函数的直接参数。

// HexColor converts hex color to color.RGBA with "#FFFFFF" format
func HexColor(hex string) color.RGBA {
    values, _ := strconv.ParseUint(string(hex[1:]), 16, 32)
    return color.RGBA{R: uint8(values >> 16), G: uint8((values >> 8) & 0xFF), B: uint8(values & 0xFF), A: 255}
}

字符串
测试

fmt.Println(HexColor("#5a9337")) // -> {90 147 55 255}

相关问题