mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-11-04 01:47:47 +03:00 
			
		
		
		
	Added type hints
This commit is contained in:
		
							parent
							
								
									4721c31b19
								
							
						
					
					
						commit
						53bbfea763
					
				| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
from __future__ import annotations
 | 
			
		||||
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from typing import TYPE_CHECKING, Union
 | 
			
		||||
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -8,6 +9,20 @@ from PIL import Image, ImageQt
 | 
			
		|||
 | 
			
		||||
from .helper import assert_image_equal_tofile, assert_image_similar, hopper
 | 
			
		||||
 | 
			
		||||
if TYPE_CHECKING:
 | 
			
		||||
    import PyQt6
 | 
			
		||||
    import PySide6
 | 
			
		||||
 | 
			
		||||
    QApplication = Union[PyQt6.QtWidgets.QApplication, PySide6.QtWidgets.QApplication]
 | 
			
		||||
    QHBoxLayout = Union[PyQt6.QtWidgets.QHBoxLayout, PySide6.QtWidgets.QHBoxLayout]
 | 
			
		||||
    QImage = Union[PyQt6.QtGui.QImage, PySide6.QtGui.QImage]
 | 
			
		||||
    QLabel = Union[PyQt6.QtWidgets.QLabel, PySide6.QtWidgets.QLabel]
 | 
			
		||||
    QPainter = Union[PyQt6.QtGui.QPainter, PySide6.QtGui.QPainter]
 | 
			
		||||
    QPixmap = Union[PyQt6.QtGui.QPixmap, PySide6.QtGui.QPixmap]
 | 
			
		||||
    QPoint = Union[PyQt6.QtCore.QPoint, PySide6.QtCore.QPoint]
 | 
			
		||||
    QRegion = Union[PyQt6.QtGui.QRegion, PySide6.QtGui.QRegion]
 | 
			
		||||
    QWidget = Union[PyQt6.QtWidgets.QWidget, PySide6.QtWidgets.QWidget]
 | 
			
		||||
 | 
			
		||||
if ImageQt.qt_is_installed:
 | 
			
		||||
    from PIL.ImageQt import QPixmap
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -20,7 +35,7 @@ if ImageQt.qt_is_installed:
 | 
			
		|||
        from PySide6.QtGui import QImage, QPainter, QRegion
 | 
			
		||||
        from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
 | 
			
		||||
 | 
			
		||||
    class Example(QWidget):
 | 
			
		||||
    class Example(QWidget):  # type: ignore[misc]
 | 
			
		||||
        def __init__(self) -> None:
 | 
			
		||||
            super().__init__()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -28,11 +43,12 @@ if ImageQt.qt_is_installed:
 | 
			
		|||
 | 
			
		||||
            qimage = ImageQt.ImageQt(img)
 | 
			
		||||
 | 
			
		||||
            pixmap1 = ImageQt.QPixmap.fromImage(qimage)
 | 
			
		||||
            pixmap1 = getattr(ImageQt.QPixmap, "fromImage")(qimage)
 | 
			
		||||
 | 
			
		||||
            QHBoxLayout(self)  # hbox
 | 
			
		||||
            # hbox
 | 
			
		||||
            QHBoxLayout(self)  # type: ignore[operator]
 | 
			
		||||
 | 
			
		||||
            lbl = QLabel(self)
 | 
			
		||||
            lbl = QLabel(self)  # type: ignore[operator]
 | 
			
		||||
            # Segfault in the problem
 | 
			
		||||
            lbl.setPixmap(pixmap1.copy())
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -46,7 +62,7 @@ def roundtrip(expected: Image.Image) -> None:
 | 
			
		|||
@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")
 | 
			
		||||
def test_sanity(tmp_path: Path) -> None:
 | 
			
		||||
    # Segfault test
 | 
			
		||||
    app: QApplication | None = QApplication([])
 | 
			
		||||
    app: QApplication | None = QApplication([])  # type: ignore[operator]
 | 
			
		||||
    ex = Example()
 | 
			
		||||
    assert app  # Silence warning
 | 
			
		||||
    assert ex  # Silence warning
 | 
			
		||||
| 
						 | 
				
			
			@ -56,7 +72,7 @@ def test_sanity(tmp_path: Path) -> None:
 | 
			
		|||
        im = hopper(mode)
 | 
			
		||||
        data = ImageQt.toqpixmap(im)
 | 
			
		||||
 | 
			
		||||
        assert isinstance(data, QPixmap)
 | 
			
		||||
        assert data.__class__.__name__ == "QPixmap"
 | 
			
		||||
        assert not data.isNull()
 | 
			
		||||
 | 
			
		||||
        # Test saving the file
 | 
			
		||||
