From e5f6923ed7e1b06d3181fb5a01d61fe6f8e8db63 Mon Sep 17 00:00:00 2001 From: kimikage Date: Sat, 7 Dec 2019 13:23:20 +0900 Subject: [PATCH] Add support for parsing 8-digit and 4-digit hex notations --- src/parse.jl | 101 ++++++++++++++++++++++++++++++++------------------ test/parse.jl | 8 ++-- 2 files changed, 70 insertions(+), 39 deletions(-) diff --git a/src/parse.jl b/src/parse.jl index 317e2d19..b09fb6fb 100644 --- a/src/parse.jl +++ b/src/parse.jl @@ -15,15 +15,16 @@ chop1(x) = SubString(x, 1, lastindex(x) - 1) # `chop` is slightly slow # Parse a number used in the "rgb()" or "hsl()" color. function parse_rgb(num::AbstractString) - if num[end] == '%' + if @inbounds num[end] == '%' return N0f8(clamp(parse(Int, chop1(num), base=10) / 100, 0, 1)) else - return reinterpret(N0f8, UInt8(clamp(parse(Int, num, base=10), 0, 255))) + v = clamp(parse(Int, num, base=10), 0, 255) + return reinterpret(N0f8, unsafe_trunc(UInt8, v)) end end function parse_hsl_hue(num::AbstractString) - if num[end] == '%' + if @inbounds num[end] == '%' error("hue cannot end in %") else return parse(Int, num, base=10) @@ -31,7 +32,7 @@ function parse_hsl_hue(num::AbstractString) end function parse_hsl_sl(num::AbstractString) - if num[end] != '%' + if @inbounds num[end] != '%' error("saturation and lightness must end in %") else return parse(Int, chop1(num), base=10) / 100 @@ -40,7 +41,7 @@ end # Parse a number used in the alpha field of "rgba()" and "hsla()". function parse_alpha_num(num::AbstractString) - if num[end] == '%' + if @inbounds num[end] == '%' return parse(Int, chop1(num), base=10) / 100f0 else # `parse(Float32, num)` is somewhat slow on Windows(x86_64-w64-mingw32). @@ -55,30 +56,52 @@ function parse_alpha_num(num::AbstractString) end function _parse_colorant(desc::AbstractString) + n0f8(x) = reinterpret(N0f8, unsafe_trunc(UInt8, x)) mat = match(col_pat_hex, desc) - if mat != nothing - prefix = mat.captures[1] - len = length(mat.captures[2]) + if mat !== nothing + prefix, len = mat.captures[1], length(mat.captures[2]) digits = parse(UInt32, mat.captures[2], base=16) if len == 6 return convert(RGB{N0f8}, reinterpret(RGB24, digits)) elseif len == 3 - return RGB{N0f8}(reinterpret(N0f8, UInt8(((digits&0xF00)>>8) * 17)), - reinterpret(N0f8, UInt8(((digits&0x0F0)>>4) * 17)), - reinterpret(N0f8, UInt8(((digits&0x00F)) * 17))) - elseif len == 8 || len == 4 - error("8-digit and 4-digit hex notations are not supported yet.") + return RGB(n0f8((digits>>8) & 0xF * 0x11), + n0f8((digits>>4) & 0xF * 0x11), + n0f8((digits>>0) & 0xF * 0x11)) + elseif len == 8 + if prefix[1] == '0' + return ARGB{N0f8}(n0f8(digits>>16), + n0f8(digits>> 8), + n0f8(digits>> 0), + n0f8(digits>>24)) + else + return RGBA{N0f8}(n0f8(digits>>24), + n0f8(digits>>16), + n0f8(digits>> 8), + n0f8(digits>> 0)) + end + elseif len == 4 + if prefix[1] == '0' + return ARGB{N0f8}(n0f8((digits>> 8) & 0xF * 0x11), + n0f8((digits>> 4) & 0xF * 0x11), + n0f8((digits>> 0) & 0xF * 0x11), + n0f8((digits>>12) & 0xF * 0x11)) + else + return RGBA{N0f8}(n0f8((digits>>12) & 0xF * 0x11), + n0f8((digits>> 8) & 0xF * 0x11), + n0f8((digits>> 4) & 0xF * 0x11), + n0f8((digits>> 0) & 0xF * 0x11)) + end end end mat = match(col_pat_rgb, desc) - if mat != nothing + if mat !== nothing return RGB{N0f8}(parse_rgb(mat.captures[1]), parse_rgb(mat.captures[2]), parse_rgb(mat.captures[3])) end mat = match(col_pat_hsl, desc) - if mat != nothing + if mat !== nothing T = ColorTypes.eltype_default(HSL) return HSL{T}(parse_hsl_hue(mat.captures[1]), parse_hsl_sl(mat.captures[2]), @@ -86,7 +109,7 @@ function _parse_colorant(desc::AbstractString) end mat = match(col_pat_rgba, desc) - if mat != nothing + if mat !== nothing return RGBA{N0f8}(parse_rgb(mat.captures[1]), parse_rgb(mat.captures[2]), parse_rgb(mat.captures[3]), @@ -94,7 +117,7 @@ function _parse_colorant(desc::AbstractString) end mat = match(col_pat_hsla, desc) - if mat != nothing + if mat !== nothing T = ColorTypes.eltype_default(HSLA) return HSLA{T}(parse_hsl_hue(mat.captures[1]), parse_hsl_sl(mat.captures[2]), @@ -104,36 +127,25 @@ function _parse_colorant(desc::AbstractString) sdesc = strip(desc) c = get(color_names, sdesc, nothing) - if c != nothing - return RGB{N0f8}(reinterpret(N0f8, UInt8(c[1])), - reinterpret(N0f8, UInt8(c[2])), - reinterpret(N0f8, UInt8(c[3]))) - end + c !== nothing && return RGB{N0f8}(n0f8(c[1]), n0f8(c[2]), n0f8(c[3])) + # since `lowercase` is slightly slow, it is applied only when needed ldesc = lowercase(sdesc) c = get(color_names, ldesc, nothing) - if c != nothing - return RGB{N0f8}(reinterpret(N0f8, UInt8(c[1])), - reinterpret(N0f8, UInt8(c[2])), - reinterpret(N0f8, UInt8(c[3]))) - end + c !== nothing && return RGB{N0f8}(n0f8(c[1]), n0f8(c[2]), n0f8(c[3])) - if ldesc == "transparent" - return RGBA{N0f8}(0,0,0,0) - end + ldesc == "transparent" && return RGBA{N0f8}(0,0,0,0) wo_spaces = replace(ldesc, r"(?<=[^ ]{3}) (?=[^ ]{3})" => "") c = get(color_names, wo_spaces, nothing) - if c != nothing + if c !== nothing camel = replace(titlecase(ldesc), " " => "") Base.depwarn( """ The X11 color names with spaces are not recommended because they are not allowed in the SVG/CSS. Use "$camel" or "$wo_spaces" instead. """, :parse) - return RGB{N0f8}(reinterpret(N0f8, UInt8(c[1])), - reinterpret(N0f8, UInt8(c[2])), - reinterpret(N0f8, UInt8(c[3]))) + return RGB{N0f8}(n0f8(c[1]), n0f8(c[2]), n0f8(c[3])) end error("Unknown color: ", desc) @@ -160,7 +172,8 @@ slightly different than W3C named colors in some cases), `rgb()`, `hsl()`, - `Colorant`: literal Colorant - `desc`: color name or description -A literal Colorant will parse according to the `desc` string (usually returning an `RGB`); any more specific choice will return a color of the specified type. +A literal Colorant will parse according to the `desc` string (usually returning +an `RGB`); any more specific choice will return a color of the specified type. # Returns @@ -172,11 +185,27 @@ A literal Colorant will parse according to the `desc` string (usually returning - an `HSLA` color if `hsla(h, s, l, a)` was used +- an `ARGB{N0f8}` color if `0xAARRGGBB`/`0xARGB` was used + - a specific `Colorant` type as specified in the first argument -!!! note +!!! note "Note for X11 named colors" The X11 color names with spaces (e.g. "sea green") are not recommended because they are not allowed in the SVG/CSS. + +!!! note "Note for hex notations" + You can parse not only the CSS-style hex notations `#RRGGBB`/`#RGB`, but + also `0xRRGGBB`/`0xRGB`. + + You can also parse the 8-digit or 4-digit hex notation into an RGB color + with alpha. However, the result depends on the prefix (i.e. `#` or `0x`). + ```@example + julia> parse(Colorant, "#FF8800AA") # transparent orange + RGBA{N0f8}(1.0,0.533,0.0,0.667) + + julia> parse(Colorant, "0xFF8800AA") # opaque purple + ARGB{N0f8}(0.533,0.0,0.667,1.0) + ``` """ Base.parse(::Type{C}, desc::AbstractString) where {C<:Colorant} = _parse_colorant(C, supertype(C), desc) Base.parse(::Type{C}, desc::Symbol) where {C<:Colorant} = parse(C, string(desc)) diff --git a/test/parse.jl b/test/parse.jl index b068b76e..d379ec50 100644 --- a/test/parse.jl +++ b/test/parse.jl @@ -21,11 +21,13 @@ using FixedPointNumbers @test parse(Colorant, "#D0FF58") === RGB(r8(0xD0),r8(0xFF),r8(0x58)) @test parse(Colorant, "0xd0ff58") === RGB(r8(0xD0),r8(0xFF),r8(0x58)) @test parse(Colorant, "#FB0") === RGB(r8(0xFF),r8(0xBB),r8(0x00)) - @test_throws ErrorException parse(Colorant, "#FB0A") + @test parse(Colorant, "#FB0A") === RGBA(r8(0xFF),r8(0xBB),r8(0x00),r8(0xAA)) + @test parse(Colorant, "0xFB0A") === ARGB(r8(0xBB),r8(0x00),r8(0xAA),r8(0xFF)) + @test parse(Colorant, "#FFBB00AA") === RGBA(r8(0xFF),r8(0xBB),r8(0x00),r8(0xAA)) + @test parse(Colorant, "0xFFBB00AA") === ARGB(r8(0xBB),r8(0x00),r8(0xAA),r8(0xFF)) @test_throws ErrorException parse(Colorant, "#BAD05") @test_throws ErrorException parse(Colorant, "#BAD0007") - @test_throws ErrorException parse(Colorant, "#FFBB00AA") # not supported yet - @test_throws ErrorException parse(Colorant, "0xFFBB00AA") # not supported yet + @test_throws ErrorException parse(Colorant, "#BAD000009") # rgb() @test parse(Colorant, "rgb(55,217,127)") === RGB{N0f8}(r8(0x37),r8(0xd9),r8(0x7f))