ImageQt.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. #
  2. # The Python Imaging Library.
  3. # $Id$
  4. #
  5. # a simple Qt image interface.
  6. #
  7. # history:
  8. # 2006-06-03 fl: created
  9. # 2006-06-04 fl: inherit from QImage instead of wrapping it
  10. # 2006-06-05 fl: removed toimage helper; move string support to ImageQt
  11. # 2013-11-13 fl: add support for Qt5 (aurelien.ballier@cyclonit.com)
  12. #
  13. # Copyright (c) 2006 by Secret Labs AB
  14. # Copyright (c) 2006 by Fredrik Lundh
  15. #
  16. # See the README file for information on usage and redistribution.
  17. #
  18. import sys
  19. import warnings
  20. from io import BytesIO
  21. from . import Image
  22. from ._util import isPath, py3
  23. qt_versions = [["5", "PyQt5"], ["side2", "PySide2"], ["4", "PyQt4"], ["side", "PySide"]]
  24. WARNING_TEXT = (
  25. "Support for EOL {} is deprecated and will be removed in a future version. "
  26. "Please upgrade to PyQt5 or PySide2."
  27. )
  28. # If a version has already been imported, attempt it first
  29. qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, reverse=True)
  30. for qt_version, qt_module in qt_versions:
  31. try:
  32. if qt_module == "PyQt5":
  33. from PyQt5.QtGui import QImage, qRgba, QPixmap
  34. from PyQt5.QtCore import QBuffer, QIODevice
  35. elif qt_module == "PySide2":
  36. from PySide2.QtGui import QImage, qRgba, QPixmap
  37. from PySide2.QtCore import QBuffer, QIODevice
  38. elif qt_module == "PyQt4":
  39. from PyQt4.QtGui import QImage, qRgba, QPixmap
  40. from PyQt4.QtCore import QBuffer, QIODevice
  41. warnings.warn(WARNING_TEXT.format(qt_module), DeprecationWarning)
  42. elif qt_module == "PySide":
  43. from PySide.QtGui import QImage, qRgba, QPixmap
  44. from PySide.QtCore import QBuffer, QIODevice
  45. warnings.warn(WARNING_TEXT.format(qt_module), DeprecationWarning)
  46. except (ImportError, RuntimeError):
  47. continue
  48. qt_is_installed = True
  49. break
  50. else:
  51. qt_is_installed = False
  52. qt_version = None
  53. def rgb(r, g, b, a=255):
  54. """(Internal) Turns an RGB color into a Qt compatible color integer."""
  55. # use qRgb to pack the colors, and then turn the resulting long
  56. # into a negative integer with the same bitpattern.
  57. return qRgba(r, g, b, a) & 0xFFFFFFFF
  58. def fromqimage(im):
  59. """
  60. :param im: A PIL Image object, or a file name
  61. (given either as Python string or a PyQt string object)
  62. """
  63. buffer = QBuffer()
  64. buffer.open(QIODevice.ReadWrite)
  65. # preserve alpha channel with png
  66. # otherwise ppm is more friendly with Image.open
  67. if im.hasAlphaChannel():
  68. im.save(buffer, "png")
  69. else:
  70. im.save(buffer, "ppm")
  71. b = BytesIO()
  72. try:
  73. b.write(buffer.data())
  74. except TypeError:
  75. # workaround for Python 2
  76. b.write(str(buffer.data()))
  77. buffer.close()
  78. b.seek(0)
  79. return Image.open(b)
  80. def fromqpixmap(im):
  81. return fromqimage(im)
  82. # buffer = QBuffer()
  83. # buffer.open(QIODevice.ReadWrite)
  84. # # im.save(buffer)
  85. # # What if png doesn't support some image features like animation?
  86. # im.save(buffer, 'ppm')
  87. # bytes_io = BytesIO()
  88. # bytes_io.write(buffer.data())
  89. # buffer.close()
  90. # bytes_io.seek(0)
  91. # return Image.open(bytes_io)
  92. def align8to32(bytes, width, mode):
  93. """
  94. converts each scanline of data from 8 bit to 32 bit aligned
  95. """
  96. bits_per_pixel = {"1": 1, "L": 8, "P": 8}[mode]
  97. # calculate bytes per line and the extra padding if needed
  98. bits_per_line = bits_per_pixel * width
  99. full_bytes_per_line, remaining_bits_per_line = divmod(bits_per_line, 8)
  100. bytes_per_line = full_bytes_per_line + (1 if remaining_bits_per_line else 0)
  101. extra_padding = -bytes_per_line % 4
  102. # already 32 bit aligned by luck
  103. if not extra_padding:
  104. return bytes
  105. new_data = []
  106. for i in range(len(bytes) // bytes_per_line):
  107. new_data.append(
  108. bytes[i * bytes_per_line : (i + 1) * bytes_per_line]
  109. + b"\x00" * extra_padding
  110. )
  111. return b"".join(new_data)
  112. def _toqclass_helper(im):
  113. data = None
  114. colortable = None
  115. # handle filename, if given instead of image name
  116. if hasattr(im, "toUtf8"):
  117. # FIXME - is this really the best way to do this?
  118. if py3:
  119. im = str(im.toUtf8(), "utf-8")
  120. else:
  121. im = unicode(im.toUtf8(), "utf-8") # noqa: F821
  122. if isPath(im):
  123. im = Image.open(im)
  124. if im.mode == "1":
  125. format = QImage.Format_Mono
  126. elif im.mode == "L":
  127. format = QImage.Format_Indexed8
  128. colortable = []
  129. for i in range(256):
  130. colortable.append(rgb(i, i, i))
  131. elif im.mode == "P":
  132. format = QImage.Format_Indexed8
  133. colortable = []
  134. palette = im.getpalette()
  135. for i in range(0, len(palette), 3):
  136. colortable.append(rgb(*palette[i : i + 3]))
  137. elif im.mode == "RGB":
  138. data = im.tobytes("raw", "BGRX")
  139. format = QImage.Format_RGB32
  140. elif im.mode == "RGBA":
  141. try:
  142. data = im.tobytes("raw", "BGRA")
  143. except SystemError:
  144. # workaround for earlier versions
  145. r, g, b, a = im.split()
  146. im = Image.merge("RGBA", (b, g, r, a))
  147. format = QImage.Format_ARGB32
  148. else:
  149. raise ValueError("unsupported image mode %r" % im.mode)
  150. __data = data or align8to32(im.tobytes(), im.size[0], im.mode)
  151. return {"data": __data, "im": im, "format": format, "colortable": colortable}
  152. if qt_is_installed:
  153. class ImageQt(QImage):
  154. def __init__(self, im):
  155. """
  156. An PIL image wrapper for Qt. This is a subclass of PyQt's QImage
  157. class.
  158. :param im: A PIL Image object, or a file name (given either as
  159. Python string or a PyQt string object).
  160. """
  161. im_data = _toqclass_helper(im)
  162. # must keep a reference, or Qt will crash!
  163. # All QImage constructors that take data operate on an existing
  164. # buffer, so this buffer has to hang on for the life of the image.
  165. # Fixes https://github.com/python-pillow/Pillow/issues/1370
  166. self.__data = im_data["data"]
  167. QImage.__init__(
  168. self,
  169. self.__data,
  170. im_data["im"].size[0],
  171. im_data["im"].size[1],
  172. im_data["format"],
  173. )
  174. if im_data["colortable"]:
  175. self.setColorTable(im_data["colortable"])
  176. def toqimage(im):
  177. return ImageQt(im)
  178. def toqpixmap(im):
  179. # # This doesn't work. For now using a dumb approach.
  180. # im_data = _toqclass_helper(im)
  181. # result = QPixmap(im_data['im'].size[0], im_data['im'].size[1])
  182. # result.loadFromData(im_data['data'])
  183. # Fix some strange bug that causes
  184. if im.mode == "RGB":
  185. im = im.convert("RGBA")
  186. qimage = toqimage(im)
  187. return QPixmap.fromImage(qimage)