Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
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.

ImageQt.py 6.7KB

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