Mixing translation with drawing

260 views
Skip to first unread message

Cartman

unread,
Apr 10, 2014, 12:21:39 PM4/10/14
to kivy-...@googlegroups.com
Hi, I made a simple example of what I'm trying to do. It's below, and it doesn't work completely, i.e. I can't translate what is drawn, it just dislocates future drawings. It works partly if I add Translate instruction on canvas.before, instead on canvas, but than again future drawings are translated too.

How should I modify this example to make it work as expected?

from kivy.app import App
from kivy.lang import Builder
from kivy.graphics import Translate, Color, Line
from kivy.uix.relativelayout import RelativeLayout
from kivy.properties import StringProperty
from kivy.uix.togglebutton import ToggleButton

Builder.load_string('''
<Paint>:
    BoxLayout:
        BoxLayout:
            orientation: 'vertical'
            size_hint_x: .2
            ToggleButton:
                text: 'draw'
                group: 'paint'
                on_press: root.state = 'draw'
            ToggleButton:
                text: 'translate'
                group: 'paint'
                on_press: root.state = 'translate'
        Widget:
            id: widget
            on_touch_down: if self.collide_point(*args[1].pos): root.touch_down(args[1])
            on_touch_move: if self.collide_point(*args[1].pos): root.touch_move(args[1])
            canvas:
                Color:
                    rgb: 1, 1, 1
                Rectangle:
                    pos: self.pos
                    size: self.size
''')

class Paint(RelativeLayout):
    state = StringProperty('draw')

    def touch_down(self, touch):
        if self.state == 'draw':
            with self.ids.widget.canvas:
                Color(0, 0, 0)
                touch.ud['line'] = Line(points=(touch.x, touch.y), width=10)
        elif self.state == 'translate':
            with self.ids.widget.canvas:
                touch.ud['start'] = (touch.x, touch.y)
                touch.ud['translate'] = Translate(0, 0)

    def touch_move(self, touch):
        if self.state == 'draw':
            touch.ud['line'].points += [touch.x, touch.y]
        elif self.state == 'translate':
            touch.ud['translate'].xy = (touch.x-touch.ud['start'][0], touch.y-touch.ud['start'][1])

class PaintApp(App):
    def build(self):
        return Paint()


if __name__ == '__main__':
    PaintApp().run()

Gabriel Pettier

unread,
Apr 10, 2014, 8:26:17 PM4/10/14
to kivy-...@googlegroups.com
Hi

You want to surround your translate instructions with PushMatrix and
PopMatrix instructions, to save/restore the matrix position.

cheers
> --
> You received this message because you are subscribed to the Google Groups "Kivy users support" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
Message has been deleted

Cartman

unread,
Apr 11, 2014, 6:12:02 AM4/11/14
to kivy-...@googlegroups.com
Thanks for the reply. I added Push/PopMatrix instructions and succeeding drawings are placed correctly now, but it seems that translation doesn't work, although it should. Something more needs to be edited and I'm not quite sure what. Do you (or someone else) have any idea?

Below is modified example with Push/PopMatrix instructions:

from kivy.app import App
from kivy.lang import Builder
from kivy.graphics import Translate, Color, Line, PushMatrix, PopMatrix
                PushMatrix()
                touch.ud['translate'] = Translate(0, 0)
                PopMatrix()

    def touch_move(self, touch):
        if self.state == 'draw':
            touch.ud['line'].points += [touch.x, touch.y]
        elif self.state == 'translate':
            touch.ud['translate'].xy = (touch.x-touch.ud['start'][0], touch.y-touch.ud['start'][1])

class PaintApp(App):
    def build(self):
        return Paint()


if __name__ == '__main__':
    PaintApp().run()

Alexander Taylor

unread,
Apr 11, 2014, 7:14:58 AM4/11/14
to kivy-...@googlegroups.com
The Translate only affects things after the Translate instruction and before the next PopMatrix. You have no graphics instructions in that area, so you see no effect.

Cartman

unread,
Apr 11, 2014, 8:23:44 AM4/11/14
to kivy-...@googlegroups.com
I understand. Do you have any suggestion on how it could be done, in the context of the above example?

Gabriel Pettier

unread,
Apr 11, 2014, 10:00:04 AM4/11/14
to kivy-...@googlegroups.com
Move the PopMatrix to canvas.after if you want the children of the
widget to be affected by the translation.

I'm not sure why you want to separate drawing and translation though, if
you want the drawing to be translated, you want both to be applied, and
the drawing to be between the translation and the popmatrix.

But maybe i'm not really getting what you are trying to do.

Cartman

