diff --git a/.gitignore b/.gitignore index 784fb54..ecb192e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,11 @@ *.jl.cov *.jl.mem -deps/x86* \ No newline at end of file +deps/x86* +*.dll +deps/deps.jl +deps/DevIL.lib +deps/ILU.lib +deps/ILUT.lib +deps/downloads/ +deps/include/ +deps/unicode/ diff --git a/.travis.yml b/.travis.yml index e50f3c1..531b6b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,14 @@ language: julia +sudo: required os: - linux - osx julia: - - release - - nightly + - 0.5 notifications: email: false -# uncomment the following lines to override the default test script -#script: -# - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi -# - julia --check-bounds=yes -e 'Pkg.clone(pwd()); Pkg.build("DevIL"); Pkg.test("DevIL"; coverage=true)' +after_success: + # push coverage results to Coveralls + - julia -e 'cd(Pkg.dir("DevIL")); Pkg.add("Coverage"); using Coverage; Coveralls.submit(Coveralls.process_folder())' + # push coverage results to Codecov + - julia -e 'cd(Pkg.dir("DevIL")); Pkg.add("Coverage"); using Coverage; Codecov.submit(Codecov.process_folder())' diff --git a/README.md b/README.md index 6e510f3..9a186b7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ +[![Coverage Status](https://coveralls.io/repos/github/JuliaGL/DevIL.jl/badge.svg)](https://coveralls.io/github/JuliaGL/DevIL.jl) +[![codecov](https://codecov.io/gh/JuliaGL/DevIL.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/JuliaGL/DevIL.jl) + + +[![Build Status](https://travis-ci.org/JuliaGL/DevIL.jl.svg?branch=master)](https://travis-ci.org/JuliaGL/DevIL.jl) +[![Build status](https://ci.appveyor.com/api/projects/status/jxwo3cbl0lroxewc/branch/master?svg=true)](https://ci.appveyor.com/project/SimonDanisch/devil-jl/branch/master) + # DevIL DevIL / OpenIL binding for Julia @@ -8,7 +15,7 @@ Currently only IL is bound, ILU and ILUT are not. ## Usage -For usage information & documentation please visit +For usage information & documentation please visit http://openil.sourceforge.net/ ## Essentials diff --git a/REQUIRE b/REQUIRE index 5a8cf0b..8bd485a 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,2 +1,6 @@ -julia 0.5- - +julia 0.5 +FixedPointNumbers +ColorTypes +ImageCore +BinDeps +@osx Homebrew diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..f2d87f3 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,31 @@ +environment: + matrix: + - JULIAVERSION: "julialang/bin/winnt/x64/0.5/julia-0.5-latest-win64.exe" + +branches: + only: + - master + - /release-.*/ + +notifications: + - provider: Email + on_build_success: false + on_build_failure: false + on_build_status_changed: false + +install: +# Download most recent Julia Windows binary + - ps: (new-object net.webclient).DownloadFile( + $("http://s3.amazonaws.com/"+$env:JULIAVERSION), + "C:\projects\julia-binary.exe") +# Run installer silently, output to C:\projects\julia + - C:\projects\julia-binary.exe /S /D=C:\projects\julia + +build_script: +# Need to convert from shallow to complete for Pkg.clone to work + - IF EXIST .git\shallow (git fetch --unshallow) + - C:\projects\julia\bin\julia -e "versioninfo(); + Pkg.clone(pwd(), \"DevIL\"); Pkg.build(\"DevIL\")" + +test_script: + - C:\projects\julia\bin\julia -e "Pkg.test(\"DevIL\")" diff --git a/deps/build.jl b/deps/build.jl index 86780f1..8803411 100644 --- a/deps/build.jl +++ b/deps/build.jl @@ -1,27 +1,41 @@ +using BinDeps +@BinDeps.setup +libnames = ["devil", "libdevil1c2", "libdevil-dev", "DevIL", "libIL", "libIL.so.1"] +libdevil = library_dependency("libdevil", aliases = libnames) + +# get library through Homebrew, if available +@static if is_apple() + if Pkg.installed("Homebrew") == nothing + error("Homebrew package not installed, please run Pkg.add(\"Homebrew\")") + end + using Homebrew + provides(Homebrew.HB, "devil", libdevil, os = :Darwin) +end + +# download a pre-compiled binary (built by GLFW) @static if is_windows() - ts = Int(floor(time())) - if Sys.ARCH == :x86_64 - # 64-bit version is not available in an end-user package, so we download the SDK - srcUrl = "http://downloads.sourceforge.net/project/openil/DevIL%20Windows%20SDK/1.7.8/DevIL-SDK-x64-1.7.8.zip?r=&ts=$ts&use_mirror=auto_select" - fileName = "DevIL-SDK-x64-1.7.8.zip" - elseif Sys.ARCH == :x86 - srcUrl = "http://downloads.sourceforge.net/project/openil/DevIL%20Win32/1.7.8/DevIL-EndUser-x86-1.7.8.zip?r=&ts=$ts&use_mirror=auto_select" - fileName = "DevIL-EndUser-x86-1.7.8.zip" - else - error("DevIL: Unsupported Windows architecture") - end - - dstDir = Pkg.dir("DevIL", "deps", string(Sys.ARCH)) - if (!isdir(dstDir)) - mkdir(dstDir) - end - dstFile = dstDir * "\\" * fileName - download(srcUrl, dstFile) - run(`"$JULIA_HOME\\7z.exe" e "$dstFile" *.dll -o"$dstDir" -y`) - rm(dstFile) + ts = floor(Int, time()) + url = if Sys.ARCH == :x86_64 + # 64-bit version is not available in an end-user package, so we download the SDK + "http://downloads.sourceforge.net/project/openil/DevIL%20Windows%20SDK/1.7.8/DevIL-SDK-x64-1.7.8.zip?r=&ts=$ts&use_mirror=auto_select" + elseif Sys.ARCH == :x86 + "http://downloads.sourceforge.net/project/openil/DevIL%20Win32/1.7.8/DevIL-EndUser-x86-1.7.8.zip?r=&ts=$ts&use_mirror=auto_select" + else + error("DevIL: Unsupported Windows architecture: $(Sys.ARCH)") + end + archive = "unicode" + libpath = "" + uri = URI(url) + provides(Binaries, uri, libdevil, unpacked_dir = archive, installed_libpath = libpath, os = :Windows) end @static if is_linux() - run(`sudo apt-get install libdevil1c2`) + provides(AptGet, "libdevil-dev", libdevil) + provides(Pacman, "libdevil-dev", libdevil) + provides(Yum, "DevIL", libdevil) end +function main() + @BinDeps.install Dict("libdevil" => "libdevil") +end +main() # move in function to get better stack traces diff --git a/src/DevIL.jl b/src/DevIL.jl index 43536ef..e9e6dcb 100644 --- a/src/DevIL.jl +++ b/src/DevIL.jl @@ -1,18 +1,17 @@ __precompile__() - module DevIL -const libIL = Libdl.find_library(["libIL", "DevIL"], - ["/usr/lib/x86_64-linux-gnu", Pkg.dir("DevIL", "deps", string(Sys.ARCH))]) +using FixedPointNumbers, ColorTypes +include(joinpath("..", "deps", "deps.jl")) # Stolen from getCFun macro macro ilFunc(cFun) - arguments = map(function (arg) - if isa(arg, Symbol) - arg = Expr(:(::), arg) - end - return arg - end, cFun.args[1].args[2:end]) + arguments = map(cFun.args[1].args[2:end]) do arg + if isa(arg, Symbol) + arg = Expr(:(::), arg) + end + return arg + end # Get info out of arguments of `cFun` argumentNames = map(arg->arg.args[1], arguments) @@ -22,7 +21,7 @@ macro ilFunc(cFun) # Construct the result. cName = cFun.args[1].args[1] cSym = Expr(:quote, cName) - symAndLib = :($cSym, $libIL) + symAndLib = :($cSym, libdevil) body = Expr(:ccall, symAndLib, returnType, Expr(:tuple, inputTypes...), argumentNames...) func = Expr(:function, Expr(:call, cName, argumentNames...), body) @@ -41,5 +40,11 @@ macro ilConst(assignment) end include("IL.jl") +include("fileio.jl") + +function __init__() + ilInit() + atexit(ilShutDown) +end end # module diff --git a/src/IL.jl b/src/IL.jl index 45efe12..7ea6536 100644 --- a/src/IL.jl +++ b/src/IL.jl @@ -15,9 +15,15 @@ typealias ILclampd Cdouble typealias ILint64 Int64 typealias ILuint64 UInt64 -typealias ILchar Cchar -typealias ILstring Ptr{Cchar} -typealias ILconst_string Ptr{Cchar} +if is_windows() + typealias ILchar Cwchar_t + typealias ILstring Cwstring + typealias ILconst_string Cwstring +else + typealias ILchar Cchar + typealias ILstring Cstring + typealias ILconst_string Cstring +end export ILenum, ILboolean, ILbitfield, ILbyte, ILshort, ILint, ILsizei, ILubyte, ILushort, ILuint, ILfloat, ILclampf, ILdouble, ILclampd, ILint64, ILuint64, ILchar, ILstring, ILconst_string @@ -28,7 +34,7 @@ export ILenum, ILboolean, ILbitfield, ILbyte, ILshort, ILint, ILsizei, ILubyte, #! Data formats \link Formats Formats\endlink @ilConst IL_COLOUR_INDEX = 0x1900 @ilConst IL_COLOR_INDEX = 0x1900 -@ilConst IL_ALPHA = 0x1906 +@ilConst IL_ALPHA = 0x1906 @ilConst IL_RGB = 0x1907 @ilConst IL_RGBA = 0x1908 @ilConst IL_BGR = 0x80E0 @@ -150,36 +156,37 @@ export ILenum, ILboolean, ILbitfield, ILbyte, ILshort, ILint, ILsizei, ILubyte, # Error Types -@ilConst IL_NO_ERROR = 0x0000 -@ilConst IL_INVALID_ENUM = 0x0501 -@ilConst IL_OUT_OF_MEMORY = 0x0502 -@ilConst IL_FORMAT_NOT_SUPPORTED = 0x0503 -@ilConst IL_INTERNAL_ERROR = 0x0504 -@ilConst IL_INVALID_VALUE = 0x0505 -@ilConst IL_ILLEGAL_OPERATION = 0x0506 -@ilConst IL_ILLEGAL_FILE_VALUE = 0x0507 -@ilConst IL_INVALID_FILE_HEADER = 0x0508 -@ilConst IL_INVALID_PARAM = 0x0509 -@ilConst IL_COULD_NOT_OPEN_FILE = 0x050A -@ilConst IL_INVALID_EXTENSION = 0x050B -@ilConst IL_FILE_ALREADY_EXISTS = 0x050C -@ilConst IL_OUT_FORMAT_SAME = 0x050D -@ilConst IL_STACK_OVERFLOW = 0x050E -@ilConst IL_STACK_UNDERFLOW = 0x050F -@ilConst IL_INVALID_CONVERSION = 0x0510 -@ilConst IL_BAD_DIMENSIONS = 0x0511 -@ilConst IL_FILE_READ_ERROR = 0x0512 # 05/12/2002: Addition by Sam. -@ilConst IL_FILE_WRITE_ERROR = 0x0512 - -@ilConst IL_LIB_GIF_ERROR = 0x05E1 -@ilConst IL_LIB_JPEG_ERROR = 0x05E2 -@ilConst IL_LIB_PNG_ERROR = 0x05E3 -@ilConst IL_LIB_TIFF_ERROR = 0x05E4 -@ilConst IL_LIB_MNG_ERROR = 0x05E5 -@ilConst IL_LIB_JP2_ERROR = 0x05E6 -@ilConst IL_LIB_EXR_ERROR = 0x05E7 -@ilConst IL_UNKNOWN_ERROR = 0x05FF - +@enum(ILerror, + IL_NO_ERROR = 0x0000, + IL_INVALID_ENUM = 0x0501, + IL_OUT_OF_MEMORY = 0x0502, + IL_FORMAT_NOT_SUPPORTED = 0x0503, + IL_INTERNAL_ERROR = 0x0504, + IL_INVALID_VALUE = 0x0505, + IL_ILLEGAL_OPERATION = 0x0506, + IL_ILLEGAL_FILE_VALUE = 0x0507, + IL_INVALID_FILE_HEADER = 0x0508, + IL_INVALID_PARAM = 0x0509, + IL_COULD_NOT_OPEN_FILE = 0x050A, + IL_INVALID_EXTENSION = 0x050B, + IL_FILE_ALREADY_EXISTS = 0x050C, + IL_OUT_FORMAT_SAME = 0x050D, + IL_STACK_OVERFLOW = 0x050E, + IL_STACK_UNDERFLOW = 0x050F, + IL_INVALID_CONVERSION = 0x0510, + IL_BAD_DIMENSIONS = 0x0511, + IL_FILE_READ_WRITE_ERROR= 0x0512, # 05/12/2002: Addition by Sam. + #IL_FILE_WRITE_ERROR = 0x0512, + + IL_LIB_GIF_ERROR = 0x05E1, + IL_LIB_JPEG_ERROR = 0x05E2, + IL_LIB_PNG_ERROR = 0x05E3, + IL_LIB_TIFF_ERROR = 0x05E4, + IL_LIB_MNG_ERROR = 0x05E5, + IL_LIB_JP2_ERROR = 0x05E6, + IL_LIB_EXR_ERROR = 0x05E7, + IL_UNKNOWN_ERROR = 0x05FF +) # Origin Definitions @ilConst IL_ORIGIN_SET = 0x0600 @@ -416,7 +423,7 @@ IL_SAVEPROC(f::Function) = cfunction(f, ILenum, (ILconst_string,)) @ilFunc ilGetBooleanv(Mode::ILenum, Param::Ptr{ILboolean})::Void @ilFunc ilGetData()::Ptr{ILubyte} @ilFunc ilGetDXTCData(Buffer::Ptr{Void}, BufferSize::ILuint, DXTCFormat::ILenum)::ILuint -@ilFunc ilGetError()::ILenum +@ilFunc ilGetError()::ILerror @ilFunc ilGetInteger(Mode::ILenum)::ILint @ilFunc ilGetIntegerv(Mode::ILenum, Param::Ptr{ILint})::Void @ilFunc ilGetLumpPos()::ILuint diff --git a/src/fileio.jl b/src/fileio.jl new file mode 100644 index 0000000..b19adfc --- /dev/null +++ b/src/fileio.jl @@ -0,0 +1,225 @@ +using FileIO: DataFormat, @format_str, Stream, File, filename, stream + + +const image_formats = Dict( + format"BMP" => IL_BMP, + format"DCX" => IL_DCX, + format"GIF" => IL_GIF, + format"HDR" => IL_HDR, + format"ICO" => IL_ICO, + format"JP2" => IL_JP2, + format"JPEG" => IL_JPG, + format"PCX" => IL_PCX, + format"PGM" => IL_PNM, + format"PNG" => IL_PNG, + format"PSD" => IL_PSD, + format"RGB" => IL_RGB, + format"TIFF" => IL_TIF, + format"TGA" => IL_TGA +) + +load{T <: DataFormat}(imagefile::File{T}, args...; key_args...) = load_(filename(imagefile), args...; key_args...) +load(filename::AbstractString, args...; key_args...) = load_(filename, args...; key_args...) +save{T <: DataFormat}(imagefile::File{T}, args...; key_args...) = save_(filename(imagefile), args...; key_args...) +save(filename::AbstractString, args...; key_args...) = save_(filename, args...; key_args...) + +load{T <: DataFormat}(imgstream::Stream{T}, args...; key_args...) = load_(stream(imgstream), args...; key_args...) +load(imgstream::IO, args...; key_args...) = load_(imgstream, args...; key_args...) +save{T <: DataFormat}(imgstream::Stream{T}, args...; key_args...) = save_(imgstream, args...; key_args...) + +function assert_devil(err, msg = "") + if !Bool(err) + println(STDERR, "DevIL Error: ", msg) + error(string(ilGetError())) + end +end + +function newimg() + img = ilGenImage() + ilBindImage(img) + img +end + +function to_string(str) + String(str) +end + +function load_(file::AbstractString; ImageType = Array) + img = newimg() + err = ilLoadImage(to_string(file)) + assert_devil(err, "while loading $file") + data = getimage() + ilDeleteImage(img) + data +end +function load_(stream::IO; ImageType = Array) + img = newimg() + err = ilLoadF(IL_TYPE_UNKNOWN, Libc.FILE(stream).ptr) + assert_devil(err, "while loading stream") + data = getimage() + ilDeleteImage(img) + data +end +function load_(lump::Vector{UInt8}; ImageType = Array) + img = newimg() + err = ilLoadL(IL_TYPE_UNKNOWN, lump, sizeof(lump)) + assert_devil(err, "while loading image from Vector{UInt8}") + data = getimage() + ilDeleteImage(img) + data +end + +const colordict = Dict( + IL_COLOR_INDEX => Void, + IL_ALPHA => Void, + IL_RGB => RGB, + IL_RGBA => RGBA, + IL_BGR => BGR, + IL_BGRA => BGRA, + IL_LUMINANCE => Gray, + IL_LUMINANCE_ALPHA => GrayA +) +devilcolor{T<:RGB}(::Type{T}) = IL_RGB +devilcolor{T<:RGBA}(::Type{T}) = IL_RGBA +devilcolor{T<:BGRA}(::Type{T}) = IL_BGRA +devilcolor{T<:BGR}(::Type{T}) = IL_BGR +devilcolor{T<:Gray}(::Type{T}) = IL_LUMINANCE +devilcolor{T<:GrayA}(::Type{T}) = IL_LUMINANCE_ALPHA +const devil_colordict = Dict(zip(values(colordict), keys(colordict))) +const pixeltypedict = Dict( + IL_BYTE => Int8, + IL_UNSIGNED_BYTE => N0f8, + IL_SHORT => Int16, + IL_UNSIGNED_SHORT => N0f16, + IL_INT => Int32, + IL_UNSIGNED_INT => N0f32, + IL_HALF => Float16, + IL_FLOAT => Float32, + IL_DOUBLE => Float64, +) +const devil_pixeltypedict = Dict(zip(values(pixeltypedict), keys(pixeltypedict))) + +function colortype() + colorformat = ilGetInteger(IL_FORMAT_MODE) + color = get(colordict, colorformat) do + error("Not a known colortype: $colorformat") + end + if color == Void + error("Colortype is indexed, which is not supported right now") + end + pixformat = ilGetInteger(IL_TYPE_MODE) + PX = get(pixeltypedict, pixformat) do + error("Not a known pixeltype: $pixformat") + end + color{PX}, colorformat, pixformat +end + +function getimage() + ilEnable(IL_ORIGIN_SET) + ilOriginFunc(IL_ORIGIN_LOWER_LEFT) + w = Int(ilGetInteger(IL_IMAGE_WIDTH)) + h = Int(ilGetInteger(IL_IMAGE_HEIGHT)) + frames = Int(ilGetInteger(IL_NUM_IMAGES)) + ctype, dcolor, dpix = colortype() + size = frames == 0 ? (w, h) : (w, h, frames) + image = Array(ctype, size) + # TODO return axis array for spatial order, xy?! + ilCopyPixels(0, 0, 0, w, h, 1, dcolor, dpix, image) + #transform(image) + rotl90(image) +end + +function devilcolor(img) + CT = eltype(img) + CET = eltype(CT) + px = get(devil_pixeltypedict, CET) do + error("Not a supported pixel type: $CET") + end + if CT <: Number + return IL_LUMINANCE, px + end + return devilcolor(CT), px, length(CT) +end + +typealias Color1{T} Color{T,1} +typealias Color2{T,C<:Color1} TransparentColor{C,T,2} +typealias Color3{T} Color{T,3} +typealias Color4{T,C<:Color3} TransparentColor{C,T,4} + +# ImageMagick element-mapping function. Converts to RGB/RGBA and uses +# N0f8 "inner" element type. +mapIM(c::Color1) = mapIM(convert(Gray, c)) +mapIM{T}(c::Gray{T}) = convert(Gray{N0f8}, c) +mapIM{T<:Normed}(c::Gray{T}) = c + +mapIM(c::Color2) = mapIM(convert(GrayA, c)) +mapIM{T}(c::GrayA{T}) = convert(GrayA{N0f8}, c) +mapIM{T<:Normed}(c::GrayA{T}) = c + +mapIM(c::Color3) = mapIM(convert(RGB, c)) +mapIM{T}(c::RGB{T}) = convert(RGB{N0f8}, c) +mapIM{T<:Normed}(c::RGB{T}) = c + +mapIM(c::Color4) = mapIM(convert(RGBA, c)) +mapIM{T}(c::RGBA{T}) = convert(RGBA{N0f8}, c) +mapIM{T<:Normed}(c::RGBA{T}) = c + +mapIM(x::UInt8) = reinterpret(N0f8, x) +mapIM(x::Bool) = convert(N0f8, x) +mapIM(x::AbstractFloat) = convert(N0f8, x) +mapIM(x::Normed) = x + +# Make the data contiguous in memory, this is necessary for +# imagemagick since it doesn't handle stride. +to_contiguous(A::Array) = A +to_contiguous(A::AbstractArray) = copy(A) +to_contiguous(A::SubArray) = copy(A) +to_contiguous(A::BitArray) = convert(Array{N0f8}, A) +#to_contiguous(A::ColorView) = to_contiguous(channelview(A)) + +to_explicit{C<:Colorant}(A::Array{C}) = to_explicit(channelview(A)) +#to_explicit{T}(A::ChannelView{T}) = to_explicit(copy!(Array{T}(size(A)), A)) +to_explicit{T<:Normed}(A::Array{T}) = rawview(A) +to_explicit{T<:AbstractFloat}(A::Array{T}) = to_explicit(convert(Array{N0f8}, A)) + + +function bind_image(img) + if ndims(img) > 3 + error("At most 3 dimensions are supported") + end + ctype, pixtype, bpx = devilcolor(img) + ilTexImage( + size(img, 2), size(img, 1), + ndims(img) == 3 ? size(img, 3) : 1, + bpx, ctype, pixtype, + rotr90(img) + ) +end + + +function save_(file::AbstractString, image) + img = newimg() + bind_image(image) + err = ilSaveImage(to_string(file)) + ilDeleteImage(img) + assert_devil(err, "while saving $file") +end + +function save_{T}(io::Stream{T}, image) + img = newimg() + bind_image(image) + err = ilSaveF( + get(image_formats, T, IL_TYPE_UNKNOWN), + Libc.FILE(stream(io)).ptr + ) + ilDeleteImage(img) + #assert_devil(err, "while saving to stream") +end + +function save_(lump::Vector{UInt8}, image) + img = newimg() + bind_image(image) + err = ilSaveL(IL_JPG, lump, sizeof(lump)) + ilDeleteImage(img) + #assert_devil(err, "while saving to Vector{UInt8}") +end diff --git a/test/REQUIRE b/test/REQUIRE new file mode 100644 index 0000000..1bfe0a1 --- /dev/null +++ b/test/REQUIRE @@ -0,0 +1,2 @@ +TestImages +FileIO diff --git a/test/runtests.jl b/test/runtests.jl index 6d7fc87..75187fc 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,5 +1,60 @@ -using DevIL +using DevIL, TestImages, FileIO using Base.Test -# write your own tests here -@test 1 == 1 +const imagepaths = map(readdir(TestImages.imagedir)) do name + joinpath(TestImages.imagedir, name) +end + +# it'd be nice to confirm that the images have the right orientation and are not +# messed up. But not erroring is already pretty good for now! +imgs = [] +@testset "from path" begin + empty!(imgs) + for path in imagepaths + push!(imgs, DevIL.load_(path)) + end + @test length(imgs) == 18 +end +@testset "from io" begin + empty!(imgs) + for path in imagepaths + push!(imgs, open(path) do io + DevIL.load_(io) + end) + end + @test length(imgs) == 18 +end +@testset "from lump" begin + empty!(imgs) + for path in imagepaths + push!(imgs, open(path) do io + DevIL.load_(read(io)) + end) + end + @test length(imgs) == 18 +end + + +@testset "saving" begin + img = first(imgs) + for imformat in (format"JPEG", format"PNG") + tmp = Vector{UInt8}(sizeof(img)) + @testset "to Vector{UInt8}" begin + DevIL.save_(tmp, img) + @test true + end + mktemp() do f, io + @testset "to stream" begin + DevIL.save_(Stream(imformat, io), img) + @test true + end + close(io) + ext = info(imformat)[2] + ext = isa(ext, Vector) ? ext[1] : ext + @testset "to path $imformat" begin + DevIL.save_(f*ext, img) + @test true + end + end + end +end