You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

218 lines
6.4 KiB

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