Merge branch 'python-pillow:main' into eps_plugin_perf

This commit is contained in:
Yay295 2023-03-02 15:29:12 -06:00 committed by GitHub
commit 281cbc2755
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
83 changed files with 545 additions and 430 deletions

View File

@ -21,9 +21,11 @@ environment:
install: install:
- '%PYTHON%\%EXECUTABLE% --version' - '%PYTHON%\%EXECUTABLE% --version'
- curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/main.zip - curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/main.zip
- curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
- 7z x pillow-depends.zip -oc:\ - 7z x pillow-depends.zip -oc:\
- 7z x pillow-test-images.zip -oc:\
- mv c:\pillow-depends-main c:\pillow-depends - mv c:\pillow-depends-main c:\pillow-depends
- xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images - xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images
- 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\ - 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\
- ..\pillow-depends\gs1000w32.exe /S - ..\pillow-depends\gs1000w32.exe /S
- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs10.0.0\bin;%PATH% - path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs10.0.0\bin;%PATH%

View File

@ -37,7 +37,8 @@ python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma python3 -m pip install pyroma
if [[ $(uname) != CYGWIN* ]]; then if [[ $(uname) != CYGWIN* ]]; then
python3 -m pip install numpy # TODO Remove condition when NumPy supports 3.12
if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi
# PyQt6 doesn't support PyPy3 # PyQt6 doesn't support PyPy3
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then if [[ $GHA_PYTHON_VERSION == 3.* ]]; then

View File

@ -13,7 +13,8 @@ python3 -m pip install -U pytest-cov
python3 -m pip install -U pytest-timeout python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma python3 -m pip install pyroma
python3 -m pip install numpy # TODO Remove condition when NumPy supports 3.12
if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi
# extra test images # extra test images
pushd depends && ./install_extra_test_images.sh && popd pushd depends && ./install_extra_test_images.sh && popd

View File

@ -59,7 +59,7 @@ jobs:
python3${{ matrix.python-minor-version }}-sip python3${{ matrix.python-minor-version }}-sip
python3${{ matrix.python-minor-version }}-tkinter python3${{ matrix.python-minor-version }}-tkinter
qt5-devel-tools qt5-devel-tools
subversion wget
xorg-server-extra xorg-server-extra
zlib-devel zlib-devel

View File

@ -87,6 +87,7 @@ jobs:
with: with:
flags: GHA_Docker flags: GHA_Docker
name: ${{ matrix.docker }} name: ${{ matrix.docker }}
gcov: true
success: success:
permissions: permissions:

View File

@ -59,8 +59,7 @@ jobs:
${{ matrix.package }}-python3-numpy \ ${{ matrix.package }}-python3-numpy \
${{ matrix.package }}-python3-olefile \ ${{ matrix.package }}-python3-olefile \
${{ matrix.package }}-python3-pip \ ${{ matrix.package }}-python3-pip \
${{ matrix.package }}-python3-setuptools \ ${{ matrix.package }}-python3-setuptools
subversion
if [ ${{ matrix.package }} == "mingw-w64-x86_64" ]; then if [ ${{ matrix.package }} == "mingw-w64-x86_64" ]; then
pacman -S --noconfirm \ pacman -S --noconfirm \

View File

@ -15,7 +15,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12-dev"]
architecture: ["x86", "x64"] architecture: ["x86", "x64"]
include: include:
# PyPy 7.3.4+ only ships 64-bit binaries for Windows # PyPy 7.3.4+ only ships 64-bit binaries for Windows
@ -38,6 +38,12 @@ jobs:
repository: python-pillow/pillow-depends repository: python-pillow/pillow-depends
path: winbuild\depends path: winbuild\depends
- name: Checkout extra test images
uses: actions/checkout@v3
with:
repository: python-pillow/test-images
path: Tests\test-images
# sets env: pythonLocation # sets env: pythonLocation
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
@ -62,7 +68,8 @@ jobs:
winbuild\depends\gs1000w32.exe /S winbuild\depends\gs1000w32.exe /S
echo "C:\Program Files (x86)\gs\gs10.0.0\bin" >> $env:GITHUB_PATH echo "C:\Program Files (x86)\gs\gs10.0.0\bin" >> $env:GITHUB_PATH
xcopy /S /Y winbuild\depends\test_images\* Tests\images\ # Install extra test images
xcopy /S /Y Tests\test-images\* Tests\images
# make cache key depend on VS version # make cache key depend on VS version
& "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" ` & "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" `

View File

@ -22,6 +22,7 @@ jobs:
python-version: [ python-version: [
"pypy3.9", "pypy3.9",
"pypy3.8", "pypy3.8",
"3.12-dev",
"3.11", "3.11",
"3.10", "3.10",
"3.9", "3.9",
@ -107,9 +108,9 @@ jobs:
- name: Upload coverage - name: Upload coverage
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v3
with: with:
file: ./coverage.xml
flags: ${{ matrix.os == 'macos-latest' && 'GHA_macOS' || 'GHA_Ubuntu' }} flags: ${{ matrix.os == 'macos-latest' && 'GHA_macOS' || 'GHA_Ubuntu' }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }} name: ${{ matrix.os }} Python ${{ matrix.python-version }}
gcov: true
success: success:
permissions: permissions:

2
.gitignore vendored
View File

@ -79,7 +79,7 @@ docs/_build/
# JetBrains # JetBrains
.idea .idea
# Extra test images installed from pillow-depends/test_images # Extra test images installed from python-pillow/test-images
Tests/images/README.md Tests/images/README.md
Tests/images/crash_1.tif Tests/images/crash_1.tif
Tests/images/crash_2.tif Tests/images/crash_2.tif

View File

@ -5,6 +5,27 @@ Changelog (Pillow)
9.5.0 (unreleased) 9.5.0 (unreleased)
------------------ ------------------
- Raise an error if ImageDraw co-ordinates are incorrectly ordered #6978
[radarhere]
- Added "corners" argument to ImageDraw rounded_rectangle() #6954
[radarhere]
- Added memoryview support to frombytes() #6974
[radarhere]
- Allow comments in FITS images #6973
[radarhere]
- Support saving PDF with different X and Y resolutions #6961
[jvanderneutstulen, radarhere, hugovk]
- Fixed writing int as UNDEFINED tag #6950
[radarhere]
- Raise an error if EXIF data is too long when saving JPEG #6939
[radarhere]
- Handle more than one directory returned by pkg-config #6896 - Handle more than one directory returned by pkg-config #6896
[sebastic, radarhere] [sebastic, radarhere]

View File

@ -13,8 +13,8 @@ By obtaining, using, and/or copying this software and/or its associated
documentation, you agree that you have read, understood, and will comply documentation, you agree that you have read, understood, and will comply
with the following terms and conditions: with the following terms and conditions:
Permission to use, copy, modify, and distribute this software and its Permission to use, copy, modify and distribute this software and its
associated documentation for any purpose and without fee is hereby granted, documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appears in all copies, and that provided that the above copyright notice appears in all copies, and that
both that copyright notice and this permission notice appear in supporting both that copyright notice and this permission notice appear in supporting
documentation, and that the name of Secret Labs AB or the author not be documentation, and that the name of Secret Labs AB or the author not be

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 844 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 934 B

View File

@ -44,6 +44,12 @@ def test_naxis_zero():
pass pass
def test_comment():
image_data = b"SIMPLE = T / comment string"
with pytest.raises(OSError):
FitsImagePlugin.FitsImageFile(BytesIO(image_data))
def test_stub_deprecated(): def test_stub_deprecated():
class Handler: class Handler:
opened = False opened = False

View File

@ -270,7 +270,10 @@ class TestFileJpeg:
# https://github.com/python-pillow/Pillow/issues/148 # https://github.com/python-pillow/Pillow/issues/148
f = str(tmp_path / "temp.jpg") f = str(tmp_path / "temp.jpg")
im = hopper() im = hopper()
im.save(f, "JPEG", quality=90, exif=b"1" * 65532) im.save(f, "JPEG", quality=90, exif=b"1" * 65533)
with pytest.raises(ValueError):
im.save(f, "JPEG", quality=90, exif=b"1" * 65534)
def test_exif_typeerror(self): def test_exif_typeerror(self):
with Image.open("Tests/images/exif_typeerror.jpg") as im: with Image.open("Tests/images/exif_typeerror.jpg") as im:
@ -445,7 +448,7 @@ class TestFileJpeg:
ims = im.get_child_images() ims = im.get_child_images()
assert len(ims) == 1 assert len(ims) == 1
assert_image_equal_tofile(ims[0], "Tests/images/flower_thumbnail.png") assert_image_similar_tofile(ims[0], "Tests/images/flower_thumbnail.png", 2.1)
def test_mp(self): def test_mp(self):
with Image.open("Tests/images/pil_sample_rgb.jpg") as im: with Image.open("Tests/images/pil_sample_rgb.jpg") as im:

View File

@ -80,6 +80,34 @@ def test_resolution(tmp_path):
assert size == (61.44, 61.44) assert size == (61.44, 61.44)
@pytest.mark.parametrize(
"params",
(
{"dpi": (75, 150)},
{"dpi": (75, 150), "resolution": 200},
),
)
def test_dpi(params, tmp_path):
im = hopper()
outfile = str(tmp_path / "temp.pdf")
im.save(outfile, **params)
with open(outfile, "rb") as fp:
contents = fp.read()
size = tuple(
float(d)
for d in contents.split(b"stream\nq ")[1].split(b" 0 0 cm")[0].split(b" 0 0 ")
)
assert size == (122.88, 61.44)
size = tuple(
float(d) for d in contents.split(b"/MediaBox [ 0 0 ")[1].split(b"]")[0].split()
)
assert size == (122.88, 61.44)
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )

View File

@ -216,6 +216,22 @@ def test_writing_other_types_to_bytes(value, tmp_path):
assert reloaded.tag_v2[700] == b"\x01" assert reloaded.tag_v2[700] == b"\x01"
def test_writing_other_types_to_undefined(tmp_path):
im = hopper()
info = TiffImagePlugin.ImageFileDirectory_v2()
tag = TiffTags.TAGS_V2[33723]
assert tag.type == TiffTags.UNDEFINED
info[33723] = 1
out = str(tmp_path / "temp.tiff")
im.save(out, tiffinfo=info)
with Image.open(out) as reloaded:
assert reloaded.tag_v2[33723] == b"1"
def test_undefined_zero(tmp_path): def test_undefined_zero(tmp_path):
# Check that the tag has not been changed since this test was created # Check that the tag has not been changed since this test was created
tag = TiffTags.TAGS_V2[45059] tag = TiffTags.TAGS_V2[45059]

