Repository: python-pillow/Pillow

Issue:
PyQt6 Support
Hi everyone!. I'm updating my app to PyQt6 and found a problem.

ImageQt.ImageQt class raises an error when try to use with QPainter:

```pycon
>>> page_image = ImageQt.ImageQt(page)
>>> painter = QtGui.QPainter()
>>> painter.begin(page_image)
TypeError: begin(self, QPaintDevice): argument 1 has unexpected type 'ImageQt'
```

Regards

Python 3.8.6
Ubuntu 20.04
PyQt6 6.0.1
Pillow 8.1.0


Discussion:
Hi. ImageQt subclasses QImage, and QImage subclasses QPaintDevice, so I don't see an obvious reason why this shouldn't work.

Are you saying that this code worked with PyQt5?
Hello, Yes, works fine with PyQt5. the problem is that inside ImageQt.py there is a list to know which package contains the class to search for.
```python
qt_versions = [
    ["side6", "PySide6"],
    ["5", "PyQt5"],
    ["side2", "PySide2"],
].
```
As PyQt6 is not defined, it cannot know which package provides each Class a little lower.

```python
# If a version has already been imported, attempt it first
qt_versions.sort (key = lambda qt_version: qt_version [1] in sys.modules, reverse = True)
for qt_version, qt_module in qt_versions:
    try:
        if qt_module == "PySide6":
            from PySide6.QtCore import QBuffer, QIODevice
            from PySide6.QtGui import QImage, QPixmap, qRgba
        elif qt_module == "PyQt5":
            from PyQt5.QtCore import QBuffer, QIODevice
            from PyQt5.QtGui import QImage, QPixmap, qRgba
        elif qt_module == "PySide2":
            from PySide2.QtCore import QBuffer, QIODevice
            from PySide2.QtGui import QImage, QPixmap, qRgba
    except (ImportError, RuntimeError):
        continue
    qt_is_installed = True
    break
else:
    qt_is_installed = False
    qt_version = None
```

Another problem that exists is the regrouping of the Enums, for example:
QImage.Format_RGB32
change to :
QImage.Format.Format_RGB32

Regards.
Ah, I misunderstood. I thought you meant something specific to the code you mentioned.

I've created #5258 to add support. If you could test it and let us know if it works for you, that could be helpful.
with the changes ... begin(self, QPaintDevice): argument 1 has unexpected type 'ImageQt'.

Original QImage is need for class ImageQt parent, not OpenMode. import OpenMode as QIODevice also gives problems.

Resolving patch:
diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py
--- a/src/PIL/ImageQt.py
+++ b/src/PIL/ImageQt.py
@@ -23,6 +23,7 @@
 from ._util import isPath
 
 qt_versions = [
+    ["6", "PyQt6"],
     ["side6", "PySide6"],
     ["5", "PyQt5"],
     ["side2", "PySide2"],
@@ -32,7 +33,10 @@
 qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, reverse=True)
 for qt_version, qt_module in qt_versions:
     try:
-        if qt_module == "PySide6":
+        if qt_module == "PyQt6":
+            from PyQt6.QtCore import QBuffer, QIODevice
+            from PyQt6.QtGui import QImage, QPixmap, qRgba
+        elif qt_module == "PySide6":
             from PySide6.QtCore import QBuffer, QIODevice
             from PySide6.QtGui import QImage, QPixmap, qRgba
         elif qt_module == "PyQt5":
@@ -63,7 +67,8 @@ def fromqimage(im):
     (given either as Python string or a PyQt string object)
     """
     buffer = QBuffer()
-    buffer.open(QIODevice.ReadWrite)
+    qt_openmode = QIODevice.OpenMode if qt_version == "6" else QIODevice
+    buffer.open(qt_openmode.ReadWrite)
     # preserve alpha channel with png
     # otherwise ppm is more friendly with Image.open
     if im.hasAlphaChannel():
@@ -132,25 +137,26 @@ def _toqclass_helper(im):
     if isPath(im):
         im = Image.open(im)
 
+    qt_format = QImage.Format if qt_version == "6" else QImage
     if im.mode == "1":
-        format = QImage.Format_Mono
+        format = qt_format.Format_Mono
     elif im.mode == "L":
-        format = QImage.Format_Indexed8
+        format = qt_format.Format_Indexed8
         colortable = []
         for i in range(256):
             colortable.append(rgb(i, i, i))
     elif im.mode == "P":
-        format = QImage.Format_Indexed8
+        format = qt_format.Format_Indexed8
         colortable = []
         palette = im.getpalette()
         for i in range(0, len(palette), 3):
             colortable.append(rgb(*palette[i : i + 3]))
     elif im.mode == "RGB":
         data = im.tobytes("raw", "BGRX")
-        format = QImage.Format_RGB32
+        format = qt_format.Format_RGB32
     elif im.mode == "RGBA":
         data = im.tobytes("raw", "BGRA")
-        format = QImage.Format_ARGB32
+        format = qt_format.Format_ARGB32
     else:
         raise ValueError(f"unsupported image mode {repr(im.mode)}")
 


Test patch:
diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py
--- a/Tests/test_imageqt.py
+++ b/Tests/test_imageqt.py
@@ -14,7 +14,9 @@ def test_rgb():
     # typedef QRgb
     # An ARGB quadruplet on the format #AARRGGBB,
     # equivalent to an unsigned int.
-    if ImageQt.qt_version == "side6":
+    if ImageQt.qt_version == "6":
+        from PyQt6.QtGui import qRgb
+    elif ImageQt.qt_version == "side6":
         from PySide6.QtGui import qRgb
     elif ImageQt.qt_version == "5":
         from PyQt5.QtGui import qRgb
diff --git a/Tests/test_qt_image_qapplication.py b/Tests/test_qt_image_qapplication.py
--- a/Tests/test_qt_image_qapplication.py
+++ b/Tests/test_qt_image_qapplication.py
@@ -7,7 +7,9 @@
 if ImageQt.qt_is_installed:
     from PIL.ImageQt import QPixmap
 
-    if ImageQt.qt_version == "side6":
+    if ImageQt.qt_version == "6":
+        from PyQt6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
+    elif ImageQt.qt_version == "side6":
         from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
     elif ImageQt.qt_version == "5":
         from PyQt5.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget