Team Ai
Datasetpublic

codekingpro/portable-devtools

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