mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-31 07:57:27 +03:00 
			
		
		
		
	Added type hints
This commit is contained in:
		
							parent
							
								
									94a8fccfa7
								
							
						
					
					
						commit
						8a05e32336
					
				|  | @ -2,11 +2,11 @@ from __future__ import annotations | |||
| 
 | ||||
| import warnings | ||||
| from io import BytesIO | ||||
| from typing import Any, cast | ||||
| from typing import Any | ||||
| 
 | ||||
| import pytest | ||||
| 
 | ||||
| from PIL import Image, MpoImagePlugin | ||||
| from PIL import Image, ImageFile, MpoImagePlugin | ||||
| 
 | ||||
| from .helper import ( | ||||
|     assert_image_equal, | ||||
|  | @ -20,11 +20,11 @@ test_files = ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"] | |||
| pytestmark = skip_unless_feature("jpg") | ||||
| 
 | ||||
| 
 | ||||
| def roundtrip(im: Image.Image, **options: Any) -> MpoImagePlugin.MpoImageFile: | ||||
| def roundtrip(im: Image.Image, **options: Any) -> ImageFile.ImageFile: | ||||
|     out = BytesIO() | ||||
|     im.save(out, "MPO", **options) | ||||
|     out.seek(0) | ||||
|     return cast(MpoImagePlugin.MpoImageFile, Image.open(out)) | ||||
|     return Image.open(out) | ||||
| 
 | ||||
| 
 | ||||
| @pytest.mark.parametrize("test_file", test_files) | ||||
|  | @ -226,6 +226,12 @@ def test_eoferror() -> None: | |||
|         im.seek(n_frames - 1) | ||||
| 
 | ||||
| 
 | ||||
| def test_adopt_jpeg() -> None: | ||||
|     with Image.open("Tests/images/hopper.jpg") as im: | ||||
|         with pytest.raises(ValueError): | ||||
|             MpoImagePlugin.MpoImageFile.adopt(im) | ||||
| 
 | ||||
| 
 | ||||
| def test_ultra_hdr() -> None: | ||||
|     with Image.open("Tests/images/ultrahdr.jpg") as im: | ||||
|         assert im.format == "JPEG" | ||||
|  | @ -275,6 +281,8 @@ def test_save_all() -> None: | |||
|     im_reloaded = roundtrip(im, save_all=True, append_images=[im2]) | ||||
| 
 | ||||
|     assert_image_equal(im, im_reloaded) | ||||
|     assert isinstance(im_reloaded, MpoImagePlugin.MpoImageFile) | ||||
|     assert im_reloaded.mpinfo is not None | ||||
|     assert im_reloaded.mpinfo[45056] == b"0100" | ||||
| 
 | ||||
|     im_reloaded.seek(1) | ||||
|  |  | |||
|  | @ -90,6 +90,7 @@ class TestImageFile: | |||
|             data = f.read() | ||||
|         with ImageFile.Parser() as p: | ||||
|             p.feed(data) | ||||
|             assert p.image is not None | ||||
|             assert (48, 48) == p.image.size | ||||
| 
 | ||||
|     @skip_unless_feature("webp") | ||||
|  | @ -103,6 +104,7 @@ class TestImageFile: | |||
|                 assert not p.image | ||||
| 
 | ||||
|                 p.feed(f.read()) | ||||
|             assert p.image is not None | ||||
|             assert (128, 128) == p.image.size | ||||
| 
 | ||||
|     @skip_unless_feature("zlib") | ||||
|  | @ -393,8 +395,9 @@ class TestPyEncoder(CodecsTest): | |||
|         with pytest.raises(NotImplementedError): | ||||
|             encoder.encode_to_pyfd() | ||||
| 
 | ||||
|         fh = BytesIO() | ||||
|         with pytest.raises(NotImplementedError): | ||||
|             encoder.encode_to_file(None, None) | ||||
|             encoder.encode_to_file(fh, 0) | ||||
| 
 | ||||
|     def test_zero_height(self) -> None: | ||||
|         with pytest.raises(UnidentifiedImageError): | ||||
|  |  | |||
|  | @ -109,3 +109,6 @@ def test_bitmapimage() -> None: | |||
| 
 | ||||
|     # reloaded = ImageTk.getimage(im_tk) | ||||
|     # assert_image_equal(reloaded, im) | ||||
| 
 | ||||
|     with pytest.raises(ValueError): | ||||
|         ImageTk.BitmapImage() | ||||
|  |  | |||
|  | @ -313,6 +313,7 @@ class _BLPBaseDecoder(ImageFile.PyDecoder): | |||
|         self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4)) | ||||
| 
 | ||||
|     def _safe_read(self, length: int) -> bytes: | ||||
|         assert self.fd is not None | ||||
|         return ImageFile._safe_read(self.fd, length) | ||||
| 
 | ||||
|     def _read_palette(self) -> list[tuple[int, int, int, int]]: | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ | |||
| from __future__ import annotations | ||||
| 
 | ||||
| import os | ||||
| from typing import IO | ||||
| from typing import IO, Any | ||||
| 
 | ||||
