Is there a way to zoom into an image on a desktop kivy app (e.g. zoom with a mouse scroll-wheel)? It appears to be discussed here: https://github.com/kivy/kivy/issues/3563 but I do not see if there was a work around given.
I began with a static image in my kivy app. I want to add the ability to zoom / pan into the image. I do not want the actual size of the image frame to change, just add zoom/pan functionality, like you might expect from interacting with google maps for example.
Possible Programming Directions
From what I've read, I should be using Scatter(?), and I see that I can manually set the Scatter scale to size up/down the image.
My initial thoughts are that I will have to add a separate widget with a scrollview to contain the scatter layout and that will keep the image frame a consistent size. Then I will need to add an event that dynamically changes the scale value.
The problems
on_motion
event seemed promising. My app can update values with a slider, but when I try a similar approach using on_motion,
I get AttributeError: motion
errors I am struggling to create the widget. Most documentation seems to use .add_widget(...)
in the python file. Is it possible to do this from the kv file? I imagine this process is similar to screens and the screen manager, but I am struggling to find an approach that works.
Is there a more straight forward way to do this?
Is there a way I can use on_motion
type event in my kv file to adjust this value using the mouse scroll-wheel?
I give a watered down example of the structure of my kivy app - along with what I tried to do to add Scatter. I think I will need to put it into it's own widget to keep the image the same size?
Toy Example
import kivy
from kivy.lang import Builder
from kivy.core.window import Window
kivy.require('1.1.0')
from kivy.app import App
presentation = Builder.load_file("scatter.kv")
class TestApp(App):
def build(self):
Window.clearcolor = (1, 1, 1, 1)
return presentation
# def foo():
# print("You've reached foo")
if __name__ == '__main__':
TestApp().run()
and
#:kivy 1.10.0
GridLayout:
cols: 2
Scatter:
scale: 5
# on_motion: root.foo()
Image :
source: 'foo.png'
allow_stretch: True
keep_ratio: True
Button:
text: 'Hello World'
Produces:
Kind of Related:
You must carefully specify the size of your content to get the desired scroll/pan effect. By default, the size_hint is (1, 1), so the content size will fit your ScrollView exactly (you will have nothing to scroll). You must deactivate at least one of the size_hint instructions (x or y) of the child to enable scrolling.
The Carousel widget provides the classic mobile-friendly carousel view where you can swipe between slides. You can add any content to the carousel and have it move horizontally or vertically. The carousel can display pages in a sequence or a loop.
To achieve my goal, I used a combination of the information in kivy python3 detect mousewheel, as pointed out ikolim, and the code given here: https://github.com/kivy/kivy/wiki/Draggable-Scalable-Button
To keep my answer brief, here's the minimalistic object that extends Scatter object.
class ResizableDraggablePicture(Scatter):
def on_touch_down(self, touch):
# Override Scatter's `on_touch_down` behavior for mouse scroll
if touch.is_mouse_scrolling:
if touch.button == 'scrolldown':
if self.scale < 10:
self.scale = self.scale * 1.1
elif touch.button == 'scrollup':
if self.scale > 1:
self.scale = self.scale * 0.8
# If some other kind of "touch": Fall back on Scatter's behavior
else:
super(ResizableDraggablePicture, self).on_touch_down(touch)
The layout is slightly different and I changed the text on the button, but the functionality of my code can be seen in the following gif:
For anybody wanting to see my entire toy project to adapt for their own purposes, the entire code is on my github: https://github.com/melissadale/Learning-Kivy/tree/master/ZoomPanning
UPDATE my code has been edited to be far more correct from an object-orientated approach, and so I could not reject the edits with a clear conscience. However, when I was first starting with kivy, I would have found this code confusing. If you want to just see the simple version that you can apply directly to verify the relevant, this is my original code:
if touch.is_mouse_scrolling:
if touch.button == 'scrolldown':
print('down')
## zoom in
if self.scale < 10:
self.scale = self.scale * 1.1
elif touch.button == 'scrollup':
## zoom out
print('up')
if self.scale > 1:
self.scale = self.scale * 0.8
The other answer by @mdoc-2011 has the problem that it doesn't anchor onto the mouse pointer, meaning that it is quite annoying to use, since the content will scroll around a lot. This can be fixed by not using the .scale
property and instead directly calling apply_transform
:
if touch.is_mouse_scrolling:
factor = None
if touch.button == 'scrolldown':
if self.scale < self.scale_max:
factor = 1.1
elif touch.button == 'scrollup':
if self.scale > self.scale_min:
factor = 1 / 1.1
if factor is not None:
self.apply_transform(Matrix().scale(factor, factor, factor),
anchor=touch.pos)
This version also uses the.scale_(min/max)
properties, and instead of 1.1
and 0.8
uses 1/1.1
which will make it more intuitive to scroll in an out.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With