Python Kivy - Save canvas to a file - python

Here I have a simple kivy paint app taken from their docs https://kivy.org/doc/stable/tutorials/firstwidget.html and modified by me, attempting to add a save button which would save the canvas to the file image.jpg
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.graphics import Color, Ellipse, Line
class MyPaintWidget(Widget):
def on_touch_down(self, touch):
with self.canvas:
Color(255, 255, 255)
Ellipse(pos=(touch.x/ 2, touch.y/ 2), size=(1, 1))
touch.ud['line'] = Line(points=(touch.x, touch.y))
def on_touch_move(self, touch):
touch.ud['line'].points += [touch.x, touch.y]
class MyPaintApp(App):
def build(self):
parent = Widget()
self.painter = MyPaintWidget()
clearbtn = Button(text='Clear',font_size=30,pos=(100,0))
clearbtn.bind(on_release=self.clear)
savebtn = Button(text='Save',font_size=30)
savebtn.bind(on_release=self.save)
parent.add_widget(self.painter)
parent.add_widget(savebtn)
parent.add_widget(clearbtn)
return parent
def save(self, obj):
self.painter.export_as_image().save('image.jpg')
def clear(self, obj):
self.painter.canvas.clear()
MyPaintApp().run()
the save button does appear next to the clear button, and it does create the file image.jpg when I click it. But for some reason the file is always a blank black image with nothing on it, instead of the canvas with whatever I have drawn.

The problem is that your MyPainter widget has its default size of (100,100), and its default position of (0,0), so it is actually behind your Save Button. Any drawing that you do in the middle of the App display is not in the MyPainter widget, so saving to an image will be blank.
The fix is to set pos and size of the MyPainter widget so that it can be drawn on. You probably also want to use collide_point() in your on_touch_down() and on_touch_move() methods to ensure that you can only draw in the MyPainter widget.

Related

Kivy Image size changed automatically

I am new to the Kivy framework and have an issue with the size of the loaded Image on my canvas. I have a png image of size 647x1778, and load it into a custom widget as follows:
class CharWidget(Widget):
def __init__(self, **kwargs):
self.glyph = kwargs.pop("glyph")
Widget.__init__(self, **kwargs)
self.path = "/tmp/satie.png"
self.png = Image(source=self.path)
self.add_widget(self.png)
def on_touch_down(self, touch):
self.png.pos = (touch.x, touch.y)
with self.canvas:
Color(1, 0, 0, .5)
Rectangle(pos=self.png.pos, size=(self.glyph["width"], self.glyph["height"]))
class SatieApp(App):
def build(self):
parent = Widget()
with parent.canvas:
Color(1,0,0)
parent.add_widget(CharWidget(glyph=Char(name="clefs.G", font="haydn").glyph))
return parent
But when I click on the window the image size is smaller that it's original size. I don't understand where this re-scaling happens, how can I prevent it. The original size is for instance in the texture_size but doing self.png.size = self.png.texture_size i.e. resizing the image size has no effect. Can any one help understanding this?

Passing arguments when double tapping on images created from a while loop

The code below generates several "social media posts" with images nested within MDCards using a while loop. When the user double taps on one of the images, I want to be able to somehow know which of the images they double-tapped so that that image can call a function.
My intention is to create a mini internal blog for my app, when the user double-taps, for example, post #2 in the newsfeed, the "likes" for that post should increase.
Currently, in the code below, all the cards call the same function
from kivy.core.window import Window
from kivy.uix.scrollview import ScrollView
from kivymd.app import MDApp
from kivymd.uix.card import MDCard
from kivymd.uix.gridlayout import MDGridLayout
from kivymd.utils.fitimage import FitImage
Window.size = (440, 760)
class ImageContainer(FitImage):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
if touch.is_double_tap:
print("double tap")
class blog(MDApp):
def build(self):
blog_page = MDGridLayout(cols=1, spacing=40, size_hint_y=None, padding=[20,])
blog_page.bind(minimum_height=blog_page.setter('height'))
count = 1
while count <= 5:
BlogPost = MDCard(elevation=1, radius=8, size_hint=(.9, None), height=300)
BlogPost.add_widget(ImageContainer(source='data/assets/img/placeholder.jpg', radius=[8,]))
blog_page.add_widget(BlogPost)
count += 1
blog = ScrollView(size_hint=(1, None), size=(Window.width, Window.height), pos_hint={'center_x': 0.5, 'top': 1})
blog.add_widget(blog_page)
return blog
if __name__== '__main__':
blog().run()
The self argument of the on_touch_down() method identifies which ImageContainer was double tapped. You can see this if you modify that method:
class ImageContainer(FitImage):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
if touch.is_double_tap:
print("double tap on", self, ", Image source:", self.source)

