animation using Gtk+/Cairo

1,316 views
Skip to first unread message

Abe Schneider

unread,
Jun 16, 2014, 7:01:46 PM6/16/14
to julia...@googlegroups.com
I was looking for a way to display a simulation in Julia. Originally I was going to just use PyPlot, but it occurred to me it would be better to just use Gtk+ + Cairo to do the drawing rather than something whose main purpose is drawing graphs.

So far, following the examples on the Github page, I have no problem creating a window with a Cairo canvas. I can also display content on the canvas fairly easily (which speaks volumes on the awesomeness of Julia and the Gtk+ library). However, after looking through the code and samples, it's not obvious to me how to redraw the canvas every fraction of a second to display new content.

I did find an example of animating with Cairo and Gtk+ in C (http://cairographics.org/threaded_animation_with_cairo/). However, I assume one would want to use Julia's timers instead of of GLibs? Secondly, there in their function 'timer_exe', call is made directly to Gtk+ to send a redraw queue to the window. Is there a cleaner way to do it with the Gtk+ library?

Thanks!
A

Tim Holy

unread,
Jun 16, 2014, 9:12:44 PM6/16/14
to julia...@googlegroups.com
ImageView's navigation.jl contains an example. The default branch is Tk
(because as far as binary distribution goes, Tk is "solved" and Gtk isn't
yet), but it has a gtk branch you can look at.

--Tim

Jameson Nash

unread,
Jun 16, 2014, 9:33:42 PM6/16/14
to julia...@googlegroups.com
I would definately use Julia's timers. See `Gtk.jl/src/cairo.jl` for an example interface to the Cairo backing to a Gtk window (used in `Winston.jl/src/gtk.jl`). If you are using this wrapper, call `draw(w)` to force a redraw immediately, or `draw(w,false)` to queue a redraw request for when Gtk is idle.

Andreas Lobinger

unread,
Jun 17, 2014, 4:46:30 AM6/17/14
to julia...@googlegroups.com
Hello colleague,

i'm doing Gtk(2)+cairo animations (in python to admit it) in a different context and i have done it using the gtk main loop and glib timers; but i had to put some effort into making the animation fast, so not to interfere with the gtk main for too long.

In the Gtk.jl afaiu the implementors not to use the gtk_main, so the straight forward way should be use julia timing. I read the gtk+cairo threading tutorial some time ago, but it looked scary to me.

What you could think about is actually implement backing storage on your own - prepare a cairo.ImageSurface draw to that (in an own thread) and copy to the gtk Canvas (with set_source and paint()), actually copy to the cairo_context of the canvas in the expose event. The copying is pretty fast nowadays.
I just realized that the 'alternative' solution on the link is something like this.


Abe Schneider

unread,
Jun 17, 2014, 7:46:32 AM6/17/14
to julia...@googlegroups.com
Thank you everyone for the fast replies!

After looking at ImageView and the sources, here's the solution I came up with:

w = Gtk.@Window() |>
(body=Gtk.@Box(:v) |>
 
(canvas=Gtk.@Canvas(600, 600)) |>
showall

function redraw_canvas(canvas)
  ctx
= getgc(canvas)
  h
= height(canvas)
  w
= width(canvas)

 
# draw background
  rectangle
(ctx, 0, 0, w, h)
  set_source_rgb
(ctx, 1, 1, 1)
  fill
(ctx)

 
# draw objects
 
# ...

 
# tell Gtk+ to redisplay
  draw
(canvas)
end

function init(canvas, delay::Float64, interval::Float64)
  update_timer
= Timer(timer -> redraw_canvas(canvas))
  start_timer
(update_timer, delay, interval)
end

update_timer
= init(canvas, 2, 1)
if !isinteractive()
  wait
(Condition())
end

stop_timer
(update_timer)

I haven't looked yet into what is required to do double-buffering (or if it's enabled by default). I also copied the 'wait(Condition())' from the docs, though it's not clear to me what the condition is (if I close the window, the program is still running -- I'm assuming that means I need to connect the signal for window destruction to said condition).

A

Tobias Knopp

unread,
Jun 17, 2014, 8:48:58 AM6/17/14
to julia...@googlegroups.com
Hi Abe,

the idea of the wait condition is that the program would be immidiately closed if run in script mode (from the shell). In the REPL one usually wants the program to return so that the REPL is still active.

Cheers,

Tobi

Tim Holy

unread,
Jun 17, 2014, 9:26:16 AM6/17/14
to julia...@googlegroups.com
On Tuesday, June 17, 2014 04:46:31 AM Abe Schneider wrote:
> I haven't looked yet into what is required to do double-buffering (or if
> it's enabled by default). I also copied the 'wait(Condition())' from the
> docs, though it's not clear to me what the condition is (if I close the
> window, the program is still running -- I'm assuming that means I need to
> connect the signal for window destruction to said condition).

Good point. You probably already figured it out on your own, but I just updated
the Gtk docs to describe a better solution.

--Tim

Jameson Nash

unread,
Jun 17, 2014, 10:44:16 AM6/17/14
to julia...@googlegroups.com
This code is not valid, since getgc does not always have a valid drawing context to return. Instead you need to provide Canvas with a callback function via a call to redraw in which you do all the work, then just call draw(canvas) in your timer callback to force an update to the view. double-buffering is enabled by default.

wait(Condition()) is the same wait(), and means sleep until this task is signaled, and thereby prevents the program from exiting early

Abe Schneider

unread,
Jun 17, 2014, 12:49:46 PM6/17/14
to julia...@googlegroups.com
@Tim: Awesome, exactly what I was looking for. Thank you.

@Jameson: Just to check, do you mean something like:

function redraw_canvas(canvas)
  draw
(canvas)
end

draw
(canvas) do widget
 
# ...
end

If so, I'll re-post my code with the update. It may be useful to someone else to see the entire code as an example.

Thanks!
A

Jameson Nash

unread,
Jun 17, 2014, 1:16:11 PM6/17/14
to julia...@googlegroups.com
Yes. Although I think the draw...do function is actually redraw...do (this is actually a shared interface with Tk.jl, although I recommend Gtk :)

Sent from my phone. 

Abe Schneider

unread,
Jun 17, 2014, 6:24:39 PM6/17/14
to julia...@googlegroups.com
Okay, what works for me using your suggestion (except for me [with a 1 day old Julia and package] it's 'draw' and not 'redraw'), I have:

function update(canvas, scene::Scene)
 
# update scene
 
# ...
 
 
# redraw
  draw
(canvas)
end

function init(canvas, scene::Scene)
  update_timer
= Timer(timer -> update(canvas, scene))
  start_timer
(update_timer, 1, 0.5)
 
return update_timer
end

function draw_scene(canvas, scene::Scene)

  ctx
= getgc(canvas)
  h
= height(canvas)
  w
= width(canvas)


  rectangle
(ctx, 0, 0, w, h)

  set_source_rgb
(ctx, 1, 1, 1)
  fill
(ctx)


 
# use scene to draw other elements
 
# ...
end

scene
= Scene(...)
update_timer
= init(canvas, scene)
draw
(canvas -> draw_scene(canvas, scene), canvas)

if !isinteractive()
  cond
= Condition()
  signal_connect
(win, :destroy) do widget
    notify
(cond)
 
end

  wait
(cond)
end

stop_timer
(update_timer)


I wasn't sure if the way I bound the scene to the redraw is the nicest approach to take. If the function took additional parameters, that seems like it might be the most straight forward. E.g.:

draw(canvas, scene) do widget
   
# ...
end

# here 'myscene' would be passed as the second parameter to the other draw
draw
(canvas, myscene)


A
Reply all
Reply to author
Forward
0 new messages