在Python Tkinter中使用图像的问题

在Python Tkinter中使用图像的问题,python,image,tkinter,Python,Image,Tkinter,我正在用Tkinter创建一个21点GUI游戏,我遇到了一个问题,当添加一张新卡时,deal按钮会清除屏幕上旧卡的图像。我的猜测是,deal()函数中的card\u图像在我再次使用该函数时被覆盖。如果是这种情况,为什么会这样?最好的解决方法是什么?谢谢 import random from tkinter import * from PIL import Image, ImageTk root =Tk() root.title('21 Blackjack') root.iconbitmap('

我正在用Tkinter创建一个21点GUI游戏,我遇到了一个问题,当添加一张新卡时,deal按钮会清除屏幕上旧卡的图像。我的猜测是,
deal()
函数中的
card\u图像在我再次使用该函数时被覆盖。如果是这种情况,为什么会这样?最好的解决方法是什么?谢谢

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()

您应该使用映像池。你真幸运,我这里有一个

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
    
然后,您可以获得任何卡,并强制其与其父卡相匹配(无论是什么):

或者只是:

somelabel['image'] = ImagePool.photo(image_name)
您可以使用获取池中每个名称的列表

deck = ImagePool.names()
或者在附加目录时获取它

deck = ImagePool.append_directory(path_to_folder).names()
这对您非常有帮助,因为您可以简单地洗牌并将该列表作为游戏中的官方“牌组”弹出。像这样:

somecard['image'] = ImagePool.photo(deck.pop(0))
假设您不仅仅拥有卡片图形,但不希望在创建卡片组时获得所有图像,那么也有一个解决方案

首先在附加卡片图像目录时提供前缀,然后在调用
names()
时使用该前缀。只要卡片图像目录中只有卡片图像,就只返回卡片名称

deck = ImagePool.append_directory(path_to_folder, prefix='cards_').names('cards_')
注:

allocated
区域仅为已分配区域,不应假设其代表实际图像的最终宽度和/或高度。图像将尽其所能适应分配的空间,而不会丢失其原始高度和宽度比。如果您不提供分配的宽度和高度图像将为其全尺寸

所有图像和照片引用都自动保存在
ImagePool
中。没有理由存储您自己的引用

整个类是静态的,因此对所有图像和照片的引用都可以在任何地方访问,而无需
ImagePool
实例。换句话说,您永远不需要这样做:
images=ImagePool()
,因此永远不需要弄清楚如何将
images
放入某个
类或其他文档中

在您开始请求图像之前,不会加载图像,然后图像会自动加载。这是一件好事。你有52张牌,但如果你在第一场比赛中只使用ex:6,那么只有6张牌将满负荷。最终,所有的牌都会被打满,并且你在游戏中没有大的打嗝,你试图一次完全创建52张牌

ImagePool
类有更多的功能,但这里介绍的功能是游戏所需的全部功能。

而不是

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")
做:


请注意,当您想要使用照片时,请使用变量
card\u image

,正如人们指出的那样,我需要将
card\u label.image=card\u image
添加到函数中,并移除
card\u image
card\u label
全局以停止移除图像。但出于某种原因,Python不喜欢在我这么做之前打包图像

函数现在看起来像这样

    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)

尝试
cards.pop()
而不是
cards.pop(0)
?不好。相同的问题只是现在它添加了相同的卡而不是新卡。请尝试使用
card\u image
card\u label
作为局部变量,而不是全局使用ithem。这样一来,每次都会创建一张新卡,这很有意义,但是不会使它们成为全局的,根本不会显示任何图像。移除全局卡并说
card\u label.image=card\u image
,这样就保留了对图像的引用。您可以使用
hand.append(cards.pop(0))
替换
hand+=cards[:1]
卡片.pop(0)
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")
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
    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)