This should be a chance to try out a drawing library and, importantly, figure out managing dependancies and such before Monday. I tried sudo apt install libcairo2-dev pkg-config
but pip install pycairo
still failed :/ Instead I did pip install cairocffi
so I guess that's what I'm using. I added it to the requirements list in settings.ini.
Here's the first example I tried, which I export to core.py so that it can be used elsewhere. The main motivation here was to check that evreything works and to see how NBDev handles this sort of thing. You'll notice it hides the actual method by default in the html docs, but gives the docstring.
hw_cairo('Hello World')
Creating a surface to mess with:
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 200, 100) # Creating a surface to play with
context = cairo.Context(surface)
context.set_source_rgb(0, 1, 0) # green
context.move_to(50, 50)
context.show_text('Hello World')
Viewing with matplotlib
from matplotlib import pyplot as plt
plt.imshow(surface_to_npy(surface))
A nicer way to view the image without the matplotlib axes is to using PIL and IPython's display. We might as well make a surface_to_PIL function and a display_surface function since we will want to do this a lot.
display_surface(surface)
import ipywidgets as widgets
# Set up surface
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 200, 100) # Creating a surface to play with
context = cairo.Context(surface)
imw = widgets.Image(
value=surface.write_to_png(),
format='png',
width=300,
height=400,
)
display(imw)
import time
for i in range(10):
context.set_source_rgb(0, 1, i/10) # green
context.move_to(50, 50+i*5)
context.show_text(f'Hello {i}')
imw.value = surface.write_to_png()
time.sleep(0.1)
display_surface(surface) # Display the final surface so that it shows in the html docs
We first make out blank canvas and display that. Whenever we want to update the image, we simply modify the value of the widget. A neat trick here is that surface.write_to_png()
returns an io.BytesIO thingee if we don't specify a target, which we can pass straight to the widget (before I was messing about trying to get this same thing to happen the long way around). This is pretty neat - we can set up a surface and then occasionally draw to it when we want to see updates. Let's demonstrate this with an example drawing a crazy amount of rectangles:
import random
# Set up surface
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 600, 400) # Creating a surface to play with
context = cairo.Context(surface)
imw = widgets.Image(
value=surface.write_to_png(),
format='png',
width=600,
height=400,
)
display(imw)
for i in range(100000):
context.set_source_rgba(0.6, i/1e4, 1-i/1e5, 1-i/1e5)
x, y = random.random()*600, random.random()*400
context.rectangle(x, y, 2, 2)
context.fill()
if i%1e4 == 0: # Change this to alter the number of updates (this affects the speed of the loop)
imw.value = surface.write_to_png()
display_surface(surface) # Show the final result for posterity (looses the alpha channel sadly...)
Conclusions
That's an hour of mostly logistics, but at the end of it we have easy ways to play with cairo in Jupyter, including converting surfaces to arrays, displaying them and, most satisfying, a good fast way to render animations for our viewing pleasure. Tomorrow I hope to start the actual fun stuff.