From 65bd7473c4dcd47fb25ef834004baed422c9ad42 Mon Sep 17 00:00:00 2001 From: Johnny Chen Date: Fri, 4 Feb 2022 06:48:58 +0800 Subject: [PATCH] JPEG support via JpegTurbo --- Project.toml | 4 +++- README.md | 1 + src/ImageIO.jl | 18 ++++++++++++++++++ test/runtests.jl | 24 ++++++++++++++++++++++-- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index 80d2fd5..5165de4 100644 --- a/Project.toml +++ b/Project.toml @@ -5,6 +5,7 @@ version = "0.6.0" [deps] FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" +JpegTurbo = "b835a17e-a41a-41e7-81f0-2f016b05efe0" Netpbm = "f09324ee-3d7c-5217-9330-fc30815ba969" OpenEXR = "52e1d378-f018-4a11-a4be-720524705ac7" PNGFiles = "f57f5aa1-a3ce-4bc8-8ab9-96f992907883" @@ -15,13 +16,14 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [compat] FileIO = "1.2" +JpegTurbo = "0.1" ImageCore = "0.8.1, 0.9" Netpbm = "1.0" OpenEXR = "0.3" PNGFiles = "0.3" QOI = "1" -TiffImages = "0.3, 0.4, 0.5" Sixel = "0.1.2" +TiffImages = "0.3, 0.4, 0.5" julia = "1.6" [extras] diff --git a/README.md b/README.md index 09098f2..0250ec6 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ FileIO.jl integration for image files | Format | Extensions | Provider | Implementation | Comment | | ------- | ---------- | -------- | ---- | ----------- | +| JPEG | `.jpg`, `.jpeg` | [JpegTurbo.jl](https://github.com/johnnychen94/JpegTurbo.jl) | Julia wrapper of [libjpeg-turbo](https://github.com/libjpeg-turbo/libjpeg-turbo) | [Benchmark results against other backends](https://github.com/johnnychen94/JpegTurbo.jl/issues/15) | | [OpenEXR](https://www.openexr.com/) | `.exr` | [OpenEXR.jl](https://github.com/twadleigh/OpenEXR.jl) | Julia wrapper of [OpenEXR](https://github.com/AcademySoftwareFoundation/openexr) | | | Portable Bitmap formats | `.pbm`, `.pgm`, `.ppm` | [Netpbm.jl](https://github.com/JuliaIO/Netpbm.jl) | pure Julia | | | PNG (Portable Network Graphics) | `.png` | [PNGFiles.jl](https://github.com/JuliaIO/PNGFiles.jl) | Julia wrapper of [libpng](https://github.com/glennrp/libpng) | [Benchmark vs. ImageMagick & QuartzImageIO](https://github.com/JuliaIO/PNGFiles.jl/issues/1#issuecomment-586749654) | diff --git a/src/ImageIO.jl b/src/ImageIO.jl index 93a7eb5..55e90a2 100644 --- a/src/ImageIO.jl +++ b/src/ImageIO.jl @@ -9,6 +9,7 @@ const idPNGFiles = Base.PkgId(UUID("f57f5aa1-a3ce-4bc8-8ab9-96f992907883"), "PNG const idTiffImages = Base.PkgId(UUID("731e570b-9d59-4bfa-96dc-6df516fadf69"), "TiffImages") const idOpenEXR = Base.PkgId(UUID("52e1d378-f018-4a11-a4be-720524705ac7"), "OpenEXR") const idQOI = Base.PkgId(UUID("4b34888f-f399-49d4-9bb3-47ed5cae4e65"), "QOI") +const idJpegTurbo = Base.PkgId(UUID("b835a17e-a41a-41e7-81f0-2f016b05efe0"), "JpegTurbo") # Enforce a type conversion to be backend independent (issue #25) # Note: If the backend does not provide efficient `convert` implementation, @@ -20,6 +21,7 @@ for FMT in ( :EXR, :QOI, :SIXEL, + :JPEG ) @eval canonical_type(::DataFormat{$(Expr(:quote, FMT))}, ::AbstractArray{T, N}) where {T,N} = Array{T,N} @@ -164,6 +166,22 @@ function save(s::Stream{DataFormat{:SIXEL}}, image::AbstractArray; kwargs...) Base.invokelatest(checked_import(idSixel).fileio_save, s, image; kwargs...) end +## JPEG +function load(f::File{DataFormat{:JPEG}}; kwargs...) + data = Base.invokelatest(checked_import(idJpegTurbo).fileio_load, f, kwargs...) + return enforce_canonical_type(f, data) +end +function load(s::Stream{DataFormat{:JPEG}}; kwargs...) + data = Base.invokelatest(checked_import(idJpegTurbo).fileio_load, s, kwargs...) + return enforce_canonical_type(s, data) +end +function save(f::File{DataFormat{:JPEG}}, image::AbstractArray; kwargs...) + Base.invokelatest(checked_import(idJpegTurbo).fileio_save, f, image; kwargs...) +end +function save(s::Stream{DataFormat{:JPEG}}, image::AbstractArray; kwargs...) + Base.invokelatest(checked_import(idJpegTurbo).fileio_save, s, image; kwargs...) +end + ## Function names labelled for FileIO. Makes FileIO lookup quicker const fileio_save = save const fileio_load = load diff --git a/test/runtests.jl b/test/runtests.jl index b60aa05..771d628 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,7 +1,7 @@ using Test using ImageIO using FileIO: File, DataFormat, Stream, @format_str -using ImageCore: N0f8, RGB, Gray, RGBA, GrayA +using ImageCore: N0f8, RGB, Gray, RGBA, GrayA, n0f8 using ImageQualityIndexes tmpdir = mktempdir() @@ -151,7 +151,7 @@ Threads.nthreads() <= 1 && @info "Threads.nthreads() = $(Threads.nthreads()), mu end @testset "sixel" begin - for typ in [Gray{N0f8}, Gray{Float64}, RGB{N0f8}, RGB{Float64}] # TODO: Add UInt8, N0f8 support in TiffImages + for typ in [Gray{N0f8}, Gray{Float64}, RGB{N0f8}, RGB{Float64}] @testset "$typ sixel" begin img = repeat(typ.(0:0.1:0.9), inner=(10, 50)) f = File{format"SIXEL"}(joinpath(tmpdir, "test_fpath.sixel")) @@ -169,4 +169,24 @@ Threads.nthreads() <= 1 && @info "Threads.nthreads() = $(Threads.nthreads()), mu end end end + + @testset "JPEG" begin + for typ in [Gray{N0f8}, Gray{Float64}, RGB{N0f8}, RGB{Float64}] + @testset "$typ JPEG" begin + img = repeat(typ.(0:0.1:0.9), inner=(10, 50)) + f = File{format"JPEG"}(joinpath(tmpdir, "test_fpath.jpg")) + ImageIO.save(f, img) + img_saveload = ImageIO.load(f) + @test eltype(img_saveload) == n0f8(typ) # JpegTurbo uses 8bit + @test assess_psnr(img, eltype(img).(img_saveload)) > 51 + @test typeof(img_saveload) == ImageIO.canonical_type(f, img_saveload) + + open(io->ImageIO.save(Stream{format"JPEG"}(io), img), joinpath(tmpdir, "test_io.jpg"), "w") + img_saveload = open(io->ImageIO.load(Stream{format"JPEG"}(io)), joinpath(tmpdir, "test_io.jpg")) + @test eltype(img_saveload) == n0f8(typ) # JpegTurbo uses 8bit + @test assess_psnr(img, eltype(img).(img_saveload)) > 51 + @test typeof(img_saveload) == ImageIO.canonical_type(f, img_saveload) + end + end + end end