mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-02-05 06:00:58 +03:00
Merge branch 'main' into gif
This commit is contained in:
commit
ca6724bb9d
4
.github/workflows/cifuzz.yml
vendored
4
.github/workflows/cifuzz.yml
vendored
|
@ -31,13 +31,13 @@ jobs:
|
||||||
language: python
|
language: python
|
||||||
dry-run: false
|
dry-run: false
|
||||||
- name: Upload New Crash
|
- name: Upload New Crash
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
if: failure() && steps.build.outcome == 'success'
|
if: failure() && steps.build.outcome == 'success'
|
||||||
with:
|
with:
|
||||||
name: artifacts
|
name: artifacts
|
||||||
path: ./out/artifacts
|
path: ./out/artifacts
|
||||||
- name: Upload Legacy Crash
|
- name: Upload Legacy Crash
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
if: steps.run.outcome == 'success'
|
if: steps.run.outcome == 'success'
|
||||||
with:
|
with:
|
||||||
name: crash
|
name: crash
|
||||||
|
|
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
|
@ -10,7 +10,7 @@ jobs:
|
||||||
name: Lint
|
name: Lint
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: pre-commit cache
|
- name: pre-commit cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
|
@ -21,7 +21,7 @@ jobs:
|
||||||
lint-pre-commit-
|
lint-pre-commit-
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v3
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
cache: pip
|
cache: pip
|
||||||
|
|
3
.github/workflows/test-docker.yml
vendored
3
.github/workflows/test-docker.yml
vendored
|
@ -25,6 +25,7 @@ jobs:
|
||||||
debian-11-bullseye-x86,
|
debian-11-bullseye-x86,
|
||||||
fedora-34-amd64,
|
fedora-34-amd64,
|
||||||
fedora-35-amd64,
|
fedora-35-amd64,
|
||||||
|
gentoo,
|
||||||
ubuntu-18.04-bionic-amd64,
|
ubuntu-18.04-bionic-amd64,
|
||||||
ubuntu-20.04-focal-amd64,
|
ubuntu-20.04-focal-amd64,
|
||||||
]
|
]
|
||||||
|
@ -40,7 +41,7 @@ jobs:
|
||||||
name: ${{ matrix.docker }}
|
name: ${{ matrix.docker }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Build system information
|
- name: Build system information
|
||||||
run: python3 .github/workflows/system-info.py
|
run: python3 .github/workflows/system-info.py
|
||||||
|
|
3
.github/workflows/test-mingw.yml
vendored
3
.github/workflows/test-mingw.yml
vendored
|
@ -29,7 +29,7 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Pillow
|
- name: Checkout Pillow
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set up shell
|
- name: Set up shell
|
||||||
run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH
|
run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH
|
||||||
|
@ -45,6 +45,7 @@ jobs:
|
||||||
${{ matrix.package }}-python-pyqt6 \
|
${{ matrix.package }}-python-pyqt6 \
|
||||||
${{ matrix.package }}-python3-setuptools \
|
${{ matrix.package }}-python3-setuptools \
|
||||||
${{ matrix.package }}-freetype \
|
${{ matrix.package }}-freetype \
|
||||||
|
${{ matrix.package }}-gcc \
|
||||||
${{ matrix.package }}-ghostscript \
|
${{ matrix.package }}-ghostscript \
|
||||||
${{ matrix.package }}-lcms2 \
|
${{ matrix.package }}-lcms2 \
|
||||||
${{ matrix.package }}-libimagequant \
|
${{ matrix.package }}-libimagequant \
|
||||||
|
|
2
.github/workflows/test-valgrind.yml
vendored
2
.github/workflows/test-valgrind.yml
vendored
|
@ -28,7 +28,7 @@ jobs:
|
||||||
name: ${{ matrix.docker }}
|
name: ${{ matrix.docker }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Build system information
|
- name: Build system information
|
||||||
run: python3 .github/workflows/system-info.py
|
run: python3 .github/workflows/system-info.py
|
||||||
|
|
15
.github/workflows/test-windows.yml
vendored
15
.github/workflows/test-windows.yml
vendored
|
@ -23,17 +23,17 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Pillow
|
- name: Checkout Pillow
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Checkout cached dependencies
|
- name: Checkout cached dependencies
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: python-pillow/pillow-depends
|
repository: python-pillow/pillow-depends
|
||||||
path: winbuild\depends
|
path: winbuild\depends
|
||||||
|
|
||||||
# sets env: pythonLocation
|
# sets env: pythonLocation
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v3
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
architecture: ${{ matrix.architecture }}
|
architecture: ${{ matrix.architecture }}
|
||||||
|
@ -137,10 +137,11 @@ jobs:
|
||||||
& $env:pythonLocation\python.exe selftest.py --installed
|
& $env:pythonLocation\python.exe selftest.py --installed
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
# failing with PyPy3
|
# skip PyPy for speed
|
||||||
- name: Enable heap verification
|
- name: Enable heap verification
|
||||||
if: "!contains(matrix.python-version, 'pypy')"
|
if: "!contains(matrix.python-version, 'pypy')"
|
||||||
run: "& 'C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x86\\gflags.exe' /p /enable $env:pythonLocation\\python.exe"
|
run: |
|
||||||
|
& reg.exe add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f
|
||||||
|
|
||||||
- name: Test Pillow
|
- name: Test Pillow
|
||||||
run: |
|
run: |
|
||||||
|
@ -155,7 +156,7 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Upload errors
|
- name: Upload errors
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: errors
|
name: errors
|
||||||
|
@ -181,7 +182,7 @@ jobs:
|
||||||
winbuild\\build\\build_pillow.cmd --disable-imagequant bdist_wheel
|
winbuild\\build\\build_pillow.cmd --disable-imagequant bdist_wheel
|
||||||
shell: cmd
|
shell: cmd
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v3
|
||||||
if: "github.event_name != 'pull_request'"
|
if: "github.event_name != 'pull_request'"
|
||||||
with:
|
with:
|
||||||
name: ${{ steps.wheel.outputs.dist }}
|
name: ${{ steps.wheel.outputs.dist }}
|
||||||
|
|
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
|
@ -36,10 +36,10 @@ jobs:
|
||||||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v3
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
cache: pip
|
cache: pip
|
||||||
|
@ -84,14 +84,14 @@ jobs:
|
||||||
mkdir -p Tests/errors
|
mkdir -p Tests/errors
|
||||||
|
|
||||||
- name: Upload errors
|
- name: Upload errors
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: errors
|
name: errors
|
||||||
path: Tests/errors
|
path: Tests/errors
|
||||||
|
|
||||||
- name: Docs
|
- name: Docs
|
||||||
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.9
|
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.10
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install sphinx-copybutton sphinx-issues sphinx-removed-in sphinx-rtd-theme sphinxext-opengraph
|
python3 -m pip install sphinx-copybutton sphinx-issues sphinx-removed-in sphinx-rtd-theme sphinxext-opengraph
|
||||||
make doccheck
|
make doccheck
|
||||||
|
|
2
.github/workflows/tidelift.yml
vendored
2
.github/workflows/tidelift.yml
vendored
|
@ -17,7 +17,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: Scan
|
- name: Scan
|
||||||
uses: tidelift/alignment-action@main
|
uses: tidelift/alignment-action@main
|
||||||
env:
|
env:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: f1d4e742c91dd5179d742b0db9293c4472b765f8 # frozen: 21.12b0
|
rev: fc0be6eb1e2a96091e6f64009ee5e9081bf8b6c6 # frozen: 22.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args: ["--target-version", "py37"]
|
args: ["--target-version", "py37"]
|
||||||
|
@ -19,7 +19,7 @@ repos:
|
||||||
- id: yesqa
|
- id: yesqa
|
||||||
|
|
||||||
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
||||||
rev: 3592548bbd98528887eeed63486cf6c9bae00b98 # frozen: v1.1.10
|
rev: ca52c4245639abd55c970e6bbbca95cab3de22d8 # frozen: v1.1.13
|
||||||
hooks:
|
hooks:
|
||||||
- id: remove-tabs
|
- id: remove-tabs
|
||||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$)
|
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$)
|
||||||
|
|
|
@ -1,2 +1,8 @@
|
||||||
|
version: 2
|
||||||
|
|
||||||
python:
|
python:
|
||||||
pip_install: true
|
install:
|
||||||
|
- method: pip
|
||||||
|
path: .
|
||||||
|
extra_requirements:
|
||||||
|
- docs
|
||||||
|
|
21
CHANGES.rst
21
CHANGES.rst
|
@ -5,6 +5,27 @@ Changelog (Pillow)
|
||||||
9.1.0 (unreleased)
|
9.1.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- When converting, clip I;16 to be unsigned, not signed #6112
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed loading L mode GIF with transparency #6086
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Improved handling of PPM header #5121
|
||||||
|
[Piolie, radarhere]
|
||||||
|
|
||||||
|
- Reset size when seeking away from "Large Thumbnail" MPO frame #6101
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Replace requirements.txt with extras #6072
|
||||||
|
[hugovk, radarhere]
|
||||||
|
|
||||||
|
- Added PyEncoder and support BLP saving #6069
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Handle TGA images with packets that cross scan lines #6087
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
- Added FITS reading #6056
|
- Added FITS reading #6056
|
||||||
[radarhere, hugovk]
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
|
53
Makefile
53
Makefile
|
@ -9,9 +9,11 @@ clean:
|
||||||
|
|
||||||
.PHONY: coverage
|
.PHONY: coverage
|
||||||
coverage:
|
coverage:
|
||||||
pytest -qq
|
python3 -c "import pytest" > /dev/null 2>&1 || python3 -m pip install pytest
|
||||||
|
python3 -m pytest -qq
|
||||||
rm -r htmlcov || true
|
rm -r htmlcov || true
|
||||||
coverage report
|
python3 -c "import coverage" > /dev/null 2>&1 || python3 -m pip install coverage
|
||||||
|
python3 -m coverage report
|
||||||
|
|
||||||
.PHONY: doc
|
.PHONY: doc
|
||||||
doc:
|
doc:
|
||||||
|
@ -33,20 +35,16 @@ help:
|
||||||
@echo "Welcome to Pillow development. Please use \`make <target>\` where <target> is one of"
|
@echo "Welcome to Pillow development. Please use \`make <target>\` where <target> is one of"
|
||||||
@echo " clean remove build products"
|
@echo " clean remove build products"
|
||||||
@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 to make standalone HTML files"
|
||||||
@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"
|
||||||
@echo " install-req install documentation and test dependencies"
|
|
||||||
@echo " install-venv (deprecated) install in virtualenv"
|
|
||||||
@echo " lint run the lint checks"
|
@echo " lint run the lint checks"
|
||||||
@echo " lint-fix run black and isort to (mostly) fix lint issues."
|
@echo " lint-fix run Black and isort to (mostly) fix lint issues"
|
||||||
@echo " release-test run code and package tests before release"
|
@echo " release-test run code and package tests before release"
|
||||||
@echo " test run tests on installed pillow"
|
@echo " test run tests on installed Pillow"
|
||||||
@echo " upload build and upload sdists to PyPI"
|
|
||||||
@echo " upload-test build and upload sdists to test.pythonpackages.com"
|
|
||||||
|
|
||||||
.PHONY: inplace
|
.PHONY: inplace
|
||||||
inplace: clean
|
inplace: clean
|
||||||
|
@ -70,28 +68,17 @@ debug:
|
||||||
make clean > /dev/null
|
make clean > /dev/null
|
||||||
CFLAGS='-g -O0' python3 -m pip install --global-option="build_ext" . > /dev/null
|
CFLAGS='-g -O0' python3 -m pip install --global-option="build_ext" . > /dev/null
|
||||||
|
|
||||||
.PHONY: install-req
|
|
||||||
install-req:
|
|
||||||
python3 -m pip install -r requirements.txt
|
|
||||||
|
|
||||||
.PHONY: install-venv
|
|
||||||
install-venv:
|
|
||||||
echo "'install-venv' is deprecated and will be removed in a future Pillow release"
|
|
||||||
virtualenv .
|
|
||||||
bin/pip install -r requirements.txt
|
|
||||||
|
|
||||||
.PHONY: release-test
|
.PHONY: release-test
|
||||||
release-test:
|
release-test:
|
||||||
$(MAKE) install-req
|
python3 -m pip install -e .[tests]
|
||||||
python3 -m pip install -e .
|
|
||||||
python3 selftest.py
|
python3 selftest.py
|
||||||
python3 -m pytest Tests
|
python3 -m pytest Tests
|
||||||
python3 -m pip install .
|
python3 -m pip install .
|
||||||
-rm dist/*.egg
|
-rm dist/*.egg
|
||||||
-rmdir dist
|
-rmdir dist
|
||||||
python3 -m pytest -qq
|
python3 -m pytest -qq
|
||||||
check-manifest
|
python3 -m check-manifest
|
||||||
pyroma .
|
python3 -m pyroma .
|
||||||
$(MAKE) readme
|
$(MAKE) readme
|
||||||
|
|
||||||
.PHONY: sdist
|
.PHONY: sdist
|
||||||
|
@ -101,26 +88,30 @@ sdist:
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
pytest -qq
|
python3 -c "import pytest" > /dev/null 2>&1 || python3 -m pip install pytest
|
||||||
|
python3 -m pytest -qq
|
||||||
|
|
||||||
.PHONY: valgrind
|
.PHONY: valgrind
|
||||||
valgrind:
|
valgrind:
|
||||||
python3 -c "import pytest_valgrind" || python3 -m pip install pytest-valgrind
|
python3 -c "import pytest_valgrind" > /dev/null 2>&1 || python3 -m pip install pytest-valgrind
|
||||||
PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \
|
PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \
|
||||||
--log-file=/tmp/valgrind-output \
|
--log-file=/tmp/valgrind-output \
|
||||||
python3 -m pytest --no-memcheck -vv --valgrind --valgrind-log=/tmp/valgrind-output
|
python3 -m pytest --no-memcheck -vv --valgrind --valgrind-log=/tmp/valgrind-output
|
||||||
|
|
||||||
.PHONY: readme
|
.PHONY: readme
|
||||||
readme:
|
readme:
|
||||||
markdown2 README.md > .long-description.html && open .long-description.html
|
python3 -c "import markdown2" > /dev/null 2>&1 || python3 -m pip install markdown2
|
||||||
|
python3 -m markdown2 README.md > .long-description.html && open .long-description.html
|
||||||
|
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint:
|
lint:
|
||||||
tox --help > /dev/null || python3 -m pip install tox
|
python3 -c "import tox" > /dev/null 2>&1 || python3 -m pip install tox
|
||||||
tox -e lint
|
python3 -m tox -e lint
|
||||||
|
|
||||||
.PHONY: lint-fix
|
.PHONY: lint-fix
|
||||||
lint-fix:
|
lint-fix:
|
||||||
black --target-version py37 .
|
python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black
|
||||||
isort .
|
python3 -c "import isort" > /dev/null 2>&1 || python3 -m pip install isort
|
||||||
|
python3 -m black --target-version py37 .
|
||||||
|
python3 -m isort .
|
||||||
|
|
|
@ -4,5 +4,5 @@ import sys
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
if sys.maxsize < 2 ** 32:
|
if sys.maxsize < 2**32:
|
||||||
im = Image.new("L", (999999, 999999), 0)
|
im = Image.new("L", (999999, 999999), 0)
|
||||||
|
|
|
@ -23,7 +23,7 @@ YDIM = 32769
|
||||||
XDIM = 48000
|
XDIM = 48000
|
||||||
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="requires 64-bit system")
|
pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit system")
|
||||||
|
|
||||||
|
|
||||||
def _write_png(tmp_path, xdim, ydim):
|
def _write_png(tmp_path, xdim, ydim):
|
||||||
|
|
|
@ -19,7 +19,7 @@ YDIM = 32769
|
||||||
XDIM = 48000
|
XDIM = 48000
|
||||||
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="requires 64-bit system")
|
pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit system")
|
||||||
|
|
||||||
|
|
||||||
def _write_png(tmp_path, xdim, ydim):
|
def _write_png(tmp_path, xdim, ydim):
|
||||||
|
|
BIN
Tests/images/cross_scan_line.png
Normal file
BIN
Tests/images/cross_scan_line.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 71 B |
BIN
Tests/images/cross_scan_line.tga
Normal file
BIN
Tests/images/cross_scan_line.tga
Normal file
Binary file not shown.
BIN
Tests/images/no_palette_with_transparency.gif
Normal file
BIN
Tests/images/no_palette_with_transparency.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 64 B |
|
@ -1,6 +1,5 @@
|
||||||
import os
|
import os
|
||||||
|
import warnings
|
||||||
import pytest
|
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
@ -20,16 +19,14 @@ def test_bad():
|
||||||
either"""
|
either"""
|
||||||
for f in get_files("b"):
|
for f in get_files("b"):
|
||||||
|
|
||||||
with pytest.warns(None) as record:
|
# Assert that there is no unclosed file warning
|
||||||
|
with warnings.catch_warnings():
|
||||||
try:
|
try:
|
||||||
with Image.open(f) as im:
|
with Image.open(f) as im:
|
||||||
im.load()
|
im.load()
|
||||||
except Exception: # as msg:
|
except Exception: # as msg:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Assert that there is no unclosed file warning
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_questionable():
|
def test_questionable():
|
||||||
"""These shouldn't crash/dos, but it's not well defined that these
|
"""These shouldn't crash/dos, but it's not well defined that these
|
||||||
|
|
|
@ -110,9 +110,9 @@ class TestCoreMemory:
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Image.core.set_blocks_max(-1)
|
Image.core.set_blocks_max(-1)
|
||||||
if sys.maxsize < 2 ** 32:
|
if sys.maxsize < 2**32:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Image.core.set_blocks_max(2 ** 29)
|
Image.core.set_blocks_max(2**29)
|
||||||
|
|
||||||
@pytest.mark.skipif(is_pypy(), reason="Images not collected")
|
@pytest.mark.skipif(is_pypy(), reason="Images not collected")
|
||||||
def test_set_blocks_max_stats(self):
|
def test_set_blocks_max_stats(self):
|
||||||
|
|
|
@ -2,7 +2,12 @@ import pytest
|
||||||
|
|
||||||
from PIL import BlpImagePlugin, Image
|
from PIL import BlpImagePlugin, Image
|
||||||
|
|
||||||
from .helper import assert_image_equal_tofile
|
from .helper import (
|
||||||
|
assert_image_equal,
|
||||||
|
assert_image_equal_tofile,
|
||||||
|
assert_image_similar,
|
||||||
|
hopper,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_load_blp1():
|
def test_load_blp1():
|
||||||
|
@ -25,6 +30,28 @@ def test_load_blp2_dxt1a():
|
||||||
assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png")
|
assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png")
|
||||||
|
|
||||||
|
|
||||||
|
def test_save(tmp_path):
|
||||||
|
f = str(tmp_path / "temp.blp")
|
||||||
|
|
||||||
|
for version in ("BLP1", "BLP2"):
|
||||||
|
im = hopper("P")
|
||||||
|
im.save(f, blp_version=version)
|
||||||
|
|
||||||
|
with Image.open(f) as reloaded:
|
||||||
|
assert_image_equal(im.convert("RGB"), reloaded)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/transparent.png") as im:
|
||||||
|
f = str(tmp_path / "temp.blp")
|
||||||
|
im.convert("P").save(f, blp_version=version)
|
||||||
|
|
||||||
|
with Image.open(f) as reloaded:
|
||||||
|
assert_image_similar(im, reloaded, 8)
|
||||||
|
|
||||||
|
im = hopper()
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
im.save(f)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"test_file",
|
"test_file",
|
||||||
[
|
[
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import DcxImagePlugin, Image
|
from PIL import DcxImagePlugin, Image
|
||||||
|
@ -31,21 +33,17 @@ def test_unclosed_file():
|
||||||
|
|
||||||
|
|
||||||
def test_closed_file():
|
def test_closed_file():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
im = Image.open(TEST_FILE)
|
im = Image.open(TEST_FILE)
|
||||||
im.load()
|
im.load()
|
||||||
im.close()
|
im.close()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_context_manager():
|
def test_context_manager():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
with Image.open(TEST_FILE) as im:
|
with Image.open(TEST_FILE) as im:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_file():
|
def test_invalid_file():
|
||||||
with open("Tests/images/flower.jpg", "rb") as fp:
|
with open("Tests/images/flower.jpg", "rb") as fp:
|
||||||
|
|
|
@ -196,6 +196,13 @@ def test__accept_false():
|
||||||
assert not output
|
assert not output
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_file():
|
||||||
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
|
||||||
|
with pytest.raises(SyntaxError):
|
||||||
|
DdsImagePlugin.DdsImageFile(invalid_file)
|
||||||
|
|
||||||
|
|
||||||
def test_short_header():
|
def test_short_header():
|
||||||
"""Check a short header"""
|
"""Check a short header"""
|
||||||
with open(TEST_FILE_DXT5, "rb") as f:
|
with open(TEST_FILE_DXT5, "rb") as f:
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import FliImagePlugin, Image
|
from PIL import FliImagePlugin, Image
|
||||||
|
@ -38,21 +40,17 @@ def test_unclosed_file():
|
||||||
|
|
||||||
|
|
||||||
def test_closed_file():
|
def test_closed_file():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
im = Image.open(static_test_file)
|
im = Image.open(static_test_file)
|
||||||
im.load()
|
im.load()
|
||||||
im.close()
|
im.close()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_context_manager():
|
def test_context_manager():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
with Image.open(static_test_file) as im:
|
with Image.open(static_test_file) as im:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_tell():
|
def test_tell():
|
||||||
# Arrange
|
# Arrange
|
||||||
|
|
|
@ -16,6 +16,13 @@ def test_load_dxt1():
|
||||||
assert_image_similar(im, target.convert("RGBA"), 15)
|
assert_image_similar(im, target.convert("RGBA"), 15)
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_file():
|
||||||
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
|
||||||
|
with pytest.raises(SyntaxError):
|
||||||
|
FtexImagePlugin.FtexImageFile(invalid_file)
|
||||||
|
|
||||||
|
|
||||||
def test_constants_deprecation():
|
def test_constants_deprecation():
|
||||||
for enum, prefix in {
|
for enum, prefix in {
|
||||||
FtexImagePlugin.Format: "FORMAT_",
|
FtexImagePlugin.Format: "FORMAT_",
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import warnings
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -39,21 +40,17 @@ def test_unclosed_file():
|
||||||
|
|
||||||
|
|
||||||
def test_closed_file():
|
def test_closed_file():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
im = Image.open(TEST_GIF)
|
im = Image.open(TEST_GIF)
|
||||||
im.load()
|
im.load()
|
||||||
im.close()
|
im.close()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_context_manager():
|
def test_context_manager():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
with Image.open(TEST_GIF) as im:
|
with Image.open(TEST_GIF) as im:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_file():
|
def test_invalid_file():
|
||||||
invalid_file = "Tests/images/flower.jpg"
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
@ -62,6 +59,17 @@ def test_invalid_file():
|
||||||
GifImagePlugin.GifImageFile(invalid_file)
|
GifImagePlugin.GifImageFile(invalid_file)
|
||||||
|
|
||||||
|
|
||||||
|
def test_l_mode_transparency():
|
||||||
|
with Image.open("Tests/images/no_palette_with_transparency.gif") as im:
|
||||||
|
assert im.mode == "L"
|
||||||
|
assert im.load()[0, 0] == 0
|
||||||
|
assert im.info["transparency"] == 255
|
||||||
|
|
||||||
|
im.seek(1)
|
||||||
|
assert im.mode == "LA"
|
||||||
|
assert im.load()[0, 0] == (0, 255)
|
||||||
|
|
||||||
|
|
||||||
def test_optimize():
|
def test_optimize():
|
||||||
def test_grayscale(optimize):
|
def test_grayscale(optimize):
|
||||||
im = Image.new("L", (1, 1), 0)
|
im = Image.new("L", (1, 1), 0)
|
||||||
|
@ -311,6 +319,22 @@ def test_n_frames():
|
||||||
assert im.is_animated == (n_frames != 1)
|
assert im.is_animated == (n_frames != 1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_change():
|
||||||
|
# Test n_frames does not change the image
|
||||||
|
with Image.open("Tests/images/dispose_bgnd.gif") as im:
|
||||||
|
im.seek(1)
|
||||||
|
expected = im.copy()
|
||||||
|
assert im.n_frames == 5
|
||||||
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
|
# Test is_animated does not change the image
|
||||||
|
with Image.open("Tests/images/dispose_bgnd.gif") as im:
|
||||||
|
im.seek(3)
|
||||||
|
expected = im.copy()
|
||||||
|
assert im.is_animated
|
||||||
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
|
|
||||||
def test_eoferror():
|
def test_eoferror():
|
||||||
with Image.open(TEST_GIF) as im:
|
with Image.open(TEST_GIF) as im:
|
||||||
n_frames = im.n_frames
|
n_frames = im.n_frames
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -19,9 +20,8 @@ def test_sanity():
|
||||||
with Image.open(TEST_FILE) as im:
|
with Image.open(TEST_FILE) as im:
|
||||||
|
|
||||||
# Assert that there is no unclosed file warning
|
# Assert that there is no unclosed file warning
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
im.load()
|
im.load()
|
||||||
assert not record
|
|
||||||
|
|
||||||
assert im.mode == "RGBA"
|
assert im.mode == "RGBA"
|
||||||
assert im.size == (1024, 1024)
|
assert im.size == (1024, 1024)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import filecmp
|
import filecmp
|
||||||
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -35,21 +36,17 @@ def test_unclosed_file():
|
||||||
|
|
||||||
|
|
||||||
def test_closed_file():
|
def test_closed_file():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
im = Image.open(TEST_IM)
|
im = Image.open(TEST_IM)
|
||||||
im.load()
|
im.load()
|
||||||
im.close()
|
im.close()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_context_manager():
|
def test_context_manager():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
with Image.open(TEST_IM) as im:
|
with Image.open(TEST_IM) as im:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_tell():
|
def test_tell():
|
||||||
# Arrange
|
# Arrange
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import warnings
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -756,9 +757,8 @@ class TestFileJpeg:
|
||||||
assert exif[282] == 180
|
assert exif[282] == 180
|
||||||
|
|
||||||
out = str(tmp_path / "out.jpg")
|
out = str(tmp_path / "out.jpg")
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
im.save(out, exif=exif)
|
im.save(out, exif=exif)
|
||||||
assert not record
|
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
with Image.open(out) as reloaded:
|
||||||
assert reloaded.getexif()[282] == 180
|
assert reloaded.getexif()[282] == 180
|
||||||
|
|
|
@ -218,7 +218,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
values = {
|
values = {
|
||||||
2: "test",
|
2: "test",
|
||||||
3: 1,
|
3: 1,
|
||||||
4: 2 ** 20,
|
4: 2**20,
|
||||||
5: TiffImagePlugin.IFDRational(100, 1),
|
5: TiffImagePlugin.IFDRational(100, 1),
|
||||||
12: 1.05,
|
12: 1.05,
|
||||||
}
|
}
|
||||||
|
@ -1019,7 +1019,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
im = hopper("RGB").resize((256, 256))
|
im = hopper("RGB").resize((256, 256))
|
||||||
out = str(tmp_path / "temp.tif")
|
out = str(tmp_path / "temp.tif")
|
||||||
|
|
||||||
TiffImagePlugin.STRIP_SIZE = 2 ** 18
|
TiffImagePlugin.STRIP_SIZE = 2**18
|
||||||
try:
|
try:
|
||||||
|
|
||||||
im.save(out, compression="tiff_adobe_deflate")
|
im.save(out, compression="tiff_adobe_deflate")
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import warnings
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -41,21 +42,17 @@ def test_unclosed_file():
|
||||||
|
|
||||||
|
|
||||||
def test_closed_file():
|
def test_closed_file():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
im = Image.open(test_files[0])
|
im = Image.open(test_files[0])
|
||||||
im.load()
|
im.load()
|
||||||
im.close()
|
im.close()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_context_manager():
|
def test_context_manager():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
with Image.open(test_files[0]) as im:
|
with Image.open(test_files[0]) as im:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_app():
|
def test_app():
|
||||||
for test_file in test_files:
|
for test_file in test_files:
|
||||||
|
@ -88,6 +85,9 @@ def test_frame_size():
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
assert im.size == (680, 480)
|
assert im.size == (680, 480)
|
||||||
|
|
||||||
|
im.seek(0)
|
||||||
|
assert im.size == (640, 480)
|
||||||
|
|
||||||
|
|
||||||
def test_ignore_frame_size():
|
def test_ignore_frame_size():
|
||||||
# Ignore the different size of the second frame
|
# Ignore the different size of the second frame
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
import warnings
|
||||||
import zlib
|
import zlib
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
@ -331,9 +332,8 @@ class TestFilePng:
|
||||||
|
|
||||||
with Image.open(TEST_PNG_FILE) as im:
|
with Image.open(TEST_PNG_FILE) as im:
|
||||||
# Assert that there is no unclosed file warning
|
# Assert that there is no unclosed file warning
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
im.verify()
|
im.verify()
|
||||||
assert not record
|
|
||||||
|
|
||||||
with Image.open(TEST_PNG_FILE) as im:
|
with Image.open(TEST_PNG_FILE) as im:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
|
@ -3,7 +3,7 @@ from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image, UnidentifiedImageError
|
||||||
|
|
||||||
from .helper import assert_image_equal_tofile, assert_image_similar, hopper
|
from .helper import assert_image_equal_tofile, assert_image_similar, hopper
|
||||||
|
|
||||||
|
@ -50,15 +50,70 @@ def test_pnm(tmp_path):
|
||||||
assert_image_equal_tofile(im, f)
|
assert_image_equal_tofile(im, f)
|
||||||
|
|
||||||
|
|
||||||
|
def test_magic(tmp_path):
|
||||||
|
path = str(tmp_path / "temp.ppm")
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
f.write(b"PyInvalid")
|
||||||
|
|
||||||
|
with pytest.raises(UnidentifiedImageError):
|
||||||
|
with Image.open(path):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_header_with_comments(tmp_path):
|
||||||
|
path = str(tmp_path / "temp.ppm")
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
f.write(b"P6 #comment\n#comment\r12#comment\r8\n128 #comment\n255\n")
|
||||||
|
|
||||||
|
with Image.open(path) as im:
|
||||||
|
assert im.size == (128, 128)
|
||||||
|
|
||||||
|
|
||||||
|
def test_non_integer_token(tmp_path):
|
||||||
|
path = str(tmp_path / "temp.ppm")
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
f.write(b"P6\nTEST")
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
with Image.open(path):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_token_too_long(tmp_path):
|
||||||
|
path = str(tmp_path / "temp.ppm")
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
f.write(b"P6\n 01234567890")
|
||||||
|
|
||||||
|
with pytest.raises(ValueError) as e:
|
||||||
|
with Image.open(path):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert str(e.value) == "Token too long in file header: b'01234567890'"
|
||||||
|
|
||||||
|
|
||||||
|
def test_too_many_colors(tmp_path):
|
||||||
|
path = str(tmp_path / "temp.ppm")
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
f.write(b"P6\n1 1\n1000\n")
|
||||||
|
|
||||||
|
with pytest.raises(ValueError) as e:
|
||||||
|
with Image.open(path):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert str(e.value) == "Too many colors for band: 1000"
|
||||||
|
|
||||||
|
|
||||||
def test_truncated_file(tmp_path):
|
def test_truncated_file(tmp_path):
|
||||||
path = str(tmp_path / "temp.pgm")
|
path = str(tmp_path / "temp.pgm")
|
||||||
with open(path, "w") as f:
|
with open(path, "w") as f:
|
||||||
f.write("P6")
|
f.write("P6")
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError) as e:
|
||||||
with Image.open(path):
|
with Image.open(path):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
assert str(e.value) == "Reached EOF while reading header"
|
||||||
|
|
||||||
|
|
||||||
def test_neg_ppm():
|
def test_neg_ppm():
|
||||||
# Storage.c accepted negative values for xsize, ysize. the
|
# Storage.c accepted negative values for xsize, ysize. the
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, PsdImagePlugin
|
from PIL import Image, PsdImagePlugin
|
||||||
|
@ -29,21 +31,17 @@ def test_unclosed_file():
|
||||||
|
|
||||||
|
|
||||||
def test_closed_file():
|
def test_closed_file():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
im = Image.open(test_file)
|
im = Image.open(test_file)
|
||||||
im.load()
|
im.load()
|
||||||
im.close()
|
im.close()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_context_manager():
|
def test_context_manager():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_file():
|
def test_invalid_file():
|
||||||
invalid_file = "Tests/images/flower.jpg"
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import warnings
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -28,21 +29,17 @@ def test_unclosed_file():
|
||||||
|
|
||||||
|
|
||||||
def test_closed_file():
|
def test_closed_file():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
im = Image.open(TEST_FILE)
|
im = Image.open(TEST_FILE)
|
||||||
im.load()
|
im.load()
|
||||||
im.close()
|
im.close()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_context_manager():
|
def test_context_manager():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
with Image.open(TEST_FILE) as im:
|
with Image.open(TEST_FILE) as im:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_save(tmp_path):
|
def test_save(tmp_path):
|
||||||
# Arrange
|
# Arrange
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, TarIO, features
|
from PIL import Image, TarIO, features
|
||||||
|
@ -31,16 +33,12 @@ def test_unclosed_file():
|
||||||
|
|
||||||
|
|
||||||
def test_close():
|
def test_close():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
tar = TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg")
|
tar = TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg")
|
||||||
tar.close()
|
tar.close()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_contextmanager():
|
def test_contextmanager():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
with TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg"):
|
with TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg"):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
|
@ -97,6 +97,11 @@ def test_id_field_rle():
|
||||||
assert im.size == (199, 199)
|
assert im.size == (199, 199)
|
||||||
|
|
||||||
|
|
||||||
|
def test_cross_scan_line():
|
||||||
|
with Image.open("Tests/images/cross_scan_line.tga") as im:
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/cross_scan_line.png")
|
||||||
|
|
||||||
|
|
||||||
def test_save(tmp_path):
|
def test_save(tmp_path):
|
||||||
test_file = "Tests/images/tga_id_field.tga"
|
test_file = "Tests/images/tga_id_field.tga"
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import os
|
import os
|
||||||
|
import warnings
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -64,20 +65,16 @@ class TestFileTiff:
|
||||||
pytest.warns(ResourceWarning, open)
|
pytest.warns(ResourceWarning, open)
|
||||||
|
|
||||||
def test_closed_file(self):
|
def test_closed_file(self):
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
im = Image.open("Tests/images/multipage.tiff")
|
im = Image.open("Tests/images/multipage.tiff")
|
||||||
im.load()
|
im.load()
|
||||||
im.close()
|
im.close()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
def test_context_manager(self):
|
def test_context_manager(self):
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
with Image.open("Tests/images/multipage.tiff") as im:
|
with Image.open("Tests/images/multipage.tiff") as im:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
def test_mac_tiff(self):
|
def test_mac_tiff(self):
|
||||||
# Read RGBa images from macOS [@PIL136]
|
# Read RGBa images from macOS [@PIL136]
|
||||||
|
|
||||||
|
|
|
@ -258,7 +258,7 @@ def test_ifd_unsigned_rational(tmp_path):
|
||||||
im = hopper()
|
im = hopper()
|
||||||
info = TiffImagePlugin.ImageFileDirectory_v2()
|
info = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
|
|
||||||
max_long = 2 ** 32 - 1
|
max_long = 2**32 - 1
|
||||||
|
|
||||||
# 4 bytes unsigned long
|
# 4 bytes unsigned long
|
||||||
numerator = max_long
|
numerator = max_long
|
||||||
|
@ -290,8 +290,8 @@ def test_ifd_signed_rational(tmp_path):
|
||||||
info = TiffImagePlugin.ImageFileDirectory_v2()
|
info = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
|
|
||||||
# pair of 4 byte signed longs
|
# pair of 4 byte signed longs
|
||||||
numerator = 2 ** 31 - 1
|
numerator = 2**31 - 1
|
||||||
denominator = -(2 ** 31)
|
denominator = -(2**31)
|
||||||
|
|
||||||
info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
|
info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
|
||||||
|
|
||||||
|
@ -302,8 +302,8 @@ def test_ifd_signed_rational(tmp_path):
|
||||||
assert numerator == reloaded.tag_v2[37380].numerator
|
assert numerator == reloaded.tag_v2[37380].numerator
|
||||||
assert denominator == reloaded.tag_v2[37380].denominator
|
assert denominator == reloaded.tag_v2[37380].denominator
|
||||||
|
|
||||||
numerator = -(2 ** 31)
|
numerator = -(2**31)
|
||||||
denominator = 2 ** 31 - 1
|
denominator = 2**31 - 1
|
||||||
|
|
||||||
info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
|
info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
|
||||||
|
|
||||||
|
@ -315,7 +315,7 @@ def test_ifd_signed_rational(tmp_path):
|
||||||
assert denominator == reloaded.tag_v2[37380].denominator
|
assert denominator == reloaded.tag_v2[37380].denominator
|
||||||
|
|
||||||
# out of bounds of 4 byte signed long
|
# out of bounds of 4 byte signed long
|
||||||
numerator = -(2 ** 31) - 1
|
numerator = -(2**31) - 1
|
||||||
denominator = 1
|
denominator = 1
|
||||||
|
|
||||||
info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
|
info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
|
||||||
|
@ -324,7 +324,7 @@ def test_ifd_signed_rational(tmp_path):
|
||||||
im.save(out, tiffinfo=info, compression="raw")
|
im.save(out, tiffinfo=info, compression="raw")
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
with Image.open(out) as reloaded:
|
||||||
assert 2 ** 31 - 1 == reloaded.tag_v2[37380].numerator
|
assert 2**31 - 1 == reloaded.tag_v2[37380].numerator
|
||||||
assert -1 == reloaded.tag_v2[37380].denominator
|
assert -1 == reloaded.tag_v2[37380].denominator
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import io
|
import io
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -127,7 +128,7 @@ class TestFileWebp:
|
||||||
|
|
||||||
self._roundtrip(tmp_path, "P", 50.0)
|
self._roundtrip(tmp_path, "P", 50.0)
|
||||||
|
|
||||||
@pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="Requires 64-bit system")
|
@pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system")
|
||||||
def test_write_encoding_error_message(self, tmp_path):
|
def test_write_encoding_error_message(self, tmp_path):
|
||||||
temp_file = str(tmp_path / "temp.webp")
|
temp_file = str(tmp_path / "temp.webp")
|
||||||
im = Image.new("RGB", (15000, 15000))
|
im = Image.new("RGB", (15000, 15000))
|
||||||
|
@ -161,9 +162,8 @@ class TestFileWebp:
|
||||||
file_path = "Tests/images/hopper.webp"
|
file_path = "Tests/images/hopper.webp"
|
||||||
with Image.open(file_path) as image:
|
with Image.open(file_path) as image:
|
||||||
temp_file = str(tmp_path / "temp.webp")
|
temp_file = str(tmp_path / "temp.webp")
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
image.save(temp_file)
|
image.save(temp_file)
|
||||||
assert not record
|
|
||||||
|
|
||||||
def test_file_pointer_could_be_reused(self):
|
def test_file_pointer_could_be_reused(self):
|
||||||
file_path = "Tests/images/hopper.webp"
|
file_path = "Tests/images/hopper.webp"
|
||||||
|
|
|
@ -2,7 +2,7 @@ from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image, XbmImagePlugin
|
||||||
|
|
||||||
from .helper import hopper
|
from .helper import hopper
|
||||||
|
|
||||||
|
@ -63,6 +63,13 @@ def test_open_filename_with_underscore():
|
||||||
assert im.size == (128, 128)
|
assert im.size == (128, 128)
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_file():
|
||||||
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
|
||||||
|
with pytest.raises(SyntaxError):
|
||||||
|
XbmImagePlugin.XbmImageFile(invalid_file)
|
||||||
|
|
||||||
|
|
||||||
def test_save_wrong_mode(tmp_path):
|
def test_save_wrong_mode(tmp_path):
|
||||||
im = hopper()
|
im = hopper()
|
||||||
out = str(tmp_path / "temp.xbm")
|
out = str(tmp_path / "temp.xbm")
|
||||||
|
|
|
@ -3,6 +3,7 @@ import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -648,9 +649,8 @@ class TestImage:
|
||||||
|
|
||||||
# Act/Assert
|
# Act/Assert
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
im.save(temp_file)
|
im.save(temp_file)
|
||||||
assert not record
|
|
||||||
|
|
||||||
def test_load_on_nonexclusive_multiframe(self):
|
def test_load_on_nonexclusive_multiframe(self):
|
||||||
with open("Tests/images/frozenpond.mpo", "rb") as fp:
|
with open("Tests/images/frozenpond.mpo", "rb") as fp:
|
||||||
|
|
|
@ -154,14 +154,17 @@ class TestImageGetPixel(AccessTest):
|
||||||
|
|
||||||
# Check 0
|
# Check 0
|
||||||
im = Image.new(mode, (0, 0), None)
|
im = Image.new(mode, (0, 0), None)
|
||||||
with pytest.raises(IndexError):
|
assert im.load() is not None
|
||||||
|
|
||||||
|
error = ValueError if self._need_cffi_access else IndexError
|
||||||
|
with pytest.raises(error):
|
||||||
im.putpixel((0, 0), c)
|
im.putpixel((0, 0), c)
|
||||||
with pytest.raises(IndexError):
|
with pytest.raises(error):
|
||||||
im.getpixel((0, 0))
|
im.getpixel((0, 0))
|
||||||
# Check 0 negative index
|
# Check 0 negative index
|
||||||
with pytest.raises(IndexError):
|
with pytest.raises(error):
|
||||||
im.putpixel((-1, -1), c)
|
im.putpixel((-1, -1), c)
|
||||||
with pytest.raises(IndexError):
|
with pytest.raises(error):
|
||||||
im.getpixel((-1, -1))
|
im.getpixel((-1, -1))
|
||||||
|
|
||||||
# check initial color
|
# check initial color
|
||||||
|
@ -176,10 +179,10 @@ class TestImageGetPixel(AccessTest):
|
||||||
|
|
||||||
# Check 0
|
# Check 0
|
||||||
im = Image.new(mode, (0, 0), c)
|
im = Image.new(mode, (0, 0), c)
|
||||||
with pytest.raises(IndexError):
|
with pytest.raises(error):
|
||||||
im.getpixel((0, 0))
|
im.getpixel((0, 0))
|
||||||
# Check 0 negative index
|
# Check 0 negative index
|
||||||
with pytest.raises(IndexError):
|
with pytest.raises(error):
|
||||||
im.getpixel((-1, -1))
|
im.getpixel((-1, -1))
|
||||||
|
|
||||||
def test_basic(self):
|
def test_basic(self):
|
||||||
|
@ -205,10 +208,10 @@ class TestImageGetPixel(AccessTest):
|
||||||
# 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*
|
||||||
for mode in ("I;16", "I;16B"):
|
for mode in ("I;16", "I;16B"):
|
||||||
self.check(mode, 2 ** 15 - 1)
|
self.check(mode, 2**15 - 1)
|
||||||
self.check(mode, 2 ** 15)
|
self.check(mode, 2**15)
|
||||||
self.check(mode, 2 ** 15 + 1)
|
self.check(mode, 2**15 + 1)
|
||||||
self.check(mode, 2 ** 16 - 1)
|
self.check(mode, 2**16 - 1)
|
||||||
|
|
||||||
def test_p_putpixel_rgb_rgba(self):
|
def test_p_putpixel_rgb_rgba(self):
|
||||||
for color in [(255, 0, 0), (255, 0, 0, 255)]:
|
for color in [(255, 0, 0), (255, 0, 0, 255)]:
|
||||||
|
@ -386,7 +389,7 @@ class TestImagePutPixelError(AccessTest):
|
||||||
def test_putpixel_overflow_error(self, mode):
|
def test_putpixel_overflow_error(self, mode):
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
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):
|
def test_putpixel_unrecognized_mode(self):
|
||||||
im = hopper("BGR;15")
|
im = hopper("BGR;15")
|
||||||
|
|
|
@ -70,6 +70,11 @@ def test_16bit():
|
||||||
with Image.open("Tests/images/16bit.cropped.tif") as im:
|
with Image.open("Tests/images/16bit.cropped.tif") as im:
|
||||||
_test_float_conversion(im)
|
_test_float_conversion(im)
|
||||||
|
|
||||||
|
for color in (65535, 65536):
|
||||||
|
im = Image.new("I", (1, 1), color)
|
||||||
|
im_i16 = im.convert("I;16")
|
||||||
|
assert im_i16.getpixel((0, 0)) == 65535
|
||||||
|
|
||||||
|
|
||||||
def test_16bit_workaround():
|
def test_16bit_workaround():
|
||||||
with Image.open("Tests/images/16bit.cropped.tif") as im:
|
with Image.open("Tests/images/16bit.cropped.tif") as im:
|
||||||
|
@ -135,6 +140,10 @@ def test_trns_l(tmp_path):
|
||||||
|
|
||||||
f = str(tmp_path / "temp.png")
|
f = str(tmp_path / "temp.png")
|
||||||
|
|
||||||
|
im_la = im.convert("LA")
|
||||||
|
assert "transparency" not in im_la.info
|
||||||
|
im_la.save(f)
|
||||||
|
|
||||||
im_rgb = im.convert("RGB")
|
im_rgb = im.convert("RGB")
|
||||||
assert im_rgb.info["transparency"] == (128, 128, 128) # undone
|
assert im_rgb.info["transparency"] == (128, 128, 128) # undone
|
||||||
im_rgb.save(f)
|
im_rgb.save(f)
|
||||||
|
|
|
@ -67,6 +67,16 @@ class TestImagingPaste:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def gradient_LA(self):
|
||||||
|
return Image.merge(
|
||||||
|
"LA",
|
||||||
|
[
|
||||||
|
self.gradient_L,
|
||||||
|
self.gradient_L.transpose(Image.Transpose.ROTATE_90),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def gradient_RGBA(self):
|
def gradient_RGBA(self):
|
||||||
return Image.merge(
|
return Image.merge(
|
||||||
|
@ -145,6 +155,28 @@ class TestImagingPaste:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_image_mask_LA(self):
|
||||||
|
for mode in ("RGBA", "RGB", "L"):
|
||||||
|
im = Image.new(mode, (200, 200), "white")
|
||||||
|
im2 = getattr(self, "gradient_" + mode)
|
||||||
|
|
||||||
|
self.assert_9points_paste(
|
||||||
|
im,
|
||||||
|
im2,
|
||||||
|
self.gradient_LA,
|
||||||
|
[
|
||||||
|
(128, 191, 255, 191),
|
||||||
|
(112, 207, 206, 111),
|
||||||
|
(128, 254, 128, 1),
|
||||||
|
(208, 208, 239, 239),
|
||||||
|
(192, 191, 191, 191),
|
||||||
|
(207, 207, 112, 113),
|
||||||
|
(255, 255, 255, 255),
|
||||||
|
(239, 207, 207, 239),
|
||||||
|
(255, 191, 128, 191),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def test_image_mask_RGBA(self):
|
def test_image_mask_RGBA(self):
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
for mode in ("RGBA", "RGB", "L"):
|
||||||
im = Image.new(mode, (200, 200), "white")
|
im = Image.new(mode, (200, 200), "white")
|
||||||
|
|
|
@ -38,7 +38,7 @@ def test_long_integers():
|
||||||
assert put(0xFFFFFFFF) == (255, 255, 255, 255)
|
assert put(0xFFFFFFFF) == (255, 255, 255, 255)
|
||||||
assert put(-1) == (255, 255, 255, 255)
|
assert put(-1) == (255, 255, 255, 255)
|
||||||
assert put(-1) == (255, 255, 255, 255)
|
assert put(-1) == (255, 255, 255, 255)
|
||||||
if sys.maxsize > 2 ** 32:
|
if sys.maxsize > 2**32:
|
||||||
assert put(sys.maxsize) == (255, 255, 255, 255)
|
assert put(sys.maxsize) == (255, 255, 255, 255)
|
||||||
else:
|
else:
|
||||||
assert put(sys.maxsize) == (255, 255, 255, 127)
|
assert put(sys.maxsize) == (255, 255, 255, 127)
|
||||||
|
|
|
@ -303,7 +303,7 @@ def test_extended_information():
|
||||||
def assert_truncated_tuple_equal(tup1, tup2, digits=10):
|
def assert_truncated_tuple_equal(tup1, tup2, digits=10):
|
||||||
# Helper function to reduce precision of tuples of floats
|
# Helper function to reduce precision of tuples of floats
|
||||||
# recursively and then check equality.
|
# recursively and then check equality.
|
||||||
power = 10 ** digits
|
power = 10**digits
|
||||||
|
|
||||||
def truncate_tuple(tuple_or_float):
|
def truncate_tuple(tuple_or_float):
|
||||||
return tuple(
|
return tuple(
|
||||||
|
|
|
@ -124,6 +124,23 @@ class TestImageFile:
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
p.close()
|
p.close()
|
||||||
|
|
||||||
|
def test_no_format(self):
|
||||||
|
buf = BytesIO(b"\x00" * 255)
|
||||||
|
|
||||||
|
class DummyImageFile(ImageFile.ImageFile):
|
||||||
|
def _open(self):
|
||||||
|
self.mode = "RGB"
|
||||||
|
self._size = (1, 1)
|
||||||
|
|
||||||
|
im = DummyImageFile(buf)
|
||||||
|
assert im.format is None
|
||||||
|
assert im.get_format_mimetype() is None
|
||||||
|
|
||||||
|
def test_oserror(self):
|
||||||
|
im = Image.new("RGB", (1, 1))
|
||||||
|
with pytest.raises(OSError):
|
||||||
|
im.save(BytesIO(), "JPEG2000", num_resolutions=2)
|
||||||
|
|
||||||
def test_truncated(self):
|
def test_truncated(self):
|
||||||
b = BytesIO(
|
b = BytesIO(
|
||||||
b"BM000000000000" # head_data
|
b"BM000000000000" # head_data
|
||||||
|
@ -179,6 +196,14 @@ class MockPyDecoder(ImageFile.PyDecoder):
|
||||||
return -1, 0
|
return -1, 0
|
||||||
|
|
||||||
|
|
||||||
|
class MockPyEncoder(ImageFile.PyEncoder):
|
||||||
|
def encode(self, buffer):
|
||||||
|
return 1, 1, b""
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
self.cleanup_called = True
|
||||||
|
|
||||||
|
|
||||||
xoff, yoff, xsize, ysize = 10, 20, 100, 100
|
xoff, yoff, xsize, ysize = 10, 20, 100, 100
|
||||||
|
|
||||||
|
|
||||||
|
@ -190,53 +215,58 @@ class MockImageFile(ImageFile.ImageFile):
|
||||||
self.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)]
|
self.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)]
|
||||||
|
|
||||||
|
|
||||||
class TestPyDecoder:
|
class CodecsTest:
|
||||||
def get_decoder(self):
|
@classmethod
|
||||||
decoder = MockPyDecoder(None)
|
def setup_class(cls):
|
||||||
|
cls.decoder = MockPyDecoder(None)
|
||||||
|
cls.encoder = MockPyEncoder(None)
|
||||||
|
|
||||||
def closure(mode, *args):
|
def decoder_closure(mode, *args):
|
||||||
decoder.__init__(mode, *args)
|
cls.decoder.__init__(mode, *args)
|
||||||
return decoder
|
return cls.decoder
|
||||||
|
|
||||||
Image.register_decoder("MOCK", closure)
|
def encoder_closure(mode, *args):
|
||||||
return decoder
|
cls.encoder.__init__(mode, *args)
|
||||||
|
return cls.encoder
|
||||||
|
|
||||||
|
Image.register_decoder("MOCK", decoder_closure)
|
||||||
|
Image.register_encoder("MOCK", encoder_closure)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPyDecoder(CodecsTest):
|
||||||
def test_setimage(self):
|
def test_setimage(self):
|
||||||
buf = BytesIO(b"\x00" * 255)
|
buf = BytesIO(b"\x00" * 255)
|
||||||
|
|
||||||
im = MockImageFile(buf)
|
im = MockImageFile(buf)
|
||||||
d = self.get_decoder()
|
|
||||||
|
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
assert d.state.xoff == xoff
|
assert self.decoder.state.xoff == xoff
|
||||||
assert d.state.yoff == yoff
|
assert self.decoder.state.yoff == yoff
|
||||||
assert d.state.xsize == xsize
|
assert self.decoder.state.xsize == xsize
|
||||||
assert d.state.ysize == ysize
|
assert self.decoder.state.ysize == ysize
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
d.set_as_raw(b"\x00")
|
self.decoder.set_as_raw(b"\x00")
|
||||||
|
|
||||||
def test_extents_none(self):
|
def test_extents_none(self):
|
||||||
buf = BytesIO(b"\x00" * 255)
|
buf = BytesIO(b"\x00" * 255)
|
||||||
|
|
||||||
im = MockImageFile(buf)
|
im = MockImageFile(buf)
|
||||||
im.tile = [("MOCK", None, 32, None)]
|
im.tile = [("MOCK", None, 32, None)]
|
||||||
d = self.get_decoder()
|
|
||||||
|
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
assert d.state.xoff == 0
|
assert self.decoder.state.xoff == 0
|
||||||
assert d.state.yoff == 0
|
assert self.decoder.state.yoff == 0
|
||||||
assert d.state.xsize == 200
|
assert self.decoder.state.xsize == 200
|
||||||
assert d.state.ysize == 200
|
assert self.decoder.state.ysize == 200
|
||||||
|
|
||||||
def test_negsize(self):
|
def test_negsize(self):
|
||||||
buf = BytesIO(b"\x00" * 255)
|
buf = BytesIO(b"\x00" * 255)
|
||||||
|
|
||||||
im = MockImageFile(buf)
|
im = MockImageFile(buf)
|
||||||
im.tile = [("MOCK", (xoff, yoff, -10, yoff + ysize), 32, None)]
|
im.tile = [("MOCK", (xoff, yoff, -10, yoff + ysize), 32, None)]
|
||||||
self.get_decoder()
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
im.load()
|
im.load()
|
||||||
|
@ -250,7 +280,6 @@ class TestPyDecoder:
|
||||||
|
|
||||||
im = MockImageFile(buf)
|
im = MockImageFile(buf)
|
||||||
im.tile = [("MOCK", (xoff, yoff, xoff + xsize + 100, yoff + ysize), 32, None)]
|
im.tile = [("MOCK", (xoff, yoff, xoff + xsize + 100, yoff + ysize), 32, None)]
|
||||||
self.get_decoder()
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
im.load()
|
im.load()
|
||||||
|
@ -259,14 +288,92 @@ class TestPyDecoder:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
def test_no_format(self):
|
def test_decode(self):
|
||||||
|
decoder = ImageFile.PyDecoder(None)
|
||||||
|
with pytest.raises(NotImplementedError):
|
||||||
|
decoder.decode(None)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPyEncoder(CodecsTest):
|
||||||
|
def test_setimage(self):
|
||||||
buf = BytesIO(b"\x00" * 255)
|
buf = BytesIO(b"\x00" * 255)
|
||||||
|
|
||||||
im = MockImageFile(buf)
|
im = MockImageFile(buf)
|
||||||
assert im.format is None
|
|
||||||
assert im.get_format_mimetype() is None
|
|
||||||
|
|
||||||
def test_oserror(self):
|
fp = BytesIO()
|
||||||
im = Image.new("RGB", (1, 1))
|
ImageFile._save(
|
||||||
with pytest.raises(OSError):
|
im, fp, [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 0, "RGB")]
|
||||||
im.save(BytesIO(), "JPEG2000", num_resolutions=2)
|
)
|
||||||
|
|
||||||
|
assert self.encoder.state.xoff == xoff
|
||||||
|
assert self.encoder.state.yoff == yoff
|
||||||
|
assert self.encoder.state.xsize == xsize
|
||||||
|
assert self.encoder.state.ysize == ysize
|
||||||
|
|
||||||
|
def test_extents_none(self):
|
||||||
|
buf = BytesIO(b"\x00" * 255)
|
||||||
|
|
||||||
|
im = MockImageFile(buf)
|
||||||
|
im.tile = [("MOCK", None, 32, None)]
|
||||||
|
|
||||||
|
fp = BytesIO()
|
||||||
|
ImageFile._save(im, fp, [("MOCK", None, 0, "RGB")])
|
||||||
|
|
||||||
|
assert self.encoder.state.xoff == 0
|
||||||
|
assert self.encoder.state.yoff == 0
|
||||||
|
assert self.encoder.state.xsize == 200
|
||||||
|
assert self.encoder.state.ysize == 200
|
||||||
|
|
||||||
|
def test_negsize(self):
|
||||||
|
buf = BytesIO(b"\x00" * 255)
|
||||||
|
|
||||||
|
im = MockImageFile(buf)
|
||||||
|
|
||||||
|
fp = BytesIO()
|
||||||
|
self.encoder.cleanup_called = False
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
ImageFile._save(
|
||||||
|
im, fp, [("MOCK", (xoff, yoff, -10, yoff + ysize), 0, "RGB")]
|
||||||
|
)
|
||||||
|
assert self.encoder.cleanup_called
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
ImageFile._save(
|
||||||
|
im, fp, [("MOCK", (xoff, yoff, xoff + xsize, -10), 0, "RGB")]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_oversize(self):
|
||||||
|
buf = BytesIO(b"\x00" * 255)
|
||||||
|
|
||||||
|
im = MockImageFile(buf)
|
||||||
|
|
||||||
|
fp = BytesIO()
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
ImageFile._save(
|
||||||
|
im,
|
||||||
|
fp,
|
||||||
|
[("MOCK", (xoff, yoff, xoff + xsize + 100, yoff + ysize), 0, "RGB")],
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
ImageFile._save(
|
||||||
|
im,
|
||||||
|
fp,
|
||||||
|
[("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize + 100), 0, "RGB")],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_encode(self):
|
||||||
|
encoder = ImageFile.PyEncoder(None)
|
||||||
|
with pytest.raises(NotImplementedError):
|
||||||
|
encoder.encode(None)
|
||||||
|
|
||||||
|
bytes_consumed, errcode = encoder.encode_to_pyfd()
|
||||||
|
assert bytes_consumed == 0
|
||||||
|
assert ImageFile.ERRORS[errcode] == "bad configuration"
|
||||||
|
|
||||||
|
encoder._pushes_fd = True
|
||||||
|
with pytest.raises(NotImplementedError):
|
||||||
|
encoder.encode_to_pyfd()
|
||||||
|
|
||||||
|
with pytest.raises(NotImplementedError):
|
||||||
|
encoder.encode_to_file(None, None)
|
||||||
|
|
|
@ -88,19 +88,6 @@ class TestImageFont:
|
||||||
|
|
||||||
ImageFont.truetype(tempfile, FONT_SIZE)
|
ImageFont.truetype(tempfile, FONT_SIZE)
|
||||||
|
|
||||||
def test_unavailable_layout_engine(self):
|
|
||||||
have_raqm = ImageFont.core.HAVE_RAQM
|
|
||||||
ImageFont.core.HAVE_RAQM = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
ttf = ImageFont.truetype(
|
|
||||||
FONT_PATH, FONT_SIZE, layout_engine=ImageFont.Layout.RAQM
|
|
||||||
)
|
|
||||||
finally:
|
|
||||||
ImageFont.core.HAVE_RAQM = have_raqm
|
|
||||||
|
|
||||||
assert ttf.layout_engine == ImageFont.Layout.BASIC
|
|
||||||
|
|
||||||
def _render(self, font):
|
def _render(self, font):
|
||||||
txt = "Hello World!"
|
txt = "Hello World!"
|
||||||
ttf = ImageFont.truetype(font, FONT_SIZE, layout_engine=self.LAYOUT_ENGINE)
|
ttf = ImageFont.truetype(font, FONT_SIZE, layout_engine=self.LAYOUT_ENGINE)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import ImageQt
|
from PIL import ImageQt
|
||||||
|
@ -30,10 +32,10 @@ def test_rgb():
|
||||||
|
|
||||||
def checkrgb(r, g, b):
|
def checkrgb(r, g, b):
|
||||||
val = ImageQt.rgb(r, g, b)
|
val = ImageQt.rgb(r, g, b)
|
||||||
val = val % 2 ** 24 # drop the alpha
|
val = val % 2**24 # drop the alpha
|
||||||
assert val >> 16 == r
|
assert val >> 16 == r
|
||||||
assert ((val >> 8) % 2 ** 8) == g
|
assert ((val >> 8) % 2**8) == g
|
||||||
assert val % 2 ** 8 == b
|
assert val % 2**8 == b
|
||||||
|
|
||||||
checkrgb(0, 0, 0)
|
checkrgb(0, 0, 0)
|
||||||
checkrgb(255, 0, 0)
|
checkrgb(255, 0, 0)
|
||||||
|
@ -56,7 +58,5 @@ def test_image():
|
||||||
|
|
||||||
|
|
||||||
def test_closed_file():
|
def test_closed_file():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
ImageQt.ImageQt("Tests/images/hopper.gif")
|
ImageQt.ImageQt("Tests/images/hopper.gif")
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
|
@ -51,8 +51,8 @@ def test_constant():
|
||||||
st = ImageStat.Stat(im)
|
st = ImageStat.Stat(im)
|
||||||
|
|
||||||
assert st.extrema[0] == (128, 128)
|
assert st.extrema[0] == (128, 128)
|
||||||
assert st.sum[0] == 128 ** 3
|
assert st.sum[0] == 128**3
|
||||||
assert st.sum2[0] == 128 ** 4
|
assert st.sum2[0] == 128**4
|
||||||
assert st.mean[0] == 128
|
assert st.mean[0] == 128
|
||||||
assert st.median[0] == 128
|
assert st.median[0] == 128
|
||||||
assert st.rms[0] == 128
|
assert st.rms[0] == 128
|
||||||
|
|
|
@ -36,7 +36,7 @@ def test_tobytes():
|
||||||
Image.MAX_IMAGE_PIXELS = max_pixels
|
Image.MAX_IMAGE_PIXELS = max_pixels
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="Requires 64-bit system")
|
@pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system")
|
||||||
def test_ysize():
|
def test_ysize():
|
||||||
numpy = pytest.importorskip("numpy", reason="NumPy not installed")
|
numpy = pytest.importorskip("numpy", reason="NumPy not installed")
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
@ -237,6 +239,5 @@ def test_no_resource_warning_for_numpy_array():
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
|
|
||||||
# Act/Assert
|
# Act/Assert
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
array(im)
|
array(im)
|
||||||
assert not record
|
|
||||||
|
|
|
@ -115,6 +115,6 @@ def test_pdf_repr():
|
||||||
assert pdf_repr(True) == b"true"
|
assert pdf_repr(True) == b"true"
|
||||||
assert pdf_repr(False) == b"false"
|
assert pdf_repr(False) == b"false"
|
||||||
assert pdf_repr(None) == b"null"
|
assert pdf_repr(None) == b"null"
|
||||||
assert pdf_repr(b"a)/b\\(c") == br"(a\)/b\\\(c)"
|
assert pdf_repr(b"a)/b\\(c") == rb"(a\)/b\\\(c)"
|
||||||
assert pdf_repr([123, True, {"a": PdfName(b"b")}]) == b"[ 123 true <<\n/a /b\n>> ]"
|
assert pdf_repr([123, True, {"a": PdfName(b"b")}]) == b"[ 123 true <<\n/a /b\n>> ]"
|
||||||
assert pdf_repr(PdfBinary(b"\x90\x1F\xA0")) == b"<901FA0>"
|
assert pdf_repr(PdfBinary(b"\x90\x1F\xA0")) == b"<901FA0>"
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
# You can set these variables from the command line.
|
# You can set these variables from the command line.
|
||||||
SPHINXOPTS =
|
SPHINXOPTS =
|
||||||
SPHINXBUILD = sphinx-build
|
SPHINXBUILD = python3 -m sphinx.cmd.build
|
||||||
PAPER =
|
PAPER =
|
||||||
BUILDDIR = _build
|
BUILDDIR = _build
|
||||||
|
|
||||||
|
@ -41,38 +41,48 @@ help:
|
||||||
clean:
|
clean:
|
||||||
-rm -rf $(BUILDDIR)/*
|
-rm -rf $(BUILDDIR)/*
|
||||||
|
|
||||||
|
install-sphinx:
|
||||||
|
python3 -c "import sphinx" > /dev/null 2>&1 || python3 -m pip install sphinx
|
||||||
|
|
||||||
html:
|
html:
|
||||||
|
$(MAKE) install-sphinx
|
||||||
$(SPHINXBUILD) -b html -W --keep-going $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
$(SPHINXBUILD) -b html -W --keep-going $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||||
|
|
||||||
dirhtml:
|
dirhtml:
|
||||||
|
$(MAKE) install-sphinx
|
||||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||||
|
|
||||||
singlehtml:
|
singlehtml:
|
||||||
|
$(MAKE) install-sphinx
|
||||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||||
|
|
||||||
pickle:
|
pickle:
|
||||||
|
$(MAKE) install-sphinx
|
||||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished; now you can process the pickle files."
|
@echo "Build finished; now you can process the pickle files."
|
||||||
|
|
||||||
json:
|
json:
|
||||||
|
$(MAKE) install-sphinx
|
||||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished; now you can process the JSON files."
|
@echo "Build finished; now you can process the JSON files."
|
||||||
|
|
||||||
htmlhelp:
|
htmlhelp:
|
||||||
|
$(MAKE) install-sphinx
|
||||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||||
|
|
||||||
qthelp:
|
qthelp:
|
||||||
|
$(MAKE) install-sphinx
|
||||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||||
|
@ -82,6 +92,7 @@ qthelp:
|
||||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PillowPILfork.qhc"
|
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PillowPILfork.qhc"
|
||||||
|
|
||||||
devhelp:
|
devhelp:
|
||||||
|
$(MAKE) install-sphinx
|
||||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished."
|
@echo "Build finished."
|
||||||
|
@ -91,11 +102,13 @@ devhelp:
|
||||||
@echo "# devhelp"
|
@echo "# devhelp"
|
||||||
|
|
||||||
epub:
|
epub:
|
||||||
|
$(MAKE) install-sphinx
|
||||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||||
|
|
||||||
latex:
|
latex:
|
||||||
|
$(MAKE) install-sphinx
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||||
|
@ -103,22 +116,26 @@ latex:
|
||||||
"(use \`make latexpdf' here to do that automatically)."
|
"(use \`make latexpdf' here to do that automatically)."
|
||||||
|
|
||||||
latexpdf:
|
latexpdf:
|
||||||
|
$(MAKE) install-sphinx
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
@echo "Running LaTeX files through pdflatex..."
|
@echo "Running LaTeX files through pdflatex..."
|
||||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||||
|
|
||||||
text:
|
text:
|
||||||
|
$(MAKE) install-sphinx
|
||||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||||
|
|
||||||
man:
|
man:
|
||||||
|
$(MAKE) install-sphinx
|
||||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||||
|
|
||||||
texinfo:
|
texinfo:
|
||||||
|
$(MAKE) install-sphinx
|
||||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||||
|
@ -126,28 +143,33 @@ texinfo:
|
||||||
"(use \`make info' here to do that automatically)."
|
"(use \`make info' here to do that automatically)."
|
||||||
|
|
||||||
info:
|
info:
|
||||||
|
$(MAKE) install-sphinx
|
||||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||||
@echo "Running Texinfo files through makeinfo..."
|
@echo "Running Texinfo files through makeinfo..."
|
||||||
make -C $(BUILDDIR)/texinfo info
|
make -C $(BUILDDIR)/texinfo info
|
||||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||||
|
|
||||||
gettext:
|
gettext:
|
||||||
|
$(MAKE) install-sphinx
|
||||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||||
|
|
||||||
changes:
|
changes:
|
||||||
|
$(MAKE) install-sphinx
|
||||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||||
@echo
|
@echo
|
||||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||||
|
|
||||||
linkcheck:
|
linkcheck:
|
||||||
|
$(MAKE) install-sphinx
|
||||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck -j auto
|
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck -j auto
|
||||||
@echo
|
@echo
|
||||||
@echo "Link check complete; look for any errors in the above output " \
|
@echo "Link check complete; look for any errors in the above output " \
|
||||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||||
|
|
||||||
doctest:
|
doctest:
|
||||||
|
$(MAKE) install-sphinx
|
||||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/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."
|
||||||
|
|
|
@ -210,7 +210,9 @@ class DdsImageFile(ImageFile.ImageFile):
|
||||||
format_description = "DirectDraw Surface"
|
format_description = "DirectDraw Surface"
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
magic, header_size = struct.unpack("<II", self.fp.read(8))
|
if not _accept(self.fp.read(4)):
|
||||||
|
raise SyntaxError("not a DDS file")
|
||||||
|
(header_size,) = struct.unpack("<I", self.fp.read(4))
|
||||||
if header_size != 124:
|
if header_size != 124:
|
||||||
raise OSError(f"Unsupported header size {repr(header_size)}")
|
raise OSError(f"Unsupported header size {repr(header_size)}")
|
||||||
header_bytes = self.fp.read(header_size - 4)
|
header_bytes = self.fp.read(header_size - 4)
|
||||||
|
|
|
@ -8,4 +8,4 @@ Appendices
|
||||||
|
|
||||||
image-file-formats
|
image-file-formats
|
||||||
text-anchors
|
text-anchors
|
||||||
writing-your-own-file-decoder
|
writing-your-own-image-plugin
|
||||||
|
|
|
@ -26,6 +26,20 @@ Fully supported formats
|
||||||
|
|
||||||
.. contents::
|
.. contents::
|
||||||
|
|
||||||
|
BLP
|
||||||
|
^^^
|
||||||
|
|
||||||
|
BLP is the Blizzard Mipmap Format, a texture format used in World of
|
||||||
|
Warcraft. Pillow supports reading ``JPEG`` Compressed or raw ``BLP1``
|
||||||
|
images, and all types of ``BLP2`` images.
|
||||||
|
|
||||||
|
Pillow supports writing BLP images. The :py:meth:`~PIL.Image.Image.save` method
|
||||||
|
can take the following keyword arguments:
|
||||||
|
|
||||||
|
**blp_version**
|
||||||
|
If present and set to "BLP1", images will be saved as BLP1. Otherwise, images
|
||||||
|
will be saved as BLP2.
|
||||||
|
|
||||||
BMP
|
BMP
|
||||||
^^^
|
^^^
|
||||||
|
|
||||||
|
@ -1042,13 +1056,6 @@ Pillow reads and writes X bitmap files (mode ``1``).
|
||||||
Read-only formats
|
Read-only formats
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
BLP
|
|
||||||
^^^
|
|
||||||
|
|
||||||
BLP is the Blizzard Mipmap Format, a texture format used in World of
|
|
||||||
Warcraft. Pillow supports reading ``JPEG`` Compressed or raw ``BLP1``
|
|
||||||
images, and all types of ``BLP2`` images.
|
|
||||||
|
|
||||||
CUR
|
CUR
|
||||||
^^^
|
^^^
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,9 @@ Writing Your Own Image Plugin
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
Pillow uses a plugin model which allows you to add your own
|
Pillow uses a plugin model which allows you to add your own
|
||||||
decoders to the library, without any changes to the library
|
decoders and encoders to the library, without any changes to the library
|
||||||
itself. Such plugins usually have names like
|
itself. Such plugins usually have names like :file:`XxxImagePlugin.py`,
|
||||||
:file:`XxxImagePlugin.py`, where ``Xxx`` is a unique format name
|
where ``Xxx`` is a unique format name (usually an abbreviation).
|
||||||
(usually an abbreviation).
|
|
||||||
|
|
||||||
.. warning:: Pillow >= 2.1.0 no longer automatically imports any file
|
.. warning:: Pillow >= 2.1.0 no longer automatically imports any file
|
||||||
in the Python path with a name ending in
|
in the Python path with a name ending in
|
||||||
|
@ -124,8 +123,12 @@ The ``tile`` attribute
|
||||||
To be able to read the file as well as just identifying it, the ``tile``
|
To be able to read the file as well as just identifying it, the ``tile``
|
||||||
attribute must also be set. This attribute consists of a list of tile
|
attribute must also be set. This attribute consists of a list of tile
|
||||||
descriptors, where each descriptor specifies how data should be loaded to a
|
descriptors, where each descriptor specifies how data should be loaded to a
|
||||||
given region in the image. In most cases, only a single descriptor is used,
|
given region in the image.
|
||||||
covering the full image.
|
|
||||||
|
In most cases, only a single descriptor is used, covering the full image.
|
||||||
|
:py:class:`.PsdImagePlugin.PsdImageFile` uses multiple tiles to combine
|
||||||
|
channels within a single layer, given that the channels are stored separately,
|
||||||
|
one after the other.
|
||||||
|
|
||||||
The tile descriptor is a 4-tuple with the following contents::
|
The tile descriptor is a 4-tuple with the following contents::
|
||||||
|
|
||||||
|
@ -325,42 +328,42 @@ The fields are used as follows:
|
||||||
Whether the first line in the image is the top line on the screen (1), or
|
Whether the first line in the image is the top line on the screen (1), or
|
||||||
the bottom line (-1). If omitted, the orientation defaults to 1.
|
the bottom line (-1). If omitted, the orientation defaults to 1.
|
||||||
|
|
||||||
.. _file-decoders:
|
.. _file-codecs:
|
||||||
|
|
||||||
Writing Your Own File Decoder in C
|
Writing Your Own File Codec in C
|
||||||
==================================
|
================================
|
||||||
|
|
||||||
There are 3 stages in a file decoder's lifetime:
|
There are 3 stages in a file codec's lifetime:
|
||||||
|
|
||||||
1. Setup: Pillow looks for a function in the decoder registry, falling
|
1. Setup: Pillow looks for a function in the decoder or encoder registry,
|
||||||
back to a function named ``[decodername]_decoder`` on the internal
|
falling back to a function named ``[codecname]_decoder`` or
|
||||||
core image object. That function is called with the ``args`` tuple
|
``[codecname]_encoder`` on the internal core image object. That function is
|
||||||
from the ``tile`` setup in the ``_open`` method.
|
called with the ``args`` tuple from the ``tile``.
|
||||||
|
|
||||||
2. Decoding: The decoder's decode function is repeatedly called with
|
2. Transforming: The codec's ``decode`` or ``encode`` function is repeatedly
|
||||||
chunks of image data.
|
called with chunks of image data.
|
||||||
|
|
||||||
3. Cleanup: If the decoder has registered a cleanup function, it will
|
3. Cleanup: If the codec has registered a cleanup function, it will
|
||||||
be called at the end of the decoding process, even if there was an
|
be called at the end of the transformation process, even if there was an
|
||||||
exception raised.
|
exception raised.
|
||||||
|
|
||||||
|
|
||||||
Setup
|
Setup
|
||||||
-----
|
-----
|
||||||
|
|
||||||
The current conventions are that the decoder setup function is named
|
The current conventions are that the codec setup function is named
|
||||||
``PyImaging_[Decodername]DecoderNew`` and defined in ``decode.c``. The
|
``PyImaging_[codecname]DecoderNew`` or ``PyImaging_[codecname]EncoderNew``
|
||||||
python binding for it is named ``[decodername]_decoder`` and is setup
|
and defined in ``decode.c`` or ``encode.c``. The Python binding for it is
|
||||||
from within the ``_imaging.c`` file in the codecs section of the
|
named ``[codecname]_decoder`` or ``[codecname]_encoder`` and is set up from
|
||||||
function array.
|
within the ``_imaging.c`` file in the codecs section of the function array.
|
||||||
|
|
||||||
The setup function needs to call ``PyImaging_DecoderNew`` and at the
|
The setup function needs to call ``PyImaging_DecoderNew`` or
|
||||||
very least, set the ``decode`` function pointer. The fields of
|
``PyImaging_EncoderNew`` and at the very least, set the ``decode`` or
|
||||||
interest in this object are:
|
``encode`` function pointer. The fields of interest in this object are:
|
||||||
|
|
||||||
**decode**
|
**decode**/**encode**
|
||||||
Function pointer to the decode function, which has access to
|
Function pointer to the decode or encode function, which has access to
|
||||||
``im``, ``state``, and the buffer of data to be added to the image.
|
``im``, ``state``, and the buffer of data to be transformed.
|
||||||
|
|
||||||
**cleanup**
|
**cleanup**
|
||||||
Function pointer to the cleanup function, has access to ``state``.
|
Function pointer to the cleanup function, has access to ``state``.
|
||||||
|
@ -370,36 +373,34 @@ interest in this object are:
|
||||||
|
|
||||||
**state**
|
**state**
|
||||||
An ImagingCodecStateInstance, will be set by Pillow. The ``context``
|
An ImagingCodecStateInstance, will be set by Pillow. The ``context``
|
||||||
member is an opaque struct that can be used by the decoder to store
|
member is an opaque struct that can be used by the codec to store
|
||||||
any format specific state or options.
|
any format specific state or options.
|
||||||
|
|
||||||
**pulls_fd**
|
**pulls_fd**/**pushes_fd**
|
||||||
**EXPERIMENTAL** -- **WARNING**, interface may change. If set to 1,
|
If the decoder has ``pulls_fd`` or the encoder has ``pushes_fd`` set to 1,
|
||||||
``state->fd`` will be a pointer to the Python file like object. The
|
``state->fd`` will be a pointer to the Python file like object. The codec may
|
||||||
decoder may use the functions in ``codec_fd.c`` to read directly
|
use the functions in ``codec_fd.c`` to read or write directly with the file
|
||||||
from the file like object rather than have the data pushed through a
|
like object rather than have the data pushed through a buffer.
|
||||||
buffer. Note that this implementation may be refactored until this
|
|
||||||
warning is removed.
|
|
||||||
|
|
||||||
.. versionadded:: 3.3.0
|
.. versionadded:: 3.3.0
|
||||||
|
|
||||||
|
|
||||||
Decoding
|
Transforming
|
||||||
--------
|
------------
|
||||||
|
|
||||||
The decode function is called with the target (core) image, the
|
The decode or encode function is called with the target (core) image, the codec
|
||||||
decoder state structure, and a buffer of data to be decoded.
|
state structure, and a buffer of data to be transformed.
|
||||||
|
|
||||||
**Experimental** -- If ``pulls_fd`` is set, then the decode function
|
It is the codec's responsibility to pull as much data as possible out of the
|
||||||
is called once, with an empty buffer. It is the decoder's
|
buffer and return the number of bytes consumed. The next call to the codec will
|
||||||
responsibility to decode the entire tile in that one call. The rest of
|
include the previous unconsumed tail. The codec function will be called
|
||||||
this section only applies if ``pulls_fd`` is not set.
|
multiple times as the data processed.
|
||||||
|
|
||||||
It is the decoder's responsibility to pull as much data as possible
|
Alternatively, if ``pulls_fd`` or ``pushes_fd`` is set, then the decode or
|
||||||
out of the buffer and return the number of bytes consumed. The next
|
encode function is called once, with an empty buffer. It is the codec's
|
||||||
call to the decoder will include the previous unconsumed tail. The
|
responsibility to transform the entire tile in that one call. Using this will
|
||||||
decoder function will be called multiple times as the data is read
|
provide a codec with more freedom, but that freedom may mean increased memory
|
||||||
from the file like object.
|
usage if the entire tile is held in memory at once by the codec.
|
||||||
|
|
||||||
If an error occurs, set ``state->errcode`` and return -1.
|
If an error occurs, set ``state->errcode`` and return -1.
|
||||||
|
|
||||||
|
@ -408,28 +409,49 @@ Return -1 on success, without setting the errcode.
|
||||||
Cleanup
|
Cleanup
|
||||||
-------
|
-------
|
||||||
|
|
||||||
The cleanup function is called after the decoder returns a negative
|
The cleanup function is called after the codec returns a negative
|
||||||
value, or if there is a read error from the file. This function should
|
value, or if there is an error. This function should free any allocated
|
||||||
free any allocated memory and release any resources from external
|
memory and release any resources from external libraries.
|
||||||
libraries.
|
|
||||||
|
|
||||||
.. _file-decoders-py:
|
.. _file-codecs-py:
|
||||||
|
|
||||||
Writing Your Own File Decoder in Python
|
Writing Your Own File Codec in Python
|
||||||
=======================================
|
=====================================
|
||||||
|
|
||||||
Python file decoders should derive from
|
Python file decoders and encoders should derive from
|
||||||
:py:class:`PIL.ImageFile.PyDecoder` and should at least override the
|
:py:class:`PIL.ImageFile.PyDecoder` and :py:class:`PIL.ImageFile.PyEncoder`
|
||||||
decode method. File decoders should be registered using
|
respectively, and should at least override the decode or encode method.
|
||||||
:py:meth:`PIL.Image.register_decoder`. As in the C implementation of
|
They should be registered using :py:meth:`PIL.Image.register_decoder` and
|
||||||
the file decoders, there are three stages in the lifetime of a
|
:py:meth:`PIL.Image.register_encoder`. As in the C implementation of
|
||||||
Python-based file decoder:
|
the file codecs, there are three stages in the lifetime of a
|
||||||
|
Python-based file codec:
|
||||||
|
|
||||||
1. Setup: Pillow looks for the decoder in the registry, then
|
1. Setup: Pillow looks for the codec in the decoder or encoder registry, then
|
||||||
instantiates the class.
|
instantiates the class.
|
||||||
|
|
||||||
2. Decoding: The decoder instance's ``decode`` method is repeatedly
|
2. Transforming: The instance's ``decode`` method is repeatedly called with
|
||||||
called with a buffer of data to be interpreted.
|
a buffer of data to be interpreted, or the ``encode`` method is repeatedly
|
||||||
|
called with the size of data to be output.
|
||||||
|
|
||||||
3. Cleanup: The decoder instance's ``cleanup`` method is called.
|
Alternatively, if the decoder's ``_pulls_fd`` property (or the encoder's
|
||||||
|
``_pushes_fd`` property) is set to ``True``, then ``decode`` and ``encode``
|
||||||
|
will only be called once. In the decoder, ``self.fd`` can be used to access
|
||||||
|
the file-like object. Using this will provide a codec with more freedom, but
|
||||||
|
that freedom may mean increased memory usage if entire file is held in
|
||||||
|
memory at once by the codec.
|
||||||
|
|
||||||
|
In ``decode``, once the data has been interpreted, ``set_as_raw`` can be
|
||||||
|
used to populate the image.
|
||||||
|
|
||||||
|
3. Cleanup: The instance's ``cleanup`` method is called once the transformation
|
||||||
|
is complete. This can be used to clean up any resources used by the codec.
|
||||||
|
|
||||||
|
If you set ``_pulls_fd`` or ``_pushes_fd`` to ``True`` however, then you
|
||||||
|
probably chose to perform any cleanup tasks at the end of ``decode`` or
|
||||||
|
``encode``.
|
||||||
|
|
||||||
|
For an example :py:class:`PIL.ImageFile.PyDecoder`, see `DdsImagePlugin
|
||||||
|
<https://github.com/python-pillow/Pillow/blob/main/docs/example/DdsImagePlugin.py>`_.
|
||||||
|
For a plugin that uses both :py:class:`PIL.ImageFile.PyDecoder` and
|
||||||
|
:py:class:`PIL.ImageFile.PyEncoder`, see `BlpImagePlugin
|
||||||
|
<https://github.com/python-pillow/Pillow/blob/main/src/PIL/BlpImagePlugin.py>`_
|
|
@ -465,6 +465,8 @@ These platforms are built and tested for every change.
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Fedora 35 | 3.10 | x86-64 |
|
| Fedora 35 | 3.10 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
|
| Gentoo | 3.9 | x86-64 |
|
||||||
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| macOS 10.15 Catalina | 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64 |
|
| macOS 10.15 Catalina | 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Ubuntu Linux 18.04 LTS (Bionic) | 3.9 | x86-64 |
|
| Ubuntu Linux 18.04 LTS (Bionic) | 3.9 | x86-64 |
|
||||||
|
|
|
@ -40,8 +40,16 @@ Classes
|
||||||
.. autoclass:: PIL.ImageFile.Parser()
|
.. autoclass:: PIL.ImageFile.Parser()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: PIL.ImageFile.PyCodec()
|
||||||
|
:members:
|
||||||
|
|
||||||
.. autoclass:: PIL.ImageFile.PyDecoder()
|
.. autoclass:: PIL.ImageFile.PyDecoder()
|
||||||
:members:
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. autoclass:: PIL.ImageFile.PyEncoder()
|
||||||
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
.. autoclass:: PIL.ImageFile.ImageFile()
|
.. autoclass:: PIL.ImageFile.ImageFile()
|
||||||
:member-order: bysource
|
:member-order: bysource
|
||||||
|
|
|
@ -23,6 +23,9 @@ All default viewers convert the image to be shown to PNG format.
|
||||||
.. autoclass:: PIL.ImageShow.EogViewer
|
.. autoclass:: PIL.ImageShow.EogViewer
|
||||||
.. autoclass:: PIL.ImageShow.XVViewer
|
.. autoclass:: PIL.ImageShow.XVViewer
|
||||||
|
|
||||||
|
To provide maximum functionality on Unix-based systems, temporary files created
|
||||||
|
from images will not be automatically removed by Pillow.
|
||||||
|
|
||||||
.. autofunction:: PIL.ImageShow.register
|
.. autofunction:: PIL.ImageShow.register
|
||||||
.. autoclass:: PIL.ImageShow.Viewer
|
.. autoclass:: PIL.ImageShow.Viewer
|
||||||
:member-order: bysource
|
:member-order: bysource
|
||||||
|
|
|
@ -14,6 +14,16 @@ for a region of an image.
|
||||||
statistics. You can also pass in a previously calculated histogram.
|
statistics. You can also pass in a previously calculated histogram.
|
||||||
|
|
||||||
:param image: A PIL image, or a precalculated histogram.
|
:param image: A PIL image, or a precalculated histogram.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
For a PIL image, calculations rely on the
|
||||||
|
:py:meth:`~PIL.Image.Image.histogram` method. The pixel counts are
|
||||||
|
grouped into 256 bins, even if the image has more than 8 bits per
|
||||||
|
channel. So ``I`` and ``F`` mode images have a maximum ``mean``,
|
||||||
|
``median`` and ``rms`` of 255, and cannot have an ``extrema`` maximum
|
||||||
|
of more than 255.
|
||||||
|
|
||||||
:param mask: An optional mask.
|
:param mask: An optional mask.
|
||||||
|
|
||||||
.. py:attribute:: extrema
|
.. py:attribute:: extrema
|
||||||
|
|
|
@ -6,7 +6,13 @@
|
||||||
The PixelAccess class provides read and write access to
|
The PixelAccess class provides read and write access to
|
||||||
:py:class:`PIL.Image` data at a pixel level.
|
:py:class:`PIL.Image` data at a pixel level.
|
||||||
|
|
||||||
.. note:: Accessing individual pixels is fairly slow. If you are looping over all of the pixels in an image, there is likely a faster way using other parts of the Pillow API.
|
.. note:: Accessing individual pixels is fairly slow. If you are
|
||||||
|
looping over all of the pixels in an image, there is likely
|
||||||
|
a faster way using other parts of the Pillow API.
|
||||||
|
|
||||||
|
:mod:`~PIL.Image`, :mod:`~PIL.ImageChops` and :mod:`~PIL.ImageOps`
|
||||||
|
have methods for many standard operations. If you wish to perform
|
||||||
|
a custom mapping, check out :py:meth:`~PIL.Image.Image.point`.
|
||||||
|
|
||||||
Example
|
Example
|
||||||
-------
|
-------
|
||||||
|
@ -39,7 +45,7 @@ Access using negative indexes is also possible.
|
||||||
|
|
||||||
|
|
||||||
:py:class:`PixelAccess` Class
|
:py:class:`PixelAccess` Class
|
||||||
-----------------------------------
|
-----------------------------
|
||||||
|
|
||||||
.. class:: PixelAccess
|
.. class:: PixelAccess
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,12 @@
|
||||||
The :py:mod:`~PIL.PyAccess` module provides a CFFI/Python implementation of the :ref:`PixelAccess`. This implementation is far faster on PyPy than the PixelAccess version.
|
The :py:mod:`~PIL.PyAccess` module provides a CFFI/Python implementation of the :ref:`PixelAccess`. This implementation is far faster on PyPy than the PixelAccess version.
|
||||||
|
|
||||||
.. note:: Accessing individual pixels is fairly slow. If you are
|
.. note:: Accessing individual pixels is fairly slow. If you are
|
||||||
looping over all of the pixels in an image, there is likely
|
looping over all of the pixels in an image, there is likely
|
||||||
a faster way using other parts of the Pillow API.
|
a faster way using other parts of the Pillow API.
|
||||||
|
|
||||||
|
:mod:`~PIL.Image`, :mod:`~PIL.ImageChops` and :mod:`~PIL.ImageOps`
|
||||||
|
have methods for many standard operations. If you wish to perform
|
||||||
|
a custom mapping, check out :py:meth:`~PIL.Image.Image.point`.
|
||||||
|
|
||||||
Example
|
Example
|
||||||
-------
|
-------
|
||||||
|
|
|
@ -53,7 +53,7 @@ Then ``sudo apt-get update && sudo apt-get install libtiff5-dbgsym``
|
||||||
|
|
||||||
virtualenv -p python3.8-dbg ~/vpy38-dbg
|
virtualenv -p python3.8-dbg ~/vpy38-dbg
|
||||||
source ~/vpy38-dbg/bin/activate
|
source ~/vpy38-dbg/bin/activate
|
||||||
cd ~/Pillow && pip install -r requirements.txt && make install
|
cd ~/Pillow && make install
|
||||||
|
|
||||||
Test Case
|
Test Case
|
||||||
---------
|
---------
|
||||||
|
|
|
@ -18,11 +18,37 @@ Rather than returning a ``SystemError``, passing the incorrect types of coordina
|
||||||
a path will now raise a more specific ``ValueError``, with the message "incorrect
|
a path will now raise a more specific ``ValueError``, with the message "incorrect
|
||||||
coordinate type".
|
coordinate type".
|
||||||
|
|
||||||
|
Replace requirements.txt with extras
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Rather than installing all dependencies for docs and tests via ``requirements.txt``,
|
||||||
|
``extras_require`` is used instead. This installs only those needed and at the same
|
||||||
|
time as installing Pillow.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
# Install with dependencies for tests:
|
||||||
|
python3 -m pip install .[tests]
|
||||||
|
|
||||||
|
# Or for building docs:
|
||||||
|
python3 -m pip install .[docs]
|
||||||
|
|
||||||
|
# Or for all:
|
||||||
|
python3 -m pip install .[docs,tests]
|
||||||
|
|
||||||
|
On macOS, the last argument may need to be wrapped in quotes, e.g.
|
||||||
|
``python3 -m pip install ".[tests]"``
|
||||||
|
|
||||||
|
Therefore ``requirements.txt`` has been removed along with the ``make install-req``
|
||||||
|
command for installing its contents.
|
||||||
|
|
||||||
Deprecations
|
Deprecations
|
||||||
^^^^^^^^^^^^
|
============
|
||||||
|
|
||||||
Constants
|
Constants
|
||||||
~~~~~~~~~
|
^^^^^^^^^
|
||||||
|
|
||||||
A number of constants have been deprecated and will be removed in Pillow 10.0.0
|
A number of constants have been deprecated and will be removed in Pillow 10.0.0
|
||||||
(2023-07-01). Instead, ``enum.IntEnum`` classes have been added.
|
(2023-07-01). Instead, ``enum.IntEnum`` classes have been added.
|
||||||
|
@ -87,7 +113,7 @@ Deprecated Use instead
|
||||||
===================================================== ============================================================
|
===================================================== ============================================================
|
||||||
|
|
||||||
ImageShow.Viewer.show_file file argument
|
ImageShow.Viewer.show_file file argument
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The ``file`` argument in :py:meth:`~PIL.ImageShow.Viewer.show_file()` has been
|
The ``file`` argument in :py:meth:`~PIL.ImageShow.Viewer.show_file()` has been
|
||||||
deprecated and will be removed in Pillow 10.0.0 (2023-07-01). It has been replaced by
|
deprecated and will be removed in Pillow 10.0.0 (2023-07-01). It has been replaced by
|
||||||
|
@ -98,7 +124,7 @@ In effect, ``viewer.show_file("test.jpg")`` will continue to work unchanged.
|
||||||
``viewer.show_file(path="test.jpg")`` instead.
|
``viewer.show_file(path="test.jpg")`` instead.
|
||||||
|
|
||||||
FitsStubImagePlugin
|
FitsStubImagePlugin
|
||||||
~~~~~~~~~~~~~~~~~~~
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
.. deprecated:: 9.1.0
|
.. deprecated:: 9.1.0
|
||||||
|
|
||||||
|
@ -127,6 +153,13 @@ By default, :py:meth:`~PIL.Image.Image.getpalette` returns RGB data from the pal
|
||||||
A ``rawmode`` argument has been added, to allow the mode to be chosen instead. ``None``
|
A ``rawmode`` argument has been added, to allow the mode to be chosen instead. ``None``
|
||||||
can be used to return data in the current mode of the palette.
|
can be used to return data in the current mode of the palette.
|
||||||
|
|
||||||
|
Added PyEncoder
|
||||||
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
:py:class:`~PIL.ImageFile.PyEncoder` has been added, allowing for file encoders to be
|
||||||
|
written in Python. See :ref:`Writing Your Own File Codec in Python<file-codecs-py>` for
|
||||||
|
more information.
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
@ -143,3 +176,10 @@ Image._repr_pretty_
|
||||||
``im._repr_pretty_`` has been added to provide a representation of an image without the
|
``im._repr_pretty_`` has been added to provide a representation of an image without the
|
||||||
identity of the object. This allows Jupyter to describe an image and have that
|
identity of the object. This allows Jupyter to describe an image and have that
|
||||||
description stay the same on subsequent executions of the same code.
|
description stay the same on subsequent executions of the same code.
|
||||||
|
|
||||||
|
Added BLP saving
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Support has been added for saving BLP images. ``blp_version`` can be used to specify
|
||||||
|
whether the image should be saved as BLP1 or BLP2, e.g.
|
||||||
|
``im.save("out.blp", blp_version="BLP1")``. By default, BLP2 will be used.
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Development, documentation & testing requirements.
|
|
||||||
black
|
|
||||||
check-manifest
|
|
||||||
coverage
|
|
||||||
defusedxml
|
|
||||||
markdown2
|
|
||||||
olefile
|
|
||||||
packaging
|
|
||||||
pyroma
|
|
||||||
pytest
|
|
||||||
pytest-cov
|
|
||||||
pytest-timeout
|
|
||||||
sphinx>=2.4
|
|
||||||
sphinx-copybutton
|
|
||||||
sphinx-issues>=3.0.1
|
|
||||||
sphinx-removed-in
|
|
||||||
sphinx-rtd-theme>=1.0
|
|
||||||
sphinxext-opengraph
|
|
21
setup.cfg
21
setup.cfg
|
@ -35,6 +35,27 @@ project_urls =
|
||||||
[options]
|
[options]
|
||||||
python_requires = >=3.7
|
python_requires = >=3.7
|
||||||
|
|
||||||
|
[options.extras_require]
|
||||||
|
docs =
|
||||||
|
olefile
|
||||||
|
sphinx>=2.4
|
||||||
|
sphinx-copybutton
|
||||||
|
sphinx-issues>=3.0.1
|
||||||
|
sphinx-removed-in
|
||||||
|
sphinx-rtd-theme>=1.0
|
||||||
|
sphinxext-opengraph
|
||||||
|
tests =
|
||||||
|
check-manifest
|
||||||
|
coverage
|
||||||
|
defusedxml
|
||||||
|
markdown2
|
||||||
|
olefile
|
||||||
|
packaging
|
||||||
|
pyroma
|
||||||
|
pytest
|
||||||
|
pytest-cov
|
||||||
|
pytest-timeout
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
extend-ignore = E203
|
extend-ignore = E203
|
||||||
max-line-length = 88
|
max-line-length = 88
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -167,7 +167,7 @@ def _find_library_dirs_ldconfig():
|
||||||
# Assuming GLIBC's ldconfig (with option -p)
|
# Assuming GLIBC's ldconfig (with option -p)
|
||||||
# Alpine Linux uses musl that can't print cache
|
# Alpine Linux uses musl that can't print cache
|
||||||
args = ["/sbin/ldconfig", "-p"]
|
args = ["/sbin/ldconfig", "-p"]
|
||||||
expr = fr".*\({abi_type}.*\) => (.*)"
|
expr = rf".*\({abi_type}.*\) => (.*)"
|
||||||
env = dict(os.environ)
|
env = dict(os.environ)
|
||||||
env["LC_ALL"] = "C"
|
env["LC_ALL"] = "C"
|
||||||
env["LANG"] = "C"
|
env["LANG"] = "C"
|
||||||
|
|
|
@ -29,6 +29,7 @@ BLP files come in many different flavours:
|
||||||
- DXT5 compression is used if alpha_encoding == 7.
|
- DXT5 compression is used if alpha_encoding == 7.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
import struct
|
import struct
|
||||||
import warnings
|
import warnings
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
|
@ -266,6 +267,10 @@ class BLPFormatError(NotImplementedError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _accept(prefix):
|
||||||
|
return prefix[:4] in (b"BLP1", b"BLP2")
|
||||||
|
|
||||||
|
|
||||||
class BlpImageFile(ImageFile.ImageFile):
|
class BlpImageFile(ImageFile.ImageFile):
|
||||||
"""
|
"""
|
||||||
Blizzard Mipmap Format
|
Blizzard Mipmap Format
|
||||||
|
@ -276,50 +281,51 @@ class BlpImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
self.magic = self.fp.read(4)
|
self.magic = self.fp.read(4)
|
||||||
self._read_blp_header()
|
|
||||||
|
|
||||||
if self.magic == b"BLP1":
|
self.fp.seek(5, os.SEEK_CUR)
|
||||||
decoder = "BLP1"
|
(self._blp_alpha_depth,) = struct.unpack("<b", self.fp.read(1))
|
||||||
self.mode = "RGB"
|
|
||||||
elif self.magic == b"BLP2":
|
self.fp.seek(2, os.SEEK_CUR)
|
||||||
decoder = "BLP2"
|
self._size = struct.unpack("<II", self.fp.read(8))
|
||||||
self.mode = "RGBA" if self._blp_alpha_depth else "RGB"
|
|
||||||
|
if self.magic in (b"BLP1", b"BLP2"):
|
||||||
|
decoder = self.magic.decode()
|
||||||
else:
|
else:
|
||||||
raise BLPFormatError(f"Bad BLP magic {repr(self.magic)}")
|
raise BLPFormatError(f"Bad BLP magic {repr(self.magic)}")
|
||||||
|
|
||||||
|
self.mode = "RGBA" if self._blp_alpha_depth else "RGB"
|
||||||
self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
|
self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
|
||||||
|
|
||||||
def _read_blp_header(self):
|
|
||||||
(self._blp_compression,) = struct.unpack("<i", self.fp.read(4))
|
|
||||||
|
|
||||||
(self._blp_encoding,) = struct.unpack("<b", self.fp.read(1))
|
|
||||||
(self._blp_alpha_depth,) = struct.unpack("<b", self.fp.read(1))
|
|
||||||
(self._blp_alpha_encoding,) = struct.unpack("<b", self.fp.read(1))
|
|
||||||
(self._blp_mips,) = struct.unpack("<b", self.fp.read(1))
|
|
||||||
|
|
||||||
self._size = struct.unpack("<II", self.fp.read(8))
|
|
||||||
|
|
||||||
if self.magic == b"BLP1":
|
|
||||||
# Only present for BLP1
|
|
||||||
(self._blp_encoding,) = struct.unpack("<i", self.fp.read(4))
|
|
||||||
(self._blp_subtype,) = struct.unpack("<i", self.fp.read(4))
|
|
||||||
|
|
||||||
self._blp_offsets = struct.unpack("<16I", self.fp.read(16 * 4))
|
|
||||||
self._blp_lengths = struct.unpack("<16I", self.fp.read(16 * 4))
|
|
||||||
|
|
||||||
|
|
||||||
class _BLPBaseDecoder(ImageFile.PyDecoder):
|
class _BLPBaseDecoder(ImageFile.PyDecoder):
|
||||||
_pulls_fd = True
|
_pulls_fd = True
|
||||||
|
|
||||||
def decode(self, buffer):
|
def decode(self, buffer):
|
||||||
try:
|
try:
|
||||||
self.fd.seek(0)
|
|
||||||
self.magic = self.fd.read(4)
|
|
||||||
self._read_blp_header()
|
self._read_blp_header()
|
||||||
self._load()
|
self._load()
|
||||||
except struct.error as e:
|
except struct.error as e:
|
||||||
raise OSError("Truncated Blp file") from e
|
raise OSError("Truncated BLP file") from e
|
||||||
return 0, 0
|
return -1, 0
|
||||||
|
|
||||||
|
def _read_blp_header(self):
|
||||||
|
self.fd.seek(4)
|
||||||
|
(self._blp_compression,) = struct.unpack("<i", self._safe_read(4))
|
||||||
|
|
||||||
|
(self._blp_encoding,) = struct.unpack("<b", self._safe_read(1))
|
||||||
|
(self._blp_alpha_depth,) = struct.unpack("<b", self._safe_read(1))
|
||||||
|
(self._blp_alpha_encoding,) = struct.unpack("<b", self._safe_read(1))
|
||||||
|
self.fd.seek(1, os.SEEK_CUR) # mips
|
||||||
|
|
||||||
|
self.size = struct.unpack("<II", self._safe_read(8))
|
||||||
|
|
||||||
|
if isinstance(self, BLP1Decoder):
|
||||||
|
# Only present for BLP1
|
||||||
|
(self._blp_encoding,) = struct.unpack("<i", self._safe_read(4))
|
||||||
|
self.fd.seek(4, os.SEEK_CUR) # subtype
|
||||||
|
|
||||||
|
self._blp_offsets = struct.unpack("<16I", self._safe_read(16 * 4))
|
||||||
|
self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4))
|
||||||
|
|
||||||
def _safe_read(self, length):
|
def _safe_read(self, length):
|
||||||
return ImageFile._safe_read(self.fd, length)
|
return ImageFile._safe_read(self.fd, length)
|
||||||
|
@ -334,23 +340,20 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
|
||||||
ret.append((b, g, r, a))
|
ret.append((b, g, r, a))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def _read_blp_header(self):
|
def _read_bgra(self, palette):
|
||||||
(self._blp_compression,) = struct.unpack("<i", self._safe_read(4))
|
data = bytearray()
|
||||||
|
_data = BytesIO(self._safe_read(self._blp_lengths[0]))
|
||||||
(self._blp_encoding,) = struct.unpack("<b", self._safe_read(1))
|
while True:
|
||||||
(self._blp_alpha_depth,) = struct.unpack("<b", self._safe_read(1))
|
try:
|
||||||
(self._blp_alpha_encoding,) = struct.unpack("<b", self._safe_read(1))
|
(offset,) = struct.unpack("<B", _data.read(1))
|
||||||
(self._blp_mips,) = struct.unpack("<b", self._safe_read(1))
|
except struct.error:
|
||||||
|
break
|
||||||
self.size = struct.unpack("<II", self._safe_read(8))
|
b, g, r, a = palette[offset]
|
||||||
|
d = (r, g, b)
|
||||||
if self.magic == b"BLP1":
|
if self._blp_alpha_depth:
|
||||||
# Only present for BLP1
|
d += (a,)
|
||||||
(self._blp_encoding,) = struct.unpack("<i", self._safe_read(4))
|
data.extend(d)
|
||||||
(self._blp_subtype,) = struct.unpack("<i", self._safe_read(4))
|
return data
|
||||||
|
|
||||||
self._blp_offsets = struct.unpack("<16I", self._safe_read(16 * 4))
|
|
||||||
self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4))
|
|
||||||
|
|
||||||
|
|
||||||
class BLP1Decoder(_BLPBaseDecoder):
|
class BLP1Decoder(_BLPBaseDecoder):
|
||||||
|
@ -360,17 +363,8 @@ class BLP1Decoder(_BLPBaseDecoder):
|
||||||
|
|
||||||
elif self._blp_compression == 1:
|
elif self._blp_compression == 1:
|
||||||
if self._blp_encoding in (4, 5):
|
if self._blp_encoding in (4, 5):
|
||||||
data = bytearray()
|
|
||||||
palette = self._read_palette()
|
palette = self._read_palette()
|
||||||
_data = BytesIO(self._safe_read(self._blp_lengths[0]))
|
data = self._read_bgra(palette)
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
(offset,) = struct.unpack("<B", _data.read(1))
|
|
||||||
except struct.error:
|
|
||||||
break
|
|
||||||
b, g, r, a = palette[offset]
|
|
||||||
data.extend([r, g, b])
|
|
||||||
|
|
||||||
self.set_as_raw(bytes(data))
|
self.set_as_raw(bytes(data))
|
||||||
else:
|
else:
|
||||||
raise BLPFormatError(
|
raise BLPFormatError(
|
||||||
|
@ -401,23 +395,16 @@ class BLP2Decoder(_BLPBaseDecoder):
|
||||||
def _load(self):
|
def _load(self):
|
||||||
palette = self._read_palette()
|
palette = self._read_palette()
|
||||||
|
|
||||||
data = bytearray()
|
|
||||||
self.fd.seek(self._blp_offsets[0])
|
self.fd.seek(self._blp_offsets[0])
|
||||||
|
|
||||||
if self._blp_compression == 1:
|
if self._blp_compression == 1:
|
||||||
# Uncompressed or DirectX compression
|
# Uncompressed or DirectX compression
|
||||||
|
|
||||||
if self._blp_encoding == Encoding.UNCOMPRESSED:
|
if self._blp_encoding == Encoding.UNCOMPRESSED:
|
||||||
_data = BytesIO(self._safe_read(self._blp_lengths[0]))
|
data = self._read_bgra(palette)
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
(offset,) = struct.unpack("<B", _data.read(1))
|
|
||||||
except struct.error:
|
|
||||||
break
|
|
||||||
b, g, r, a = palette[offset]
|
|
||||||
data.extend((r, g, b))
|
|
||||||
|
|
||||||
elif self._blp_encoding == Encoding.DXT:
|
elif self._blp_encoding == Encoding.DXT:
|
||||||
|
data = bytearray()
|
||||||
if self._blp_alpha_encoding == AlphaEncoding.DXT1:
|
if self._blp_alpha_encoding == AlphaEncoding.DXT1:
|
||||||
linesize = (self.size[0] + 3) // 4 * 8
|
linesize = (self.size[0] + 3) // 4 * 8
|
||||||
for yb in range((self.size[1] + 3) // 4):
|
for yb in range((self.size[1] + 3) // 4):
|
||||||
|
@ -452,12 +439,59 @@ class BLP2Decoder(_BLPBaseDecoder):
|
||||||
self.set_as_raw(bytes(data))
|
self.set_as_raw(bytes(data))
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
class BLPEncoder(ImageFile.PyEncoder):
|
||||||
return prefix[:4] in (b"BLP1", b"BLP2")
|
_pushes_fd = True
|
||||||
|
|
||||||
|
def _write_palette(self):
|
||||||
|
data = b""
|
||||||
|
palette = self.im.getpalette("RGBA", "RGBA")
|
||||||
|
for i in range(256):
|
||||||
|
r, g, b, a = palette[i * 4 : (i + 1) * 4]
|
||||||
|
data += struct.pack("<4B", b, g, r, a)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def encode(self, bufsize):
|
||||||
|
palette_data = self._write_palette()
|
||||||
|
|
||||||
|
offset = 20 + 16 * 4 * 2 + len(palette_data)
|
||||||
|
data = struct.pack("<16I", offset, *((0,) * 15))
|
||||||
|
|
||||||
|
w, h = self.im.size
|
||||||
|
data += struct.pack("<16I", w * h, *((0,) * 15))
|
||||||
|
|
||||||
|
data += palette_data
|
||||||
|
|
||||||
|
for y in range(h):
|
||||||
|
for x in range(w):
|
||||||
|
data += struct.pack("<B", self.im.getpixel((x, y)))
|
||||||
|
|
||||||
|
return len(data), 0, data
|
||||||
|
|
||||||
|
|
||||||
|
def _save(im, fp, filename, save_all=False):
|
||||||
|
if im.mode != "P":
|
||||||
|
raise ValueError("Unsupported BLP image mode")
|
||||||
|
|
||||||
|
magic = b"BLP1" if im.encoderinfo.get("blp_version") == "BLP1" else b"BLP2"
|
||||||
|
fp.write(magic)
|
||||||
|
|
||||||
|
fp.write(struct.pack("<i", 1)) # Uncompressed or DirectX compression
|
||||||
|
fp.write(struct.pack("<b", Encoding.UNCOMPRESSED))
|
||||||
|
fp.write(struct.pack("<b", 1 if im.palette.mode == "RGBA" else 0))
|
||||||
|
fp.write(struct.pack("<b", 0)) # alpha encoding
|
||||||
|
fp.write(struct.pack("<b", 0)) # mips
|
||||||
|
fp.write(struct.pack("<II", *im.size))
|
||||||
|
if magic == b"BLP1":
|
||||||
|
fp.write(struct.pack("<i", 5))
|
||||||
|
fp.write(struct.pack("<i", 0))
|
||||||
|
|
||||||
|
ImageFile._save(im, fp, [("BLP", (0, 0) + im.size, 0, im.mode)])
|
||||||
|
|
||||||
|
|
||||||
Image.register_open(BlpImageFile.format, BlpImageFile, _accept)
|
Image.register_open(BlpImageFile.format, BlpImageFile, _accept)
|
||||||
Image.register_extension(BlpImageFile.format, ".blp")
|
Image.register_extension(BlpImageFile.format, ".blp")
|
||||||
|
|
||||||
Image.register_decoder("BLP1", BLP1Decoder)
|
Image.register_decoder("BLP1", BLP1Decoder)
|
||||||
Image.register_decoder("BLP2", BLP2Decoder)
|
Image.register_decoder("BLP2", BLP2Decoder)
|
||||||
|
|
||||||
|
Image.register_save(BlpImageFile.format, _save)
|
||||||
|
Image.register_encoder("BLP", BLPEncoder)
|
||||||
|
|
|
@ -102,7 +102,7 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
file_info["height"] = (
|
file_info["height"] = (
|
||||||
i32(header_data, 4)
|
i32(header_data, 4)
|
||||||
if not file_info["y_flip"]
|
if not file_info["y_flip"]
|
||||||
else 2 ** 32 - i32(header_data, 4)
|
else 2**32 - i32(header_data, 4)
|
||||||
)
|
)
|
||||||
file_info["planes"] = i16(header_data, 8)
|
file_info["planes"] = i16(header_data, 8)
|
||||||
file_info["bits"] = i16(header_data, 10)
|
file_info["bits"] = i16(header_data, 10)
|
||||||
|
@ -322,7 +322,7 @@ def _save(im, fp, filename, bitmap_header=True):
|
||||||
if bitmap_header:
|
if bitmap_header:
|
||||||
offset = 14 + header + colors * 4
|
offset = 14 + header + colors * 4
|
||||||
file_size = offset + image
|
file_size = offset + image
|
||||||
if file_size > 2 ** 32 - 1:
|
if file_size > 2**32 - 1:
|
||||||
raise ValueError("File size is too large for the BMP format")
|
raise ValueError("File size is too large for the BMP format")
|
||||||
fp.write(
|
fp.write(
|
||||||
b"BM" # file type (magic)
|
b"BM" # file type (magic)
|
||||||
|
|
|
@ -111,7 +111,9 @@ class DdsImageFile(ImageFile.ImageFile):
|
||||||
format_description = "DirectDraw Surface"
|
format_description = "DirectDraw Surface"
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
magic, header_size = struct.unpack("<II", self.fp.read(8))
|
if not _accept(self.fp.read(4)):
|
||||||
|
raise SyntaxError("not a DDS file")
|
||||||
|
(header_size,) = struct.unpack("<I", self.fp.read(4))
|
||||||
if header_size != 124:
|
if header_size != 124:
|
||||||
raise OSError(f"Unsupported header size {repr(header_size)}")
|
raise OSError(f"Unsupported header size {repr(header_size)}")
|
||||||
header_bytes = self.fp.read(header_size - 4)
|
header_bytes = self.fp.read(header_size - 4)
|
||||||
|
|
|
@ -26,7 +26,11 @@ from ._binary import o8
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return len(prefix) >= 6 and i16(prefix, 4) in [0xAF11, 0xAF12]
|
return (
|
||||||
|
len(prefix) >= 6
|
||||||
|
and i16(prefix, 4) in [0xAF11, 0xAF12]
|
||||||
|
and i16(prefix, 14) in [0, 3] # flags
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -44,11 +48,7 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
# HEAD
|
# HEAD
|
||||||
s = self.fp.read(128)
|
s = self.fp.read(128)
|
||||||
if not (
|
if not (_accept(s) and s[20:22] == b"\x00\x00"):
|
||||||
_accept(s)
|
|
||||||
and i16(s, 14) in [0, 3] # flags
|
|
||||||
and s[20:22] == b"\x00\x00" # reserved
|
|
||||||
):
|
|
||||||
raise SyntaxError("not an FLI/FLC file")
|
raise SyntaxError("not an FLI/FLC file")
|
||||||
|
|
||||||
# frames
|
# frames
|
||||||
|
|
|
@ -94,7 +94,8 @@ class FtexImageFile(ImageFile.ImageFile):
|
||||||
format_description = "Texture File Format (IW2:EOC)"
|
format_description = "Texture File Format (IW2:EOC)"
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
struct.unpack("<I", self.fp.read(4)) # magic
|
if not _accept(self.fp.read(4)):
|
||||||
|
raise SyntaxError("not an FTEX file")
|
||||||
struct.unpack("<i", self.fp.read(4)) # version
|
struct.unpack("<i", self.fp.read(4)) # version
|
||||||
self._size = struct.unpack("<2i", self.fp.read(8))
|
self._size = struct.unpack("<2i", self.fp.read(8))
|
||||||
mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8))
|
mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8))
|
||||||
|
|
|
@ -43,9 +43,9 @@ class GbrImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
header_size = i32(self.fp.read(4))
|
header_size = i32(self.fp.read(4))
|
||||||
version = i32(self.fp.read(4))
|
|
||||||
if header_size < 20:
|
if header_size < 20:
|
||||||
raise SyntaxError("not a GIMP brush")
|
raise SyntaxError("not a GIMP brush")
|
||||||
|
version = i32(self.fp.read(4))
|
||||||
if version not in (1, 2):
|
if version not in (1, 2):
|
||||||
raise SyntaxError(f"Unsupported GIMP brush version: {version}")
|
raise SyntaxError(f"Unsupported GIMP brush version: {version}")
|
||||||
|
|
||||||
|
|
|
@ -175,9 +175,15 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
if self.__frame == 1:
|
if self.__frame == 1:
|
||||||
self.pyaccess = None
|
self.pyaccess = None
|
||||||
if "transparency" in self.info:
|
if "transparency" in self.info:
|
||||||
self.mode = "RGBA"
|
if self.mode == "P":
|
||||||
self.im.putpalettealpha(self.info["transparency"], 0)
|
self.im.putpalettealpha(self.info["transparency"], 0)
|
||||||
self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG)
|
self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG)
|
||||||
|
self.mode = "RGBA"
|
||||||
|
else:
|
||||||
|
self.im = self.im.convert_transparent(
|
||||||
|
"LA", self.info["transparency"]
|
||||||
|
)
|
||||||
|
self.mode = "LA"
|
||||||
|
|
||||||
del self.info["transparency"]
|
del self.info["transparency"]
|
||||||
else:
|
else:
|
||||||
|
@ -315,7 +321,7 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
self.dispose = Image.core.fill(dispose_mode, dispose_size, color)
|
self.dispose = Image.core.fill(dispose_mode, dispose_size, color)
|
||||||
else:
|
else:
|
||||||
# replace with previous contents
|
# replace with previous contents
|
||||||
if self.im:
|
if self.im is not None:
|
||||||
# only dispose the extent in this frame
|
# only dispose the extent in this frame
|
||||||
self.dispose = self._crop(self.im, self.dispose_extent)
|
self.dispose = self._crop(self.im, self.dispose_extent)
|
||||||
elif frame_transparency is not None:
|
elif frame_transparency is not None:
|
||||||
|
@ -382,15 +388,18 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
if self.__frame == 0:
|
if self.__frame == 0:
|
||||||
return
|
return
|
||||||
if self._frame_transparency is not None:
|
if self._frame_transparency is not None:
|
||||||
self.im.putpalettealpha(self._frame_transparency, 0)
|
if self.mode == "P":
|
||||||
frame_im = self.im.convert("RGBA")
|
self.im.putpalettealpha(self._frame_transparency, 0)
|
||||||
|
frame_im = self.im.convert("RGBA")
|
||||||
|
else:
|
||||||
|
frame_im = self.im.convert_transparent("LA", self._frame_transparency)
|
||||||
else:
|
else:
|
||||||
frame_im = self.im.convert("RGB")
|
frame_im = self.im.convert("RGB")
|
||||||
frame_im = self._crop(frame_im, self.dispose_extent)
|
frame_im = self._crop(frame_im, self.dispose_extent)
|
||||||
|
|
||||||
self.im = self._prev_im
|
self.im = self._prev_im
|
||||||
self.mode = self.im.mode
|
self.mode = self.im.mode
|
||||||
if frame_im.mode == "RGBA":
|
if frame_im.mode in ("LA", "RGBA"):
|
||||||
self.im.paste(frame_im, self.dispose_extent, frame_im)
|
self.im.paste(frame_im, self.dispose_extent, frame_im)
|
||||||
else:
|
else:
|
||||||
self.im.paste(frame_im, self.dispose_extent)
|
self.im.paste(frame_im, self.dispose_extent)
|
||||||
|
|
|
@ -38,7 +38,7 @@ class GimpPaletteFile:
|
||||||
break
|
break
|
||||||
|
|
||||||
# skip fields and comment lines
|
# skip fields and comment lines
|
||||||
if re.match(br"\w+:|#", s):
|
if re.match(rb"\w+:|#", s):
|
||||||
continue
|
continue
|
||||||
if len(s) > 100:
|
if len(s) > 100:
|
||||||
raise SyntaxError("bad palette file")
|
raise SyntaxError("bad palette file")
|
||||||
|
|
|
@ -167,7 +167,7 @@ class IcnsFile:
|
||||||
self.dct = dct = {}
|
self.dct = dct = {}
|
||||||
self.fobj = fobj
|
self.fobj = fobj
|
||||||
sig, filesize = nextheader(fobj)
|
sig, filesize = nextheader(fobj)
|
||||||
if sig != MAGIC:
|
if not _accept(sig):
|
||||||
raise SyntaxError("not an icns file")
|
raise SyntaxError("not an icns file")
|
||||||
i = HEADERSIZE
|
i = HEADERSIZE
|
||||||
while i < filesize:
|
while i < filesize:
|
||||||
|
@ -287,7 +287,7 @@ class IcnsImageFile(ImageFile.ImageFile):
|
||||||
)
|
)
|
||||||
|
|
||||||
px = Image.Image.load(self)
|
px = Image.Image.load(self)
|
||||||
if self.im and self.im.size == self.size:
|
if self.im is not None and self.im.size == self.size:
|
||||||
# Already loaded
|
# Already loaded
|
||||||
return px
|
return px
|
||||||
self.load_prepare()
|
self.load_prepare()
|
||||||
|
|
|
@ -304,7 +304,7 @@ class IcoImageFile(ImageFile.ImageFile):
|
||||||
self._size = value
|
self._size = value
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
if self.im and self.im.size == self.size:
|
if self.im is not None and self.im.size == self.size:
|
||||||
# Already loaded
|
# Already loaded
|
||||||
return Image.Image.load(self)
|
return Image.Image.load(self)
|
||||||
im = self.ico.getimage(self.size)
|
im = self.ico.getimage(self.size)
|
||||||
|
|
|
@ -100,7 +100,7 @@ for i in range(2, 33):
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Read IM directory
|
# Read IM directory
|
||||||
|
|
||||||
split = re.compile(br"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$")
|
split = re.compile(rb"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$")
|
||||||
|
|
||||||
|
|
||||||
def number(s):
|
def number(s):
|
||||||
|
|
|
@ -847,7 +847,7 @@ class Image:
|
||||||
:returns: An image access object.
|
:returns: An image access object.
|
||||||
:rtype: :ref:`PixelAccess` or :py:class:`PIL.PyAccess`
|
:rtype: :ref:`PixelAccess` or :py:class:`PIL.PyAccess`
|
||||||
"""
|
"""
|
||||||
if self.im and self.palette and self.palette.dirty:
|
if self.im is not None and self.palette and self.palette.dirty:
|
||||||
# realize palette
|
# realize palette
|
||||||
mode, arr = self.palette.getdata()
|
mode, arr = self.palette.getdata()
|
||||||
self.im.putpalette(mode, arr)
|
self.im.putpalette(mode, arr)
|
||||||
|
@ -864,7 +864,7 @@ class Image:
|
||||||
self.palette.mode = palette_mode
|
self.palette.mode = palette_mode
|
||||||
self.palette.palette = self.im.getpalette(palette_mode, palette_mode)
|
self.palette.palette = self.im.getpalette(palette_mode, palette_mode)
|
||||||
|
|
||||||
if self.im:
|
if self.im is not None:
|
||||||
if cffi and USE_CFFI_ACCESS:
|
if cffi and USE_CFFI_ACCESS:
|
||||||
if self.pyaccess:
|
if self.pyaccess:
|
||||||
return self.pyaccess
|
return self.pyaccess
|
||||||
|
@ -975,7 +975,9 @@ class Image:
|
||||||
delete_trns = False
|
delete_trns = False
|
||||||
# transparency handling
|
# transparency handling
|
||||||
if has_transparency:
|
if has_transparency:
|
||||||
if self.mode in ("1", "L", "I", "RGB") and mode == "RGBA":
|
if (self.mode in ("1", "L", "I") and mode in ("LA", "RGBA")) or (
|
||||||
|
self.mode == "RGB" and mode == "RGBA"
|
||||||
|
):
|
||||||
# Use transparent conversion to promote from transparent
|
# Use transparent conversion to promote from transparent
|
||||||
# color to an alpha channel.
|
# color to an alpha channel.
|
||||||
new_im = self._new(
|
new_im = self._new(
|
||||||
|
@ -1492,11 +1494,12 @@ class Image:
|
||||||
|
|
||||||
def histogram(self, mask=None, extrema=None):
|
def histogram(self, mask=None, extrema=None):
|
||||||
"""
|
"""
|
||||||
Returns a histogram for the image. The histogram is returned as
|
Returns a histogram for the image. The histogram is returned as a
|
||||||
a list of pixel counts, one for each pixel value in the source
|
list of pixel counts, one for each pixel value in the source
|
||||||
image. If the image has more than one band, the histograms for
|
image. Counts are grouped into 256 bins for each band, even if
|
||||||
all bands are concatenated (for example, the histogram for an
|
the image has more than 8 bits per band. If the image has more
|
||||||
"RGB" image contains 768 values).
|
than one band, the histograms for all bands are concatenated (for
|
||||||
|
example, the histogram for an "RGB" image contains 768 values).
|
||||||
|
|
||||||
A bilevel image (mode "1") is treated as a greyscale ("L") image
|
A bilevel image (mode "1") is treated as a greyscale ("L") image
|
||||||
by this method.
|
by this method.
|
||||||
|
@ -1564,8 +1567,8 @@ class Image:
|
||||||
also use color strings as supported by the ImageColor module.
|
also use color strings as supported by the ImageColor module.
|
||||||
|
|
||||||
If a mask is given, this method updates only the regions
|
If a mask is given, this method updates only the regions
|
||||||
indicated by the mask. You can use either "1", "L" or "RGBA"
|
indicated by the mask. You can use either "1", "L", "LA", "RGBA"
|
||||||
images (in the latter case, the alpha band is used as mask).
|
or "RGBa" images (if present, the alpha band is used as mask).
|
||||||
Where the mask is 255, the given image is copied as is. Where
|
Where the mask is 255, the given image is copied as is. Where
|
||||||
the mask is 0, the current value is preserved. Intermediate
|
the mask is 0, the current value is preserved. Intermediate
|
||||||
values will mix the two images together, including their alpha
|
values will mix the two images together, including their alpha
|
||||||
|
@ -1613,7 +1616,7 @@ class Image:
|
||||||
elif isImageType(im):
|
elif isImageType(im):
|
||||||
im.load()
|
im.load()
|
||||||
if self.mode != im.mode:
|
if self.mode != im.mode:
|
||||||
if self.mode != "RGB" or im.mode not in ("RGBA", "RGBa"):
|
if self.mode != "RGB" or im.mode not in ("LA", "RGBA", "RGBa"):
|
||||||
# should use an adapter for this!
|
# should use an adapter for this!
|
||||||
im = im.convert(self.mode)
|
im = im.convert(self.mode)
|
||||||
im = im.im
|
im = im.im
|
||||||
|
@ -2779,9 +2782,9 @@ def frombytes(mode, size, data, decoder_name="raw", *args):
|
||||||
In its simplest form, this function takes three arguments
|
In its simplest form, this function takes three arguments
|
||||||
(mode, size, and unpacked pixel data).
|
(mode, size, and unpacked pixel data).
|
||||||
|
|
||||||
You can also use any pixel decoder supported by PIL. For more
|
You can also use any pixel decoder supported by PIL. For more
|
||||||
information on available decoders, see the section
|
information on available decoders, see the section
|
||||||
:ref:`Writing Your Own File Decoder <file-decoders>`.
|
:ref:`Writing Your Own File Codec <file-codecs>`.
|
||||||
|
|
||||||
Note that this function decodes pixel data only, not entire images.
|
Note that this function decodes pixel data only, not entire images.
|
||||||
If you have an entire image in a string, wrap it in a
|
If you have an entire image in a string, wrap it in a
|
||||||
|
|
|
@ -49,7 +49,11 @@ ERRORS = {
|
||||||
-8: "bad configuration",
|
-8: "bad configuration",
|
||||||
-9: "out of memory error",
|
-9: "out of memory error",
|
||||||
}
|
}
|
||||||
"""Dict of known error codes returned from :meth:`.PyDecoder.decode`."""
|
"""
|
||||||
|
Dict of known error codes returned from :meth:`.PyDecoder.decode`,
|
||||||
|
:meth:`.PyEncoder.encode` :meth:`.PyEncoder.encode_to_pyfd` and
|
||||||
|
:meth:`.PyEncoder.encode_to_file`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -219,15 +223,15 @@ class ImageFile(Image.Image):
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
for decoder_name, extents, offset, args in self.tile:
|
for decoder_name, extents, offset, args in self.tile:
|
||||||
|
seek(offset)
|
||||||
decoder = Image._getdecoder(
|
decoder = Image._getdecoder(
|
||||||
self.mode, decoder_name, args, self.decoderconfig
|
self.mode, decoder_name, args, self.decoderconfig
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
seek(offset)
|
|
||||||
decoder.setimage(self.im, extents)
|
decoder.setimage(self.im, extents)
|
||||||
if decoder.pulls_fd:
|
if decoder.pulls_fd:
|
||||||
decoder.setfd(self.fp)
|
decoder.setfd(self.fp)
|
||||||
status, err_code = decoder.decode(b"")
|
err_code = decoder.decode(b"")[1]
|
||||||
else:
|
else:
|
||||||
b = prefix
|
b = prefix
|
||||||
while True:
|
while True:
|
||||||
|
@ -495,40 +499,33 @@ def _save(im, fp, tile, bufsize=0):
|
||||||
try:
|
try:
|
||||||
fh = fp.fileno()
|
fh = fp.fileno()
|
||||||
fp.flush()
|
fp.flush()
|
||||||
except (AttributeError, io.UnsupportedOperation) as exc:
|
exc = None
|
||||||
# compress to Python file-compatible object
|
except (AttributeError, io.UnsupportedOperation) as e:
|
||||||
for e, b, o, a in tile:
|
exc = e
|
||||||
e = Image._getencoder(im.mode, e, a, im.encoderconfig)
|
for e, b, o, a in tile:
|
||||||
if o > 0:
|
if o > 0:
|
||||||
fp.seek(o)
|
fp.seek(o)
|
||||||
e.setimage(im.im, b)
|
encoder = Image._getencoder(im.mode, e, a, im.encoderconfig)
|
||||||
if e.pushes_fd:
|
try:
|
||||||
e.setfd(fp)
|
encoder.setimage(im.im, b)
|
||||||
l, s = e.encode_to_pyfd()
|
if encoder.pushes_fd:
|
||||||
|
encoder.setfd(fp)
|
||||||
|
l, s = encoder.encode_to_pyfd()
|
||||||
else:
|
else:
|
||||||
while True:
|
if exc:
|
||||||
l, s, d = e.encode(bufsize)
|
# compress to Python file-compatible object
|
||||||
fp.write(d)
|
while True:
|
||||||
if s:
|
l, s, d = encoder.encode(bufsize)
|
||||||
break
|
fp.write(d)
|
||||||
|
if s:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# slight speedup: compress to real file object
|
||||||
|
s = encoder.encode_to_file(fh, bufsize)
|
||||||
if s < 0:
|
if s < 0:
|
||||||
raise OSError(f"encoder error {s} when writing image file") from exc
|
raise OSError(f"encoder error {s} when writing image file") from exc
|
||||||
e.cleanup()
|
finally:
|
||||||
else:
|
encoder.cleanup()
|
||||||
# slight speedup: compress to real file object
|
|
||||||
for e, b, o, a in tile:
|
|
||||||
e = Image._getencoder(im.mode, e, a, im.encoderconfig)
|
|
||||||
if o > 0:
|
|
||||||
fp.seek(o)
|
|
||||||
e.setimage(im.im, b)
|
|
||||||
if e.pushes_fd:
|
|
||||||
e.setfd(fp)
|
|
||||||
l, s = e.encode_to_pyfd()
|
|
||||||
else:
|
|
||||||
s = e.encode_to_file(fh, bufsize)
|
|
||||||
if s < 0:
|
|
||||||
raise OSError(f"encoder error {s} when writing image file")
|
|
||||||
e.cleanup()
|
|
||||||
if hasattr(fp, "flush"):
|
if hasattr(fp, "flush"):
|
||||||
fp.flush()
|
fp.flush()
|
||||||
|
|
||||||
|
@ -577,16 +574,7 @@ class PyCodecState:
|
||||||
return (self.xoff, self.yoff, self.xoff + self.xsize, self.yoff + self.ysize)
|
return (self.xoff, self.yoff, self.xoff + self.xsize, self.yoff + self.ysize)
|
||||||
|
|
||||||
|
|
||||||
class PyDecoder:
|
class PyCodec:
|
||||||
"""
|
|
||||||
Python implementation of a format decoder. Override this class and
|
|
||||||
add the decoding logic in the :meth:`decode` method.
|
|
||||||
|
|
||||||
See :ref:`Writing Your Own File Decoder in Python<file-decoders-py>`
|
|
||||||
"""
|
|
||||||
|
|
||||||
_pulls_fd = False
|
|
||||||
|
|
||||||
def __init__(self, mode, *args):
|
def __init__(self, mode, *args):
|
||||||
self.im = None
|
self.im = None
|
||||||
self.state = PyCodecState()
|
self.state = PyCodecState()
|
||||||
|
@ -596,31 +584,16 @@ class PyDecoder:
|
||||||
|
|
||||||
def init(self, args):
|
def init(self, args):
|
||||||
"""
|
"""
|
||||||
Override to perform decoder specific initialization
|
Override to perform codec specific initialization
|
||||||
|
|
||||||
:param args: Array of args items from the tile entry
|
:param args: Array of args items from the tile entry
|
||||||
:returns: None
|
:returns: None
|
||||||
"""
|
"""
|
||||||
self.args = args
|
self.args = args
|
||||||
|
|
||||||
@property
|
|
||||||
def pulls_fd(self):
|
|
||||||
return self._pulls_fd
|
|
||||||
|
|
||||||
def decode(self, buffer):
|
|
||||||
"""
|
|
||||||
Override to perform the decoding process.
|
|
||||||
|
|
||||||
:param buffer: A bytes object with the data to be decoded.
|
|
||||||
:returns: A tuple of ``(bytes consumed, errcode)``.
|
|
||||||
If finished with decoding return <0 for the bytes consumed.
|
|
||||||
Err codes are from :data:`.ImageFile.ERRORS`.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
"""
|
"""
|
||||||
Override to perform decoder specific cleanup
|
Override to perform codec specific cleanup
|
||||||
|
|
||||||
:returns: None
|
:returns: None
|
||||||
"""
|
"""
|
||||||
|
@ -628,16 +601,16 @@ class PyDecoder:
|
||||||
|
|
||||||
def setfd(self, fd):
|
def setfd(self, fd):
|
||||||
"""
|
"""
|
||||||
Called from ImageFile to set the python file-like object
|
Called from ImageFile to set the Python file-like object
|
||||||
|
|
||||||
:param fd: A python file-like object
|
:param fd: A Python file-like object
|
||||||
:returns: None
|
:returns: None
|
||||||
"""
|
"""
|
||||||
self.fd = fd
|
self.fd = fd
|
||||||
|
|
||||||
def setimage(self, im, extents=None):
|
def setimage(self, im, extents=None):
|
||||||
"""
|
"""
|
||||||
Called from ImageFile to set the core output image for the decoder
|
Called from ImageFile to set the core output image for the codec
|
||||||
|
|
||||||
:param im: A core image object
|
:param im: A core image object
|
||||||
:param extents: a 4 tuple of (x0, y0, x1, y1) defining the rectangle
|
:param extents: a 4 tuple of (x0, y0, x1, y1) defining the rectangle
|
||||||
|
@ -670,6 +643,32 @@ class PyDecoder:
|
||||||
):
|
):
|
||||||
raise ValueError("Tile cannot extend outside image")
|
raise ValueError("Tile cannot extend outside image")
|
||||||
|
|
||||||
|
|
||||||
|
class PyDecoder(PyCodec):
|
||||||
|
"""
|
||||||
|
Python implementation of a format decoder. Override this class and
|
||||||
|
add the decoding logic in the :meth:`decode` method.
|
||||||
|
|
||||||
|
See :ref:`Writing Your Own File Codec in Python<file-codecs-py>`
|
||||||
|
"""
|
||||||
|
|
||||||
|
_pulls_fd = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pulls_fd(self):
|
||||||
|
return self._pulls_fd
|
||||||
|
|
||||||
|
def decode(self, buffer):
|
||||||
|
"""
|
||||||
|
Override to perform the decoding process.
|
||||||
|
|
||||||
|
:param buffer: A bytes object with the data to be decoded.
|
||||||
|
:returns: A tuple of ``(bytes consumed, errcode)``.
|
||||||
|
If finished with decoding return -1 for the bytes consumed.
|
||||||
|
Err codes are from :data:`.ImageFile.ERRORS`.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
def set_as_raw(self, data, rawmode=None):
|
def set_as_raw(self, data, rawmode=None):
|
||||||
"""
|
"""
|
||||||
Convenience method to set the internal image from a stream of raw data
|
Convenience method to set the internal image from a stream of raw data
|
||||||
|
@ -690,3 +689,60 @@ class PyDecoder:
|
||||||
raise ValueError("not enough image data")
|
raise ValueError("not enough image data")
|
||||||
if s[1] != 0:
|
if s[1] != 0:
|
||||||
raise ValueError("cannot decode image data")
|
raise ValueError("cannot decode image data")
|
||||||
|
|
||||||
|
|
||||||
|
class PyEncoder(PyCodec):
|
||||||
|
"""
|
||||||
|
Python implementation of a format encoder. Override this class and
|
||||||
|
add the decoding logic in the :meth:`encode` method.
|
||||||
|
|
||||||
|
See :ref:`Writing Your Own File Codec in Python<file-codecs-py>`
|
||||||
|
"""
|
||||||
|
|
||||||
|
_pushes_fd = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pushes_fd(self):
|
||||||
|
return self._pushes_fd
|
||||||
|
|
||||||
|
def encode(self, bufsize):
|
||||||
|
"""
|
||||||
|
Override to perform the encoding process.
|
||||||
|
|
||||||
|
:param bufsize: Buffer size.
|
||||||
|
:returns: A tuple of ``(bytes encoded, errcode, bytes)``.
|
||||||
|
If finished with encoding return 1 for the error code.
|
||||||
|
Err codes are from :data:`.ImageFile.ERRORS`.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def encode_to_pyfd(self):
|
||||||
|
"""
|
||||||
|
If ``pushes_fd`` is ``True``, then this method will be used,
|
||||||
|
and ``encode()`` will only be called once.
|
||||||
|
|
||||||
|
:returns: A tuple of ``(bytes consumed, errcode)``.
|
||||||
|
Err codes are from :data:`.ImageFile.ERRORS`.
|
||||||
|
"""
|
||||||
|
if not self.pushes_fd:
|
||||||
|
return 0, -8 # bad configuration
|
||||||
|
bytes_consumed, errcode, data = self.encode(0)
|
||||||
|
if data:
|
||||||
|
self.fd.write(data)
|
||||||
|
return bytes_consumed, errcode
|
||||||
|
|
||||||
|
def encode_to_file(self, fh, bufsize):
|
||||||
|
"""
|
||||||
|
:param fh: File handle.
|
||||||
|
:param bufsize: Buffer size.
|
||||||
|
|
||||||
|
:returns: If finished successfully, return 0.
|
||||||
|
Otherwise, return an error code. Err codes are from
|
||||||
|
:data:`.ImageFile.ERRORS`.
|
||||||
|
"""
|
||||||
|
errcode = 0
|
||||||
|
while errcode == 0:
|
||||||
|
status, errcode, buf = self.encode(bufsize)
|
||||||
|
if status > 0:
|
||||||
|
fh.write(buf[status:])
|
||||||
|
return errcode
|
||||||
|
|
|
@ -839,7 +839,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
|
||||||
:file:`/System/Library/Fonts/` and :file:`~/Library/Fonts/` on
|
:file:`/System/Library/Fonts/` and :file:`~/Library/Fonts/` on
|
||||||
macOS.
|
macOS.
|
||||||
|
|
||||||
:param size: The requested size, in points.
|
:param size: The requested size, in pixels.
|
||||||
:param index: Which font face to load (default is first available face).
|
:param index: Which font face to load (default is first available face).
|
||||||
:param encoding: Which font encoding to use (default is Unicode). Possible
|
:param encoding: Which font encoding to use (default is Unicode). Possible
|
||||||
encodings include (see the FreeType documentation for more
|
encodings include (see the FreeType documentation for more
|
||||||
|
|
|
@ -25,7 +25,12 @@ _viewers = []
|
||||||
|
|
||||||
def register(viewer, order=1):
|
def register(viewer, order=1):
|
||||||
"""
|
"""
|
||||||
The :py:func:`register` function is used to register additional viewers.
|
The :py:func:`register` function is used to register additional viewers::
|
||||||
|
|
||||||
|
from PIL import ImageShow
|
||||||
|
ImageShow.register(MyViewer()) # MyViewer will be used as a last resort
|
||||||
|
ImageShow.register(MySecondViewer(), 0) # MySecondViewer will be prioritised
|
||||||
|
ImageShow.register(ImageShow.XVViewer(), 0) # XVViewer will be prioritised
|
||||||
|
|
||||||
:param viewer: The viewer to be registered.
|
:param viewer: The viewer to be registered.
|
||||||
:param order:
|
:param order:
|
||||||
|
|
|
@ -91,7 +91,7 @@ class Stat:
|
||||||
for i in range(0, len(self.h), 256):
|
for i in range(0, len(self.h), 256):
|
||||||
sum2 = 0.0
|
sum2 = 0.0
|
||||||
for j in range(256):
|
for j in range(256):
|
||||||
sum2 += (j ** 2) * float(self.h[i + j])
|
sum2 += (j**2) * float(self.h[i + j])
|
||||||
v.append(sum2)
|
v.append(sum2)
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ from . import Image, ImageFile
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
field = re.compile(br"([a-z]*) ([^ \r\n]*)")
|
field = re.compile(rb"([a-z]*) ([^ \r\n]*)")
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -132,7 +132,7 @@ def _res_to_dpi(num, denom, exp):
|
||||||
calculated as (num / denom) * 10^exp and stored in dots per meter,
|
calculated as (num / denom) * 10^exp and stored in dots per meter,
|
||||||
to floating-point dots per inch."""
|
to floating-point dots per inch."""
|
||||||
if denom != 0:
|
if denom != 0:
|
||||||
return (254 * num * (10 ** exp)) / (10000 * denom)
|
return (254 * num * (10**exp)) / (10000 * denom)
|
||||||
|
|
||||||
|
|
||||||
def _parse_jp2_header(fp):
|
def _parse_jp2_header(fp):
|
||||||
|
|
|
@ -46,6 +46,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||||
self._after_jpeg_open()
|
self._after_jpeg_open()
|
||||||
|
|
||||||
def _after_jpeg_open(self, mpheader=None):
|
def _after_jpeg_open(self, mpheader=None):
|
||||||
|
self._initial_size = self.size
|
||||||
self.mpinfo = mpheader if mpheader is not None else self._getmp()
|
self.mpinfo = mpheader if mpheader is not None else self._getmp()
|
||||||
self.n_frames = self.mpinfo[0xB001]
|
self.n_frames = self.mpinfo[0xB001]
|
||||||
self.__mpoffsets = [
|
self.__mpoffsets = [
|
||||||
|
@ -77,6 +78,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||||
segment = self.fp.read(2)
|
segment = self.fp.read(2)
|
||||||
if not segment:
|
if not segment:
|
||||||
raise ValueError("No data found for frame")
|
raise ValueError("No data found for frame")
|
||||||
|
self._size = self._initial_size
|
||||||
if i16(segment) == 0xFFE1: # APP1
|
if i16(segment) == 0xFFE1: # APP1
|
||||||
n = i16(self.fp.read(2)) - 2
|
n = i16(self.fp.read(2)) - 2
|
||||||
self.info["exif"] = ImageFile._safe_read(self.fp, n)
|
self.info["exif"] = ImageFile._safe_read(self.fp, n)
|
||||||
|
|
|
@ -148,7 +148,7 @@ class MspDecoder(ImageFile.PyDecoder):
|
||||||
|
|
||||||
self.set_as_raw(img.getvalue(), ("1", 0, 1))
|
self.set_as_raw(img.getvalue(), ("1", 0, 1))
|
||||||
|
|
||||||
return 0, 0
|
return -1, 0
|
||||||
|
|
||||||
|
|
||||||
Image.register_decoder("MSP", MspDecoder)
|
Image.register_decoder("MSP", MspDecoder)
|
||||||
|
|
|
@ -576,42 +576,42 @@ class PdfParser:
|
||||||
self.xref_table[reference.object_id] = (offset, 0)
|
self.xref_table[reference.object_id] = (offset, 0)
|
||||||
return reference
|
return reference
|
||||||
|
|
||||||
delimiter = br"[][()<>{}/%]"
|
delimiter = rb"[][()<>{}/%]"
|
||||||
delimiter_or_ws = br"[][()<>{}/%\000\011\012\014\015\040]"
|
delimiter_or_ws = rb"[][()<>{}/%\000\011\012\014\015\040]"
|
||||||
whitespace = br"[\000\011\012\014\015\040]"
|
whitespace = rb"[\000\011\012\014\015\040]"
|
||||||
whitespace_or_hex = br"[\000\011\012\014\015\0400-9a-fA-F]"
|
whitespace_or_hex = rb"[\000\011\012\014\015\0400-9a-fA-F]"
|
||||||
whitespace_optional = whitespace + b"*"
|
whitespace_optional = whitespace + b"*"
|
||||||
whitespace_mandatory = whitespace + b"+"
|
whitespace_mandatory = whitespace + b"+"
|
||||||
# No "\012" aka "\n" or "\015" aka "\r":
|
# No "\012" aka "\n" or "\015" aka "\r":
|
||||||
whitespace_optional_no_nl = br"[\000\011\014\040]*"
|
whitespace_optional_no_nl = rb"[\000\011\014\040]*"
|
||||||
newline_only = br"[\r\n]+"
|
newline_only = rb"[\r\n]+"
|
||||||
newline = whitespace_optional_no_nl + newline_only + whitespace_optional_no_nl
|
newline = whitespace_optional_no_nl + newline_only + whitespace_optional_no_nl
|
||||||
re_trailer_end = re.compile(
|
re_trailer_end = re.compile(
|
||||||
whitespace_mandatory
|
whitespace_mandatory
|
||||||
+ br"trailer"
|
+ rb"trailer"
|
||||||
+ whitespace_optional
|
+ whitespace_optional
|
||||||
+ br"\<\<(.*\>\>)"
|
+ rb"\<\<(.*\>\>)"
|
||||||
+ newline
|
+ newline
|
||||||
+ br"startxref"
|
+ rb"startxref"
|
||||||
+ newline
|
+ newline
|
||||||
+ br"([0-9]+)"
|
+ rb"([0-9]+)"
|
||||||
+ newline
|
+ newline
|
||||||
+ br"%%EOF"
|
+ rb"%%EOF"
|
||||||
+ whitespace_optional
|
+ whitespace_optional
|
||||||
+ br"$",
|
+ rb"$",
|
||||||
re.DOTALL,
|
re.DOTALL,
|
||||||
)
|
)
|
||||||
re_trailer_prev = re.compile(
|
re_trailer_prev = re.compile(
|
||||||
whitespace_optional
|
whitespace_optional
|
||||||
+ br"trailer"
|
+ rb"trailer"
|
||||||
+ whitespace_optional
|
+ whitespace_optional
|
||||||
+ br"\<\<(.*?\>\>)"
|
+ rb"\<\<(.*?\>\>)"
|
||||||
+ newline
|
+ newline
|
||||||
+ br"startxref"
|
+ rb"startxref"
|
||||||
+ newline
|
+ newline
|
||||||
+ br"([0-9]+)"
|
+ rb"([0-9]+)"
|
||||||
+ newline
|
+ newline
|
||||||
+ br"%%EOF"
|
+ rb"%%EOF"
|
||||||
+ whitespace_optional,
|
+ whitespace_optional,
|
||||||
re.DOTALL,
|
re.DOTALL,
|
||||||
)
|
)
|
||||||
|
@ -655,12 +655,12 @@ class PdfParser:
|
||||||
re_whitespace_optional = re.compile(whitespace_optional)
|
re_whitespace_optional = re.compile(whitespace_optional)
|
||||||
re_name = re.compile(
|
re_name = re.compile(
|
||||||
whitespace_optional
|
whitespace_optional
|
||||||
+ br"/([!-$&'*-.0-;=?-Z\\^-z|~]+)(?="
|
+ rb"/([!-$&'*-.0-;=?-Z\\^-z|~]+)(?="
|
||||||
+ delimiter_or_ws
|
+ delimiter_or_ws
|
||||||
+ br")"
|
+ rb")"
|
||||||
)
|
)
|
||||||
re_dict_start = re.compile(whitespace_optional + br"\<\<")
|
re_dict_start = re.compile(whitespace_optional + rb"\<\<")
|
||||||
re_dict_end = re.compile(whitespace_optional + br"\>\>" + whitespace_optional)
|
re_dict_end = re.compile(whitespace_optional + rb"\>\>" + whitespace_optional)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def interpret_trailer(cls, trailer_data):
|
def interpret_trailer(cls, trailer_data):
|
||||||
|
@ -689,7 +689,7 @@ class PdfParser:
|
||||||
)
|
)
|
||||||
return trailer
|
return trailer
|
||||||
|
|
||||||
re_hashes_in_name = re.compile(br"([^#]*)(#([0-9a-fA-F]{2}))?")
|
re_hashes_in_name = re.compile(rb"([^#]*)(#([0-9a-fA-F]{2}))?")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def interpret_name(cls, raw, as_text=False):
|
def interpret_name(cls, raw, as_text=False):
|
||||||
|
@ -704,53 +704,53 @@ class PdfParser:
|
||||||
else:
|
else:
|
||||||
return bytes(name)
|
return bytes(name)
|
||||||
|
|
||||||
re_null = re.compile(whitespace_optional + br"null(?=" + delimiter_or_ws + br")")
|
re_null = re.compile(whitespace_optional + rb"null(?=" + delimiter_or_ws + rb")")
|
||||||
re_true = re.compile(whitespace_optional + br"true(?=" + delimiter_or_ws + br")")
|
re_true = re.compile(whitespace_optional + rb"true(?=" + delimiter_or_ws + rb")")
|
||||||
re_false = re.compile(whitespace_optional + br"false(?=" + delimiter_or_ws + br")")
|
re_false = re.compile(whitespace_optional + rb"false(?=" + delimiter_or_ws + rb")")
|
||||||
re_int = re.compile(
|
re_int = re.compile(
|
||||||
whitespace_optional + br"([-+]?[0-9]+)(?=" + delimiter_or_ws + br")"
|
whitespace_optional + rb"([-+]?[0-9]+)(?=" + delimiter_or_ws + rb")"
|
||||||
)
|
)
|
||||||
re_real = re.compile(
|
re_real = re.compile(
|
||||||
whitespace_optional
|
whitespace_optional
|
||||||
+ br"([-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+))(?="
|
+ rb"([-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+))(?="
|
||||||
+ delimiter_or_ws
|
+ delimiter_or_ws
|
||||||
+ br")"
|
+ rb")"
|
||||||
)
|
)
|
||||||
re_array_start = re.compile(whitespace_optional + br"\[")
|
re_array_start = re.compile(whitespace_optional + rb"\[")
|
||||||
re_array_end = re.compile(whitespace_optional + br"]")
|
re_array_end = re.compile(whitespace_optional + rb"]")
|
||||||
re_string_hex = re.compile(
|
re_string_hex = re.compile(
|
||||||
whitespace_optional + br"\<(" + whitespace_or_hex + br"*)\>"
|
whitespace_optional + rb"\<(" + whitespace_or_hex + rb"*)\>"
|
||||||
)
|
)
|
||||||
re_string_lit = re.compile(whitespace_optional + br"\(")
|
re_string_lit = re.compile(whitespace_optional + rb"\(")
|
||||||
re_indirect_reference = re.compile(
|
re_indirect_reference = re.compile(
|
||||||
whitespace_optional
|
whitespace_optional
|
||||||
+ br"([-+]?[0-9]+)"
|
+ rb"([-+]?[0-9]+)"
|
||||||
+ whitespace_mandatory
|
+ whitespace_mandatory
|
||||||
+ br"([-+]?[0-9]+)"
|
+ rb"([-+]?[0-9]+)"
|
||||||
+ whitespace_mandatory
|
+ whitespace_mandatory
|
||||||
+ br"R(?="
|
+ rb"R(?="
|
||||||
+ delimiter_or_ws
|
+ delimiter_or_ws
|
||||||
+ br")"
|
+ rb")"
|
||||||
)
|
)
|
||||||
re_indirect_def_start = re.compile(
|
re_indirect_def_start = re.compile(
|
||||||
whitespace_optional
|
whitespace_optional
|
||||||
+ br"([-+]?[0-9]+)"
|
+ rb"([-+]?[0-9]+)"
|
||||||
+ whitespace_mandatory
|
+ whitespace_mandatory
|
||||||
+ br"([-+]?[0-9]+)"
|
+ rb"([-+]?[0-9]+)"
|
||||||
+ whitespace_mandatory
|
+ whitespace_mandatory
|
||||||
+ br"obj(?="
|
+ rb"obj(?="
|
||||||
+ delimiter_or_ws
|
+ delimiter_or_ws
|
||||||
+ br")"
|
+ rb")"
|
||||||
)
|
)
|
||||||
re_indirect_def_end = re.compile(
|
re_indirect_def_end = re.compile(
|
||||||
whitespace_optional + br"endobj(?=" + delimiter_or_ws + br")"
|
whitespace_optional + rb"endobj(?=" + delimiter_or_ws + rb")"
|
||||||
)
|
)
|
||||||
re_comment = re.compile(
|
re_comment = re.compile(
|
||||||
br"(" + whitespace_optional + br"%[^\r\n]*" + newline + br")*"
|
rb"(" + whitespace_optional + rb"%[^\r\n]*" + newline + rb")*"
|
||||||
)
|
)
|
||||||
re_stream_start = re.compile(whitespace_optional + br"stream\r?\n")
|
re_stream_start = re.compile(whitespace_optional + rb"stream\r?\n")
|
||||||
re_stream_end = re.compile(
|
re_stream_end = re.compile(
|
||||||
whitespace_optional + br"endstream(?=" + delimiter_or_ws + br")"
|
whitespace_optional + rb"endstream(?=" + delimiter_or_ws + rb")"
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -876,7 +876,7 @@ class PdfParser:
|
||||||
raise PdfFormatError("unrecognized object: " + repr(data[offset : offset + 32]))
|
raise PdfFormatError("unrecognized object: " + repr(data[offset : offset + 32]))
|
||||||
|
|
||||||
re_lit_str_token = re.compile(
|
re_lit_str_token = re.compile(
|
||||||
br"(\\[nrtbf()\\])|(\\[0-9]{1,3})|(\\(\r\n|\r|\n))|(\r\n|\r|\n)|(\()|(\))"
|
rb"(\\[nrtbf()\\])|(\\[0-9]{1,3})|(\\(\r\n|\r|\n))|(\r\n|\r|\n)|(\()|(\))"
|
||||||
)
|
)
|
||||||
escaped_chars = {
|
escaped_chars = {
|
||||||
b"n": b"\n",
|
b"n": b"\n",
|
||||||
|
@ -922,16 +922,16 @@ class PdfParser:
|
||||||
offset = m.end()
|
offset = m.end()
|
||||||
raise PdfFormatError("unfinished literal string")
|
raise PdfFormatError("unfinished literal string")
|
||||||
|
|
||||||
re_xref_section_start = re.compile(whitespace_optional + br"xref" + newline)
|
re_xref_section_start = re.compile(whitespace_optional + rb"xref" + newline)
|
||||||
re_xref_subsection_start = re.compile(
|
re_xref_subsection_start = re.compile(
|
||||||
whitespace_optional
|
whitespace_optional
|
||||||
+ br"([0-9]+)"
|
+ rb"([0-9]+)"
|
||||||
+ whitespace_mandatory
|
+ whitespace_mandatory
|
||||||
+ br"([0-9]+)"
|
+ rb"([0-9]+)"
|
||||||
+ whitespace_optional
|
+ whitespace_optional
|
||||||
+ newline_only
|
+ newline_only
|
||||||
)
|
)
|
||||||
re_xref_entry = re.compile(br"([0-9]{10}) ([0-9]{5}) ([fn])( \r| \n|\r\n)")
|
re_xref_entry = re.compile(rb"([0-9]{10}) ([0-9]{5}) ([fn])( \r| \n|\r\n)")
|
||||||
|
|
||||||
def read_xref_table(self, xref_section_offset):
|
def read_xref_table(self, xref_section_offset):
|
||||||
subsection_found = False
|
subsection_found = False
|
||||||
|
|
|
@ -48,7 +48,7 @@ from ._binary import o32be as o32
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
is_cid = re.compile(br"\w\w\w\w").match
|
is_cid = re.compile(rb"\w\w\w\w").match
|
||||||
|
|
||||||
|
|
||||||
_MAGIC = b"\211PNG\r\n\032\n"
|
_MAGIC = b"\211PNG\r\n\032\n"
|
||||||
|
|
|
@ -49,26 +49,46 @@ class PpmImageFile(ImageFile.ImageFile):
|
||||||
format = "PPM"
|
format = "PPM"
|
||||||
format_description = "Pbmplus image"
|
format_description = "Pbmplus image"
|
||||||
|
|
||||||
def _token(self, s=b""):
|
def _read_magic(self):
|
||||||
while True: # read until next whitespace
|
magic = b""
|
||||||
|
# read until whitespace or longest available magic number
|
||||||
|
for _ in range(6):
|
||||||
c = self.fp.read(1)
|
c = self.fp.read(1)
|
||||||
if not c or c in b_whitespace:
|
if not c or c in b_whitespace:
|
||||||
break
|
break
|
||||||
if c > b"\x79":
|
magic += c
|
||||||
raise ValueError("Expected ASCII value, found binary")
|
return magic
|
||||||
s = s + c
|
|
||||||
if len(s) > 9:
|
def _read_token(self):
|
||||||
raise ValueError("Expected int, got > 9 digits")
|
token = b""
|
||||||
return s
|
while len(token) <= 10: # read until next whitespace or limit of 10 characters
|
||||||
|
c = self.fp.read(1)
|
||||||
|
if not c:
|
||||||
|
break
|
||||||
|
elif c in b_whitespace: # token ended
|
||||||
|
if not token:
|
||||||
|
# skip whitespace at start
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
elif c == b"#":
|
||||||
|
# ignores rest of the line; stops at CR, LF or EOF
|
||||||
|
while self.fp.read(1) not in b"\r\n":
|
||||||
|
pass
|
||||||
|
continue
|
||||||
|
token += c
|
||||||
|
if not token:
|
||||||
|
# Token was not even 1 byte
|
||||||
|
raise ValueError("Reached EOF while reading header")
|
||||||
|
elif len(token) > 10:
|
||||||
|
raise ValueError(f"Token too long in file header: {token}")
|
||||||
|
return token
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
|
magic_number = self._read_magic()
|
||||||
# check magic
|
try:
|
||||||
s = self.fp.read(1)
|
mode = MODES[magic_number]
|
||||||
if s != b"P":
|
except KeyError:
|
||||||
raise SyntaxError("not a PPM file")
|
raise SyntaxError("not a PPM file")
|
||||||
magic_number = self._token(s)
|
|
||||||
mode = MODES[magic_number]
|
|
||||||
|
|
||||||
self.custom_mimetype = {
|
self.custom_mimetype = {
|
||||||
b"P4": "image/x-portable-bitmap",
|
b"P4": "image/x-portable-bitmap",
|
||||||
|
@ -83,29 +103,19 @@ class PpmImageFile(ImageFile.ImageFile):
|
||||||
self.mode = rawmode = mode
|
self.mode = rawmode = mode
|
||||||
|
|
||||||
for ix in range(3):
|
for ix in range(3):
|
||||||
while True:
|
token = int(self._read_token())
|
||||||
while True:
|
if ix == 0: # token is the x size
|
||||||
s = self.fp.read(1)
|
xsize = token
|
||||||
if s not in b_whitespace:
|
elif ix == 1: # token is the y size
|
||||||
break
|
ysize = token
|
||||||
if s == b"":
|
|
||||||
raise ValueError("File does not extend beyond magic number")
|
|
||||||
if s != b"#":
|
|
||||||
break
|
|
||||||
s = self.fp.readline()
|
|
||||||
s = int(self._token(s))
|
|
||||||
if ix == 0:
|
|
||||||
xsize = s
|
|
||||||
elif ix == 1:
|
|
||||||
ysize = s
|
|
||||||
if mode == "1":
|
if mode == "1":
|
||||||
break
|
break
|
||||||
elif ix == 2:
|
elif ix == 2: # token is maxval
|
||||||
# maxgrey
|
maxval = token
|
||||||
if s > 255:
|
if maxval > 255:
|
||||||
if not mode == "L":
|
if not mode == "L":
|
||||||
raise ValueError(f"Too many colors for band: {s}")
|
raise ValueError(f"Too many colors for band: {token}")
|
||||||
if s < 2 ** 16:
|
if maxval < 2**16:
|
||||||
self.mode = "I"
|
self.mode = "I"
|
||||||
rawmode = "I;16B"
|
rawmode = "I;16B"
|
||||||
else:
|
else:
|
||||||
|
@ -126,7 +136,7 @@ def _save(im, fp, filename):
|
||||||
elif im.mode == "L":
|
elif im.mode == "L":
|
||||||
rawmode, head = "L", b"P5"
|
rawmode, head = "L", b"P5"
|
||||||
elif im.mode == "I":
|
elif im.mode == "I":
|
||||||
if im.getextrema()[1] < 2 ** 16:
|
if im.getextrema()[1] < 2**16:
|
||||||
rawmode, head = "I;16B", b"P5"
|
rawmode, head = "I;16B", b"P5"
|
||||||
else:
|
else:
|
||||||
rawmode, head = "I;32B", b"P5"
|
rawmode, head = "I;32B", b"P5"
|
||||||
|
|
|
@ -155,14 +155,6 @@ class PsdImageFile(ImageFile.ImageFile):
|
||||||
# return layer number (0=image, 1..max=layers)
|
# return layer number (0=image, 1..max=layers)
|
||||||
return self.frame
|
return self.frame
|
||||||
|
|
||||||
def load_prepare(self):
|
|
||||||
# create image memory if necessary
|
|
||||||
if not self.im or self.im.mode != self.mode or self.im.size != self.size:
|
|
||||||
self.im = Image.core.fill(self.mode, self.size, 0)
|
|
||||||
# create palette (optional)
|
|
||||||
if self.mode == "P":
|
|
||||||
Image.Image.load(self)
|
|
||||||
|
|
||||||
def _close__fp(self):
|
def _close__fp(self):
|
||||||
try:
|
try:
|
||||||
if self.__fp != self.fp:
|
if self.__fp != self.fp:
|
||||||
|
|
|
@ -493,7 +493,7 @@ class ImageFileDirectory_v2(MutableMapping):
|
||||||
endianness.
|
endianness.
|
||||||
:param prefix: Override the endianness of the file.
|
:param prefix: Override the endianness of the file.
|
||||||
"""
|
"""
|
||||||
if ifh[:4] not in PREFIXES:
|
if not _accept(ifh):
|
||||||
raise SyntaxError(f"not a TIFF file (header {repr(ifh)} not valid)")
|
raise SyntaxError(f"not a TIFF file (header {repr(ifh)} not valid)")
|
||||||
self._prefix = prefix if prefix is not None else ifh[:2]
|
self._prefix = prefix if prefix is not None else ifh[:2]
|
||||||
if self._prefix == MM:
|
if self._prefix == MM:
|
||||||
|
@ -577,9 +577,9 @@ class ImageFileDirectory_v2(MutableMapping):
|
||||||
else TiffTags.SIGNED_RATIONAL
|
else TiffTags.SIGNED_RATIONAL
|
||||||
)
|
)
|
||||||
elif all(isinstance(v, int) for v in values):
|
elif all(isinstance(v, int) for v in values):
|
||||||
if all(0 <= v < 2 ** 16 for v in values):
|
if all(0 <= v < 2**16 for v in values):
|
||||||
self.tagtype[tag] = TiffTags.SHORT
|
self.tagtype[tag] = TiffTags.SHORT
|
||||||
elif all(-(2 ** 15) < v < 2 ** 15 for v in values):
|
elif all(-(2**15) < v < 2**15 for v in values):
|
||||||
self.tagtype[tag] = TiffTags.SIGNED_SHORT
|
self.tagtype[tag] = TiffTags.SIGNED_SHORT
|
||||||
else:
|
else:
|
||||||
self.tagtype[tag] = (
|
self.tagtype[tag] = (
|
||||||
|
@ -734,7 +734,7 @@ class ImageFileDirectory_v2(MutableMapping):
|
||||||
@_register_writer(5)
|
@_register_writer(5)
|
||||||
def write_rational(self, *values):
|
def write_rational(self, *values):
|
||||||
return b"".join(
|
return b"".join(
|
||||||
self._pack("2L", *_limit_rational(frac, 2 ** 32 - 1)) for frac in values
|
self._pack("2L", *_limit_rational(frac, 2**32 - 1)) for frac in values
|
||||||
)
|
)
|
||||||
|
|
||||||
@_register_loader(7, 1)
|
@_register_loader(7, 1)
|
||||||
|
@ -757,7 +757,7 @@ class ImageFileDirectory_v2(MutableMapping):
|
||||||
@_register_writer(10)
|
@_register_writer(10)
|
||||||
def write_signed_rational(self, *values):
|
def write_signed_rational(self, *values):
|
||||||
return b"".join(
|
return b"".join(
|
||||||
self._pack("2l", *_limit_signed_rational(frac, 2 ** 31 - 1, -(2 ** 31)))
|
self._pack("2l", *_limit_signed_rational(frac, 2**31 - 1, -(2**31)))
|
||||||
for frac in values
|
for frac in values
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1670,7 +1670,7 @@ def _save(im, fp, filename):
|
||||||
strip_byte_counts = 1 if stride == 0 else stride * rows_per_strip
|
strip_byte_counts = 1 if stride == 0 else stride * rows_per_strip
|
||||||
strips_per_image = (im.size[1] + rows_per_strip - 1) // rows_per_strip
|
strips_per_image = (im.size[1] + rows_per_strip - 1) // rows_per_strip
|
||||||
ifd[ROWSPERSTRIP] = rows_per_strip
|
ifd[ROWSPERSTRIP] = rows_per_strip
|
||||||
if strip_byte_counts >= 2 ** 16:
|
if strip_byte_counts >= 2**16:
|
||||||
ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG
|
ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG
|
||||||
ifd[STRIPBYTECOUNTS] = (strip_byte_counts,) * (strips_per_image - 1) + (
|
ifd[STRIPBYTECOUNTS] = (strip_byte_counts,) * (strips_per_image - 1) + (
|
||||||
stride * im.size[1] - strip_byte_counts * (strips_per_image - 1),
|
stride * im.size[1] - strip_byte_counts * (strips_per_image - 1),
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
|
|
||||||
from . import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
from ._binary import i16le as word
|
from ._binary import i16le as word
|
||||||
from ._binary import i32le as dword
|
|
||||||
from ._binary import si16le as short
|
from ._binary import si16le as short
|
||||||
from ._binary import si32le as _long
|
from ._binary import si32le as _long
|
||||||
|
|
||||||
|
@ -112,7 +111,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
||||||
if s[22:26] != b"\x01\x00\t\x00":
|
if s[22:26] != b"\x01\x00\t\x00":
|
||||||
raise SyntaxError("Unsupported WMF file format")
|
raise SyntaxError("Unsupported WMF file format")
|
||||||
|
|
||||||
elif dword(s) == 1 and s[40:44] == b" EMF":
|
elif s[:4] == b"\x01\x00\x00\x00" and s[40:44] == b" EMF":
|
||||||
# enhanced metafile
|
# enhanced metafile
|
||||||
|
|
||||||
# get bounding box
|
# get bounding box
|
||||||
|
|
|
@ -25,7 +25,7 @@ from . import Image, ImageFile
|
||||||
|
|
||||||
# XBM header
|
# XBM header
|
||||||
xbm_head = re.compile(
|
xbm_head = re.compile(
|
||||||
br"\s*#define[ \t]+.*_width[ \t]+(?P<width>[0-9]+)[\r\n]+"
|
rb"\s*#define[ \t]+.*_width[ \t]+(?P<width>[0-9]+)[\r\n]+"
|
||||||
b"#define[ \t]+.*_height[ \t]+(?P<height>[0-9]+)[\r\n]+"
|
b"#define[ \t]+.*_height[ \t]+(?P<height>[0-9]+)[\r\n]+"
|
||||||
b"(?P<hotspot>"
|
b"(?P<hotspot>"
|
||||||
b"#define[ \t]+[^_]*_x_hot[ \t]+(?P<xhot>[0-9]+)[\r\n]+"
|
b"#define[ \t]+[^_]*_x_hot[ \t]+(?P<xhot>[0-9]+)[\r\n]+"
|
||||||
|
@ -52,18 +52,19 @@ class XbmImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
m = xbm_head.match(self.fp.read(512))
|
m = xbm_head.match(self.fp.read(512))
|
||||||
|
|
||||||
if m:
|
if not m:
|
||||||
|
raise SyntaxError("not a XBM file")
|
||||||
|
|
||||||
xsize = int(m.group("width"))
|
xsize = int(m.group("width"))
|
||||||
ysize = int(m.group("height"))
|
ysize = int(m.group("height"))
|
||||||
|
|
||||||
if m.group("hotspot"):
|
if m.group("hotspot"):
|
||||||
self.info["hotspot"] = (int(m.group("xhot")), int(m.group("yhot")))
|
self.info["hotspot"] = (int(m.group("xhot")), int(m.group("yhot")))
|
||||||
|
|
||||||
self.mode = "1"
|
self.mode = "1"
|
||||||
self._size = xsize, ysize
|
self._size = xsize, ysize
|
||||||
|
|
||||||
self.tile = [("xbm", (0, 0) + self.size, m.end(), None)]
|
self.tile = [("xbm", (0, 0) + self.size, m.end(), None)]
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename):
|
def _save(im, fp, filename):
|
||||||
|
|
|
@ -149,14 +149,13 @@ _encode(ImagingEncoderObject *encoder, PyObject *args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_encode_to_pyfd(ImagingEncoderObject *encoder, PyObject *args) {
|
_encode_to_pyfd(ImagingEncoderObject *encoder) {
|
||||||
PyObject *result;
|
PyObject *result;
|
||||||
int status;
|
int status;
|
||||||
|
|
||||||
if (!encoder->pushes_fd) {
|
if (!encoder->pushes_fd) {
|
||||||
// UNDONE, appropriate errcode???
|
// UNDONE, appropriate errcode???
|
||||||
result = Py_BuildValue("ii", 0, IMAGING_CODEC_CONFIG);
|
result = Py_BuildValue("ii", 0, IMAGING_CODEC_CONFIG);
|
||||||
;
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,7 +306,7 @@ static struct PyMethodDef methods[] = {
|
||||||
{"encode", (PyCFunction)_encode, METH_VARARGS},
|
{"encode", (PyCFunction)_encode, METH_VARARGS},
|
||||||
{"cleanup", (PyCFunction)_encode_cleanup, METH_VARARGS},
|
{"cleanup", (PyCFunction)_encode_cleanup, METH_VARARGS},
|
||||||
{"encode_to_file", (PyCFunction)_encode_to_file, METH_VARARGS},
|
{"encode_to_file", (PyCFunction)_encode_to_file, METH_VARARGS},
|
||||||
{"encode_to_pyfd", (PyCFunction)_encode_to_pyfd, METH_VARARGS},
|
{"encode_to_pyfd", (PyCFunction)_encode_to_pyfd, METH_NOARGS},
|
||||||
{"setimage", (PyCFunction)_setimage, METH_VARARGS},
|
{"setimage", (PyCFunction)_setimage, METH_VARARGS},
|
||||||
{"setfd", (PyCFunction)_setfd, METH_VARARGS},
|
{"setfd", (PyCFunction)_setfd, METH_VARARGS},
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
#define MAX(a, b) (a) > (b) ? (a) : (b)
|
#define MAX(a, b) (a) > (b) ? (a) : (b)
|
||||||
#define MIN(a, b) (a) < (b) ? (a) : (b)
|
#define MIN(a, b) (a) < (b) ? (a) : (b)
|
||||||
|
|
||||||
#define CLIP16(v) ((v) <= -32768 ? -32768 : (v) >= 32767 ? 32767 : (v))
|
#define CLIP16(v) ((v) <= 0 ? 0 : (v) >= 65535 ? 65535 : (v))
|
||||||
|
|
||||||
/* ITU-R Recommendation 601-2 (assuming nonlinear RGB) */
|
/* ITU-R Recommendation 601-2 (assuming nonlinear RGB) */
|
||||||
#define L(rgb) ((INT32)(rgb)[0] * 299 + (INT32)(rgb)[1] * 587 + (INT32)(rgb)[2] * 114)
|
#define L(rgb) ((INT32)(rgb)[0] * 299 + (INT32)(rgb)[1] * 587 + (INT32)(rgb)[2] * 114)
|
||||||
|
@ -1634,29 +1634,15 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
|
||||||
return (Imaging)ImagingError_ModeError();
|
return (Imaging)ImagingError_ModeError();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!((strcmp(imIn->mode, "RGB") == 0 || strcmp(imIn->mode, "1") == 0 ||
|
if (strcmp(imIn->mode, "RGB") == 0 && strcmp(mode, "RGBA") == 0) {
|
||||||
strcmp(imIn->mode, "I") == 0 || strcmp(imIn->mode, "L") == 0) &&
|
|
||||||
strcmp(mode, "RGBA") == 0))
|
|
||||||
#ifdef notdef
|
|
||||||
{
|
|
||||||
return (Imaging)ImagingError_ValueError("conversion not supported");
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
{
|
|
||||||
static char buf[100];
|
|
||||||
snprintf(
|
|
||||||
buf,
|
|
||||||
100,
|
|
||||||
"conversion from %.10s to %.10s not supported in convert_transparent",
|
|
||||||
imIn->mode,
|
|
||||||
mode);
|
|
||||||
return (Imaging)ImagingError_ValueError(buf);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (strcmp(imIn->mode, "RGB") == 0) {
|
|
||||||
convert = rgb2rgba;
|
convert = rgb2rgba;
|
||||||
} else {
|
} else if ((strcmp(imIn->mode, "1") == 0 ||
|
||||||
|
strcmp(imIn->mode, "I") == 0 ||
|
||||||
|
strcmp(imIn->mode, "L") == 0
|
||||||
|
) && (
|
||||||
|
strcmp(mode, "RGBA") == 0 ||
|
||||||
|
strcmp(mode, "LA") == 0
|
||||||
|
)) {
|
||||||
if (strcmp(imIn->mode, "1") == 0) {
|
if (strcmp(imIn->mode, "1") == 0) {
|
||||||
convert = bit2rgb;
|
convert = bit2rgb;
|
||||||
} else if (strcmp(imIn->mode, "I") == 0) {
|
} else if (strcmp(imIn->mode, "I") == 0) {
|
||||||
|
@ -1665,6 +1651,15 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
|
||||||
convert = l2rgb;
|
convert = l2rgb;
|
||||||
}
|
}
|
||||||
g = b = r;
|
g = b = r;
|
||||||
|
} else {
|
||||||
|
static char buf[100];
|
||||||
|
snprintf(
|
||||||
|
buf,
|
||||||
|
100,
|
||||||
|
"conversion from %.10s to %.10s not supported in convert_transparent",
|
||||||
|
imIn->mode,
|
||||||
|
mode);
|
||||||
|
return (Imaging)ImagingError_ValueError(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
imOut = ImagingNew2Dirty(mode, imOut, imIn);
|
imOut = ImagingNew2Dirty(mode, imOut, imIn);
|
||||||
|
|
|
@ -295,7 +295,7 @@ ImagingPaste(
|
||||||
paste_mask_L(imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
|
paste_mask_L(imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
|
||||||
ImagingSectionLeave(&cookie);
|
ImagingSectionLeave(&cookie);
|
||||||
|
|
||||||
} else if (strcmp(imMask->mode, "RGBA") == 0) {
|
} else if (strcmp(imMask->mode, "LA") == 0 || strcmp(imMask->mode, "RGBA") == 0) {
|
||||||
ImagingSectionEnter(&cookie);
|
ImagingSectionEnter(&cookie);
|
||||||
paste_mask_RGBA(
|
paste_mask_RGBA(
|
||||||
imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
|
imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user