| 
						 | 
				
			
			@ -64,14 +80,14 @@ def test_sanity(tmp_path: Path) -> None:
 | 
			
		|||
        data.save(tempfile)
 | 
			
		||||
 | 
			
		||||
        # Render the image
 | 
			
		||||
        qimage = ImageQt.ImageQt(im)
 | 
			
		||||
        data = QPixmap.fromImage(qimage)
 | 
			
		||||
        qt_format = QImage.Format if ImageQt.qt_version == "6" else QImage
 | 
			
		||||
        qimage = QImage(128, 128, qt_format.Format_ARGB32)
 | 
			
		||||
        painter = QPainter(qimage)
 | 
			
		||||
        image_label = QLabel()
 | 
			
		||||
        imageqt = ImageQt.ImageQt(im)
 | 
			
		||||
        data = getattr(QPixmap, "fromImage")(imageqt)
 | 
			
		||||
        qt_format = getattr(QImage, "Format") if ImageQt.qt_version == "6" else QImage
 | 
			
		||||
        qimage = QImage(128, 128, getattr(qt_format, "Format_ARGB32"))  # type: ignore[operator]
 | 
			
		||||
        painter = QPainter(qimage)  # type: ignore[operator]
 | 
			
		||||
        image_label = QLabel()  # type: ignore[operator]
 | 
			
		||||
        image_label.setPixmap(data)
 | 
			
		||||
        image_label.render(painter, QPoint(0, 0), QRegion(0, 0, 128, 128))
 | 
			
		||||
        image_label.render(painter, QPoint(0, 0), QRegion(0, 0, 128, 128))  # type: ignore[operator]
 | 
			
		||||
        painter.end()
 | 
			
		||||
        rendered_tempfile = str(tmp_path / f"temp_rendered_{mode}.png")
 | 
			
		||||
        qimage.save(rendered_tempfile)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,7 +21,7 @@ def test_sanity(mode: str, tmp_path: Path) -> None:
 | 
			
		|||
    src = hopper(mode)
 | 
			
		||||
    data = ImageQt.toqimage(src)
 | 
			
		||||
 | 
			
		||||
    assert isinstance(data, QImage)
 | 
			
		||||
    assert isinstance(data, QImage)  # type: ignore[arg-type, misc]
 | 
			
		||||
    assert not data.isNull()
 | 
			
		||||
 | 
			
		||||
    # reload directly from the qimage
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -166,5 +166,4 @@ warn_unused_ignores = true
 | 
			
		|||
exclude = [
 | 
			
		||||
  '^Tests/oss-fuzz/fuzz_font.py$',
 | 
			
		||||
  '^Tests/oss-fuzz/fuzz_pillow.py$',
 | 
			
		||||
  '^Tests/test_qt_image_qapplication.py$',
 | 
			
		||||
]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -71,7 +71,7 @@ def Ghostscript(
 | 
			
		|||
    fp: IO[bytes],
 | 
			
		||||
    scale: int = 1,
 | 
			
		||||
    transparency: bool = False,
 | 
			
		||||
) -> Image.Image:
 | 
			
		||||
) -> Image.core.ImagingCore:
 | 
			
		||||
    """Render an image using Ghostscript"""
 | 
			
		||||
    global gs_binary
 | 
			
		||||
    if not has_ghostscript():
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -223,7 +223,7 @@ if TYPE_CHECKING:
 | 
			
		|||
 | 
			
		||||
    from IPython.lib.pretty import PrettyPrinter
 | 
			
		||||
 | 
			
		||||
    from . import ImageFile, ImageFilter, ImagePalette, TiffImagePlugin
 | 
			
		||||
    from . import ImageFile, ImageFilter, ImagePalette, ImageQt, TiffImagePlugin
 | 
			
		||||
    from ._typing import NumpyArray, StrOrBytesPath, TypeGuard
 | 
			
		||||
ID: list[str] = []
 | 
			
		||||
