Repository: python-pillow/Pillow

Issue:
ImageQt does not work as expected in PyQt5

### What did you do?

Attempted to use ImageQt to load a Pillow image in a PyQt5 application

### What did you expect to happen?

I expected the image to load correctly. There are no errors, but it does not load the image in PyQt5 correctly.

### What actually happened?

The image loaded as a mostly white image with kind of ghost image of the actual photo (see screenshot). I have attached the PyQt5 and PySide6 code, including screenshots from when I ran both files.

**Note: The same code works in PySide6, but not in PyQt5.**

### What are your OS, Python and Pillow versions?

* OS: MacOS Mojave and Windows 10
* Python: Python 3.9
* Pillow: 8.0.0
![pink_flower](https://user-images.githubusercontent.com/2895418/107862335-d27f7580-6e11-11eb-9cf9-d4a19d2d615b.jpg)




<!--
Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.

The best reproductions are self-contained scripts with minimal dependencies. If you are using a framework such as Plone, Django, or Buildout, try to replicate the issue just using Pillow.
-->

```python
import sys

from PIL import Image, ImageQt
from PyQt5.QtGui import QPixmap, QImage
from PyQt5.QtWidgets import QWidget, QLabel
from PyQt5.QtWidgets import QVBoxLayout, QApplication


class ImageViewer(QWidget):

    def __init__(self):
        QWidget.__init__(self)
        self.setWindowTitle("PyQt Image Viewer")

        # Open up image in Pillow
        image = Image.open("pink_flower.jpg")
        qt_image = ImageQt.ImageQt(image)
        pixmap = QPixmap.fromImage(qt_image)

        self.image_label = QLabel('')
        self.image_label.setPixmap(pixmap)

        self.main_layout = QVBoxLayout()
        self.main_layout.addWidget(self.image_label)
        self.setLayout(self.main_layout)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    viewer = ImageViewer()
    viewer.show()
    app.exec_()
```

[pyqt_pillow_issue.zip](https://github.com/python-pillow/Pillow/files/5976581/pyqt_pillow_issue.zip)


Discussion:
Ok, got it. You're hitting this bug

https://github.com/python-pillow/Pillow/blob/fdd8b68b83fd1afdbbe5d0a0460ac1413299dcc5/src/PIL/ImageQt.py#L203-L205

If I change your code to
```python
        # Open up image in Pillow
        image = Image.open("pink_flower.jpg")
        pixmap = ImageQt.toqpixmap(image)
```
then it works.
Huh. That's interesting. I know I tried that, but I didn't remove the `pixmap = QPixmap.fromImage(qt_image)` line, which raises an error if left in there.

This definitely works if you remove that line too.
Thanks for looking into this and finding the error so quickly!
I've created #5268 to change Pillow so that your original code works.
Thanks for looking into this. I appreciate it!

Resolving patch:
diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py
--- a/src/PIL/ImageQt.py
+++ b/src/PIL/ImageQt.py
@@ -153,7 +153,10 @@ def _toqclass_helper(im):
         for i in range(0, len(palette), 3):
             colortable.append(rgb(*palette[i : i + 3]))
     elif im.mode == "RGB":
-        data = im.tobytes("raw", "BGRX")
+        # Populate the 4th channel with 255
+        im = im.convert("RGBA")
+
+        data = im.tobytes("raw", "BGRA")
         format = qt_format.Format_RGB32
     elif im.mode == "RGBA":
         data = im.tobytes("raw", "BGRA")
@@ -206,9 +209,5 @@ def toqpixmap(im):
     # im_data = _toqclass_helper(im)
     # result = QPixmap(im_data["size"][0], im_data["size"][1])
     # result.loadFromData(im_data["data"])
-    # Fix some strange bug that causes
-    if im.mode == "RGB":
-        im = im.convert("RGBA")
-
     qimage = toqimage(im)
     return QPixmap.fromImage(qimage)


Test patch:
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
@@ -2,18 +2,26 @@
 
 from PIL import ImageQt
 
-from .helper import assert_image_equal, hopper
+from .helper import assert_image_equal, assert_image_equal_tofile, hopper
 
 if ImageQt.qt_is_installed:
     from PIL.ImageQt import QPixmap
 
     if ImageQt.qt_version == "6":
+        from PyQt6.QtCore import QPoint
+        from PyQt6.QtGui import QImage, QPainter, QRegion
         from PyQt6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
     elif ImageQt.qt_version == "side6":
+        from PySide6.QtCore import QPoint
+        from PySide6.QtGui import QImage, QPainter, QRegion
         from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
     elif ImageQt.qt_version == "5":
+        from PyQt5.QtCore import QPoint
+        from PyQt5.QtGui import QImage, QPainter, QRegion
         from PyQt5.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
     elif ImageQt.qt_version == "side2":
+        from PySide2.QtCore import QPoint
+        from PySide2.QtGui import QImage, QPainter, QRegion
         from PySide2.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
 
     class Example(QWidget):
@@ -49,7 +57,8 @@ def test_sanity(tmp_path):
 
     for mode in ("1", "RGB", "RGBA", "L", "P"):
         # to QPixmap
-        data = ImageQt.toqpixmap(hopper(mode))
+        im = hopper(mode)
+        data = ImageQt.toqpixmap(im)
 
         assert isinstance(data, QPixmap)
         assert not data.isNull()
@@ -58,6 +67,20 @@ def test_sanity(tmp_path):
         tempfile = str(tmp_path / f"temp_{mode}.png")
         data.save(tempfile)
 
+        # Render the image
+        qimage = ImageQt.ImageQt(im)
+        data = QPixmap.fromImage(qimage)
+        qt_format = QImage.Format if ImageQt.qt_version == "6" else QImage
+        qimage = QImage(128, 128, qt_format.Format_ARGB32)
+        painter = QPainter(qimage)
+        image_label = QLabel()
+        image_label.setPixmap(data)
+        image_label.render(painter, QPoint(0, 0), QRegion(0, 0, 128, 128))
+        painter.end()
+        rendered_tempfile = str(tmp_path / f"temp_rendered_{mode}.png")
+        qimage.save(rendered_tempfile)
+        assert_image_equal_tofile(im.convert("RGBA"), rendered_tempfile)
+
         # from QPixmap
         roundtrip(hopper(mode))