Merge branch 'main' into enum

This commit is contained in:
Andrew Murray 2022-02-10 09:52:24 +11:00 committed by GitHub
commit b38a67fa12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 1131 additions and 455 deletions

View File

@ -12,7 +12,7 @@ environment:
matrix: matrix:
- PYTHON: C:/Python310 - PYTHON: C:/Python310
ARCHITECTURE: x86 ARCHITECTURE: x86
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
- PYTHON: C:/Python37-x64 - PYTHON: C:/Python37-x64
ARCHITECTURE: x64 ARCHITECTURE: x64
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# gather the coverage data # gather the coverage data
pip3 install codecov python3 -m pip install codecov
if [[ $MATRIX_DOCKER ]]; then if [[ $MATRIX_DOCKER ]]; then
coverage xml --ignore-errors coverage xml --ignore-errors
else else

View File

@ -19,7 +19,7 @@ set -e
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\ sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\ ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
cmake imagemagick libharfbuzz-dev libfribidi-dev cmake meson imagemagick libharfbuzz-dev libfribidi-dev
python3 -m pip install --upgrade pip python3 -m pip install --upgrade pip
python3 -m pip install --upgrade wheel python3 -m pip install --upgrade wheel

View File

@ -19,9 +19,10 @@ jobs:
amazon-2-amd64, amazon-2-amd64,
arch, arch,
centos-7-amd64, centos-7-amd64,
centos-8-amd64,
centos-stream-8-amd64, centos-stream-8-amd64,
centos-stream-9-amd64,
debian-10-buster-x86, debian-10-buster-x86,
debian-11-bullseye-x86,
fedora-34-amd64, fedora-34-amd64,
fedora-35-amd64, fedora-35-amd64,
ubuntu-18.04-bionic-amd64, ubuntu-18.04-bionic-amd64,

View File

@ -4,7 +4,7 @@ on: [push, pull_request, workflow_dispatch]
jobs: jobs:
build: build:
runs-on: windows-2019 runs-on: windows-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:

View File

@ -4,7 +4,7 @@ on: [push, pull_request, workflow_dispatch]
jobs: jobs:
build: build:
runs-on: windows-2019 runs-on: windows-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:

View File

@ -5,6 +5,24 @@ Changelog (Pillow)
9.1.0 (unreleased) 9.1.0 (unreleased)
------------------ ------------------
- Added unpacker from RGBA;15 to RGB #6031
[radarhere]
- Enable arm64 for MSVC on Windows #5811
[gaborkertesz-linaro, gaborkertesz]
- Keep IPython/Jupyter text/plain output stable #5891
[shamrin, radarhere]
- Raise an error when performing a negative crop #5972
[radarhere, hugovk]
- Deprecated show_file "file" argument in favour of "path" #5959
[radarhere]
- Fixed SPIDER images for use with Bio-formats library #5956
[radarhere]
- Ensure duplicated file pointer is closed #5946 - Ensure duplicated file pointer is closed #5946
[radarhere] [radarhere]
@ -17,6 +35,15 @@ Changelog (Pillow)
- Remove readonly from Image.__eq__ #5930 - Remove readonly from Image.__eq__ #5930
[hugovk] [hugovk]
9.0.1 (2022-02-03)
------------------
- In show_file, use os.remove to remove temporary images. CVE-2022-24303 #6010
[radarhere, hugovk]
- Restrict builtins within lambdas for ImageMath.eval. CVE-2022-22817 #6009
[radarhere]
9.0.0 (2022-01-02) 9.0.0 (2022-01-02)
------------------ ------------------

View File

@ -105,7 +105,7 @@ test:
.PHONY: valgrind .PHONY: valgrind
valgrind: valgrind:
python3 -c "import pytest_valgrind" || pip3 install pytest-valgrind python3 -c "import pytest_valgrind" || python3 -m pip install pytest-valgrind
PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \ PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \
--log-file=/tmp/valgrind-output \ --log-file=/tmp/valgrind-output \
python3 -m pytest --no-memcheck -vv --valgrind --valgrind-log=/tmp/valgrind-output python3 -m pytest --no-memcheck -vv --valgrind --valgrind-log=/tmp/valgrind-output

View File

@ -24,16 +24,19 @@ As of 2019, Pillow development is
<tr> <tr>
<th>tests</th> <th>tests</th>
<td> <td>
<a href="https://github.com/python-pillow/Pillow/actions?query=workflow%3ALint"><img <a href="https://github.com/python-pillow/Pillow/actions/workflows/lint.yml"><img
alt="GitHub Actions build status (Lint)" alt="GitHub Actions build status (Lint)"
src="https://github.com/python-pillow/Pillow/workflows/Lint/badge.svg"></a> src="https://github.com/python-pillow/Pillow/workflows/Lint/badge.svg"></a>
<a href="https://github.com/python-pillow/Pillow/actions?query=workflow%3ATest"><img <a href="https://github.com/python-pillow/Pillow/actions/workflows/test.yml"><img
alt="GitHub Actions build status (Test Linux and macOS)" alt="GitHub Actions build status (Test Linux and macOS)"
src="https://github.com/python-pillow/Pillow/workflows/Test/badge.svg"></a> src="https://github.com/python-pillow/Pillow/workflows/Test/badge.svg"></a>
<a href="https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Windows%22"><img <a href="https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml"><img
alt="GitHub Actions build status (Test Windows)" alt="GitHub Actions build status (Test Windows)"
src="https://github.com/python-pillow/Pillow/workflows/Test%20Windows/badge.svg"></a> src="https://github.com/python-pillow/Pillow/workflows/Test%20Windows/badge.svg"></a>
<a href="https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Docker%22"><img <a href="https://github.com/python-pillow/Pillow/actions/workflows/test-mingw.yml"><img
alt="GitHub Actions build status (Test MinGW)"
src="https://github.com/python-pillow/Pillow/workflows/Test%20MinGW/badge.svg"></a>
<a href="https://github.com/python-pillow/Pillow/actions/workflows/test-docker.yml"><img
alt="GitHub Actions build status (Test Docker)" alt="GitHub Actions build status (Test Docker)"
src="https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg"></a> src="https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg"></a>
<a href="https://ci.appveyor.com/project/python-pillow/Pillow"><img <a href="https://ci.appveyor.com/project/python-pillow/Pillow"><img
@ -42,10 +45,10 @@ As of 2019, Pillow development is
<a href="https://github.com/python-pillow/pillow-wheels/actions"><img <a href="https://github.com/python-pillow/pillow-wheels/actions"><img
alt="GitHub Actions wheels build status (Wheels)" alt="GitHub Actions wheels build status (Wheels)"
src="https://github.com/python-pillow/pillow-wheels/workflows/Wheels/badge.svg"></a> src="https://github.com/python-pillow/pillow-wheels/workflows/Wheels/badge.svg"></a>
<a href="https://travis-ci.com/github/python-pillow/pillow-wheels"><img <a href="https://app.travis-ci.com/github/python-pillow/pillow-wheels"><img
alt="Travis CI wheels build status (aarch64)" alt="Travis CI wheels build status (aarch64)"
src="https://img.shields.io/travis/com/python-pillow/pillow-wheels/main.svg?label=aarch64%20wheels"></a> src="https://img.shields.io/travis/com/python-pillow/pillow-wheels/main.svg?label=aarch64%20wheels"></a>
<a href="https://codecov.io/gh/python-pillow/Pillow"><img <a href="https://app.codecov.io/gh/python-pillow/Pillow"><img
alt="Code coverage" alt="Code coverage"
src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a> src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a>
<a href="https://github.com/python-pillow/Pillow/actions/workflows/tidelift.yml"><img <a href="https://github.com/python-pillow/Pillow/actions/workflows/tidelift.yml"><img

View File

