admin / Synapse-Cortex
publicSelf Hosted ITSM Tool with RBAC/Tenanting and MFA
Synapse-Cortex / synapse-cortex / .venv / Lib / site-packages / PIL / ImageQt.py
6903 B · main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | # # The Python Imaging Library. # $Id$ # # a simple Qt image interface. # # history: # 2006-06-03 fl: created # 2006-06-04 fl: inherit from QImage instead of wrapping it # 2006-06-05 fl: removed toimage helper; move string support to ImageQt # 2013-11-13 fl: add support for Qt5 ([email protected]) # # Copyright (c) 2006 by Secret Labs AB # Copyright (c) 2006 by Fredrik Lundh # # See the README file for information on usage and redistribution. # from __future__ import annotations import sys from io import BytesIO from . import Image from ._util import is_path TYPE_CHECKING = False if TYPE_CHECKING: from collections.abc import Callable from typing import Any from . import ImageFile QBuffer: type qt_version: str | None qt_versions = [ ["6", "PyQt6"], ["side6", "PySide6"], ] # If a version has already been imported, attempt it first qt_versions.sort(key=lambda version: version[1] in sys.modules, reverse=True) for version, qt_module in qt_versions: try: qRgba: Callable[[int, int, int, int], int] if qt_module == "PyQt6": from PyQt6.QtCore import QBuffer, QByteArray, QIODevice from PyQt6.QtGui import QImage, QPixmap, qRgba elif qt_module == "PySide6": from PySide6.QtCore import ( # type: ignore[assignment] QBuffer, QByteArray, QIODevice, ) from PySide6.QtGui import QImage, QPixmap, qRgba # type: ignore[assignment] except (ImportError, RuntimeError): continue qt_is_installed = True qt_version = version break else: qt_is_installed = False qt_version = None def rgb(r: int, g: int, b: int, a: int = 255) -> int: """(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 def fromqimage(im: QImage | QPixmap) -> ImageFile.ImageFile: """ :param im: QImage or PIL ImageQt object """ buffer = QBuffer() qt_openmode: object if qt_version == "6": try: qt_openmode = getattr(QIODevice, "OpenModeFlag") except AttributeError: qt_openmode = getattr(QIODevice, "OpenMode") else: qt_openmode = QIODevice buffer.open(getattr(qt_openmode, "ReadWrite")) # preserve alpha channel with png # otherwise ppm is more friendly with Image.open if im.hasAlphaChannel(): im.save(buffer, "png") else: im.save(buffer, "ppm") b = BytesIO() b.write(buffer.data()) buffer.close() b.seek(0) return Image.open(b) def fromqpixmap(im: QPixmap) -> ImageFile.ImageFile: return fromqimage(im) def align8to32(bytes: bytes, width: int, mode: str) -> bytes: """ converts each scanline of data from 8 bit to 32 bit aligned """ bits_per_pixel = {"1": 1, "L": 8, "P": 8, "I;16": 16}[mode] # calculate bytes per line and the extra padding if needed bits_per_line = bits_per_pixel * width full_bytes_per_line, remaining_bits_per_line = divmod(bits_per_line, 8) bytes_per_line = full_bytes_per_line + (1 if remaining_bits_per_line else 0) extra_padding = -bytes_per_line % 4 # already 32 bit aligned by luck if not extra_padding: return bytes new_data = [ bytes[i * bytes_per_line : (i + 1) * bytes_per_line] + b"\x00" * extra_padding for i in range(len(bytes) // bytes_per_line) ] return b"".join(new_data) def _toqclass_helper(im: Image.Image | str | QByteArray) -> dict[str, Any]: data = None colortable = None exclusive_fp = False # handle filename, if given instead of image name if hasattr(im, "toUtf8"): # FIXME - is this really the best way to do this? im = str(im.toUtf8(), "utf-8") if is_path(im): im = Image.open(im) exclusive_fp = True assert isinstance(im, Image.Image) qt_format = getattr(QImage, "Format") if qt_version == "6" else QImage if im.mode == "1": format = getattr(qt_format, "Format_Mono") elif im.mode == "L": format = getattr(qt_format, "Format_Indexed8") colortable = [rgb(i, i, i) for i in range(256)] elif im.mode == "P": format = getattr(qt_format, "Format_Indexed8") palette = im.getpalette() assert palette is not None colortable = [rgb(*palette[i : i + 3]) for i in range(0, len(palette), 3)] elif im.mode == "RGB": # Populate the 4th channel with 255 im = im.convert("RGBA") data = im.tobytes("raw", "BGRA") format = getattr(qt_format, "Format_RGB32") elif im.mode == "RGBA": data = im.tobytes("raw", "BGRA") format = getattr(qt_format, "Format_ARGB32") elif im.mode == "I;16": im = im.point(lambda i: i * 256) format = getattr(qt_format, "Format_Grayscale16") else: if exclusive_fp: im.close() msg = f"unsupported image mode {repr(im.mode)}" raise ValueError(msg) size = im.size __data = data or align8to32(im.tobytes(), size[0], im.mode) if exclusive_fp: im.close() return {"data": __data, "size": size, "format": format, "colortable": colortable} if qt_is_installed: class ImageQt(QImage): def __init__(self, im: Image.Image | str | QByteArray) -> None: """ 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 string or a PyQt string object). """ im_data = _toqclass_helper(im) # must keep a reference, or Qt will crash! # All QImage constructors that take data operate on an existing # buffer, so this buffer has to hang on for the life of the image. # Fixes https://github.com/python-pillow/Pillow/issues/1370 self.__data = im_data["data"] super().__init__( self.__data, im_data["size"][0], im_data["size"][1], im_data["format"], ) if im_data["colortable"]: self.setColorTable(im_data["colortable"]) def toqimage(im: Image.Image | str | QByteArray) -> ImageQt: return ImageQt(im) def toqpixmap(im: Image.Image | str | QByteArray) -> QPixmap: qimage = toqimage(im) pixmap = getattr(QPixmap, "fromImage")(qimage) if qt_version == "6": pixmap.detach() return pixmap |