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:
- '%PYTHON%\%EXECUTABLE% --version'
- 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-test-images.zip -oc:\
- 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:\
- ..\pillow-depends\gs1000w32.exe /S
- 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
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
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 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
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 }}-tkinter
qt5-devel-tools
subversion
wget
xorg-server-extra
zlib-devel

View File

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

View File

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

View File

@ -15,7 +15,7 @@ jobs:
strategy:
fail-fast: false
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"]
include:
# PyPy 7.3.4+ only ships 64-bit binaries for Windows
@ -38,6 +38,12 @@ jobs:
repository: python-pillow/pillow-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
- name: Set up Python
uses: actions/setup-python@v4
@ -62,7 +68,8 @@ jobs:
winbuild\depends\gs1000w32.exe /S
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
& "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" `

View File

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

2
.gitignore vendored
View File

@ -79,7 +79,7 @@ docs/_build/
# JetBrains
.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/crash_1.tif
Tests/images/crash_2.tif

View File

@ -5,6 +5,27 @@ Changelog (Pillow)
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
[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
with the following terms and conditions:
Permission to use, copy, modify, and distribute this software and its
associated documentation for any purpose and without fee is hereby granted,
Permission to use, copy, modify and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appears in all copies, and that
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

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
def test_comment():
image_data = b"SIMPLE = T / comment string"
with pytest.raises(OSError):
FitsImagePlugin.FitsImageFile(BytesIO(image_data))
def test_stub_deprecated():
class Handler:
opened = False

View File

@ -270,7 +270,10 @@ class TestFileJpeg:
# https://github.com/python-pillow/Pillow/issues/148
f = str(tmp_path / "temp.jpg")
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):
with Image.open("Tests/images/exif_typeerror.jpg") as im:
@ -445,7 +448,7 @@ class TestFileJpeg:
ims = im.get_child_images()
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):
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)
@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(
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"
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):
# Check that the tag has not been changed since this test was created
tag = TiffTags.TAGS_V2[45059]

View File

@ -1,10 +1,17 @@
import pytest
from PIL import Image
from .helper import assert_image_equal, hopper
def test_sanity():
@pytest.mark.parametrize("data_type", ("bytes", "memoryview"))
def test_sanity(data_type):
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)

View File

@ -355,7 +355,13 @@ def ellipse_various_sizes_helper(filled):
for w in ellipse_sizes:
y = 1
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:
draw.ellipse(border, fill="white")
else:
@ -735,6 +741,36 @@ def test_rounded_rectangle(xy):
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(
"xy, radius, type",
[
@ -902,9 +938,6 @@ def test_square():
img, draw = create_base_image_draw((10, 10))
draw.rectangle((2, 2, 7, 7), BLACK)
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():
@ -1469,3 +1502,21 @@ def test_polygon2():
draw.polygon([(18, 30), (19, 31), (18, 30), (85, 30), (60, 72)], "red")
expected = "Tests/images/imagedraw_outline_polygon_RGB.png"
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):
result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected))
# 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")

View File

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

View File

@ -1,15 +1,12 @@
#!/bin/bash
#!/usr/bin/env bash
# install extra test images
# Use SVN to just fetch a single Git subdirectory
svn_export()
{
if [ ! -z $1 ]; then
echo ""
echo "Retrying svn export..."
echo ""
fi
archive=test-images-main
svn export --force https://github.com/python-pillow/pillow-depends/trunk/test_images ../Tests/images
}
svn_export || svn_export retry || svn_export retry || svn_export retry
./download-and-extract.sh $archive https://github.com/python-pillow/test-images/archive/main.tar.gz
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`
=========================================================================== =============================================================================================================
Previous code:
.. code-block:: python
Previous code::
from PIL import Image, ImageDraw, ImageFont
@ -204,9 +202,7 @@ Previous code:
width, height = font.getsize_multiline("Hello\nworld")
width, height = draw.multiline_textsize("Hello\nworld")
Use instead:
.. code-block:: python
Use instead::
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
deterministic way.
Previous method:
.. code-block:: python
Previous method::
im = Image.open("hopper.png")
im.save("out.jpg")
Use instead:
.. code-block:: python
Use instead::
with Image.open("hopper.png") as im:
im.save("out.jpg")

