Issue using images in Python Tkinter - python

I am creating a Blackjack GUI game with Tkinter and I'm running into an issue where the deal button clears the image of the old card from the screen when a new one is added. My educated guess is the card_image inside the deal() function is overwriting itself when I use the function again. If this is the case why is this and what's the best fix? Thanks.
import random
from tkinter import *
from PIL import Image, ImageTk
root =Tk()
root.title('21 Blackjack')
root.iconbitmap('images/21_cards.ico')
root.geometry('1280x750')
root.configure(bg='green')
cards = []
suits = ['hearts', 'clubs', 'diamonds', 'spades']
face_cards = ['ace', 'jack', 'queen', 'king']
extension = 'png'
for y in suits:
for x in range(2, 11):
name = 'images/{}-{}.{}'.format(str(x), y, extension)
cards.append(name)
for x in face_cards:
name = 'images/{}-{}.{}'.format(str(x), y, extension)
cards.append(name)
print(cards)
print(len(cards))
random.shuffle(cards)
print(cards[0])
hand = []
def deal():
global card_image, card_label, hand
card_image = ImageTk.PhotoImage(Image.open(cards[0]).resize((180, 245), Image.ANTIALIAS))
card_label = Label(root, image=card_image, relief="raised").pack(side="left")
hand += cards[:1]
cards.pop(0)
print(hand)
deal_button = Button(root, text="deal", command=deal).pack()
root.mainloop()

Instead of
card_image = ImageTk.PhotoImage(Image.open(cards[0]).resize((180, 245), Image.ANTIALIAS))
card_label = Label(root, image=card_image, relief="raised").pack(side="left")
Do:
card_label = Label(root, image=card_image, relief="raised").pack(side="left")
card_image = ImageTk.PhotoImage(Image.open(cards[0]).resize((180, 245), Image.ANTIALIAS))
card_label.image = card_image #Keeps reference to the image so it's not garbage collected
Do note when you want use the photo use the variable card_image

As people have pointed out I needed to add card_label.image = card_image to the function and remove card_image & card_label global to stop the image being removed. But for some reason Python didn't like having the image being packed before I did this.
The function now looks like this.
global hand
card_image = ImageTk.PhotoImage(Image.open(cards[0]).resize((180, 245), Image.ANTIALIAS))
card_label = Label(root, image=card_image, relief="raised")
card_label.image = card_image
card_label.pack(side="left")
hand += cards[:1]
cards.pop(0)
print(hand)

