Merge branch 'main' of ssh://github.com/python-pillow/Pillow into fix-alpha-for-overlapping-glyphs

This commit is contained in:
ZachNagengast 2023-11-07 06:34:31 -08:00
commit 11bea8fea6
35 changed files with 127 additions and 111 deletions

View File

@ -13,7 +13,7 @@ indent_style = space
trim_trailing_whitespace = true trim_trailing_whitespace = true
[*.yml] [*.{toml,yml}]
# Two-space indentation # Two-space indentation
indent_size = 2 indent_size = 2

View File

@ -1,3 +0,0 @@
[flake8]
extend-ignore = E203
max-line-length = 88

View File

@ -1,21 +1,16 @@
repos: repos:
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v3.13.0 rev: v0.1.4
hooks: hooks:
- id: pyupgrade - id: ruff
args: [--py38-plus] args: [--fix, --exit-non-zero-on-fix]
- repo: https://github.com/psf/black-pre-commit-mirror - repo: https://github.com/psf/black-pre-commit-mirror
rev: 23.9.1 rev: 23.10.1
hooks: hooks:
- id: black - id: black
args: [--target-version=py38] args: [--target-version=py38]
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
- repo: https://github.com/PyCQA/bandit - repo: https://github.com/PyCQA/bandit
rev: 1.7.5 rev: 1.7.5
hooks: hooks:
@ -23,32 +18,19 @@ repos:
args: [--severity-level=high] args: [--severity-level=high]
files: ^src/ files: ^src/
- repo: https://github.com/asottile/yesqa
rev: v1.5.0
hooks:
- id: yesqa
- repo: https://github.com/Lucas-C/pre-commit-hooks - repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.5.4 rev: v1.5.4
hooks: hooks:
- id: remove-tabs - id: remove-tabs
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$) exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
- repo: https://github.com/PyCQA/flake8
rev: 6.1.0
hooks:
- id: flake8
additional_dependencies:
[flake8-2020, flake8-errmsg, flake8-implicit-str-concat, flake8-logging]
- repo: https://github.com/pre-commit/pygrep-hooks - repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0 rev: v1.10.0
hooks: hooks:
- id: python-check-blanket-noqa
- id: rst-backticks - id: rst-backticks
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0 rev: v4.5.0
hooks: hooks:
- id: check-executables-have-shebangs - id: check-executables-have-shebangs
- id: check-merge-conflict - id: check-merge-conflict
@ -61,17 +43,17 @@ repos:
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/ exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
- repo: https://github.com/sphinx-contrib/sphinx-lint - repo: https://github.com/sphinx-contrib/sphinx-lint
rev: v0.6.8 rev: v0.8.1
hooks: hooks:
- id: sphinx-lint - id: sphinx-lint
- repo: https://github.com/tox-dev/pyproject-fmt - repo: https://github.com/tox-dev/pyproject-fmt
rev: 1.2.0 rev: 1.4.1
hooks: hooks:
- id: pyproject-fmt - id: pyproject-fmt
- repo: https://github.com/abravalheri/validate-pyproject - repo: https://github.com/abravalheri/validate-pyproject
rev: v0.14 rev: v0.15
hooks: hooks:
- id: validate-pyproject - id: validate-pyproject

View File

@ -5,6 +5,7 @@ include *.md
include *.py include *.py
include *.rst include *.rst
include *.sh include *.sh
include *.toml
include *.txt include *.txt
include *.yaml include *.yaml
include .flake8 include .flake8

View File

@ -49,7 +49,7 @@ help:
@echo " install make and install" @echo " install make and install"
@echo " install-coverage make and install with C coverage" @echo " install-coverage make and install with C coverage"
@echo " lint run the lint checks" @echo " lint run the lint checks"
@echo " lint-fix run Black and isort to (mostly) fix lint issues" @echo " lint-fix run Ruff to (mostly) fix lint issues"
@echo " release-test run code and package tests before release" @echo " release-test run code and package tests before release"
@echo " test run tests on installed Pillow" @echo " test run tests on installed Pillow"
@ -118,6 +118,6 @@ lint:
.PHONY: lint-fix .PHONY: lint-fix
lint-fix: lint-fix:
python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black
python3 -c "import isort" > /dev/null 2>&1 || python3 -m pip install isort
python3 -m black --target-version py38 . python3 -m black --target-version py38 .
python3 -m isort . python3 -c "import ruff" > /dev/null 2>&1 || python3 -m pip install ruff
python3 -m ruff --fix .

