diff --git a/Project.toml b/Project.toml index 9f4581b..c970726 100644 --- a/Project.toml +++ b/Project.toml @@ -20,7 +20,7 @@ Graphics = "0.4, 1.0" MappedArrays = "0.2" MosaicViews = "0.2" OffsetArrays = "0.8, 0.9, 0.10, 0.11, 1.0.1" -PaddedViews = "0.4.1, 0.5" +PaddedViews = "0.5.4" Reexport = "0.2" Requires = "0.5, 1" julia = "1" diff --git a/src/ImageCore.jl b/src/ImageCore.jl index 80bdb17..70c013b 100644 --- a/src/ImageCore.jl +++ b/src/ImageCore.jl @@ -17,7 +17,8 @@ if !isdefined(ColorTypes, :XRGB) end @reexport using MosaicViews -using MappedArrays, PaddedViews, Graphics +@reexport using PaddedViews +using MappedArrays, Graphics using OffsetArrays # for show.jl using .ColorTypes: colorant_string using Colors: Fractional @@ -65,7 +66,6 @@ export permuteddimsview, rawview, normedview, - paddedviews, reinterpretc, # conversions # float16, @@ -149,6 +149,26 @@ much faster to create, but generally slower to use. """ permuteddimsview(A, perm) = Base.PermutedDimsArrays.PermutedDimsArray(A, perm) +# PaddedViews support +# This make sure Colorants as `fillvalue` are correctly filled, for example, let +# `PaddedView(ARGB(0, 0, 0, 0), img)` correctly filled with transparent color even when +# `img` is of eltype `RGB` +function PaddedViews.filltype(::Type{FC}, ::Type{C}) where {FC<:Colorant, C<:Colorant} + # rand(RGB, 4, 4) has eltype RGB{Any} but it isn't a concrete type + # although the consensus[1] is to not make a concrete eltype, this op is needed to make a + # type-stable colorant construction in _filltype without error; there's no RGB{Any} thing + # [1]: https://github.com/JuliaLang/julia/pull/34948 + T = eltype(C) === Any ? eltype(FC) : eltype(C) + _filltype(FC, base_colorant_type(C){T}) +end +_filltype(::Type{<:Colorant}, ::Type{C}) where {C<:Colorant} = C +_filltype(::Type{FC}, ::Type{C}) where {FC<:Color3, C<:AbstractGray} = + base_colorant_type(FC){promote_type(eltype(FC), eltype(C))} +_filltype(::Type{FC}, ::Type{C}) where {FC<:TransparentColor, C<:AbstractGray} = + alphacolor(FC){promote_type(eltype(FC), eltype(C))} +_filltype(::Type{FC}, ::Type{C}) where {FC<:TransparentColor, C<:Color3} = + alphacolor(C){promote_type(eltype(FC), eltype(C))} + # Support transpose Base.transpose(a::AbstractMatrix{C}) where {C<:Colorant} = permutedims(a, (2,1)) function Base.transpose(a::AbstractVector{C}) where C<:Colorant diff --git a/test/views.jl b/test/views.jl index 1dd4787..694c1d5 100644 --- a/test/views.jl +++ b/test/views.jl @@ -1,6 +1,7 @@ # some views are in colorchannels.jl using Colors, FixedPointNumbers, ImageCore, OffsetArrays, Test using OffsetArrays: IdentityUnitRange +using PaddedViews: filltype @testset "rawview" begin a = map(N0f8, rand(3,5)) @@ -269,6 +270,89 @@ end out = mosaicview(A, A; npad=2, fillvalue=GrayA(0.), nrow=2) |> collect @test_reference "references/mosaicviews/4d_transparent_4.png" out by=isequal end + +@testset "PaddedViews" begin + # don't promote to Colorant if it's a numerical array + @test @inferred(filltype(Gray{N0f8}, Float32)) === Float32 + + # cases that don't promote array eltype: + # * (Number, Colorant) + # * (Gray, Gray) + # * (Gray, Color3) + # * (Color3, Color3) + # * (Color3, TransparentColor) + # * (TransparentColor, TransparentColor) + @test @inferred(filltype(Float32, Gray{N0f8})) == Gray{N0f8} + @test @inferred(filltype(Float32, RGB{N0f8})) == RGB{N0f8} + @test @inferred(filltype(Gray{Float32}, Gray{N0f8})) === Gray{N0f8} + @test @inferred(filltype(Gray{N0f8}, Gray{Float32})) === Gray{Float32} + @test @inferred(filltype(Gray{Float32}, RGB{N0f8})) === RGB{N0f8} + @test @inferred(filltype(Gray{N0f8}, RGB{Float32})) === RGB{Float32} + @test @inferred(filltype(RGB{Float32}, RGB{N0f8})) === RGB{N0f8} + @test @inferred(filltype(RGB{N0f8}, RGB{Float32})) === RGB{Float32} + @test @inferred(filltype(Lab{Float32}, RGB{N0f8})) === RGB{N0f8} + @test @inferred(filltype(RGB{N0f8}, Lab{Float32})) === Lab{Float32} + + @test @inferred(filltype(Gray{N0f8}, AGray{Float32})) === AGray{Float32} + @test @inferred(filltype(Gray{Float32}, AGray{N0f8})) === AGray{N0f8} + @test @inferred(filltype(Gray{N0f8}, ARGB{Float32})) === ARGB{Float32} + @test @inferred(filltype(Gray{Float32}, ARGB{N0f8})) === ARGB{N0f8} + @test @inferred(filltype(BGR{N0f8}, ARGB{Float32})) === ARGB{Float32} + @test @inferred(filltype(BGR{Float32}, ARGB{N0f8})) === ARGB{N0f8} + @test @inferred(filltype(AGray{N0f8}, ARGB{Float32})) === ARGB{Float32} + @test @inferred(filltype(AGray{Float32}, ARGB{N0f8})) === ARGB{N0f8} + + # cases that promote both colorant type and storage type + # * (Color3, Gray) + # * (TransparentColor, Colorant) + @test @inferred(filltype(RGB{N0f8}, Gray{Float32})) === RGB{Float32} + @test @inferred(filltype(RGB{Float32}, Gray{N0f8})) === RGB{Float32} + @test @inferred(filltype(Lab{Float32}, Gray{N0f8})) === Lab{Float32} + @test @inferred(filltype(Lab{Float32}, Gray{Float64})) === Lab{Float64} + + @test @inferred(filltype(AGray{N0f8}, Gray{Float32})) === AGray{Float32} + @test @inferred(filltype(AGray{Float32}, Gray{N0f8})) === AGray{Float32} + @test @inferred(filltype(AGray{N0f8}, RGB{Float32})) === ARGB{Float32} + @test @inferred(filltype(AGray{Float32}, RGB{N0f8})) === ARGB{Float32} + @test @inferred(filltype(AGray{N0f8}, Lab{Float32})) === ALab{Float32} + @test @inferred(filltype(AGray{Float64}, Lab{Float32})) === ALab{Float64} + + @test @inferred(filltype(ARGB{N0f8}, Gray{Float32})) === ARGB{Float32} + @test @inferred(filltype(ARGB{Float32}, Gray{N0f8})) === ARGB{Float32} + @test @inferred(filltype(ARGB{N0f8}, BGR{Float32})) === ABGR{Float32} + @test @inferred(filltype(ARGB{Float32}, BGR{N0f8})) === ABGR{Float32} + @test @inferred(filltype(ARGB{N0f8}, Lab{Float32})) === ALab{Float32} + @test @inferred(filltype(ARGB{Float64}, Lab{Float32})) === ALab{Float64} + + + A = Gray{N0f8}[Gray(0.) Gray(0.3); Gray(0.6) Gray(1.)] + fv = Gray{N0f8}(0) + Ap = PaddedView(fv, A, (-1:4, -1:4)) + @test eltype(Ap) == Gray{N0f8} + @test @inferred(getindex(Ap, -1, -1)) === fv + @test @inferred(getindex(Ap, 2, 2)) === Gray{N0f8}(1.) + @test Ap[axes(A)...] == A + + fv = RGB{Float32}(1., 0., 0.) + Ap = PaddedView(fv, A, (-1:4, -1:4)) + @test eltype(Ap) == RGB{Float32} + @test @inferred(getindex(Ap, -1, -1)) === fv + @test @inferred(getindex(Ap, 2, 2)) === RGB{Float32}(1., 1., 1.) + @test Ap[axes(A)...] == RGB{Float32}.(A) + + fv = AGray{Float32}(0.5, 0) + Ap = PaddedView(fv, A, (-1:4, -1:4)) + @test eltype(Ap) == AGray{Float32} + @test @inferred(getindex(Ap, -1, -1)) === fv + @test @inferred(getindex(Ap, 2, 2)) === AGray{Float32}(1., 1.) + @test Ap[axes(A)...] == AGray{Float32}.(A) + + fv = ARGB{Float32}(0.5, 0., 0., 0.) + Ap = PaddedView(fv, A, (-1:4, -1:4)) + @test eltype(Ap) == ARGB{Float32} + @test @inferred(getindex(Ap, -1, -1)) === fv + @test @inferred(getindex(Ap, 2, 2)) === ARGB{Float32}(1., 1., 1., 1.) + @test Ap[axes(A)...] == ARGB{Float32}.(A) end nothing