How to update a texture from array in Kivy? - python

I'm new to Kivy, but have watched the tutorials. I want to have a widget containing a texture or image generated from an array, which will change at each frame. See below for what I currently have. Current behaviour is wrong when I resize the window - I think that the old Rectangle is never being deleted, but I can't see how to do that. It also shows the same image in a default (100,100) view at the bottom left of the main window. What do I need to change to achieve the desired behaviour, and not get artifacts when resizing the window?
from kivy.app import App
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.layout import Layout
from kivy.graphics import Rectangle
from kivy.graphics.texture import Texture
from kivy.clock import Clock
import numpy as np
import random
class MainDisplay(Layout):
tex = ObjectProperty(None)
def __init__(self, **kwargs):
super(MainDisplay, self).__init__(**kwargs)
Clock.schedule_once(self.texture_init, 0)
def texture_init(self, instance):
self.tex = Texture.create()
def update(self, dt):
size = 64 * 64 * 3
buf = np.array([int(random.random() * x * 255 / size) for x in range(size)])
print('update', max(buf), min(buf), np.mean(buf))
# then blit the buffer
self.tex.blit_buffer(buf.tostring(), colorfmt='bgr', bufferfmt='ubyte')
print('end update')
print(self.canvas)
print(self.size, self.pos, self, self.parent)
with self.canvas:
Rectangle(texture=self.tex, size=(self.width / 2, self.height / 2), pos=(self.center_x / 2, self.center_y / 2))
class MainWindow(BoxLayout):
md = ObjectProperty(None)
def __init__(self, **kwargs):
super(MainWindow, self).__init__(**kwargs)
def update(self, dt):
self.md.update(dt)
class ProtoApp(App):
def build(self):
mainWindow = MainWindow()
Clock.schedule_interval(mainWindow.update, 1.0/10.0)
return mainWindow
if __name__ == "__main__":
ProtoApp().run()
with the proto.kv file:
<MainWindow>:
md: md
MainDisplay:
id: md
size_hint: (0.5, 0.5)
Thanks in advance for your help!

Problem
Whenever the window is resized, it is creating new rectangle and leaving traces of the previous one.
Solution
Use the canvas's built-in function, clear()
Snippets
def update(self, dt):
size = 64 * 64 * 3
buf = np.array([int(random.random() * x * 255 / size) for x in range(size)])
# then blit the buffer
self.tex.blit_buffer(buf.tostring(), colorfmt='bgr', bufferfmt='ubyte')
with self.canvas:
self.rect = Rectangle(texture=self.tex, size=(self.width / 2, self.height / 2),
pos=(self.center_x / 2, self.center_y / 2))
self.bind(pos=self.update_rect, size=self.update_rect)
def update_rect(self, *args):
self.canvas.clear()
self.rect.pos = self.pos
self.rect.size = self.size

Related

How to align a rectangle using kivy?

Does anybody know the code to center align a rectangle using kivy but in a py file not kv. This is my code:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle
from kivy.graphics import Color
class PongGame(Widget):
def __init__(self, **kwargs):
super(PongGame, self).__init__(**kwargs)
with self.canvas:
Color(255,255,255, mode='rgba')
self.rect = Rectangle(pos=(self.x + 50, self.y + 50), size=(50, 50))
# print(self.rect)
class PingyPong(App):
def build(self):
return PongGame()
# class MakeRectangle(Widget):
# def __init__(self, **kwargs):
# with self.canvas:
# # Color(255,255,255, mode='rgba')
# self.rect = Rectangle(pos=(200, 200), size=(50, 50))
# class PingPong(App):
# def build(self):
# return Rectangle()
if __name__ == '__main__':
PingyPong().run()
This is the output:
enter image description here
please help
You can do it by getting Window size and then create rectangle at the center
from kivy.core.window import Window
class PongGame(Widget):
def __init__(self, **kwargs):
super(PongGame, self).__init__(**kwargs)
with self.canvas:
Color(255,255,255, mode='rgba')
window_size = Window.size
size = 50
self.rect = Rectangle(pos=(window_size[0]/2-size/2, window_size[1]/2-size/2), size=(size, size))
print()

Kivy Widget not moving

