代码之家  ›  专栏  ›  技术社区  ›  Kirill

将十六进制字符串解析为图像/颜色

  •  1
  • Kirill  · 技术社区  · 6 年前

    如何解析RGB颜色 web color 将(3或6个十六进制数字)格式化为 Color image/color ?Go有内置的解析器吗? 我想能够分析两者 #XXXXXX #XXX 颜色格式。 color 医生对此一言不发: https://golang.org/pkg/image/color/ 但是这个任务很常见,所以我认为Go有一些功能(我只是没有找到)。

    3 回复  |  直到 6 年前
        1
  •  3
  •   icza    6 年前

    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。快速解

    如果性能真的很重要, S扫描() 是个很糟糕的选择。它需要一个实现必须解析的格式字符串,并根据该字符串解析输入,并使用反射将结果存储到指向的值中。

    因为任务基本上只是“解析”一个十六进制值,所以我们可以做得更好。我们甚至不需要调用一般的十六进制分析库(例如 encoding/hex )我们可以自己做。我们甚至不需要将输入视为 string 或作为一系列 rune s,我们可以降低到将其视为一系列字节的级别。是的,去商店 一串 值作为内存中的UTF-8字节序列,但如果输入是有效的颜色字符串,则其所有字节都必须在 0..127 它映射到字节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'
            case b >= 'A' && b <= 'F':
                return b - 'A'
            }
            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
    

    三。基准点

    让我们对这两个解决方案进行基准测试。基准代码将包括用长格式和短格式调用它们。排除错误情况。

    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次 更快,不使用分配(与“优雅”的解决方案不同)。

        2
  •  2
  •   Peter saif iqbal    6 年前

    一个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
  •  -1
  •   user10753492    6 年前

    您可以使用 strconv.ParseUint

    strconv.ParseUint(str, 16, 8)
    

    这个 16 表示以16为基数(十六进制),8表示位计数,在本例中为一个字节。

    您可以使用它将每两个字符解析为其组件

    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 用于更正