Skip to content

Commit

Permalink
Fix stack overflow in undefined color space conversions (#466)
Browse files Browse the repository at this point in the history
This avoids stack overflow in conversions for color types which are not defined in ColorTypes.jl.

This also attempts to convert via `RGB` instead of directly to the destination type as a fallback.
That improves support for source types other than `Color3`.
  • Loading branch information
kimikage authored Apr 25, 2021
1 parent 007a8c8 commit a4886a0
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 23 deletions.
50 changes: 27 additions & 23 deletions src/conversions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ end

ColorTypes._convert(::Type{Cdest}, ::Type{Odest}, ::Type{Osrc}, c) where {Cdest<:Color,Odest,Osrc} = cnvt(Cdest, c)


# with the whitepoint `wp` as the third argument
convert(::Type{XYZ}, c, wp::XYZ) = cnvt(XYZ{eltype(wp)}, c, wp)
convert(::Type{Lab}, c, wp::XYZ) = cnvt(Lab{eltype(wp)}, c, wp)
Expand All @@ -62,12 +61,13 @@ convert(::Type{Luv{T}}, c, wp::XYZ) where {T} = cnvt(Luv{T}, c, wp)

# Fallback to catch undefined operations
cnvt(::Type{C}, c::TransparentColor) where {C<:Color} = cnvt(C, color(c))
cnvt(::Type{C}, c) where {C} = convert(C, c)
cnvt(::Type{C}, c) where {C} = convert(C, convert(RGB{eltype(C)}, c)::RGB{eltype(C)})

# Conversions from grayscale
# --------------------------
cnvt(::Type{C}, g::AbstractGray) where {C<:Color3} = cnvt(C, convert(RGB{eltype(C)}, g))

function ColorTypes._convert(::Type{Cdest}, ::Type{Odest}, ::Type{Osrc}, g) where {Cdest<:Color,Odest,Osrc<:AbstractGray}
cnvt(Cdest, convert(RGB{eltype(Cdest)}, g))
end

macro mul3x3(T, M, c1, c2, c3)
esc(quote
Expand All @@ -92,9 +92,6 @@ function srgb_compand(v::Fractional)
v <= 0.0031308 ? 12.92v : 1.055 * pow5_12(v) - 0.055
end

cnvt(::Type{CV}, c::AbstractRGB) where {CV<:AbstractRGB} = CV(red(c), green(c), blue(c))


function _hsx_to_rgb(im::UInt8, v, n, m)
#=
if hue < 60; im = 0b000001 # ---------+
Expand Down Expand Up @@ -195,7 +192,14 @@ function cnvt(::Type{CV}, c::YCbCr) where CV<:AbstractRGB
clamp01(0.004567ny + 0.00791058ncb - 2.79201e-7ncr))
end

cnvt(::Type{CV}, c::Color3) where {CV<:AbstractRGB} = cnvt(CV, convert(XYZ{eltype(c)}, c))
# To avoid stack overflow, the source types which do not support direct or
# indirect conversion to RGB should be rejected.
cnvt(::Type{CV}, c::Union{LMS, xyY} ) where {CV<:AbstractRGB} = cnvt(CV, convert(XYZ, c))
cnvt(::Type{CV}, c::Union{Lab, Luv, LCHab, LCHuv}) where {CV<:AbstractRGB} = cnvt(CV, convert(XYZ, c))
cnvt(::Type{CV}, c::Union{DIN99d, DIN99o, DIN99} ) where {CV<:AbstractRGB} = cnvt(CV, convert(XYZ, c))
@noinline function cnvt(::Type{CV}, @nospecialize(c::Color)) where {CV<:AbstractRGB}
error("No conversion of ", c, " to ", CV, " has been defined")
end

# AbstractGray --> AbstractRGB conversions are implemented in ColorTypes.jl

Expand All @@ -221,7 +225,7 @@ function cnvt(::Type{HSV{T}}, c::AbstractRGB) where T
end


cnvt(::Type{HSV{T}}, c::Color3) where {T} = cnvt(HSV{T}, convert(RGB{T}, c))
cnvt(::Type{HSV{T}}, c::Color) where {T} = cnvt(HSV{T}, convert(RGB{T}, c))


# Everything to HSL
Expand All @@ -247,7 +251,7 @@ function cnvt(::Type{HSL{T}}, c::AbstractRGB) where T
end


cnvt(::Type{HSL{T}}, c::Color3) where {T} = cnvt(HSL{T}, convert(RGB{T}, c))
cnvt(::Type{HSL{T}}, c::Color) where {T} = cnvt(HSL{T}, convert(RGB{T}, c))


# Everything to HSI
Expand All @@ -269,7 +273,7 @@ cnvt(::Type{HSL{T}}, c::Color3) where {T} = cnvt(HSL{T}, convert(RGB{T}, c))
HSI{T}(b > g ? F(360) - h : h, s, i)
end

cnvt(::Type{HSI{T}}, c::Color3) where {T} = cnvt(HSI{T}, convert(RGB{T}, c))
cnvt(::Type{HSI{T}}, c::Color) where {T} = cnvt(HSI{T}, convert(RGB{T}, c))

# Everything to XYZ
# -----------------
Expand Down Expand Up @@ -386,7 +390,7 @@ end

cnvt(::Type{XYZ{T}}, c::Union{LCHab, DIN99, DIN99o}) where {T} = cnvt(XYZ{T}, convert(Lab{T}, c))
cnvt(::Type{XYZ{T}}, c::LCHuv) where {T} = cnvt(XYZ{T}, convert(Luv{T}, c))
cnvt(::Type{XYZ{T}}, c::Color3) where {T} = cnvt(XYZ{T}, convert(RGB{T}, c))
cnvt(::Type{XYZ{T}}, c::Color) where {T} = cnvt(XYZ{T}, convert(RGB{T}, c))

# Everything to xyY
# -----------------
Expand All @@ -400,7 +404,7 @@ function cnvt(::Type{xyY{T}}, c::XYZ) where T

end

cnvt(::Type{xyY{T}}, c::Color3) where {T} = cnvt(xyY{T}, convert(XYZ{T}, c))
cnvt(::Type{xyY{T}}, c::Color) where {T} = cnvt(xyY{T}, convert(XYZ{T}, c))



Expand Down Expand Up @@ -486,7 +490,7 @@ function cnvt(::Type{Lab{T}}, c::DIN99o) where T
end


cnvt(::Type{Lab{T}}, c::Color3) where {T} = cnvt(Lab{T}, convert(XYZ{T}, c))
cnvt(::Type{Lab{T}}, c::Color) where {T} = cnvt(Lab{T}, convert(XYZ{T}, c))


# Everything to Luv
Expand All @@ -511,7 +515,7 @@ function cnvt(::Type{Luv{T}}, c::LCHuv) where T
Luv{T}(c.l, u, v)
end

cnvt(::Type{Luv{T}}, c::Color3) where {T} = cnvt(Luv{T}, convert(XYZ{T}, c))
cnvt(::Type{Luv{T}}, c::Color) where {T} = cnvt(Luv{T}, convert(XYZ{T}, c))


# Everything to LCHuv
Expand All @@ -522,7 +526,7 @@ function cnvt(::Type{LCHuv{T}}, c::Luv) where T
end


cnvt(::Type{LCHuv{T}}, c::Color3) where {T} = cnvt(LCHuv{T}, convert(Luv{T}, c))
cnvt(::Type{LCHuv{T}}, c::Color) where {T} = cnvt(LCHuv{T}, convert(Luv{T}, c))


# Everything to LCHab
Expand All @@ -533,7 +537,7 @@ function cnvt(::Type{LCHab{T}}, c::Lab) where T
end


cnvt(::Type{LCHab{T}}, c::Color3) where {T} = cnvt(LCHab{T}, convert(Lab{T}, c))
cnvt(::Type{LCHab{T}}, c::Color) where {T} = cnvt(LCHab{T}, convert(Lab{T}, c))


# Everything to DIN99
Expand Down Expand Up @@ -570,7 +574,7 @@ function cnvt(::Type{DIN99{T}}, c::Lab) where T
end


cnvt(::Type{DIN99{T}}, c::Color3) where {T} = cnvt(DIN99{T}, convert(Lab{T}, c))
cnvt(::Type{DIN99{T}}, c::Color) where {T} = cnvt(DIN99{T}, convert(Lab{T}, c))


# Everything to DIN99d
Expand Down Expand Up @@ -603,7 +607,7 @@ function cnvt(::Type{DIN99d{T}}, c::XYZ{T}) where T
end


cnvt(::Type{DIN99d{T}}, c::Color3) where {T} = cnvt(DIN99d{T}, convert(XYZ{T}, c))
cnvt(::Type{DIN99d{T}}, c::Color) where {T} = cnvt(DIN99d{T}, convert(XYZ{T}, c))


# Everything to DIN99o
Expand Down Expand Up @@ -642,7 +646,7 @@ function cnvt(::Type{DIN99o{T}}, c::Lab) where T
end


cnvt(::Type{DIN99o{T}}, c::Color3) where {T} = cnvt(DIN99o{T}, convert(Lab{T}, c))
cnvt(::Type{DIN99o{T}}, c::Color) where {T} = cnvt(DIN99o{T}, convert(Lab{T}, c))


# Everything to LMS
Expand All @@ -668,7 +672,7 @@ function cnvt(::Type{LMS{T}}, c::XYZ) where T
end


cnvt(::Type{LMS{T}}, c::Color3) where {T} = cnvt(LMS{T}, convert(XYZ{T}, c))
cnvt(::Type{LMS{T}}, c::Color) where {T} = cnvt(LMS{T}, convert(XYZ{T}, c))

# Everything to YIQ
# -----------------
Expand All @@ -684,7 +688,7 @@ function cnvt(::Type{YIQ{T}}, c::AbstractRGB) where T
0.211456*red(rgb)-0.522591*green(rgb)+0.311135*blue(rgb))
end

