diff --git a/.gitignore b/.gitignore index 9146a07..c7c7790 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ +# Platform- and user- specific contents and configurations .DS_Store +.vscode # Files generated by invoking Julia with --code-coverage *.jl.cov diff --git a/src/ImageIO.jl b/src/ImageIO.jl index 2f1af0a..31ade9f 100644 --- a/src/ImageIO.jl +++ b/src/ImageIO.jl @@ -1,12 +1,25 @@ module ImageIO using UUIDs -using FileIO: File, DataFormat, Stream, stream +using FileIO: File, DataFormat, Stream, stream, Formatted const idNetpbm = Base.PkgId(UUID("f09324ee-3d7c-5217-9330-fc30815ba969"), "Netpbm") const idPNGFiles = Base.PkgId(UUID("f57f5aa1-a3ce-4bc8-8ab9-96f992907883"), "PNGFiles") const idTiffImages = Base.PkgId(UUID("731e570b-9d59-4bfa-96dc-6df516fadf69"), "TiffImages") +# Enforce a type conversion to be backend independent (issue #25) +# Note: If the backend does not provide efficient `convert` implementation, +# there will be an extra memeory allocation and thus hurt the performance. +for FMT in ( + :PBMBinary, :PGMBinary, :PPMBinary, :PBMText, :PGMText, :PPMText, + :TIFF, + :PNG, +) + @eval canonical_type(::DataFormat{$(Expr(:quote, FMT))}, ::AbstractArray{T, N}) where {T,N} = + Array{T,N} +end +@inline canonical_type(::Formatted{T}, data) where T = canonical_type(T(), data) + ## PNGs const load_locker = Threads.ReentrantLock() @@ -19,10 +32,12 @@ function checked_import(pkgid) end function load(f::File{DataFormat{:PNG}}; kwargs...) - return Base.invokelatest(checked_import(idPNGFiles).load, f.filename, kwargs...) + data = Base.invokelatest(checked_import(idPNGFiles).load, f.filename, kwargs...) + return convert(canonical_type(f, data), data) end function load(s::Stream{DataFormat{:PNG}}; kwargs...) - return Base.invokelatest(checked_import(idPNGFiles).load, stream(s), kwargs...) + data = Base.invokelatest(checked_import(idPNGFiles).load, stream(s), kwargs...) + return convert(canonical_type(s, data), data) end function save(f::File{DataFormat{:PNG}}, image::S; kwargs...) where {T, S<:Union{AbstractMatrix, AbstractArray{T,3}}} @@ -44,11 +59,13 @@ end for NETPBMFORMAT in (:PBMBinary, :PGMBinary, :PPMBinary, :PBMText, :PGMText, :PPMText) @eval begin function load(f::File{DataFormat{$(Expr(:quote,NETPBMFORMAT))}}) - return Base.invokelatest(checked_import(idNetpbm).load, f) + data = Base.invokelatest(checked_import(idNetpbm).load, f) + return convert(canonical_type(f, data), data) end function load(s::Stream{DataFormat{$(Expr(:quote,NETPBMFORMAT))}}) - return Base.invokelatest(checked_import(idNetpbm).load, s) + data = Base.invokelatest(checked_import(idNetpbm).load, s) + return convert(canonical_type(s, data), data) end function save(f::File{DataFormat{$(Expr(:quote,NETPBMFORMAT))}}, image::S; kwargs...) where {S<:AbstractMatrix} @@ -64,10 +81,12 @@ end ## TIFFs function load(f::File{DataFormat{:TIFF}}; kwargs...) - return Base.invokelatest(checked_import(idTiffImages).load, f.filename, kwargs...) + data = Base.invokelatest(checked_import(idTiffImages).load, f.filename, kwargs...) + return convert(canonical_type(f, data), data) end function load(s::Stream{DataFormat{:TIFF}}; kwargs...) - return Base.invokelatest(checked_import(idTiffImages).load, stream(s), kwargs...) + data = Base.invokelatest(checked_import(idTiffImages).load, stream(s), kwargs...) + return convert(canonical_type(s, data), data) end function save(f::File{DataFormat{:TIFF}}, image::S) where {T, S<:Union{AbstractMatrix, AbstractArray{T,3}}} diff --git a/test/runtests.jl b/test/runtests.jl index ffa8570..c9b5c64 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -29,14 +29,16 @@ Threads.nthreads() <= 1 && @info "Threads.nthreads() = $(Threads.nthreads()), mu else @test img == img_saveload end + @test typeof(img_saveload) == ImageIO.canonical_type(f, img_saveload) - open(io->ImageIO.save(Stream(format"PNG", io), img, permute_horizontal=false), joinpath(tmpdir, "test_io.png"), "w") - img_saveload = open(io->ImageIO.load(Stream(format"PNG", io)), joinpath(tmpdir, "test_io.png")) + open(io->ImageIO.save(Stream{format"PNG"}(io), img, permute_horizontal=false), joinpath(tmpdir, "test_io.png"), "w") + img_saveload = open(io->ImageIO.load(Stream{format"PNG"}(io)), joinpath(tmpdir, "test_io.png")) if typ == UInt8 @test all(img .== reinterpret(UInt8, img_saveload)) else @test img == img_saveload end + @test typeof(img_saveload) == ImageIO.canonical_type(f, img_saveload) end end end @@ -49,10 +51,12 @@ Threads.nthreads() <= 1 && @info "Threads.nthreads() = $(Threads.nthreads()), mu ImageIO.save(f, img) img_saveload = ImageIO.load(f) @test img == img_saveload + @test typeof(img_saveload) == ImageIO.canonical_type(f, img_saveload) - open(io->ImageIO.save(Stream(fmt, io), img), joinpath(tmpdir, "test_io.pbm"), "w") - img_saveload = open(io->ImageIO.load(Stream(fmt, io)), joinpath(tmpdir, "test_io.pbm")) + open(io->ImageIO.save(Stream{fmt}(io), img), joinpath(tmpdir, "test_io.pbm"), "w") + img_saveload = open(io->ImageIO.load(Stream{fmt}(io)), joinpath(tmpdir, "test_io.pbm")) @test img == img_saveload + @test typeof(img_saveload) == ImageIO.canonical_type(f, img_saveload) end end @@ -63,10 +67,12 @@ Threads.nthreads() <= 1 && @info "Threads.nthreads() = $(Threads.nthreads()), mu ImageIO.save(f, img) img_saveload = ImageIO.load(f) @test img == img_saveload + @test typeof(img_saveload) == ImageIO.canonical_type(f, img_saveload) - open(io->ImageIO.save(Stream(fmt, io), img), joinpath(tmpdir, "test_io.pgm"), "w") - img_saveload = open(io->ImageIO.load(Stream(fmt, io)), joinpath(tmpdir, "test_io.pgm")) + open(io->ImageIO.save(Stream{fmt}(io), img), joinpath(tmpdir, "test_io.pgm"), "w") + img_saveload = open(io->ImageIO.load(Stream{fmt}(io)), joinpath(tmpdir, "test_io.pgm")) @test img == img_saveload + @test typeof(img_saveload) == ImageIO.canonical_type(f, img_saveload) end end @@ -77,10 +83,12 @@ Threads.nthreads() <= 1 && @info "Threads.nthreads() = $(Threads.nthreads()), mu ImageIO.save(f, img) img_saveload = ImageIO.load(f) @test img == img_saveload + @test typeof(img_saveload) == ImageIO.canonical_type(f, img_saveload) - open(io->ImageIO.save(Stream(fmt, io), img), joinpath(tmpdir, "test_io.ppm"), "w") - img_saveload = open(io->ImageIO.load(Stream(fmt, io)), joinpath(tmpdir, "test_io.ppm")) + open(io->ImageIO.save(Stream{fmt}(io), img), joinpath(tmpdir, "test_io.ppm"), "w") + img_saveload = open(io->ImageIO.load(Stream{fmt}(io)), joinpath(tmpdir, "test_io.ppm")) @test img == img_saveload + @test typeof(img_saveload) == ImageIO.canonical_type(f, img_saveload) end end end @@ -93,10 +101,12 @@ Threads.nthreads() <= 1 && @info "Threads.nthreads() = $(Threads.nthreads()), mu ImageIO.save(f, img) img_saveload = ImageIO.load(f) @test img == img_saveload + @test typeof(img_saveload) == ImageIO.canonical_type(f, img_saveload) - open(io->ImageIO.save(Stream(format"TIFF", io), img), joinpath(tmpdir, "test_io.tiff"), "w") - img_saveload = open(io->ImageIO.load(Stream(format"TIFF", io)), joinpath(tmpdir, "test_io.tiff")) + open(io->ImageIO.save(Stream{format"TIFF"}(io), img), joinpath(tmpdir, "test_io.tiff"), "w") + img_saveload = open(io->ImageIO.load(Stream{format"TIFF"}(io)), joinpath(tmpdir, "test_io.tiff")) @test img == img_saveload + @test typeof(img_saveload) == ImageIO.canonical_type(f, img_saveload) end end end