【问题标题】:Parse hex string to image/color将十六进制字符串解析为图像/颜色
【发布时间】:2019-06-09 09:49:24
【问题描述】:

如何将 web color 格式(3 或 6 个十六进制数字)的 RGB 颜色从 image/color 解析为 Color? Go 有任何内置的解析器吗? 我希望能够解析#XXXXXX#XXX 颜色格式。 color 文档对此一无所知:https://golang.org/pkg/image/color/ 但是这个任务很常见,所以我相信 go 有一些功能(我只是没有找到)。


更新:我基于accepted 创建了小型 Go 库,答案:github.com/g4s8/hexcolor

【问题讨论】:

  • 对不起,stdlib 中没有这个功能。你必须自己写。

标签: string go colors hex


【解决方案1】:

您可以使用 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的更正

【讨论】:

  • 要解析 uint8,请使用 ParseUint,而不是 ParseInt,并将最后一个参数设置为 8。否则,"#-e-4-c" 之类的内容将被视为有效输入。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    前言:我在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,甚至可以视为一系列runes,我们可能会降低到将其视为一系列字节的水平。是的,Go 将 string 值作为 UTF-8 字节序列存储在内存中,但如果输入是有效的颜色字符串,则其所有字节必须在映射到字节 1 对 1 的 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 倍,并且不使用分配(与“优雅”解决方案不同)。

    【讨论】:

    • 谢谢。甚至ParseHexColorFast 在使用 16 位颜色托盘时也能提供正确的颜色
    猜你喜欢
    • 2012-08-29
    • 1970-01-01
    • 2012-11-24
    • 2018-07-24
    • 2015-08-02
    • 1970-01-01
    • 2012-11-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多