View File

@ -1,10 +1,17 @@
import pytest
from PIL import Image from PIL import Image
from .helper import assert_image_equal, hopper from .helper import assert_image_equal, hopper
def test_sanity(): @pytest.mark.parametrize("data_type", ("bytes", "memoryview"))
def test_sanity(data_type):
im1 = hopper() im1 = hopper()
im2 = Image.frombytes(im1.mode, im1.size, im1.tobytes())
data = im1.tobytes()
if data_type == "memoryview":
data = memoryview(data)
im2 = Image.frombytes(im1.mode, im1.size, data)
assert_image_equal(im1, im2) assert_image_equal(im1, im2)

View File

@ -355,7 +355,13 @@ def ellipse_various_sizes_helper(filled):
for w in ellipse_sizes: for w in ellipse_sizes:
y = 1 y = 1
for h in ellipse_sizes: for h in ellipse_sizes:
border = [x, y, x + w - 1, y + h - 1] x1 = x + w
if w:
x1 -= 1
y1 = y + h
if h:
y1 -= 1
border = [x, y, x1, y1]
if filled: if filled:
draw.ellipse(border, fill="white") draw.ellipse(border, fill="white")
else: else:
@ -735,6 +741,36 @@ def test_rounded_rectangle(xy):
assert_image_equal_tofile(im, "Tests/images/imagedraw_rounded_rectangle.png") assert_image_equal_tofile(im, "Tests/images/imagedraw_rounded_rectangle.png")
@pytest.mark.parametrize("top_left", (True, False))
@pytest.mark.parametrize("top_right", (True, False))
@pytest.mark.parametrize("bottom_right", (True, False))
@pytest.mark.parametrize("bottom_left", (True, False))
def test_rounded_rectangle_corners(top_left, top_right, bottom_right, bottom_left):
corners = (top_left, top_right, bottom_right, bottom_left)
# Arrange
im = Image.new("RGB", (200, 200))
draw = ImageDraw.Draw(im)
# Act
draw.rounded_rectangle(
(10, 20, 190, 180), 30, fill="red", outline="green", width=5, corners=corners
)
# Assert
suffix = "".join(
(
("y" if top_left else "n"),
("y" if top_right else "n"),
("y" if bottom_right else "n"),
("y" if bottom_left else "n"),
)
)
assert_image_equal_tofile(
im, "Tests/images/imagedraw_rounded_rectangle_corners_" + suffix + ".png"
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"xy, radius, type", "xy, radius, type",
[ [
@ -902,9 +938,6 @@ def test_square():
img, draw = create_base_image_draw((10, 10)) img, draw = create_base_image_draw((10, 10))
draw.rectangle((2, 2, 7, 7), BLACK) draw.rectangle((2, 2, 7, 7), BLACK)
assert_image_equal_tofile(img, expected, "square as normal rectangle failed") assert_image_equal_tofile(img, expected, "square as normal rectangle failed")
img, draw = create_base_image_draw((10, 10))
draw.rectangle((7, 7, 2, 2), BLACK)
assert_image_equal_tofile(img, expected, "square as inverted rectangle failed")
def test_triangle_right(): def test_triangle_right():
@ -1469,3 +1502,21 @@ def test_polygon2():
draw.polygon([(18, 30), (19, 31), (18, 30), (85, 30), (60, 72)], "red") draw.polygon([(18, 30), (19, 31), (18, 30), (85, 30), (60, 72)], "red")
expected = "Tests/images/imagedraw_outline_polygon_RGB.png" expected = "Tests/images/imagedraw_outline_polygon_RGB.png"
assert_image_similar_tofile(im, expected, 1) assert_image_similar_tofile(im, expected, 1)
@pytest.mark.parametrize("xy", ((1, 1, 0, 1), (1, 1, 1, 0)))
def test_incorrectly_ordered_coordinates(xy):
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im)
with pytest.raises(ValueError):
draw.arc(xy, 10, 260)
with pytest.raises(ValueError):
draw.chord(xy, 10, 260)
with pytest.raises(ValueError):
draw.ellipse(xy)
with pytest.raises(ValueError):
draw.pieslice(xy, 10, 260)
with pytest.raises(ValueError):
draw.rectangle(xy)
with pytest.raises(ValueError):
draw.rounded_rectangle(xy)

View File

@ -48,7 +48,7 @@ if ImageQt.qt_is_installed:
def roundtrip(expected): def roundtrip(expected):
result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected)) result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected))
# Qt saves all pixmaps as rgb # Qt saves all pixmaps as rgb
assert_image_similar(result, expected.convert("RGB"), 0.3) assert_image_similar(result, expected.convert("RGB"), 1)
@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed") @pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")

View File

@ -8,5 +8,5 @@ if [ ! -f $archive.tar.gz ]; then
wget -O $archive.tar.gz $url wget -O $archive.tar.gz $url
fi fi
rm -r $archive rmdir $archive
tar -xvzf $archive.tar.gz tar -xvzf $archive.tar.gz

View File

