Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JPEG support via JpegTurbo #45

Merged
merged 1 commit into from
Feb 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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]
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |
Expand Down
18 changes: 18 additions & 0 deletions src/ImageIO.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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}
Expand Down Expand Up @@ -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
Expand Down
24 changes: 22 additions & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -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()
Expand Down Expand Up @@ -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"))
Expand All @@ -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