View File

@ -45,7 +45,7 @@ def test_direct():
assert caccess[(0, 0)] == access[(0, 0)] assert caccess[(0, 0)] == access[(0, 0)]
print("Size: %sx%s" % im.size) print("Size: %sx%s" % im.size) # noqa: UP031
timer(iterate_get, "PyAccess - get", im.size, access) timer(iterate_get, "PyAccess - get", im.size, access)
timer(iterate_set, "PyAccess - set", im.size, access) timer(iterate_set, "PyAccess - set", im.size, access)
timer(iterate_get, "C-api - get", im.size, caccess) timer(iterate_get, "C-api - get", im.size, caccess)

View File

@ -350,7 +350,7 @@ def test_apng_save(tmp_path):
im.load() im.load()
assert not im.is_animated assert not im.is_animated
assert im.n_frames == 1 assert im.n_frames == 1
assert im.get_format_mimetype() == "image/apng" assert im.get_format_mimetype() == "image/png"
assert im.info.get("default_image") is None assert im.info.get("default_image") is None
assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255)
@ -450,26 +450,29 @@ def test_apng_save_duration_loop(tmp_path):
test_file, save_all=True, append_images=[frame, frame], duration=[500, 100, 150] test_file, save_all=True, append_images=[frame, frame], duration=[500, 100, 150]
) )
with Image.open(test_file) as im: with Image.open(test_file) as im:
im.load()
assert im.n_frames == 1 assert im.n_frames == 1
assert im.info.get("duration") == 750 assert "duration" not in im.info
different_frame = Image.new("RGBA", (128, 64))
frame.save(
test_file,
save_all=True,
append_images=[frame, different_frame],
duration=[500, 100, 150],
)
with Image.open(test_file) as im:
assert im.n_frames == 2
assert im.info["duration"] == 600
im.seek(1)
assert im.info["duration"] == 150
# test info duration # test info duration
frame.info["duration"] = 750 frame.info["duration"] = 300
frame.save(test_file, save_all=True) frame.save(test_file, save_all=True, append_images=[frame, different_frame])
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert im.info.get("duration") == 750 assert im.n_frames == 2
assert im.info["duration"] == 600
def test_apng_save_duplicate_duration(tmp_path):
test_file = str(tmp_path / "temp.png")
frame = Image.new("RGB", (1, 1))
# Test a single duration is correctly combined across duplicate frames
frame.save(test_file, save_all=True, append_images=[frame, frame], duration=500)
with Image.open(test_file) as im:
assert im.n_frames == 1
assert im.info.get("duration") == 1500
def test_apng_save_disposal(tmp_path): def test_apng_save_disposal(tmp_path):
@ -674,7 +677,8 @@ def test_seek_after_close():
@pytest.mark.parametrize("mode", ("RGBA", "RGB", "P")) @pytest.mark.parametrize("mode", ("RGBA", "RGB", "P"))
@pytest.mark.parametrize("default_image", (True, False)) @pytest.mark.parametrize("default_image", (True, False))
def test_different_modes_in_later_frames(mode, default_image, tmp_path): @pytest.mark.parametrize("duplicate", (True, False))
def test_different_modes_in_later_frames(mode, default_image, duplicate, tmp_path):
test_file = str(tmp_path / "temp.png") test_file = str(tmp_path / "temp.png")
im = Image.new("L", (1, 1)) im = Image.new("L", (1, 1))
@ -682,7 +686,7 @@ def test_different_modes_in_later_frames(mode, default_image, tmp_path):
test_file, test_file,
save_all=True, save_all=True,
default_image=default_image, default_image=default_image,
append_images=[Image.new(mode, (1, 1))], append_images=[im.convert(mode) if duplicate else Image.new(mode, (1, 1), 1)],
) )
with Image.open(test_file) as reloaded: with Image.open(test_file) as reloaded:
assert reloaded.mode == mode assert reloaded.mode == mode

View File

