Merge branch 'python-pillow:main' into eps_plugin_perf
|
@ -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%
|
||||
|
|
|
@ -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
|
||||
|
|
3
.github/workflows/macos-install.sh
vendored
|
@ -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
|
||||
|
|
2
.github/workflows/test-cygwin.yml
vendored
|
@ -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
|
||||
|
||||
|
|
1
.github/workflows/test-docker.yml
vendored
|
@ -87,6 +87,7 @@ jobs:
|
|||
with:
|
||||
flags: GHA_Docker
|
||||
name: ${{ matrix.docker }}
|
||||
gcov: true
|
||||
|
||||
success:
|
||||
permissions:
|
||||
|
|
3
.github/workflows/test-mingw.yml
vendored
|
@ -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 \
|
||||
|
|
11
.github/workflows/test-windows.yml
vendored
|
@ -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" `
|
||||
|
|
3
.github/workflows/test.yml
vendored
|
@ -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
|
@ -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
|
||||
|
|
21
CHANGES.rst
|
@ -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]
|
||||
|
||||
|
|
4
LICENSE
|
@ -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
|
||||
|
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nnnn.png
Normal file
After Width: | Height: | Size: 544 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nnny.png
Normal file
After Width: | Height: | Size: 685 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nnyn.png
Normal file
After Width: | Height: | Size: 649 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nnyy.png
Normal file
After Width: | Height: | Size: 755 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nynn.png
Normal file
After Width: | Height: | Size: 643 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nyny.png
Normal file
After Width: | Height: | Size: 775 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nyyn.png
Normal file
After Width: | Height: | Size: 741 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nyyy.png
Normal file
After Width: | Height: | Size: 844 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_ynnn.png
Normal file
After Width: | Height: | Size: 656 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_ynny.png
Normal file
After Width: | Height: | Size: 785 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_ynyn.png
Normal file
After Width: | Height: | Size: 752 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_ynyy.png
Normal file
After Width: | Height: | Size: 856 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_yynn.png
Normal file
After Width: | Height: | Size: 737 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_yyny.png
Normal file
After Width: | Height: | Size: 870 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_yyyn.png
Normal file
After Width: | Height: | Size: 835 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_yyyy.png
Normal file
After Width: | Height: | Size: 934 B |
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 document’s title. If not appending to an existing PDF file, this will
|
||||
default to the filename.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 |
|
||||
| +----------------------------+---------------------+
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"])
|
||||
|
|
|
@ -10,7 +10,7 @@ for image enhancement.
|
|||
Example: Vary the sharpness of an image
|
||||
---------------------------------------
|
||||
|
||||
.. code-block:: python
|
||||
::
|
||||
|
||||
from PIL import ImageEnhance
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ and **xmllib** modules.
|
|||
Example: Parse an image
|
||||
-----------------------
|
||||
|
||||
.. code-block:: python
|
||||
::
|
||||
|
||||
from PIL import ImageFile
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ the imToolkit package.
|
|||
Example
|
||||
-------
|
||||
|
||||
.. code-block:: python
|
||||
::
|
||||
|
||||
from PIL import ImageFont, ImageDraw
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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"])
|
||||
|
|
|
@ -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 = (
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -312,7 +312,7 @@ TAGS = {
|
|||
34910: "HylaFAX FaxRecvTime",
|
||||
36864: "ExifVersion",
|
||||
36867: "DateTimeOriginal",
|
||||
36868: "DateTImeDigitized",
|
||||
36868: "DateTimeDigitized",
|
||||
37121: "ComponentsConfiguration",
|
||||
37122: "CompressedBitsPerPixel",
|
||||
37724: "ImageSourceData",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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-"),
|
||||
|
|