@ -86,21 +86,12 @@ class TestDecompressionCrop:
pytest.warns(Image.DecompressionBombWarning, src.crop, box) pytest.warns(Image.DecompressionBombWarning, src.crop, box)
def test_crop_decompression_checks(self): def test_crop_decompression_checks(self):
im = Image.new("RGB", (100, 100)) im = Image.new("RGB", (100, 100))
good_values = ((-9999, -9999, -9990, -9990), (-999, -999, -990, -990)) for value in ((-9999, -9999, -9990, -9990), (-999, -999, -990, -990)):
warning_values = ((-160, -160, 99, 99), (160, 160, -99, -99))
error_values = ((-99909, -99990, 99999, 99999), (99909, 99990, -99999, -99999))
for value in good_values:
assert im.crop(value).size == (9, 9) assert im.crop(value).size == (9, 9)
for value in warning_values: pytest.warns(Image.DecompressionBombWarning, im.crop, (-160, -160, 99, 99))
pytest.warns(Image.DecompressionBombWarning, im.crop, value)
for value in error_values:
with pytest.raises(Image.DecompressionBombError): with pytest.raises(Image.DecompressionBombError):
im.crop(value) im.crop((-99909, -99990, 99999, 99999))

View File

@ -58,6 +58,15 @@ def test_sanity():
assert image2_scale2.format == "EPS" assert image2_scale2.format == "EPS"
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
def test_load():
with Image.open(FILE1) as im:
assert im.load()[0, 0] == (255, 255, 255)
# Test again now that it has already been loaded once
assert im.load()[0, 0] == (255, 255, 255)
def test_invalid_file(): def test_invalid_file():
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"

View File

@ -5,20 +5,28 @@ from PIL import GbrImagePlugin, Image
from .helper import assert_image_equal_tofile from .helper import assert_image_equal_tofile
def test_invalid_file():
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):
GbrImagePlugin.GbrImageFile(invalid_file)
def test_gbr_file(): def test_gbr_file():
with Image.open("Tests/images/gbr.gbr") as im: with Image.open("Tests/images/gbr.gbr") as im:
assert_image_equal_tofile(im, "Tests/images/gbr.png") assert_image_equal_tofile(im, "Tests/images/gbr.png")
def test_load():
with Image.open("Tests/images/gbr.gbr") as im:
assert im.load()[0, 0] == (0, 0, 0, 0)
# Test again now that it has already been loaded once
assert im.load()[0, 0] == (0, 0, 0, 0)
def test_multiple_load_operations(): def test_multiple_load_operations():
with Image.open("Tests/images/gbr.gbr") as im: with Image.open("Tests/images/gbr.gbr") as im:
im.load() im.load()
im.load() im.load()
assert_image_equal_tofile(im, "Tests/images/gbr.png") assert_image_equal_tofile(im, "Tests/images/gbr.png")
def test_invalid_file():
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):
GbrImagePlugin.GbrImageFile(invalid_file)

View File

@ -28,6 +28,14 @@ def test_sanity():
assert im.format == "ICNS" assert im.format == "ICNS"
def test_load():
with Image.open(TEST_FILE) as im:
assert im.load()[0, 0] == (0, 0, 0, 0)
# Test again now that it has already been loaded once
assert im.load()[0, 0] == (0, 0, 0, 0)
def test_save(tmp_path): def test_save(tmp_path):
temp_file = str(tmp_path / "temp.icns") temp_file = str(tmp_path / "temp.icns")

View File

@ -18,6 +18,11 @@ def test_sanity():
assert im.get_format_mimetype() == "image/x-icon" assert im.get_format_mimetype() == "image/x-icon"
def test_load():
with Image.open(TEST_ICO_FILE) as im:
assert im.load()[0, 0] == (1, 1, 9, 255)
def test_mask(): def test_mask():
with Image.open("Tests/images/hopper_mask.ico") as im: with Image.open("Tests/images/hopper_mask.ico") as im:
assert_image_equal_tofile(im, "Tests/images/hopper_mask.png") assert_image_equal_tofile(im, "Tests/images/hopper_mask.png")

View File

@ -123,7 +123,7 @@ def test_no_icc_profile():
def test_combined_larger_than_size(): def test_combined_larger_than_size():
# The 'combined' sizes of the individual parts is larger than the # The combined size of the individual parts is larger than the
# declared 'size' of the extra data field, resulting in a backwards seek. # declared 'size' of the extra data field, resulting in a backwards seek.
# If we instead take the 'size' of the extra data field as the source of truth, # If we instead take the 'size' of the extra data field as the source of truth,

View File

@ -2,15 +2,11 @@ from PIL import WalImageFile
from .helper import assert_image_equal_tofile from .helper import assert_image_equal_tofile
def test_open():
# Arrange
TEST_FILE = "Tests/images/hopper.wal" TEST_FILE = "Tests/images/hopper.wal"
# Act
with WalImageFile.open(TEST_FILE) as im:
# Assert def test_open():
with WalImageFile.open(TEST_FILE) as im:
assert im.format == "WAL" assert im.format == "WAL"
assert im.format_description == "Quake2 Texture" assert im.format_description == "Quake2 Texture"
assert im.mode == "P" assert im.mode == "P"
@ -19,3 +15,11 @@ def test_open():
assert isinstance(im, WalImageFile.WalImageFile) assert isinstance(im, WalImageFile.WalImageFile)
assert_image_equal_tofile(im, "Tests/images/hopper_wal.png") assert_image_equal_tofile(im, "Tests/images/hopper_wal.png")
def test_load():
with WalImageFile.open(TEST_FILE) as im:
assert im.load()[0, 0] == 122
# Test again now that it has already been loaded once
assert im.load()[0, 0] == 122

View File

@ -24,6 +24,12 @@ def test_load_raw():
assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref.png", 2.0) assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref.png", 2.0)
def test_load():
with Image.open("Tests/images/drawing.emf") as im:
if hasattr(Image.core, "drawwmf"):
assert im.load()[0, 0] == (255, 255, 255)
def test_register_handler(tmp_path): def test_register_handler(tmp_path):
class TestHandler: class TestHandler:
methodCalled = False methodCalled = False

View File

@ -89,6 +89,17 @@ class TestImage:
# with pytest.raises(MemoryError): # with pytest.raises(MemoryError):
# Image.new("L", (1000000, 1000000)) # Image.new("L", (1000000, 1000000))
def test_repr_pretty(self):
class Pretty:
def text(self, text):
self.pretty_output = text
im = Image.new("L", (100, 100))
p = Pretty()
im._repr_pretty_(p, None)
assert p.pretty_output == "<PIL.Image.Image image mode=L size=100x100>"
def test_open_formats(self): def test_open_formats(self):
PNGFILE = "Tests/images/hopper.png" PNGFILE = "Tests/images/hopper.png"
JPGFILE = "Tests/images/hopper.jpg" JPGFILE = "Tests/images/hopper.jpg"

View File

@ -47,16 +47,12 @@ def test_wide_crop():
assert crop(-25, 75, 25, 125) == (1875, 625) assert crop(-25, 75, 25, 125) == (1875, 625)
def test_negative_crop(): @pytest.mark.parametrize("box", ((8, 2, 2, 8), (2, 8, 8, 2), (8, 8, 2, 2)))
# Check negative crop size (@PIL171) def test_negative_crop(box):
im = Image.new("RGB", (10, 10))
im = Image.new("L", (512, 512)) with pytest.raises(ValueError):
im = im.crop((400, 400, 200, 200)) im.crop(box)
assert im.size == (0, 0)
assert len(im.getdata()) == 0
with pytest.raises(IndexError):
im.getdata()[0]
def test_crop_float(): def test_crop_float():

View File

