Is there a way to get a fade-in/-out animation on a canvas rectangle background in kivy? I tried it by using the Clock.schedule_interval() function. But several problems occured concerning concurrency and data raise.
One of my tries looks as follows:
def calendarClicked(self, *args):
self.alpha = 1.0
self.alphaDelta = 0.01
self.root.canvas.add(Color(1,0,0,self.alpha))
self.root.canvas.add(Rectangle(size=self.root.size))
def fadeIn(self, dt):
self.alpha -= self.alphaDelta
self.root.canvas.add(Color(1,0,0,self.alpha))
self.root.canvas.add(Rectangle(size=self.root.size))
return self.alpha >= 0.5
Clock.schedule_interval(partial(fadeIn, self), 0.1)
Another idea was to use the kivy.animation. But I coudn't find a way to edit the color instead of the position of an object/widget.
Thanks in advance!
You can use an Animation on canvas instructions just like you would a widget. The problem is that you're not modifying the canvas instructions at all, you just keep adding more and more canvas instructions. You need to add the instructions one time only, then update the values via Animation.
def calendarClicked(self, *args):
if not hasattr(self, 'color'):
with self.root.canvas:
self.color = Color(1, 0, 0, 0)
self.rect = Rectangle(size=self.root.size)
self.color.a = 0
anim = Animation(a=1.0)
anim.start(self.color)
Related
I am using Tkinter and the grid() layout manager to create a GUI. I am showing the image in my GUI using a label, on a tabbed window:
label2 = ttk.Label(tab2)
image2 = PhotoImage(file="lizard.gif")
label2['image'] = image2
label2.grid(column=0, row=0, columnspan=3)
For illustration, let's say the image is 300 x 900. If I know a set of coordinates within the image, how can I overlay a shaded box on the image, defined by the known (A,B,C,D which are shown just for the illustration purpose) coordinates?
Let me give you a step by step solution.
You can use a tkinter.Label() to display your image as you did, you can also choose other widgets. But for situation, let's choose tkinter.Canvas() widget instead (but same reasoning is valid if you choose to use tkinter.Label())
Technical issues:
Your problem contains 2 main sub-problems to resolve:
How to overlay 2 images the way you want.
How to display an image using tkinter.Canvas()
To be able to read an image of jpg format , you need to use a specific PIL (or its Pillow fork) method and a class:
PIL.Image.open()
PIL.ImageTk.PhotoImage()
This is done by 3 lines in the below program:
self.im = Image.open(self.saved_image)
self.photo = ImageTk.PhotoImage(self.im)
And then display self.photo in the self.canvas widget we opted for:
self.canvas.create_image(0,0, anchor=tk.N+tk.W, image = self.photo)
Second, to reproduce the effect you desire, use cv2.addWeighted() OpenCV method. But I feel you have already done that. So I just show you the portion of code of the program that does it:
self.img = cv2.imread(self.image_to_read)
self.overlay = self.img.copy()
cv2.rectangle(self.overlay, (500,50), (400,100), (0, 255, 0), -1)
self.opacity = 0.4
cv2.addWeighted(self.overlay, self.opacity, self.img, 1 - self.opacity, 0, self.img)
cv2.imwrite( self.saved_image, self.img)
Program design:
I use 2 methods:
- __init__(): Prepare the frame and call the GUI initialization method.
- initialize_user_interface(): Draw the GUI and perform the previous operations.
But for scalability reasons, it is better to create a separate method to handle the different operations of the image.
Full program (OpenCV + tkinter)
Here is the source code (I used Python 3.4):
'''
Created on Apr 05, 2016
#author: Bill Begueradj
'''
import tkinter as tk
from PIL import Image, ImageTk
import cv2
import numpy as np
import PIL
class Begueradj(tk.Frame):
'''
classdocs
'''
def __init__(self, parent):
'''
Prepare the frame and call the GUI initialization method.
'''
tk.Frame.__init__(self, parent)
self.parent=parent
self.initialize_user_interface()
def initialize_user_interface(self):
"""Draw a user interface allowing the user to type
"""
self.parent.title("Bill BEGUERADJ: Image overlay with OpenCV + Tkinter")
self.parent.grid_rowconfigure(0,weight=1)
self.parent.grid_columnconfigure(0,weight=1)
self.image_to_read = 'begueradj.jpg'
self.saved_image = 'bill_begueradj.jpg'
self.img = cv2.imread(self.image_to_read)
self.overlay = self.img.copy()
cv2.rectangle(self.overlay, (500,50), (400,100), (0, 255, 0), -1)
self.opacity = 0.4
cv2.addWeighted(self.overlay, self.opacity, self.img, 1 - self.opacity, 0, self.img)
cv2.imwrite( self.saved_image, self.img)
self.im = Image.open(self.saved_image)
self.photo = ImageTk.PhotoImage(self.im)
self.canvas = tk.Canvas(self.parent, width = 580, height = 360)
self.canvas.grid(row = 0, column = 0)
self.canvas.create_image(0,0, anchor=tk.N+tk.W, image = self.photo)
def main():
root=tk.Tk()
d=Begueradj(root)
root.mainloop()
if __name__=="__main__":
main()
Demo:
This is a screenshot of the running program:
You will need to use a canvas widget. That will allow you to draw an image, and then overlay a rectangle on it.
Although the above answers were wonderfully in depth, they did not fit my exact situation (Specifically use of Python 2.7, etc.). However, this solution gave me exactly what I was looking for:
canvas = Canvas(tab2, width=875, height=400)
image2=PhotoImage(file='lizard.gif')
canvas.create_image(440,180,image=image2)
canvas.grid(column=0, row=0, columnspan=3)
The rectangle is added over the canvas using:
x1 = 3, y1 = 10, x2 = 30, y2 = 20
canvas.create_rectangle(x1, y1, x2, y2, fill="blue", stipple="gray12")
stipple comes from this example, to help add transparency to the rectangle.
Colleagues,
I am designing a GUI with two buttons and one is to display a graph, hourly temperature.
The issue that I am facing is that I can not make a function(update_graph) that updates the value with self.after.
This part creates page 1 and i working fine, until I call update_graph
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Page One!!!", font=LARGE_FONT)
label.pack(pady=10,padx=10)
button1 = tk.Button(self, text="Back to Home",
command=lambda: controller.show_frame(StartPage))
button1.pack()
button2 = tk.Button(self, text="Page Two",
command=lambda: controller.show_frame(PageTwo))
button2.pack()
canvas = Canvas(self, width=400, height=400, bg = 'white')
canvas.pack()
# create x and y axes
canvas.create_line(100,250,400,250, width=2)
canvas.create_line(100,250,100,50, width=2)
# creates divisions for each axle
for i in range(11):
x = 100 + (i * 30)
canvas.create_line(x,250,x,245, width=2)
canvas.create_text(x,254, text='%d'% (10*i), anchor=N)
for i in range(6):
y = 250 - (i * 40)
canvas.create_line(100,y,105,y, width=2)
canvas.create_text(96,y, text='%5.1f'% (50.*i), anchor=E)
self.update_graph()
def update_graph(self):
# here is canvas create line that causes a trouble
canvas.create_line(100,250,140,200, width=2)
self.after(100,self.update_graph)
Whith this code I get an error "canvas is not defined".
If I add self to canvas in update_graph, I get
self.canvas.create_line(100,250,140,200, width=2)
AttributeError: 'PageOne' object has no attribute 'canvas'
What am I missing here?
canvas is only defined in the scope of the constructor (__init__) method. If you want to be able to access it elsewhere in the class, you need to make it an instance variable. Instead of,
canvas = Canvas(self, width=400, height=400, bg = 'white')
make it,
self.canvas = Canvas(self, width=400, height=400, bg = 'white')
now, everywhere else in the code where you reference canvas, change it to self.canvas. This should fix the problem.
On an unrelated note, a problem that I'm seeing in update_graph is that it calls itself recursively, or over and over. Perhaps you could change it to something like this:
def update_graph(self):
# This line is quite long. Maybe you could shorten it?
self.after(100, lambda: canvas.create_line(100,250,
140,200, width=2))
Hope this helps!
EDIT: My redefinition of update_graph only makes sense if you want one fixed line drawn. If you intend to add other functionality, such as periodic updates, the original code is correct, as Bryan pointed out.
It might sound unproper 'images in layers' like in Photoshop but in fact that is what I have and would like to make working.
I use a set of wx.boxsizer's to have a nice&organized screen after launching my program.
In one row of Horizontal wx.BoxSizer's I have 3 columns done with different wx.Panels and each of that is containing moving wx.StaticBitmaps done by a Timer function.
The second wx.Panel which is in the middle with 0 proportion to mantain original panel size contains 2 images actually, one PNG with transparency and the moving wx.StaticBitmap which should be the background for this PNG.
This is not working out for me. I simply want that the PNG to be over the other image which is moved by the timer. 2 layers if you like.
It is creating a nice very simple and basic action effect (like the object in the PNG is running)
Now I thought of a few ways to go about this but none of them worked:
Figure out how python decides which image to bring in front and which would stay behind. Manipulate that
Keep the moving image in the sizer and take out the PNG and place it over the moving image (than I need to dynamically determine where is that moving image)
Make the moving image the wx.panel background and then probably I can simply call the transparent PNG over it.
I will paste here a part of my script for the brave eyes:
class AnimationPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.loc = wx.Image("intro/runner.png",wx.BITMAP_TYPE_PNG).ConvertToBitmap()
self.locpic = wx.StaticBitmap(self, -1, self.loc, (0, 0), (self.loc.GetWidth(), self.loc.GetHeight()))
self.xer = 3080
self.xer2 = 2310
self.xer3 = 1540
self.env = wx.Image("intro/environ1.png",wx.BITMAP_TYPE_PNG).ConvertToBitmap()
self.env2 = wx.Image("intro/environ2.png",wx.BITMAP_TYPE_PNG).ConvertToBitmap()
self.env3 = wx.Image("intro/environ3.png",wx.BITMAP_TYPE_PNG).ConvertToBitmap()
self.picture = wx.StaticBitmap(self, -1, self.env, (0, 0), (self.env.GetWidth(), self.env.GetHeight()))
self.picture2 = wx.StaticBitmap(self, -1, self.env2, (770, 0), (self.env2.GetWidth(), self.env2.GetHeight()))
self.picture3 = wx.StaticBitmap(self, -1, self.env3, (1540, 0), (self.env3.GetWidth(), self.env3.GetHeight()))
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
self.timer.Start(5)
def OnTimer(self, event):
if self.xer <= 3080:
self.xer += 1
self.picture.Move((self.xer,0))
else:
self.xer = -770
if self.xer2 <= 3080:
self.xer2 += 1
self.picture2.Move((self.xer2,0))
else:
self.xer2 = -770
if self.xer3 <= 3080:
self.xer3 += 1
self.picture3.Move((self.xer3,0))
else:
self.xer3 = -770
This is in the main frame:
ap = AnimationPanel(self)
v2box = wx.BoxSizer(wx.HORIZONTAL)
v2box.Add(someother, 1, wx.EXPAND)
v2box.Add(ap, 0, wx.EXPAND)
v2box.Add(someother, 1, wx.EXPAND)
I did put research in this but I'm quite of a beginner so please help me out with some simple tips or suggestions if it's possible.
Thanks.
In the wxpython demo install there is a samples folder that has a pySketch demo.
In this code it creates dc drawn objects that can be moved in front and behind other objects.
A quick look over this code it looks like the drawn items are stored in a list and then drawn in the list order.
I guess that's how you would implement your layering, you would have a list of layers and in each layer you would store your items for that layer.
I am having problems with the following piece of code:
def handle_image_flip(self, old_rect):
new_rect = self.last_frame.get_rect()
self.owner.rect = new_rect
self.owner.rect.y = old_rect.y
if self.owner.facing == -1:
self.owner.rect.right = old_rect.right
else:
self.owner.rect.x = old_rect.x
def animate(self, tick):
if tick - self.last_update > self.image_info[self.action]["frame_rate"]:
self.last_frame = self.get_next_frame(tick)
old_rect = self.owner.rect.copy()
self.owner.image = self.last_frame
self.handle_image_flip(old_rect)
self.last_update = tick
Where:
self.owner is the sprite this piece of code handles
self.owner.facing is the direction the sprite is facing
self.last_frame is the new image I want to display
Since the sprites have different widths, I am getting glitchy animations when facing
LEFT (-1).
There are no problems when moving RIGHT whatsoever.
Any ideas?
If you track player's location using Rect.center or Rect.centerx vs default of topleft then different widths can work.
Depending on your game, using sprite sheets may be beneficial.
in your if statement, in one case you change self.owner.rect.right, in the other case you change self.owner.rect.x.
This could be part of the problem since when you are moving left, you are changing .right rather than .x
I run an animation on my buttons where I change i.e. Opacity. When the animation is complete all the button's Opacity goes back to initial values. The code:
CABasicAnimation animation = CABasicAnimation.FromKeyPath("opacity");
animation.To = NSNumber.FromFloat( 0.1f );
animation.Duration = animationDuration;
animation.TimingFunction = CAMediaTimingFunction.FromName (CAMediaTimingFunction.EaseOut);
How can I set the animation to stay in the To value?
I don't see the error in the code, but there is another way to do that instead of CoreAnimation, which is a bit verbose.
For UIKit elements it is usually easier to use the UIView.Animate method. So , your sample would be:
UIView.Animate (
animationDuration,
0,
UIViewAnimationOptions.CurveEaseOut,
delegate {yourButton.Alpha = 0.1f; },
null);