mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-01 02:13:11 +03:00
Merge branch 'main' into add-cygwin-to-ci
This commit is contained in:
commit
afa3cea96a
|
@ -35,6 +35,8 @@ python3 -m pip install -U pytest-cov
|
||||||
python3 -m pip install -U pytest-timeout
|
python3 -m pip install -U pytest-timeout
|
||||||
python3 -m pip install pyroma
|
python3 -m pip install pyroma
|
||||||
python3 -m pip install test-image-results
|
python3 -m pip install test-image-results
|
||||||
|
# TODO Remove condition when NumPy supports 3.11
|
||||||
|
if ! [ "$GHA_PYTHON_VERSION" == "3.11-dev" ]; then python3 -m pip install numpy ; fi
|
||||||
|
|
||||||
if [[ $(uname) != CYGWIN* ]]; then
|
if [[ $(uname) != CYGWIN* ]]; then
|
||||||
PYTHONOPTIMIZE=0 python3 -m pip install cffi
|
PYTHONOPTIMIZE=0 python3 -m pip install cffi
|
||||||
|
|
|
@ -16,7 +16,6 @@ trim_trailing_whitespace = true
|
||||||
[*.yml]
|
[*.yml]
|
||||||
# Two-space indentation
|
# Two-space indentation
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
indent_style = space
|
|
||||||
|
|
||||||
# Tab indentation (no size specified)
|
# Tab indentation (no size specified)
|
||||||
[Makefile]
|
[Makefile]
|
||||||
|
|
3
.github/workflows/macos-install.sh
vendored
3
.github/workflows/macos-install.sh
vendored
|
@ -15,7 +15,8 @@ python3 -m pip install pyroma
|
||||||
python3 -m pip install test-image-results
|
python3 -m pip install test-image-results
|
||||||
|
|
||||||
echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg
|
echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg
|
||||||
python3 -m pip install numpy
|
# TODO Remove condition when NumPy supports 3.11
|
||||||
|
if ! [ "$GHA_PYTHON_VERSION" == "3.11-dev" ]; then python3 -m pip install numpy ; fi
|
||||||
|
|
||||||
# extra test images
|
# extra test images
|
||||||
pushd depends && ./install_extra_test_images.sh && popd
|
pushd depends && ./install_extra_test_images.sh && popd
|
||||||
|
|
2
.github/workflows/test-windows.yml
vendored
2
.github/workflows/test-windows.yml
vendored
|
@ -8,7 +8,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11-dev"]
|
||||||
architecture: ["x86", "x64"]
|
architecture: ["x86", "x64"]
|
||||||
include:
|
include:
|
||||||
# PyPy 7.3.4+ only ships 64-bit binaries for Windows
|
# PyPy 7.3.4+ only ships 64-bit binaries for Windows
|
||||||
|
|
3
.github/workflows/test.yml
vendored
3
.github/workflows/test.yml
vendored
|
@ -15,6 +15,7 @@ jobs:
|
||||||
python-version: [
|
python-version: [
|
||||||
"pypy-3.8",
|
"pypy-3.8",
|
||||||
"pypy-3.7",
|
"pypy-3.7",
|
||||||
|
"3.11-dev",
|
||||||
"3.10",
|
"3.10",
|
||||||
"3.9",
|
"3.9",
|
||||||
"3.8",
|
"3.8",
|
||||||
|
@ -59,6 +60,8 @@ jobs:
|
||||||
if: startsWith(matrix.os, 'macOS')
|
if: startsWith(matrix.os, 'macOS')
|
||||||
run: |
|
run: |
|
||||||
.github/workflows/macos-install.sh
|
.github/workflows/macos-install.sh
|
||||||
|
env:
|
||||||
|
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -5,6 +5,12 @@ Changelog (Pillow)
|
||||||
9.2.0 (unreleased)
|
9.2.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Corrected screencapture argument in ImageGrab.grab() #6244
|
||||||
|
[axt-one]
|
||||||
|
|
||||||
|
- Deprecate support for Qt 5 (PyQt5 and PySide2) #6237
|
||||||
|
[hugovk, radarhere]
|
||||||
|
|
||||||
- Increase wait time of temporary file deletion on Windows #6224
|
- Increase wait time of temporary file deletion on Windows #6224
|
||||||
[AlexTedeschi]
|
[AlexTedeschi]
|
||||||
|
|
||||||
|
|
|
@ -567,7 +567,7 @@ class TestTransformColorLut3D:
|
||||||
assert tuple(lut.size) == tuple(source.size)
|
assert tuple(lut.size) == tuple(source.size)
|
||||||
assert len(lut.table) == len(source.table)
|
assert len(lut.table) == len(source.table)
|
||||||
assert lut.table != source.table
|
assert lut.table != source.table
|
||||||
assert lut.table[0:10] == [0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]
|
assert lut.table[:10] == [0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]
|
||||||
|
|
||||||
def test_3_to_4_channels(self):
|
def test_3_to_4_channels(self):
|
||||||
source = ImageFilter.Color3DLUT.generate((6, 5, 4), lambda r, g, b: (r, g, b))
|
source = ImageFilter.Color3DLUT.generate((6, 5, 4), lambda r, g, b: (r, g, b))
|
||||||
|
@ -576,7 +576,7 @@ class TestTransformColorLut3D:
|
||||||
assert len(lut.table) != len(source.table)
|
assert len(lut.table) != len(source.table)
|
||||||
assert lut.table != source.table
|
assert lut.table != source.table
|
||||||
# fmt: off
|
# fmt: off
|
||||||
assert lut.table[0:16] == [
|
assert lut.table[:16] == [
|
||||||
0.0, 0.0, 0.0, 1, 0.2**2, 0.0, 0.0, 1,
|
0.0, 0.0, 0.0, 1, 0.2**2, 0.0, 0.0, 1,
|
||||||
0.4**2, 0.0, 0.0, 1, 0.6**2, 0.0, 0.0, 1]
|
0.4**2, 0.0, 0.0, 1, 0.6**2, 0.0, 0.0, 1]
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
@ -592,7 +592,7 @@ class TestTransformColorLut3D:
|
||||||
assert len(lut.table) != len(source.table)
|
assert len(lut.table) != len(source.table)
|
||||||
assert lut.table != source.table
|
assert lut.table != source.table
|
||||||
# fmt: off
|
# fmt: off
|
||||||
assert lut.table[0:18] == [
|
assert lut.table[:18] == [
|
||||||
1.0, 1.0, 1.0, 0.75, 1.0, 1.0, 0.0, 1.0, 1.0,
|
1.0, 1.0, 1.0, 0.75, 1.0, 1.0, 0.0, 1.0, 1.0,
|
||||||
1.0, 0.96, 1.0, 0.75, 0.96, 1.0, 0.0, 0.96, 1.0]
|
1.0, 0.96, 1.0, 0.75, 0.96, 1.0, 0.0, 0.96, 1.0]
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
@ -606,7 +606,7 @@ class TestTransformColorLut3D:
|
||||||
assert len(lut.table) == len(source.table)
|
assert len(lut.table) == len(source.table)
|
||||||
assert lut.table != source.table
|
assert lut.table != source.table
|
||||||
# fmt: off
|
# fmt: off
|
||||||
assert lut.table[0:16] == [
|
assert lut.table[:16] == [
|
||||||
0.0, 0.0, 0.0, 0.5, 0.2**2, 0.0, 0.0, 0.5,
|
0.0, 0.0, 0.0, 0.5, 0.2**2, 0.0, 0.0, 0.5,
|
||||||
0.4**2, 0.0, 0.0, 0.5, 0.6**2, 0.0, 0.0, 0.5]
|
0.4**2, 0.0, 0.0, 0.5, 0.6**2, 0.0, 0.0, 0.5]
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
@ -622,7 +622,7 @@ class TestTransformColorLut3D:
|
||||||
assert len(lut.table) == len(source.table)
|
assert len(lut.table) == len(source.table)
|
||||||
assert lut.table != source.table
|
assert lut.table != source.table
|
||||||
# fmt: off
|
# fmt: off
|
||||||
assert lut.table[0:18] == [
|
assert lut.table[:18] == [
|
||||||
0.0, 0.0, 0.0, 0.16, 0.0, 0.0, 0.24, 0.0, 0.0,
|
0.0, 0.0, 0.0, 0.16, 0.0, 0.0, 0.24, 0.0, 0.0,
|
||||||
0.24, 0.0, 0.0, 0.8 - (0.8**2), 0, 0, 0, 0, 0]
|
0.24, 0.0, 0.0, 0.8 - (0.8**2), 0, 0, 0, 0, 0]
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
@ -639,7 +639,7 @@ class TestTransformColorLut3D:
|
||||||
assert len(lut.table) == len(source.table)
|
assert len(lut.table) == len(source.table)
|
||||||
assert lut.table != source.table
|
assert lut.table != source.table
|
||||||
# fmt: off
|
# fmt: off
|
||||||
assert lut.table[0:16] == [
|
assert lut.table[:16] == [
|
||||||
0.0, 0.0, 0.0, 0.5, 0.25, 0.0, 0.0, 0.5,
|
0.0, 0.0, 0.0, 0.5, 0.25, 0.0, 0.0, 0.5,
|
||||||
0.0, 0.0, 0.0, 0.5, 0.0, 0.16, 0.0, 0.5]
|
0.0, 0.0, 0.0, 0.5, 0.0, 0.16, 0.0, 0.5]
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
|
@ -70,12 +70,12 @@ class TestDecompressionBomb:
|
||||||
|
|
||||||
class TestDecompressionCrop:
|
class TestDecompressionCrop:
|
||||||
@classmethod
|
@classmethod
|
||||||
def setup_class(self):
|
def setup_class(cls):
|
||||||
width, height = 128, 128
|
width, height = 128, 128
|
||||||
Image.MAX_IMAGE_PIXELS = height * width * 4 - 1
|
Image.MAX_IMAGE_PIXELS = height * width * 4 - 1
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def teardown_class(self):
|
def teardown_class(cls):
|
||||||
Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT
|
Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT
|
||||||
|
|
||||||
def test_enlarge_crop(self):
|
def test_enlarge_crop(self):
|
||||||
|
|
|
@ -4,15 +4,13 @@ import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import IcnsImagePlugin, Image, _binary, features
|
from PIL import IcnsImagePlugin, Image, _binary
|
||||||
|
|
||||||
from .helper import assert_image_equal, assert_image_similar_tofile
|
from .helper import assert_image_equal, assert_image_similar_tofile, skip_unless_feature
|
||||||
|
|
||||||
# sample icon file
|
# sample icon file
|
||||||
TEST_FILE = "Tests/images/pillow.icns"
|
TEST_FILE = "Tests/images/pillow.icns"
|
||||||
|
|
||||||
ENABLE_JPEG2K = features.check_codec("jpg_2000")
|
|
||||||
|
|
||||||
|
|
||||||
def test_sanity():
|
def test_sanity():
|
||||||
# Loading this icon by default should result in the largest size
|
# Loading this icon by default should result in the largest size
|
||||||
|
@ -111,14 +109,12 @@ def test_older_icon():
|
||||||
assert im2.size == (wr, hr)
|
assert im2.size == (wr, hr)
|
||||||
|
|
||||||
|
|
||||||
|
@skip_unless_feature("jpg_2000")
|
||||||
def test_jp2_icon():
|
def test_jp2_icon():
|
||||||
# This icon uses JPEG 2000 images instead of the PNG images.
|
# This icon uses JPEG 2000 images instead of the PNG images.
|
||||||
# The advantage of doing this is that OS X 10.5 supports JPEG 2000
|
# The advantage of doing this is that OS X 10.5 supports JPEG 2000
|
||||||
# but not PNG; some commercial software therefore does just this.
|
# but not PNG; some commercial software therefore does just this.
|
||||||
|
|
||||||
if not ENABLE_JPEG2K:
|
|
||||||
return
|
|
||||||
|
|
||||||
with Image.open("Tests/images/pillow3.icns") as im:
|
with Image.open("Tests/images/pillow3.icns") as im:
|
||||||
for w, h, r in im.info["sizes"]:
|
for w, h, r in im.info["sizes"]:
|
||||||
wr = w * r
|
wr = w * r
|
||||||
|
@ -149,6 +145,7 @@ def test_not_an_icns_file():
|
||||||
IcnsImagePlugin.IcnsFile(fp)
|
IcnsImagePlugin.IcnsFile(fp)
|
||||||
|
|
||||||
|
|
||||||
|
@skip_unless_feature("jpg_2000")
|
||||||
def test_icns_decompression_bomb():
|
def test_icns_decompression_bomb():
|
||||||
with Image.open(
|
with Image.open(
|
||||||
"Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns"
|
"Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns"
|
||||||
|
|
|
@ -743,7 +743,7 @@ class TestFileJpeg:
|
||||||
|
|
||||||
# Act / Assert
|
# Act / Assert
|
||||||
# "When the image resolution is unknown, 72 [dpi] is designated."
|
# "When the image resolution is unknown, 72 [dpi] is designated."
|
||||||
# http://www.exiv2.org/tags.html
|
# https://exiv2.org/tags.html
|
||||||
assert im.info.get("dpi") == (72, 72)
|
assert im.info.get("dpi") == (72, 72)
|
||||||
|
|
||||||
def test_invalid_exif(self):
|
def test_invalid_exif(self):
|
||||||
|
|
|
@ -123,7 +123,7 @@ def test_token_too_long(tmp_path):
|
||||||
with Image.open(path):
|
with Image.open(path):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
assert str(e.value) == "Token too long in file header: b'01234567890'"
|
assert str(e.value) == "Token too long in file header: 01234567890"
|
||||||
|
|
||||||
|
|
||||||
def test_truncated_file(tmp_path):
|
def test_truncated_file(tmp_path):
|
||||||
|
|
|
@ -185,6 +185,17 @@ class TestFileWebp:
|
||||||
Image.open(blob).load()
|
Image.open(blob).load()
|
||||||
Image.open(blob).load()
|
Image.open(blob).load()
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"background",
|
||||||
|
(0, (0,), (-1, 0, 1, 2), (253, 254, 255, 256)),
|
||||||
|
)
|
||||||
|
@skip_unless_feature("webp_anim")
|
||||||
|
def test_invalid_background(self, background, tmp_path):
|
||||||
|
temp_file = str(tmp_path / "temp.webp")
|
||||||
|
im = hopper()
|
||||||
|
with pytest.raises(OSError):
|
||||||
|
im.save(temp_file, save_all=True, append_images=[im], background=background)
|
||||||
|
|
||||||
@skip_unless_feature("webp_anim")
|
@skip_unless_feature("webp_anim")
|
||||||
def test_background_from_gif(self, tmp_path):
|
def test_background_from_gif(self, tmp_path):
|
||||||
# Save L mode GIF with background
|
# Save L mode GIF with background
|
||||||
|
|
|
@ -7,7 +7,7 @@ import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImagePalette, UnidentifiedImageError
|
from PIL import Image, ImageDraw, ImagePalette, UnidentifiedImageError, features
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
|
@ -161,6 +161,8 @@ class TestImage:
|
||||||
assert im.size == (128, 128)
|
assert im.size == (128, 128)
|
||||||
|
|
||||||
for ext in (".jpg", ".jp2"):
|
for ext in (".jpg", ".jp2"):
|
||||||
|
if ext == ".jp2" and not features.check_codec("jpg_2000"):
|
||||||
|
pytest.skip("jpg_2000 not available")
|
||||||
temp_file = str(tmp_path / ("temp." + ext))
|
temp_file = str(tmp_path / ("temp." + ext))
|
||||||
if os.path.exists(temp_file):
|
if os.path.exists(temp_file):
|
||||||
os.remove(temp_file)
|
os.remove(temp_file)
|
||||||
|
@ -170,7 +172,7 @@ class TestImage:
|
||||||
temp_file = str(tmp_path / "temp.jpg")
|
temp_file = str(tmp_path / "temp.jpg")
|
||||||
|
|
||||||
class FP:
|
class FP:
|
||||||
def write(a, b):
|
def write(self, b):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
fp = FP()
|
fp = FP()
|
||||||
|
|
|
@ -27,15 +27,15 @@ def test_sanity():
|
||||||
"HSV",
|
"HSV",
|
||||||
)
|
)
|
||||||
|
|
||||||
for mode in modes:
|
for input_mode in modes:
|
||||||
im = hopper(mode)
|
im = hopper(input_mode)
|
||||||
for mode in modes:
|
for output_mode in modes:
|
||||||
convert(im, mode)
|
convert(im, output_mode)
|
||||||
|
|
||||||
# Check 0
|
# Check 0
|
||||||
im = Image.new(mode, (0, 0))
|
im = Image.new(input_mode, (0, 0))
|
||||||
for mode in modes:
|
for output_mode in modes:
|
||||||
convert(im, mode)
|
convert(im, output_mode)
|
||||||
|
|
||||||
|
|
||||||
def test_default():
|
def test_default():
|
||||||
|
|
|
@ -458,7 +458,7 @@ class TestCoreResampleBox:
|
||||||
def split_range(size, tiles):
|
def split_range(size, tiles):
|
||||||
scale = size / tiles
|
scale = size / tiles
|
||||||
for i in range(tiles):
|
for i in range(tiles):
|
||||||
yield (int(round(scale * i)), int(round(scale * (i + 1))))
|
yield int(round(scale * i)), int(round(scale * (i + 1)))
|
||||||
|
|
||||||
tiled = Image.new(im.mode, dst_size)
|
tiled = Image.new(im.mode, dst_size)
|
||||||
scale = (im.size[0] / tiled.size[0], im.size[1] / tiled.size[1])
|
scale = (im.size[0] / tiled.size[0], im.size[1] / tiled.size[1])
|
||||||
|
|
|
@ -77,9 +77,9 @@ def test_consecutive():
|
||||||
def test_palette_mmap():
|
def test_palette_mmap():
|
||||||
# Using mmap in ImageFile can require to reload the palette.
|
# Using mmap in ImageFile can require to reload the palette.
|
||||||
with Image.open("Tests/images/multipage-mmap.tiff") as im:
|
with Image.open("Tests/images/multipage-mmap.tiff") as im:
|
||||||
color1 = im.getpalette()[0:3]
|
color1 = im.getpalette()[:3]
|
||||||
im.seek(0)
|
im.seek(0)
|
||||||
color2 = im.getpalette()[0:3]
|
color2 = im.getpalette()[:3]
|
||||||
assert color1 == color2
|
assert color1 == color2
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -97,8 +97,8 @@ Deprecated Use instead
|
||||||
``Image.TRANSPOSE`` ``Image.Transpose.TRANSPOSE``
|
``Image.TRANSPOSE`` ``Image.Transpose.TRANSPOSE``
|
||||||
``Image.TRANSVERSE`` ``Image.Transpose.TRANSVERSE``
|
``Image.TRANSVERSE`` ``Image.Transpose.TRANSVERSE``
|
||||||
``Image.BOX`` ``Image.Resampling.BOX``
|
``Image.BOX`` ``Image.Resampling.BOX``
|
||||||
``Image.BILINEAR`` ``Image.Resampling.BILNEAR``
|
``Image.BILINEAR`` ``Image.Resampling.BILINEAR``
|
||||||
``Image.LINEAR`` ``Image.Resampling.BILNEAR``
|
``Image.LINEAR`` ``Image.Resampling.BILINEAR``
|
||||||
``Image.HAMMING`` ``Image.Resampling.HAMMING``
|
``Image.HAMMING`` ``Image.Resampling.HAMMING``
|
||||||
``Image.BICUBIC`` ``Image.Resampling.BICUBIC``
|
``Image.BICUBIC`` ``Image.Resampling.BICUBIC``
|
||||||
``Image.CUBIC`` ``Image.Resampling.BICUBIC``
|
``Image.CUBIC`` ``Image.Resampling.BICUBIC``
|
||||||
|
|
|
@ -465,11 +465,13 @@ These platforms are built and tested for every change.
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Gentoo | 3.9 | x86-64 |
|
| Gentoo | 3.9 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| macOS 10.15 Catalina | 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64 |
|
| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64 |
|
||||||
|
| | PyPy3 | |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Ubuntu Linux 18.04 LTS (Bionic) | 3.9 | x86-64 |
|
| Ubuntu Linux 18.04 LTS (Bionic) | 3.9 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Ubuntu Linux 20.04 LTS (Focal) | 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64 |
|
| Ubuntu Linux 20.04 LTS (Focal) | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64 |
|
||||||
|
| | PyPy3 | |
|
||||||
| +----------------------------+---------------------+
|
| +----------------------------+---------------------+
|
||||||
| | 3.8 | arm64v8, ppc64le, |
|
| | 3.8 | arm64v8, ppc64le, |
|
||||||
| | | s390x |
|
| | | s390x |
|
||||||
|
@ -478,7 +480,8 @@ These platforms are built and tested for every change.
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Windows Server 2016 | 3.7 | x86-64 |
|
| Windows Server 2016 | 3.7 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Windows Server 2019 | 3.7, 3.8, 3.9, 3.10, PyPy3 | x86, x86-64 |
|
| Windows Server 2022 | 3.7, 3.8, 3.9, 3.10, 3.11, | x86, x86-64 |
|
||||||
|
| | PyPy3 | |
|
||||||
| +----------------------------+---------------------+
|
| +----------------------------+---------------------+
|
||||||
| | 3.9 (MinGW) | x86, x86-64 |
|
| | 3.9 (MinGW) | x86, x86-64 |
|
||||||
| +----------------------------+---------------------+
|
| +----------------------------+---------------------+
|
||||||
|
|
|
@ -25,3 +25,8 @@ The :py:class:`~PIL.ImageSequence.Iterator` class
|
||||||
|
|
||||||
.. autoclass:: PIL.ImageSequence.Iterator
|
.. autoclass:: PIL.ImageSequence.Iterator
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
Functions
|
||||||
|
---------
|
||||||
|
|
||||||
|
.. autofunction:: PIL.ImageSequence.all_frames
|
||||||
|
|
|
@ -10,6 +10,10 @@ metadata tag numbers, names, and type information.
|
||||||
.. method:: lookup(tag)
|
.. method:: lookup(tag)
|
||||||
|
|
||||||
:param tag: Integer tag number
|
:param tag: Integer tag number
|
||||||
|
:param group: Which :py:data:`~PIL.TiffTags.TAGS_V2_GROUPS` to look in
|
||||||
|
|
||||||
|
.. versionadded:: 8.3.0
|
||||||
|
|
||||||
:returns: Taginfo namedtuple, From the :py:data:`~PIL.TiffTags.TAGS_V2` info if possible,
|
:returns: Taginfo namedtuple, From the :py:data:`~PIL.TiffTags.TAGS_V2` info if possible,
|
||||||
otherwise just populating the value and name from :py:data:`~PIL.TiffTags.TAGS`.
|
otherwise just populating the value and name from :py:data:`~PIL.TiffTags.TAGS`.
|
||||||
If the tag is not recognized, "unknown" is returned for the name
|
If the tag is not recognized, "unknown" is returned for the name
|
||||||
|
@ -42,6 +46,16 @@ metadata tag numbers, names, and type information.
|
||||||
|
|
||||||
.. versionadded:: 3.0.0
|
.. versionadded:: 3.0.0
|
||||||
|
|
||||||
|
.. py:data:: PIL.TiffTags.TAGS_V2_GROUPS
|
||||||
|
:type: dict
|
||||||
|
|
||||||
|
:py:data:`~PIL.TiffTags.TAGS_V2` is one dimensional and
|
||||||
|
doesn't account for the fact that tags actually exist in
|
||||||
|
`different groups <https://exiftool.org/TagNames/EXIF.html>`_.
|
||||||
|
This dictionary is used when the tag in question is part of a group.
|
||||||
|
|
||||||
|
.. versionadded:: 8.3.0
|
||||||
|
|
||||||
.. py:data:: PIL.TiffTags.TAGS
|
.. py:data:: PIL.TiffTags.TAGS
|
||||||
:type: dict
|
:type: dict
|
||||||
|
|
||||||
|
|
|
@ -76,8 +76,8 @@ Deprecated Use instead
|
||||||
``Image.TRANSPOSE`` ``Image.Transpose.TRANSPOSE``
|
``Image.TRANSPOSE`` ``Image.Transpose.TRANSPOSE``
|
||||||
``Image.TRANSVERSE`` ``Image.Transpose.TRANSVERSE``
|
``Image.TRANSVERSE`` ``Image.Transpose.TRANSVERSE``
|
||||||
``Image.BOX`` ``Image.Resampling.BOX``
|
``Image.BOX`` ``Image.Resampling.BOX``
|
||||||
``Image.BILINEAR`` ``Image.Resampling.BILNEAR``
|
``Image.BILINEAR`` ``Image.Resampling.BILINEAR``
|
||||||
``Image.LINEAR`` ``Image.Resampling.BILNEAR``
|
``Image.LINEAR`` ``Image.Resampling.BILINEAR``
|
||||||
``Image.HAMMING`` ``Image.Resampling.HAMMING``
|
``Image.HAMMING`` ``Image.Resampling.HAMMING``
|
||||||
``Image.BICUBIC`` ``Image.Resampling.BICUBIC``
|
``Image.BICUBIC`` ``Image.Resampling.BICUBIC``
|
||||||
``Image.CUBIC`` ``Image.Resampling.BICUBIC``
|
``Image.CUBIC`` ``Image.Resampling.BICUBIC``
|
||||||
|
|
|
@ -20,7 +20,7 @@ def testimage():
|
||||||
|
|
||||||
>>> from PIL import Image, ImageDraw, ImageFilter, ImageMath
|
>>> from PIL import Image, ImageDraw, ImageFilter, ImageMath
|
||||||
>>> im = Image.new("1", (128, 128)) # monochrome
|
>>> im = Image.new("1", (128, 128)) # monochrome
|
||||||
>>> def _info(im): return (im.format, im.mode, im.size)
|
>>> def _info(im): return im.format, im.mode, im.size
|
||||||
>>> _info(im)
|
>>> _info(im)
|
||||||
(None, '1', (128, 128))
|
(None, '1', (128, 128))
|
||||||
>>> _info(Image.new("L", (128, 128))) # grayscale (luminance)
|
>>> _info(Image.new("L", (128, 128))) # grayscale (luminance)
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -269,7 +269,7 @@ def _pkg_config(name):
|
||||||
.strip()
|
.strip()
|
||||||
.replace("-I", "")
|
.replace("-I", "")
|
||||||
)
|
)
|
||||||
return (libs, cflags)
|
return libs, cflags
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ def __getattr__(name):
|
||||||
|
|
||||||
|
|
||||||
def unpack_565(i):
|
def unpack_565(i):
|
||||||
return (((i >> 11) & 0x1F) << 3, ((i >> 5) & 0x3F) << 2, (i & 0x1F) << 3)
|
return ((i >> 11) & 0x1F) << 3, ((i >> 5) & 0x3F) << 2, (i & 0x1F) << 3
|
||||||
|
|
||||||
|
|
||||||
def decode_dxt1(data, alpha=False):
|
def decode_dxt1(data, alpha=False):
|
||||||
|
|
|
@ -76,10 +76,8 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
read, seek = self.fp.read, self.fp.seek
|
read, seek = self.fp.read, self.fp.seek
|
||||||
if header:
|
if header:
|
||||||
seek(header)
|
seek(header)
|
||||||
file_info = {}
|
|
||||||
# read bmp header size @offset 14 (this is part of the header size)
|
# read bmp header size @offset 14 (this is part of the header size)
|
||||||
file_info["header_size"] = i32(read(4))
|
file_info = {"header_size": i32(read(4)), "direction": -1}
|
||||||
file_info["direction"] = -1
|
|
||||||
|
|
||||||
# -------------------- If requested, read header at a specific position
|
# -------------------- If requested, read header at a specific position
|
||||||
# read the rest of the bmp header, without its size
|
# read the rest of the bmp header, without its size
|
||||||
|
|
|
@ -325,7 +325,7 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
else:
|
else:
|
||||||
raise SyntaxError("not an EPS file")
|
raise SyntaxError("not an EPS file")
|
||||||
|
|
||||||
return (length, offset)
|
return length, offset
|
||||||
|
|
||||||
def load(self, scale=1, transparency=False):
|
def load(self, scale=1, transparency=False):
|
||||||
# Load EPS via Ghostscript
|
# Load EPS via Ghostscript
|
||||||
|
|
|
@ -22,7 +22,7 @@ from ._binary import i32le as i32
|
||||||
# we map from colour field tuples to (mode, rawmode) descriptors
|
# we map from colour field tuples to (mode, rawmode) descriptors
|
||||||
MODES = {
|
MODES = {
|
||||||
# opacity
|
# opacity
|
||||||
(0x00007FFE): ("A", "L"),
|
(0x00007FFE,): ("A", "L"),
|
||||||
# monochrome
|
# monochrome
|
||||||
(0x00010000,): ("L", "L"),
|
(0x00010000,): ("L", "L"),
|
||||||
(0x00018000, 0x00017FFE): ("RGBA", "LA"),
|
(0x00018000, 0x00017FFE): ("RGBA", "LA"),
|
||||||
|
@ -162,7 +162,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
"raw",
|
"raw",
|
||||||
(x, y, x + xtile, y + ytile),
|
(x, y, x + xtile, y + ytile),
|
||||||
i32(s, i) + 28,
|
i32(s, i) + 28,
|
||||||
(self.rawmode),
|
(self.rawmode,),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -101,7 +101,7 @@ class FtexImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
if format == Format.DXT1:
|
if format == Format.DXT1:
|
||||||
self.mode = "RGBA"
|
self.mode = "RGBA"
|
||||||
self.tile = [("bcn", (0, 0) + self.size, 0, (1))]
|
self.tile = [("bcn", (0, 0) + self.size, 0, 1)]
|
||||||
elif format == Format.UNCOMPRESSED:
|
elif format == Format.UNCOMPRESSED:
|
||||||
self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
|
self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -80,7 +80,7 @@ def open(fp, mode="r"):
|
||||||
"""
|
"""
|
||||||
Load texture from a GD image file.
|
Load texture from a GD image file.
|
||||||
|
|
||||||
:param filename: GD file name, or an opened file handle.
|
:param fp: GD file name, or an opened file handle.
|
||||||
:param mode: Optional mode. In this version, if the mode argument
|
:param mode: Optional mode. In this version, if the mode argument
|
||||||
is given, it must be "r".
|
is given, it must be "r".
|
||||||
:returns: An image instance.
|
:returns: An image instance.
|
||||||
|
|
|
@ -990,8 +990,6 @@ def getheader(im, palette=None, info=None):
|
||||||
return header, used_palette_colors
|
return header, used_palette_colors
|
||||||
|
|
||||||
|
|
||||||
# To specify duration, add the time in milliseconds to getdata(),
|
|
||||||
# e.g. getdata(im_frame, duration=1000)
|
|
||||||
def getdata(im, offset=(0, 0), **params):
|
def getdata(im, offset=(0, 0), **params):
|
||||||
"""
|
"""
|
||||||
Legacy Method
|
Legacy Method
|
||||||
|
@ -1000,10 +998,13 @@ def getdata(im, offset=(0, 0), **params):
|
||||||
The first string is a local image header, the rest contains
|
The first string is a local image header, the rest contains
|
||||||
encoded image data.
|
encoded image data.
|
||||||
|
|
||||||
|
To specify duration, add the time in milliseconds,
|
||||||
|
e.g. ``getdata(im_frame, duration=1000)``
|
||||||
|
|
||||||
:param im: Image object
|
:param im: Image object
|
||||||
:param offset: Tuple of (x, y) pixels. Defaults to (0,0)
|
:param offset: Tuple of (x, y) pixels. Defaults to (0, 0)
|
||||||
:param \\**params: E.g. duration or other encoder info parameters
|
:param \\**params: e.g. duration or other encoder info parameters
|
||||||
:returns: List of Bytes containing gif encoded frame data
|
:returns: List of bytes containing GIF encoded frame data
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ def register_handler(handler):
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[0:4] == b"GRIB" and prefix[7] == 1
|
return prefix[:4] == b"GRIB" and prefix[7] == 1
|
||||||
|
|
||||||
|
|
||||||
class GribStubImageFile(ImageFile.StubImageFile):
|
class GribStubImageFile(ImageFile.StubImageFile):
|
||||||
|
|
|
@ -210,7 +210,7 @@ class ImImageFile(ImageFile.ImageFile):
|
||||||
self.mode = self.info[MODE]
|
self.mode = self.info[MODE]
|
||||||
|
|
||||||
# Skip forward to start of image data
|
# Skip forward to start of image data
|
||||||
while s and s[0:1] != b"\x1A":
|
while s and s[:1] != b"\x1A":
|
||||||
s = self.fp.read(1)
|
s = self.fp.read(1)
|
||||||
if not s:
|
if not s:
|
||||||
raise SyntaxError("File truncated")
|
raise SyntaxError("File truncated")
|
||||||
|
|
|
@ -2546,7 +2546,7 @@ class Image:
|
||||||
h = box[3] - box[1]
|
h = box[3] - box[1]
|
||||||
|
|
||||||
if method == Transform.AFFINE:
|
if method == Transform.AFFINE:
|
||||||
data = data[0:6]
|
data = data[:6]
|
||||||
|
|
||||||
elif method == Transform.EXTENT:
|
elif method == Transform.EXTENT:
|
||||||
# convert extent to an affine transform
|
# convert extent to an affine transform
|
||||||
|
@ -2557,12 +2557,12 @@ class Image:
|
||||||
data = (xs, 0, x0, 0, ys, y0)
|
data = (xs, 0, x0, 0, ys, y0)
|
||||||
|
|
||||||
elif method == Transform.PERSPECTIVE:
|
elif method == Transform.PERSPECTIVE:
|
||||||
data = data[0:8]
|
data = data[:8]
|
||||||
|
|
||||||
elif method == Transform.QUAD:
|
elif method == Transform.QUAD:
|
||||||
# quadrilateral warp. data specifies the four corners
|
# quadrilateral warp. data specifies the four corners
|
||||||
# given as NW, SW, SE, and NE.
|
# given as NW, SW, SE, and NE.
|
||||||
nw = data[0:2]
|
nw = data[:2]
|
||||||
sw = data[2:4]
|
sw = data[2:4]
|
||||||
se = data[4:6]
|
se = data[4:6]
|
||||||
ne = data[6:8]
|
ne = data[6:8]
|
||||||
|
@ -2956,12 +2956,11 @@ _fromarray_typemap = {
|
||||||
((1, 1, 2), "|u1"): ("LA", "LA"),
|
((1, 1, 2), "|u1"): ("LA", "LA"),
|
||||||
((1, 1, 3), "|u1"): ("RGB", "RGB"),
|
((1, 1, 3), "|u1"): ("RGB", "RGB"),
|
||||||
((1, 1, 4), "|u1"): ("RGBA", "RGBA"),
|
((1, 1, 4), "|u1"): ("RGBA", "RGBA"),
|
||||||
|
# shortcuts:
|
||||||
|
((1, 1), _ENDIAN + "i4"): ("I", "I"),
|
||||||
|
((1, 1), _ENDIAN + "f4"): ("F", "F"),
|
||||||
}
|
}
|
||||||
|
|
||||||
# shortcuts
|
|
||||||
_fromarray_typemap[((1, 1), _ENDIAN + "i4")] = ("I", "I")
|
|
||||||
_fromarray_typemap[((1, 1), _ENDIAN + "f4")] = ("F", "F")
|
|
||||||
|
|
||||||
|
|
||||||
def _decompression_bomb_check(size):
|
def _decompression_bomb_check(size):
|
||||||
if MAX_IMAGE_PIXELS is None:
|
if MAX_IMAGE_PIXELS is None:
|
||||||
|
|
|
@ -316,6 +316,7 @@ def offset(image, xoffset, yoffset=None):
|
||||||
distances. Data wraps around the edges. If ``yoffset`` is omitted, it
|
distances. Data wraps around the edges. If ``yoffset`` is omitted, it
|
||||||
is assumed to be equal to ``xoffset``.
|
is assumed to be equal to ``xoffset``.
|
||||||
|
|
||||||
|
:param image: Input image.
|
||||||
:param xoffset: The horizontal distance.
|
:param xoffset: The horizontal distance.
|
||||||
:param yoffset: The vertical distance. If omitted, both
|
:param yoffset: The vertical distance. If omitted, both
|
||||||
distances are set to the same value.
|
distances are set to the same value.
|
||||||
|
|
|
@ -150,7 +150,7 @@ FLAGS = {
|
||||||
"SOFTPROOFING": 16384, # Do softproofing
|
"SOFTPROOFING": 16384, # Do softproofing
|
||||||
"PRESERVEBLACK": 32768, # Black preservation
|
"PRESERVEBLACK": 32768, # Black preservation
|
||||||
"NODEFAULTRESOURCEDEF": 16777216, # CRD special
|
"NODEFAULTRESOURCEDEF": 16777216, # CRD special
|
||||||
"GRIDPOINTS": lambda n: ((n) & 0xFF) << 16, # Gridpoints
|
"GRIDPOINTS": lambda n: (n & 0xFF) << 16, # Gridpoints
|
||||||
}
|
}
|
||||||
|
|
||||||
_MAX_FLAG = 0
|
_MAX_FLAG = 0
|
||||||
|
@ -1014,4 +1014,4 @@ def versions():
|
||||||
(pyCMS) Fetches versions.
|
(pyCMS) Fetches versions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return (VERSION, core.littlecms_version, sys.version.split()[0], Image.__version__)
|
return VERSION, core.littlecms_version, sys.version.split()[0], Image.__version__
|
||||||
|
|
|
@ -45,7 +45,7 @@ def getrgb(color):
|
||||||
|
|
||||||
# check for known string formats
|
# check for known string formats
|
||||||
if re.match("#[a-f0-9]{3}$", color):
|
if re.match("#[a-f0-9]{3}$", color):
|
||||||
return (int(color[1] * 2, 16), int(color[2] * 2, 16), int(color[3] * 2, 16))
|
return int(color[1] * 2, 16), int(color[2] * 2, 16), int(color[3] * 2, 16)
|
||||||
|
|
||||||
if re.match("#[a-f0-9]{4}$", color):
|
if re.match("#[a-f0-9]{4}$", color):
|
||||||
return (
|
return (
|
||||||
|
@ -56,7 +56,7 @@ def getrgb(color):
|
||||||
)
|
)
|
||||||
|
|
||||||
if re.match("#[a-f0-9]{6}$", color):
|
if re.match("#[a-f0-9]{6}$", color):
|
||||||
return (int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16))
|
return int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16)
|
||||||
|
|
||||||
if re.match("#[a-f0-9]{8}$", color):
|
if re.match("#[a-f0-9]{8}$", color):
|
||||||
return (
|
return (
|
||||||
|
@ -68,7 +68,7 @@ def getrgb(color):
|
||||||
|
|
||||||
m = re.match(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
|
m = re.match(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
|
||||||
if m:
|
if m:
|
||||||
return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
|
return int(m.group(1)), int(m.group(2)), int(m.group(3))
|
||||||
|
|
||||||
m = re.match(r"rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color)
|
m = re.match(r"rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$", color)
|
||||||
if m:
|
if m:
|
||||||
|
@ -114,25 +114,26 @@ def getrgb(color):
|
||||||
|
|
||||||
m = re.match(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
|
m = re.match(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color)
|
||||||
if m:
|
if m:
|
||||||
return (int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4)))
|
return int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4))
|
||||||
raise ValueError(f"unknown color specifier: {repr(color)}")
|
raise ValueError(f"unknown color specifier: {repr(color)}")
|
||||||
|
|
||||||
|
|
||||||
def getcolor(color, mode):
|
def getcolor(color, mode):
|
||||||
"""
|
"""
|
||||||
Same as :py:func:`~PIL.ImageColor.getrgb`, but converts the RGB value to a
|
Same as :py:func:`~PIL.ImageColor.getrgb`, but converts the RGB value to a
|
||||||
greyscale value if the mode is not color or a palette image. If the string
|
greyscale value if ``mode`` is not color or a palette image. If the string
|
||||||
cannot be parsed, this function raises a :py:exc:`ValueError` exception.
|
cannot be parsed, this function raises a :py:exc:`ValueError` exception.
|
||||||
|
|
||||||
.. versionadded:: 1.1.4
|
.. versionadded:: 1.1.4
|
||||||
|
|
||||||
:param color: A color string
|
:param color: A color string
|
||||||
:return: ``(graylevel [, alpha]) or (red, green, blue[, alpha])``
|
:param mode: Convert result to this mode
|
||||||
|
:return: ``(graylevel[, alpha]) or (red, green, blue[, alpha])``
|
||||||
"""
|
"""
|
||||||
# same as getrgb, but converts the result to the given mode
|
# same as getrgb, but converts the result to the given mode
|
||||||
color, alpha = getrgb(color), 255
|
color, alpha = getrgb(color), 255
|
||||||
if len(color) == 4:
|
if len(color) == 4:
|
||||||
color, alpha = color[0:3], color[3]
|
color, alpha = color[:3], color[3]
|
||||||
|
|
||||||
if Image.getmodebase(mode) == "L":
|
if Image.getmodebase(mode) == "L":
|
||||||
r, g, b = color
|
r, g, b = color
|
||||||
|
@ -140,7 +141,7 @@ def getcolor(color, mode):
|
||||||
# scaled to 24 bits to match the convert's implementation.
|
# scaled to 24 bits to match the convert's implementation.
|
||||||
color = (r * 19595 + g * 38470 + b * 7471 + 0x8000) >> 16
|
color = (r * 19595 + g * 38470 + b * 7471 + 0x8000) >> 16
|
||||||
if mode[-1] == "A":
|
if mode[-1] == "A":
|
||||||
return (color, alpha)
|
return color, alpha
|
||||||
else:
|
else:
|
||||||
if mode[-1] == "A":
|
if mode[-1] == "A":
|
||||||
return color + (alpha,)
|
return color + (alpha,)
|
||||||
|
|
|
@ -571,7 +571,7 @@ class PyCodecState:
|
||||||
self.yoff = 0
|
self.yoff = 0
|
||||||
|
|
||||||
def extents(self):
|
def extents(self):
|
||||||
return (self.xoff, self.yoff, self.xoff + self.xsize, self.yoff + self.ysize)
|
return self.xoff, self.yoff, self.xoff + self.xsize, self.yoff + self.ysize
|
||||||
|
|
||||||
|
|
||||||
class PyCodec:
|
class PyCodec:
|
||||||
|
@ -681,7 +681,7 @@ class PyDecoder(PyCodec):
|
||||||
|
|
||||||
if not rawmode:
|
if not rawmode:
|
||||||
rawmode = self.mode
|
rawmode = self.mode
|
||||||
d = Image._getdecoder(self.mode, "raw", (rawmode))
|
d = Image._getdecoder(self.mode, "raw", rawmode)
|
||||||
d.setimage(self.im, self.state.extents())
|
d.setimage(self.im, self.state.extents())
|
||||||
s = d.decode(data)
|
s = d.decode(data)
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@ _UNSPECIFIED = object()
|
||||||
|
|
||||||
|
|
||||||
class ImageFont:
|
class ImageFont:
|
||||||
"PIL font wrapper"
|
"""PIL font wrapper"""
|
||||||
|
|
||||||
def _load_pilfont(self, filename):
|
def _load_pilfont(self, filename):
|
||||||
|
|
||||||
|
@ -172,7 +172,7 @@ class ImageFont:
|
||||||
|
|
||||||
|
|
||||||
class FreeTypeFont:
|
class FreeTypeFont:
|
||||||
"FreeType font wrapper (requires _imagingft service)"
|
"""FreeType font wrapper (requires _imagingft service)"""
|
||||||
|
|
||||||
def __init__(self, font=None, size=10, index=0, encoding="", layout_engine=None):
|
def __init__(self, font=None, size=10, index=0, encoding="", layout_engine=None):
|
||||||
# FIXME: use service provider instead
|
# FIXME: use service provider instead
|
||||||
|
@ -774,7 +774,7 @@ class FreeTypeFont:
|
||||||
|
|
||||||
|
|
||||||
class TransposedFont:
|
class TransposedFont:
|
||||||
"Wrapper for writing rotated or mirrored text"
|
"""Wrapper for writing rotated or mirrored text"""
|
||||||
|
|
||||||
def __init__(self, font, orientation=None):
|
def __init__(self, font, orientation=None):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -33,7 +33,7 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N
|
||||||
args = ["screencapture"]
|
args = ["screencapture"]
|
||||||
if bbox:
|
if bbox:
|
||||||
left, top, right, bottom = bbox
|
left, top, right, bottom = bbox
|
||||||
args += ["-R", f"{left},{right},{right-left},{bottom-top}"]
|
args += ["-R", f"{left},{top},{right-left},{bottom-top}"]
|
||||||
subprocess.call(args + ["-x", filepath])
|
subprocess.call(args + ["-x", filepath])
|
||||||
im = Image.open(filepath)
|
im = Image.open(filepath)
|
||||||
im.load()
|
im.load()
|
||||||
|
|
|
@ -119,13 +119,13 @@ class LutBuilder:
|
||||||
# mirror
|
# mirror
|
||||||
if "M" in options:
|
if "M" in options:
|
||||||
n = len(patterns)
|
n = len(patterns)
|
||||||
for pattern, res in patterns[0:n]:
|
for pattern, res in patterns[:n]:
|
||||||
patterns.append((self._string_permute(pattern, MIRROR_MATRIX), res))
|
patterns.append((self._string_permute(pattern, MIRROR_MATRIX), res))
|
||||||
|
|
||||||
# negate
|
# negate
|
||||||
if "N" in options:
|
if "N" in options:
|
||||||
n = len(patterns)
|
n = len(patterns)
|
||||||
for pattern, res in patterns[0:n]:
|
for pattern, res in patterns[:n]:
|
||||||
# Swap 0 and 1
|
# Swap 0 and 1
|
||||||
pattern = pattern.replace("0", "Z").replace("1", "0").replace("Z", "1")
|
pattern = pattern.replace("0", "Z").replace("1", "0").replace("Z", "1")
|
||||||
res = 1 - int(res)
|
res = 1 - int(res)
|
||||||
|
|
|
@ -184,6 +184,8 @@ class PhotoImage:
|
||||||
:param im: A PIL image. The size must match the target region. If the
|
:param im: A PIL image. The size must match the target region. If the
|
||||||
mode does not match, the image is converted to the mode of
|
mode does not match, the image is converted to the mode of
|
||||||
the bitmap image.
|
the bitmap image.
|
||||||
|
:param box: Deprecated. This parameter will be removed in Pillow 10
|
||||||
|
(2023-07-01).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if box is not None:
|
if box is not None:
|
||||||
|
|
|
@ -124,7 +124,7 @@ def _parse_codestream(fp):
|
||||||
else:
|
else:
|
||||||
mode = None
|
mode = None
|
||||||
|
|
||||||
return (size, mode)
|
return size, mode
|
||||||
|
|
||||||
|
|
||||||
def _res_to_dpi(num, denom, exp):
|
def _res_to_dpi(num, denom, exp):
|
||||||
|
@ -191,7 +191,7 @@ def _parse_jp2_header(fp):
|
||||||
if size is None or mode is None:
|
if size is None or mode is None:
|
||||||
raise SyntaxError("Malformed JP2 header")
|
raise SyntaxError("Malformed JP2 header")
|
||||||
|
|
||||||
return (size, mode, mimetype, dpi)
|
return size, mode, mimetype, dpi
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -331,7 +331,7 @@ MARKER = {
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
# Magic number was taken from https://en.wikipedia.org/wiki/JPEG
|
# Magic number was taken from https://en.wikipedia.org/wiki/JPEG
|
||||||
return prefix[0:3] == b"\xFF\xD8\xFF"
|
return prefix[:3] == b"\xFF\xD8\xFF"
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -445,7 +445,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
self.decoderconfig = (scale, 0)
|
self.decoderconfig = (scale, 0)
|
||||||
|
|
||||||
box = (0, 0, original_size[0] / scale, original_size[1] / scale)
|
box = (0, 0, original_size[0] / scale, original_size[1] / scale)
|
||||||
return (self.mode, box)
|
return self.mode, box
|
||||||
|
|
||||||
def load_djpeg(self):
|
def load_djpeg(self):
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ You can get the subsampling of a JPEG with the
|
||||||
:func:`.JpegImagePlugin.get_sampling` function.
|
:func:`.JpegImagePlugin.get_sampling` function.
|
||||||
|
|
||||||
In JPEG compressed data a JPEG marker is used instead of an EXIF tag.
|
In JPEG compressed data a JPEG marker is used instead of an EXIF tag.
|
||||||
(ref.: https://www.exiv2.org/tags.html)
|
(ref.: https://exiv2.org/tags.html)
|
||||||
|
|
||||||
|
|
||||||
Quantization tables
|
Quantization tables
|
||||||
|
|
|
@ -31,7 +31,7 @@ class PaletteFile:
|
||||||
|
|
||||||
if not s:
|
if not s:
|
||||||
break
|
break
|
||||||
if s[0:1] == b"#":
|
if s[:1] == b"#":
|
||||||
continue
|
continue
|
||||||
if len(s) > 100:
|
if len(s) > 100:
|
||||||
raise SyntaxError("bad palette file")
|
raise SyntaxError("bad palette file")
|
||||||
|
|
|
@ -590,7 +590,7 @@ class PdfParser:
|
||||||
whitespace_mandatory
|
whitespace_mandatory
|
||||||
+ rb"trailer"
|
+ rb"trailer"
|
||||||
+ whitespace_optional
|
+ whitespace_optional
|
||||||
+ rb"\<\<(.*\>\>)"
|
+ rb"<<(.*>>)"
|
||||||
+ newline
|
+ newline
|
||||||
+ rb"startxref"
|
+ rb"startxref"
|
||||||
+ newline
|
+ newline
|
||||||
|
@ -605,7 +605,7 @@ class PdfParser:
|
||||||
whitespace_optional
|
whitespace_optional
|
||||||
+ rb"trailer"
|
+ rb"trailer"
|
||||||
+ whitespace_optional
|
+ whitespace_optional
|
||||||
+ rb"\<\<(.*?\>\>)"
|
+ rb"<<(.*?>>)"
|
||||||
+ newline
|
+ newline
|
||||||
+ rb"startxref"
|
+ rb"startxref"
|
||||||
+ newline
|
+ newline
|
||||||
|
@ -659,8 +659,8 @@ class PdfParser:
|
||||||
+ delimiter_or_ws
|
+ delimiter_or_ws
|
||||||
+ rb")"
|
+ rb")"
|
||||||
)
|
)
|
||||||
re_dict_start = re.compile(whitespace_optional + rb"\<\<")
|
re_dict_start = re.compile(whitespace_optional + rb"<<")
|
||||||
re_dict_end = re.compile(whitespace_optional + rb"\>\>" + whitespace_optional)
|
re_dict_end = re.compile(whitespace_optional + rb">>" + whitespace_optional)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def interpret_trailer(cls, trailer_data):
|
def interpret_trailer(cls, trailer_data):
|
||||||
|
@ -719,7 +719,7 @@ class PdfParser:
|
||||||
re_array_start = re.compile(whitespace_optional + rb"\[")
|
re_array_start = re.compile(whitespace_optional + rb"\[")
|
||||||
re_array_end = re.compile(whitespace_optional + rb"]")
|
re_array_end = re.compile(whitespace_optional + rb"]")
|
||||||
re_string_hex = re.compile(
|
re_string_hex = re.compile(
|
||||||
whitespace_optional + rb"\<(" + whitespace_or_hex + rb"*)\>"
|
whitespace_optional + rb"<(" + whitespace_or_hex + rb"*)>"
|
||||||
)
|
)
|
||||||
re_string_lit = re.compile(whitespace_optional + rb"\(")
|
re_string_lit = re.compile(whitespace_optional + rb"\(")
|
||||||
re_indirect_reference = re.compile(
|
re_indirect_reference = re.compile(
|
||||||
|
|
|
@ -83,7 +83,7 @@ class PpmImageFile(ImageFile.ImageFile):
|
||||||
# Token was not even 1 byte
|
# Token was not even 1 byte
|
||||||
raise ValueError("Reached EOF while reading header")
|
raise ValueError("Reached EOF while reading header")
|
||||||
elif len(token) > 10:
|
elif len(token) > 10:
|
||||||
raise ValueError(f"Token too long in file header: {token}")
|
raise ValueError(f"Token too long in file header: {token.decode()}")
|
||||||
return token
|
return token
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
|
|
|
@ -178,7 +178,7 @@ def _layerinfo(fp, ct_bytes):
|
||||||
if ct_bytes < (abs(ct) * 20):
|
if ct_bytes < (abs(ct) * 20):
|
||||||
raise SyntaxError("Layer block too short for number of layers requested")
|
raise SyntaxError("Layer block too short for number of layers requested")
|
||||||
|
|
||||||
for i in range(abs(ct)):
|
for _ in range(abs(ct)):
|
||||||
|
|
||||||
# bounding box
|
# bounding box
|
||||||
y0 = i32(read(4))
|
y0 = i32(read(4))
|
||||||
|
@ -193,7 +193,7 @@ def _layerinfo(fp, ct_bytes):
|
||||||
if len(types) > 4:
|
if len(types) > 4:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for i in types:
|
for _ in types:
|
||||||
type = i16(read(2))
|
type = i16(read(2))
|
||||||
|
|
||||||
if type == 65535:
|
if type == 65535:
|
||||||
|
|
|
@ -135,7 +135,7 @@ class _PyAccess32_2(PyAccess):
|
||||||
|
|
||||||
def get_pixel(self, x, y):
|
def get_pixel(self, x, y):
|
||||||
pixel = self.pixels[y][x]
|
pixel = self.pixels[y][x]
|
||||||
return (pixel.r, pixel.a)
|
return pixel.r, pixel.a
|
||||||
|
|
||||||
def set_pixel(self, x, y, color):
|
def set_pixel(self, x, y, color):
|
||||||
pixel = self.pixels[y][x]
|
pixel = self.pixels[y][x]
|
||||||
|
@ -152,7 +152,7 @@ class _PyAccess32_3(PyAccess):
|
||||||
|
|
||||||
def get_pixel(self, x, y):
|
def get_pixel(self, x, y):
|
||||||
pixel = self.pixels[y][x]
|
pixel = self.pixels[y][x]
|
||||||
return (pixel.r, pixel.g, pixel.b)
|
return pixel.r, pixel.g, pixel.b
|
||||||
|
|
||||||
def set_pixel(self, x, y, color):
|
def set_pixel(self, x, y, color):
|
||||||
pixel = self.pixels[y][x]
|
pixel = self.pixels[y][x]
|
||||||
|
@ -171,7 +171,7 @@ class _PyAccess32_4(PyAccess):
|
||||||
|
|
||||||
def get_pixel(self, x, y):
|
def get_pixel(self, x, y):
|
||||||
pixel = self.pixels[y][x]
|
pixel = self.pixels[y][x]
|
||||||
return (pixel.r, pixel.g, pixel.b, pixel.a)
|
return pixel.r, pixel.g, pixel.b, pixel.a
|
||||||
|
|
||||||
def set_pixel(self, x, y, color):
|
def set_pixel(self, x, y, color):
|
||||||
pixel = self.pixels[y][x]
|
pixel = self.pixels[y][x]
|
||||||
|
|
|
@ -338,12 +338,12 @@ class IFDRational(Rational):
|
||||||
self._val = Fraction(value, denominator)
|
self._val = Fraction(value, denominator)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def numerator(a):
|
def numerator(self):
|
||||||
return a._numerator
|
return self._numerator
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def denominator(a):
|
def denominator(self):
|
||||||
return a._denominator
|
return self._denominator
|
||||||
|
|
||||||
def limit_rational(self, max_denominator):
|
def limit_rational(self, max_denominator):
|
||||||
"""
|
"""
|
||||||
|
@ -353,10 +353,10 @@ class IFDRational(Rational):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.denominator == 0:
|
if self.denominator == 0:
|
||||||
return (self.numerator, self.denominator)
|
return self.numerator, self.denominator
|
||||||
|
|
||||||
f = self._val.limit_denominator(max_denominator)
|
f = self._val.limit_denominator(max_denominator)
|
||||||
return (f.numerator, f.denominator)
|
return f.numerator, f.denominator
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return str(float(self._val))
|
return str(float(self._val))
|
||||||
|
@ -1748,9 +1748,8 @@ def _save(im, fp, filename):
|
||||||
SUBIFD,
|
SUBIFD,
|
||||||
]
|
]
|
||||||
|
|
||||||
atts = {}
|
|
||||||
# bits per sample is a single short in the tiff directory, not a list.
|
# bits per sample is a single short in the tiff directory, not a list.
|
||||||
atts[BITSPERSAMPLE] = bits[0]
|
atts = {BITSPERSAMPLE: bits[0]}
|
||||||
# Merge the ones that we have with (optional) more bits from
|
# Merge the ones that we have with (optional) more bits from
|
||||||
# the original file, e.g x,y resolution so that we can
|
# the original file, e.g x,y resolution so that we can
|
||||||
# save(load('')) == original file.
|
# save(load('')) == original file.
|
||||||
|
|
|
@ -36,8 +36,12 @@ class TagInfo(namedtuple("_TagInfo", "value name type length enum")):
|
||||||
def lookup(tag, group=None):
|
def lookup(tag, group=None):
|
||||||
"""
|
"""
|
||||||
:param tag: Integer tag number
|
:param tag: Integer tag number
|
||||||
:returns: Taginfo namedtuple, From the TAGS_V2 info if possible,
|
:param group: Which :py:data:`~PIL.TiffTags.TAGS_V2_GROUPS` to look in
|
||||||
otherwise just populating the value and name from TAGS.
|
|
||||||
|
.. versionadded:: 8.3.0
|
||||||
|
|
||||||
|
:returns: Taginfo namedtuple, From the ``TAGS_V2`` info if possible,
|
||||||
|
otherwise just populating the value and name from ``TAGS``.
|
||||||
If the tag is not recognized, "unknown" is returned for the name
|
If the tag is not recognized, "unknown" is returned for the name
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -222,11 +222,10 @@ def _save_all(im, fp, filename):
|
||||||
if (
|
if (
|
||||||
not isinstance(background, (list, tuple))
|
not isinstance(background, (list, tuple))
|
||||||
or len(background) != 4
|
or len(background) != 4
|
||||||
or not all(v >= 0 and v < 256 for v in background)
|
or not all(0 <= v < 256 for v in background)
|
||||||
):
|
):
|
||||||
raise OSError(
|
raise OSError(
|
||||||
"Background color is not an RGBA tuple clamped to (0-255): %s"
|
f"Background color is not an RGBA tuple clamped to (0-255): {background}"
|
||||||
% str(background)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Convert to packed uint
|
# Convert to packed uint
|
||||||
|
|
|
@ -31,7 +31,7 @@ xbm_head = re.compile(
|
||||||
b"#define[ \t]+[^_]*_x_hot[ \t]+(?P<xhot>[0-9]+)[\r\n]+"
|
b"#define[ \t]+[^_]*_x_hot[ \t]+(?P<xhot>[0-9]+)[\r\n]+"
|
||||||
b"#define[ \t]+[^_]*_y_hot[ \t]+(?P<yhot>[0-9]+)[\r\n]+"
|
b"#define[ \t]+[^_]*_y_hot[ \t]+(?P<yhot>[0-9]+)[\r\n]+"
|
||||||
b")?"
|
b")?"
|
||||||
b"[\\000-\\377]*_bits\\[\\]"
|
rb"[\000-\377]*_bits\[]"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ class XpmImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
palette = [b"\0\0\0"] * 256
|
palette = [b"\0\0\0"] * 256
|
||||||
|
|
||||||
for i in range(pal):
|
for _ in range(pal):
|
||||||
|
|
||||||
s = self.fp.readline()
|
s = self.fp.readline()
|
||||||
if s[-2:] == b"\r\n":
|
if s[-2:] == b"\r\n":
|
||||||
|
@ -83,7 +83,7 @@ class XpmImageFile(ImageFile.ImageFile):
|
||||||
rgb = s[i + 1]
|
rgb = s[i + 1]
|
||||||
if rgb == b"None":
|
if rgb == b"None":
|
||||||
self.info["transparency"] = c
|
self.info["transparency"] = c
|
||||||
elif rgb[0:1] == b"#":
|
elif rgb[:1] == b"#":
|
||||||
# FIXME: handle colour names (see ImagePalette.py)
|
# FIXME: handle colour names (see ImagePalette.py)
|
||||||
rgb = int(rgb[1:], 16)
|
rgb = int(rgb[1:], 16)
|
||||||
palette[c] = (
|
palette[c] = (
|
||||||
|
|
Loading…
Reference in New Issue
Block a user