@ -1024,6 +1024,19 @@ def test_oom(test_file):
font.getmask("Test Text") font.getmask("Test Text")
def test_raqm_missing_warning(monkeypatch):
monkeypatch.setattr(ImageFont.core, "HAVE_RAQM", False)
with pytest.warns(UserWarning) as record:
font = ImageFont.truetype(
FONT_PATH, FONT_SIZE, layout_engine=ImageFont.Layout.RAQM
)
assert font.layout_engine == ImageFont.Layout.BASIC
assert str(record[-1].message) == (
"Raqm layout was requested, but Raqm is not available. "
"Falling back to basic layout."
)
def test_constants_deprecation(): def test_constants_deprecation():
for enum, prefix in { for enum, prefix in {
ImageFont.Layout: "LAYOUT_", ImageFont.Layout: "LAYOUT_",

View File

@ -52,9 +52,17 @@ def test_ops():
assert pixel(ImageMath.eval("float(B)**33", images)) == "F 8589934592.0" assert pixel(ImageMath.eval("float(B)**33", images)) == "F 8589934592.0"
def test_prevent_exec(): @pytest.mark.parametrize(
"expression",
(
"exec('pass')",
"(lambda: exec('pass'))()",
"(lambda: (lambda: exec('pass'))())()",
),
)
def test_prevent_exec(expression):
with pytest.raises(ValueError): with pytest.raises(ValueError):
ImageMath.eval("exec('pass')") ImageMath.eval(expression)
def test_logical(): def test_logical():

View File

@ -79,3 +79,20 @@ def test_ipythonviewer():
im = hopper() im = hopper()
assert test_viewer.show(im) == 1 assert test_viewer.show(im) == 1
@pytest.mark.skipif(
not on_ci() or is_win32(),
reason="Only run on CIs; hangs on Windows CIs",
)
def test_file_deprecated(tmp_path):
f = str(tmp_path / "temp.jpg")
for viewer in ImageShow._viewers:
hopper().save(f)
with pytest.warns(DeprecationWarning):
try:
viewer.show_file(file=f)
except NotImplementedError:
pass
with pytest.raises(TypeError):
viewer.show_file()

View File

@ -189,8 +189,9 @@ def test_putdata():
assert len(im.getdata()) == len(arr) assert len(im.getdata()) == len(arr)
def test_roundtrip_eye(): @pytest.mark.parametrize(
for dtype in ( "dtype",
(
bool, bool,
numpy.bool8, numpy.bool8,
numpy.int8, numpy.int8,
@ -202,7 +203,9 @@ def test_roundtrip_eye():
float, float,
numpy.float32, numpy.float32,
numpy.float64, numpy.float64,
): ),
)
def test_roundtrip_eye(dtype):
arr = numpy.eye(10, dtype=dtype) arr = numpy.eye(10, dtype=dtype)
numpy.testing.assert_array_equal(arr, numpy.array(Image.fromarray(arr))) numpy.testing.assert_array_equal(arr, numpy.array(Image.fromarray(arr)))

View File

@ -1,14 +1,15 @@
#!/bin/bash #!/bin/bash
# install libimagequant # install libimagequant
archive=libimagequant-2.17.0 archive=libimagequant-4.0.0
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
pushd $archive pushd $archive/imagequant-sys
make shared cargo install cargo-c
sudo cp libimagequant.so* /usr/lib/ cargo cinstall --prefix=/usr --destdir=.
sudo cp libimagequant.h /usr/include/ sudo cp usr/lib/libimagequant.so* /usr/lib/
sudo cp usr/include/libimagequant.h /usr/include/
popd popd

View File

@ -2,13 +2,13 @@
# install raqm # install raqm
archive=raqm-0.7.1 archive=libraqm-0.9.0
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
pushd $archive pushd $archive
./configure --prefix=/usr && make -j4 && sudo make -j4 install meson build --prefix=/usr && sudo ninja -C build install
popd popd

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# install webp # install webp
archive=libwebp-1.2.1 archive=libwebp-1.2.2
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz

View File

@ -12,7 +12,7 @@ The fork author's goal is to foster and support active development of PIL throug
.. _GitHub Actions: https://github.com/python-pillow/Pillow/actions .. _GitHub Actions: https://github.com/python-pillow/Pillow/actions
.. _AppVeyor: https://ci.appveyor.com/project/Python-pillow/pillow .. _AppVeyor: https://ci.appveyor.com/project/Python-pillow/pillow
.. _Travis CI: https://travis-ci.com/github/python-pillow/pillow-wheels .. _Travis CI: https://app.travis-ci.com/github/python-pillow/pillow-wheels
.. _GitHub: https://github.com/python-pillow/Pillow .. _GitHub: https://github.com/python-pillow/Pillow
.. _Python Package Index: https://pypi.org/project/Pillow/ .. _Python Package Index: https://pypi.org/project/Pillow/

View File

@ -53,6 +53,19 @@ Before Pillow 8.3.0, ``ImagePalette`` required palette data of particular length
default, and the size parameter could be used to override that. Pillow 8.3.0 removed default, and the size parameter could be used to override that. Pillow 8.3.0 removed
the default required length, also removing the need for the size parameter. the default required length, also removing the need for the size parameter.
ImageShow.Viewer.show_file file argument
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 9.1.0
The ``file`` argument in :py:meth:`~PIL.ImageShow.Viewer.show_file()` has been
deprecated and will be removed in Pillow 10.0.0 (2023-07-01). It has been replaced by
``path``.
In effect, ``viewer.show_file("test.jpg")`` will continue to work unchanged.
``viewer.show_file(file="test.jpg")`` will raise a deprecation warning, and suggest
``viewer.show_file(path="test.jpg")`` instead.
Removed features Removed features
---------------- ----------------

View File

@ -10,21 +10,25 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more <h
:alt: Documentation Status :alt: Documentation Status
.. image:: https://github.com/python-pillow/Pillow/workflows/Lint/badge.svg .. image:: https://github.com/python-pillow/Pillow/workflows/Lint/badge.svg
:target: https://github.com/python-pillow/Pillow/actions?query=workflow%3ALint :target: https://github.com/python-pillow/Pillow/actions/workflows/lint.yml
:alt: GitHub Actions build status (Lint) :alt: GitHub Actions build status (Lint)
.. image:: https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg .. image:: https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg
:target: https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Docker%22 :target: https://github.com/python-pillow/Pillow/actions/workflows/test-docker.yml
:alt: GitHub Actions build status (Test Docker) :alt: GitHub Actions build status (Test Docker)
.. image:: https://github.com/python-pillow/Pillow/workflows/Test/badge.svg .. image:: https://github.com/python-pillow/Pillow/workflows/Test/badge.svg
:target: https://github.com/python-pillow/Pillow/actions?query=workflow%3ATest :target: https://github.com/python-pillow/Pillow/actions/workflows/test.yml
:alt: GitHub Actions build status (Test Linux and macOS) :alt: GitHub Actions build status (Test Linux and macOS)
.. image:: https://github.com/python-pillow/Pillow/workflows/Test%20Windows/badge.svg .. image:: https://github.com/python-pillow/Pillow/workflows/Test%20Windows/badge.svg
:target: https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Windows%22 :target: https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml
:alt: GitHub Actions build status (Test Windows) :alt: GitHub Actions build status (Test Windows)
.. image:: https://github.com/python-pillow/Pillow/workflows/Test%20MinGW/badge.svg
:target: https://github.com/python-pillow/Pillow/actions/workflows/test-mingw.yml
:alt: GitHub Actions build status (Test MinGW)
.. image:: https://img.shields.io/appveyor/build/python-pillow/Pillow/main.svg?label=Windows%20build .. image:: https://img.shields.io/appveyor/build/python-pillow/Pillow/main.svg?label=Windows%20build
:target: https://ci.appveyor.com/project/python-pillow/Pillow :target: https://ci.appveyor.com/project/python-pillow/Pillow
:alt: AppVeyor CI build status (Windows) :alt: AppVeyor CI build status (Windows)
@ -34,11 +38,11 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more <h
:alt: GitHub Actions wheels build status (Wheels) :alt: GitHub Actions wheels build status (Wheels)
.. image:: https://img.shields.io/travis/com/python-pillow/pillow-wheels/main.svg?label=aarch64%20wheels .. image:: https://img.shields.io/travis/com/python-pillow/pillow-wheels/main.svg?label=aarch64%20wheels
:target: https://travis-ci.com/github/python-pillow/pillow-wheels :target: https://app.travis-ci.com/github/python-pillow/pillow-wheels
:alt: Travis CI wheels build status (aarch64) :alt: Travis CI wheels build status (aarch64)
.. image:: https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg .. image:: https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg
:target: https://codecov.io/gh/python-pillow/Pillow :target: https://app.codecov.io/gh/python-pillow/Pillow
:alt: Code coverage :alt: Code coverage
.. image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg .. image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg

View File

@ -169,7 +169,7 @@ Many of Pillow's features require external libraries:
* **littlecms** provides color management * **littlecms** provides color management
* Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and * Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and
above uses liblcms2. Tested with **1.19** and **2.7-2.12**. above uses liblcms2. Tested with **1.19** and **2.7-2.13.1**.
* **libwebp** provides the WebP format. * **libwebp** provides the WebP format.
@ -187,7 +187,7 @@ Many of Pillow's features require external libraries:
* **libimagequant** provides improved color quantization * **libimagequant** provides improved color quantization
* Pillow has been tested with libimagequant **2.6-2.17.0** * Pillow has been tested with libimagequant **2.6-4.0**
* Libimagequant is licensed GPLv3, which is more restrictive than * Libimagequant is licensed GPLv3, which is more restrictive than
the Pillow license, therefore we will not be distributing binaries the Pillow license, therefore we will not be distributing binaries
with libimagequant support enabled. with libimagequant support enabled.
@ -394,7 +394,8 @@ Prerequisites for **Ubuntu 16.04 LTS - 20.04 LTS** are installed with::
libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \ libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \
libharfbuzz-dev libfribidi-dev libxcb1-dev libharfbuzz-dev libfribidi-dev libxcb1-dev
Then see ``depends/install_raqm.sh`` to install libraqm. To install libraqm, ``sudo apt-get install meson`` and then see
``depends/install_raqm.sh``.
Prerequisites are installed on recent **Red Hat**, **CentOS** or **Fedora** with:: Prerequisites are installed on recent **Red Hat**, **CentOS** or **Fedora** with::
@ -452,12 +453,14 @@ These platforms are built and tested for every change.
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| CentOS 7 | 3.9 | x86-64 | | CentOS 7 | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| CentOS 8 | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| CentOS Stream 8 | 3.9 | x86-64 | | CentOS Stream 8 | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| CentOS Stream 9 | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Debian 10 Buster | 3.7 | x86 | | Debian 10 Buster | 3.7 | x86 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Debian 11 Bullseye | 3.9 | x86 |
+----------------------------------+----------------------------+---------------------+
| Fedora 34 | 3.9 | x86-64 | | Fedora 34 | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Fedora 35 | 3.10 | x86-64 | | Fedora 35 | 3.10 | x86-64 |
@ -493,9 +496,13 @@ These platforms have been reported to work at the versions mentioned.
| Operating system | | Tested Python | | Latest tested | | Tested | | Operating system | | Tested Python | | Latest tested | | Tested |
| | | versions | | Pillow version | | processors | | | | versions | | Pillow version | | processors |
+==================================+===========================+==================+==============+ +==================================+===========================+==================+==============+
| macOS 11.0 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm | | macOS 12 Big Sur | 3.7, 3.8, 3.9, 3.10 | 9.0.1 |arm |
+----------------------------------+---------------------------+------------------+--------------+
| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm |
| +---------------------------+------------------+--------------+ | +---------------------------+------------------+--------------+
| | 3.6, 3.7, 3.8, 3.9, 3.10 | 8.4.0 |x86-64 | | | 3.7, 3.8, 3.9, 3.10 | 9.0.1 |x86-64 |
| +---------------------------+------------------+--------------+
| | 3.6 | 8.4.0 |x86-64 |
+----------------------------------+---------------------------+------------------+--------------+ +----------------------------------+---------------------------+------------------+--------------+
| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |x86-64 | | macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |x86-64 |
| +---------------------------+------------------+ | | +---------------------------+------------------+ |
@ -523,6 +530,8 @@ These platforms have been reported to work at the versions mentioned.
+----------------------------------+---------------------------+------------------+--------------+ +----------------------------------+---------------------------+------------------+--------------+
| CentOS 6.3 | 2.7, 3.3 | |x86 | | CentOS 6.3 | 2.7, 3.3 | |x86 |
+----------------------------------+---------------------------+------------------+--------------+ +----------------------------------+---------------------------+------------------+--------------+
| CentOS 8 | 3.9 | 9.0.0 |x86-64 |
+----------------------------------+---------------------------+------------------+--------------+
| Fedora 23 | 2.7, 3.4 | 3.1.0 |x86-64 | | Fedora 23 | 2.7, 3.4 | 3.1.0 |x86-64 |
+----------------------------------+---------------------------+------------------+--------------+ +----------------------------------+---------------------------+------------------+--------------+
| Ubuntu Linux 12.04 LTS (Precise) | | 2.6, 3.2, 3.3, 3.4, 3.5 | 3.4.1 |x86,x86-64 | | Ubuntu Linux 12.04 LTS (Precise) | | 2.6, 3.2, 3.3, 3.4, 3.5 | 3.4.1 |x86,x86-64 |

View File

@ -0,0 +1,23 @@
9.0.1
-----
Security
========
This release addresses several security problems.
:cve:`CVE-2022-24303`: If the path to the temporary directory on Linux or macOS
contained a space, this would break removal of the temporary image file after
``im.show()`` (and related actions), and potentially remove an unrelated file. This
has been present since PIL.
:cve:`CVE-2022-22817`: While Pillow 9.0 restricted top-level builtins available to
:py:meth:`PIL.ImageMath.eval`, it did not prevent builtins available to lambda
expressions. These are now also restricted.
Other Changes
=============
Pillow 9.0 added support for ``xdg-open`` as an image viewer, but there have been
reports that the temporary image file was removed too quickly to be loaded into the
final application. A delay has been added.

View File

@ -14,6 +14,7 @@ expected to be backported to earlier versions.
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
9.0.1
9.0.0 9.0.0
8.4.0 8.4.0
8.3.2 8.3.2

View File

@ -329,12 +329,12 @@ class EpsImageFile(ImageFile.ImageFile):
def load(self, scale=1, transparency=False): def load(self, scale=1, transparency=False):
# Load EPS via Ghostscript # Load EPS via Ghostscript
if not self.tile: if self.tile:
return
self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency) self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency)
self.mode = self.im.mode self.mode = self.im.mode
self._size = self.im.size self._size = self.im.size
self.tile = [] self.tile = []
return Image.Image.load(self)
def load_seek(self, *args, **kwargs): def load_seek(self, *args, **kwargs):
# we can't incrementally load, so force ImageFile.parser to # we can't incrementally load, so force ImageFile.parser to