@ -1,15 +1,12 @@
#!/bin/bash #!/usr/bin/env bash
# install extra test images # install extra test images
# Use SVN to just fetch a single Git subdirectory archive=test-images-main
svn_export()
{
if [ ! -z $1 ]; then
echo ""
echo "Retrying svn export..."
echo ""
fi
svn export --force https://github.com/python-pillow/pillow-depends/trunk/test_images ../Tests/images ./download-and-extract.sh $archive https://github.com/python-pillow/test-images/archive/main.tar.gz
}
svn_export || svn_export retry || svn_export retry || svn_export retry mv $archive/* ../Tests/images/
# Cleanup old tarball and empty directory
rm $archive.tar.gz
rmdir $archive

View File

@ -187,9 +187,7 @@ Deprecated Use
:py:meth:`.ImageDraw2.Draw.textsize` :py:meth:`.ImageDraw2.Draw.textbbox` and :py:meth:`.ImageDraw2.Draw.textlength` :py:meth:`.ImageDraw2.Draw.textsize` :py:meth:`.ImageDraw2.Draw.textbbox` and :py:meth:`.ImageDraw2.Draw.textlength`
=========================================================================== ============================================================================================================= =========================================================================== =============================================================================================================
Previous code: Previous code::
.. code-block:: python
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
@ -204,9 +202,7 @@ Previous code:
width, height = font.getsize_multiline("Hello\nworld") width, height = font.getsize_multiline("Hello\nworld")
width, height = draw.multiline_textsize("Hello\nworld") width, height = draw.multiline_textsize("Hello\nworld")
Use instead: Use instead::
.. code-block:: python
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
@ -346,16 +342,12 @@ Implicitly closing the image's underlying file in ``Image.__del__`` has been rem
Use a context manager or call ``Image.close()`` instead to close the file in a Use a context manager or call ``Image.close()`` instead to close the file in a
deterministic way. deterministic way.
Previous method: Previous method::
.. code-block:: python
im = Image.open("hopper.png") im = Image.open("hopper.png")
im.save("out.jpg") im.save("out.jpg")
Use instead: Use instead::
.. code-block:: python
with Image.open("hopper.png") as im: with Image.open("hopper.png") as im:
im.save("out.jpg") im.save("out.jpg")

View File

@ -1104,7 +1104,7 @@ using the general tags available through tiffinfo.
Either an integer or a float. Either an integer or a float.
**dpi** **dpi**
A tuple of (x_resolution, y_resolution), with inches as the resolution A tuple of ``(x_resolution, y_resolution)``, with inches as the resolution
unit. For consistency with other image formats, the x and y resolutions unit. For consistency with other image formats, the x and y resolutions
of the dpi will be rounded to the nearest integer. of the dpi will be rounded to the nearest integer.
@ -1126,7 +1126,7 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
If present and true, instructs the WebP writer to use lossless compression. If present and true, instructs the WebP writer to use lossless compression.
**quality** **quality**
Integer, 1-100, Defaults to 80. For lossy, 0 gives the smallest Integer, 0-100, Defaults to 80. For lossy, 0 gives the smallest
size and 100 the largest. For lossless, this parameter is the amount size and 100 the largest. For lossless, this parameter is the amount
of effort put into the compression: 0 is the fastest, but gives larger of effort put into the compression: 0 is the fastest, but gives larger
files compared to the slowest, but best, 100. files compared to the slowest, but best, 100.
@ -1147,6 +1147,10 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
The exif data to include in the saved file. Only supported if The exif data to include in the saved file. Only supported if
the system WebP library was built with webpmux support. the system WebP library was built with webpmux support.
**xmp**
The XMP data to include in the saved file. Only supported if
the system WebP library was built with webpmux support.
Saving sequences Saving sequences
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
@ -1389,9 +1393,7 @@ WMF, EMF
Pillow can identify WMF and EMF files. Pillow can identify WMF and EMF files.
On Windows, it can read WMF and EMF files. By default, it will load the image On Windows, it can read WMF and EMF files. By default, it will load the image
at 72 dpi. To load it at another resolution: at 72 dpi. To load it at another resolution::
.. code-block:: python
from PIL import Image from PIL import Image
@ -1400,9 +1402,7 @@ at 72 dpi. To load it at another resolution:
To add other read or write support, use To add other read or write support, use
:py:func:`PIL.WmfImagePlugin.register_handler` to register a WMF and EMF :py:func:`PIL.WmfImagePlugin.register_handler` to register a WMF and EMF
handler. handler. ::
.. code-block:: python
from PIL import Image from PIL import Image
from PIL import WmfImagePlugin from PIL import WmfImagePlugin
@ -1493,6 +1493,11 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
image, will determine the physical dimensions of the page that will be image, will determine the physical dimensions of the page that will be
saved in the PDF. saved in the PDF.
**dpi**
A tuple of ``(x_resolution, y_resolution)``, with inches as the resolution
unit. If both the ``resolution`` parameter and the ``dpi`` parameter are
present, ``resolution`` will be ignored.
**title** **title**
The documents title. If not appending to an existing PDF file, this will The documents title. If not appending to an existing PDF file, this will
default to the filename. default to the filename.

View File

@ -29,7 +29,7 @@ For example, in the following image, the text is ``ms`` (middle-baseline) aligne
:alt: ms (middle-baseline) aligned text. :alt: ms (middle-baseline) aligned text.
:align: left :align: left
.. code-block:: python ::
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont

View File

@ -108,9 +108,7 @@ Note that the image plugin must be explicitly registered using
:py:func:`PIL.Image.register_open`. Although not required, it is also a good :py:func:`PIL.Image.register_open`. Although not required, it is also a good
idea to register any extensions used by this format. idea to register any extensions used by this format.
Once the plugin has been imported, it can be used: Once the plugin has been imported, it can be used::
.. code-block:: python
from PIL import Image from PIL import Image
import SpamImagePlugin import SpamImagePlugin
@ -169,9 +167,7 @@ The raw decoder
The ``raw`` decoder is used to read uncompressed data from an image file. It The ``raw`` decoder is used to read uncompressed data from an image file. It
can be used with most uncompressed file formats, such as PPM, BMP, uncompressed can be used with most uncompressed file formats, such as PPM, BMP, uncompressed
TIFF, and many others. To use the raw decoder with the TIFF, and many others. To use the raw decoder with the
:py:func:`PIL.Image.frombytes` function, use the following syntax: :py:func:`PIL.Image.frombytes` function, use the following syntax::
.. code-block:: python
image = Image.frombytes( image = Image.frombytes(
mode, size, data, "raw", mode, size, data, "raw",
@ -281,9 +277,7 @@ decoder that can be used to read various packed formats into a floating point
image memory. image memory.
To use the bit decoder with the :py:func:`PIL.Image.frombytes` function, use To use the bit decoder with the :py:func:`PIL.Image.frombytes` function, use
the following syntax: the following syntax::
.. code-block:: python
image = Image.frombytes( image = Image.frombytes(
mode, size, data, "bit", mode, size, data, "bit",

View File

@ -150,7 +150,7 @@ Many of Pillow's features require external libraries:
* **littlecms** provides color management * **littlecms** provides color management
* Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and * Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and
above uses liblcms2. Tested with **1.19** and **2.7-2.14**. above uses liblcms2. Tested with **1.19** and **2.7-2.15**.
* **libwebp** provides the WebP format. * **libwebp** provides the WebP format.
@ -442,21 +442,23 @@ These platforms are built and tested for every change.
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Gentoo | 3.9 | x86-64 | | Gentoo | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64 | | macOS 12 Monterey | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64 |
| | PyPy3 | | | | 3.12, PyPy3 | |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 18.04 LTS (Bionic) | 3.9 | x86-64 | | Ubuntu Linux 18.04 LTS (Bionic) | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 20.04 LTS (Focal) | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64 | | Ubuntu Linux 20.04 LTS (Focal) | 3.8 | x86-64 |
| | PyPy3 | |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | arm64v8, ppc64le, | | Ubuntu Linux 22.04 LTS (Jammy) | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64 |
| | | s390x, x86-64 | | | 3.12, PyPy3 | |
| +----------------------------+---------------------+
| | 3.10 | arm64v8, ppc64le, |
| | | s390x |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Windows Server 2016 | 3.7 | x86-64 | | Windows Server 2016 | 3.7 | x86-64 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Windows Server 2022 | 3.7, 3.8, 3.9, 3.10, 3.11, | x86, x86-64 | | Windows Server 2022 | 3.7, 3.8, 3.9, 3.10, 3.11, | x86, x86-64 |
| | PyPy3 | | | | 3.12, PyPy3 | |
| +----------------------------+---------------------+ | +----------------------------+---------------------+
| | 3.9 (MinGW) | x86, x86-64 | | | 3.9 (MinGW) | x86, x86-64 |
| +----------------------------+---------------------+ | +----------------------------+---------------------+

View File

@ -17,9 +17,7 @@ Open, rotate, and display an image (using the default viewer)
The following script loads an image, rotates it 45 degrees, and displays it The following script loads an image, rotates it 45 degrees, and displays it
using an external viewer (usually xv on Unix, and the Paint program on using an external viewer (usually xv on Unix, and the Paint program on
Windows). Windows). ::
.. code-block:: python
from PIL import Image from PIL import Image
with Image.open("hopper.jpg") as im: with Image.open("hopper.jpg") as im:
@ -29,9 +27,7 @@ Create thumbnails
^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^
The following script creates nice thumbnails of all JPEG images in the The following script creates nice thumbnails of all JPEG images in the
current directory preserving aspect ratios with 128x128 max resolution. current directory preserving aspect ratios with 128x128 max resolution. ::
.. code-block:: python
from PIL import Image from PIL import Image
import glob, os import glob, os
@ -127,9 +123,7 @@ methods. Unless otherwise stated, all methods return a new instance of the
.. automethod:: PIL.Image.Image.convert .. automethod:: PIL.Image.Image.convert
The following example converts an RGB image (linearly calibrated according to The following example converts an RGB image (linearly calibrated according to
ITU-R 709, using the D65 luminant) to the CIE XYZ color space: ITU-R 709, using the D65 luminant) to the CIE XYZ color space::
.. code-block:: python
rgb2xyz = ( rgb2xyz = (
0.412453, 0.357580, 0.180423, 0, 0.412453, 0.357580, 0.180423, 0,
@ -140,9 +134,7 @@ ITU-R 709, using the D65 luminant) to the CIE XYZ color space:
.. automethod:: PIL.Image.Image.copy .. automethod:: PIL.Image.Image.copy
.. automethod:: PIL.Image.Image.crop .. automethod:: PIL.Image.Image.crop
This crops the input image with the provided coordinates: This crops the input image with the provided coordinates::
.. code-block:: python
from PIL import Image from PIL import Image
@ -162,9 +154,7 @@ This crops the input image with the provided coordinates:
.. automethod:: PIL.Image.Image.entropy .. automethod:: PIL.Image.Image.entropy
.. automethod:: PIL.Image.Image.filter .. automethod:: PIL.Image.Image.filter
This blurs the input image using a filter from the ``ImageFilter`` module: This blurs the input image using a filter from the ``ImageFilter`` module::
.. code-block:: python
from PIL import Image, ImageFilter from PIL import Image, ImageFilter
@ -176,9 +166,7 @@ This blurs the input image using a filter from the ``ImageFilter`` module:
.. automethod:: PIL.Image.Image.frombytes .. automethod:: PIL.Image.Image.frombytes
.. automethod:: PIL.Image.Image.getbands .. automethod:: PIL.Image.Image.getbands
This helps to get the bands of the input image: This helps to get the bands of the input image::
.. code-block:: python
from PIL import Image from PIL import Image
@ -187,9 +175,7 @@ This helps to get the bands of the input image:
.. automethod:: PIL.Image.Image.getbbox .. automethod:: PIL.Image.Image.getbbox
This helps to get the bounding box coordinates of the input image: This helps to get the bounding box coordinates of the input image::
.. code-block:: python
from PIL import Image from PIL import Image
@ -217,9 +203,7 @@ This helps to get the bounding box coordinates of the input image:
.. automethod:: PIL.Image.Image.remap_palette .. automethod:: PIL.Image.Image.remap_palette
.. automethod:: PIL.Image.Image.resize .. automethod:: PIL.Image.Image.resize
This resizes the given image from ``(width, height)`` to ``(width/2, height/2)``: This resizes the given image from ``(width, height)`` to ``(width/2, height/2)``::
.. code-block:: python
from PIL import Image from PIL import Image
@ -231,9 +215,7 @@ This resizes the given image from ``(width, height)`` to ``(width/2, height/2)``
.. automethod:: PIL.Image.Image.rotate .. automethod:: PIL.Image.Image.rotate
This rotates the input image by ``theta`` degrees counter clockwise: This rotates the input image by ``theta`` degrees counter clockwise::
.. code-block:: python
from PIL import Image from PIL import Image
@ -256,9 +238,7 @@ This rotates the input image by ``theta`` degrees counter clockwise:
.. automethod:: PIL.Image.Image.transpose .. automethod:: PIL.Image.Image.transpose
This flips the input image by using the :data:`Transpose.FLIP_LEFT_RIGHT` This flips the input image by using the :data:`Transpose.FLIP_LEFT_RIGHT`
method. method. ::
.. code-block:: python
from PIL import Image from PIL import Image

View File

@ -16,7 +16,7 @@ For a more advanced drawing library for PIL, see the `aggdraw module`_.
Example: Draw a gray cross over an image Example: Draw a gray cross over an image
---------------------------------------- ----------------------------------------
.. code-block:: python ::
import sys import sys
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
@ -78,7 +78,7 @@ libraries, and may not available in all PIL builds.
Example: Draw Partial Opacity Text Example: Draw Partial Opacity Text
---------------------------------- ----------------------------------
.. code-block:: python ::
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
@ -105,7 +105,7 @@ Example: Draw Partial Opacity Text
Example: Draw Multiline Text Example: Draw Multiline Text
---------------------------- ----------------------------
.. code-block:: python ::
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
@ -318,8 +318,8 @@ Methods
Draws a rectangle. Draws a rectangle.
:param xy: Two points to define the bounding box. Sequence of either :param xy: Two points to define the bounding box. Sequence of either
``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. The bounding box ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0`` and
is inclusive of both endpoints. ``y1 >= y0``. The bounding box is inclusive of both endpoints.
:param outline: Color to use for the outline. :param outline: Color to use for the outline.
:param fill: Color to use for the fill. :param fill: Color to use for the fill.
:param width: The line width, in pixels. :param width: The line width, in pixels.
@ -331,12 +331,14 @@ Methods
Draws a rounded rectangle. Draws a rounded rectangle.
:param xy: Two points to define the bounding box. Sequence of either :param xy: Two points to define the bounding box. Sequence of either
``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. The bounding box ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0`` and
is inclusive of both endpoints. ``y1 >= y0``. The bounding box is inclusive of both endpoints.
:param radius: Radius of the corners. :param radius: Radius of the corners.
:param outline: Color to use for the outline. :param outline: Color to use for the outline.
:param fill: Color to use for the fill. :param fill: Color to use for the fill.
:param width: The line width, in pixels. :param width: The line width, in pixels.
:param corners: A tuple of whether to round each corner,
`(top_left, top_right, bottom_right, bottom_left)`.
.. versionadded:: 8.2.0 .. versionadded:: 8.2.0
@ -597,18 +599,14 @@ Methods
string due to kerning. If you need to adjust for kerning, include the following string due to kerning. If you need to adjust for kerning, include the following
character and subtract its length. character and subtract its length.
For example, instead of For example, instead of ::
.. code-block:: python
hello = draw.textlength("Hello", font) hello = draw.textlength("Hello", font)
world = draw.textlength("World", font) world = draw.textlength("World", font)
hello_world = hello + world # not adjusted for kerning hello_world = hello + world # not adjusted for kerning
assert hello_world == draw.textlength("HelloWorld", font) # may fail assert hello_world == draw.textlength("HelloWorld", font) # may fail
use use ::
.. code-block:: python
hello = draw.textlength("HelloW", font) - draw.textlength( hello = draw.textlength("HelloW", font) - draw.textlength(
"W", font "W", font
@ -617,9 +615,7 @@ Methods
hello_world = hello + world # adjusted for kerning hello_world = hello + world # adjusted for kerning
assert hello_world == draw.textlength("HelloWorld", font) # True assert hello_world == draw.textlength("HelloWorld", font) # True
or disable kerning with (requires libraqm) or disable kerning with (requires libraqm) ::
.. code-block:: python
hello = draw.textlength("Hello", font, features=["-kern"]) hello = draw.textlength("Hello", font, features=["-kern"])
world = draw.textlength("World", font, features=["-kern"]) world = draw.textlength("World", font, features=["-kern"])

View File

@ -10,7 +10,7 @@ for image enhancement.
Example: Vary the sharpness of an image Example: Vary the sharpness of an image
--------------------------------------- ---------------------------------------
.. code-block:: python ::
from PIL import ImageEnhance from PIL import ImageEnhance

View File

@ -15,7 +15,7 @@ and **xmllib** modules.
Example: Parse an image Example: Parse an image
----------------------- -----------------------
.. code-block:: python ::
from PIL import ImageFile from PIL import ImageFile

View File

@ -11,7 +11,7 @@ filters, which can be be used with the :py:meth:`Image.filter()
Example: Filter an image Example: Filter an image
------------------------ ------------------------
.. code-block:: python ::
from PIL import ImageFilter from PIL import ImageFilter

View File

@ -21,7 +21,7 @@ the imToolkit package.
Example Example
------- -------
.. code-block:: python ::
from PIL import ImageFont, ImageDraw from PIL import ImageFont, ImageDraw

View File

@ -11,7 +11,7 @@ an expression string and one or more images.
Example: Using the :py:mod:`~PIL.ImageMath` module Example: Using the :py:mod:`~PIL.ImageMath` module
-------------------------------------------------- --------------------------------------------------
.. code-block:: python ::
from PIL import Image, ImageMath from PIL import Image, ImageMath

View File

@ -60,9 +60,7 @@ vector data. Path objects can be passed to the methods on the
.. py:method:: PIL.ImagePath.Path.transform(matrix) .. py:method:: PIL.ImagePath.Path.transform(matrix)
Transforms the path in place, using an affine transform. The matrix is a Transforms the path in place, using an affine transform. The matrix is a
6-tuple (a, b, c, d, e, f), and each point is mapped as follows: 6-tuple (a, b, c, d, e, f), and each point is mapped as follows::
.. code-block:: python
xOut = xIn * a + yIn * b + c xOut = xIn * a + yIn * b + c
yOut = xIn * d + yIn * e + f yOut = xIn * d + yIn * e + f

View File

@ -10,7 +10,7 @@ iterate over the frames of an image sequence.
Extracting frames from an animation Extracting frames from an animation
----------------------------------- -----------------------------------
.. code-block:: python ::
from PIL import Image, ImageSequence from PIL import Image, ImageSequence

View File

@ -9,9 +9,7 @@ Windows.
ImageWin can be used with PythonWin and other user interface toolkits that ImageWin can be used with PythonWin and other user interface toolkits that
provide access to Windows device contexts or window handles. For example, provide access to Windows device contexts or window handles. For example,
Tkinter makes the window handle available via the winfo_id method: Tkinter makes the window handle available via the winfo_id method::
.. code-block:: python
from PIL import ImageWin from PIL import ImageWin

View File

@ -18,9 +18,7 @@ Example
------- -------
The following script loads an image, accesses one pixel from it, then The following script loads an image, accesses one pixel from it, then
changes it. changes it. ::
.. code-block:: python
from PIL import Image from PIL import Image
@ -35,9 +33,7 @@ Results in the following::
(23, 24, 68) (23, 24, 68)
(0, 0, 0) (0, 0, 0)
Access using negative indexes is also possible. Access using negative indexes is also possible. ::
.. code-block:: python
px[-1, -1] = (0, 0, 0) px[-1, -1] = (0, 0, 0)
print(px[-1, -1]) print(px[-1, -1])

View File

@ -17,9 +17,7 @@ The :py:mod:`~PIL.PyAccess` module provides a CFFI/Python implementation of the
Example Example
------- -------
The following script loads an image, accesses one pixel from it, then changes it. The following script loads an image, accesses one pixel from it, then changes it. ::
.. code-block:: python
from PIL import Image from PIL import Image
@ -34,9 +32,7 @@ Results in the following::
(23, 24, 68) (23, 24, 68)
(0, 0, 0) (0, 0, 0)
Access using negative indexes is also possible. Access using negative indexes is also possible. ::
.. code-block:: python
px[-1, -1] = (0, 0, 0) px[-1, -1] = (0, 0, 0)
print(px[-1, -1]) print(px[-1, -1])

View File

@ -61,9 +61,7 @@ Image Lifecycle
* ``Image.Image.close()`` Closes the file and destroys the core image object. * ``Image.Image.close()`` Closes the file and destroys the core image object.
The Pillow context manager will also close the file, but will not destroy The Pillow context manager will also close the file, but will not destroy
the core image object. e.g.: the core image object. e.g.::
.. code-block:: python
with Image.open("test.jpg") as img: with Image.open("test.jpg") as img:
img.load() img.load()

View File

@ -13,16 +13,12 @@ Implicitly closing the image's underlying file in ``Image.__del__`` has been dep
Use a context manager or call ``Image.close()`` instead to close the file in a Use a context manager or call ``Image.close()`` instead to close the file in a
deterministic way. deterministic way.
Deprecated: Deprecated::
.. code-block:: python
im = Image.open("hopper.png") im = Image.open("hopper.png")
im.save("out.jpg") im.save("out.jpg")
Use instead: Use instead::
.. code-block:: python
with Image.open("hopper.png") as im: with Image.open("hopper.png") as im:
im.save("out.jpg") im.save("out.jpg")
@ -79,9 +75,7 @@ Image quality for JPEG compressed TIFF
The TIFF encoder accepts a ``quality`` parameter for ``jpeg`` compressed TIFF files. A The TIFF encoder accepts a ``quality`` parameter for ``jpeg`` compressed TIFF files. A
value from 0 (worst) to 100 (best) controls the image quality, similar to the JPEG value from 0 (worst) to 100 (best) controls the image quality, similar to the JPEG
encoder. The default is 75. For example: encoder. The default is 75. For example::
.. code-block:: python
im.save("out.tif", compression="jpeg", quality=85) im.save("out.tif", compression="jpeg", quality=85)

View File

@ -10,9 +10,7 @@ Text stroking
``stroke_width`` and ``stroke_fill`` arguments have been added to text drawing ``stroke_width`` and ``stroke_fill`` arguments have been added to text drawing
operations. They allow text to be outlined, setting the width of the stroke and operations. They allow text to be outlined, setting the width of the stroke and
and the color respectively. If not provided, ``stroke_fill`` will default to and the color respectively. If not provided, ``stroke_fill`` will default to
the ``fill`` parameter. the ``fill`` parameter. ::
.. code-block:: python
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
@ -28,9 +26,7 @@ the ``fill`` parameter.
draw.multiline_text((10, 10), "A\nB", "#f00", font, draw.multiline_text((10, 10), "A\nB", "#f00", font,
stroke_width=2, stroke_fill="#0f0") stroke_width=2, stroke_fill="#0f0")
For example, For example, ::
.. code-block:: python
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont

View File

@ -118,9 +118,7 @@ Loading WMF images at a given DPI
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
On Windows, Pillow can read WMF files, with a default DPI of 72. An image can On Windows, Pillow can read WMF files, with a default DPI of 72. An image can
now also be loaded at another resolution: now also be loaded at another resolution::
.. code-block:: python
from PIL import Image from PIL import Image
with Image.open("drawing.wmf") as im: with Image.open("drawing.wmf") as im:
@ -136,16 +134,12 @@ Implicitly closing the image's underlying file in ``Image.__del__`` has been rem
Use a context manager or call :py:meth:`~PIL.Image.Image.close` instead to close Use a context manager or call :py:meth:`~PIL.Image.Image.close` instead to close
the file in a deterministic way. the file in a deterministic way.
Previous method: Previous method::
.. code-block:: python
im = Image.open("hopper.png") im = Image.open("hopper.png")
im.save("out.jpg") im.save("out.jpg")
Use instead: Use instead::
.. code-block:: python
with Image.open("hopper.png") as im: with Image.open("hopper.png") as im:
im.save("out.jpg") im.save("out.jpg")

View File

@ -10,9 +10,7 @@ Allow saving of zero quality JPEG images
If no quality was specified when saving a JPEG, Pillow internally used a value If no quality was specified when saving a JPEG, Pillow internally used a value
of zero to indicate that the default quality should be used. However, this of zero to indicate that the default quality should be used. However, this
removed the ability to actually save a JPEG with zero quality. This has now removed the ability to actually save a JPEG with zero quality. This has now
been resolved. been resolved. ::
.. code-block:: python
from PIL import Image from PIL import Image
im = Image.open("hopper.jpg") im = Image.open("hopper.jpg")

View File

@ -76,9 +76,7 @@ ImageDraw.rounded_rectangle
Added :py:meth:`~PIL.ImageDraw.ImageDraw.rounded_rectangle`. It works the same as Added :py:meth:`~PIL.ImageDraw.ImageDraw.rounded_rectangle`. It works the same as
:py:meth:`~PIL.ImageDraw.ImageDraw.rectangle`, except with an additional ``radius`` :py:meth:`~PIL.ImageDraw.ImageDraw.rectangle`, except with an additional ``radius``
argument. ``radius`` is limited to half of the width or the height, so that users can argument. ``radius`` is limited to half of the width or the height, so that users can
create a circle, but not any other ellipse. create a circle, but not any other ellipse. ::
.. code-block:: python
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
im = Image.new("RGB", (200, 200)) im = Image.new("RGB", (200, 200))

View File

@ -24,9 +24,7 @@ Added "transparency" argument for loading EPS images
This new argument switches the Ghostscript device from "ppmraw" to "pngalpha", This new argument switches the Ghostscript device from "ppmraw" to "pngalpha",
generating an RGBA image with a transparent background instead of an RGB image with a generating an RGBA image with a transparent background instead of an RGB image with a
white background. white background. ::
.. code-block:: python
with Image.open("sample.eps") as im: with Image.open("sample.eps") as im:
im.load(transparency=True) im.load(transparency=True)

View File

@ -155,9 +155,7 @@ altered slightly with this change.
Added support for pickling TrueType fonts Added support for pickling TrueType fonts
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TrueType fonts may now be pickled and unpickled. For example: TrueType fonts may now be pickled and unpickled. For example::
.. code-block:: python
import pickle import pickle
from PIL import ImageFont from PIL import ImageFont

View File

@ -182,17 +182,13 @@ GifImagePlugin loading strategy
Pillow 9.0.0 introduced the conversion of subsequent GIF frames to ``RGB`` or ``RGBA``. This Pillow 9.0.0 introduced the conversion of subsequent GIF frames to ``RGB`` or ``RGBA``. This
behaviour can now be changed so that the first ``P`` frame is converted to ``RGB`` as behaviour can now be changed so that the first ``P`` frame is converted to ``RGB`` as
well. well. ::
.. code-block:: python
from PIL import GifImagePlugin from PIL import GifImagePlugin
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS
Or subsequent frames can be kept in ``P`` mode as long as there is only a single Or subsequent frames can be kept in ``P`` mode as long as there is only a single
palette. palette. ::
.. code-block:: python
from PIL import GifImagePlugin from PIL import GifImagePlugin
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY

View File

@ -59,9 +59,7 @@ Deprecated Use
:py:meth:`.ImageDraw2.Draw.textsize` :py:meth:`.ImageDraw2.Draw.textbbox` and :py:meth:`.ImageDraw2.Draw.textlength` :py:meth:`.ImageDraw2.Draw.textsize` :py:meth:`.ImageDraw2.Draw.textbbox` and :py:meth:`.ImageDraw2.Draw.textlength`
=========================================================================== ============================================================================================================= =========================================================================== =============================================================================================================
Previous code: Previous code::
.. code-block:: python
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
@ -76,9 +74,7 @@ Previous code:
width, height = font.getsize_multiline("Hello\nworld") width, height = font.getsize_multiline("Hello\nworld")
width, height = draw.multiline_textsize("Hello\nworld") width, height = draw.multiline_textsize("Hello\nworld")
Use instead: Use instead::
.. code-block:: python
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont

View File

@ -64,16 +64,27 @@ def bdf_char(f):
bitmap.append(s[:-1]) bitmap.append(s[:-1])
bitmap = b"".join(bitmap) bitmap = b"".join(bitmap)
[x, y, l, d] = [int(p) for p in props["BBX"].split()] # The word BBX
[dx, dy] = [int(p) for p in props["DWIDTH"].split()] # followed by the width in x (BBw), height in y (BBh),
# and x and y displacement (BBxoff0, BByoff0)
# of the lower left corner from the origin of the character.
width, height, x_disp, y_disp = [int(p) for p in props["BBX"].split()]
bbox = (dx, dy), (l, -d - y, x + l, -d), (0, 0, x, y) # The word DWIDTH
# followed by the width in x and y of the character in device pixels.
dwx, dwy = [int(p) for p in props["DWIDTH"].split()]
bbox = (
(dwx, dwy),
(x_disp, -y_disp - height, width + x_disp, -y_disp),
(0, 0, width, height),
)
try: try:
im = Image.frombytes("1", (x, y), bitmap, "hex", "1") im = Image.frombytes("1", (width, height), bitmap, "hex", "1")
except ValueError: except ValueError:
# deal with zero-width characters # deal with zero-width characters
im = Image.new("1", (x, y)) im = Image.new("1", (width, height))
return id, int(props["ENCODING"]), bbox, im return id, int(props["ENCODING"]), bbox, im

View File

@ -32,7 +32,7 @@ class FitsImageFile(ImageFile.ImageFile):
keyword = header[:8].strip() keyword = header[:8].strip()
if keyword == b"END": if keyword == b"END":
break break
value = header[8:].strip() value = header[8:].split(b"/")[0].strip()
if value.startswith(b"="): if value.startswith(b"="):
value = value[1:].strip() value = value[1:].strip()
if not headers and (not _accept(keyword) or value != b"T"): if not headers and (not _accept(keyword) or value != b"T"):

View File

@ -765,17 +765,17 @@ class Image:
bufsize = max(65536, self.size[0] * 4) # see RawEncode.c bufsize = max(65536, self.size[0] * 4) # see RawEncode.c
data = [] output = []
while True: while True:
l, s, d = e.encode(bufsize) bytes_consumed, errcode, data = e.encode(bufsize)
data.append(d) output.append(data)
if s: if errcode:
break break
if s < 0: if errcode < 0:
msg = f"encoder error {s} in tobytes" msg = f"encoder error {errcode} in tobytes"
raise RuntimeError(msg) raise RuntimeError(msg)
return b"".join(data) return b"".join(output)
def tobitmap(self, name="image"): def tobitmap(self, name="image"):
""" """
@ -1432,6 +1432,11 @@ class Image:
return {get_name(root.tag): get_value(root)} return {get_name(root.tag): get_value(root)}
def getexif(self): def getexif(self):
"""
Gets EXIF data from the image.
:returns: an :py:class:`~PIL.Image.Exif` object.
"""
if self._exif is None: if self._exif is None:
self._exif = Exif() self._exif = Exif()
self._exif._loaded = False self._exif._loaded = False
@ -3601,6 +3606,39 @@ atexit.register(core.clear_cache)
class Exif(MutableMapping): class Exif(MutableMapping):
"""
This class provides read and write access to EXIF image data::
from PIL import Image
im = Image.open("exif.png")
exif = im.getexif() # Returns an instance of this class
Information can be read and written, iterated over or deleted::
print(exif[274]) # 1
exif[274] = 2
for k, v in exif.items():
print("Tag", k, "Value", v) # Tag 274 Value 2
del exif[274]
To access information beyond IFD0, :py:meth:`~PIL.Image.Exif.get_ifd`
returns a dictionary::
from PIL import ExifTags
im = Image.open("exif_gps.jpg")
exif = im.getexif()
gps_ifd = exif.get_ifd(ExifTags.IFD.GPSInfo)
print(gps_ifd)
Other IFDs include ``ExifTags.IFD.Exif``, ``ExifTags.IFD.Makernote``,
``ExifTags.IFD.Interop`` and ``ExifTags.IFD.IFD1``.
:py:mod:`~PIL.ExifTags` also has enum classes to provide names for data::
print(exif[ExifTags.Base.Software]) # PIL
print(gps_ifd[ExifTags.GPS.GPSDateStamp]) # 1999:99:99 99:99:99
"""
endian = None endian = None
bigtiff = False bigtiff = False

View File

@ -38,9 +38,7 @@ def duplicate(image):
def invert(image): def invert(image):
""" """
Invert an image (channel). Invert an image (channel). ::
.. code-block:: python
out = MAX - image out = MAX - image
@ -54,9 +52,7 @@ def invert(image):
def lighter(image1, image2): def lighter(image1, image2):
""" """
Compares the two images, pixel by pixel, and returns a new image containing Compares the two images, pixel by pixel, and returns a new image containing
the lighter values. the lighter values. ::
.. code-block:: python
out = max(image1, image2) out = max(image1, image2)
@ -71,9 +67,7 @@ def lighter(image1, image2):
def darker(image1, image2): def darker(image1, image2):
""" """
Compares the two images, pixel by pixel, and returns a new image containing Compares the two images, pixel by pixel, and returns a new image containing
the darker values. the darker values. ::
.. code-block:: python
out = min(image1, image2) out = min(image1, image2)
@ -88,9 +82,7 @@ def darker(image1, image2):
def difference(image1, image2): def difference(image1, image2):
""" """
Returns the absolute value of the pixel-by-pixel difference between the two Returns the absolute value of the pixel-by-pixel difference between the two
images. images. ::
.. code-block:: python
out = abs(image1 - image2) out = abs(image1 - image2)
@ -107,9 +99,7 @@ def multiply(image1, image2):
Superimposes two images on top of each other. Superimposes two images on top of each other.
If you multiply an image with a solid black image, the result is black. If If you multiply an image with a solid black image, the result is black. If
you multiply with a solid white image, the image is unaffected. you multiply with a solid white image, the image is unaffected. ::
.. code-block:: python
out = image1 * image2 / MAX out = image1 * image2 / MAX
@ -123,9 +113,7 @@ def multiply(image1, image2):
def screen(image1, image2): def screen(image1, image2):
""" """
Superimposes two inverted images on top of each other. Superimposes two inverted images on top of each other. ::
.. code-block:: python
out = MAX - ((MAX - image1) * (MAX - image2) / MAX) out = MAX - ((MAX - image1) * (MAX - image2) / MAX)
@ -176,9 +164,7 @@ def overlay(image1, image2):
def add(image1, image2, scale=1.0, offset=0): def add(image1, image2, scale=1.0, offset=0):
""" """
Adds two images, dividing the result by scale and adding the Adds two images, dividing the result by scale and adding the
offset. If omitted, scale defaults to 1.0, and offset to 0.0. offset. If omitted, scale defaults to 1.0, and offset to 0.0. ::
.. code-block:: python
out = ((image1 + image2) / scale + offset) out = ((image1 + image2) / scale + offset)
@ -193,9 +179,7 @@ def add(image1, image2, scale=1.0, offset=0):
def subtract(image1, image2, scale=1.0, offset=0): def subtract(image1, image2, scale=1.0, offset=0):
""" """
Subtracts two images, dividing the result by scale and adding the offset. Subtracts two images, dividing the result by scale and adding the offset.
If omitted, scale defaults to 1.0, and offset to 0.0. If omitted, scale defaults to 1.0, and offset to 0.0. ::
.. code-block:: python
out = ((image1 - image2) / scale + offset) out = ((image1 - image2) / scale + offset)
@ -208,9 +192,7 @@ def subtract(image1, image2, scale=1.0, offset=0):
def add_modulo(image1, image2): def add_modulo(image1, image2):
"""Add two images, without clipping the result. """Add two images, without clipping the result. ::
.. code-block:: python
out = ((image1 + image2) % MAX) out = ((image1 + image2) % MAX)
@ -223,9 +205,7 @@ def add_modulo(image1, image2):
def subtract_modulo(image1, image2): def subtract_modulo(image1, image2):
"""Subtract two images, without clipping the result. """Subtract two images, without clipping the result. ::
.. code-block:: python
out = ((image1 - image2) % MAX) out = ((image1 - image2) % MAX)
@ -243,9 +223,7 @@ def logical_and(image1, image2):
Both of the images must have mode "1". If you would like to perform a Both of the images must have mode "1". If you would like to perform a
logical AND on an image with a mode other than "1", try logical AND on an image with a mode other than "1", try
:py:meth:`~PIL.ImageChops.multiply` instead, using a black-and-white mask :py:meth:`~PIL.ImageChops.multiply` instead, using a black-and-white mask
as the second image. as the second image. ::
.. code-block:: python
out = ((image1 and image2) % MAX) out = ((image1 and image2) % MAX)
@ -260,9 +238,7 @@ def logical_and(image1, image2):
def logical_or(image1, image2): def logical_or(image1, image2):
"""Logical OR between two images. """Logical OR between two images.
Both of the images must have mode "1". Both of the images must have mode "1". ::
.. code-block:: python
out = ((image1 or image2) % MAX) out = ((image1 or image2) % MAX)
@ -277,9 +253,7 @@ def logical_or(image1, image2):
def logical_xor(image1, image2): def logical_xor(image1, image2):
"""Logical XOR between two images. """Logical XOR between two images.
Both of the images must have mode "1". Both of the images must have mode "1". ::
.. code-block:: python
out = ((bool(image1) != bool(image2)) % MAX) out = ((bool(image1) != bool(image2)) % MAX)

View File

@ -295,29 +295,43 @@ class ImageDraw:
if ink is not None and ink != fill and width != 0: if ink is not None and ink != fill and width != 0:
self.draw.draw_rectangle(xy, ink, 0, width) self.draw.draw_rectangle(xy, ink, 0, width)
def rounded_rectangle(self, xy, radius=0, fill=None, outline=None, width=1): def rounded_rectangle(
self, xy, radius=0, fill=None, outline=None, width=1, *, corners=None
):
"""Draw a rounded rectangle.""" """Draw a rounded rectangle."""
if isinstance(xy[0], (list, tuple)): if isinstance(xy[0], (list, tuple)):
(x0, y0), (x1, y1) = xy (x0, y0), (x1, y1) = xy
else: else:
x0, y0, x1, y1 = xy x0, y0, x1, y1 = xy
if x1 < x0:
msg = "x1 must be greater than or equal to x0"
raise ValueError(msg)
if y1 < y0:
msg = "y1 must be greater than or equal to y0"
raise ValueError(msg)
if corners is None:
corners = (True, True, True, True)
d = radius * 2 d = radius * 2
full_x = d >= x1 - x0 full_x, full_y = False, False
if full_x: if all(corners):
# The two left and two right corners are joined full_x = d >= x1 - x0
d = x1 - x0 if full_x:
full_y = d >= y1 - y0 # The two left and two right corners are joined
if full_y: d = x1 - x0
# The two top and two bottom corners are joined full_y = d >= y1 - y0
d = y1 - y0 if full_y:
if full_x and full_y: # The two top and two bottom corners are joined
# If all corners are joined, that is a circle d = y1 - y0
return self.ellipse(xy, fill, outline, width) if full_x and full_y:
# If all corners are joined, that is a circle
return self.ellipse(xy, fill, outline, width)
if d == 0: if d == 0 or not any(corners):
# If the corners have no curve, that is a rectangle # If the corners have no curve,
# or there are no corners,
# that is a rectangle
return self.rectangle(xy, fill, outline, width) return self.rectangle(xy, fill, outline, width)
r = d // 2 r = d // 2
@ -338,12 +352,17 @@ class ImageDraw:
) )
else: else:
# Draw four separate corners # Draw four separate corners
parts = ( parts = []
((x1 - d, y0, x1, y0 + d), 270, 360), for i, part in enumerate(
((x1 - d, y1 - d, x1, y1), 0, 90), (
((x0, y1 - d, x0 + d, y1), 90, 180), ((x0, y0, x0 + d, y0 + d), 180, 270),
((x0, y0, x0 + d, y0 + d), 180, 270), ((x1 - d, y0, x1, y0 + d), 270, 360),
) ((x1 - d, y1 - d, x1, y1), 0, 90),
((x0, y1 - d, x0 + d, y1), 90, 180),
)
):
if corners[i]:
parts.append(part)
for part in parts: for part in parts:
if pieslice: if pieslice:
self.draw.draw_pieslice(*(part + (fill, 1))) self.draw.draw_pieslice(*(part + (fill, 1)))
@ -358,25 +377,50 @@ class ImageDraw:
else: else:
self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill, 1) self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill, 1)
if not full_x and not full_y: if not full_x and not full_y:
self.draw.draw_rectangle((x0, y0 + r + 1, x0 + r, y1 - r - 1), fill, 1) left = [x0, y0, x0 + r, y1]
self.draw.draw_rectangle((x1 - r, y0 + r + 1, x1, y1 - r - 1), fill, 1) if corners[0]:
left[1] += r + 1
if corners[3]:
left[3] -= r + 1
self.draw.draw_rectangle(left, fill, 1)
right = [x1 - r, y0, x1, y1]
if corners[1]:
right[1] += r + 1
if corners[2]:
right[3] -= r + 1
self.draw.draw_rectangle(right, fill, 1)
if ink is not None and ink != fill and width != 0: if ink is not None and ink != fill and width != 0:
draw_corners(False) draw_corners(False)
if not full_x: if not full_x:
self.draw.draw_rectangle( top = [x0, y0, x1, y0 + width - 1]
(x0 + r + 1, y0, x1 - r - 1, y0 + width - 1), ink, 1 if corners[0]:
) top[0] += r + 1
self.draw.draw_rectangle( if corners[1]:
(x0 + r + 1, y1 - width + 1, x1 - r - 1, y1), ink, 1 top[2] -= r + 1
) self.draw.draw_rectangle(top, ink, 1)
bottom = [x0, y1 - width + 1, x1, y1]
if corners[3]:
bottom[0] += r + 1
if corners[2]:
bottom[2] -= r + 1
self.draw.draw_rectangle(bottom, ink, 1)
if not full_y: if not full_y:
self.draw.draw_rectangle( left = [x0, y0, x0 + width - 1, y1]
(x0, y0 + r + 1, x0 + width - 1, y1 - r - 1), ink, 1 if corners[0]:
) left[1] += r + 1
self.draw.draw_rectangle( if corners[3]:
(x1 - width + 1, y0 + r + 1, x1, y1 - r - 1), ink, 1 left[3] -= r + 1
) self.draw.draw_rectangle(left, ink, 1)
right = [x1 - width + 1, y0, x1, y1]
if corners[1]:
right[1] += r + 1
if corners[2]:
right[3] -= r + 1
self.draw.draw_rectangle(right, ink, 1)
def _multiline_check(self, text): def _multiline_check(self, text):
"""Draw text.""" """Draw text."""

View File

@ -530,20 +530,20 @@ def _encode_tile(im, fp, tile, bufsize, fh, exc=None):
encoder.setimage(im.im, b) encoder.setimage(im.im, b)
if encoder.pushes_fd: if encoder.pushes_fd:
encoder.setfd(fp) encoder.setfd(fp)
l, s = encoder.encode_to_pyfd() errcode = encoder.encode_to_pyfd()[1]
else: else:
if exc: if exc:
# compress to Python file-compatible object # compress to Python file-compatible object
while True: while True:
l, s, d = encoder.encode(bufsize) errcode, data = encoder.encode(bufsize)[1:]
fp.write(d) fp.write(data)
if s: if errcode:
break break
else: else:
# slight speedup: compress to real file object # slight speedup: compress to real file object
s = encoder.encode_to_file(fh, bufsize) errcode = encoder.encode_to_file(fh, bufsize)
if s < 0: if errcode < 0:
msg = f"encoder error {s} when writing image file" msg = f"encoder error {errcode} when writing image file"
raise OSError(msg) from exc raise OSError(msg) from exc
finally: finally:
encoder.cleanup() encoder.cleanup()

View File

@ -297,27 +297,21 @@ class FreeTypeFont:
string due to kerning. If you need to adjust for kerning, include the following string due to kerning. If you need to adjust for kerning, include the following
character and subtract its length. character and subtract its length.
For example, instead of For example, instead of ::
.. code-block:: python
hello = font.getlength("Hello") hello = font.getlength("Hello")
world = font.getlength("World") world = font.getlength("World")
hello_world = hello + world # not adjusted for kerning hello_world = hello + world # not adjusted for kerning
assert hello_world == font.getlength("HelloWorld") # may fail assert hello_world == font.getlength("HelloWorld") # may fail
use use ::
.. code-block:: python
hello = font.getlength("HelloW") - font.getlength("W") # adjusted for kerning hello = font.getlength("HelloW") - font.getlength("W") # adjusted for kerning
world = font.getlength("World") world = font.getlength("World")
hello_world = hello + world # adjusted for kerning hello_world = hello + world # adjusted for kerning
assert hello_world == font.getlength("HelloWorld") # True assert hello_world == font.getlength("HelloWorld") # True
or disable kerning with (requires libraqm) or disable kerning with (requires libraqm) ::
.. code-block:: python
hello = draw.textlength("Hello", font, features=["-kern"]) hello = draw.textlength("Hello", font, features=["-kern"])
world = draw.textlength("World", font, features=["-kern"]) world = draw.textlength("World", font, features=["-kern"])

View File

@ -730,10 +730,10 @@ def _save(im, fp, filename):
extra = info.get("extra", b"") extra = info.get("extra", b"")
MAX_BYTES_IN_MARKER = 65533
icc_profile = info.get("icc_profile") icc_profile = info.get("icc_profile")
if icc_profile: if icc_profile:
ICC_OVERHEAD_LEN = 14 ICC_OVERHEAD_LEN = 14
MAX_BYTES_IN_MARKER = 65533
MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN
markers = [] markers = []
while icc_profile: while icc_profile:
@ -764,6 +764,9 @@ def _save(im, fp, filename):
exif = info.get("exif", b"") exif = info.get("exif", b"")
if isinstance(exif, Image.Exif): if isinstance(exif, Image.Exif):
exif = exif.tobytes() exif = exif.tobytes()
if len(exif) > MAX_BYTES_IN_MARKER:
msg = "EXIF data is too long"
raise ValueError(msg)
# get keyword arguments # get keyword arguments
im.encoderconfig = ( im.encoderconfig = (

View File

@ -86,9 +86,22 @@ class PcfFontFile(FontFile.FontFile):
for ch, ix in enumerate(encoding): for ch, ix in enumerate(encoding):
if ix is not None: if ix is not None:
x, y, l, r, w, a, d, f = metrics[ix] (
glyph = (w, 0), (l, d - y, x + l, d), (0, 0, x, y), bitmaps[ix] xsize,
self.glyph[ch] = glyph ysize,
left,
right,
width,
ascent,
descent,
attributes,
) = metrics[ix]
self.glyph[ch] = (
(width, 0),
(left, descent - ysize, xsize + left, descent),
(0, 0, xsize, ysize),
bitmaps[ix],
)
def _getformat(self, tag): def _getformat(self, tag):
format, size, offset = self.toc[tag] format, size, offset = self.toc[tag]
@ -206,9 +219,11 @@ class PcfFontFile(FontFile.FontFile):
mode = "1" mode = "1"
for i in range(nbitmaps): for i in range(nbitmaps):
x, y, l, r, w, a, d, f = metrics[i] xsize, ysize = metrics[i][:2]
b, e = offsets[i], offsets[i + 1] b, e = offsets[i : i + 2]
bitmaps.append(Image.frombytes("1", (x, y), data[b:e], "raw", mode, pad(x))) bitmaps.append(
Image.frombytes("1", (xsize, ysize), data[b:e], "raw", mode, pad(xsize))
)
return bitmaps return bitmaps

View File

@ -53,7 +53,12 @@ def _save(im, fp, filename, save_all=False):
else: else:
existing_pdf = PdfParser.PdfParser(f=fp, filename=filename, mode="w+b") existing_pdf = PdfParser.PdfParser(f=fp, filename=filename, mode="w+b")
resolution = im.encoderinfo.get("resolution", 72.0) dpi = im.encoderinfo.get("dpi")
if dpi:
x_resolution = dpi[0]
y_resolution = dpi[1]
else:
x_resolution = y_resolution = im.encoderinfo.get("resolution", 72.0)
info = { info = {
"title": None "title": None
@ -214,8 +219,8 @@ def _save(im, fp, filename, save_all=False):
stream=stream, stream=stream,
Type=PdfParser.PdfName("XObject"), Type=PdfParser.PdfName("XObject"),
Subtype=PdfParser.PdfName("Image"), Subtype=PdfParser.PdfName("Image"),
Width=width, # * 72.0 / resolution, Width=width, # * 72.0 / x_resolution,
Height=height, # * 72.0 / resolution, Height=height, # * 72.0 / y_resolution,
Filter=filter, Filter=filter,
BitsPerComponent=bits, BitsPerComponent=bits,
Decode=decode, Decode=decode,
@ -235,8 +240,8 @@ def _save(im, fp, filename, save_all=False):
MediaBox=[ MediaBox=[
0, 0,
0, 0,
width * 72.0 / resolution, width * 72.0 / x_resolution,
height * 72.0 / resolution, height * 72.0 / y_resolution,
], ],
Contents=contents_refs[page_number], Contents=contents_refs[page_number],
) )
@ -245,8 +250,8 @@ def _save(im, fp, filename, save_all=False):
# page contents # page contents
page_contents = b"q %f 0 0 %f 0 0 cm /image Do Q\n" % ( page_contents = b"q %f 0 0 %f 0 0 cm /image Do Q\n" % (
width * 72.0 / resolution, width * 72.0 / x_resolution,
height * 72.0 / resolution, height * 72.0 / y_resolution,
) )
existing_pdf.write_obj(contents_refs[page_number], stream=page_contents) existing_pdf.write_obj(contents_refs[page_number], stream=page_contents)

View File

@ -764,6 +764,8 @@ class ImageFileDirectory_v2(MutableMapping):
@_register_writer(7) @_register_writer(7)
def write_undefined(self, value): def write_undefined(self, value):
if isinstance(value, int):
value = str(value).encode("ascii", "replace")
return value return value
@_register_loader(10, 8) @_register_loader(10, 8)
@ -1843,13 +1845,13 @@ def _save(im, fp, filename):
e.setimage(im.im, (0, 0) + im.size) e.setimage(im.im, (0, 0) + im.size)
while True: while True:
# undone, change to self.decodermaxblock: # undone, change to self.decodermaxblock:
l, s, d = e.encode(16 * 1024) errcode, data = e.encode(16 * 1024)[1:]
if not _fp: if not _fp:
fp.write(d) fp.write(data)
if s: if errcode:
break break
if s < 0: if errcode < 0:
msg = f"encoder error {s} when writing image file" msg = f"encoder error {errcode} when writing image file"
raise OSError(msg) raise OSError(msg)
else: else:

View File

@ -312,7 +312,7 @@ TAGS = {
34910: "HylaFAX FaxRecvTime", 34910: "HylaFAX FaxRecvTime",
36864: "ExifVersion", 36864: "ExifVersion",
36867: "DateTimeOriginal", 36867: "DateTimeOriginal",
36868: "DateTImeDigitized", 36868: "DateTimeDigitized",
37121: "ComponentsConfiguration", 37121: "ComponentsConfiguration",
37122: "CompressedBitsPerPixel", 37122: "CompressedBitsPerPixel",
37724: "ImageSourceData", 37724: "ImageSourceData",

View File

@ -251,6 +251,10 @@ PyImaging_GetBuffer(PyObject *buffer, Py_buffer *view) {
static const char *must_be_sequence = "argument must be a sequence"; static const char *must_be_sequence = "argument must be a sequence";
static const char *must_be_two_coordinates = static const char *must_be_two_coordinates =
"coordinate list must contain exactly 2 coordinates"; "coordinate list must contain exactly 2 coordinates";
static const char *incorrectly_ordered_x_coordinate =
"x1 must be greater than or equal to x0";
static const char *incorrectly_ordered_y_coordinate =
"y1 must be greater than or equal to y0";
static const char *wrong_mode = "unrecognized image mode"; static const char *wrong_mode = "unrecognized image mode";
static const char *wrong_raw_mode = "unrecognized raw mode"; static const char *wrong_raw_mode = "unrecognized raw mode";
static const char *outside_image = "image index out of range"; static const char *outside_image = "image index out of range";
@ -2805,6 +2809,16 @@ _draw_arc(ImagingDrawObject *self, PyObject *args) {
free(xy); free(xy);
return NULL; return NULL;
} }
if (xy[2] < xy[0]) {
PyErr_SetString(PyExc_ValueError, incorrectly_ordered_x_coordinate);
free(xy);
return NULL;
}
if (xy[3] < xy[1]) {
PyErr_SetString(PyExc_ValueError, incorrectly_ordered_y_coordinate);
free(xy);
return NULL;
}
n = ImagingDrawArc( n = ImagingDrawArc(
self->image->image, self->image->image,
@ -2886,6 +2900,16 @@ _draw_chord(ImagingDrawObject *self, PyObject *args) {
free(xy); free(xy);
return NULL; return NULL;
} }
if (xy[2] < xy[0]) {
PyErr_SetString(PyExc_ValueError, incorrectly_ordered_x_coordinate);
free(xy);
return NULL;
}
if (xy[3] < xy[1]) {
PyErr_SetString(PyExc_ValueError, incorrectly_ordered_y_coordinate);
free(xy);
return NULL;
}
n = ImagingDrawChord( n = ImagingDrawChord(
self->image->image, self->image->image,
@ -2932,6 +2956,16 @@ _draw_ellipse(ImagingDrawObject *self, PyObject *args) {
free(xy); free(xy);
return NULL; return NULL;
} }
if (xy[2] < xy[0]) {
PyErr_SetString(PyExc_ValueError, incorrectly_ordered_x_coordinate);
free(xy);
return NULL;
}
if (xy[3] < xy[1]) {
PyErr_SetString(PyExc_ValueError, incorrectly_ordered_y_coordinate);
free(xy);
return NULL;
}
n = ImagingDrawEllipse( n = ImagingDrawEllipse(
self->image->image, self->image->image,
@ -3101,6 +3135,16 @@ _draw_pieslice(ImagingDrawObject *self, PyObject *args) {
free(xy); free(xy);
return NULL; return NULL;
} }
if (xy[2] < xy[0]) {
PyErr_SetString(PyExc_ValueError, incorrectly_ordered_x_coordinate);
free(xy);
return NULL;
}
if (xy[3] < xy[1]) {
PyErr_SetString(PyExc_ValueError, incorrectly_ordered_y_coordinate);
free(xy);
return NULL;
}
n = ImagingDrawPieslice( n = ImagingDrawPieslice(
self->image->image, self->image->image,
@ -3197,6 +3241,16 @@ _draw_rectangle(ImagingDrawObject *self, PyObject *args) {
free(xy); free(xy);
return NULL; return NULL;
} }
if (xy[2] < xy[0]) {
PyErr_SetString(PyExc_ValueError, incorrectly_ordered_x_coordinate);
free(xy);
return NULL;
}
if (xy[3] < xy[1]) {
PyErr_SetString(PyExc_ValueError, incorrectly_ordered_y_coordinate);
free(xy);
return NULL;
}
n = ImagingDrawRectangle( n = ImagingDrawRectangle(
self->image->image, self->image->image,
@ -3984,8 +4038,6 @@ PyImaging_GrabScreenWin32(PyObject *self, PyObject *args);
extern PyObject * extern PyObject *
PyImaging_GrabClipboardWin32(PyObject *self, PyObject *args); PyImaging_GrabClipboardWin32(PyObject *self, PyObject *args);
extern PyObject * extern PyObject *
PyImaging_ListWindowsWin32(PyObject *self, PyObject *args);
extern PyObject *
PyImaging_EventLoopWin32(PyObject *self, PyObject *args); PyImaging_EventLoopWin32(PyObject *self, PyObject *args);
extern PyObject * extern PyObject *
PyImaging_DrawWmf(PyObject *self, PyObject *args); PyImaging_DrawWmf(PyObject *self, PyObject *args);
@ -4069,7 +4121,6 @@ static PyMethodDef functions[] = {
{"grabclipboard_win32", (PyCFunction)PyImaging_GrabClipboardWin32, METH_VARARGS}, {"grabclipboard_win32", (PyCFunction)PyImaging_GrabClipboardWin32, METH_VARARGS},
{"createwindow", (PyCFunction)PyImaging_CreateWindowWin32, METH_VARARGS}, {"createwindow", (PyCFunction)PyImaging_CreateWindowWin32, METH_VARARGS},
{"eventloop", (PyCFunction)PyImaging_EventLoopWin32, METH_VARARGS}, {"eventloop", (PyCFunction)PyImaging_EventLoopWin32, METH_VARARGS},
{"listwindows", (PyCFunction)PyImaging_ListWindowsWin32, METH_VARARGS},
{"drawwmf", (PyCFunction)PyImaging_DrawWmf, METH_VARARGS}, {"drawwmf", (PyCFunction)PyImaging_DrawWmf, METH_VARARGS},
#endif #endif
#ifdef HAVE_XCB #ifdef HAVE_XCB

View File

@ -116,12 +116,11 @@ _dealloc(ImagingDecoderObject *decoder) {
static PyObject * static PyObject *
_decode(ImagingDecoderObject *decoder, PyObject *args) { _decode(ImagingDecoderObject *decoder, PyObject *args) {
UINT8 *buffer; Py_buffer buffer;
Py_ssize_t bufsize;
int status; int status;
ImagingSectionCookie cookie; ImagingSectionCookie cookie;
if (!PyArg_ParseTuple(args, "y#", &buffer, &bufsize)) { if (!PyArg_ParseTuple(args, "y*", &buffer)) {
return NULL; return NULL;
} }
@ -129,12 +128,13 @@ _decode(ImagingDecoderObject *decoder, PyObject *args) {
ImagingSectionEnter(&cookie); ImagingSectionEnter(&cookie);
} }
status = decoder->decode(decoder->im, &decoder->state, buffer, bufsize); status = decoder->decode(decoder->im, &decoder->state, buffer.buf, buffer.len);
if (!decoder->pulls_fd) { if (!decoder->pulls_fd) {
ImagingSectionLeave(&cookie); ImagingSectionLeave(&cookie);
} }
PyBuffer_Release(&buffer);
return Py_BuildValue("ii", status, decoder->state.errcode); return Py_BuildValue("ii", status, decoder->state.errcode);
} }

View File

@ -421,79 +421,6 @@ error:
return NULL; return NULL;
} }
static BOOL CALLBACK
list_windows_callback(HWND hwnd, LPARAM lParam) {
PyObject *window_list = (PyObject *)lParam;
PyObject *item;
PyObject *title;
RECT inner, outer;
int title_size;
int status;
/* get window title */
title_size = GetWindowTextLength(hwnd);
if (title_size > 0) {
title = PyUnicode_FromStringAndSize(NULL, title_size);
if (title) {
GetWindowTextW(hwnd, PyUnicode_AS_UNICODE(title), title_size + 1);
}
} else {
title = PyUnicode_FromString("");
}
if (!title) {
return 0;
}
/* get bounding boxes */
GetClientRect(hwnd, &inner);
GetWindowRect(hwnd, &outer);
item = Py_BuildValue(
F_HANDLE "N(iiii)(iiii)",
hwnd,
title,
inner.left,
inner.top,
inner.right,
inner.bottom,
outer.left,
outer.top,
outer.right,
outer.bottom);
if (!item) {
return 0;
}
status = PyList_Append(window_list, item);
Py_DECREF(item);
if (status < 0) {
return 0;
}
return 1;
}
PyObject *
PyImaging_ListWindowsWin32(PyObject *self, PyObject *args) {
PyObject *window_list;
window_list = PyList_New(0);
if (!window_list) {
return NULL;
}
EnumWindows(list_windows_callback, (LPARAM)window_list);
if (PyErr_Occurred()) {
Py_DECREF(window_list);
return NULL;
}
return window_list;
}
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
/* Windows clipboard grabber */ /* Windows clipboard grabber */