When the program runs, the ScreenManager shows the Main screen on which I added the white little square(Ball).
The widget Ball should be moving around but I cannot figure out why it is still static.
The update method is working but the position of the widget is not being updated. I tried to move things around and no effect.
If someone could help me to understand where I am wrong, would be great. Thx so much.
import kivy
kivy.require('1.10.1')
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.screenmanager import ScreenManager,Screen
from kivy.graphics import Rectangle
from kivy.uix.widget import Widget
from kivy.properties import ListProperty
from kivy.clock import Clock
# simple Screen on which the ball should move
class Main(Screen):
def __init__(self, **kwargs):
super(Main, self).__init__(**kwargs)
self.add_widget(Ball())
with self.canvas.before:
Rectangle(source = 'BG1.png',
size = Window.size,
pos = self.pos)
# the Ball should be bouncing around
class Ball(Widget):
velocity = ListProperty([10, 15])
def __init__(self, **kwargs):
super(Ball, self).__init__(**kwargs)
Clock.schedule_interval (self.update, 1 / 60)
with self.canvas:
Rectangle (color=[0, 0, 0, 1],
size=(10, 10),
pos = self.pos)
def update(self, *args):
print('wtf')
self.x += self.velocity[0]
self.y += self.velocity[1]
if self.x < 0 or (self.x + self.width) > Window.width:
self.velocity[0] *= -1
if self.y < 0 or (self.y + self.height) > Window.height:
self.velocity[1] *= -1
Window.size = (400, 300)
sm = ScreenManager()
sm.add_widget(Main(name = 'main'))
class Bubble(App):
def build(self):
return sm
if __name__ == '__main__':
Bubble().run()
Thank you Embryo and John, your answers led me to find the right answer. I found this code on this blog. Solved it all:Kivy blog
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle, Color
class CornerRectangleWidget(Widget)
def __init__(self, **kwargs):
super(CornerRectangleWidget, self).__init__(**kwargs)
with self.canvas:
Color(1, 0, 0, 1) # set the colour to red
self.rect = Rectangle(pos=self.center,
size=(self.width/2.,
self.height/2.))
self.bind(pos=self.update_rect,
size=self.update_rect)
def update_rect(self, *args):
self.rect.pos = self.pos
self.rect.size = self.size
The problem is that when you set up Canvas instructions in python (rather than in kv), you don't get the automatic bindings that kv does for you. So the Rectangle that you define in the Ball.__init__() method gets its pos defined as the pos of the Ball at its __init__() moment (which is [0,0]), and it does not change automatically. You can fix that by doing the update yourself. First, in your Ball.__init__() change your with self.canvas block to:
with self.canvas:
Color(0,0,0,1)
self.rect = Rectangle (size=(10, 10),
pos = self.pos)
One change is defining the Color, and the second is creating a reference to the Rectangle. Then in your update method add the line:
self.rect.pos = self.pos
This will move the rectangle with the Ball position.
Also, your update method has some problems because the size of the Ball is the same size as the Window. It is not the size you provide in the Rectangle.
Well, what is happening is this...
You are drawing the rectangle once, when you instantiate the Ball widget.
This is what you see in the bottom left corner.
After that you move the widget, but you don't draw it again.
If you print(self.pos) in the update function, you'll see it moving...

How to convert Kivy touch corordinates to widget space