@ -1,6 +1,6 @@
import sys import sys
from setuptools.build_meta import * # noqa: F401, F403 from setuptools.build_meta import * # noqa: F403
from setuptools.build_meta import build_wheel from setuptools.build_meta import build_wheel
backend_class = build_wheel.__self__.__class__ backend_class = build_wheel.__self__.__class__

View File

@ -318,14 +318,14 @@ def setup(app):
linkcheck_allowed_redirects = { linkcheck_allowed_redirects = {
r"https://www.bestpractices.dev/projects/6331": r"https://www.bestpractices.dev/en/.*", # noqa: E501 r"https://www.bestpractices.dev/projects/6331": r"https://www.bestpractices.dev/en/.*",
r"https://badges.gitter.im/python-pillow/Pillow.svg": r"https://badges.gitter.im/repo.svg", # noqa: E501 r"https://badges.gitter.im/python-pillow/Pillow.svg": r"https://badges.gitter.im/repo.svg",
r"https://gitter.im/python-pillow/Pillow?.*": r"https://app.gitter.im/#/room/#python-pillow_Pillow:gitter.im?.*", # noqa: E501 r"https://gitter.im/python-pillow/Pillow?.*": r"https://app.gitter.im/#/room/#python-pillow_Pillow:gitter.im?.*",
r"https://pillow.readthedocs.io/?badge=latest": r"https://pillow.readthedocs.io/en/stable/?badge=latest", # noqa: E501 r"https://pillow.readthedocs.io/?badge=latest": r"https://pillow.readthedocs.io/en/stable/?badge=latest",
r"https://pillow.readthedocs.io": r"https://pillow.readthedocs.io/en/stable/", r"https://pillow.readthedocs.io": r"https://pillow.readthedocs.io/en/stable/",
r"https://tidelift.com/badges/package/pypi/Pillow?.*": r"https://img.shields.io/badge/.*", # noqa: E501 r"https://tidelift.com/badges/package/pypi/Pillow?.*": r"https://img.shields.io/badge/.*",
r"https://zenodo.org/badge/17549/python-pillow/Pillow.svg": r"https://zenodo.org/badge/doi/[\.0-9]+/zenodo.[0-9]+.svg", # noqa: E501 r"https://zenodo.org/badge/17549/python-pillow/Pillow.svg": r"https://zenodo.org/badge/doi/[\.0-9]+/zenodo.[0-9]+.svg",
r"https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow": r"https://zenodo.org/record/[0-9]+", # noqa: E501 r"https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow": r"https://zenodo.org/record/[0-9]+",
} }
# sphinx.ext.extlinks # sphinx.ext.extlinks

View File

