mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-10 16:22:22 +03:00
Merge branch 'python-pillow:main' into eps_plugin_perf
This commit is contained in:
commit
b8b153fd77
|
@ -27,8 +27,8 @@ install:
|
||||||
- mv c:\pillow-depends-main c:\pillow-depends
|
- mv c:\pillow-depends-main c:\pillow-depends
|
||||||
- xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images
|
- xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images
|
||||||
- 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\
|
- 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\
|
||||||
- ..\pillow-depends\gs1000w32.exe /S
|
- choco install ghostscript --version=10.0.0.20230317
|
||||||
- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs10.0.0\bin;%PATH%
|
- path c:\nasm-2.15.05;C:\Program Files\gs\gs10.00.0\bin;%PATH%
|
||||||
- cd c:\pillow\winbuild\
|
- cd c:\pillow\winbuild\
|
||||||
- ps: |
|
- ps: |
|
||||||
c:\python37\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\
|
c:\python37\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\
|
||||||
|
|
2
.github/workflows/cifuzz.yml
vendored
2
.github/workflows/cifuzz.yml
vendored
|
@ -3,10 +3,12 @@ name: CIFuzz
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
|
- ".github/workflows/cifuzz.yml"
|
||||||
- "**.c"
|
- "**.c"
|
||||||
- "**.h"
|
- "**.h"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
|
- ".github/workflows/cifuzz.yml"
|
||||||
- "**.c"
|
- "**.c"
|
||||||
- "**.h"
|
- "**.h"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
55
.github/workflows/docs.yml
vendored
Normal file
55
.github/workflows/docs.yml
vendored
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
name: Docs
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/docs.yml"
|
||||||
|
- "docs/**"
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/docs.yml"
|
||||||
|
- "docs/**"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_COLOR: 1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Docs
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
cache: pip
|
||||||
|
cache-dependency-path: ".ci/*.sh"
|
||||||
|
|
||||||
|
- name: Build system information
|
||||||
|
run: python3 .github/workflows/system-info.py
|
||||||
|
|
||||||
|
- name: Install Linux dependencies
|
||||||
|
run: |
|
||||||
|
.ci/install.sh
|
||||||
|
env:
|
||||||
|
GHA_PYTHON_VERSION: "3.x"
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
.ci/build.sh
|
||||||
|
|
||||||
|
- name: Docs
|
||||||
|
run: |
|
||||||
|
make doccheck
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
|
@ -20,7 +20,7 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Check issues"
|
- name: "Check issues"
|
||||||
uses: actions/stale@v7
|
uses: actions/stale@v8
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
only-labels: "Awaiting OP Action"
|
only-labels: "Awaiting OP Action"
|
||||||
|
|
11
.github/workflows/test-cygwin.yml
vendored
11
.github/workflows/test-cygwin.yml
vendored
|
@ -1,6 +1,15 @@
|
||||||
name: Test Cygwin
|
name: Test Cygwin
|
||||||
|
|
||||||
on: [push, pull_request, workflow_dispatch]
|
on:
|
||||||
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- ".github/workflows/docs.yml"
|
||||||
|
- "docs/**"
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- ".github/workflows/docs.yml"
|
||||||
|
- "docs/**"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
13
.github/workflows/test-docker.yml
vendored
13
.github/workflows/test-docker.yml
vendored
|
@ -1,6 +1,15 @@
|
||||||
name: Test Docker
|
name: Test Docker
|
||||||
|
|
||||||
on: [push, pull_request, workflow_dispatch]
|
on:
|
||||||
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- ".github/workflows/docs.yml"
|
||||||
|
- "docs/**"
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- ".github/workflows/docs.yml"
|
||||||
|
- "docs/**"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
@ -24,11 +33,11 @@ jobs:
|
||||||
# Then run the remainder
|
# Then run the remainder
|
||||||
alpine,
|
alpine,
|
||||||
amazon-2-amd64,
|
amazon-2-amd64,
|
||||||
|
amazon-2023-amd64,
|
||||||
arch,
|
arch,
|
||||||
centos-7-amd64,
|
centos-7-amd64,
|
||||||
centos-stream-8-amd64,
|
centos-stream-8-amd64,
|
||||||
centos-stream-9-amd64,
|
centos-stream-9-amd64,
|
||||||
debian-10-buster-x86,
|
|
||||||
debian-11-bullseye-x86,
|
debian-11-bullseye-x86,
|
||||||
fedora-36-amd64,
|
fedora-36-amd64,
|
||||||
fedora-37-amd64,
|
fedora-37-amd64,
|
||||||
|
|
11
.github/workflows/test-mingw.yml
vendored
11
.github/workflows/test-mingw.yml
vendored
|
@ -1,6 +1,15 @@
|
||||||
name: Test MinGW
|
name: Test MinGW
|
||||||
|
|
||||||
on: [push, pull_request, workflow_dispatch]
|
on:
|
||||||
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- ".github/workflows/docs.yml"
|
||||||
|
- "docs/**"
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- ".github/workflows/docs.yml"
|
||||||
|
- "docs/**"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
2
.github/workflows/test-valgrind.yml
vendored
2
.github/workflows/test-valgrind.yml
vendored
|
@ -5,10 +5,12 @@ name: Test Valgrind
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
|
- ".github/workflows/test-valgrind.yml"
|
||||||
- "**.c"
|
- "**.c"
|
||||||
- "**.h"
|
- "**.h"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
|
- ".github/workflows/test-valgrind.yml"
|
||||||
- "**.c"
|
- "**.c"
|
||||||
- "**.h"
|
- "**.h"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
17
.github/workflows/test-windows.yml
vendored
17
.github/workflows/test-windows.yml
vendored
|
@ -1,6 +1,15 @@
|
||||||
name: Test Windows
|
name: Test Windows
|
||||||
|
|
||||||
on: [push, pull_request, workflow_dispatch]
|
on:
|
||||||
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- ".github/workflows/docs.yml"
|
||||||
|
- "docs/**"
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- ".github/workflows/docs.yml"
|
||||||
|
- "docs/**"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
@ -65,8 +74,8 @@ jobs:
|
||||||
7z x winbuild\depends\nasm-2.15.05-win64.zip "-o$env:RUNNER_WORKSPACE\"
|
7z x winbuild\depends\nasm-2.15.05-win64.zip "-o$env:RUNNER_WORKSPACE\"
|
||||||
echo "$env:RUNNER_WORKSPACE\nasm-2.15.05" >> $env:GITHUB_PATH
|
echo "$env:RUNNER_WORKSPACE\nasm-2.15.05" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
winbuild\depends\gs1000w32.exe /S
|
choco install ghostscript --version=10.0.0.20230317
|
||||||
echo "C:\Program Files (x86)\gs\gs10.0.0\bin" >> $env:GITHUB_PATH
|
echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
# Install extra test images
|
# Install extra test images
|
||||||
xcopy /S /Y Tests\test-images\* Tests\images
|
xcopy /S /Y Tests\test-images\* Tests\images
|
||||||
|
@ -88,7 +97,7 @@ jobs:
|
||||||
- name: Prepare build
|
- name: Prepare build
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
& python.exe winbuild\build_prepare.py -v --python=$env:pythonLocation --srcdir
|
& python.exe winbuild\build_prepare.py -v --python $env:pythonLocation
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
- name: Build dependencies / libjpeg-turbo
|
- name: Build dependencies / libjpeg-turbo
|
||||||
|
|
16
.github/workflows/test.yml
vendored
16
.github/workflows/test.yml
vendored
|
@ -1,6 +1,15 @@
|
||||||
name: Test
|
name: Test
|
||||||
|
|
||||||
on: [push, pull_request, workflow_dispatch]
|
on:
|
||||||
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- ".github/workflows/docs.yml"
|
||||||
|
- "docs/**"
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- ".github/workflows/docs.yml"
|
||||||
|
- "docs/**"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
@ -96,11 +105,6 @@ jobs:
|
||||||
name: errors
|
name: errors
|
||||||
path: Tests/errors
|
path: Tests/errors
|
||||||
|
|
||||||
- name: Docs
|
|
||||||
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.11
|
|
||||||
run: |
|
|
||||||
make doccheck
|
|
||||||
|
|
||||||
- name: After success
|
- name: After success
|
||||||
run: |
|
run: |
|
||||||
.ci/after_success.sh
|
.ci/after_success.sh
|
||||||
|
|
48
CHANGES.rst
48
CHANGES.rst
|
@ -5,6 +5,54 @@ Changelog (Pillow)
|
||||||
9.5.0 (unreleased)
|
9.5.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Support custom comments and PLT markers when saving JPEG2000 images #6903
|
||||||
|
[joshware, radarhere, hugovk]
|
||||||
|
|
||||||
|
- Load before getting size in __array_interface__ #7034
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Support creating BGR;15, BGR;16 and BGR;24 images, but drop support for BGR;32 #7010
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Consider transparency when applying APNG blend mask #7018
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Round duration when saving animated WebP images #6996
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added reading of JPEG2000 comments #6909
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Decrement reference count #7003
|
||||||
|
[radarhere, nulano]
|
||||||
|
|
||||||
|
- Allow libtiff_support_custom_tags to be missing #7020
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Improved I;16N support #6834
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added QOI reading #6852
|
||||||
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
- Added saving RGBA images as PDFs #6925
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Do not raise an error if os.environ does not contain PATH #6935
|
||||||
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
- Close OleFileIO instance when closing or exiting FPX or MIC #7005
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added __int__ to IFDRational for Python >= 3.11 #6998
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added memoryview support to Dib.frombytes() #6988
|
||||||
|
[radarhere, nulano]
|
||||||
|
|
||||||
|
- Close file pointer copy in the libtiff encoder if still open #6986
|
||||||
|
[fcarron, radarhere]
|
||||||
|
|
||||||
- Raise an error if ImageDraw co-ordinates are incorrectly ordered #6978
|
- Raise an error if ImageDraw co-ordinates are incorrectly ordered #6978
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
|
|
11
Makefile
11
Makefile
|
@ -16,10 +16,16 @@ coverage:
|
||||||
python3 -m coverage report
|
python3 -m coverage report
|
||||||
|
|
||||||
.PHONY: doc
|
.PHONY: doc
|
||||||
doc:
|
.PHONY: html
|
||||||
|
doc html:
|
||||||
python3 -c "import PIL" > /dev/null 2>&1 || python3 -m pip install .
|
python3 -c "import PIL" > /dev/null 2>&1 || python3 -m pip install .
|
||||||
$(MAKE) -C docs html
|
$(MAKE) -C docs html
|
||||||
|
|
||||||
|
.PHONY: htmlview
|
||||||
|
htmlview:
|
||||||
|
python3 -c "import PIL" > /dev/null 2>&1 || python3 -m pip install .
|
||||||
|
$(MAKE) -C docs htmlview
|
||||||
|
|
||||||
.PHONY: doccheck
|
.PHONY: doccheck
|
||||||
doccheck:
|
doccheck:
|
||||||
$(MAKE) doc
|
$(MAKE) doc
|
||||||
|
@ -38,7 +44,8 @@ help:
|
||||||
@echo " coverage run coverage test (in progress)"
|
@echo " coverage run coverage test (in progress)"
|
||||||
@echo " doc make HTML docs"
|
@echo " doc make HTML docs"
|
||||||
@echo " docserve run an HTTP server on the docs directory"
|
@echo " docserve run an HTTP server on the docs directory"
|
||||||
@echo " html to make standalone HTML files"
|
@echo " html make HTML docs"
|
||||||
|
@echo " htmlview open the index page built by the html target in your browser"
|
||||||
@echo " inplace make inplace extension"
|
@echo " inplace make inplace extension"
|
||||||
@echo " install make and install"
|
@echo " install make and install"
|
||||||
@echo " install-coverage make and install with C coverage"
|
@echo " install-coverage make and install with C coverage"
|
||||||
|
|
|
@ -20,7 +20,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
HAS_UPLOADER = False
|
HAS_UPLOADER = False
|
||||||
|
|
||||||
if os.environ.get("SHOW_ERRORS", None):
|
if os.environ.get("SHOW_ERRORS"):
|
||||||
# local img.show for errors.
|
# local img.show for errors.
|
||||||
HAS_UPLOADER = True
|
HAS_UPLOADER = True
|
||||||
|
|
||||||
|
@ -271,7 +271,7 @@ def netpbm_available():
|
||||||
|
|
||||||
def magick_command():
|
def magick_command():
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
magickhome = os.environ.get("MAGICK_HOME", "")
|
magickhome = os.environ.get("MAGICK_HOME")
|
||||||
if magickhome:
|
if magickhome:
|
||||||
imagemagick = [os.path.join(magickhome, "convert.exe")]
|
imagemagick = [os.path.join(magickhome, "convert.exe")]
|
||||||
graphicsmagick = [os.path.join(magickhome, "gm.exe"), "convert"]
|
graphicsmagick = [os.path.join(magickhome, "gm.exe"), "convert"]
|
||||||
|
|
BIN
Tests/images/blend_transparency.png
Normal file
BIN
Tests/images/blend_transparency.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 211 B |
BIN
Tests/images/comment.jp2
Normal file
BIN
Tests/images/comment.jp2
Normal file
Binary file not shown.
BIN
Tests/images/hopper.qoi
Normal file
BIN
Tests/images/hopper.qoi
Normal file
Binary file not shown.
BIN
Tests/images/pil123rgba.qoi
Normal file
BIN
Tests/images/pil123rgba.qoi
Normal file
Binary file not shown.
|
@ -177,13 +177,14 @@ class TestEnvVars:
|
||||||
Image._apply_env_variables({"PILLOW_BLOCK_SIZE": "2m"})
|
Image._apply_env_variables({"PILLOW_BLOCK_SIZE": "2m"})
|
||||||
assert Image.core.get_block_size() == 2 * 1024 * 1024
|
assert Image.core.get_block_size() == 2 * 1024 * 1024
|
||||||
|
|
||||||
def test_warnings(self):
|
@pytest.mark.parametrize(
|
||||||
pytest.warns(
|
"var",
|
||||||
UserWarning, Image._apply_env_variables, {"PILLOW_ALIGNMENT": "15"}
|
(
|
||||||
)
|
{"PILLOW_ALIGNMENT": "15"},
|
||||||
pytest.warns(
|
{"PILLOW_BLOCK_SIZE": "1024"},
|
||||||
UserWarning, Image._apply_env_variables, {"PILLOW_BLOCK_SIZE": "1024"}
|
{"PILLOW_BLOCKS_MAX": "wat"},
|
||||||
)
|
),
|
||||||
pytest.warns(
|
|
||||||
UserWarning, Image._apply_env_variables, {"PILLOW_BLOCKS_MAX": "wat"}
|
|
||||||
)
|
)
|
||||||
|
def test_warnings(self, var):
|
||||||
|
with pytest.warns(UserWarning):
|
||||||
|
Image._apply_env_variables(var)
|
||||||
|
|
|
@ -36,12 +36,10 @@ class TestDecompressionBomb:
|
||||||
Image.MAX_IMAGE_PIXELS = 128 * 128 - 1
|
Image.MAX_IMAGE_PIXELS = 128 * 128 - 1
|
||||||
assert Image.MAX_IMAGE_PIXELS == 128 * 128 - 1
|
assert Image.MAX_IMAGE_PIXELS == 128 * 128 - 1
|
||||||
|
|
||||||
def open():
|
with pytest.warns(Image.DecompressionBombWarning):
|
||||||
with Image.open(TEST_FILE):
|
with Image.open(TEST_FILE):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
pytest.warns(Image.DecompressionBombWarning, open)
|
|
||||||
|
|
||||||
def test_exception(self):
|
def test_exception(self):
|
||||||
# Set limit to trigger exception on the test file
|
# Set limit to trigger exception on the test file
|
||||||
Image.MAX_IMAGE_PIXELS = 64 * 128 - 1
|
Image.MAX_IMAGE_PIXELS = 64 * 128 - 1
|
||||||
|
@ -87,7 +85,8 @@ class TestDecompressionCrop:
|
||||||
# same decompression bomb warnings on them.
|
# same decompression bomb warnings on them.
|
||||||
with hopper() as src:
|
with hopper() as src:
|
||||||
box = (0, 0, src.width * 2, src.height * 2)
|
box = (0, 0, src.width * 2, src.height * 2)
|
||||||
pytest.warns(Image.DecompressionBombWarning, src.crop, box)
|
with 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))
|
||||||
|
@ -95,7 +94,8 @@ class TestDecompressionCrop:
|
||||||
for value in ((-9999, -9999, -9990, -9990), (-999, -999, -990, -990)):
|
for value in ((-9999, -9999, -9990, -9990), (-999, -999, -990, -990)):
|
||||||
assert im.crop(value).size == (9, 9)
|
assert im.crop(value).size == (9, 9)
|
||||||
|
|
||||||
pytest.warns(Image.DecompressionBombWarning, im.crop, (-160, -160, 99, 99))
|
with pytest.warns(Image.DecompressionBombWarning):
|
||||||
|
im.crop((-160, -160, 99, 99))
|
||||||
|
|
||||||
with pytest.raises(Image.DecompressionBombError):
|
with pytest.raises(Image.DecompressionBombError):
|
||||||
im.crop((-99909, -99990, 99999, 99999))
|
im.crop((-99909, -99990, 99999, 99999))
|
||||||
|
|
|
@ -163,6 +163,12 @@ def test_apng_blend():
|
||||||
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
||||||
|
|
||||||
|
|
||||||
|
def test_apng_blend_transparency():
|
||||||
|
with Image.open("Tests/images/blend_transparency.png") as im:
|
||||||
|
im.seek(1)
|
||||||
|
assert im.getpixel((0, 0)) == (255, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
def test_apng_chunk_order():
|
def test_apng_chunk_order():
|
||||||
with Image.open("Tests/images/apng/fctl_actl.png") as im:
|
with Image.open("Tests/images/apng/fctl_actl.png") as im:
|
||||||
im.seek(im.n_frames - 1)
|
im.seek(im.n_frames - 1)
|
||||||
|
@ -263,13 +269,11 @@ def test_apng_chunk_errors():
|
||||||
with Image.open("Tests/images/apng/chunk_no_actl.png") as im:
|
with Image.open("Tests/images/apng/chunk_no_actl.png") as im:
|
||||||
assert not im.is_animated
|
assert not im.is_animated
|
||||||
|
|
||||||
def open():
|
with pytest.warns(UserWarning):
|
||||||
with Image.open("Tests/images/apng/chunk_multi_actl.png") as im:
|
with Image.open("Tests/images/apng/chunk_multi_actl.png") as im:
|
||||||
im.load()
|
im.load()
|
||||||
assert not im.is_animated
|
assert not im.is_animated
|
||||||
|
|
||||||
pytest.warns(UserWarning, open)
|
|
||||||
|
|
||||||
with Image.open("Tests/images/apng/chunk_actl_after_idat.png") as im:
|
with Image.open("Tests/images/apng/chunk_actl_after_idat.png") as im:
|
||||||
assert not im.is_animated
|
assert not im.is_animated
|
||||||
|
|
||||||
|
@ -287,21 +291,17 @@ def test_apng_chunk_errors():
|
||||||
|
|
||||||
|
|
||||||
def test_apng_syntax_errors():
|
def test_apng_syntax_errors():
|
||||||
def open_frames_zero():
|
with pytest.warns(UserWarning):
|
||||||
with Image.open("Tests/images/apng/syntax_num_frames_zero.png") as im:
|
with Image.open("Tests/images/apng/syntax_num_frames_zero.png") as im:
|
||||||
assert not im.is_animated
|
assert not im.is_animated
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
pytest.warns(UserWarning, open_frames_zero)
|
with pytest.warns(UserWarning):
|
||||||
|
|
||||||
def open_frames_zero_default():
|
|
||||||
with Image.open("Tests/images/apng/syntax_num_frames_zero_default.png") as im:
|
with Image.open("Tests/images/apng/syntax_num_frames_zero_default.png") as im:
|
||||||
assert not im.is_animated
|
assert not im.is_animated
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
pytest.warns(UserWarning, open_frames_zero_default)
|
|
||||||
|
|
||||||
# we can handle this case gracefully
|
# we can handle this case gracefully
|
||||||
exception = None
|
exception = None
|
||||||
with Image.open("Tests/images/apng/syntax_num_frames_low.png") as im:
|
with Image.open("Tests/images/apng/syntax_num_frames_low.png") as im:
|
||||||
|
@ -316,13 +316,11 @@ def test_apng_syntax_errors():
|
||||||
im.seek(im.n_frames - 1)
|
im.seek(im.n_frames - 1)
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
def open():
|
with pytest.warns(UserWarning):
|
||||||
with Image.open("Tests/images/apng/syntax_num_frames_invalid.png") as im:
|
with Image.open("Tests/images/apng/syntax_num_frames_invalid.png") as im:
|
||||||
assert not im.is_animated
|
assert not im.is_animated
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
pytest.warns(UserWarning, open)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"test_file",
|
"test_file",
|
||||||
|
|
|
@ -56,6 +56,7 @@ def test_handler(tmp_path):
|
||||||
|
|
||||||
def load(self, im):
|
def load(self, im):
|
||||||
self.loaded = True
|
self.loaded = True
|
||||||
|
im.fp.close()
|
||||||
return Image.new("RGB", (1, 1))
|
return Image.new("RGB", (1, 1))
|
||||||
|
|
||||||
def save(self, im, fp, filename):
|
def save(self, im, fp, filename):
|
||||||
|
|
|
@ -28,7 +28,8 @@ def test_unclosed_file():
|
||||||
im = Image.open(TEST_FILE)
|
im = Image.open(TEST_FILE)
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
pytest.warns(ResourceWarning, open)
|
with pytest.warns(ResourceWarning):
|
||||||
|
open()
|
||||||
|
|
||||||
|
|
||||||
def test_closed_file():
|
def test_closed_file():
|
||||||
|
|
|
@ -60,6 +60,7 @@ def test_stub_deprecated():
|
||||||
|
|
||||||
def load(self, im):
|
def load(self, im):
|
||||||
self.loaded = True
|
self.loaded = True
|
||||||
|
im.fp.close()
|
||||||
return Image.new("RGB", (1, 1))
|
return Image.new("RGB", (1, 1))
|
||||||
|
|
||||||
handler = Handler()
|
handler = Handler()
|
||||||
|
|
|
@ -36,7 +36,8 @@ def test_unclosed_file():
|
||||||
im = Image.open(static_test_file)
|
im = Image.open(static_test_file)
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
pytest.warns(ResourceWarning, open)
|
with pytest.warns(ResourceWarning):
|
||||||
|
open()
|
||||||
|
|
||||||
|
|
||||||
def test_closed_file():
|
def test_closed_file():
|
||||||
|
|
|
@ -18,6 +18,16 @@ def test_sanity():
|
||||||
assert_image_equal_tofile(im, "Tests/images/input_bw_one_band.png")
|
assert_image_equal_tofile(im, "Tests/images/input_bw_one_band.png")
|
||||||
|
|
||||||
|
|
||||||
|
def test_close():
|
||||||
|
with Image.open("Tests/images/input_bw_one_band.fpx") as im:
|
||||||
|
pass
|
||||||
|
assert im.ole.fp.closed
|
||||||
|
|
||||||
|
im = Image.open("Tests/images/input_bw_one_band.fpx")
|
||||||
|
im.close()
|
||||||
|
assert im.ole.fp.closed
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_file():
|
def test_invalid_file():
|
||||||
# Test an invalid OLE file
|
# Test an invalid OLE file
|
||||||
invalid_file = "Tests/images/flower.jpg"
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
|
|
@ -36,7 +36,8 @@ def test_unclosed_file():
|
||||||
im = Image.open(TEST_GIF)
|
im = Image.open(TEST_GIF)
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
pytest.warns(ResourceWarning, open)
|
with pytest.warns(ResourceWarning):
|
||||||
|
open()
|
||||||
|
|
||||||
|
|
||||||
def test_closed_file():
|
def test_closed_file():
|
||||||
|
@ -1087,7 +1088,8 @@ def test_rgb_transparency(tmp_path):
|
||||||
im = Image.new("RGB", (1, 1))
|
im = Image.new("RGB", (1, 1))
|
||||||
im.info["transparency"] = b""
|
im.info["transparency"] = b""
|
||||||
ims = [Image.new("RGB", (1, 1))]
|
ims = [Image.new("RGB", (1, 1))]
|
||||||
pytest.warns(UserWarning, im.save, out, save_all=True, append_images=ims)
|
with pytest.warns(UserWarning):
|
||||||
|
im.save(out, save_all=True, append_images=ims)
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
with Image.open(out) as reloaded:
|
||||||
assert "transparency" not in reloaded.info
|
assert "transparency" not in reloaded.info
|
||||||
|
|
|
@ -56,6 +56,7 @@ def test_handler(tmp_path):
|
||||||
|
|
||||||
def load(self, im):
|
def load(self, im):
|
||||||
self.loaded = True
|
self.loaded = True
|
||||||
|
im.fp.close()
|
||||||
return Image.new("RGB", (1, 1))
|
return Image.new("RGB", (1, 1))
|
||||||
|
|
||||||
def save(self, im, fp, filename):
|
def save(self, im, fp, filename):
|
||||||
|
|
|
@ -57,6 +57,7 @@ def test_handler(tmp_path):
|
||||||
|
|
||||||
def load(self, im):
|
def load(self, im):
|
||||||
self.loaded = True
|
self.loaded = True
|
||||||
|
im.fp.close()
|
||||||
return Image.new("RGB", (1, 1))
|
return Image.new("RGB", (1, 1))
|
||||||
|
|
||||||
def save(self, im, fp, filename):
|
def save(self, im, fp, filename):
|
||||||
|
|
|
@ -212,12 +212,10 @@ def test_save_append_images(tmp_path):
|
||||||
def test_unexpected_size():
|
def test_unexpected_size():
|
||||||
# This image has been manually hexedited to state that it is 16x32
|
# This image has been manually hexedited to state that it is 16x32
|
||||||
# while the image within is still 16x16
|
# while the image within is still 16x16
|
||||||
def open():
|
with pytest.warns(UserWarning):
|
||||||
with Image.open("Tests/images/hopper_unexpected.ico") as im:
|
with Image.open("Tests/images/hopper_unexpected.ico") as im:
|
||||||
assert im.size == (16, 16)
|
assert im.size == (16, 16)
|
||||||
|
|
||||||
pytest.warns(UserWarning, open)
|
|
||||||
|
|
||||||
|
|
||||||
def test_draw_reloaded(tmp_path):
|
def test_draw_reloaded(tmp_path):
|
||||||
with Image.open(TEST_ICO_FILE) as im:
|
with Image.open(TEST_ICO_FILE) as im:
|
||||||
|
|
|
@ -32,7 +32,8 @@ def test_unclosed_file():
|
||||||
im = Image.open(TEST_IM)
|
im = Image.open(TEST_IM)
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
pytest.warns(ResourceWarning, open)
|
with pytest.warns(ResourceWarning):
|
||||||
|
open()
|
||||||
|
|
||||||
|
|
||||||
def test_closed_file():
|
def test_closed_file():
|
||||||
|
|
|
@ -4,13 +4,21 @@ from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageFile, Jpeg2KImagePlugin, UnidentifiedImageError, features
|
from PIL import (
|
||||||
|
Image,
|
||||||
|
ImageFile,
|
||||||
|
Jpeg2KImagePlugin,
|
||||||
|
UnidentifiedImageError,
|
||||||
|
_binary,
|
||||||
|
features,
|
||||||
|
)
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
assert_image_similar,
|
assert_image_similar,
|
||||||
assert_image_similar_tofile,
|
assert_image_similar_tofile,
|
||||||
skip_unless_feature,
|
skip_unless_feature,
|
||||||
|
skip_unless_feature_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
EXTRA_DIR = "Tests/images/jpeg2000"
|
EXTRA_DIR = "Tests/images/jpeg2000"
|
||||||
|
@ -353,6 +361,35 @@ def test_subsampling_decode(name):
|
||||||
assert_image_similar(im, expected, epsilon)
|
assert_image_similar(im, expected, epsilon)
|
||||||
|
|
||||||
|
|
||||||
|
def test_comment():
|
||||||
|
with Image.open("Tests/images/comment.jp2") as im:
|
||||||
|
assert im.info["comment"] == b"Created by OpenJPEG version 2.5.0"
|
||||||
|
|
||||||
|
# Test an image that is truncated partway through a codestream
|
||||||
|
with open("Tests/images/comment.jp2", "rb") as fp:
|
||||||
|
b = BytesIO(fp.read(130))
|
||||||
|
with Image.open(b) as im:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_comment():
|
||||||
|
for comment in ("Created by Pillow", b"Created by Pillow"):
|
||||||
|
out = BytesIO()
|
||||||
|
test_card.save(out, "JPEG2000", comment=comment)
|
||||||
|
|
||||||
|
with Image.open(out) as im:
|
||||||
|
assert im.info["comment"] == b"Created by Pillow"
|
||||||
|
|
||||||
|
out = BytesIO()
|
||||||
|
long_comment = b" " * 65531
|
||||||
|
test_card.save(out, "JPEG2000", comment=long_comment)
|
||||||
|
with Image.open(out) as im:
|
||||||
|
assert im.info["comment"] == long_comment
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
test_card.save(out, "JPEG2000", comment=long_comment + b" ")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"test_file",
|
"test_file",
|
||||||
[
|
[
|
||||||
|
@ -370,3 +407,29 @@ def test_crashes(test_file):
|
||||||
im.load()
|
im.load()
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@skip_unless_feature_version("jpg_2000", "2.4.0")
|
||||||
|
def test_plt_marker():
|
||||||
|
# Search the start of the codesteam for PLT
|
||||||
|
out = BytesIO()
|
||||||
|
test_card.save(out, "JPEG2000", no_jp2=True, plt=True)
|
||||||
|
out.seek(0)
|
||||||
|
while True:
|
||||||
|
marker = out.read(2)
|
||||||
|
if not marker:
|
||||||
|
assert False, "End of stream without PLT"
|
||||||
|
|
||||||
|
jp2_boxid = _binary.i16be(marker)
|
||||||
|
if jp2_boxid == 0xFF4F:
|
||||||
|
# SOC has no length
|
||||||
|
continue
|
||||||
|
elif jp2_boxid == 0xFF58:
|
||||||
|
# PLT
|
||||||
|
return
|
||||||
|
elif jp2_boxid == 0xFF93:
|
||||||
|
assert False, "SOD without finding PLT first"
|
||||||
|
|
||||||
|
hdr = out.read(2)
|
||||||
|
length = _binary.i16be(hdr)
|
||||||
|
out.seek(length - 2, os.SEEK_CUR)
|
||||||
|
|
|
@ -984,6 +984,36 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
) as im:
|
) as im:
|
||||||
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
|
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"file_name, mode, size, tile",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"tiff_wrong_bits_per_sample.tiff",
|
||||||
|
"RGBA",
|
||||||
|
(52, 53),
|
||||||
|
[("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"tiff_wrong_bits_per_sample_2.tiff",
|
||||||
|
"RGB",
|
||||||
|
(16, 16),
|
||||||
|
[("raw", (0, 0, 16, 16), 8, ("RGB", 0, 1))],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"tiff_wrong_bits_per_sample_3.tiff",
|
||||||
|
"RGBA",
|
||||||
|
(512, 256),
|
||||||
|
[("libtiff", (0, 0, 512, 256), 0, ("RGBA", "tiff_lzw", False, 48782))],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_wrong_bits_per_sample(self, file_name, mode, size, tile):
|
||||||
|
with Image.open("Tests/images/" + file_name) as im:
|
||||||
|
assert im.mode == mode
|
||||||
|
assert im.size == size
|
||||||
|
assert im.tile == tile
|
||||||
|
im.load()
|
||||||
|
|
||||||
def test_no_rows_per_strip(self):
|
def test_no_rows_per_strip(self):
|
||||||
# This image does not have a RowsPerStrip TIFF tag
|
# This image does not have a RowsPerStrip TIFF tag
|
||||||
infile = "Tests/images/no_rows_per_strip.tif"
|
infile = "Tests/images/no_rows_per_strip.tif"
|
||||||
|
@ -1065,3 +1095,27 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
out = str(tmp_path / "temp.tif")
|
out = str(tmp_path / "temp.tif")
|
||||||
with pytest.raises(SystemError):
|
with pytest.raises(SystemError):
|
||||||
im.save(out, compression=compression)
|
im.save(out, compression=compression)
|
||||||
|
|
||||||
|
def test_save_many_compressed(self, tmp_path):
|
||||||
|
im = hopper()
|
||||||
|
out = str(tmp_path / "temp.tif")
|
||||||
|
for _ in range(10000):
|
||||||
|
im.save(out, compression="jpeg")
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"path, sizes",
|
||||||
|
(
|
||||||
|
("Tests/images/hopper.tif", ()),
|
||||||
|
("Tests/images/child_ifd.tiff", (16, 8)),
|
||||||
|
("Tests/images/child_ifd_jpeg.tiff", (20,)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_get_child_images(self, path, sizes):
|
||||||
|
with Image.open(path) as im:
|
||||||
|
ims = im.get_child_images()
|
||||||
|
|
||||||
|
assert len(ims) == len(sizes)
|
||||||
|
for i, im in enumerate(ims):
|
||||||
|
w = sizes[i]
|
||||||
|
expected = Image.new("RGB", (w, w), "#f00")
|
||||||
|
assert_image_similar(im, expected, 1)
|
||||||
|
|
|
@ -51,6 +51,16 @@ def test_seek():
|
||||||
assert im.tell() == 0
|
assert im.tell() == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_close():
|
||||||
|
with Image.open(TEST_FILE) as im:
|
||||||
|
pass
|
||||||
|
assert im.ole.fp.closed
|
||||||
|
|
||||||
|
im = Image.open(TEST_FILE)
|
||||||
|
im.close()
|
||||||
|
assert im.ole.fp.closed
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_file():
|
def test_invalid_file():
|
||||||
# Test an invalid OLE file
|
# Test an invalid OLE file
|
||||||
invalid_file = "Tests/images/flower.jpg"
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
|
|
@ -42,7 +42,8 @@ def test_unclosed_file():
|
||||||
im = Image.open(test_files[0])
|
im = Image.open(test_files[0])
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
pytest.warns(ResourceWarning, open)
|
with pytest.warns(ResourceWarning):
|
||||||
|
open()
|
||||||
|
|
||||||
|
|
||||||
def test_closed_file():
|
def test_closed_file():
|
||||||
|
|
|
@ -8,7 +8,7 @@ import pytest
|
||||||
|
|
||||||
from PIL import Image, PdfParser, features
|
from PIL import Image, PdfParser, features
|
||||||
|
|
||||||
from .helper import hopper, mark_if_feature_version
|
from .helper import hopper, mark_if_feature_version, skip_unless_feature
|
||||||
|
|
||||||
|
|
||||||
def helper_save_as_pdf(tmp_path, mode, **kwargs):
|
def helper_save_as_pdf(tmp_path, mode, **kwargs):
|
||||||
|
@ -42,6 +42,11 @@ def test_save(tmp_path, mode):
|
||||||
helper_save_as_pdf(tmp_path, mode)
|
helper_save_as_pdf(tmp_path, mode)
|
||||||
|
|
||||||
|
|
||||||
|
@skip_unless_feature("jpg_2000")
|
||||||
|
def test_save_rgba(tmp_path):
|
||||||
|
helper_save_as_pdf(tmp_path, "RGBA")
|
||||||
|
|
||||||
|
|
||||||
def test_monochrome(tmp_path):
|
def test_monochrome(tmp_path):
|
||||||
# Arrange
|
# Arrange
|
||||||
mode = "1"
|
mode = "1"
|
||||||
|
|
|
@ -27,7 +27,8 @@ def test_unclosed_file():
|
||||||
im = Image.open(test_file)
|
im = Image.open(test_file)
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
pytest.warns(ResourceWarning, open)
|
with pytest.warns(ResourceWarning):
|
||||||
|
open()
|
||||||
|
|
||||||
|
|
||||||
def test_closed_file():
|
def test_closed_file():
|
||||||
|
|
28
Tests/test_file_qoi.py
Normal file
28
Tests/test_file_qoi.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from PIL import Image, QoiImagePlugin
|
||||||
|
|
||||||
|
from .helper import assert_image_equal_tofile, assert_image_similar_tofile
|
||||||
|
|
||||||
|
|
||||||
|
def test_sanity():
|
||||||
|
with Image.open("Tests/images/hopper.qoi") as im:
|
||||||
|
assert im.mode == "RGB"
|
||||||
|
assert im.size == (128, 128)
|
||||||
|
assert im.format == "QOI"
|
||||||
|
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/hopper.png")
|
||||||
|
|
||||||
|
with Image.open("Tests/images/pil123rgba.qoi") as im:
|
||||||
|
assert im.mode == "RGBA"
|
||||||
|
assert im.size == (162, 150)
|
||||||
|
assert im.format == "QOI"
|
||||||
|
|
||||||
|
assert_image_similar_tofile(im, "Tests/images/pil123rgba.png", 0.03)
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_file():
|
||||||
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
|
||||||
|
with pytest.raises(SyntaxError):
|
||||||
|
QoiImagePlugin.QoiImageFile(invalid_file)
|
|
@ -25,7 +25,8 @@ def test_unclosed_file():
|
||||||
im = Image.open(TEST_FILE)
|
im = Image.open(TEST_FILE)
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
pytest.warns(ResourceWarning, open)
|
with pytest.warns(ResourceWarning):
|
||||||
|
open()
|
||||||
|
|
||||||
|
|
||||||
def test_closed_file():
|
def test_closed_file():
|
||||||
|
|
|
@ -29,11 +29,9 @@ def test_sanity(codec, test_path, format):
|
||||||
|
|
||||||
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
|
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
|
||||||
def test_unclosed_file():
|
def test_unclosed_file():
|
||||||
def open():
|
with pytest.warns(ResourceWarning):
|
||||||
TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg")
|
TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg")
|
||||||
|
|
||||||
pytest.warns(ResourceWarning, open)
|
|
||||||
|
|
||||||
|
|
||||||
def test_close():
|
def test_close():
|
||||||
with warnings.catch_warnings():
|
with warnings.catch_warnings():
|
||||||
|
|
|
@ -163,7 +163,9 @@ def test_save_id_section(tmp_path):
|
||||||
|
|
||||||
# Save with custom id section greater than 255 characters
|
# Save with custom id section greater than 255 characters
|
||||||
id_section = b"Test content" * 25
|
id_section = b"Test content" * 25
|
||||||
pytest.warns(UserWarning, lambda: im.save(out, id_section=id_section))
|
with pytest.warns(UserWarning):
|
||||||
|
im.save(out, id_section=id_section)
|
||||||
|
|
||||||
with Image.open(out) as test_im:
|
with Image.open(out) as test_im:
|
||||||
assert test_im.info["id_section"] == id_section[:255]
|
assert test_im.info["id_section"] == id_section[:255]
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,8 @@ class TestFileTiff:
|
||||||
im = Image.open("Tests/images/multipage.tiff")
|
im = Image.open("Tests/images/multipage.tiff")
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
pytest.warns(ResourceWarning, open)
|
with pytest.warns(ResourceWarning):
|
||||||
|
open()
|
||||||
|
|
||||||
def test_closed_file(self):
|
def test_closed_file(self):
|
||||||
with warnings.catch_warnings():
|
with warnings.catch_warnings():
|
||||||
|
@ -83,24 +84,6 @@ class TestFileTiff:
|
||||||
with Image.open("Tests/images/multipage.tiff") as im:
|
with Image.open("Tests/images/multipage.tiff") as im:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"path, sizes",
|
|
||||||
(
|
|
||||||
("Tests/images/hopper.tif", ()),
|
|
||||||
("Tests/images/child_ifd.tiff", (16, 8)),
|
|
||||||
("Tests/images/child_ifd_jpeg.tiff", (20,)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def test_get_child_images(self, path, sizes):
|
|
||||||
with Image.open(path) as im:
|
|
||||||
ims = im.get_child_images()
|
|
||||||
|
|
||||||
assert len(ims) == len(sizes)
|
|
||||||
for i, im in enumerate(ims):
|
|
||||||
w = sizes[i]
|
|
||||||
expected = Image.new("RGB", (w, w), "#f00")
|
|
||||||
assert_image_similar(im, expected, 1)
|
|
||||||
|
|
||||||
def test_mac_tiff(self):
|
def test_mac_tiff(self):
|
||||||
# Read RGBa images from macOS [@PIL136]
|
# Read RGBa images from macOS [@PIL136]
|
||||||
|
|
||||||
|
@ -117,36 +100,6 @@ class TestFileTiff:
|
||||||
with Image.open("Tests/images/hopper_bigtiff.tif") as im:
|
with Image.open("Tests/images/hopper_bigtiff.tif") as im:
|
||||||
assert_image_equal_tofile(im, "Tests/images/hopper.tif")
|
assert_image_equal_tofile(im, "Tests/images/hopper.tif")
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"file_name,mode,size,tile",
|
|
||||||
[
|
|
||||||
(
|
|
||||||
"tiff_wrong_bits_per_sample.tiff",
|
|
||||||
"RGBA",
|
|
||||||
(52, 53),
|
|
||||||
[("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"tiff_wrong_bits_per_sample_2.tiff",
|
|
||||||
"RGB",
|
|
||||||
(16, 16),
|
|
||||||
[("raw", (0, 0, 16, 16), 8, ("RGB", 0, 1))],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"tiff_wrong_bits_per_sample_3.tiff",
|
|
||||||
"RGBA",
|
|
||||||
(512, 256),
|
|
||||||
[("libtiff", (0, 0, 512, 256), 0, ("RGBA", "tiff_lzw", False, 48782))],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_wrong_bits_per_sample(self, file_name, mode, size, tile):
|
|
||||||
with Image.open("Tests/images/" + file_name) as im:
|
|
||||||
assert im.mode == mode
|
|
||||||
assert im.size == size
|
|
||||||
assert im.tile == tile
|
|
||||||
im.load()
|
|
||||||
|
|
||||||
def test_set_legacy_api(self):
|
def test_set_legacy_api(self):
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
with pytest.raises(Exception) as e:
|
with pytest.raises(Exception) as e:
|
||||||
|
@ -231,7 +184,8 @@ class TestFileTiff:
|
||||||
def test_bad_exif(self):
|
def test_bad_exif(self):
|
||||||
with Image.open("Tests/images/hopper_bad_exif.jpg") as i:
|
with Image.open("Tests/images/hopper_bad_exif.jpg") as i:
|
||||||
# Should not raise struct.error.
|
# Should not raise struct.error.
|
||||||
pytest.warns(UserWarning, i._getexif)
|
with pytest.warns(UserWarning):
|
||||||
|
i._getexif()
|
||||||
|
|
||||||
def test_save_rgba(self, tmp_path):
|
def test_save_rgba(self, tmp_path):
|
||||||
im = hopper("RGBA")
|
im = hopper("RGBA")
|
||||||
|
|
|
@ -252,7 +252,8 @@ def test_empty_metadata():
|
||||||
head = f.read(8)
|
head = f.read(8)
|
||||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||||
# Should not raise struct.error.
|
# Should not raise struct.error.
|
||||||
pytest.warns(UserWarning, info.load, f)
|
with pytest.warns(UserWarning):
|
||||||
|
info.load(f)
|
||||||
|
|
||||||
|
|
||||||
def test_iccprofile(tmp_path):
|
def test_iccprofile(tmp_path):
|
||||||
|
@ -418,11 +419,12 @@ def test_too_many_entries():
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
|
|
||||||
# 277: ("SamplesPerPixel", SHORT, 1),
|
# 277: ("SamplesPerPixel", SHORT, 1),
|
||||||
ifd._tagdata[277] = struct.pack("hh", 4, 4)
|
ifd._tagdata[277] = struct.pack("<hh", 4, 4)
|
||||||
ifd.tagtype[277] = TiffTags.SHORT
|
ifd.tagtype[277] = TiffTags.SHORT
|
||||||
|
|
||||||
# Should not raise ValueError.
|
# Should not raise ValueError.
|
||||||
pytest.warns(UserWarning, lambda: ifd[277])
|
with pytest.warns(UserWarning):
|
||||||
|
assert ifd[277] == 4
|
||||||
|
|
||||||
|
|
||||||
def test_tag_group_data():
|
def test_tag_group_data():
|
||||||
|
|
|
@ -29,7 +29,10 @@ class TestUnsupportedWebp:
|
||||||
WebPImagePlugin.SUPPORTED = False
|
WebPImagePlugin.SUPPORTED = False
|
||||||
|
|
||||||
file_path = "Tests/images/hopper.webp"
|
file_path = "Tests/images/hopper.webp"
|
||||||
pytest.warns(UserWarning, lambda: pytest.raises(OSError, Image.open, file_path))
|
with pytest.warns(UserWarning):
|
||||||
|
with pytest.raises(OSError):
|
||||||
|
with Image.open(file_path):
|
||||||
|
pass
|
||||||
|
|
||||||
if HAVE_WEBP:
|
if HAVE_WEBP:
|
||||||
WebPImagePlugin.SUPPORTED = True
|
WebPImagePlugin.SUPPORTED = True
|
||||||
|
|
|
@ -134,6 +134,18 @@ def test_timestamp_and_duration(tmp_path):
|
||||||
ts += durations[frame]
|
ts += durations[frame]
|
||||||
|
|
||||||
|
|
||||||
|
def test_float_duration(tmp_path):
|
||||||
|
temp_file = str(tmp_path / "temp.webp")
|
||||||
|
with Image.open("Tests/images/iss634.apng") as im:
|
||||||
|
assert im.info["duration"] == 70.0
|
||||||
|
|
||||||
|
im.save(temp_file, save_all=True)
|
||||||
|
|
||||||
|
with Image.open(temp_file) as reloaded:
|
||||||
|
reloaded.load()
|
||||||
|
assert reloaded.info["duration"] == 70
|
||||||
|
|
||||||
|
|
||||||
def test_seeking(tmp_path):
|
def test_seeking(tmp_path):
|
||||||
"""
|
"""
|
||||||
Create an animated WebP file, and then try seeking through frames in reverse-order,
|
Create an animated WebP file, and then try seeking through frames in reverse-order,
|
||||||
|
|
|
@ -48,6 +48,9 @@ class TestImage:
|
||||||
"RGBX",
|
"RGBX",
|
||||||
"RGBA",
|
"RGBA",
|
||||||
"RGBa",
|
"RGBa",
|
||||||
|
"BGR;15",
|
||||||
|
"BGR;16",
|
||||||
|
"BGR;24",
|
||||||
"CMYK",
|
"CMYK",
|
||||||
"YCbCr",
|
"YCbCr",
|
||||||
"LAB",
|
"LAB",
|
||||||
|
@ -57,9 +60,7 @@ class TestImage:
|
||||||
def test_image_modes_success(self, mode):
|
def test_image_modes_success(self, mode):
|
||||||
Image.new(mode, (1, 1))
|
Image.new(mode, (1, 1))
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize("mode", ("", "bad", "very very long"))
|
||||||
"mode", ("", "bad", "very very long", "BGR;15", "BGR;16", "BGR;24", "BGR;32")
|
|
||||||
)
|
|
||||||
def test_image_modes_fail(self, mode):
|
def test_image_modes_fail(self, mode):
|
||||||
with pytest.raises(ValueError) as e:
|
with pytest.raises(ValueError) as e:
|
||||||
Image.new(mode, (1, 1))
|
Image.new(mode, (1, 1))
|
||||||
|
|
|
@ -132,22 +132,26 @@ class TestImageGetPixel(AccessTest):
|
||||||
return 1
|
return 1
|
||||||
return tuple(range(1, bands + 1))
|
return tuple(range(1, bands + 1))
|
||||||
|
|
||||||
def check(self, mode, c=None):
|
def check(self, mode, expected_color=None):
|
||||||
if not c:
|
if not expected_color:
|
||||||
c = self.color(mode)
|
expected_color = self.color(mode)
|
||||||
|
|
||||||
# check putpixel
|
# check putpixel
|
||||||
im = Image.new(mode, (1, 1), None)
|
im = Image.new(mode, (1, 1), None)
|
||||||
im.putpixel((0, 0), c)
|
im.putpixel((0, 0), expected_color)
|
||||||
assert (
|
actual_color = im.getpixel((0, 0))
|
||||||
im.getpixel((0, 0)) == c
|
assert actual_color == expected_color, (
|
||||||
), f"put/getpixel roundtrip failed for mode {mode}, color {c}"
|
f"put/getpixel roundtrip failed for mode {mode}, "
|
||||||
|
f"expected {expected_color} got {actual_color}"
|
||||||
|
)
|
||||||
|
|
||||||
# check putpixel negative index
|
# check putpixel negative index
|
||||||
im.putpixel((-1, -1), c)
|
im.putpixel((-1, -1), expected_color)
|
||||||
assert (
|
actual_color = im.getpixel((-1, -1))
|
||||||
im.getpixel((-1, -1)) == c
|
assert actual_color == expected_color, (
|
||||||
), f"put/getpixel roundtrip negative index failed for mode {mode}, color {c}"
|
f"put/getpixel roundtrip negative index failed for mode {mode}, "
|
||||||
|
f"expected {expected_color} got {actual_color}"
|
||||||
|
)
|
||||||
|
|
||||||
# Check 0
|
# Check 0
|
||||||
im = Image.new(mode, (0, 0), None)
|
im = Image.new(mode, (0, 0), None)
|
||||||
|
@ -155,27 +159,32 @@ class TestImageGetPixel(AccessTest):
|
||||||
|
|
||||||
error = ValueError if self._need_cffi_access else IndexError
|
error = ValueError if self._need_cffi_access else IndexError
|
||||||
with pytest.raises(error):
|
with pytest.raises(error):
|
||||||
im.putpixel((0, 0), c)
|
im.putpixel((0, 0), expected_color)
|
||||||
with pytest.raises(error):
|
with pytest.raises(error):
|
||||||
im.getpixel((0, 0))
|
im.getpixel((0, 0))
|
||||||
# Check 0 negative index
|
# Check 0 negative index
|
||||||
with pytest.raises(error):
|
with pytest.raises(error):
|
||||||
im.putpixel((-1, -1), c)
|
im.putpixel((-1, -1), expected_color)
|
||||||
with pytest.raises(error):
|
with pytest.raises(error):
|
||||||
im.getpixel((-1, -1))
|
im.getpixel((-1, -1))
|
||||||
|
|
||||||
# check initial color
|
# check initial color
|
||||||
im = Image.new(mode, (1, 1), c)
|
im = Image.new(mode, (1, 1), expected_color)
|
||||||
assert (
|
actual_color = im.getpixel((0, 0))
|
||||||
im.getpixel((0, 0)) == c
|
assert actual_color == expected_color, (
|
||||||
), f"initial color failed for mode {mode}, color {c} "
|
f"initial color failed for mode {mode}, "
|
||||||
|
f"expected {expected_color} got {actual_color}"
|
||||||
|
)
|
||||||
|
|
||||||
# check initial color negative index
|
# check initial color negative index
|
||||||
assert (
|
actual_color = im.getpixel((-1, -1))
|
||||||
im.getpixel((-1, -1)) == c
|
assert actual_color == expected_color, (
|
||||||
), f"initial color failed with negative index for mode {mode}, color {c} "
|
f"initial color failed with negative index for mode {mode}, "
|
||||||
|
f"expected {expected_color} got {actual_color}"
|
||||||
|
)
|
||||||
|
|
||||||
# Check 0
|
# Check 0
|
||||||
im = Image.new(mode, (0, 0), c)
|
im = Image.new(mode, (0, 0), expected_color)
|
||||||
with pytest.raises(error):
|
with pytest.raises(error):
|
||||||
im.getpixel((0, 0))
|
im.getpixel((0, 0))
|
||||||
# Check 0 negative index
|
# Check 0 negative index
|
||||||
|
@ -205,13 +214,13 @@ class TestImageGetPixel(AccessTest):
|
||||||
self.check(mode)
|
self.check(mode)
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("I;16", "I;16B"))
|
@pytest.mark.parametrize("mode", ("I;16", "I;16B"))
|
||||||
def test_signedness(self, mode):
|
@pytest.mark.parametrize(
|
||||||
|
"expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1)
|
||||||
|
)
|
||||||
|
def test_signedness(self, mode, expected_color):
|
||||||
# see https://github.com/python-pillow/Pillow/issues/452
|
# see https://github.com/python-pillow/Pillow/issues/452
|
||||||
# pixelaccess is using signed int* instead of uint*
|
# pixelaccess is using signed int* instead of uint*
|
||||||
self.check(mode, 2**15 - 1)
|
self.check(mode, expected_color)
|
||||||
self.check(mode, 2**15)
|
|
||||||
self.check(mode, 2**15 + 1)
|
|
||||||
self.check(mode, 2**16 - 1)
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("P", "PA"))
|
@pytest.mark.parametrize("mode", ("P", "PA"))
|
||||||
@pytest.mark.parametrize("color", ((255, 0, 0), (255, 0, 0, 255)))
|
@pytest.mark.parametrize("color", ((255, 0, 0), (255, 0, 0, 255)))
|
||||||
|
@ -266,15 +275,10 @@ class TestCffi(AccessTest):
|
||||||
# self._test_get_access(hopper('PA')) # PA -- how do I make a PA image?
|
# self._test_get_access(hopper('PA')) # PA -- how do I make a PA image?
|
||||||
self._test_get_access(hopper("F"))
|
self._test_get_access(hopper("F"))
|
||||||
|
|
||||||
im = Image.new("I;16", (10, 10), 40000)
|
for mode in ("I;16", "I;16L", "I;16B", "I;16N", "I"):
|
||||||
self._test_get_access(im)
|
im = Image.new(mode, (10, 10), 40000)
|
||||||
im = Image.new("I;16L", (10, 10), 40000)
|
|
||||||
self._test_get_access(im)
|
|
||||||
im = Image.new("I;16B", (10, 10), 40000)
|
|
||||||
self._test_get_access(im)
|
self._test_get_access(im)
|
||||||
|
|
||||||
im = Image.new("I", (10, 10), 40000)
|
|
||||||
self._test_get_access(im)
|
|
||||||
# These don't actually appear to be modes that I can actually make,
|
# These don't actually appear to be modes that I can actually make,
|
||||||
# as unpack sets them directly into the I mode.
|
# as unpack sets them directly into the I mode.
|
||||||
# im = Image.new('I;32L', (10, 10), -2**10)
|
# im = Image.new('I;32L', (10, 10), -2**10)
|
||||||
|
@ -313,15 +317,10 @@ class TestCffi(AccessTest):
|
||||||
# self._test_set_access(i, (128, 128)) #PA -- undone how to make
|
# self._test_set_access(i, (128, 128)) #PA -- undone how to make
|
||||||
self._test_set_access(hopper("F"), 1024.0)
|
self._test_set_access(hopper("F"), 1024.0)
|
||||||
|
|
||||||
im = Image.new("I;16", (10, 10), 40000)
|
for mode in ("I;16", "I;16L", "I;16B", "I;16N", "I"):
|
||||||
self._test_set_access(im, 45000)
|
im = Image.new(mode, (10, 10), 40000)
|
||||||
im = Image.new("I;16L", (10, 10), 40000)
|
|
||||||
self._test_set_access(im, 45000)
|
|
||||||
im = Image.new("I;16B", (10, 10), 40000)
|
|
||||||
self._test_set_access(im, 45000)
|
self._test_set_access(im, 45000)
|
||||||
|
|
||||||
im = Image.new("I", (10, 10), 40000)
|
|
||||||
self._test_set_access(im, 45000)
|
|
||||||
# im = Image.new('I;32L', (10, 10), -(2**10))
|
# im = Image.new('I;32L', (10, 10), -(2**10))
|
||||||
# self._test_set_access(im, -(2**13)+1)
|
# self._test_set_access(im, -(2**13)+1)
|
||||||
# im = Image.new('I;32B', (10, 10), 2**10)
|
# im = Image.new('I;32B', (10, 10), 2**10)
|
||||||
|
@ -354,8 +353,8 @@ class TestCffi(AccessTest):
|
||||||
|
|
||||||
|
|
||||||
class TestImagePutPixelError(AccessTest):
|
class TestImagePutPixelError(AccessTest):
|
||||||
IMAGE_MODES1 = ["L", "LA", "RGB", "RGBA"]
|
IMAGE_MODES1 = ["LA", "RGB", "RGBA", "BGR;15"]
|
||||||
IMAGE_MODES2 = ["I", "I;16", "BGR;15"]
|
IMAGE_MODES2 = ["L", "I", "I;16"]
|
||||||
INVALID_TYPES = ["foo", 1.0, None]
|
INVALID_TYPES = ["foo", 1.0, None]
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", IMAGE_MODES1)
|
@pytest.mark.parametrize("mode", IMAGE_MODES1)
|
||||||
|
@ -370,6 +369,11 @@ class TestImagePutPixelError(AccessTest):
|
||||||
(
|
(
|
||||||
("L", (0, 2), "color must be int or single-element tuple"),
|
("L", (0, 2), "color must be int or single-element tuple"),
|
||||||
("LA", (0, 3), "color must be int, or tuple of one or two elements"),
|
("LA", (0, 3), "color must be int, or tuple of one or two elements"),
|
||||||
|
(
|
||||||
|
"BGR;15",
|
||||||
|
(0, 2),
|
||||||
|
"color must be int, or tuple of one or three elements",
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"RGB",
|
"RGB",
|
||||||
(0, 2, 5),
|
(0, 2, 5),
|
||||||
|
@ -398,11 +402,6 @@ class TestImagePutPixelError(AccessTest):
|
||||||
with pytest.raises(OverflowError):
|
with pytest.raises(OverflowError):
|
||||||
im.putpixel((0, 0), 2**80)
|
im.putpixel((0, 0), 2**80)
|
||||||
|
|
||||||
def test_putpixel_unrecognized_mode(self):
|
|
||||||
im = hopper("BGR;15")
|
|
||||||
with pytest.raises(ValueError, match="unrecognized image mode"):
|
|
||||||
im.putpixel((0, 0), 0)
|
|
||||||
|
|
||||||
|
|
||||||
class TestEmbeddable:
|
class TestEmbeddable:
|
||||||
@pytest.mark.xfail(reason="failing test")
|
@pytest.mark.xfail(reason="failing test")
|
||||||
|
|
|
@ -254,17 +254,6 @@ def test_p2pa_palette():
|
||||||
assert im_pa.getpalette() == im.getpalette()
|
assert im_pa.getpalette() == im.getpalette()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
|
|
||||||
def test_rgb_lab(mode):
|
|
||||||
im = Image.new(mode, (1, 1))
|
|
||||||
converted_im = im.convert("LAB")
|
|
||||||
assert converted_im.getpixel((0, 0)) == (0, 128, 128)
|
|
||||||
|
|
||||||
im = Image.new("LAB", (1, 1), (255, 0, 0))
|
|
||||||
converted_im = im.convert(mode)
|
|
||||||
assert converted_im.getpixel((0, 0))[:3] == (0, 255, 255)
|
|
||||||
|
|
||||||
|
|
||||||
def test_matrix_illegal_conversion():
|
def test_matrix_illegal_conversion():
|
||||||
# Arrange
|
# Arrange
|
||||||
im = hopper("CMYK")
|
im = hopper("CMYK")
|
||||||
|
|
|
@ -625,3 +625,14 @@ def test_constants_deprecation():
|
||||||
for name in enum.__members__:
|
for name in enum.__members__:
|
||||||
with pytest.warns(DeprecationWarning):
|
with pytest.warns(DeprecationWarning):
|
||||||
assert getattr(ImageCms, prefix + name) == enum[name]
|
assert getattr(ImageCms, prefix + name) == enum[name]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
|
||||||
|
def test_rgb_lab(mode):
|
||||||
|
im = Image.new(mode, (1, 1))
|
||||||
|
converted_im = im.convert("LAB")
|
||||||
|
assert converted_im.getpixel((0, 0)) == (0, 128, 128)
|
||||||
|
|
||||||
|
im = Image.new("LAB", (1, 1), (255, 0, 0))
|
||||||
|
converted_im = im.convert(mode)
|
||||||
|
assert converted_im.getpixel((0, 0))[:3] == (0, 255, 255)
|
||||||
|
|
|
@ -351,7 +351,8 @@ def test_rotated_transposed_font(font, orientation):
|
||||||
assert bbox_b[3] == 20 + bbox_a[2] - bbox_a[0]
|
assert bbox_b[3] == 20 + bbox_a[2] - bbox_a[0]
|
||||||
|
|
||||||
# text length is undefined for vertical text
|
# text length is undefined for vertical text
|
||||||
pytest.raises(ValueError, draw.textlength, word)
|
with pytest.raises(ValueError):
|
||||||
|
draw.textlength(word)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -872,25 +873,23 @@ def test_anchor_invalid(font):
|
||||||
d.font = font
|
d.font = font
|
||||||
|
|
||||||
for anchor in ["", "l", "a", "lax", "sa", "xa", "lx"]:
|
for anchor in ["", "l", "a", "lax", "sa", "xa", "lx"]:
|
||||||
pytest.raises(ValueError, lambda: font.getmask2("hello", anchor=anchor))
|
with pytest.raises(ValueError):
|
||||||
pytest.raises(ValueError, lambda: font.getbbox("hello", anchor=anchor))
|
font.getmask2("hello", anchor=anchor)
|
||||||
pytest.raises(ValueError, lambda: d.text((0, 0), "hello", anchor=anchor))
|
with pytest.raises(ValueError):
|
||||||
pytest.raises(ValueError, lambda: d.textbbox((0, 0), "hello", anchor=anchor))
|
font.getbbox("hello", anchor=anchor)
|
||||||
pytest.raises(
|
with pytest.raises(ValueError):
|
||||||
ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor)
|
d.text((0, 0), "hello", anchor=anchor)
|
||||||
)
|
with pytest.raises(ValueError):
|
||||||
pytest.raises(
|
d.textbbox((0, 0), "hello", anchor=anchor)
|
||||||
ValueError,
|
with pytest.raises(ValueError):
|
||||||
lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor),
|
d.multiline_text((0, 0), "foo\nbar", anchor=anchor)
|
||||||
)
|
with pytest.raises(ValueError):
|
||||||
|
d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor)
|
||||||
for anchor in ["lt", "lb"]:
|
for anchor in ["lt", "lb"]:
|
||||||
pytest.raises(
|
with pytest.raises(ValueError):
|
||||||
ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor)
|
d.multiline_text((0, 0), "foo\nbar", anchor=anchor)
|
||||||
)
|
with pytest.raises(ValueError):
|
||||||
pytest.raises(
|
d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor)
|
||||||
ValueError,
|
|
||||||
lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("bpp", (1, 2, 4, 8))
|
@pytest.mark.parametrize("bpp", (1, 2, 4, 8))
|
||||||
|
|
|
@ -360,37 +360,20 @@ def test_anchor_invalid_ttb():
|
||||||
d.font = font
|
d.font = font
|
||||||
|
|
||||||
for anchor in ["", "l", "a", "lax", "xa", "la", "ls", "ld", "lx"]:
|
for anchor in ["", "l", "a", "lax", "xa", "la", "ls", "ld", "lx"]:
|
||||||
pytest.raises(
|
with pytest.raises(ValueError):
|
||||||
ValueError, lambda: font.getmask2("hello", anchor=anchor, direction="ttb")
|
font.getmask2("hello", anchor=anchor, direction="ttb")
|
||||||
)
|
with pytest.raises(ValueError):
|
||||||
pytest.raises(
|
font.getbbox("hello", anchor=anchor, direction="ttb")
|
||||||
ValueError, lambda: font.getbbox("hello", anchor=anchor, direction="ttb")
|
with pytest.raises(ValueError):
|
||||||
)
|
d.text((0, 0), "hello", anchor=anchor, direction="ttb")
|
||||||
pytest.raises(
|
with pytest.raises(ValueError):
|
||||||
ValueError, lambda: d.text((0, 0), "hello", anchor=anchor, direction="ttb")
|
d.textbbox((0, 0), "hello", anchor=anchor, direction="ttb")
|
||||||
)
|
with pytest.raises(ValueError):
|
||||||
pytest.raises(
|
d.multiline_text((0, 0), "foo\nbar", anchor=anchor, direction="ttb")
|
||||||
ValueError,
|
with pytest.raises(ValueError):
|
||||||
lambda: d.textbbox((0, 0), "hello", anchor=anchor, direction="ttb"),
|
d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor, direction="ttb")
|
||||||
)
|
|
||||||
pytest.raises(
|
|
||||||
ValueError,
|
|
||||||
lambda: d.multiline_text(
|
|
||||||
(0, 0), "foo\nbar", anchor=anchor, direction="ttb"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
pytest.raises(
|
|
||||||
ValueError,
|
|
||||||
lambda: d.multiline_textbbox(
|
|
||||||
(0, 0), "foo\nbar", anchor=anchor, direction="ttb"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
# ttb multiline text does not support anchors at all
|
# ttb multiline text does not support anchors at all
|
||||||
pytest.raises(
|
with pytest.raises(ValueError):
|
||||||
ValueError,
|
d.multiline_text((0, 0), "foo\nbar", anchor="mm", direction="ttb")
|
||||||
lambda: d.multiline_text((0, 0), "foo\nbar", anchor="mm", direction="ttb"),
|
with pytest.raises(ValueError):
|
||||||
)
|
d.multiline_textbbox((0, 0), "foo\nbar", anchor="mm", direction="ttb")
|
||||||
pytest.raises(
|
|
||||||
ValueError,
|
|
||||||
lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor="mm", direction="ttb"),
|
|
||||||
)
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ def test_show_without_viewers():
|
||||||
viewers = ImageShow._viewers
|
viewers = ImageShow._viewers
|
||||||
ImageShow._viewers = []
|
ImageShow._viewers = []
|
||||||
|
|
||||||
im = hopper()
|
with hopper() as im:
|
||||||
assert not ImageShow.show(im)
|
assert not ImageShow.show(im)
|
||||||
|
|
||||||
ImageShow._viewers = viewers
|
ImageShow._viewers = viewers
|
||||||
|
|
|
@ -100,6 +100,9 @@ class TestImageWinDib:
|
||||||
# Act
|
# Act
|
||||||
# Make one the same as the using tobytes()/frombytes()
|
# Make one the same as the using tobytes()/frombytes()
|
||||||
test_buffer = dib1.tobytes()
|
test_buffer = dib1.tobytes()
|
||||||
|
for datatype in ("bytes", "memoryview"):
|
||||||
|
if datatype == "memoryview":
|
||||||
|
test_buffer = memoryview(test_buffer)
|
||||||
dib2.frombytes(test_buffer)
|
dib2.frombytes(test_buffer)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
|
|
|
@ -207,6 +207,9 @@ class TestLibPack:
|
||||||
0x01000083,
|
0x01000083,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_I16(self):
|
||||||
|
self.assert_pack("I;16N", "I;16N", 2, 0x0201, 0x0403, 0x0605)
|
||||||
|
|
||||||
def test_F_float(self):
|
def test_F_float(self):
|
||||||
self.assert_pack("F", "F;32F", 4, 1.539989614439558e-36, 4.063216068939723e-34)
|
self.assert_pack("F", "F;32F", 4, 1.539989614439558e-36, 4.063216068939723e-34)
|
||||||
|
|
||||||
|
@ -761,10 +764,12 @@ class TestLibUnpack:
|
||||||
self.assert_unpack("I;16", "I;16N", 2, 0x0201, 0x0403, 0x0605)
|
self.assert_unpack("I;16", "I;16N", 2, 0x0201, 0x0403, 0x0605)
|
||||||
self.assert_unpack("I;16B", "I;16N", 2, 0x0201, 0x0403, 0x0605)
|
self.assert_unpack("I;16B", "I;16N", 2, 0x0201, 0x0403, 0x0605)
|
||||||
self.assert_unpack("I;16L", "I;16N", 2, 0x0201, 0x0403, 0x0605)
|
self.assert_unpack("I;16L", "I;16N", 2, 0x0201, 0x0403, 0x0605)
|
||||||
|
self.assert_unpack("I;16N", "I;16N", 2, 0x0201, 0x0403, 0x0605)
|
||||||
else:
|
else:
|
||||||
self.assert_unpack("I;16", "I;16N", 2, 0x0102, 0x0304, 0x0506)
|
self.assert_unpack("I;16", "I;16N", 2, 0x0102, 0x0304, 0x0506)
|
||||||
self.assert_unpack("I;16B", "I;16N", 2, 0x0102, 0x0304, 0x0506)
|
self.assert_unpack("I;16B", "I;16N", 2, 0x0102, 0x0304, 0x0506)
|
||||||
self.assert_unpack("I;16L", "I;16N", 2, 0x0102, 0x0304, 0x0506)
|
self.assert_unpack("I;16L", "I;16N", 2, 0x0102, 0x0304, 0x0506)
|
||||||
|
self.assert_unpack("I;16N", "I;16N", 2, 0x0102, 0x0304, 0x0506)
|
||||||
|
|
||||||
def test_CMYK16(self):
|
def test_CMYK16(self):
|
||||||
self.assert_unpack("CMYK", "CMYK;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16))
|
self.assert_unpack("CMYK", "CMYK;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16))
|
||||||
|
|
|
@ -88,10 +88,7 @@ def test_tobytes():
|
||||||
def test_convert():
|
def test_convert():
|
||||||
im = original.copy()
|
im = original.copy()
|
||||||
|
|
||||||
verify(im.convert("I;16"))
|
for mode in ("I;16", "I;16B", "I;16N"):
|
||||||
verify(im.convert("I;16").convert("L"))
|
verify(im.convert(mode))
|
||||||
verify(im.convert("I;16").convert("I"))
|
verify(im.convert(mode).convert("L"))
|
||||||
|
verify(im.convert(mode).convert("I"))
|
||||||
verify(im.convert("I;16B"))
|
|
||||||
verify(im.convert("I;16B").convert("L"))
|
|
||||||
verify(im.convert("I;16B").convert("I"))
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import assert_deep_equal, assert_image, hopper
|
from .helper import assert_deep_equal, assert_image, hopper, skip_unless_feature
|
||||||
|
|
||||||
numpy = pytest.importorskip("numpy", reason="NumPy not installed")
|
numpy = pytest.importorskip("numpy", reason="NumPy not installed")
|
||||||
|
|
||||||
|
@ -219,6 +219,13 @@ def test_zero_size():
|
||||||
assert im.size == (0, 0)
|
assert im.size == (0, 0)
|
||||||
|
|
||||||
|
|
||||||
|
@skip_unless_feature("libtiff")
|
||||||
|
def test_load_first():
|
||||||
|
with Image.open("Tests/images/g4_orientation_5.tif") as im:
|
||||||
|
a = numpy.array(im)
|
||||||
|
assert a.shape == (88, 590)
|
||||||
|
|
||||||
|
|
||||||
def test_bool():
|
def test_bool():
|
||||||
# https://github.com/python-pillow/Pillow/issues/2044
|
# https://github.com/python-pillow/Pillow/issues/2044
|
||||||
a = numpy.zeros((10, 2), dtype=bool)
|
a = numpy.zeros((10, 2), dtype=bool)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# install libimagequant
|
# install libimagequant
|
||||||
|
|
||||||
archive=libimagequant-4.1.0
|
archive=libimagequant-4.1.1
|
||||||
|
|
||||||
./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
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||||
help:
|
help:
|
||||||
@echo "Please use \`make <target>' where <target> is one of"
|
@echo "Please use \`make <target>' where <target> is one of"
|
||||||
@echo " html to make standalone HTML files"
|
@echo " html to make standalone HTML files"
|
||||||
|
@echo " htmlview to open the index page built by the html target in your browser"
|
||||||
@echo " serve to start a local server for viewing docs"
|
@echo " serve to start a local server for viewing docs"
|
||||||
@echo " livehtml to start a local server for viewing docs and auto-reload on change"
|
@echo " livehtml to start a local server for viewing docs and auto-reload on change"
|
||||||
@echo " dirhtml to make HTML files named index.html in directories"
|
@echo " dirhtml to make HTML files named index.html in directories"
|
||||||
|
@ -45,7 +46,7 @@ clean:
|
||||||
-rm -rf $(BUILDDIR)/*
|
-rm -rf $(BUILDDIR)/*
|
||||||
|
|
||||||
install-sphinx:
|
install-sphinx:
|
||||||
$(PYTHON) -m pip install --quiet furo olefile sphinx sphinx-copybutton sphinx-inline-tabs sphinx-issues sphinx-removed-in sphinxext-opengraph
|
$(PYTHON) -m pip install --quiet furo olefile sphinx sphinx-copybutton sphinx-inline-tabs sphinx-removed-in sphinxext-opengraph
|
||||||
|
|
||||||
.PHONY: html
|
.PHONY: html
|
||||||
html:
|
html:
|
||||||
|
@ -196,6 +197,10 @@ doctest:
|
||||||
@echo "Testing of doctests in the sources finished, look at the " \
|
@echo "Testing of doctests in the sources finished, look at the " \
|
||||||
"results in $(BUILDDIR)/doctest/output.txt."
|
"results in $(BUILDDIR)/doctest/output.txt."
|
||||||
|
|
||||||
|
.PHONY: htmlview
|
||||||
|
htmlview: html
|
||||||
|
$(PYTHON) -c "import os, webbrowser; webbrowser.open('file://' + os.path.realpath('$(BUILDDIR)/html/index.html'))"
|
||||||
|
|
||||||
.PHONY: livehtml
|
.PHONY: livehtml
|
||||||
livehtml: html
|
livehtml: html
|
||||||
livereload $(BUILDDIR)/html -p 33233
|
livereload $(BUILDDIR)/html -p 33233
|
||||||
|
|
15
docs/conf.py
15
docs/conf.py
|
@ -28,11 +28,11 @@ needs_sphinx = "2.4"
|
||||||
# ones.
|
# ones.
|
||||||
extensions = [
|
extensions = [
|
||||||
"sphinx.ext.autodoc",
|
"sphinx.ext.autodoc",
|
||||||
|
"sphinx.ext.extlinks",
|
||||||
"sphinx.ext.intersphinx",
|
"sphinx.ext.intersphinx",
|
||||||
"sphinx.ext.viewcode",
|
"sphinx.ext.viewcode",
|
||||||
"sphinx_copybutton",
|
"sphinx_copybutton",
|
||||||
"sphinx_inline_tabs",
|
"sphinx_inline_tabs",
|
||||||
"sphinx_issues",
|
|
||||||
"sphinx_removed_in",
|
"sphinx_removed_in",
|
||||||
"sphinxext.opengraph",
|
"sphinxext.opengraph",
|
||||||
]
|
]
|
||||||
|
@ -317,8 +317,17 @@ def setup(app):
|
||||||
app.add_css_file("css/dark.css")
|
app.add_css_file("css/dark.css")
|
||||||
|
|
||||||
|
|
||||||
# GitHub repo for sphinx-issues
|
# sphinx.ext.extlinks
|
||||||
issues_github_path = "python-pillow/Pillow"
|
# This config is a dictionary of external sites,
|
||||||
|
# mapping unique short aliases to a base URL and a prefix.
|
||||||
|
# https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html
|
||||||
|
_repo = "https://github.com/python-pillow/Pillow/"
|
||||||
|
extlinks = {
|
||||||
|
"cve": ("https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-%s", "CVE-%s"),
|
||||||
|
"cwe": ("https://cwe.mitre.org/data/definitions/%s.html", "CWE-%s"),
|
||||||
|
"issue": (_repo + "issues/%s", "#%s"),
|
||||||
|
"pr": (_repo + "pull/%s", "#%s"),
|
||||||
|
}
|
||||||
|
|
||||||
# sphinxext.opengraph
|
# sphinxext.opengraph
|
||||||
ogp_image = (
|
ogp_image = (
|
||||||
|
|
|
@ -271,7 +271,7 @@ FreeType 2.7
|
||||||
Support for FreeType 2.7 has been removed.
|
Support for FreeType 2.7 has been removed.
|
||||||
|
|
||||||
We recommend upgrading to at least `FreeType`_ 2.10.4, which fixed a severe
|
We recommend upgrading to at least `FreeType`_ 2.10.4, which fixed a severe
|
||||||
vulnerability introduced in FreeType 2.6 (:cve:`CVE-2020-15999`).
|
vulnerability introduced in FreeType 2.6 (:cve:`2020-15999`).
|
||||||
|
|
||||||
.. _FreeType: https://freetype.org/
|
.. _FreeType: https://freetype.org/
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,6 @@ Pillow also provides limited support for a few additional modes, including:
|
||||||
* ``BGR;15`` (15-bit reversed true colour)
|
* ``BGR;15`` (15-bit reversed true colour)
|
||||||
* ``BGR;16`` (16-bit reversed true colour)
|
* ``BGR;16`` (16-bit reversed true colour)
|
||||||
* ``BGR;24`` (24-bit reversed true colour)
|
* ``BGR;24`` (24-bit reversed true colour)
|
||||||
* ``BGR;32`` (32-bit reversed true colour)
|
|
||||||
|
|
||||||
Premultiplied alpha is where the values for each other channel have been
|
Premultiplied alpha is where the values for each other channel have been
|
||||||
multiplied by the alpha. For example, an RGBA pixel of ``(10, 20, 30, 127)``
|
multiplied by the alpha. For example, an RGBA pixel of ``(10, 20, 30, 127)``
|
||||||
|
|
|
@ -589,6 +589,19 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
||||||
|
|
||||||
.. versionadded:: 9.1.0
|
.. versionadded:: 9.1.0
|
||||||
|
|
||||||
|
**comment**
|
||||||
|
Adds a custom comment to the file, replacing the default
|
||||||
|
"Created by OpenJPEG version" comment.
|
||||||
|
|
||||||
|
.. versionadded:: 9.5.0
|
||||||
|
|
||||||
|
**plt**
|
||||||
|
If ``True`` and OpenJPEG 2.4.0 or later is available, then include a PLT
|
||||||
|
(packet length, tile-part header) marker in the produced file.
|
||||||
|
Defaults to ``False``.
|
||||||
|
|
||||||
|
.. versionadded:: 9.5.0
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
To enable JPEG 2000 support, you need to build and install the OpenJPEG
|
To enable JPEG 2000 support, you need to build and install the OpenJPEG
|
||||||
|
@ -1457,8 +1470,13 @@ PDF
|
||||||
^^^
|
^^^
|
||||||
|
|
||||||
Pillow can write PDF (Acrobat) images. Such images are written as binary PDF 1.4
|
Pillow can write PDF (Acrobat) images. Such images are written as binary PDF 1.4
|
||||||
files, using either JPEG or HEX encoding depending on the image mode (and
|
files. Different encoding methods are used, depending on the image mode.
|
||||||
whether JPEG support is available or not).
|
|
||||||
|
* 1 mode images are saved using TIFF encoding, or JPEG encoding if libtiff support is
|
||||||
|
unavailable
|
||||||
|
* L, RGB and CMYK mode images use JPEG encoding
|
||||||
|
* P mode images use HEX encoding
|
||||||
|
* RGBA mode images use JPEG2000 encoding
|
||||||
|
|
||||||
.. _pdf-saving:
|
.. _pdf-saving:
|
||||||
|
|
||||||
|
@ -1544,6 +1562,13 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
|
||||||
|
|
||||||
.. versionadded:: 5.3.0
|
.. versionadded:: 5.3.0
|
||||||
|
|
||||||
|
QOI
|
||||||
|
^^^
|
||||||
|
|
||||||
|
.. versionadded:: 9.5.0
|
||||||
|
|
||||||
|
Pillow identifies and reads images in Quite OK Image format.
|
||||||
|
|
||||||
XV Thumbnails
|
XV Thumbnails
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,18 @@ Install Pillow with :command:`pip`::
|
||||||
python3 -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
python3 -m pip install --upgrade Pillow
|
python3 -m pip install --upgrade Pillow
|
||||||
|
|
||||||
|
While we provide binaries for both x86-64 and arm64, we do not provide universal2
|
||||||
|
binaries. However, it is simple to combine our current binaries to create one::
|
||||||
|
|
||||||
|
python3 -m pip download --only-binary=:all: --platform macosx_10_10_x86_64 Pillow
|
||||||
|
python3 -m pip download --only-binary=:all: --platform macosx_11_0_arm64 Pillow
|
||||||
|
python3 -m pip install delocate
|
||||||
|
|
||||||
|
Then, with the names of the downloaded wheels, use Python to combine them::
|
||||||
|
|
||||||
|
from delocate.fuse import fuse_wheels
|
||||||
|
fuse_wheels('Pillow-9.4.0-2-cp39-cp39-macosx_10_10_x86_64.whl', 'Pillow-9.4.0-cp39-cp39-macosx_11_0_arm64.whl', 'Pillow-9.4.0-cp39-cp39-macosx_11_0_universal2.whl')
|
||||||
|
|
||||||
.. tab:: Windows
|
.. tab:: Windows
|
||||||
|
|
||||||
We provide Pillow binaries for Windows compiled for the matrix of
|
We provide Pillow binaries for Windows compiled for the matrix of
|
||||||
|
@ -169,7 +181,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-4.1**
|
* Pillow has been tested with libimagequant **2.6-4.1.1**
|
||||||
* 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.
|
||||||
|
@ -186,8 +198,8 @@ Many of Pillow's features require external libraries:
|
||||||
* Pillow wheels since version 8.2.0 include a modified version of libraqm that
|
* Pillow wheels since version 8.2.0 include a modified version of libraqm that
|
||||||
loads libfribidi at runtime if it is installed.
|
loads libfribidi at runtime if it is installed.
|
||||||
On Windows this requires compiling FriBiDi and installing ``fribidi.dll``
|
On Windows this requires compiling FriBiDi and installing ``fribidi.dll``
|
||||||
into a directory listed in the `Dynamic-Link Library Search Order (Microsoft Docs)
|
into a directory listed in the `Dynamic-link library search order (Microsoft Learn)
|
||||||
<https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order#search-order-for-desktop-applications>`_
|
<https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order#search-order-for-unpackaged-apps>`_
|
||||||
(``fribidi-0.dll`` or ``libfribidi-0.dll`` are also detected).
|
(``fribidi-0.dll`` or ``libfribidi-0.dll`` are also detected).
|
||||||
See `Build Options`_ to see how to build this version.
|
See `Build Options`_ to see how to build this version.
|
||||||
* Previous versions of Pillow (5.0.0 to 8.1.2) linked libraqm dynamically at runtime.
|
* Previous versions of Pillow (5.0.0 to 8.1.2) linked libraqm dynamically at runtime.
|
||||||
|
@ -424,6 +436,8 @@ These platforms are built and tested for every change.
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Amazon Linux 2 | 3.7 | x86-64 |
|
| Amazon Linux 2 | 3.7 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
|
| Amazon Linux 2023 | 3.9 | x86-64 |
|
||||||
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Arch | 3.9 | x86-64 |
|
| Arch | 3.9 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| CentOS 7 | 3.9 | x86-64 |
|
| CentOS 7 | 3.9 | x86-64 |
|
||||||
|
@ -432,8 +446,6 @@ These platforms are built and tested for every change.
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| CentOS Stream 9 | 3.9 | x86-64 |
|
| CentOS Stream 9 | 3.9 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Debian 10 Buster | 3.7 | x86 |
|
|
||||||
+----------------------------------+----------------------------+---------------------+
|
|
||||||
| Debian 11 Bullseye | 3.9 | x86 |
|
| Debian 11 Bullseye | 3.9 | x86 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Fedora 36 | 3.10 | x86-64 |
|
| Fedora 36 | 3.10 | x86-64 |
|
||||||
|
|
|
@ -19,6 +19,7 @@ if "%1" == "help" (
|
||||||
:help
|
:help
|
||||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||||
echo. html to make standalone HTML files
|
echo. html to make standalone HTML files
|
||||||
|
echo. htmlview to open the index page built by the html target in your browser
|
||||||
echo. dirhtml to make HTML files named index.html in directories
|
echo. dirhtml to make HTML files named index.html in directories
|
||||||
echo. singlehtml to make a single large HTML file
|
echo. singlehtml to make a single large HTML file
|
||||||
echo. pickle to make pickle files
|
echo. pickle to make pickle files
|
||||||
|
@ -44,11 +45,22 @@ if "%1" == "clean" (
|
||||||
goto end
|
goto end
|
||||||
)
|
)
|
||||||
|
|
||||||
if "%1" == "html" (
|
set html=false
|
||||||
|
if "%1%" == "html" set html=true
|
||||||
|
if "%1%" == "htmlview" set html=true
|
||||||
|
if "%html%" == "true" (
|
||||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||||
if errorlevel 1 exit /b 1
|
if errorlevel 1 exit /b 1
|
||||||
echo.
|
echo.
|
||||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||||
|
|
||||||
|
if "%1" == "htmlview" (
|
||||||
|
if EXIST "%BUILDDIR%\html\index.html" (
|
||||||
|
echo.Opening "%BUILDDIR%\html\index.html" in the default web browser...
|
||||||
|
start "" "%BUILDDIR%\html\index.html"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
goto end
|
goto end
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -320,8 +320,8 @@ Methods
|
||||||
:param xy: Two points to define the bounding box. Sequence of either
|
:param xy: Two points to define the bounding box. Sequence of either
|
||||||
``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0`` and
|
``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0`` and
|
||||||
``y1 >= y0``. The bounding box is inclusive of both endpoints.
|
``y1 >= y0``. The bounding box is inclusive of both endpoints.
|
||||||
:param outline: Color to use for the outline.
|
|
||||||
:param fill: Color to use for the fill.
|
:param fill: Color to use for the fill.
|
||||||
|
:param outline: Color to use for the outline.
|
||||||
:param width: The line width, in pixels.
|
:param width: The line width, in pixels.
|
||||||
|
|
||||||
.. versionadded:: 5.3.0
|
.. versionadded:: 5.3.0
|
||||||
|
@ -334,11 +334,11 @@ Methods
|
||||||
``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0`` and
|
``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``, where ``x1 >= x0`` and
|
||||||
``y1 >= y0``. The bounding box is inclusive of both endpoints.
|
``y1 >= y0``. The bounding box is inclusive of both endpoints.
|
||||||
:param radius: Radius of the corners.
|
:param radius: Radius of the corners.
|
||||||
:param outline: Color to use for the outline.
|
|
||||||
:param fill: Color to use for the fill.
|
:param fill: Color to use for the fill.
|
||||||
|
:param outline: Color to use for the outline.
|
||||||
:param width: The line width, in pixels.
|
:param width: The line width, in pixels.
|
||||||
:param corners: A tuple of whether to round each corner,
|
:param corners: A tuple of whether to round each corner,
|
||||||
`(top_left, top_right, bottom_right, bottom_left)`.
|
``(top_left, top_right, bottom_right, bottom_left)``.
|
||||||
|
|
||||||
.. versionadded:: 8.2.0
|
.. versionadded:: 8.2.0
|
||||||
|
|
||||||
|
|
|
@ -10,19 +10,13 @@ distributions.
|
||||||
|
|
||||||
- ``python3-dbg`` package for the gdb extensions and python symbols
|
- ``python3-dbg`` package for the gdb extensions and python symbols
|
||||||
- ``gdb`` and ``valgrind``
|
- ``gdb`` and ``valgrind``
|
||||||
- Potentially debug symbols for libraries. On ubuntu they're shipped
|
- Potentially debug symbols for libraries. On Ubuntu you can follow those
|
||||||
in package-dbgsym packages, from a different repo.
|
instructions to install the corresponding packages: `Debug Symbol Packages <https://wiki.ubuntu.com/Debug%20Symbol%20Packages#Getting_-dbgsym.ddeb_packages>`_
|
||||||
|
|
||||||
::
|
Then ``sudo apt-get install libtiff5-dbgsym``
|
||||||
|
|
||||||
deb http://ddebs.ubuntu.com focal main restricted universe multiverse
|
- There's a bug with the ``python3-dbg`` package for at least Python 3.8 on
|
||||||
deb http://ddebs.ubuntu.com focal-updates main restricted universe multiverse
|
Ubuntu 20.04, and you need to add a new link or two to make it autoload when
|
||||||
deb http://ddebs.ubuntu.com focal-proposed main restricted universe multiverse
|
|
||||||
|
|
||||||
Then ``sudo apt-get update && sudo apt-get install libtiff5-dbgsym``
|
|
||||||
|
|
||||||
- There's a bug with the dbg package for at least python 3.8 on ubuntu
|
|
||||||
20.04, and you need to add a new link or two to make it autoload when
|
|
||||||
running python:
|
running python:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
|
@ -6,7 +6,7 @@ CVE-2016-0740 -- Buffer overflow in TiffDecode.c
|
||||||
------------------------------------------------
|
------------------------------------------------
|
||||||
|
|
||||||
Pillow 3.1.0 and earlier when linked against libtiff >= 4.0.0 on x64
|
Pillow 3.1.0 and earlier when linked against libtiff >= 4.0.0 on x64
|
||||||
may overflow a buffer when reading a specially crafted tiff file (:cve:`CVE-2016-0740`).
|
may overflow a buffer when reading a specially crafted tiff file (:cve:`2016-0740`).
|
||||||
|
|
||||||
Specifically, libtiff >= 4.0.0 changed the return type of
|
Specifically, libtiff >= 4.0.0 changed the return type of
|
||||||
``TIFFScanlineSize`` from ``int32`` to machine dependent
|
``TIFFScanlineSize`` from ``int32`` to machine dependent
|
||||||
|
@ -24,7 +24,7 @@ CVE-2016-0775 -- Buffer overflow in FliDecode.c
|
||||||
-----------------------------------------------
|
-----------------------------------------------
|
||||||
|
|
||||||
In all versions of Pillow, dating back at least to the last PIL 1.1.7
|
In all versions of Pillow, dating back at least to the last PIL 1.1.7
|
||||||
release, FliDecode.c has a buffer overflow error (:cve:`CVE-2016-0775`).
|
release, FliDecode.c has a buffer overflow error (:cve:`2016-0775`).
|
||||||
|
|
||||||
Around line 192:
|
Around line 192:
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ CVE-2016-2533 -- Buffer overflow in PcdDecode.c
|
||||||
-----------------------------------------------
|
-----------------------------------------------
|
||||||
|
|
||||||
In all versions of Pillow, dating back at least to the last PIL 1.1.7
|
In all versions of Pillow, dating back at least to the last PIL 1.1.7
|
||||||
release, ``PcdDecode.c`` has a buffer overflow error (:cve:`CVE-2016-2533`).
|
release, ``PcdDecode.c`` has a buffer overflow error (:cve:`2016-2533`).
|
||||||
|
|
||||||
The ``state.buffer`` for ``PcdDecode.c`` is allocated based on a 3
|
The ``state.buffer`` for ``PcdDecode.c`` is allocated based on a 3
|
||||||
bytes per pixel sizing, where ``PcdDecode.c`` wrote into the buffer
|
bytes per pixel sizing, where ``PcdDecode.c`` wrote into the buffer
|
||||||
|
|
|
@ -7,7 +7,7 @@ CVE-2016-3076 -- Buffer overflow in Jpeg2KEncode.c
|
||||||
|
|
||||||
Pillow between 2.5.0 and 3.1.1 may overflow a buffer when writing
|
Pillow between 2.5.0 and 3.1.1 may overflow a buffer when writing
|
||||||
large Jpeg2000 files, allowing for code execution or other memory
|
large Jpeg2000 files, allowing for code execution or other memory
|
||||||
corruption (:cve:`CVE-2016-3076`).
|
corruption (:cve:`2016-3076`).
|
||||||
|
|
||||||
This occurs specifically in the function ``j2k_encode_entry``, at the line:
|
This occurs specifically in the function ``j2k_encode_entry``, at the line:
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ Security
|
||||||
========
|
========
|
||||||
|
|
||||||
This release catches several buffer overruns, as well as addressing
|
This release catches several buffer overruns, as well as addressing
|
||||||
:cve:`CVE-2019-16865`. The CVE is regarding DOS problems, such as consuming large
|
:cve:`2019-16865`. The CVE is regarding DOS problems, such as consuming large
|
||||||
amounts of memory, or taking a large amount of time to process an image.
|
amounts of memory, or taking a large amount of time to process an image.
|
||||||
|
|
||||||
In RawDecode.c, an error is now thrown if skip is calculated to be less than
|
In RawDecode.c, an error is now thrown if skip is calculated to be less than
|
||||||
|
|
|
@ -6,13 +6,13 @@ Security
|
||||||
|
|
||||||
This release addresses several security problems.
|
This release addresses several security problems.
|
||||||
|
|
||||||
:cve:`CVE-2019-19911` is regarding FPX images. If an image reports that it has a large
|
:cve:`2019-19911` is regarding FPX images. If an image reports that it has a large
|
||||||
number of bands, a large amount of resources will be used when trying to process the
|
number of bands, a large amount of resources will be used when trying to process the
|
||||||
image. This is fixed by limiting the number of bands to those usable by Pillow.
|
image. This is fixed by limiting the number of bands to those usable by Pillow.
|
||||||
|
|
||||||
Buffer overruns were found when processing an SGI (:cve:`CVE-2020-5311`),
|
Buffer overruns were found when processing an SGI (:cve:`2020-5311`),
|
||||||
PCX (:cve:`CVE-2020-5312`) or FLI image (:cve:`CVE-2020-5313`). Checks have been added
|
PCX (:cve:`2020-5312`) or FLI image (:cve:`2020-5313`). Checks have been added
|
||||||
to prevent this.
|
to prevent this.
|
||||||
|
|
||||||
:cve:`CVE-2020-5310`: Overflow checks have been added when calculating the size of a
|
:cve:`2020-5310`: Overflow checks have been added when calculating the size of a
|
||||||
memory block to be reallocated in the processing of a TIFF image.
|
memory block to be reallocated in the processing of a TIFF image.
|
||||||
|
|
|
@ -72,11 +72,11 @@ Security
|
||||||
|
|
||||||
This release includes security fixes.
|
This release includes security fixes.
|
||||||
|
|
||||||
* :cve:`CVE-2020-10177` Fix multiple out-of-bounds reads in FLI decoding
|
* :cve:`2020-10177` Fix multiple out-of-bounds reads in FLI decoding
|
||||||
* :cve:`CVE-2020-10378` Fix bounds overflow in PCX decoding
|
* :cve:`2020-10378` Fix bounds overflow in PCX decoding
|
||||||
* :cve:`CVE-2020-10379` Fix two buffer overflows in TIFF decoding
|
* :cve:`2020-10379` Fix two buffer overflows in TIFF decoding
|
||||||
* :cve:`CVE-2020-10994` Fix bounds overflow in JPEG 2000 decoding
|
* :cve:`2020-10994` Fix bounds overflow in JPEG 2000 decoding
|
||||||
* :cve:`CVE-2020-11538` Fix buffer overflow in SGI-RLE decoding
|
* :cve:`2020-11538` Fix buffer overflow in SGI-RLE decoding
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
Security
|
Security
|
||||||
========
|
========
|
||||||
|
|
||||||
Update FreeType used in binary wheels to `2.10.4`_ to fix :cve:`CVE-2020-15999`:
|
Update FreeType used in binary wheels to `2.10.4`_ to fix :cve:`2020-15999`:
|
||||||
|
|
||||||
- A heap buffer overflow has been found in the handling of embedded PNG bitmaps,
|
- A heap buffer overflow has been found in the handling of embedded PNG bitmaps,
|
||||||
introduced in FreeType version 2.6.
|
introduced in FreeType version 2.6.
|
||||||
|
|
|
@ -11,7 +11,7 @@ Support for FreeType 2.7 is deprecated and will be removed in Pillow 9.0.0 (2022
|
||||||
when FreeType 2.8 will be the minimum supported.
|
when FreeType 2.8 will be the minimum supported.
|
||||||
|
|
||||||
We recommend upgrading to at least FreeType `2.10.4`_, which fixed a severe
|
We recommend upgrading to at least FreeType `2.10.4`_, which fixed a severe
|
||||||
vulnerability introduced in FreeType 2.6 (:cve:`CVE-2020-15999`).
|
vulnerability introduced in FreeType 2.6 (:cve:`2020-15999`).
|
||||||
|
|
||||||
.. _2.10.4: https://sourceforge.net/projects/freetype/files/freetype2/2.10.4/
|
.. _2.10.4: https://sourceforge.net/projects/freetype/files/freetype2/2.10.4/
|
||||||
|
|
||||||
|
@ -40,13 +40,13 @@ This release includes security fixes.
|
||||||
|
|
||||||
* An out-of-bounds read when saving TIFFs with custom metadata through LibTIFF
|
* An out-of-bounds read when saving TIFFs with custom metadata through LibTIFF
|
||||||
* An out-of-bounds read when saving a GIF of 1px width
|
* An out-of-bounds read when saving a GIF of 1px width
|
||||||
* :cve:`CVE-2020-35653` Buffer read overrun in PCX decoding
|
* :cve:`2020-35653` Buffer read overrun in PCX decoding
|
||||||
|
|
||||||
The PCX image decoder used the reported image stride to calculate the row buffer,
|
The PCX image decoder used the reported image stride to calculate the row buffer,
|
||||||
rather than calculating it from the image size. This issue dates back to the PIL fork.
|
rather than calculating it from the image size. This issue dates back to the PIL fork.
|
||||||
Thanks to Google's `OSS-Fuzz`_ project for finding this.
|
Thanks to Google's `OSS-Fuzz`_ project for finding this.
|
||||||
|
|
||||||
* :cve:`CVE-2020-35654` Fix TIFF out-of-bounds write error
|
* :cve:`2020-35654` Fix TIFF out-of-bounds write error
|
||||||
|
|
||||||
Out-of-bounds write in ``TiffDecode.c`` when reading corrupt YCbCr files in some
|
Out-of-bounds write in ``TiffDecode.c`` when reading corrupt YCbCr files in some
|
||||||
LibTIFF versions (4.1.0/Ubuntu 20.04, but not 4.0.9/Ubuntu 18.04). In some cases
|
LibTIFF versions (4.1.0/Ubuntu 20.04, but not 4.0.9/Ubuntu 18.04). In some cases
|
||||||
|
@ -55,7 +55,7 @@ an out-of-bounds write in ``TiffDecode.c``. This potentially affects Pillow vers
|
||||||
from 6.0.0 to 8.0.1, depending on the version of LibTIFF. This was reported through
|
from 6.0.0 to 8.0.1, depending on the version of LibTIFF. This was reported through
|
||||||
`Tidelift`_.
|
`Tidelift`_.
|
||||||
|
|
||||||
* :cve:`CVE-2020-35655` Fix for SGI Decode buffer overrun
|
* :cve:`2020-35655` Fix for SGI Decode buffer overrun
|
||||||
|
|
||||||
4 byte read overflow in ``SgiRleDecode.c``, where the code was not correctly checking the
|
4 byte read overflow in ``SgiRleDecode.c``, where the code was not correctly checking the
|
||||||
offsets and length tables. Independently reported through `Tidelift`_ and Google's
|
offsets and length tables. Independently reported through `Tidelift`_ and Google's
|
||||||
|
|
|
@ -4,19 +4,19 @@
|
||||||
Security
|
Security
|
||||||
========
|
========
|
||||||
|
|
||||||
:cve:`CVE-2021-25289`: The previous fix for :cve:`CVE-2020-35654` was insufficient
|
:cve:`2021-25289`: The previous fix for :cve:`2020-35654` was insufficient
|
||||||
due to incorrect error checking in ``TiffDecode.c``.
|
due to incorrect error checking in ``TiffDecode.c``.
|
||||||
|
|
||||||
:cve:`CVE-2021-25290`: In ``TiffDecode.c``, there is a negative-offset ``memcpy``
|
:cve:`2021-25290`: In ``TiffDecode.c``, there is a negative-offset ``memcpy``
|
||||||
with an invalid size.
|
with an invalid size.
|
||||||
|
|
||||||
:cve:`CVE-2021-25291`: In ``TiffDecode.c``, invalid tile boundaries could lead to
|
:cve:`2021-25291`: In ``TiffDecode.c``, invalid tile boundaries could lead to
|
||||||
an out-of-bounds read in ``TIFFReadRGBATile``.
|
an out-of-bounds read in ``TIFFReadRGBATile``.
|
||||||
|
|
||||||
:cve:`CVE-2021-25292`: The PDF parser has a catastrophic backtracking regex
|
:cve:`2021-25292`: The PDF parser has a catastrophic backtracking regex
|
||||||
that could be used as a DOS attack.
|
that could be used as a DOS attack.
|
||||||
|
|
||||||
:cve:`CVE-2021-25293`: There is an out-of-bounds read in ``SgiRleDecode.c``,
|
:cve:`2021-25293`: There is an out-of-bounds read in ``SgiRleDecode.c``,
|
||||||
since Pillow 4.3.0.
|
since Pillow 4.3.0.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
Security
|
Security
|
||||||
========
|
========
|
||||||
|
|
||||||
There is an exhaustion of memory DOS in the BLP (:cve:`CVE-2021-27921`),
|
There is an exhaustion of memory DOS in the BLP (:cve:`2021-27921`),
|
||||||
ICNS (:cve:`CVE-2021-27922`) and ICO (:cve:`CVE-2021-27923`) container formats
|
ICNS (:cve:`2021-27922`) and ICO (:cve:`2021-27923`) container formats
|
||||||
where Pillow did not properly check the reported size of the contained image.
|
where Pillow did not properly check the reported size of the contained image.
|
||||||
These images could cause arbitrarily large memory allocations. This was reported
|
These images could cause arbitrarily large memory allocations. This was reported
|
||||||
by Jiayi Lin, Luke Shaffer, Xinran Xie, and Akshay Ajayan of
|
by Jiayi Lin, Luke Shaffer, Xinran Xie, and Akshay Ajayan of
|
||||||
|
|
|
@ -129,15 +129,15 @@ Security
|
||||||
|
|
||||||
These were all found with `OSS-Fuzz`_.
|
These were all found with `OSS-Fuzz`_.
|
||||||
|
|
||||||
:cve:`CVE-2021-25287`, :cve:`CVE-2021-25288`: Fix OOB read in Jpeg2KDecode
|
:cve:`2021-25287`, :cve:`2021-25288`: Fix OOB read in Jpeg2KDecode
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
* For J2k images with multiple bands, it's legal to have different widths for each band,
|
* For J2k images with multiple bands, it's legal to have different widths for each band,
|
||||||
e.g. 1 byte for ``L``, 4 bytes for ``A``.
|
e.g. 1 byte for ``L``, 4 bytes for ``A``.
|
||||||
* This dates to Pillow 2.4.0.
|
* This dates to Pillow 2.4.0.
|
||||||
|
|
||||||
:cve:`CVE-2021-28675`: Fix DOS in PsdImagePlugin
|
:cve:`2021-28675`: Fix DOS in PsdImagePlugin
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
* :py:class:`.PsdImagePlugin.PsdImageFile` did not sanity check the number of input
|
* :py:class:`.PsdImagePlugin.PsdImageFile` did not sanity check the number of input
|
||||||
layers with regard to the size of the data block, this could lead to a
|
layers with regard to the size of the data block, this could lead to a
|
||||||
|
@ -145,15 +145,15 @@ These were all found with `OSS-Fuzz`_.
|
||||||
:py:meth:`~PIL.Image.Image.load`.
|
:py:meth:`~PIL.Image.Image.load`.
|
||||||
* This dates to the PIL fork.
|
* This dates to the PIL fork.
|
||||||
|
|
||||||
:cve:`CVE-2021-28676`: Fix FLI DOS
|
:cve:`2021-28676`: Fix FLI DOS
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
* ``FliDecode.c`` did not properly check that the block advance was non-zero,
|
* ``FliDecode.c`` did not properly check that the block advance was non-zero,
|
||||||
potentially leading to an infinite loop on load.
|
potentially leading to an infinite loop on load.
|
||||||
* This dates to the PIL fork.
|
* This dates to the PIL fork.
|
||||||
|
|
||||||
:cve:`CVE-2021-28677`: Fix EPS DOS on _open
|
:cve:`2021-28677`: Fix EPS DOS on _open
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
* The readline used in EPS has to deal with any combination of ``\r`` and ``\n`` as line
|
* The readline used in EPS has to deal with any combination of ``\r`` and ``\n`` as line
|
||||||
endings. It accidentally used a quadratic method of accumulating lines while looking
|
endings. It accidentally used a quadratic method of accumulating lines while looking
|
||||||
|
@ -162,8 +162,8 @@ These were all found with `OSS-Fuzz`_.
|
||||||
open phase, before an image was accepted for opening.
|
open phase, before an image was accepted for opening.
|
||||||
* This dates to the PIL fork.
|
* This dates to the PIL fork.
|
||||||
|
|
||||||
:cve:`CVE-2021-28678`: Fix BLP DOS
|
:cve:`2021-28678`: Fix BLP DOS
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
* ``BlpImagePlugin`` did not properly check that reads after jumping to file offsets
|
* ``BlpImagePlugin`` did not properly check that reads after jumping to file offsets
|
||||||
returned data. This could lead to a denial-of-service where the decoder could be run a
|
returned data. This could lead to a denial-of-service where the decoder could be run a
|
||||||
|
|
|
@ -85,7 +85,7 @@ Security
|
||||||
Buffer overflow
|
Buffer overflow
|
||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
This release addresses :cve:`CVE-2021-34552`. PIL since 1.1.4 and Pillow since 1.0
|
This release addresses :cve:`2021-34552`. PIL since 1.1.4 and Pillow since 1.0
|
||||||
allowed parameters passed into a convert function to trigger buffer overflow in
|
allowed parameters passed into a convert function to trigger buffer overflow in
|
||||||
Convert.c.
|
Convert.c.
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
Security
|
Security
|
||||||
========
|
========
|
||||||
|
|
||||||
* :cve:`CVE-2021-23437`: Avoid a potential ReDoS (regular expression denial of service)
|
* :cve:`2021-23437`: Avoid a potential ReDoS (regular expression denial of service)
|
||||||
in :py:class:`~PIL.ImageColor`'s :py:meth:`~PIL.ImageColor.getrgb` by raising
|
in :py:class:`~PIL.ImageColor`'s :py:meth:`~PIL.ImageColor.getrgb` by raising
|
||||||
:py:exc:`ValueError` if the color specifier is too long. Present since Pillow 5.2.0.
|
:py:exc:`ValueError` if the color specifier is too long. Present since Pillow 5.2.0.
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ FreeType 2.7
|
||||||
Support for FreeType 2.7 has been removed; FreeType 2.8 is the minimum supported.
|
Support for FreeType 2.7 has been removed; FreeType 2.8 is the minimum supported.
|
||||||
|
|
||||||
We recommend upgrading to at least `FreeType`_ 2.10.4, which fixed a severe
|
We recommend upgrading to at least `FreeType`_ 2.10.4, which fixed a severe
|
||||||
vulnerability introduced in FreeType 2.6 (:cve:`CVE-2020-15999`).
|
vulnerability introduced in FreeType 2.6 (:cve:`2020-15999`).
|
||||||
|
|
||||||
.. _FreeType: https://freetype.org/
|
.. _FreeType: https://freetype.org/
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ Google's `OSS-Fuzz`_ project for finding this issue.
|
||||||
Restrict builtins available to ImageMath.eval
|
Restrict builtins available to ImageMath.eval
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
:cve:`CVE-2022-22817`: To limit :py:class:`PIL.ImageMath` to working with images, Pillow
|
:cve:`2022-22817`: To limit :py:class:`PIL.ImageMath` to working with images, Pillow
|
||||||
will now restrict the builtins available to :py:meth:`PIL.ImageMath.eval`. This will
|
will now restrict the builtins available to :py:meth:`PIL.ImageMath.eval`. This will
|
||||||
help prevent problems arising if users evaluate arbitrary expressions, such as
|
help prevent problems arising if users evaluate arbitrary expressions, such as
|
||||||
``ImageMath.eval("exec(exit())")``.
|
``ImageMath.eval("exec(exit())")``.
|
||||||
|
@ -127,7 +127,7 @@ help prevent problems arising if users evaluate arbitrary expressions, such as
|
||||||
Fixed ImagePath.Path array handling
|
Fixed ImagePath.Path array handling
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
:cve:`CVE-2022-22815` (:cwe:`CWE-126`) and :cve:`CVE-2022-22816` (:cwe:`CWE-665`) were
|
:cve:`2022-22815` (:cwe:`126`) and :cve:`2022-22816` (:cwe:`665`) were
|
||||||
found when initializing ``ImagePath.Path``.
|
found when initializing ``ImagePath.Path``.
|
||||||
|
|
||||||
.. _OSS-Fuzz: https://github.com/google/oss-fuzz
|
.. _OSS-Fuzz: https://github.com/google/oss-fuzz
|
||||||
|
|
|
@ -6,12 +6,12 @@ Security
|
||||||
|
|
||||||
This release addresses several security problems.
|
This release addresses several security problems.
|
||||||
|
|
||||||
:cve:`CVE-2022-24303`: If the path to the temporary directory on Linux or macOS
|
: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
|
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
|
``im.show()`` (and related actions), and potentially remove an unrelated file. This
|
||||||
has been present since PIL.
|
has been present since PIL.
|
||||||
|
|
||||||
:cve:`CVE-2022-22817`: While Pillow 9.0 restricted top-level builtins available to
|
: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
|
:py:meth:`PIL.ImageMath.eval`, it did not prevent builtins available to lambda
|
||||||
expressions. These are now also restricted.
|
expressions. These are now also restricted.
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ Security
|
||||||
|
|
||||||
This release addresses several security problems.
|
This release addresses several security problems.
|
||||||
|
|
||||||
:cve:`CVE-2022-30595`: When reading a TGA file with RLE packets that cross scan lines,
|
:cve:`2022-30595`: When reading a TGA file with RLE packets that cross scan lines,
|
||||||
Pillow reads the information past the end of the first line without deducting that
|
Pillow reads the information past the end of the first line without deducting that
|
||||||
from the length of the remaining file data. This vulnerability was introduced in Pillow
|
from the length of the remaining file data. This vulnerability was introduced in Pillow
|
||||||
9.1.0, and can cause a heap buffer overflow.
|
9.1.0, and can cause a heap buffer overflow.
|
||||||
|
|
92
docs/releasenotes/9.5.0.rst
Normal file
92
docs/releasenotes/9.5.0.rst
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
9.5.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
Backwards Incompatible Changes
|
||||||
|
==============================
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
Deprecations
|
||||||
|
============
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
API Changes
|
||||||
|
===========
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
API Additions
|
||||||
|
=============
|
||||||
|
|
||||||
|
QOI file format
|
||||||
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Pillow can now read images in Quite OK Image format.
|
||||||
|
|
||||||
|
Added ``dpi`` argument when saving PDFs
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
When saving a PDF, resolution could already be specified using the
|
||||||
|
``resolution`` argument. Now, a tuple of ``(x_resolution, y_resolution)`` can
|
||||||
|
be provided as ``dpi``. If both are provided, ``dpi`` will override
|
||||||
|
``resolution``.
|
||||||
|
|
||||||
|
Added ``corners`` argument to ``ImageDraw.rounded_rectangle()``
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
:py:meth:`.ImageDraw.rounded_rectangle` now accepts a keyword argument of
|
||||||
|
``corners``. This a tuple of Booleans, specifying whether to round each corner,
|
||||||
|
``(top_left, top_right, bottom_right, bottom_left)``.
|
||||||
|
|
||||||
|
JPEG2000 comments and PLT marker
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
When opening a JPEG2000 image, the comment may now be read into
|
||||||
|
:py:attr:`~PIL.Image.Image.info`. The ``comment`` keyword argument can be used
|
||||||
|
to save it back again.
|
||||||
|
|
||||||
|
If OpenJPEG 2.4.0 or later is available and the ``plt`` keyword argument
|
||||||
|
is present and true when saving JPEG2000 images, tell the encoder to generate
|
||||||
|
PLT markers.
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
Other Changes
|
||||||
|
=============
|
||||||
|
|
||||||
|
Added support for saving PDFs in RGBA mode
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Using the JPXDecode filter, PDFs can now be saved in RGBA mode.
|
||||||
|
|
||||||
|
Improved I;16N support
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Support has been added for I;16N access, packing and unpacking. Conversion to
|
||||||
|
and from L mode has also been added.
|
||||||
|
|
||||||
|
BGR;* modes
|
||||||
|
^^^^^^^^^^^
|
||||||
|
|
||||||
|
It is now possible to create new BGR;15, BGR;16 and BGR;24 images. Conversely, BGR;32
|
||||||
|
has been removed from ImageMode and its associated methods, dropping the little support
|
||||||
|
Pillow had for the mode.
|
||||||
|
|
||||||
|
With that, all modes listed under :ref:`concept-modes` can now be used to create a new
|
||||||
|
image.
|
|
@ -14,6 +14,7 @@ expected to be backported to earlier versions.
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
9.5.0
|
||||||
9.4.0
|
9.4.0
|
||||||
9.3.0
|
9.3.0
|
||||||
9.2.0
|
9.2.0
|
||||||
|
|
|
@ -48,7 +48,6 @@ docs =
|
||||||
sphinx>=2.4
|
sphinx>=2.4
|
||||||
sphinx-copybutton
|
sphinx-copybutton
|
||||||
sphinx-inline-tabs
|
sphinx-inline-tabs
|
||||||
sphinx-issues>=3.0.1
|
|
||||||
sphinx-removed-in
|
sphinx-removed-in
|
||||||
sphinxext-opengraph
|
sphinxext-opengraph
|
||||||
tests =
|
tests =
|
||||||
|
|
8
setup.py
8
setup.py
|
@ -242,7 +242,9 @@ def _find_include_dir(self, dirname, include):
|
||||||
return subdir
|
return subdir
|
||||||
|
|
||||||
|
|
||||||
def _cmd_exists(cmd):
|
def _cmd_exists(cmd: str) -> bool:
|
||||||
|
if "PATH" not in os.environ:
|
||||||
|
return False
|
||||||
return any(
|
return any(
|
||||||
os.access(os.path.join(path, cmd), os.X_OK)
|
os.access(os.path.join(path, cmd), os.X_OK)
|
||||||
for path in os.environ["PATH"].split(os.pathsep)
|
for path in os.environ["PATH"].split(os.pathsep)
|
||||||
|
@ -570,9 +572,7 @@ class pil_build_ext(build_ext):
|
||||||
):
|
):
|
||||||
for dirname in _find_library_dirs_ldconfig():
|
for dirname in _find_library_dirs_ldconfig():
|
||||||
_add_directory(library_dirs, dirname)
|
_add_directory(library_dirs, dirname)
|
||||||
if sys.platform.startswith("linux") and os.environ.get(
|
if sys.platform.startswith("linux") and os.environ.get("ANDROID_ROOT"):
|
||||||
"ANDROID_ROOT", None
|
|
||||||
):
|
|
||||||
# termux support for android.
|
# termux support for android.
|
||||||
# system libraries (zlib) are installed in /system/lib
|
# system libraries (zlib) are installed in /system/lib
|
||||||
# headers are at $PREFIX/include
|
# headers are at $PREFIX/include
|
||||||
|
|
|
@ -235,6 +235,14 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
return ImageFile.ImageFile.load(self)
|
return ImageFile.ImageFile.load(self)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.ole.close()
|
||||||
|
super().close()
|
||||||
|
|
||||||
|
def __exit__(self, *args):
|
||||||
|
self.ole.close()
|
||||||
|
super().__exit__()
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
|
@ -686,11 +686,7 @@ class Image:
|
||||||
@property
|
@property
|
||||||
def __array_interface__(self):
|
def __array_interface__(self):
|
||||||
# numpy array interface support
|
# numpy array interface support
|
||||||
new = {}
|
new = {"version": 3}
|
||||||
shape, typestr = _conv_type_shape(self)
|
|
||||||
new["shape"] = shape
|
|
||||||
new["typestr"] = typestr
|
|
||||||
new["version"] = 3
|
|
||||||
try:
|
try:
|
||||||
if self.mode == "1":
|
if self.mode == "1":
|
||||||
# Binary images need to be extended from bits to bytes
|
# Binary images need to be extended from bits to bytes
|
||||||
|
@ -709,6 +705,7 @@ class Image:
|
||||||
if parse_version(numpy.__version__) < parse_version("1.23"):
|
if parse_version(numpy.__version__) < parse_version("1.23"):
|
||||||
warnings.warn(e)
|
warnings.warn(e)
|
||||||
raise
|
raise
|
||||||
|
new["shape"], new["typestr"] = _conv_type_shape(self)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
|
|
|
@ -423,7 +423,6 @@ class ImageDraw:
|
||||||
self.draw.draw_rectangle(right, ink, 1)
|
self.draw.draw_rectangle(right, ink, 1)
|
||||||
|
|
||||||
def _multiline_check(self, text):
|
def _multiline_check(self, text):
|
||||||
"""Draw text."""
|
|
||||||
split_character = "\n" if isinstance(text, str) else b"\n"
|
split_character = "\n" if isinstance(text, str) else b"\n"
|
||||||
|
|
||||||
return split_character in text
|
return split_character in text
|
||||||
|
@ -464,6 +463,7 @@ class ImageDraw:
|
||||||
*args,
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
|
"""Draw text."""
|
||||||
if self._multiline_check(text):
|
if self._multiline_check(text):
|
||||||
return self.multiline_text(
|
return self.multiline_text(
|
||||||
xy,
|
xy,
|
||||||
|
|
|
@ -1012,7 +1012,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
|
||||||
if windir:
|
if windir:
|
||||||
dirs.append(os.path.join(windir, "fonts"))
|
dirs.append(os.path.join(windir, "fonts"))
|
||||||
elif sys.platform in ("linux", "linux2"):
|
elif sys.platform in ("linux", "linux2"):
|
||||||
lindirs = os.environ.get("XDG_DATA_DIRS", "")
|
lindirs = os.environ.get("XDG_DATA_DIRS")
|
||||||
if not lindirs:
|
if not lindirs:
|
||||||
# According to the freedesktop spec, XDG_DATA_DIRS should
|
# According to the freedesktop spec, XDG_DATA_DIRS should
|
||||||
# default to /usr/share
|
# default to /usr/share
|
||||||
|
|
|
@ -58,10 +58,9 @@ def getmode(mode):
|
||||||
"HSV": ("RGB", "L", ("H", "S", "V"), "|u1"),
|
"HSV": ("RGB", "L", ("H", "S", "V"), "|u1"),
|
||||||
# extra experimental modes
|
# extra experimental modes
|
||||||
"RGBa": ("RGB", "L", ("R", "G", "B", "a"), "|u1"),
|
"RGBa": ("RGB", "L", ("R", "G", "B", "a"), "|u1"),
|
||||||
"BGR;15": ("RGB", "L", ("B", "G", "R"), endian + "u2"),
|
"BGR;15": ("RGB", "L", ("B", "G", "R"), "|u1"),
|
||||||
"BGR;16": ("RGB", "L", ("B", "G", "R"), endian + "u2"),
|
"BGR;16": ("RGB", "L", ("B", "G", "R"), "|u1"),
|
||||||
"BGR;24": ("RGB", "L", ("B", "G", "R"), endian + "u3"),
|
"BGR;24": ("RGB", "L", ("B", "G", "R"), "|u1"),
|
||||||
"BGR;32": ("RGB", "L", ("B", "G", "R"), endian + "u4"),
|
|
||||||
"LA": ("L", "L", ("L", "A"), "|u1"),
|
"LA": ("L", "L", ("L", "A"), "|u1"),
|
||||||
"La": ("L", "L", ("L", "a"), "|u1"),
|
"La": ("L", "L", ("L", "a"), "|u1"),
|
||||||
"PA": ("RGB", "L", ("P", "A"), "|u1"),
|
"PA": ("RGB", "L", ("P", "A"), "|u1"),
|
||||||
|
|
|
@ -17,7 +17,7 @@ import io
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
from . import Image, ImageFile
|
from . import Image, ImageFile, _binary
|
||||||
|
|
||||||
|
|
||||||
class BoxReader:
|
class BoxReader:
|
||||||
|
@ -99,7 +99,7 @@ def _parse_codestream(fp):
|
||||||
count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
|
count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
|
||||||
|
|
||||||
hdr = fp.read(2)
|
hdr = fp.read(2)
|
||||||
lsiz = struct.unpack(">H", hdr)[0]
|
lsiz = _binary.i16be(hdr)
|
||||||
siz = hdr + fp.read(lsiz - 2)
|
siz = hdr + fp.read(lsiz - 2)
|
||||||
lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, _, _, _, _, csiz = struct.unpack_from(
|
lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, _, _, _, _, csiz = struct.unpack_from(
|
||||||
">HHIIIIIIIIH", siz
|
">HHIIIIIIIIH", siz
|
||||||
|
@ -218,6 +218,8 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
|
||||||
self._size, self.mode, self.custom_mimetype, dpi = header
|
self._size, self.mode, self.custom_mimetype, dpi = header
|
||||||
if dpi is not None:
|
if dpi is not None:
|
||||||
self.info["dpi"] = dpi
|
self.info["dpi"] = dpi
|
||||||
|
if self.fp.read(12).endswith(b"jp2c\xff\x4f\xff\x51"):
|
||||||
|
self._parse_comment()
|
||||||
else:
|
else:
|
||||||
msg = "not a JPEG 2000 file"
|
msg = "not a JPEG 2000 file"
|
||||||
raise SyntaxError(msg)
|
raise SyntaxError(msg)
|
||||||
|
@ -254,6 +256,28 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def _parse_comment(self):
|
||||||
|
hdr = self.fp.read(2)
|
||||||
|
length = _binary.i16be(hdr)
|
||||||
|
self.fp.seek(length - 2, os.SEEK_CUR)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
marker = self.fp.read(2)
|
||||||
|
if not marker:
|
||||||
|
break
|
||||||
|
typ = marker[1]
|
||||||
|
if typ in (0x90, 0xD9):
|
||||||
|
# Start of tile or end of codestream
|
||||||
|
break
|
||||||
|
hdr = self.fp.read(2)
|
||||||
|
length = _binary.i16be(hdr)
|
||||||
|
if typ == 0x64:
|
||||||
|
# Comment
|
||||||
|
self.info["comment"] = self.fp.read(length - 2)[2:]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.fp.seek(length - 2, os.SEEK_CUR)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def reduce(self):
|
def reduce(self):
|
||||||
# https://github.com/python-pillow/Pillow/issues/4343 found that the
|
# https://github.com/python-pillow/Pillow/issues/4343 found that the
|
||||||
|
@ -327,8 +351,12 @@ def _save(im, fp, filename):
|
||||||
cinema_mode = info.get("cinema_mode", "no")
|
cinema_mode = info.get("cinema_mode", "no")
|
||||||
mct = info.get("mct", 0)
|
mct = info.get("mct", 0)
|
||||||
signed = info.get("signed", False)
|
signed = info.get("signed", False)
|
||||||
fd = -1
|
comment = info.get("comment")
|
||||||
|
if isinstance(comment, str):
|
||||||
|
comment = comment.encode()
|
||||||
|
plt = info.get("plt", False)
|
||||||
|
|
||||||
|
fd = -1
|
||||||
if hasattr(fp, "fileno"):
|
if hasattr(fp, "fileno"):
|
||||||
try:
|
try:
|
||||||
fd = fp.fileno()
|
fd = fp.fileno()
|
||||||
|
@ -350,6 +378,8 @@ def _save(im, fp, filename):
|
||||||
mct,
|
mct,
|
||||||
signed,
|
signed,
|
||||||
fd,
|
fd,
|
||||||
|
comment,
|
||||||
|
plt,
|
||||||
)
|
)
|
||||||
|
|
||||||
ImageFile._save(im, fp, [("jpeg2k", (0, 0) + im.size, 0, kind)])
|
ImageFile._save(im, fp, [("jpeg2k", (0, 0) + im.size, 0, kind)])
|
||||||
|
|
|
@ -89,6 +89,14 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
||||||
def tell(self):
|
def tell(self):
|
||||||
return self.frame
|
return self.frame
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.ole.close()
|
||||||
|
super().close()
|
||||||
|
|
||||||
|
def __exit__(self, *args):
|
||||||
|
self.ole.close()
|
||||||
|
super().__exit__()
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
|
@ -173,6 +173,10 @@ def _save(im, fp, filename, save_all=False):
|
||||||
filter = "DCTDecode"
|
filter = "DCTDecode"
|
||||||
colorspace = PdfParser.PdfName("DeviceRGB")
|
colorspace = PdfParser.PdfName("DeviceRGB")
|
||||||
procset = "ImageC" # color images
|
procset = "ImageC" # color images
|
||||||
|
elif im.mode == "RGBA":
|
||||||
|
filter = "JPXDecode"
|
||||||
|
colorspace = PdfParser.PdfName("DeviceRGB")
|
||||||
|
procset = "ImageC" # color images
|
||||||
elif im.mode == "CMYK":
|
elif im.mode == "CMYK":
|
||||||
filter = "DCTDecode"
|
filter = "DCTDecode"
|
||||||
colorspace = PdfParser.PdfName("DeviceCMYK")
|
colorspace = PdfParser.PdfName("DeviceCMYK")
|
||||||
|
@ -199,6 +203,8 @@ def _save(im, fp, filename, save_all=False):
|
||||||
)
|
)
|
||||||
elif filter == "DCTDecode":
|
elif filter == "DCTDecode":
|
||||||
Image.SAVE["JPEG"](im, op, filename)
|
Image.SAVE["JPEG"](im, op, filename)
|
||||||
|
elif filter == "JPXDecode":
|
||||||
|
Image.SAVE["JPEG2000"](im, op, filename)
|
||||||
elif filter == "FlateDecode":
|
elif filter == "FlateDecode":
|
||||||
ImageFile._save(im, op, [("zip", (0, 0) + im.size, 0, im.mode)])
|
ImageFile._save(im, op, [("zip", (0, 0) + im.size, 0, im.mode)])
|
||||||
elif filter == "RunLengthDecode":
|
elif filter == "RunLengthDecode":
|
||||||
|
|
|
@ -1003,9 +1003,13 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
else:
|
else:
|
||||||
if self._prev_im and self.blend_op == Blend.OP_OVER:
|
if self._prev_im and self.blend_op == Blend.OP_OVER:
|
||||||
updated = self._crop(self.im, self.dispose_extent)
|
updated = self._crop(self.im, self.dispose_extent)
|
||||||
self._prev_im.paste(
|
if self.im.mode == "RGB" and "transparency" in self.info:
|
||||||
updated, self.dispose_extent, updated.convert("RGBA")
|
mask = updated.convert_transparent(
|
||||||
|
"RGBA", self.info["transparency"]
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
mask = updated.convert("RGBA")
|
||||||
|
self._prev_im.paste(updated, self.dispose_extent, mask)
|
||||||
self.im = self._prev_im
|
self.im = self._prev_im
|
||||||
if self.pyaccess:
|
if self.pyaccess:
|
||||||
self.pyaccess = None
|
self.pyaccess = None
|
||||||
|
|
|
@ -320,6 +320,7 @@ mode_map = {
|
||||||
"1": _PyAccess8,
|
"1": _PyAccess8,
|
||||||
"L": _PyAccess8,
|
"L": _PyAccess8,
|
||||||
"P": _PyAccess8,
|
"P": _PyAccess8,
|
||||||
|
"I;16N": _PyAccessI16_N,
|
||||||
"LA": _PyAccess32_2,
|
"LA": _PyAccess32_2,
|
||||||
"La": _PyAccess32_2,
|
"La": _PyAccess32_2,
|
||||||
"PA": _PyAccess32_2,
|
"PA": _PyAccess32_2,
|
||||||
|
|
105
src/PIL/QoiImagePlugin.py
Normal file
105
src/PIL/QoiImagePlugin.py
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
#
|
||||||
|
# The Python Imaging Library.
|
||||||
|
#
|
||||||
|
# QOI support for PIL
|
||||||
|
#
|
||||||
|
# See the README file for information on usage and redistribution.
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from . import Image, ImageFile
|
||||||
|
from ._binary import i32be as i32
|
||||||
|
from ._binary import o8
|
||||||
|
|
||||||
|
|
||||||
|
def _accept(prefix):
|
||||||
|
return prefix[:4] == b"qoif"
|
||||||
|
|
||||||
|
|
||||||
|
class QoiImageFile(ImageFile.ImageFile):
|
||||||
|
format = "QOI"
|
||||||
|
format_description = "Quite OK Image"
|
||||||
|
|
||||||
|
def _open(self):
|
||||||
|
if not _accept(self.fp.read(4)):
|
||||||
|
msg = "not a QOI file"
|
||||||
|
raise SyntaxError(msg)
|
||||||
|
|
||||||
|
self._size = tuple(i32(self.fp.read(4)) for i in range(2))
|
||||||
|
|
||||||
|
channels = self.fp.read(1)[0]
|
||||||
|
self.mode = "RGB" if channels == 3 else "RGBA"
|
||||||
|
|
||||||
|
self.fp.seek(1, os.SEEK_CUR) # colorspace
|
||||||
|
self.tile = [("qoi", (0, 0) + self._size, self.fp.tell(), None)]
|
||||||
|
|
||||||
|
|
||||||
|
class QoiDecoder(ImageFile.PyDecoder):
|
||||||
|
_pulls_fd = True
|
||||||
|
|
||||||
|
def _add_to_previous_pixels(self, value):
|
||||||
|
self._previous_pixel = value
|
||||||
|
|
||||||
|
r, g, b, a = value
|
||||||
|
hash_value = (r * 3 + g * 5 + b * 7 + a * 11) % 64
|
||||||
|
self._previously_seen_pixels[hash_value] = value
|
||||||
|
|
||||||
|
def decode(self, buffer):
|
||||||
|
self._previously_seen_pixels = {}
|
||||||
|
self._previous_pixel = None
|
||||||
|
self._add_to_previous_pixels(b"".join(o8(i) for i in (0, 0, 0, 255)))
|
||||||
|
|
||||||
|
data = bytearray()
|
||||||
|
bands = Image.getmodebands(self.mode)
|
||||||
|
while len(data) < self.state.xsize * self.state.ysize * bands:
|
||||||
|
byte = self.fd.read(1)[0]
|
||||||
|
if byte == 0b11111110: # QOI_OP_RGB
|
||||||
|
value = self.fd.read(3) + o8(255)
|
||||||
|
elif byte == 0b11111111: # QOI_OP_RGBA
|
||||||
|
value = self.fd.read(4)
|
||||||
|
else:
|
||||||
|
op = byte >> 6
|
||||||
|
if op == 0: # QOI_OP_INDEX
|
||||||
|
op_index = byte & 0b00111111
|
||||||
|
value = self._previously_seen_pixels.get(op_index, (0, 0, 0, 0))
|
||||||
|
elif op == 1: # QOI_OP_DIFF
|
||||||
|
value = (
|
||||||
|
(self._previous_pixel[0] + ((byte & 0b00110000) >> 4) - 2)
|
||||||
|
% 256,
|
||||||
|
(self._previous_pixel[1] + ((byte & 0b00001100) >> 2) - 2)
|
||||||
|
% 256,
|
||||||
|
(self._previous_pixel[2] + (byte & 0b00000011) - 2) % 256,
|
||||||
|
)
|
||||||
|
value += (self._previous_pixel[3],)
|
||||||
|
elif op == 2: # QOI_OP_LUMA
|
||||||
|
second_byte = self.fd.read(1)[0]
|
||||||
|
diff_green = (byte & 0b00111111) - 32
|
||||||
|
diff_red = ((second_byte & 0b11110000) >> 4) - 8
|
||||||
|
diff_blue = (second_byte & 0b00001111) - 8
|
||||||
|
|
||||||
|
value = tuple(
|
||||||
|
(self._previous_pixel[i] + diff_green + diff) % 256
|
||||||
|
for i, diff in enumerate((diff_red, 0, diff_blue))
|
||||||
|
)
|
||||||
|
value += (self._previous_pixel[3],)
|
||||||
|
elif op == 3: # QOI_OP_RUN
|
||||||
|
run_length = (byte & 0b00111111) + 1
|
||||||
|
value = self._previous_pixel
|
||||||
|
if bands == 3:
|
||||||
|
value = value[:3]
|
||||||
|
data += value * run_length
|
||||||
|
continue
|
||||||
|
value = b"".join(o8(i) for i in value)
|
||||||
|
self._add_to_previous_pixels(value)
|
||||||
|
|
||||||
|
if bands == 3:
|
||||||
|
value = value[:3]
|
||||||
|
data += value
|
||||||
|
self.set_as_raw(bytes(data))
|
||||||
|
return -1, 0
|
||||||
|
|
||||||
|
|
||||||
|
Image.register_open(QoiImageFile.format, QoiImageFile, _accept)
|
||||||
|
Image.register_decoder("qoi", QoiDecoder)
|
||||||
|
Image.register_extension(QoiImageFile.format, ".qoi")
|
|
@ -425,6 +425,9 @@ class IFDRational(Rational):
|
||||||
__ceil__ = _delegate("__ceil__")
|
__ceil__ = _delegate("__ceil__")
|
||||||
__floor__ = _delegate("__floor__")
|
__floor__ = _delegate("__floor__")
|
||||||
__round__ = _delegate("__round__")
|
__round__ = _delegate("__round__")
|
||||||
|
# Python >= 3.11
|
||||||
|
if hasattr(Fraction, "__int__"):
|
||||||
|
__int__ = _delegate("__int__")
|
||||||
|
|
||||||
|
|
||||||
class ImageFileDirectory_v2(MutableMapping):
|
class ImageFileDirectory_v2(MutableMapping):
|
||||||
|
@ -1804,7 +1807,7 @@ def _save(im, fp, filename):
|
||||||
# Custom items are supported for int, float, unicode, string and byte
|
# Custom items are supported for int, float, unicode, string and byte
|
||||||
# values. Other types and tuples require a tagtype.
|
# values. Other types and tuples require a tagtype.
|
||||||
if tag not in TiffTags.LIBTIFF_CORE:
|
if tag not in TiffTags.LIBTIFF_CORE:
|
||||||
if not Image.core.libtiff_support_custom_tags:
|
if not getattr(Image.core, "libtiff_support_custom_tags", False):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if tag in ifd.tagtype:
|
if tag in ifd.tagtype:
|
||||||
|
@ -1850,6 +1853,11 @@ def _save(im, fp, filename):
|
||||||
fp.write(data)
|
fp.write(data)
|
||||||
if errcode:
|
if errcode:
|
||||||
break
|
break
|
||||||
|
if _fp:
|
||||||
|
try:
|
||||||
|
os.close(_fp)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
if errcode < 0:
|
if errcode < 0:
|
||||||
msg = f"encoder error {errcode} when writing image file"
|
msg = f"encoder error {errcode} when writing image file"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
|
|
|
@ -285,7 +285,7 @@ def _save_all(im, fp, filename):
|
||||||
# Append the frame to the animation encoder
|
# Append the frame to the animation encoder
|
||||||
enc.add(
|
enc.add(
|
||||||
frame.tobytes("raw", rawmode),
|
frame.tobytes("raw", rawmode),
|
||||||
timestamp,
|
round(timestamp),
|
||||||
frame.size[0],
|
frame.size[0],
|
||||||
frame.size[1],
|
frame.size[1],
|
||||||
rawmode,
|
rawmode,
|
||||||
|
@ -305,7 +305,7 @@ def _save_all(im, fp, filename):
|
||||||
im.seek(cur_idx)
|
im.seek(cur_idx)
|
||||||
|
|
||||||
# Force encoder to flush frames
|
# Force encoder to flush frames
|
||||||
enc.add(None, timestamp, 0, 0, "", lossless, quality, 0)
|
enc.add(None, round(timestamp), 0, 0, "", lossless, quality, 0)
|
||||||
|
|
||||||
# Get the final output from the encoder
|
# Get the final output from the encoder
|
||||||
data = enc.assemble(icc_profile, exif, xmp)
|
data = enc.assemble(icc_profile, exif, xmp)
|
||||||
|
|
|
@ -59,6 +59,7 @@ _plugins = [
|
||||||
"PngImagePlugin",
|
"PngImagePlugin",
|
||||||
"PpmImagePlugin",
|
"PpmImagePlugin",
|
||||||
"PsdImagePlugin",
|
"PsdImagePlugin",
|
||||||
|
"QoiImagePlugin",
|
||||||
"SgiImagePlugin",
|
"SgiImagePlugin",
|
||||||
"SpiderImagePlugin",
|
"SpiderImagePlugin",
|
||||||
"SunImagePlugin",
|
"SunImagePlugin",
|
||||||
|
|
127
src/_imaging.c
127
src/_imaging.c
|
@ -491,7 +491,7 @@ getink(PyObject *color, Imaging im, char *ink) {
|
||||||
int g = 0, b = 0, a = 0;
|
int g = 0, b = 0, a = 0;
|
||||||
double f = 0;
|
double f = 0;
|
||||||
/* Windows 64 bit longs are 32 bits, and 0xFFFFFFFF (white) is a
|
/* Windows 64 bit longs are 32 bits, and 0xFFFFFFFF (white) is a
|
||||||
python long (not int) that raises an overflow error when trying
|
Python long (not int) that raises an overflow error when trying
|
||||||
to return it into a 32 bit C long
|
to return it into a 32 bit C long
|
||||||
*/
|
*/
|
||||||
PY_LONG_LONG r = 0;
|
PY_LONG_LONG r = 0;
|
||||||
|
@ -502,9 +502,13 @@ getink(PyObject *color, Imaging im, char *ink) {
|
||||||
be cast to either UINT8 or INT32 */
|
be cast to either UINT8 or INT32 */
|
||||||
|
|
||||||
int rIsInt = 0;
|
int rIsInt = 0;
|
||||||
if (PyTuple_Check(color) && PyTuple_GET_SIZE(color) == 1) {
|
int tupleSize;
|
||||||
|
if (PyTuple_Check(color)) {
|
||||||
|
tupleSize = PyTuple_GET_SIZE(color);
|
||||||
|
if (tupleSize == 1) {
|
||||||
color = PyTuple_GetItem(color, 0);
|
color = PyTuple_GetItem(color, 0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (im->type == IMAGING_TYPE_UINT8 || im->type == IMAGING_TYPE_INT32 ||
|
if (im->type == IMAGING_TYPE_UINT8 || im->type == IMAGING_TYPE_INT32 ||
|
||||||
im->type == IMAGING_TYPE_SPECIAL) {
|
im->type == IMAGING_TYPE_SPECIAL) {
|
||||||
if (PyLong_Check(color)) {
|
if (PyLong_Check(color)) {
|
||||||
|
@ -513,15 +517,13 @@ getink(PyObject *color, Imaging im, char *ink) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
rIsInt = 1;
|
rIsInt = 1;
|
||||||
} else if (im->type == IMAGING_TYPE_UINT8) {
|
} else if (im->bands == 1) {
|
||||||
if (!PyTuple_Check(color)) {
|
|
||||||
PyErr_SetString(PyExc_TypeError, "color must be int or tuple");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_TypeError, "color must be int or single-element tuple");
|
PyExc_TypeError, "color must be int or single-element tuple");
|
||||||
return NULL;
|
return NULL;
|
||||||
|
} else if (!PyTuple_Check(color)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "color must be int or tuple");
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -531,7 +533,7 @@ getink(PyObject *color, Imaging im, char *ink) {
|
||||||
if (im->bands == 1) {
|
if (im->bands == 1) {
|
||||||
/* unsigned integer, single layer */
|
/* unsigned integer, single layer */
|
||||||
if (rIsInt != 1) {
|
if (rIsInt != 1) {
|
||||||
if (PyTuple_GET_SIZE(color) != 1) {
|
if (tupleSize != 1) {
|
||||||
PyErr_SetString(PyExc_TypeError, "color must be int or single-element tuple");
|
PyErr_SetString(PyExc_TypeError, "color must be int or single-element tuple");
|
||||||
return NULL;
|
return NULL;
|
||||||
} else if (!PyArg_ParseTuple(color, "L", &r)) {
|
} else if (!PyArg_ParseTuple(color, "L", &r)) {
|
||||||
|
@ -541,7 +543,6 @@ getink(PyObject *color, Imaging im, char *ink) {
|
||||||
ink[0] = (char)CLIP8(r);
|
ink[0] = (char)CLIP8(r);
|
||||||
ink[1] = ink[2] = ink[3] = 0;
|
ink[1] = ink[2] = ink[3] = 0;
|
||||||
} else {
|
} else {
|
||||||
a = 255;
|
|
||||||
if (rIsInt) {
|
if (rIsInt) {
|
||||||
/* compatibility: ABGR */
|
/* compatibility: ABGR */
|
||||||
a = (UINT8)(r >> 24);
|
a = (UINT8)(r >> 24);
|
||||||
|
@ -549,7 +550,7 @@ getink(PyObject *color, Imaging im, char *ink) {
|
||||||
g = (UINT8)(r >> 8);
|
g = (UINT8)(r >> 8);
|
||||||
r = (UINT8)r;
|
r = (UINT8)r;
|
||||||
} else {
|
} else {
|
||||||
int tupleSize = PyTuple_GET_SIZE(color);
|
a = 255;
|
||||||
if (im->bands == 2) {
|
if (im->bands == 2) {
|
||||||
if (tupleSize != 1 && tupleSize != 2) {
|
if (tupleSize != 1 && tupleSize != 2) {
|
||||||
PyErr_SetString(PyExc_TypeError, "color must be int, or tuple of one or two elements");
|
PyErr_SetString(PyExc_TypeError, "color must be int, or tuple of one or two elements");
|
||||||
|
@ -593,6 +594,41 @@ getink(PyObject *color, Imaging im, char *ink) {
|
||||||
ink[1] = (UINT8)(r >> 8);
|
ink[1] = (UINT8)(r >> 8);
|
||||||
ink[2] = ink[3] = 0;
|
ink[2] = ink[3] = 0;
|
||||||
return ink;
|
return ink;
|
||||||
|
} else {
|
||||||
|
if (rIsInt) {
|
||||||
|
b = (UINT8)(r >> 16);
|
||||||
|
g = (UINT8)(r >> 8);
|
||||||
|
r = (UINT8)r;
|
||||||
|
} else if (tupleSize != 3) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "color must be int, or tuple of one or three elements");
|
||||||
|
return NULL;
|
||||||
|
} else if (!PyArg_ParseTuple(color, "Lii", &r, &g, &b)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (!strcmp(im->mode, "BGR;15")) {
|
||||||
|
UINT16 v = ((((UINT16)r) << 7) & 0x7c00) +
|
||||||
|
((((UINT16)g) << 2) & 0x03e0) +
|
||||||
|
((((UINT16)b) >> 3) & 0x001f);
|
||||||
|
|
||||||
|
ink[0] = (UINT8)v;
|
||||||
|
ink[1] = (UINT8)(v >> 8);
|
||||||
|
ink[2] = ink[3] = 0;
|
||||||
|
return ink;
|
||||||
|
} else if (!strcmp(im->mode, "BGR;16")) {
|
||||||
|
UINT16 v = ((((UINT16)r) << 8) & 0xf800) +
|
||||||
|
((((UINT16)g) << 3) & 0x07e0) +
|
||||||
|
((((UINT16)b) >> 3) & 0x001f);
|
||||||
|
ink[0] = (UINT8)v;
|
||||||
|
ink[1] = (UINT8)(v >> 8);
|
||||||
|
ink[2] = ink[3] = 0;
|
||||||
|
return ink;
|
||||||
|
} else if (!strcmp(im->mode, "BGR;24")) {
|
||||||
|
ink[0] = (UINT8)b;
|
||||||
|
ink[1] = (UINT8)g;
|
||||||
|
ink[2] = (UINT8)r;
|
||||||
|
ink[3] = 0;
|
||||||
|
return ink;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3810,6 +3846,7 @@ static PyTypeObject PixelAccess_Type = {
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_get_stats(PyObject *self, PyObject *args) {
|
_get_stats(PyObject *self, PyObject *args) {
|
||||||
PyObject *d;
|
PyObject *d;
|
||||||
|
PyObject *v;
|
||||||
ImagingMemoryArena arena = &ImagingDefaultArena;
|
ImagingMemoryArena arena = &ImagingDefaultArena;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, ":get_stats")) {
|
if (!PyArg_ParseTuple(args, ":get_stats")) {
|
||||||
|
@ -3820,15 +3857,29 @@ _get_stats(PyObject *self, PyObject *args) {
|
||||||
if (!d) {
|
if (!d) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
PyDict_SetItemString(d, "new_count", PyLong_FromLong(arena->stats_new_count));
|
v = PyLong_FromLong(arena->stats_new_count);
|
||||||
PyDict_SetItemString(
|
PyDict_SetItemString(d, "new_count", v ? v : Py_None);
|
||||||
d, "allocated_blocks", PyLong_FromLong(arena->stats_allocated_blocks));
|
Py_XDECREF(v);
|
||||||
PyDict_SetItemString(
|
|
||||||
d, "reused_blocks", PyLong_FromLong(arena->stats_reused_blocks));
|
v = PyLong_FromLong(arena->stats_allocated_blocks);
|
||||||
PyDict_SetItemString(
|
PyDict_SetItemString(d, "allocated_blocks", v ? v : Py_None);
|
||||||
d, "reallocated_blocks", PyLong_FromLong(arena->stats_reallocated_blocks));
|
Py_XDECREF(v);
|
||||||
PyDict_SetItemString(d, "freed_blocks", PyLong_FromLong(arena->stats_freed_blocks));
|
|
||||||
PyDict_SetItemString(d, "blocks_cached", PyLong_FromLong(arena->blocks_cached));
|
v = PyLong_FromLong(arena->stats_reused_blocks);
|
||||||
|
PyDict_SetItemString(d, "reused_blocks", v ? v : Py_None);
|
||||||
|
Py_XDECREF(v);
|
||||||
|
|
||||||
|
v = PyLong_FromLong(arena->stats_reallocated_blocks);
|
||||||
|
PyDict_SetItemString(d, "reallocated_blocks", v ? v : Py_None);
|
||||||
|
Py_XDECREF(v);
|
||||||
|
|
||||||
|
v = PyLong_FromLong(arena->stats_freed_blocks);
|
||||||
|
PyDict_SetItemString(d, "freed_blocks", v ? v : Py_None);
|
||||||
|
Py_XDECREF(v);
|
||||||
|
|
||||||
|
v = PyLong_FromLong(arena->blocks_cached);
|
||||||
|
PyDict_SetItemString(d, "blocks_cached", v ? v : Py_None);
|
||||||
|
Py_XDECREF(v);
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4197,28 +4248,33 @@ setup_module(PyObject *m) {
|
||||||
#ifdef HAVE_LIBJPEG
|
#ifdef HAVE_LIBJPEG
|
||||||
{
|
{
|
||||||
extern const char *ImagingJpegVersion(void);
|
extern const char *ImagingJpegVersion(void);
|
||||||
PyDict_SetItemString(
|
PyObject *v = PyUnicode_FromString(ImagingJpegVersion());
|
||||||
d, "jpeglib_version", PyUnicode_FromString(ImagingJpegVersion()));
|
PyDict_SetItemString(d, "jpeglib_version", v ? v : Py_None);
|
||||||
|
Py_XDECREF(v);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_OPENJPEG
|
#ifdef HAVE_OPENJPEG
|
||||||
{
|
{
|
||||||
extern const char *ImagingJpeg2KVersion(void);
|
extern const char *ImagingJpeg2KVersion(void);
|
||||||
PyDict_SetItemString(
|
PyObject *v = PyUnicode_FromString(ImagingJpeg2KVersion());
|
||||||
d, "jp2klib_version", PyUnicode_FromString(ImagingJpeg2KVersion()));
|
PyDict_SetItemString(d, "jp2klib_version", v ? v : Py_None);
|
||||||
|
Py_XDECREF(v);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
PyObject *have_libjpegturbo;
|
PyObject *have_libjpegturbo;
|
||||||
#ifdef LIBJPEG_TURBO_VERSION
|
#ifdef LIBJPEG_TURBO_VERSION
|
||||||
have_libjpegturbo = Py_True;
|
have_libjpegturbo = Py_True;
|
||||||
|
{
|
||||||
#define tostr1(a) #a
|
#define tostr1(a) #a
|
||||||
#define tostr(a) tostr1(a)
|
#define tostr(a) tostr1(a)
|
||||||
PyDict_SetItemString(
|
PyObject *v = PyUnicode_FromString(tostr(LIBJPEG_TURBO_VERSION));
|
||||||
d, "libjpeg_turbo_version", PyUnicode_FromString(tostr(LIBJPEG_TURBO_VERSION)));
|
PyDict_SetItemString(d, "libjpeg_turbo_version", v ? v : Py_None);
|
||||||
|
Py_XDECREF(v);
|
||||||
#undef tostr
|
#undef tostr
|
||||||
#undef tostr1
|
#undef tostr1
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
have_libjpegturbo = Py_False;
|
have_libjpegturbo = Py_False;
|
||||||
#endif
|
#endif
|
||||||
|
@ -4230,8 +4286,9 @@ setup_module(PyObject *m) {
|
||||||
have_libimagequant = Py_True;
|
have_libimagequant = Py_True;
|
||||||
{
|
{
|
||||||
extern const char *ImagingImageQuantVersion(void);
|
extern const char *ImagingImageQuantVersion(void);
|
||||||
PyDict_SetItemString(
|
PyObject *v = PyUnicode_FromString(ImagingImageQuantVersion());
|
||||||
d, "imagequant_version", PyUnicode_FromString(ImagingImageQuantVersion()));
|
PyDict_SetItemString(d, "imagequant_version", v ? v : Py_None);
|
||||||
|
Py_XDECREF(v);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
have_libimagequant = Py_False;
|
have_libimagequant = Py_False;
|
||||||
|
@ -4248,16 +4305,18 @@ setup_module(PyObject *m) {
|
||||||
PyModule_AddIntConstant(m, "FIXED", Z_FIXED);
|
PyModule_AddIntConstant(m, "FIXED", Z_FIXED);
|
||||||
{
|
{
|
||||||
extern const char *ImagingZipVersion(void);
|
extern const char *ImagingZipVersion(void);
|
||||||
PyDict_SetItemString(
|
PyObject *v = PyUnicode_FromString(ImagingZipVersion());
|
||||||
d, "zlib_version", PyUnicode_FromString(ImagingZipVersion()));
|
PyDict_SetItemString(d, "zlib_version", v ? v : Py_None);
|
||||||
|
Py_XDECREF(v);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_LIBTIFF
|
#ifdef HAVE_LIBTIFF
|
||||||
{
|
{
|
||||||
extern const char *ImagingTiffVersion(void);
|
extern const char *ImagingTiffVersion(void);
|
||||||
PyDict_SetItemString(
|
PyObject *v = PyUnicode_FromString(ImagingTiffVersion());
|
||||||
d, "libtiff_version", PyUnicode_FromString(ImagingTiffVersion()));
|
PyDict_SetItemString(d, "libtiff_version", v ? v : Py_None);
|
||||||
|
Py_XDECREF(v);
|
||||||
|
|
||||||
// Test for libtiff 4.0 or later, excluding libtiff 3.9.6 and 3.9.7
|
// Test for libtiff 4.0 or later, excluding libtiff 3.9.6 and 3.9.7
|
||||||
PyObject *support_custom_tags;
|
PyObject *support_custom_tags;
|
||||||
|
@ -4280,7 +4339,9 @@ setup_module(PyObject *m) {
|
||||||
Py_INCREF(have_xcb);
|
Py_INCREF(have_xcb);
|
||||||
PyModule_AddObject(m, "HAVE_XCB", have_xcb);
|
PyModule_AddObject(m, "HAVE_XCB", have_xcb);
|
||||||
|
|
||||||
PyDict_SetItemString(d, "PILLOW_VERSION", PyUnicode_FromString(version));
|
PyObject *pillow_version = PyUnicode_FromString(version);
|
||||||
|
PyDict_SetItemString(d, "PILLOW_VERSION", pillow_version ? pillow_version : Py_None);
|
||||||
|
Py_XDECREF(pillow_version);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ cms_profile_open(PyObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
cms_profile_fromstring(PyObject *self, PyObject *args) {
|
cms_profile_frombytes(PyObject *self, PyObject *args) {
|
||||||
cmsHPROFILE hProfile;
|
cmsHPROFILE hProfile;
|
||||||
|
|
||||||
char *pProfile;
|
char *pProfile;
|
||||||
|
@ -950,6 +950,8 @@ _is_intent_supported(CmsProfileObject *self, int clut) {
|
||||||
return Py_None;
|
return Py_None;
|
||||||
}
|
}
|
||||||
PyDict_SetItem(result, id, entry);
|
PyDict_SetItem(result, id, entry);
|
||||||
|
Py_DECREF(id);
|
||||||
|
Py_DECREF(entry);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -960,8 +962,7 @@ _is_intent_supported(CmsProfileObject *self, int clut) {
|
||||||
static PyMethodDef pyCMSdll_methods[] = {
|
static PyMethodDef pyCMSdll_methods[] = {
|
||||||
|
|
||||||
{"profile_open", cms_profile_open, METH_VARARGS},
|
{"profile_open", cms_profile_open, METH_VARARGS},
|
||||||
{"profile_frombytes", cms_profile_fromstring, METH_VARARGS},
|
{"profile_frombytes", cms_profile_frombytes, METH_VARARGS},
|
||||||
{"profile_fromstring", cms_profile_fromstring, METH_VARARGS},
|
|
||||||
{"profile_tobytes", cms_profile_tobytes, METH_VARARGS},
|
{"profile_tobytes", cms_profile_tobytes, METH_VARARGS},
|
||||||
|
|
||||||
/* profile and transform functions */
|
/* profile and transform functions */
|
||||||
|
@ -1532,7 +1533,8 @@ setup_module(PyObject *m) {
|
||||||
} else {
|
} else {
|
||||||
v = PyUnicode_FromFormat("%d.%d", vn / 1000, (vn / 10) % 100);
|
v = PyUnicode_FromFormat("%d.%d", vn / 1000, (vn / 10) % 100);
|
||||||
}
|
}
|
||||||
PyDict_SetItemString(d, "littlecms_version", v);
|
PyDict_SetItemString(d, "littlecms_version", v ? v : Py_None);
|
||||||
|
Py_XDECREF(v);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user