You should use an image pool. Lucky for you, I have one right here
import os
from glob import glob
from PIL import Image, ImageTk
from typing import List, Tuple, Union, Dict, Type
from dataclasses import dataclass
#dataclass
class Image_dc:
image :Image.Image
rotate :int = 0
photo :ImageTk.PhotoImage = None
def size(self) -> Tuple[int]:
if not self.photo is None:
return self.photo.width(), self.photo.height()
return self.image.width, self.image.height
class ImagePool(object):
##__> PRIVATE INTERFACE <__##
__PATHS = dict()
__IMAGES = dict()
#staticmethod
def __name(path) -> str:
return os.path.basename(os.path.splitext(path)[0])
#staticmethod
def __unique(path:str, prefix:str='') -> str:
name = f'{prefix}{ImagePool.__name(path)}'
if name in ImagePool.names():
sol = 'using a prefix' if not prefix else f'changing your prefix ({prefix})'
msg = ("WARNING:\n"
f"{name} was not loaded due to a same-name conflict.\n"
f"You may want to consider {sol}.\n\n")
print(msg)
return None
return name
#staticmethod
def __request(name:str) -> Image_dc:
if name in ImagePool.__PATHS:
path = ImagePool.paths(name)
if os.path.isfile(path):
if name not in ImagePool.__IMAGES:
ImagePool.__IMAGES[name] = Image_dc(Image.open(path))
return ImagePool.__IMAGES[name]
else:
raise ValueError(f'ImagePool::__request - Path Error:\n\tpath is not a valid file\n\t{path}')
raise NameError(f'ImagePool::__request - Name Error:\n\t"{name}" does not exist')
return None
#staticmethod
def __size(iw:int, ih:int, w:int=None, h:int=None, scale:float=1.0) -> Tuple[int]:
if not w is None and not h is None:
if iw>ih:
ih = ih*(w/iw)
r = h/ih if (ih/h) > 1 else 1
iw, ih = w*r, ih*r
else:
iw = iw*(h/ih)
r = w/iw if (iw/w) > 1 else 1
iw, ih = iw*r, h*r
return int(iw*scale), int(ih*scale)
##__> PUBLIC INTERFACE <__##
#staticmethod
def names(prefix:str='') -> List[str]:
names = [*ImagePool.__PATHS]
return names if not prefix else list(filter(lambda name, pre=prefix: name.startswith(pre), names))
#staticmethod
def paths(name:str=None) -> Union[Dict, str]:
if name is None:
return ImagePool.__PATHS
if name in ImagePool.__PATHS:
return ImagePool.__PATHS[name]
raise NameError(f'ImagePool::paths - Name Error:\n\tname "{name}" does not exist')
#staticmethod
def images(name:str=None, prefix:str='') -> Union[Dict, Image.Image]:
if name is None:
return {name:ImagePool.__request(name).image for name in self.names(prefix)}
return ImagePool.__request(name).image
#staticmethod
def photos(name:str=None, prefix:str='') -> Union[Dict, ImageTk.PhotoImage]:
if name is None:
return {name:ImagePool.__request(name).photo for name in self.names(prefix)}
return ImagePool.__request(name).photo
#staticmethod
def append_file(path:str, prefix:str='') -> Type:
if not os.path.isfile(path):
raise ValueError(f'ImagePool::append_file - Value Error:\n\tpath is not valid\n\t{path}')
name = ImagePool.__unique(path, prefix)
if name:
ImagePool.__PATHS[name] = path
return ImagePool
#staticmethod
def append_directory(directory:str, filters=['*.png', '*.jpg'], prefix:str='') -> Type:
if not os.path.isdir(directory):
raise ValueError(f'ImagePool::append_directory - Value Error:\n\tdirectory is not valid\n\t{directory}')
filters = filters if isinstance(filters, (List, Tuple)) else [filters]
for filter in filters:
for path in glob(f'{directory}/{filter}'):
ImagePool.append_file(path, prefix)
return ImagePool
#staticmethod
def photo(name:str, width:int=None, height:int=None, scale:float=1.00, rotate:int=None) -> ImageTk.PhotoImage:
image_t = ImagePool.__request(name)
size = ImagePool.__size(*image_t.size(), width, height, scale)
rotate = image_t.rotate if rotate is None else rotate
#only resize if the new size or rotation is different than the current photo size or rotation
#however, a small margin for error must be considered for the size
diff = tuple(map(lambda i, j: i-j, image_t.size(), size))
if (diff > (1, 1)) or (diff < (-1, -1)) or (image_t.rotate != rotate):
image_t.rotate = rotate
image_t.photo = ImageTk.PhotoImage(image_t.image.resize(size, Image.LANCZOS).rotate(rotate))
return image_t.photo
Using that file you can do a lot of things very easily. You can put all your card images in a folder and get them all with:
ImagePool.append_directory(path_to_folder)
You can then get any card and force it to fit in it's parent (no-matter-what) with:
somelabel['image'] = ImagePool.photo(image_name, allotted_width, allotted_height)
or just:
somelabel['image'] = ImagePool.photo(image_name)
you can get a list of every name in the pool with
deck = ImagePool.names()
or grab it while you also append a directory
deck = ImagePool.append_directory(path_to_folder).names()
That is very helpful to you, because you can simply shuffle and pop that list as the official "deck" in your game. Like this:
somecard['image'] = ImagePool.photo(deck.pop(0))
Assuming you will have more than just card graphics, but do not want to get all the images when creating a deck, there is a solution for that, as well.
first supply a prefix when appending the cards image directory, and then use that prefix when calling names(). As long as only card images were in the cards image directory, only card names will be returned.
deck = ImagePool.append_directory(path_to_folder, prefix='cards_').names('cards_')
notes:
allotted area is only allotted and in no way should be assumed to represent the final width and/or height of the actual image. The image will do whatever it has to to fit in the allotted space without losing it's original height and width ratio. If you don't supply allotted width and height the image will be it's full size.
All image and photo references are automatically maintained in ImagePool. There is no reason to store your own references
The entire class is static so references to all the images and photos can be accessed anywhere without an ImagePool instance. In other words, you never need to do this: images = ImagePool() and therefore never need to figure out how to get images into some class or other document.
No images actually load until you start requesting them, and then it happens automatically. This is a good thing. You have 52 cards, but if you only use ex: 6 in the first game ~ only 6 will fully load. Eventually all the cards will get played and be fully loaded, and you didn't have some huge burp in your game where you tried to fully create 52 cards all at once.
The ImagePool class has more features, but the ones explained here are all you should need for your game.