| from . import Image, ImageFile, ImagePalette | ||||
| from ._binary import i16le as i16 | ||||
|  | @ -72,16 +72,20 @@ class BmpImageFile(ImageFile.ImageFile): | |||
|     for k, v in COMPRESSIONS.items(): | ||||
|         vars()[k] = v | ||||
| 
 | ||||
|     def _bitmap(self, header=0, offset=0): | ||||
|     def _bitmap(self, header: int = 0, offset: int = 0) -> None: | ||||
|         """Read relevant info about the BMP""" | ||||
|         read, seek = self.fp.read, self.fp.seek | ||||
|         if header: | ||||
|             seek(header) | ||||
|         # read bmp header size @offset 14 (this is part of the header size) | ||||
|         file_info = {"header_size": i32(read(4)), "direction": -1} | ||||
|         file_info: dict[str, bool | int | tuple[int, ...]] = { | ||||
|             "header_size": i32(read(4)), | ||||
|             "direction": -1, | ||||
|         } | ||||
| 
 | ||||
|         # -------------------- If requested, read header at a specific position | ||||
|         # read the rest of the bmp header, without its size | ||||
|         assert isinstance(file_info["header_size"], int) | ||||
|         header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4) | ||||
| 
 | ||||
|         # ------------------------------- Windows Bitmap v2, IBM OS/2 Bitmap v1 | ||||
|  | @ -92,7 +96,7 @@ class BmpImageFile(ImageFile.ImageFile): | |||
|             file_info["height"] = i16(header_data, 2) | ||||
|             file_info["planes"] = i16(header_data, 4) | ||||
|             file_info["bits"] = i16(header_data, 6) | ||||
|             file_info["compression"] = self.RAW | ||||
|             file_info["compression"] = self.COMPRESSIONS["RAW"] | ||||
|             file_info["palette_padding"] = 3 | ||||
| 
 | ||||
|         # --------------------------------------------- Windows Bitmap v3 to v5 | ||||
|  | @ -122,8 +126,9 @@ class BmpImageFile(ImageFile.ImageFile): | |||
|             ) | ||||
|             file_info["colors"] = i32(header_data, 28) | ||||
|             file_info["palette_padding"] = 4 | ||||
|             assert isinstance(file_info["pixels_per_meter"], tuple) | ||||
|             self.info["dpi"] = tuple(x / 39.3701 for x in file_info["pixels_per_meter"]) | ||||
|             if file_info["compression"] == self.BITFIELDS: | ||||
|             if file_info["compression"] == self.COMPRESSIONS["BITFIELDS"]: | ||||
|                 masks = ["r_mask", "g_mask", "b_mask"] | ||||
|                 if len(header_data) >= 48: | ||||
|                     if len(header_data) >= 52: | ||||
|  | @ -144,6 +149,10 @@ class BmpImageFile(ImageFile.ImageFile): | |||
|                     file_info["a_mask"] = 0x0 | ||||
|                     for mask in masks: | ||||
|                         file_info[mask] = i32(read(4)) | ||||
|                 assert isinstance(file_info["r_mask"], int) | ||||
|                 assert isinstance(file_info["g_mask"], int) | ||||
|                 assert isinstance(file_info["b_mask"], int) | ||||
|                 assert isinstance(file_info["a_mask"], int) | ||||
|                 file_info["rgb_mask"] = ( | ||||
|                     file_info["r_mask"], | ||||
|                     file_info["g_mask"], | ||||
|  | @ -164,24 +173,26 @@ class BmpImageFile(ImageFile.ImageFile): | |||
|         self._size = file_info["width"], file_info["height"] | ||||
| 
 | ||||
|         # ------- If color count was not found in the header, compute from bits | ||||
|         assert isinstance(file_info["bits"], int) | ||||
|         file_info["colors"] = ( | ||||
|             file_info["colors"] | ||||
|             if file_info.get("colors", 0) | ||||
|             else (1 << file_info["bits"]) | ||||
|         ) | ||||
|         assert isinstance(file_info["colors"], int) | ||||
|         if offset == 14 + file_info["header_size"] and file_info["bits"] <= 8: | ||||
|             offset += 4 * file_info["colors"] | ||||
| 
 | ||||
|         # ---------------------- Check bit depth for unusual unsupported values | ||||
|         self._mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None)) | ||||
|         if self.mode is None: | ||||
|         self._mode, raw_mode = BIT2MODE.get(file_info["bits"], ("", "")) | ||||
|         if not self.mode: | ||||
|             msg = f"Unsupported BMP pixel depth ({file_info['bits']})" | ||||
|             raise OSError(msg) | ||||
| 
 | ||||