cnvt(::Type{YIQ{T}}, c::Color3) where {T} = cnvt(YIQ{T}, convert(RGB{T}, c))
cnvt(::Type{YIQ{T}}, c::Color) where {T} = cnvt(YIQ{T}, convert(RGB{T}, c))


# Everything to YCbCr
Expand All @@ -701,7 +705,7 @@ function cnvt(::Type{YCbCr{T}}, c::AbstractRGB) where T
128+112*red(rgb)-93.786*green(rgb)-18.214*blue(rgb))
end

cnvt(::Type{YCbCr{T}}, c::Color3) where {T} = cnvt(YCbCr{T}, convert(RGB{T}, c))
cnvt(::Type{YCbCr{T}}, c::Color) where {T} = cnvt(YCbCr{T}, convert(RGB{T}, c))


# To Gray
Expand Down
30 changes: 30 additions & 0 deletions test/conversion.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@ using Colors, FixedPointNumbers
using Test
using ColorTypes: eltype_default, parametric3

struct C3{T} <: Color{T,3}
c1::T; c2::T; c3::T;
end
struct C4{T} <: Color{T,4}
c1::T; c2::T; c3::T; c4::T;
end
function ColorTypes._convert(::Type{Cdest}, ::Type{Odest}, ::Type{Osrc}, c) where {Cdest<:AbstractRGB,Odest,Osrc<:C4}
Cdest(c.c1 + c.c4, c.c2 + c.c4, c.c3 + c.c4)
end
function ColorTypes._convert(::Type{Cdest}, ::Type{Odest}, ::Type{Osrc}, c) where {Cdest<:C4,Odest,Osrc<:AbstractRGB}
r, g, b = red(c), green(c), blue(c)
c4 = min(r, g, b)
Cdest(r - c4, g - c4, b - c4, c4)
end

