Daniel
Daniel

Reputation: 777

Getting a PhotoImage object from a tkinter label

Is there any way to get a tkinter.PhotoImage object from a tkinter.Label instance? I know there's this question, which has a partially satisfying answer, but I really need to get a PhotoImage object:

>>> import tkinter as tk
>>>
>>> root = tk.Tk()
>>>
>>> image1 = tk.PhotoImage(file="img.gif")
>>> image2 = tk.PhotoImage(file="img.gif")
>>>
>>> label = tk.Label(root, image=image1)
>>> label._image_ref = image1
>>> label.cget("image") == image2
False

Is there perhaps a function which allows me to get an image object from a pyimage string? I.e. one obtained from label.cget("image")?


The answer is, apparantly, you can't. The closest you can get to doing this is getting the image source (file or data) and checking (probably by hashing) whether or not the two images are the same. tkinter.PhotoImage does not implement an __eq__, so you can't just compare two images for equal data. Here's a final example which solves the problem (mostly):

import hashlib
import os
import tkinter as tk

_BUFFER_SIZE = 65536


def _read_buffered(filename):
    """Read bytes from a file, in chunks.
    Arguments:
    - filename: str: The name of the file to read from.
    Returns:
    - bytes: The file's contents.
    """
    contents = []
    with open(filename, "rb") as fd:
        while True:
            chunk = fd.read(_BUFFER_SIZE)
            if not chunk:
                break
            contents.append(chunk)
        return bytes().join(contents)


def displays_image(image_file, widget):
    """Check whether or not 'widget' displays 'image_file'.
    Reading an entire image from a file is computationally expensive!
    Note that this function will always return False if 'widget' is not mapped.
    This doesn't work for images that were initialized from bytes.
    Arguments:
    - image_file: str: The path to an image file.
    - widget: tk.Widget: A tkinter widget capable of displaying images.
    Returns:
    - bool: True if the image is being displayed, else False.
    """
    expected_hash = hashlib.sha256(_read_buffered(image_file)).hexdigest()
    if widget.winfo_ismapped():
        widget_file = widget.winfo_toplevel().call(
            widget.cget("image"), "cget", "-file"
        )
        if os.path.getsize(widget_file) != os.path.getsize(image_file):
            # Size differs, the contents can never be the same.
            return False
        image_hash = hashlib.sha256(
            _read_buffered(widget_file)
        ).hexdigest()
        return image_hash == expected_hash

Upvotes: 4

Views: 5067

Answers (1)

CommonSense
CommonSense

Reputation: 4482

As a tkinter is a wrapper over tk , so does PhotoImage in same fashion is a wrapper over image. And it's clear that you can't just move backwards and create a PhotoImage, from that image.

But still, because you can execute tk commands and both PhotoImage and image have similar structure, your best bet would be something like this:

import tkinter as tk

root = tk.Tk()

image1 = tk.PhotoImage(file="img.gif")
image2 = tk.PhotoImage(file="img.gif")

label = tk.Label(root, image=image1)
label._image_ref = image1

foo = root.call(label.cget('image'), 'cget', '-file')
bar = image2['file']
print('1st image:\t%s\n2nd image:\t%s\nEqual:\t%r' % (foo, bar, foo == bar))

Upvotes: 1

Related Questions