From 2d706d74dcbedbec7c5ede1bf3a9723464b95566 Mon Sep 17 00:00:00 2001 From: Roman Inflianskas Date: Mon, 15 Sep 2014 22:24:56 +0400 Subject: [PATCH 01/10] add functions to convert: Image <-> QImage; Image <-> QPixmap (see #897) --- PIL/Image.py | 16 ++- PIL/ImageQt.py | 172 ++++++++++++++++++++------------ Tests/test_image_fromqimage.py | 36 +++++++ Tests/test_image_fromqpixmap.py | 36 +++++++ Tests/test_image_toqimage.py | 24 +++++ Tests/test_image_toqpixmap.py | 25 +++++ Tests/test_imageqt.py | 47 +++++---- 7 files changed, 275 insertions(+), 81 deletions(-) create mode 100644 Tests/test_image_fromqimage.py create mode 100644 Tests/test_image_fromqpixmap.py create mode 100644 Tests/test_image_toqimage.py create mode 100644 Tests/test_image_toqpixmap.py diff --git a/PIL/Image.py b/PIL/Image.py index 967a36fdb..6ae0377a3 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -101,12 +101,13 @@ except ImportError: import __builtin__ builtins = __builtin__ -from PIL import ImageMode +from PIL import ImageMode, ImageQt from PIL._binary import i8 from PIL._util import isPath from PIL._util import isStringType from PIL._util import deferred_error + import os import sys import io @@ -1936,6 +1937,14 @@ class Image(object): im = self.im.effect_spread(distance) return self._new(im) + if ImageQt.qt_is_installed: + def toqimage(self): + return ImageQt.toqimage(self) + + def toqpixmap(self): + return ImageQt.toqpixmap(self) + + # -------------------------------------------------------------------- # Lazy operations @@ -2185,6 +2194,11 @@ def fromarray(obj, mode=None): return frombuffer(mode, size, obj, "raw", rawmode, 0, 1) + +if ImageQt.qt_is_installed: + from PIL.ImageQt import fromqimage, fromqpixmap + + _fromarray_typemap = { # (shape, typestr) => mode, rawmode # first two members of shape are set to one diff --git a/PIL/ImageQt.py b/PIL/ImageQt.py index 6b7d4d66e..611019676 100644 --- a/PIL/ImageQt.py +++ b/PIL/ImageQt.py @@ -16,87 +16,133 @@ # See the README file for information on usage and redistribution. # -from PIL import Image +import PIL from PIL._util import isPath import sys -if 'PyQt4.QtGui' not in sys.modules: - try: - from PyQt5.QtGui import QImage, qRgba - except: - try: - from PyQt4.QtGui import QImage, qRgba - except: - from PySide.QtGui import QImage, qRgba +qt_is_installed = True +try: + from PyQt5.QtGui import QGuiApplication, QImage, qRgb, qRgba, QPixmap + from PyQt5.QtCore import QBuffer, QIODevice +except ImportError: + try: + from PyQt4.QtGui import QGuiApplication, QImage, qRgb, qRgba, QPixmap + from PyQt4.QtCore import QBuffer, QIODevice + except ImportError: + try: + from PySide.QtGui import QGuiApplication, QImage, qRgb, qRgba, QPixmap + from PySide.QtCore import QBuffer, QIODevice + except ImportError: + qt_is_installed = False -else: #PyQt4 is used - from PyQt4.QtGui import QImage, qRgba - -## -# (Internal) Turns an RGB color into a Qt compatible color integer. +from io import BytesIO def rgb(r, g, b, a=255): + """(Internal) Turns an RGB color into a Qt compatible color integer.""" # use qRgb to pack the colors, and then turn the resulting long # into a negative integer with the same bitpattern. return (qRgba(r, g, b, a) & 0xffffffff) -## -# An PIL image wrapper for Qt. This is a subclass of PyQt4's QImage -# class. -# -# @param im A PIL Image object, or a file name (given either as Python -# string or a PyQt string object). +# :param im A PIL Image object, or a file name (given either as Python string or a PyQt string object). -class ImageQt(QImage): +def fromqimage(im): + buffer = QBuffer() + buffer.open(QIODevice.ReadWrite) + im.save(buffer, 'ppm') + bytes_io = BytesIO() + try: + bytes_io.write(buffer.data()) + except TypeError: + # workaround for Python 2 + bytes_io.write(str(buffer.data())) + buffer.close() + bytes_io.seek(0) + return PIL.Image.open(bytes_io) - def __init__(self, im): - data = None - colortable = None +def fromqpixmap(im): + return fromqimage(im) + # buffer = QBuffer() + # buffer.open(QIODevice.ReadWrite) + # # im.save(buffer) + # # What if png doesn't support some image features like animation? + # im.save(buffer, 'ppm') + # bytes_io = BytesIO() + # bytes_io.write(buffer.data()) + # buffer.close() + # bytes_io.seek(0) + # return PIL.Image.open(bytes_io) - # handle filename, if given instead of image name - if hasattr(im, "toUtf8"): - # FIXME - is this really the best way to do this? - if str is bytes: - im = unicode(im.toUtf8(), "utf-8") - else: - im = str(im.toUtf8(), "utf-8") - if isPath(im): - im = Image.open(im) - if im.mode == "1": - format = QImage.Format_Mono - elif im.mode == "L": - format = QImage.Format_Indexed8 - colortable = [] - for i in range(256): - colortable.append(rgb(i, i, i)) - elif im.mode == "P": - format = QImage.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 - elif im.mode == "RGBA": - try: - data = im.tobytes("raw", "BGRA") - except SystemError: - # workaround for earlier versions - r, g, b, a = im.split() - im = Image.merge("RGBA", (b, g, r, a)) - format = QImage.Format_ARGB32 +def _toqclass_helper(im): + data = None + colortable = None + + # handle filename, if given instead of image name + if hasattr(im, "toUtf8"): + # FIXME - is this really the best way to do this? + if str is bytes: + im = unicode(im.toUtf8(), "utf-8") else: - raise ValueError("unsupported image mode %r" % im.mode) + im = str(im.toUtf8(), "utf-8") + if isPath(im): + im = PIL.Image.open(im) - # must keep a reference, or Qt will crash! - self.__data = data or im.tobytes() + if im.mode == "1": + format = QImage.Format_Mono + elif im.mode == "L": + format = QImage.Format_Indexed8 + colortable = [] + for i in range(256): + colortable.append(rgb(i, i, i)) + elif im.mode == "P": + format = QImage.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 + elif im.mode == "RGBA": + try: + data = im.tobytes("raw", "BGRA") + except SystemError: + # workaround for earlier versions + r, g, b, a = im.split() + im = PIL.Image.merge("RGBA", (b, g, r, a)) + format = QImage.Format_ARGB32 + else: + raise ValueError("unsupported image mode %r" % im.mode) - QImage.__init__(self, self.__data, im.size[0], im.size[1], format) + # must keep a reference, or Qt will crash! + __data = data or im.tobytes() + return { + 'data': __data, 'im': im, 'format': format, 'colortable': colortable + } - if colortable: - self.setColorTable(colortable) + +def toqimage(im): + im_data = _toqclass_helper(im) + result = QImage( + im_data['data'], im_data['im'].size[0], im_data['im'].size[1], + im_data['format'] + ) + if im_data['colortable']: + result.setColorTable(im_data['colortable']) + return result + + +def toqpixmap(im): + # This doesn't work. For now using a dumb approach. + # im_data = _toqclass_helper(im) + # result = QPixmap(im_data['im'].size[0], im_data['im'].size[1]) + # result.loadFromData(im_data['data']) + # Fix some strange bug that causes + if im.mode == 'RGB': + im = im.convert('RGBA') + qimage = im.toqimage() + qimage.save('/tmp/hopper_{}_qpixmap_qimage.png'.format(im.mode)) + return QPixmap.fromImage(qimage) \ No newline at end of file diff --git a/Tests/test_image_fromqimage.py b/Tests/test_image_fromqimage.py new file mode 100644 index 000000000..f55bde2f1 --- /dev/null +++ b/Tests/test_image_fromqimage.py @@ -0,0 +1,36 @@ +from helper import unittest, PillowTestCase, hopper, image +from test_imageqt import PillowQtTestCase + +from PIL import Image, ImageQt + +if ImageQt.qt_is_installed: + from PIL.ImageQt import QImage + + +class TestFromQImage(PillowQtTestCase, PillowTestCase): + + def roundtrip(self, expected): + result = Image.fromqimage(expected.toqimage()) + # Qt saves all images as rgb + self.assert_image_equal(result, expected.convert('RGB')) + + def test_sanity_1(self): + self.roundtrip(hopper('1')) + + def test_sanity_rgb(self): + self.roundtrip(hopper('RGB')) + + def test_sanity_rgba(self): + self.roundtrip(hopper('RGBA')) + + def test_sanity_l(self): + self.roundtrip(hopper('L')) + + def test_sanity_p(self): + self.roundtrip(hopper('P')) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_image_fromqpixmap.py b/Tests/test_image_fromqpixmap.py new file mode 100644 index 000000000..e96b3a374 --- /dev/null +++ b/Tests/test_image_fromqpixmap.py @@ -0,0 +1,36 @@ +from helper import unittest, PillowTestCase, hopper +from test_imageqt import PillowQPixmapTestCase + +from PIL import Image, ImageQt + +if ImageQt.qt_is_installed: + from PIL.ImageQt import QPixmap + + +class TestFromQPixmap(PillowQPixmapTestCase, PillowTestCase): + + def roundtrip(self, expected): + result = Image.fromqpixmap(expected.toqpixmap()) + # Qt saves all pixmaps as rgb + self.assert_image_equal(result, expected.convert('RGB')) + + def test_sanity_1(self): + self.roundtrip(hopper('1')) + + def test_sanity_rgb(self): + self.roundtrip(hopper('RGB')) + + def test_sanity_rgba(self): + self.roundtrip(hopper('RGBA')) + + def test_sanity_l(self): + self.roundtrip(hopper('L')) + + def test_sanity_p(self): + self.roundtrip(hopper('P')) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_image_toqimage.py b/Tests/test_image_toqimage.py new file mode 100644 index 000000000..125728bf0 --- /dev/null +++ b/Tests/test_image_toqimage.py @@ -0,0 +1,24 @@ +from helper import unittest, PillowTestCase, hopper +from test_imageqt import PillowQtTestCase + +from PIL import ImageQt + + +if ImageQt.qt_is_installed: + from PIL.ImageQt import QImage + + +class TestToQImage(PillowQtTestCase, PillowTestCase): + + def test_sanity(self): + for mode in ('1', 'RGB', 'RGBA', 'L', 'P'): + data = ImageQt.toqimage(hopper(mode)) + data.save('/tmp/hopper_{}_qimage.png'.format(mode)) + self.assertTrue(isinstance(data, QImage)) + self.assertFalse(data.isNull()) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_image_toqpixmap.py b/Tests/test_image_toqpixmap.py new file mode 100644 index 000000000..b857a66ca --- /dev/null +++ b/Tests/test_image_toqpixmap.py @@ -0,0 +1,25 @@ +from helper import unittest, PillowTestCase, hopper +from test_imageqt import PillowQPixmapTestCase + +from PIL import ImageQt + +if ImageQt.qt_is_installed: + from PIL.ImageQt import QPixmap + + +class TestToQPixmap(PillowQPixmapTestCase, PillowTestCase): + + def test_sanity(self): + QPixmap('Tests/images/hopper.ppm').save( + '/tmp/hopper_RGB_qpixmap_file.png') + for mode in ('1', 'RGB', 'RGBA', 'L', 'P'): + data = ImageQt.toqpixmap(hopper(mode)) + data.save('/tmp/hopper_{}_qpixmap.png'.format(mode)) + self.assertTrue(isinstance(data, QPixmap)) + self.assertFalse(data.isNull()) + + +if __name__ == '__main__': + unittest.main() + +# End of file diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index ecab9a956..ec76f55d0 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -1,20 +1,19 @@ from helper import unittest, PillowTestCase, hopper -try: - from PIL import ImageQt - from PyQt5.QtGui import QImage, qRgb, qRgba -except: - try: - from PyQt4.QtGui import QImage, qRgb, qRgba - except: - try: - from PySide.QtGui import QImage, qRgb, qRgba - except: - # Will be skipped in setUp - pass +from PIL import ImageQt -class TestImageQt(PillowTestCase): +if ImageQt.qt_is_installed: + from PIL.ImageQt import QGuiApplication, QImage, qRgb, qRgba + + def skip_if_qt_is_not_installed(_): + pass +else: + def skip_if_qt_is_not_installed(test_case): + test_case.skipTest('PyQt4, PyQt5, or PySide is not installed') + + +class PillowQtTestCase: def setUp(self): try: @@ -27,6 +26,24 @@ class TestImageQt(PillowTestCase): from PySide.QtGui import QImage, qRgb, qRgba except ImportError: self.skipTest('PyQt4 or 5 or PySide not installed') + skip_if_qt_is_not_installed(self) + + def tearDown(self): + pass + + +class PillowQPixmapTestCase(PillowQtTestCase): + + def setUp(self): + PillowQtTestCase.setUp(self) + self.app = QGuiApplication([]) + + def tearDown(self): + PillowQtTestCase.tearDown(self) + self.app.quit() + + +class TestImageQt(PillowQtTestCase, PillowTestCase): def test_rgb(self): # from https://qt-project.org/doc/qt-4.8/qcolor.html @@ -48,10 +65,6 @@ class TestImageQt(PillowTestCase): checkrgb(0, 255, 0) checkrgb(0, 0, 255) - def test_image(self): - for mode in ('1', 'RGB', 'RGBA', 'L', 'P'): - ImageQt.ImageQt(hopper(mode)) - if __name__ == '__main__': unittest.main() From 854d343aa5dd12d13db5c0fe79dd6efc6d9f548f Mon Sep 17 00:00:00 2001 From: Roman Inflianskas Date: Mon, 15 Sep 2014 23:01:05 +0400 Subject: [PATCH 02/10] add functions to convert: Image <-> QImage; Image <-> QPixmap (see #897); fix typo that breaks tests --- Tests/test_image_fromqimage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_image_fromqimage.py b/Tests/test_image_fromqimage.py index f55bde2f1..56dac0c66 100644 --- a/Tests/test_image_fromqimage.py +++ b/Tests/test_image_fromqimage.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase, hopper, image +from helper import unittest, PillowTestCase, hopper from test_imageqt import PillowQtTestCase from PIL import Image, ImageQt From b318595666740b07969b83d6b8a289e8936a4ed8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 7 May 2015 19:55:52 +1000 Subject: [PATCH 03/10] Re-added ImageQt class --- PIL/ImageQt.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/PIL/ImageQt.py b/PIL/ImageQt.py index 611019676..395aa815c 100644 --- a/PIL/ImageQt.py +++ b/PIL/ImageQt.py @@ -125,14 +125,7 @@ def _toqclass_helper(im): def toqimage(im): - im_data = _toqclass_helper(im) - result = QImage( - im_data['data'], im_data['im'].size[0], im_data['im'].size[1], - im_data['format'] - ) - if im_data['colortable']: - result.setColorTable(im_data['colortable']) - return result + return ImageQt(im) def toqpixmap(im): @@ -145,4 +138,22 @@ def toqpixmap(im): im = im.convert('RGBA') qimage = im.toqimage() qimage.save('/tmp/hopper_{}_qpixmap_qimage.png'.format(im.mode)) - return QPixmap.fromImage(qimage) \ No newline at end of file + return QPixmap.fromImage(qimage) + +## +# An PIL image wrapper for Qt. This is a subclass of PyQt4's QImage +# class. +# +# @param im A PIL Image object, or a file name (given either as Python +# string or a PyQt string object). + +class ImageQt(QImage): + + def __init__(self, im): + im_data = _toqclass_helper(im) + QImage.__init__(self, + im_data['data'], im_data['im'].size[0], im_data['im'].size[1], + im_data['format'] + ) + if im_data['colortable']: + self.setColorTable(im_data['colortable']) \ No newline at end of file From fa1c4bffaf21f7e5656fc8d2bd6b6298d450522c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 7 May 2015 20:26:26 +1000 Subject: [PATCH 04/10] Do not attempt to subclass QImage if Qt is not installed --- PIL/ImageQt.py | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/PIL/ImageQt.py b/PIL/ImageQt.py index 395aa815c..480564481 100644 --- a/PIL/ImageQt.py +++ b/PIL/ImageQt.py @@ -22,18 +22,18 @@ import sys qt_is_installed = True try: - from PyQt5.QtGui import QGuiApplication, QImage, qRgb, qRgba, QPixmap - from PyQt5.QtCore import QBuffer, QIODevice + from PyQt5.QtGui import QGuiApplication, QImage, qRgb, qRgba, QPixmap + from PyQt5.QtCore import QBuffer, QIODevice except ImportError: - try: - from PyQt4.QtGui import QGuiApplication, QImage, qRgb, qRgba, QPixmap - from PyQt4.QtCore import QBuffer, QIODevice - except ImportError: - try: - from PySide.QtGui import QGuiApplication, QImage, qRgb, qRgba, QPixmap - from PySide.QtCore import QBuffer, QIODevice - except ImportError: - qt_is_installed = False + try: + from PyQt4.QtGui import QGuiApplication, QImage, qRgb, qRgba, QPixmap + from PyQt4.QtCore import QBuffer, QIODevice + except ImportError: + try: + from PySide.QtGui import QGuiApplication, QImage, qRgb, qRgba, QPixmap + from PySide.QtCore import QBuffer, QIODevice + except ImportError: + qt_is_installed = False from io import BytesIO @@ -147,13 +147,14 @@ def toqpixmap(im): # @param im A PIL Image object, or a file name (given either as Python # string or a PyQt string object). -class ImageQt(QImage): +if qt_is_installed: + class ImageQt(QImage): - def __init__(self, im): - im_data = _toqclass_helper(im) - QImage.__init__(self, - im_data['data'], im_data['im'].size[0], im_data['im'].size[1], - im_data['format'] - ) - if im_data['colortable']: - self.setColorTable(im_data['colortable']) \ No newline at end of file + def __init__(self, im): + im_data = _toqclass_helper(im) + QImage.__init__(self, + im_data['data'], im_data['im'].size[0], im_data['im'].size[1], + im_data['format'] + ) + if im_data['colortable']: + self.setColorTable(im_data['colortable']) \ No newline at end of file From 43e2c92802dbb71dd0d5fe10afdd0d566b79aaf2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 19 Jun 2015 15:35:56 +1000 Subject: [PATCH 05/10] Removed unused imports --- PIL/ImageQt.py | 47 ++++++++++++++++++--------------- Tests/test_image_fromqimage.py | 7 ++--- Tests/test_image_fromqpixmap.py | 10 +++---- Tests/test_image_toqimage.py | 1 + Tests/test_image_toqpixmap.py | 3 ++- Tests/test_imageqt.py | 31 +++++++++++++--------- 6 files changed, 52 insertions(+), 47 deletions(-) diff --git a/PIL/ImageQt.py b/PIL/ImageQt.py index 480564481..95819029d 100644 --- a/PIL/ImageQt.py +++ b/PIL/ImageQt.py @@ -18,20 +18,23 @@ import PIL from PIL._util import isPath -import sys qt_is_installed = True +qt_version = None try: - from PyQt5.QtGui import QGuiApplication, QImage, qRgb, qRgba, QPixmap + from PyQt5.QtGui import QImage, qRgba, QPixmap from PyQt5.QtCore import QBuffer, QIODevice + qt_version = '5' except ImportError: try: - from PyQt4.QtGui import QGuiApplication, QImage, qRgb, qRgba, QPixmap + from PyQt4.QtGui import QImage, qRgba, QPixmap from PyQt4.QtCore import QBuffer, QIODevice + qt_version = '4' except ImportError: try: - from PySide.QtGui import QGuiApplication, QImage, qRgb, qRgba, QPixmap + from PySide.QtGui import QImage, qRgba, QPixmap from PySide.QtCore import QBuffer, QIODevice + qt_version = 'side' except ImportError: qt_is_installed = False @@ -123,23 +126,6 @@ def _toqclass_helper(im): 'data': __data, 'im': im, 'format': format, 'colortable': colortable } - -def toqimage(im): - return ImageQt(im) - - -def toqpixmap(im): - # This doesn't work. For now using a dumb approach. - # im_data = _toqclass_helper(im) - # result = QPixmap(im_data['im'].size[0], im_data['im'].size[1]) - # result.loadFromData(im_data['data']) - # Fix some strange bug that causes - if im.mode == 'RGB': - im = im.convert('RGBA') - qimage = im.toqimage() - qimage.save('/tmp/hopper_{}_qpixmap_qimage.png'.format(im.mode)) - return QPixmap.fromImage(qimage) - ## # An PIL image wrapper for Qt. This is a subclass of PyQt4's QImage # class. @@ -157,4 +143,21 @@ if qt_is_installed: im_data['format'] ) if im_data['colortable']: - self.setColorTable(im_data['colortable']) \ No newline at end of file + self.setColorTable(im_data['colortable']) + + +def toqimage(im): + return ImageQt(im) + + +def toqpixmap(im): + # This doesn't work. For now using a dumb approach. + # im_data = _toqclass_helper(im) + # result = QPixmap(im_data['im'].size[0], im_data['im'].size[1]) + # result.loadFromData(im_data['data']) + # Fix some strange bug that causes + if im.mode == 'RGB': + im = im.convert('RGBA') + qimage = toqimage(im) + qimage.save('/tmp/hopper_{}_qpixmap_qimage.png'.format(im.mode)) + return QPixmap.fromImage(qimage) \ No newline at end of file diff --git a/Tests/test_image_fromqimage.py b/Tests/test_image_fromqimage.py index 56dac0c66..957ec9cc1 100644 --- a/Tests/test_image_fromqimage.py +++ b/Tests/test_image_fromqimage.py @@ -1,16 +1,13 @@ from helper import unittest, PillowTestCase, hopper from test_imageqt import PillowQtTestCase -from PIL import Image, ImageQt - -if ImageQt.qt_is_installed: - from PIL.ImageQt import QImage +from PIL import ImageQt class TestFromQImage(PillowQtTestCase, PillowTestCase): def roundtrip(self, expected): - result = Image.fromqimage(expected.toqimage()) + result = ImageQt.fromqimage(expected.toqimage()) # Qt saves all images as rgb self.assert_image_equal(result, expected.convert('RGB')) diff --git a/Tests/test_image_fromqpixmap.py b/Tests/test_image_fromqpixmap.py index e96b3a374..78e4d6770 100644 --- a/Tests/test_image_fromqpixmap.py +++ b/Tests/test_image_fromqpixmap.py @@ -1,16 +1,14 @@ from helper import unittest, PillowTestCase, hopper -from test_imageqt import PillowQPixmapTestCase +from test_imageqt import PillowQtTestCase, PillowQPixmapTestCase -from PIL import Image, ImageQt - -if ImageQt.qt_is_installed: - from PIL.ImageQt import QPixmap +from PIL import ImageQt class TestFromQPixmap(PillowQPixmapTestCase, PillowTestCase): def roundtrip(self, expected): - result = Image.fromqpixmap(expected.toqpixmap()) + PillowQtTestCase.setUp(self) + result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected)) # Qt saves all pixmaps as rgb self.assert_image_equal(result, expected.convert('RGB')) diff --git a/Tests/test_image_toqimage.py b/Tests/test_image_toqimage.py index 125728bf0..d44a64847 100644 --- a/Tests/test_image_toqimage.py +++ b/Tests/test_image_toqimage.py @@ -11,6 +11,7 @@ if ImageQt.qt_is_installed: class TestToQImage(PillowQtTestCase, PillowTestCase): def test_sanity(self): + PillowQtTestCase.setUp(self) for mode in ('1', 'RGB', 'RGBA', 'L', 'P'): data = ImageQt.toqimage(hopper(mode)) data.save('/tmp/hopper_{}_qimage.png'.format(mode)) diff --git a/Tests/test_image_toqpixmap.py b/Tests/test_image_toqpixmap.py index b857a66ca..dd527732f 100644 --- a/Tests/test_image_toqpixmap.py +++ b/Tests/test_image_toqpixmap.py @@ -1,5 +1,5 @@ from helper import unittest, PillowTestCase, hopper -from test_imageqt import PillowQPixmapTestCase +from test_imageqt import PillowQtTestCase, PillowQPixmapTestCase from PIL import ImageQt @@ -10,6 +10,7 @@ if ImageQt.qt_is_installed: class TestToQPixmap(PillowQPixmapTestCase, PillowTestCase): def test_sanity(self): + PillowQtTestCase.setUp(self) QPixmap('Tests/images/hopper.ppm').save( '/tmp/hopper_RGB_qpixmap_file.png') for mode in ('1', 'RGB', 'RGBA', 'L', 'P'): diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index ec76f55d0..5959bee34 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -1,10 +1,10 @@ -from helper import unittest, PillowTestCase, hopper +from helper import unittest, PillowTestCase from PIL import ImageQt if ImageQt.qt_is_installed: - from PIL.ImageQt import QGuiApplication, QImage, qRgb, qRgba + from PIL.ImageQt import qRgba def skip_if_qt_is_not_installed(_): pass @@ -16,26 +16,25 @@ else: class PillowQtTestCase: def setUp(self): - try: - from PyQt5.QtGui import QImage, qRgb, qRgba - except ImportError: - try: - from PyQt4.QtGui import QImage, qRgb, qRgba - except ImportError: - try: - from PySide.QtGui import QImage, qRgb, qRgba - except ImportError: - self.skipTest('PyQt4 or 5 or PySide not installed') skip_if_qt_is_not_installed(self) def tearDown(self): pass - class PillowQPixmapTestCase(PillowQtTestCase): def setUp(self): PillowQtTestCase.setUp(self) + try: + if ImageQt.qt_version == '5': + from PyQt5.QtGui import QGuiApplication + elif ImageQt.qt_version == '4': + from PyQt4.QtGui import QGuiApplication + elif ImageQt.qt_version == 'side': + from PySide.QtGui import QGuiApplication + except ImportError: + self.skipTest('QGuiApplication not installed') + self.app = QGuiApplication([]) def tearDown(self): @@ -50,6 +49,12 @@ class TestImageQt(PillowQtTestCase, PillowTestCase): # typedef QRgb # An ARGB quadruplet on the format #AARRGGBB, # equivalent to an unsigned int. + if ImageQt.qt_version == '5': + from PyQt5.QtGui import qRgb + elif ImageQt.qt_version == '4': + from PyQt4.QtGui import qRgb + elif ImageQt.qt_version == 'side': + from PySide.QtGui import qRgb self.assertEqual(qRgb(0, 0, 0), qRgba(0, 0, 0, 255)) From 33d51d4255593c825454981bc934a5cf51d79c70 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 19 Jun 2015 15:36:23 +1000 Subject: [PATCH 06/10] Flake8 and health fixes --- PIL/ImageQt.py | 10 +++++----- Tests/test_imageqt.py | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/PIL/ImageQt.py b/PIL/ImageQt.py index 95819029d..52dd7d79f 100644 --- a/PIL/ImageQt.py +++ b/PIL/ImageQt.py @@ -48,7 +48,8 @@ def rgb(r, g, b, a=255): return (qRgba(r, g, b, a) & 0xffffffff) -# :param im A PIL Image object, or a file name (given either as Python string or a PyQt string object). +# :param im A PIL Image object, or a file name +# (given either as Python string or a PyQt string object) def fromqimage(im): buffer = QBuffer() @@ -139,9 +140,8 @@ if qt_is_installed: def __init__(self, im): im_data = _toqclass_helper(im) QImage.__init__(self, - im_data['data'], im_data['im'].size[0], im_data['im'].size[1], - im_data['format'] - ) + im_data['data'], im_data['im'].size[0], + im_data['im'].size[1], im_data['format']) if im_data['colortable']: self.setColorTable(im_data['colortable']) @@ -160,4 +160,4 @@ def toqpixmap(im): im = im.convert('RGBA') qimage = toqimage(im) qimage.save('/tmp/hopper_{}_qpixmap_qimage.png'.format(im.mode)) - return QPixmap.fromImage(qimage) \ No newline at end of file + return QPixmap.fromImage(qimage) diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index 5959bee34..b17d27e71 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -13,7 +13,7 @@ else: test_case.skipTest('PyQt4, PyQt5, or PySide is not installed') -class PillowQtTestCase: +class PillowQtTestCase(object): def setUp(self): skip_if_qt_is_not_installed(self) @@ -21,6 +21,7 @@ class PillowQtTestCase: def tearDown(self): pass + class PillowQPixmapTestCase(PillowQtTestCase): def setUp(self): @@ -34,7 +35,7 @@ class PillowQPixmapTestCase(PillowQtTestCase): from PySide.QtGui import QGuiApplication except ImportError: self.skipTest('QGuiApplication not installed') - + self.app = QGuiApplication([]) def tearDown(self): From c644bf9455c373a1e3fdc358d347bb7a1a9d2a98 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 19 Jun 2015 15:55:35 +1000 Subject: [PATCH 07/10] Do not import ImageQt until it is requested --- PIL/Image.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 6ae0377a3..805cb0717 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -101,13 +101,12 @@ except ImportError: import __builtin__ builtins = __builtin__ -from PIL import ImageMode, ImageQt +from PIL import ImageMode from PIL._binary import i8 from PIL._util import isPath from PIL._util import isStringType from PIL._util import deferred_error - import os import sys import io @@ -1937,13 +1936,17 @@ class Image(object): im = self.im.effect_spread(distance) return self._new(im) - if ImageQt.qt_is_installed: - def toqimage(self): - return ImageQt.toqimage(self) - - def toqpixmap(self): - return ImageQt.toqpixmap(self) + def toqimage(self): + from PIL import ImageQt + if not ImageQt.qt_is_installed: + raise ImportError("Qt bindings are not installed") + return ImageQt.toqimage(self) + def toqpixmap(self): + from PIL import ImageQt + if not ImageQt.qt_is_installed: + raise ImportError("Qt bindings are not installed") + return ImageQt.toqpixmap(self) # -------------------------------------------------------------------- @@ -2195,10 +2198,19 @@ def fromarray(obj, mode=None): return frombuffer(mode, size, obj, "raw", rawmode, 0, 1) -if ImageQt.qt_is_installed: - from PIL.ImageQt import fromqimage, fromqpixmap +def fromqimage(im): + from PIL import ImageQt + if not ImageQt.qt_is_installed: + raise ImportError("Qt bindings are not installed") + return ImageQt.fromqimage(im) +def fromqpixmap(im): + from PIL import ImageQt + if not ImageQt.qt_is_installed: + raise ImportError("Qt bindings are not installed") + return ImageQt.fromqpixmap(im) + _fromarray_typemap = { # (shape, typestr) => mode, rawmode # first two members of shape are set to one From 40b659764df36c432002309aa1bea5829d22e697 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 19 Jun 2015 18:23:17 +1000 Subject: [PATCH 08/10] Restored deleted test --- Tests/test_imageqt.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index b17d27e71..3c977ff21 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -1,4 +1,4 @@ -from helper import unittest, PillowTestCase +from helper import unittest, PillowTestCase, hopper from PIL import ImageQt @@ -10,7 +10,7 @@ if ImageQt.qt_is_installed: pass else: def skip_if_qt_is_not_installed(test_case): - test_case.skipTest('PyQt4, PyQt5, or PySide is not installed') + test_case.skipTest('Qt bindings are not installed') class PillowQtTestCase(object): @@ -71,6 +71,10 @@ class TestImageQt(PillowQtTestCase, PillowTestCase): checkrgb(0, 255, 0) checkrgb(0, 0, 255) + def test_image(self): + for mode in ('1', 'RGB', 'RGBA', 'L', 'P'): + ImageQt.ImageQt(hopper(mode)) + if __name__ == '__main__': unittest.main() From c1b1f184b85672bba72d75a19f2e783ef6dca079 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 19 Jun 2015 18:28:13 +1000 Subject: [PATCH 09/10] Updated documentation URL --- Tests/test_imageqt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index 3c977ff21..b0fad6a45 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -46,7 +46,7 @@ class PillowQPixmapTestCase(PillowQtTestCase): class TestImageQt(PillowQtTestCase, PillowTestCase): def test_rgb(self): - # from https://qt-project.org/doc/qt-4.8/qcolor.html + # from https://doc.qt.io/qt-4.8/qcolor.html # typedef QRgb # An ARGB quadruplet on the format #AARRGGBB, # equivalent to an unsigned int. From b553ad7a70549b03362d25fda12eef6958168fa2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 21 Jun 2015 16:31:51 +1000 Subject: [PATCH 10/10] Further fixes --- PIL/Image.py | 4 ++++ PIL/ImageQt.py | 21 +++++++++++---------- Tests/test_image_toqimage.py | 6 +++++- Tests/test_image_toqpixmap.py | 9 ++++++--- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 805cb0717..a6b08d196 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1937,12 +1937,14 @@ class Image(object): return self._new(im) def toqimage(self): + """Returns a QImage copy of this image""" from PIL import ImageQt if not ImageQt.qt_is_installed: raise ImportError("Qt bindings are not installed") return ImageQt.toqimage(self) def toqpixmap(self): + """Returns a QPixmap copy of this image""" from PIL import ImageQt if not ImageQt.qt_is_installed: raise ImportError("Qt bindings are not installed") @@ -2199,6 +2201,7 @@ def fromarray(obj, mode=None): def fromqimage(im): + """Creates an image instance from a QImage image""" from PIL import ImageQt if not ImageQt.qt_is_installed: raise ImportError("Qt bindings are not installed") @@ -2206,6 +2209,7 @@ def fromqimage(im): def fromqpixmap(im): + """Creates an image instance from a QPixmap image""" from PIL import ImageQt if not ImageQt.qt_is_installed: raise ImportError("Qt bindings are not installed") diff --git a/PIL/ImageQt.py b/PIL/ImageQt.py index 52dd7d79f..0e8cee009 100644 --- a/PIL/ImageQt.py +++ b/PIL/ImageQt.py @@ -18,6 +18,7 @@ import PIL from PIL._util import isPath +from io import BytesIO qt_is_installed = True qt_version = None @@ -38,8 +39,6 @@ except ImportError: except ImportError: qt_is_installed = False -from io import BytesIO - def rgb(r, g, b, a=255): """(Internal) Turns an RGB color into a Qt compatible color integer.""" @@ -55,15 +54,17 @@ def fromqimage(im): buffer = QBuffer() buffer.open(QIODevice.ReadWrite) im.save(buffer, 'ppm') - bytes_io = BytesIO() + + b = BytesIO() try: - bytes_io.write(buffer.data()) + b.write(buffer.data()) except TypeError: # workaround for Python 2 - bytes_io.write(str(buffer.data())) + b.write(str(buffer.data())) buffer.close() - bytes_io.seek(0) - return PIL.Image.open(bytes_io) + b.seek(0) + + return PIL.Image.open(b) def fromqpixmap(im): @@ -128,7 +129,7 @@ def _toqclass_helper(im): } ## -# An PIL image wrapper for Qt. This is a subclass of PyQt4's QImage +# An PIL image wrapper for Qt. This is a subclass of PyQt's QImage # class. # # @param im A PIL Image object, or a file name (given either as Python @@ -151,13 +152,13 @@ def toqimage(im): def toqpixmap(im): - # This doesn't work. For now using a dumb approach. + # # This doesn't work. For now using a dumb approach. # im_data = _toqclass_helper(im) # result = QPixmap(im_data['im'].size[0], im_data['im'].size[1]) # result.loadFromData(im_data['data']) # Fix some strange bug that causes if im.mode == 'RGB': im = im.convert('RGBA') + qimage = toqimage(im) - qimage.save('/tmp/hopper_{}_qpixmap_qimage.png'.format(im.mode)) return QPixmap.fromImage(qimage) diff --git a/Tests/test_image_toqimage.py b/Tests/test_image_toqimage.py index d44a64847..38b83c023 100644 --- a/Tests/test_image_toqimage.py +++ b/Tests/test_image_toqimage.py @@ -14,10 +14,14 @@ class TestToQImage(PillowQtTestCase, PillowTestCase): PillowQtTestCase.setUp(self) for mode in ('1', 'RGB', 'RGBA', 'L', 'P'): data = ImageQt.toqimage(hopper(mode)) - data.save('/tmp/hopper_{}_qimage.png'.format(mode)) + self.assertTrue(isinstance(data, QImage)) self.assertFalse(data.isNull()) + # Test saving the file + tempfile = self.tempfile('temp_{}.png'.format(mode)) + data.save(tempfile) + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_image_toqpixmap.py b/Tests/test_image_toqpixmap.py index dd527732f..95db2e8f7 100644 --- a/Tests/test_image_toqpixmap.py +++ b/Tests/test_image_toqpixmap.py @@ -11,14 +11,17 @@ class TestToQPixmap(PillowQPixmapTestCase, PillowTestCase): def test_sanity(self): PillowQtTestCase.setUp(self) - QPixmap('Tests/images/hopper.ppm').save( - '/tmp/hopper_RGB_qpixmap_file.png') + for mode in ('1', 'RGB', 'RGBA', 'L', 'P'): data = ImageQt.toqpixmap(hopper(mode)) - data.save('/tmp/hopper_{}_qpixmap.png'.format(mode)) + self.assertTrue(isinstance(data, QPixmap)) self.assertFalse(data.isNull()) + # Test saving the file + tempfile = self.tempfile('temp_{}.png'.format(mode)) + data.save(tempfile) + if __name__ == '__main__': unittest.main()