Related

How can I return the closest left and right child of a single node in simple binary tree?

I got a problem with my code, here it is : I want to return the closest left and right child of a node, but whenever I try to, it returns me ALL the children of this node.
I separated the code in two files, one contains the binary tree class (not BST, just a simple BT), and the other contains tkinter things, that suppose to show the node and his children. See below for the code, and sorry for some of the parts in french, if u need words to be translated i'm up.
binary tree file :
class ArbreBinaire:
def __init__(self, valeur):
self.valeur = valeur
self.enfant_gauche = None
self.enfant_droit = None
def __str__(self):
return f"{self.valeur}, ({self.enfant_gauche}, {self.enfant_droit})"
[enter image description here](https://i.stack.imgur.com/KJ3Gr.png)
def insert_gauche(self, valeur):
if self.enfant_gauche == None:
self.enfant_gauche = ArbreBinaire(valeur)
else:
new_node = ArbreBinaire(valeur)
new_node.enfant_gauche = self.enfant_gauche
self.enfant_gauche = new_node
def insert_droit(self, valeur):
if self.enfant_droit == None:
self.enfant_droit = ArbreBinaire(valeur)
else:
new_node = ArbreBinaire(valeur)
new_node.enfant_droit = self.enfant_droit
self.enfant_droit = new_node
def get_valeur(self):
return self.valeur
def get_gauche(self):
return self.enfant_gauche
def get_droit(self):
return self.enfant_droit
tkinter and main parts file :
from arb import ArbreBinaire
from tkinter import *
def classe_arb():
global racine, b_node, f_node, c_node, g_node, h_node
arb = ArbreBinaire
racine = ArbreBinaire('A')
racine.insert_gauche('B')
racine.insert_droit('F')
b_node = racine.get_gauche()
b_node.insert_gauche('C')
b_node.insert_droit('D')
f_node = racine.get_droit()
f_node.insert_gauche('G')
f_node.insert_droit('H')
c_node = b_node.get_gauche()
c_node.insert_droit('E')
g_node = f_node.get_gauche()
g_node.insert_gauche('I')
h_node = f_node.get_droit()
h_node.insert_droit('J')
return arb
def accueil():
global fenetre
fenetre = Tk()
fenetre.title("Bienvenue dans l'énigme du manoir")
fenetre.geometry("1000x500")
bt_jouer= Button(fenetre, text="Jouer", fg="green", command=lambda: valeur_bouton(1))
bt_jouer.pack()
bt_quitter = Button(fenetre, text="Quitter", fg="red", command=quit)
bt_quitter.pack()
fenetre.mainloop()
def jeu(phrase, rep1, rep2):
global run
run = True
while run == True:
global wd_jeu
wd_jeu = Tk()
wd_jeu.title("L'énigme du manoir")
wd_jeu.geometry("1000x500")
label = Label(wd_jeu, text=phrase, bg = "blue", fg = "white", font = "Castellar")
label.pack()
option1 = Button(wd_jeu, text=rep1, fg="black")
option1.pack()
option2 = Button(wd_jeu, text=rep2, fg="black")
option2.pack()
wd_jeu.mainloop()
def valeur_bouton(nb):
if nb == 0:
None
if nb == 1:
fenetre.destroy()
jeu('Hello', racine.valeur, racine.enfant_gauche)
classe_arb()
accueil()
Okay, so basically the problem is in the last function : "racine.valeur" and "racine.enfant_gauche". The first one works very well, it returns me the node with no problem. The second one "racine.enfant_gauche" is supposed to return the closest left child of the A node (B in this case), but it prints me all the children : "B, (C, (None, E, (None, None)), D, (None, None))".
I tried many things like this, like getting the value with the method "get_gauche" but it doesnt work as well. Thanks in advance for your help.
The __str__ method is called recursively because the format string f"{self.valeur}, ({self.enfant_gauche}, {self.enfant_droit})" will use the __str__ method of each child to resolve the embedded text.
To avoid the recursion, you could define method to get values of the left and right (e.g. valeur_gauche(), valeur_droite()) and use those instead in the format string. You could also get the values within your __str__ function and assemble the resulting string accordingly.
The expression racine.enfant_gauche evaluates to an instance of ArbreBinaire, and so when you pass it as argument to jeu and pass it to the Button constructor, the node's __str__ method will be called to retrieve the string that must be printed. This __str__ method will create a string representation that includes all descendants.
The expression racine.valeur on the other hand is a string (a single letter in your example), and so when you pass that to jeu, which passes it to the Button constructor, it will render as that string (as expected).
If you want the same behaviour for the left child, you should also take the .valeur attribute, like so:
jeu('Hello', racine.valeur, racine.enfant_gauche.valeur)

Property dependency and update in python

I have a class which should transform the image retrieved based on a path received(url or os image).
Not sure how is the right way to implement this to follow best principles, like SOLID, or should i even care about it?! in my case.
In particular, whenever i update the 'path' attribute the code should automatically update also the
'img' attribute based on the given path.
class TransfImage(path):
def __init__(self,path):
self._path = path
#self._img = img_from_url() ?! would be better to use here the method
#property
def path(self):
return self._path
#path.setter
def path(self,my_path):
if valid_os_file_image(my_path):
self._path = my_path
self.img = img_from_os_file(my_path) #not sure if is the proper way to set the image here
elif valid_url_image(my_path):
self._path = my_path
self.img = img_from_url(my_path) #not sure if is the proper way to set the image here
#property
def img(self):
return self._img
#img.setter
def img(self,my_img):
self._img = my_img
def img_from_os_file(self,my_os_file):
return cv.imread(my_os_file)
def img_from_url(self, url_path):
try:
req = urllib.request.urlopen(url_path)
except ValueError:
raise ValueError('not a valid url')
try:
arr = np.asarray(bytearray(req.read()), dtype=np.uint8)
img = cv.imdecode(arr, -1)
except ValueError as e:
print('could not read img from url', e)
else:
return img
def im_show(self):
cv.imshow(self.img)
t = TransfImage('lena.jpg')
t.im_show() #should show lena.jpg
t.path = 'cards.jpg'
t.im_show() #should show 'cards.jpg
There is no visible reason in your code to make img a property, Apart from this I see no problem (except for a few calls to methods missing a self prefix).

Python CodeSkulptor Pause Drawing from Inside For Loop

I want to build some visualizations for searching algorithms (BFS, A* etc.) within a grid.
My solution should show each step of the algorithm using CodeSkulptor simplegui (or the offline version using SimpleGUICS2Pygame.)
I've made a version which highlights all the cells visited by changing their color, but I've run into trouble trying to make the path display step-by-step with a time delay between each step.
I've extracted the essence of the problem and created a minimal example representing it in the code below, also run-able online here: http://www.codeskulptor.org/#user47_jB2CYfNrH2_2.py
What I want is during the change_colors() function, for there to be a delay between each iteration.
CodeSkulptor doesn't have time.sleep() available, and I don't think it would help anyway.
CodeSkulptor does have timers available, which might be one solution, although I can't see how to use one in this instance.
Code below:
import time
try:
import simplegui
except ImportError:
import SimpleGUICS2Pygame.simpleguics2pygame as simplegui
simplegui.Frame._hide_status = True
TITLE = "TEST"
FRAME_WIDTH = 400
FRAME_HEIGHT = 400
DELAY = 10
class Square:
"""This class represents a simple Square object."""
def __init__(self, size, pos, pen_size=2, pen_color="red", fill_color="blue"):
"""Constructor - create an instance of Square."""
self._size = size
self._pos = pos
self._pen_size = pen_size
self._pen_color = pen_color
self._fill_color = fill_color
def set_color(self, color):
self._fill_color = color
def get_color(self):
return self._fill_color
def is_in(self, pos):
"""
Determine whether coordinates are within the area of this Square.
"""
return self._pos[0] < pos[0] < self._pos[0] + self._size and self._pos[1] < pos[1] < self._pos[1] + self._size
def draw(self, canvas):
"""
calls canvas.draw_image() to display self on canvas.
"""
points = [(self._pos[0], self._pos[1]), (self._pos[0] + self._size, self._pos[1]),
(self._pos[0] + self._size, self._pos[1] + self._size), (self._pos[0], self._pos[1] + self._size)]
canvas.draw_polygon(points, self._pen_size, self._pen_color, self._fill_color)
def __str__(self):
return "Square: {}".format(self._pos)
def draw(canvas):
for square in squares:
square.draw(canvas)
def change_colors():
for square in squares:
# time.sleep(1) # Not implemented in CodeSkulptor and would'nt work anyway
square.set_color("green")
frame = simplegui.create_frame(TITLE, FRAME_WIDTH, FRAME_HEIGHT)
frame.set_draw_handler(draw)
width = 20
squares = []
for i in range(10):
squares.append(Square(width, (i * width, 0)))
change_colors()
frame.start()
Any help appreciated.
Yes, you need to use a timer. Something like this:
I = 0
def change_next_color():
if I < len(squares):
squares[I].set_color("green")
global I
I += 1
timer = simplegui.create_timer(1000, change_next_color)
timer.start()
http://www.codeskulptor.org/#user47_udyXzppCdw2OqdI.py
I also replaced
simplegui.Frame._hide_status = True
by simplegui.Frame._hide_controlpanel = True
https://simpleguics2pygame.readthedocs.io/en/latest/simpleguics2pygame/frame.html#SimpleGUICS2Pygame.simpleguics2pygame.frame.Frame._hide_controlpanel
See also _keep_timers option of SimpleGUICS2Pygame to help you:
https://simpleguics2pygame.readthedocs.io/en/latest/simpleguics2pygame/frame.html#SimpleGUICS2Pygame.simpleguics2pygame.frame.Frame._keep_timers
Possible improvements:
Find a better solution that don't use a global counter.
Stop timer when all work is finished.

Difference between print(*) and *.print()

I was staring at a very very long code. I am just trying the GUI part. There were these lines :
sh = packing_options[best_index].sheets[idx]
sh.print()
for rect in sh.rect_list:
rect.print()
I wanted to show the value stored in sh and rect for the GUI window.
When I use
b3 = tk.Label(top, text=sh)
b3.grid(row=0, column=2)
b4 = tk.Label(top, text=rect)
b4.grid(row=0, column=2)
It gives this as the result : <main.Sheet object at 0x0000024F5CF4A320>
The code for classes are given below:
class Rect:
def __init__(self, w, l):
self.l = l
self.w = w
def print(self):
print('Rect w:' + str(self.w) + 'l:' + str(self.l))
def area(self):
return self.l * self.w
## class for sheet to save all rectangles' positions
class Sheet:
# initialization
def __init__(self, W, L):
self.W = W # width (=horizontal length)
self.L = L # length (=height, vertical length)
self.rect_list = [] # rectangle list
self.rect_pos = [] # rectangle starting position from left bottom
self.rect_rotate = [] # rectangle rotation indicator list, 0: not rotated, 1: rotated
self.lhl_x = [0,W] # lowest horizontal line boundary point list
self.lhl_y = [0] # lowest horizontal line height list: each element is height
# area of sheet
def area(self):
return self.L * self.W
def print(self):
print('sheet W:' + str(self.W) + ' L:' + str(self.L))
How do I get the real value and store it in a variable to use print(*) (To use to show in GUI ultimately)
from itertools import enumerate
from tkinter import messagebox
def sheet_info(sheet):
str = "Sheet W:{} L:{} Contains {} rectangles:".format(sheet.W, sheet.L, len(sheet.rect_list))
for idx, rect in enumerate(sheet.rect_list):
str += "\n Rectangle W:{} L:{} at position {}".format(rect.w, rect.l, sheet.rect_pos[idx])
return str
messagebox.showinfo("sheet info", sheet_info(sh))
This should do it. May contain mistakes as i can't verify it atm.
Think of it as the behaviour of Python to print ing the object of a regular class , I.e if you provide the __str__ method it will print the object in stringified form with detail you are looking for (ofcourse you decide what is going to be printed) but if there is no __str__ method in the class, then printing an object will give you a queer output including address location of the object (similar to your example ) as standard print method does not know printing an object.
<__main__.Foobar instance at 0x7cf2a22e>
print(sh) is doing the same thing (nothing unexpected).
For this particular question both the print s are different .print is tkinter method defined on sh and print is python's method.
If you want to use the sh in python's built-in print function then you need to have a __str__ (or __repr__) method in the class Sheet , which will be the string representation of your object, because print(sh) finally invokes Sheet.__str__():
And in that __str__ method you can simply put all the details you wish to see on screen using the self keyword.
Very simple example in this direction will be
def __str__(self):
print('sheet W:' + str(self.W) + ' L:' + str(self.L))
__str__ is the string representation of an object and better think of it as toString function in Java (if you are familiar with it)
from tkinter import messagebox
messagebox.showinfo("Title", "your text here")

Pass element to multiple function in Python 3

So I'm working on a program that shuffles cards in a deck and returns a picture of the card when the user hits the "deal" button. But I am trying to pass fileName from the deal function to the refreshImages function and I can't seem to get it to work. Wondering if someone can show me how and explain why. Thanks.
def deal(self):
card = self.deck.deal()
self.stateLabel["text"] = str(card)
fileName = "DECK/" + str(card.rank) + card.suit[0] + ".gif"
self.refreshImages(fileName)
if len(self.deck) == 0:
self.dealBtn["state"] = "disabled"
def shuffle(self):
card = self.deck.shuffle()
fileName = "DECK/" + str(card.rank) + card.suit[0] + "gif"
self.refreshImages(fileName)
if len(seld.deck) == 0:
self.dealBtn["state"] = "disabled"
def refreshImages(self):
"""Updates the images in the window."""
self.image = PhotoImage(file = fileName)
self.cardLabel1["image"] = self.image
Since your deal method calls the refreshImages method with a file name as an argument, the refreshImages method should be declared with such a parameter; otherwise the caller's local variable fileName won't get magically passed to the method being called:
def refreshImages(self, fileName):
"""Updates the images in the window."""
self.image = PhotoImage(file = fileName)
self.cardLabel1["image"] = self.image

Categories

Resources