Repository: python-pillow/Pillow

Issue:
Pillow 2.9 causes PySide application to crash (whereas 2.8.2 does not)
Hello, the program below causes a crash on line 24 (setPixmap() line). It does not crash when I use Pillow 2.8.2. What might be the problem?

``` python
import sys
from PySide import QtGui, QtCore
from PIL import Image
from PIL import ImageQt


class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()

        pil_image = Image.open("test.png")

        print("Creating ImageQt.")
        qimage = ImageQt.ImageQt(pil_image)

        print("Converting to QPixmap.")
        pixmap1 = QtGui.QPixmap.fromImage(qimage)
        print("Converted QPixmap.")

        hbox = QtGui.QHBoxLayout(self)

        lbl = QtGui.QLabel(self)
        lbl.setPixmap(pixmap1.copy())

        hbox.addWidget(lbl)
        self.setLayout(hbox)

        self.setGeometry(300, 300, 280, 170)
        self.show()


def main():
    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()
```



Discussion:
Works for me with Pillow 2.9.0 / Python 2.7.9 / Windows 7 using [hopper.png](https://github.com/python-pillow/Pillow/blob/master/Tests/images/hopper.png) from our test suite.

What's the full error traceback? Does it work for you with hopper.png? Is the error only with your test.png image? 

I now tested with hopper.png and it worked. Then I tried to scale that image to 1000*1000 since all the test images I used were big, and it again crashes. I haven't tried on Windows. I tried with different image sizes and it crashes above 450*450. Can you also  try with a big image?

I also tried big images with 2.8.2 and it does not crash.

My system is:

Python: 2.7.6
OS: Ubuntu 14.04
I installed Pillow 2.9 and 2.8.2 several times.

Resizing the image to 1000*1000 I don't get a crash but the image popup just shows a black, 1000*1000 box. Closing the popup and the script finishes normally.

Do you get a traceback when it crashes? Any errors shown?

No just "Segmentation fault (core dumped)".

I tried with Python 2.7.10 on Mac OS X using Pillow 2.9.0, and had no problems. I tried a 1000x1000 image and a 2000x2000 image, and experienced neither the black box or a crash.

Would you be able to put one of the images online by any chance, so that others can test it?

Has this issue been solved? I'm encountering this issue with Pillow 3.4.2. I tested the above code with both PNG and JPEG images, at various sizes. No luck.

@petercertik We've not been able to reproduce it, so it's not been solved. Does it happen with any of the images in the Tests/images folder? Or can you attach an image that causes the problem? Do you get the problem without using PySide? What version OS, Python and PySide are you using?

I appreciate the quick response,

I went ahead and tested it with hopper.jpg and hopper.png, both of which worked. When hopper.png worked, I brought it into GIMP and scaled it to a size of 1280*1280, re-ran the test, and watched it seg fault again. I'll attach the image I'm working with, which is a still from a CollegeHumour video, made using FFMPEG. The code I'm personally working on utilizes PySide, but I also did testing with the above code, which doesn't use PySide. I'm using Python version 2.7.12, PySide version 1.2.4, Pillow 3.4.2, and Ubuntu 16.04.1 LTS. It's also worth noting that, even though I used the system installed Python, I tested it with a freshly installed Python copy in a virtual environment as well, with no difference.

Thanks,
Peter

![test](https://cloud.githubusercontent.com/assets/13127038/19776068/eabe3202-9c2f-11e6-8dd1-6bff816c6b65.png)
![pillow_seg_fault](https://cloud.githubusercontent.com/assets/13127038/19776075/ef6f21c6-9c2f-11e6-8c4f-4c6774951618.png)

ok, top of the backtrace is here:

```
#0  __memcpy_ssse3 () at ../sysdeps/x86_64/multiarch/memcpy-ssse3.S:91
#1  0x00007ffff2c04426 in QImage::copy(QRect const&) const () from /usr/lib/x86_64-linux-gnu/libQtGui.so.4
#2  0x00007ffff2c2a406 in QRasterPixmapData::copy(QPixmapData const*, QRect const&) () from /usr/lib/x86_64-linux-gnu/libQtGui.so.4
#3  0x00007ffff2c1cd3e in QPixmap::copy(QRect const&) const () from /usr/lib/x86_64-linux-gnu/libQtGui.so.4
#4  0x00007ffff3c0a8b0 in Sbk_QPixmapFunc_copy () from /home/erics/vpy27-dbg/local/lib/python2.7/site-packages/PySide/QtGui.so
#5  0x00000000004868b4 in PyCFunction_Call (func=<built-in method copy of PySide.QtGui.QPixmap object at remote 0x1a396f0>, arg=(), 
    kw=0x0) at ../Objects/methodobject.c:85
#6  0x000000000052645a in call_function (pp_stack=0x7fffffffd230, oparg=0) at ../Python/ceval.c:4021
#7  0x0000000000521326 in PyEval_EvalFrameEx (f=
    Frame 0x184d2d0, for file pyside.py, line 24, in __init__ (self=<Example at remote 0x15ed948>, pil_image=<PngImageFile(info={'aspect': (1, 1)}, category=0, palette=None, pyaccess=None, decoderconfig=(), text={}, map=None, filename='test.png', fp=None, readonly=0, im=<ImagingCore at remote 0x15def40>, mode='RGB', decodermaxblock=65536, tile=[], _PngImageFile__idat=0, png=None, size=(1920, 1080)) at remote 0x1a29a70>, qimage=<ImageQt at remote 0x1a1f9c0>, pixmap1=<PySide.QtGui.QPixmap at remote 0x1a396f0>, hbox=<PySide.QtGui.QHBoxLayout at remote 0x1a39768>, lbl=<PySide.QtGui.QLabel at remote 0x1a397e0>), throwflag=0) at ../Python/ceval.c:2666
#8  0x0000000000523c2e in PyEval_EvalCodeEx (co=0x9c8040, globals=
    {'QtGui': <module at remote 0xeabd60>, 'ImageQt': <module at remote 0x178acf8>, '__builtins__': <module at remote 0x7ffff7fd7470>, 'Image': <module at remote 0x1606dc8>, '__file__': 'pyside.py', '__package__': None, 'sys': <module at remote 0x7ffff7fd75a8>, '__name__': '__main__', 'main': <function at remote 0x17a2ed0>, 'QtCore': <module at remote 0xb35dc8>, '__doc__': None, 'Example': <Shiboken.ObjectType at remote 0x16470a0>}, locals=0x0, args=0x15e56a8, argcount=1, kws=0x0, kwcount=0, defs=0x0, defcount=0, closure=0x0)
```

Indicates that it's dying in the `pixmap1.copy()` call in line 24. 

And git bisect points at an entirely reasonable commit:

```
2d706d74dcbedbec7c5ede1bf3a9723464b95566 is the first bad commit
commit 2d706d74dcbedbec7c5ede1bf3a9723464b95566
Author: Roman Inflianskas <infroma@gmail.com>
Date:   Mon Sep 15 22:24:56 2014 +0400

    add functions to convert: Image <-> QImage; Image <-> QPixmap (see #897)

:040000 040000 3c685af34e1a42731d3883e9ce606ce0b83b5706 10a988f0d1e2f7a71386fad7a94fcce4085c551d M  PIL
:040000 040000 20c2d258b716bcafeeb31e7e102e7f73e7bcfadc 1c967d3b0adb8b47d525f484fe71cea2158f4b5b M  Tests
bisect run success
```

Commit 2d706d74dcbedbec7c5ede1bf3a9723464b95566 is from PR https://github.com/python-pillow/Pillow/pull/1217. Ping @rominf.

So, there's a comment in the `_toqclass_helper()` function: 
`
 # must keep a reference, or Qt will crash!
`
Yeah. Turns out that is important. If you read the docs, all of the initializers seem to make this from an existing memory buffer. If that buffer goes away, boom goes the process. 

```
diff --git a/PIL/ImageQt.py b/PIL/ImageQt.py
index 3f7ae25..2c6616b 100644
--- a/PIL/ImageQt.py
+++ b/PIL/ImageQt.py
@@ -175,8 +175,9 @@ if qt_is_installed:
                 string or a PyQt string object).
             """
             im_data = _toqclass_helper(im)
+            self.__data = im_data['data']
             QImage.__init__(self,
-                            im_data['data'], im_data['im'].size[0],
+                            self.__data, im_data['im'].size[0],
                             im_data['im'].size[1], im_data['format'])
             if im_data['colortable']:
                 self.setColorTable(im_data['colortable'])
```

Pardon my ignorance, but doesn't the code snippet above follow that guideline? None of the variables are being overwritten, or going out of scope? Or is it due to how Qt works, and it crashes once the exec_() method is entered? 

That's a change that I just put in to keep the reference, and the test above passes. 

Unfortunately the one that I'm trying to add for the test suite isn't. I'm getting a segfault at the `QPixmap.fromImage(qimage)` line in this one.  It's possible that this will only work from within a qt app. 

```
    def test_segfault(self):
        PillowQtTestCase.setUp(self)

        img = hopper().resize((1000,1000))

        qimage = ImageQt.toqimage(img)

        pixmap1 = QPixmap.fromImage(qimage)

        # This should not segfault
        pm2 = pixmap1.copy()

```

To be clear, could you post the code that allowed the test to pass? I believe I understand, but I want to be sure. Thank you. 

I think @wiredfool is right — Q(Core|Gui)?Application [should be initialized](http://pyqt.sourceforge.net/Docs/PyQt4/qcoreapplication.html#details) before usage this function.


Resolving patch:
diff --git a/PIL/ImageQt.py b/PIL/ImageQt.py
--- a/PIL/ImageQt.py
+++ b/PIL/ImageQt.py
@@ -157,7 +157,6 @@ def _toqclass_helper(im):
     else:
         raise ValueError("unsupported image mode %r" % im.mode)
 
-    # must keep a reference, or Qt will crash!
     __data = data or align8to32(im.tobytes(), im.size[0], im.mode)
     return {
         'data': __data, 'im': im, 'format': format, 'colortable': colortable
@@ -175,8 +174,13 @@ def __init__(self, im):
                 string or a PyQt string object).
             """
             im_data = _toqclass_helper(im)
+            # must keep a reference, or Qt will crash!
+            # All QImage constructors that take data operate on an existing
+            # buffer, so this buffer has to hang on for the life of the image.
+            # Fixes https://github.com/python-pillow/Pillow/issues/1370
+            self.__data = im_data['data']
             QImage.__init__(self,
-                            im_data['data'], im_data['im'].size[0],
+                            self.__data, im_data['im'].size[0],
                             im_data['im'].size[1], im_data['format'])
             if im_data['colortable']:
                 self.setColorTable(im_data['colortable'])


Test patch:
diff --git a/Tests/test_image_toqimage.py b/Tests/test_image_toqimage.py
--- a/Tests/test_image_toqimage.py
+++ b/Tests/test_image_toqimage.py
@@ -5,7 +5,17 @@
 
 
 if ImageQt.qt_is_installed:
-    from PIL.ImageQt import QImage
+    from PIL.ImageQt import QImage, QPixmap
+
+    try:
+        from PyQt5 import QtGui
+    except (ImportError, RuntimeError):
+        try:
+            from PyQt4 import QtGui
+        except (ImportError, RuntimeError):
+            from PySide import QtGui
+            
+
 
 
 class TestToQImage(PillowQtTestCase, PillowTestCase):
@@ -23,5 +33,41 @@ def test_sanity(self):
             data.save(tempfile)
 
 
+    def test_segfault(self):
+        PillowQtTestCase.setUp(self)
+
+        app = QtGui.QApplication([])
+        ex = Example()
+
+
+if ImageQt.qt_is_installed:
+    class Example(QtGui.QWidget):
+
+        def __init__(self):
+            super(Example, self).__init__()
+
+            img = hopper().resize((1000,1000))
+
+            qimage = ImageQt.ImageQt(img)
+
+            pixmap1 = QtGui.QPixmap.fromImage(qimage)
+
+            hbox = QtGui.QHBoxLayout(self)
+
+            lbl = QtGui.QLabel(self)
+            # Segfault in the problem
+            lbl.setPixmap(pixmap1.copy())
+
+
+
+
+def main():
+    app = QtGui.QApplication(sys.argv)
+    ex = Example()
+    sys.exit(app.exec_())
+
+
+
+
 if __name__ == '__main__':
     unittest.main()