I have been insisting in the importance of PushMatrix and PopMatrix to control the use of the canvas in Kivy in:

In today's post I explain a very specific example of how to rotate an Image without affecting the whole interface. I start with the common problem that the novice will experience and explain how to fix it:

 

1. The Initial Interface:

This is the screenshot of the interface we are going to work with. We want to rotate the image on the top-right of the layout.

GridLayout with one Button (top-left), one Image (top-right) and another Button (bottom-left)

This is the respective code:

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.lang import Builder

Builder.load_string("""
<KivyImage@Image>:
    source: 'kivy.png'
    pos: self.parent.pos
    size_hint: .5,.4
    canvas:
        Color:
            rgba: 1,0,0,.5
        Line:
            rectangle: self.x, self.y, self.width, self.height

<TestApp>:
    cols: 2
    Button:
        text: "Place Holder 1"
    FloatLayout:
        KivyImage:
    Button:
        text: "Place Holder 2"
""")

class TestApp(App, GridLayout):
    def build(self):
        return self

if __name__ == "__main__":
    TestApp().run()

The interface is very simple. A GridLayout with 2 columns and 3 widgets. A Button (Place Holder 1), a FloatLayout with an Image inside and another Button (Place Holder 2). The FloatLayout contains the KivyImage class that we are going to be working with. The KivyImage also contains a rectangular border as a reference to follow our notations.

 

2. The Problem:

The instruction we need to use for rotating is Rotate. We use three parameters for it: axis that sets the axis are we going to use for the rotation, usually z since; angle that sets the amount of degrees we want to rotate. After using the Rotate instruction, our code will look like this:

<KivyImage@Image>:
    source: 'kivy.png'
    pos: self.parent.pos
    size_hint: .5,.4
    canvas:
        Rotate:
            axis: 0,0,1
            angle: 60
            origin: self.center
        Color:
            rgba: 1,0,0,.5
        Line:
            rectangle: self.x, self.y, self.width, self.height

Here is the result:

After the Rotation

As you can see, after applying the instruction, everything is rotated (including the last Button). This means, that when we use context instructions (Scale, Rotate and Translate), the whole coordinate space is affected.

More over, our image didn't Rotate just the Rectangle!

 

3. Avoiding a global rotation:

We need to control the rotation, so the whole interface after the Rotate instruction is not affected. One way would be manually un-rotate with something like:

        Rotate:
            axis: 0,0,1
            angle: -60
            origin: self.center

Of course, that will be quite tedious. Instead, we are going to use two important instructions: PushMatrix, that saves the current context, and PopMatrix, that recovers the last saved context. Now the code looks like this:

<KivyImage@Image>:
    source: 'kivy.png'
    pos: self.parent.pos
    size_hint: .5,.4
    canvas:
        PushMatrix
        Rotate:
            axis: 0,0,1
            angle: 60
            origin: self.center
        Color:
            rgba: 1,0,0,.5
        Line:
            rectangle: self.x, self.y, self.width, self.height
        PopMatrix

And our new output should look like the following:

Using PushMatrix and Pop

Almost there, except that our images continues in the same original position. Let's finally Rotate the image.

 

4. Rotate the image for once!:

Sorry, I tell you why the image is not rotated. In the 2nd step, even though the whole context where rotated, the image didn't. The only explanation is that the Rotate where executed after the image is display on the screen.

Somehow, we need to guarantee that our Rotate instruction gets executed before. There is three sets of canvas instructions: canvas.before, canvas and canvas.after. Their names are intuitive, so let's reorganize our instructions:

<<KivyImage@Image>:
    source: 'kivy.png'
    pos: self.parent.pos
    size_hint: .5,.4
    canvas.before:
        PushMatrix
        Rotate:
            axis: 0,0,1
            angle: 60
            origin: self.center
    canvas:
        Color:
            rgba: 1,0,0,.5
        Line:
            rectangle: self.x, self.y, self.width, self.height
    canvas.after:
        PopMatrix

And finally we got our image rotated without affecting the rest of the interface:

Rotated Image

It is not strictly necessary to move the PopMatrix to the canvas.after section. The difference would be in the children of the image. In this case, it doesn't have. But in case it has children, then they will be rotated if the PopMatrix is in the canvas.after section.

Read more about the canvas in 10 things you should know about the Kivy canvas.

One thought on “Simple Rotations of Images in Kivy

Leave a reply to Dan Cancel reply