|         # ---------------- Process BMP with Bitfields compression (not palette) | ||||
|         decoder_name = "raw" | ||||
|         if file_info["compression"] == self.BITFIELDS: | ||||
|             SUPPORTED = { | ||||
|         if file_info["compression"] == self.COMPRESSIONS["BITFIELDS"]: | ||||
|             SUPPORTED: dict[int, list[tuple[int, ...]]] = { | ||||
|                 32: [ | ||||
|                     (0xFF0000, 0xFF00, 0xFF, 0x0), | ||||
|                     (0xFF000000, 0xFF0000, 0xFF00, 0x0), | ||||
|  | @ -213,12 +224,14 @@ class BmpImageFile(ImageFile.ImageFile): | |||
|                     file_info["bits"] == 32 | ||||
|                     and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]] | ||||
|                 ): | ||||
|                     assert isinstance(file_info["rgba_mask"], tuple) | ||||
|                     raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])] | ||||
|                     self._mode = "RGBA" if "A" in raw_mode else self.mode | ||||
|                 elif ( | ||||
|                     file_info["bits"] in (24, 16) | ||||
|                     and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]] | ||||
|                 ): | ||||
|                     assert isinstance(file_info["rgb_mask"], tuple) | ||||
|                     raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])] | ||||
|                 else: | ||||
|                     msg = "Unsupported BMP bitfields layout" | ||||
|  | @ -226,10 +239,13 @@ class BmpImageFile(ImageFile.ImageFile): | |||
|             else: | ||||
|                 msg = "Unsupported BMP bitfields layout" | ||||
|                 raise OSError(msg) | ||||
|         elif file_info["compression"] == self.RAW: | ||||
|         elif file_info["compression"] == self.COMPRESSIONS["RAW"]: | ||||
|             if file_info["bits"] == 32 and header == 22:  # 32-bit .cur offset | ||||
|                 raw_mode, self._mode = "BGRA", "RGBA" | ||||
|         elif file_info["compression"] in (self.RLE8, self.RLE4): | ||||
|         elif file_info["compression"] in ( | ||||
|             self.COMPRESSIONS["RLE8"], | ||||
|             self.COMPRESSIONS["RLE4"], | ||||
|         ): | ||||
|             decoder_name = "bmp_rle" | ||||
|         else: | ||||
|             msg = f"Unsupported BMP compression ({file_info['compression']})" | ||||
|  | @ -242,6 +258,7 @@ class BmpImageFile(ImageFile.ImageFile): | |||
|                 msg = f"Unsupported BMP Palette size ({file_info['colors']})" | ||||
|                 raise OSError(msg) | ||||
|             else: | ||||
|                 assert isinstance(file_info["palette_padding"], int) | ||||
|                 padding = file_info["palette_padding"] | ||||
|                 palette = read(padding * file_info["colors"]) | ||||
|                 grayscale = True | ||||
|  | @ -269,10 +286,11 @@ class BmpImageFile(ImageFile.ImageFile): | |||
| 
 | ||||