Dynamic grid in Kivy with each grid element containing multiple widgets

This is my first post here, but I will try to be as detailled as I can.
So my application is in python, and involves a grid in kivy, where each element in the grid is supposed to contain 4 additional widgets and possibility for a fifth. The four additional widgets are supposed to be in a cross shape at the edges and the fifth in the middle.
Problem is, whenever I add a sub widget it lands in the bottom left corner on position 0,0 of the main window
So far so good. Right now I am just trying to get even one widget inside another widget to display correctly.
Heres what I attempted:
<GridCell#Label>
BoxLayout:
orientation:'horizontal'
Button:
text:'One'
size:10,10
size_hint:None,None
Building a .kv file for my app, where I would put a box layout inside of it and then a button.
Also I tried the following class configuration:
class GridCell(Label):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.root = FloatLayout()
self.button = Button(text="test")
self.button.x = self.root.x
self.button.center_y = self.root.center_y
self.root.add_widget(self.button)
self.add_widget(self.root)
Also did not work.
I am adding the grid cells by just calling .add on the grid with a newly created widget for each iteration of a for loop.
All the child widgets are apparently created, but they all land in the bottom left corner!
This is the whole code of the gui right now:
import kivy
import World
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.graphics import Color, Rectangle
kivy.require('1.10.0')
class GridCell(Label):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.root = FloatLayout()
self.button = Button(text="blargh")
self.button.x = self.root.x
self.button.center_y = self.root.center_y
self.root.add_widget(self.button)
self.add_widget(self.root)
def on_size(self, *args):
self.canvas.before.clear()
if self.id is "cliff":
with self.canvas.before:
Color(249 / 255, 6 / 255, 6 / 255, 0.3)
Rectangle(pos=self.pos, size=self.size)
if self.id is "goal":
with self.canvas.before:
Color(6 / 255, 6 / 255, 249 / 255, 0.3)
Rectangle(pos=self.pos, size=self.size)
if self.id is "start":
with self.canvas.before:
Color(11 / 255, 174 / 255, 6 / 255, 0.3)
Rectangle(pos=self.pos, size=self.size)
if self.id is "normal":
with self.canvas.before:
Color(119 / 255, 115 / 255, 115 / 255, 0.3)
Rectangle(pos=self.pos, size=self.size)
class GameGridApp(App):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.grid = GridLayout(cols=8, rows=5)
self.load_into()
def load_into(self):
world = World.World(8, 5)
world.build_gamegrid()
for cell in world.gamegrid:
name = str(cell.name)
grid_cell = GridCell()
grid_cell.text = name
if cell.start:
grid_cell.id = "start"
if cell.goal:
grid_cell.id = "goal"
if cell.cliff:
grid_cell.id = "cliff"
if cell.field:
grid_cell.id = "normal"
self.grid.add_widget(grid_cell)
def build(self):
return self.grid
customLabel = GameGridApp()
customLabel.run()
I may give an idea , that create a 'subgrids' object and a 'main grid' object that contain the 'subgrids'. These two objects would be GridLayout objects.
Here is a simple example in python2.7 :
import kivy
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
class SubGrids(GridLayout):
def __init__(self):
GridLayout.__init__(self, cols=3, rows=3);
self.add_widget(Label(text='1st'));
self.add_widget(Label(text=''));
self.add_widget(Label(text='2nd'));
self.add_widget(Label(text=''));
self.add_widget(Label(text='3rd'));
self.add_widget(Label(text=''));
self.add_widget(Label(text='4th'));
self.add_widget(Label(text=''));
self.add_widget(Label(text='5th'));
class Grids(GridLayout):
def __init__(self):
GridLayout.__init__(self, cols=2, rows = 2);
self.add_widget(SubGrids());
self.add_widget(SubGrids());
self.add_widget(SubGrids());
self.add_widget(SubGrids());
class Example(App):
def build(self):
return Grids()
if __name__ == '__main__':
x = Example();
x.run();
Hope this gives an idea.

Python / Kivy - Changing Screen with phone screen orientation (android)

I haven't found anything on the internet so far, all I want to know is if its possible to change the current screen in kivy depending on the orientation of the phone's screen (Landscape or portrait) and how to do it. or at least a link to a tutorial because I couldn't find one myself.
I haven't found a way of doing this by recognizing the android device's orientation. However, the following program changes it's contents by comparing the window's height and width. The program's Label changes dynamically, so when the width is greater (landscape mode) it's Label has a different text then when the height is greater (portrait mode). Currently it works on Windows, but unfortunately I don't have a Linux system to build an android version. Theoretically it should work, but i'm not 100% sure.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.graphics import Rectangle, Color
class MainScreen(FloatLayout, Label):
"""MAIN WINDOW CLASS"""
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
with self.canvas.before:
Color(0.988, 0.725, 0.074, 1, mode='rgba')
self.rect = Rectangle(pos=self.pos, size=self.size)
self.bind(size=self.update_rect)
self.titlos = Label(text="",
bold=True,
text_size=(None,None),
font_size="20sp",
pos_hint={'center_x': 0.5, 'center_y': .85},
size_hint_y=None,
size = self.size,
height=self.texture_size[1],
halign="center",
valign = "middle",
color=(0.055, 0.235, 0.541, 1))
self.add_widget(self.titlos)
self.bind(size=self.update_orientation)
def update_rect(self, *args):
"""FUNCTION TO UPDATE THE RECATANGLE OF CANVAS TO FIT THE WHOLE SCREEN OF MAINSCREEN ALWAYS"""
self.rect.pos = self.pos
self.rect.size = self.size
def update_orientation(self, *args):
"""FUNCTION TO UPDATE THE SCREEN CONTENTS WHEN THE WINDOW SIZE CHANGES"""
if self.parent.size[1] > self.parent.size[0]:
self.titlos.text = "This is\nPortrait\nOrientation"
else:
self.titlos.text = "This is Landscape Orientation"
# This is just for checking. Not essential to the program.
print("Width:", self.parent.size[0], ", Height:", self.parent.size[1])
class main(App):
"""BUILDING THE APP"""
def build(self):
return MainScreen()
if __name__ == "__main__":
main().run()
You can use the update_orientation function to include whatever you need to change when the orientation changes.

How to allow user to choose file as background image in kivy?

Or, how to do so using python, give it an ID, and set it as background image in kv language?
I would like to be able to draw on top of an image instead of a black screen, which I am doing here:
edited
new problem: upload button does not work, here is new code
from random import random
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.graphics import Color, Line, Rectangle
from kivy.uix.filechooser import FileChooserListView, FileChooserIconView
from kivy.uix.floatlayout import FloatLayout
class MyPaintWidget(Widget):
def on_touch_down(self, touch):
color = (random(), random(), random())
with self.canvas:
Color(*color)
d = 30.
touch.ud['line'] = Line(points=(touch.x, touch.y))
def on_touch_move(self, touch):
touch.ud['line'].points += [touch.x, touch.y]
class MyPaintApp(App):
def build(self):
parent = Widget()
painter = MyPaintWidget()
Choose = Button(text = 'upload image')
parent.add_widget(painter)
parent.add_widget(Choose)
def chooose_file(obj):
fc = FileChooserIconView(title= 'upload image')
image_path = self.fc.selection[0]
image_name = file_path.split('/')[-1]
with self.canvas.before:
Rectangle(
size=self.size,
pos=self.pos,
source=image_name)
Choose.bind(on_release=choose_file)
return parent
if __name__ == '__main__':
MyPaintApp().run()
What about this:
If you used the kivy filechooser to get the user to select an image file,
then you could use the .selection attribute of the filechooser to get the name and/or path of that file. Once you have that, you could use it to set the source of a Rectangle on the canvas of the layout etc. where you want the background image.
For instance, to set a background image on a BoxLayout, inside the class that inherits from the BoxLayout:
fc = FileChooserIconView(title="Choose Image")
image_path = self.fc.selection[0]
image_name = file_path.split('/')[-1]
with self.canvas.before:
Rectangle(
size=self.size,
pos=self.pos,
source=image_name)
This is of course a very simplistic example, and isn't really taking the rest of your code into account, but with the kivy docs on FileChooser you should get it. Worth noting also that you could do this in the kv file, perhaps much more cleanly.

Categories

Resources