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