|         # ---------------------------- Finally set the tile data for the plugin | ||||
|         self.info["compression"] = file_info["compression"] | ||||
|         args = [raw_mode] | ||||
|         args: list[Any] = [raw_mode] | ||||
|         if decoder_name == "bmp_rle": | ||||
|             args.append(file_info["compression"] == self.RLE4) | ||||
|             args.append(file_info["compression"] == self.COMPRESSIONS["RLE4"]) | ||||
|         else: | ||||
|             assert isinstance(file_info["width"], int) | ||||
|             args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3)) | ||||
|         args.append(file_info["direction"]) | ||||
|         self.tile = [ | ||||
|  |  | |||
|  | @ -485,7 +485,7 @@ class Parser: | |||
| 
 | ||||
|                 self.image = im | ||||
| 
 | ||||
|     def __enter__(self): | ||||
|     def __enter__(self) -> Parser: | ||||
|         return self | ||||
| 
 | ||||
|     def __exit__(self, *args: object) -> None: | ||||
|  | @ -580,7 +580,7 @@ def _encode_tile(im, fp, tile: list[_Tile], bufsize, fh, exc=None): | |||
|             encoder.cleanup() | ||||
| 
 | ||||
| 
 | ||||
| def _safe_read(fp, size): | ||||
| def _safe_read(fp: IO[bytes], size: int) -> bytes: | ||||
|     """ | ||||
|     Reads large blocks in a safe way.  Unlike fp.read(n), this function | ||||
|     doesn't trust the user.  If the requested size is larger than | ||||
|  | @ -601,18 +601,18 @@ def _safe_read(fp, size): | |||
|             msg = "Truncated File Read" | ||||
|             raise OSError(msg) | ||||
|         return data | ||||
|     data = [] | ||||
|     blocks: list[bytes] = [] | ||||
|     remaining_size = size | ||||
|     while remaining_size > 0: | ||||
|         block = fp.read(min(remaining_size, SAFEBLOCK)) | ||||
|         if not block: | ||||
|             break | ||||
|         data.append(block) | ||||
|         blocks.append(block) | ||||
|         remaining_size -= len(block) | ||||
|     if sum(len(d) for d in data) < size: | ||||
|     if sum(len(block) for block in blocks) < size: | ||||
|         msg = "Truncated File Read" | ||||
|         raise OSError(msg) | ||||
|     return b"".join(data) | ||||
|     return b"".join(blocks) | ||||
| 
 | ||||
| 
 | ||||
| class PyCodecState: | ||||
|  | @ -636,7 +636,7 @@ class PyCodec: | |||
|         self.mode = mode | ||||
|         self.init(args) | ||||
| 
 | ||||
|     def init(self, args): | ||||
|     def init(self, args) -> None: | ||||
|         """ | ||||
|         Override to perform codec specific initialization | ||||
| 
 | ||||
|  | @ -653,7 +653,7 @@ class PyCodec: | |||
|         """ | ||||
|         pass | ||||
| 
 | ||||
|     def setfd(self, fd): | ||||
|     def setfd(self, fd) -> None: | ||||
|         """ | ||||
|         Called from ImageFile to set the Python file-like object | ||||
| 
 | ||||
|  | @ -793,7 +793,7 @@ class PyEncoder(PyCodec): | |||
|             self.fd.write(data) | ||||
|         return bytes_consumed, errcode | ||||
| 
 | ||||
|     def encode_to_file(self, fh, bufsize): | ||||
|     def encode_to_file(self, fh: IO[bytes], bufsize: int) -> int: | ||||
|         """ | ||||
|         :param fh: File handle. | ||||
|         :param bufsize: Buffer size. | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ from __future__ import annotations | |||
| 
 | ||||
| import tkinter | ||||
| from io import BytesIO | ||||
| from typing import Any | ||||
| from typing import TYPE_CHECKING, Any, cast | ||||
| 
 | ||||
| from . import Image, ImageFile | ||||
| 
 | ||||
|  | @ -61,7 +61,9 @@ def _get_image_from_kw(kw: dict[str, Any]) -> ImageFile.ImageFile | None: | |||
|     return Image.open(source) | ||||
| 
 | ||||
| 
 | ||||
| def _pyimagingtkcall(command, photo, id): | ||||
| def _pyimagingtkcall( | ||||
|     command: str, photo: PhotoImage | tkinter.PhotoImage, id: int | ||||
| ) -> None: | ||||
|     tk = photo.tk | ||||
|     try: | ||||
|         tk.call(command, photo, id) | ||||
|  | @ -215,11 +217,14 @@ class BitmapImage: | |||
|     :param image: A PIL image. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, image=None, **kw): | ||||
|     def __init__(self, image: Image.Image | None = None, **kw: Any) -> None: | ||||
|         # Tk compatibility: file or data | ||||
|         if image is None: | ||||
|             image = _get_image_from_kw(kw) | ||||
| 
 | ||||
|         if image is None: | ||||
|             msg = "Image is required" | ||||
|             raise ValueError(msg) | ||||
|         self.__mode = image.mode | ||||
|         self.__size = image.size | ||||
| 
 | ||||
|  | @ -278,18 +283,23 @@ def getimage(photo: PhotoImage) -> Image.Image: | |||
|     return im | ||||
| 
 | ||||
| 
 | ||||
| def _show(image, title): | ||||
| def _show(image: Image.Image, title: str | None) -> None: | ||||
|     """Helper for the Image.show method.""" | ||||
| 
 | ||||
|     class UI(tkinter.Label): | ||||
|         def __init__(self, master, im): | ||||
|         def __init__(self, master: tkinter.Toplevel, im: Image.Image) -> None: | ||||
|             self.image: BitmapImage | PhotoImage | ||||
|             if im.mode == "1": | ||||
|                 self.image = BitmapImage(im, foreground="white", master=master) | ||||
|             else: | ||||
|                 self.image = PhotoImage(im, master=master) | ||||
|             super().__init__(master, image=self.image, bg="black", bd=0) | ||||
|             if TYPE_CHECKING: | ||||
|                 image = cast(tkinter._Image, self.image) | ||||
|             else: | ||||
|                 image = self.image | ||||
|             super().__init__(master, image=image, bg="black", bd=0) | ||||
| 
 | ||||
|     if not tkinter._default_root: | ||||
|     if not getattr(tkinter, "_default_root"): | ||||
|         msg = "tkinter not initialized" | ||||
|         raise OSError(msg) | ||||
|     top = tkinter.Toplevel() | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ class BoxReader: | |||
|     and to easily step into and read sub-boxes. | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, fp, length=-1): | ||||
|     def __init__(self, fp: IO[bytes], length: int = -1) -> None: | ||||
|         self.fp = fp | ||||
|         self.has_length = length >= 0 | ||||
|         self.length = length | ||||
|  | @ -97,7 +97,7 @@ class BoxReader: | |||
|         return tbox | ||||
| 
 | ||||
| 
 | ||||
| def _parse_codestream(fp) -> tuple[tuple[int, int], str]: | ||||
| def _parse_codestream(fp: IO[bytes]) -> tuple[tuple[int, int], str]: | ||||
|     """Parse the JPEG 2000 codestream to extract the size and component | ||||
|     count from the SIZ marker segment, returning a PIL (size, mode) tuple.""" | ||||
| 
 | ||||
|  | @ -137,7 +137,15 @@ def _res_to_dpi(num: int, denom: int, exp: int) -> float | None: | |||
|     return (254 * num * (10**exp)) / (10000 * denom) | ||||
| 
 | ||||
| 
 | ||||
| def _parse_jp2_header(fp): | ||||
| def _parse_jp2_header( | ||||
|     fp: IO[bytes], | ||||
| ) -> tuple[ | ||||
|     tuple[int, int], | ||||
|     str, | ||||
|     str | None, | ||||
|     tuple[float, float] | None, | ||||
|     ImagePalette.ImagePalette | None, | ||||
| ]: | ||||
|     """Parse the JP2 header box to extract size, component count, | ||||
|     color space information, and optionally DPI information, | ||||
|     returning a (size, mode, mimetype, dpi) tuple.""" | ||||
|  | @ -155,6 +163,7 @@ def _parse_jp2_header(fp): | |||
|         elif tbox == b"ftyp": | ||||
|             if reader.read_fields(">4s")[0] == b"jpx ": | ||||
|                 mimetype = "image/jpx" | ||||
|     assert header is not None | ||||
| 
 | ||||
|     size = None | ||||
|     mode = None | ||||
|  | @ -168,6 +177,9 @@ def _parse_jp2_header(fp): | |||
| 
 | ||||
|         if tbox == b"ihdr": | ||||
|             height, width, nc, bpc = header.read_fields(">IIHB") | ||||
|             assert isinstance(height, int) | ||||
|             assert isinstance(width, int) | ||||
|             assert isinstance(bpc, int) | ||||
|             size = (width, height) | ||||
|             if nc == 1 and (bpc & 0x7F) > 8: | ||||
|                 mode = "I;16" | ||||
|  | @ -185,11 +197,21 @@ def _parse_jp2_header(fp): | |||
|                 mode = "CMYK" | ||||
|         elif tbox == b"pclr" and mode in ("L", "LA"): | ||||
|             ne, npc = header.read_fields(">HB") | ||||
|             bitdepths = header.read_fields(">" + ("B" * npc)) | ||||
|             if max(bitdepths) <= 8: | ||||
|             assert isinstance(ne, int) | ||||
|             assert isinstance(npc, int) | ||||
|             max_bitdepth = 0 | ||||
|             for bitdepth in header.read_fields(">" + ("B" * npc)): | ||||
|                 assert isinstance(bitdepth, int) | ||||
|                 if bitdepth > max_bitdepth: | ||||
|                     max_bitdepth = bitdepth | ||||
|             if max_bitdepth <= 8: | ||||
|                 palette = ImagePalette.ImagePalette() | ||||
|                 for i in range(ne): | ||||
|                     palette.getcolor(header.read_fields(">" + ("B" * npc))) | ||||
|                     color: list[int] = [] | ||||
|                     for value in header.read_fields(">" + ("B" * npc)): | ||||
|                         assert isinstance(value, int) | ||||
|                         color.append(value) | ||||
|                     palette.getcolor(tuple(color)) | ||||
|                 mode = "P" if mode == "L" else "PA" | ||||
|         elif tbox == b"res ": | ||||
|             res = header.read_boxes() | ||||
|  | @ -197,6 +219,12 @@ def _parse_jp2_header(fp): | |||
|                 tres = res.next_box_type() | ||||
|                 if tres == b"resc": | ||||
|                     vrcn, vrcd, hrcn, hrcd, vrce, hrce = res.read_fields(">HHHHBB") | ||||
|                     assert isinstance(vrcn, int) | ||||
|                     assert isinstance(vrcd, int) | ||||
|                     assert isinstance(hrcn, int) | ||||
|                     assert isinstance(hrcd, int) | ||||
|                     assert isinstance(vrce, int) | ||||
|                     assert isinstance(hrce, int) | ||||
|                     hres = _res_to_dpi(hrcn, hrcd, hrce) | ||||
|                     vres = _res_to_dpi(vrcn, vrcd, vrce) | ||||
|                     if hres is not None and vres is not None: | ||||
|  |  | |||
|  | @ -60,7 +60,7 @@ def Skip(self: JpegImageFile, marker: int) -> None: | |||
|     ImageFile._safe_read(self.fp, n) | ||||
| 
 | ||||
| 
 | ||||
| def APP(self, marker): | ||||
| def APP(self: JpegImageFile, marker: int) -> None: | ||||
|     # | ||||
|     # Application marker.  Store these in the APP dictionary. | ||||
|     # Also look for well-known application markers. | ||||
|  | @ -133,13 +133,14 @@ def APP(self, marker): | |||
|                 offset += 4 | ||||
|                 data = s[offset : offset + size] | ||||
|                 if code == 0x03ED:  # ResolutionInfo | ||||
|                     data = { | ||||
|                     photoshop[code] = { | ||||
|                         "XResolution": i32(data, 0) / 65536, | ||||
|                         "DisplayedUnitsX": i16(data, 4), | ||||
|                         "YResolution": i32(data, 8) / 65536, | ||||
|                         "DisplayedUnitsY": i16(data, 12), | ||||
|                     } | ||||
|                 photoshop[code] = data | ||||
|                 else: | ||||
|                     photoshop[code] = data | ||||
|                 offset += size | ||||
|                 offset += offset & 1  # align | ||||
|             except struct.error: | ||||
|  | @ -338,6 +339,7 @@ class JpegImageFile(ImageFile.ImageFile): | |||
| 
 | ||||
|         # Create attributes | ||||
|         self.bits = self.layers = 0 | ||||
|         self._exif_offset = 0 | ||||
| 
 | ||||
|         # JPEG specifics (internal) | ||||
|         self.layer = [] | ||||
|  | @ -498,17 +500,17 @@ class JpegImageFile(ImageFile.ImageFile): | |||
|         ): | ||||
|             self.info["dpi"] = 72, 72 | ||||
| 
 | ||||
|     def _getmp(self): | ||||
|     def _getmp(self) -> dict[int, Any] | None: | ||||
|         return _getmp(self) | ||||
| 
 | ||||
| 
 | ||||
| def _getexif(self) -> dict[str, Any] | None: | ||||
| def _getexif(self: JpegImageFile) -> dict[str, Any] | None: | ||||
|     if "exif" not in self.info: | ||||
|         return None | ||||
|     return self.getexif()._get_merged_dict() | ||||
| 
 | ||||
| 
 | ||||
| def _getmp(self): | ||||
| def _getmp(self: JpegImageFile) -> dict[int, Any] | None: | ||||
|     # Extract MP information.  This method was inspired by the "highly | ||||
|     # experimental" _getexif version that's been in use for years now, | ||||
|     # itself based on the ImageFileDirectory class in the TIFF plugin. | ||||
|  | @ -616,7 +618,7 @@ samplings = { | |||
| # fmt: on | ||||
| 
 | ||||
| 
 | ||||
| def get_sampling(im): | ||||
| def get_sampling(im: Image.Image) -> int: | ||||
|     # There's no subsampling when images have only 1 layer | ||||
|     # (grayscale images) or when they are CMYK (4 layers), | ||||
|     # so set subsampling to the default value. | ||||
|  | @ -624,7 +626,7 @@ def get_sampling(im): | |||
|     # NOTE: currently Pillow can't encode JPEG to YCCK format. | ||||
|     # If YCCK support is added in the future, subsampling code will have | ||||
|     # to be updated (here and in JpegEncode.c) to deal with 4 layers. | ||||
|     if not hasattr(im, "layers") or im.layers in (1, 4): | ||||
|     if not isinstance(im, JpegImageFile) or im.layers in (1, 4): | ||||
|         return -1 | ||||
|     sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3] | ||||
|     return samplings.get(sampling, -1) | ||||
|  |  | |||
|  | @ -22,7 +22,7 @@ from __future__ import annotations | |||
| import itertools | ||||
| import os | ||||
| import struct | ||||
| from typing import IO | ||||
| from typing import IO, Any, cast | ||||
| 
 | ||||
| from . import ( | ||||
|     Image, | ||||
|  | @ -101,8 +101,11 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): | |||
|         JpegImagePlugin.JpegImageFile._open(self) | ||||
|         self._after_jpeg_open() | ||||
| 
 | ||||
|     def _after_jpeg_open(self, mpheader=None): | ||||
|     def _after_jpeg_open(self, mpheader: dict[int, Any] | None = None) -> None: | ||||
|         self.mpinfo = mpheader if mpheader is not None else self._getmp() | ||||
|         if self.mpinfo is None: | ||||
|             msg = "Image appears to be a malformed MPO file" | ||||
|             raise ValueError(msg) | ||||
|         self.n_frames = self.mpinfo[0xB001] | ||||
|         self.__mpoffsets = [ | ||||
|             mpent["DataOffset"] + self.info["mpoffset"] for mpent in self.mpinfo[0xB002] | ||||
|  | @ -149,7 +152,10 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): | |||
|         return self.__frame | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def adopt(jpeg_instance, mpheader=None): | ||||
|     def adopt( | ||||
|         jpeg_instance: JpegImagePlugin.JpegImageFile, | ||||
|         mpheader: dict[int, Any] | None = None, | ||||
|     ) -> MpoImageFile: | ||||
|         """ | ||||
|         Transform the instance of JpegImageFile into | ||||
|         an instance of MpoImageFile. | ||||
|  | @ -161,8 +167,9 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): | |||
|         double call to _open. | ||||
|         """ | ||||
|         jpeg_instance.__class__ = MpoImageFile | ||||
|         jpeg_instance._after_jpeg_open(mpheader) | ||||
|         return jpeg_instance | ||||
|         mpo_instance = cast(MpoImageFile, jpeg_instance) | ||||
|         mpo_instance._after_jpeg_open(mpheader) | ||||
|         return mpo_instance | ||||
| 
 | ||||
| 
 | ||||
| # --------------------------------------------------------------------- | ||||
|  |  | |||
|  | @ -230,6 +230,7 @@ class ChunkStream: | |||
| 
 | ||||
|         cids = [] | ||||
| 
 | ||||
|         assert self.fp is not None | ||||
|         while True: | ||||
|             try: | ||||
|                 cid, pos, length = self.read() | ||||
|  | @ -407,6 +408,7 @@ class PngStream(ChunkStream): | |||
| 
 | ||||
|     def chunk_iCCP(self, pos: int, length: int) -> bytes: | ||||
|         # ICC profile | ||||
|         assert self.fp is not None | ||||
|         s = ImageFile._safe_read(self.fp, length) | ||||
|         # according to PNG spec, the iCCP chunk contains: | ||||
|         # Profile name  1-79 bytes (character string) | ||||
|  | @ -434,6 +436,7 @@ class PngStream(ChunkStream): | |||
| 
 | ||||
|     def chunk_IHDR(self, pos: int, length: int) -> bytes: | ||||
|         # image header | ||||
|         assert self.fp is not None | ||||
|         s = ImageFile._safe_read(self.fp, length) | ||||
|         if length < 13: | ||||
|             if ImageFile.LOAD_TRUNCATED_IMAGES: | ||||
|  | @ -471,6 +474,7 @@ class PngStream(ChunkStream): | |||
| 
 | ||||
|     def chunk_PLTE(self, pos: int, length: int) -> bytes: | ||||
|         # palette | ||||
|         assert self.fp is not None | ||||
|         s = ImageFile._safe_read(self.fp, length) | ||||
|         if self.im_mode == "P": | ||||
|             self.im_palette = "RGB", s | ||||
|  | @ -478,6 +482,7 @@ class PngStream(ChunkStream): | |||
| 
 | ||||
|     def chunk_tRNS(self, pos: int, length: int) -> bytes: | ||||
|         # transparency | ||||
|         assert self.fp is not None | ||||
|         s = ImageFile._safe_read(self.fp, length) | ||||
|         if self.im_mode == "P": | ||||
|             if _simple_palette.match(s): | ||||
|  | @ -498,6 +503,7 @@ class PngStream(ChunkStream): | |||
| 
 | ||||
|     def chunk_gAMA(self, pos: int, length: int) -> bytes: | ||||
|         # gamma setting | ||||
|         assert self.fp is not None | ||||
|         s = ImageFile._safe_read(self.fp, length) | ||||
|         self.im_info["gamma"] = i32(s) / 100000.0 | ||||
|         return s | ||||
|  | @ -506,6 +512,7 @@ class PngStream(ChunkStream): | |||
|         # chromaticity, 8 unsigned ints, actual value is scaled by 100,000 | ||||
|         # WP x,y, Red x,y, Green x,y Blue x,y | ||||
| 
 | ||||
|         assert self.fp is not None | ||||
|         s = ImageFile._safe_read(self.fp, length) | ||||
|         raw_vals = struct.unpack(">%dI" % (len(s) // 4), s) | ||||
|         self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals) | ||||
|  | @ -518,6 +525,7 @@ class PngStream(ChunkStream): | |||
|         # 2 saturation | ||||
|         # 3 absolute colorimetric | ||||
| 
 | ||||
|         assert self.fp is not None | ||||
|         s = ImageFile._safe_read(self.fp, length) | ||||
|         if length < 1: | ||||
|             if ImageFile.LOAD_TRUNCATED_IMAGES: | ||||
|  | @ -529,6 +537,7 @@ class PngStream(ChunkStream): | |||
| 
 | ||||
|     def chunk_pHYs(self, pos: int, length: int) -> bytes: | ||||
|         # pixels per unit | ||||
|         assert self.fp is not None | ||||
|         s = ImageFile._safe_read(self.fp, length) | ||||
|         if length < 9: | ||||
|             if ImageFile.LOAD_TRUNCATED_IMAGES: | ||||
|  | @ -546,6 +555,7 @@ class PngStream(ChunkStream): | |||
| 
 | ||||
|     def chunk_tEXt(self, pos: int, length: int) -> bytes: | ||||
|         # text | ||||
|         assert self.fp is not None | ||||
|         s = ImageFile._safe_read(self.fp, length) | ||||
|         try: | ||||
|             k, v = s.split(b"\0", 1) | ||||
|  | @ -554,17 +564,18 @@ class PngStream(ChunkStream): | |||
|             k = s | ||||
|             v = b"" | ||||
|         if k: | ||||
|             k = k.decode("latin-1", "strict") | ||||
|             k_str = k.decode("latin-1", "strict") | ||||
|             v_str = v.decode("latin-1", "replace") | ||||
| 
 | ||||
|             self.im_info[k] = v if k == "exif" else v_str | ||||
|             self.im_text[k] = v_str | ||||
|             self.im_info[k_str] = v if k == b"exif" else v_str | ||||
|             self.im_text[k_str] = v_str | ||||
|             self.check_text_memory(len(v_str)) | ||||
| 
 | ||||
|         return s | ||||
| 
 | ||||
|     def chunk_zTXt(self, pos: int, length: int) -> bytes: | ||||
|         # compressed text | ||||
|         assert self.fp is not None | ||||
|         s = ImageFile._safe_read(self.fp, length) | ||||
|         try: | ||||
|             k, v = s.split(b"\0", 1) | ||||
|  | @ -589,16 +600,17 @@ class PngStream(ChunkStream): | |||
|             v = b"" | ||||
| 
 | ||||
|         if k: | ||||
|             k = k.decode("latin-1", "strict") | ||||
|             v = v.decode("latin-1", "replace") | ||||
|             k_str = k.decode("latin-1", "strict") | ||||
|             v_str = v.decode("latin-1", "replace") | ||||
| 
 | ||||
|             self.im_info[k] = self.im_text[k] = v | ||||
|             self.check_text_memory(len(v)) | ||||
|             self.im_info[k_str] = self.im_text[k_str] = v_str | ||||
|             self.check_text_memory(len(v_str)) | ||||
| 
 | ||||
|         return s | ||||
| 
 | ||||
|     def chunk_iTXt(self, pos: int, length: int) -> bytes: | ||||
|         # international text | ||||
|         assert self.fp is not None | ||||
|         r = s = ImageFile._safe_read(self.fp, length) | ||||
|         try: | ||||
|             k, r = r.split(b"\0", 1) | ||||
|  | @ -627,25 +639,27 @@ class PngStream(ChunkStream): | |||
|         if k == b"XML:com.adobe.xmp": | ||||
|             self.im_info["xmp"] = v | ||||
|         try: | ||||
|             k = k.decode("latin-1", "strict") | ||||
|             lang = lang.decode("utf-8", "strict") | ||||
|             tk = tk.decode("utf-8", "strict") | ||||
|             v = v.decode("utf-8", "strict") | ||||
|             k_str = k.decode("latin-1", "strict") | ||||
|             lang_str = lang.decode("utf-8", "strict") | ||||
|             tk_str = tk.decode("utf-8", "strict") | ||||
|             v_str = v.decode("utf-8", "strict") | ||||
|         except UnicodeError: | ||||
|             return s | ||||
| 
 | ||||
|         self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk) | ||||
|         self.check_text_memory(len(v)) | ||||
|         self.im_info[k_str] = self.im_text[k_str] = iTXt(v_str, lang_str, tk_str) | ||||
|         self.check_text_memory(len(v_str)) | ||||
| 
 | ||||
|         return s | ||||
| 
 | ||||
|     def chunk_eXIf(self, pos: int, length: int) -> bytes: | ||||
|         assert self.fp is not None | ||||
|         s = ImageFile._safe_read(self.fp, length) | ||||
|         self.im_info["exif"] = b"Exif\x00\x00" + s | ||||
|         return s | ||||
| 
 | ||||
|     # APNG chunks | ||||
|     def chunk_acTL(self, pos: int, length: int) -> bytes: | ||||
|         assert self.fp is not None | ||||
|         s = ImageFile._safe_read(self.fp, length) | ||||
|         if length < 8: | ||||
|             if ImageFile.LOAD_TRUNCATED_IMAGES: | ||||
|  | @ -666,6 +680,7 @@ class PngStream(ChunkStream): | |||
|         return s | ||||
| 
 | ||||
|     def chunk_fcTL(self, pos: int, length: int) -> bytes: | ||||
|         assert self.fp is not None | ||||
|         s = ImageFile._safe_read(self.fp, length) | ||||
|         if length < 26: | ||||
|             if ImageFile.LOAD_TRUNCATED_IMAGES: | ||||
|  | @ -695,6 +710,7 @@ class PngStream(ChunkStream): | |||
|         return s | ||||
| 
 | ||||
|     def chunk_fdAT(self, pos: int, length: int) -> bytes: | ||||
|         assert self.fp is not None | ||||
|         if length < 4: | ||||
|             if ImageFile.LOAD_TRUNCATED_IMAGES: | ||||
|                 s = ImageFile._safe_read(self.fp, length) | ||||
|  |  | |||
|  | @ -185,7 +185,7 @@ def _layerinfo(fp, ct_bytes): | |||
|     # read layerinfo block | ||||
|     layers = [] | ||||
| 
 | ||||
|     def read(size): | ||||
|     def read(size: int) -> bytes: | ||||
|         return ImageFile._safe_read(fp, size) | ||||
| 
 | ||||
|     ct = si16(read(2)) | ||||
|  |  | |||
|  | @ -115,7 +115,7 @@ class WebPImageFile(ImageFile.ImageFile): | |||
|         self.__loaded = -1 | ||||
|         self.__timestamp = 0 | ||||
| 
 | ||||
|     def _get_next(self): | ||||
|     def _get_next(self) -> tuple[bytes, int, int]: | ||||
|         # Get next frame | ||||
|         ret = self._decoder.get_next() | ||||
|         self.__physical_frame += 1 | ||||
|  |  | |||
|  | @ -152,7 +152,7 @@ class WmfStubImageFile(ImageFile.StubImageFile): | |||
|     def _load(self) -> ImageFile.StubHandler | None: | ||||
|         return _handler | ||||
| 
 | ||||
|     def load(self, dpi=None): | ||||
|     def load(self, dpi: int | None = None) -> Image.core.PixelAccess | None: | ||||
|         if dpi is not None and self._inch is not None: | ||||
|             self.info["dpi"] = dpi | ||||
|             x0, y0, x1, y1 = self.info["wmf_bbox"] | ||||
|  |  | |||
							
								
								
									
										3
									
								
								src/PIL/_imagingtk.pyi
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/PIL/_imagingtk.pyi
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| from typing import Any | ||||
| 
 | ||||
| def __getattr__(name: str) -> Any: ... | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user