@testset "Conversion" begin
r8(x) = reinterpret(N0f8, x)

Expand Down Expand Up @@ -205,6 +220,21 @@ using ColorTypes: eltype_default, parametric3
@test convert(RGB{Float64}, HSI{BigFloat}(-360120, .5, .5)) RGB{Float64}(.25,.25,1)
end

@testset "custom types" begin
# issue #465
@test_throws ErrorException convert(RGB, C3{Float32}(1, 2, 3))
@test_throws ErrorException convert(RGB24, C3{Float64}(1, 2, 3))
@test_throws ErrorException convert(Lab, C3{Float16}(1, 2, 3))

@test @inferred(convert(RGB, C4{Float32}(0.1, 0.2, 0.3, 0.4))) RGB{Float32}(0.5, 0.6, 0.7)
@test @inferred(convert(RGB24, C4{Float64}(0.1, 0.2, 0.3, 0.4))) RGB24(0.5, 0.6, 0.7)
@test @inferred(convert(ALab, C4{Float16}(0.1, 0.2, 0.3, 0.4))) ALab{Float16}(62.12, -2.86, -16.25, 1.0)

@test @inferred(convert(C4, RGB{Float32}(0.5, 0.6, 0.7))) C4{Float32}(0.0, 0.1, 0.2, 0.5)
@test @inferred(convert(C4{N0f8}, ARGB32(0.5, 0.6, 0.7))) C4{N0f16}(0.0, 0.1, 0.2, 0.5)
@test @inferred(convert(C4, Lab{Float16}(62.12, -2.86, -16.25))) C4{Float16}(0.0, 0.1, 0.2, 0.5)
end

# Test accuracy of conversion
include("test_conversions.jl")

Expand Down

0 comments on commit a4886a0

Please sign in to comment.