diff options
author | kickbutt <kickbutt@yandex-team.com> | 2024-01-23 23:36:43 +0300 |
---|---|---|
committer | kickbutt <kickbutt@yandex-team.com> | 2024-01-23 23:55:22 +0300 |
commit | fe742a0b69a530f86d1ea7aa84978d673256f8b7 (patch) | |
tree | a045a5eb8dba770797e84d0b233098605396027d | |
parent | bd7d89b121ae7b9f4427766292c950fcc91c2975 (diff) | |
download | ydb-fe742a0b69a530f86d1ea7aa84978d673256f8b7.tar.gz |
Fix separator in CUDA_ARCHITECTURES
115 files changed, 1598 insertions, 909 deletions
diff --git a/build/scripts/link_exe.py b/build/scripts/link_exe.py index a5744e5f25..980ac05d7e 100644 --- a/build/scripts/link_exe.py +++ b/build/scripts/link_exe.py @@ -55,7 +55,7 @@ def prune_cuda_libraries(cmd, prune_arches, nvprune_exe, build_root): tmp_names_gen = name_generator('cuda_pruned_libs') arch_args = [] - for arch in prune_arches.split(','): + for arch in prune_arches.split(':'): arch_args.append('-gencode') arch_args.append('arch={},code={}'.format(compute_arch(arch), arch)) @@ -200,7 +200,8 @@ def parse_args(): parser.add_option('--source-root') parser.add_option('--clang-ver') parser.add_option('--dynamic-cuda', action='store_true') - parser.add_option('--cuda-architectures') + parser.add_option('--cuda-architectures', + help='List of supported CUDA architectures, separated by ":" (e.g. "sm_52:compute_70:lto_90a"') parser.add_option('--nvprune-exe') parser.add_option('--build-root') parser.add_option('--arch') diff --git a/build/ymake_conf.py b/build/ymake_conf.py index acf6dfd9a0..33c55da3d5 100755 --- a/build/ymake_conf.py +++ b/build/ymake_conf.py @@ -2396,7 +2396,7 @@ class Cuda(object): host, target = self.build.host_target if not target.is_linux_x86_64: # do not impose any restrictions, when build not for "linux 64-bit" - return [] + return '' # do not include 'lto' type, # because we already perform static linking @@ -2422,7 +2422,7 @@ class Cuda(object): for typ in supported_types for ver in supported_vers] - return ','.join(cuda_architectures) + return ':'.join(cuda_architectures) def auto_use_arcadia_cuda(self): return not self.cuda_root.from_user diff --git a/contrib/python/Pillow/py3/.dist-info/METADATA b/contrib/python/Pillow/py3/.dist-info/METADATA index bdad4e521b..78d0239337 100644 --- a/contrib/python/Pillow/py3/.dist-info/METADATA +++ b/contrib/python/Pillow/py3/.dist-info/METADATA @@ -1,22 +1,20 @@ Metadata-Version: 2.1 -Name: Pillow -Version: 10.1.0 +Name: pillow +Version: 10.2.0 Summary: Python Imaging Library (Fork) -Home-page: https://python-pillow.org -Author: Jeffrey A. Clark (Alex) -Author-email: aclark@aclark.net +Author-email: "Jeffrey A. Clark (Alex)" <aclark@aclark.net> License: HPND +Project-URL: Changelog, https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst Project-URL: Documentation, https://pillow.readthedocs.io -Project-URL: Source, https://github.com/python-pillow/Pillow Project-URL: Funding, https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=pypi +Project-URL: Homepage, https://python-pillow.org +Project-URL: Mastodon, https://fosstodon.org/@pillow Project-URL: Release notes, https://pillow.readthedocs.io/en/stable/releasenotes/index.html -Project-URL: Changelog, https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst +Project-URL: Source, https://github.com/python-pillow/Pillow Project-URL: Twitter, https://twitter.com/PythonPillow -Project-URL: Mastodon, https://fosstodon.org/@pillow Keywords: Imaging Classifier: Development Status :: 6 - Mature Classifier: License :: OSI Approved :: Historical Permission Notice and Disclaimer (HPND) -Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 @@ -41,6 +39,10 @@ Requires-Dist: sphinx-copybutton ; extra == 'docs' Requires-Dist: sphinx-inline-tabs ; extra == 'docs' Requires-Dist: sphinx-removed-in ; extra == 'docs' Requires-Dist: sphinxext-opengraph ; extra == 'docs' +Provides-Extra: fpx +Requires-Dist: olefile ; extra == 'fpx' +Provides-Extra: mic +Requires-Dist: olefile ; extra == 'mic' Provides-Extra: tests Requires-Dist: check-manifest ; extra == 'tests' Requires-Dist: coverage ; extra == 'tests' @@ -52,6 +54,10 @@ Requires-Dist: pyroma ; extra == 'tests' Requires-Dist: pytest ; extra == 'tests' Requires-Dist: pytest-cov ; extra == 'tests' Requires-Dist: pytest-timeout ; extra == 'tests' +Provides-Extra: typing +Requires-Dist: typing-extensions ; (python_version < "3.10") and extra == 'typing' +Provides-Extra: xmp +Requires-Dist: defusedxml ; extra == 'xmp' <p align="center"> <img width="248" height="250" src="https://raw.githubusercontent.com/python-pillow/pillow-logo/main/pillow-logo-248x250.png" alt="Pillow logo"> diff --git a/contrib/python/Pillow/py3/CHANGES.rst b/contrib/python/Pillow/py3/CHANGES.rst index d2f2bb4623..85036f6425 100644 --- a/contrib/python/Pillow/py3/CHANGES.rst +++ b/contrib/python/Pillow/py3/CHANGES.rst @@ -2,6 +2,114 @@ Changelog (Pillow) ================== +10.2.0 (2024-01-02) +------------------- + +- Add ``keep_rgb`` option when saving JPEG to prevent conversion of RGB colorspace #7553 + [bgilbert, radarhere] + +- Trim glyph size in ImageFont.getmask() #7669, #7672 + [radarhere, nulano] + +- Deprecate IptcImagePlugin helpers #7664 + [nulano, hugovk, radarhere] + +- Allow uncompressed TIFF images to be saved in chunks #7650 + [radarhere] + +- Concatenate multiple JPEG EXIF markers #7496 + [radarhere] + +- Changed IPTC tile tuple to match other plugins #7661 + [radarhere] + +- Do not assign new fp attribute when exiting context manager #7566 + [radarhere] + +- Support arbitrary masks for uncompressed RGB DDS images #7589 + [radarhere, akx] + +- Support setting ROWSPERSTRIP tag #7654 + [radarhere] + +- Apply ImageFont.MAX_STRING_LENGTH to ImageFont.getmask() #7662 + [radarhere] + +- Optimise ``ImageColor`` using ``functools.lru_cache`` #7657 + [hugovk] + +- Restricted environment keys for ImageMath.eval() #7655 + [wiredfool, radarhere] + +- Optimise ``ImageMode.getmode`` using ``functools.lru_cache`` #7641 + [hugovk, radarhere] + +- Fix incorrect color blending for overlapping glyphs #7497 + [ZachNagengast, nulano, radarhere] + +- Attempt memory mapping when tile args is a string #7565 + [radarhere] + +- Fill identical pixels with transparency in subsequent frames when saving GIF #7568 + [radarhere] + +- Corrected duration when combining multiple GIF frames into single frame #7521 + [radarhere] + +- Handle disposing GIF background from outside palette #7515 + [radarhere] + +- Seek past the data when skipping a PSD layer #7483 + [radarhere] + +- Import plugins relative to the module #7576 + [deliangyang, jaxx0n] + +- Translate encoder error codes to strings; deprecate ``ImageFile.raise_oserror()`` #7609 + [bgilbert, radarhere] + +- Support reading BC4U and DX10 BC1 images #6486 + [REDxEYE, radarhere, hugovk] + +- Optimize ImageStat.Stat.extrema #7593 + [florath, radarhere] + +- Handle pathlib.Path in FreeTypeFont #7578 + [radarhere, hugovk, nulano] + +- Added support for reading DX10 BC4 DDS images #7603 + [sambvfx, radarhere] + +- Optimized ImageStat.Stat.count #7599 + [florath] + +- Correct PDF palette size when saving #7555 + [radarhere] + +- Fixed closing file pointer with olefile 0.47 #7594 + [radarhere] + +- Raise ValueError when TrueType font size is not greater than zero #7584, #7587 + [akx, radarhere] + +- If absent, do not try to close fp when closing image #7557 + [RaphaelVRossi, radarhere] + +- Allow configuring JPEG restart marker interval on save #7488 + [bgilbert, radarhere] + +- Decrement reference count for PyObject #7549 + [radarhere] + +- Implement ``streamtype=1`` option for tables-only JPEG encoding #7491 + [bgilbert, radarhere] + +- If save_all PNG only has one frame, do not create animated image #7522 + [radarhere] + +- Fixed frombytes() for images with a zero dimension #7493 + [radarhere] + 10.1.0 (2023-10-15) ------------------- @@ -2191,7 +2299,7 @@ Changelog (Pillow) - Cache EXIF information #3498 [Glandos] -- Added transparency for all PNG greyscale modes #3744 +- Added transparency for all PNG grayscale modes #3744 [radarhere] - Fix deprecation warnings in Python 3.8 #3749 @@ -4693,7 +4801,7 @@ Changelog (Pillow) - Fix Bicubic interpolation #970 [homm] -- Support for 4-bit greyscale TIFF images #980 +- Support for 4-bit grayscale TIFF images #980 [hugovk] - Updated manifest #957 @@ -6768,7 +6876,7 @@ The test suite includes 750 individual tests. - You can now convert directly between all modes supported by PIL. When converting colour images to "P", PIL defaults to - a "web" palette and dithering. When converting greyscale + a "web" palette and dithering. When converting grayscale images to "1", PIL uses a thresholding and dithering. - Added a "dither" option to "convert". By default, "convert" @@ -6846,13 +6954,13 @@ The test suite includes 530 individual tests. - Fixed "paste" to allow a mask also for mode "F" images. - The BMP driver now saves mode "1" images. When loading images, the mode - is set to "L" for 8-bit files with greyscale palettes, and to "P" for + is set to "L" for 8-bit files with grayscale palettes, and to "P" for other 8-bit files. - The IM driver now reads and saves "1" images (file modes "0 1" or "L 1"). - The JPEG and GIF drivers now saves "1" images. For JPEG, the image - is saved as 8-bit greyscale (it will load as mode "L"). For GIF, the + is saved as 8-bit grayscale (it will load as mode "L"). For GIF, the image will be loaded as a "P" image. - Fixed a potential buffer overrun in the GIF encoder. @@ -7156,7 +7264,7 @@ The test suite includes 400 individual tests. drawing capabilities can be used to render vector and metafile formats. -- Added restricted drivers for images from Image Tools (greyscale +- Added restricted drivers for images from Image Tools (grayscale only) and LabEye/IFUNC (common interchange modes only). - Some minor improvements to the sample scripts provided in the diff --git a/contrib/python/Pillow/py3/LICENSE b/contrib/python/Pillow/py3/LICENSE index cf65e86d73..0069eb5bce 100644 --- a/contrib/python/Pillow/py3/LICENSE +++ b/contrib/python/Pillow/py3/LICENSE @@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is Pillow is the friendly PIL fork. It is - Copyright © 2010-2023 by Jeffrey A. Clark (Alex) and contributors. + Copyright © 2010-2024 by Jeffrey A. Clark (Alex) and contributors. Like PIL, Pillow is licensed under the open source HPND License: diff --git a/contrib/python/Pillow/py3/PIL/BdfFontFile.py b/contrib/python/Pillow/py3/PIL/BdfFontFile.py index 161954831a..e3eda4fe98 100644 --- a/contrib/python/Pillow/py3/PIL/BdfFontFile.py +++ b/contrib/python/Pillow/py3/PIL/BdfFontFile.py @@ -20,7 +20,9 @@ """ Parse X Bitmap Distribution Format (BDF) """ +from __future__ import annotations +from typing import BinaryIO from . import FontFile, Image @@ -36,7 +38,17 @@ bdf_slant = { bdf_spacing = {"P": "Proportional", "M": "Monospaced", "C": "Cell"} -def bdf_char(f): +def bdf_char( + f: BinaryIO, +) -> ( + tuple[ + str, + int, + tuple[tuple[int, int], tuple[int, int, int, int], tuple[int, int, int, int]], + Image.Image, + ] + | None +): # skip to STARTCHAR while True: s = f.readline() @@ -56,13 +68,12 @@ def bdf_char(f): props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii") # load bitmap - bitmap = [] + bitmap = bytearray() while True: s = f.readline() if not s or s[:7] == b"ENDCHAR": break - bitmap.append(s[:-1]) - bitmap = b"".join(bitmap) + bitmap += s[:-1] # The word BBX # followed by the width in x (BBw), height in y (BBh), @@ -92,7 +103,7 @@ def bdf_char(f): class BdfFontFile(FontFile.FontFile): """Font file plugin for the X11 BDF format.""" - def __init__(self, fp): + def __init__(self, fp: BinaryIO): super().__init__() s = fp.readline() diff --git a/contrib/python/Pillow/py3/PIL/BlpImagePlugin.py b/contrib/python/Pillow/py3/PIL/BlpImagePlugin.py index 398696d5c7..b8f38b78a2 100644 --- a/contrib/python/Pillow/py3/PIL/BlpImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/BlpImagePlugin.py @@ -28,6 +28,7 @@ BLP files come in many different flavours: - DXT3 compression is used if alpha_encoding == 1. - DXT5 compression is used if alpha_encoding == 7. """ +from __future__ import annotations import os import struct diff --git a/contrib/python/Pillow/py3/PIL/BmpImagePlugin.py b/contrib/python/Pillow/py3/PIL/BmpImagePlugin.py index 9abfd0b5b8..6f730cfef1 100644 --- a/contrib/python/Pillow/py3/PIL/BmpImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/BmpImagePlugin.py @@ -22,7 +22,7 @@ # # See the README file for information on usage and redistribution. # - +from __future__ import annotations import os @@ -230,21 +230,21 @@ class BmpImageFile(ImageFile.ImageFile): else: padding = file_info["palette_padding"] palette = read(padding * file_info["colors"]) - greyscale = True + grayscale = True indices = ( (0, 255) if file_info["colors"] == 2 else list(range(file_info["colors"])) ) - # ----------------- Check if greyscale and ignore palette if so + # ----------------- Check if grayscale and ignore palette if so for ind, val in enumerate(indices): rgb = palette[ind * padding : ind * padding + 3] if rgb != o8(val) * 3: - greyscale = False + grayscale = False - # ------- If all colors are grey, white or black, ditch palette - if greyscale: + # ------- If all colors are gray, white or black, ditch palette + if grayscale: self._mode = "1" if file_info["colors"] == 2 else "L" raw_mode = self.mode else: @@ -396,7 +396,7 @@ def _save(im, fp, filename, bitmap_header=True): dpi = info.get("dpi", (96, 96)) # 1 meter == 39.3701 inches - ppm = tuple(map(lambda x: int(x * 39.3701 + 0.5), dpi)) + ppm = tuple(int(x * 39.3701 + 0.5) for x in dpi) stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3) header = 40 # or 64 for OS/2 version 2 diff --git a/contrib/python/Pillow/py3/PIL/BufrStubImagePlugin.py b/contrib/python/Pillow/py3/PIL/BufrStubImagePlugin.py index eef25aa14c..60f3ec25b3 100644 --- a/contrib/python/Pillow/py3/PIL/BufrStubImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/BufrStubImagePlugin.py @@ -8,6 +8,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations from . import Image, ImageFile diff --git a/contrib/python/Pillow/py3/PIL/ContainerIO.py b/contrib/python/Pillow/py3/PIL/ContainerIO.py index 45e80b39af..0035296a45 100644 --- a/contrib/python/Pillow/py3/PIL/ContainerIO.py +++ b/contrib/python/Pillow/py3/PIL/ContainerIO.py @@ -13,18 +13,19 @@ # # See the README file for information on usage and redistribution. # - +from __future__ import annotations import io +from typing import IO, AnyStr, Generic, Literal -class ContainerIO: +class ContainerIO(Generic[AnyStr]): """ A file object that provides read access to a part of an existing file (for example a TAR file). """ - def __init__(self, file, offset, length): + def __init__(self, file: IO[AnyStr], offset: int, length: int) -> None: """ Create file object. @@ -32,7 +33,7 @@ class ContainerIO: :param offset: Start of region, in bytes. :param length: Size of region, in bytes. """ - self.fh = file + self.fh: IO[AnyStr] = file self.pos = 0 self.offset = offset self.length = length @@ -41,10 +42,10 @@ class ContainerIO: ## # Always false. - def isatty(self): + def isatty(self) -> bool: return False - def seek(self, offset, mode=io.SEEK_SET): + def seek(self, offset: int, mode: Literal[0, 1, 2] = io.SEEK_SET) -> None: """ Move file pointer. @@ -63,7 +64,7 @@ class ContainerIO: self.pos = max(0, min(self.pos, self.length)) self.fh.seek(self.offset + self.pos) - def tell(self): + def tell(self) -> int: """ Get current file pointer. @@ -71,7 +72,7 @@ class ContainerIO: """ return self.pos - def read(self, n=0): + def read(self, n: int = 0) -> AnyStr: """ Read data. @@ -84,17 +85,17 @@ class ContainerIO: else: n = self.length - self.pos if not n: # EOF - return b"" if "b" in self.fh.mode else "" + return b"" if "b" in self.fh.mode else "" # type: ignore[return-value] self.pos = self.pos + n return self.fh.read(n) - def readline(self): + def readline(self) -> AnyStr: """ Read a line of text. :returns: An 8-bit string. """ - s = b"" if "b" in self.fh.mode else "" + s: AnyStr = b"" if "b" in self.fh.mode else "" # type: ignore[assignment] newline_character = b"\n" if "b" in self.fh.mode else "\n" while True: c = self.read(1) @@ -105,7 +106,7 @@ class ContainerIO: break return s - def readlines(self): + def readlines(self) -> list[AnyStr]: """ Read multiple lines of text. diff --git a/contrib/python/Pillow/py3/PIL/CurImagePlugin.py b/contrib/python/Pillow/py3/PIL/CurImagePlugin.py index 94efff3415..5fb2b0193c 100644 --- a/contrib/python/Pillow/py3/PIL/CurImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/CurImagePlugin.py @@ -15,6 +15,8 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations + from . import BmpImagePlugin, Image from ._binary import i16le as i16 from ._binary import i32le as i32 @@ -64,8 +66,6 @@ class CurImageFile(BmpImagePlugin.BmpImageFile): d, e, o, a = self.tile[0] self.tile[0] = d, (0, 0) + self.size, o, a - return - # # -------------------------------------------------------------------- diff --git a/contrib/python/Pillow/py3/PIL/DcxImagePlugin.py b/contrib/python/Pillow/py3/PIL/DcxImagePlugin.py index cde9d42f09..f7344df44a 100644 --- a/contrib/python/Pillow/py3/PIL/DcxImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/DcxImagePlugin.py @@ -20,6 +20,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations from . import Image from ._binary import i32le as i32 diff --git a/contrib/python/Pillow/py3/PIL/DdsImagePlugin.py b/contrib/python/Pillow/py3/PIL/DdsImagePlugin.py index 54f358c7f9..eb4c8f557a 100644 --- a/contrib/python/Pillow/py3/PIL/DdsImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/DdsImagePlugin.py @@ -3,109 +3,323 @@ A Pillow loader for .dds files (S3TC-compressed aka DXTC) Jerome Leclanche <jerome@leclan.ch> Documentation: - https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt +https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt The contents of this file are hereby released in the public domain (CC0) Full text of the CC0 license: - https://creativecommons.org/publicdomain/zero/1.0/ +https://creativecommons.org/publicdomain/zero/1.0/ """ +from __future__ import annotations +import io import struct -from io import BytesIO +import sys +from enum import IntEnum, IntFlag from . import Image, ImageFile, ImagePalette +from ._binary import i32le as i32 +from ._binary import o8 from ._binary import o32le as o32 # Magic ("DDS ") DDS_MAGIC = 0x20534444 -# DDS flags -DDSD_CAPS = 0x1 -DDSD_HEIGHT = 0x2 -DDSD_WIDTH = 0x4 -DDSD_PITCH = 0x8 -DDSD_PIXELFORMAT = 0x1000 -DDSD_MIPMAPCOUNT = 0x20000 -DDSD_LINEARSIZE = 0x80000 -DDSD_DEPTH = 0x800000 - -# DDS caps -DDSCAPS_COMPLEX = 0x8 -DDSCAPS_TEXTURE = 0x1000 -DDSCAPS_MIPMAP = 0x400000 - -DDSCAPS2_CUBEMAP = 0x200 -DDSCAPS2_CUBEMAP_POSITIVEX = 0x400 -DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800 -DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000 -DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000 -DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000 -DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000 -DDSCAPS2_VOLUME = 0x200000 - -# Pixel Format -DDPF_ALPHAPIXELS = 0x1 -DDPF_ALPHA = 0x2 -DDPF_FOURCC = 0x4 -DDPF_PALETTEINDEXED8 = 0x20 -DDPF_RGB = 0x40 -DDPF_LUMINANCE = 0x20000 - -# dds.h - -DDS_FOURCC = DDPF_FOURCC -DDS_RGB = DDPF_RGB -DDS_RGBA = DDPF_RGB | DDPF_ALPHAPIXELS -DDS_LUMINANCE = DDPF_LUMINANCE -DDS_LUMINANCEA = DDPF_LUMINANCE | DDPF_ALPHAPIXELS -DDS_ALPHA = DDPF_ALPHA -DDS_PAL8 = DDPF_PALETTEINDEXED8 - -DDS_HEADER_FLAGS_TEXTURE = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT -DDS_HEADER_FLAGS_MIPMAP = DDSD_MIPMAPCOUNT -DDS_HEADER_FLAGS_VOLUME = DDSD_DEPTH -DDS_HEADER_FLAGS_PITCH = DDSD_PITCH -DDS_HEADER_FLAGS_LINEARSIZE = DDSD_LINEARSIZE - -DDS_HEIGHT = DDSD_HEIGHT -DDS_WIDTH = DDSD_WIDTH +# DDS flags +class DDSD(IntFlag): + CAPS = 0x1 + HEIGHT = 0x2 + WIDTH = 0x4 + PITCH = 0x8 + PIXELFORMAT = 0x1000 + MIPMAPCOUNT = 0x20000 + LINEARSIZE = 0x80000 + DEPTH = 0x800000 -DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS_TEXTURE -DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS_COMPLEX | DDSCAPS_MIPMAP -DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS_COMPLEX -DDS_CUBEMAP_POSITIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX -DDS_CUBEMAP_NEGATIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX -DDS_CUBEMAP_POSITIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY -DDS_CUBEMAP_NEGATIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY -DDS_CUBEMAP_POSITIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ -DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ +# DDS caps +class DDSCAPS(IntFlag): + COMPLEX = 0x8 + TEXTURE = 0x1000 + MIPMAP = 0x400000 -# DXT1 -DXT1_FOURCC = 0x31545844 +class DDSCAPS2(IntFlag): + CUBEMAP = 0x200 + CUBEMAP_POSITIVEX = 0x400 + CUBEMAP_NEGATIVEX = 0x800 + CUBEMAP_POSITIVEY = 0x1000 + CUBEMAP_NEGATIVEY = 0x2000 + CUBEMAP_POSITIVEZ = 0x4000 + CUBEMAP_NEGATIVEZ = 0x8000 + VOLUME = 0x200000 -# DXT3 -DXT3_FOURCC = 0x33545844 -# DXT5 -DXT5_FOURCC = 0x35545844 +# Pixel Format +class DDPF(IntFlag): + ALPHAPIXELS = 0x1 + ALPHA = 0x2 + FOURCC = 0x4 + PALETTEINDEXED8 = 0x20 + RGB = 0x40 + LUMINANCE = 0x20000 # dxgiformat.h - -DXGI_FORMAT_R8G8B8A8_TYPELESS = 27 -DXGI_FORMAT_R8G8B8A8_UNORM = 28 -DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29 -DXGI_FORMAT_BC5_TYPELESS = 82 -DXGI_FORMAT_BC5_UNORM = 83 -DXGI_FORMAT_BC5_SNORM = 84 -DXGI_FORMAT_BC6H_UF16 = 95 -DXGI_FORMAT_BC6H_SF16 = 96 -DXGI_FORMAT_BC7_TYPELESS = 97 -DXGI_FORMAT_BC7_UNORM = 98 -DXGI_FORMAT_BC7_UNORM_SRGB = 99 +class DXGI_FORMAT(IntEnum): + UNKNOWN = 0 + R32G32B32A32_TYPELESS = 1 + R32G32B32A32_FLOAT = 2 + R32G32B32A32_UINT = 3 + R32G32B32A32_SINT = 4 + R32G32B32_TYPELESS = 5 + R32G32B32_FLOAT = 6 + R32G32B32_UINT = 7 + R32G32B32_SINT = 8 + R16G16B16A16_TYPELESS = 9 + R16G16B16A16_FLOAT = 10 + R16G16B16A16_UNORM = 11 + R16G16B16A16_UINT = 12 + R16G16B16A16_SNORM = 13 + R16G16B16A16_SINT = 14 + R32G32_TYPELESS = 15 + R32G32_FLOAT = 16 + R32G32_UINT = 17 + R32G32_SINT = 18 + R32G8X24_TYPELESS = 19 + D32_FLOAT_S8X24_UINT = 20 + R32_FLOAT_X8X24_TYPELESS = 21 + X32_TYPELESS_G8X24_UINT = 22 + R10G10B10A2_TYPELESS = 23 + R10G10B10A2_UNORM = 24 + R10G10B10A2_UINT = 25 + R11G11B10_FLOAT = 26 + R8G8B8A8_TYPELESS = 27 + R8G8B8A8_UNORM = 28 + R8G8B8A8_UNORM_SRGB = 29 + R8G8B8A8_UINT = 30 + R8G8B8A8_SNORM = 31 + R8G8B8A8_SINT = 32 + R16G16_TYPELESS = 33 + R16G16_FLOAT = 34 + R16G16_UNORM = 35 + R16G16_UINT = 36 + R16G16_SNORM = 37 + R16G16_SINT = 38 + R32_TYPELESS = 39 + D32_FLOAT = 40 + R32_FLOAT = 41 + R32_UINT = 42 + R32_SINT = 43 + R24G8_TYPELESS = 44 + D24_UNORM_S8_UINT = 45 + R24_UNORM_X8_TYPELESS = 46 + X24_TYPELESS_G8_UINT = 47 + R8G8_TYPELESS = 48 + R8G8_UNORM = 49 + R8G8_UINT = 50 + R8G8_SNORM = 51 + R8G8_SINT = 52 + R16_TYPELESS = 53 + R16_FLOAT = 54 + D16_UNORM = 55 + R16_UNORM = 56 + R16_UINT = 57 + R16_SNORM = 58 + R16_SINT = 59 + R8_TYPELESS = 60 + R8_UNORM = 61 + R8_UINT = 62 + R8_SNORM = 63 + R8_SINT = 64 + A8_UNORM = 65 + R1_UNORM = 66 + R9G9B9E5_SHAREDEXP = 67 + R8G8_B8G8_UNORM = 68 + G8R8_G8B8_UNORM = 69 + BC1_TYPELESS = 70 + BC1_UNORM = 71 + BC1_UNORM_SRGB = 72 + BC2_TYPELESS = 73 + BC2_UNORM = 74 + BC2_UNORM_SRGB = 75 + BC3_TYPELESS = 76 + BC3_UNORM = 77 + BC3_UNORM_SRGB = 78 + BC4_TYPELESS = 79 + BC4_UNORM = 80 + BC4_SNORM = 81 + BC5_TYPELESS = 82 + BC5_UNORM = 83 + BC5_SNORM = 84 + B5G6R5_UNORM = 85 + B5G5R5A1_UNORM = 86 + B8G8R8A8_UNORM = 87 + B8G8R8X8_UNORM = 88 + R10G10B10_XR_BIAS_A2_UNORM = 89 + B8G8R8A8_TYPELESS = 90 + B8G8R8A8_UNORM_SRGB = 91 + B8G8R8X8_TYPELESS = 92 + B8G8R8X8_UNORM_SRGB = 93 + BC6H_TYPELESS = 94 + BC6H_UF16 = 95 + BC6H_SF16 = 96 + BC7_TYPELESS = 97 + BC7_UNORM = 98 + BC7_UNORM_SRGB = 99 + AYUV = 100 + Y410 = 101 + Y416 = 102 + NV12 = 103 + P010 = 104 + P016 = 105 + OPAQUE_420 = 106 + YUY2 = 107 + Y210 = 108 + Y216 = 109 + NV11 = 110 + AI44 = 111 + IA44 = 112 + P8 = 113 + A8P8 = 114 + B4G4R4A4_UNORM = 115 + P208 = 130 + V208 = 131 + V408 = 132 + SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 189 + SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 190 + + +class D3DFMT(IntEnum): + UNKNOWN = 0 + R8G8B8 = 20 + A8R8G8B8 = 21 + X8R8G8B8 = 22 + R5G6B5 = 23 + X1R5G5B5 = 24 + A1R5G5B5 = 25 + A4R4G4B4 = 26 + R3G3B2 = 27 + A8 = 28 + A8R3G3B2 = 29 + X4R4G4B4 = 30 + A2B10G10R10 = 31 + A8B8G8R8 = 32 + X8B8G8R8 = 33 + G16R16 = 34 + A2R10G10B10 = 35 + A16B16G16R16 = 36 + A8P8 = 40 + P8 = 41 + L8 = 50 + A8L8 = 51 + A4L4 = 52 + V8U8 = 60 + L6V5U5 = 61 + X8L8V8U8 = 62 + Q8W8V8U8 = 63 + V16U16 = 64 + A2W10V10U10 = 67 + D16_LOCKABLE = 70 + D32 = 71 + D15S1 = 73 + D24S8 = 75 + D24X8 = 77 + D24X4S4 = 79 + D16 = 80 + D32F_LOCKABLE = 82 + D24FS8 = 83 + D32_LOCKABLE = 84 + S8_LOCKABLE = 85 + L16 = 81 + VERTEXDATA = 100 + INDEX16 = 101 + INDEX32 = 102 + Q16W16V16U16 = 110 + R16F = 111 + G16R16F = 112 + A16B16G16R16F = 113 + R32F = 114 + G32R32F = 115 + A32B32G32R32F = 116 + CxV8U8 = 117 + A1 = 118 + A2B10G10R10_XR_BIAS = 119 + BINARYBUFFER = 199 + + UYVY = i32(b"UYVY") + R8G8_B8G8 = i32(b"RGBG") + YUY2 = i32(b"YUY2") + G8R8_G8B8 = i32(b"GRGB") + DXT1 = i32(b"DXT1") + DXT2 = i32(b"DXT2") + DXT3 = i32(b"DXT3") + DXT4 = i32(b"DXT4") + DXT5 = i32(b"DXT5") + DX10 = i32(b"DX10") + BC4S = i32(b"BC4S") + BC4U = i32(b"BC4U") + BC5S = i32(b"BC5S") + BC5U = i32(b"BC5U") + ATI1 = i32(b"ATI1") + ATI2 = i32(b"ATI2") + MULTI2_ARGB8 = i32(b"MET1") + + +# Backward compatibility layer +module = sys.modules[__name__] +for item in DDSD: + setattr(module, "DDSD_" + item.name, item.value) +for item in DDSCAPS: + setattr(module, "DDSCAPS_" + item.name, item.value) +for item in DDSCAPS2: + setattr(module, "DDSCAPS2_" + item.name, item.value) +for item in DDPF: + setattr(module, "DDPF_" + item.name, item.value) + +DDS_FOURCC = DDPF.FOURCC +DDS_RGB = DDPF.RGB +DDS_RGBA = DDPF.RGB | DDPF.ALPHAPIXELS +DDS_LUMINANCE = DDPF.LUMINANCE +DDS_LUMINANCEA = DDPF.LUMINANCE | DDPF.ALPHAPIXELS +DDS_ALPHA = DDPF.ALPHA +DDS_PAL8 = DDPF.PALETTEINDEXED8 + +DDS_HEADER_FLAGS_TEXTURE = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT +DDS_HEADER_FLAGS_MIPMAP = DDSD.MIPMAPCOUNT +DDS_HEADER_FLAGS_VOLUME = DDSD.DEPTH +DDS_HEADER_FLAGS_PITCH = DDSD.PITCH +DDS_HEADER_FLAGS_LINEARSIZE = DDSD.LINEARSIZE + +DDS_HEIGHT = DDSD.HEIGHT +DDS_WIDTH = DDSD.WIDTH + +DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS.TEXTURE +DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS.COMPLEX | DDSCAPS.MIPMAP +DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS.COMPLEX + +DDS_CUBEMAP_POSITIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEX +DDS_CUBEMAP_NEGATIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEX +DDS_CUBEMAP_POSITIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEY +DDS_CUBEMAP_NEGATIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEY +DDS_CUBEMAP_POSITIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEZ +DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEZ + +DXT1_FOURCC = D3DFMT.DXT1 +DXT3_FOURCC = D3DFMT.DXT3 +DXT5_FOURCC = D3DFMT.DXT5 + +DXGI_FORMAT_R8G8B8A8_TYPELESS = DXGI_FORMAT.R8G8B8A8_TYPELESS +DXGI_FORMAT_R8G8B8A8_UNORM = DXGI_FORMAT.R8G8B8A8_UNORM +DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = DXGI_FORMAT.R8G8B8A8_UNORM_SRGB +DXGI_FORMAT_BC5_TYPELESS = DXGI_FORMAT.BC5_TYPELESS +DXGI_FORMAT_BC5_UNORM = DXGI_FORMAT.BC5_UNORM +DXGI_FORMAT_BC5_SNORM = DXGI_FORMAT.BC5_SNORM +DXGI_FORMAT_BC6H_UF16 = DXGI_FORMAT.BC6H_UF16 +DXGI_FORMAT_BC6H_SF16 = DXGI_FORMAT.BC6H_SF16 +DXGI_FORMAT_BC7_TYPELESS = DXGI_FORMAT.BC7_TYPELESS +DXGI_FORMAT_BC7_UNORM = DXGI_FORMAT.BC7_UNORM +DXGI_FORMAT_BC7_UNORM_SRGB = DXGI_FORMAT.BC7_UNORM_SRGB class DdsImageFile(ImageFile.ImageFile): @@ -124,166 +338,222 @@ class DdsImageFile(ImageFile.ImageFile): if len(header_bytes) != 120: msg = f"Incomplete header: {len(header_bytes)} bytes" raise OSError(msg) - header = BytesIO(header_bytes) + header = io.BytesIO(header_bytes) flags, height, width = struct.unpack("<3I", header.read(12)) self._size = (width, height) - self._mode = "RGBA" + extents = (0, 0) + self.size pitch, depth, mipmaps = struct.unpack("<3I", header.read(12)) struct.unpack("<11I", header.read(44)) # reserved # pixel format - pfsize, pfflags = struct.unpack("<2I", header.read(8)) - fourcc = header.read(4) - (bitcount,) = struct.unpack("<I", header.read(4)) - masks = struct.unpack("<4I", header.read(16)) - if pfflags & DDPF_LUMINANCE: - # Texture contains uncompressed L or LA data - if pfflags & DDPF_ALPHAPIXELS: - self._mode = "LA" - else: - self._mode = "L" - - self.tile = [("raw", (0, 0) + self.size, 0, (self.mode, 0, 1))] - elif pfflags & DDPF_RGB: + pfsize, pfflags, fourcc, bitcount = struct.unpack("<4I", header.read(16)) + n = 0 + rawmode = None + if pfflags & DDPF.RGB: # Texture contains uncompressed RGB data - masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)} - rawmode = "" - if pfflags & DDPF_ALPHAPIXELS: - rawmode += masks[0xFF000000] + if pfflags & DDPF.ALPHAPIXELS: + self._mode = "RGBA" + mask_count = 4 else: self._mode = "RGB" - rawmode += masks[0xFF0000] + masks[0xFF00] + masks[0xFF] + mask_count = 3 - self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))] - elif pfflags & DDPF_PALETTEINDEXED8: + masks = struct.unpack(f"<{mask_count}I", header.read(mask_count * 4)) + self.tile = [("dds_rgb", extents, 0, (bitcount, masks))] + return + elif pfflags & DDPF.LUMINANCE: + if bitcount == 8: + self._mode = "L" + elif bitcount == 16 and pfflags & DDPF.ALPHAPIXELS: + self._mode = "LA" + else: + msg = f"Unsupported bitcount {bitcount} for {pfflags}" + raise OSError(msg) + elif pfflags & DDPF.PALETTEINDEXED8: self._mode = "P" self.palette = ImagePalette.raw("RGBA", self.fp.read(1024)) - self.tile = [("raw", (0, 0) + self.size, 0, "L")] - else: - data_start = header_size + 4 - n = 0 - if fourcc == b"DXT1": + elif pfflags & DDPF.FOURCC: + offset = header_size + 4 + if fourcc == D3DFMT.DXT1: + self._mode = "RGBA" self.pixel_format = "DXT1" n = 1 - elif fourcc == b"DXT3": + elif fourcc == D3DFMT.DXT3: + self._mode = "RGBA" self.pixel_format = "DXT3" n = 2 - elif fourcc == b"DXT5": + elif fourcc == D3DFMT.DXT5: + self._mode = "RGBA" self.pixel_format = "DXT5" n = 3 - elif fourcc == b"ATI1": + elif fourcc in (D3DFMT.BC4U, D3DFMT.ATI1): + self._mode = "L" self.pixel_format = "BC4" n = 4 - self._mode = "L" - elif fourcc in (b"ATI2", b"BC5U"): - self.pixel_format = "BC5" - n = 5 + elif fourcc == D3DFMT.BC5S: self._mode = "RGB" - elif fourcc == b"BC5S": self.pixel_format = "BC5S" n = 5 + elif fourcc in (D3DFMT.BC5U, D3DFMT.ATI2): self._mode = "RGB" - elif fourcc == b"DX10": - data_start += 20 + self.pixel_format = "BC5" + n = 5 + elif fourcc == D3DFMT.DX10: + offset += 20 # ignoring flags which pertain to volume textures and cubemaps (dxgi_format,) = struct.unpack("<I", self.fp.read(4)) self.fp.read(16) - if dxgi_format in (DXGI_FORMAT_BC5_TYPELESS, DXGI_FORMAT_BC5_UNORM): + if dxgi_format in ( + DXGI_FORMAT.BC1_UNORM, + DXGI_FORMAT.BC1_TYPELESS, + ): + self._mode = "RGBA" + self.pixel_format = "BC1" + n = 1 + elif dxgi_format in (DXGI_FORMAT.BC4_TYPELESS, DXGI_FORMAT.BC4_UNORM): + self._mode = "L" + self.pixel_format = "BC4" + n = 4 + elif dxgi_format in (DXGI_FORMAT.BC5_TYPELESS, DXGI_FORMAT.BC5_UNORM): + self._mode = "RGB" self.pixel_format = "BC5" n = 5 + elif dxgi_format == DXGI_FORMAT.BC5_SNORM: self._mode = "RGB" - elif dxgi_format == DXGI_FORMAT_BC5_SNORM: self.pixel_format = "BC5S" n = 5 + elif dxgi_format == DXGI_FORMAT.BC6H_UF16: self._mode = "RGB" - elif dxgi_format == DXGI_FORMAT_BC6H_UF16: self.pixel_format = "BC6H" n = 6 + elif dxgi_format == DXGI_FORMAT.BC6H_SF16: self._mode = "RGB" - elif dxgi_format == DXGI_FORMAT_BC6H_SF16: self.pixel_format = "BC6HS" n = 6 - self._mode = "RGB" - elif dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM): - self.pixel_format = "BC7" - n = 7 - elif dxgi_format == DXGI_FORMAT_BC7_UNORM_SRGB: + elif dxgi_format in ( + DXGI_FORMAT.BC7_TYPELESS, + DXGI_FORMAT.BC7_UNORM, + DXGI_FORMAT.BC7_UNORM_SRGB, + ): + self._mode = "RGBA" self.pixel_format = "BC7" - self.info["gamma"] = 1 / 2.2 n = 7 + if dxgi_format == DXGI_FORMAT.BC7_UNORM_SRGB: + self.info["gamma"] = 1 / 2.2 elif dxgi_format in ( - DXGI_FORMAT_R8G8B8A8_TYPELESS, - DXGI_FORMAT_R8G8B8A8_UNORM, - DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, + DXGI_FORMAT.R8G8B8A8_TYPELESS, + DXGI_FORMAT.R8G8B8A8_UNORM, + DXGI_FORMAT.R8G8B8A8_UNORM_SRGB, ): - self.tile = [("raw", (0, 0) + self.size, 0, ("RGBA", 0, 1))] - if dxgi_format == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + self._mode = "RGBA" + if dxgi_format == DXGI_FORMAT.R8G8B8A8_UNORM_SRGB: self.info["gamma"] = 1 / 2.2 - return else: msg = f"Unimplemented DXGI format {dxgi_format}" raise NotImplementedError(msg) else: msg = f"Unimplemented pixel format {repr(fourcc)}" raise NotImplementedError(msg) + else: + msg = f"Unknown pixel format flags {pfflags}" + raise NotImplementedError(msg) + if n: self.tile = [ - ("bcn", (0, 0) + self.size, data_start, (n, self.pixel_format)) + ImageFile._Tile("bcn", extents, offset, (n, self.pixel_format)) ] + else: + self.tile = [ImageFile._Tile("raw", extents, 0, rawmode or self.mode)] def load_seek(self, pos): pass +class DdsRgbDecoder(ImageFile.PyDecoder): + _pulls_fd = True + + def decode(self, buffer): + bitcount, masks = self.args + + # Some masks will be padded with zeros, e.g. R 0b11 G 0b1100 + # Calculate how many zeros each mask is padded with + mask_offsets = [] + # And the maximum value of each channel without the padding + mask_totals = [] + for mask in masks: + offset = 0 + if mask != 0: + while mask >> (offset + 1) << (offset + 1) == mask: + offset += 1 + mask_offsets.append(offset) + mask_totals.append(mask >> offset) + + data = bytearray() + bytecount = bitcount // 8 + while len(data) < self.state.xsize * self.state.ysize * len(masks): + value = int.from_bytes(self.fd.read(bytecount), "little") + for i, mask in enumerate(masks): + masked_value = value & mask + # Remove the zero padding, and scale it to 8 bits + data += o8( + int(((masked_value >> mask_offsets[i]) / mask_totals[i]) * 255) + ) + self.set_as_raw(bytes(data)) + return -1, 0 + + def _save(im, fp, filename): if im.mode not in ("RGB", "RGBA", "L", "LA"): msg = f"cannot write mode {im.mode} as DDS" raise OSError(msg) - rawmode = im.mode - masks = [0xFF0000, 0xFF00, 0xFF] - if im.mode in ("L", "LA"): - pixel_flags = DDPF_LUMINANCE + alpha = im.mode[-1] == "A" + if im.mode[0] == "L": + pixel_flags = DDPF.LUMINANCE + rawmode = im.mode + if alpha: + rgba_mask = [0x000000FF, 0x000000FF, 0x000000FF] + else: + rgba_mask = [0xFF000000, 0xFF000000, 0xFF000000] else: - pixel_flags = DDPF_RGB - rawmode = rawmode[::-1] - if im.mode in ("LA", "RGBA"): - pixel_flags |= DDPF_ALPHAPIXELS - masks.append(0xFF000000) + pixel_flags = DDPF.RGB + rawmode = im.mode[::-1] + rgba_mask = [0x00FF0000, 0x0000FF00, 0x000000FF] - bitcount = len(masks) * 8 - while len(masks) < 4: - masks.append(0) + if alpha: + r, g, b, a = im.split() + im = Image.merge("RGBA", (a, r, g, b)) + if alpha: + pixel_flags |= DDPF.ALPHAPIXELS + rgba_mask.append(0xFF000000 if alpha else 0) + + flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PITCH | DDSD.PIXELFORMAT + bitcount = len(im.getbands()) * 8 + pitch = (im.width * bitcount + 7) // 8 fp.write( o32(DDS_MAGIC) - + o32(124) # header size - + o32( - DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PITCH | DDSD_PIXELFORMAT - ) # flags - + o32(im.height) - + o32(im.width) - + o32((im.width * bitcount + 7) // 8) # pitch - + o32(0) # depth - + o32(0) # mipmaps - + o32(0) * 11 # reserved - + o32(32) # pfsize - + o32(pixel_flags) # pfflags - + o32(0) # fourcc - + o32(bitcount) # bitcount - + b"".join(o32(mask) for mask in masks) # rgbabitmask - + o32(DDSCAPS_TEXTURE) # dwCaps - + o32(0) # dwCaps2 - + o32(0) # dwCaps3 - + o32(0) # dwCaps4 - + o32(0) # dwReserved2 + + struct.pack( + "<7I", + 124, # header size + flags, # flags + im.height, + im.width, + pitch, + 0, # depth + 0, # mipmaps + ) + + struct.pack("11I", *((0,) * 11)) # reserved + # pfsize, pfflags, fourcc, bitcount + + struct.pack("<4I", 32, pixel_flags, 0, bitcount) + + struct.pack("<4I", *rgba_mask) # dwRGBABitMask + + struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0) + ) + ImageFile._save( + im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))] ) - if im.mode == "RGBA": - r, g, b, a = im.split() - im = Image.merge("RGBA", (a, r, g, b)) - ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]) def _accept(prefix): @@ -291,5 +561,6 @@ def _accept(prefix): Image.register_open(DdsImageFile.format, DdsImageFile, _accept) +Image.register_decoder("dds_rgb", DdsRgbDecoder) Image.register_save(DdsImageFile.format, _save) Image.register_extension(DdsImageFile.format, ".dds") diff --git a/contrib/python/Pillow/py3/PIL/EpsImagePlugin.py b/contrib/python/Pillow/py3/PIL/EpsImagePlugin.py index 9b2fce0ac0..d2e60aa075 100644 --- a/contrib/python/Pillow/py3/PIL/EpsImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/EpsImagePlugin.py @@ -19,6 +19,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import io import os @@ -77,14 +78,11 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False): # Hack to support hi-res rendering scale = int(scale) or 1 - # orig_size = size - # orig_bbox = bbox - size = (size[0] * scale, size[1] * scale) + width = size[0] * scale + height = size[1] * scale # resolution is dependent on bbox and size - res = ( - 72.0 * size[0] / (bbox[2] - bbox[0]), - 72.0 * size[1] / (bbox[3] - bbox[1]), - ) + res_x = 72.0 * width / (bbox[2] - bbox[0]) + res_y = 72.0 * height / (bbox[3] - bbox[1]) out_fd, outfile = tempfile.mkstemp() os.close(out_fd) @@ -121,8 +119,8 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False): command = [ gs_binary, "-q", # quiet mode - "-g%dx%d" % size, # set output geometry (pixels) - "-r%fx%f" % res, # set input DPI (dots per inch) + f"-g{width:d}x{height:d}", # set output geometry (pixels) + f"-r{res_x:f}x{res_y:f}", # set input DPI (dots per inch) "-dBATCH", # exit after processing "-dNOPAUSE", # don't pause between pages "-dSAFER", # safe mode diff --git a/contrib/python/Pillow/py3/PIL/ExifTags.py b/contrib/python/Pillow/py3/PIL/ExifTags.py index 2347c6d4c2..60a4d9774a 100644 --- a/contrib/python/Pillow/py3/PIL/ExifTags.py +++ b/contrib/python/Pillow/py3/PIL/ExifTags.py @@ -13,6 +13,7 @@ This module provides constants and clear-text names for various well-known EXIF tags. """ +from __future__ import annotations from enum import IntEnum diff --git a/contrib/python/Pillow/py3/PIL/FitsImagePlugin.py b/contrib/python/Pillow/py3/PIL/FitsImagePlugin.py index e0e51aaac7..7dce2d60f7 100644 --- a/contrib/python/Pillow/py3/PIL/FitsImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/FitsImagePlugin.py @@ -8,6 +8,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import math @@ -54,12 +55,10 @@ class FitsImageFile(ImageFile.ImageFile): self._mode = "L" elif number_of_bits == 16: self._mode = "I" - # rawmode = "I;16S" elif number_of_bits == 32: self._mode = "I" elif number_of_bits in (-32, -64): self._mode = "F" - # rawmode = "F" if number_of_bits == -32 else "F;64F" offset = math.ceil(self.fp.tell() / 2880) * 2880 self.tile = [("raw", (0, 0) + self.size, offset, (self.mode, 0, -1))] diff --git a/contrib/python/Pillow/py3/PIL/FliImagePlugin.py b/contrib/python/Pillow/py3/PIL/FliImagePlugin.py index 8f641ece99..9769761fc1 100644 --- a/contrib/python/Pillow/py3/PIL/FliImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/FliImagePlugin.py @@ -14,6 +14,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import os @@ -150,7 +151,8 @@ class FliImageFile(ImageFile.ImageFile): s = self.fp.read(4) if not s: - raise EOFError + msg = "missing frame size" + raise EOFError(msg) framesize = i32(s) diff --git a/contrib/python/Pillow/py3/PIL/FontFile.py b/contrib/python/Pillow/py3/PIL/FontFile.py index 5ec0a6632e..3ec1ae819f 100644 --- a/contrib/python/Pillow/py3/PIL/FontFile.py +++ b/contrib/python/Pillow/py3/PIL/FontFile.py @@ -13,16 +13,19 @@ # # See the README file for information on usage and redistribution. # - +from __future__ import annotations import os +from typing import BinaryIO from . import Image, _binary WIDTH = 800 -def puti16(fp, values): +def puti16( + fp: BinaryIO, values: tuple[int, int, int, int, int, int, int, int, int, int] +) -> None: """Write network order (big-endian) 16-bit sequence""" for v in values: if v < 0: @@ -33,16 +36,34 @@ def puti16(fp, values): class FontFile: """Base class for raster font file handlers.""" - bitmap = None - - def __init__(self): - self.info = {} - self.glyph = [None] * 256 - - def __getitem__(self, ix): + bitmap: Image.Image | None = None + + def __init__(self) -> None: + self.info: dict[bytes, bytes | int] = {} + self.glyph: list[ + tuple[ + tuple[int, int], + tuple[int, int, int, int], + tuple[int, int, int, int], + Image.Image, + ] + | None + ] = [None] * 256 + + def __getitem__( + self, ix: int + ) -> ( + tuple[ + tuple[int, int], + tuple[int, int, int, int], + tuple[int, int, int, int], + Image.Image, + ] + | None + ): return self.glyph[ix] - def compile(self): + def compile(self) -> None: """Create metrics and bitmap""" if self.bitmap: @@ -51,7 +72,7 @@ class FontFile: # create bitmap large enough to hold all data h = w = maxwidth = 0 lines = 1 - for glyph in self: + for glyph in self.glyph: if glyph: d, dst, src, im = glyph h = max(h, src[3] - src[1]) @@ -65,20 +86,22 @@ class FontFile: ysize = lines * h if xsize == 0 and ysize == 0: - return "" + return self.ysize = h # paste glyphs into bitmap self.bitmap = Image.new("1", (xsize, ysize)) - self.metrics = [None] * 256 + self.metrics: list[ + tuple[tuple[int, int], tuple[int, int, int, int], tuple[int, int, int, int]] + | None + ] = [None] * 256 x = y = 0 for i in range(256): glyph = self[i] if glyph: d, dst, src, im = glyph xx = src[2] - src[0] - # yy = src[3] - src[1] x0, y0 = x, y x = x + xx if x > WIDTH: @@ -89,12 +112,15 @@ class FontFile: self.bitmap.paste(im.crop(src), s) self.metrics[i] = d, dst, s - def save(self, filename): + def save(self, filename: str) -> None: """Save font""" self.compile() # font data + if not self.bitmap: + msg = "No bitmap created" + raise ValueError(msg) self.bitmap.save(os.path.splitext(filename)[0] + ".pbm", "PNG") # font metrics @@ -105,6 +131,6 @@ class FontFile: for id in range(256): m = self.metrics[id] if not m: - puti16(fp, [0] * 10) + puti16(fp, (0,) * 10) else: puti16(fp, m[0] + m[1] + m[2]) diff --git a/contrib/python/Pillow/py3/PIL/FpxImagePlugin.py b/contrib/python/Pillow/py3/PIL/FpxImagePlugin.py index a878cbfd20..75680a94e0 100644 --- a/contrib/python/Pillow/py3/PIL/FpxImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/FpxImagePlugin.py @@ -14,6 +14,8 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations + import olefile from . import Image, ImageFile @@ -97,16 +99,15 @@ class FpxImageFile(ImageFile.ImageFile): s = prop[0x2000002 | id] - colors = [] bands = i32(s, 4) if bands > 4: msg = "Invalid number of bands" raise OSError(msg) - for i in range(bands): - # note: for now, we ignore the "uncalibrated" flag - colors.append(i32(s, 8 + i * 4) & 0x7FFFFFFF) - self._mode, self.rawmode = MODES[tuple(colors)] + # note: for now, we ignore the "uncalibrated" flag + colors = tuple(i32(s, 8 + i * 4) & 0x7FFFFFFF for i in range(bands)) + + self._mode, self.rawmode = MODES[colors] # load JPEG tables, if any self.jpeg = {} @@ -227,6 +228,7 @@ class FpxImageFile(ImageFile.ImageFile): break # isn't really required self.stream = stream + self._fp = self.fp self.fp = None def load(self): diff --git a/contrib/python/Pillow/py3/PIL/FtexImagePlugin.py b/contrib/python/Pillow/py3/PIL/FtexImagePlugin.py index c2e4ead717..d5513a56a1 100644 --- a/contrib/python/Pillow/py3/PIL/FtexImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/FtexImagePlugin.py @@ -50,6 +50,7 @@ bytes for that mipmap level. Note: All data is stored in little-Endian (Intel) byte order. """ +from __future__ import annotations import struct from enum import IntEnum diff --git a/contrib/python/Pillow/py3/PIL/GbrImagePlugin.py b/contrib/python/Pillow/py3/PIL/GbrImagePlugin.py index ec6e9de6e7..6722fa2b14 100644 --- a/contrib/python/Pillow/py3/PIL/GbrImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/GbrImagePlugin.py @@ -23,6 +23,7 @@ # Version 2 files are saved by GIMP v2.8 (at least) # Version 3 files have a format specifier of 18 for 16bit floats in # the color depth field. This is currently unsupported by Pillow. +from __future__ import annotations from . import Image, ImageFile from ._binary import i32be as i32 diff --git a/contrib/python/Pillow/py3/PIL/GdImageFile.py b/contrib/python/Pillow/py3/PIL/GdImageFile.py index 3599994a8e..d84876eb6b 100644 --- a/contrib/python/Pillow/py3/PIL/GdImageFile.py +++ b/contrib/python/Pillow/py3/PIL/GdImageFile.py @@ -25,7 +25,7 @@ implementation is provided for convenience and demonstrational purposes only. """ - +from __future__ import annotations from . import ImageFile, ImagePalette, UnidentifiedImageError from ._binary import i16be as i16 diff --git a/contrib/python/Pillow/py3/PIL/GifImagePlugin.py b/contrib/python/Pillow/py3/PIL/GifImagePlugin.py index 92074b0d49..57d87078bc 100644 --- a/contrib/python/Pillow/py3/PIL/GifImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/GifImagePlugin.py @@ -23,6 +23,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import itertools import math @@ -30,7 +31,15 @@ import os import subprocess from enum import IntEnum -from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence +from . import ( + Image, + ImageChops, + ImageFile, + ImageMath, + ImageOps, + ImagePalette, + ImageSequence, +) from ._binary import i16le as i16 from ._binary import o8 from ._binary import o16le as o16 @@ -183,7 +192,8 @@ class GifImageFile(ImageFile.ImageFile): s = self.fp.read(1) if not s or s == b";": - raise EOFError + msg = "no more images in GIF file" + raise EOFError(msg) palette = None @@ -280,15 +290,11 @@ class GifImageFile(ImageFile.ImageFile): bits = self.fp.read(1)[0] self.__offset = self.fp.tell() break - - else: - pass - # raise OSError, "illegal GIF tag `%x`" % s[0] s = None if interlace is None: - # self._fp = None - raise EOFError + msg = "image not found in GIF frame" + raise EOFError(msg) self.__frame = frame if not update_image: @@ -333,6 +339,8 @@ class GifImageFile(ImageFile.ImageFile): def _rgb(color): if self._frame_palette: + if color * 3 + 3 > len(self._frame_palette.palette): + color = 0 color = tuple(self._frame_palette.palette[color * 3 : color * 3 + 3]) else: color = (color, color, color) @@ -537,7 +545,15 @@ def _normalize_palette(im, palette, info): else: used_palette_colors = _get_optimize(im, info) if used_palette_colors is not None: - return im.remap_palette(used_palette_colors, source_palette) + im = im.remap_palette(used_palette_colors, source_palette) + if "transparency" in info: + try: + info["transparency"] = used_palette_colors.index( + info["transparency"] + ) + except ValueError: + del info["transparency"] + return im im.palette.palette = source_palette return im @@ -565,13 +581,11 @@ def _write_single_frame(im, fp, palette): def _getbbox(base_im, im_frame): - if _get_palette_bytes(im_frame) == _get_palette_bytes(base_im): - delta = ImageChops.subtract_modulo(im_frame, base_im) - else: - delta = ImageChops.subtract_modulo( - im_frame.convert("RGBA"), base_im.convert("RGBA") - ) - return delta.getbbox(alpha_only=False) + if _get_palette_bytes(im_frame) != _get_palette_bytes(base_im): + im_frame = im_frame.convert("RGBA") + base_im = base_im.convert("RGBA") + delta = ImageChops.subtract_modulo(im_frame, base_im) + return delta, delta.getbbox(alpha_only=False) def _write_multiple_frames(im, fp, palette): @@ -579,6 +593,7 @@ def _write_multiple_frames(im, fp, palette): disposal = im.encoderinfo.get("disposal", im.info.get("disposal")) im_frames = [] + previous_im = None frame_count = 0 background_im = None for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])): @@ -592,9 +607,9 @@ def _write_multiple_frames(im, fp, palette): im.encoderinfo.setdefault(k, v) encoderinfo = im.encoderinfo.copy() - im_frame = _normalize_palette(im_frame, palette, encoderinfo) if "transparency" in im_frame.info: encoderinfo.setdefault("transparency", im_frame.info["transparency"]) + im_frame = _normalize_palette(im_frame, palette, encoderinfo) if isinstance(duration, (list, tuple)): encoderinfo["duration"] = duration[frame_count] elif duration is None and "duration" in im_frame.info: @@ -603,14 +618,16 @@ def _write_multiple_frames(im, fp, palette): encoderinfo["disposal"] = disposal[frame_count] frame_count += 1 + diff_frame = None if im_frames: # delta frame - previous = im_frames[-1] - bbox = _getbbox(previous["im"], im_frame) + delta, bbox = _getbbox(previous_im, im_frame) if not bbox: # This frame is identical to the previous frame if encoderinfo.get("duration"): - previous["encoderinfo"]["duration"] += encoderinfo["duration"] + im_frames[-1]["encoderinfo"]["duration"] += encoderinfo[ + "duration" + ] continue if encoderinfo.get("disposal") == 2: if background_im is None: @@ -620,33 +637,67 @@ def _write_multiple_frames(im, fp, palette): background = _get_background(im_frame, color) background_im = Image.new("P", im_frame.size, background) background_im.putpalette(im_frames[0]["im"].palette) - bbox = _getbbox(background_im, im_frame) + delta, bbox = _getbbox(background_im, im_frame) + if encoderinfo.get("optimize") and im_frame.mode != "1": + if "transparency" not in encoderinfo: + try: + encoderinfo[ + "transparency" + ] = im_frame.palette._new_color_index(im_frame) + except ValueError: + pass + if "transparency" in encoderinfo: + # When the delta is zero, fill the image with transparency + diff_frame = im_frame.copy() + fill = Image.new( + "P", diff_frame.size, encoderinfo["transparency"] + ) + if delta.mode == "RGBA": + r, g, b, a = delta.split() + mask = ImageMath.eval( + "convert(max(max(max(r, g), b), a) * 255, '1')", + r=r, + g=g, + b=b, + a=a, + ) + else: + if delta.mode == "P": + # Convert to L without considering palette + delta_l = Image.new("L", delta.size) + delta_l.putdata(delta.getdata()) + delta = delta_l + mask = ImageMath.eval("convert(im * 255, '1')", im=delta) + diff_frame.paste(fill, mask=ImageOps.invert(mask)) else: bbox = None - im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo}) - - if len(im_frames) > 1: - for frame_data in im_frames: - im_frame = frame_data["im"] - if not frame_data["bbox"]: - # global header - for s in _get_global_header(im_frame, frame_data["encoderinfo"]): - fp.write(s) - offset = (0, 0) - else: - # compress difference - if not palette: - frame_data["encoderinfo"]["include_color_table"] = True - - im_frame = im_frame.crop(frame_data["bbox"]) - offset = frame_data["bbox"][:2] - _write_frame_data(fp, im_frame, offset, frame_data["encoderinfo"]) - return True - elif "duration" in im.encoderinfo and isinstance( - im.encoderinfo["duration"], (list, tuple) - ): - # Since multiple frames will not be written, add together the frame durations - im.encoderinfo["duration"] = sum(im.encoderinfo["duration"]) + previous_im = im_frame + im_frames.append( + {"im": diff_frame or im_frame, "bbox": bbox, "encoderinfo": encoderinfo} + ) + + if len(im_frames) == 1: + if "duration" in im.encoderinfo: + # Since multiple frames will not be written, use the combined duration + im.encoderinfo["duration"] = im_frames[0]["encoderinfo"]["duration"] + return + + for frame_data in im_frames: + im_frame = frame_data["im"] + if not frame_data["bbox"]: + # global header + for s in _get_global_header(im_frame, frame_data["encoderinfo"]): + fp.write(s) + offset = (0, 0) + else: + # compress difference + if not palette: + frame_data["encoderinfo"]["include_color_table"] = True + + im_frame = im_frame.crop(frame_data["bbox"]) + offset = frame_data["bbox"][:2] + _write_frame_data(fp, im_frame, offset, frame_data["encoderinfo"]) + return True def _save_all(im, fp, filename): @@ -659,7 +710,7 @@ def _save(im, fp, filename, save_all=False): palette = im.encoderinfo.get("palette", im.info.get("palette")) else: palette = None - im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True) + im.encoderinfo.setdefault("optimize", True) if not save_all or not _write_multiple_frames(im, fp, palette): _write_single_frame(im, fp, palette) @@ -681,22 +732,10 @@ def get_interlace(im): def _write_local_header(fp, im, offset, flags): - transparent_color_exists = False try: - transparency = int(im.encoderinfo["transparency"]) - except (KeyError, ValueError): - pass - else: - # optimize the block away if transparent color is not used - transparent_color_exists = True - - used_palette_colors = _get_optimize(im, im.encoderinfo) - if used_palette_colors is not None: - # adjust the transparency index after optimize - try: - transparency = used_palette_colors.index(transparency) - except ValueError: - transparent_color_exists = False + transparency = im.encoderinfo["transparency"] + except KeyError: + transparency = None if "duration" in im.encoderinfo: duration = int(im.encoderinfo["duration"] / 10) @@ -705,11 +744,9 @@ def _write_local_header(fp, im, offset, flags): disposal = int(im.encoderinfo.get("disposal", 0)) - if transparent_color_exists or duration != 0 or disposal: - packed_flag = 1 if transparent_color_exists else 0 + if transparency is not None or duration != 0 or disposal: + packed_flag = 1 if transparency is not None else 0 packed_flag |= disposal << 2 - if not transparent_color_exists: - transparency = 0 fp.write( b"!" @@ -717,7 +754,7 @@ def _write_local_header(fp, im, offset, flags): + o8(4) # length + o8(packed_flag) # packed fields + o16(duration) # duration - + o8(transparency) # transparency index + + o8(transparency or 0) # transparency index + o8(0) ) @@ -805,7 +842,7 @@ def _get_optimize(im, info): :param info: encoderinfo :returns: list of indexes of palette entries in use, or None """ - if im.mode in ("P", "L") and info and info.get("optimize", 0): + if im.mode in ("P", "L") and info and info.get("optimize"): # Potentially expensive operation. # The palette saves 3 bytes per color not used, but palette diff --git a/contrib/python/Pillow/py3/PIL/GimpGradientFile.py b/contrib/python/Pillow/py3/PIL/GimpGradientFile.py index 8e801be0b8..2d8c78ea91 100644 --- a/contrib/python/Pillow/py3/PIL/GimpGradientFile.py +++ b/contrib/python/Pillow/py3/PIL/GimpGradientFile.py @@ -18,7 +18,7 @@ Stuff to translate curve segments to palette values (derived from the corresponding code in GIMP, written by Federico Mena Quintero. See the GIMP distribution for more information.) """ - +from __future__ import annotations from math import log, pi, sin, sqrt diff --git a/contrib/python/Pillow/py3/PIL/GimpPaletteFile.py b/contrib/python/Pillow/py3/PIL/GimpPaletteFile.py index d388928945..a3109ebaa1 100644 --- a/contrib/python/Pillow/py3/PIL/GimpPaletteFile.py +++ b/contrib/python/Pillow/py3/PIL/GimpPaletteFile.py @@ -13,6 +13,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import re diff --git a/contrib/python/Pillow/py3/PIL/GribStubImagePlugin.py b/contrib/python/Pillow/py3/PIL/GribStubImagePlugin.py index c1c71da08c..f8106800c4 100644 --- a/contrib/python/Pillow/py3/PIL/GribStubImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/GribStubImagePlugin.py @@ -8,6 +8,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations from . import Image, ImageFile diff --git a/contrib/python/Pillow/py3/PIL/Hdf5StubImagePlugin.py b/contrib/python/Pillow/py3/PIL/Hdf5StubImagePlugin.py index c26b480acf..65409e269f 100644 --- a/contrib/python/Pillow/py3/PIL/Hdf5StubImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/Hdf5StubImagePlugin.py @@ -8,6 +8,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations from . import Image, ImageFile diff --git a/contrib/python/Pillow/py3/PIL/IcnsImagePlugin.py b/contrib/python/Pillow/py3/PIL/IcnsImagePlugin.py index 0aa4f7a845..d877b4ecba 100644 --- a/contrib/python/Pillow/py3/PIL/IcnsImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/IcnsImagePlugin.py @@ -16,6 +16,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import io import os @@ -391,8 +392,8 @@ if __name__ == "__main__": with open(sys.argv[1], "rb") as fp: imf = IcnsImageFile(fp) for size in imf.info["sizes"]: - imf.size = size - imf.save("out-%s-%s-%s.png" % size) + width, height, scale = imf.size = size + imf.save(f"out-{width}-{height}-{scale}.png") with Image.open(sys.argv[1]) as im: im.save("out.png") if sys.platform == "windows": diff --git a/contrib/python/Pillow/py3/PIL/IcoImagePlugin.py b/contrib/python/Pillow/py3/PIL/IcoImagePlugin.py index 0445a2ab22..1b22f8645d 100644 --- a/contrib/python/Pillow/py3/PIL/IcoImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/IcoImagePlugin.py @@ -20,7 +20,7 @@ # Icon format references: # * https://en.wikipedia.org/wiki/ICO_(file_format) # * https://msdn.microsoft.com/en-us/library/ms997538.aspx - +from __future__ import annotations import warnings from io import BytesIO @@ -174,9 +174,7 @@ class IcoFile: self.entry = sorted(self.entry, key=lambda x: x["color_depth"]) # ICO images are usually squares - # self.entry = sorted(self.entry, key=lambda x: x['width']) - self.entry = sorted(self.entry, key=lambda x: x["square"]) - self.entry.reverse() + self.entry = sorted(self.entry, key=lambda x: x["square"], reverse=True) def sizes(self): """ diff --git a/contrib/python/Pillow/py3/PIL/ImImagePlugin.py b/contrib/python/Pillow/py3/PIL/ImImagePlugin.py index b42ba7cac7..97d726a8a6 100644 --- a/contrib/python/Pillow/py3/PIL/ImImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/ImImagePlugin.py @@ -24,7 +24,7 @@ # # See the README file for information on usage and redistribution. # - +from __future__ import annotations import os import re diff --git a/contrib/python/Pillow/py3/PIL/Image.py b/contrib/python/Pillow/py3/PIL/Image.py index 1adca9ad5b..1bba9aad2c 100644 --- a/contrib/python/Pillow/py3/PIL/Image.py +++ b/contrib/python/Pillow/py3/PIL/Image.py @@ -24,6 +24,8 @@ # See the README file for information on usage and redistribution. # +from __future__ import annotations + import atexit import builtins import io @@ -40,7 +42,7 @@ from enum import IntEnum from pathlib import Path try: - import defusedxml.ElementTree as ElementTree + from defusedxml import ElementTree except ImportError: ElementTree = None @@ -90,7 +92,7 @@ try: raise ImportError(msg) except ImportError as v: - core = DeferredError(ImportError("The _imaging C module is not installed.")) + core = DeferredError.new(ImportError("The _imaging C module is not installed.")) # Explanations for ways that we know we might have an import error if str(v).startswith("Module use of python"): # The _imaging C module is present, but not compiled for @@ -355,10 +357,11 @@ def init(): if _initialized >= 2: return 0 + parent_name = __name__.rpartition(".")[0] for plugin in _plugins: try: logger.debug("Importing %s", plugin) - __import__(f"PIL.{plugin}", globals(), locals(), []) + __import__(f"{parent_name}.{plugin}", globals(), locals(), []) except ImportError as e: logger.debug("Image: failed to import %s: %s", plugin, e) @@ -476,8 +479,8 @@ class Image: * :py:func:`~PIL.Image.frombytes` """ - format = None - format_description = None + format: str | None = None + format_description: str | None = None _close_exclusive_fp_after_loading = True def __init__(self): @@ -527,15 +530,19 @@ class Image: def __enter__(self): return self + def _close_fp(self): + if getattr(self, "_fp", False): + if self._fp != self.fp: + self._fp.close() + self._fp = DeferredError(ValueError("Operation on closed image")) + if self.fp: + self.fp.close() + def __exit__(self, *args): - if hasattr(self, "fp") and getattr(self, "_exclusive_fp", False): - if getattr(self, "_fp", False): - if self._fp != self.fp: - self._fp.close() - self._fp = DeferredError(ValueError("Operation on closed image")) - if self.fp: - self.fp.close() - self.fp = None + if hasattr(self, "fp"): + if getattr(self, "_exclusive_fp", False): + self._close_fp() + self.fp = None def close(self): """ @@ -549,16 +556,12 @@ class Image: :py:meth:`~PIL.Image.Image.load` method. See :ref:`file-handling` for more information. """ - try: - if getattr(self, "_fp", False): - if self._fp != self.fp: - self._fp.close() - self._fp = DeferredError(ValueError("Operation on closed image")) - if self.fp: - self.fp.close() - self.fp = None - except Exception as msg: - logger.debug("Error closing: %s", msg) + if hasattr(self, "fp"): + try: + self._close_fp() + self.fp = None + except Exception as msg: + logger.debug("Error closing: %s", msg) if getattr(self, "map", None): self.map = None @@ -791,6 +794,9 @@ class Image: but loads data into this image instead of creating a new image object. """ + if self.width == 0 or self.height == 0: + return + # may pass tuple instead of argument list if len(args) == 1 and isinstance(args[0], tuple): args = args[0] @@ -878,12 +884,12 @@ class Image: "L", "RGB" and "CMYK". The ``matrix`` argument only supports "L" and "RGB". - When translating a color image to greyscale (mode "L"), + When translating a color image to grayscale (mode "L"), the library uses the ITU-R 601-2 luma transform:: L = R * 299/1000 + G * 587/1000 + B * 114/1000 - The default method of converting a greyscale ("L") or "RGB" + The default method of converting a grayscale ("L") or "RGB" image into a bilevel (mode "1") image uses Floyd-Steinberg dither to approximate the original image luminosity levels. If dither is ``None``, all values larger than 127 are set to 255 (white), @@ -1156,7 +1162,7 @@ class Image: if palette.mode != "P": msg = "bad mode for palette image" raise ValueError(msg) - if self.mode != "RGB" and self.mode != "L": + if self.mode not in {"RGB", "L"}: msg = "only RGB or L mode images can be quantized to a palette" raise ValueError(msg) im = self.im.convert("P", dither, palette.im) @@ -1174,7 +1180,7 @@ class Image: return im - def copy(self): + def copy(self) -> Image: """ Copies this image. Use this method if you wish to paste things into an image, but still retain the original. @@ -1187,7 +1193,7 @@ class Image: __copy__ = copy - def crop(self, box=None): + def crop(self, box=None) -> Image: """ Returns a rectangular region from this image. The box is a 4-tuple defining the left, upper, right, and lower pixel @@ -1238,7 +1244,7 @@ class Image: Configures the image file loader so it returns a version of the image that as closely as possible matches the given mode and size. For example, you can use this method to convert a color - JPEG to greyscale while loading it. + JPEG to grayscale while loading it. If any changes are made, returns a tuple with the chosen ``mode`` and ``box`` with coordinates of the original image within the altered one. @@ -1284,9 +1290,9 @@ class Image: if self.im.bands == 1 or multiband: return self._new(filter.filter(self.im)) - ims = [] - for c in range(self.im.bands): - ims.append(self._new(filter.filter(self.im.getband(c)))) + ims = [ + self._new(filter.filter(self.im.getband(c))) for c in range(self.im.bands) + ] return merge(self.mode, ims) def getbands(self): @@ -1335,10 +1341,7 @@ class Image: self.load() if self.mode in ("1", "L", "P"): h = self.im.histogram() - out = [] - for i in range(256): - if h[i]: - out.append((h[i], i)) + out = [(h[i], i) for i in range(256) if h[i]] if len(out) > maxcolors: return None return out @@ -1379,10 +1382,7 @@ class Image: self.load() if self.im.bands > 1: - extrema = [] - for i in range(self.im.bands): - extrema.append(self.im.getband(i).getextrema()) - return tuple(extrema) + return tuple(self.im.getband(i).getextrema() for i in range(self.im.bands)) return self.im.getextrema() def _getxmp(self, xmp_tags): @@ -1610,13 +1610,13 @@ class Image: than one band, the histograms for all bands are concatenated (for example, the histogram for an "RGB" image contains 768 values). - A bilevel image (mode "1") is treated as a greyscale ("L") image + A bilevel image (mode "1") is treated as a grayscale ("L") image by this method. If a mask is provided, the method returns a histogram for those parts of the image where the mask image is non-zero. The mask image must have the same size as the image, and be either a - bi-level image (mode "1") or a greyscale image ("L"). + bi-level image (mode "1") or a grayscale image ("L"). :param mask: An optional mask. :param extrema: An optional tuple of manually-specified extrema. @@ -1636,13 +1636,13 @@ class Image: """ Calculates and returns the entropy for the image. - A bilevel image (mode "1") is treated as a greyscale ("L") + A bilevel image (mode "1") is treated as a grayscale ("L") image by this method. If a mask is provided, the method employs the histogram for those parts of the image where the mask image is non-zero. The mask image must have the same size as the image, and be - either a bi-level image (mode "1") or a greyscale image ("L"). + either a bi-level image (mode "1") or a grayscale image ("L"). :param mask: An optional mask. :param extrema: An optional tuple of manually-specified extrema. @@ -1658,7 +1658,7 @@ class Image: return self.im.entropy(extrema) return self.im.entropy() - def paste(self, im, box=None, mask=None): + def paste(self, im, box=None, mask=None) -> None: """ Pastes another image into this image. The box argument is either a 2-tuple giving the upper left corner, a 4-tuple defining the @@ -1862,7 +1862,8 @@ class Image: # do things the hard way im = self.im.convert(mode) if im.mode not in ("LA", "PA", "RGBA"): - raise ValueError from e # sanity check + msg = "alpha channel could not be added" + raise ValueError(msg) from e # sanity check self.im = im self.pyaccess = None self._mode = self.im.mode @@ -2350,7 +2351,7 @@ class Image: (w, h), Transform.AFFINE, matrix, resample, fillcolor=fillcolor ) - def save(self, fp, format=None, **params): + def save(self, fp, format=None, **params) -> None: """ Saves this image under the given filename. If no format is specified, the format to use is determined from the filename @@ -2448,7 +2449,7 @@ class Image: if open_fp: fp.close() - def seek(self, frame): + def seek(self, frame) -> Image: """ Seeks to the given frame in this sequence file. If you seek beyond the end of the sequence, the method raises an @@ -2467,7 +2468,8 @@ class Image: # overridden by file handlers if frame != 0: - raise EOFError + msg = "no more images in file" + raise EOFError(msg) def show(self, title=None): """ @@ -2534,7 +2536,7 @@ class Image: return self._new(self.im.getband(channel)) - def tell(self): + def tell(self) -> int: """ Returns the current frame number. See :py:meth:`~PIL.Image.Image.seek`. @@ -2641,7 +2643,7 @@ class Image: resample=Resampling.NEAREST, fill=1, fillcolor=None, - ): + ) -> Image: """ Transforms this image. This method creates a new image with the given size, and the same mode as the original, and copies data @@ -2874,7 +2876,7 @@ class ImageTransformHandler: def _wedge(): - """Create greyscale wedge (for debugging only)""" + """Create grayscale wedge (for debugging only)""" return Image()._new(core.wedge("L")) @@ -2900,7 +2902,7 @@ def _check_size(size): return True -def new(mode, size, color=0): +def new(mode, size, color=0) -> Image: """ Creates a new image with the given mode and size. @@ -2939,7 +2941,7 @@ def new(mode, size, color=0): return im._new(core.fill(mode, size, color)) -def frombytes(mode, size, data, decoder_name="raw", *args): +def frombytes(mode, size, data, decoder_name="raw", *args) -> Image: """ Creates a copy of an image memory from pixel data in a buffer. @@ -2965,15 +2967,16 @@ def frombytes(mode, size, data, decoder_name="raw", *args): _check_size(size) - # may pass tuple instead of argument list - if len(args) == 1 and isinstance(args[0], tuple): - args = args[0] + im = new(mode, size) + if im.width != 0 and im.height != 0: + # may pass tuple instead of argument list + if len(args) == 1 and isinstance(args[0], tuple): + args = args[0] - if decoder_name == "raw" and args == (): - args = mode + if decoder_name == "raw" and args == (): + args = mode - im = new(mode, size) - im.frombytes(data, decoder_name, args) + im.frombytes(data, decoder_name, args) return im @@ -3094,7 +3097,8 @@ def fromarray(obj, mode=None): try: mode, rawmode = _fromarray_typemap[typekey] except KeyError as e: - msg = "Cannot handle this data type: %s, %s" % typekey + typekey_shape, typestr = typekey + msg = f"Cannot handle this data type: {typekey_shape}, {typestr}" raise TypeError(msg) from e else: rawmode = mode @@ -3186,7 +3190,7 @@ def _decompression_bomb_check(size): ) -def open(fp, mode="r", formats=None): +def open(fp, mode="r", formats=None) -> Image: """ Opens and identifies the given image file. @@ -3411,7 +3415,7 @@ def merge(mode, bands): # Plugin registry -def register_open(id, factory, accept=None): +def register_open(id, factory, accept=None) -> None: """ Register an image file plugin. This function should not be used in application code. @@ -3465,7 +3469,7 @@ def register_save_all(id, driver): SAVE_ALL[id.upper()] = driver -def register_extension(id, extension): +def register_extension(id, extension) -> None: """ Registers an image extension. This function should not be used in application code. diff --git a/contrib/python/Pillow/py3/PIL/ImageChops.py b/contrib/python/Pillow/py3/PIL/ImageChops.py index 7012003179..29a5c995fd 100644 --- a/contrib/python/Pillow/py3/PIL/ImageChops.py +++ b/contrib/python/Pillow/py3/PIL/ImageChops.py @@ -15,11 +15,13 @@ # See the README file for information on usage and redistribution. # +from __future__ import annotations + from . import Image -def constant(image, value): - """Fill a channel with a given grey level. +def constant(image: Image.Image, value: int) -> Image.Image: + """Fill a channel with a given gray level. :rtype: :py:class:`~PIL.Image.Image` """ @@ -27,7 +29,7 @@ def constant(image, value): return Image.new("L", image.size, value) -def duplicate(image): +def duplicate(image: Image.Image) -> Image.Image: """Copy a channel. Alias for :py:meth:`PIL.Image.Image.copy`. :rtype: :py:class:`~PIL.Image.Image` @@ -36,7 +38,7 @@ def duplicate(image): return image.copy() -def invert(image): +def invert(image: Image.Image) -> Image.Image: """ Invert an image (channel). :: @@ -49,7 +51,7 @@ def invert(image): return image._new(image.im.chop_invert()) -def lighter(image1, image2): +def lighter(image1: Image.Image, image2: Image.Image) -> Image.Image: """ Compares the two images, pixel by pixel, and returns a new image containing the lighter values. :: @@ -64,7 +66,7 @@ def lighter(image1, image2): return image1._new(image1.im.chop_lighter(image2.im)) -def darker(image1, image2): +def darker(image1: Image.Image, image2: Image.Image) -> Image.Image: """ Compares the two images, pixel by pixel, and returns a new image containing the darker values. :: @@ -79,7 +81,7 @@ def darker(image1, image2): return image1._new(image1.im.chop_darker(image2.im)) -def difference(image1, image2): +def difference(image1: Image.Image, image2: Image.Image) -> Image.Image: """ Returns the absolute value of the pixel-by-pixel difference between the two images. :: @@ -94,7 +96,7 @@ def difference(image1, image2): return image1._new(image1.im.chop_difference(image2.im)) -def multiply(image1, image2): +def multiply(image1: Image.Image, image2: Image.Image) -> Image.Image: """ Superimposes two images on top of each other. @@ -111,7 +113,7 @@ def multiply(image1, image2): return image1._new(image1.im.chop_multiply(image2.im)) -def screen(image1, image2): +def screen(image1: Image.Image, image2: Image.Image) -> Image.Image: """ Superimposes two inverted images on top of each other. :: @@ -125,7 +127,7 @@ def screen(image1, image2): return image1._new(image1.im.chop_screen(image2.im)) -def soft_light(image1, image2): +def soft_light(image1: Image.Image, image2: Image.Image) -> Image.Image: """ Superimposes two images on top of each other using the Soft Light algorithm @@ -137,7 +139,7 @@ def soft_light(image1, image2): return image1._new(image1.im.chop_soft_light(image2.im)) -def hard_light(image1, image2): +def hard_light(image1: Image.Image, image2: Image.Image) -> Image.Image: """ Superimposes two images on top of each other using the Hard Light algorithm @@ -149,7 +151,7 @@ def hard_light(image1, image2): return image1._new(image1.im.chop_hard_light(image2.im)) -def overlay(image1, image2): +def overlay(image1: Image.Image, image2: Image.Image) -> Image.Image: """ Superimposes two images on top of each other using the Overlay algorithm @@ -161,7 +163,9 @@ def overlay(image1, image2): return image1._new(image1.im.chop_overlay(image2.im)) -def add(image1, image2, scale=1.0, offset=0): +def add( + image1: Image.Image, image2: Image.Image, scale: float = 1.0, offset: float = 0 +) -> Image.Image: """ Adds two images, dividing the result by scale and adding the offset. If omitted, scale defaults to 1.0, and offset to 0.0. :: @@ -176,7 +180,9 @@ def add(image1, image2, scale=1.0, offset=0): return image1._new(image1.im.chop_add(image2.im, scale, offset)) -def subtract(image1, image2, scale=1.0, offset=0): +def subtract( + image1: Image.Image, image2: Image.Image, scale: float = 1.0, offset: float = 0 +) -> Image.Image: """ Subtracts two images, dividing the result by scale and adding the offset. If omitted, scale defaults to 1.0, and offset to 0.0. :: @@ -191,7 +197,7 @@ def subtract(image1, image2, scale=1.0, offset=0): return image1._new(image1.im.chop_subtract(image2.im, scale, offset)) -def add_modulo(image1, image2): +def add_modulo(image1: Image.Image, image2: Image.Image) -> Image.Image: """Add two images, without clipping the result. :: out = ((image1 + image2) % MAX) @@ -204,7 +210,7 @@ def add_modulo(image1, image2): return image1._new(image1.im.chop_add_modulo(image2.im)) -def subtract_modulo(image1, image2): +def subtract_modulo(image1: Image.Image, image2: Image.Image) -> Image.Image: """Subtract two images, without clipping the result. :: out = ((image1 - image2) % MAX) @@ -217,7 +223,7 @@ def subtract_modulo(image1, image2): return image1._new(image1.im.chop_subtract_modulo(image2.im)) -def logical_and(image1, image2): +def logical_and(image1: Image.Image, image2: Image.Image) -> Image.Image: """Logical AND between two images. Both of the images must have mode "1". If you would like to perform a @@ -235,7 +241,7 @@ def logical_and(image1, image2): return image1._new(image1.im.chop_and(image2.im)) -def logical_or(image1, image2): +def logical_or(image1: Image.Image, image2: Image.Image) -> Image.Image: """Logical OR between two images. Both of the images must have mode "1". :: @@ -250,7 +256,7 @@ def logical_or(image1, image2): return image1._new(image1.im.chop_or(image2.im)) -def logical_xor(image1, image2): +def logical_xor(image1: Image.Image, image2: Image.Image) -> Image.Image: """Logical XOR between two images. Both of the images must have mode "1". :: @@ -265,7 +271,7 @@ def logical_xor(image1, image2): return image1._new(image1.im.chop_xor(image2.im)) -def blend(image1, image2, alpha): +def blend(image1: Image.Image, image2: Image.Image, alpha: float) -> Image.Image: """Blend images using constant transparency weight. Alias for :py:func:`PIL.Image.blend`. @@ -275,7 +281,9 @@ def blend(image1, image2, alpha): return Image.blend(image1, image2, alpha) -def composite(image1, image2, mask): +def composite( + image1: Image.Image, image2: Image.Image, mask: Image.Image +) -> Image.Image: """Create composite using transparency mask. Alias for :py:func:`PIL.Image.composite`. @@ -285,7 +293,7 @@ def composite(image1, image2, mask): return Image.composite(image1, image2, mask) -def offset(image, xoffset, yoffset=None): +def offset(image: Image.Image, xoffset: int, yoffset: int | None = None) -> Image.Image: """Returns a copy of the image where data has been offset by the given distances. Data wraps around the edges. If ``yoffset`` is omitted, it is assumed to be equal to ``xoffset``. diff --git a/contrib/python/Pillow/py3/PIL/ImageCms.py b/contrib/python/Pillow/py3/PIL/ImageCms.py index 3a337f9f20..643fce830b 100644 --- a/contrib/python/Pillow/py3/PIL/ImageCms.py +++ b/contrib/python/Pillow/py3/PIL/ImageCms.py @@ -14,6 +14,7 @@ # See the README file for information on usage and redistribution. See # below for the original description. +from __future__ import annotations import sys from enum import IntEnum @@ -27,7 +28,7 @@ except ImportError as ex: # anything in core. from ._util import DeferredError - _imagingcms = DeferredError(ex) + _imagingcms = DeferredError.new(ex) DESCRIPTION = """ pyCMS @@ -787,11 +788,8 @@ def getProfileInfo(profile): # info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint description = profile.profile.profile_description cpright = profile.profile.copyright - arr = [] - for elt in (description, cpright): - if elt: - arr.append(elt) - return "\r\n\r\n".join(arr) + "\r\n\r\n" + elements = [element for element in (description, cpright) if element] + return "\r\n\r\n".join(elements) + "\r\n\r\n" except (AttributeError, OSError, TypeError, ValueError) as v: raise PyCMSError(v) from v diff --git a/contrib/python/Pillow/py3/PIL/ImageColor.py b/contrib/python/Pillow/py3/PIL/ImageColor.py index befc1fd1d8..ad59b06677 100644 --- a/contrib/python/Pillow/py3/PIL/ImageColor.py +++ b/contrib/python/Pillow/py3/PIL/ImageColor.py @@ -16,12 +16,15 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import re +from functools import lru_cache from . import Image +@lru_cache def getrgb(color): """ Convert a color string to an RGB or RGBA tuple. If the string cannot be @@ -120,11 +123,12 @@ def getrgb(color): raise ValueError(msg) +@lru_cache def getcolor(color, mode): """ Same as :py:func:`~PIL.ImageColor.getrgb` for most modes. However, if ``mode`` is HSV, converts the RGB value to a HSV value, or if ``mode`` is - not color or a palette image, converts the RGB value to a greyscale value. + not color or a palette image, converts the RGB value to a grayscale value. If the string cannot be parsed, this function raises a :py:exc:`ValueError` exception. diff --git a/contrib/python/Pillow/py3/PIL/ImageDraw.py b/contrib/python/Pillow/py3/PIL/ImageDraw.py index fbf320d72a..84665f54ff 100644 --- a/contrib/python/Pillow/py3/PIL/ImageDraw.py +++ b/contrib/python/Pillow/py3/PIL/ImageDraw.py @@ -29,9 +29,11 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import math import numbers +import struct from . import Image, ImageColor @@ -542,7 +544,8 @@ class ImageDraw: # font.getmask2(mode="RGBA") returns color in RGB bands and mask in A # extract mask and set text alpha color, mask = mask, mask.getband(3) - color.fillband(3, (ink >> 24) & 0xFF) + ink_alpha = struct.pack("i", ink)[3] + color.fillband(3, ink_alpha) x, y = coord self.im.paste(color, (x, y, x + mask.size[0], y + mask.size[1]), mask) else: @@ -921,7 +924,7 @@ def floodfill(image, xy, value, border=None, thresh=0): if border is None: fill = _color_diff(p, background) <= thresh else: - fill = p != value and p != border + fill = p not in (value, border) if fill: pixel[s, t] = value new_edge.add((s, t)) diff --git a/contrib/python/Pillow/py3/PIL/ImageDraw2.py b/contrib/python/Pillow/py3/PIL/ImageDraw2.py index 7ce0224a67..35ee5834e3 100644 --- a/contrib/python/Pillow/py3/PIL/ImageDraw2.py +++ b/contrib/python/Pillow/py3/PIL/ImageDraw2.py @@ -22,7 +22,7 @@ .. seealso:: :py:mod:`PIL.ImageDraw` """ - +from __future__ import annotations from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath diff --git a/contrib/python/Pillow/py3/PIL/ImageEnhance.py b/contrib/python/Pillow/py3/PIL/ImageEnhance.py index 3b79d5c46a..93a50d2a2b 100644 --- a/contrib/python/Pillow/py3/PIL/ImageEnhance.py +++ b/contrib/python/Pillow/py3/PIL/ImageEnhance.py @@ -17,6 +17,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations from . import Image, ImageFilter, ImageStat @@ -59,7 +60,7 @@ class Contrast(_Enhance): This class can be used to control the contrast of an image, similar to the contrast control on a TV set. An enhancement factor of 0.0 - gives a solid grey image. A factor of 1.0 gives the original image. + gives a solid gray image. A factor of 1.0 gives the original image. """ def __init__(self, image): diff --git a/contrib/python/Pillow/py3/PIL/ImageFile.py b/contrib/python/Pillow/py3/PIL/ImageFile.py index 8e4f7dfb2c..0923979af8 100644 --- a/contrib/python/Pillow/py3/PIL/ImageFile.py +++ b/contrib/python/Pillow/py3/PIL/ImageFile.py @@ -26,13 +26,16 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import io import itertools import struct import sys +from typing import Any, NamedTuple from . import Image +from ._deprecate import deprecate from ._util import is_path MAXBLOCK = 65536 @@ -61,15 +64,25 @@ Dict of known error codes returned from :meth:`.PyDecoder.decode`, # Helpers -def raise_oserror(error): +def _get_oserror(error, *, encoder): try: msg = Image.core.getcodecstatus(error) except AttributeError: msg = ERRORS.get(error) if not msg: - msg = f"decoder error {error}" - msg += " when reading image file" - raise OSError(msg) + msg = f"{'encoder' if encoder else 'decoder'} error {error}" + msg += f" when {'writing' if encoder else 'reading'} image file" + return OSError(msg) + + +def raise_oserror(error): + deprecate( + "raise_oserror", + 12, + action="It is only useful for translating error codes returned by a codec's " + "decode() method, which ImageFile already does automatically.", + ) + raise _get_oserror(error, encoder=False) def _tilesort(t): @@ -77,6 +90,13 @@ def _tilesort(t): return t[2] +class _Tile(NamedTuple): + encoder_name: str + extents: tuple[int, int, int, int] + offset: int + args: tuple[Any, ...] | str | None + + # # -------------------------------------------------------------------- # ImageFile base class @@ -187,6 +207,8 @@ class ImageFile(Image.Image): if use_mmap: # try memory mapping decoder_name, extents, offset, args = self.tile[0] + if isinstance(args, str): + args = (args, 0, 1) if ( decoder_name == "raw" and len(args) >= 3 @@ -200,8 +222,8 @@ class ImageFile(Image.Image): with open(self.filename) as fp: self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ) if offset + self.size[1] * args[1] > self.map.size(): - # buffer is not large enough - raise OSError + msg = "buffer is not large enough" + raise OSError(msg) self.im = Image.core.map_buffer( self.map, self.size, decoder_name, offset, args ) @@ -285,7 +307,7 @@ class ImageFile(Image.Image): if not self.map and not LOAD_TRUNCATED_IMAGES and err_code < 0: # still raised if decoder fails to return anything - raise_oserror(err_code) + raise _get_oserror(err_code, encoder=False) return Image.Image.load(self) @@ -412,7 +434,7 @@ class Parser: if e < 0: # decoding error self.image = None - raise_oserror(e) + raise _get_oserror(e, encoder=False) else: # end of image return @@ -430,7 +452,6 @@ class Parser: with io.BytesIO(self.data) as fp: im = Image.open(fp) except OSError: - # traceback.print_exc() pass # not enough data else: flag = hasattr(im, "load_seek") or hasattr(im, "load_read") @@ -521,13 +542,13 @@ def _save(im, fp, tile, bufsize=0): fp.flush() -def _encode_tile(im, fp, tile, bufsize, fh, exc=None): - for e, b, o, a in tile: - if o > 0: - fp.seek(o) - encoder = Image._getencoder(im.mode, e, a, im.encoderconfig) +def _encode_tile(im, fp, tile: list[_Tile], bufsize, fh, exc=None): + for encoder_name, extents, offset, args in tile: + if offset > 0: + fp.seek(offset) + encoder = Image._getencoder(im.mode, encoder_name, args, im.encoderconfig) try: - encoder.setimage(im.im, b) + encoder.setimage(im.im, extents) if encoder.pushes_fd: encoder.setfd(fp) errcode = encoder.encode_to_pyfd()[1] @@ -543,8 +564,7 @@ def _encode_tile(im, fp, tile, bufsize, fh, exc=None): # slight speedup: compress to real file object errcode = encoder.encode_to_file(fh, bufsize) if errcode < 0: - msg = f"encoder error {errcode} when writing image file" - raise OSError(msg) from exc + raise _get_oserror(errcode, encoder=True) from exc finally: encoder.cleanup() @@ -690,7 +710,8 @@ class PyDecoder(PyCodec): If finished with decoding return -1 for the bytes consumed. Err codes are from :data:`.ImageFile.ERRORS`. """ - raise NotImplementedError() + msg = "unavailable in base decoder" + raise NotImplementedError(msg) def set_as_raw(self, data, rawmode=None): """ @@ -739,7 +760,8 @@ class PyEncoder(PyCodec): If finished with encoding return 1 for the error code. Err codes are from :data:`.ImageFile.ERRORS`. """ - raise NotImplementedError() + msg = "unavailable in base encoder" + raise NotImplementedError(msg) def encode_to_pyfd(self): """ diff --git a/contrib/python/Pillow/py3/PIL/ImageFilter.py b/contrib/python/Pillow/py3/PIL/ImageFilter.py index 57268b8f53..035b83c4d7 100644 --- a/contrib/python/Pillow/py3/PIL/ImageFilter.py +++ b/contrib/python/Pillow/py3/PIL/ImageFilter.py @@ -14,6 +14,8 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations + import functools @@ -222,7 +224,7 @@ class UnsharpMask(MultibandFilter): .. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking - """ # noqa: E501 + """ name = "UnsharpMask" @@ -394,7 +396,7 @@ class Color3DLUT(MultibandFilter): if hasattr(table, "shape"): try: import numpy - except ImportError: # pragma: no cover + except ImportError: pass if numpy and isinstance(table, numpy.ndarray): diff --git a/contrib/python/Pillow/py3/PIL/ImageFont.py b/contrib/python/Pillow/py3/PIL/ImageFont.py index c295621351..8213d030a4 100644 --- a/contrib/python/Pillow/py3/PIL/ImageFont.py +++ b/contrib/python/Pillow/py3/PIL/ImageFont.py @@ -25,12 +25,16 @@ # See the README file for information on usage and redistribution. # +from __future__ import annotations + import base64 import os import sys import warnings from enum import IntEnum from io import BytesIO +from pathlib import Path +from typing import BinaryIO from . import Image from ._util import is_directory, is_path @@ -49,7 +53,7 @@ try: except ImportError as ex: from ._util import DeferredError - core = DeferredError(ex) + core = DeferredError.new(ex) def _string_length_check(text): @@ -145,6 +149,8 @@ class ImageFont: :return: An internal PIL storage memory instance as defined by the :py:mod:`PIL.Image.core` interface module. """ + _string_length_check(text) + Image._decompression_bomb_check(self.font.getsize(text)) return self.font.getmask(text, mode) def getbbox(self, text, *args, **kwargs): @@ -185,9 +191,20 @@ class ImageFont: class FreeTypeFont: """FreeType font wrapper (requires _imagingft service)""" - def __init__(self, font=None, size=10, index=0, encoding="", layout_engine=None): + def __init__( + self, + font: bytes | str | Path | BinaryIO | None = None, + size: float = 10, + index: int = 0, + encoding: str = "", + layout_engine: Layout | None = None, + ) -> None: # FIXME: use service provider instead + if size <= 0: + msg = "font size must be greater than 0" + raise ValueError(msg) + self.path = font self.size = size self.index = index @@ -213,6 +230,8 @@ class FreeTypeFont: ) if is_path(font): + if isinstance(font, Path): + font = str(font) if sys.platform == "win32": font_bytes_path = font if isinstance(font, bytes) else font.encode() try: @@ -375,8 +394,9 @@ class FreeTypeFont: :param stroke_width: The width of the text stroke. :param anchor: The text anchor alignment. Determines the relative location of - the anchor to the text. The default alignment is top left. - See :ref:`text-anchors` for valid values. + the anchor to the text. The default alignment is top left, + specifically ``la`` for horizontal text and ``lt`` for + vertical text. See :ref:`text-anchors` for details. :return: ``(left, top, right, bottom)`` bounding box """ @@ -449,8 +469,9 @@ class FreeTypeFont: .. versionadded:: 6.2.0 :param anchor: The text anchor alignment. Determines the relative location of - the anchor to the text. The default alignment is top left. - See :ref:`text-anchors` for valid values. + the anchor to the text. The default alignment is top left, + specifically ``la`` for horizontal text and ``lt`` for + vertical text. See :ref:`text-anchors` for details. .. versionadded:: 8.0.0 @@ -541,8 +562,9 @@ class FreeTypeFont: .. versionadded:: 6.2.0 :param anchor: The text anchor alignment. Determines the relative location of - the anchor to the text. The default alignment is top left. - See :ref:`text-anchors` for valid values. + the anchor to the text. The default alignment is top left, + specifically ``la`` for horizontal text and ``lt`` for + vertical text. See :ref:`text-anchors` for details. .. versionadded:: 8.0.0 @@ -565,16 +587,16 @@ class FreeTypeFont: im = None size = None - def fill(mode, im_size): + def fill(width, height): nonlocal im, size - size = im_size + size = (width, height) if Image.MAX_IMAGE_PIXELS is not None: - pixels = max(1, size[0]) * max(1, size[1]) + pixels = max(1, width) * max(1, height) if pixels > 2 * Image.MAX_IMAGE_PIXELS: return - im = Image.core.fill(mode, size) + im = Image.core.fill("RGBA" if mode == "RGBA" else "L", size) return im offset = self.font.render( @@ -712,7 +734,6 @@ class TransposedFont: if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270): msg = "text length is undefined for text rotated by 90 or 270 degrees" raise ValueError(msg) - _string_length_check(text) return self.font.getlength(text, *args, **kwargs) @@ -775,7 +796,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None): This specifies the character set to use. It does not alter the encoding of any text provided in subsequent operations. :param layout_engine: Which layout engine to use, if available: - :data:`.ImageFont.Layout.BASIC` or :data:`.ImageFont.Layout.RAQM`. + :attr:`.ImageFont.Layout.BASIC` or :attr:`.ImageFont.Layout.RAQM`. If it is available, Raqm layout will be used by default. Otherwise, basic layout will be used. @@ -788,6 +809,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None): .. versionadded:: 4.2.0 :return: A font object. :exception OSError: If the file could not be read. + :exception ValueError: If the font size is not greater than zero. """ def freetype(font): diff --git a/contrib/python/Pillow/py3/PIL/ImageGrab.py b/contrib/python/Pillow/py3/PIL/ImageGrab.py index bcfffc3dc1..a4993d3d4b 100644 --- a/contrib/python/Pillow/py3/PIL/ImageGrab.py +++ b/contrib/python/Pillow/py3/PIL/ImageGrab.py @@ -14,6 +14,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import io import os diff --git a/contrib/python/Pillow/py3/PIL/ImageMath.py b/contrib/python/Pillow/py3/PIL/ImageMath.py index eb6bbe6c62..b77f4bce56 100644 --- a/contrib/python/Pillow/py3/PIL/ImageMath.py +++ b/contrib/python/Pillow/py3/PIL/ImageMath.py @@ -14,16 +14,13 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import builtins from . import Image, _imagingmath -def _isconstant(v): - return isinstance(v, (int, float)) - - class _Operand: """Wraps an image operand, providing standard operators""" @@ -43,7 +40,7 @@ class _Operand: raise ValueError(msg) else: # argument was a constant - if _isconstant(im1) and self.im.mode in ("1", "L", "I"): + if isinstance(im1, (int, float)) and self.im.mode in ("1", "L", "I"): return Image.new("I", self.im.size, im1) else: return Image.new("F", self.im.size, im1) @@ -237,9 +234,14 @@ def eval(expression, _dict={}, **kw): # build execution namespace args = ops.copy() + for k in list(_dict.keys()) + list(kw.keys()): + if "__" in k or hasattr(builtins, k): + msg = f"'{k}' not allowed" + raise ValueError(msg) + args.update(_dict) args.update(kw) - for k, v in list(args.items()): + for k, v in args.items(): if hasattr(v, "im"): args[k] = _Operand(v) diff --git a/contrib/python/Pillow/py3/PIL/ImageMode.py b/contrib/python/Pillow/py3/PIL/ImageMode.py index a0b3351429..0b31f60817 100644 --- a/contrib/python/Pillow/py3/PIL/ImageMode.py +++ b/contrib/python/Pillow/py3/PIL/ImageMode.py @@ -12,79 +12,85 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import sys - -# mode descriptor cache -_modes = None +from functools import lru_cache class ModeDescriptor: """Wrapper for mode strings.""" - def __init__(self, mode, bands, basemode, basetype, typestr): + def __init__( + self, + mode: str, + bands: tuple[str, ...], + basemode: str, + basetype: str, + typestr: str, + ) -> None: self.mode = mode self.bands = bands self.basemode = basemode self.basetype = basetype self.typestr = typestr - def __str__(self): + def __str__(self) -> str: return self.mode -def getmode(mode): +@lru_cache +def getmode(mode: str) -> ModeDescriptor: """Gets a mode descriptor for the given mode.""" - global _modes - if not _modes: - # initialize mode cache - modes = {} - endian = "<" if sys.byteorder == "little" else ">" - for m, (basemode, basetype, bands, typestr) in { - # core modes - # Bits need to be extended to bytes - "1": ("L", "L", ("1",), "|b1"), - "L": ("L", "L", ("L",), "|u1"), - "I": ("L", "I", ("I",), endian + "i4"), - "F": ("L", "F", ("F",), endian + "f4"), - "P": ("P", "L", ("P",), "|u1"), - "RGB": ("RGB", "L", ("R", "G", "B"), "|u1"), - "RGBX": ("RGB", "L", ("R", "G", "B", "X"), "|u1"), - "RGBA": ("RGB", "L", ("R", "G", "B", "A"), "|u1"), - "CMYK": ("RGB", "L", ("C", "M", "Y", "K"), "|u1"), - "YCbCr": ("RGB", "L", ("Y", "Cb", "Cr"), "|u1"), - # UNDONE - unsigned |u1i1i1 - "LAB": ("RGB", "L", ("L", "A", "B"), "|u1"), - "HSV": ("RGB", "L", ("H", "S", "V"), "|u1"), - # extra experimental modes - "RGBa": ("RGB", "L", ("R", "G", "B", "a"), "|u1"), - "BGR;15": ("RGB", "L", ("B", "G", "R"), "|u1"), - "BGR;16": ("RGB", "L", ("B", "G", "R"), "|u1"), - "BGR;24": ("RGB", "L", ("B", "G", "R"), "|u1"), - "LA": ("L", "L", ("L", "A"), "|u1"), - "La": ("L", "L", ("L", "a"), "|u1"), - "PA": ("RGB", "L", ("P", "A"), "|u1"), - }.items(): - modes[m] = ModeDescriptor(m, bands, basemode, basetype, typestr) - # mapping modes - for i16mode, typestr in { - # I;16 == I;16L, and I;32 == I;32L - "I;16": "<u2", - "I;16S": "<i2", - "I;16L": "<u2", - "I;16LS": "<i2", - "I;16B": ">u2", - "I;16BS": ">i2", - "I;16N": endian + "u2", - "I;16NS": endian + "i2", - "I;32": "<u4", - "I;32B": ">u4", - "I;32L": "<u4", - "I;32S": "<i4", - "I;32BS": ">i4", - "I;32LS": "<i4", - }.items(): - modes[i16mode] = ModeDescriptor(i16mode, ("I",), "L", "L", typestr) - # set global mode cache atomically - _modes = modes - return _modes[mode] + # initialize mode cache + endian = "<" if sys.byteorder == "little" else ">" + + modes = { + # core modes + # Bits need to be extended to bytes + "1": ("L", "L", ("1",), "|b1"), + "L": ("L", "L", ("L",), "|u1"), + "I": ("L", "I", ("I",), endian + "i4"), + "F": ("L", "F", ("F",), endian + "f4"), + "P": ("P", "L", ("P",), "|u1"), + "RGB": ("RGB", "L", ("R", "G", "B"), "|u1"), + "RGBX": ("RGB", "L", ("R", "G", "B", "X"), "|u1"), + "RGBA": ("RGB", "L", ("R", "G", "B", "A"), "|u1"), + "CMYK": ("RGB", "L", ("C", "M", "Y", "K"), "|u1"), + "YCbCr": ("RGB", "L", ("Y", "Cb", "Cr"), "|u1"), + # UNDONE - unsigned |u1i1i1 + "LAB": ("RGB", "L", ("L", "A", "B"), "|u1"), + "HSV": ("RGB", "L", ("H", "S", "V"), "|u1"), + # extra experimental modes + "RGBa": ("RGB", "L", ("R", "G", "B", "a"), "|u1"), + "BGR;15": ("RGB", "L", ("B", "G", "R"), "|u1"), + "BGR;16": ("RGB", "L", ("B", "G", "R"), "|u1"), + "BGR;24": ("RGB", "L", ("B", "G", "R"), "|u1"), + "LA": ("L", "L", ("L", "A"), "|u1"), + "La": ("L", "L", ("L", "a"), "|u1"), + "PA": ("RGB", "L", ("P", "A"), "|u1"), + } + if mode in modes: + base_mode, base_type, bands, type_str = modes[mode] + return ModeDescriptor(mode, bands, base_mode, base_type, type_str) + + mapping_modes = { + # I;16 == I;16L, and I;32 == I;32L + "I;16": "<u2", + "I;16S": "<i2", + "I;16L": "<u2", + "I;16LS": "<i2", + "I;16B": ">u2", + "I;16BS": ">i2", + "I;16N": endian + "u2", + "I;16NS": endian + "i2", + "I;32": "<u4", + "I;32B": ">u4", + "I;32L": "<u4", + "I;32S": "<i4", + "I;32BS": ">i4", + "I;32LS": "<i4", + } + + type_str = mapping_modes[mode] + return ModeDescriptor(mode, ("I",), "L", "L", type_str) diff --git a/contrib/python/Pillow/py3/PIL/ImageMorph.py b/contrib/python/Pillow/py3/PIL/ImageMorph.py index 6fccc315b3..282e7d2a54 100644 --- a/contrib/python/Pillow/py3/PIL/ImageMorph.py +++ b/contrib/python/Pillow/py3/PIL/ImageMorph.py @@ -4,6 +4,7 @@ # 2014-06-04 Initial version. # # Copyright (c) 2014 Dov Grobgeld <dov.grobgeld@gmail.com> +from __future__ import annotations import re diff --git a/contrib/python/Pillow/py3/PIL/ImageOps.py b/contrib/python/Pillow/py3/PIL/ImageOps.py index 42f2152b39..a9e626b2b2 100644 --- a/contrib/python/Pillow/py3/PIL/ImageOps.py +++ b/contrib/python/Pillow/py3/PIL/ImageOps.py @@ -16,6 +16,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import functools import operator @@ -56,7 +57,7 @@ def _lut(image, lut): lut = lut + lut + lut return image.point(lut) else: - msg = "not supported for this image mode" + msg = f"not supported for mode {image.mode}" raise OSError(msg) @@ -557,9 +558,7 @@ def invert(image): :param image: The image to invert. :return: An image. """ - lut = [] - for i in range(256): - lut.append(255 - i) + lut = list(range(255, -1, -1)) return image.point(lut) if image.mode == "1" else _lut(image, lut) @@ -581,10 +580,8 @@ def posterize(image, bits): :param bits: The number of bits to keep for each channel (1-8). :return: An image. """ - lut = [] mask = ~(2 ** (8 - bits) - 1) - for i in range(256): - lut.append(i & mask) + lut = [i & mask for i in range(256)] return _lut(image, lut) @@ -593,7 +590,7 @@ def solarize(image, threshold=128): Invert all pixel values above a threshold. :param image: The image to solarize. - :param threshold: All pixels above this greyscale level are inverted. + :param threshold: All pixels above this grayscale level are inverted. :return: An image. """ lut = [] diff --git a/contrib/python/Pillow/py3/PIL/ImagePalette.py b/contrib/python/Pillow/py3/PIL/ImagePalette.py index f0c0947086..fbcfa309d2 100644 --- a/contrib/python/Pillow/py3/PIL/ImagePalette.py +++ b/contrib/python/Pillow/py3/PIL/ImagePalette.py @@ -15,6 +15,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import array @@ -102,6 +103,30 @@ class ImagePalette: # Declare tostring as an alias for tobytes tostring = tobytes + def _new_color_index(self, image=None, e=None): + if not isinstance(self.palette, bytearray): + self._palette = bytearray(self.palette) + index = len(self.palette) // 3 + special_colors = () + if image: + special_colors = ( + image.info.get("background"), + image.info.get("transparency"), + ) + while index in special_colors: + index += 1 + if index >= 256: + if image: + # Search for an unused index + for i, count in reversed(list(enumerate(image.histogram()))): + if count == 0 and i not in special_colors: + index = i + break + if index >= 256: + msg = "cannot allocate more than 256 colors" + raise ValueError(msg) from e + return index + def getcolor(self, color, image=None): """Given an rgb tuple, allocate palette entry. @@ -124,27 +149,7 @@ class ImagePalette: return self.colors[color] except KeyError as e: # allocate new color slot - if not isinstance(self.palette, bytearray): - self._palette = bytearray(self.palette) - index = len(self.palette) // 3 - special_colors = () - if image: - special_colors = ( - image.info.get("background"), - image.info.get("transparency"), - ) - while index in special_colors: - index += 1 - if index >= 256: - if image: - # Search for an unused index - for i, count in reversed(list(enumerate(image.histogram()))): - if count == 0 and i not in special_colors: - index = i - break - if index >= 256: - msg = "cannot allocate more than 256 colors" - raise ValueError(msg) from e + index = self._new_color_index(image, e) self.colors[color] = index if index * 3 < len(self.palette): self._palette = ( @@ -200,20 +205,15 @@ def raw(rawmode, data): def make_linear_lut(black, white): - lut = [] if black == 0: - for i in range(256): - lut.append(white * i // 255) - else: - raise NotImplementedError # FIXME - return lut + return [white * i // 255 for i in range(256)] + + msg = "unavailable when black is non-zero" + raise NotImplementedError(msg) # FIXME def make_gamma_lut(exp): - lut = [] - for i in range(256): - lut.append(int(((i / 255.0) ** exp) * 255.0 + 0.5)) - return lut + return [int(((i / 255.0) ** exp) * 255.0 + 0.5) for i in range(256)] def negative(mode="RGB"): @@ -225,9 +225,7 @@ def negative(mode="RGB"): def random(mode="RGB"): from random import randint - palette = [] - for i in range(256 * len(mode)): - palette.append(randint(0, 255)) + palette = [randint(0, 255) for _ in range(256 * len(mode))] return ImagePalette(mode, palette) @@ -256,8 +254,6 @@ def load(filename): if lut: break except (SyntaxError, ValueError): - # import traceback - # traceback.print_exc() pass else: msg = "cannot load palette" diff --git a/contrib/python/Pillow/py3/PIL/ImagePath.py b/contrib/python/Pillow/py3/PIL/ImagePath.py index 3d3538c97b..77e8a609a5 100644 --- a/contrib/python/Pillow/py3/PIL/ImagePath.py +++ b/contrib/python/Pillow/py3/PIL/ImagePath.py @@ -13,6 +13,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations from . import Image diff --git a/contrib/python/Pillow/py3/PIL/ImageSequence.py b/contrib/python/Pillow/py3/PIL/ImageSequence.py index c4bb6334ac..2c18502763 100644 --- a/contrib/python/Pillow/py3/PIL/ImageSequence.py +++ b/contrib/python/Pillow/py3/PIL/ImageSequence.py @@ -14,6 +14,11 @@ # ## +from __future__ import annotations + +from typing import Callable + +from . import Image class Iterator: @@ -28,33 +33,38 @@ class Iterator: :param im: An image object. """ - def __init__(self, im): + def __init__(self, im: Image.Image): if not hasattr(im, "seek"): msg = "im must have seek method" raise AttributeError(msg) self.im = im self.position = getattr(self.im, "_min_frame", 0) - def __getitem__(self, ix): + def __getitem__(self, ix: int) -> Image.Image: try: self.im.seek(ix) return self.im except EOFError as e: - raise IndexError from e # end of sequence + msg = "end of sequence" + raise IndexError(msg) from e - def __iter__(self): + def __iter__(self) -> Iterator: return self - def __next__(self): + def __next__(self) -> Image.Image: try: self.im.seek(self.position) self.position += 1 return self.im except EOFError as e: - raise StopIteration from e + msg = "end of sequence" + raise StopIteration(msg) from e -def all_frames(im, func=None): +def all_frames( + im: Image.Image | list[Image.Image], + func: Callable[[Image.Image], Image.Image] | None = None, +) -> list[Image.Image]: """ Applies a given function to all frames in an image or a list of images. The frames are returned as a list of separate images. diff --git a/contrib/python/Pillow/py3/PIL/ImageShow.py b/contrib/python/Pillow/py3/PIL/ImageShow.py index 8b1c3f8bb6..fad3e09800 100644 --- a/contrib/python/Pillow/py3/PIL/ImageShow.py +++ b/contrib/python/Pillow/py3/PIL/ImageShow.py @@ -11,6 +11,8 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations + import os import shutil import subprocess @@ -99,7 +101,8 @@ class Viewer: Returns the command used to display the file. Not implemented in the base class. """ - raise NotImplementedError + msg = "unavailable in base viewer" + raise NotImplementedError(msg) def save_image(self, image): """Save to temporary file and return filename.""" diff --git a/contrib/python/Pillow/py3/PIL/ImageStat.py b/contrib/python/Pillow/py3/PIL/ImageStat.py index b7ebddf066..13864e59cf 100644 --- a/contrib/python/Pillow/py3/PIL/ImageStat.py +++ b/contrib/python/Pillow/py3/PIL/ImageStat.py @@ -20,10 +20,9 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations -import functools import math -import operator class Stat: @@ -53,26 +52,22 @@ class Stat: """Get min/max values for each band in the image""" def minmax(histogram): - n = 255 - x = 0 + res_min, res_max = 255, 0 for i in range(256): if histogram[i]: - n = min(n, i) - x = max(x, i) - return n, x # returns (255, 0) if there's no data in the histogram + res_min = i + break + for i in range(255, -1, -1): + if histogram[i]: + res_max = i + break + return res_min, res_max - v = [] - for i in range(0, len(self.h), 256): - v.append(minmax(self.h[i:])) - return v + return [minmax(self.h[i:]) for i in range(0, len(self.h), 256)] def _getcount(self): """Get total number of pixels in each layer""" - - v = [] - for i in range(0, len(self.h), 256): - v.append(functools.reduce(operator.add, self.h[i : i + 256])) - return v + return [sum(self.h[i : i + 256]) for i in range(0, len(self.h), 256)] def _getsum(self): """Get sum of all pixels in each layer""" @@ -98,11 +93,7 @@ class Stat: def _getmean(self): """Get average pixel level for each layer""" - - v = [] - for i in self.bands: - v.append(self.sum[i] / self.count[i]) - return v + return [self.sum[i] / self.count[i] for i in self.bands] def _getmedian(self): """Get median pixel level for each layer""" @@ -121,28 +112,18 @@ class Stat: def _getrms(self): """Get RMS for each layer""" - - v = [] - for i in self.bands: - v.append(math.sqrt(self.sum2[i] / self.count[i])) - return v + return [math.sqrt(self.sum2[i] / self.count[i]) for i in self.bands] def _getvar(self): """Get variance for each layer""" - - v = [] - for i in self.bands: - n = self.count[i] - v.append((self.sum2[i] - (self.sum[i] ** 2.0) / n) / n) - return v + return [ + (self.sum2[i] - (self.sum[i] ** 2.0) / self.count[i]) / self.count[i] + for i in self.bands + ] def _getstddev(self): """Get standard deviation for each layer""" - - v = [] - for i in self.bands: - v.append(math.sqrt(self.var[i])) - return v + return [math.sqrt(self.var[i]) for i in self.bands] Global = Stat # compatibility diff --git a/contrib/python/Pillow/py3/PIL/ImageTransform.py b/contrib/python/Pillow/py3/PIL/ImageTransform.py index 7881f0d262..84c81f1844 100644 --- a/contrib/python/Pillow/py3/PIL/ImageTransform.py +++ b/contrib/python/Pillow/py3/PIL/ImageTransform.py @@ -12,18 +12,28 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations + +from typing import Sequence from . import Image class Transform(Image.ImageTransformHandler): - def __init__(self, data): + method: Image.Transform + + def __init__(self, data: Sequence[int]) -> None: self.data = data - def getdata(self): + def getdata(self) -> tuple[int, Sequence[int]]: return self.method, self.data - def transform(self, size, image, **options): + def transform( + self, + size: tuple[int, int], + image: Image.Image, + **options: dict[str, str | int | tuple[int, ...] | list[int]], + ) -> Image.Image: # can be overridden method, data = self.getdata() return image.transform(size, method, data, **options) diff --git a/contrib/python/Pillow/py3/PIL/ImageWin.py b/contrib/python/Pillow/py3/PIL/ImageWin.py index ca9b14c8ad..75910d2d9a 100644 --- a/contrib/python/Pillow/py3/PIL/ImageWin.py +++ b/contrib/python/Pillow/py3/PIL/ImageWin.py @@ -16,6 +16,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations from . import Image @@ -54,9 +55,9 @@ class Dib: "L", "P", or "RGB". If the display requires a palette, this constructor creates a suitable - palette and associates it with the image. For an "L" image, 128 greylevels + palette and associates it with the image. For an "L" image, 128 graylevels are allocated. For an "RGB" image, a 6x6x6 colour cube is used, together - with 20 greylevels. + with 20 graylevels. To make sure that palettes work properly under Windows, you must call the ``palette`` method upon certain events from Windows. diff --git a/contrib/python/Pillow/py3/PIL/ImtImagePlugin.py b/contrib/python/Pillow/py3/PIL/ImtImagePlugin.py index d409fcd59d..7469c592dd 100644 --- a/contrib/python/Pillow/py3/PIL/ImtImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/ImtImagePlugin.py @@ -13,7 +13,7 @@ # # See the README file for information on usage and redistribution. # - +from __future__ import annotations import re diff --git a/contrib/python/Pillow/py3/PIL/IptcImagePlugin.py b/contrib/python/Pillow/py3/PIL/IptcImagePlugin.py index 316cd17c73..4096094348 100644 --- a/contrib/python/Pillow/py3/PIL/IptcImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/IptcImagePlugin.py @@ -14,31 +14,50 @@ # # See the README file for information on usage and redistribution. # -import os -import tempfile +from __future__ import annotations + +from io import BytesIO +from typing import Sequence from . import Image, ImageFile -from ._binary import i8 from ._binary import i16be as i16 from ._binary import i32be as i32 -from ._binary import o8 +from ._deprecate import deprecate COMPRESSION = {1: "raw", 5: "jpeg"} -PAD = o8(0) * 4 + +def __getattr__(name: str) -> bytes: + if name == "PAD": + deprecate("IptcImagePlugin.PAD", 12) + return b"\0\0\0\0" + msg = f"module '{__name__}' has no attribute '{name}'" + raise AttributeError(msg) # # Helpers -def i(c): - return i32((PAD + c)[-4:]) +def _i(c: bytes) -> int: + return i32((b"\0\0\0\0" + c)[-4:]) + + +def _i8(c: int | bytes) -> int: + return c if isinstance(c, int) else c[0] + +def i(c: bytes) -> int: + """.. deprecated:: 10.2.0""" + deprecate("IptcImagePlugin.i", 12) + return _i(c) -def dump(c): + +def dump(c: Sequence[int | bytes]) -> None: + """.. deprecated:: 10.2.0""" + deprecate("IptcImagePlugin.dump", 12) for i in c: - print("%02x" % i8(i), end=" ") + print("%02x" % _i8(i), end=" ") print() @@ -51,10 +70,10 @@ class IptcImageFile(ImageFile.ImageFile): format = "IPTC" format_description = "IPTC/NAA" - def getint(self, key): - return i(self.info[key]) + def getint(self, key: tuple[int, int]) -> int: + return _i(self.info[key]) - def field(self): + def field(self) -> tuple[tuple[int, int] | None, int]: # # get a IPTC field header s = self.fp.read(5) @@ -76,13 +95,13 @@ class IptcImageFile(ImageFile.ImageFile): elif size == 128: size = 0 elif size > 128: - size = i(self.fp.read(size - 128)) + size = _i(self.fp.read(size - 128)) else: size = i16(s, 3) return tag, size - def _open(self): + def _open(self) -> None: # load descriptive fields while True: offset = self.fp.tell() @@ -102,10 +121,10 @@ class IptcImageFile(ImageFile.ImageFile): self.info[tag] = tagdata # mode - layers = i8(self.info[(3, 60)][0]) - component = i8(self.info[(3, 60)][1]) + layers = self.info[(3, 60)][0] + component = self.info[(3, 60)][1] if (3, 65) in self.info: - id = i8(self.info[(3, 65)][0]) - 1 + id = self.info[(3, 65)][0] - 1 else: id = 0 if layers == 1 and not component: @@ -127,27 +146,22 @@ class IptcImageFile(ImageFile.ImageFile): # tile if tag == (8, 10): - self.tile = [ - ("iptc", (compression, offset), (0, 0, self.size[0], self.size[1])) - ] + self.tile = [("iptc", (0, 0) + self.size, offset, compression)] def load(self): if len(self.tile) != 1 or self.tile[0][0] != "iptc": return ImageFile.ImageFile.load(self) - type, tile, box = self.tile[0] - - encoding, offset = tile + offset, compression = self.tile[0][2:] self.fp.seek(offset) # Copy image data to temporary file - o_fd, outfile = tempfile.mkstemp(text=False) - o = os.fdopen(o_fd) - if encoding == "raw": + o = BytesIO() + if compression == "raw": # To simplify access to the extracted file, # prepend a PPM header - o.write("P5\n%d %d\n255\n" % self.size) + o.write(b"P5\n%d %d\n255\n" % self.size) while True: type, size = self.field() if type != (8, 10): @@ -158,17 +172,10 @@ class IptcImageFile(ImageFile.ImageFile): break o.write(s) size -= len(s) - o.close() - try: - with Image.open(outfile) as _im: - _im.load() - self.im = _im.im - finally: - try: - os.unlink(outfile) - except OSError: - pass + with Image.open(o) as _im: + _im.load() + self.im = _im.im Image.register_open(IptcImageFile.format, IptcImageFile) @@ -184,8 +191,6 @@ def getiptcinfo(im): :returns: A dictionary containing IPTC information, or None if no IPTC information block was found. """ - import io - from . import JpegImagePlugin, TiffImagePlugin data = None @@ -220,7 +225,7 @@ def getiptcinfo(im): # parse the IPTC information chunk im.info = {} - im.fp = io.BytesIO(data) + im.fp = BytesIO(data) try: im._open() diff --git a/contrib/python/Pillow/py3/PIL/Jpeg2KImagePlugin.py b/contrib/python/Pillow/py3/PIL/Jpeg2KImagePlugin.py index 963d6c1a31..4b778a0d33 100644 --- a/contrib/python/Pillow/py3/PIL/Jpeg2KImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/Jpeg2KImagePlugin.py @@ -13,6 +13,8 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations + import io import os import struct @@ -334,10 +336,7 @@ def _save(im, fp, filename): if quality_layers is not None and not ( isinstance(quality_layers, (list, tuple)) and all( - [ - isinstance(quality_layer, (int, float)) - for quality_layer in quality_layers - ] + isinstance(quality_layer, (int, float)) for quality_layer in quality_layers ) ): msg = "quality_layers must be a sequence of numbers" diff --git a/contrib/python/Pillow/py3/PIL/JpegImagePlugin.py b/contrib/python/Pillow/py3/PIL/JpegImagePlugin.py index 917bbf39fb..81b8749a33 100644 --- a/contrib/python/Pillow/py3/PIL/JpegImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/JpegImagePlugin.py @@ -31,6 +31,8 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations + import array import io import math @@ -85,10 +87,12 @@ def APP(self, marker): self.info["dpi"] = jfif_density self.info["jfif_unit"] = jfif_unit self.info["jfif_density"] = jfif_density - elif marker == 0xFFE1 and s[:5] == b"Exif\0": - if "exif" not in self.info: - # extract EXIF information (incomplete) - self.info["exif"] = s # FIXME: value will change + elif marker == 0xFFE1 and s[:6] == b"Exif\0\0": + # extract EXIF information + if "exif" in self.info: + self.info["exif"] += s[6:] + else: + self.info["exif"] = s self._exif_offset = self.fp.tell() - n + 6 elif marker == 0xFFE2 and s[:5] == b"FPXR\0": # extract FlashPix information (incomplete) @@ -165,7 +169,8 @@ def APP(self, marker): except TypeError: dpi = x_resolution if math.isnan(dpi): - raise ValueError + msg = "DPI is not a number" + raise ValueError(msg) if resolution_unit == 3: # cm # 1 dpcm = 2.54 dpi dpi *= 2.54 @@ -232,9 +237,7 @@ def SOF(self, marker): # fixup icc profile self.icclist.sort() # sort by sequence number if self.icclist[0][13] == len(self.icclist): - profile = [] - for p in self.icclist: - profile.append(p[14:]) + profile = [p[14:] for p in self.icclist] icc_profile = b"".join(profile) else: icc_profile = None # wrong number of fragments @@ -396,7 +399,7 @@ class JpegImageFile(ImageFile.ImageFile): # self.__offset = self.fp.tell() break s = self.fp.read(1) - elif i == 0 or i == 0xFFFF: + elif i in {0, 0xFFFF}: # padded marker or junk; move on s = b"\xff" elif i == 0xFF00: # Skip extraneous data (escaped 0xFF) @@ -719,7 +722,8 @@ def _save(im, fp, filename): for idx, table in enumerate(qtables): try: if len(table) != 64: - raise TypeError + msg = "Invalid quantization table" + raise TypeError(msg) table = array.array("H", table) except TypeError as e: msg = "Invalid quantization table" @@ -781,10 +785,13 @@ def _save(im, fp, filename): progressive, info.get("smooth", 0), optimize, + info.get("keep_rgb", False), info.get("streamtype", 0), dpi[0], dpi[1], subsampling, + info.get("restart_marker_blocks", 0), + info.get("restart_marker_rows", 0), qtables, comment, extra, diff --git a/contrib/python/Pillow/py3/PIL/JpegPresets.py b/contrib/python/Pillow/py3/PIL/JpegPresets.py index a678e248e9..9ecfdb2599 100644 --- a/contrib/python/Pillow/py3/PIL/JpegPresets.py +++ b/contrib/python/Pillow/py3/PIL/JpegPresets.py @@ -62,6 +62,7 @@ Libjpeg ref.: https://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html """ +from __future__ import annotations # fmt: off presets = { diff --git a/contrib/python/Pillow/py3/PIL/McIdasImagePlugin.py b/contrib/python/Pillow/py3/PIL/McIdasImagePlugin.py index bb79e71de5..9a85c0d15b 100644 --- a/contrib/python/Pillow/py3/PIL/McIdasImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/McIdasImagePlugin.py @@ -15,6 +15,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import struct diff --git a/contrib/python/Pillow/py3/PIL/MicImagePlugin.py b/contrib/python/Pillow/py3/PIL/MicImagePlugin.py index 801318930d..f4529d9ae7 100644 --- a/contrib/python/Pillow/py3/PIL/MicImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/MicImagePlugin.py @@ -15,7 +15,7 @@ # # See the README file for information on usage and redistribution. # - +from __future__ import annotations import olefile @@ -51,10 +51,11 @@ class MicImageFile(TiffImagePlugin.TiffImageFile): # find ACI subfiles with Image members (maybe not the # best way to identify MIC files, but what the... ;-) - self.images = [] - for path in self.ole.listdir(): - if path[1:] and path[0][-4:] == ".ACI" and path[1] == "Image": - self.images.append(path) + self.images = [ + path + for path in self.ole.listdir() + if path[1:] and path[0][-4:] == ".ACI" and path[1] == "Image" + ] # if we didn't find any images, this is probably not # an MIC file. @@ -66,6 +67,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile): self._n_frames = len(self.images) self.is_animated = self._n_frames > 1 + self.__fp = self.fp self.seek(0) def seek(self, frame): @@ -87,10 +89,12 @@ class MicImageFile(TiffImagePlugin.TiffImageFile): return self.frame def close(self): + self.__fp.close() self.ole.close() super().close() def __exit__(self, *args): + self.__fp.close() self.ole.close() super().__exit__() diff --git a/contrib/python/Pillow/py3/PIL/MpegImagePlugin.py b/contrib/python/Pillow/py3/PIL/MpegImagePlugin.py index bfa88fe99c..f4e598ca3a 100644 --- a/contrib/python/Pillow/py3/PIL/MpegImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/MpegImagePlugin.py @@ -12,7 +12,7 @@ # # See the README file for information on usage and redistribution. # - +from __future__ import annotations from . import Image, ImageFile from ._binary import i8 diff --git a/contrib/python/Pillow/py3/PIL/MpoImagePlugin.py b/contrib/python/Pillow/py3/PIL/MpoImagePlugin.py index f9261c77d6..199a100904 100644 --- a/contrib/python/Pillow/py3/PIL/MpoImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/MpoImagePlugin.py @@ -17,6 +17,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import itertools import os @@ -33,9 +34,6 @@ from . import ( from ._binary import i16be as i16 from ._binary import o32le -# def _accept(prefix): -# return JpegImagePlugin._accept(prefix) - def _save(im, fp, filename): JpegImagePlugin._save(im, fp, filename) diff --git a/contrib/python/Pillow/py3/PIL/MspImagePlugin.py b/contrib/python/Pillow/py3/PIL/MspImagePlugin.py index 3f3609f1c2..77dac65b6b 100644 --- a/contrib/python/Pillow/py3/PIL/MspImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/MspImagePlugin.py @@ -22,6 +22,7 @@ # Figure 206. Windows Paint Version 2: "LinS" Format. Used in Windows V2.03 # # See also: https://www.fileformat.info/format/mspaint/egff.htm +from __future__ import annotations import io import struct diff --git a/contrib/python/Pillow/py3/PIL/PSDraw.py b/contrib/python/Pillow/py3/PIL/PSDraw.py index 13b3048f67..848fc2f716 100644 --- a/contrib/python/Pillow/py3/PIL/PSDraw.py +++ b/contrib/python/Pillow/py3/PIL/PSDraw.py @@ -14,6 +14,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import sys @@ -109,7 +110,7 @@ class PSDraw: if im.mode == "1": dpi = 200 # fax else: - dpi = 100 # greyscale + dpi = 100 # grayscale # image size (on paper) x = im.size[0] * 72 / dpi y = im.size[1] * 72 / dpi diff --git a/contrib/python/Pillow/py3/PIL/PaletteFile.py b/contrib/python/Pillow/py3/PIL/PaletteFile.py index 4a2c497fc4..dc31754020 100644 --- a/contrib/python/Pillow/py3/PIL/PaletteFile.py +++ b/contrib/python/Pillow/py3/PIL/PaletteFile.py @@ -12,6 +12,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations from ._binary import o8 diff --git a/contrib/python/Pillow/py3/PIL/PalmImagePlugin.py b/contrib/python/Pillow/py3/PIL/PalmImagePlugin.py index a88a907917..65be7fef7b 100644 --- a/contrib/python/Pillow/py3/PIL/PalmImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/PalmImagePlugin.py @@ -6,6 +6,7 @@ ## # Image plugin for Palm pixmap images (output only). ## +from __future__ import annotations from . import Image, ImageFile from ._binary import o8 @@ -124,7 +125,7 @@ def _save(im, fp, filename): if im.encoderinfo.get("bpp") in (1, 2, 4): # this is 8-bit grayscale, so we shift it to get the high-order bits, # and invert it because - # Palm does greyscale from white (0) to black (1) + # Palm does grayscale from white (0) to black (1) bpp = im.encoderinfo["bpp"] im = im.point( lambda x, shift=8 - bpp, maxval=(1 << bpp) - 1: maxval - (x >> shift) diff --git a/contrib/python/Pillow/py3/PIL/PcdImagePlugin.py b/contrib/python/Pillow/py3/PIL/PcdImagePlugin.py index c7cbca8c5d..a0515b302e 100644 --- a/contrib/python/Pillow/py3/PIL/PcdImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/PcdImagePlugin.py @@ -13,7 +13,7 @@ # # See the README file for information on usage and redistribution. # - +from __future__ import annotations from . import Image, ImageFile diff --git a/contrib/python/Pillow/py3/PIL/PcfFontFile.py b/contrib/python/Pillow/py3/PIL/PcfFontFile.py index 8db5822fe7..0d1968b140 100644 --- a/contrib/python/Pillow/py3/PIL/PcfFontFile.py +++ b/contrib/python/Pillow/py3/PIL/PcfFontFile.py @@ -15,8 +15,10 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import io +from typing import BinaryIO, Callable from . import FontFile, Image from ._binary import i8 @@ -40,7 +42,7 @@ PCF_SWIDTHS = 1 << 6 PCF_GLYPH_NAMES = 1 << 7 PCF_BDF_ACCELERATORS = 1 << 8 -BYTES_PER_ROW = [ +BYTES_PER_ROW: list[Callable[[int], int]] = [ lambda bits: ((bits + 7) >> 3), lambda bits: ((bits + 15) >> 3) & ~1, lambda bits: ((bits + 31) >> 3) & ~3, @@ -48,7 +50,7 @@ BYTES_PER_ROW = [ ] -def sz(s, o): +def sz(s: bytes, o: int) -> bytes: return s[o : s.index(b"\0", o)] @@ -57,7 +59,7 @@ class PcfFontFile(FontFile.FontFile): name = "name" - def __init__(self, fp, charset_encoding="iso8859-1"): + def __init__(self, fp: BinaryIO, charset_encoding: str = "iso8859-1"): self.charset_encoding = charset_encoding magic = l32(fp.read(4)) @@ -103,7 +105,9 @@ class PcfFontFile(FontFile.FontFile): bitmaps[ix], ) - def _getformat(self, tag): + def _getformat( + self, tag: int + ) -> tuple[BinaryIO, int, Callable[[bytes], int], Callable[[bytes], int]]: format, size, offset = self.toc[tag] fp = self.fp @@ -118,7 +122,7 @@ class PcfFontFile(FontFile.FontFile): return fp, format, i16, i32 - def _load_properties(self): + def _load_properties(self) -> dict[bytes, bytes | int]: # # font properties @@ -129,27 +133,24 @@ class PcfFontFile(FontFile.FontFile): nprops = i32(fp.read(4)) # read property description - p = [] - for i in range(nprops): - p.append((i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4)))) + p = [(i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4))) for _ in range(nprops)] + if nprops & 3: fp.seek(4 - (nprops & 3), io.SEEK_CUR) # pad data = fp.read(i32(fp.read(4))) for k, s, v in p: - k = sz(data, k) - if s: - v = sz(data, v) - properties[k] = v + property_value: bytes | int = sz(data, v) if s else v + properties[sz(data, k)] = property_value return properties - def _load_metrics(self): + def _load_metrics(self) -> list[tuple[int, int, int, int, int, int, int, int]]: # # font metrics - metrics = [] + metrics: list[tuple[int, int, int, int, int, int, int, int]] = [] fp, format, i16, i32 = self._getformat(PCF_METRICS) @@ -182,12 +183,12 @@ class PcfFontFile(FontFile.FontFile): return metrics - def _load_bitmaps(self, metrics): + def _load_bitmaps( + self, metrics: list[tuple[int, int, int, int, int, int, int, int]] + ) -> list[Image.Image]: # # bitmap data - bitmaps = [] - fp, format, i16, i32 = self._getformat(PCF_BITMAPS) nbitmaps = i32(fp.read(4)) @@ -196,13 +197,9 @@ class PcfFontFile(FontFile.FontFile): msg = "Wrong number of bitmaps" raise OSError(msg) - offsets = [] - for i in range(nbitmaps): - offsets.append(i32(fp.read(4))) + offsets = [i32(fp.read(4)) for _ in range(nbitmaps)] - bitmap_sizes = [] - for i in range(4): - bitmap_sizes.append(i32(fp.read(4))) + bitmap_sizes = [i32(fp.read(4)) for _ in range(4)] # byteorder = format & 4 # non-zero => MSB bitorder = format & 8 # non-zero => MSB @@ -218,6 +215,7 @@ class PcfFontFile(FontFile.FontFile): if bitorder: mode = "1" + bitmaps = [] for i in range(nbitmaps): xsize, ysize = metrics[i][:2] b, e = offsets[i : i + 2] @@ -227,7 +225,7 @@ class PcfFontFile(FontFile.FontFile): return bitmaps - def _load_encoding(self): + def _load_encoding(self) -> list[int | None]: fp, format, i16, i32 = self._getformat(PCF_BDF_ENCODINGS) first_col, last_col = i16(fp.read(2)), i16(fp.read(2)) @@ -238,7 +236,7 @@ class PcfFontFile(FontFile.FontFile): nencoding = (last_col - first_col + 1) * (last_row - first_row + 1) # map character code to bitmap index - encoding = [None] * min(256, nencoding) + encoding: list[int | None] = [None] * min(256, nencoding) encoding_offsets = [i16(fp.read(2)) for _ in range(nencoding)] diff --git a/contrib/python/Pillow/py3/PIL/PcxImagePlugin.py b/contrib/python/Pillow/py3/PIL/PcxImagePlugin.py index 854d9e83ee..98ecefd051 100644 --- a/contrib/python/Pillow/py3/PIL/PcxImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/PcxImagePlugin.py @@ -24,6 +24,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import io import logging @@ -91,7 +92,7 @@ class PcxImageFile(ImageFile.ImageFile): self.fp.seek(-769, io.SEEK_END) s = self.fp.read(769) if len(s) == 769 and s[0] == 12: - # check if the palette is linear greyscale + # check if the palette is linear grayscale for i in range(256): if s[i * 3 + 1 : i * 3 + 4] != o8(i) * 3: mode = rawmode = "P" @@ -203,7 +204,7 @@ def _save(im, fp, filename): palette += b"\x00" * (768 - len(palette)) fp.write(palette) # 768 bytes elif im.mode == "L": - # greyscale palette + # grayscale palette fp.write(o8(12)) for i in range(256): fp.write(o8(i) * 3) diff --git a/contrib/python/Pillow/py3/PIL/PdfImagePlugin.py b/contrib/python/Pillow/py3/PIL/PdfImagePlugin.py index 09fc0c7e6c..3506aadce8 100644 --- a/contrib/python/Pillow/py3/PIL/PdfImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/PdfImagePlugin.py @@ -19,6 +19,7 @@ ## # Image plugin for PDF images (output only). ## +from __future__ import annotations import io import math @@ -96,7 +97,7 @@ def _write_image(im, filename, existing_pdf, image_refs): dict_obj["ColorSpace"] = [ PdfParser.PdfName("Indexed"), PdfParser.PdfName("DeviceRGB"), - 255, + len(palette) // 3 - 1, PdfParser.PdfBinary(palette), ] procset = "ImageI" # indexed color diff --git a/contrib/python/Pillow/py3/PIL/PdfParser.py b/contrib/python/Pillow/py3/PIL/PdfParser.py index dc1012f54d..0144600066 100644 --- a/contrib/python/Pillow/py3/PIL/PdfParser.py +++ b/contrib/python/Pillow/py3/PIL/PdfParser.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import calendar import codecs import collections @@ -82,7 +84,7 @@ class IndirectReference( collections.namedtuple("IndirectReferenceTuple", ["object_id", "generation"]) ): def __str__(self): - return "%s %s R" % self + return f"{self.object_id} {self.generation} R" def __bytes__(self): return self.__str__().encode("us-ascii") @@ -103,7 +105,7 @@ class IndirectReference( class IndirectObjectDef(IndirectReference): def __str__(self): - return "%s %s obj" % self + return f"{self.object_id} {self.generation} obj" class XrefTable: diff --git a/contrib/python/Pillow/py3/PIL/PixarImagePlugin.py b/contrib/python/Pillow/py3/PIL/PixarImagePlugin.py index 850272311d..af866feb36 100644 --- a/contrib/python/Pillow/py3/PIL/PixarImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/PixarImagePlugin.py @@ -18,6 +18,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations from . import Image, ImageFile from ._binary import i16le as i16 diff --git a/contrib/python/Pillow/py3/PIL/PngImagePlugin.py b/contrib/python/Pillow/py3/PIL/PngImagePlugin.py index 5e5a8cf6a2..e4ed938801 100644 --- a/contrib/python/Pillow/py3/PIL/PngImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/PngImagePlugin.py @@ -30,6 +30,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import itertools import logging @@ -56,7 +57,7 @@ _MAGIC = b"\211PNG\r\n\032\n" _MODES = { # supported bits/color combinations, and corresponding modes/rawmodes - # Greyscale + # Grayscale (1, 0): ("1", "1"), (2, 0): ("L", "L;2"), (4, 0): ("L", "L;4"), @@ -70,7 +71,7 @@ _MODES = { (2, 3): ("P", "P;2"), (4, 3): ("P", "P;4"), (8, 3): ("P", "P"), - # Greyscale with alpha + # Grayscale with alpha (8, 4): ("LA", "LA"), (16, 4): ("RGBA", "LA;16B"), # LA;16B->LA not yet available # Truecolour with alpha @@ -438,11 +439,12 @@ class PngStream(ChunkStream): tile = [("zip", (0, 0) + self.im_size, pos, self.im_rawmode)] self.im_tile = tile self.im_idat = length - raise EOFError + msg = "image data found" + raise EOFError(msg) def chunk_IEND(self, pos, length): - # end of PNG image - raise EOFError + msg = "end of PNG image" + raise EOFError(msg) def chunk_PLTE(self, pos, length): # palette @@ -891,7 +893,8 @@ class PngImageFile(ImageFile.ImageFile): self.dispose_extent = self.info.get("bbox") if not self.tile: - raise EOFError + msg = "image not found in APNG frame" + raise EOFError(msg) # setup frame disposal (actual disposal done when needed in the next _seek()) if self._prev_im is None and self.dispose_op == Disposal.OP_PREVIOUS: @@ -1154,6 +1157,9 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) encoderinfo["duration"] = duration im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo}) + if len(im_frames) == 1 and not default_image: + return im_frames[0]["im"] + # animation control chunk( fp, @@ -1389,8 +1395,10 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): chunk(fp, b"eXIf", exif) if save_all: - _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) - else: + im = _write_multiple_frames( + im, fp, chunk, rawmode, default_image, append_images + ) + if im: ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)]) if info: diff --git a/contrib/python/Pillow/py3/PIL/PpmImagePlugin.py b/contrib/python/Pillow/py3/PIL/PpmImagePlugin.py index e480ab0558..25dbfa5b0b 100644 --- a/contrib/python/Pillow/py3/PIL/PpmImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/PpmImagePlugin.py @@ -13,7 +13,7 @@ # # See the README file for information on usage and redistribution. # - +from __future__ import annotations from . import Image, ImageFile from ._binary import i16be as i16 @@ -328,9 +328,6 @@ def _save(im, fp, filename): fp.write(b"65535\n") ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]) - # ALTERNATIVE: save via builtin debug function - # im._dump(filename) - # # -------------------------------------------------------------------- diff --git a/contrib/python/Pillow/py3/PIL/PsdImagePlugin.py b/contrib/python/Pillow/py3/PIL/PsdImagePlugin.py index 2f019bb8c3..5cff564137 100644 --- a/contrib/python/Pillow/py3/PIL/PsdImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/PsdImagePlugin.py @@ -15,6 +15,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import io @@ -186,6 +187,9 @@ def _layerinfo(fp, ct_bytes): ct_types = i16(read(2)) types = list(range(ct_types)) if len(types) > 4: + fp.seek(len(types) * 6 + 12, io.SEEK_CUR) + size = i32(read(4)) + fp.seek(size, io.SEEK_CUR) continue for _ in types: diff --git a/contrib/python/Pillow/py3/PIL/PyAccess.py b/contrib/python/Pillow/py3/PIL/PyAccess.py index 99b46a4a66..07bb712d83 100644 --- a/contrib/python/Pillow/py3/PIL/PyAccess.py +++ b/contrib/python/Pillow/py3/PIL/PyAccess.py @@ -18,6 +18,7 @@ # * Fill.c uses the integer form, but it's still going to use the old # Access.c implementation. # +from __future__ import annotations import logging import sys @@ -42,7 +43,7 @@ except ImportError as ex: # anything in core. from ._util import DeferredError - FFI = ffi = DeferredError(ex) + FFI = ffi = DeferredError.new(ex) logger = logging.getLogger(__name__) @@ -244,7 +245,7 @@ class _PyAccessI16_L(PyAccess): except TypeError: color = min(color[0], 65535) - pixel.l = color & 0xFF # noqa: E741 + pixel.l = color & 0xFF pixel.r = color >> 8 @@ -265,7 +266,7 @@ class _PyAccessI16_B(PyAccess): except Exception: color = min(color[0], 65535) - pixel.l = color >> 8 # noqa: E741 + pixel.l = color >> 8 pixel.r = color & 0xFF diff --git a/contrib/python/Pillow/py3/PIL/QoiImagePlugin.py b/contrib/python/Pillow/py3/PIL/QoiImagePlugin.py index 66344faac5..a7b9d4a9e3 100644 --- a/contrib/python/Pillow/py3/PIL/QoiImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/QoiImagePlugin.py @@ -5,6 +5,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import os diff --git a/contrib/python/Pillow/py3/PIL/SgiImagePlugin.py b/contrib/python/Pillow/py3/PIL/SgiImagePlugin.py index acb9ce5a38..f9a10f6109 100644 --- a/contrib/python/Pillow/py3/PIL/SgiImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/SgiImagePlugin.py @@ -20,7 +20,7 @@ # # See the README file for information on usage and redistribution. # - +from __future__ import annotations import os import struct @@ -123,7 +123,7 @@ class SgiImageFile(ImageFile.ImageFile): def _save(im, fp, filename): - if im.mode != "RGB" and im.mode != "RGBA" and im.mode != "L": + if im.mode not in {"RGB", "RGBA", "L"}: msg = "Unsupported SGI image mode" raise ValueError(msg) @@ -155,7 +155,7 @@ def _save(im, fp, filename): # Z Dimension: Number of channels z = len(im.mode) - if dim == 1 or dim == 2: + if dim in {1, 2}: z = 1 # assert we've got the right number of bands. diff --git a/contrib/python/Pillow/py3/PIL/SpiderImagePlugin.py b/contrib/python/Pillow/py3/PIL/SpiderImagePlugin.py index 408b982b51..86582fb128 100644 --- a/contrib/python/Pillow/py3/PIL/SpiderImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/SpiderImagePlugin.py @@ -32,6 +32,8 @@ # Details about the Spider image format: # https://spider.wadsworth.org/spider_doc/spider/docs/image_doc.html # +from __future__ import annotations + import os import struct import sys @@ -238,9 +240,7 @@ def makeSpiderHeader(im): if nvalues < 23: return [] - hdr = [] - for i in range(nvalues): - hdr.append(0.0) + hdr = [0.0] * nvalues # NB these are Fortran indices hdr[1] = 1.0 # nslice (=1 for an image) diff --git a/contrib/python/Pillow/py3/PIL/SunImagePlugin.py b/contrib/python/Pillow/py3/PIL/SunImagePlugin.py index 6a8d5d86b7..11ce3dfefd 100644 --- a/contrib/python/Pillow/py3/PIL/SunImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/SunImagePlugin.py @@ -15,7 +15,7 @@ # # See the README file for information on usage and redistribution. # - +from __future__ import annotations from . import Image, ImageFile, ImagePalette from ._binary import i32be as i32 diff --git a/contrib/python/Pillow/py3/PIL/TarIO.py b/contrib/python/Pillow/py3/PIL/TarIO.py index 32928f6af3..7470663b4a 100644 --- a/contrib/python/Pillow/py3/PIL/TarIO.py +++ b/contrib/python/Pillow/py3/PIL/TarIO.py @@ -13,16 +13,18 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import io +from types import TracebackType from . import ContainerIO -class TarIO(ContainerIO.ContainerIO): +class TarIO(ContainerIO.ContainerIO[bytes]): """A file object that provides read access to a given member of a TAR file.""" - def __init__(self, tarfile, file): + def __init__(self, tarfile: str, file: str) -> None: """ Create file object. @@ -56,11 +58,16 @@ class TarIO(ContainerIO.ContainerIO): super().__init__(self.fh, self.fh.tell(), size) # Context manager support - def __enter__(self): + def __enter__(self) -> TarIO: return self - def __exit__(self, *args): + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: self.close() - def close(self): + def close(self) -> None: self.fh.close() diff --git a/contrib/python/Pillow/py3/PIL/TgaImagePlugin.py b/contrib/python/Pillow/py3/PIL/TgaImagePlugin.py index f24ee4f5c3..65c7484f75 100644 --- a/contrib/python/Pillow/py3/PIL/TgaImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/TgaImagePlugin.py @@ -15,7 +15,7 @@ # # See the README file for information on usage and redistribution. # - +from __future__ import annotations import warnings diff --git a/contrib/python/Pillow/py3/PIL/TiffImagePlugin.py b/contrib/python/Pillow/py3/PIL/TiffImagePlugin.py index dabf8dbfb5..e20d4d5ea8 100644 --- a/contrib/python/Pillow/py3/PIL/TiffImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/TiffImagePlugin.py @@ -38,6 +38,8 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations + import io import itertools import logging @@ -1702,25 +1704,27 @@ def _save(im, fp, filename): colormap += [0] * (256 - colors) ifd[COLORMAP] = colormap # data orientation - stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8) - # aim for given strip size (64 KB by default) when using libtiff writer - if libtiff: - im_strip_size = encoderinfo.get("strip_size", STRIP_SIZE) - rows_per_strip = 1 if stride == 0 else min(im_strip_size // stride, im.size[1]) - # JPEG encoder expects multiple of 8 rows - if compression == "jpeg": - rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, im.size[1]) - else: - rows_per_strip = im.size[1] - if rows_per_strip == 0: - rows_per_strip = 1 - strip_byte_counts = 1 if stride == 0 else stride * rows_per_strip - strips_per_image = (im.size[1] + rows_per_strip - 1) // rows_per_strip - ifd[ROWSPERSTRIP] = rows_per_strip + w, h = ifd[IMAGEWIDTH], ifd[IMAGELENGTH] + stride = len(bits) * ((w * bits[0] + 7) // 8) + if ROWSPERSTRIP not in ifd: + # aim for given strip size (64 KB by default) when using libtiff writer + if libtiff: + im_strip_size = encoderinfo.get("strip_size", STRIP_SIZE) + rows_per_strip = 1 if stride == 0 else min(im_strip_size // stride, h) + # JPEG encoder expects multiple of 8 rows + if compression == "jpeg": + rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, h) + else: + rows_per_strip = h + if rows_per_strip == 0: + rows_per_strip = 1 + ifd[ROWSPERSTRIP] = rows_per_strip + strip_byte_counts = 1 if stride == 0 else stride * ifd[ROWSPERSTRIP] + strips_per_image = (h + ifd[ROWSPERSTRIP] - 1) // ifd[ROWSPERSTRIP] if strip_byte_counts >= 2**16: ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG ifd[STRIPBYTECOUNTS] = (strip_byte_counts,) * (strips_per_image - 1) + ( - stride * im.size[1] - strip_byte_counts * (strips_per_image - 1), + stride * h - strip_byte_counts * (strips_per_image - 1), ) ifd[STRIPOFFSETS] = tuple( range(0, strip_byte_counts * strips_per_image, strip_byte_counts) @@ -1885,13 +1889,14 @@ class AppendingTiffWriter: 8, # long8 ] - # StripOffsets = 273 - # FreeOffsets = 288 - # TileOffsets = 324 - # JPEGQTables = 519 - # JPEGDCTables = 520 - # JPEGACTables = 521 - Tags = {273, 288, 324, 519, 520, 521} + Tags = { + 273, # StripOffsets + 288, # FreeOffsets + 324, # TileOffsets + 519, # JPEGQTables + 520, # JPEGDCTables + 521, # JPEGACTables + } def __init__(self, fn, new=False): if hasattr(fn, "read"): @@ -1941,8 +1946,6 @@ class AppendingTiffWriter: iimm = self.f.read(4) if not iimm: - # msg = "nothing written into new page" - # raise RuntimeError(msg) # Make it easy to finish a frame without committing to a new one. return diff --git a/contrib/python/Pillow/py3/PIL/TiffTags.py b/contrib/python/Pillow/py3/PIL/TiffTags.py index 30b05e4e1d..88ff2f4fcd 100644 --- a/contrib/python/Pillow/py3/PIL/TiffTags.py +++ b/contrib/python/Pillow/py3/PIL/TiffTags.py @@ -16,6 +16,7 @@ # This module provides constants and clear-text names for various # well-known TIFF tags. ## +from __future__ import annotations from collections import namedtuple @@ -56,7 +57,7 @@ def lookup(tag, group=None): ## # Map tag numbers to tag info. # -# id: (Name, Type, Length, enum_values) +# id: (Name, Type, Length[, enum_values]) # # The length here differs from the length in the tiff spec. For # numbers, the tiff spec is for the number of fields returned. We @@ -427,7 +428,7 @@ def _populate(): TAGS_V2[k] = TagInfo(k, *v) - for group, tags in TAGS_V2_GROUPS.items(): + for tags in TAGS_V2_GROUPS.values(): for k, v in tags.items(): tags[k] = TagInfo(k, *v) @@ -438,22 +439,6 @@ _populate() TYPES = {} -# was: -# TYPES = { -# 1: "byte", -# 2: "ascii", -# 3: "short", -# 4: "long", -# 5: "rational", -# 6: "signed byte", -# 7: "undefined", -# 8: "signed short", -# 9: "signed long", -# 10: "signed rational", -# 11: "float", -# 12: "double", -# } - # # These tags are handled by default in libtiff, without # adding to the custom dictionary. From tif_dir.c, searching for diff --git a/contrib/python/Pillow/py3/PIL/WalImageFile.py b/contrib/python/Pillow/py3/PIL/WalImageFile.py index 3d9f97f848..c5bf3e04cf 100644 --- a/contrib/python/Pillow/py3/PIL/WalImageFile.py +++ b/contrib/python/Pillow/py3/PIL/WalImageFile.py @@ -22,6 +22,7 @@ and has been tested with a few sample files found using google. is not registered for use with :py:func:`PIL.Image.open()`. To open a WAL file, use the :py:func:`PIL.WalImageFile.open()` function instead. """ +from __future__ import annotations from . import Image, ImageFile from ._binary import i32le as i32 diff --git a/contrib/python/Pillow/py3/PIL/WebPImagePlugin.py b/contrib/python/Pillow/py3/PIL/WebPImagePlugin.py index 612fc09467..59556206a3 100644 --- a/contrib/python/Pillow/py3/PIL/WebPImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/WebPImagePlugin.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from io import BytesIO from . import Image, ImageFile @@ -168,6 +170,9 @@ class WebPImageFile(ImageFile.ImageFile): return super().load() + def load_seek(self, pos): + pass + def tell(self): if not _webp.HAVE_WEBPANIM: return super().tell() diff --git a/contrib/python/Pillow/py3/PIL/WmfImagePlugin.py b/contrib/python/Pillow/py3/PIL/WmfImagePlugin.py index 3e5fb01512..b5b8c69b17 100644 --- a/contrib/python/Pillow/py3/PIL/WmfImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/WmfImagePlugin.py @@ -18,6 +18,7 @@ # https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-WMF/[MS-WMF].pdf # http://wvware.sourceforge.net/caolan/index.html # http://wvware.sourceforge.net/caolan/ora-wmf.html +from __future__ import annotations from . import Image, ImageFile from ._binary import i16le as word diff --git a/contrib/python/Pillow/py3/PIL/XVThumbImagePlugin.py b/contrib/python/Pillow/py3/PIL/XVThumbImagePlugin.py index eda60c5c5c..47ba1c5480 100644 --- a/contrib/python/Pillow/py3/PIL/XVThumbImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/XVThumbImagePlugin.py @@ -16,6 +16,7 @@ # To do: # FIXME: make save work (this requires quantization support) # +from __future__ import annotations from . import Image, ImageFile, ImagePalette from ._binary import o8 diff --git a/contrib/python/Pillow/py3/PIL/XbmImagePlugin.py b/contrib/python/Pillow/py3/PIL/XbmImagePlugin.py index 71cd57d74d..566acbfe5a 100644 --- a/contrib/python/Pillow/py3/PIL/XbmImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/XbmImagePlugin.py @@ -18,6 +18,7 @@ # # See the README file for information on usage and redistribution. # +from __future__ import annotations import re diff --git a/contrib/python/Pillow/py3/PIL/XpmImagePlugin.py b/contrib/python/Pillow/py3/PIL/XpmImagePlugin.py index 8491d3b7e9..bf73c9bef0 100644 --- a/contrib/python/Pillow/py3/PIL/XpmImagePlugin.py +++ b/contrib/python/Pillow/py3/PIL/XpmImagePlugin.py @@ -13,7 +13,7 @@ # # See the README file for information on usage and redistribution. # - +from __future__ import annotations import re diff --git a/contrib/python/Pillow/py3/PIL/__init__.py b/contrib/python/Pillow/py3/PIL/__init__.py index 2bb8f6d7f1..3fcac8643c 100644 --- a/contrib/python/Pillow/py3/PIL/__init__.py +++ b/contrib/python/Pillow/py3/PIL/__init__.py @@ -12,6 +12,7 @@ Use PIL.__version__ for this Pillow version. ;-) """ +from __future__ import annotations from . import _version diff --git a/contrib/python/Pillow/py3/PIL/__main__.py b/contrib/python/Pillow/py3/PIL/__main__.py index d249991d05..c49139f9ed 100644 --- a/contrib/python/Pillow/py3/PIL/__main__.py +++ b/contrib/python/Pillow/py3/PIL/__main__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .features import pilinfo diff --git a/contrib/python/Pillow/py3/PIL/_binary.py b/contrib/python/Pillow/py3/PIL/_binary.py index a74ee9eb6f..0a07e8d0e1 100644 --- a/contrib/python/Pillow/py3/PIL/_binary.py +++ b/contrib/python/Pillow/py3/PIL/_binary.py @@ -13,21 +13,21 @@ """Binary input/output support routines.""" - +from __future__ import annotations from struct import pack, unpack_from -def i8(c): - return c if c.__class__ is int else c[0] +def i8(c: bytes) -> int: + return c[0] -def o8(i): +def o8(i: int) -> bytes: return bytes((i & 255,)) # Input, le = little endian, be = big endian -def i16le(c, o=0): +def i16le(c: bytes, o: int = 0) -> int: """ Converts a 2-bytes (16 bits) string to an unsigned integer. @@ -37,7 +37,7 @@ def i16le(c, o=0): return unpack_from("<H", c, o)[0] -def si16le(c, o=0): +def si16le(c: bytes, o: int = 0) -> int: """ Converts a 2-bytes (16 bits) string to a signed integer. @@ -47,7 +47,7 @@ def si16le(c, o=0): return unpack_from("<h", c, o)[0] -def si16be(c, o=0): +def si16be(c: bytes, o: int = 0) -> int: """ Converts a 2-bytes (16 bits) string to a signed integer, big endian. @@ -57,7 +57,7 @@ def si16be(c, o=0): return unpack_from(">h", c, o)[0] -def i32le(c, o=0): +def i32le(c: bytes, o: int = 0) -> int: """ Converts a 4-bytes (32 bits) string to an unsigned integer. @@ -67,7 +67,7 @@ def i32le(c, o=0): return unpack_from("<I", c, o)[0] -def si32le(c, o=0): +def si32le(c: bytes, o: int = 0) -> int: """ Converts a 4-bytes (32 bits) string to a signed integer. @@ -77,26 +77,26 @@ def si32le(c, o=0): return unpack_from("<i", c, o)[0] -def i16be(c, o=0): +def i16be(c: bytes, o: int = 0) -> int: return unpack_from(">H", c, o)[0] -def i32be(c, o=0): +def i32be(c: bytes, o: int = 0) -> int: return unpack_from(">I", c, o)[0] # Output, le = little endian, be = big endian -def o16le(i): +def o16le(i: int) -> bytes: return pack("<H", i) -def o32le(i): +def o32le(i: int) -> bytes: return pack("<I", i) -def o16be(i): +def o16be(i: int) -> bytes: return pack(">H", i) -def o32be(i): +def o32be(i: int) -> bytes: return pack(">I", i) diff --git a/contrib/python/Pillow/py3/PIL/_deprecate.py b/contrib/python/Pillow/py3/PIL/_deprecate.py index 2f2a3df13e..33a0e07b3f 100644 --- a/contrib/python/Pillow/py3/PIL/_deprecate.py +++ b/contrib/python/Pillow/py3/PIL/_deprecate.py @@ -47,6 +47,8 @@ def deprecate( raise RuntimeError(msg) elif when == 11: removed = "Pillow 11 (2024-10-15)" + elif when == 12: + removed = "Pillow 12 (2025-10-15)" else: msg = f"Unknown removal version: {when}. Update {__name__}?" raise ValueError(msg) diff --git a/contrib/python/Pillow/py3/PIL/_typing.py b/contrib/python/Pillow/py3/PIL/_typing.py new file mode 100644 index 0000000000..608b2b41fa --- /dev/null +++ b/contrib/python/Pillow/py3/PIL/_typing.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import sys + +if sys.version_info >= (3, 10): + from typing import TypeGuard +else: + try: + from typing_extensions import TypeGuard + except ImportError: + from typing import Any + + class TypeGuard: # type: ignore[no-redef] + def __class_getitem__(cls, item: Any) -> type[bool]: + return bool + + +__all__ = ["TypeGuard"] diff --git a/contrib/python/Pillow/py3/PIL/_util.py b/contrib/python/Pillow/py3/PIL/_util.py index ba27b7e49e..13f369cca1 100644 --- a/contrib/python/Pillow/py3/PIL/_util.py +++ b/contrib/python/Pillow/py3/PIL/_util.py @@ -1,19 +1,32 @@ +from __future__ import annotations + import os from pathlib import Path +from typing import Any, NoReturn + +from ._typing import TypeGuard -def is_path(f): +def is_path(f: Any) -> TypeGuard[bytes | str | Path]: return isinstance(f, (bytes, str, Path)) -def is_directory(f): +def is_directory(f: Any) -> TypeGuard[bytes | str | Path]: """Checks if an object is a string, and that it points to a directory.""" return is_path(f) and os.path.isdir(f) class DeferredError: - def __init__(self, ex): + def __init__(self, ex: BaseException): self.ex = ex - def __getattr__(self, elt): + def __getattr__(self, elt: str) -> NoReturn: raise self.ex + + @staticmethod + def new(ex: BaseException) -> Any: + """ + Creates an object that raises the wrapped exception ``ex`` when used, + and casts it to :py:obj:`~typing.Any` type. + """ + return DeferredError(ex) diff --git a/contrib/python/Pillow/py3/PIL/_version.py b/contrib/python/Pillow/py3/PIL/_version.py index 0936d1a7f3..1018b96b52 100644 --- a/contrib/python/Pillow/py3/PIL/_version.py +++ b/contrib/python/Pillow/py3/PIL/_version.py @@ -1,2 +1,4 @@ # Master version for Pillow -__version__ = "10.1.0" +from __future__ import annotations + +__version__ = "10.2.0" diff --git a/contrib/python/Pillow/py3/PIL/features.py b/contrib/python/Pillow/py3/PIL/features.py index 0936499fb8..9db159cdf4 100644 --- a/contrib/python/Pillow/py3/PIL/features.py +++ b/contrib/python/Pillow/py3/PIL/features.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import collections import os import sys diff --git a/contrib/python/Pillow/py3/RELEASING.md b/contrib/python/Pillow/py3/RELEASING.md index 0229dbbc1c..b3fd72a520 100644 --- a/contrib/python/Pillow/py3/RELEASING.md +++ b/contrib/python/Pillow/py3/RELEASING.md @@ -20,16 +20,7 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th. git tag 5.2.0 git push --tags ``` -* [ ] Create and check source distribution: - ```bash - make sdist - ``` -* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) -* [ ] Check and upload all binaries and source distributions e.g.: - ```bash - python3 -m twine check --strict dist/* - python3 -m twine upload dist/Pillow-5.2.0* - ``` +* [ ] Create and upload all [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions) * [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) * [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), increment and append `.dev0` to version identifier in `src/PIL/_version.py` and then: @@ -59,12 +50,7 @@ Released as needed for security, installation or critical bug fixes. ```bash make sdist ``` -* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) -* [ ] Check and upload all binaries and source distributions e.g.: - ```bash - python3 -m twine check --strict dist/* - python3 -m twine upload dist/Pillow-5.2.1* - ``` +* [ ] Create and upload all [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions) * [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then: ```bash git push @@ -86,34 +72,22 @@ Released as needed privately to individual vendors for critical security-related git tag 2.5.3 git push origin --tags ``` -* [ ] Create and check source distribution: - ```bash - make sdist - ``` -* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) +* [ ] Create and upload all [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions) * [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then: ```bash git push origin 2.5.x ``` -## Binary Distributions +## Source and Binary Distributions -### macOS and Linux -* [ ] Download wheels from the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) - and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli): - ```bash - gh run download --dir dist - # select dist-x.y.z - ``` +* [ ] Check the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) + has passed, including the "Upload release to PyPI" job. This will have been triggered + by the new tag. * [ ] Download the Linux aarch64 wheels created by Travis CI from [GitHub releases](https://github.com/python-pillow/Pillow/releases) - and copy into `dist`. - -### Windows -* [ ] Download the artifacts from the [GitHub Actions "Test Windows" workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml) - and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli): + and copy into `dist`. Check and upload them e.g.: ```bash - gh run download --dir dist - # select dist-x.y.z + python3 -m twine check --strict dist/* + python3 -m twine upload dist/Pillow-5.2.0* ``` ## Publicize Release diff --git a/contrib/python/Pillow/py3/_imaging.c b/contrib/python/Pillow/py3/_imaging.c index 2270c77fe7..59f80a3541 100644 --- a/contrib/python/Pillow/py3/_imaging.c +++ b/contrib/python/Pillow/py3/_imaging.c @@ -2649,6 +2649,26 @@ _font_new(PyObject *self_, PyObject *args) { self->glyphs[i].sy0 = S16(B16(glyphdata, 14)); self->glyphs[i].sx1 = S16(B16(glyphdata, 16)); self->glyphs[i].sy1 = S16(B16(glyphdata, 18)); + + // Do not allow glyphs to extend beyond bitmap image + // Helps prevent DOS by stopping cropped images being larger than the original + if (self->glyphs[i].sx0 < 0) { + self->glyphs[i].dx0 -= self->glyphs[i].sx0; + self->glyphs[i].sx0 = 0; + } + if (self->glyphs[i].sy0 < 0) { + self->glyphs[i].dy0 -= self->glyphs[i].sy0; + self->glyphs[i].sy0 = 0; + } + if (self->glyphs[i].sx1 > self->bitmap->xsize) { + self->glyphs[i].dx1 -= self->glyphs[i].sx1 - self->bitmap->xsize; + self->glyphs[i].sx1 = self->bitmap->xsize; + } + if (self->glyphs[i].sy1 > self->bitmap->ysize) { + self->glyphs[i].dy1 -= self->glyphs[i].sy1 - self->bitmap->ysize; + self->glyphs[i].sy1 = self->bitmap->ysize; + } + if (self->glyphs[i].dy0 < y0) { y0 = self->glyphs[i].dy0; } @@ -2721,7 +2741,7 @@ _font_text_asBytes(PyObject *encoded_string, unsigned char **text) { static PyObject * _font_getmask(ImagingFontObject *self, PyObject *args) { Imaging im; - Imaging bitmap; + Imaging bitmap = NULL; int x, b; int i = 0; int status; @@ -2730,7 +2750,7 @@ _font_getmask(ImagingFontObject *self, PyObject *args) { PyObject *encoded_string; unsigned char *text; - char *mode = ""; + char *mode; if (!PyArg_ParseTuple(args, "O|s:getmask", &encoded_string, &mode)) { return NULL; @@ -2753,10 +2773,13 @@ _font_getmask(ImagingFontObject *self, PyObject *args) { b = self->baseline; for (x = 0; text[i]; i++) { glyph = &self->glyphs[text[i]]; - bitmap = - ImagingCrop(self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1); - if (!bitmap) { - goto failed; + if (i == 0 || text[i] != text[i - 1]) { + ImagingDelete(bitmap); + bitmap = + ImagingCrop(self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1); + if (!bitmap) { + goto failed; + } } status = ImagingPaste( im, @@ -2766,17 +2789,18 @@ _font_getmask(ImagingFontObject *self, PyObject *args) { glyph->dy0 + b, glyph->dx1 + x, glyph->dy1 + b); - ImagingDelete(bitmap); if (status < 0) { goto failed; } x = x + glyph->dx; b = b + glyph->dy; } + ImagingDelete(bitmap); free(text); return PyImagingNew(im); failed: + ImagingDelete(bitmap); free(text); ImagingDelete(im); Py_RETURN_NONE; diff --git a/contrib/python/Pillow/py3/_imagingft.c b/contrib/python/Pillow/py3/_imagingft.c index 069e7cd147..19a2d6fb9b 100644 --- a/contrib/python/Pillow/py3/_imagingft.c +++ b/contrib/python/Pillow/py3/_imagingft.c @@ -877,7 +877,7 @@ font_render(FontObject *self, PyObject *args) { width += stroke_width * 2 + ceil(x_start); height += stroke_width * 2 + ceil(y_start); - image = PyObject_CallFunction(fill, "s(ii)", strcmp(mode, "RGBA") == 0 ? "RGBA" : "L", width, height); + image = PyObject_CallFunction(fill, "ii", width, height); if (image == Py_None) { PyMem_Del(glyph_info); return Py_BuildValue("ii", 0, 0); @@ -885,7 +885,9 @@ font_render(FontObject *self, PyObject *args) { PyMem_Del(glyph_info); return NULL; } - id = PyLong_AsSsize_t(PyObject_GetAttrString(image, "id")); + PyObject *imageId = PyObject_GetAttrString(image, "id"); + id = PyLong_AsSsize_t(imageId); + Py_XDECREF(imageId); im = (Imaging)id; x_offset -= stroke_width; @@ -1047,8 +1049,8 @@ font_render(FontObject *self, PyObject *args) { if (yy >= 0 && yy < im->ysize) { /* blend this glyph into the buffer */ int k; - unsigned char v; unsigned char *target; + unsigned int tmp; if (color) { /* target[RGB] returns the color, target[A] returns the mask */ /* target bands get split again in ImageDraw.text */ @@ -1059,34 +1061,55 @@ font_render(FontObject *self, PyObject *args) { if (color && bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) { /* paste color glyph */ for (k = x0; k < x1; k++) { - if (target[k * 4 + 3] < source[k * 4 + 3]) { - /* unpremultiply BGRa to RGBA */ - target[k * 4 + 0] = CLIP8( - (255 * (int)source[k * 4 + 2]) / source[k * 4 + 3]); - target[k * 4 + 1] = CLIP8( - (255 * (int)source[k * 4 + 1]) / source[k * 4 + 3]); - target[k * 4 + 2] = CLIP8( - (255 * (int)source[k * 4 + 0]) / source[k * 4 + 3]); - target[k * 4 + 3] = source[k * 4 + 3]; + unsigned int src_alpha = source[k * 4 + 3]; + + /* paste only if source has data */ + if (src_alpha > 0) { + /* unpremultiply BGRa */ + int src_red = CLIP8((255 * (int)source[k * 4 + 2]) / src_alpha); + int src_green = CLIP8((255 * (int)source[k * 4 + 1]) / src_alpha); + int src_blue = CLIP8((255 * (int)source[k * 4 + 0]) / src_alpha); + + /* blend required if target has data */ + if (target[k * 4 + 3] > 0) { + /* blend RGBA colors */ + target[k * 4 + 0] = BLEND(src_alpha, target[k * 4 + 0], src_red, tmp); + target[k * 4 + 1] = BLEND(src_alpha, target[k * 4 + 1], src_green, tmp); + target[k * 4 + 2] = BLEND(src_alpha, target[k * 4 + 2], src_blue, tmp); + target[k * 4 + 3] = CLIP8(src_alpha + MULDIV255(target[k * 4 + 3], (255 - src_alpha), tmp)); + } else { + /* paste unpremultiplied RGBA values */ + target[k * 4 + 0] = src_red; + target[k * 4 + 1] = src_green; + target[k * 4 + 2] = src_blue; + target[k * 4 + 3] = src_alpha; + } } } } else if (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) { if (color) { unsigned char *ink = (unsigned char *)&foreground_ink; for (k = x0; k < x1; k++) { - v = source[k] * convert_scale; - if (target[k * 4 + 3] < v) { - target[k * 4 + 0] = ink[0]; - target[k * 4 + 1] = ink[1]; - target[k * 4 + 2] = ink[2]; - target[k * 4 + 3] = v; + unsigned int src_alpha = source[k] * convert_scale; + if (src_alpha > 0) { + if (target[k * 4 + 3] > 0) { + target[k * 4 + 0] = BLEND(src_alpha, target[k * 4 + 0], ink[0], tmp); + target[k * 4 + 1] = BLEND(src_alpha, target[k * 4 + 1], ink[1], tmp); + target[k * 4 + 2] = BLEND(src_alpha, target[k * 4 + 2], ink[2], tmp); + target[k * 4 + 3] = CLIP8(src_alpha + MULDIV255(target[k * 4 + 3], (255 - src_alpha), tmp)); + } else { + target[k * 4 + 0] = ink[0]; + target[k * 4 + 1] = ink[1]; + target[k * 4 + 2] = ink[2]; + target[k * 4 + 3] = src_alpha; + } } } } else { for (k = x0; k < x1; k++) { - v = source[k] * convert_scale; - if (target[k] < v) { - target[k] = v; + unsigned int src_alpha = source[k] * convert_scale; + if (src_alpha > 0) { + target[k] = target[k] > 0 ? CLIP8(src_alpha + MULDIV255(target[k], (255 - src_alpha), tmp)) : src_alpha; } } } diff --git a/contrib/python/Pillow/py3/encode.c b/contrib/python/Pillow/py3/encode.c index 08544aedeb..c7dd510150 100644 --- a/contrib/python/Pillow/py3/encode.c +++ b/contrib/python/Pillow/py3/encode.c @@ -1042,9 +1042,12 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { Py_ssize_t progressive = 0; Py_ssize_t smooth = 0; Py_ssize_t optimize = 0; + int keep_rgb = 0; Py_ssize_t streamtype = 0; /* 0=interchange, 1=tables only, 2=image only */ Py_ssize_t xdpi = 0, ydpi = 0; Py_ssize_t subsampling = -1; /* -1=default, 0=none, 1=medium, 2=high */ + Py_ssize_t restart_marker_blocks = 0; + Py_ssize_t restart_marker_rows = 0; PyObject *qtables = NULL; unsigned int *qarrays = NULL; int qtablesLen = 0; @@ -1057,17 +1060,20 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { if (!PyArg_ParseTuple( args, - "ss|nnnnnnnnOz#y#y#", + "ss|nnnnpnnnnnnOz#y#y#", &mode, &rawmode, &quality, &progressive, &smooth, &optimize, + &keep_rgb, &streamtype, &xdpi, &ydpi, &subsampling, + &restart_marker_blocks, + &restart_marker_rows, &qtables, &comment, &comment_size, @@ -1146,6 +1152,7 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { strncpy(((JPEGENCODERSTATE *)encoder->state.context)->rawmode, rawmode, 8); + ((JPEGENCODERSTATE *)encoder->state.context)->keep_rgb = keep_rgb; ((JPEGENCODERSTATE *)encoder->state.context)->quality = quality; ((JPEGENCODERSTATE *)encoder->state.context)->qtables = qarrays; ((JPEGENCODERSTATE *)encoder->state.context)->qtablesLen = qtablesLen; @@ -1156,6 +1163,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { ((JPEGENCODERSTATE *)encoder->state.context)->streamtype = streamtype; ((JPEGENCODERSTATE *)encoder->state.context)->xdpi = xdpi; ((JPEGENCODERSTATE *)encoder->state.context)->ydpi = ydpi; + ((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_blocks = restart_marker_blocks; + ((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_rows = restart_marker_rows; ((JPEGENCODERSTATE *)encoder->state.context)->comment = comment; ((JPEGENCODERSTATE *)encoder->state.context)->comment_size = comment_size; ((JPEGENCODERSTATE *)encoder->state.context)->extra = extra; diff --git a/contrib/python/Pillow/py3/libImaging/Convert.c b/contrib/python/Pillow/py3/libImaging/Convert.c index b08519d304..99d2a4ada7 100644 --- a/contrib/python/Pillow/py3/libImaging/Convert.c +++ b/contrib/python/Pillow/py3/libImaging/Convert.c @@ -1013,7 +1013,7 @@ static struct { static void p2bit(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; - /* FIXME: precalculate greyscale palette? */ + /* FIXME: precalculate grayscale palette? */ for (x = 0; x < xsize; x++) { *out++ = (L(&palette->palette[in[x] * 4]) >= 128000) ? 255 : 0; } @@ -1022,7 +1022,7 @@ p2bit(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { static void pa2bit(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; - /* FIXME: precalculate greyscale palette? */ + /* FIXME: precalculate grayscale palette? */ for (x = 0; x < xsize; x++, in += 4) { *out++ = (L(&palette->palette[in[0] * 4]) >= 128000) ? 255 : 0; } @@ -1031,7 +1031,7 @@ pa2bit(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { static void p2l(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; - /* FIXME: precalculate greyscale palette? */ + /* FIXME: precalculate grayscale palette? */ for (x = 0; x < xsize; x++) { *out++ = L24(&palette->palette[in[x] * 4]) >> 16; } @@ -1040,7 +1040,7 @@ p2l(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { static void pa2l(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; - /* FIXME: precalculate greyscale palette? */ + /* FIXME: precalculate grayscale palette? */ for (x = 0; x < xsize; x++, in += 4) { *out++ = L24(&palette->palette[in[0] * 4]) >> 16; } @@ -1070,7 +1070,7 @@ p2pa(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { static void p2la(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; - /* FIXME: precalculate greyscale palette? */ + /* FIXME: precalculate grayscale palette? */ for (x = 0; x < xsize; x++, out += 4) { const UINT8 *rgba = &palette->palette[*in++ * 4]; out[0] = out[1] = out[2] = L24(rgba) >> 16; @@ -1081,7 +1081,7 @@ p2la(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { static void pa2la(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; - /* FIXME: precalculate greyscale palette? */ + /* FIXME: precalculate grayscale palette? */ for (x = 0; x < xsize; x++, in += 4, out += 4) { out[0] = out[1] = out[2] = L24(&palette->palette[in[0] * 4]) >> 16; out[3] = in[3]; @@ -1335,9 +1335,9 @@ topalette( imOut->palette = ImagingPaletteDuplicate(palette); if (imIn->bands == 1) { - /* greyscale image */ + /* grayscale image */ - /* Greyscale palette: copy data as is */ + /* Grayscale palette: copy data as is */ ImagingSectionEnter(&cookie); for (y = 0; y < imIn->ysize; y++) { if (alpha) { diff --git a/contrib/python/Pillow/py3/libImaging/Dib.c b/contrib/python/Pillow/py3/libImaging/Dib.c index f8a2901b8c..1b5bfe1324 100644 --- a/contrib/python/Pillow/py3/libImaging/Dib.c +++ b/contrib/python/Pillow/py3/libImaging/Dib.c @@ -142,9 +142,9 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) { GetSystemPaletteEntries(dib->dc, 0, 256, pal->palPalEntry); if (strcmp(mode, "L") == 0) { - /* Greyscale DIB. Fill all 236 slots with a greyscale ramp + /* Grayscale DIB. Fill all 236 slots with a grayscale ramp * (this is usually overkill on Windows since VGA only offers - * 6 bits greyscale resolution). Ignore the slots already + * 6 bits grayscale resolution). Ignore the slots already * allocated by Windows */ i = 10; @@ -160,7 +160,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) { #ifdef CUBE216 /* Colour DIB. Create a 6x6x6 colour cube (216 entries) and - * add 20 extra greylevels for best result with greyscale + * add 20 extra graylevels for best result with grayscale * images. */ i = 10; diff --git a/contrib/python/Pillow/py3/libImaging/Jpeg.h b/contrib/python/Pillow/py3/libImaging/Jpeg.h index 1d75508187..98eaac28dd 100644 --- a/contrib/python/Pillow/py3/libImaging/Jpeg.h +++ b/contrib/python/Pillow/py3/libImaging/Jpeg.h @@ -74,6 +74,9 @@ typedef struct { /* Optimize Huffman tables (slow) */ int optimize; + /* Disable automatic conversion of RGB images to YCbCr if nonzero */ + int keep_rgb; + /* Stream type (0=full, 1=tables only, 2=image only) */ int streamtype; @@ -83,6 +86,10 @@ typedef struct { /* Chroma Subsampling (-1=default, 0=none, 1=medium, 2=high) */ int subsampling; + /* Restart marker interval, in MCU blocks or MCU rows, or 0 for none */ + unsigned int restart_marker_blocks; + unsigned int restart_marker_rows; + /* Converter input mode (input to the shuffler) */ char rawmode[8 + 1]; diff --git a/contrib/python/Pillow/py3/libImaging/JpegEncode.c b/contrib/python/Pillow/py3/libImaging/JpegEncode.c index 2a24eff39c..00f3d5f74d 100644 --- a/contrib/python/Pillow/py3/libImaging/JpegEncode.c +++ b/contrib/python/Pillow/py3/libImaging/JpegEncode.c @@ -137,6 +137,30 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { /* Compressor configuration */ jpeg_set_defaults(&context->cinfo); + /* Prevent RGB -> YCbCr conversion */ + if (context->keep_rgb) { + switch (context->cinfo.in_color_space) { + case JCS_RGB: +#ifdef JCS_EXTENSIONS + case JCS_EXT_RGBX: +#endif + switch (context->subsampling) { + case -1: /* Default */ + case 0: /* No subsampling */ + break; + default: + /* Would subsample the green and blue + channels, which doesn't make sense */ + state->errcode = IMAGING_CODEC_CONFIG; + return -1; + } + jpeg_set_colorspace(&context->cinfo, JCS_RGB); + break; + default: + break; + } + } + /* Use custom quantization tables */ if (context->qtables) { int i; @@ -210,6 +234,8 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { } context->cinfo.smoothing_factor = context->smooth; context->cinfo.optimize_coding = (boolean)context->optimize; + context->cinfo.restart_interval = context->restart_marker_blocks; + context->cinfo.restart_in_rows = context->restart_marker_rows; if (context->xdpi > 0 && context->ydpi > 0) { context->cinfo.write_JFIF_header = TRUE; context->cinfo.density_unit = 1; /* dots per inch */ @@ -218,9 +244,9 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { } switch (context->streamtype) { case 1: - /* tables only -- not yet implemented */ - state->errcode = IMAGING_CODEC_CONFIG; - return -1; + /* tables only */ + jpeg_write_tables(&context->cinfo); + goto cleanup; case 2: /* image only */ jpeg_suppress_tables(&context->cinfo, TRUE); @@ -316,6 +342,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { } jpeg_finish_compress(&context->cinfo); +cleanup: /* Clean up */ if (context->comment) { free(context->comment); diff --git a/contrib/python/Pillow/py3/libImaging/Pack.c b/contrib/python/Pillow/py3/libImaging/Pack.c index 14c8f1461a..d47344245c 100644 --- a/contrib/python/Pillow/py3/libImaging/Pack.c +++ b/contrib/python/Pillow/py3/libImaging/Pack.c @@ -549,16 +549,16 @@ static struct { {"1", "1;IR", 1, pack1IR}, {"1", "L", 8, pack1L}, - /* greyscale */ + /* grayscale */ {"L", "L", 8, copy1}, {"L", "L;16", 16, packL16}, {"L", "L;16B", 16, packL16B}, - /* greyscale w. alpha */ + /* grayscale w. alpha */ {"LA", "LA", 16, packLA}, {"LA", "LA;L", 16, packLAL}, - /* greyscale w. alpha premultiplied */ + /* grayscale w. alpha premultiplied */ {"La", "La", 16, packLA}, /* palette */ diff --git a/contrib/python/Pillow/py3/libImaging/Palette.c b/contrib/python/Pillow/py3/libImaging/Palette.c index 059d7b72ac..78916bca52 100644 --- a/contrib/python/Pillow/py3/libImaging/Palette.c +++ b/contrib/python/Pillow/py3/libImaging/Palette.c @@ -75,7 +75,7 @@ ImagingPaletteNewBrowser(void) { } palette->size = i; - /* FIXME: add 30-level greyscale wedge here? */ + /* FIXME: add 30-level grayscale wedge here? */ return palette; } diff --git a/contrib/python/Pillow/py3/libImaging/Quant.c b/contrib/python/Pillow/py3/libImaging/Quant.c index c84acb9988..398fbf6db5 100644 --- a/contrib/python/Pillow/py3/libImaging/Quant.c +++ b/contrib/python/Pillow/py3/libImaging/Quant.c @@ -1697,7 +1697,7 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { image data? */ if (!strcmp(im->mode, "L")) { - /* greyscale */ + /* grayscale */ /* FIXME: converting a "L" image to "P" with 256 colors should be done by a simple copy... */ diff --git a/contrib/python/Pillow/py3/libImaging/Storage.c b/contrib/python/Pillow/py3/libImaging/Storage.c index 128595f654..b1b03c515a 100644 --- a/contrib/python/Pillow/py3/libImaging/Storage.c +++ b/contrib/python/Pillow/py3/libImaging/Storage.c @@ -80,18 +80,18 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { im->palette = ImagingPaletteNew("RGB"); } else if (strcmp(mode, "L") == 0) { - /* 8-bit greyscale (luminance) images */ + /* 8-bit grayscale (luminance) images */ im->bands = im->pixelsize = 1; im->linesize = xsize; } else if (strcmp(mode, "LA") == 0) { - /* 8-bit greyscale (luminance) with alpha */ + /* 8-bit grayscale (luminance) with alpha */ im->bands = 2; im->pixelsize = 4; /* store in image32 memory */ im->linesize = xsize * 4; } else if (strcmp(mode, "La") == 0) { - /* 8-bit greyscale (luminance) with premultiplied alpha */ + /* 8-bit grayscale (luminance) with premultiplied alpha */ im->bands = 2; im->pixelsize = 4; /* store in image32 memory */ im->linesize = xsize * 4; diff --git a/contrib/python/Pillow/py3/libImaging/TiffDecode.c b/contrib/python/Pillow/py3/libImaging/TiffDecode.c index 35122f1824..e3b81590ec 100644 --- a/contrib/python/Pillow/py3/libImaging/TiffDecode.c +++ b/contrib/python/Pillow/py3/libImaging/TiffDecode.c @@ -14,6 +14,10 @@ #ifdef HAVE_LIBTIFF +#ifdef HAVE_UNISTD_H +#include <unistd.h> /* lseek */ +#endif + #ifndef uint #define uint uint32 #endif diff --git a/contrib/python/Pillow/py3/libImaging/TiffDecode.h b/contrib/python/Pillow/py3/libImaging/TiffDecode.h index c7c7d48ed0..02454ba039 100644 --- a/contrib/python/Pillow/py3/libImaging/TiffDecode.h +++ b/contrib/python/Pillow/py3/libImaging/TiffDecode.h @@ -13,12 +13,6 @@ #include <tiff.h> #endif -/* UNDONE -- what are we using from this? */ -/*#ifndef _UNISTD_H - # include <unistd.h> - # endif -*/ - #ifndef min #define min(x, y) ((x > y) ? y : x) #define max(x, y) ((x < y) ? y : x) diff --git a/contrib/python/Pillow/py3/libImaging/Unpack.c b/contrib/python/Pillow/py3/libImaging/Unpack.c index 279bdcdc8a..6c7d52f58d 100644 --- a/contrib/python/Pillow/py3/libImaging/Unpack.c +++ b/contrib/python/Pillow/py3/libImaging/Unpack.c @@ -819,7 +819,7 @@ ImagingUnpackXBGR(UINT8 *_out, const UINT8 *in, int pixels) { static void unpackRGBALA(UINT8 *_out, const UINT8 *in, int pixels) { int i; - /* greyscale with alpha */ + /* grayscale with alpha */ for (i = 0; i < pixels; i++) { UINT32 iv = MAKE_UINT32(in[0], in[0], in[0], in[1]); memcpy(_out, &iv, sizeof(iv)); @@ -831,7 +831,7 @@ unpackRGBALA(UINT8 *_out, const UINT8 *in, int pixels) { static void unpackRGBALA16B(UINT8 *_out, const UINT8 *in, int pixels) { int i; - /* 16-bit greyscale with alpha, big-endian */ + /* 16-bit grayscale with alpha, big-endian */ for (i = 0; i < pixels; i++) { UINT32 iv = MAKE_UINT32(in[0], in[0], in[0], in[2]); memcpy(_out, &iv, sizeof(iv)); @@ -1108,7 +1108,7 @@ unpackCMYKI(UINT8 *_out, const UINT8 *in, int pixels) { /* There are two representations of LAB images for whatever precision: L: Uint (in PS, it's 0-100) A: Int (in ps, -128 .. 128, or elsewhere 0..255, with 128 as middle. - Channels in PS display a 0 value as middle grey, + Channels in PS display a 0 value as middle gray, LCMS appears to use 128 as the 0 value for these channels) B: Int (as above) @@ -1172,7 +1172,7 @@ unpackI16R_I16(UINT8 *out, const UINT8 *in, int pixels) { static void unpackI12_I16(UINT8 *out, const UINT8 *in, int pixels) { - /* Fillorder 1/MSB -> LittleEndian, for 12bit integer greyscale tiffs. + /* Fillorder 1/MSB -> LittleEndian, for 12bit integer grayscale tiffs. According to the TIFF spec: @@ -1527,7 +1527,7 @@ static struct { {"1", "1;IR", 1, unpack1IR}, {"1", "1;8", 8, unpack18}, - /* greyscale */ + /* grayscale */ {"L", "L;2", 2, unpackL2}, {"L", "L;2I", 2, unpackL2I}, {"L", "L;2R", 2, unpackL2R}, @@ -1544,11 +1544,11 @@ static struct { {"L", "L;16", 16, unpackL16}, {"L", "L;16B", 16, unpackL16B}, - /* greyscale w. alpha */ + /* grayscale w. alpha */ {"LA", "LA", 16, unpackLA}, {"LA", "LA;L", 16, unpackLAL}, - /* greyscale w. alpha premultiplied */ + /* grayscale w. alpha premultiplied */ {"La", "La", 16, unpackLA}, /* palette */ diff --git a/contrib/python/Pillow/py3/ya.make b/contrib/python/Pillow/py3/ya.make index 199e5efe62..e988fc2f9f 100644 --- a/contrib/python/Pillow/py3/ya.make +++ b/contrib/python/Pillow/py3/ya.make @@ -12,9 +12,9 @@ LICENSE( LICENSE_TEXTS(.yandex_meta/licenses.list.txt) -VERSION(10.1.0) +VERSION(10.2.0) -ORIGINAL_SOURCE(mirror://pypi/P/Pillow/Pillow-10.1.0.tar.gz) +ORIGINAL_SOURCE(mirror://pypi/p/pillow/pillow-10.2.0.tar.gz) PEERDIR( contrib/libs/freetype @@ -49,7 +49,7 @@ CFLAGS( -DHAVE_LIBZ -DHAVE_OPENJPEG -DHAVE_WEBPMUX - -DPILLOW_VERSION=\"10.1.0\" + -DPILLOW_VERSION=\"10.2.0\" ) IF (NOT OPENSOURCE) @@ -161,6 +161,7 @@ PY_SRCS( PIL/__main__.py PIL/_binary.py PIL/_deprecate.py + PIL/_typing.py PIL/_util.py PIL/_version.py PIL/BdfFontFile.py diff --git a/yt/yt/library/program/config.cpp b/yt/yt/library/program/config.cpp index 4b8a8c4abe..bff7c7ddc3 100644 --- a/yt/yt/library/program/config.cpp +++ b/yt/yt/library/program/config.cpp @@ -63,6 +63,8 @@ void TStockpileConfig::Register(TRegistrar registrar) void THeapProfilerConfig::Register(TRegistrar registrar) { + registrar.Parameter("sampling_rate", &TThis::SamplingRate) + .Default(); registrar.Parameter("snapshot_update_period", &TThis::SnapshotUpdatePeriod) .Default(TDuration::Seconds(5)); } diff --git a/yt/yt/library/program/config.h b/yt/yt/library/program/config.h index e96e5d191b..0f70fa8b88 100644 --- a/yt/yt/library/program/config.h +++ b/yt/yt/library/program/config.h @@ -116,6 +116,10 @@ class THeapProfilerConfig : public NYTree::TYsonStruct { public: + // Sampling rate for tcmalloc in bytes. + // See https://github.com/google/tcmalloc/blob/master/docs/sampling.md + std::optional<i64> SamplingRate; + // Period of update snapshot in heap profiler. std::optional<TDuration> SnapshotUpdatePeriod; |