@ -10,7 +10,7 @@ Deprecated features
------------------- -------------------
Below are features which are considered deprecated. Where appropriate, Below are features which are considered deprecated. Where appropriate,
a ``DeprecationWarning`` is issued. a :py:exc:`DeprecationWarning` is issued.
PSFile PSFile
~~~~~~ ~~~~~~
@ -267,7 +267,7 @@ ImageFile.raise_ioerror
.. deprecated:: 7.2.0 .. deprecated:: 7.2.0
.. versionremoved:: 9.0.0 .. versionremoved:: 9.0.0
``IOError`` was merged into ``OSError`` in Python 3.3. :py:exc:`IOError` was merged into :py:exc:`OSError` in Python 3.3.
So, ``ImageFile.raise_ioerror`` has been removed. So, ``ImageFile.raise_ioerror`` has been removed.
Use ``ImageFile.raise_oserror`` instead. Use ``ImageFile.raise_oserror`` instead.
@ -293,9 +293,9 @@ im.offset
``im.offset()`` has been removed, call :py:func:`.ImageChops.offset()` instead. ``im.offset()`` has been removed, call :py:func:`.ImageChops.offset()` instead.
It was documented as deprecated in PIL 1.1.2, It was documented as deprecated in PIL 1.1.2,
raised a ``DeprecationWarning`` since 1.1.5, raised a :py:exc:`DeprecationWarning` since 1.1.5,
an ``Exception`` since Pillow 3.0.0 an :py:exc:`Exception` since Pillow 3.0.0
and ``NotImplementedError`` since 3.3.0. and :py:exc:`NotImplementedError` since 3.3.0.
Image.fromstring, im.fromstring and im.tostring Image.fromstring, im.fromstring and im.tostring
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -307,9 +307,9 @@ Image.fromstring, im.fromstring and im.tostring
* ``im.fromstring()`` has been removed, call :py:meth:`~PIL.Image.Image.frombytes()` instead. * ``im.fromstring()`` has been removed, call :py:meth:`~PIL.Image.Image.frombytes()` instead.
* ``im.tostring()`` has been removed, call :py:meth:`~PIL.Image.Image.tobytes()` instead. * ``im.tostring()`` has been removed, call :py:meth:`~PIL.Image.Image.tobytes()` instead.
They issued a ``DeprecationWarning`` since 2.0.0, They issued a :py:exc:`DeprecationWarning` since 2.0.0,
an ``Exception`` since 3.0.0 an :py:exc:`Exception` since 3.0.0
and ``NotImplementedError`` since 3.3.0. and :py:exc:`NotImplementedError` since 3.3.0.
ImageCms.CmsProfile attributes ImageCms.CmsProfile attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -318,7 +318,7 @@ ImageCms.CmsProfile attributes
.. versionremoved:: 8.0.0 .. versionremoved:: 8.0.0
Some attributes in :py:class:`PIL.ImageCms.CmsProfile` have been removed. From 6.0.0, Some attributes in :py:class:`PIL.ImageCms.CmsProfile` have been removed. From 6.0.0,
they issued a ``DeprecationWarning``: they issued a :py:exc:`DeprecationWarning`:
======================== =================================================== ======================== ===================================================
Removed Use instead Removed Use instead
@ -442,7 +442,7 @@ PIL.OleFileIO
.. deprecated:: 4.0.0 .. deprecated:: 4.0.0
.. versionremoved:: 6.0.0 .. versionremoved:: 6.0.0
PIL.OleFileIO was removed as a vendored file in Pillow 4.0.0 (2017-01) in favour of ``PIL.OleFileIO`` was removed as a vendored file in Pillow 4.0.0 (2017-01) in favour of
the upstream :pypi:`olefile` Python package, and replaced with an ``ImportError`` in 5.0.0 the upstream :pypi:`olefile` Python package, and replaced with an :py:exc:`ImportError` in 5.0.0
(2018-01). The deprecated file has now been removed from Pillow. If needed, install from (2018-01). The deprecated file has now been removed from Pillow. If needed, install from
PyPI (eg. ``python3 -m pip install olefile``). PyPI (eg. ``python3 -m pip install olefile``).

View File

@ -20,7 +20,7 @@ the imToolkit package.
.. warning:: .. warning::
To protect against potential DOS attacks when using arbitrary strings as To protect against potential DOS attacks when using arbitrary strings as
text input, Pillow will raise a ``ValueError`` if the number of characters text input, Pillow will raise a :py:exc:`ValueError` if the number of characters
is over a certain limit, :py:data:`MAX_STRING_LENGTH`. is over a certain limit, :py:data:`MAX_STRING_LENGTH`.
This threshold can be changed by setting This threshold can be changed by setting
@ -89,5 +89,5 @@ Constants
.. data:: MAX_STRING_LENGTH .. data:: MAX_STRING_LENGTH
Set to 1,000,000, to protect against potential DOS attacks. Pillow will Set to 1,000,000, to protect against potential DOS attacks. Pillow will
raise a ``ValueError`` if the number of characters is over this limit. The raise a :py:exc:`ValueError` if the number of characters is over this limit. The
check can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``. check can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``.

View File

@ -8,7 +8,7 @@ Setting image mode
^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
If you attempt to set the mode of an image directly, e.g. If you attempt to set the mode of an image directly, e.g.
``im.mode = "RGBA"``, you will now receive an ``AttributeError``. This is ``im.mode = "RGBA"``, you will now receive an :py:exc:`AttributeError`. This is
not about removing existing functionality, but instead about raising an not about removing existing functionality, but instead about raising an
explicit error to prevent later consequences. The ``convert`` method is the explicit error to prevent later consequences. The ``convert`` method is the
correct way to change an image's mode. correct way to change an image's mode.

View File