View File

@ -1104,7 +1104,7 @@ using the general tags available through tiffinfo.
Either an integer or a float.
**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
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.
**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
of effort put into the compression: 0 is the fastest, but gives larger
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 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
~~~~~~~~~~~~~~~~
@ -1389,9 +1393,7 @@ WMF, EMF
Pillow can identify WMF and EMF files.
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:
.. code-block:: python
at 72 dpi. To load it at another resolution::
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
:py:func:`PIL.WmfImagePlugin.register_handler` to register a WMF and EMF
handler.
.. code-block:: python
handler. ::
from PIL import Image
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
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**
The documents title. If not appending to an existing PDF file, this will
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.
:align: left
.. code-block:: python
::
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
idea to register any extensions used by this format.
Once the plugin has been imported, it can be used:
.. code-block:: python
Once the plugin has been imported, it can be used::
from PIL import Image
import SpamImagePlugin
@ -169,9 +167,7 @@ The raw decoder
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
TIFF, and many others. To use the raw decoder with the
:py:func:`PIL.Image.frombytes` function, use the following syntax:
.. code-block:: python
:py:func:`PIL.Image.frombytes` function, use the following syntax::
image = Image.frombytes(
mode, size, data, "raw",
@ -281,9 +277,7 @@ decoder that can be used to read various packed formats into a floating point
image memory.
To use the bit decoder with the :py:func:`PIL.Image.frombytes` function, use
the following syntax:
.. code-block:: python
the following syntax::
image = Image.frombytes(
mode, size, data, "bit",

View File

@ -150,7 +150,7 @@ Many of Pillow's features require external libraries:
* **littlecms** provides color management
* 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.
@ -442,21 +442,23 @@ These platforms are built and tested for every change.
+----------------------------------+----------------------------+---------------------+
| Gentoo | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64 |
| | PyPy3 | |
| macOS 12 Monterey | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64 |
| | 3.12, PyPy3 | |
+----------------------------------+----------------------------+---------------------+
| 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 |
| | PyPy3 | |
| Ubuntu Linux 20.04 LTS (Focal) | 3.8 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | arm64v8, ppc64le, |
| | | s390x, x86-64 |
| Ubuntu Linux 22.04 LTS (Jammy) | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64 |
| | 3.12, PyPy3 | |
| +----------------------------+---------------------+
| | 3.10 | arm64v8, ppc64le, |
| | | s390x |
+----------------------------------+----------------------------+---------------------+
| Windows Server 2016 | 3.7 | 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 |
| +----------------------------+---------------------+

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
using an external viewer (usually xv on Unix, and the Paint program on
Windows).
.. code-block:: python
Windows). ::
from PIL import Image
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
current directory preserving aspect ratios with 128x128 max resolution.
.. code-block:: python
current directory preserving aspect ratios with 128x128 max resolution. ::
from PIL import Image
import glob, os
@ -127,9 +123,7 @@ methods. Unless otherwise stated, all methods return a new instance of the
.. automethod:: PIL.Image.Image.convert
The following example converts an RGB image (linearly calibrated according to
ITU-R 709, using the D65 luminant) to the CIE XYZ color space:
.. code-block:: python
ITU-R 709, using the D65 luminant) to the CIE XYZ color space::
rgb2xyz = (
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.crop
This crops the input image with the provided coordinates:
.. code-block:: python
This crops the input image with the provided coordinates::
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.filter
This blurs the input image using a filter from the ``ImageFilter`` module:
.. code-block:: python
This blurs the input image using a filter from the ``ImageFilter`` module::
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.getbands
This helps to get the bands of the input image:
.. code-block:: python
This helps to get the bands of the input image::
from PIL import Image
@ -187,9 +175,7 @@ This helps to get the bands of the input image:
.. automethod:: PIL.Image.Image.getbbox
This helps to get the bounding box coordinates of the input image:
.. code-block:: python
This helps to get the bounding box coordinates of the input 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.resize
This resizes the given image from ``(width, height)`` to ``(width/2, height/2)``:
.. code-block:: python
This resizes the given image from ``(width, height)`` to ``(width/2, height/2)``::
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
This rotates the input image by ``theta`` degrees counter clockwise:
.. code-block:: python
This rotates the input image by ``theta`` degrees counter clockwise::
from PIL import Image
@ -256,9 +238,7 @@ This rotates the input image by ``theta`` degrees counter clockwise:
.. automethod:: PIL.Image.Image.transpose
This flips the input image by using the :data:`Transpose.FLIP_LEFT_RIGHT`
method.
.. code-block:: python
method. ::
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
----------------------------------------
.. code-block:: python
::
import sys
from PIL import Image, ImageDraw
@ -78,7 +78,7 @@ libraries, and may not available in all PIL builds.
Example: Draw Partial Opacity Text
----------------------------------
.. code-block:: python
::
from PIL import Image, ImageDraw, ImageFont
@ -105,7 +105,7 @@ Example: Draw Partial Opacity Text
Example: Draw Multiline Text
----------------------------
.. code-block:: python
::
from PIL import Image, ImageDraw, ImageFont
@ -318,8 +318,8 @@ Methods
Draws a rectangle.
:param xy: Two points to define the bounding box. Sequence of either
``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. The bounding box
is inclusive of both endpoints.
``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0`` and
``y1 >= y0``. The bounding box is inclusive of both endpoints.
:param outline: Color to use for the outline.
:param fill: Color to use for the fill.
:param width: The line width, in pixels.
@ -331,12 +331,14 @@ Methods
Draws a rounded rectangle.
:param xy: Two points to define the bounding box. Sequence of either
``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. The bounding box
is inclusive of both endpoints.
``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0`` and
``y1 >= y0``. The bounding box is inclusive of both endpoints.
:param radius: Radius of the corners.
:param outline: Color to use for the outline.
:param fill: Color to use for the fill.
: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
@ -597,18 +599,14 @@ Methods
string due to kerning. If you need to adjust for kerning, include the following
character and subtract its length.
For example, instead of
.. code-block:: python
For example, instead of ::
hello = draw.textlength("Hello", font)
world = draw.textlength("World", font)
hello_world = hello + world # not adjusted for kerning
assert hello_world == draw.textlength("HelloWorld", font) # may fail
use
.. code-block:: python
use ::
hello = draw.textlength("HelloW", font) - draw.textlength(
"W", font
@ -617,9 +615,7 @@ Methods
hello_world = hello + world # adjusted for kerning
assert hello_world == draw.textlength("HelloWorld", font) # True
or disable kerning with (requires libraqm)
.. code-block:: python
or disable kerning with (requires libraqm) ::
hello = draw.textlength("Hello", 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
---------------------------------------
.. code-block:: python
::
from PIL import ImageEnhance

View File

@ -15,7 +15,7 @@ and **xmllib** modules.
Example: Parse an image
-----------------------
.. code-block:: python
::
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
------------------------
.. code-block:: python
::
from PIL import ImageFilter

View File

@ -21,7 +21,7 @@ the imToolkit package.
Example
-------
.. code-block:: python
::
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
--------------------------------------------------
.. code-block:: python
::
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)
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:
.. code-block:: python
6-tuple (a, b, c, d, e, f), and each point is mapped as follows::
xOut = xIn * a + yIn * b + c
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
-----------------------------------
.. code-block:: python
::
from PIL import Image, ImageSequence

View File

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

View File

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

View File

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

View File

@ -61,9 +61,7 @@ Image Lifecycle
* ``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 core image object. e.g.:
.. code-block:: python
the core image object. e.g.::
with Image.open("test.jpg") as img:
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
deterministic way.
Deprecated:
.. code-block:: python
Deprecated::
im = Image.open("hopper.png")
im.save("out.jpg")
Use instead:
.. code-block:: python
Use instead::
with Image.open("hopper.png") as im:
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
value from 0 (worst) to 100 (best) controls the image quality, similar to the JPEG
encoder. The default is 75. For example:
.. code-block:: python
encoder. The default is 75. For example::
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
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
the ``fill`` parameter.
.. code-block:: python
the ``fill`` parameter. ::
from PIL import Image, ImageDraw, ImageFont
@ -28,9 +26,7 @@ the ``fill`` parameter.
draw.multiline_text((10, 10), "A\nB", "#f00", font,
stroke_width=2, stroke_fill="#0f0")
For example,
.. code-block:: python
For example, ::
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
now also be loaded at another resolution:
.. code-block:: python
now also be loaded at another resolution::
from PIL import Image
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
the file in a deterministic way.
Previous method:
.. code-block:: python
Previous method::
im = Image.open("hopper.png")
im.save("out.jpg")
Use instead:
.. code-block:: python
Use instead::
with Image.open("hopper.png") as im:
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
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
been resolved.
.. code-block:: python
been resolved. ::
from PIL import Image
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
: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
create a circle, but not any other ellipse.
.. code-block:: python
create a circle, but not any other ellipse. ::
from PIL import Image, ImageDraw
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",
generating an RGBA image with a transparent background instead of an RGB image with a
white background.
.. code-block:: python
white background. ::
with Image.open("sample.eps") as im:
im.load(transparency=True)

View File

@ -155,9 +155,7 @@ altered slightly with this change.
Added support for pickling TrueType fonts
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TrueType fonts may now be pickled and unpickled. For example:
.. code-block:: python
TrueType fonts may now be pickled and unpickled. For example::
import pickle
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
behaviour can now be changed so that the first ``P`` frame is converted to ``RGB`` as
well.
.. code-block:: python
well. ::
from PIL import GifImagePlugin
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS
Or subsequent frames can be kept in ``P`` mode as long as there is only a single
palette.
.. code-block:: python
palette. ::
from PIL import GifImagePlugin
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`
=========================================================================== =============================================================================================================
Previous code:
.. code-block:: python
Previous code::
from PIL import Image, ImageDraw, ImageFont
@ -76,9 +74,7 @@ Previous code:
width, height = font.getsize_multiline("Hello\nworld")
width, height = draw.multiline_textsize("Hello\nworld")
Use instead:
.. code-block:: python
Use instead::
from PIL import Image, ImageDraw, ImageFont

View File

@ -64,16 +64,27 @@ def bdf_char(f):
bitmap.append(s[:-1])
bitmap = b"".join(bitmap)
[x, y, l, d] = [int(p) for p in props["BBX"].split()]
[dx, dy] = [int(p) for p in props["DWIDTH"].split()]
# The word BBX
# 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:
im = Image.frombytes("1", (x, y), bitmap, "hex", "1")
im = Image.frombytes("1", (width, height), bitmap, "hex", "1")
except ValueError:
# deal with zero-width characters
im = Image.new("1", (x, y))
im = Image.new("1", (width, height))
return id, int(props["ENCODING"]), bbox, im

View File

@ -32,7 +32,7 @@ class FitsImageFile(ImageFile.ImageFile):
keyword = header[:8].strip()
if keyword == b"END":
break
value = header[8:].strip()
value = header[8:].split(b"/")[0].strip()
if value.startswith(b"="):
value = value[1:].strip()
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
data = []
output = []
while True:
l, s, d = e.encode(bufsize)
data.append(d)
if s:
bytes_consumed, errcode, data = e.encode(bufsize)
output.append(data)
if errcode:
break
if s < 0:
msg = f"encoder error {s} in tobytes"
if errcode < 0:
msg = f"encoder error {errcode} in tobytes"
raise RuntimeError(msg)
return b"".join(data)
return b"".join(output)
def tobitmap(self, name="image"):
"""
@ -1432,6 +1432,11 @@ class Image:
return {get_name(root.tag): get_value(root)}
def getexif(self):
"""
Gets EXIF data from the image.
:returns: an :py:class:`~PIL.Image.Exif` object.
"""
if self._exif is None:
self._exif = Exif()
self._exif._loaded = False
@ -3601,6 +3606,39 @@ atexit.register(core.clear_cache)
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
bigtiff = False

View File

@ -38,9 +38,7 @@ def duplicate(image):
def invert(image):
"""
Invert an image (channel).
.. code-block:: python
Invert an image (channel). ::
out = MAX - image
@ -54,9 +52,7 @@ def invert(image):
def lighter(image1, image2):
"""
Compares the two images, pixel by pixel, and returns a new image containing
the lighter values.
.. code-block:: python
the lighter values. ::
out = max(image1, image2)
@ -71,9 +67,7 @@ def lighter(image1, image2):
def darker(image1, image2):
"""
Compares the two images, pixel by pixel, and returns a new image containing
the darker values.
.. code-block:: python
the darker values. ::
out = min(image1, image2)
@ -88,9 +82,7 @@ def darker(image1, image2):
def difference(image1, image2):
"""
Returns the absolute value of the pixel-by-pixel difference between the two
images.
.. code-block:: python
images. ::
out = abs(image1 - image2)
@ -107,9 +99,7 @@ def multiply(image1, image2):
Superimposes two images on top of each other.
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.
.. code-block:: python
you multiply with a solid white image, the image is unaffected. ::
out = image1 * image2 / MAX
@ -123,9 +113,7 @@ def multiply(image1, image2):
def screen(image1, image2):
"""
Superimposes two inverted images on top of each other.
.. code-block:: python
Superimposes two inverted images on top of each other. ::
out = MAX - ((MAX - image1) * (MAX - image2) / MAX)
@ -176,9 +164,7 @@ def overlay(image1, image2):
def add(image1, image2, scale=1.0, offset=0):
"""
Adds two images, dividing the result by scale and adding the
offset. If omitted, scale defaults to 1.0, and offset to 0.0.
.. code-block:: python
offset. If omitted, scale defaults to 1.0, and offset to 0.0. ::
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):
"""
Subtracts two images, dividing the result by scale and adding the offset.
If omitted, scale defaults to 1.0, and offset to 0.0.
.. code-block:: python
If omitted, scale defaults to 1.0, and offset to 0.0. ::
out = ((image1 - image2) / scale + offset)
@ -208,9 +192,7 @@ def subtract(image1, image2, scale=1.0, offset=0):
def add_modulo(image1, image2):
"""Add two images, without clipping the result.
.. code-block:: python
"""Add two images, without clipping the result. ::
out = ((image1 + image2) % MAX)
@ -223,9 +205,7 @@ def add_modulo(image1, image2):
def subtract_modulo(image1, image2):
"""Subtract two images, without clipping the result.
.. code-block:: python
"""Subtract two images, without clipping the result. ::
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
logical AND on an image with a mode other than "1", try
:py:meth:`~PIL.ImageChops.multiply` instead, using a black-and-white mask
as the second image.
.. code-block:: python
as the second image. ::
out = ((image1 and image2) % MAX)
@ -260,9 +238,7 @@ def logical_and(image1, image2):
def logical_or(image1, image2):
"""Logical OR between two images.
Both of the images must have mode "1".
.. code-block:: python
Both of the images must have mode "1". ::
out = ((image1 or image2) % MAX)
@ -277,9 +253,7 @@ def logical_or(image1, image2):
def logical_xor(image1, image2):
"""Logical XOR between two images.
Both of the images must have mode "1".
.. code-block:: python
Both of the images must have mode "1". ::
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:
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."""
if isinstance(xy[0], (list, tuple)):
(x0, y0), (x1, y1) = xy
else:
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
full_x = d >= x1 - x0
if full_x:
# The two left and two right corners are joined
d = x1 - x0
full_y = d >= y1 - y0
if full_y:
# The two top and two bottom corners are joined
d = y1 - y0
if full_x and full_y:
# If all corners are joined, that is a circle
return self.ellipse(xy, fill, outline, width)
full_x, full_y = False, False
if all(corners):
full_x = d >= x1 - x0
if full_x:
# The two left and two right corners are joined
d = x1 - x0
full_y = d >= y1 - y0
if full_y:
# The two top and two bottom corners are joined
d = y1 - y0
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 the corners have no curve, that is a rectangle
if d == 0 or not any(corners):
# If the corners have no curve,
# or there are no corners,
# that is a rectangle
return self.rectangle(xy, fill, outline, width)
r = d // 2
@ -338,12 +352,17 @@ class ImageDraw:
)
else:
# Draw four separate corners
parts = (
((x1 - d, y0, x1, y0 + d), 270, 360),
((x1 - d, y1 - d, x1, y1), 0, 90),
((x0, y1 - d, x0 + d, y1), 90, 180),
((x0, y0, x0 + d, y0 + d), 180, 270),
)
parts = []
for i, part in enumerate(
(
((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:
if pieslice:
self.draw.draw_pieslice(*(part + (fill, 1)))
@ -358,25 +377,50 @@ class ImageDraw:
else:
self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill, 1)
if not full_x and not full_y:
self.draw.draw_rectangle((x0, y0 + r + 1, x0 + r, y1 - r - 1), fill, 1)
self.draw.draw_rectangle((x1 - r, y0 + r + 1, x1, y1 - r - 1), fill, 1)
left = [x0, y0, x0 + r, y1]
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:
draw_corners(False)
if not full_x:
self.draw.draw_rectangle(
(x0 + r + 1, y0, x1 - r - 1, y0 + width - 1), ink, 1
)
self.draw.draw_rectangle(
(x0 + r + 1, y1 - width + 1, x1 - r - 1, y1), ink, 1
)
top = [x0, y0, x1, y0 + width - 1]
if corners[0]:
top[0] += r + 1
if corners[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:
self.draw.draw_rectangle(
(x0, y0 + r + 1, x0 + width - 1, y1 - r - 1), ink, 1
)
self.draw.draw_rectangle(
(x1 - width + 1, y0 + r + 1, x1, y1 - r - 1), ink, 1
)
left = [x0, y0, x0 + width - 1, y1]
if corners[0]:
left[1] += r + 1
if corners[3]:
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):
"""Draw text."""

View File

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

View File

@ -297,27 +297,21 @@ class FreeTypeFont:
string due to kerning. If you need to adjust for kerning, include the following
character and subtract its length.
For example, instead of
.. code-block:: python
For example, instead of ::
hello = font.getlength("Hello")
world = font.getlength("World")
hello_world = hello + world # not adjusted for kerning
assert hello_world == font.getlength("HelloWorld") # may fail
use
.. code-block:: python
use ::
hello = font.getlength("HelloW") - font.getlength("W") # adjusted for kerning
world = font.getlength("World")
hello_world = hello + world # adjusted for kerning
assert hello_world == font.getlength("HelloWorld") # True
or disable kerning with (requires libraqm)
.. code-block:: python
or disable kerning with (requires libraqm) ::
hello = draw.textlength("Hello", 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"")
MAX_BYTES_IN_MARKER = 65533
icc_profile = info.get("icc_profile")
if icc_profile:
ICC_OVERHEAD_LEN = 14
MAX_BYTES_IN_MARKER = 65533
MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN
markers = []
while icc_profile:
@ -764,6 +764,9 @@ def _save(im, fp, filename):
exif = info.get("exif", b"")
if isinstance(exif, Image.Exif):
exif = exif.tobytes()
if len(exif) > MAX_BYTES_IN_MARKER:
msg = "EXIF data is too long"
raise ValueError(msg)
# get keyword arguments
im.encoderconfig = (

View File

@ -86,9 +86,22 @@ class PcfFontFile(FontFile.FontFile):
for ch, ix in enumerate(encoding):
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]
self.glyph[ch] = glyph
(
xsize,
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):
format, size, offset = self.toc[tag]
@ -206,9 +219,11 @@ class PcfFontFile(FontFile.FontFile):
mode = "1"
for i in range(nbitmaps):
x, y, l, r, w, a, d, f = metrics[i]
b, e = offsets[i], offsets[i + 1]
bitmaps.append(Image.frombytes("1", (x, y), data[b:e], "raw", mode, pad(x)))
xsize, ysize = metrics[i][:2]
b, e = offsets[i : i + 2]
bitmaps.append(
Image.frombytes("1", (xsize, ysize), data[b:e], "raw", mode, pad(xsize))
)
return bitmaps

View File

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

View File

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

View File

@ -312,7 +312,7 @@ TAGS = {
34910: "HylaFAX FaxRecvTime",
36864: "ExifVersion",
36867: "DateTimeOriginal",
36868: "DateTImeDigitized",
36868: "DateTimeDigitized",
37121: "ComponentsConfiguration",
37122: "CompressedBitsPerPixel",
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_two_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_raw_mode = "unrecognized raw mode";
static const char *outside_image = "image index out of range";
@ -2805,6 +2809,16 @@ _draw_arc(ImagingDrawObject *self, PyObject *args) {
free(xy);
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(
self->image->image,
@ -2886,6 +2900,16 @@ _draw_chord(ImagingDrawObject *self, PyObject *args) {
free(xy);
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(
self->image->image,
@ -2932,6 +2956,16 @@ _draw_ellipse(ImagingDrawObject *self, PyObject *args) {
free(xy);
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(
self->image->image,
@ -3101,6 +3135,16 @@ _draw_pieslice(ImagingDrawObject *self, PyObject *args) {
free(xy);
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(
self->image->image,
@ -3197,6 +3241,16 @@ _draw_rectangle(ImagingDrawObject *self, PyObject *args) {
free(xy);
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(
self->image->image,
@ -3984,8 +4038,6 @@ PyImaging_GrabScreenWin32(PyObject *self, PyObject *args);
extern PyObject *
PyImaging_GrabClipboardWin32(PyObject *self, PyObject *args);
extern PyObject *
PyImaging_ListWindowsWin32(PyObject *self, PyObject *args);
extern PyObject *
PyImaging_EventLoopWin32(PyObject *self, PyObject *args);
extern PyObject *
PyImaging_DrawWmf(PyObject *self, PyObject *args);
@ -4069,7 +4121,6 @@ static PyMethodDef functions[] = {
{"grabclipboard_win32", (PyCFunction)PyImaging_GrabClipboardWin32, METH_VARARGS},
{"createwindow", (PyCFunction)PyImaging_CreateWindowWin32, METH_VARARGS},
{"eventloop", (PyCFunction)PyImaging_EventLoopWin32, METH_VARARGS},
{"listwindows", (PyCFunction)PyImaging_ListWindowsWin32, METH_VARARGS},
{"drawwmf", (PyCFunction)PyImaging_DrawWmf, METH_VARARGS},
#endif
#ifdef HAVE_XCB

View File

@ -116,12 +116,11 @@ _dealloc(ImagingDecoderObject *decoder) {
static PyObject *
_decode(ImagingDecoderObject *decoder, PyObject *args) {
UINT8 *buffer;
Py_ssize_t bufsize;
Py_buffer buffer;
int status;
ImagingSectionCookie cookie;
if (!PyArg_ParseTuple(args, "y#", &buffer, &bufsize)) {
if (!PyArg_ParseTuple(args, "y*", &buffer)) {
return NULL;
}
@ -129,12 +128,13 @@ _decode(ImagingDecoderObject *decoder, PyObject *args) {
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) {
ImagingSectionLeave(&cookie);
}
PyBuffer_Release(&buffer);
return Py_BuildValue("ii", status, decoder->state.errcode);
}

View File

@ -421,79 +421,6 @@ error:
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 */

View File

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

View File

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

View File

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