Pysimplegui: Chess on macOS: _tkinter.TclError: couldn't recognize data in image file "./nrookb.png"

Created on 4 May 2019  ·  22Comments  ·  Source: PySimpleGUI/PySimpleGUI

Type of Issues (Enhancement, Error, Bug, Question)

Python ___tkinter.TclError__ raised:

  • ___tkinter.TclError: couldn't recognize data in image file "./nrookb.png"__

I can $ __open ./nrookb.png__ and the image looks perfect in the macOS __Preview.app__.

Operating System

$ __sw_vers__
ProductName: Mac OS X
ProductVersion: 10.14.4
BuildVersion: 18E226

Python version

$ __python --version__
Python 3.7.3

PySimpleGUI Port and Version

3.29.0 freshly installed via $ __git clone https://github.com/PySimpleGUI/PySimpleGUI__

Code or partial code causing the problem

$ __python ./Demo_Chess_Board.py__

Traceback (most recent call last):
  File "Demo_Chess_Board.py", line 160, in <module>
    PlayGame()
  File "Demo_Chess_Board.py", line 130, in PlayGame
    button, value = window.Read()
  File "/usr/local/lib/python3.7/site-packages/PySimpleGUI/PySimpleGUI.py", line 3760, in Read
    self.Show()
  File "/usr/local/lib/python3.7/site-packages/PySimpleGUI/PySimpleGUI.py", line 3685, in Show
    StartupTK(self)
  File "/usr/local/lib/python3.7/site-packages/PySimpleGUI/PySimpleGUI.py", line 6105, in StartupTK
    ConvertFlexToTK(my_flex_form)
  File "/usr/local/lib/python3.7/site-packages/PySimpleGUI/PySimpleGUI.py", line 6023, in ConvertFlexToTK
    PackFormIntoFrame(MyFlexForm, master, MyFlexForm)
  File "/usr/local/lib/python3.7/site-packages/PySimpleGUI/PySimpleGUI.py", line 5706, in PackFormIntoFrame
    PackFormIntoFrame(element, toplevel_form.TKroot, toplevel_form)
  File "/usr/local/lib/python3.7/site-packages/PySimpleGUI/PySimpleGUI.py", line 5638, in PackFormIntoFrame
    PackFormIntoFrame(element, element.TKFrame, toplevel_form)
  File "/usr/local/lib/python3.7/site-packages/PySimpleGUI/PySimpleGUI.py", line 4903, in PackFormIntoFrame
    PackFormIntoFrame(element, element.TKColFrame, toplevel_form)
  File "/usr/local/lib/python3.7/site-packages/PySimpleGUI/PySimpleGUI.py", line 5051, in PackFormIntoFrame
    photo = tk.PhotoImage(file=element.ImageFilename)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/tkinter/__init__.py", line 3545, in __init__
    Image.__init__(self, 'photo', name, cnf, master, **kw)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/tkinter/__init__.py", line 3501, in __init__
    self.tk.call(('image', 'create', imgtype, name,) + options)
_tkinter.TclError: couldn't recognize data in image file "./nrookb.png"
duplicate

Most helpful comment

This is a dupe of #153 .

You can try running this to figure out what your tkinter version is:
python -c "import tkinter; print(tkinter.TkVersion);"

I'm running 8.6. There is a note in the previous issue that 8.5 has trouble with these other file formats like PNG.

I'm going to close this one as a dupe. I know it's not the outcome you had hoped for, but I'm simply not going to pull PIL into PySimpleGUI nor am I going to modify the demo programs that work on all other platforms other than the Mac. I try to write these demo problems to be portable across all of the ports of PySimpleGUI. That's the goal. The only GUI Framework and OS environment not capable of displaying PNG files is the Mac that's running tk 8.5.

All 22 comments

Minimal code to exhibit the same behavior is:

$ __python3 -c "import tkinter as tk ; root = tk.Tk() ; tk.PhotoImage(file='./nrookb.png')"__

Using __Pillow__'s __Image.open()__ seems to be the solution:
https://stackoverflow.com/questions/47357090/tkinter-error-couldnt-recognize-data-in-image-file

This looks like another Mac specific problem.

I suppose the pieces could all be converted to GIF.

I thought this problem only existed with the 2.7 version of tkinter.

I could could submit to a __try / except__ solution if you would be interested.

Not going to be interested in bringing PIL into PySimpleGUI. If it's in the application then there's no issue, but can't be in PySimpleGUI.py file.

Have you looked for a fix for this problem that tkinter / Mac has on the net? Seems like that's where the real fix awaits.

Have you looked for a fix for this problem that tkinter / Mac has on the net?
Yes. https://github.com/PySimpleGUI/PySimpleGUI/issues/1369#issuecomment-489316700

I meant using tkinter calls, not PIL.

And also getting verification that this is a Mac problem. You may be able to update your tkinter code and get around it.

I don't think there's much more that I can do on this one.

