How can I combine Cairo frames into a GIF?

I have used Cairo to create a series of drawings collected into an array. I would like to combine all of these images into a GIF.

FileIO allows me to convert one single frame into a PNG with FileIO.save("test.png", imgs[1]) where imgs is my array of Cairo images. However, when I attempt to run FileIO.save("test.gif", imgs) I get the following error:

┌ Warning: Mapping to the storage type failed; perhaps your data had out-of-range values?
│ Try `map(clamp01nan, img)` to clamp values to a valid range.
└ @ ImageMagick C:\Users\[user]\.julia\packages\ImageMagick\Fh2BX\src\ImageMagick.jl:179
Error encountered while save File{DataFormat{:GIF}, String}("test.gif").

Fatal error:
MethodError: no method matching mapIM(::Cairo.CairoSurfaceBase{UInt32})
Closest candidates are:
  mapIM(::Gray{T}) where T<:FixedPointNumbers.Normed at C:\Users\[user]\.julia\packages\ImageMagick\Fh2BX\src\ImageMagick.jl:302
  mapIM(::Gray{T}) where T at C:\Users\[user]\.julia\packages\ImageMagick\Fh2BX\src\ImageMagick.jl:301
  mapIM(::FixedPointNumbers.Normed) at C:\Users\[user]\.julia\packages\ImageMagick\Fh2BX\src\ImageMagick.jl:321
  ...
Stacktrace:
  [1] (::ImageMagick.var"#42#43"{typeof(identity)})(x::Cairo.CairoSurfaceBase{UInt32})
    @ ImageMagick C:\Users\[user]\.julia\packages\ImageMagick\Fh2BX\src\ImageMagick.jl:177
  [2] iterate
    @ .\generator.jl:47 [inlined]
  [3] _collect
    @ .\array.jl:744 [inlined]
  [4] collect_similar(cont::Vector{Cairo.CairoSurfaceBase{UInt32}}, itr::Base.Generator{Vector{Cairo.CairoSurfaceBase{UInt32}}, ImageMagick.var"#42#43"{typeof(identity)}})
    @ Base .\array.jl:653
  [5] map(f::Function, A::Vector{Cairo.CairoSurfaceBase{UInt32}})
    @ Base .\abstractarray.jl:2849
  [6] image2wand(img::Any, mapi::typeof(identity), quality::Nothing, permute_horizontal::Bool; kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ ImageMagick C:\Users\[user]\.julia\packages\ImageMagick\Fh2BX\src\ImageMagick.jl:177
  [7] image2wand
    @ C:\Users\[user]\.julia\packages\ImageMagick\Fh2BX\src\ImageMagick.jl:175 [inlined]
  [8] #save_#39
    @ C:\Users\[user]\.julia\packages\ImageMagick\Fh2BX\src\ImageMagick.jl:162 [inlined]
  [9] save_ (repeats 2 times)
    @ C:\Users\[user]\.julia\packages\ImageMagick\Fh2BX\src\ImageMagick.jl:162 [inlined]
 [10] save(imagefile::File, args::Any; key_args::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ ImageMagick C:\Users\[user]\.julia\packages\ImageMagick\Fh2BX\src\ImageMagick.jl:126

Stacktrace:
  [1] handle_error(e::MethodError, q::Base.PkgId, bt::Vector{Union{Ptr{Nothing}, Base.InterpreterIP}})
    @ FileIO C:\Users\[user]\.julia\packages\FileIO\BE7iZ\src\error_handling.jl:61
  [2] handle_exceptions(exceptions::Vector{Tuple{Any, Union{Base.PkgId, Module}, Vector}}, action::String)
    @ FileIO C:\Users\[user]\.julia\packages\FileIO\BE7iZ\src\error_handling.jl:56
  [3] action(call::Symbol, libraries::Vector{Union{Base.PkgId, Module}}, file::FileIO.Formatted, args::Vector{Cairo.CairoSurfaceBase{UInt32}}; options::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ FileIO C:\Users\[user]\.julia\packages\FileIO\BE7iZ\src\loadsave.jl:228
  [4] action
    @ C:\Users\[user]\.julia\packages\FileIO\BE7iZ\src\loadsave.jl:197 [inlined]
  [5] action(call::Symbol, libraries::Vector{Union{Base.PkgId, Module}}, sym::Symbol, file::String, args::Vector{Cairo.CairoSurfaceBase{UInt32}}; options::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ FileIO C:\Users\[user]\.julia\packages\FileIO\BE7iZ\src\loadsave.jl:185
  [6] action
    @ C:\Users\[user]\.julia\packages\FileIO\BE7iZ\src\loadsave.jl:185 [inlined]
  [7] #save#20
    @ C:\Users\[user]\.julia\packages\FileIO\BE7iZ\src\loadsave.jl:129 [inlined]
  [8] save(file::String, args::Vector{Cairo.CairoSurfaceBase{UInt32}})
    @ FileIO C:\Users\[user]\.julia\packages\FileIO\BE7iZ\src\loadsave.jl:126
  [9] top-level scope
    @ In[35]:1
 [10] eval
    @ .\boot.jl:373 [inlined]
 [11] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
    @ Base .\loading.jl:1196

Hi there! As a workround you could save the PNG images in a directory and then run use FFMPEG.jl to create the animation…

Without some example code that can be run, it’s not easy for people to find out what’s going on here.

I’m thinking I’ll attempt that.

Here’s an example:

img0 = CairoRGBSurface(512, 512)
cr = CairoContext(img0)

set_source_rgb(cr, 1, 0, 0)
Cairo.arc(cr, 256, 256, 10, 0, 2*\pi)
Cairo.stroke(cr)

imgs = [img0]

for i in 1:5
    img = CairoRGBSurface(0, 0, 0)
    cr = CairoContext(img)

    set_source_rgb(cr, 1, 0, 0)
    Cairo.arc(cr, 256+20*i, 256, 10, 0, 2*\pi)
    Cairo.stroke(cr)

    push!(imgs, img)
end

FileIO.save("test.gif", imgs)

Yes, I think there are some issues with the GIF-saving - I’d stick to PNGs and FFMPEG myself… :slight_smile: