mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-25 17:36:18 +03:00
Merge branch 'main' into apng
This commit is contained in:
commit
17b19b5668
|
@ -22,7 +22,8 @@ set -e
|
||||||
if [[ $(uname) != CYGWIN* ]]; then
|
if [[ $(uname) != CYGWIN* ]]; then
|
||||||
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
|
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
|
||||||
ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
|
ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
|
||||||
cmake meson imagemagick libharfbuzz-dev libfribidi-dev
|
cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
|
||||||
|
sway wl-clipboard
|
||||||
fi
|
fi
|
||||||
|
|
||||||
python3 -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
|
@ -41,7 +42,7 @@ if [[ $(uname) != CYGWIN* ]]; then
|
||||||
if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi
|
if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi
|
||||||
|
|
||||||
# PyQt6 doesn't support PyPy3
|
# PyQt6 doesn't support PyPy3
|
||||||
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
if [[ "$GHA_PYTHON_VERSION" != "3.12-dev" && $GHA_PYTHON_VERSION == 3.* ]]; then
|
||||||
sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
|
sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
|
||||||
python3 -m pip install pyqt6
|
python3 -m pip install pyqt6
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -13,10 +13,6 @@ indent_style = space
|
||||||
|
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
[*.rst]
|
|
||||||
# Four-space indentation
|
|
||||||
indent_size = 4
|
|
||||||
|
|
||||||
[*.yml]
|
[*.yml]
|
||||||
# Two-space indentation
|
# Two-space indentation
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
1
.github/workflows/test-docker.yml
vendored
1
.github/workflows/test-docker.yml
vendored
|
@ -39,6 +39,7 @@ jobs:
|
||||||
centos-stream-8-amd64,
|
centos-stream-8-amd64,
|
||||||
centos-stream-9-amd64,
|
centos-stream-9-amd64,
|
||||||
debian-11-bullseye-x86,
|
debian-11-bullseye-x86,
|
||||||
|
debian-12-bookworm-x86,
|
||||||
fedora-37-amd64,
|
fedora-37-amd64,
|
||||||
fedora-38-amd64,
|
fedora-38-amd64,
|
||||||
gentoo,
|
gentoo,
|
||||||
|
|
4
.github/workflows/test-windows.yml
vendored
4
.github/workflows/test-windows.yml
vendored
|
@ -65,8 +65,8 @@ jobs:
|
||||||
- name: Print build system information
|
- name: Print build system information
|
||||||
run: python3 .github/workflows/system-info.py
|
run: python3 .github/workflows/system-info.py
|
||||||
|
|
||||||
- name: python3 -m pip install wheel pytest pytest-cov pytest-timeout defusedxml
|
- name: python3 -m pip install setuptools wheel pytest pytest-cov pytest-timeout defusedxml
|
||||||
run: python3 -m pip install wheel pytest pytest-cov pytest-timeout defusedxml
|
run: python3 -m pip install setuptools wheel pytest pytest-cov pytest-timeout defusedxml
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
id: install
|
id: install
|
||||||
|
|
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
|
@ -84,7 +84,9 @@ jobs:
|
||||||
python3 -m pip install pytest-reverse
|
python3 -m pip install pytest-reverse
|
||||||
fi
|
fi
|
||||||
if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then
|
if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then
|
||||||
xvfb-run -s '-screen 0 1024x768x24' .ci/test.sh
|
xvfb-run -s '-screen 0 1024x768x24' sway&
|
||||||
|
export WAYLAND_DISPLAY=wayland-1
|
||||||
|
.ci/test.sh
|
||||||
else
|
else
|
||||||
.ci/test.sh
|
.ci/test.sh
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -4,9 +4,6 @@ repos:
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args: [--target-version=py38]
|
args: [--target-version=py38]
|
||||||
# Only .py files, until https://github.com/psf/black/issues/402 resolved
|
|
||||||
files: \.py$
|
|
||||||
types: []
|
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/isort
|
- repo: https://github.com/PyCQA/isort
|
||||||
rev: 5.12.0
|
rev: 5.12.0
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
version: 2
|
version: 2
|
||||||
|
|
||||||
formats: all
|
formats: [pdf]
|
||||||
|
|
||||||
build:
|
build:
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
|
|
51
CHANGES.rst
51
CHANGES.rst
|
@ -5,6 +5,57 @@ Changelog (Pillow)
|
||||||
10.0.0 (unreleased)
|
10.0.0 (unreleased)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
- Fixed combining single duration across duplicate APNG frames #7146
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Remove temporary file when error is raised #7148
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Do not use temporary file when grabbing clipboard on Linux #7200
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- If the clipboard fails to open on Windows, wait and try again #7141
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed saving multiple 1 mode frames to GIF #7181
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Replaced absolute PIL import with relative import #7173
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Replaced deprecated Py_FileSystemDefaultEncoding for Python >= 3.12 #7192
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Improved wl-paste mimetype handling in ImageGrab #7094
|
||||||
|
[rrcgat, radarhere]
|
||||||
|
|
||||||
|
- Added _repr_jpeg_() for IPython display_jpeg #7135
|
||||||
|
[n3011, radarhere, nulano]
|
||||||
|
|
||||||
|
- Use "/sbin/ldconfig" if ldconfig is not found #7068
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Prefer screenshots using XCB over gnome-screenshot #7143
|
||||||
|
[nulano, radarhere]
|
||||||
|
|
||||||
|
- Fixed joined corners for ImageDraw rounded_rectangle() odd dimensions #7151
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Support reading signed 8-bit TIFF images #7111
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added width argument to ImageDraw regular_polygon #7132
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Support I mode for ImageFilter.BuiltinFilter #7108
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Raise error from stderr of Linux ImageGrab.grabclipboard() command #7112
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added unpacker from I;16B to I;16 #7125
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
- Support float font sizes #7107
|
- Support float font sizes #7107
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
|
|
|
@ -75,43 +75,42 @@ post-patch:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def test_qtables_leak():
|
standard_l_qtable = (
|
||||||
|
# fmt: off
|
||||||
|
16, 11, 10, 16, 24, 40, 51, 61,
|
||||||
|
12, 12, 14, 19, 26, 58, 60, 55,
|
||||||
|
14, 13, 16, 24, 40, 57, 69, 56,
|
||||||
|
14, 17, 22, 29, 51, 87, 80, 62,
|
||||||
|
18, 22, 37, 56, 68, 109, 103, 77,
|
||||||
|
24, 35, 55, 64, 81, 104, 113, 92,
|
||||||
|
49, 64, 78, 87, 103, 121, 120, 101,
|
||||||
|
72, 92, 95, 98, 112, 100, 103, 99,
|
||||||
|
# fmt: on
|
||||||
|
)
|
||||||
|
|
||||||
|
standard_chrominance_qtable = (
|
||||||
|
# fmt: off
|
||||||
|
17, 18, 24, 47, 99, 99, 99, 99,
|
||||||
|
18, 21, 26, 66, 99, 99, 99, 99,
|
||||||
|
24, 26, 56, 99, 99, 99, 99, 99,
|
||||||
|
47, 66, 99, 99, 99, 99, 99, 99,
|
||||||
|
99, 99, 99, 99, 99, 99, 99, 99,
|
||||||
|
99, 99, 99, 99, 99, 99, 99, 99,
|
||||||
|
99, 99, 99, 99, 99, 99, 99, 99,
|
||||||
|
99, 99, 99, 99, 99, 99, 99, 99,
|
||||||
|
# fmt: on
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"qtables",
|
||||||
|
(
|
||||||
|
(standard_l_qtable, standard_chrominance_qtable),
|
||||||
|
[standard_l_qtable, standard_chrominance_qtable],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_qtables_leak(qtables):
|
||||||
im = hopper("RGB")
|
im = hopper("RGB")
|
||||||
|
|
||||||
standard_l_qtable = [
|
|
||||||
int(s)
|
|
||||||
for s in """
|
|
||||||
16 11 10 16 24 40 51 61
|
|
||||||
12 12 14 19 26 58 60 55
|
|
||||||
14 13 16 24 40 57 69 56
|
|
||||||
14 17 22 29 51 87 80 62
|
|
||||||
18 22 37 56 68 109 103 77
|
|
||||||
24 35 55 64 81 104 113 92
|
|
||||||
49 64 78 87 103 121 120 101
|
|
||||||
72 92 95 98 112 100 103 99
|
|
||||||
""".split(
|
|
||||||
None
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
standard_chrominance_qtable = [
|
|
||||||
int(s)
|
|
||||||
for s in """
|
|
||||||
17 18 24 47 99 99 99 99
|
|
||||||
18 21 26 66 99 99 99 99
|
|
||||||
24 26 56 99 99 99 99 99
|
|
||||||
47 66 99 99 99 99 99 99
|
|
||||||
99 99 99 99 99 99 99 99
|
|
||||||
99 99 99 99 99 99 99 99
|
|
||||||
99 99 99 99 99 99 99 99
|
|
||||||
99 99 99 99 99 99 99 99
|
|
||||||
""".split(
|
|
||||||
None
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
qtables = [standard_l_qtable, standard_chrominance_qtable]
|
|
||||||
|
|
||||||
for _ in range(iterations):
|
for _ in range(iterations):
|
||||||
test_output = BytesIO()
|
test_output = BytesIO()
|
||||||
im.save(test_output, "JPEG", qtables=qtables)
|
im.save(test_output, "JPEG", qtables=qtables)
|
||||||
|
|
BIN
Tests/images/8bit.s.tif
Normal file
BIN
Tests/images/8bit.s.tif
Normal file
Binary file not shown.
BIN
Tests/images/hopper_emboss_I.png
Normal file
BIN
Tests/images/hopper_emboss_I.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
Tests/images/hopper_emboss_more_I.png
Normal file
BIN
Tests/images/hopper_emboss_more_I.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
Tests/images/imagedraw_rounded_rectangle_x_odd.png
Normal file
BIN
Tests/images/imagedraw_rounded_rectangle_x_odd.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 565 B |
BIN
Tests/images/imagedraw_rounded_rectangle_y_odd.png
Normal file
BIN
Tests/images/imagedraw_rounded_rectangle_y_odd.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 527 B |
BIN
Tests/images/imagedraw_triangle_width.png
Normal file
BIN
Tests/images/imagedraw_triangle_width.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 499 B |
|
@ -461,6 +461,17 @@ def test_apng_save_duration_loop(tmp_path):
|
||||||
assert im.info.get("duration") == 750
|
assert im.info.get("duration") == 750
|
||||||
|
|
||||||
|
|
||||||
|
def test_apng_save_duplicate_duration(tmp_path):
|
||||||
|
test_file = str(tmp_path / "temp.png")
|
||||||
|
frame = Image.new("RGB", (1, 1))
|
||||||
|
|
||||||
|
# Test a single duration is correctly combined across duplicate frames
|
||||||
|
frame.save(test_file, save_all=True, append_images=[frame, frame], duration=500)
|
||||||
|
with Image.open(test_file) as im:
|
||||||
|
assert im.n_frames == 1
|
||||||
|
assert im.info.get("duration") == 1500
|
||||||
|
|
||||||
|
|
||||||
def test_apng_save_disposal(tmp_path):
|
def test_apng_save_disposal(tmp_path):
|
||||||
test_file = str(tmp_path / "temp.png")
|
test_file = str(tmp_path / "temp.png")
|
||||||
size = (128, 64)
|
size = (128, 64)
|
||||||
|
|
|
@ -252,6 +252,19 @@ def test_roundtrip_save_all(tmp_path):
|
||||||
assert reread.n_frames == 5
|
assert reread.n_frames == 5
|
||||||
|
|
||||||
|
|
||||||
|
def test_roundtrip_save_all_1(tmp_path):
|
||||||
|
out = str(tmp_path / "temp.gif")
|
||||||
|
im = Image.new("1", (1, 1))
|
||||||
|
im2 = Image.new("1", (1, 1), 1)
|
||||||
|
im.save(out, save_all=True, append_images=[im2])
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert reloaded.getpixel((0, 0)) == 0
|
||||||
|
|
||||||
|
reloaded.seek(1)
|
||||||
|
assert reloaded.getpixel((0, 0)) == 255
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path, mode",
|
"path, mode",
|
||||||
(
|
(
|
||||||
|
|
|
@ -922,6 +922,19 @@ class TestFileJpeg:
|
||||||
im.load()
|
im.load()
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
||||||
|
|
||||||
|
def test_repr_jpeg(self):
|
||||||
|
im = hopper()
|
||||||
|
|
||||||
|
with Image.open(BytesIO(im._repr_jpeg_())) as repr_jpeg:
|
||||||
|
assert repr_jpeg.format == "JPEG"
|
||||||
|
assert_image_similar(im, repr_jpeg, 17)
|
||||||
|
|
||||||
|
def test_repr_jpeg_error(self):
|
||||||
|
im = hopper("F")
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
im._repr_jpeg_()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not is_win32(), reason="Windows only")
|
@pytest.mark.skipif(not is_win32(), reason="Windows only")
|
||||||
@skip_unless_feature("jpg")
|
@skip_unless_feature("jpg")
|
||||||
|
|
|
@ -96,10 +96,17 @@ class TestFileTiff:
|
||||||
|
|
||||||
assert_image_similar_tofile(im, "Tests/images/pil136.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/pil136.png", 1)
|
||||||
|
|
||||||
def test_bigtiff(self):
|
def test_bigtiff(self, tmp_path):
|
||||||
with Image.open("Tests/images/hopper_bigtiff.tif") as im:
|
with Image.open("Tests/images/hopper_bigtiff.tif") as im:
|
||||||
assert_image_equal_tofile(im, "Tests/images/hopper.tif")
|
assert_image_equal_tofile(im, "Tests/images/hopper.tif")
|
||||||
|
|
||||||
|
with Image.open("Tests/images/hopper_bigtiff.tif") as im:
|
||||||
|
# multistrip support not yet implemented
|
||||||
|
del im.tag_v2[273]
|
||||||
|
|
||||||
|
outfile = str(tmp_path / "temp.tif")
|
||||||
|
im.save(outfile, save_all=True, append_images=[im], tiffinfo=im.tag_v2)
|
||||||
|
|
||||||
def test_set_legacy_api(self):
|
def test_set_legacy_api(self):
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
with pytest.raises(Exception) as e:
|
with pytest.raises(Exception) as e:
|
||||||
|
@ -198,6 +205,12 @@ class TestFileTiff:
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
im.save(outfile)
|
im.save(outfile)
|
||||||
|
|
||||||
|
def test_8bit_s(self):
|
||||||
|
with Image.open("Tests/images/8bit.s.tif") as im:
|
||||||
|
im.load()
|
||||||
|
assert im.mode == "L"
|
||||||
|
assert im.getpixel((50, 50)) == 184
|
||||||
|
|
||||||
def test_little_endian(self):
|
def test_little_endian(self):
|
||||||
with Image.open("Tests/images/16bit.cropped.tif") as im:
|
with Image.open("Tests/images/16bit.cropped.tif") as im:
|
||||||
assert im.getpixel((0, 0)) == 480
|
assert im.getpixel((0, 0)) == 480
|
||||||
|
|
|
@ -30,15 +30,16 @@ from .helper import assert_image_equal, hopper
|
||||||
ImageFilter.UnsharpMask(10),
|
ImageFilter.UnsharpMask(10),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@pytest.mark.parametrize("mode", ("L", "RGB", "CMYK"))
|
@pytest.mark.parametrize("mode", ("L", "I", "RGB", "CMYK"))
|
||||||
def test_sanity(filter_to_apply, mode):
|
def test_sanity(filter_to_apply, mode):
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
out = im.filter(filter_to_apply)
|
if mode != "I" or isinstance(filter_to_apply, ImageFilter.BuiltinFilter):
|
||||||
assert out.mode == im.mode
|
out = im.filter(filter_to_apply)
|
||||||
assert out.size == im.size
|
assert out.mode == im.mode
|
||||||
|
assert out.size == im.size
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("L", "RGB", "CMYK"))
|
@pytest.mark.parametrize("mode", ("L", "I", "RGB", "CMYK"))
|
||||||
def test_sanity_error(mode):
|
def test_sanity_error(mode):
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
|
@ -130,10 +131,12 @@ def test_kernel_not_enough_coefficients():
|
||||||
ImageFilter.Kernel((3, 3), (0, 0))
|
ImageFilter.Kernel((3, 3), (0, 0))
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("L", "LA", "RGB", "CMYK"))
|
@pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK"))
|
||||||
def test_consistency_3x3(mode):
|
def test_consistency_3x3(mode):
|
||||||
with Image.open("Tests/images/hopper.bmp") as source:
|
with Image.open("Tests/images/hopper.bmp") as source:
|
||||||
with Image.open("Tests/images/hopper_emboss.bmp") as reference:
|
reference_name = "hopper_emboss"
|
||||||
|
reference_name += "_I.png" if mode == "I" else ".bmp"
|
||||||
|
with Image.open("Tests/images/" + reference_name) as reference:
|
||||||
kernel = ImageFilter.Kernel(
|
kernel = ImageFilter.Kernel(
|
||||||
(3, 3),
|
(3, 3),
|
||||||
# fmt: off
|
# fmt: off
|
||||||
|
@ -146,16 +149,20 @@ def test_consistency_3x3(mode):
|
||||||
source = source.split() * 2
|
source = source.split() * 2
|
||||||
reference = reference.split() * 2
|
reference = reference.split() * 2
|
||||||
|
|
||||||
assert_image_equal(
|
if mode == "I":
|
||||||
Image.merge(mode, source[: len(mode)]).filter(kernel),
|
source = source[0].convert(mode)
|
||||||
Image.merge(mode, reference[: len(mode)]),
|
else:
|
||||||
)
|
source = Image.merge(mode, source[: len(mode)])
|
||||||
|
reference = Image.merge(mode, reference[: len(mode)])
|
||||||
|
assert_image_equal(source.filter(kernel), reference)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("L", "LA", "RGB", "CMYK"))
|
@pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK"))
|
||||||
def test_consistency_5x5(mode):
|
def test_consistency_5x5(mode):
|
||||||
with Image.open("Tests/images/hopper.bmp") as source:
|
with Image.open("Tests/images/hopper.bmp") as source:
|
||||||
with Image.open("Tests/images/hopper_emboss_more.bmp") as reference:
|
reference_name = "hopper_emboss_more"
|
||||||
|
reference_name += "_I.png" if mode == "I" else ".bmp"
|
||||||
|
with Image.open("Tests/images/" + reference_name) as reference:
|
||||||
kernel = ImageFilter.Kernel(
|
kernel = ImageFilter.Kernel(
|
||||||
(5, 5),
|
(5, 5),
|
||||||
# fmt: off
|
# fmt: off
|
||||||
|
@ -170,10 +177,12 @@ def test_consistency_5x5(mode):
|
||||||
source = source.split() * 2
|
source = source.split() * 2
|
||||||
reference = reference.split() * 2
|
reference = reference.split() * 2
|
||||||
|
|
||||||
assert_image_equal(
|
if mode == "I":
|
||||||
Image.merge(mode, source[: len(mode)]).filter(kernel),
|
source = source[0].convert(mode)
|
||||||
Image.merge(mode, reference[: len(mode)]),
|
else:
|
||||||
)
|
source = Image.merge(mode, source[: len(mode)])
|
||||||
|
reference = Image.merge(mode, reference[: len(mode)])
|
||||||
|
assert_image_equal(source.filter(kernel), reference)
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_box_blur_filter():
|
def test_invalid_box_blur_filter():
|
||||||
|
|
|
@ -32,6 +32,14 @@ def test_putpalette():
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
palette("YCbCr")
|
palette("YCbCr")
|
||||||
|
|
||||||
|
with Image.open("Tests/images/hopper_gray.jpg") as im:
|
||||||
|
assert im.mode == "L"
|
||||||
|
im.putpalette(list(range(256)) * 3)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/la.tga") as im:
|
||||||
|
assert im.mode == "LA"
|
||||||
|
im.putpalette(list(range(256)) * 3)
|
||||||
|
|
||||||
|
|
||||||
def test_imagepalette():
|
def test_imagepalette():
|
||||||
im = hopper("P")
|
im = hopper("P")
|
||||||
|
|
|
@ -27,15 +27,21 @@ X1 = int(X0 * 3)
|
||||||
Y0 = int(H / 4)
|
Y0 = int(H / 4)
|
||||||
Y1 = int(X0 * 3)
|
Y1 = int(X0 * 3)
|
||||||
|
|
||||||
# Two kinds of bounding box
|
# Bounding boxes
|
||||||
BBOX1 = [(X0, Y0), (X1, Y1)]
|
BBOX = (((X0, Y0), (X1, Y1)), [(X0, Y0), (X1, Y1)], (X0, Y0, X1, Y1), [X0, Y0, X1, Y1])
|
||||||
BBOX2 = [X0, Y0, X1, Y1]
|
|
||||||
|
|
||||||
# Two kinds of coordinate sequences
|
# Coordinate sequences
|
||||||
POINTS1 = [(10, 10), (20, 40), (30, 30)]
|
POINTS = (
|
||||||
POINTS2 = [10, 10, 20, 40, 30, 30]
|
((10, 10), (20, 40), (30, 30)),
|
||||||
|
[(10, 10), (20, 40), (30, 30)],
|
||||||
|
(10, 10, 20, 40, 30, 30),
|
||||||
|
[10, 10, 20, 40, 30, 30],
|
||||||
|
)
|
||||||
|
|
||||||
KITE_POINTS = [(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)]
|
KITE_POINTS = (
|
||||||
|
((10, 50), (70, 10), (90, 50), (70, 90), (10, 50)),
|
||||||
|
[(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_sanity():
|
def test_sanity():
|
||||||
|
@ -63,7 +69,7 @@ def test_mode_mismatch():
|
||||||
ImageDraw.ImageDraw(im, mode="L")
|
ImageDraw.ImageDraw(im, mode="L")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("bbox", (BBOX1, BBOX2))
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
@pytest.mark.parametrize("start, end", ((0, 180), (0.5, 180.4)))
|
@pytest.mark.parametrize("start, end", ((0, 180), (0.5, 180.4)))
|
||||||
def test_arc(bbox, start, end):
|
def test_arc(bbox, start, end):
|
||||||
# Arrange
|
# Arrange
|
||||||
|
@ -77,7 +83,8 @@ def test_arc(bbox, start, end):
|
||||||
assert_image_similar_tofile(im, "Tests/images/imagedraw_arc.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_arc.png", 1)
|
||||||
|
|
||||||
|
|
||||||
def test_arc_end_le_start():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_arc_end_le_start(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
@ -85,13 +92,14 @@ def test_arc_end_le_start():
|
||||||
end = 0
|
end = 0
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.arc(BBOX1, start=start, end=end)
|
draw.arc(bbox, start=start, end=end)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_arc_end_le_start.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_arc_end_le_start.png")
|
||||||
|
|
||||||
|
|
||||||
def test_arc_no_loops():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_arc_no_loops(bbox):
|
||||||
# No need to go in loops
|
# No need to go in loops
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
|
@ -100,57 +108,61 @@ def test_arc_no_loops():
|
||||||
end = 370
|
end = 370
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.arc(BBOX1, start=start, end=end)
|
draw.arc(bbox, start=start, end=end)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_no_loops.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_no_loops.png", 1)
|
||||||
|
|
||||||
|
|
||||||
def test_arc_width():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_arc_width(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.arc(BBOX1, 10, 260, width=5)
|
draw.arc(bbox, 10, 260, width=5)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width.png", 1)
|
||||||
|
|
||||||
|
|
||||||
def test_arc_width_pieslice_large():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_arc_width_pieslice_large(bbox):
|
||||||
# Tests an arc with a large enough width that it is a pieslice
|
# Tests an arc with a large enough width that it is a pieslice
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.arc(BBOX1, 10, 260, fill="yellow", width=100)
|
draw.arc(bbox, 10, 260, fill="yellow", width=100)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width_pieslice.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width_pieslice.png", 1)
|
||||||
|
|
||||||
|
|
||||||
def test_arc_width_fill():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_arc_width_fill(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.arc(BBOX1, 10, 260, fill="yellow", width=5)
|
draw.arc(bbox, 10, 260, fill="yellow", width=5)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width_fill.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width_fill.png", 1)
|
||||||
|
|
||||||
|
|
||||||
def test_arc_width_non_whole_angle():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_arc_width_non_whole_angle(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
expected = "Tests/images/imagedraw_arc_width_non_whole_angle.png"
|
expected = "Tests/images/imagedraw_arc_width_non_whole_angle.png"
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.arc(BBOX1, 10, 259.5, width=5)
|
draw.arc(bbox, 10, 259.5, width=5)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_similar_tofile(im, expected, 1)
|
assert_image_similar_tofile(im, expected, 1)
|
||||||
|
@ -184,7 +196,7 @@ def test_bitmap():
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("RGB", "L"))
|
@pytest.mark.parametrize("mode", ("RGB", "L"))
|
||||||
@pytest.mark.parametrize("bbox", (BBOX1, BBOX2))
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
def test_chord(mode, bbox):
|
def test_chord(mode, bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new(mode, (W, H))
|
im = Image.new(mode, (W, H))
|
||||||
|
@ -198,37 +210,40 @@ def test_chord(mode, bbox):
|
||||||
assert_image_similar_tofile(im, expected, 1)
|
assert_image_similar_tofile(im, expected, 1)
|
||||||
|
|
||||||
|
|
||||||
def test_chord_width():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_chord_width(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.chord(BBOX1, 10, 260, outline="yellow", width=5)
|
draw.chord(bbox, 10, 260, outline="yellow", width=5)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_similar_tofile(im, "Tests/images/imagedraw_chord_width.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_chord_width.png", 1)
|
||||||
|
|
||||||
|
|
||||||
def test_chord_width_fill():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_chord_width_fill(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.chord(BBOX1, 10, 260, fill="red", outline="yellow", width=5)
|
draw.chord(bbox, 10, 260, fill="red", outline="yellow", width=5)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_similar_tofile(im, "Tests/images/imagedraw_chord_width_fill.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_chord_width_fill.png", 1)
|
||||||
|
|
||||||
|
|
||||||
def test_chord_zero_width():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_chord_zero_width(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.chord(BBOX1, 10, 260, fill="red", outline="yellow", width=0)
|
draw.chord(bbox, 10, 260, fill="red", outline="yellow", width=0)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_chord_zero_width.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_chord_zero_width.png")
|
||||||
|
@ -247,7 +262,7 @@ def test_chord_too_fat():
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("RGB", "L"))
|
@pytest.mark.parametrize("mode", ("RGB", "L"))
|
||||||
@pytest.mark.parametrize("bbox", (BBOX1, BBOX2))
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
def test_ellipse(mode, bbox):
|
def test_ellipse(mode, bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new(mode, (W, H))
|
im = Image.new(mode, (W, H))
|
||||||
|
@ -261,13 +276,14 @@ def test_ellipse(mode, bbox):
|
||||||
assert_image_similar_tofile(im, expected, 1)
|
assert_image_similar_tofile(im, expected, 1)
|
||||||
|
|
||||||
|
|
||||||
def test_ellipse_translucent():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_ellipse_translucent(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im, "RGBA")
|
draw = ImageDraw.Draw(im, "RGBA")
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.ellipse(BBOX1, fill=(0, 255, 0, 127))
|
draw.ellipse(bbox, fill=(0, 255, 0, 127))
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
expected = "Tests/images/imagedraw_ellipse_translucent.png"
|
expected = "Tests/images/imagedraw_ellipse_translucent.png"
|
||||||
|
@ -297,13 +313,14 @@ def test_ellipse_symmetric():
|
||||||
assert_image_equal(im, im.transpose(Image.Transpose.FLIP_LEFT_RIGHT))
|
assert_image_equal(im, im.transpose(Image.Transpose.FLIP_LEFT_RIGHT))
|
||||||
|
|
||||||
|
|
||||||
def test_ellipse_width():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_ellipse_width(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.ellipse(BBOX1, outline="blue", width=5)
|
draw.ellipse(bbox, outline="blue", width=5)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width.png", 1)
|
||||||
|
@ -321,25 +338,27 @@ def test_ellipse_width_large():
|
||||||
assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width_large.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width_large.png", 1)
|
||||||
|
|
||||||
|
|
||||||
def test_ellipse_width_fill():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_ellipse_width_fill(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.ellipse(BBOX1, fill="green", outline="blue", width=5)
|
draw.ellipse(bbox, fill="green", outline="blue", width=5)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width_fill.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width_fill.png", 1)
|
||||||
|
|
||||||
|
|
||||||
def test_ellipse_zero_width():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_ellipse_zero_width(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.ellipse(BBOX1, fill="green", outline="blue", width=0)
|
draw.ellipse(bbox, fill="green", outline="blue", width=0)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_ellipse_zero_width.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_ellipse_zero_width.png")
|
||||||
|
@ -386,7 +405,7 @@ def test_ellipse_various_sizes_filled():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("points", (POINTS1, POINTS2))
|
@pytest.mark.parametrize("points", POINTS)
|
||||||
def test_line(points):
|
def test_line(points):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
|
@ -458,7 +477,7 @@ def test_transform():
|
||||||
assert_image_equal(im, expected)
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("bbox", (BBOX1, BBOX2))
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
@pytest.mark.parametrize("start, end", ((-92, 46), (-92.2, 46.2)))
|
@pytest.mark.parametrize("start, end", ((-92, 46), (-92.2, 46.2)))
|
||||||
def test_pieslice(bbox, start, end):
|
def test_pieslice(bbox, start, end):
|
||||||
# Arrange
|
# Arrange
|
||||||
|
@ -472,38 +491,41 @@ def test_pieslice(bbox, start, end):
|
||||||
assert_image_similar_tofile(im, "Tests/images/imagedraw_pieslice.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_pieslice.png", 1)
|
||||||
|
|
||||||
|
|
||||||
def test_pieslice_width():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_pieslice_width(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.pieslice(BBOX1, 10, 260, outline="blue", width=5)
|
draw.pieslice(bbox, 10, 260, outline="blue", width=5)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_similar_tofile(im, "Tests/images/imagedraw_pieslice_width.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_pieslice_width.png", 1)
|
||||||
|
|
||||||
|
|
||||||
def test_pieslice_width_fill():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_pieslice_width_fill(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
expected = "Tests/images/imagedraw_pieslice_width_fill.png"
|
expected = "Tests/images/imagedraw_pieslice_width_fill.png"
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.pieslice(BBOX1, 10, 260, fill="white", outline="blue", width=5)
|
draw.pieslice(bbox, 10, 260, fill="white", outline="blue", width=5)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_similar_tofile(im, expected, 1)
|
assert_image_similar_tofile(im, expected, 1)
|
||||||
|
|
||||||
|
|
||||||
def test_pieslice_zero_width():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_pieslice_zero_width(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.pieslice(BBOX1, 10, 260, fill="white", outline="blue", width=0)
|
draw.pieslice(bbox, 10, 260, fill="white", outline="blue", width=0)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_pieslice_zero_width.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_pieslice_zero_width.png")
|
||||||
|
@ -551,7 +573,7 @@ def test_pieslice_no_spikes():
|
||||||
assert_image_equal(im, im_pre_erase)
|
assert_image_equal(im, im_pre_erase)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("points", (POINTS1, POINTS2))
|
@pytest.mark.parametrize("points", POINTS)
|
||||||
def test_point(points):
|
def test_point(points):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
|
@ -564,7 +586,7 @@ def test_point(points):
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_point.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_point.png")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("points", (POINTS1, POINTS2))
|
@pytest.mark.parametrize("points", POINTS)
|
||||||
def test_polygon(points):
|
def test_polygon(points):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
|
@ -578,7 +600,8 @@ def test_polygon(points):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("RGB", "L"))
|
@pytest.mark.parametrize("mode", ("RGB", "L"))
|
||||||
def test_polygon_kite(mode):
|
@pytest.mark.parametrize("kite_points", KITE_POINTS)
|
||||||
|
def test_polygon_kite(mode, kite_points):
|
||||||
# Test drawing lines of different gradients (dx>dy, dy>dx) and
|
# Test drawing lines of different gradients (dx>dy, dy>dx) and
|
||||||
# vertical (dx==0) and horizontal (dy==0) lines
|
# vertical (dx==0) and horizontal (dy==0) lines
|
||||||
# Arrange
|
# Arrange
|
||||||
|
@ -587,7 +610,7 @@ def test_polygon_kite(mode):
|
||||||
expected = f"Tests/images/imagedraw_polygon_kite_{mode}.png"
|
expected = f"Tests/images/imagedraw_polygon_kite_{mode}.png"
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.polygon(KITE_POINTS, fill="blue", outline="yellow")
|
draw.polygon(kite_points, fill="blue", outline="yellow")
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_equal_tofile(im, expected)
|
assert_image_equal_tofile(im, expected)
|
||||||
|
@ -634,7 +657,7 @@ def test_polygon_translucent():
|
||||||
assert_image_equal_tofile(im, expected)
|
assert_image_equal_tofile(im, expected)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("bbox", (BBOX1, BBOX2))
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
def test_rectangle(bbox):
|
def test_rectangle(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
|
@ -661,63 +684,68 @@ def test_big_rectangle():
|
||||||
assert_image_similar_tofile(im, "Tests/images/imagedraw_big_rectangle.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_big_rectangle.png", 1)
|
||||||
|
|
||||||
|
|
||||||
def test_rectangle_width():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_rectangle_width(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
expected = "Tests/images/imagedraw_rectangle_width.png"
|
expected = "Tests/images/imagedraw_rectangle_width.png"
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.rectangle(BBOX1, outline="green", width=5)
|
draw.rectangle(bbox, outline="green", width=5)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_equal_tofile(im, expected)
|
assert_image_equal_tofile(im, expected)
|
||||||
|
|
||||||
|
|
||||||
def test_rectangle_width_fill():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_rectangle_width_fill(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
expected = "Tests/images/imagedraw_rectangle_width_fill.png"
|
expected = "Tests/images/imagedraw_rectangle_width_fill.png"
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.rectangle(BBOX1, fill="blue", outline="green", width=5)
|
draw.rectangle(bbox, fill="blue", outline="green", width=5)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_equal_tofile(im, expected)
|
assert_image_equal_tofile(im, expected)
|
||||||
|
|
||||||
|
|
||||||
def test_rectangle_zero_width():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_rectangle_zero_width(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.rectangle(BBOX1, fill="blue", outline="green", width=0)
|
draw.rectangle(bbox, fill="blue", outline="green", width=0)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_zero_width.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_zero_width.png")
|
||||||
|
|
||||||
|
|
||||||
def test_rectangle_I16():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_rectangle_I16(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("I;16", (W, H))
|
im = Image.new("I;16", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.rectangle(BBOX1, fill="black", outline="green")
|
draw.rectangle(bbox, fill="black", outline="green")
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_equal_tofile(im.convert("I"), "Tests/images/imagedraw_rectangle_I.png")
|
assert_image_equal_tofile(im.convert("I"), "Tests/images/imagedraw_rectangle_I.png")
|
||||||
|
|
||||||
|
|
||||||
def test_rectangle_translucent_outline():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_rectangle_translucent_outline(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im, "RGBA")
|
draw = ImageDraw.Draw(im, "RGBA")
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.rectangle(BBOX1, fill="black", outline=(0, 255, 0, 127), width=5)
|
draw.rectangle(bbox, fill="black", outline=(0, 255, 0, 127), width=5)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_equal_tofile(
|
assert_image_equal_tofile(
|
||||||
|
@ -794,13 +822,14 @@ def test_rounded_rectangle_non_integer_radius(xy, radius, type):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_rounded_rectangle_zero_radius():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_rounded_rectangle_zero_radius(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.rounded_rectangle(BBOX1, 0, fill="blue", outline="green", width=5)
|
draw.rounded_rectangle(bbox, 0, fill="blue", outline="green", width=5)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_width_fill.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_width_fill.png")
|
||||||
|
@ -810,7 +839,9 @@ def test_rounded_rectangle_zero_radius():
|
||||||
"xy, suffix",
|
"xy, suffix",
|
||||||
[
|
[
|
||||||
((20, 10, 80, 90), "x"),
|
((20, 10, 80, 90), "x"),
|
||||||
|
((20, 10, 81, 90), "x_odd"),
|
||||||
((10, 20, 90, 80), "y"),
|
((10, 20, 90, 80), "y"),
|
||||||
|
((10, 20, 90, 81), "y_odd"),
|
||||||
((20, 20, 80, 80), "both"),
|
((20, 20, 80, 80), "both"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -830,14 +861,15 @@ def test_rounded_rectangle_translucent(xy, suffix):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_floodfill():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_floodfill(bbox):
|
||||||
red = ImageColor.getrgb("red")
|
red = ImageColor.getrgb("red")
|
||||||
|
|
||||||
for mode, value in [("L", 1), ("RGBA", (255, 0, 0, 0)), ("RGB", red)]:
|
for mode, value in [("L", 1), ("RGBA", (255, 0, 0, 0)), ("RGB", red)]:
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new(mode, (W, H))
|
im = Image.new(mode, (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
draw.rectangle(BBOX2, outline="yellow", fill="green")
|
draw.rectangle(bbox, outline="yellow", fill="green")
|
||||||
centre_point = (int(W / 2), int(H / 2))
|
centre_point = (int(W / 2), int(H / 2))
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
|
@ -862,13 +894,14 @@ def test_floodfill():
|
||||||
assert_image_equal(im, Image.new("RGB", (1, 1), red))
|
assert_image_equal(im, Image.new("RGB", (1, 1), red))
|
||||||
|
|
||||||
|
|
||||||
def test_floodfill_border():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_floodfill_border(bbox):
|
||||||
# floodfill() is experimental
|
# floodfill() is experimental
|
||||||
|
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
draw.rectangle(BBOX2, outline="yellow", fill="green")
|
draw.rectangle(bbox, outline="yellow", fill="green")
|
||||||
centre_point = (int(W / 2), int(H / 2))
|
centre_point = (int(W / 2), int(H / 2))
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
|
@ -883,13 +916,14 @@ def test_floodfill_border():
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_floodfill2.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_floodfill2.png")
|
||||||
|
|
||||||
|
|
||||||
def test_floodfill_thresh():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_floodfill_thresh(bbox):
|
||||||
# floodfill() is experimental
|
# floodfill() is experimental
|
||||||
|
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
draw.rectangle(BBOX2, outline="darkgreen", fill="green")
|
draw.rectangle(bbox, outline="darkgreen", fill="green")
|
||||||
centre_point = (int(W / 2), int(H / 2))
|
centre_point = (int(W / 2), int(H / 2))
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
|
@ -1309,7 +1343,8 @@ def test_setting_default_font():
|
||||||
assert isinstance(draw.getfont(), ImageFont.ImageFont)
|
assert isinstance(draw.getfont(), ImageFont.ImageFont)
|
||||||
|
|
||||||
|
|
||||||
def test_same_color_outline():
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
def test_same_color_outline(bbox):
|
||||||
# Prepare shape
|
# Prepare shape
|
||||||
x0, y0 = 5, 5
|
x0, y0 = 5, 5
|
||||||
x1, y1 = 5, 50
|
x1, y1 = 5, 50
|
||||||
|
@ -1325,12 +1360,12 @@ def test_same_color_outline():
|
||||||
for mode in ["RGB", "L"]:
|
for mode in ["RGB", "L"]:
|
||||||
for fill, outline in [["red", None], ["red", "red"], ["red", "#f00"]]:
|
for fill, outline in [["red", None], ["red", "red"], ["red", "#f00"]]:
|
||||||
for operation, args in {
|
for operation, args in {
|
||||||
"chord": [BBOX1, 0, 180],
|
"chord": [bbox, 0, 180],
|
||||||
"ellipse": [BBOX1],
|
"ellipse": [bbox],
|
||||||
"shape": [s],
|
"shape": [s],
|
||||||
"pieslice": [BBOX1, -90, 45],
|
"pieslice": [bbox, -90, 45],
|
||||||
"polygon": [[(18, 30), (85, 30), (60, 72)]],
|
"polygon": [[(18, 30), (85, 30), (60, 72)]],
|
||||||
"rectangle": [BBOX1],
|
"rectangle": [bbox],
|
||||||
}.items():
|
}.items():
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new(mode, (W, H))
|
im = Image.new(mode, (W, H))
|
||||||
|
@ -1347,20 +1382,20 @@ def test_same_color_outline():
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"n_sides, rotation, polygon_name",
|
"n_sides, polygon_name, args",
|
||||||
[(4, 0, "square"), (8, 0, "regular_octagon"), (4, 45, "square")],
|
[
|
||||||
|
(4, "square", {}),
|
||||||
|
(8, "regular_octagon", {}),
|
||||||
|
(4, "square_rotate_45", {"rotation": 45}),
|
||||||
|
(3, "triangle_width", {"width": 5, "outline": "yellow"}),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
def test_draw_regular_polygon(n_sides, rotation, polygon_name):
|
def test_draw_regular_polygon(n_sides, polygon_name, args):
|
||||||
im = Image.new("RGBA", size=(W, H), color=(255, 0, 0, 0))
|
im = Image.new("RGBA", size=(W, H), color=(255, 0, 0, 0))
|
||||||
filename_base = f"Tests/images/imagedraw_{polygon_name}"
|
filename = f"Tests/images/imagedraw_{polygon_name}.png"
|
||||||
filename = (
|
|
||||||
f"{filename_base}.png"
|
|
||||||
if rotation == 0
|
|
||||||
else f"{filename_base}_rotate_{rotation}.png"
|
|
||||||
)
|
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
bounding_circle = ((W // 2, H // 2), 25)
|
bounding_circle = ((W // 2, H // 2), 25)
|
||||||
draw.regular_polygon(bounding_circle, n_sides, rotation=rotation, fill="red")
|
draw.regular_polygon(bounding_circle, n_sides, fill="red", **args)
|
||||||
assert_image_equal_tofile(im, filename)
|
assert_image_equal_tofile(im, filename)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -27,15 +27,16 @@ X1 = int(X0 * 3)
|
||||||
Y0 = int(H / 4)
|
Y0 = int(H / 4)
|
||||||
Y1 = int(X0 * 3)
|
Y1 = int(X0 * 3)
|
||||||
|
|
||||||
# Two kinds of bounding box
|
# Bounding boxes
|
||||||
BBOX1 = [(X0, Y0), (X1, Y1)]
|
BBOX = (((X0, Y0), (X1, Y1)), [(X0, Y0), (X1, Y1)], (X0, Y0, X1, Y1), [X0, Y0, X1, Y1])
|
||||||
BBOX2 = [X0, Y0, X1, Y1]
|
|
||||||
|
|
||||||
# Two kinds of coordinate sequences
|
# Coordinate sequences
|
||||||
POINTS1 = [(10, 10), (20, 40), (30, 30)]
|
POINTS = (
|
||||||
POINTS2 = [10, 10, 20, 40, 30, 30]
|
((10, 10), (20, 40), (30, 30)),
|
||||||
|
[(10, 10), (20, 40), (30, 30)],
|
||||||
KITE_POINTS = [(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)]
|
(10, 10, 20, 40, 30, 30),
|
||||||
|
[10, 10, 20, 40, 30, 30],
|
||||||
|
)
|
||||||
|
|
||||||
FONT_PATH = "Tests/fonts/FreeMono.ttf"
|
FONT_PATH = "Tests/fonts/FreeMono.ttf"
|
||||||
|
|
||||||
|
@ -52,7 +53,7 @@ def test_sanity():
|
||||||
draw.line(list(range(10)), pen)
|
draw.line(list(range(10)), pen)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("bbox", (BBOX1, BBOX2))
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
def test_ellipse(bbox):
|
def test_ellipse(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
|
@ -80,7 +81,7 @@ def test_ellipse_edge():
|
||||||
assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_edge.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_edge.png", 1)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("points", (POINTS1, POINTS2))
|
@pytest.mark.parametrize("points", POINTS)
|
||||||
def test_line(points):
|
def test_line(points):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
|
@ -94,7 +95,8 @@ def test_line(points):
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png")
|
||||||
|
|
||||||
|
|
||||||
def test_line_pen_as_brush():
|
@pytest.mark.parametrize("points", POINTS)
|
||||||
|
def test_line_pen_as_brush(points):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw2.Draw(im)
|
draw = ImageDraw2.Draw(im)
|
||||||
|
@ -103,13 +105,13 @@ def test_line_pen_as_brush():
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
# Pass in the pen as the brush parameter
|
# Pass in the pen as the brush parameter
|
||||||
draw.line(POINTS1, pen, brush)
|
draw.line(points, pen, brush)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("points", (POINTS1, POINTS2))
|
@pytest.mark.parametrize("points", POINTS)
|
||||||
def test_polygon(points):
|
def test_polygon(points):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
|
@ -124,7 +126,7 @@ def test_polygon(points):
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon.png")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("bbox", (BBOX1, BBOX2))
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
def test_rectangle(bbox):
|
def test_rectangle(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
|
|
|
@ -463,6 +463,11 @@ def test_default_font():
|
||||||
assert_image_equal_tofile(im, "Tests/images/default_font.png")
|
assert_image_equal_tofile(im, "Tests/images/default_font.png")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mode", (None, "1", "RGBA"))
|
||||||
|
def test_getbbox(font, mode):
|
||||||
|
assert (0, 4, 12, 16) == font.getbbox("A", mode)
|
||||||
|
|
||||||
|
|
||||||
def test_getbbox_empty(font):
|
def test_getbbox_empty(font):
|
||||||
# issue #2614, should not crash.
|
# issue #2614, should not crash.
|
||||||
assert (0, 0, 0, 0) == font.getbbox("")
|
assert (0, 0, 0, 0) == font.getbbox("")
|
||||||
|
|
|
@ -98,3 +98,18 @@ $ms = new-object System.IO.MemoryStream(, $bytes)
|
||||||
|
|
||||||
im = ImageGrab.grabclipboard()
|
im = ImageGrab.grabclipboard()
|
||||||
assert_image_equal_tofile(im, "Tests/images/hopper.png")
|
assert_image_equal_tofile(im, "Tests/images/hopper.png")
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
(
|
||||||
|
sys.platform != "linux"
|
||||||
|
or not all(shutil.which(cmd) for cmd in ("wl-paste", "wl-copy"))
|
||||||
|
),
|
||||||
|
reason="Linux with wl-clipboard only",
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize("ext", ("gif", "png", "ico"))
|
||||||
|
def test_grabclipboard_wl_clipboard(self, ext):
|
||||||
|
image_path = "Tests/images/hopper." + ext
|
||||||
|
with open(image_path, "rb") as fp:
|
||||||
|
subprocess.call(["wl-copy"], stdin=fp)
|
||||||
|
im = ImageGrab.grabclipboard()
|
||||||
|
assert_image_equal_tofile(im, image_path)
|
||||||
|
|
|
@ -28,7 +28,7 @@ def test_path():
|
||||||
(6.0, 7.0),
|
(6.0, 7.0),
|
||||||
(8.0, 9.0),
|
(8.0, 9.0),
|
||||||
]
|
]
|
||||||
assert p.tolist(1) == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
|
assert p.tolist(True) == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
|
||||||
|
|
||||||
assert p.getbbox() == (0.0, 1.0, 8.0, 9.0)
|
assert p.getbbox() == (0.0, 1.0, 8.0, 9.0)
|
||||||
|
|
||||||
|
@ -38,48 +38,65 @@ def test_path():
|
||||||
p.transform((1, 0, 1, 0, 1, 1))
|
p.transform((1, 0, 1, 0, 1, 1))
|
||||||
assert list(p) == [(1.0, 2.0), (5.0, 6.0), (9.0, 10.0)]
|
assert list(p) == [(1.0, 2.0), (5.0, 6.0), (9.0, 10.0)]
|
||||||
|
|
||||||
# alternative constructors
|
|
||||||
p = ImagePath.Path([0, 1])
|
|
||||||
assert list(p) == [(0.0, 1.0)]
|
|
||||||
p = ImagePath.Path([0.0, 1.0])
|
|
||||||
assert list(p) == [(0.0, 1.0)]
|
|
||||||
p = ImagePath.Path([0, 1])
|
|
||||||
assert list(p) == [(0.0, 1.0)]
|
|
||||||
p = ImagePath.Path([(0, 1)])
|
|
||||||
assert list(p) == [(0.0, 1.0)]
|
|
||||||
p = ImagePath.Path(p)
|
|
||||||
assert list(p) == [(0.0, 1.0)]
|
|
||||||
p = ImagePath.Path(p.tolist(0))
|
|
||||||
assert list(p) == [(0.0, 1.0)]
|
|
||||||
p = ImagePath.Path(p.tolist(1))
|
|
||||||
assert list(p) == [(0.0, 1.0)]
|
|
||||||
p = ImagePath.Path(array.array("f", [0, 1]))
|
|
||||||
assert list(p) == [(0.0, 1.0)]
|
|
||||||
|
|
||||||
arr = array.array("f", [0, 1])
|
@pytest.mark.parametrize(
|
||||||
p = ImagePath.Path(arr.tobytes())
|
"coords",
|
||||||
|
(
|
||||||
|
(0, 1),
|
||||||
|
[0, 1],
|
||||||
|
(0.0, 1.0),
|
||||||
|
[0.0, 1.0],
|
||||||
|
((0, 1),),
|
||||||
|
[(0, 1)],
|
||||||
|
((0.0, 1.0),),
|
||||||
|
[(0.0, 1.0)],
|
||||||
|
array.array("f", [0, 1]),
|
||||||
|
array.array("f", [0, 1]).tobytes(),
|
||||||
|
ImagePath.Path((0, 1)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_path_constructors(coords):
|
||||||
|
# Arrange / Act
|
||||||
|
p = ImagePath.Path(coords)
|
||||||
|
|
||||||
|
# Assert
|
||||||
assert list(p) == [(0.0, 1.0)]
|
assert list(p) == [(0.0, 1.0)]
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_coords():
|
@pytest.mark.parametrize(
|
||||||
# Arrange
|
"coords",
|
||||||
coords = ["a", "b"]
|
(
|
||||||
|
("a", "b"),
|
||||||
# Act / Assert
|
([0, 1],),
|
||||||
|
[[0, 1]],
|
||||||
|
([0.0, 1.0],),
|
||||||
|
[[0.0, 1.0]],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_invalid_path_constructors(coords):
|
||||||
|
# Act
|
||||||
with pytest.raises(ValueError) as e:
|
with pytest.raises(ValueError) as e:
|
||||||
ImagePath.Path(coords)
|
ImagePath.Path(coords)
|
||||||
|
|
||||||
|
# Assert
|
||||||
assert str(e.value) == "incorrect coordinate type"
|
assert str(e.value) == "incorrect coordinate type"
|
||||||
|
|
||||||
|
|
||||||
def test_path_odd_number_of_coordinates():
|
@pytest.mark.parametrize(
|
||||||
# Arrange
|
"coords",
|
||||||
coords = [0]
|
(
|
||||||
|
(0,),
|
||||||
# Act / Assert
|
[0],
|
||||||
|
(0, 1, 2),
|
||||||
|
[0, 1, 2],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_path_odd_number_of_coordinates(coords):
|
||||||
|
# Act
|
||||||
with pytest.raises(ValueError) as e:
|
with pytest.raises(ValueError) as e:
|
||||||
ImagePath.Path(coords)
|
ImagePath.Path(coords)
|
||||||
|
|
||||||
|
# Assert
|
||||||
assert str(e.value) == "wrong number of coordinates"
|
assert str(e.value) == "wrong number of coordinates"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -757,6 +757,7 @@ class TestLibUnpack:
|
||||||
|
|
||||||
def test_I16(self):
|
def test_I16(self):
|
||||||
self.assert_unpack("I;16", "I;16", 2, 0x0201, 0x0403, 0x0605)
|
self.assert_unpack("I;16", "I;16", 2, 0x0201, 0x0403, 0x0605)
|
||||||
|
self.assert_unpack("I;16", "I;16B", 2, 0x0102, 0x0304, 0x0506)
|
||||||
self.assert_unpack("I;16B", "I;16B", 2, 0x0102, 0x0304, 0x0506)
|
self.assert_unpack("I;16B", "I;16B", 2, 0x0102, 0x0304, 0x0506)
|
||||||
self.assert_unpack("I;16L", "I;16L", 2, 0x0201, 0x0403, 0x0605)
|
self.assert_unpack("I;16L", "I;16L", 2, 0x0201, 0x0403, 0x0605)
|
||||||
self.assert_unpack("I;16", "I;12", 2, 0x0010, 0x0203, 0x0040)
|
self.assert_unpack("I;16", "I;12", 2, 0x0010, 0x0203, 0x0040)
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
# Documentation: https://docs.codecov.io/docs/codecov-yaml
|
# Documentation: https://docs.codecov.com/docs/codecov-yaml
|
||||||
|
|
||||||
codecov:
|
codecov:
|
||||||
# Avoid "Missing base report" due to committing CHANGES.rst with "[CI skip]"
|
# Avoid "Missing base report" due to committing CHANGES.rst with "[CI skip]"
|
||||||
# https://github.com/codecov/support/issues/363
|
# https://github.com/codecov/support/issues/363
|
||||||
# https://docs.codecov.io/docs/comparing-commits
|
# https://docs.codecov.com/docs/comparing-commits
|
||||||
allow_coverage_offsets: true
|
allow_coverage_offsets: true
|
||||||
|
|
||||||
comment: false
|
comment: false
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# install libimagequant
|
# install libimagequant
|
||||||
|
|
||||||
archive=libimagequant-4.1.1
|
archive=libimagequant-4.2.0
|
||||||
|
|
||||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
from livereload.compiler import shell
|
from livereload.compiler import shell
|
||||||
from livereload.task import Task
|
from livereload.task import Task
|
||||||
|
|
||||||
Task.add('*.rst', shell('make html'))
|
Task.add("*.rst", shell("make html"))
|
||||||
Task.add('*/*.rst', shell('make html'))
|
Task.add("*/*.rst", shell("make html"))
|
||||||
Task.add('Makefile', shell('make html'))
|
Task.add("Makefile", shell("make html"))
|
||||||
Task.add('conf.py', shell('make html'))
|
Task.add("conf.py", shell("make html"))
|
||||||
|
|
11
docs/conf.py
11
docs/conf.py
|
@ -317,6 +317,17 @@ def setup(app):
|
||||||
app.add_css_file("css/dark.css")
|
app.add_css_file("css/dark.css")
|
||||||
|
|
||||||
|
|
||||||
|
linkcheck_allowed_redirects = {
|
||||||
|
r"https://bestpractices.coreinfrastructure.org/projects/6331": r"https://bestpractices.coreinfrastructure.org/en/.*", # noqa: E501
|
||||||
|
r"https://badges.gitter.im/python-pillow/Pillow.svg": r"https://badges.gitter.im/repo.svg", # noqa: E501
|
||||||
|
r"https://gitter.im/python-pillow/Pillow?.*": r"https://app.gitter.im/#/room/#python-pillow_Pillow:gitter.im?.*", # noqa: E501
|
||||||
|
r"https://pillow.readthedocs.io/?badge=latest": r"https://pillow.readthedocs.io/en/stable/?badge=latest", # noqa: E501
|
||||||
|
r"https://pillow.readthedocs.io": r"https://pillow.readthedocs.io/en/stable/",
|
||||||
|
r"https://tidelift.com/badges/package/pypi/Pillow?.*": r"https://img.shields.io/badge/.*", # noqa: E501
|
||||||
|
r"https://zenodo.org/badge/17549/python-pillow/Pillow.svg": r"https://zenodo.org/badge/doi/[\.0-9]+/zenodo.[0-9]+.svg", # noqa: E501
|
||||||
|
r"https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow": r"https://zenodo.org/record/[0-9]+", # noqa: E501
|
||||||
|
}
|
||||||
|
|
||||||
# sphinx.ext.extlinks
|
# sphinx.ext.extlinks
|
||||||
# This config is a dictionary of external sites,
|
# This config is a dictionary of external sites,
|
||||||
# mapping unique short aliases to a base URL and a prefix.
|
# mapping unique short aliases to a base URL and a prefix.
|
||||||
|
|
|
@ -210,7 +210,7 @@ open-source users (and will reach EOL on 2023-12-08 for commercial licence holde
|
||||||
|
|
||||||
Support for PyQt5 and PySide2 has been removed from ``ImageQt``. Upgrade to
|
Support for PyQt5 and PySide2 has been removed from ``ImageQt``. Upgrade to
|
||||||
`PyQt6 <https://www.riverbankcomputing.com/static/Docs/PyQt6/>`_ or
|
`PyQt6 <https://www.riverbankcomputing.com/static/Docs/PyQt6/>`_ or
|
||||||
`PySide6 <https://doc.qt.io/qtforpython/>`_ instead.
|
`PySide6 <https://doc.qt.io/qtforpython-6/>`_ instead.
|
||||||
|
|
||||||
Image.coerce_e
|
Image.coerce_e
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
|
|
@ -95,9 +95,8 @@ in the upper left corner. Note that the coordinates refer to the implied pixel
|
||||||
corners; the centre of a pixel addressed as (0, 0) actually lies at (0.5, 0.5).
|
corners; the centre of a pixel addressed as (0, 0) actually lies at (0.5, 0.5).
|
||||||
|
|
||||||
Coordinates are usually passed to the library as 2-tuples (x, y). Rectangles
|
Coordinates are usually passed to the library as 2-tuples (x, y). Rectangles
|
||||||
are represented as 4-tuples, with the upper left corner given first. For
|
are represented as 4-tuples, (x1, y1, x2, y2), with the upper left corner given
|
||||||
example, a rectangle covering all of an 800x600 pixel image is written as (0,
|
first.
|
||||||
0, 800, 600).
|
|
||||||
|
|
||||||
Palette
|
Palette
|
||||||
-------
|
-------
|
||||||
|
|
|
@ -181,7 +181,7 @@ Many of Pillow's features require external libraries:
|
||||||
|
|
||||||
* **libimagequant** provides improved color quantization
|
* **libimagequant** provides improved color quantization
|
||||||
|
|
||||||
* Pillow has been tested with libimagequant **2.6-4.1.1**
|
* Pillow has been tested with libimagequant **2.6-4.2**
|
||||||
* Libimagequant is licensed GPLv3, which is more restrictive than
|
* Libimagequant is licensed GPLv3, which is more restrictive than
|
||||||
the Pillow license, therefore we will not be distributing binaries
|
the Pillow license, therefore we will not be distributing binaries
|
||||||
with libimagequant support enabled.
|
with libimagequant support enabled.
|
||||||
|
@ -448,6 +448,8 @@ These platforms are built and tested for every change.
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Debian 11 Bullseye | 3.9 | x86 |
|
| Debian 11 Bullseye | 3.9 | x86 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
|
| Debian 12 Bookworm | 3.11 | x86 |
|
||||||
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Fedora 37 | 3.11 | x86-64 |
|
| Fedora 37 | 3.11 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Fedora 38 | 3.11 | x86-64 |
|
| Fedora 38 | 3.11 | x86-64 |
|
||||||
|
|
|
@ -243,6 +243,7 @@ Methods
|
||||||
.. py:method:: ImageDraw.line(xy, fill=None, width=0, joint=None)
|
.. py:method:: ImageDraw.line(xy, fill=None, width=0, joint=None)
|
||||||
|
|
||||||
Draws a line between the coordinates in the ``xy`` list.
|
Draws a line between the coordinates in the ``xy`` list.
|
||||||
|
The coordinate pixels are included in the drawn line.
|
||||||
|
|
||||||
:param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or
|
:param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or
|
||||||
numeric values like ``[x, y, x, y, ...]``.
|
numeric values like ``[x, y, x, y, ...]``.
|
||||||
|
@ -287,7 +288,7 @@ Methods
|
||||||
|
|
||||||
The polygon outline consists of straight lines between the given
|
The polygon outline consists of straight lines between the given
|
||||||
coordinates, plus a straight line between the last and the first
|
coordinates, plus a straight line between the last and the first
|
||||||
coordinate.
|
coordinate. The coordinate pixels are included in the drawn polygon.
|
||||||
|
|
||||||
:param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or
|
:param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or
|
||||||
numeric values like ``[x, y, x, y, ...]``.
|
numeric values like ``[x, y, x, y, ...]``.
|
||||||
|
@ -296,7 +297,7 @@ Methods
|
||||||
:param width: The line width, in pixels.
|
:param width: The line width, in pixels.
|
||||||
|
|
||||||
|
|
||||||
.. py:method:: ImageDraw.regular_polygon(bounding_circle, n_sides, rotation=0, fill=None, outline=None)
|
.. py:method:: ImageDraw.regular_polygon(bounding_circle, n_sides, rotation=0, fill=None, outline=None, width=1)
|
||||||
|
|
||||||
Draws a regular polygon inscribed in ``bounding_circle``,
|
Draws a regular polygon inscribed in ``bounding_circle``,
|
||||||
with ``n_sides``, and rotation of ``rotation`` degrees.
|
with ``n_sides``, and rotation of ``rotation`` degrees.
|
||||||
|
@ -311,6 +312,7 @@ Methods
|
||||||
(e.g. ``rotation=90``, applies a 90 degree rotation).
|
(e.g. ``rotation=90``, applies a 90 degree rotation).
|
||||||
:param fill: Color to use for the fill.
|
:param fill: Color to use for the fill.
|
||||||
:param outline: Color to use for the outline.
|
:param outline: Color to use for the outline.
|
||||||
|
:param width: The line width, in pixels.
|
||||||
|
|
||||||
|
|
||||||
.. py:method:: ImageDraw.rectangle(xy, fill=None, outline=None, width=1)
|
.. py:method:: ImageDraw.rectangle(xy, fill=None, outline=None, width=1)
|
||||||
|
|
|
@ -15,8 +15,9 @@ or the clipboard to a PIL image memory.
|
||||||
returned as an "RGBA" on macOS, or an "RGB" image otherwise.
|
returned as an "RGBA" on macOS, or an "RGB" image otherwise.
|
||||||
If the bounding box is omitted, the entire screen is copied.
|
If the bounding box is omitted, the entire screen is copied.
|
||||||
|
|
||||||
On Linux, if ``xdisplay`` is ``None`` then ``gnome-screenshot`` will be used if it
|
On Linux, if ``xdisplay`` is ``None`` and the default X11 display does not return
|
||||||
is installed. To capture the default X11 display instead, pass ``xdisplay=""``.
|
a snapshot of the screen, ``gnome-screenshot`` will be used as fallback if it is
|
||||||
|
installed. To disable this behaviour, pass ``xdisplay=""`` instead.
|
||||||
|
|
||||||
.. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS), 7.1.0 (Linux)
|
.. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS), 7.1.0 (Linux)
|
||||||
|
|
||||||
|
@ -39,9 +40,11 @@ or the clipboard to a PIL image memory.
|
||||||
|
|
||||||
.. py:function:: grabclipboard()
|
.. py:function:: grabclipboard()
|
||||||
|
|
||||||
Take a snapshot of the clipboard image, if any. Only macOS and Windows are currently supported.
|
Take a snapshot of the clipboard image, if any.
|
||||||
|
|
||||||
.. versionadded:: 1.1.4 (Windows), 3.3.0 (macOS)
|
On Linux, ``wl-paste`` or ``xclip`` is required.
|
||||||
|
|
||||||
|
.. versionadded:: 1.1.4 (Windows), 3.3.0 (macOS), 9.4.0 (Linux)
|
||||||
|
|
||||||
:return: On Windows, an image, a list of filenames,
|
:return: On Windows, an image, a list of filenames,
|
||||||
or None if the clipboard does not contain image data or filenames.
|
or None if the clipboard does not contain image data or filenames.
|
||||||
|
@ -49,3 +52,5 @@ or the clipboard to a PIL image memory.
|
||||||
|
|
||||||
On Mac, an image,
|
On Mac, an image,
|
||||||
or None if the clipboard does not contain image data.
|
or None if the clipboard does not contain image data.
|
||||||
|
|
||||||
|
On Linux, an image.
|
||||||
|
|
|
@ -48,7 +48,7 @@ vector data. Path objects can be passed to the methods on the
|
||||||
|
|
||||||
Maps the path through a function.
|
Maps the path through a function.
|
||||||
|
|
||||||
.. py:method:: PIL.ImagePath.Path.tolist(flat=0)
|
.. py:method:: PIL.ImagePath.Path.tolist(flat=False)
|
||||||
|
|
||||||
Converts the path to a Python list [(x, y), …].
|
Converts the path to a Python list [(x, y), …].
|
||||||
|
|
||||||
|
|
|
@ -117,7 +117,7 @@ open-source users (and will reach EOL on 2023-12-08 for commercial licence holde
|
||||||
|
|
||||||
Support for PyQt5 and PySide2 has been removed from ``ImageQt``. Upgrade to
|
Support for PyQt5 and PySide2 has been removed from ``ImageQt``. Upgrade to
|
||||||
`PyQt6 <https://www.riverbankcomputing.com/static/Docs/PyQt6/>`_ or
|
`PyQt6 <https://www.riverbankcomputing.com/static/Docs/PyQt6/>`_ or
|
||||||
`PySide6 <https://doc.qt.io/qtforpython/>`_ instead.
|
`PySide6 <https://doc.qt.io/qtforpython-6/>`_ instead.
|
||||||
|
|
||||||
Image.coerce_e
|
Image.coerce_e
|
||||||
^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^
|
||||||
|
@ -135,10 +135,11 @@ TODO
|
||||||
API Changes
|
API Changes
|
||||||
===========
|
===========
|
||||||
|
|
||||||
TODO
|
Added line width parameter to ImageDraw regular_polygon
|
||||||
^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
An optional line ``width`` parameter has been added to
|
||||||
|
``ImageDraw.Draw.regular_polygon``.
|
||||||
|
|
||||||
API Additions
|
API Additions
|
||||||
=============
|
=============
|
||||||
|
@ -159,7 +160,20 @@ TODO
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
TODO
|
Support display_jpeg() in IPython
|
||||||
^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
In addition to ``display()`` and ``display_png``, ``display_jpeg()`` can now
|
||||||
|
also be used to display images in IPython::
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from IPython.display import display_jpeg
|
||||||
|
|
||||||
|
im = Image.new("RGB", (100, 100), (255, 0, 0))
|
||||||
|
display_jpeg(im)
|
||||||
|
|
||||||
|
Support reading signed 8-bit TIFF images
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
TIFF images with signed integer data, 8 bits per sample and a photometric
|
||||||
|
interpretaton of BlackIsZero can now be read.
|
||||||
|
|
|
@ -15,7 +15,7 @@ open-source users (and will reach EOL on 2023-12-08 for commercial licence holde
|
||||||
Support for PyQt5 and PySide2 has been deprecated from ``ImageQt`` and will be removed
|
Support for PyQt5 and PySide2 has been deprecated from ``ImageQt`` and will be removed
|
||||||
in Pillow 10 (2023-07-01). Upgrade to
|
in Pillow 10 (2023-07-01). Upgrade to
|
||||||
`PyQt6 <https://www.riverbankcomputing.com/static/Docs/PyQt6/>`_ or
|
`PyQt6 <https://www.riverbankcomputing.com/static/Docs/PyQt6/>`_ or
|
||||||
`PySide6 <https://doc.qt.io/qtforpython/>`_ instead.
|
`PySide6 <https://doc.qt.io/qtforpython-6/>`_ instead.
|
||||||
|
|
||||||
FreeTypeFont.getmask2 fill parameter
|
FreeTypeFont.getmask2 fill parameter
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
6
setup.py
6
setup.py
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
import struct
|
import struct
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
@ -150,6 +151,7 @@ def _dbg(s, tp=None):
|
||||||
def _find_library_dirs_ldconfig():
|
def _find_library_dirs_ldconfig():
|
||||||
# Based on ctypes.util from Python 2
|
# Based on ctypes.util from Python 2
|
||||||
|
|
||||||
|
ldconfig = "ldconfig" if shutil.which("ldconfig") else "/sbin/ldconfig"
|
||||||
if sys.platform.startswith("linux") or sys.platform.startswith("gnu"):
|
if sys.platform.startswith("linux") or sys.platform.startswith("gnu"):
|
||||||
if struct.calcsize("l") == 4:
|
if struct.calcsize("l") == 4:
|
||||||
machine = os.uname()[4] + "-32"
|
machine = os.uname()[4] + "-32"
|
||||||
|
@ -166,14 +168,14 @@ def _find_library_dirs_ldconfig():
|
||||||
|
|
||||||
# Assuming GLIBC's ldconfig (with option -p)
|
# Assuming GLIBC's ldconfig (with option -p)
|
||||||
# Alpine Linux uses musl that can't print cache
|
# Alpine Linux uses musl that can't print cache
|
||||||
args = ["ldconfig", "-p"]
|
args = [ldconfig, "-p"]
|
||||||
expr = rf".*\({abi_type}.*\) => (.*)"
|
expr = rf".*\({abi_type}.*\) => (.*)"
|
||||||
env = dict(os.environ)
|
env = dict(os.environ)
|
||||||
env["LC_ALL"] = "C"
|
env["LC_ALL"] = "C"
|
||||||
env["LANG"] = "C"
|
env["LANG"] = "C"
|
||||||
|
|
||||||
elif sys.platform.startswith("freebsd"):
|
elif sys.platform.startswith("freebsd"):
|
||||||
args = ["ldconfig", "-r"]
|
args = [ldconfig, "-r"]
|
||||||
expr = r".* => (.*)"
|
expr = r".* => (.*)"
|
||||||
env = {}
|
env = {}
|
||||||
|
|
||||||
|
|
|
@ -134,6 +134,13 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False):
|
||||||
|
|
||||||
if gs_windows_binary is not None:
|
if gs_windows_binary is not None:
|
||||||
if not gs_windows_binary:
|
if not gs_windows_binary:
|
||||||
|
try:
|
||||||
|
os.unlink(outfile)
|
||||||
|
if infile_temp:
|
||||||
|
os.unlink(infile_temp)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
msg = "Unable to locate Ghostscript on paths"
|
msg = "Unable to locate Ghostscript on paths"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
command[0] = gs_windows_binary
|
command[0] = gs_windows_binary
|
||||||
|
@ -354,7 +361,6 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
check_required_header_comments()
|
check_required_header_comments()
|
||||||
|
|
||||||
if not self._size:
|
if not self._size:
|
||||||
self._size = 1, 1 # errors if this isn't set. why (1,1)?
|
|
||||||
msg = "cannot determine EPS bounding box"
|
msg = "cannot determine EPS bounding box"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
|
|
||||||
|
|
|
@ -879,7 +879,7 @@ def _get_palette_bytes(im):
|
||||||
:param im: Image object
|
:param im: Image object
|
||||||
:returns: Bytes, len<=768 suitable for inclusion in gif header
|
:returns: Bytes, len<=768 suitable for inclusion in gif header
|
||||||
"""
|
"""
|
||||||
return im.palette.palette
|
return im.palette.palette if im.palette else b""
|
||||||
|
|
||||||
|
|
||||||
def _get_background(im, info_background):
|
def _get_background(im, info_background):
|
||||||
|
|
|
@ -22,11 +22,11 @@ import os
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from PIL import Image, ImageFile, PngImagePlugin, features
|
from . import Image, ImageFile, PngImagePlugin, features
|
||||||
|
|
||||||
enable_jpeg2k = features.check_codec("jpg_2000")
|
enable_jpeg2k = features.check_codec("jpg_2000")
|
||||||
if enable_jpeg2k:
|
if enable_jpeg2k:
|
||||||
from PIL import Jpeg2KImagePlugin
|
from . import Jpeg2KImagePlugin
|
||||||
|
|
||||||
MAGIC = b"icns"
|
MAGIC = b"icns"
|
||||||
HEADERSIZE = 8
|
HEADERSIZE = 8
|
||||||
|
|
|
@ -633,19 +633,34 @@ class Image:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _repr_png_(self):
|
def _repr_image(self, image_format):
|
||||||
"""iPython display hook support
|
"""Helper function for iPython display hook.
|
||||||
|
|
||||||
:returns: png version of the image as bytes
|
:param image_format: Image format.
|
||||||
|
:returns: image as bytes, saved into the given format.
|
||||||
"""
|
"""
|
||||||
b = io.BytesIO()
|
b = io.BytesIO()
|
||||||
try:
|
try:
|
||||||
self.save(b, "PNG")
|
self.save(b, image_format)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
msg = "Could not save to PNG for display"
|
msg = f"Could not save to {image_format} for display"
|
||||||
raise ValueError(msg) from e
|
raise ValueError(msg) from e
|
||||||
return b.getvalue()
|
return b.getvalue()
|
||||||
|
|
||||||
|
def _repr_png_(self):
|
||||||
|
"""iPython display hook support for PNG format.
|
||||||
|
|
||||||
|
:returns: PNG version of the image as bytes
|
||||||
|
"""
|
||||||
|
return self._repr_image("PNG")
|
||||||
|
|
||||||
|
def _repr_jpeg_(self):
|
||||||
|
"""iPython display hook support for JPEG format.
|
||||||
|
|
||||||
|
:returns: JPEG version of the image as bytes
|
||||||
|
"""
|
||||||
|
return self._repr_image("JPEG")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def __array_interface__(self):
|
def __array_interface__(self):
|
||||||
# numpy array interface support
|
# numpy array interface support
|
||||||
|
@ -1108,7 +1123,6 @@ class Image:
|
||||||
Available methods are :data:`Dither.NONE` or :data:`Dither.FLOYDSTEINBERG`
|
Available methods are :data:`Dither.NONE` or :data:`Dither.FLOYDSTEINBERG`
|
||||||
(default).
|
(default).
|
||||||
:returns: A new image
|
:returns: A new image
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.load()
|
self.load()
|
||||||
|
@ -1240,7 +1254,7 @@ class Image:
|
||||||
if ymargin is None:
|
if ymargin is None:
|
||||||
ymargin = xmargin
|
ymargin = xmargin
|
||||||
self.load()
|
self.load()
|
||||||
return self._new(self.im.expand(xmargin, ymargin, 0))
|
return self._new(self.im.expand(xmargin, ymargin))
|
||||||
|
|
||||||
def filter(self, filter):
|
def filter(self, filter):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -18,10 +18,10 @@
|
||||||
import sys
|
import sys
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
|
|
||||||
from PIL import Image
|
from . import Image
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PIL import _imagingcms
|
from . import _imagingcms
|
||||||
except ImportError as ex:
|
except ImportError as ex:
|
||||||
# Allow error import for doc purposes, but error out when accessing
|
# Allow error import for doc purposes, but error out when accessing
|
||||||
# anything in core.
|
# anything in core.
|
||||||
|
@ -271,7 +271,7 @@ def get_display_profile(handle=None):
|
||||||
if sys.platform != "win32":
|
if sys.platform != "win32":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
from PIL import ImageWin
|
from . import ImageWin
|
||||||
|
|
||||||
if isinstance(handle, ImageWin.HDC):
|
if isinstance(handle, ImageWin.HDC):
|
||||||
profile = core.get_display_profile_win32(handle, 1)
|
profile = core.get_display_profile_win32(handle, 1)
|
||||||
|
|
|
@ -279,11 +279,11 @@ class ImageDraw:
|
||||||
self.im.paste(im.im, (0, 0) + im.size, mask.im)
|
self.im.paste(im.im, (0, 0) + im.size, mask.im)
|
||||||
|
|
||||||
def regular_polygon(
|
def regular_polygon(
|
||||||
self, bounding_circle, n_sides, rotation=0, fill=None, outline=None
|
self, bounding_circle, n_sides, rotation=0, fill=None, outline=None, width=1
|
||||||
):
|
):
|
||||||
"""Draw a regular polygon."""
|
"""Draw a regular polygon."""
|
||||||
xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)
|
xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)
|
||||||
self.polygon(xy, fill, outline)
|
self.polygon(xy, fill, outline, width)
|
||||||
|
|
||||||
def rectangle(self, xy, fill=None, outline=None, width=1):
|
def rectangle(self, xy, fill=None, outline=None, width=1):
|
||||||
"""Draw a rectangle."""
|
"""Draw a rectangle."""
|
||||||
|
@ -314,11 +314,11 @@ class ImageDraw:
|
||||||
|
|
||||||
full_x, full_y = False, False
|
full_x, full_y = False, False
|
||||||
if all(corners):
|
if all(corners):
|
||||||
full_x = d >= x1 - x0
|
full_x = d >= x1 - x0 - 1
|
||||||
if full_x:
|
if full_x:
|
||||||
# The two left and two right corners are joined
|
# The two left and two right corners are joined
|
||||||
d = x1 - x0
|
d = x1 - x0
|
||||||
full_y = d >= y1 - y0
|
full_y = d >= y1 - y0 - 1
|
||||||
if full_y:
|
if full_y:
|
||||||
# The two top and two bottom corners are joined
|
# The two top and two bottom corners are joined
|
||||||
d = y1 - y0
|
d = y1 - y0
|
||||||
|
|
|
@ -35,7 +35,7 @@ class BuiltinFilter(MultibandFilter):
|
||||||
|
|
||||||
class Kernel(BuiltinFilter):
|
class Kernel(BuiltinFilter):
|
||||||
"""
|
"""
|
||||||
Create a convolution kernel. The current version only
|
Create a convolution kernel. The current version only
|
||||||
supports 3x3 and 5x5 integer and floating point kernels.
|
supports 3x3 and 5x5 integer and floating point kernels.
|
||||||
|
|
||||||
In the current version, kernels can only be applied to
|
In the current version, kernels can only be applied to
|
||||||
|
@ -43,9 +43,10 @@ class Kernel(BuiltinFilter):
|
||||||
|
|
||||||
:param size: Kernel size, given as (width, height). In the current
|
:param size: Kernel size, given as (width, height). In the current
|
||||||
version, this must be (3,3) or (5,5).
|
version, this must be (3,3) or (5,5).
|
||||||
:param kernel: A sequence containing kernel weights.
|
:param kernel: A sequence containing kernel weights. The kernel will
|
||||||
|
be flipped vertically before being applied to the image.
|
||||||
:param scale: Scale factor. If given, the result for each pixel is
|
:param scale: Scale factor. If given, the result for each pixel is
|
||||||
divided by this value. The default is the sum of the
|
divided by this value. The default is the sum of the
|
||||||
kernel weights.
|
kernel weights.
|
||||||
:param offset: Offset. If given, this value is added to the result,
|
:param offset: Offset. If given, this value is added to the result,
|
||||||
after it has been divided by the scale factor.
|
after it has been divided by the scale factor.
|
||||||
|
|
|
@ -26,7 +26,6 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import math
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
|
@ -226,10 +225,6 @@ class FreeTypeFont:
|
||||||
path, size, index, encoding, layout_engine = state
|
path, size, index, encoding, layout_engine = state
|
||||||
self.__init__(path, size, index, encoding, layout_engine)
|
self.__init__(path, size, index, encoding, layout_engine)
|
||||||
|
|
||||||
def _multiline_split(self, text):
|
|
||||||
split_character = "\n" if isinstance(text, str) else b"\n"
|
|
||||||
return text.split(split_character)
|
|
||||||
|
|
||||||
def getname(self):
|
def getname(self):
|
||||||
"""
|
"""
|
||||||
:return: A tuple of the font family (e.g. Helvetica) and the font style
|
:return: A tuple of the font family (e.g. Helvetica) and the font style
|
||||||
|
@ -551,28 +546,23 @@ class FreeTypeFont:
|
||||||
:py:mod:`PIL.Image.core` interface module, and the text offset, the
|
:py:mod:`PIL.Image.core` interface module, and the text offset, the
|
||||||
gap between the starting coordinate and the first marking
|
gap between the starting coordinate and the first marking
|
||||||
"""
|
"""
|
||||||
size, offset = self.font.getsize(
|
|
||||||
text, mode, direction, features, language, anchor
|
|
||||||
)
|
|
||||||
if start is None:
|
if start is None:
|
||||||
start = (0, 0)
|
start = (0, 0)
|
||||||
size = tuple(math.ceil(size[i] + stroke_width * 2 + start[i]) for i in range(2))
|
im, size, offset = self.font.render(
|
||||||
offset = offset[0] - stroke_width, offset[1] - stroke_width
|
text,
|
||||||
|
Image.core.fill,
|
||||||
|
mode,
|
||||||
|
direction,
|
||||||
|
features,
|
||||||
|
language,
|
||||||
|
stroke_width,
|
||||||
|
anchor,
|
||||||
|
ink,
|
||||||
|
start[0],
|
||||||
|
start[1],
|
||||||
|
Image.MAX_IMAGE_PIXELS,
|
||||||
|
)
|
||||||
Image._decompression_bomb_check(size)
|
Image._decompression_bomb_check(size)
|
||||||
im = Image.core.fill("RGBA" if mode == "RGBA" else "L", size, 0)
|
|
||||||
if min(size):
|
|
||||||
self.font.render(
|
|
||||||
text,
|
|
||||||
im.id,
|
|
||||||
mode,
|
|
||||||
direction,
|
|
||||||
features,
|
|
||||||
language,
|
|
||||||
stroke_width,
|
|
||||||
ink,
|
|
||||||
start[0],
|
|
||||||
start[1],
|
|
||||||
)
|
|
||||||
return im, offset
|
return im, offset
|
||||||
|
|
||||||
def font_variant(
|
def font_variant(
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import io
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
@ -61,7 +62,17 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N
|
||||||
left, top, right, bottom = bbox
|
left, top, right, bottom = bbox
|
||||||
im = im.crop((left - x0, top - y0, right - x0, bottom - y0))
|
im = im.crop((left - x0, top - y0, right - x0, bottom - y0))
|
||||||
return im
|
return im
|
||||||
elif shutil.which("gnome-screenshot"):
|
try:
|
||||||
|
if not Image.core.HAVE_XCB:
|
||||||
|
msg = "Pillow was built without XCB support"
|
||||||
|
raise OSError(msg)
|
||||||
|
size, data = Image.core.grabscreen_x11(xdisplay)
|
||||||
|
except OSError:
|
||||||
|
if (
|
||||||
|
xdisplay is None
|
||||||
|
and sys.platform not in ("darwin", "win32")
|
||||||
|
and shutil.which("gnome-screenshot")
|
||||||
|
):
|
||||||
fh, filepath = tempfile.mkstemp(".png")
|
fh, filepath = tempfile.mkstemp(".png")
|
||||||
os.close(fh)
|
os.close(fh)
|
||||||
subprocess.call(["gnome-screenshot", "-f", filepath])
|
subprocess.call(["gnome-screenshot", "-f", filepath])
|
||||||
|
@ -73,15 +84,13 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N
|
||||||
im.close()
|
im.close()
|
||||||
return im_cropped
|
return im_cropped
|
||||||
return im
|
return im
|
||||||
# use xdisplay=None for default display on non-win32/macOS systems
|
else:
|
||||||
if not Image.core.HAVE_XCB:
|
raise
|
||||||
msg = "Pillow was built without XCB support"
|
else:
|
||||||
raise OSError(msg)
|
im = Image.frombytes("RGB", size, data, "raw", "BGRX", size[0] * 4, 1)
|
||||||
size, data = Image.core.grabscreen_x11(xdisplay)
|
if bbox:
|
||||||
im = Image.frombytes("RGB", size, data, "raw", "BGRX", size[0] * 4, 1)
|
im = im.crop(bbox)
|
||||||
if bbox:
|
return im
|
||||||
im = im.crop(bbox)
|
|
||||||
return im
|
|
||||||
|
|
||||||
|
|
||||||
def grabclipboard():
|
def grabclipboard():
|
||||||
|
@ -120,8 +129,6 @@ def grabclipboard():
|
||||||
files = data[o:].decode("mbcs").split("\0")
|
files = data[o:].decode("mbcs").split("\0")
|
||||||
return files[: files.index("")]
|
return files[: files.index("")]
|
||||||
if isinstance(data, bytes):
|
if isinstance(data, bytes):
|
||||||
import io
|
|
||||||
|
|
||||||
data = io.BytesIO(data)
|
data = io.BytesIO(data)
|
||||||
if fmt == "png":
|
if fmt == "png":
|
||||||
from . import PngImagePlugin
|
from . import PngImagePlugin
|
||||||
|
@ -134,16 +141,29 @@ def grabclipboard():
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
if shutil.which("wl-paste"):
|
if shutil.which("wl-paste"):
|
||||||
|
output = subprocess.check_output(["wl-paste", "-l"]).decode()
|
||||||
|
mimetypes = output.splitlines()
|
||||||
|
if "image/png" in mimetypes:
|
||||||
|
mimetype = "image/png"
|
||||||
|
elif mimetypes:
|
||||||
|
mimetype = mimetypes[0]
|
||||||
|
else:
|
||||||
|
mimetype = None
|
||||||
|
|
||||||
args = ["wl-paste"]
|
args = ["wl-paste"]
|
||||||
|
if mimetype:
|
||||||
|
args.extend(["-t", mimetype])
|
||||||
elif shutil.which("xclip"):
|
elif shutil.which("xclip"):
|
||||||
args = ["xclip", "-selection", "clipboard", "-t", "image/png", "-o"]
|
args = ["xclip", "-selection", "clipboard", "-t", "image/png", "-o"]
|
||||||
else:
|
else:
|
||||||
msg = "wl-paste or xclip is required for ImageGrab.grabclipboard() on Linux"
|
msg = "wl-paste or xclip is required for ImageGrab.grabclipboard() on Linux"
|
||||||
raise NotImplementedError(msg)
|
raise NotImplementedError(msg)
|
||||||
fh, filepath = tempfile.mkstemp()
|
p = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
subprocess.call(args, stdout=fh)
|
err = p.stderr
|
||||||
os.close(fh)
|
if err:
|
||||||
im = Image.open(filepath)
|
msg = f"{args[0]} error: {err.strip().decode()}"
|
||||||
|
raise ChildProcessError(msg)
|
||||||
|
data = io.BytesIO(p.stdout)
|
||||||
|
im = Image.open(data)
|
||||||
im.load()
|
im.load()
|
||||||
os.unlink(filepath)
|
|
||||||
return im
|
return im
|
||||||
|
|
|
@ -17,7 +17,7 @@ import subprocess
|
||||||
import sys
|
import sys
|
||||||
from shlex import quote
|
from shlex import quote
|
||||||
|
|
||||||
from PIL import Image
|
from . import Image
|
||||||
|
|
||||||
_viewers = []
|
_viewers = []
|
||||||
|
|
||||||
|
|
|
@ -457,6 +457,11 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
if os.path.exists(self.filename):
|
if os.path.exists(self.filename):
|
||||||
subprocess.check_call(["djpeg", "-outfile", path, self.filename])
|
subprocess.check_call(["djpeg", "-outfile", path, self.filename])
|
||||||
else:
|
else:
|
||||||
|
try:
|
||||||
|
os.unlink(path)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
msg = "Invalid Filename"
|
msg = "Invalid Filename"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
|
|
@ -1146,11 +1146,14 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
|
||||||
and prev_disposal == encoderinfo.get("disposal")
|
and prev_disposal == encoderinfo.get("disposal")
|
||||||
and prev_blend == encoderinfo.get("blend")
|
and prev_blend == encoderinfo.get("blend")
|
||||||
):
|
):
|
||||||
if isinstance(duration, (list, tuple)):
|
previous["encoderinfo"]["duration"] += encoderinfo.get(
|
||||||
previous["encoderinfo"]["duration"] += encoderinfo["duration"]
|
"duration", duration
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
bbox = None
|
bbox = None
|
||||||
|
if "duration" not in encoderinfo:
|
||||||
|
encoderinfo["duration"] = duration
|
||||||
im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo})
|
im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo})
|
||||||
|
|
||||||
# animation control
|
# animation control
|
||||||
|
@ -1175,7 +1178,7 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
|
||||||
im_frame = im_frame.crop(bbox)
|
im_frame = im_frame.crop(bbox)
|
||||||
size = im_frame.size
|
size = im_frame.size
|
||||||
encoderinfo = frame_data["encoderinfo"]
|
encoderinfo = frame_data["encoderinfo"]
|
||||||
frame_duration = int(round(encoderinfo.get("duration", duration)))
|
frame_duration = int(round(encoderinfo["duration"]))
|
||||||
frame_disposal = encoderinfo.get("disposal", disposal)
|
frame_disposal = encoderinfo.get("disposal", disposal)
|
||||||
frame_blend = encoderinfo.get("blend", blend)
|
frame_blend = encoderinfo.get("blend", blend)
|
||||||
# frame control
|
# frame control
|
||||||
|
|
|
@ -36,7 +36,7 @@ import os
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from PIL import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
|
|
||||||
|
|
||||||
def isInt(f):
|
def isInt(f):
|
||||||
|
@ -191,7 +191,7 @@ class SpiderImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
# returns a ImageTk.PhotoImage object, after rescaling to 0..255
|
# returns a ImageTk.PhotoImage object, after rescaling to 0..255
|
||||||
def tkPhotoImage(self):
|
def tkPhotoImage(self):
|
||||||
from PIL import ImageTk
|
from . import ImageTk
|
||||||
|
|
||||||
return ImageTk.PhotoImage(self.convert2byte(), palette=256)
|
return ImageTk.PhotoImage(self.convert2byte(), palette=256)
|
||||||
|
|
||||||
|
|
|
@ -170,6 +170,8 @@ OPEN_INFO = {
|
||||||
(MM, 0, (1,), 2, (8,), ()): ("L", "L;IR"),
|
(MM, 0, (1,), 2, (8,), ()): ("L", "L;IR"),
|
||||||
(II, 1, (1,), 1, (8,), ()): ("L", "L"),
|
(II, 1, (1,), 1, (8,), ()): ("L", "L"),
|
||||||
(MM, 1, (1,), 1, (8,), ()): ("L", "L"),
|
(MM, 1, (1,), 1, (8,), ()): ("L", "L"),
|
||||||
|
(II, 1, (2,), 1, (8,), ()): ("L", "L"),
|
||||||
|
(MM, 1, (2,), 1, (8,), ()): ("L", "L"),
|
||||||
(II, 1, (1,), 2, (8,), ()): ("L", "L;R"),
|
(II, 1, (1,), 2, (8,), ()): ("L", "L;R"),
|
||||||
(MM, 1, (1,), 2, (8,), ()): ("L", "L;R"),
|
(MM, 1, (1,), 2, (8,), ()): ("L", "L;R"),
|
||||||
(II, 1, (1,), 1, (12,), ()): ("I;16", "I;12"),
|
(II, 1, (1,), 1, (12,), ()): ("I;16", "I;12"),
|
||||||
|
@ -1892,6 +1894,10 @@ class AppendingTiffWriter:
|
||||||
8, # srational
|
8, # srational
|
||||||
4, # float
|
4, # float
|
||||||
8, # double
|
8, # double
|
||||||
|
4, # ifd
|
||||||
|
2, # unicode
|
||||||
|
4, # complex
|
||||||
|
8, # long8
|
||||||
]
|
]
|
||||||
|
|
||||||
# StripOffsets = 273
|
# StripOffsets = 273
|
||||||
|
|
|
@ -1027,12 +1027,11 @@ _crop(ImagingObject *self, PyObject *args) {
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_expand_image(ImagingObject *self, PyObject *args) {
|
_expand_image(ImagingObject *self, PyObject *args) {
|
||||||
int x, y;
|
int x, y;
|
||||||
int mode = 0;
|
if (!PyArg_ParseTuple(args, "ii", &x, &y)) {
|
||||||
if (!PyArg_ParseTuple(args, "ii|i", &x, &y, &mode)) {
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return PyImagingNew(ImagingExpand(self->image, x, y, mode));
|
return PyImagingNew(ImagingExpand(self->image, x, y));
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
|
243
src/_imagingft.c
243
src/_imagingft.c
|
@ -132,6 +132,27 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if PY_MAJOR_VERSION > 3 || PY_MINOR_VERSION > 11
|
||||||
|
PyConfig config;
|
||||||
|
PyConfig_InitPythonConfig(&config);
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(
|
||||||
|
args,
|
||||||
|
kw,
|
||||||
|
"etf|nsy#n",
|
||||||
|
kwlist,
|
||||||
|
config.filesystem_encoding,
|
||||||
|
&filename,
|
||||||
|
&size,
|
||||||
|
&index,
|
||||||
|
&encoding,
|
||||||
|
&font_bytes,
|
||||||
|
&font_bytes_size,
|
||||||
|
&layout_engine)) {
|
||||||
|
PyConfig_Clear(&config);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
PyConfig_Clear(&config);
|
||||||
|
#else
|
||||||
if (!PyArg_ParseTupleAndKeywords(
|
if (!PyArg_ParseTupleAndKeywords(
|
||||||
args,
|
args,
|
||||||
kw,
|
kw,
|
||||||
|
@ -147,6 +168,7 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
|
||||||
&layout_engine)) {
|
&layout_engine)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
self = PyObject_New(FontObject, &Font_Type);
|
self = PyObject_New(FontObject, &Font_Type);
|
||||||
if (!self) {
|
if (!self) {
|
||||||
|
@ -232,9 +254,7 @@ text_layout_raqm(
|
||||||
const char *dir,
|
const char *dir,
|
||||||
PyObject *features,
|
PyObject *features,
|
||||||
const char *lang,
|
const char *lang,
|
||||||
GlyphInfo **glyph_info,
|
GlyphInfo **glyph_info) {
|
||||||
int mask,
|
|
||||||
int color) {
|
|
||||||
size_t i = 0, count = 0, start = 0;
|
size_t i = 0, count = 0, start = 0;
|
||||||
raqm_t *rq;
|
raqm_t *rq;
|
||||||
raqm_glyph_t *glyphs = NULL;
|
raqm_glyph_t *glyphs = NULL;
|
||||||
|
@ -471,7 +491,7 @@ text_layout(
|
||||||
#ifdef HAVE_RAQM
|
#ifdef HAVE_RAQM
|
||||||
if (have_raqm && self->layout_engine == LAYOUT_RAQM) {
|
if (have_raqm && self->layout_engine == LAYOUT_RAQM) {
|
||||||
count = text_layout_raqm(
|
count = text_layout_raqm(
|
||||||
string, self, dir, features, lang, glyph_info, mask, color);
|
string, self, dir, features, lang, glyph_info);
|
||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
|
@ -529,73 +549,25 @@ font_getlength(FontObject *self, PyObject *args) {
|
||||||
return PyLong_FromLong(length);
|
return PyLong_FromLong(length);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static int
|
||||||
font_getsize(FontObject *self, PyObject *args) {
|
bounding_box_and_anchors(FT_Face face, const char *anchor, int horizontal_dir, GlyphInfo *glyph_info, size_t count, int load_flags, int *width, int *height, int *x_offset, int *y_offset) {
|
||||||
int position; /* pen position along primary axis, in 26.6 precision */
|
int position; /* pen position along primary axis, in 26.6 precision */
|
||||||
int advanced; /* pen position along primary axis, in pixels */
|
int advanced; /* pen position along primary axis, in pixels */
|
||||||
int px, py; /* position of current glyph, in pixels */
|
int px, py; /* position of current glyph, in pixels */
|
||||||
int x_min, x_max, y_min, y_max; /* text bounding box, in pixels */
|
int x_min, x_max, y_min, y_max; /* text bounding box, in pixels */
|
||||||
int x_anchor, y_anchor; /* offset of point drawn at (0, 0), in pixels */
|
int x_anchor, y_anchor; /* offset of point drawn at (0, 0), in pixels */
|
||||||
int load_flags; /* FreeType load_flags parameter */
|
|
||||||
int error;
|
int error;
|
||||||
FT_Face face;
|
|
||||||
FT_Glyph glyph;
|
FT_Glyph glyph;
|
||||||
FT_BBox bbox; /* glyph bounding box */
|
FT_BBox bbox; /* glyph bounding box */
|
||||||
GlyphInfo *glyph_info = NULL; /* computed text layout */
|
size_t i; /* glyph_info index */
|
||||||
size_t i, count; /* glyph_info index and length */
|
|
||||||
int horizontal_dir; /* is primary axis horizontal? */
|
|
||||||
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
|
||||||
int color = 0; /* is FT_LOAD_COLOR enabled? */
|
|
||||||
const char *mode = NULL;
|
|
||||||
const char *dir = NULL;
|
|
||||||
const char *lang = NULL;
|
|
||||||
const char *anchor = NULL;
|
|
||||||
PyObject *features = Py_None;
|
|
||||||
PyObject *string;
|
|
||||||
|
|
||||||
/* calculate size and bearing for a given string */
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(
|
|
||||||
args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
|
||||||
|
|
||||||
mask = mode && strcmp(mode, "1") == 0;
|
|
||||||
color = mode && strcmp(mode, "RGBA") == 0;
|
|
||||||
|
|
||||||
if (anchor == NULL) {
|
|
||||||
anchor = horizontal_dir ? "la" : "lt";
|
|
||||||
}
|
|
||||||
if (strlen(anchor) != 2) {
|
|
||||||
goto bad_anchor;
|
|
||||||
}
|
|
||||||
|
|
||||||
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
|
|
||||||
if (PyErr_Occurred()) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
load_flags = FT_LOAD_DEFAULT;
|
|
||||||
if (mask) {
|
|
||||||
load_flags |= FT_LOAD_TARGET_MONO;
|
|
||||||
}
|
|
||||||
if (color) {
|
|
||||||
load_flags |= FT_LOAD_COLOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* text bounds are given by:
|
* text bounds are given by:
|
||||||
* - bounding boxes of individual glyphs
|
* - bounding boxes of individual glyphs
|
||||||
* - pen line, i.e. 0 to `advanced` along primary axis
|
* - pen line, i.e. 0 to `advanced` along primary axis
|
||||||
* this means point (0, 0) is part of the text bounding box
|
* this means point (0, 0) is part of the text bounding box
|
||||||
*/
|
*/
|
||||||
face = NULL;
|
|
||||||
position = x_min = x_max = y_min = y_max = 0;
|
position = x_min = x_max = y_min = y_max = 0;
|
||||||
for (i = 0; i < count; i++) {
|
for (i = 0; i < count; i++) {
|
||||||
face = self->face;
|
|
||||||
|
|
||||||
if (horizontal_dir) {
|
if (horizontal_dir) {
|
||||||
px = PIXEL(position + glyph_info[i].x_offset);
|
px = PIXEL(position + glyph_info[i].x_offset);
|
||||||
py = PIXEL(glyph_info[i].y_offset);
|
py = PIXEL(glyph_info[i].y_offset);
|
||||||
|
@ -618,12 +590,14 @@ font_getsize(FontObject *self, PyObject *args) {
|
||||||
|
|
||||||
error = FT_Load_Glyph(face, glyph_info[i].index, load_flags);
|
error = FT_Load_Glyph(face, glyph_info[i].index, load_flags);
|
||||||
if (error) {
|
if (error) {
|
||||||
return geterror(error);
|
geterror(error);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
error = FT_Get_Glyph(face->glyph, &glyph);
|
error = FT_Get_Glyph(face->glyph, &glyph);
|
||||||
if (error) {
|
if (error) {
|
||||||
return geterror(error);
|
geterror(error);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &bbox);
|
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &bbox);
|
||||||
|
@ -647,13 +621,15 @@ font_getsize(FontObject *self, PyObject *args) {
|
||||||
FT_Done_Glyph(glyph);
|
FT_Done_Glyph(glyph);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (glyph_info) {
|
if (anchor == NULL) {
|
||||||
PyMem_Free(glyph_info);
|
anchor = horizontal_dir ? "la" : "lt";
|
||||||
glyph_info = NULL;
|
}
|
||||||
|
if (strlen(anchor) != 2) {
|
||||||
|
goto bad_anchor;
|
||||||
}
|
}
|
||||||
|
|
||||||
x_anchor = y_anchor = 0;
|
x_anchor = y_anchor = 0;
|
||||||
if (face) {
|
if (count) {
|
||||||
if (horizontal_dir) {
|
if (horizontal_dir) {
|
||||||
switch (anchor[0]) {
|
switch (anchor[0]) {
|
||||||
case 'l': // left
|
case 'l': // left
|
||||||
|
@ -671,15 +647,15 @@ font_getsize(FontObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
switch (anchor[1]) {
|
switch (anchor[1]) {
|
||||||
case 'a': // ascender
|
case 'a': // ascender
|
||||||
y_anchor = PIXEL(self->face->size->metrics.ascender);
|
y_anchor = PIXEL(face->size->metrics.ascender);
|
||||||
break;
|
break;
|
||||||
case 't': // top
|
case 't': // top
|
||||||
y_anchor = y_max;
|
y_anchor = y_max;
|
||||||
break;
|
break;
|
||||||
case 'm': // middle (ascender + descender) / 2
|
case 'm': // middle (ascender + descender) / 2
|
||||||
y_anchor = PIXEL(
|
y_anchor = PIXEL(
|
||||||
(self->face->size->metrics.ascender +
|
(face->size->metrics.ascender +
|
||||||
self->face->size->metrics.descender) /
|
face->size->metrics.descender) /
|
||||||
2);
|
2);
|
||||||
break;
|
break;
|
||||||
case 's': // horizontal baseline
|
case 's': // horizontal baseline
|
||||||
|
@ -689,7 +665,7 @@ font_getsize(FontObject *self, PyObject *args) {
|
||||||
y_anchor = y_min;
|
y_anchor = y_min;
|
||||||
break;
|
break;
|
||||||
case 'd': // descender
|
case 'd': // descender
|
||||||
y_anchor = PIXEL(self->face->size->metrics.descender);
|
y_anchor = PIXEL(face->size->metrics.descender);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
goto bad_anchor;
|
goto bad_anchor;
|
||||||
|
@ -729,17 +705,74 @@ font_getsize(FontObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*width = x_max - x_min;
|
||||||
return Py_BuildValue(
|
*height = y_max - y_min;
|
||||||
"(ii)(ii)",
|
*x_offset = -x_anchor + x_min;
|
||||||
(x_max - x_min),
|
*y_offset = -(-y_anchor + y_max);
|
||||||
(y_max - y_min),
|
return 0;
|
||||||
(-x_anchor + x_min),
|
|
||||||
-(-y_anchor + y_max));
|
|
||||||
|
|
||||||
bad_anchor:
|
bad_anchor:
|
||||||
PyErr_Format(PyExc_ValueError, "bad anchor specified: %s", anchor);
|
PyErr_Format(PyExc_ValueError, "bad anchor specified: %s", anchor);
|
||||||
return NULL;
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
font_getsize(FontObject *self, PyObject *args) {
|
||||||
|
int width, height, x_offset, y_offset;
|
||||||
|
int load_flags; /* FreeType load_flags parameter */
|
||||||
|
int error;
|
||||||
|
GlyphInfo *glyph_info = NULL; /* computed text layout */
|
||||||
|
size_t count; /* glyph_info length */
|
||||||
|
int horizontal_dir; /* is primary axis horizontal? */
|
||||||
|
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
||||||
|
int color = 0; /* is FT_LOAD_COLOR enabled? */
|
||||||
|
const char *mode = NULL;
|
||||||
|
const char *dir = NULL;
|
||||||
|
const char *lang = NULL;
|
||||||
|
const char *anchor = NULL;
|
||||||
|
PyObject *features = Py_None;
|
||||||
|
PyObject *string;
|
||||||
|
|
||||||
|
/* calculate size and bearing for a given string */
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(
|
||||||
|
args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
||||||
|
|
||||||
|
mask = mode && strcmp(mode, "1") == 0;
|
||||||
|
color = mode && strcmp(mode, "RGBA") == 0;
|
||||||
|
|
||||||
|
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
|
||||||
|
if (PyErr_Occurred()) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
load_flags = FT_LOAD_DEFAULT;
|
||||||
|
if (mask) {
|
||||||
|
load_flags |= FT_LOAD_TARGET_MONO;
|
||||||
|
}
|
||||||
|
if (color) {
|
||||||
|
load_flags |= FT_LOAD_COLOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = bounding_box_and_anchors(self->face, anchor, horizontal_dir, glyph_info, count, load_flags, &width, &height, &x_offset, &y_offset);
|
||||||
|
if (glyph_info) {
|
||||||
|
PyMem_Free(glyph_info);
|
||||||
|
glyph_info = NULL;
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Py_BuildValue(
|
||||||
|
"(ii)(ii)",
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
x_offset,
|
||||||
|
y_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -763,6 +796,7 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
unsigned int bitmap_y; /* glyph bitmap y index */
|
unsigned int bitmap_y; /* glyph bitmap y index */
|
||||||
unsigned char *source; /* glyph bitmap source buffer */
|
unsigned char *source; /* glyph bitmap source buffer */
|
||||||
unsigned char convert_scale; /* scale factor for non-8bpp bitmaps */
|
unsigned char convert_scale; /* scale factor for non-8bpp bitmaps */
|
||||||
|
PyObject *image;
|
||||||
Imaging im;
|
Imaging im;
|
||||||
Py_ssize_t id;
|
Py_ssize_t id;
|
||||||
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
||||||
|
@ -773,27 +807,34 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
const char *mode = NULL;
|
const char *mode = NULL;
|
||||||
const char *dir = NULL;
|
const char *dir = NULL;
|
||||||
const char *lang = NULL;
|
const char *lang = NULL;
|
||||||
|
const char *anchor = NULL;
|
||||||
PyObject *features = Py_None;
|
PyObject *features = Py_None;
|
||||||
PyObject *string;
|
PyObject *string;
|
||||||
|
PyObject *fill;
|
||||||
float x_start = 0;
|
float x_start = 0;
|
||||||
float y_start = 0;
|
float y_start = 0;
|
||||||
|
int width, height, x_offset, y_offset;
|
||||||
|
int horizontal_dir; /* is primary axis horizontal? */
|
||||||
|
PyObject *max_image_pixels = Py_None;
|
||||||
|
|
||||||
/* render string into given buffer (the buffer *must* have
|
/* render string into given buffer (the buffer *must* have
|
||||||
the right size, or this will crash) */
|
the right size, or this will crash) */
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args,
|
args,
|
||||||
"On|zzOziLff:render",
|
"OO|zzOzizLffO:render",
|
||||||
&string,
|
&string,
|
||||||
&id,
|
&fill,
|
||||||
&mode,
|
&mode,
|
||||||
&dir,
|
&dir,
|
||||||
&features,
|
&features,
|
||||||
&lang,
|
&lang,
|
||||||
&stroke_width,
|
&stroke_width,
|
||||||
|
&anchor,
|
||||||
&foreground_ink_long,
|
&foreground_ink_long,
|
||||||
&x_start,
|
&x_start,
|
||||||
&y_start)) {
|
&y_start,
|
||||||
|
&max_image_pixels)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -819,8 +860,41 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
if (PyErr_Occurred()) {
|
if (PyErr_Occurred()) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (count == 0) {
|
|
||||||
Py_RETURN_NONE;
|
load_flags = stroke_width ? FT_LOAD_NO_BITMAP : FT_LOAD_DEFAULT;
|
||||||
|
if (mask) {
|
||||||
|
load_flags |= FT_LOAD_TARGET_MONO;
|
||||||
|
}
|
||||||
|
if (color) {
|
||||||
|
load_flags |= FT_LOAD_COLOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
||||||
|
|
||||||
|
error = bounding_box_and_anchors(self->face, anchor, horizontal_dir, glyph_info, count, load_flags, &width, &height, &x_offset, &y_offset);
|
||||||
|
if (error) {
|
||||||
|
PyMem_Del(glyph_info);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
width += stroke_width * 2 + ceil(x_start);
|
||||||
|
height += stroke_width * 2 + ceil(y_start);
|
||||||
|
if (max_image_pixels != Py_None) {
|
||||||
|
if (width * height > PyLong_AsLong(max_image_pixels) * 2) {
|
||||||
|
PyMem_Del(glyph_info);
|
||||||
|
return Py_BuildValue("O(ii)(ii)", Py_None, width, height, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
image = PyObject_CallFunction(fill, "s(ii)", strcmp(mode, "RGBA") == 0 ? "RGBA" : "L", width, height);
|
||||||
|
id = PyLong_AsSsize_t(PyObject_GetAttrString(image, "id"));
|
||||||
|
im = (Imaging)id;
|
||||||
|
|
||||||
|
x_offset -= stroke_width;
|
||||||
|
y_offset -= stroke_width;
|
||||||
|
if (count == 0 || width == 0 || height == 0) {
|
||||||
|
PyMem_Del(glyph_info);
|
||||||
|
return Py_BuildValue("O(ii)(ii)", image, width, height, x_offset, y_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stroke_width) {
|
if (stroke_width) {
|
||||||
|
@ -837,15 +911,6 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
|
|
||||||
im = (Imaging)id;
|
|
||||||
load_flags = stroke_width ? FT_LOAD_NO_BITMAP : FT_LOAD_DEFAULT;
|
|
||||||
if (mask) {
|
|
||||||
load_flags |= FT_LOAD_TARGET_MONO;
|
|
||||||
}
|
|
||||||
if (color) {
|
|
||||||
load_flags |= FT_LOAD_COLOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* calculate x_min and y_max
|
* calculate x_min and y_max
|
||||||
* must match font_getsize or there may be clipping!
|
* must match font_getsize or there may be clipping!
|
||||||
|
@ -1042,7 +1107,7 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
FT_Stroker_Done(stroker);
|
FT_Stroker_Done(stroker);
|
||||||
PyMem_Del(glyph_info);
|
PyMem_Del(glyph_info);
|
||||||
Py_RETURN_NONE;
|
return Py_BuildValue("O(ii)(ii)", image, width, height, x_offset, y_offset);
|
||||||
|
|
||||||
glyph_error:
|
glyph_error:
|
||||||
if (stroker != NULL) {
|
if (stroker != NULL) {
|
||||||
|
|
|
@ -437,8 +437,14 @@ PyImaging_GrabClipboardWin32(PyObject *self, PyObject *args) {
|
||||||
LPCSTR format_names[] = {"DIB", "DIB", "file", "png", NULL};
|
LPCSTR format_names[] = {"DIB", "DIB", "file", "png", NULL};
|
||||||
|
|
||||||
if (!OpenClipboard(NULL)) {
|
if (!OpenClipboard(NULL)) {
|
||||||
PyErr_SetString(PyExc_OSError, "failed to open clipboard");
|
// Maybe the clipboard is temporarily in use by another process.
|
||||||
return NULL;
|
// Wait and try again
|
||||||
|
Sleep(500);
|
||||||
|
|
||||||
|
if (!OpenClipboard(NULL)) {
|
||||||
|
PyErr_SetString(PyExc_OSError, "failed to open clipboard");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// find best format as set by clipboard owner
|
// find best format as set by clipboard owner
|
||||||
|
|
|
@ -37,8 +37,19 @@ clip8(float in) {
|
||||||
return (UINT8)in;
|
return (UINT8)in;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline INT32
|
||||||
|
clip32(float in) {
|
||||||
|
if (in <= 0.0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (in >= pow(2, 31) - 1) {
|
||||||
|
return pow(2, 31) - 1;
|
||||||
|
}
|
||||||
|
return (INT32)in;
|
||||||
|
}
|
||||||
|
|
||||||
Imaging
|
Imaging
|
||||||
ImagingExpand(Imaging imIn, int xmargin, int ymargin, int mode) {
|
ImagingExpand(Imaging imIn, int xmargin, int ymargin) {
|
||||||
Imaging imOut;
|
Imaging imOut;
|
||||||
int x, y;
|
int x, y;
|
||||||
ImagingSectionCookie cookie;
|
ImagingSectionCookie cookie;
|
||||||
|
@ -96,8 +107,8 @@ ImagingExpand(Imaging imIn, int xmargin, int ymargin, int mode) {
|
||||||
void
|
void
|
||||||
ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
|
ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
|
||||||
#define KERNEL1x3(in0, x, kernel, d) \
|
#define KERNEL1x3(in0, x, kernel, d) \
|
||||||
(_i2f((UINT8)in0[x - d]) * (kernel)[0] + _i2f((UINT8)in0[x]) * (kernel)[1] + \
|
(_i2f(in0[x - d]) * (kernel)[0] + _i2f(in0[x]) * (kernel)[1] + \
|
||||||
_i2f((UINT8)in0[x + d]) * (kernel)[2])
|
_i2f(in0[x + d]) * (kernel)[2])
|
||||||
|
|
||||||
int x = 0, y = 0;
|
int x = 0, y = 0;
|
||||||
|
|
||||||
|
@ -105,21 +116,40 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
|
||||||
if (im->bands == 1) {
|
if (im->bands == 1) {
|
||||||
// Add one time for rounding
|
// Add one time for rounding
|
||||||
offset += 0.5;
|
offset += 0.5;
|
||||||
for (y = 1; y < im->ysize - 1; y++) {
|
if (im->type == IMAGING_TYPE_INT32) {
|
||||||
UINT8 *in_1 = (UINT8 *)im->image[y - 1];
|
for (y = 1; y < im->ysize - 1; y++) {
|
||||||
UINT8 *in0 = (UINT8 *)im->image[y];
|
INT32 *in_1 = (INT32 *)im->image[y - 1];
|
||||||
UINT8 *in1 = (UINT8 *)im->image[y + 1];
|
INT32 *in0 = (INT32 *)im->image[y];
|
||||||
UINT8 *out = (UINT8 *)imOut->image[y];
|
INT32 *in1 = (INT32 *)im->image[y + 1];
|
||||||
|
INT32 *out = (INT32 *)imOut->image[y];
|
||||||
|
|
||||||
out[0] = in0[0];
|
out[0] = in0[0];
|
||||||
for (x = 1; x < im->xsize - 1; x++) {
|
for (x = 1; x < im->xsize - 1; x++) {
|
||||||
float ss = offset;
|
float ss = offset;
|
||||||
ss += KERNEL1x3(in1, x, &kernel[0], 1);
|
ss += KERNEL1x3(in1, x, &kernel[0], 1);
|
||||||
ss += KERNEL1x3(in0, x, &kernel[3], 1);
|
ss += KERNEL1x3(in0, x, &kernel[3], 1);
|
||||||
ss += KERNEL1x3(in_1, x, &kernel[6], 1);
|
ss += KERNEL1x3(in_1, x, &kernel[6], 1);
|
||||||
out[x] = clip8(ss);
|
out[x] = clip32(ss);
|
||||||
|
}
|
||||||
|
out[x] = in0[x];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (y = 1; y < im->ysize - 1; y++) {
|
||||||
|
UINT8 *in_1 = (UINT8 *)im->image[y - 1];
|
||||||
|
UINT8 *in0 = (UINT8 *)im->image[y];
|
||||||
|
UINT8 *in1 = (UINT8 *)im->image[y + 1];
|
||||||
|
UINT8 *out = (UINT8 *)imOut->image[y];
|
||||||
|
|
||||||
|
out[0] = in0[0];
|
||||||
|
for (x = 1; x < im->xsize - 1; x++) {
|
||||||
|
float ss = offset;
|
||||||
|
ss += KERNEL1x3(in1, x, &kernel[0], 1);
|
||||||
|
ss += KERNEL1x3(in0, x, &kernel[3], 1);
|
||||||
|
ss += KERNEL1x3(in_1, x, &kernel[6], 1);
|
||||||
|
out[x] = clip8(ss);
|
||||||
|
}
|
||||||
|
out[x] = in0[x];
|
||||||
}
|
}
|
||||||
out[x] = in0[x];
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Add one time for rounding
|
// Add one time for rounding
|
||||||
|
@ -195,10 +225,10 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
|
||||||
void
|
void
|
||||||
ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) {
|
ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) {
|
||||||
#define KERNEL1x5(in0, x, kernel, d) \
|
#define KERNEL1x5(in0, x, kernel, d) \
|
||||||
(_i2f((UINT8)in0[x - d - d]) * (kernel)[0] + \
|
(_i2f(in0[x - d - d]) * (kernel)[0] + \
|
||||||
_i2f((UINT8)in0[x - d]) * (kernel)[1] + _i2f((UINT8)in0[x]) * (kernel)[2] + \
|
_i2f(in0[x - d]) * (kernel)[1] + _i2f(in0[x]) * (kernel)[2] + \
|
||||||
_i2f((UINT8)in0[x + d]) * (kernel)[3] + \
|
_i2f(in0[x + d]) * (kernel)[3] + \
|
||||||
_i2f((UINT8)in0[x + d + d]) * (kernel)[4])
|
_i2f(in0[x + d + d]) * (kernel)[4])
|
||||||
|
|
||||||
int x = 0, y = 0;
|
int x = 0, y = 0;
|
||||||
|
|
||||||
|
@ -207,27 +237,52 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) {
|
||||||
if (im->bands == 1) {
|
if (im->bands == 1) {
|
||||||
// Add one time for rounding
|
// Add one time for rounding
|
||||||
offset += 0.5;
|
offset += 0.5;
|
||||||
for (y = 2; y < im->ysize - 2; y++) {
|
if (im->type == IMAGING_TYPE_INT32) {
|
||||||
UINT8 *in_2 = (UINT8 *)im->image[y - 2];
|
for (y = 2; y < im->ysize - 2; y++) {
|
||||||
UINT8 *in_1 = (UINT8 *)im->image[y - 1];
|
INT32 *in_2 = (INT32 *)im->image[y - 2];
|
||||||
UINT8 *in0 = (UINT8 *)im->image[y];
|
INT32 *in_1 = (INT32 *)im->image[y - 1];
|
||||||
UINT8 *in1 = (UINT8 *)im->image[y + 1];
|
INT32 *in0 = (INT32 *)im->image[y];
|
||||||
UINT8 *in2 = (UINT8 *)im->image[y + 2];
|
INT32 *in1 = (INT32 *)im->image[y + 1];
|
||||||
UINT8 *out = (UINT8 *)imOut->image[y];
|
INT32 *in2 = (INT32 *)im->image[y + 2];
|
||||||
|
INT32 *out = (INT32 *)imOut->image[y];
|
||||||
|
|
||||||
out[0] = in0[0];
|
out[0] = in0[0];
|
||||||
out[1] = in0[1];
|
out[1] = in0[1];
|
||||||
for (x = 2; x < im->xsize - 2; x++) {
|
for (x = 2; x < im->xsize - 2; x++) {
|
||||||
float ss = offset;
|
float ss = offset;
|
||||||
ss += KERNEL1x5(in2, x, &kernel[0], 1);
|
ss += KERNEL1x5(in2, x, &kernel[0], 1);
|
||||||
ss += KERNEL1x5(in1, x, &kernel[5], 1);
|
ss += KERNEL1x5(in1, x, &kernel[5], 1);
|
||||||
ss += KERNEL1x5(in0, x, &kernel[10], 1);
|
ss += KERNEL1x5(in0, x, &kernel[10], 1);
|
||||||
ss += KERNEL1x5(in_1, x, &kernel[15], 1);
|
ss += KERNEL1x5(in_1, x, &kernel[15], 1);
|
||||||
ss += KERNEL1x5(in_2, x, &kernel[20], 1);
|
ss += KERNEL1x5(in_2, x, &kernel[20], 1);
|
||||||
out[x] = clip8(ss);
|
out[x] = clip32(ss);
|
||||||
|
}
|
||||||
|
out[x + 0] = in0[x + 0];
|
||||||
|
out[x + 1] = in0[x + 1];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (y = 2; y < im->ysize - 2; y++) {
|
||||||
|
UINT8 *in_2 = (UINT8 *)im->image[y - 2];
|
||||||
|
UINT8 *in_1 = (UINT8 *)im->image[y - 1];
|
||||||
|
UINT8 *in0 = (UINT8 *)im->image[y];
|
||||||
|
UINT8 *in1 = (UINT8 *)im->image[y + 1];
|
||||||
|
UINT8 *in2 = (UINT8 *)im->image[y + 2];
|
||||||
|
UINT8 *out = (UINT8 *)imOut->image[y];
|
||||||
|
|
||||||
|
out[0] = in0[0];
|
||||||
|
out[1] = in0[1];
|
||||||
|
for (x = 2; x < im->xsize - 2; x++) {
|
||||||
|
float ss = offset;
|
||||||
|
ss += KERNEL1x5(in2, x, &kernel[0], 1);
|
||||||
|
ss += KERNEL1x5(in1, x, &kernel[5], 1);
|
||||||
|
ss += KERNEL1x5(in0, x, &kernel[10], 1);
|
||||||
|
ss += KERNEL1x5(in_1, x, &kernel[15], 1);
|
||||||
|
ss += KERNEL1x5(in_2, x, &kernel[20], 1);
|
||||||
|
out[x] = clip8(ss);
|
||||||
|
}
|
||||||
|
out[x + 0] = in0[x + 0];
|
||||||
|
out[x + 1] = in0[x + 1];
|
||||||
}
|
}
|
||||||
out[x + 0] = in0[x + 0];
|
|
||||||
out[x + 1] = in0[x + 1];
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Add one time for rounding
|
// Add one time for rounding
|
||||||
|
@ -327,7 +382,7 @@ ImagingFilter(Imaging im, int xsize, int ysize, const FLOAT32 *kernel, FLOAT32 o
|
||||||
Imaging imOut;
|
Imaging imOut;
|
||||||
ImagingSectionCookie cookie;
|
ImagingSectionCookie cookie;
|
||||||
|
|
||||||
if (!im || im->type != IMAGING_TYPE_UINT8) {
|
if (im->type != IMAGING_TYPE_UINT8 && im->type != IMAGING_TYPE_INT32) {
|
||||||
return (Imaging)ImagingError_ModeError();
|
return (Imaging)ImagingError_ModeError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,12 +58,6 @@
|
||||||
#error Cannot find required 32-bit integer type
|
#error Cannot find required 32-bit integer type
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if SIZEOF_LONG == 8
|
|
||||||
#define INT64 long
|
|
||||||
#elif SIZEOF_LONG_LONG == 8
|
|
||||||
#define INT64 long
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define INT8 signed char
|
#define INT8 signed char
|
||||||
#define UINT8 unsigned char
|
#define UINT8 unsigned char
|
||||||
|
|
||||||
|
|
|
@ -290,7 +290,7 @@ ImagingConvertTransparent(Imaging im, const char *mode, int r, int g, int b);
|
||||||
extern Imaging
|
extern Imaging
|
||||||
ImagingCrop(Imaging im, int x0, int y0, int x1, int y1);
|
ImagingCrop(Imaging im, int x0, int y0, int x1, int y1);
|
||||||
extern Imaging
|
extern Imaging
|
||||||
ImagingExpand(Imaging im, int x, int y, int mode);
|
ImagingExpand(Imaging im, int x, int y);
|
||||||
extern Imaging
|
extern Imaging
|
||||||
ImagingFill(Imaging im, const void *ink);
|
ImagingFill(Imaging im, const void *ink);
|
||||||
extern int
|
extern int
|
||||||
|
|
|
@ -464,7 +464,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!context->num_resolutions) {
|
if (!context->num_resolutions) {
|
||||||
while (tile_width < (1 << (params.numresolution - 1U)) || tile_height < (1 << (params.numresolution - 1U))) {
|
while (tile_width < (1U << (params.numresolution - 1U)) || tile_height < (1U << (params.numresolution - 1U))) {
|
||||||
params.numresolution -= 1;
|
params.numresolution -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,8 +37,6 @@
|
||||||
#include "Imaging.h"
|
#include "Imaging.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
int ImagingNewCount = 0;
|
|
||||||
|
|
||||||
/* --------------------------------------------------------------------
|
/* --------------------------------------------------------------------
|
||||||
* Standard image object.
|
* Standard image object.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1149,6 +1149,16 @@ unpackI16N_I16(UINT8 *out, const UINT8 *in, int pixels) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static void
|
static void
|
||||||
|
unpackI16B_I16(UINT8 *out, const UINT8 *in, int pixels) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < pixels; i++) {
|
||||||
|
out[0] = in[1];
|
||||||
|
out[1] = in[0];
|
||||||
|
in += 2;
|
||||||
|
out += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static void
|
||||||
unpackI16R_I16(UINT8 *out, const UINT8 *in, int pixels) {
|
unpackI16R_I16(UINT8 *out, const UINT8 *in, int pixels) {
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < pixels; i++) {
|
for (i = 0; i < pixels; i++) {
|
||||||
|
@ -1542,10 +1552,12 @@ static struct {
|
||||||
{"P", "P;4L", 4, unpackP4L},
|
{"P", "P;4L", 4, unpackP4L},
|
||||||
{"P", "P", 8, copy1},
|
{"P", "P", 8, copy1},
|
||||||
{"P", "P;R", 8, unpackLR},
|
{"P", "P;R", 8, unpackLR},
|
||||||
|
{"P", "L", 8, copy1},
|
||||||
|
|
||||||
/* palette w. alpha */
|
/* palette w. alpha */
|
||||||
{"PA", "PA", 16, unpackLA},
|
{"PA", "PA", 16, unpackLA},
|
||||||
{"PA", "PA;L", 16, unpackLAL},
|
{"PA", "PA;L", 16, unpackLAL},
|
||||||
|
{"PA", "LA", 16, unpackLA},
|
||||||
|
|
||||||
/* true colour */
|
/* true colour */
|
||||||
{"RGB", "RGB", 24, ImagingUnpackRGB},
|
{"RGB", "RGB", 24, ImagingUnpackRGB},
|
||||||
|
@ -1764,6 +1776,7 @@ static struct {
|
||||||
{"I;16L", "I;16L", 16, copy2},
|
{"I;16L", "I;16L", 16, copy2},
|
||||||
{"I;16N", "I;16N", 16, copy2},
|
{"I;16N", "I;16N", 16, copy2},
|
||||||
|
|
||||||
|
{"I;16", "I;16B", 16, unpackI16B_I16},
|
||||||
{"I;16", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian.
|
{"I;16", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian.
|
||||||
{"I;16L", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian.
|
{"I;16L", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian.
|
||||||
{"I;16B", "I;16N", 16, unpackI16N_I16B},
|
{"I;16B", "I;16N", 16, unpackI16N_I16B},
|
||||||
|
|
|
@ -152,9 +152,9 @@ deps = {
|
||||||
"libs": [r"*.lib"],
|
"libs": [r"*.lib"],
|
||||||
},
|
},
|
||||||
"xz": {
|
"xz": {
|
||||||
"url": SF_PROJECTS + "/lzmautils/files/xz-5.4.2.tar.gz/download",
|
"url": SF_PROJECTS + "/lzmautils/files/xz-5.4.3.tar.gz/download",
|
||||||
"filename": "xz-5.4.2.tar.gz",
|
"filename": "xz-5.4.3.tar.gz",
|
||||||
"dir": "xz-5.4.2",
|
"dir": "xz-5.4.3",
|
||||||
"license": "COPYING",
|
"license": "COPYING",
|
||||||
"build": [
|
"build": [
|
||||||
*cmds_cmake("liblzma", "-DBUILD_SHARED_LIBS:BOOL=OFF"),
|
*cmds_cmake("liblzma", "-DBUILD_SHARED_LIBS:BOOL=OFF"),
|
||||||
|
@ -337,9 +337,9 @@ deps = {
|
||||||
"libs": [r"imagequant.lib"],
|
"libs": [r"imagequant.lib"],
|
||||||
},
|
},
|
||||||
"harfbuzz": {
|
"harfbuzz": {
|
||||||
"url": "https://github.com/harfbuzz/harfbuzz/archive/7.2.0.zip",
|
"url": "https://github.com/harfbuzz/harfbuzz/archive/7.3.0.zip",
|
||||||
"filename": "harfbuzz-7.2.0.zip",
|
"filename": "harfbuzz-7.3.0.zip",
|
||||||
"dir": "harfbuzz-7.2.0",
|
"dir": "harfbuzz-7.3.0",
|
||||||
"license": "COPYING",
|
"license": "COPYING",
|
||||||
"build": [
|
"build": [
|
||||||
*cmds_cmake(
|
*cmds_cmake(
|
||||||
|
@ -352,12 +352,12 @@ deps = {
|
||||||
"libs": [r"*.lib"],
|
"libs": [r"*.lib"],
|
||||||
},
|
},
|
||||||
"fribidi": {
|
"fribidi": {
|
||||||
"url": "https://github.com/fribidi/fribidi/archive/v1.0.12.zip",
|
"url": "https://github.com/fribidi/fribidi/archive/v1.0.13.zip",
|
||||||
"filename": "fribidi-1.0.12.zip",
|
"filename": "fribidi-1.0.13.zip",
|
||||||
"dir": "fribidi-1.0.12",
|
"dir": "fribidi-1.0.13",
|
||||||
"license": "COPYING",
|
"license": "COPYING",
|
||||||
"build": [
|
"build": [
|
||||||
cmd_copy(r"COPYING", r"{bin_dir}\fribidi-1.0.12-COPYING"),
|
cmd_copy(r"COPYING", r"{bin_dir}\fribidi-1.0.13-COPYING"),
|
||||||
cmd_copy(r"{winbuild_dir}\fribidi.cmake", r"CMakeLists.txt"),
|
cmd_copy(r"{winbuild_dir}\fribidi.cmake", r"CMakeLists.txt"),
|
||||||
*cmds_cmake("fribidi"),
|
*cmds_cmake("fribidi"),
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in New Issue
Block a user