unread,
Apr 11, 2014, 12:36:43 PM4/11/14
to kivy-...@googlegroups.com
Maybe what I'm trying to do isn't possible with Kivy's context instructions. All I wanted is to simulate behaviour from the graphical editors, where you can draw something, then Translate/Rotate/Scale that, then normally draw more, etc. Here, if I add Translate to the canvas that affects only future drawings, and I want them to be unaffected. Instead I want to translate what is already drawn before.

Gabriel Pettier

unread,
Apr 11, 2014, 3:21:50 PM4/11/14
to kivy-...@googlegroups.com
You want the Translate instruction to be always there, not just in
translate mode, what you want to happen in translate mode is to update
the translate.xy value, based on user interaction.

Canvas is a list of instructions, that get passed to the GPU, which
executes them each frame. If you want the drawing to be translated, the
drawing must be preceded by the translate instruction in the canvas.

The solution is to create the Translate instraction at the creation of
the widget, and put it first, along with the PushMatrix, the easiest is
to put them in canvas.before, and to put the PopMatrix in the
canvas.after. Then instead of creating new Translate instruction each
time you are int translate mode, update the existing one.

Hope this helps.
> > an email to kivy-users+...@googlegroups.com <javascript:>.

Cartman

unread,
Apr 11, 2014, 5:16:55 PM4/11/14
to kivy-...@googlegroups.com
I did as you suggested, thank you. Translation works, but further drawings are dislocated also. Don't know how to solve that.

Here is the code:

from kivy.app import App
from kivy.lang import Builder
from kivy.graphics import Translate, Color, Line, PushMatrix, PopMatrix
from kivy.uix.relativelayout import RelativeLayout
from kivy.properties import StringProperty
from kivy.uix.togglebutton import ToggleButton

Builder.load_string('''
<Paint>:
    BoxLayout:
        BoxLayout:
            orientation: 'vertical'
            size_hint_x: .2
            ToggleButton:
                text: 'draw'
                group: 'paint'
                on_press: root.state = 'draw'
            ToggleButton:
                text: 'translate'
                group: 'paint'
                on_press: root.state = 'translate'
        Widget:
            id: widget
            on_touch_down: if self.collide_point(*args[1].pos): root.touch_down(args[1])
            on_touch_move: if self.collide_point(*args[1].pos): root.touch_move(args[1])
            canvas.before:
                Color:
                    rgb: 1, 1, 1
                Rectangle:
                    pos: self.pos
                    size: self.size
''')

class Paint(RelativeLayout):
    state = StringProperty('draw')

    def __init__(self, **kwargs):
        super(Paint, self).__init__(**kwargs)
        with self.ids.widget.canvas.before:
            PushMatrix()
            self.translate = Translate(0, 0)
        with self.ids.widget.canvas.after:
            PopMatrix()

    def touch_down(self, touch):
        if self.state == 'draw':
            with self.ids.widget.canvas:
                Color(0, 0, 0)
                touch.ud['line'] = Line(points=(touch.x, touch.y), width=10)
        elif self.state == 'translate':
            touch.ud['start'] = (touch.x, touch.y)
            touch.ud['translation'] = self.translate.xy

    def touch_move(self, touch):
        if self.state == 'draw':
            touch.ud['line'].points += [touch.x, touch.y]
        elif self.state == 'translate':
            self.translate.xy = (touch.ud['translation'][0]+(touch.x-touch.ud['start'][0]),
                                 touch.ud['translation'][1]+(touch.y-touch.ud['start'][1]))

class PaintApp(App):
    def build(self):
        return Paint()


if __name__ == '__main__':
    PaintApp().run()

Gabriel Pettier

unread,
Apr 11, 2014, 7:04:39 PM4/11/14
to kivy-...@googlegroups.com
Ah well, of course, you don't take the translation into account when
drawing the new points, an easy way to fix this with your current code
would be to substract the current translation to the coords you use for
the ponist for the new line.

in both fonction change to :

touch.ud['line'] = Line(points=(touch.x - self.translate.x, touch.y - self.translate.y), width=10)

touch.ud['line'].points += [touch.x - self.translate.x, touch.y - self.translate.y]

regards

Cartman

unread,
Apr 12, 2014, 6:09:48 AM4/12/14
to kivy-...@googlegroups.com
Thank you very much! Now it works as expected.
I was hoping this can be solved somehow with Push/PopMatrix, because I wanted to implement Rotation also, but now, when I saw how it's problematic to make translation, I probably won't.

Regards

Gabriel Pettier

unread,
Apr 12, 2014, 7:21:38 AM4/12/14
to kivy-...@googlegroups.com
Well, there are classes like Scatter and ScatterPlane that manage these
things, and do the relevant maths behind the scene, you'll probably want
to have a look at touch.to_local/to_parent/to_window etc if you need
coordinate transformations in these cases, but kivy can definitly make
things easier for you :)
Reply all
Reply to author
Forward
0 new messages