Skip to content

Commit

Permalink
Modify error messages in constructors
Browse files Browse the repository at this point in the history
  • Loading branch information
kimikage committed May 28, 2021
1 parent e674ba9 commit 0d9ab7b
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 58 deletions.
7 changes: 0 additions & 7 deletions src/conversions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,6 @@ convert(::Type{T}, x::Gray24) where {T<:Real} = convert(T, gray(x))
real(x::ColorantN{1}) = comp1(x)
real(x::Type{<:ColorantN{1}}) = real(eltype(x))

# Define some constructors that just call convert since the fallback constructor in Base
# is removed in Julia 0.7
# The parametric types are handled in @make_constructors and @make_alpha
for t in (:ARGB32, :Gray24, :RGB24)
@eval $t(x) = convert($t, x)
end

# reinterpret
for T in (RGB24, ARGB32, Gray24, AGray32)
@eval begin
Expand Down
78 changes: 29 additions & 49 deletions src/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -390,10 +390,6 @@ end
Gray24() = reinterpret(Gray24, UInt32(0))
_Gray24(val::UInt8) = (g = UInt32(val); reinterpret(Gray24, g<<16 | g<<8 | g))
Gray24(val::N0f8) = _Gray24(reinterpret(val))
function Gray24(val::Real)
checkval(Gray24, val)
Gray24(val%N0f8)
end

"""
`AGray32` uses a `UInt32` representation of color, 0xAAIIIIII, where
Expand All @@ -414,7 +410,7 @@ end
AGray32() = reinterpret(AGray32, 0xff000000)
_AGray32(val::UInt8, alpha::UInt8 = 0xff) = (g = UInt32(val); reinterpret(AGray32, UInt32(alpha)<<24 | g<<16 | g<<8 | g))
AGray32(val::N0f8, alpha::N0f8 = N0f8(1)) = _AGray32(reinterpret(val), reinterpret(alpha))
function AGray32(val::Real, alpha = 1)
function AGray32(val::Real, alpha)
checkval(AGray32, val, alpha)
AGray32(val%N0f8, alpha%N0f8)
end
Expand Down Expand Up @@ -615,6 +611,18 @@ end
for C in (RGB, BGR, XRGB, RGBX)
@eval (::Type{$C{T}})(r::GrayLike, g::GrayLike, b::GrayLike) where T = $C{T}(gray(r), gray(g), gray(b))
end
function (::Type{C})(x::UInt32) where C <: Union{RGB24, Gray24, ARGB32, AGray32}
x <= UInt32(1) || throw_bit_pattern_error(C, x)
reinterpret(C, C <: Color ? (-x) & 0xffffff : (-x) | 0xff000000)
end
function (::Type{C})(x::GrayLike) where C <: Union{RGB24, Gray24, ARGB32, AGray32}
checkval(C, real(x))
v = _rem(real(x), N0f8)
return C <: Union{RGB24, ARGB32} ? C(v, v, v) : C(v)
end
function (::Type{C})(x) where C <: Union{RGB24, Gray24, ARGB32, AGray32}
convert(C, x)
end

alphacolor(::Type{C}) where {C<:AlphaColor} = base_colorant_type(C)
alphacolor(::Type{C}) where {C<:ColorAlpha} = alphacolor(base_color_type(C))
Expand Down Expand Up @@ -675,26 +683,16 @@ end
isok(T, a, b) & isok(T, c, d) & isok(T, e) || throw_colorerror(C, (a, b, c, d, e))
end

checkval(::Type{C}, a::T) where {T<:Normed, C<:Colorant{T}} = nothing
checkval(::Type{C}, a::T, b::T) where {T<:Normed, C<:Colorant{T}} = nothing
checkval(::Type{C}, a::T, b::T, c::T) where {T<:Normed, C<:Colorant{T}} = nothing
checkval(::Type{C}, a::T, b::T, c::T, d::T) where {T<:Normed, C<:Colorant{T}} = nothing
checkval(::Type{C}, a::T, b::T, c::T, d::T, e::T) where {T<:Normed, C<:Colorant{T}} = nothing

checkval(::Type{C}, a) where {C<:Colorant} = nothing
checkval(::Type{C}, a, b) where {C<:Colorant} = nothing
checkval(::Type{C}, a, b, c) where {C<:Colorant} = nothing
checkval(::Type{C}, a, b, c, d) where {C<:Colorant} = nothing
checkval(::Type{C}, a, b, c, d, e) where {C<:Colorant} = nothing
checkval(::Type{C}, args::Vararg{T}) where {T<:Normed, C<:Colorant{T}} = nothing
checkval(::Type{C}, args...) where {C<:Colorant} = nothing


function throw_colorerror_(::Type{T}, values) where T<:Normed
io = IOBuffer()
show(IOContext(io, :compact=>true), typemin(T)); Tmin = String(take!(io))
show(IOContext(io, :compact=>true), typemax(T)); Tmax = String(take!(io))
Tmin = repr(typemin(T), context=:compact=>true)
Tmax = repr(typemax(T), context=:compact=>true)
bitstring = sizeof(T) == 1 ? "an 8-bit" : "a $(8*sizeof(T))-bit"
throw(ArgumentError("""
element type $T is $bitstring type representing $(2^(8*sizeof(T))) values from $Tmin to $Tmax,
component type $T is $bitstring type representing $(2^(8*sizeof(T))) values from $Tmin to $Tmax,
but the values $values do not lie within this range.
See the READMEs for FixedPointNumbers and ColorTypes for more information."""))
end
Expand All @@ -708,44 +706,26 @@ function throw_colorerror(::Type{C}, values::Tuple{Vararg{Integer}}) where C<:Co
vstr = "$values are integers"
end
Cstr = colorant_string_with_eltype(C)
args = join(map(v->"$v/255", values), ',')
args = join(map(v -> "$v/255", values), ", ")
throw(ArgumentError("""
$vstr in the range 0-255, but integer inputs are encoded with the N0f8
type, an 8-bit type representing 256 discrete values between 0 and 1.
The components of $(nameof(C)) are normalized to the range 0-1,
but $vstr in the range 0-255.
Consider dividing your input values by 255, for example: $Cstr($args)
Or use `reinterpret(N0f8, x)` if `x` is a `UInt8`.
The component type N0f8 is an 8-bit type representing 256 values from 0 to 1.
You can also use `reinterpret(N0f8, x % UInt8)` to encode the input into N0f8.
See the READMEs for FixedPointNumbers and ColorTypes for more information."""))
end
function throw_colorerror(::Type{C},
values::Tuple{Vararg{Integer}}) where C<:Union{RGB24,ARGB32,Gray24,AGray32}
# For consistency, some sets of `UInt32` inputs are valid for the
# constructors of these non-parametric colors,
# e.g. `RGB24(UInt32(1), UInt32(0), UInt32(0))` is valid.
# Therefore, this error should be thrown after the range checking.

# We want to suggest `reinterpret` only when users call the 1-arg version of
# constructors (e.g. `RGB24(0x123456)`) or try to convert a `UInt32` number.
# However, the current implementation cannot distinguish what the users
# actually called.
# So, we suggest using `reinterpret` when the input is opaque "gray".
tripled(t) = t[1] isa UInt32 && t[1] === t[2] && t[2] === t[3]
if C === RGB24 && length(values) == 3 && tripled(values)
elseif C === ARGB32 && length(values) == 4 && tripled(values) && values[4] == 1
elseif C === Gray24 && length(values) == 1 && values[1] isa UInt32
elseif C === AGray32 && length(values) == 2 && values[1] isa UInt32 && values[2] == 1
else
throw_colorerror_(N0f8, values)
end
hex = string(values[1], base=16, pad=8)
function throw_colorerror(::Type{C}, values::Tuple) where {T<:Normed, C<:Colorant{T}}
throw_colorerror_(T, values)
end

function throw_bit_pattern_error(@nospecialize(C), value)
hex = string(value, base=16, pad=8)
throw(ArgumentError("""
$C cannot be constructed or converted directly from a UInt32 input as a bit pattern.
Use `reinterpret($C, 0x$hex)` instead."""))
end

function throw_colorerror(::Type{C}, values::Tuple) where {T<:Normed, C<:Colorant{T}}
throw_colorerror_(T, values)
end

_rem(x,::Type{T}) where {T<:Normed} = x % T
_rem(x, ::Type{T}) where {T} = x

Expand Down
8 changes: 6 additions & 2 deletions test/conversions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -846,18 +846,22 @@ end
@test Gray24(1) == 1
@test !(Gray24(1) == 0x00ffffff)
@test !(AGray32(1,0) == 0x00ffffff)
@test reinterpret(UInt32, RGB24(0x000001)) === 0x00ffffff
@test reinterpret(UInt32, ARGB32(0x000000)) === 0xff000000
@test reinterpret(UInt32, Gray24(0x000000)) === 0x00000000
@test reinterpret(UInt32, AGray32(0x000001)) === 0xffffffff
ret = @test_throws ArgumentError RGB24(0x00ffffff)
@test occursin("Use `reinterpret(RGB24, 0x00ffffff)`", ret.value.msg)
ret = @test_throws ArgumentError ARGB32(0x00ffffff)
@test_broken occursin("Use `reinterpret(ARGB32, 0x00ffffff)`", ret.value.msg)
@test occursin("Use `reinterpret(ARGB32, 0x00ffffff)`", ret.value.msg)
ret = @test_throws ArgumentError Gray24(0x00ffffff)
@test occursin("Use `reinterpret(Gray24, 0x00ffffff)`", ret.value.msg)
ret = @test_throws ArgumentError AGray32(0x00ffffff)
@test occursin("Use `reinterpret(AGray32, 0x00ffffff)`", ret.value.msg)
c = 0x123456
ret = @test_throws ArgumentError RGB24(c >> 16 & 0xFF, c >> 8 & 0xFF, c & 0xFF)
@test !occursin("Use `reinterpret(RGB24", ret.value.msg)
@test occursin("(0x00000012, 0x00000034, 0x00000056) do not lie", ret.value.msg)
@test occursin("(0x00000012, 0x00000034, 0x00000056) are integers", ret.value.msg)
@test_throws ArgumentError RGB24[0x00000000,0x00808080]
@test_throws ArgumentError RGB24[0x00000000,0x00808080,0x00ffffff]
@test_throws ArgumentError RGB24[0x00000000,0x00808080,0x00ffffff,0x000000ff]
Expand Down

0 comments on commit 0d9ab7b

Please sign in to comment.