i have a simple ModalView and its size is (640,426).My window's size is (1366,732).My Screen Resolution is (1366,768).When i click at the top left corner of the ModalView,i get something like 363,690.Which is my touch coordinates taken from the window itself.i would however like to somehow convert this value to local widget space so that touching the top left corner i get the coordinate (0,0) instead of (363,690).Is this possible with kivy or any other way.What Im trying to do,for those interested is to crop an image using a box drawn by the user.Drawing the box isn't the problem,the problem is getting those bounds and transfering them to the image's coords.
NB: I read about the to_local(),to_parent(),to_window() and those functions are simply not working...for some reason,maybe i missed a thing there,would appreciate your help big time
Here is the code similar to my usecase but stripped
from kivy.app import App
from kivy.uix.modalview import ModalView
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.label import Label
class CropBounds(ModalView):
def __init__(self, **kwargs):
super(CropBounds, self).__init__(**kwargs)
self.to_crop = True
self.size = (400,400)
print('Center: ',self.center)
print('Size: ',self.size)
print('Window Center: ',Window.center)
print('Window Size:(',Window.width,',',Window.height,')')
def on_touch_down(self, touch):
self.canvas.clear()
if self.collide_point(*touch.pos) and self.to_crop:
with self.canvas:
self.start_x = touch.x
self.start_y = touch.y
touch.ud['area'] = Line(points=(touch.x, touch.y, touch.x, 400,touch.x, touch.y,touch.x, touch.y, touch.x, touch.y))
print("Pos: ",touch.pos)
print(touch.x,touch.y)
return True
return MainWindow().on_touch_down(touch)
class GalleryWindow(BoxLayout):
def __init__(self, **kwargs):
super(GalleryWindow, self).__init__(**kwargs)
self.add_widget(Button(text='crop',size_hint=(1,None),size=(None,40),on_release=self.crop_img))
def crop_img(self):
bounds = CropBounds()
bounds.open()
class GalleryApp(App):
def build(self):
return GalleryWindow()
if __name__=='__main__':
GalleryApp().run()
Subtracting the ModalView position from the touch coordinates does work. I think your are getting confused about the size and position of your ModalView. The way your code is written, the ModalView is the same size and position as your GalleryWindow (recall that the default size_hint is (1.0, 1.0)). So, for there to be any difference between coordinates in the ModalView and GalleryWindow, you need to change the size_hint for the ModalView.
After correcting many errors in your code (to get it to run). I have made some changes to demonstrate the position of the ModalView and the location of the touch.
Here is the code:
from kivy.app import App
from kivy.core.window import Window
from kivy.graphics.vertex_instructions import Line
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.modalview import ModalView
from kivy.uix.button import Button
class CropBounds(ModalView):
def __init__(self, **kwargs):
super(CropBounds, self).__init__(**kwargs)
self.to_crop = True
self.size_hint = (None, None) # without this, the size below has no effect
self.size = (400,400)
print('Center: ',self.center)
print('Size: ',self.size)
print('Window Center: ',Window.center)
print('Window Size:(',Window.width,',',Window.height,')')
def on_touch_down(self, touch):
if self.collide_point(*touch.pos) and self.to_crop:
self.canvas.clear()
with self.canvas:
# plot the boundary of the ModalView
Line(points=[self.pos[0], self.pos[1],
self.pos[0], self.pos[1] + self.height,
self.pos[0] + self.width, self.pos[1] + self.height,
self.pos[0] + self.width, self.pos[1],
self.pos[0], self.pos[1]])
# plot a line from the touch point to the pos of the ModalView
Line(points=[self.pos[0], self.pos[1], touch.x, touch.y])
# calculate touch minus position of ModalView
touch_in_modal = (touch.x - self.pos[0], touch.y - self.pos[1])
print('touch : ' + str(touch.pos) + ', touch in modal: ' + str(touch_in_modal))
return True
#return MainWindow().on_touch_down(touch)
class GalleryWindow(BoxLayout):
def __init__(self, **kwargs):
super(GalleryWindow, self).__init__(**kwargs)
self.add_widget(Button(text='crop',size_hint=(1,None),size=(40,40),on_release=self.crop_img))
def crop_img(self, *args):
bounds = CropBounds()
bounds.open()
class GalleryApp(App):
def build(self):
return GalleryWindow()
if __name__=='__main__':
GalleryApp().run()
If you want to see what was happening in your code, just comment out the size_hint line.
Also, when we ask you to post a MCV example, please try running what you post. If we have to debug your example before we can see what you problem is, you won't get many responses.

How could I downscale images with kivy/python with good results?