Demo programs, like the Chess program, are just that.... Demonstrations on how you can use the package. If there's a bug / missing feature on the Mac, which seems to happen quite a bit, then there's really not much I can do. As you presented in the minimal example, this problem has nothing to do with PySimpleGUI and is all tkinter problems.

Also, the Chess program has been greatly expanded and improved upon. I'm on the verge of taking mine down given how much further the new version takes things. For example, with my version, castling isn't supported. It's was meant more of a demonstration of how a game can be programmed by using buttons in clever ways.

It seems very odd to me that all Macs are incapable of displaying PNG images using tkinter. I don't believe that is the case across the board. Otherwise much if not all of the image based demos and examples would fail.

Would be great to hear from other Mac users to see if they have encountered this problem. Again, I suspect that the version of tkinter you're running is out of date. It's behaving more like the Python 2.7 version of tkinter than the Python 3 version.

This is a dupe of #153 .

You can try running this to figure out what your tkinter version is:
python -c "import tkinter; print(tkinter.TkVersion);"

I'm running 8.6. There is a note in the previous issue that 8.5 has trouble with these other file formats like PNG.

I'm going to close this one as a dupe. I know it's not the outcome you had hoped for, but I'm simply not going to pull PIL into PySimpleGUI nor am I going to modify the demo programs that work on all other platforms other than the Mac. I try to write these demo problems to be portable across all of the ports of PySimpleGUI. That's the goal. The only GUI Framework and OS environment not capable of displaying PNG files is the Mac that's running tk 8.5.

I have same problem (while trying to present image loaded by PIL and my to version is 8.6):

❯ python -c "import PIL; print(PIL.__version__);"
7.2.0

❯ python -c "import tkinter; print(tkinter.TkVersion);"
8.6

It's ultimately a tkinter problem if it's complaining about the format.

There is a demo recently released with code that I use to "convert" images using PIL. You'll find a function called convert_to_bytes in the demo program Demo_Image_Element_Image_Viewer_PIL_Based.

This function is becoming core to the image based demos now. I used it in the Matplotlib demo too as the function resizes as well as converts from any format into the "data" format used by the PySimpleGUI elements.

This is the function I'm talking about.

So far it's not let me down. I've resized resized resized files using it. You can directly use it in a layout when you're creating the elements. Assign it to your data parameter for Images and Buttons and you'll be able to use JPGs, etc, in PySimpleGUI and easily control resizing them.

I hope this function works out as well as it's looking like it is.

import PIL.Image
import io
import base64

def convert_to_bytes(file_or_bytes, resize=None):
    '''
    Will convert into bytes and optionally resize an image that is a file or a base64 bytes object.
    Turns into  PNG format in the process so that can be displayed by tkinter
    :param file_or_bytes: either a string filename or a bytes base64 image object
    :type file_or_bytes:  (Union[str, bytes])
    :param resize:  optional new size
    :type resize: (Tuple[int, int] or None)
    :return: (bytes) a byte-string object
    :rtype: (bytes)
    '''
    if isinstance(file_or_bytes, str):
        img = PIL.Image.open(file_or_bytes)
    else:
        try:
            img = PIL.Image.open(io.BytesIO(base64.b64decode(file_or_bytes)))
        except Exception as e:
            dataBytesIO = io.BytesIO(file_or_bytes)
            img = PIL.Image.open(dataBytesIO)

    cur_width, cur_height = img.size
    if resize:
        new_width, new_height = resize
        scale = min(new_height/cur_height, new_width/cur_width)
        img = img.resize((int(cur_width*scale), int(cur_height*scale)), PIL.Image.ANTIALIAS)
    bio = io.BytesIO()
    img.save(bio, format="PNG")
    del img
    return bio.getvalue()

A suggestion to explicitly close the memory handle if processing tons of large images:

bio = io.BytesIO()
img.save(bio, format="PNG")
del img
return bio.getvalue()

—>

    with io.BytesIO() as bio:
        img.save(bio, format="PNG")
        del img
        return bio.getvalue()

I'll change it... thank you!

Exactly the kind of help I need with these PIL integrations. I kinda lucked into this function having experimented with bits and pieces of it.

Updated the 2 Demos that use it - PIL image viewer and Matplotlib Grid of Graphs.

If there's a Cookbook Recipe I'll be sure and fix it there too. I'll make sure it's propagated as I see this as a really core kinda capability and bit of source code to have.

I'm not sure how well it'll function with something like OpenCV... yet.... hoping it'll be fast enough to use with opencv to resize the frames before displaying.

import base64
import io
import PIL.Image