View File

@ -85,25 +85,22 @@ point32(Imaging im, int x, int y, int ink) {
static inline void static inline void
point32rgba(Imaging im, int x, int y, int ink) { point32rgba(Imaging im, int x, int y, int ink) {
unsigned int tmp1; unsigned int tmp;
if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) { if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) {
UINT8 *out = (UINT8 *)im->image[y] + x * 4; UINT8 *out = (UINT8 *)im->image[y] + x * 4;
UINT8 *in = (UINT8 *)&ink; UINT8 *in = (UINT8 *)&ink;
out[0] = BLEND(in[3], out[0], in[0], tmp1); out[0] = BLEND(in[3], out[0], in[0], tmp);
out[1] = BLEND(in[3], out[1], in[1], tmp1); out[1] = BLEND(in[3], out[1], in[1], tmp);
out[2] = BLEND(in[3], out[2], in[2], tmp1); out[2] = BLEND(in[3], out[2], in[2], tmp);
} }
} }
static inline void static inline void
hline8(Imaging im, int x0, int y0, int x1, int ink) { hline8(Imaging im, int x0, int y0, int x1, int ink) {
int tmp, pixelwidth; int pixelwidth;
if (y0 >= 0 && y0 < im->ysize) { if (y0 >= 0 && y0 < im->ysize) {
if (x0 > x1) {
tmp = x0, x0 = x1, x1 = tmp;
}
if (x0 < 0) { if (x0 < 0) {
x0 = 0; x0 = 0;
} else if (x0 >= im->xsize) { } else if (x0 >= im->xsize) {
@ -126,13 +123,9 @@ hline8(Imaging im, int x0, int y0, int x1, int ink) {
static inline void static inline void
hline32(Imaging im, int x0, int y0, int x1, int ink) { hline32(Imaging im, int x0, int y0, int x1, int ink) {
int tmp;
INT32 *p; INT32 *p;
if (y0 >= 0 && y0 < im->ysize) { if (y0 >= 0 && y0 < im->ysize) {
if (x0 > x1) {
tmp = x0, x0 = x1, x1 = tmp;
}
if (x0 < 0) { if (x0 < 0) {
x0 = 0; x0 = 0;
} else if (x0 >= im->xsize) { } else if (x0 >= im->xsize) {
@ -152,13 +145,9 @@ hline32(Imaging im, int x0, int y0, int x1, int ink) {
static inline void static inline void
hline32rgba(Imaging im, int x0, int y0, int x1, int ink) { hline32rgba(Imaging im, int x0, int y0, int x1, int ink) {
int tmp; unsigned int tmp;
unsigned int tmp1;
if (y0 >= 0 && y0 < im->ysize) { if (y0 >= 0 && y0 < im->ysize) {
if (x0 > x1) {
tmp = x0, x0 = x1, x1 = tmp;
}
if (x0 < 0) { if (x0 < 0) {
x0 = 0; x0 = 0;
} else if (x0 >= im->xsize) { } else if (x0 >= im->xsize) {
@ -173,9 +162,9 @@ hline32rgba(Imaging im, int x0, int y0, int x1, int ink) {
UINT8 *out = (UINT8 *)im->image[y0] + x0 * 4; UINT8 *out = (UINT8 *)im->image[y0] + x0 * 4;
UINT8 *in = (UINT8 *)&ink; UINT8 *in = (UINT8 *)&ink;
while (x0 <= x1) { while (x0 <= x1) {
out[0] = BLEND(in[3], out[0], in[0], tmp1); out[0] = BLEND(in[3], out[0], in[0], tmp);
out[1] = BLEND(in[3], out[1], in[1], tmp1); out[1] = BLEND(in[3], out[1], in[1], tmp);
out[2] = BLEND(in[3], out[2], in[2], tmp1); out[2] = BLEND(in[3], out[2], in[2], tmp);
x0++; x0++;
out += 4; out += 4;
} }

View File

@ -96,9 +96,7 @@ directory.
Example Example
------- -------
The following is a simplified version of the script used on AppVeyor: The following is a simplified version of the script used on AppVeyor::
.. code-block::
set PYTHON=C:\Python38\bin set PYTHON=C:\Python38\bin
cd /D C:\Pillow\winbuild cd /D C:\Pillow\winbuild

View File

@ -109,9 +109,9 @@ header = [
deps = { deps = {
"libjpeg": { "libjpeg": {
"url": SF_PROJECTS "url": SF_PROJECTS
+ "/libjpeg-turbo/files/2.1.5/libjpeg-turbo-2.1.5.tar.gz/download", + "/libjpeg-turbo/files/2.1.5.1/libjpeg-turbo-2.1.5.1.tar.gz/download",
"filename": "libjpeg-turbo-2.1.5.tar.gz", "filename": "libjpeg-turbo-2.1.5.1.tar.gz",
"dir": "libjpeg-turbo-2.1.5", "dir": "libjpeg-turbo-2.1.5.1",
"license": ["README.ijg", "LICENSE.md"], "license": ["README.ijg", "LICENSE.md"],
"license_pattern": ( "license_pattern": (
"(LEGAL ISSUES\n============\n\n.+?)\n\nREFERENCES\n==========" "(LEGAL ISSUES\n============\n\n.+?)\n\nREFERENCES\n=========="
@ -253,9 +253,9 @@ deps = {
"libs": ["*.lib"], "libs": ["*.lib"],
}, },
"freetype": { "freetype": {
"url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.12.1.tar.gz", # noqa: E501 "url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.13.0.tar.gz", # noqa: E501
"filename": "freetype-2.12.1.tar.gz", "filename": "freetype-2.13.0.tar.gz",
"dir": "freetype-2.12.1", "dir": "freetype-2.13.0",
"license": ["LICENSE.TXT", r"docs\FTL.TXT", r"docs\GPLv2.TXT"], "license": ["LICENSE.TXT", r"docs\FTL.TXT", r"docs\GPLv2.TXT"],
"patch": { "patch": {
r"builds\windows\vc2010\freetype.vcxproj": { r"builds\windows\vc2010\freetype.vcxproj": {
@ -289,9 +289,9 @@ deps = {
# "bins": [r"objs\{msbuild_arch}\Release\freetype.dll"], # "bins": [r"objs\{msbuild_arch}\Release\freetype.dll"],
}, },
"lcms2": { "lcms2": {
"url": SF_PROJECTS + "/lcms/files/lcms/2.14/lcms2-2.14.tar.gz/download", "url": SF_PROJECTS + "/lcms/files/lcms/2.15/lcms2-2.15.tar.gz/download",
"filename": "lcms2-2.14.tar.gz", "filename": "lcms2-2.15.tar.gz",
"dir": "lcms2-2.14", "dir": "lcms2-2.15",
"license": "COPYING", "license": "COPYING",
"patch": { "patch": {
r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": { r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": {
@ -356,9 +356,9 @@ deps = {
"libs": [r"imagequant.lib"], "libs": [r"imagequant.lib"],
}, },
"harfbuzz": { "harfbuzz": {
"url": "https://github.com/harfbuzz/harfbuzz/archive/6.0.0.zip", "url": "https://github.com/harfbuzz/harfbuzz/archive/7.0.1.zip",
"filename": "harfbuzz-6.0.0.zip", "filename": "harfbuzz-7.0.1.zip",
"dir": "harfbuzz-6.0.0", "dir": "harfbuzz-7.0.1",
"license": "COPYING", "license": "COPYING",
"build": [ "build": [
cmd_set("CXXFLAGS", "-d2FH4-"), cmd_set("CXXFLAGS", "-d2FH4-"),