diff options
| author | AlexSm <[email protected]> | 2024-01-26 16:00:50 +0100 | 
|---|---|---|
| committer | GitHub <[email protected]> | 2024-01-26 16:00:50 +0100 | 
| commit | 7ebcfd058d924bcc8c23da70e034f7415687885c (patch) | |
| tree | e4f00d163c77528c1855f2d7af54a8be83fc1ccb /contrib/python/Pillow | |
| parent | 64ca2dcd06312b9eef624054ceb5f787e11be79a (diff) | |
| parent | 6d79e7793c2c462134f4b4a7d911abc7b9b0766f (diff) | |
Merge pull request #1260 from ydb-platform/mergelibs10
mergelibs10
Diffstat (limited to 'contrib/python/Pillow')
111 files changed, 1587 insertions, 905 deletions
diff --git a/contrib/python/Pillow/py3/.dist-info/METADATA b/contrib/python/Pillow/py3/.dist-info/METADATA index bdad4e521be..78d0239337e 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: [email protected] +Author-email: "Jeffrey A. Clark (Alex)" <[email protected]>  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 d2f2bb46231..85036f6425b 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 cf65e86d734..0069eb5bcec 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 161954831ae..e3eda4fe98c 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 398696d5c77..b8f38b78a2e 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 9abfd0b5b8d..6f730cfef16 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 eef25aa14cf..60f3ec25b38 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 45e80b39af7..0035296a45c 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 94efff34156..5fb2b0193ca 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 cde9d42f09f..f7344df44a7 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 54f358c7f9a..eb4c8f557af 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 <[email protected]>  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 9b2fce0ac01..d2e60aa0759 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 2347c6d4c27..60a4d9774ae 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 e0e51aaac73..7dce2d60f76 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 8f641ece998..9769761fc15 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 5ec0a6632e3..3ec1ae819fc 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 a878cbfd200..75680a94e02 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 c2e4ead7174..d5513a56a11 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 ec6e9de6e7b..6722fa2b144 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 3599994a8e3..d84876eb6b6 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 92074b0d49e..57d87078bca 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 8e801be0b8a..2d8c78ea91a 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 d388928945a..a3109ebaa1b 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 c1c71da08c9..f8106800c42 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 c26b480acf2..65409e269fc 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 0aa4f7a8458..d877b4ecba6 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 0445a2ab22f..1b22f8645dc 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 b42ba7cac70..97d726a8a65 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 1adca9ad5b1..1bba9aad2c1 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 70120031797..29a5c995fd8 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 3a337f9f209..643fce830ba 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 befc1fd1d88..ad59b066779 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 fbf320d72a9..84665f54fff 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 7ce0224a67c..35ee5834e34 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 3b79d5c46a1..93a50d2a2b9 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 8e4f7dfb2c8..0923979af8b 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 57268b8f53d..035b83c4d77 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 c2956213519..8213d030a46 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 bcfffc3dc13..a4993d3d4b8 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 eb6bbe6c62e..b77f4bce567 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 a0b33514296..0b31f608174 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 6fccc315b3d..282e7d2a54e 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 <[email protected]> +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 42f2152b39a..a9e626b2b2a 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 f0c09470863..fbcfa309d29 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 3d3538c97b7..77e8a609a55 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 c4bb6334acf..2c185027630 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 8b1c3f8bb63..fad3e098003 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 b7ebddf066a..13864e59cfc 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 7881f0d262b..84c81f1844f 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 ca9b14c8adf..75910d2d9ab 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 d409fcd59de..7469c592dd2 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 316cd17c732..4096094348a 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 963d6c1a31c..4b778a0d33a 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 917bbf39fbb..81b8749a332 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 a678e248e9a..9ecfdb2599a 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 bb79e71de5d..9a85c0d15b0 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 801318930d5..f4529d9ae74 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 bfa88fe99c4..f4e598ca3a0 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 f9261c77d68..199a100904c 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 3f3609f1c20..77dac65b6b3 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 13b3048f67e..848fc2f716a 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 4a2c497fc49..dc31754020e 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 a88a907917d..65be7fef7b1 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 c7cbca8c5d7..a0515b302eb 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 8db5822fe7d..0d1968b140a 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 854d9e83ee7..98ecefd0514 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 09fc0c7e6ce..3506aadce83 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 dc1012f54d3..0144600066f 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 850272311de..af866feb362 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 5e5a8cf6a2d..e4ed9388011 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 e480ab05581..25dbfa5b0bc 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 2f019bb8c34..5cff564137d 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 99b46a4a66c..07bb712d83e 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 66344faac58..a7b9d4a9e3a 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 acb9ce5a38c..f9a10f6109c 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 408b982b515..86582fb128c 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 6a8d5d86b73..11ce3dfefd0 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 32928f6af30..7470663b4a1 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 f24ee4f5c31..65c7484f756 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 dabf8dbfb5f..e20d4d5ea81 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 30b05e4e1d4..88ff2f4fcd5 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 3d9f97f8485..c5bf3e04cf7 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 612fc09467a..59556206a3c 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 3e5fb01512d..b5b8c69b171 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 eda60c5c5ca..47ba1c54803 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 71cd57d74da..566acbfe5af 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 8491d3b7e92..bf73c9bef06 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 2bb8f6d7f10..3fcac8643cb 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 d249991d053..c49139f9edc 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 a74ee9eb6f3..0a07e8d0e12 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 2f2a3df13e3..33a0e07b3f4 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 00000000000..608b2b41fa8 --- /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 ba27b7e49e9..13f369cca1d 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 0936d1a7f35..1018b96b525 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 0936499fb8f..9db159cdf47 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 0229dbbc1cc..b3fd72a520e 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 2270c77fe7e..59f80a35415 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 069e7cd1478..19a2d6fb9b4 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 08544aedeb0..c7dd510150e 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 b08519d3045..99d2a4ada70 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 f8a2901b8c7..1b5bfe1324e 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 1d755081871..98eaac28dd6 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 2a24eff39ca..00f3d5f74db 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 14c8f1461aa..d47344245cc 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 059d7b72aca..78916bca52b 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 c84acb99889..398fbf6db57 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 128595f6547..b1b03c515ad 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 35122f18245..e3b81590ec2 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 c7c7d48ed02..02454ba0396 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 279bdcdc8ad..6c7d52f58d1 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 199e5efe626..e988fc2f9f5 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  | 
