ImageQt.py 5.7 KB

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