mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-06-30 01:43:17 +03:00
Merge branch 'main' into enum
This commit is contained in:
commit
b38a67fa12
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
3
.github/workflows/test-docker.yml
vendored
3
.github/workflows/test-docker.yml
vendored
|
@ -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,
|
||||||
|
|
2
.github/workflows/test-mingw.yml
vendored
2
.github/workflows/test-mingw.yml
vendored
|
@ -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:
|
||||||
|
|
2
.github/workflows/test-windows.yml
vendored
2
.github/workflows/test-windows.yml
vendored
|
@ -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:
|
||||||
|
|
27
CHANGES.rst
27
CHANGES.rst
|
@ -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)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -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
|
||||||
|
|
15
README.md
15
README.md
|
@ -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
|
||||||
|
|
|
@ -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((-99909, -99990, 99999, 99999))
|
||||||
im.crop(value)
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -2,15 +2,11 @@ from PIL import WalImageFile
|
||||||
|
|
||||||
from .helper import assert_image_equal_tofile
|
from .helper import assert_image_equal_tofile
|
||||||
|
|
||||||
|
TEST_FILE = "Tests/images/hopper.wal"
|
||||||
|
|
||||||
|
|
||||||
def test_open():
|
def test_open():
|
||||||
# Arrange
|
|
||||||
TEST_FILE = "Tests/images/hopper.wal"
|
|
||||||
|
|
||||||
# Act
|
|
||||||
with WalImageFile.open(TEST_FILE) as im:
|
with WalImageFile.open(TEST_FILE) as im:
|
||||||
|
|
||||||
# Assert
|
|
||||||
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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_",
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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,9 +203,11 @@ def test_roundtrip_eye():
|
||||||
float,
|
float,
|
||||||
numpy.float32,
|
numpy.float32,
|
||||||
numpy.float64,
|
numpy.float64,
|
||||||
):
|
),
|
||||||
arr = numpy.eye(10, dtype=dtype)
|
)
|
||||||
numpy.testing.assert_array_equal(arr, numpy.array(Image.fromarray(arr)))
|
def test_roundtrip_eye(dtype):
|
||||||
|
arr = numpy.eye(10, dtype=dtype)
|
||||||
|
numpy.testing.assert_array_equal(arr, numpy.array(Image.fromarray(arr)))
|
||||||
|
|
||||||
|
|
||||||
def test_zero_size():
|
def test_zero_size():
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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/
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 |
|
||||||
|
|
23
docs/releasenotes/9.0.1.rst
Normal file
23
docs/releasenotes/9.0.1.rst
Normal 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.
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
self.im = Image.core.new(self.mode, self.size)
|
||||||
return
|
self.frombytes(self.fp.read(self._data_size))
|
||||||
|
return Image.Image.load(self)
|
||||||
self.im = Image.core.new(self.mode, self.size)
|
|
||||||
self.frombytes(self.fp.read(self._data_size))
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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))
|
||||||
|
|
||||||
|
|
|
@ -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."""
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
for name in code.co_names:
|
|
||||||
if name not in args and name != "abs":
|
|
||||||
raise ValueError(f"'{name}' not allowed")
|
|
||||||
|
|
||||||
|
def scan(code):
|
||||||
|
for const in code.co_consts:
|
||||||
|
if type(const) == type(compiled_code):
|
||||||
|
scan(const)
|
||||||
|
|
||||||
|
for name in code.co_names:
|
||||||
|
if name not in args and name != "abs":
|
||||||
|
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
|
||||||
|
|
|
@ -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(
|
||||||
os.remove(path)
|
"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.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"):
|
||||||
|
|
|
@ -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.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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)))
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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 {}
|
||||||
|
|
|
@ -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
|
self.im = Image.core.new(self.mode, self.size)
|
||||||
return
|
self.frombytes(self.fp.read(self.size[0] * self.size[1]))
|
||||||
|
self.putpalette(quake2palette)
|
||||||
self.im = Image.core.new(self.mode, self.size)
|
return Image.Image.load(self)
|
||||||
self.frombytes(self.fp.read(self.size[0] * self.size[1]))
|
|
||||||
self.putpalette(quake2palette)
|
|
||||||
Image.Image.load(self)
|
|
||||||
|
|
||||||
|
|
||||||
def open(filename):
|
def open(filename):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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 */
|
||||||
}
|
}
|
||||||
|
|
|
@ -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},
|
||||||
|
|
2
src/thirdparty/raqm/COPYING
vendored
2
src/thirdparty/raqm/COPYING
vendored
|
@ -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
|
||||||
|
|
16
src/thirdparty/raqm/NEWS
vendored
16
src/thirdparty/raqm/NEWS
vendored
|
@ -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
|
||||||
====================================
|
====================================
|
||||||
|
|
||||||
|
|
30
src/thirdparty/raqm/README.md
vendored
30
src/thirdparty/raqm/README.md
vendored
|
@ -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
|
||||||
|
|
6
src/thirdparty/raqm/raqm-version.h
vendored
6
src/thirdparty/raqm/raqm-version.h
vendored
|
@ -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) <= \
|
||||||
|
|
838
src/thirdparty/raqm/raqm.c
vendored
838
src/thirdparty/raqm/raqm.c
vendored
File diff suppressed because it is too large
Load Diff
18
src/thirdparty/raqm/raqm.h
vendored
18
src/thirdparty/raqm/raqm.h
vendored
|
@ -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,
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 = ""
|
||||||
|
|
Loading…
Reference in New Issue
Block a user