OPEN: dict[
 | 
			
		||||
| 
						 | 
				
			
			@ -2978,7 +2978,7 @@ class Image:
 | 
			
		|||
        self.load()
 | 
			
		||||
        return self._new(self.im.effect_spread(distance))
 | 
			
		||||
 | 
			
		||||
    def toqimage(self):
 | 
			
		||||
    def toqimage(self) -> ImageQt.ImageQt:
 | 
			
		||||
        """Returns a QImage copy of this image"""
 | 
			
		||||
        from . import ImageQt
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2987,7 +2987,7 @@ class Image:
 | 
			
		|||
            raise ImportError(msg)
 | 
			
		||||
        return ImageQt.toqimage(self)
 | 
			
		||||
 | 
			
		||||
    def toqpixmap(self):
 | 
			
		||||
    def toqpixmap(self) -> ImageQt.QPixmap:
 | 
			
		||||
        """Returns a QPixmap copy of this image"""
 | 
			
		||||
        from . import ImageQt
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3314,7 +3314,7 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
 | 
			
		|||
    return frombuffer(mode, size, obj, "raw", rawmode, 0, 1)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def fromqimage(im) -> ImageFile.ImageFile:
 | 
			
		||||
def fromqimage(im: ImageQt.QImage) -> ImageFile.ImageFile:
 | 
			
		||||
    """Creates an image instance from a QImage image"""
 | 
			
		||||
    from . import ImageQt
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3324,7 +3324,7 @@ def fromqimage(im) -> ImageFile.ImageFile:
 | 
			
		|||
    return ImageQt.fromqimage(im)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def fromqpixmap(im) -> ImageFile.ImageFile:
 | 
			
		||||
def fromqpixmap(im: ImageQt.QPixmap) -> ImageFile.ImageFile:
 | 
			
		||||
    """Creates an image instance from a QPixmap image"""
 | 
			
		||||
    from . import ImageQt
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,14 +19,23 @@ from __future__ import annotations
 | 
			
		|||
 | 
			
		||||
import sys
 | 
			
		||||
from io import BytesIO
 | 
			
		||||
from typing import TYPE_CHECKING, Callable
 | 
			
		||||
from typing import TYPE_CHECKING, Any, Callable, Union
 | 
			
		||||
 | 
			
		||||
from . import Image
 | 
			
		||||
from ._util import is_path
 | 
			
		||||
 | 
			
		||||
if TYPE_CHECKING:
 | 
			
		||||
    import PyQt6
 | 
			
		||||
    import PySide6
 | 
			
		||||
 | 
			
		||||
    from . import ImageFile
 | 
			
		||||
 | 
			
		||||
    QBuffer: type
 | 
			
		||||
    QByteArray = Union[PyQt6.QtCore.QByteArray, PySide6.QtCore.QByteArray]
 | 
			
		||||
    QIODevice = Union[PyQt6.QtCore.QIODevice, PySide6.QtCore.QIODevice]
 | 
			
		||||
    QImage = Union[PyQt6.QtGui.QImage, PySide6.QtGui.QImage]
 | 
			
		||||
    QPixmap = Union[PyQt6.QtGui.QPixmap, PySide6.QtGui.QPixmap]
 | 
			
		||||
 | 
			
		||||
qt_version: str | None
 | 
			
		||||
qt_versions = [
 | 
			
		||||
    ["6", "PyQt6"],
 | 
			
		||||
| 
						 | 
				
			
			@ -37,10 +46,6 @@ qt_versions = [
 | 
			
		|||
qt_versions.sort(key=lambda version: version[1] in sys.modules, reverse=True)
 | 
			
		||||
for version, qt_module in qt_versions:
 | 
			
		||||
    try:
 | 
			
		||||
        QBuffer: type
 | 
			
		||||
        QIODevice: type
 | 
			
		||||
        QImage: type
 | 
			
		||||
        QPixmap: type
 | 
			
		||||
        qRgba: Callable[[int, int, int, int], int]
 | 
			
		||||
        if qt_module == "PyQt6":
 | 
			
		||||
            from PyQt6.QtCore import QBuffer, QIODevice
 | 
			
		||||
| 
						 | 
				
			
			@ -65,19 +70,20 @@ def rgb(r: int, g: int, b: int, a: int = 255) -> int:
 | 
			
		|||
    return qRgba(r, g, b, a) & 0xFFFFFFFF
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def fromqimage(im):
 | 
			
		||||
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 = QIODevice.OpenModeFlag
 | 
			
		||||
            qt_openmode = getattr(QIODevice, "OpenModeFlag")
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            qt_openmode = QIODevice.OpenMode
 | 
			
		||||
            qt_openmode = getattr(QIODevice, "OpenMode")
 | 
			
		||||
    else:
 | 
			
		||||
        qt_openmode = QIODevice
 | 
			
		||||
    buffer.open(qt_openmode.ReadWrite)
 | 
			
		||||
    buffer.open(getattr(qt_openmode, "ReadWrite"))
 | 
			
		||||
    # preserve alpha channel with png
 | 
			
		||||
    # otherwise ppm is more friendly with Image.open
 | 
			
		||||
    if im.hasAlphaChannel():
 | 
			
		||||
| 
						 | 
				
			
			@ -93,7 +99,7 @@ def fromqimage(im):
 | 
			
		|||
    return Image.open(b)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def fromqpixmap(im) -> ImageFile.ImageFile:
 | 
			
		||||
def fromqpixmap(im: QPixmap) -> ImageFile.ImageFile:
 | 
			
		||||
    return fromqimage(im)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -123,7 +129,7 @@ def align8to32(bytes: bytes, width: int, mode: str) -> bytes:
 | 
			
		|||
    return b"".join(new_data)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _toqclass_helper(im):
 | 
			
		||||
def _toqclass_helper(im: Image.Image | str | QByteArray) -> dict[str, Any]:
 | 
			
		||||
    data = None
 | 
			
		||||
    colortable = None
 | 
			
		||||
    exclusive_fp = False
 | 
			
		||||
| 
						 | 
				
			
			@ -135,30 +141,32 @@ def _toqclass_helper(im):
 | 
			
		|||
    if is_path(im):
 | 
			
		||||
        im = Image.open(im)
 | 
			
		||||
        exclusive_fp = True
 | 
			
		||||
    assert isinstance(im, Image.Image)
 | 
			
		||||
 | 
			
		||||
    qt_format = QImage.Format if qt_version == "6" else QImage
 | 
			
		||||
    qt_format = getattr(QImage, "Format") if qt_version == "6" else QImage
 | 
			
		||||
    if im.mode == "1":
 | 
			
		||||
        format = qt_format.Format_Mono
 | 
			
		||||
        format = getattr(qt_format, "Format_Mono")
 | 
			
		||||
    elif im.mode == "L":
 | 
			
		||||
        format = qt_format.Format_Indexed8
 | 
			
		||||
        format = getattr(qt_format, "Format_Indexed8")
 | 
			
		||||
        colortable = [rgb(i, i, i) for i in range(256)]
 | 
			
		||||
    elif im.mode == "P":
 | 
			
		||||
        format = qt_format.Format_Indexed8
 | 
			
		||||
        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 = qt_format.Format_RGB32
 | 
			
		||||
        format = getattr(qt_format, "Format_RGB32")
 | 
			
		||||
    elif im.mode == "RGBA":
 | 
			
		||||
        data = im.tobytes("raw", "BGRA")
 | 
			
		||||
        format = qt_format.Format_ARGB32
 | 
			
		||||
        format = getattr(qt_format, "Format_ARGB32")
 | 
			
		||||
    elif im.mode == "I;16":
 | 
			
		||||
        im = im.point(lambda i: i * 256)
 | 
			
		||||
 | 
			
		||||
        format = qt_format.Format_Grayscale16
 | 
			
		||||
        format = getattr(qt_format, "Format_Grayscale16")
 | 
			
		||||
    else:
 | 
			
		||||
        if exclusive_fp:
 | 
			
		||||
            im.close()
 | 
			
		||||
| 
						 | 
				
			
			@ -174,8 +182,8 @@ def _toqclass_helper(im):
 | 
			
		|||
 | 
			
		||||
if qt_is_installed:
 | 
			
		||||
 | 
			
		||||
    class ImageQt(QImage):
 | 
			
		||||
        def __init__(self, im) -> None:
 | 
			
		||||
    class ImageQt(QImage):  # type: ignore[misc]
 | 
			
		||||
        def __init__(self, im: Image.Image | str | QByteArray) -> None:
 | 
			
		||||
            """
 | 
			
		||||
            An PIL image wrapper for Qt.  This is a subclass of PyQt's QImage
 | 
			
		||||
            class.
 | 
			
		||||
| 
						 | 
				
			
			@ -199,10 +207,10 @@ if qt_is_installed:
 | 
			
		|||
                self.setColorTable(im_data["colortable"])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def toqimage(im) -> ImageQt:
 | 
			
		||||
def toqimage(im: Image.Image | str | QByteArray) -> ImageQt:
 | 
			
		||||
    return ImageQt(im)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def toqpixmap(im):
 | 
			
		||||
def toqpixmap(im: Image.Image | str | QByteArray) -> QPixmap:
 | 
			
		||||
    qimage = toqimage(im)
 | 
			
		||||
    return QPixmap.fromImage(qimage)
 | 
			
		||||
    return getattr(QPixmap, "fromImage")(qimage)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user