View File

@ -84,12 +84,10 @@ class GbrImageFile(ImageFile.ImageFile):
self._data_size = width * height * color_depth self._data_size = width * height * color_depth
def load(self): def load(self):
if self.im: if not self.im:
# Already loaded
return
self.im = Image.core.new(self.mode, self.size) self.im = Image.core.new(self.mode, self.size)
self.frombytes(self.fp.read(self._data_size)) self.frombytes(self.fp.read(self._data_size))
return Image.Image.load(self)
# #

View File

@ -286,21 +286,22 @@ class IcnsImageFile(ImageFile.ImageFile):
self.best_size[1] * self.best_size[2], self.best_size[1] * self.best_size[2],
) )
Image.Image.load(self) px = Image.Image.load(self)
if self.im and self.im.size == self.size: if self.im and self.im.size == self.size:
# Already loaded # Already loaded
return return px
self.load_prepare() self.load_prepare()
# This is likely NOT the best way to do it, but whatever. # This is likely NOT the best way to do it, but whatever.
im = self.icns.getimage(self.best_size) im = self.icns.getimage(self.best_size)
# If this is a PNG or JPEG 2000, it won't be loaded yet # If this is a PNG or JPEG 2000, it won't be loaded yet
im.load() px = im.load()
self.im = im.im self.im = im.im
self.mode = im.mode self.mode = im.mode
self.size = im.size self.size = im.size
self.load_end()
return px
def _save(im, fp, filename): def _save(im, fp, filename):

View File

@ -306,7 +306,7 @@ class IcoImageFile(ImageFile.ImageFile):
def load(self): def load(self):
if self.im and self.im.size == self.size: if self.im and self.im.size == self.size:
# Already loaded # Already loaded
return return Image.Image.load(self)
im = self.ico.getimage(self.size) im = self.ico.getimage(self.size)
# if tile is PNG, it won't really be loaded yet # if tile is PNG, it won't really be loaded yet
im.load() im.load()

View File