@ -10,7 +10,7 @@ operations. As a result PIL was unable to open them as images, requiring a wrap
``cStringIO`` or ``BytesIO``. ``cStringIO`` or ``BytesIO``.
Now new functionality has been added to ``Image.open()`` by way of an ``.seek(0)`` check and Now new functionality has been added to ``Image.open()`` by way of an ``.seek(0)`` check and
catch on exception ``AttributeError`` or ``io.UnsupportedOperation``. If this is caught we catch on exception :py:exc:`AttributeError` or :py:exc:`io.UnsupportedOperation`. If this is caught we
attempt to wrap the object using ``io.BytesIO`` (which will only work on buffer-file-like attempt to wrap the object using ``io.BytesIO`` (which will only work on buffer-file-like
objects). objects).

View File

@ -19,7 +19,7 @@ Deprecation Warning when Saving JPEGs
JPEG images cannot contain an alpha channel. Pillow prior to 3.4.0 JPEG images cannot contain an alpha channel. Pillow prior to 3.4.0
silently drops the alpha channel. With this release Pillow will now silently drops the alpha channel. With this release Pillow will now
issue a ``DeprecationWarning`` when attempting to save a ``RGBA`` mode issue a :py:exc:`DeprecationWarning` when attempting to save a ``RGBA`` mode
image as a JPEG. This will become an error in Pillow 4.2. image as a JPEG. This will become an error in Pillow 4.2.
New DDS Decoders New DDS Decoders

View File

@ -8,7 +8,7 @@ Image size
^^^^^^^^^^ ^^^^^^^^^^
If you attempt to set the size of an image directly, e.g. If you attempt to set the size of an image directly, e.g.
``im.size = (100, 100)``, you will now receive an ``AttributeError``. This is ``im.size = (100, 100)``, you will now receive an :py:exc:`AttributeError`. This is
not about removing existing functionality, but instead about raising an not about removing existing functionality, but instead about raising an
explicit error to prevent later consequences. The ``resize`` method is the explicit error to prevent later consequences. The ``resize`` method is the
correct way to change an image's size. correct way to change an image's size.
@ -16,7 +16,8 @@ correct way to change an image's size.
The exceptions to this are: The exceptions to this are:
* The ICO and ICNS image formats, which use ``im.size = (100, 100)`` to select a subimage. * The ICO and ICNS image formats, which use ``im.size = (100, 100)`` to select a subimage.
* The TIFF image format, which now has a ``DeprecationWarning`` for this action, as direct image size setting was previously necessary to work around an issue with tile extents. * The TIFF image format, which now has a :py:exc:`DeprecationWarning` for this action,
as direct image size setting was previously necessary to work around an issue with tile extents.
API Additions API Additions

View File

@ -15,7 +15,7 @@ PNG: Handle IDAT chunks after image end
Some PNG images have multiple IDAT chunks. In some cases, Pillow will stop Some PNG images have multiple IDAT chunks. In some cases, Pillow will stop
reading image data before the IDAT chunks finish. A regression caused an reading image data before the IDAT chunks finish. A regression caused an
``EOFError`` exception when previously there was none. This is now fixed, and :py:exc:`EOFError` exception when previously there was none. This is now fixed, and
file reading continues in case there are subsequent text chunks. file reading continues in case there are subsequent text chunks.
PNG: MIME type PNG: MIME type
@ -30,7 +30,7 @@ File closing
^^^^^^^^^^^^ ^^^^^^^^^^^^
A regression caused an unsupported image file to report a A regression caused an unsupported image file to report a
``ValueError: seek of closed file`` exception instead of an ``OSError``. This ``ValueError: seek of closed file`` exception instead of an :py:exc:`OSError`. This
has been fixed by ensuring that image plugins only close their internal ``__fp`` has been fixed by ensuring that image plugins only close their internal ``__fp``
if they are not the same as ``ImageFile``'s ``fp``, allowing each to manage their own if they are not the same as ``ImageFile``'s ``fp``, allowing each to manage their own
file pointers. file pointers.

View File

@ -103,7 +103,7 @@ ImageCms.CmsProfile attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Some attributes in ``ImageCms.CmsProfile`` have been deprecated since Pillow 3.2.0. From Some attributes in ``ImageCms.CmsProfile`` have been deprecated since Pillow 3.2.0. From
6.0.0, they issue a ``DeprecationWarning``: 6.0.0, they issue a :py:exc:`DeprecationWarning`:
======================== =============================== ======================== ===============================
Deprecated Use instead Deprecated Use instead