def convert_to_bytes(file_or_bytes, resize=None):
    '''
    Will convert into bytes and optionally resize an image that is a file or a base64 bytes object.
    Turns into  PNG format in the process so that can be displayed by tkinter
    :param file_or_bytes: either a string filename or a bytes base64 image object
    :type file_or_bytes:  (Union[str, bytes])
    :param resize:  optional new size
    :type resize: (Tuple[int, int] or None)
    :return: (bytes) a byte-string object
    :rtype: (bytes)
    '''
    if isinstance(file_or_bytes, str):
        img = PIL.Image.open(file_or_bytes)
    else:
        try:
            with io.BytesIO(base64.b64decode(file_or_bytes)) as bio:
                img = PIL.Image.open(bio)
        except Exception:
            with io.BytesIO(file_or_bytes) as bio:
                img = PIL.Image.open(bio)

    cur_width, cur_height = img.size
    if resize:
        new_width, new_height = resize
        scale = min(new_height/cur_height, new_width/cur_width)
        img = img.resize((int(cur_width*scale), int(cur_height*scale)), PIL.Image.ANTIALIAS)
    with io.BytesIO() as bio:
        img.save(bio, format="PNG")
        del img
        return bio.getvalue()

I hope this helps. I tend to avoid broad exceptions:
https://realpython.com/the-most-diabolical-python-antipattern

Hmm... it's having trouble the resizing as it's crashing complaining that the file has been closed (it has since it's the img variable, right?)

C:\Python\Anaconda3\python.exe C:/Python/PycharmProjects/GooeyGUI/DemoPrograms/Demo_Matplotlib_Grid_of_Graphs_Using_PIL.py
Traceback (most recent call last):
  File "C:/Python/PycharmProjects/GooeyGUI/DemoPrograms/Demo_Matplotlib_Grid_of_Graphs_Using_PIL.py", line 941, in <module>
    image = convert_to_bytes(image, (figure_w, figure_h))
  File "C:/Python/PycharmProjects/GooeyGUI/DemoPrograms/Demo_Matplotlib_Grid_of_Graphs_Using_PIL.py", line 894, in convert_to_bytes
    img = img.resize((int(cur_width*scale), int(cur_height*scale)), PIL.Image.ANTIALIAS)
  File "C:\Python\Anaconda3\lib\site-packages\PIL\Image.py", line 1882, in resize
    im = self.convert(self.mode[:-1] + "a")
  File "C:\Python\Anaconda3\lib\site-packages\PIL\Image.py", line 901, in convert
    self.load()
  File "C:\Python\Anaconda3\lib\site-packages\PIL\ImageFile.py", line 224, in load
    seek(offset)
ValueError: I/O operation on closed file.

I get the error when I try to run it in the Demo program:

Demo_Matplotlib_Grid_of_Graphs_Using_PIL.txt

I don't think I can use the context managers ultimately. One of the properties of the function is that it can be called multiple times. It can convert a file, then later you can resize the results. The stream is used later when you call the function.

My sense is that context managers can only be used in conjunction w/ io.BytesIO.getvalue() which explains why https://github.com/PySimpleGUI/PySimpleGUI/issues/1369#issuecomment-663908594 and is a good idea and why https://github.com/PySimpleGUI/PySimpleGUI/issues/1369#issuecomment-663910503 did not.

        try:
            with io.BytesIO(base64.b64decode(file_or_bytes)) as bio:
                img = PIL.Image.open(bio.getvalue())
        except Exception:
            with io.BytesIO(file_or_bytes) as bio:
                img = PIL.Image.open(bio.getvalue())

I'm sorry to be so thick on this one, but I assume the code you've supplied is a patch to the code you provided earlier so that the currently proposed new solution is:

def convert_to_bytes(file_or_bytes, resize=None):
    '''
    Will convert into bytes and optionally resize an image that is a file or a base64 bytes object.
    Turns into  PNG format in the process so that can be displayed by tkinter
    :param file_or_bytes: either a string filename or a bytes base64 image object
    :type file_or_bytes:  (Union[str, bytes])
    :param resize:  optional new size
    :type resize: (Tuple[int, int] or None)
    :return: (bytes) a byte-string object
    :rtype: (bytes)
    '''
    if isinstance(file_or_bytes, str):
        img = PIL.Image.open(file_or_bytes)
    else:
        try:
            with io.BytesIO(base64.b64decode(file_or_bytes)) as bio:
                img = PIL.Image.open(bio.getvalue())
        except Exception:
            with io.BytesIO(file_or_bytes) as bio:
                img = PIL.Image.open(bio.getvalue())

    cur_width, cur_height = img.size
    if resize:
        new_width, new_height = resize
        scale = min(new_height/cur_height, new_width/cur_width)
        img = img.resize((int(cur_width*scale), int(cur_height*scale)), PIL.Image.ANTIALIAS)
    with io.BytesIO() as bio:
        img.save(bio, format="PNG")
        del img
        return bio.getvalue()

If so, then it immediately crashes with the program I supplied as a test a couple of posts back. This time with a slightly different crash.

I appreciate the help, but I'm starting to feel like a junior engineer acting like a QA tester that's trying these different functions and reporting back how well or not they function in existing (regression testable) programs.

I highly respect what you've done in Python and the help you're offering, it's just not working out very well and I'm going in a frustrated circle of having code that has been performing great to spending a chunk of time with these backwards results improvements.

I'm trying hard here to balance out everything, but struggling to do so in a way that's not disrespectful but also doesn't keep me occupied doing testing and debugging.

Was this page helpful?
0 / 5 - 0 ratings