@ -702,6 +702,22 @@ class Image:
id(self), id(self),
) )
def _repr_pretty_(self, p, cycle):
"""IPython plain text display support"""
# Same as __repr__ but without unpredicatable id(self),
# to keep Jupyter notebook `text/plain` output stable.
p.text(
"<%s.%s image mode=%s size=%dx%d>"
% (
self.__class__.__module__,
self.__class__.__name__,
self.mode,
self.size[0],
self.size[1],
)
)
def _repr_png_(self): def _repr_png_(self):
"""iPython display hook support """iPython display hook support
@ -1213,6 +1229,11 @@ class Image:
if box is None: if box is None:
return self.copy() return self.copy()
if box[2] < box[0]:
raise ValueError("Coordinate 'right' is less than 'left'")
elif box[3] < box[1]:
raise ValueError("Coordinate 'lower' is less than 'upper'")
self.load() self.load()
return self._new(self._crop(self.im, box)) return self._new(self._crop(self.im, box))

View File

@ -328,6 +328,7 @@ class StubImageFile(ImageFile):
# become the other object (!) # become the other object (!)
self.__class__ = image.__class__ self.__class__ = image.__class__
self.__dict__ = image.__dict__ self.__dict__ = image.__dict__
return image.load()
def _load(self): def _load(self):
"""(Hook) Find actual image loader.""" """(Hook) Find actual image loader."""

View File

@ -196,6 +196,12 @@ class FreeTypeFont:
if core.HAVE_RAQM: if core.HAVE_RAQM:
layout_engine = Layout.RAQM layout_engine = Layout.RAQM
elif layout_engine == Layout.RAQM and not core.HAVE_RAQM: elif layout_engine == Layout.RAQM and not core.HAVE_RAQM:
import warnings
warnings.warn(
"Raqm layout was requested, but Raqm is not available. "
"Falling back to basic layout."
)
layout_engine = Layout.BASIC layout_engine = Layout.BASIC
self.layout_engine = layout_engine self.layout_engine = layout_engine

View File

@ -240,11 +240,18 @@ def eval(expression, _dict={}, **kw):
if hasattr(v, "im"): if hasattr(v, "im"):
args[k] = _Operand(v) args[k] = _Operand(v)
code = compile(expression, "<string>", "eval") compiled_code = compile(expression, "<string>", "eval")
def scan(code):
for const in code.co_consts:
if type(const) == type(compiled_code):
scan(const)
for name in code.co_names: for name in code.co_names:
if name not in args and name != "abs": if name not in args and name != "abs":
raise ValueError(f"'{name}' not allowed") raise ValueError(f"'{name}' not allowed")
scan(compiled_code)
out = builtins.eval(expression, {"__builtins": {"abs": abs}}, args) out = builtins.eval(expression, {"__builtins": {"abs": abs}}, args)
try: try:
return out.im return out.im

View File

@ -15,7 +15,7 @@ import os
import shutil import shutil
import subprocess import subprocess
import sys import sys
import tempfile import warnings
from shlex import quote from shlex import quote
from PIL import Image from PIL import Image
@ -105,11 +105,37 @@ class Viewer:
"""Display the given image.""" """Display the given image."""
return self.show_file(self.save_image(image), **options) return self.show_file(self.save_image(image), **options)
def show_file(self, file, **options): def show_file(self, path=None, **options):
"""Display the given file.""" """
os.system(self.get_command(file, **options)) Display given file.
Before Pillow 9.1.0, the first argument was ``file``. This is now deprecated,
and will be removed in Pillow 10.0.0 (2023-07-01). ``path`` should be used
instead.
"""
if path is None:
if "file" in options:
warnings.warn(
"The 'file' argument is deprecated and will be removed in Pillow "
"10 (2023-07-01). Use 'path' instead.",
DeprecationWarning,
)
path = options.pop("file")
else:
raise TypeError("Missing required argument: 'path'")
os.system(self.get_command(path, **options))
return 1 return 1
def _remove_path_after_delay(self, path):
subprocess.Popen(
[
sys.executable,
"-c",
"import os, sys, time; time.sleep(20); os.remove(sys.argv[1])",
path,
]
)
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -145,18 +171,26 @@ class MacViewer(Viewer):
command = f"({command} {quote(file)}; sleep 20; rm -f {quote(file)})&" command = f"({command} {quote(file)}; sleep 20; rm -f {quote(file)})&"
return command return command
def show_file(self, file, **options): def show_file(self, path=None, **options):
"""Display given file""" """
fd, path = tempfile.mkstemp() Display given file.
with os.fdopen(fd, "w") as f:
f.write(file) Before Pillow 9.1.0, the first argument was ``file``. This is now deprecated,
with open(path) as f: and will be removed in Pillow 10.0.0 (2023-07-01). ``path`` should be used
subprocess.Popen( instead.
["im=$(cat); open -a Preview.app $im; sleep 20; rm -f $im"], """
shell=True, if path is None:
stdin=f, if "file" in options:
warnings.warn(
"The 'file' argument is deprecated and will be removed in Pillow "
"10 (2023-07-01). Use 'path' instead.",
DeprecationWarning,
) )
os.remove(path) path = options.pop("file")
else:
raise TypeError("Missing required argument: 'path'")
subprocess.call(["open", "-a", "Preview.app", path])
self._remove_path_after_delay(path)
return 1 return 1
@ -172,19 +206,6 @@ class UnixViewer(Viewer):
command = self.get_command_ex(file, **options)[0] command = self.get_command_ex(file, **options)[0]
return f"({command} {quote(file)}; rm -f {quote(file)})&" return f"({command} {quote(file)}; rm -f {quote(file)})&"
def show_file(self, file, **options):
"""Display given file"""
fd, path = tempfile.mkstemp()
with os.fdopen(fd, "w") as f:
f.write(file)
with open(path) as f:
command = self.get_command_ex(file, **options)[0]
subprocess.Popen(
["im=$(cat);" + command + " $im; rm -f $im"], shell=True, stdin=f
)
os.remove(path)
return 1
class XDGViewer(UnixViewer): class XDGViewer(UnixViewer):
""" """
@ -195,6 +216,28 @@ class XDGViewer(UnixViewer):
command = executable = "xdg-open" command = executable = "xdg-open"
return command, executable return command, executable
def show_file(self, path=None, **options):
"""
Display given file.
Before Pillow 9.1.0, the first argument was ``file``. This is now deprecated,
and will be removed in Pillow 10.0.0 (2023-07-01). ``path`` should be used
instead.
"""
if path is None:
if "file" in options:
warnings.warn(
"The 'file' argument is deprecated and will be removed in Pillow "
"10 (2023-07-01). Use 'path' instead.",
DeprecationWarning,
)
path = options.pop("file")
else:
raise TypeError("Missing required argument: 'path'")
subprocess.Popen(["xdg-open", path])
self._remove_path_after_delay(path)
return 1
class DisplayViewer(UnixViewer): class DisplayViewer(UnixViewer):
""" """
@ -208,6 +251,32 @@ class DisplayViewer(UnixViewer):
command += f" -name {quote(title)}" command += f" -name {quote(title)}"
return command, executable return command, executable
def show_file(self, path=None, **options):
"""
Display given file.
Before Pillow 9.1.0, the first argument was ``file``. This is now deprecated,
and ``path`` should be used instead.
"""
if path is None:
if "file" in options:
warnings.warn(
"The 'file' argument is deprecated and will be removed in Pillow "
"10 (2023-07-01). Use 'path' instead.",
DeprecationWarning,
)
path = options.pop("file")
else:
raise TypeError("Missing required argument: 'path'")
args = ["display"]
if "title" in options:
args += ["-name", options["title"]]
args.append(path)
subprocess.Popen(args)
os.remove(path)
return 1
class GmDisplayViewer(UnixViewer): class GmDisplayViewer(UnixViewer):
"""The GraphicsMagick ``gm display`` command.""" """The GraphicsMagick ``gm display`` command."""
@ -217,6 +286,27 @@ class GmDisplayViewer(UnixViewer):
command = "gm display" command = "gm display"
return command, executable return command, executable
def show_file(self, path=None, **options):
"""
Display given file.
Before Pillow 9.1.0, the first argument was ``file``. This is now deprecated,
and ``path`` should be used instead.
"""
if path is None:
if "file" in options:
warnings.warn(
"The 'file' argument is deprecated and will be removed in Pillow "
"10 (2023-07-01). Use 'path' instead.",
DeprecationWarning,
)
path = options.pop("file")
else:
raise TypeError("Missing required argument: 'path'")
subprocess.Popen(["gm", "display", path])
os.remove(path)
return 1
class EogViewer(UnixViewer): class EogViewer(UnixViewer):
"""The GNOME Image Viewer ``eog`` command.""" """The GNOME Image Viewer ``eog`` command."""
@ -226,6 +316,27 @@ class EogViewer(UnixViewer):
command = "eog -n" command = "eog -n"
return command, executable return command, executable
def show_file(self, path=None, **options):
"""
Display given file.
Before Pillow 9.1.0, the first argument was ``file``. This is now deprecated,
and ``path`` should be used instead.
"""
if path is None:
if "file" in options:
warnings.warn(
"The 'file' argument is deprecated and will be removed in Pillow "
"10 (2023-07-01). Use 'path' instead.",
DeprecationWarning,
)
path = options.pop("file")
else:
raise TypeError("Missing required argument: 'path'")
subprocess.Popen(["eog", "-n", path])
os.remove(path)
return 1
class XVViewer(UnixViewer): class XVViewer(UnixViewer):
""" """
@ -241,6 +352,32 @@ class XVViewer(UnixViewer):
command += f" -name {quote(title)}" command += f" -name {quote(title)}"
return command, executable return command, executable
def show_file(self, path=None, **options):
"""
Display given file.
Before Pillow 9.1.0, the first argument was ``file``. This is now deprecated,
and ``path`` should be used instead.
"""
if path is None:
if "file" in options:
warnings.warn(
"The 'file' argument is deprecated and will be removed in Pillow "
"10 (2023-07-01). Use 'path' instead.",
DeprecationWarning,
)
path = options.pop("file")
else:
raise TypeError("Missing required argument: 'path'")
args = ["xv"]
if "title" in options:
args += ["-name", options["title"]]
args.append(path)
subprocess.Popen(args)
os.remove(path)
return 1
if sys.platform not in ("win32", "darwin"): # unixoids if sys.platform not in ("win32", "darwin"): # unixoids
if shutil.which("xdg-open"): if shutil.which("xdg-open"):