View File

@ -58,7 +58,7 @@ file. ``ImageFont.FreeTypeFont`` has four new methods,
:py:meth:`PIL.ImageFont.FreeTypeFont.set_variation_by_name` for using named styles, and :py:meth:`PIL.ImageFont.FreeTypeFont.set_variation_by_name` for using named styles, and
:py:meth:`PIL.ImageFont.FreeTypeFont.get_variation_axes` and :py:meth:`PIL.ImageFont.FreeTypeFont.get_variation_axes` and
:py:meth:`PIL.ImageFont.FreeTypeFont.set_variation_by_axes` for using font axes :py:meth:`PIL.ImageFont.FreeTypeFont.set_variation_by_axes` for using font axes
instead. An ``IOError`` will be raised if the font is not a variation font. FreeType instead. An :py:exc:`IOError` will be raised if the font is not a variation font. FreeType
2.9.1 or greater is required. 2.9.1 or greater is required.
Other Changes Other Changes

View File

@ -85,7 +85,7 @@ Custom unidentified image error
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Pillow will now throw a custom ``UnidentifiedImageError`` when an image cannot be Pillow will now throw a custom ``UnidentifiedImageError`` when an image cannot be
identified. For backwards compatibility, this will inherit from ``OSError``. identified. For backwards compatibility, this will inherit from :py:exc:`OSError`.
New argument ``reducing_gap`` for Image.resize() and Image.thumbnail() methods New argument ``reducing_gap`` for Image.resize() and Image.thumbnail() methods
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -7,7 +7,7 @@ Fix another regression seeking PNG files
This fixes a regression introduced in 7.1.0 when adding support for APNG files. This fixes a regression introduced in 7.1.0 when adding support for APNG files.
When calling ``seek(n)`` on a regular PNG where ``n > 0``, it failed to raise an When calling ``seek(n)`` on a regular PNG where ``n > 0``, it failed to raise an
``EOFError`` as it should have done, resulting in: :py:exc:`EOFError` as it should have done, resulting in:
.. code-block:: pycon .. code-block:: pycon

View File

@ -53,6 +53,6 @@ a custom :py:class:`~PIL.ImageShow.Viewer` class.
ImageFile.raise_ioerror ImageFile.raise_ioerror
~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~
``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror`` :py:exc:`IOError` was merged into :py:exc:`OSError` in Python 3.3. So, ``ImageFile.raise_ioerror``
is now deprecated and will be removed in a future release. Use is now deprecated and will be removed in a future release. Use
``ImageFile.raise_oserror`` instead. ``ImageFile.raise_oserror`` instead.

View File

@ -168,7 +168,7 @@ offset.
Error for large BMP files Error for large BMP files
^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^
Previously, if a BMP file was too large, an ``OSError`` would be raised. Now, Previously, if a BMP file was too large, an :py:exc:`OSError` would be raised. Now,
``DecompressionBombError`` is used instead, as Pillow already uses for other formats. ``DecompressionBombError`` is used instead, as Pillow already uses for other formats.
Dark theme for docs Dark theme for docs

View File

@ -22,9 +22,10 @@ Catch OSError when checking if destination is sys.stdout
======================================================== ========================================================
In 8.3.0, a check to see if the destination was ``sys.stdout`` when saving an image was In 8.3.0, a check to see if the destination was ``sys.stdout`` when saving an image was
updated. This lead to an OSError being raised if the environment restricted access. updated. This lead to an :py:exc:`OSError` being raised if the environment restricted
access.
The OSError is now silently caught. The :py:exc:`OSError` is now silently caught.
Fixed removing orientation in ImageOps.exif_transpose Fixed removing orientation in ImageOps.exif_transpose
===================================================== =====================================================
@ -34,7 +35,7 @@ original image EXIF data was not modified, and the orientation was only removed
the modified copy. the modified copy.
However, for certain images the orientation was already missing from the modified However, for certain images the orientation was already missing from the modified
image, leading to a KeyError. image, leading to a :py:exc:`KeyError`.
This error has been resolved, and the copying of metadata to the modified image This error has been resolved, and the copying of metadata to the modified image
improved. improved.

