mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-27 01:34:24 +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
|
||||
dry-run: false
|
||||
- name: Upload New Crash
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
if: failure() && steps.build.outcome == 'success'
|
||||
with:
|
||||
name: artifacts
|
||||
path: ./out/artifacts
|
||||
- name: Upload Legacy Crash
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
if: steps.run.outcome == 'success'
|
||||
with:
|
||||
name: crash
|
||||
|
|
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
|
@ -10,7 +10,7 @@ jobs:
|
|||
name: Lint
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: pre-commit cache
|
||||
uses: actions/cache@v2
|
||||
|
@ -21,7 +21,7 @@ jobs:
|
|||
lint-pre-commit-
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.10"
|
||||
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,
|
||||
fedora-34-amd64,
|
||||
fedora-35-amd64,
|
||||
gentoo,
|
||||
ubuntu-18.04-bionic-amd64,
|
||||
ubuntu-20.04-focal-amd64,
|
||||
]
|
||||
|
@ -40,7 +41,7 @@ jobs:
|
|||
name: ${{ matrix.docker }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build system information
|
||||
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:
|
||||
- name: Checkout Pillow
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up shell
|
||||
run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH
|
||||
|
@ -45,6 +45,7 @@ jobs:
|
|||
${{ matrix.package }}-python-pyqt6 \
|
||||
${{ matrix.package }}-python3-setuptools \
|
||||
${{ matrix.package }}-freetype \
|
||||
${{ matrix.package }}-gcc \
|
||||
${{ matrix.package }}-ghostscript \
|
||||
${{ matrix.package }}-lcms2 \
|
||||
${{ 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 }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build system information
|
||||
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:
|
||||
- name: Checkout Pillow
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Checkout cached dependencies
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: python-pillow/pillow-depends
|
||||
path: winbuild\depends
|
||||
|
||||
# sets env: pythonLocation
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
architecture: ${{ matrix.architecture }}
|
||||
|
@ -137,10 +137,11 @@ jobs:
|
|||
& $env:pythonLocation\python.exe selftest.py --installed
|
||||
shell: pwsh
|
||||
|
||||
# failing with PyPy3
|
||||
# skip PyPy for speed
|
||||
- name: Enable heap verification
|
||||
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
|
||||
run: |
|
||||
|
@ -155,7 +156,7 @@ jobs:
|
|||
shell: bash
|
||||
|
||||
- name: Upload errors
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
with:
|
||||
name: errors
|
||||
|
@ -181,7 +182,7 @@ jobs:
|
|||
winbuild\\build\\build_pillow.cmd --disable-imagequant bdist_wheel
|
||||
shell: cmd
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: "github.event_name != 'pull_request'"
|
||||
with:
|
||||
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 }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: pip
|
||||
|
@ -84,14 +84,14 @@ jobs:
|
|||
mkdir -p Tests/errors
|
||||
|
||||
- name: Upload errors
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
with:
|
||||
name: errors
|
||||
path: Tests/errors
|
||||
|
||||
- name: Docs
|
||||
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.9
|
||||
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.10
|
||||
run: |
|
||||
python3 -m pip install sphinx-copybutton sphinx-issues sphinx-removed-in sphinx-rtd-theme sphinxext-opengraph
|
||||
make doccheck
|
||||
|
|
2
.github/workflows/tidelift.yml
vendored
2
.github/workflows/tidelift.yml
vendored
|
@ -17,7 +17,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Scan
|
||||
uses: tidelift/alignment-action@main
|
||||
env:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: f1d4e742c91dd5179d742b0db9293c4472b765f8 # frozen: 21.12b0
|
||||
rev: fc0be6eb1e2a96091e6f64009ee5e9081bf8b6c6 # frozen: 22.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
args: ["--target-version", "py37"]
|
||||
|
@ -19,7 +19,7 @@ repos:
|
|||
- id: yesqa
|
||||
|
||||
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
||||
rev: 3592548bbd98528887eeed63486cf6c9bae00b98 # frozen: v1.1.10
|
||||
rev: ca52c4245639abd55c970e6bbbca95cab3de22d8 # frozen: v1.1.13
|
||||
hooks:
|
||||
- id: remove-tabs
|
||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$)
|
||||
|
|
|
@ -1,2 +1,8 @@
|
|||
version: 2
|
||||
|
||||
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)
|
||||
------------------
|
||||
|
||||
- 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
|
||||
[radarhere, hugovk]
|
||||
|
||||
|
|
53
Makefile
53
Makefile
|
@ -9,9 +9,11 @@ clean:
|
|||
|
||||
.PHONY: 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
|
||||
coverage report
|
||||
python3 -c "import coverage" > /dev/null 2>&1 || python3 -m pip install coverage
|
||||
python3 -m coverage report
|
||||
|
||||
.PHONY: doc
|
||||
doc:
|
||||
|
@ -33,20 +35,16 @@ help:
|
|||
@echo "Welcome to Pillow development. Please use \`make <target>\` where <target> is one of"
|
||||
@echo " clean remove build products"
|
||||
@echo " coverage run coverage test (in progress)"
|
||||
@echo " doc make html docs"
|
||||
@echo " docserve run an http server on the docs directory"
|
||||
@echo " doc make HTML docs"
|
||||
@echo " docserve run an HTTP server on the docs directory"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " inplace make inplace extension"
|
||||
@echo " install make and install"
|
||||
@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-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 " 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"
|
||||
@echo " test run tests on installed Pillow"
|
||||
|
||||
.PHONY: inplace
|
||||
inplace: clean
|
||||
|
@ -70,28 +68,17 @@ debug:
|
|||
make clean > /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
|
||||
release-test:
|
||||
$(MAKE) install-req
|
||||
python3 -m pip install -e .
|
||||
python3 -m pip install -e .[tests]
|
||||
python3 selftest.py
|
||||
python3 -m pytest Tests
|
||||
python3 -m pip install .
|
||||
-rm dist/*.egg
|
||||
-rmdir dist
|
||||
python3 -m pytest -qq
|
||||
check-manifest
|
||||
pyroma .
|
||||
python3 -m check-manifest
|
||||
python3 -m pyroma .
|
||||
$(MAKE) readme
|
||||
|
||||
.PHONY: sdist
|
||||
|
@ -101,26 +88,30 @@ sdist:
|
|||
|
||||
.PHONY: test
|
||||
test:
|
||||
pytest -qq
|
||||
python3 -c "import pytest" > /dev/null 2>&1 || python3 -m pip install pytest
|
||||
python3 -m pytest -qq
|
||||
|
||||
.PHONY: 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 \
|
||||
--log-file=/tmp/valgrind-output \
|
||||
python3 -m pytest --no-memcheck -vv --valgrind --valgrind-log=/tmp/valgrind-output
|
||||
|
||||
.PHONY: 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
|
||||
lint:
|
||||
tox --help > /dev/null || python3 -m pip install tox
|
||||
tox -e lint
|
||||
python3 -c "import tox" > /dev/null 2>&1 || python3 -m pip install tox
|
||||
python3 -m tox -e lint
|
||||
|
||||
.PHONY: lint-fix
|
||||
lint-fix:
|
||||
black --target-version py37 .
|
||||
isort .
|
||||
python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black
|
||||
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
|
||||
|
||||
if sys.maxsize < 2 ** 32:
|
||||
if sys.maxsize < 2**32:
|
||||
im = Image.new("L", (999999, 999999), 0)
|
||||
|
|
|
@ -23,7 +23,7 @@ YDIM = 32769
|
|||
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):
|
||||
|
|
|
@ -19,7 +19,7 @@ YDIM = 32769
|
|||
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):
|
||||
|
|
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 pytest
|
||||
import warnings
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
@ -20,16 +19,14 @@ def test_bad():
|
|||
either"""
|
||||
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:
|
||||
with Image.open(f) as im:
|
||||
im.load()
|
||||
except Exception: # as msg:
|
||||
pass
|
||||
|
||||
# Assert that there is no unclosed file warning
|
||||
assert not record
|
||||
|
||||
|
||||
def test_questionable():
|
||||
"""These shouldn't crash/dos, but it's not well defined that these
|
||||
|
|
|
@ -110,9 +110,9 @@ class TestCoreMemory:
|
|||
|
||||
with pytest.raises(ValueError):
|
||||
Image.core.set_blocks_max(-1)
|
||||
if sys.maxsize < 2 ** 32:
|
||||
if sys.maxsize < 2**32:
|
||||
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")
|
||||
def test_set_blocks_max_stats(self):
|
||||
|
|
|
@ -2,7 +2,12 @@ import pytest
|
|||
|
||||
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():
|
||||
|
@ -25,6 +30,28 @@ def test_load_blp2_dxt1a():
|
|||
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(
|
||||
"test_file",
|
||||
[
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import DcxImagePlugin, Image
|
||||
|
@ -31,21 +33,17 @@ def test_unclosed_file():
|
|||
|
||||
|
||||
def test_closed_file():
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
im = Image.open(TEST_FILE)
|
||||
im.load()
|
||||
im.close()
|
||||
|
||||
assert not record
|
||||
|
||||
|
||||
def test_context_manager():
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
with Image.open(TEST_FILE) as im:
|
||||
im.load()
|
||||
|
||||
assert not record
|
||||
|
||||
|
||||
def test_invalid_file():
|
||||
with open("Tests/images/flower.jpg", "rb") as fp:
|
||||
|
|
|
@ -196,6 +196,13 @@ def test__accept_false():
|
|||
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():
|
||||
"""Check a short header"""
|
||||
with open(TEST_FILE_DXT5, "rb") as f:
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import FliImagePlugin, Image
|
||||
|
@ -38,21 +40,17 @@ def test_unclosed_file():
|
|||
|
||||
|
||||
def test_closed_file():
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
im = Image.open(static_test_file)
|
||||
im.load()
|
||||
im.close()
|
||||
|
||||
assert not record
|
||||
|
||||
|
||||
def test_context_manager():
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
with Image.open(static_test_file) as im:
|
||||
im.load()
|
||||
|
||||
assert not record
|
||||
|
||||
|
||||
def test_tell():
|
||||
# Arrange
|
||||
|
|
|
@ -16,6 +16,13 @@ def test_load_dxt1():
|
|||
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():
|
||||
for enum, prefix in {
|
||||
FtexImagePlugin.Format: "FORMAT_",
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import warnings
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
@ -39,21 +40,17 @@ def test_unclosed_file():
|
|||
|
||||
|
||||
def test_closed_file():
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
im = Image.open(TEST_GIF)
|
||||
im.load()
|
||||
im.close()
|
||||
|
||||
assert not record
|
||||
|
||||
|
||||
def test_context_manager():
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
with Image.open(TEST_GIF) as im:
|
||||
im.load()
|
||||
|
||||
assert not record
|
||||
|
||||
|
||||
def test_invalid_file():
|
||||
invalid_file = "Tests/images/flower.jpg"
|
||||
|
@ -62,6 +59,17 @@ def test_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_grayscale(optimize):
|
||||
im = Image.new("L", (1, 1), 0)
|
||||
|
@ -311,6 +319,22 @@ def test_n_frames():
|
|||
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():
|
||||
with Image.open(TEST_GIF) as im:
|
||||
n_frames = im.n_frames
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import io
|
||||
import os
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -19,9 +20,8 @@ def test_sanity():
|
|||
with Image.open(TEST_FILE) as im:
|
||||
|
||||
# Assert that there is no unclosed file warning
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
im.load()
|
||||
assert not record
|
||||
|
||||
assert im.mode == "RGBA"
|
||||
assert im.size == (1024, 1024)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import filecmp
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -35,21 +36,17 @@ def test_unclosed_file():
|
|||
|
||||
|
||||
def test_closed_file():
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
im = Image.open(TEST_IM)
|
||||
im.load()
|
||||
im.close()
|
||||
|
||||
assert not record
|
||||
|
||||
|
||||
def test_context_manager():
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
with Image.open(TEST_IM) as im:
|
||||
im.load()
|
||||
|
||||
assert not record
|
||||
|
||||
|
||||
def test_tell():
|
||||
# Arrange
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import re
|
||||
import warnings
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
@ -756,9 +757,8 @@ class TestFileJpeg:
|
|||
assert exif[282] == 180
|
||||
|
||||
out = str(tmp_path / "out.jpg")
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
im.save(out, exif=exif)
|
||||
assert not record
|
||||
|
||||
with Image.open(out) as reloaded:
|
||||
assert reloaded.getexif()[282] == 180
|
||||
|
|
|
@ -218,7 +218,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
values = {
|
||||
2: "test",
|
||||
3: 1,
|
||||
4: 2 ** 20,
|
||||
4: 2**20,
|
||||
5: TiffImagePlugin.IFDRational(100, 1),
|
||||
12: 1.05,
|
||||
}
|
||||
|
@ -1019,7 +1019,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
im = hopper("RGB").resize((256, 256))
|
||||
out = str(tmp_path / "temp.tif")
|
||||
|
||||
TiffImagePlugin.STRIP_SIZE = 2 ** 18
|
||||
TiffImagePlugin.STRIP_SIZE = 2**18
|
||||
try:
|
||||
|
||||
im.save(out, compression="tiff_adobe_deflate")
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import warnings
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
@ -41,21 +42,17 @@ def test_unclosed_file():
|
|||
|
||||
|
||||
def test_closed_file():
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
im = Image.open(test_files[0])
|
||||
im.load()
|
||||
im.close()
|
||||
|
||||
assert not record
|
||||
|
||||
|
||||
def test_context_manager():
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
with Image.open(test_files[0]) as im:
|
||||
im.load()
|
||||
|
||||
assert not record
|
||||
|
||||
|
||||
def test_app():
|
||||
for test_file in test_files:
|
||||
|
@ -88,6 +85,9 @@ def test_frame_size():
|
|||
im.seek(1)
|
||||
assert im.size == (680, 480)
|
||||
|
||||
im.seek(0)
|
||||
assert im.size == (640, 480)
|
||||
|
||||
|
||||
def test_ignore_frame_size():
|
||||
# Ignore the different size of the second frame
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import re
|
||||
import sys
|
||||
import warnings
|
||||
import zlib
|
||||
from io import BytesIO
|
||||
|
||||
|
@ -331,9 +332,8 @@ class TestFilePng:
|
|||
|
||||
with Image.open(TEST_PNG_FILE) as im:
|
||||
# Assert that there is no unclosed file warning
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
im.verify()
|
||||
assert not record
|
||||
|
||||
with Image.open(TEST_PNG_FILE) as im:
|
||||
im.load()
|
||||
|
|
|
@ -3,7 +3,7 @@ from io import BytesIO
|
|||
|
||||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
from PIL import Image, UnidentifiedImageError
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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):
|
||||
path = str(tmp_path / "temp.pgm")
|
||||
with open(path, "w") as f:
|
||||
f.write("P6")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
with pytest.raises(ValueError) as e:
|
||||
with Image.open(path):
|
||||
pass
|
||||
|
||||
assert str(e.value) == "Reached EOF while reading header"
|
||||
|
||||
|
||||
def test_neg_ppm():
|
||||
# Storage.c accepted negative values for xsize, ysize. the
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, PsdImagePlugin
|
||||
|
@ -29,21 +31,17 @@ def test_unclosed_file():
|
|||
|
||||
|
||||
def test_closed_file():
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
im = Image.open(test_file)
|
||||
im.load()
|
||||
im.close()
|
||||
|
||||
assert not record
|
||||
|
||||
|
||||
def test_context_manager():
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
with Image.open(test_file) as im:
|
||||
im.load()
|
||||
|
||||
assert not record
|
||||
|
||||
|
||||
def test_invalid_file():
|
||||
invalid_file = "Tests/images/flower.jpg"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import tempfile
|
||||
import warnings
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
@ -28,21 +29,17 @@ def test_unclosed_file():
|
|||
|
||||
|
||||
def test_closed_file():
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
im = Image.open(TEST_FILE)
|
||||
im.load()
|
||||
im.close()
|
||||
|
||||
assert not record
|
||||
|
||||
|
||||
def test_context_manager():
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
with Image.open(TEST_FILE) as im:
|
||||
im.load()
|
||||
|
||||
assert not record
|
||||
|
||||
|
||||
def test_save(tmp_path):
|
||||
# Arrange
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, TarIO, features
|
||||
|
@ -31,16 +33,12 @@ def test_unclosed_file():
|
|||
|
||||
|
||||
def test_close():
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
tar = TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg")
|
||||
tar.close()
|
||||
|
||||
assert not record
|
||||
|
||||
|
||||
def test_contextmanager():
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
with TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg"):
|
||||
pass
|
||||
|
||||
assert not record
|
||||
|
|
|
@ -97,6 +97,11 @@ def test_id_field_rle():
|
|||
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):
|
||||
test_file = "Tests/images/tga_id_field.tga"
|
||||
with Image.open(test_file) as im:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import warnings
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
@ -64,20 +65,16 @@ class TestFileTiff:
|
|||
pytest.warns(ResourceWarning, open)
|
||||
|
||||
def test_closed_file(self):
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
im = Image.open("Tests/images/multipage.tiff")
|
||||
im.load()
|
||||
im.close()
|
||||
|
||||
assert not record
|
||||
|
||||
def test_context_manager(self):
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
with Image.open("Tests/images/multipage.tiff") as im:
|
||||
im.load()
|
||||
|
||||
assert not record
|
||||
|
||||
def test_mac_tiff(self):
|
||||
# Read RGBa images from macOS [@PIL136]
|
||||
|
||||
|
|
|
@ -258,7 +258,7 @@ def test_ifd_unsigned_rational(tmp_path):
|
|||
im = hopper()
|
||||
info = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
|
||||
max_long = 2 ** 32 - 1
|
||||
max_long = 2**32 - 1
|
||||
|
||||
# 4 bytes unsigned long
|
||||
numerator = max_long
|
||||
|
@ -290,8 +290,8 @@ def test_ifd_signed_rational(tmp_path):
|
|||
info = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
|
||||
# pair of 4 byte signed longs
|
||||
numerator = 2 ** 31 - 1
|
||||
denominator = -(2 ** 31)
|
||||
numerator = 2**31 - 1
|
||||
denominator = -(2**31)
|
||||
|
||||
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 denominator == reloaded.tag_v2[37380].denominator
|
||||
|
||||
numerator = -(2 ** 31)
|
||||
denominator = 2 ** 31 - 1
|
||||
numerator = -(2**31)
|
||||
denominator = 2**31 - 1
|
||||
|
||||
info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
|
||||
|
||||
|
@ -315,7 +315,7 @@ def test_ifd_signed_rational(tmp_path):
|
|||
assert denominator == reloaded.tag_v2[37380].denominator
|
||||
|
||||
# out of bounds of 4 byte signed long
|
||||
numerator = -(2 ** 31) - 1
|
||||
numerator = -(2**31) - 1
|
||||
denominator = 1
|
||||
|
||||
info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
|
||||
|
@ -324,7 +324,7 @@ def test_ifd_signed_rational(tmp_path):
|
|||
im.save(out, tiffinfo=info, compression="raw")
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import io
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -127,7 +128,7 @@ class TestFileWebp:
|
|||
|
||||
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):
|
||||
temp_file = str(tmp_path / "temp.webp")
|
||||
im = Image.new("RGB", (15000, 15000))
|
||||
|
@ -161,9 +162,8 @@ class TestFileWebp:
|
|||
file_path = "Tests/images/hopper.webp"
|
||||
with Image.open(file_path) as image:
|
||||
temp_file = str(tmp_path / "temp.webp")
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
image.save(temp_file)
|
||||
assert not record
|
||||
|
||||
def test_file_pointer_could_be_reused(self):
|
||||
file_path = "Tests/images/hopper.webp"
|
||||
|
|
|
@ -2,7 +2,7 @@ from io import BytesIO
|
|||
|
||||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
from PIL import Image, XbmImagePlugin
|
||||
|
||||
from .helper import hopper
|
||||
|
||||
|
@ -63,6 +63,13 @@ def test_open_filename_with_underscore():
|
|||
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):
|
||||
im = hopper()
|
||||
out = str(tmp_path / "temp.xbm")
|
||||
|
|
|
@ -3,6 +3,7 @@ import os
|
|||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -648,9 +649,8 @@ class TestImage:
|
|||
|
||||
# Act/Assert
|
||||
with Image.open(test_file) as im:
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
im.save(temp_file)
|
||||
assert not record
|
||||
|
||||
def test_load_on_nonexclusive_multiframe(self):
|
||||
with open("Tests/images/frozenpond.mpo", "rb") as fp:
|
||||
|
|
|
@ -154,14 +154,17 @@ class TestImageGetPixel(AccessTest):
|
|||
|
||||
# Check 0
|
||||
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)
|
||||
with pytest.raises(IndexError):
|
||||
with pytest.raises(error):
|
||||
im.getpixel((0, 0))
|
||||
# Check 0 negative index
|
||||
with pytest.raises(IndexError):
|
||||
with pytest.raises(error):
|
||||
im.putpixel((-1, -1), c)
|
||||
with pytest.raises(IndexError):
|
||||
with pytest.raises(error):
|
||||
im.getpixel((-1, -1))
|
||||
|
||||
# check initial color
|
||||
|
@ -176,10 +179,10 @@ class TestImageGetPixel(AccessTest):
|
|||
|
||||
# Check 0
|
||||
im = Image.new(mode, (0, 0), c)
|
||||
with pytest.raises(IndexError):
|
||||
with pytest.raises(error):
|
||||
im.getpixel((0, 0))
|
||||
# Check 0 negative index
|
||||
with pytest.raises(IndexError):
|
||||
with pytest.raises(error):
|
||||
im.getpixel((-1, -1))
|
||||
|
||||
def test_basic(self):
|
||||
|
@ -205,10 +208,10 @@ class TestImageGetPixel(AccessTest):
|
|||
# see https://github.com/python-pillow/Pillow/issues/452
|
||||
# pixelaccess is using signed int* instead of uint*
|
||||
for mode in ("I;16", "I;16B"):
|
||||
self.check(mode, 2 ** 15 - 1)
|
||||
self.check(mode, 2 ** 15)
|
||||
self.check(mode, 2 ** 15 + 1)
|
||||
self.check(mode, 2 ** 16 - 1)
|
||||
self.check(mode, 2**15 - 1)
|
||||
self.check(mode, 2**15)
|
||||
self.check(mode, 2**15 + 1)
|
||||
self.check(mode, 2**16 - 1)
|
||||
|
||||
def test_p_putpixel_rgb_rgba(self):
|
||||
for color in [(255, 0, 0), (255, 0, 0, 255)]:
|
||||
|
@ -386,7 +389,7 @@ class TestImagePutPixelError(AccessTest):
|
|||
def test_putpixel_overflow_error(self, mode):
|
||||
im = hopper(mode)
|
||||
with pytest.raises(OverflowError):
|
||||
im.putpixel((0, 0), 2 ** 80)
|
||||
im.putpixel((0, 0), 2**80)
|
||||
|
||||
def test_putpixel_unrecognized_mode(self):
|
||||
im = hopper("BGR;15")
|
||||
|
|
|
@ -70,6 +70,11 @@ def test_16bit():
|
|||
with Image.open("Tests/images/16bit.cropped.tif") as 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():
|
||||
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")
|
||||
|
||||
im_la = im.convert("LA")
|
||||
assert "transparency" not in im_la.info
|
||||
im_la.save(f)
|
||||
|
||||
im_rgb = im.convert("RGB")
|
||||
assert im_rgb.info["transparency"] == (128, 128, 128) # undone
|
||||
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
|
||||
def gradient_RGBA(self):
|
||||
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):
|
||||
for mode in ("RGBA", "RGB", "L"):
|
||||
im = Image.new(mode, (200, 200), "white")
|
||||
|
|
|
@ -38,7 +38,7 @@ def test_long_integers():
|
|||
assert put(0xFFFFFFFF) == (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)
|
||||
else:
|
||||
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):
|
||||
# Helper function to reduce precision of tuples of floats
|
||||
# recursively and then check equality.
|
||||
power = 10 ** digits
|
||||
power = 10**digits
|
||||
|
||||
def truncate_tuple(tuple_or_float):
|
||||
return tuple(
|
||||
|
|
|
@ -124,6 +124,23 @@ class TestImageFile:
|
|||
with pytest.raises(OSError):
|
||||
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):
|
||||
b = BytesIO(
|
||||
b"BM000000000000" # head_data
|
||||
|
@ -179,6 +196,14 @@ class MockPyDecoder(ImageFile.PyDecoder):
|
|||
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
|
||||
|
||||
|
||||
|
@ -190,53 +215,58 @@ class MockImageFile(ImageFile.ImageFile):
|
|||
self.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)]
|
||||
|
||||
|
||||
class TestPyDecoder:
|
||||
def get_decoder(self):
|
||||
decoder = MockPyDecoder(None)
|
||||
class CodecsTest:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.decoder = MockPyDecoder(None)
|
||||
cls.encoder = MockPyEncoder(None)
|
||||
|
||||
def closure(mode, *args):
|
||||
decoder.__init__(mode, *args)
|
||||
return decoder
|
||||
def decoder_closure(mode, *args):
|
||||
cls.decoder.__init__(mode, *args)
|
||||
return cls.decoder
|
||||
|
||||
Image.register_decoder("MOCK", closure)
|
||||
return decoder
|
||||
def encoder_closure(mode, *args):
|
||||
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):
|
||||
buf = BytesIO(b"\x00" * 255)
|
||||
|
||||
im = MockImageFile(buf)
|
||||
d = self.get_decoder()
|
||||
|
||||
im.load()
|
||||
|
||||
assert d.state.xoff == xoff
|
||||
assert d.state.yoff == yoff
|
||||
assert d.state.xsize == xsize
|
||||
assert d.state.ysize == ysize
|
||||
assert self.decoder.state.xoff == xoff
|
||||
assert self.decoder.state.yoff == yoff
|
||||
assert self.decoder.state.xsize == xsize
|
||||
assert self.decoder.state.ysize == ysize
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
d.set_as_raw(b"\x00")
|
||||
self.decoder.set_as_raw(b"\x00")
|
||||
|
||||
def test_extents_none(self):
|
||||
buf = BytesIO(b"\x00" * 255)
|
||||
|
||||
im = MockImageFile(buf)
|
||||
im.tile = [("MOCK", None, 32, None)]
|
||||
d = self.get_decoder()
|
||||
|
||||
im.load()
|
||||
|
||||
assert d.state.xoff == 0
|
||||
assert d.state.yoff == 0
|
||||
assert d.state.xsize == 200
|
||||
assert d.state.ysize == 200
|
||||
assert self.decoder.state.xoff == 0
|
||||
assert self.decoder.state.yoff == 0
|
||||
assert self.decoder.state.xsize == 200
|
||||
assert self.decoder.state.ysize == 200
|
||||
|
||||
def test_negsize(self):
|
||||
buf = BytesIO(b"\x00" * 255)
|
||||
|
||||
im = MockImageFile(buf)
|
||||
im.tile = [("MOCK", (xoff, yoff, -10, yoff + ysize), 32, None)]
|
||||
self.get_decoder()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
im.load()
|
||||
|
@ -250,7 +280,6 @@ class TestPyDecoder:
|
|||
|
||||
im = MockImageFile(buf)
|
||||
im.tile = [("MOCK", (xoff, yoff, xoff + xsize + 100, yoff + ysize), 32, None)]
|
||||
self.get_decoder()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
im.load()
|
||||
|
@ -259,14 +288,92 @@ class TestPyDecoder:
|
|||
with pytest.raises(ValueError):
|
||||
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)
|
||||
|
||||
im = MockImageFile(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)
|
||||
fp = BytesIO()
|
||||
ImageFile._save(
|
||||
im, fp, [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 0, "RGB")]
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
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):
|
||||
txt = "Hello World!"
|
||||
ttf = ImageFont.truetype(font, FONT_SIZE, layout_engine=self.LAYOUT_ENGINE)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import ImageQt
|
||||
|
@ -30,10 +32,10 @@ def test_rgb():
|
|||
|
||||
def checkrgb(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 >> 8) % 2 ** 8) == g
|
||||
assert val % 2 ** 8 == b
|
||||
assert ((val >> 8) % 2**8) == g
|
||||
assert val % 2**8 == b
|
||||
|
||||
checkrgb(0, 0, 0)
|
||||
checkrgb(255, 0, 0)
|
||||
|
@ -56,7 +58,5 @@ def test_image():
|
|||
|
||||
|
||||
def test_closed_file():
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
ImageQt.ImageQt("Tests/images/hopper.gif")
|
||||
|
||||
assert not record
|
||||
|
|
|
@ -51,8 +51,8 @@ def test_constant():
|
|||
st = ImageStat.Stat(im)
|
||||
|
||||
assert st.extrema[0] == (128, 128)
|
||||
assert st.sum[0] == 128 ** 3
|
||||
assert st.sum2[0] == 128 ** 4
|
||||
assert st.sum[0] == 128**3
|
||||
assert st.sum2[0] == 128**4
|
||||
assert st.mean[0] == 128
|
||||
assert st.median[0] == 128
|
||||
assert st.rms[0] == 128
|
||||
|
|
|
@ -36,7 +36,7 @@ def test_tobytes():
|
|||
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():
|
||||
numpy = pytest.importorskip("numpy", reason="NumPy not installed")
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
|
@ -237,6 +239,5 @@ def test_no_resource_warning_for_numpy_array():
|
|||
with Image.open(test_file) as im:
|
||||
|
||||
# Act/Assert
|
||||
with pytest.warns(None) as record:
|
||||
with warnings.catch_warnings():
|
||||
array(im)
|
||||
assert not record
|
||||
|
|
|
@ -115,6 +115,6 @@ def test_pdf_repr():
|
|||
assert pdf_repr(True) == b"true"
|
||||
assert pdf_repr(False) == b"false"
|
||||
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(PdfBinary(b"\x90\x1F\xA0")) == b"<901FA0>"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
SPHINXBUILD = python3 -m sphinx.cmd.build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
|
@ -41,38 +41,48 @@ help:
|
|||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
install-sphinx:
|
||||
python3 -c "import sphinx" > /dev/null 2>&1 || python3 -m pip install sphinx
|
||||
|
||||
html:
|
||||
$(MAKE) install-sphinx
|
||||
$(SPHINXBUILD) -b html -W --keep-going $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(MAKE) install-sphinx
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(MAKE) install-sphinx
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(MAKE) install-sphinx
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(MAKE) install-sphinx
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(MAKE) install-sphinx
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(MAKE) install-sphinx
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
|
@ -82,6 +92,7 @@ qthelp:
|
|||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PillowPILfork.qhc"
|
||||
|
||||
devhelp:
|
||||
$(MAKE) install-sphinx
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
|
@ -91,11 +102,13 @@ devhelp:
|
|||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(MAKE) install-sphinx
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(MAKE) install-sphinx
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
|
@ -103,22 +116,26 @@ latex:
|
|||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(MAKE) install-sphinx
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(MAKE) install-sphinx
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(MAKE) install-sphinx
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(MAKE) install-sphinx
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
|
@ -126,28 +143,33 @@ texinfo:
|
|||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(MAKE) install-sphinx
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(MAKE) install-sphinx
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(MAKE) install-sphinx
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(MAKE) install-sphinx
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck -j auto
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(MAKE) install-sphinx
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
|
|
@ -210,7 +210,9 @@ class DdsImageFile(ImageFile.ImageFile):
|
|||
format_description = "DirectDraw Surface"
|
||||
|
||||
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:
|
||||
raise OSError(f"Unsupported header size {repr(header_size)}")
|
||||
header_bytes = self.fp.read(header_size - 4)
|
||||
|
|
|
@ -8,4 +8,4 @@ Appendices
|
|||
|
||||
image-file-formats
|
||||
text-anchors
|
||||
writing-your-own-file-decoder
|
||||
writing-your-own-image-plugin
|
||||
|
|
|
@ -26,6 +26,20 @@ Fully supported formats
|
|||
|
||||
.. 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
|
||||
^^^
|
||||
|
||||
|
@ -1042,13 +1056,6 @@ Pillow reads and writes X bitmap files (mode ``1``).
|
|||
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
|
||||
^^^
|
||||
|
||||
|
|
|
@ -4,10 +4,9 @@ Writing Your Own Image Plugin
|
|||
=============================
|
||||
|
||||
Pillow uses a plugin model which allows you to add your own
|
||||
decoders to the library, without any changes to the library
|
||||
itself. Such plugins usually have names like
|
||||
:file:`XxxImagePlugin.py`, where ``Xxx`` is a unique format name
|
||||
(usually an abbreviation).
|
||||
decoders and encoders to the library, without any changes to the library
|
||||
itself. Such plugins usually have names like :file:`XxxImagePlugin.py`,
|
||||
where ``Xxx`` is a unique format name (usually an abbreviation).
|
||||
|
||||
.. warning:: Pillow >= 2.1.0 no longer automatically imports any file
|
||||
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``
|
||||
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
|
||||
given region in the image. In most cases, only a single descriptor is used,
|
||||
covering the full image.
|
||||
given region in the 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::
|
||||
|
||||
|
@ -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
|
||||
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
|
||||
back to a function named ``[decodername]_decoder`` on the internal
|
||||
core image object. That function is called with the ``args`` tuple
|
||||
from the ``tile`` setup in the ``_open`` method.
|
||||
1. Setup: Pillow looks for a function in the decoder or encoder registry,
|
||||
falling back to a function named ``[codecname]_decoder`` or
|
||||
``[codecname]_encoder`` on the internal core image object. That function is
|
||||
called with the ``args`` tuple from the ``tile``.
|
||||
|
||||
2. Decoding: The decoder's decode function is repeatedly called with
|
||||
chunks of image data.
|
||||
2. Transforming: The codec's ``decode`` or ``encode`` function is repeatedly
|
||||
called with chunks of image data.
|
||||
|
||||
3. Cleanup: If the decoder has registered a cleanup function, it will
|
||||
be called at the end of the decoding process, even if there was an
|
||||
3. Cleanup: If the codec has registered a cleanup function, it will
|
||||
be called at the end of the transformation process, even if there was an
|
||||
exception raised.
|
||||
|
||||
|
||||
Setup
|
||||
-----
|
||||
|
||||
The current conventions are that the decoder setup function is named
|
||||
``PyImaging_[Decodername]DecoderNew`` and defined in ``decode.c``. The
|
||||
python binding for it is named ``[decodername]_decoder`` and is setup
|
||||
from within the ``_imaging.c`` file in the codecs section of the
|
||||
function array.
|
||||
The current conventions are that the codec setup function is named
|
||||
``PyImaging_[codecname]DecoderNew`` or ``PyImaging_[codecname]EncoderNew``
|
||||
and defined in ``decode.c`` or ``encode.c``. The Python binding for it is
|
||||
named ``[codecname]_decoder`` or ``[codecname]_encoder`` and is set up from
|
||||
within the ``_imaging.c`` file in the codecs section of the function array.
|
||||
|
||||
The setup function needs to call ``PyImaging_DecoderNew`` and at the
|
||||
very least, set the ``decode`` function pointer. The fields of
|
||||
interest in this object are:
|
||||
The setup function needs to call ``PyImaging_DecoderNew`` or
|
||||
``PyImaging_EncoderNew`` and at the very least, set the ``decode`` or
|
||||
``encode`` function pointer. The fields of interest in this object are:
|
||||
|
||||
**decode**
|
||||
Function pointer to the decode function, which has access to
|
||||
``im``, ``state``, and the buffer of data to be added to the image.
|
||||
**decode**/**encode**
|
||||
Function pointer to the decode or encode function, which has access to
|
||||
``im``, ``state``, and the buffer of data to be transformed.
|
||||
|
||||
**cleanup**
|
||||
Function pointer to the cleanup function, has access to ``state``.
|
||||
|
@ -370,36 +373,34 @@ interest in this object are:
|
|||
|
||||
**state**
|
||||
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.
|
||||
|
||||
**pulls_fd**
|
||||
**EXPERIMENTAL** -- **WARNING**, interface may change. If set to 1,
|
||||
``state->fd`` will be a pointer to the Python file like object. The
|
||||
decoder may use the functions in ``codec_fd.c`` to read directly
|
||||
from the file like object rather than have the data pushed through a
|
||||
buffer. Note that this implementation may be refactored until this
|
||||
warning is removed.
|
||||
**pulls_fd**/**pushes_fd**
|
||||
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 codec may
|
||||
use the functions in ``codec_fd.c`` to read or write directly with the file
|
||||
like object rather than have the data pushed through a buffer.
|
||||
|
||||
.. versionadded:: 3.3.0
|
||||
|
||||
|
||||
Decoding
|
||||
--------
|
||||
Transforming
|
||||
------------
|
||||
|
||||
The decode function is called with the target (core) image, the
|
||||
decoder state structure, and a buffer of data to be decoded.
|
||||
The decode or encode function is called with the target (core) image, the codec
|
||||
state structure, and a buffer of data to be transformed.
|
||||
|
||||
**Experimental** -- If ``pulls_fd`` is set, then the decode function
|
||||
is called once, with an empty buffer. It is the decoder's
|
||||
responsibility to decode the entire tile in that one call. The rest of
|
||||
this section only applies if ``pulls_fd`` is not set.
|
||||
It is the codec's responsibility to pull as much data as possible out of the
|
||||
buffer and return the number of bytes consumed. The next call to the codec will
|
||||
include the previous unconsumed tail. The codec function will be called
|
||||
multiple times as the data processed.
|
||||
|
||||
It is the decoder's responsibility to pull as much data as possible
|
||||
out of the buffer and return the number of bytes consumed. The next
|
||||
call to the decoder will include the previous unconsumed tail. The
|
||||
decoder function will be called multiple times as the data is read
|
||||
from the file like object.
|
||||
Alternatively, if ``pulls_fd`` or ``pushes_fd`` is set, then the decode or
|
||||
encode function is called once, with an empty buffer. It is the codec's
|
||||
responsibility to transform the entire tile in that one call. Using this will
|
||||
provide a codec with more freedom, but that freedom may mean increased memory
|
||||
usage if the entire tile is held in memory at once by the codec.
|
||||
|
||||
If an error occurs, set ``state->errcode`` and return -1.
|
||||
|
||||
|
@ -408,28 +409,49 @@ Return -1 on success, without setting the errcode.
|
|||
Cleanup
|
||||
-------
|
||||
|
||||
The cleanup function is called after the decoder returns a negative
|
||||
value, or if there is a read error from the file. This function should
|
||||
free any allocated memory and release any resources from external
|
||||
libraries.
|
||||
The cleanup function is called after the codec returns a negative
|
||||
value, or if there is an error. This function should free any allocated
|
||||
memory and release any resources from external 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
|
||||
:py:class:`PIL.ImageFile.PyDecoder` and should at least override the
|
||||
decode method. File decoders should be registered using
|
||||
:py:meth:`PIL.Image.register_decoder`. As in the C implementation of
|
||||
the file decoders, there are three stages in the lifetime of a
|
||||
Python-based file decoder:
|
||||
Python file decoders and encoders should derive from
|
||||
:py:class:`PIL.ImageFile.PyDecoder` and :py:class:`PIL.ImageFile.PyEncoder`
|
||||
respectively, and should at least override the decode or encode method.
|
||||
They should be registered using :py:meth:`PIL.Image.register_decoder` and
|
||||
:py:meth:`PIL.Image.register_encoder`. As in the C implementation of
|
||||
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.
|
||||
|
||||
2. Decoding: The decoder instance's ``decode`` method is repeatedly
|
||||
called with a buffer of data to be interpreted.
|
||||
2. Transforming: The instance's ``decode`` method is repeatedly called with
|
||||
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 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Gentoo | 3.9 | 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 |
|
||||
|
|
|
@ -40,8 +40,16 @@ Classes
|
|||
.. autoclass:: PIL.ImageFile.Parser()
|
||||
:members:
|
||||
|
||||
.. autoclass:: PIL.ImageFile.PyCodec()
|
||||
:members:
|
||||
|
||||
.. autoclass:: PIL.ImageFile.PyDecoder()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: PIL.ImageFile.PyEncoder()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: PIL.ImageFile.ImageFile()
|
||||
: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.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
|
||||
.. autoclass:: PIL.ImageShow.Viewer
|
||||
:member-order: bysource
|
||||
|
|
|
@ -14,6 +14,16 @@ for a region of an image.
|
|||
statistics. You can also pass in a previously calculated 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.
|
||||
|
||||
.. py:attribute:: extrema
|
||||
|
|
|
@ -6,7 +6,13 @@
|
|||
The PixelAccess class provides read and write access to
|
||||
: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
|
||||
-------
|
||||
|
@ -39,7 +45,7 @@ Access using negative indexes is also possible.
|
|||
|
||||
|
||||
:py:class:`PixelAccess` Class
|
||||
-----------------------------------
|
||||
-----------------------------
|
||||
|
||||
.. 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.
|
||||
|
||||
.. 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.
|
||||
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
|
||||
-------
|
||||
|
|
|
@ -53,7 +53,7 @@ Then ``sudo apt-get update && sudo apt-get install libtiff5-dbgsym``
|
|||
|
||||
virtualenv -p python3.8-dbg ~/vpy38-dbg
|
||||
source ~/vpy38-dbg/bin/activate
|
||||
cd ~/Pillow && pip install -r requirements.txt && make install
|
||||
cd ~/Pillow && make install
|
||||
|
||||
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
|
||||
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
|
||||
^^^^^^^^^^^^
|
||||
============
|
||||
|
||||
Constants
|
||||
~~~~~~~~~
|
||||
^^^^^^^^^
|
||||
|
||||
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.
|
||||
|
@ -87,7 +113,7 @@ Deprecated Use instead
|
|||
===================================================== ============================================================
|
||||
|
||||
ImageShow.Viewer.show_file file argument
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
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
|
||||
|
@ -98,7 +124,7 @@ In effect, ``viewer.show_file("test.jpg")`` will continue to work unchanged.
|
|||
``viewer.show_file(path="test.jpg")`` instead.
|
||||
|
||||
FitsStubImagePlugin
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. 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``
|
||||
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
|
||||
=============
|
||||
|
||||
|
@ -143,3 +176,10 @@ Image._repr_pretty_
|
|||
``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
|
||||
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]
|
||||
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]
|
||||
extend-ignore = E203
|
||||
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)
|
||||
# Alpine Linux uses musl that can't print cache
|
||||
args = ["/sbin/ldconfig", "-p"]
|
||||
expr = fr".*\({abi_type}.*\) => (.*)"
|
||||
expr = rf".*\({abi_type}.*\) => (.*)"
|
||||
env = dict(os.environ)
|
||||
env["LC_ALL"] = "C"
|
||||
env["LANG"] = "C"
|
||||
|
|
|
@ -29,6 +29,7 @@ BLP files come in many different flavours:
|
|||
- DXT5 compression is used if alpha_encoding == 7.
|
||||
"""
|
||||
|
||||
import os
|
||||
import struct
|
||||
import warnings
|
||||
from enum import IntEnum
|
||||
|
@ -266,6 +267,10 @@ class BLPFormatError(NotImplementedError):
|
|||
pass
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:4] in (b"BLP1", b"BLP2")
|
||||
|
||||
|
||||
class BlpImageFile(ImageFile.ImageFile):
|
||||
"""
|
||||
Blizzard Mipmap Format
|
||||
|
@ -276,50 +281,51 @@ class BlpImageFile(ImageFile.ImageFile):
|
|||
|
||||
def _open(self):
|
||||
self.magic = self.fp.read(4)
|
||||
self._read_blp_header()
|
||||
|
||||
if self.magic == b"BLP1":
|
||||
decoder = "BLP1"
|
||||
self.mode = "RGB"
|
||||
elif self.magic == b"BLP2":
|
||||
decoder = "BLP2"
|
||||
self.mode = "RGBA" if self._blp_alpha_depth else "RGB"
|
||||
self.fp.seek(5, os.SEEK_CUR)
|
||||
(self._blp_alpha_depth,) = struct.unpack("<b", self.fp.read(1))
|
||||
|
||||
self.fp.seek(2, os.SEEK_CUR)
|
||||
self._size = struct.unpack("<II", self.fp.read(8))
|
||||
|
||||
if self.magic in (b"BLP1", b"BLP2"):
|
||||
decoder = self.magic.decode()
|
||||
else:
|
||||
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))]
|
||||
|
||||
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):
|
||||
_pulls_fd = True
|
||||
|
||||
def decode(self, buffer):
|
||||
try:
|
||||
self.fd.seek(0)
|
||||
self.magic = self.fd.read(4)
|
||||
self._read_blp_header()
|
||||
self._load()
|
||||
except struct.error as e:
|
||||
raise OSError("Truncated Blp file") from e
|
||||
return 0, 0
|
||||
raise OSError("Truncated BLP file") from e
|
||||
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):
|
||||
return ImageFile._safe_read(self.fd, length)
|
||||
|
@ -334,23 +340,20 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
|
|||
ret.append((b, g, r, a))
|
||||
return ret
|
||||
|
||||
def _read_blp_header(self):
|
||||
(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._blp_mips,) = struct.unpack("<b", self._safe_read(1))
|
||||
|
||||
self.size = struct.unpack("<II", self._safe_read(8))
|
||||
|
||||
if self.magic == b"BLP1":
|
||||
# Only present for BLP1
|
||||
(self._blp_encoding,) = struct.unpack("<i", self._safe_read(4))
|
||||
(self._blp_subtype,) = struct.unpack("<i", self._safe_read(4))
|
||||
|
||||
self._blp_offsets = struct.unpack("<16I", self._safe_read(16 * 4))
|
||||
self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4))
|
||||
def _read_bgra(self, palette):
|
||||
data = bytearray()
|
||||
_data = BytesIO(self._safe_read(self._blp_lengths[0]))
|
||||
while True:
|
||||
try:
|
||||
(offset,) = struct.unpack("<B", _data.read(1))
|
||||
except struct.error:
|
||||
break
|
||||
b, g, r, a = palette[offset]
|
||||
d = (r, g, b)
|
||||
if self._blp_alpha_depth:
|
||||
d += (a,)
|
||||
data.extend(d)
|
||||
return data
|
||||
|
||||
|
||||
class BLP1Decoder(_BLPBaseDecoder):
|
||||
|
@ -360,17 +363,8 @@ class BLP1Decoder(_BLPBaseDecoder):
|
|||
|
||||
elif self._blp_compression == 1:
|
||||
if self._blp_encoding in (4, 5):
|
||||
data = bytearray()
|
||||
palette = self._read_palette()
|
||||
_data = BytesIO(self._safe_read(self._blp_lengths[0]))
|
||||
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])
|
||||
|
||||
data = self._read_bgra(palette)
|
||||
self.set_as_raw(bytes(data))
|
||||
else:
|
||||
raise BLPFormatError(
|
||||
|
@ -401,23 +395,16 @@ class BLP2Decoder(_BLPBaseDecoder):
|
|||
def _load(self):
|
||||
palette = self._read_palette()
|
||||
|
||||
data = bytearray()
|
||||
self.fd.seek(self._blp_offsets[0])
|
||||
|
||||
if self._blp_compression == 1:
|
||||
# Uncompressed or DirectX compression
|
||||
|
||||
if self._blp_encoding == Encoding.UNCOMPRESSED:
|
||||
_data = BytesIO(self._safe_read(self._blp_lengths[0]))
|
||||
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))
|
||||
data = self._read_bgra(palette)
|
||||
|
||||
elif self._blp_encoding == Encoding.DXT:
|
||||
data = bytearray()
|
||||
if self._blp_alpha_encoding == AlphaEncoding.DXT1:
|
||||
linesize = (self.size[0] + 3) // 4 * 8
|
||||
for yb in range((self.size[1] + 3) // 4):
|
||||
|
@ -452,12 +439,59 @@ class BLP2Decoder(_BLPBaseDecoder):
|
|||
self.set_as_raw(bytes(data))
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
return prefix[:4] in (b"BLP1", b"BLP2")
|
||||
class BLPEncoder(ImageFile.PyEncoder):
|
||||
_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_extension(BlpImageFile.format, ".blp")
|
||||
|
||||
Image.register_decoder("BLP1", BLP1Decoder)
|
||||
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"] = (
|
||||
i32(header_data, 4)
|
||||
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["bits"] = i16(header_data, 10)
|
||||
|
@ -322,7 +322,7 @@ def _save(im, fp, filename, bitmap_header=True):
|
|||
if bitmap_header:
|
||||
offset = 14 + header + colors * 4
|
||||
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")
|
||||
fp.write(
|
||||
b"BM" # file type (magic)
|
||||
|
|
|
@ -111,7 +111,9 @@ class DdsImageFile(ImageFile.ImageFile):
|
|||
format_description = "DirectDraw Surface"
|
||||
|
||||
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:
|
||||
raise OSError(f"Unsupported header size {repr(header_size)}")
|
||||
header_bytes = self.fp.read(header_size - 4)
|
||||
|
|
|
@ -26,7 +26,11 @@ from ._binary import o8
|
|||
|
||||
|
||||
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
|
||||
s = self.fp.read(128)
|
||||
if not (
|
||||
_accept(s)
|
||||
and i16(s, 14) in [0, 3] # flags
|
||||
and s[20:22] == b"\x00\x00" # reserved
|
||||
):
|
||||
if not (_accept(s) and s[20:22] == b"\x00\x00"):
|
||||
raise SyntaxError("not an FLI/FLC file")
|
||||
|
||||
# frames
|
||||
|
|
|
@ -94,7 +94,8 @@ class FtexImageFile(ImageFile.ImageFile):
|
|||
format_description = "Texture File Format (IW2:EOC)"
|
||||
|
||||
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
|
||||
self._size = 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):
|
||||
header_size = i32(self.fp.read(4))
|
||||
version = i32(self.fp.read(4))
|
||||
if header_size < 20:
|
||||
raise SyntaxError("not a GIMP brush")
|
||||
version = i32(self.fp.read(4))
|
||||
if version not in (1, 2):
|
||||
raise SyntaxError(f"Unsupported GIMP brush version: {version}")
|
||||
|
||||
|
|
|
@ -175,9 +175,15 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
if self.__frame == 1:
|
||||
self.pyaccess = None
|
||||
if "transparency" in self.info:
|
||||
self.mode = "RGBA"
|
||||
self.im.putpalettealpha(self.info["transparency"], 0)
|
||||
self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG)
|
||||
if self.mode == "P":
|
||||
self.im.putpalettealpha(self.info["transparency"], 0)
|
||||
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"]
|
||||
else:
|
||||
|
@ -315,7 +321,7 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
self.dispose = Image.core.fill(dispose_mode, dispose_size, color)
|
||||
else:
|
||||
# replace with previous contents
|
||||
if self.im:
|
||||
if self.im is not None:
|
||||
# only dispose the extent in this frame
|
||||
self.dispose = self._crop(self.im, self.dispose_extent)
|
||||
elif frame_transparency is not None:
|
||||
|
@ -382,15 +388,18 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
if self.__frame == 0:
|
||||
return
|
||||
if self._frame_transparency is not None:
|
||||
self.im.putpalettealpha(self._frame_transparency, 0)
|
||||
frame_im = self.im.convert("RGBA")
|
||||
if self.mode == "P":
|
||||
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:
|
||||
frame_im = self.im.convert("RGB")
|
||||
frame_im = self._crop(frame_im, self.dispose_extent)
|
||||
|
||||
self.im = self._prev_im
|
||||
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)
|
||||
else:
|
||||
self.im.paste(frame_im, self.dispose_extent)
|
||||
|
|
|
@ -38,7 +38,7 @@ class GimpPaletteFile:
|
|||
break
|
||||
|
||||
# skip fields and comment lines
|
||||
if re.match(br"\w+:|#", s):
|
||||
if re.match(rb"\w+:|#", s):
|
||||
continue
|
||||
if len(s) > 100:
|
||||
raise SyntaxError("bad palette file")
|
||||
|
|
|
@ -167,7 +167,7 @@ class IcnsFile:
|
|||
self.dct = dct = {}
|
||||
self.fobj = fobj
|
||||
sig, filesize = nextheader(fobj)
|
||||
if sig != MAGIC:
|
||||
if not _accept(sig):
|
||||
raise SyntaxError("not an icns file")
|
||||
i = HEADERSIZE
|
||||
while i < filesize:
|
||||
|
@ -287,7 +287,7 @@ class IcnsImageFile(ImageFile.ImageFile):
|
|||
)
|
||||
|
||||
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
|
||||
return px
|
||||
self.load_prepare()
|
||||
|
|
|
@ -304,7 +304,7 @@ class IcoImageFile(ImageFile.ImageFile):
|
|||
self._size = value
|
||||
|
||||
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
|
||||
return Image.Image.load(self)
|
||||
im = self.ico.getimage(self.size)
|
||||
|
|
|
@ -100,7 +100,7 @@ for i in range(2, 33):
|
|||
# --------------------------------------------------------------------
|
||||
# Read IM directory
|
||||
|
||||
split = re.compile(br"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$")
|
||||
split = re.compile(rb"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$")
|
||||
|
||||
|
||||
def number(s):
|
||||
|
|
|
@ -847,7 +847,7 @@ class Image:
|
|||
:returns: An image access object.
|
||||
: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
|
||||
mode, arr = self.palette.getdata()
|
||||
self.im.putpalette(mode, arr)
|
||||
|
@ -864,7 +864,7 @@ class Image:
|
|||
self.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 self.pyaccess:
|
||||
return self.pyaccess
|
||||
|
@ -975,7 +975,9 @@ class Image:
|
|||
delete_trns = False
|
||||
# transparency handling
|
||||
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
|
||||
# color to an alpha channel.
|
||||
new_im = self._new(
|
||||
|
@ -1492,11 +1494,12 @@ class Image:
|
|||
|
||||
def histogram(self, mask=None, extrema=None):
|
||||
"""
|
||||
Returns a histogram for the image. The histogram is returned as
|
||||
a list of pixel counts, one for each pixel value in the source
|
||||
image. If the image has more than one band, the histograms for
|
||||
all bands are concatenated (for example, the histogram for an
|
||||
"RGB" image contains 768 values).
|
||||
Returns a histogram for the image. The histogram is returned as a
|
||||
list of pixel counts, one for each pixel value in the source
|
||||
image. Counts are grouped into 256 bins for each band, even if
|
||||
the image has more than 8 bits per band. If the image has more
|
||||
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
|
||||
by this method.
|
||||
|
@ -1564,8 +1567,8 @@ class Image:
|
|||
also use color strings as supported by the ImageColor module.
|
||||
|
||||
If a mask is given, this method updates only the regions
|
||||
indicated by the mask. You can use either "1", "L" or "RGBA"
|
||||
images (in the latter case, the alpha band is used as mask).
|
||||
indicated by the mask. You can use either "1", "L", "LA", "RGBA"
|
||||
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
|
||||
the mask is 0, the current value is preserved. Intermediate
|
||||
values will mix the two images together, including their alpha
|
||||
|
@ -1613,7 +1616,7 @@ class Image:
|
|||
elif isImageType(im):
|
||||
im.load()
|
||||
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!
|
||||
im = im.convert(self.mode)
|
||||
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
|
||||
(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
|
||||
: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.
|
||||
If you have an entire image in a string, wrap it in a
|
||||
|
|
|
@ -49,7 +49,11 @@ ERRORS = {
|
|||
-8: "bad configuration",
|
||||
-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:
|
||||
seek(offset)
|
||||
decoder = Image._getdecoder(
|
||||
self.mode, decoder_name, args, self.decoderconfig
|
||||
)
|
||||
try:
|
||||
seek(offset)
|
||||
decoder.setimage(self.im, extents)
|
||||
if decoder.pulls_fd:
|
||||
decoder.setfd(self.fp)
|
||||
status, err_code = decoder.decode(b"")
|
||||
err_code = decoder.decode(b"")[1]
|
||||
else:
|
||||
b = prefix
|
||||
while True:
|
||||
|
@ -495,40 +499,33 @@ def _save(im, fp, tile, bufsize=0):
|
|||
try:
|
||||
fh = fp.fileno()
|
||||
fp.flush()
|
||||
except (AttributeError, io.UnsupportedOperation) as exc:
|
||||
# compress to Python file-compatible 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()
|
||||
exc = None
|
||||
except (AttributeError, io.UnsupportedOperation) as e:
|
||||
exc = e
|
||||
for e, b, o, a in tile:
|
||||
if o > 0:
|
||||
fp.seek(o)
|
||||
encoder = Image._getencoder(im.mode, e, a, im.encoderconfig)
|
||||
try:
|
||||
encoder.setimage(im.im, b)
|
||||
if encoder.pushes_fd:
|
||||
encoder.setfd(fp)
|
||||
l, s = encoder.encode_to_pyfd()
|
||||
else:
|
||||
while True:
|
||||
l, s, d = e.encode(bufsize)
|
||||
fp.write(d)
|
||||
if s:
|
||||
break
|
||||
if exc:
|
||||
# compress to Python file-compatible object
|
||||
while True:
|
||||
l, s, d = encoder.encode(bufsize)
|
||||
fp.write(d)
|
||||
if s:
|
||||
break
|
||||
else:
|
||||
# slight speedup: compress to real file object
|
||||
s = encoder.encode_to_file(fh, bufsize)
|
||||
if s < 0:
|
||||
raise OSError(f"encoder error {s} when writing image file") from exc
|
||||
e.cleanup()
|
||||
else:
|
||||
# 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()
|
||||
finally:
|
||||
encoder.cleanup()
|
||||
if hasattr(fp, "flush"):
|
||||
fp.flush()
|
||||
|
||||
|
@ -577,16 +574,7 @@ class PyCodecState:
|
|||
return (self.xoff, self.yoff, self.xoff + self.xsize, self.yoff + self.ysize)
|
||||
|
||||
|
||||
class PyDecoder:
|
||||
"""
|
||||
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
|
||||
|
||||
class PyCodec:
|
||||
def __init__(self, mode, *args):
|
||||
self.im = None
|
||||
self.state = PyCodecState()
|
||||
|
@ -596,31 +584,16 @@ class PyDecoder:
|
|||
|
||||
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
|
||||
:returns: None
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Override to perform decoder specific cleanup
|
||||
Override to perform codec specific cleanup
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
|
@ -628,16 +601,16 @@ class PyDecoder:
|
|||
|
||||
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
|
||||
"""
|
||||
self.fd = fd
|
||||
|
||||
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 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")
|
||||
|
||||
|
||||
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):
|
||||
"""
|
||||
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")
|
||||
if s[1] != 0:
|
||||
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
|
||||
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 encoding: Which font encoding to use (default is Unicode). Possible
|
||||
encodings include (see the FreeType documentation for more
|
||||
|
|
|
@ -25,7 +25,12 @@ _viewers = []
|
|||
|
||||
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 order:
|
||||
|
|
|
@ -91,7 +91,7 @@ class Stat:
|
|||
for i in range(0, len(self.h), 256):
|
||||
sum2 = 0.0
|
||||
for j in range(256):
|
||||
sum2 += (j ** 2) * float(self.h[i + j])
|
||||
sum2 += (j**2) * float(self.h[i + j])
|
||||
v.append(sum2)
|
||||
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,
|
||||
to floating-point dots per inch."""
|
||||
if denom != 0:
|
||||
return (254 * num * (10 ** exp)) / (10000 * denom)
|
||||
return (254 * num * (10**exp)) / (10000 * denom)
|
||||
|
||||
|
||||
def _parse_jp2_header(fp):
|
||||
|
|
|
@ -46,6 +46,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
|||
self._after_jpeg_open()
|
||||
|
||||
def _after_jpeg_open(self, mpheader=None):
|
||||
self._initial_size = self.size
|
||||
self.mpinfo = mpheader if mpheader is not None else self._getmp()
|
||||
self.n_frames = self.mpinfo[0xB001]
|
||||
self.__mpoffsets = [
|
||||
|
@ -77,6 +78,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
|||
segment = self.fp.read(2)
|
||||
if not segment:
|
||||
raise ValueError("No data found for frame")
|
||||
self._size = self._initial_size
|
||||
if i16(segment) == 0xFFE1: # APP1
|
||||
n = i16(self.fp.read(2)) - 2
|
||||
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))
|
||||
|
||||
return 0, 0
|
||||
return -1, 0
|
||||
|
||||
|
||||
Image.register_decoder("MSP", MspDecoder)
|
||||
|
|
|
@ -576,42 +576,42 @@ class PdfParser:
|
|||
self.xref_table[reference.object_id] = (offset, 0)
|
||||
return reference
|
||||
|
||||
delimiter = br"[][()<>{}/%]"
|
||||
delimiter_or_ws = br"[][()<>{}/%\000\011\012\014\015\040]"
|
||||
whitespace = br"[\000\011\012\014\015\040]"
|
||||
whitespace_or_hex = br"[\000\011\012\014\015\0400-9a-fA-F]"
|
||||
delimiter = rb"[][()<>{}/%]"
|
||||
delimiter_or_ws = rb"[][()<>{}/%\000\011\012\014\015\040]"
|
||||
whitespace = rb"[\000\011\012\014\015\040]"
|
||||
whitespace_or_hex = rb"[\000\011\012\014\015\0400-9a-fA-F]"
|
||||
whitespace_optional = whitespace + b"*"
|
||||
whitespace_mandatory = whitespace + b"+"
|
||||
# No "\012" aka "\n" or "\015" aka "\r":
|
||||
whitespace_optional_no_nl = br"[\000\011\014\040]*"
|
||||
newline_only = br"[\r\n]+"
|
||||
whitespace_optional_no_nl = rb"[\000\011\014\040]*"
|
||||
newline_only = rb"[\r\n]+"
|
||||
newline = whitespace_optional_no_nl + newline_only + whitespace_optional_no_nl
|
||||
re_trailer_end = re.compile(
|
||||
whitespace_mandatory
|
||||
+ br"trailer"
|
||||
+ rb"trailer"
|
||||
+ whitespace_optional
|
||||
+ br"\<\<(.*\>\>)"
|
||||
+ rb"\<\<(.*\>\>)"
|
||||
+ newline
|
||||
+ br"startxref"
|
||||
+ rb"startxref"
|
||||
+ newline
|
||||
+ br"([0-9]+)"
|
||||
+ rb"([0-9]+)"
|
||||
+ newline
|
||||
+ br"%%EOF"
|
||||
+ rb"%%EOF"
|
||||
+ whitespace_optional
|
||||
+ br"$",
|
||||
+ rb"$",
|
||||
re.DOTALL,
|
||||
)
|
||||
re_trailer_prev = re.compile(
|
||||
whitespace_optional
|
||||
+ br"trailer"
|
||||
+ rb"trailer"
|
||||
+ whitespace_optional
|
||||
+ br"\<\<(.*?\>\>)"
|
||||
+ rb"\<\<(.*?\>\>)"
|
||||
+ newline
|
||||
+ br"startxref"
|
||||
+ rb"startxref"
|
||||
+ newline
|
||||
+ br"([0-9]+)"
|
||||
+ rb"([0-9]+)"
|
||||
+ newline
|
||||
+ br"%%EOF"
|
||||
+ rb"%%EOF"
|
||||
+ whitespace_optional,
|
||||
re.DOTALL,
|
||||
)
|
||||
|
@ -655,12 +655,12 @@ class PdfParser:
|
|||
re_whitespace_optional = re.compile(whitespace_optional)
|
||||
re_name = re.compile(
|
||||
whitespace_optional
|
||||
+ br"/([!-$&'*-.0-;=?-Z\\^-z|~]+)(?="
|
||||
+ rb"/([!-$&'*-.0-;=?-Z\\^-z|~]+)(?="
|
||||
+ delimiter_or_ws
|
||||
+ br")"
|
||||
+ rb")"
|
||||
)
|
||||
re_dict_start = re.compile(whitespace_optional + br"\<\<")
|
||||
re_dict_end = re.compile(whitespace_optional + br"\>\>" + whitespace_optional)
|
||||
re_dict_start = re.compile(whitespace_optional + rb"\<\<")
|
||||
re_dict_end = re.compile(whitespace_optional + rb"\>\>" + whitespace_optional)
|
||||
|
||||
@classmethod
|
||||
def interpret_trailer(cls, trailer_data):
|
||||
|
@ -689,7 +689,7 @@ class PdfParser:
|
|||
)
|
||||
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
|
||||
def interpret_name(cls, raw, as_text=False):
|
||||
|
@ -704,53 +704,53 @@ class PdfParser:
|
|||
else:
|
||||
return bytes(name)
|
||||
|
||||
re_null = re.compile(whitespace_optional + br"null(?=" + delimiter_or_ws + br")")
|
||||
re_true = re.compile(whitespace_optional + br"true(?=" + delimiter_or_ws + br")")
|
||||
re_false = re.compile(whitespace_optional + br"false(?=" + delimiter_or_ws + br")")
|
||||
re_null = re.compile(whitespace_optional + rb"null(?=" + delimiter_or_ws + rb")")
|
||||
re_true = re.compile(whitespace_optional + rb"true(?=" + delimiter_or_ws + rb")")
|
||||
re_false = re.compile(whitespace_optional + rb"false(?=" + delimiter_or_ws + rb")")
|
||||
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(
|
||||
whitespace_optional
|
||||
+ br"([-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+))(?="
|
||||
+ rb"([-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+))(?="
|
||||
+ delimiter_or_ws
|
||||
+ br")"
|
||||
+ rb")"
|
||||
)
|
||||
re_array_start = re.compile(whitespace_optional + br"\[")
|
||||
re_array_end = re.compile(whitespace_optional + br"]")
|
||||
re_array_start = re.compile(whitespace_optional + rb"\[")
|
||||
re_array_end = re.compile(whitespace_optional + rb"]")
|
||||
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(
|
||||
whitespace_optional
|
||||
+ br"([-+]?[0-9]+)"
|
||||
+ rb"([-+]?[0-9]+)"
|
||||
+ whitespace_mandatory
|
||||
+ br"([-+]?[0-9]+)"
|
||||
+ rb"([-+]?[0-9]+)"
|
||||
+ whitespace_mandatory
|
||||
+ br"R(?="
|
||||
+ rb"R(?="
|
||||
+ delimiter_or_ws
|
||||
+ br")"
|
||||
+ rb")"
|
||||
)
|
||||
re_indirect_def_start = re.compile(
|
||||
whitespace_optional
|
||||
+ br"([-+]?[0-9]+)"
|
||||
+ rb"([-+]?[0-9]+)"
|
||||
+ whitespace_mandatory
|
||||
+ br"([-+]?[0-9]+)"
|
||||
+ rb"([-+]?[0-9]+)"
|
||||
+ whitespace_mandatory
|
||||
+ br"obj(?="
|
||||
+ rb"obj(?="
|
||||
+ delimiter_or_ws
|
||||
+ br")"
|
||||
+ rb")"
|
||||
)
|
||||
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(
|
||||
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(
|
||||
whitespace_optional + br"endstream(?=" + delimiter_or_ws + br")"
|
||||
whitespace_optional + rb"endstream(?=" + delimiter_or_ws + rb")"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
@ -876,7 +876,7 @@ class PdfParser:
|
|||
raise PdfFormatError("unrecognized object: " + repr(data[offset : offset + 32]))
|
||||
|
||||
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 = {
|
||||
b"n": b"\n",
|
||||
|
@ -922,16 +922,16 @@ class PdfParser:
|
|||
offset = m.end()
|
||||
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(
|
||||
whitespace_optional
|
||||
+ br"([0-9]+)"
|
||||
+ rb"([0-9]+)"
|
||||
+ whitespace_mandatory
|
||||
+ br"([0-9]+)"
|
||||
+ rb"([0-9]+)"
|
||||
+ whitespace_optional
|
||||
+ 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):
|
||||
subsection_found = False
|
||||
|
|
|
@ -48,7 +48,7 @@ from ._binary import o32be as o32
|
|||
|
||||
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"
|
||||
|
|
|
@ -49,26 +49,46 @@ class PpmImageFile(ImageFile.ImageFile):
|
|||
format = "PPM"
|
||||
format_description = "Pbmplus image"
|
||||
|
||||
def _token(self, s=b""):
|
||||
while True: # read until next whitespace
|
||||
def _read_magic(self):
|
||||
magic = b""
|
||||
# read until whitespace or longest available magic number
|
||||
for _ in range(6):
|
||||
c = self.fp.read(1)
|
||||
if not c or c in b_whitespace:
|
||||
break
|
||||
if c > b"\x79":
|
||||
raise ValueError("Expected ASCII value, found binary")
|
||||
s = s + c
|
||||
if len(s) > 9:
|
||||
raise ValueError("Expected int, got > 9 digits")
|
||||
return s
|
||||
magic += c
|
||||
return magic
|
||||
|
||||
def _read_token(self):
|
||||
token = b""
|
||||
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):
|
||||
|
||||
# check magic
|
||||
s = self.fp.read(1)
|
||||
if s != b"P":
|
||||
magic_number = self._read_magic()
|
||||
try:
|
||||
mode = MODES[magic_number]
|
||||
except KeyError:
|
||||
raise SyntaxError("not a PPM file")
|
||||
magic_number = self._token(s)
|
||||
mode = MODES[magic_number]
|
||||
|
||||
self.custom_mimetype = {
|
||||
b"P4": "image/x-portable-bitmap",
|
||||
|
@ -83,29 +103,19 @@ class PpmImageFile(ImageFile.ImageFile):
|
|||
self.mode = rawmode = mode
|
||||
|
||||
for ix in range(3):
|
||||
while True:
|
||||
while True:
|
||||
s = self.fp.read(1)
|
||||
if s not in b_whitespace:
|
||||
break
|
||||
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
|
||||
token = int(self._read_token())
|
||||
if ix == 0: # token is the x size
|
||||
xsize = token
|
||||
elif ix == 1: # token is the y size
|
||||
ysize = token
|
||||
if mode == "1":
|
||||
break
|
||||
elif ix == 2:
|
||||
# maxgrey
|
||||
if s > 255:
|
||||
elif ix == 2: # token is maxval
|
||||
maxval = token
|
||||
if maxval > 255:
|
||||
if not mode == "L":
|
||||
raise ValueError(f"Too many colors for band: {s}")
|
||||
if s < 2 ** 16:
|
||||
raise ValueError(f"Too many colors for band: {token}")
|
||||
if maxval < 2**16:
|
||||
self.mode = "I"
|
||||
rawmode = "I;16B"
|
||||
else:
|
||||
|
@ -126,7 +136,7 @@ def _save(im, fp, filename):
|
|||
elif im.mode == "L":
|
||||
rawmode, head = "L", b"P5"
|
||||
elif im.mode == "I":
|
||||
if im.getextrema()[1] < 2 ** 16:
|
||||
if im.getextrema()[1] < 2**16:
|
||||
rawmode, head = "I;16B", b"P5"
|
||||
else:
|
||||
rawmode, head = "I;32B", b"P5"
|
||||
|
|
|
@ -155,14 +155,6 @@ class PsdImageFile(ImageFile.ImageFile):
|
|||
# return layer number (0=image, 1..max=layers)
|
||||
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):
|
||||
try:
|
||||
if self.__fp != self.fp:
|
||||
|
|
|
@ -493,7 +493,7 @@ class ImageFileDirectory_v2(MutableMapping):
|
|||
endianness.
|
||||
: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)")
|
||||
self._prefix = prefix if prefix is not None else ifh[:2]
|
||||
if self._prefix == MM:
|
||||
|
@ -577,9 +577,9 @@ class ImageFileDirectory_v2(MutableMapping):
|
|||
else TiffTags.SIGNED_RATIONAL
|
||||
)
|
||||
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
|
||||
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
|
||||
else:
|
||||
self.tagtype[tag] = (
|
||||
|
@ -734,7 +734,7 @@ class ImageFileDirectory_v2(MutableMapping):
|
|||
@_register_writer(5)
|
||||
def write_rational(self, *values):
|
||||
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)
|
||||
|
@ -757,7 +757,7 @@ class ImageFileDirectory_v2(MutableMapping):
|
|||
@_register_writer(10)
|
||||
def write_signed_rational(self, *values):
|
||||
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
|
||||
)
|
||||
|
||||
|
@ -1670,7 +1670,7 @@ def _save(im, fp, filename):
|
|||
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
|
||||
ifd[ROWSPERSTRIP] = rows_per_strip
|
||||
if strip_byte_counts >= 2 ** 16:
|
||||
if strip_byte_counts >= 2**16:
|
||||
ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG
|
||||
ifd[STRIPBYTECOUNTS] = (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 ._binary import i16le as word
|
||||
from ._binary import i32le as dword
|
||||
from ._binary import si16le as short
|
||||
from ._binary import si32le as _long
|
||||
|
||||
|
@ -112,7 +111,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
|||
if s[22:26] != b"\x01\x00\t\x00":
|
||||
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
|
||||
|
||||
# get bounding box
|
||||
|
|
|
@ -25,7 +25,7 @@ from . import Image, ImageFile
|
|||
|
||||
# XBM header
|
||||
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"(?P<hotspot>"
|
||||
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))
|
||||
|
||||
if m:
|
||||
if not m:
|
||||
raise SyntaxError("not a XBM file")
|
||||
|
||||
xsize = int(m.group("width"))
|
||||
ysize = int(m.group("height"))
|
||||
xsize = int(m.group("width"))
|
||||
ysize = int(m.group("height"))
|
||||
|
||||
if m.group("hotspot"):
|
||||
self.info["hotspot"] = (int(m.group("xhot")), int(m.group("yhot")))
|
||||
if m.group("hotspot"):
|
||||
self.info["hotspot"] = (int(m.group("xhot")), int(m.group("yhot")))
|
||||
|
||||
self.mode = "1"
|
||||
self._size = xsize, ysize
|
||||
self.mode = "1"
|
||||
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):
|
||||
|
|
|
@ -149,14 +149,13 @@ _encode(ImagingEncoderObject *encoder, PyObject *args) {
|
|||
}
|
||||
|
||||
static PyObject *
|
||||
_encode_to_pyfd(ImagingEncoderObject *encoder, PyObject *args) {
|
||||
_encode_to_pyfd(ImagingEncoderObject *encoder) {
|
||||
PyObject *result;
|
||||
int status;
|
||||
|
||||
if (!encoder->pushes_fd) {
|
||||
// UNDONE, appropriate errcode???
|
||||
result = Py_BuildValue("ii", 0, IMAGING_CODEC_CONFIG);
|
||||
;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -307,7 +306,7 @@ static struct PyMethodDef methods[] = {
|
|||
{"encode", (PyCFunction)_encode, METH_VARARGS},
|
||||
{"cleanup", (PyCFunction)_encode_cleanup, 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},
|
||||
{"setfd", (PyCFunction)_setfd, METH_VARARGS},
|
||||
{NULL, NULL} /* sentinel */
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
#define MAX(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) */
|
||||
#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();
|
||||
}
|
||||
|
||||
if (!((strcmp(imIn->mode, "RGB") == 0 || strcmp(imIn->mode, "1") == 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) {
|
||||
if (strcmp(imIn->mode, "RGB") == 0 && strcmp(mode, "RGBA") == 0) {
|
||||
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) {
|
||||
convert = bit2rgb;
|
||||
} 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;
|
||||
}
|
||||
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);
|
||||
|
|
|
@ -295,7 +295,7 @@ ImagingPaste(
|
|||
paste_mask_L(imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
|
||||
ImagingSectionLeave(&cookie);
|
||||
|
||||
} else if (strcmp(imMask->mode, "RGBA") == 0) {
|
||||
} else if (strcmp(imMask->mode, "LA") == 0 || strcmp(imMask->mode, "RGBA") == 0) {
|
||||
ImagingSectionEnter(&cookie);
|
||||
paste_mask_RGBA(
|
||||
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