View File

@ -482,6 +482,7 @@ class JpegImageFile(ImageFile.ImageFile):
""" """
Returns a dictionary containing the XMP tags. Returns a dictionary containing the XMP tags.
Requires defusedxml to be installed. Requires defusedxml to be installed.
:returns: XMP tags in a dictionary. :returns: XMP tags in a dictionary.
""" """

View File

@ -1009,6 +1009,7 @@ class PngImageFile(ImageFile.ImageFile):
""" """
Returns a dictionary containing the XMP tags. Returns a dictionary containing the XMP tags.
Requires defusedxml to be installed. Requires defusedxml to be installed.
:returns: XMP tags in a dictionary. :returns: XMP tags in a dictionary.
""" """
return ( return (

View File

@ -195,7 +195,6 @@ def _layerinfo(fp, ct_bytes):
x1 = i32(read(4)) x1 = i32(read(4))
# image info # image info
info = []
mode = [] mode = []
ct_types = i16(read(2)) ct_types = i16(read(2))
types = list(range(ct_types)) types = list(range(ct_types))
@ -211,8 +210,7 @@ def _layerinfo(fp, ct_bytes):
m = "RGBA"[type] m = "RGBA"[type]
mode.append(m) mode.append(m)
size = i32(read(4)) read(4) # size
info.append((m, size))
# figure out the image mode # figure out the image mode
mode.sort() mode.sort()
@ -229,26 +227,22 @@ def _layerinfo(fp, ct_bytes):
read(12) # filler read(12) # filler
name = "" name = ""
size = i32(read(4)) # length of the extra data field size = i32(read(4)) # length of the extra data field
combined = 0
if size: if size:
data_end = fp.tell() + size data_end = fp.tell() + size
length = i32(read(4)) length = i32(read(4))
if length: if length:
fp.seek(length - 16, io.SEEK_CUR) fp.seek(length - 16, io.SEEK_CUR)
combined += length + 4
length = i32(read(4)) length = i32(read(4))
if length: if length:
fp.seek(length, io.SEEK_CUR) fp.seek(length, io.SEEK_CUR)
combined += length + 4
length = i8(read(1)) length = i8(read(1))
if length: if length:
# Don't know the proper encoding, # Don't know the proper encoding,
# Latin-1 should be a good guess # Latin-1 should be a good guess
name = read(length).decode("latin-1", "replace") name = read(length).decode("latin-1", "replace")
combined += length + 1
fp.seek(data_end) fp.seek(data_end)
layers.append((name, mode, (x0, y0, x1, y1))) layers.append((name, mode, (x0, y0, x1, y1)))

View File

@ -238,17 +238,18 @@ def makeSpiderHeader(im):
if 1024 % lenbyt != 0: if 1024 % lenbyt != 0:
labrec += 1 labrec += 1
labbyt = labrec * lenbyt labbyt = labrec * lenbyt
hdr = []
nvalues = int(labbyt / 4) nvalues = int(labbyt / 4)
if nvalues < 23:
return []
hdr = []
for i in range(nvalues): for i in range(nvalues):
hdr.append(0.0) hdr.append(0.0)
if len(hdr) < 23:
return []
# NB these are Fortran indices # NB these are Fortran indices
hdr[1] = 1.0 # nslice (=1 for an image) hdr[1] = 1.0 # nslice (=1 for an image)
hdr[2] = float(nrow) # number of rows per slice hdr[2] = float(nrow) # number of rows per slice
hdr[3] = float(nrow) # number of records in the image
hdr[5] = 1.0 # iform for 2D image hdr[5] = 1.0 # iform for 2D image
hdr[12] = float(nsam) # number of pixels per line hdr[12] = float(nsam) # number of pixels per line
hdr[13] = float(labrec) # number of records in file header hdr[13] = float(labrec) # number of records in file header
@ -259,10 +260,7 @@ def makeSpiderHeader(im):
hdr = hdr[1:] hdr = hdr[1:]
hdr.append(0.0) hdr.append(0.0)
# pack binary data into a string # pack binary data into a string
hdrstr = [] return [struct.pack("f", v) for v in hdr]
for v in hdr:
hdrstr.append(struct.pack("f", v))
return hdrstr
def _save(im, fp, filename): def _save(im, fp, filename):

View File

@ -1124,6 +1124,7 @@ class TiffImageFile(ImageFile.ImageFile):
""" """
Returns a dictionary containing the XMP tags. Returns a dictionary containing the XMP tags.
Requires defusedxml to be installed. Requires defusedxml to be installed.
:returns: XMP tags in a dictionary. :returns: XMP tags in a dictionary.
""" """
return self._getxmp(self.tag_v2[700]) if 700 in self.tag_v2 else {} return self._getxmp(self.tag_v2[700]) if 700 in self.tag_v2 else {}

View File

@ -51,14 +51,11 @@ class WalImageFile(ImageFile.ImageFile):
self.info["next_name"] = next_name self.info["next_name"] = next_name
def load(self): def load(self):
if self.im: if not self.im:
# Already loaded
return
self.im = Image.core.new(self.mode, self.size) self.im = Image.core.new(self.mode, self.size)
self.frombytes(self.fp.read(self.size[0] * self.size[1])) self.frombytes(self.fp.read(self.size[0] * self.size[1]))
self.putpalette(quake2palette) self.putpalette(quake2palette)
Image.Image.load(self) return Image.Image.load(self)
def open(filename): def open(filename):

View File

@ -158,7 +158,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
(x1 - x0) * self.info["dpi"] // self._inch, (x1 - x0) * self.info["dpi"] // self._inch,
(y1 - y0) * self.info["dpi"] // self._inch, (y1 - y0) * self.info["dpi"] // self._inch,
) )
super().load() return super().load()
def _save(im, fp, filename): def _save(im, fp, filename):

View File