View File

@ -63,7 +63,7 @@ a custom :py:class:`~PIL.ImageShow.Viewer` class.
ImageFile.raise_ioerror ImageFile.raise_ioerror
^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror`` :py:exc:`IOError` was merged into :py:exc:`OSError` in Python 3.3. So, ``ImageFile.raise_ioerror``
has been removed. Use ``ImageFile.raise_oserror`` instead. has been removed. Use ``ImageFile.raise_oserror`` instead.

View File

@ -8,14 +8,14 @@ Raise an error when performing a negative crop
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Performing a negative crop on an image previously just returned a ``(0, 0)`` image. Now Performing a negative crop on an image previously just returned a ``(0, 0)`` image. Now
it will raise a ``ValueError``, to help reduce confusion if a user has unintentionally it will raise a :py:exc:`ValueError`, to help reduce confusion if a user has unintentionally
provided the wrong arguments. provided the wrong arguments.
Added specific error if path coordinate type is incorrect Added specific error if path coordinate type is incorrect
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Rather than returning a ``SystemError``, passing the incorrect types of coordinates into Rather than returning a :py:exc:`SystemError`, passing the incorrect types of coordinates into
a path will now raise a more specific ``ValueError``, with the message "incorrect a path will now raise a more specific :py:exc:`ValueError`, with the message "incorrect
coordinate type". coordinate type".
Replace requirements.txt with extras Replace requirements.txt with extras

View File

@ -77,8 +77,34 @@ package-dir = {"" = "src"}
[tool.setuptools.dynamic] [tool.setuptools.dynamic]
version = {attr = "PIL.__version__"} version = {attr = "PIL.__version__"}
[tool.isort] [tool.ruff]
profile = "black" target-version = "py38"
line-length = 88
select = [
"E", # pycodestyle errors
"EM", # flake8-errmsg
"F", # pyflakes errors
"I", # isort
"ISC", # flake8-implicit-str-concat
"PGH", # pygrep-hooks
"RUF100", # unused noqa (yesqa)
"UP", # pyupgrade
"W", # pycodestyle warnings
"YTT", # flake8-2020
# "LOG", # TODO: enable flake8-logging when it's not in preview anymore
]
extend-ignore = [
"E203", # Whitespace before ':'
"E221", # Multiple spaces before operator
"E226", # Missing whitespace around arithmetic operator
"E241", # Multiple spaces after ','
]
[tool.ruff.per-file-ignores]
"Tests/*.py" = ["I001"]
[tool.ruff.isort]
known-first-party = ["PIL"]
[tool.pytest.ini_options] [tool.pytest.ini_options]
addopts = "-ra --color=yes" addopts = "-ra --color=yes"

View File

@ -122,7 +122,7 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False):
gs_binary, gs_binary,
"-q", # quiet mode "-q", # quiet mode
"-g%dx%d" % size, # set output geometry (pixels) "-g%dx%d" % size, # set output geometry (pixels)
"-r%fx%f" % res, # set input DPI (dots per inch) "-r%fx%f" % res, # set input DPI (dots per inch) # noqa: UP031
"-dBATCH", # exit after processing "-dBATCH", # exit after processing
"-dNOPAUSE", # don't pause between pages "-dNOPAUSE", # don't pause between pages
"-dSAFER", # safe mode "-dSAFER", # safe mode

View File

@ -392,7 +392,7 @@ if __name__ == "__main__":
imf = IcnsImageFile(fp) imf = IcnsImageFile(fp)
for size in imf.info["sizes"]: for size in imf.info["sizes"]:
imf.size = size imf.size = size
imf.save("out-%s-%s-%s.png" % size) imf.save("out-%s-%s-%s.png" % size) # noqa: UP031
with Image.open(sys.argv[1]) as im: with Image.open(sys.argv[1]) as im:
im.save("out.png") im.save("out.png")
if sys.platform == "windows": if sys.platform == "windows":

View File

@ -3100,7 +3100,7 @@ def fromarray(obj, mode=None):
try: try:
mode, rawmode = _fromarray_typemap[typekey] mode, rawmode = _fromarray_typemap[typekey]
except KeyError as e: except KeyError as e:
msg = "Cannot handle this data type: %s, %s" % typekey msg = "Cannot handle this data type: %s, %s" % typekey # noqa: UP031
raise TypeError(msg) from e raise TypeError(msg) from e
else: else:
rawmode = mode rawmode = mode

