Repository: python-pillow/Pillow

Issue:
Problem with using ImageTk on Cygwin
While attempting to use `ImageTk` on Cygwin (64-bit, Windows 10, Python 3.8), I ran into an unhandled Exception. Just attempting to show some black square Pillow Image using a tkinter `Canvas` causes this failure. I've tried several versions of Pillow, from `8.4.0` down to `5.4.1`, all with the same result.

Further description of the dysfunction including a minimal example script can be found at https://github.com/friedrichromstedt/bughunting-02. This repo provides a summary along with detailed information on my work regarding the subject.

I've reported to Cygwin upstream https://sourceware.org/pipermail/cygwin/2021-October/249643.html, but up to now this did not lead to a solution. It might be that the problem is rooted somewhere outside of Pillow, but I would appreciate any pointer.


Discussion:
Hi. Let me ask this question - does tkinter work fine by itself?
```python
import tkinter
root = tkinter.Tk()
root.mainloop()
```
Yes, the following narrowed-down script works well:

```python
import tkinter
from PIL import Image, ImageTk


image = Image.new('RGB', (100, 100))

tk = tkinter.Tk()
canvas = tkinter.Canvas(tk)
canvas.pack()

#photoimage = ImageTk.PhotoImage(image)
#canvas.create_image((0, 0),
#    image=photoimage,
#    anchor='nw',
#)

tk.mainloop()
```

This script is based on the `reproduce.py` file in the repo I mentioned.

It is the line `photoimage = ImageTk.PhotoImage(image)` which triggers the error; commenting out only the call of `canvas.create_image` does not suffice to make the script succeed.
One of the guidelines for [reporting bugs on Cygwin](https://cygwin.com/problems.html) is this:

> - Run `cygcheck -s -v -r > cygcheck.out` and include that file as an attachment in your report. Please do not compress or otherwise encode the output. Just attach it as a straight text file so that it can be easily viewed.
>   Note: It is ok to redact sensitive information like username and to remove site-specific environment variables but please note that fact in your email message so that we'll know that the cygcheck output has been modified. 

Have you done that, and does anything in there look like it could pose a problem?

From [your traceback in the reproducer repository](https://github.com/friedrichromstedt/bughunting-02#description-of-the-bug-and-of-the-expected-behaviour), it looks like the problem is with the line `from . import _imagingtk` inside a module in the PIL top-level package.  What happens if you try to run `import PIL._imagingtk` in python? 

If `python -c 'import PIL._imagingtk'` fails with a similarly unhelpful error message, it may be helpful to increase the verbosity to see what file it's trying to open when it fails: `python -v -c 'import PIL.imagingtk'`, then `python -vv -c 'import PIL._imagingtk'` and so on, until it tells you the actual file causing problems.  Is that file where you expect it to be?
What are the permissions on that file: in particular, do you have execute permissions?  Does `cygcheck /path/to/site-packages/PIL/_imagingtk*` report any errors (`ldd /path/to/site-packages/PIL/_imagingtk*` should provide the same information in a different format)?

OK, the diagnostic information you asked for is provided in https://github.com/friedrichromstedt/bughunting-02/tree/v2021-10-26_1#diagnostic-information-26-october-2021.  Notice that I am referring to the tagged version to ensure permanent links.

The output of `cygcheck -svr` looks okay as far as I can tell, as does the result from `cygcheck`-ing and `ldd`-ing the extension module DLLs.

Importing `PIL._imagingtk` *fails*.  It produces the `RuntimeError: No such process` exception (cf. the traceback in https://github.com/friedrichromstedt/bughunting-02/tree/v2021-10-26_1#description-of-the-bug-and-of-the-expected-behaviour).  The `_tkinter.TclError: invalid command name "PyImagingPhoto"` exception seems to be caused by the `photoimage = ImageTk.PhotoImage(image)` line in the reproduction script.  It is unclear to me how the `from . import _imagingtk` instruction results from handling the initial `TclError`.
Interesting. I'm guessing that `from PIL import _tkinter_finder` also fails.

Does
```python
import tkinter
tkinter.__file__
```
fail?
None of these fails.

See:
```pycon
$ python
Python 3.8.10 (default, May 20 2021, 11:41:59)
[GCC 10.2.0] on cygwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from PIL import _tkinter_finder
>>>
```
and
```pycon
$ python
Python 3.8.10 (default, May 20 2021, 11:41:59)
[GCC 10.2.0] on cygwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import tkinter
>>> tkinter.__file__
'/usr/lib/python3.8/tkinter/__init__.py'
```
The two modules/packages can also be imported in the same `python` session in any order.
Some more observations:

*   The error occurred in very early versions of Pillow as well (I've tried down to v5.4.1, see https://github.com/friedrichromstedt/bughunting-02/tree/v2021-10-26_1#upgradingdowngrading-pillow).
*   I have a (very!) vague memory of having used `PIL.ImageTk` with Cygwin some months ago.

So maybe Pillow is not the actual culprit.  I've also downgraded Tcl/Tk from recent 8.6.11 to 8.6.8 in the abovementioned section, to no avail.  However, since Pillow is the piece of code which *yields* the exception, I have the hope that some helpful conclusion can be drawn here.
Any further ideas how to track down this issue?
https://github.com/python-pillow/Pillow/blob/a215f725ba98efeb48194460b8796be3acd6173f/src/_imagingtk.c#L54-L55
and
https://github.com/python-pillow/Pillow/blob/95cff6e959bb3c37848158ed2145d49d49806a31/src/Tk/tkImaging.c#L380-L397
look to be the only places that could fail on import.

```python
import tkinter
tk = tkinter.Tk()
canvas = tkinter.Canvas(tk)
canvas.pack()
photoimage = tkinter.PhotoImage(file="/path/to/image.png")
canvas.create_image((0, 0), image=photoimage, anchor='nw')
tk.mainloop()
```
works fine.

I'm not sure how to check further what's going on beyond this.

An interesting comment &mdash;
https://github.com/python-pillow/Pillow/blob/95cff6e959bb3c37848158ed2145d49d49806a31/src/Tk/tkImaging.c#L224-L230
&mdash; suggests that this is one of the times that "Cygwin uses Windows linking semantics" is relevant: I suspect Pillow is currently trying to import symbols defined in the Tcl/Tk shared libraries from Python's _tkinter shared library.  It would be interesting to have the Cygwin build of Pillow use the Windows code path and see if that fixes the issue.

Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -887,7 +887,7 @@ def build_extensions(self):
         else:
             self._remove_extension("PIL._webp")
 
-        tk_libs = ["psapi"] if sys.platform == "win32" else []
+        tk_libs = ["psapi"] if sys.platform in ("win32", "cygwin") else []
         self._update_extension("PIL._imagingtk", tk_libs)
 
         build_ext.build_extensions(self)


Test patch:
diff --git a/Tests/test_image.py b/Tests/test_image.py
--- a/Tests/test_image.py
+++ b/Tests/test_image.py
@@ -193,6 +193,10 @@ def test_internals(self):
         assert not im.readonly
 
     @pytest.mark.skipif(is_win32(), reason="Test requires opening tempfile twice")
+    @pytest.mark.skipif(
+        sys.platform == "cygwin",
+        reason="Test requires opening an mmaped file for writing",
+    )
     def test_readonly_save(self, tmp_path):
         temp_file = str(tmp_path / "temp.bmp")
         shutil.copy("Tests/images/rgb32bf-rgba.bmp", temp_file)