@ -29,7 +29,7 @@
* 1995-09-12 fl Created * 1995-09-12 fl Created
* 1996-04-08 fl Ready for release * 1996-04-08 fl Ready for release
* 1997-05-09 fl Use command instead of image type * 1997-05-09 fl Use command instead of image type
* 2001-03-18 fl Initialize alpha layer pointer (struct changed in 8.3) * 2001-03-18 fl Initialize alpha layer pointer (struct changed in Tk 8.3)
* 2003-04-23 fl Fixed building for Tk 8.4.1 and later (Jack Jansen) * 2003-04-23 fl Fixed building for Tk 8.4.1 and later (Jack Jansen)
* 2004-06-24 fl Fixed building for Tk 8.4.6 and later. * 2004-06-24 fl Fixed building for Tk 8.4.6 and later.
* *
@ -116,7 +116,7 @@ PyImagingPhotoPut(
block.offset[1] = 1; block.offset[1] = 1;
block.offset[2] = 2; block.offset[2] = 2;
if (strcmp(im->mode, "RGBA") == 0) { if (strcmp(im->mode, "RGBA") == 0) {
block.offset[3] = 3; /* alpha (or reserved, under 8.2) */ block.offset[3] = 3; /* alpha (or reserved, under Tk 8.2) */
} else { } else {
block.offset[3] = 0; /* no alpha */ block.offset[3] = 0; /* no alpha */
} }

View File

@ -1529,6 +1529,7 @@ static struct {
{"RGB", "RGBX", 32, copy4}, {"RGB", "RGBX", 32, copy4},
{"RGB", "RGBX;L", 32, unpackRGBAL}, {"RGB", "RGBX;L", 32, unpackRGBAL},
{"RGB", "RGBA;L", 32, unpackRGBAL}, {"RGB", "RGBA;L", 32, unpackRGBAL},
{"RGB", "RGBA;15", 16, ImagingUnpackRGBA15},
{"RGB", "BGRX", 32, ImagingUnpackBGRX}, {"RGB", "BGRX", 32, ImagingUnpackBGRX},
{"RGB", "XRGB", 32, ImagingUnpackXRGB}, {"RGB", "XRGB", 32, ImagingUnpackXRGB},
{"RGB", "XBGR", 32, ImagingUnpackXBGR}, {"RGB", "XBGR", 32, ImagingUnpackXBGR},

View File

@ -1,7 +1,7 @@
The MIT License (MIT) The MIT License (MIT)
Copyright © 2015 Information Technology Authority (ITA) <foss@ita.gov.om> Copyright © 2015 Information Technology Authority (ITA) <foss@ita.gov.om>
Copyright © 2016 Khaled Hosny <khaledhosny@eglug.org> Copyright © 2016-2022 Khaled Hosny <khaled@aliftype.com>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,4 +1,18 @@
Overview of changes leading to 0.7.1 Overview of changes leading to 0.8.0
Monday, December 13, 2021
====================================
Remove autotools build.
Support using SheenBiDi instead of FriBiDi for Unicode BiDi support.
Fix running tests with Python <= 3.6.
New API:
* raqm_get_par_resolved_direction
* raqm_get_direction_at_index
Overview of changes leading to 0.7.2
Monday, September 27, 2021 Monday, September 27, 2021
==================================== ====================================

View File

@ -6,26 +6,26 @@ Raqm
Raqm is a small library that encapsulates the logic for complex text layout and Raqm is a small library that encapsulates the logic for complex text layout and
provides a convenient API. provides a convenient API.
It currently provides bidirectional text support (using [FriBiDi][1]), shaping It currently provides bidirectional text support (using [FriBiDi][1] or
(using [HarfBuzz][2]), and proper script itemization. As a result, [SheenBidi][2]), shaping (using [HarfBuzz][3]), and proper script itemization.
Raqm can support most writing systems covered by Unicode. As a result, Raqm can support most writing systems covered by Unicode.
The documentation can be accessed on the web at: The documentation can be accessed on the web at:
> http://host-oman.github.io/libraqm/ > http://host-oman.github.io/libraqm/
Raqm (Arabic: رَقْم) is writing, also number or digit and the Arabic word for Raqm (Arabic: رَقْم) is writing, also number or digit and the Arabic word for
digital (رَقَمِيّ) shares the same root, so it is a play on “digital writing”. digital (رَقَمِيّ) shares the same root, so it is a play on “digital writing”.
Building Building
-------- --------
Raqm depends on the following libraries: Raqm depends on the following libraries:
* [FreeType][3] * [FreeType][4]
* [HarfBuzz][2] * [HarfBuzz][3]
* [FriBiDi][1] * [FriBiDi][1] or [SheenBidi][2]
To build the documentation you will also need: To build the documentation you will also need:
* [GTK-Doc][4] * [GTK-Doc][5]
To install dependencies on Fedora: To install dependencies on Fedora:
@ -48,11 +48,11 @@ directory:
$ ninja -C build $ ninja -C build
$ ninja -C build install $ ninja -C build install
To build the documentation, pass `-Ddocs=enable` to the `meson`. To build the documentation, pass `-Ddocs=true` to the `meson`.
To run the tests: To run the tests:
$ ninja -C test $ ninja -C build test
Contributing Contributing
------------ ------------
@ -68,6 +68,7 @@ Projects using Raqm
3. [FontView](https://github.com/googlei18n/fontview) 3. [FontView](https://github.com/googlei18n/fontview)
4. [Pillow](https://github.com/python-pillow) 4. [Pillow](https://github.com/python-pillow)
5. [mplcairo](https://github.com/anntzer/mplcairo) 5. [mplcairo](https://github.com/anntzer/mplcairo)
6. [CEGUI](https://github.com/cegui/cegui)
The following projects have patches to support complex text layout using Raqm: The following projects have patches to support complex text layout using Raqm:
@ -77,7 +78,8 @@ The following projects have patches to support complex text layout using Raqm:
[1]: http://fribidi.org [1]: https://github.com/fribidi/fribidi
[2]: http://harfbuzz.org [2]: https://github.com/Tehreer/SheenBidi
[3]: https://www.freetype.org [3]: https://github.com/harfbuzz/harfbuzz
[4]: https://www.gtk.org/gtk-doc [4]: https://www.freetype.org
[5]: https://www.gtk.org/gtk-doc

View File

@ -32,10 +32,10 @@
#define _RAQM_VERSION_H_ #define _RAQM_VERSION_H_
#define RAQM_VERSION_MAJOR 0 #define RAQM_VERSION_MAJOR 0
#define RAQM_VERSION_MINOR 7 #define RAQM_VERSION_MINOR 9
#define RAQM_VERSION_MICRO 2 #define RAQM_VERSION_MICRO 0
#define RAQM_VERSION_STRING "0.7.2" #define RAQM_VERSION_STRING "0.9.0"
#define RAQM_VERSION_ATLEAST(major,minor,micro) \ #define RAQM_VERSION_ATLEAST(major,minor,micro) \
((major)*10000+(minor)*100+(micro) <= \ ((major)*10000+(minor)*100+(micro) <= \

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/* /*
* Copyright © 2015 Information Technology Authority (ITA) <foss@ita.gov.om> * Copyright © 2015 Information Technology Authority (ITA) <foss@ita.gov.om>
* Copyright © 2016 Khaled Hosny <khaledhosny@eglug.org> * Copyright © 2016-2022 Khaled Hosny <khaled@aliftype.com>
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to * of this software and associated documentation files (the "Software"), to
@ -106,6 +106,9 @@ raqm_reference (raqm_t *rq);
RAQM_API void RAQM_API void
raqm_destroy (raqm_t *rq); raqm_destroy (raqm_t *rq);
RAQM_API void
raqm_clear_contents (raqm_t *rq);
RAQM_API bool RAQM_API bool
raqm_set_text (raqm_t *rq, raqm_set_text (raqm_t *rq,
const uint32_t *text, const uint32_t *text,
@ -145,6 +148,12 @@ RAQM_API bool
raqm_set_freetype_load_flags (raqm_t *rq, raqm_set_freetype_load_flags (raqm_t *rq,
int flags); int flags);
RAQM_API bool
raqm_set_freetype_load_flags_range (raqm_t *rq,
int flags,
size_t start,
size_t len);
RAQM_API bool RAQM_API bool
raqm_set_invisible_glyph (raqm_t *rq, raqm_set_invisible_glyph (raqm_t *rq,
int gid); int gid);
@ -156,6 +165,13 @@ RAQM_API raqm_glyph_t *
raqm_get_glyphs (raqm_t *rq, raqm_get_glyphs (raqm_t *rq,
size_t *length); size_t *length);
RAQM_API raqm_direction_t
raqm_get_par_resolved_direction (raqm_t *rq);
RAQM_API raqm_direction_t
raqm_get_direction_at_index (raqm_t *rq,
size_t index);
RAQM_API bool RAQM_API bool
raqm_index_to_position (raqm_t *rq, raqm_index_to_position (raqm_t *rq,
size_t *index, size_t *index,

View File

@ -24,7 +24,7 @@ Download and install:
* `CMake 3.12 or newer <https://cmake.org/download/>`_ * `CMake 3.12 or newer <https://cmake.org/download/>`_
(also available as Visual Studio component C++ CMake tools for Windows) (also available as Visual Studio component C++ CMake tools for Windows)
* `NASM <https://www.nasm.us/pub/nasm/releasebuilds/?C=M;O=D>`_ * x86/x64: `NASM <https://www.nasm.us/pub/nasm/releasebuilds/?C=M;O=D>`_
Any version of Visual Studio 2017 or newer should be supported, Any version of Visual Studio 2017 or newer should be supported,
including Visual Studio 2017 Community, or Build Tools for Visual Studio 2019. including Visual Studio 2017 Community, or Build Tools for Visual Studio 2019.
@ -42,8 +42,8 @@ behaviour of ``build_prepare.py``:
If ``PYTHON`` is unset, the version of Python used to run If ``PYTHON`` is unset, the version of Python used to run
``build_prepare.py`` will be used. If only ``PYTHON`` is set, ``build_prepare.py`` will be used. If only ``PYTHON`` is set,
``EXECUTABLE`` defaults to ``python.exe``. ``EXECUTABLE`` defaults to ``python.exe``.
* ``ARCHITECTURE`` is used to select a ``x86`` or ``x64`` build. By default, * ``ARCHITECTURE`` is used to select a ``x86``, ``x64`` or ``ARM64``build.
uses same architecture as the version of Python used to run ``build_prepare.py``. By default, uses same architecture as the version of Python used to run ``build_prepare.py``.
is used. is used.
* ``PILLOW_BUILD`` can be used to override the ``winbuild\build`` directory * ``PILLOW_BUILD`` can be used to override the ``winbuild\build`` directory
path, used to store generated build scripts and compiled libraries. path, used to store generated build scripts and compiled libraries.

View File

@ -1,4 +1,5 @@
import os import os
import platform
import shutil import shutil
import struct import struct
import subprocess import subprocess
@ -93,6 +94,7 @@ SF_MIRROR = "http://iweb.dl.sourceforge.net"
architectures = { architectures = {
"x86": {"vcvars_arch": "x86", "msbuild_arch": "Win32"}, "x86": {"vcvars_arch": "x86", "msbuild_arch": "Win32"},
"x64": {"vcvars_arch": "x86_amd64", "msbuild_arch": "x64"}, "x64": {"vcvars_arch": "x86_amd64", "msbuild_arch": "x64"},
"ARM64": {"vcvars_arch": "x86_arm64", "msbuild_arch": "ARM64"},
} }
header = [ header = [
@ -154,9 +156,9 @@ deps = {
# "bins": [r"libtiff\*.dll"], # "bins": [r"libtiff\*.dll"],
}, },
"libwebp": { "libwebp": {
"url": "http://downloads.webmproject.org/releases/webp/libwebp-1.2.1.tar.gz", "url": "http://downloads.webmproject.org/releases/webp/libwebp-1.2.2.tar.gz",
"filename": "libwebp-1.2.1.tar.gz", "filename": "libwebp-1.2.2.tar.gz",
"dir": "libwebp-1.2.1", "dir": "libwebp-1.2.2",
"build": [ "build": [
cmd_rmdir(r"output\release-static"), # clean cmd_rmdir(r"output\release-static"), # clean
cmd_nmake( cmd_nmake(
@ -219,25 +221,25 @@ deps = {
# "bins": [r"objs\{msbuild_arch}\Release\freetype.dll"], # "bins": [r"objs\{msbuild_arch}\Release\freetype.dll"],
}, },
"lcms2": { "lcms2": {
"url": SF_MIRROR + "/project/lcms/lcms/2.12/lcms2-2.12.tar.gz", "url": SF_MIRROR + "/project/lcms/lcms/2.13/lcms2-2.13.1.tar.gz",
"filename": "lcms2-2.12.tar.gz", "filename": "lcms2-2.13.1.tar.gz",
"dir": "lcms2-2.12", "dir": "lcms2-2.13.1",
"patch": { "patch": {
r"Projects\VC2017\lcms2_static\lcms2_static.vcxproj": { r"Projects\VC2019\lcms2_static\lcms2_static.vcxproj": {
# default is /MD for x86 and /MT for x64, we need /MD always # default is /MD for x86 and /MT for x64, we need /MD always
"<RuntimeLibrary>MultiThreaded</RuntimeLibrary>": "<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>", # noqa: E501 "<RuntimeLibrary>MultiThreaded</RuntimeLibrary>": "<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>", # noqa: E501
# retarget to default toolset (selected by vcvarsall.bat) # retarget to default toolset (selected by vcvarsall.bat)
"<PlatformToolset>v141</PlatformToolset>": "<PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>", # noqa: E501 "<PlatformToolset>v142</PlatformToolset>": "<PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>", # noqa: E501
# retarget to latest (selected by vcvarsall.bat) # retarget to latest (selected by vcvarsall.bat)
"<WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>": "<WindowsTargetPlatformVersion>$(WindowsSDKVersion)</WindowsTargetPlatformVersion>", # noqa: E501 "<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>": "<WindowsTargetPlatformVersion>$(WindowsSDKVersion)</WindowsTargetPlatformVersion>", # noqa: E501
} }
}, },
"build": [ "build": [
cmd_rmdir("Lib"), cmd_rmdir("Lib"),
cmd_rmdir(r"Projects\VC2017\Release"), cmd_rmdir(r"Projects\VC2019\Release"),
cmd_msbuild(r"Projects\VC2017\lcms2.sln", "Release", "Clean"), cmd_msbuild(r"Projects\VC2019\lcms2.sln", "Release", "Clean"),
cmd_msbuild( cmd_msbuild(
r"Projects\VC2017\lcms2.sln", "Release", "lcms2_static:Rebuild" r"Projects\VC2019\lcms2.sln", "Release", "lcms2_static:Rebuild"
), ),
cmd_xcopy("include", "{inc_dir}"), cmd_xcopy("include", "{inc_dir}"),
], ],
@ -278,9 +280,9 @@ deps = {
"libs": [r"imagequant.lib"], "libs": [r"imagequant.lib"],
}, },
"harfbuzz": { "harfbuzz": {
"url": "https://github.com/harfbuzz/harfbuzz/archive/3.2.0.zip", "url": "https://github.com/harfbuzz/harfbuzz/archive/3.3.2.zip",
"filename": "harfbuzz-3.2.0.zip", "filename": "harfbuzz-3.3.2.zip",
"dir": "harfbuzz-3.2.0", "dir": "harfbuzz-3.3.2",
"build": [ "build": [
cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"),
cmd_nmake(target="clean"), cmd_nmake(target="clean"),
@ -490,7 +492,10 @@ if __name__ == "__main__":
python_dir = os.environ.get("PYTHON") python_dir = os.environ.get("PYTHON")
python_exe = os.environ.get("EXECUTABLE", "python.exe") python_exe = os.environ.get("EXECUTABLE", "python.exe")
architecture = os.environ.get( architecture = os.environ.get(
"ARCHITECTURE", "x86" if struct.calcsize("P") == 4 else "x64" "ARCHITECTURE",
"ARM64"
if platform.machine() == "ARM64"
else ("x86" if struct.calcsize("P") == 4 else "x64"),
) )
build_dir = os.environ.get("PILLOW_BUILD", os.path.join(winbuild_dir, "build")) build_dir = os.environ.get("PILLOW_BUILD", os.path.join(winbuild_dir, "build"))
sources_dir = "" sources_dir = ""