I am trying to display images at a smaller size then their source and while I have achieved this, the quality is horrible on any resolution other then 1080p. Here's my code thus far:
import random
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.uix.image import Image
from kivy.logger import Logger
from kivy.metrics import Metrics
from kivy.graphics import Color, Rectangle
from kivy.uix.scatter import Scatter
Window.fullscreen = True
Window.size = (1920, 1080)
Player1Hand=['Spade', 'Spade', 'Spade',
'Heart', 'Heart',
'Heart', 'Diamond', 'Club',
'Club', 'Club']
random.shuffle(Player1Hand)
global Draw_Card
Draw_Card = 0
class Sprite(Image):
def __init__(self, **kwargs):
super(Sprite, self).__init__(allow_stretch=True, **kwargs)
self.texture.mag_filter = 'linear'
w, h = self.texture_size
self.size = (params.scale * w, params.scale * h)
class CardSprite(Image):
def __init__(self, **kwargs):
super(CardSprite, self).__init__(allow_stretch=True, **kwargs)
self.texture.mag_filter = 'linear'
w = 210.9
h = 240
self.size = (params.scale * w, params.scale * h)
class PlayerHandArea(Widget):
def __init__(self):
super(PlayerHandArea, self).__init__()
with self.canvas:
Color(1,0,0,0.5)
Rectangle(size=((1152 * params.scale), (240 * params.scale)),
pos = ((384 * params.scale), 0))
class DrawCard(Widget):
def __init__(self):
super(DrawCard, self).__init__()
global Draw_Card
while Draw_Card > 0:
scatter = Scatter(do_rotation=False, do_scale=False,
size_hint=(None,None),
size=(210.9 * params.scale, 240 * params.scale))
self.NextCard = (CardSprite
(source='Images/'+(Player1Hand.pop(0))+'.png'))
self.add_widget(scatter)
scatter.add_widget(self.NextCard)
Draw_Card += -1
class Blank(Widget):
def __init__(self, pos, size):
super(Blank, self).__init__()
with self.canvas:
Color(0, 0, 0)
Rectangle(pos=pos, size=size)
Color(1, 1, 1)
class Game(Widget):
def __init__(self):
super(Game, self).__init__()
self.background = (Sprite
(source='Images/Background.jpg'))
self.size = self.background.size
self.add_widget(self.background)
self.add_widget(PlayerHandArea())
global Draw_Card
Draw_Card += 5
self.add_widget(DrawCard())
self.add_widget(Blank(*params.blank_rect))
class GameApp(App):
def build(self):
params.init()
top = Widget()
top.add_widget(Game())
return top
class params(object):
def init(self):
self.bg_width, self.bg_height = 1920, 1080
self.width, self.height = Window.size
self.center = Window.center
ws = float(self.width) / self.bg_width
hs = float(self.height) / self.bg_height
self.scale = min(ws, hs)
Logger.info('size=%r; dpi=%r; density=%r; SCALE=%r',
Window.size, Metrics.dpi, Metrics.density, self.scale)
if ws > hs:
gap = self.width - (self.bg_width * hs)
self.blank_rect = ((self.width - gap, 0), (gap, self.height))
else:
gap = self.height - (self.bg_height * ws)
self.blank_rect = ((0, self.height - gap), (self.width, gap))
params = params()
if __name__ == '__main__':
GameApp().run()
So I'm using Richard Jones example on setting the size of your images based on the screen size of whos using it. This works perfect when run at 1920 by 1080 but when I switch the Window.size to say 720p the downscaled images have so much noise in them it looks awful. Ivied tried using linear and nearest for the mag_filter and also setting the card's size to dp(210.9) and dp(240) but they come out the same. Does anyone know a better way to scale down with kivy using different resolutions or a document that explains how to get this to work? The original image sizes are 746 by 1037.

Trouble with Kivy Python? Function error and image error

I am trying to use Kivy to build an app.
Here is the code:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Color, Ellipse, Line
from kivy.core.window import Window
class Background_Animation(Widget):
def __init__(self, imageStr, **kwargs):
super(Background_Animation, self).__init__(**kwargs)
self.__init__= __init__(self, imageStr, **kwargs)
with self.canvas :
self.size= (Window.width * 0.002 * 25, Window.height *0.002 * 25 )
# bind the fbo to the current opengl context
self.bind(pos == self.update_graphics_pos)
# x center position
self.x = self.center_x
# declare the image
imageStr= Image("~/Downloads/psychTREE.jpg", **kwargs)
# y center position
self.y = self.center_y
self.pos= (self.x, self.y)
self.image= self.pos
return image
def build(self):
self.build = build
run = __init__(self, imageStr, **kwargs)
return run
if __name__ == "__main__" :
build(self, imageStr, **kwargs).run()
When I try to run the code, it says name "build" is not defined . Any ideas on how to solve this problem? Also, how would I make the image appear in the gui?

Categories