View File

@ -222,7 +222,7 @@ class UnsharpMask(MultibandFilter):
.. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking .. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking
""" # noqa: E501 """
name = "UnsharpMask" name = "UnsharpMask"

View File

@ -18,10 +18,9 @@ import os
import tempfile import tempfile
from . import Image, ImageFile from . import Image, ImageFile
from ._binary import i8 from ._binary import i8, o8
from ._binary import i16be as i16 from ._binary import i16be as i16
from ._binary import i32be as i32 from ._binary import i32be as i32
from ._binary import o8
COMPRESSION = {1: "raw", 5: "jpeg"} COMPRESSION = {1: "raw", 5: "jpeg"}

View File

@ -82,7 +82,7 @@ class IndirectReference(
collections.namedtuple("IndirectReferenceTuple", ["object_id", "generation"]) collections.namedtuple("IndirectReferenceTuple", ["object_id", "generation"])
): ):
def __str__(self): def __str__(self):
return "%s %s R" % self return "%s %s R" % self # noqa: UP031
def __bytes__(self): def __bytes__(self):
return self.__str__().encode("us-ascii") return self.__str__().encode("us-ascii")
@ -103,7 +103,7 @@ class IndirectReference(
class IndirectObjectDef(IndirectReference): class IndirectObjectDef(IndirectReference):
def __str__(self): def __str__(self):
return "%s %s obj" % self return "%s %s obj" % self # noqa: UP031
class XrefTable: class XrefTable:

View File

@ -1156,6 +1156,9 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
encoderinfo["duration"] = duration encoderinfo["duration"] = duration
im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo}) 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 # animation control
chunk( chunk(
fp, fp,
@ -1391,8 +1394,10 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
chunk(fp, b"eXIf", exif) chunk(fp, b"eXIf", exif)
if save_all: if save_all:
_write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) im = _write_multiple_frames(
else: im, fp, chunk, rawmode, default_image, append_images
)
if im:
ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)]) ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
if info: if info:

View File

@ -244,7 +244,7 @@ class _PyAccessI16_L(PyAccess):
except TypeError: except TypeError:
color = min(color[0], 65535) color = min(color[0], 65535)
pixel.l = color & 0xFF # noqa: E741 pixel.l = color & 0xFF
pixel.r = color >> 8 pixel.r = color >> 8
@ -265,7 +265,7 @@ class _PyAccessI16_B(PyAccess):
except Exception: except Exception:
color = min(color[0], 65535) color = min(color[0], 65535)
pixel.l = color >> 8 # noqa: E741 pixel.l = color >> 8
pixel.r = color & 0xFF pixel.r = color & 0xFF

View File

@ -239,7 +239,7 @@ DEPS = {
"libs": ["*.lib"], "libs": ["*.lib"],
}, },
"freetype": { "freetype": {
"url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.13.2.tar.gz", # noqa: E501 "url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.13.2.tar.gz",
"filename": "freetype-2.13.2.tar.gz", "filename": "freetype-2.13.2.tar.gz",
"dir": "freetype-2.13.2", "dir": "freetype-2.13.2",
"license": ["LICENSE.TXT", r"docs\FTL.TXT", r"docs\GPLv2.TXT"], "license": ["LICENSE.TXT", r"docs\FTL.TXT", r"docs\GPLv2.TXT"],
@ -321,7 +321,7 @@ DEPS = {
}, },
"libimagequant": { "libimagequant": {
# commit: Merge branch 'master' into msvc (matches 2.17.0 tag) # commit: Merge branch 'master' into msvc (matches 2.17.0 tag)
"url": "https://github.com/ImageOptim/libimagequant/archive/e4c1334be0eff290af5e2b4155057c2953a313ab.zip", # noqa: E501 "url": "https://github.com/ImageOptim/libimagequant/archive/e4c1334be0eff290af5e2b4155057c2953a313ab.zip",
"filename": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab.zip", "filename": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab.zip",
"dir": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab", "dir": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab",
"license": "COPYRIGHT", "license": "COPYRIGHT",