diff --git a/src/TiffImages.jl b/src/TiffImages.jl index 34797fdf..8de6660e 100644 --- a/src/TiffImages.jl +++ b/src/TiffImages.jl @@ -25,4 +25,6 @@ include(joinpath("types", "dense.jl")) include(joinpath("types", "mmap.jl")) include("load.jl") +@deprecate TiffFile(::Type{O}) where O<:Unsigned TiffFile{O}() + end # module diff --git a/src/files.jl b/src/files.jl index e887153d..b9415406 100644 --- a/src/files.jl +++ b/src/files.jl @@ -5,12 +5,12 @@ Wrap `io` with helper parameters to keep track of file attributes. $(FIELDS) """ -mutable struct TiffFile{O <: Unsigned} +mutable struct TiffFile{O <: Unsigned, S <: Stream} """The relative path to this file""" filepath::String """The file stream""" - io::Stream + io::S """Location of the first IFD in the file stream""" first_offset::Int @@ -19,18 +19,18 @@ mutable struct TiffFile{O <: Unsigned} need_bswap::Bool end -function TiffFile(offset_size::Type{O}) where O <: Unsigned - TiffFile{offset_size}("", Stream(format"TIFF", IOBuffer()), -1, false) -end +TiffFile{O}(s::Stream) where O <: Unsigned = TiffFile{O, typeof(s)}("", s, -1, false) +TiffFile{O}(io::IO) where O <: Unsigned = TiffFile{O}(Stream(format"TIFF", io)) +TiffFile{O}() where O <: Unsigned = TiffFile{O}(IOBuffer()) function Base.read(io::Stream, ::Type{TiffFile}) seekstart(io) - filepath = extract_filename(io) + filepath = String(extract_filename(io))::String bs = need_bswap(io) offset_size = offsetsize(io) first_offset_raw = read(io, offset_size) first_offset = Int(bs ? bswap(first_offset_raw) : first_offset_raw) - TiffFile{offset_size}(filepath, io, first_offset, bs) + TiffFile{offset_size, typeof(io)}(filepath, io, first_offset, bs) end Base.read(io::IOStream, t::Type{TiffFile}) = read(Stream(format"TIFF", io, extract_filename(io)), t) @@ -103,8 +103,8 @@ function Base.read!(file::TiffFile, arr::BitArray) arr end -Base.write(file::TiffFile, t) = write(file.io.io, t) -Base.write(file::TiffFile, arr::AbstractVector{Any}) = write(file.io.io, Array{UInt8}(arr)) +Base.write(file::TiffFile, t) = write(file.io.io, t)::Int +Base.write(file::TiffFile, arr::AbstractVector{Any}) = write(file.io.io, Array{UInt8}(arr))::Int Base.seek(file::TiffFile, n::Integer) = seek(file.io, n) diff --git a/src/ifds.jl b/src/ifds.jl index 138d10c7..db149f5c 100644 --- a/src/ifds.jl +++ b/src/ifds.jl @@ -24,8 +24,8 @@ IFD(o::Type{O}) where {O <: Unsigned} = IFD{O}(OrderedDict{UInt16, Tag}()) Base.length(ifd::IFD) = length(ifd.tags) Base.keys(ifd::IFD) = keys(ifd.tags) -Base.iterate(ifd::IFD) = iterate(ifd.tags) -Base.iterate(ifd::IFD, n::Integer) = iterate(ifd.tags, n) +Base.iterate(ifd::IFD) = iterate(ifd.tags)::Union{Nothing,Tuple{Pair{UInt16,<:Tag},Int}} +Base.iterate(ifd::IFD, n::Int) = iterate(ifd.tags, n)::Union{Nothing,Tuple{Pair{UInt16,<:Tag},Int}} Base.getindex(ifd::IFD, key::TiffTag) = getindex(ifd, UInt16(key)) Base.getindex(ifd::IFD{O}, key::UInt16) where {O} = getindex(ifd.tags, key) Base.in(key::TiffTag, v::IFD) = in(UInt16(key), v) @@ -110,22 +110,22 @@ struct IFDLayout end function output(ifd::IFD) - nrows = Int(ifd[IMAGELENGTH].data) - ncols = Int(ifd[IMAGEWIDTH].data) + nrows = Int(ifd[IMAGELENGTH].data)::Int + ncols = Int(ifd[IMAGEWIDTH].data)::Int - samplesperpixel = Int(ifd[SAMPLESPERPIXEL].data) + samplesperpixel = Int(ifd[SAMPLESPERPIXEL].data)::Int sampleformats = fill(UInt16(0x01), samplesperpixel) if SAMPLEFORMAT in ifd - sampleformats = ifd[SAMPLEFORMAT].data + sampleformats = ifd[SAMPLEFORMAT].data # can a type be specified for this? end - interpretation = Int(ifd[PHOTOMETRIC].data) + interpretation = Int(ifd[PHOTOMETRIC].data)::Int strip_nbytes = ifd[STRIPBYTECOUNTS].data - nbytes = Int(sum(strip_nbytes)) - bitsperpixel = ifd[BITSPERSAMPLE].data - rawtypes = Set{DataType}() - mappedtypes = Set{DataType}() + nbytes = Int(sum(strip_nbytes))::Int + bitsperpixel = ifd[BITSPERSAMPLE].data::Union{Int,UInt16,Vector{UInt16}} + rawtypes = Base.IdSet{Any}() + mappedtypes = Base.IdSet{Any}() for i in 1:samplesperpixel rawtype = rawtype_mapping[(SampleFormats(sampleformats[i]), bitsperpixel[i])] push!(rawtypes, rawtype) @@ -138,7 +138,7 @@ function output(ifd::IFD) end end (length(rawtypes) > 1) && error("Variable per-pixel storage types are not yet supported") - rawtype = first(rawtypes) + rawtype = first(rawtypes)::DataType readtype = rawtype compression = COMPRESSION_NONE @@ -158,7 +158,7 @@ function output(ifd::IFD) nbytes, readtype, rawtype, - first(mappedtypes), + first(mappedtypes)::DataType, compression, PhotometricInterpretations(interpretation)) end @@ -175,7 +175,7 @@ function Base.read!(target::AbstractArray{T, N}, tf::TiffFile, ifd::IFD) where { if layout.compression != COMPRESSION_NONE # strip_nbytes is the number of bytes pre-inflation so we need to # calculate the expected size once decompressed and update the values - strip_nbytes = fill(rowsperstrip*layout.ncols, length(strip_nbytes)) + strip_nbytes = fill(rowsperstrip*layout.ncols, length(strip_nbytes)::Int) strip_nbytes[end] = (layout.nrows - (rowsperstrip * (nstrips-1))) * layout.ncols end @@ -189,13 +189,13 @@ function Base.read!(target::AbstractArray{T, N}, tf::TiffFile, ifd::IFD) where { if nstrips > 1 startbyte = 1 for i in 1:nstrips - seek(tf, strip_offsets[i]) - nbytes = Int(strip_nbytes[i] / sizeof(T)) + seek(tf, strip_offsets[i]::Core.BuiltinInts) + nbytes = Int(strip_nbytes[i]::Core.BuiltinInts / sizeof(T)) read!(tf, view(target, startbyte:(startbyte+nbytes-1)), layout.compression) startbyte += nbytes end else - seek(tf, strip_offsets[1]) + seek(tf, strip_offsets[1]::Core.BuiltinInts) read!(tf, target, layout.compression) end end @@ -222,14 +222,24 @@ function Base.write(tf::TiffFile{O}, ifd::IFD{O}) where {O <: Unsigned} write(tf, O(0)) for (tag, poses) in remotedata + tag = tag::Tag data_pos = position(tf.io) + data = tag.data # add NUL terminator to the end of Strings that don't have it already - data = (eltype(tag) == String && !endswith(tag.data, '\0')) ? tag.data * "\0" : tag.data - write(tf, data) + if eltype(tag) === String + data = data::SubString{String} + if !endswith(data, '\0') + data *= "\0" + end + write(tf, data) # compile-time dispatch + else + write(tf, data) # run-time dispatch + end push!(poses, data_pos) end for (tag, poses) in remotedata + tag = tag::Tag orig_pos, data_pos = poses seek(tf, orig_pos) write(tf, tag, data_pos) diff --git a/src/layout.jl b/src/layout.jl index b366b5a5..37d63ece 100644 --- a/src/layout.jl +++ b/src/layout.jl @@ -1,6 +1,6 @@ -nrows(ifd::IFD) = Int(ifd[IMAGELENGTH].data) -ncols(ifd::IFD) = Int(ifd[IMAGEWIDTH].data) -nsamples(ifd::IFD) = Int(ifd[SAMPLESPERPIXEL].data) +nrows(ifd::IFD) = Int(ifd[IMAGELENGTH].data)::Int +ncols(ifd::IFD) = Int(ifd[IMAGEWIDTH].data)::Int +nsamples(ifd::IFD) = Int(ifd[SAMPLESPERPIXEL].data)::Int """ interpretation(ifd) @@ -36,8 +36,8 @@ interpretation(::Val{PHOTOMETRIC_YCBCR}) = YCbCr interpretation(::Val{PHOTOMETRIC_CIELAB}) = Lab function interpretation(p::PhotometricInterpretations, extrasamples::ExtraSamples, samplesperpixel::Int) - interp = interpretation(p) - len = length(interp) + interp = interpretation(p)::Type{<:Colorant} + len = length(interp)::Int if len + 1 == samplesperpixel return interpretation(p, extrasamples, Val(samplesperpixel)) elseif len == samplesperpixel @@ -45,16 +45,16 @@ function interpretation(p::PhotometricInterpretations, extrasamples::ExtraSample elseif len < samplesperpixel return interp, true else - error("TIFF file says it contains $interp values, but only has $samplesperpixel samples per pixel instead of the minimum required $(length(interp))") + error("TIFF file says it contains $interp values, but only has $samplesperpixel samples per pixel instead of the minimum required $len") end end _pad(::Type{RGB}) = RGBX _pad(::Type{T}) where {T} = T interpretation(p::PhotometricInterpretations, extrasamples::ExtraSamples, nsamples::Val) = interpretation(p, Val(extrasamples), nsamples) -interpretation(p::PhotometricInterpretations, ::Val{EXTRASAMPLE_UNSPECIFIED}, ::Val) = interpretation(p), true +interpretation(p::PhotometricInterpretations, ::Val{EXTRASAMPLE_UNSPECIFIED}, @nospecialize(::Val)) = interpretation(p), true interpretation(p::PhotometricInterpretations, ::Val{EXTRASAMPLE_UNSPECIFIED}, ::Val{4}) = _pad(interpretation(p)), false -interpretation(p::PhotometricInterpretations, ::Val{EXTRASAMPLE_ASSOCALPHA}, ::Val) = coloralpha(interpretation(p)), false +interpretation(p::PhotometricInterpretations, ::Val{EXTRASAMPLE_ASSOCALPHA}, @nospecialize(::Val)) = coloralpha(interpretation(p)), false interpretation(p::PhotometricInterpretations, ::Val{EXTRASAMPLE_UNASSALPHA}, nsamples::Val) = interpretation(p, Val(EXTRASAMPLE_ASSOCALPHA), nsamples) _mappedtype(::Type{T}) where {T} = T @@ -76,10 +76,11 @@ end Allocate a cache for this IFD with correct type and size. """ -getcache(ifd::IFD) = getcache(ifd, Val(rawtype(ifd))) -getcache(ifd::IFD, ::Val{Bool}) = BitArray(undef, ncols(ifd), nrows(ifd)) -function getcache(ifd::IFD, ::Val{T}) where {T} +function getcache(ifd::IFD) + T = rawtype(ifd) + if T === Bool + return BitArray(undef, ncols(ifd), nrows(ifd)) + end colortype, extras = interpretation(ifd) - - Array{colortype{_mappedtype(T)}}(undef, ncols(ifd), nrows(ifd)) + return Array{colortype{_mappedtype(T)}}(undef, ncols(ifd), nrows(ifd)) end diff --git a/src/load.jl b/src/load.jl index 174908f1..c47fdf13 100644 --- a/src/load.jl +++ b/src/load.jl @@ -4,9 +4,8 @@ function load(filepath::String; verbose=true, mmap = false) end end -function load(io::IOStream; verbose=true, mmap = false) - tf = read(io, TiffFile) - +load(io::IOStream; verbose=true, mmap = false) = load(read(io, TiffFile); verbose=verbose, mmap=mmap) +function load(tf::TiffFile; verbose=true, mmap = false) isdense = true ifds = IFD{offset(tf)}[] @@ -33,7 +32,11 @@ function load(io::IOStream; verbose=true, mmap = false) if mmap loaded = DiskTaggedImage(tf, ifds) else - loaded = load(tf, ifds, Val(nplanes); verbose=verbose) + if nplanes == 1 + loaded = load(tf, ifds, nothing; verbose=verbose) + else + loaded = load(tf, ifds, nplanes; verbose=verbose) + end end if eltype(loaded) <: Palette @@ -54,15 +57,15 @@ function load(io::IOStream; verbose=true, mmap = false) return DenseTaggedImage(data, ifds) end -function load(tf::TiffFile, ifds, ::Val{1}; verbose = true) +function load(tf::TiffFile, ifds::AbstractVector{<:IFD}, ::Nothing; verbose = true) ifd = ifds[1] cache = getcache(ifd) read!(cache, tf, ifd) - return Array(cache') + return Matrix(cache') end -function load(tf::TiffFile, ifds, ::Val{N}; verbose = true) where {N} +function load(tf::TiffFile, ifds::AbstractVector{<:IFD}, N; verbose = true) ifd = ifds[1] cache = getcache(ifd) diff --git a/src/tags.jl b/src/tags.jl index 59e78c27..6edf58e0 100644 --- a/src/tags.jl +++ b/src/tags.jl @@ -45,7 +45,8 @@ load(tf::TiffFile, t::Tag) = t function load(tf::TiffFile{O}, t::Tag{RemoteData{O}}) where {O <: Unsigned} T = t.data.datatype N = t.data.count - rawdata = Vector{UInt8}(undef, bytes(T)*N) + nb = bytes(T)::Int + rawdata = Vector{UInt8}(undef, nb*N) pos = position(tf.io) seek(tf, t.data.position) @@ -54,7 +55,7 @@ function load(tf::TiffFile{O}, t::Tag{RemoteData{O}}) where {O <: Unsigned} # if this datatype is comprised of multiple bytes and this file needs to be # bitswapped then we'll need to reverse the byte order inside each datatype # unit - if tf.need_bswap && bytes(T) >= 2 + if tf.need_bswap && nb >= 2 reverse!(rawdata) data = Array{T}(reverse(reinterpret(T, rawdata))) elseif T == String @@ -70,7 +71,7 @@ function load(tf::TiffFile{O}, t::Tag{RemoteData{O}}) where {O <: Unsigned} end seek(tf, pos) - Tag(t.tag, data) + Tag(t.tag, data)::Tag end bytes(x::Type) = sizeof(x) @@ -91,19 +92,19 @@ function Base.read(tf::TiffFile{O}, ::Type{Tag}) where O <: Unsigned T = tiff_to_julian[datatype] end - nbytes = bytes(T) * count + nbytes = bytes(T)::Int * count if nbytes <= sizeof(O) if tf.need_bswap reverse!(view(data, 1:nbytes)) end - if T == String + if T === String return Tag(tag, String(data)) - elseif T == Any - return Tag(tag, Array{Any}(data)) + elseif T === Any + return Tag(tag, Vector{Any}(data)) elseif count == 1 - return Tag(tag, first(reinterpret(T, data))) + return Tag(tag, first(reinterpret(T, data)))::Tag else - return Tag(tag, Array(reinterpret(T, data)[1:Int(count)])) + return Tag(tag, reinterpret(T, data)[1:Int(count)]) end else (tf.need_bswap) && reverse!(data) @@ -145,13 +146,19 @@ function Base.write(tf::TiffFile{O}, t::Tag{T}) where {O <: Unsigned, T} return false end + data = t.data # add NUL terminator to the end of Strings that don't have it already - data = (T == String && !endswith(t.data, '\0')) ? t.data * "\0" : t.data + if T === String + data = data::SubString{String} + if !endswith(data, '\0') + data *= "\0" + end + end write(tf, t.tag) write(tf, julian_to_tiff[eltype(t)]) write(tf, O(length(t))) - nbytes = write(tf.io, data) + nbytes = write(tf.io, data)::Int # write padding if nbytes < sizeof(O) diff --git a/src/types/dense.jl b/src/types/dense.jl index 5382515a..db215035 100644 --- a/src/types/dense.jl +++ b/src/types/dense.jl @@ -83,8 +83,7 @@ Base.write(io::IOStream, img::DenseTaggedImage) = write(Stream(format"TIFF", io, function Base.write(io::Stream, img::DenseTaggedImage) O = offset(img) - tf = TiffFile(O) - tf.io = io + tf = TiffFile{O}(io) prev_ifd_record = write(tf) # record that will have be updated @@ -104,7 +103,7 @@ function Base.write(io::Stream, img::DenseTaggedImage) ifd[COMPRESSION] = COMPRESSION_NONE ifd[STRIPOFFSETS] = O(data_pos) ifd[STRIPBYTECOUNTS] = O(ifd_pos-data_pos) - ifd[SOFTWARE] = "$(parentmodule(IFD)).jl v$PKGVERSION" + ifd[SOFTWARE] = "$(parentmodule(IFD)::Module).jl v$PKGVERSION" sort!(ifd.tags) seek(tf.io, ifd_pos) diff --git a/src/utils.jl b/src/utils.jl index d1d0d741..5a0ca8e5 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -29,10 +29,11 @@ const fdpattern = r"" Extract the name of the file backing a stream """ function extract_filename(io::IOStream) - filename = match(filepattern, io.name) + name = String(io.name) + filename = match(filepattern, name) if filename !== nothing - return filename[1] - elseif match(fdpattern, io.name) !== nothing + return String(filename[1]) + elseif match(fdpattern, name) !== nothing return "" else error("Can't extract filename from the given stream") @@ -156,7 +157,7 @@ const julian_to_tiff = Dict( ) # sampleformat, bitspersample => Julian type -const rawtype_mapping = Dict( +const rawtype_mapping = Dict{Tuple{TiffImages.SampleFormats, UInt16}, DataType}( (SAMPLEFORMAT_UINT, 1) => Bool, (SAMPLEFORMAT_UINT, 8) => UInt8, (SAMPLEFORMAT_UINT, 16) => UInt16, diff --git a/test/tags.jl b/test/tags.jl index 71ac0b36..d31b8b34 100644 --- a/test/tags.jl +++ b/test/tags.jl @@ -1,5 +1,5 @@ @testset "Unspecified type" begin - tf = TiffImages.TiffFile(UInt32) + tf = TiffImages.TiffFile{UInt32}() write(tf, UInt16(TiffImages.IMAGEWIDTH)) write(tf, 0x0007) @@ -11,7 +11,7 @@ end @testset "Data array only part of data field" begin - tf = TiffImages.TiffFile(UInt64) + tf = TiffImages.TiffFile{UInt64}() write(tf, UInt16(TiffImages.BITSPERSAMPLE)) write(tf, 0x0003) @@ -24,7 +24,7 @@ end end @testset "Rational, full space" begin - tf = TiffImages.TiffFile(UInt64) + tf = TiffImages.TiffFile{UInt64}() write(tf, UInt16(TiffImages.XRESOLUTION)) write(tf, 0x0005) write(tf, UInt64(1)) diff --git a/test/writer.jl b/test/writer.jl index a560e87a..4a03b044 100644 --- a/test/writer.jl +++ b/test/writer.jl @@ -1,6 +1,6 @@ @testset "tags" begin @testset "exact fit tags" begin - tf = TiffImages.TiffFile(UInt32) + tf = TiffImages.TiffFile{UInt32}() t1 = TiffImages.Tag(UInt16(TiffImages.IMAGEWIDTH), 0x00000200) @test write(tf, t1) # should fit so true @@ -10,7 +10,7 @@ end @testset "small tags" begin - tf = TiffImages.TiffFile(UInt32) + tf = TiffImages.TiffFile{UInt32}() t1 = TiffImages.Tag(UInt16(TiffImages.COMPRESSION), 0x0001) @test write(tf, t1) # should fit but needs padding @test position(tf.io) == 12 # should be full length @@ -21,7 +21,7 @@ end @testset "large tags" begin - tf = TiffImages.TiffFile(UInt32) + tf = TiffImages.TiffFile{UInt32}() offsets = UInt32[8, 129848, 259688, 389528] t1 = TiffImages.Tag(UInt16(TiffImages.STRIPOFFSETS), offsets) @@ -46,7 +46,7 @@ end @testset "String length equal to offset size" begin - tf = TiffImages.TiffFile(UInt32) + tf = TiffImages.TiffFile{UInt32}() t1 = TiffImages.Tag(TiffImages.SOFTWARE, "test") # should fail because it's too large to fit @@ -68,7 +68,7 @@ end @testset "ifds" begin - tf = TiffImages.TiffFile(UInt32) + tf = TiffImages.TiffFile{UInt32}() ifd = TiffImages.IFD(UInt32) ifd[TiffImages.IMAGEDESCRIPTION] = "Testing IFD read/write" ifd[TiffImages.IMAGEWIDTH] = UInt32(512)