Разобрать шестнадцатеричную строку в изображение / цвет - PullRequest
0 голосов
/ 15 января 2019

Как можно проанализировать цвет RGB в формате web color (3 или 6 шестнадцатеричных цифр) в Color из image/color?Есть ли какой-нибудь встроенный парсер для этого?Я хочу иметь возможность анализировать форматы цветов #XXXXXX и #XXX.color docs ничего не говорит об этом: https://golang.org/pkg/image/color/ но эта задача очень распространена, поэтому я считаю, что у go есть некоторые функции для этого (которых я просто не нашел).

Ответы [ 3 ]

0 голосов
/ 15 января 2019

Цвет RGBA составляет всего 4 байта, по одному для красного, зеленого, синего и альфа-каналов. Для трех или шести шестнадцатеричных цифр альфа-байт обычно подразумевается равным 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

0 голосов
/ 15 января 2019

1. Элегантное решение

Вот еще одно решение с использованием fmt.Sscanf(). Это конечно не самое быстрое решение, но оно элегантное. Он сканирует прямо в поля color.RGBA struct:

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 с, мы можем понизить до уровня обработки их как серии байтов. Да, Go хранит значения string в виде байтовых последовательностей 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' + 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. Ориентиры

Давайте оценим эти 2 решения. Код сравнения будет включать вызов их в длинных и коротких форматах. Ошибка исключена.

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

Как мы видим, «быстрое» решение примерно в 1050 * 250 раз быстрее и не использует распределения (в отличие от «элегантного» решения).

0 голосов
/ 15 января 2019

Вы можете преобразовать любые 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
}

Редактировать: Спасибо Питеру за исправление

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...