2019-12-26 05:10:39 +03:00
|
|
|
import io
|
2019-07-06 23:40:53 +03:00
|
|
|
import os
|
|
|
|
import shutil
|
2019-10-07 16:28:36 +03:00
|
|
|
import tempfile
|
2012-10-16 00:26:38 +04:00
|
|
|
|
2020-02-03 12:11:32 +03:00
|
|
|
import pytest
|
2020-08-07 13:28:33 +03:00
|
|
|
|
|
|
|
import PIL
|
2020-06-21 15:17:18 +03:00
|
|
|
from PIL import Image, ImageDraw, ImagePalette, ImageShow, UnidentifiedImageError
|
2019-07-06 23:40:53 +03:00
|
|
|
|
2020-01-30 17:56:07 +03:00
|
|
|
from .helper import (
|
|
|
|
assert_image_equal,
|
|
|
|
assert_image_similar,
|
|
|
|
assert_not_all_same,
|
|
|
|
hopper,
|
|
|
|
is_win32,
|
2020-07-02 13:28:00 +03:00
|
|
|
skip_unless_feature,
|
2020-01-30 17:56:07 +03:00
|
|
|
)
|
2012-10-16 00:26:38 +04:00
|
|
|
|
|
|
|
|
2020-02-23 00:03:01 +03:00
|
|
|
class TestImage:
|
2017-08-05 21:58:31 +03:00
|
|
|
def test_image_modes_success(self):
|
|
|
|
for mode in [
|
2019-06-13 18:54:24 +03:00
|
|
|
"1",
|
|
|
|
"P",
|
|
|
|
"PA",
|
|
|
|
"L",
|
|
|
|
"LA",
|
|
|
|
"La",
|
|
|
|
"F",
|
|
|
|
"I",
|
|
|
|
"I;16",
|
|
|
|
"I;16L",
|
|
|
|
"I;16B",
|
|
|
|
"I;16N",
|
|
|
|
"RGB",
|
|
|
|
"RGBX",
|
|
|
|
"RGBA",
|
|
|
|
"RGBa",
|
|
|
|
"CMYK",
|
|
|
|
"YCbCr",
|
|
|
|
"LAB",
|
|
|
|
"HSV",
|
2017-08-05 21:58:31 +03:00
|
|
|
]:
|
|
|
|
Image.new(mode, (1, 1))
|
|
|
|
|
|
|
|
def test_image_modes_fail(self):
|
|
|
|
for mode in [
|
2019-06-13 18:54:24 +03:00
|
|
|
"",
|
|
|
|
"bad",
|
|
|
|
"very very long",
|
|
|
|
"BGR;15",
|
|
|
|
"BGR;16",
|
|
|
|
"BGR;24",
|
|
|
|
"BGR;32",
|
2017-08-05 21:58:31 +03:00
|
|
|
]:
|
2020-02-22 16:06:21 +03:00
|
|
|
with pytest.raises(ValueError) as e:
|
2017-12-19 16:12:02 +03:00
|
|
|
Image.new(mode, (1, 1))
|
2020-02-22 16:06:21 +03:00
|
|
|
assert str(e.value) == "unrecognized image mode"
|
2017-08-05 21:58:31 +03:00
|
|
|
|
2019-11-19 13:20:02 +03:00
|
|
|
def test_exception_inheritance(self):
|
2020-04-07 09:58:21 +03:00
|
|
|
assert issubclass(UnidentifiedImageError, OSError)
|
2019-11-19 13:20:02 +03:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
def test_sanity(self):
|
2012-10-16 00:26:38 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
im = Image.new("L", (100, 100))
|
2020-02-22 16:06:21 +03:00
|
|
|
assert repr(im)[:45] == "<PIL.Image.Image image mode=L size=100x100 at"
|
|
|
|
assert im.mode == "L"
|
|
|
|
assert im.size == (100, 100)
|
2012-10-16 00:26:38 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
im = Image.new("RGB", (100, 100))
|
2020-02-22 16:06:21 +03:00
|
|
|
assert repr(im)[:45] == "<PIL.Image.Image image mode=RGB size=100x100 "
|
|
|
|
assert im.mode == "RGB"
|
|
|
|
assert im.size == (100, 100)
|
2012-10-16 00:26:38 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
Image.new("L", (100, 100), None)
|
|
|
|
im2 = Image.new("L", (100, 100), 0)
|
|
|
|
im3 = Image.new("L", (100, 100), "black")
|
2012-10-16 00:26:38 +04:00
|
|
|
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im2.getcolors() == [(10000, 0)]
|
|
|
|
assert im3.getcolors() == [(10000, 0)]
|
2012-10-16 00:26:38 +04:00
|
|
|
|
2020-02-22 16:06:21 +03:00
|
|
|
with pytest.raises(ValueError):
|
|
|
|
Image.new("X", (100, 100))
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
Image.new("", (100, 100))
|
|
|
|
# with pytest.raises(MemoryError):
|
|
|
|
# Image.new("L", (1000000, 1000000))
|
2013-07-01 02:42:19 +04:00
|
|
|
|
2015-06-24 03:35:37 +03:00
|
|
|
def test_width_height(self):
|
|
|
|
im = Image.new("RGB", (1, 2))
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.width == 1
|
|
|
|
assert im.height == 2
|
2015-06-24 03:35:37 +03:00
|
|
|
|
2020-02-22 16:06:21 +03:00
|
|
|
with pytest.raises(AttributeError):
|
2018-09-30 05:58:02 +03:00
|
|
|
im.size = (3, 4)
|
2015-06-24 03:35:37 +03:00
|
|
|
|
2015-06-09 07:36:34 +03:00
|
|
|
def test_invalid_image(self):
|
2019-09-26 15:12:28 +03:00
|
|
|
import io
|
2019-06-13 18:54:24 +03:00
|
|
|
|
2019-09-26 15:12:28 +03:00
|
|
|
im = io.BytesIO(b"")
|
2020-02-22 16:06:21 +03:00
|
|
|
with pytest.raises(UnidentifiedImageError):
|
|
|
|
Image.open(im)
|
2015-06-09 07:36:34 +03:00
|
|
|
|
2017-09-01 13:36:51 +03:00
|
|
|
def test_bad_mode(self):
|
2020-02-22 16:06:21 +03:00
|
|
|
with pytest.raises(ValueError):
|
|
|
|
Image.open("filename", "bad mode")
|
2015-06-09 07:36:34 +03:00
|
|
|
|
2019-12-26 05:10:39 +03:00
|
|
|
def test_stringio(self):
|
2020-02-22 16:06:21 +03:00
|
|
|
with pytest.raises(ValueError):
|
|
|
|
Image.open(io.StringIO())
|
2019-12-26 05:10:39 +03:00
|
|
|
|
2020-02-23 00:03:01 +03:00
|
|
|
def test_pathlib(self, tmp_path):
|
2016-12-17 20:50:50 +03:00
|
|
|
from PIL.Image import Path
|
2019-06-13 18:54:24 +03:00
|
|
|
|
Improve handling of file resources
Follow Python's file object semantics. User code is responsible for
closing resources (usually through a context manager) in a deterministic
way.
To achieve this, remove __del__ functions. These functions used to
closed open file handlers in an attempt to silence Python
ResourceWarnings. However, using __del__ has the following drawbacks:
- __del__ isn't called until the object's reference count reaches 0.
Therefore, resource handlers remain open or in use longer than
necessary.
- The __del__ method isn't guaranteed to execute on system exit. See the
Python documentation:
https://docs.python.org/3/reference/datamodel.html#object.__del__
> It is not guaranteed that __del__() methods are called for objects
> that still exist when the interpreter exits.
- Exceptions that occur inside __del__ are ignored instead of raised.
This has the potential of hiding bugs. This is also in the Python
documentation:
> Warning: Due to the precarious circumstances under which __del__()
> methods are invoked, exceptions that occur during their execution
> are ignored, and a warning is printed to sys.stderr instead.
Instead, always close resource handlers when they are no longer in use.
This will close the file handler at a specified point in the user's code
and not wait until the interpreter chooses to. It is always guaranteed
to run. And, if an exception occurs while closing the file handler, the
bug will not be ignored.
Now, when code receives a ResourceWarning, it will highlight an area
that is mishandling resources. It should not simply be silenced, but
fixed by closing resources with a context manager.
All warnings that were emitted during tests have been cleaned up. To
enable warnings, I passed the `-Wa` CLI option to Python. This exposed
some mishandling of resources in ImageFile.__init__() and
SpiderImagePlugin.loadImageSeries(), they too were fixed.
2019-05-25 19:30:58 +03:00
|
|
|
with Image.open(Path("Tests/images/multipage-mmap.tiff")) as im:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.mode == "P"
|
|
|
|
assert im.size == (10, 10)
|
2019-05-04 08:00:49 +03:00
|
|
|
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(Path("Tests/images/hopper.jpg")) as im:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.mode == "RGB"
|
|
|
|
assert im.size == (128, 128)
|
2015-08-05 14:29:24 +03:00
|
|
|
|
2020-02-23 00:03:01 +03:00
|
|
|
temp_file = str(tmp_path / "temp.jpg")
|
2019-11-25 23:03:23 +03:00
|
|
|
if os.path.exists(temp_file):
|
|
|
|
os.remove(temp_file)
|
|
|
|
im.save(Path(temp_file))
|
2016-02-27 05:10:50 +03:00
|
|
|
|
2020-02-23 00:03:01 +03:00
|
|
|
def test_fp_name(self, tmp_path):
|
|
|
|
temp_file = str(tmp_path / "temp.jpg")
|
2016-11-15 12:13:25 +03:00
|
|
|
|
2019-09-30 17:56:31 +03:00
|
|
|
class FP:
|
2016-11-15 12:13:25 +03:00
|
|
|
def write(a, b):
|
|
|
|
pass
|
2019-06-13 18:54:24 +03:00
|
|
|
|
2016-11-15 12:13:25 +03:00
|
|
|
fp = FP()
|
|
|
|
fp.name = temp_file
|
|
|
|
|
|
|
|
im = hopper()
|
|
|
|
im.save(fp)
|
|
|
|
|
2015-10-03 10:12:44 +03:00
|
|
|
def test_tempfile(self):
|
|
|
|
# see #1460, pathlib support breaks tempfile.TemporaryFile on py27
|
|
|
|
# Will error out on save on 3.0.0
|
|
|
|
im = hopper()
|
2016-12-28 01:54:10 +03:00
|
|
|
with tempfile.TemporaryFile() as fp:
|
2019-06-13 18:54:24 +03:00
|
|
|
im.save(fp, "JPEG")
|
2016-12-28 01:54:10 +03:00
|
|
|
fp.seek(0)
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(fp) as reloaded:
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_similar(im, reloaded, 20)
|
2015-10-03 10:12:44 +03:00
|
|
|
|
2020-02-23 00:03:01 +03:00
|
|
|
def test_unknown_extension(self, tmp_path):
|
2017-02-17 16:39:16 +03:00
|
|
|
im = hopper()
|
2020-02-23 00:03:01 +03:00
|
|
|
temp_file = str(tmp_path / "temp.unknown")
|
2020-02-22 16:06:21 +03:00
|
|
|
with pytest.raises(ValueError):
|
|
|
|
im.save(temp_file)
|
2017-02-17 16:39:16 +03:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
def test_internals(self):
|
|
|
|
im = Image.new("L", (100, 100))
|
|
|
|
im.readonly = 1
|
|
|
|
im._copy()
|
2020-02-22 16:06:21 +03:00
|
|
|
assert not im.readonly
|
2014-06-10 13:10:47 +04:00
|
|
|
|
|
|
|
im.readonly = 1
|
|
|
|
im.paste(0, (0, 0, 100, 100))
|
2020-02-22 16:06:21 +03:00
|
|
|
assert not im.readonly
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2020-02-23 00:03:01 +03:00
|
|
|
@pytest.mark.skipif(is_win32(), reason="Test requires opening tempfile twice")
|
|
|
|
def test_readonly_save(self, tmp_path):
|
|
|
|
temp_file = str(tmp_path / "temp.bmp")
|
2019-03-17 15:37:40 +03:00
|
|
|
shutil.copy("Tests/images/rgb32bf-rgba.bmp", temp_file)
|
|
|
|
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(temp_file) as im:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.readonly
|
2019-11-25 23:03:23 +03:00
|
|
|
im.save(temp_file)
|
2019-03-17 15:37:40 +03:00
|
|
|
|
2020-02-23 00:03:01 +03:00
|
|
|
def test_dump(self, tmp_path):
|
2018-04-01 07:28:37 +03:00
|
|
|
im = Image.new("L", (10, 10))
|
2020-02-23 00:03:01 +03:00
|
|
|
im._dump(str(tmp_path / "temp_L.ppm"))
|
2018-04-01 07:28:37 +03:00
|
|
|
|
|
|
|
im = Image.new("RGB", (10, 10))
|
2020-02-23 00:03:01 +03:00
|
|
|
im._dump(str(tmp_path / "temp_RGB.ppm"))
|
2018-04-01 07:28:37 +03:00
|
|
|
|
|
|
|
im = Image.new("HSV", (10, 10))
|
2020-02-22 16:06:21 +03:00
|
|
|
with pytest.raises(ValueError):
|
2020-02-23 00:03:01 +03:00
|
|
|
im._dump(str(tmp_path / "temp_HSV.ppm"))
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2014-07-05 17:29:40 +04:00
|
|
|
def test_comparison_with_other_type(self):
|
|
|
|
# Arrange
|
2019-06-13 18:54:24 +03:00
|
|
|
item = Image.new("RGB", (25, 25), "#000")
|
2014-07-05 17:29:40 +04:00
|
|
|
num = 12
|
|
|
|
|
|
|
|
# Act/Assert
|
|
|
|
# Shouldn't cause AttributeError (#774)
|
2020-02-22 16:06:21 +03:00
|
|
|
assert item is not None
|
|
|
|
assert item != num
|
2014-07-05 17:29:40 +04:00
|
|
|
|
2014-07-10 03:00:26 +04:00
|
|
|
def test_expand_x(self):
|
|
|
|
# Arrange
|
2014-09-04 09:03:55 +04:00
|
|
|
im = hopper()
|
2014-07-10 03:00:26 +04:00
|
|
|
orig_size = im.size
|
|
|
|
xmargin = 5
|
|
|
|
|
|
|
|
# Act
|
|
|
|
im = im._expand(xmargin)
|
|
|
|
|
|
|
|
# Assert
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.size[0] == orig_size[0] + 2 * xmargin
|
|
|
|
assert im.size[1] == orig_size[1] + 2 * xmargin
|
2014-07-10 03:00:26 +04:00
|
|
|
|
|
|
|
def test_expand_xy(self):
|
|
|
|
# Arrange
|
2014-09-04 09:03:55 +04:00
|
|
|
im = hopper()
|
2014-07-10 03:00:26 +04:00
|
|
|
orig_size = im.size
|
|
|
|
xmargin = 5
|
|
|
|
ymargin = 3
|
|
|
|
|
|
|
|
# Act
|
|
|
|
im = im._expand(xmargin, ymargin)
|
|
|
|
|
|
|
|
# Assert
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.size[0] == orig_size[0] + 2 * xmargin
|
|
|
|
assert im.size[1] == orig_size[1] + 2 * ymargin
|
2014-07-10 03:00:26 +04:00
|
|
|
|
|
|
|
def test_getbands(self):
|
2017-08-12 14:10:39 +03:00
|
|
|
# Assert
|
2020-02-22 16:06:21 +03:00
|
|
|
assert hopper("RGB").getbands() == ("R", "G", "B")
|
|
|
|
assert hopper("YCbCr").getbands() == ("Y", "Cb", "Cr")
|
2014-07-10 03:00:26 +04:00
|
|
|
|
2017-08-12 14:10:39 +03:00
|
|
|
def test_getchannel_wrong_params(self):
|
|
|
|
im = hopper()
|
2014-07-10 03:00:26 +04:00
|
|
|
|
2020-02-22 16:06:21 +03:00
|
|
|
with pytest.raises(ValueError):
|
|
|
|
im.getchannel(-1)
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
im.getchannel(3)
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
im.getchannel("Z")
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
im.getchannel("1")
|
2017-08-12 14:10:39 +03:00
|
|
|
|
|
|
|
def test_getchannel(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
im = hopper("YCbCr")
|
2017-08-12 14:10:39 +03:00
|
|
|
Y, Cb, Cr = im.split()
|
|
|
|
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_equal(Y, im.getchannel(0))
|
|
|
|
assert_image_equal(Y, im.getchannel("Y"))
|
|
|
|
assert_image_equal(Cb, im.getchannel(1))
|
|
|
|
assert_image_equal(Cb, im.getchannel("Cb"))
|
|
|
|
assert_image_equal(Cr, im.getchannel(2))
|
|
|
|
assert_image_equal(Cr, im.getchannel("Cr"))
|
2014-07-10 03:00:26 +04:00
|
|
|
|
|
|
|
def test_getbbox(self):
|
|
|
|
# Arrange
|
2014-09-04 09:03:55 +04:00
|
|
|
im = hopper()
|
2014-07-10 03:00:26 +04:00
|
|
|
|
|
|
|
# Act
|
|
|
|
bbox = im.getbbox()
|
|
|
|
|
|
|
|
# Assert
|
2020-02-22 16:06:21 +03:00
|
|
|
assert bbox == (0, 0, 128, 128)
|
2014-07-10 03:00:26 +04:00
|
|
|
|
2014-07-15 01:42:31 +04:00
|
|
|
def test_ne(self):
|
|
|
|
# Arrange
|
2019-06-13 18:54:24 +03:00
|
|
|
im1 = Image.new("RGB", (25, 25), "black")
|
|
|
|
im2 = Image.new("RGB", (25, 25), "white")
|
2014-07-15 01:42:31 +04:00
|
|
|
|
|
|
|
# Act / Assert
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im1 != im2
|
2014-07-15 01:42:31 +04:00
|
|
|
|
|
|
|
def test_alpha_composite(self):
|
2017-02-14 12:27:02 +03:00
|
|
|
# https://stackoverflow.com/questions/3374878
|
2014-07-15 01:42:31 +04:00
|
|
|
# Arrange
|
2019-06-13 18:54:24 +03:00
|
|
|
expected_colors = sorted(
|
|
|
|
[
|
|
|
|
(1122, (128, 127, 0, 255)),
|
|
|
|
(1089, (0, 255, 0, 255)),
|
|
|
|
(3300, (255, 0, 0, 255)),
|
|
|
|
(1156, (170, 85, 0, 192)),
|
|
|
|
(1122, (0, 255, 0, 128)),
|
|
|
|
(1122, (255, 0, 0, 128)),
|
|
|
|
(1089, (0, 255, 0, 0)),
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
dst = Image.new("RGBA", size=(100, 100), color=(0, 255, 0, 255))
|
2014-07-15 01:42:31 +04:00
|
|
|
draw = ImageDraw.Draw(dst)
|
|
|
|
draw.rectangle((0, 33, 100, 66), fill=(0, 255, 0, 128))
|
|
|
|
draw.rectangle((0, 67, 100, 100), fill=(0, 255, 0, 0))
|
2019-06-13 18:54:24 +03:00
|
|
|
src = Image.new("RGBA", size=(100, 100), color=(255, 0, 0, 255))
|
2014-07-15 01:42:31 +04:00
|
|
|
draw = ImageDraw.Draw(src)
|
|
|
|
draw.rectangle((33, 0, 66, 100), fill=(255, 0, 0, 128))
|
|
|
|
draw.rectangle((67, 0, 100, 100), fill=(255, 0, 0, 0))
|
|
|
|
|
|
|
|
# Act
|
|
|
|
img = Image.alpha_composite(dst, src)
|
|
|
|
|
|
|
|
# Assert
|
|
|
|
img_colors = sorted(img.getcolors())
|
2020-02-22 16:06:21 +03:00
|
|
|
assert img_colors == expected_colors
|
2014-07-15 01:42:31 +04:00
|
|
|
|
2017-06-20 19:54:59 +03:00
|
|
|
def test_alpha_inplace(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
src = Image.new("RGBA", (128, 128), "blue")
|
2017-06-20 19:54:59 +03:00
|
|
|
|
2019-06-13 18:54:24 +03:00
|
|
|
over = Image.new("RGBA", (128, 128), "red")
|
|
|
|
mask = hopper("L")
|
2017-06-20 19:54:59 +03:00
|
|
|
over.putalpha(mask)
|
|
|
|
|
|
|
|
target = Image.alpha_composite(src, over)
|
|
|
|
|
|
|
|
# basic
|
|
|
|
full = src.copy()
|
|
|
|
full.alpha_composite(over)
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_equal(full, target)
|
2017-06-20 19:54:59 +03:00
|
|
|
|
|
|
|
# with offset down to right
|
|
|
|
offset = src.copy()
|
|
|
|
offset.alpha_composite(over, (64, 64))
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_equal(offset.crop((64, 64, 127, 127)), target.crop((0, 0, 63, 63)))
|
2020-02-22 16:06:21 +03:00
|
|
|
assert offset.size == (128, 128)
|
2017-06-20 19:54:59 +03:00
|
|
|
|
|
|
|
# offset and crop
|
|
|
|
box = src.copy()
|
2017-06-28 00:03:38 +03:00
|
|
|
box.alpha_composite(over, (64, 64), (0, 0, 32, 32))
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_equal(box.crop((64, 64, 96, 96)), target.crop((0, 0, 32, 32)))
|
|
|
|
assert_image_equal(box.crop((96, 96, 128, 128)), src.crop((0, 0, 32, 32)))
|
2020-02-22 16:06:21 +03:00
|
|
|
assert box.size == (128, 128)
|
2017-06-26 22:04:44 +03:00
|
|
|
|
|
|
|
# source point
|
|
|
|
source = src.copy()
|
2017-06-28 00:03:38 +03:00
|
|
|
source.alpha_composite(over, (32, 32), (32, 32, 96, 96))
|
2017-06-26 22:04:44 +03:00
|
|
|
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_equal(source.crop((32, 32, 96, 96)), target.crop((32, 32, 96, 96)))
|
2020-02-22 16:06:21 +03:00
|
|
|
assert source.size == (128, 128)
|
2017-06-20 19:54:59 +03:00
|
|
|
|
2017-09-01 13:36:51 +03:00
|
|
|
# errors
|
2020-02-22 16:06:21 +03:00
|
|
|
with pytest.raises(ValueError):
|
|
|
|
source.alpha_composite(over, "invalid source")
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
source.alpha_composite(over, (0, 0), "invalid destination")
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
source.alpha_composite(over, 0)
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
source.alpha_composite(over, (0, 0), 0)
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
source.alpha_composite(over, (0, -1))
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
source.alpha_composite(over, (0, 0), (0, -1))
|
2017-09-01 13:36:51 +03:00
|
|
|
|
2016-06-20 18:36:26 +03:00
|
|
|
def test_registered_extensions_uninitialized(self):
|
2017-01-07 05:20:16 +03:00
|
|
|
# Arrange
|
|
|
|
Image._initialized = 0
|
|
|
|
extension = Image.EXTENSION
|
|
|
|
Image.EXTENSION = {}
|
|
|
|
|
2016-06-20 18:36:26 +03:00
|
|
|
# Act
|
2017-01-07 05:20:16 +03:00
|
|
|
Image.registered_extensions()
|
2016-06-20 18:36:26 +03:00
|
|
|
|
|
|
|
# Assert
|
2020-02-22 16:06:21 +03:00
|
|
|
assert Image._initialized == 2
|
2017-01-07 05:20:16 +03:00
|
|
|
|
|
|
|
# Restore the original state and assert
|
|
|
|
Image.EXTENSION = extension
|
2020-02-22 16:06:21 +03:00
|
|
|
assert Image.EXTENSION
|
2016-06-20 18:36:26 +03:00
|
|
|
|
|
|
|
def test_registered_extensions(self):
|
|
|
|
# Arrange
|
|
|
|
# Open an image to trigger plugin registration
|
Improve handling of file resources
Follow Python's file object semantics. User code is responsible for
closing resources (usually through a context manager) in a deterministic
way.
To achieve this, remove __del__ functions. These functions used to
closed open file handlers in an attempt to silence Python
ResourceWarnings. However, using __del__ has the following drawbacks:
- __del__ isn't called until the object's reference count reaches 0.
Therefore, resource handlers remain open or in use longer than
necessary.
- The __del__ method isn't guaranteed to execute on system exit. See the
Python documentation:
https://docs.python.org/3/reference/datamodel.html#object.__del__
> It is not guaranteed that __del__() methods are called for objects
> that still exist when the interpreter exits.
- Exceptions that occur inside __del__ are ignored instead of raised.
This has the potential of hiding bugs. This is also in the Python
documentation:
> Warning: Due to the precarious circumstances under which __del__()
> methods are invoked, exceptions that occur during their execution
> are ignored, and a warning is printed to sys.stderr instead.
Instead, always close resource handlers when they are no longer in use.
This will close the file handler at a specified point in the user's code
and not wait until the interpreter chooses to. It is always guaranteed
to run. And, if an exception occurs while closing the file handler, the
bug will not be ignored.
Now, when code receives a ResourceWarning, it will highlight an area
that is mishandling resources. It should not simply be silenced, but
fixed by closing resources with a context manager.
All warnings that were emitted during tests have been cleaned up. To
enable warnings, I passed the `-Wa` CLI option to Python. This exposed
some mishandling of resources in ImageFile.__init__() and
SpiderImagePlugin.loadImageSeries(), they too were fixed.
2019-05-25 19:30:58 +03:00
|
|
|
with Image.open("Tests/images/rgb.jpg"):
|
|
|
|
pass
|
2016-06-20 18:36:26 +03:00
|
|
|
|
|
|
|
# Act
|
2017-01-07 05:20:16 +03:00
|
|
|
extensions = Image.registered_extensions()
|
2016-06-20 18:36:26 +03:00
|
|
|
|
|
|
|
# Assert
|
2020-02-22 16:06:21 +03:00
|
|
|
assert extensions
|
2019-06-13 18:54:24 +03:00
|
|
|
for ext in [".cur", ".icns", ".tif", ".tiff"]:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert ext in extensions
|
2016-06-20 18:36:26 +03:00
|
|
|
|
2014-09-02 16:14:00 +04:00
|
|
|
def test_effect_mandelbrot(self):
|
|
|
|
# Arrange
|
|
|
|
size = (512, 512)
|
|
|
|
extent = (-3, -2.5, 2, 2.5)
|
|
|
|
quality = 100
|
|
|
|
|
|
|
|
# Act
|
|
|
|
im = Image.effect_mandelbrot(size, extent, quality)
|
|
|
|
|
|
|
|
# Assert
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.size == (512, 512)
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open("Tests/images/effect_mandelbrot.png") as im2:
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_equal(im, im2)
|
2014-09-02 16:14:00 +04:00
|
|
|
|
2014-09-02 17:39:35 +04:00
|
|
|
def test_effect_mandelbrot_bad_arguments(self):
|
|
|
|
# Arrange
|
|
|
|
size = (512, 512)
|
|
|
|
# Get coordinates the wrong way round:
|
|
|
|
extent = (+3, +2.5, -2, -2.5)
|
|
|
|
# Quality < 2:
|
|
|
|
quality = 1
|
|
|
|
|
|
|
|
# Act/Assert
|
2020-02-22 16:06:21 +03:00
|
|
|
with pytest.raises(ValueError):
|
|
|
|
Image.effect_mandelbrot(size, extent, quality)
|
2014-09-02 17:39:35 +04:00
|
|
|
|
2014-09-02 15:45:54 +04:00
|
|
|
def test_effect_noise(self):
|
2014-09-02 15:11:08 +04:00
|
|
|
# Arrange
|
|
|
|
size = (100, 100)
|
|
|
|
sigma = 128
|
|
|
|
|
|
|
|
# Act
|
2017-12-19 18:12:58 +03:00
|
|
|
im = Image.effect_noise(size, sigma)
|
|
|
|
|
|
|
|
# Assert
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.size == (100, 100)
|
|
|
|
assert im.mode == "L"
|
2017-12-19 18:12:58 +03:00
|
|
|
p0 = im.getpixel((0, 0))
|
|
|
|
p1 = im.getpixel((0, 1))
|
|
|
|
p2 = im.getpixel((0, 2))
|
|
|
|
p3 = im.getpixel((0, 3))
|
|
|
|
p4 = im.getpixel((0, 4))
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_not_all_same([p0, p1, p2, p3, p4])
|
2014-09-02 15:11:08 +04:00
|
|
|
|
2014-09-02 16:53:58 +04:00
|
|
|
def test_effect_spread(self):
|
|
|
|
# Arrange
|
2014-09-04 09:03:55 +04:00
|
|
|
im = hopper()
|
2014-09-02 16:53:58 +04:00
|
|
|
distance = 10
|
|
|
|
|
|
|
|
# Act
|
|
|
|
im2 = im.effect_spread(distance)
|
|
|
|
|
|
|
|
# Assert
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.size == (128, 128)
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open("Tests/images/effect_spread.png") as im3:
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_similar(im2, im3, 110)
|
2014-09-02 16:53:58 +04:00
|
|
|
|
2016-10-03 13:38:15 +03:00
|
|
|
def test_check_size(self):
|
2020-02-23 00:03:01 +03:00
|
|
|
# Checking that the _check_size function throws value errors when we want it to
|
2020-02-22 16:06:21 +03:00
|
|
|
with pytest.raises(ValueError):
|
2019-06-13 18:54:24 +03:00
|
|
|
Image.new("RGB", 0) # not a tuple
|
2020-02-22 16:06:21 +03:00
|
|
|
with pytest.raises(ValueError):
|
2019-06-13 18:54:24 +03:00
|
|
|
Image.new("RGB", (0,)) # Tuple too short
|
2020-02-22 16:06:21 +03:00
|
|
|
with pytest.raises(ValueError):
|
2019-06-13 18:54:24 +03:00
|
|
|
Image.new("RGB", (-1, -1)) # w,h < 0
|
2016-11-29 22:25:49 +03:00
|
|
|
|
|
|
|
# this should pass with 0 sized images, #2259
|
2019-06-13 18:54:24 +03:00
|
|
|
im = Image.new("L", (0, 0))
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.size == (0, 0)
|
2016-10-03 13:38:15 +03:00
|
|
|
|
2019-06-13 18:54:24 +03:00
|
|
|
im = Image.new("L", (0, 100))
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.size == (0, 100)
|
2017-09-17 02:58:01 +03:00
|
|
|
|
2019-06-13 18:54:24 +03:00
|
|
|
im = Image.new("L", (100, 0))
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.size == (100, 0)
|
2017-09-17 02:58:01 +03:00
|
|
|
|
2020-02-22 16:06:21 +03:00
|
|
|
assert Image.new("RGB", (1, 1))
|
2016-10-04 03:06:35 +03:00
|
|
|
# Should pass lists too
|
2019-06-13 18:54:24 +03:00
|
|
|
i = Image.new("RGB", [1, 1])
|
2020-02-22 16:06:21 +03:00
|
|
|
assert isinstance(i.size, tuple)
|
2016-10-03 13:50:25 +03:00
|
|
|
|
|
|
|
def test_storage_neg(self):
|
|
|
|
# Storage.c accepted negative values for xsize, ysize. Was
|
|
|
|
# test_neg_ppm, but the core function for that has been
|
|
|
|
# removed Calling directly into core to test the error in
|
|
|
|
# Storage.c, rather than the size check above
|
2016-10-04 03:06:35 +03:00
|
|
|
|
2020-02-22 16:06:21 +03:00
|
|
|
with pytest.raises(ValueError):
|
2019-06-13 18:54:24 +03:00
|
|
|
Image.core.fill("RGB", (2, -2), (0, 0, 0))
|
2016-10-03 13:50:25 +03:00
|
|
|
|
2017-01-29 19:17:31 +03:00
|
|
|
def test_linear_gradient_wrong_mode(self):
|
|
|
|
# Arrange
|
|
|
|
wrong_mode = "RGB"
|
|
|
|
|
|
|
|
# Act / Assert
|
2020-02-22 16:06:21 +03:00
|
|
|
with pytest.raises(ValueError):
|
|
|
|
Image.linear_gradient(wrong_mode)
|
2017-01-29 19:17:31 +03:00
|
|
|
|
|
|
|
def test_linear_gradient(self):
|
|
|
|
|
|
|
|
# Arrange
|
2017-02-06 23:03:17 +03:00
|
|
|
target_file = "Tests/images/linear_gradient.png"
|
2017-01-29 19:17:31 +03:00
|
|
|
for mode in ["L", "P"]:
|
|
|
|
|
|
|
|
# Act
|
|
|
|
im = Image.linear_gradient(mode)
|
|
|
|
|
|
|
|
# Assert
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.size == (256, 256)
|
|
|
|
assert im.mode == mode
|
|
|
|
assert im.getpixel((0, 0)) == 0
|
|
|
|
assert im.getpixel((255, 255)) == 255
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(target_file) as target:
|
|
|
|
target = target.convert(mode)
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_equal(im, target)
|
2017-01-29 19:17:31 +03:00
|
|
|
|
2017-01-29 19:44:24 +03:00
|
|
|
def test_radial_gradient_wrong_mode(self):
|
|
|
|
# Arrange
|
|
|
|
wrong_mode = "RGB"
|
|
|
|
|
|
|
|
# Act / Assert
|
2020-02-22 16:06:21 +03:00
|
|
|
with pytest.raises(ValueError):
|
|
|
|
Image.radial_gradient(wrong_mode)
|
2017-01-29 19:44:24 +03:00
|
|
|
|
|
|
|
def test_radial_gradient(self):
|
|
|
|
|
|
|
|
# Arrange
|
2017-02-06 23:03:17 +03:00
|
|
|
target_file = "Tests/images/radial_gradient.png"
|
2017-01-29 19:44:24 +03:00
|
|
|
for mode in ["L", "P"]:
|
|
|
|
|
|
|
|
# Act
|
|
|
|
im = Image.radial_gradient(mode)
|
|
|
|
|
|
|
|
# Assert
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.size == (256, 256)
|
|
|
|
assert im.mode == mode
|
|
|
|
assert im.getpixel((0, 0)) == 255
|
|
|
|
assert im.getpixel((128, 128)) == 0
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(target_file) as target:
|
|
|
|
target = target.convert(mode)
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_equal(im, target)
|
2017-01-29 19:44:24 +03:00
|
|
|
|
2017-09-04 13:32:15 +03:00
|
|
|
def test_register_extensions(self):
|
|
|
|
test_format = "a"
|
|
|
|
exts = ["b", "c"]
|
|
|
|
for ext in exts:
|
|
|
|
Image.register_extension(test_format, ext)
|
|
|
|
ext_individual = Image.EXTENSION.copy()
|
|
|
|
for ext in exts:
|
|
|
|
del Image.EXTENSION[ext]
|
|
|
|
|
|
|
|
Image.register_extensions(test_format, exts)
|
|
|
|
ext_multiple = Image.EXTENSION.copy()
|
|
|
|
for ext in exts:
|
|
|
|
del Image.EXTENSION[ext]
|
|
|
|
|
2020-02-22 16:06:21 +03:00
|
|
|
assert ext_individual == ext_multiple
|
2017-09-04 13:32:15 +03:00
|
|
|
|
2017-09-01 13:36:51 +03:00
|
|
|
def test_remap_palette(self):
|
|
|
|
# Test illegal image mode
|
Improve handling of file resources
Follow Python's file object semantics. User code is responsible for
closing resources (usually through a context manager) in a deterministic
way.
To achieve this, remove __del__ functions. These functions used to
closed open file handlers in an attempt to silence Python
ResourceWarnings. However, using __del__ has the following drawbacks:
- __del__ isn't called until the object's reference count reaches 0.
Therefore, resource handlers remain open or in use longer than
necessary.
- The __del__ method isn't guaranteed to execute on system exit. See the
Python documentation:
https://docs.python.org/3/reference/datamodel.html#object.__del__
> It is not guaranteed that __del__() methods are called for objects
> that still exist when the interpreter exits.
- Exceptions that occur inside __del__ are ignored instead of raised.
This has the potential of hiding bugs. This is also in the Python
documentation:
> Warning: Due to the precarious circumstances under which __del__()
> methods are invoked, exceptions that occur during their execution
> are ignored, and a warning is printed to sys.stderr instead.
Instead, always close resource handlers when they are no longer in use.
This will close the file handler at a specified point in the user's code
and not wait until the interpreter chooses to. It is always guaranteed
to run. And, if an exception occurs while closing the file handler, the
bug will not be ignored.
Now, when code receives a ResourceWarning, it will highlight an area
that is mishandling resources. It should not simply be silenced, but
fixed by closing resources with a context manager.
All warnings that were emitted during tests have been cleaned up. To
enable warnings, I passed the `-Wa` CLI option to Python. This exposed
some mishandling of resources in ImageFile.__init__() and
SpiderImagePlugin.loadImageSeries(), they too were fixed.
2019-05-25 19:30:58 +03:00
|
|
|
with hopper() as im:
|
2020-02-22 16:06:21 +03:00
|
|
|
with pytest.raises(ValueError):
|
|
|
|
im.remap_palette(None)
|
2017-09-01 13:36:51 +03:00
|
|
|
|
2017-08-31 16:18:59 +03:00
|
|
|
def test__new(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
im = hopper("RGB")
|
|
|
|
im_p = hopper("P")
|
2017-08-31 16:18:59 +03:00
|
|
|
|
2019-06-13 18:54:24 +03:00
|
|
|
blank_p = Image.new("P", (10, 10))
|
|
|
|
blank_pa = Image.new("PA", (10, 10))
|
2017-08-31 16:18:59 +03:00
|
|
|
blank_p.palette = None
|
|
|
|
blank_pa.palette = None
|
2017-09-04 13:32:15 +03:00
|
|
|
|
2017-08-31 16:18:59 +03:00
|
|
|
def _make_new(base_image, im, palette_result=None):
|
|
|
|
new_im = base_image._new(im)
|
2020-02-22 16:06:21 +03:00
|
|
|
assert new_im.mode == im.mode
|
|
|
|
assert new_im.size == im.size
|
|
|
|
assert new_im.info == base_image.info
|
2017-08-31 16:18:59 +03:00
|
|
|
if palette_result is not None:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert new_im.palette.tobytes() == palette_result.tobytes()
|
2017-08-31 16:18:59 +03:00
|
|
|
else:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert new_im.palette is None
|
2017-09-04 13:32:15 +03:00
|
|
|
|
2017-08-31 16:18:59 +03:00
|
|
|
_make_new(im, im_p, im_p.palette)
|
|
|
|
_make_new(im_p, im, None)
|
|
|
|
_make_new(im, blank_p, ImagePalette.ImagePalette())
|
|
|
|
_make_new(im, blank_pa, ImagePalette.ImagePalette())
|
2017-09-04 13:32:15 +03:00
|
|
|
|
2019-03-15 09:30:28 +03:00
|
|
|
def test_p_from_rgb_rgba(self):
|
|
|
|
for mode, color in [
|
2019-06-13 18:54:24 +03:00
|
|
|
("RGB", "#DDEEFF"),
|
2019-03-15 09:30:28 +03:00
|
|
|
("RGB", (221, 238, 255)),
|
2019-06-13 18:54:24 +03:00
|
|
|
("RGBA", (221, 238, 255, 255)),
|
2019-03-15 09:30:28 +03:00
|
|
|
]:
|
|
|
|
im = Image.new("P", (100, 100), color)
|
|
|
|
expected = Image.new(mode, (100, 100), color)
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_equal(im.convert(mode), expected)
|
2019-03-15 09:30:28 +03:00
|
|
|
|
2020-06-21 15:17:18 +03:00
|
|
|
def test_showxv_deprecation(self):
|
|
|
|
class TestViewer(ImageShow.Viewer):
|
|
|
|
def show_image(self, image, **options):
|
|
|
|
return True
|
|
|
|
|
|
|
|
viewer = TestViewer()
|
|
|
|
ImageShow.register(viewer, -1)
|
|
|
|
|
|
|
|
im = Image.new("RGB", (50, 50), "white")
|
|
|
|
|
|
|
|
with pytest.warns(DeprecationWarning):
|
|
|
|
Image._showxv(im)
|
|
|
|
|
|
|
|
# Restore original state
|
|
|
|
ImageShow._viewers.pop(0)
|
|
|
|
|
2020-02-23 00:03:01 +03:00
|
|
|
def test_no_resource_warning_on_save(self, tmp_path):
|
2014-12-30 17:20:42 +03:00
|
|
|
# https://github.com/python-pillow/Pillow/issues/835
|
|
|
|
# Arrange
|
2019-06-13 18:54:24 +03:00
|
|
|
test_file = "Tests/images/hopper.png"
|
2020-02-23 00:03:01 +03:00
|
|
|
temp_file = str(tmp_path / "temp.jpg")
|
2014-12-30 17:20:42 +03:00
|
|
|
|
|
|
|
# Act/Assert
|
|
|
|
with Image.open(test_file) as im:
|
2020-02-03 12:11:32 +03:00
|
|
|
pytest.warns(None, im.save, temp_file)
|
2014-12-30 17:20:42 +03:00
|
|
|
|
2019-03-12 00:54:43 +03:00
|
|
|
def test_load_on_nonexclusive_multiframe(self):
|
|
|
|
with open("Tests/images/frozenpond.mpo", "rb") as fp:
|
2019-06-13 18:54:24 +03:00
|
|
|
|
2019-03-12 00:54:43 +03:00
|
|
|
def act(fp):
|
|
|
|
im = Image.open(fp)
|
|
|
|
im.load()
|
2019-06-13 18:54:24 +03:00
|
|
|
|
2019-03-12 00:54:43 +03:00
|
|
|
act(fp)
|
|
|
|
|
|
|
|
with Image.open(fp) as im:
|
|
|
|
im.load()
|
|
|
|
|
2020-02-22 16:06:21 +03:00
|
|
|
assert not fp.closed
|
2019-03-12 00:54:43 +03:00
|
|
|
|
2020-07-02 13:28:00 +03:00
|
|
|
def test_exif_jpeg(self, tmp_path):
|
|
|
|
with Image.open("Tests/images/exif-72dpi-int.jpg") as im: # Little endian
|
|
|
|
exif = im.getexif()
|
|
|
|
assert 258 not in exif
|
|
|
|
assert 40960 in exif
|
|
|
|
assert exif[40963] == 450
|
|
|
|
assert exif[11] == "gThumb 3.0.1"
|
|
|
|
|
|
|
|
out = str(tmp_path / "temp.jpg")
|
|
|
|
exif[258] = 8
|
|
|
|
del exif[40960]
|
|
|
|
exif[40963] = 455
|
|
|
|
exif[11] = "Pillow test"
|
|
|
|
im.save(out, exif=exif)
|
|
|
|
with Image.open(out) as reloaded:
|
|
|
|
reloaded_exif = reloaded.getexif()
|
|
|
|
assert reloaded_exif[258] == 8
|
|
|
|
assert 40960 not in reloaded_exif
|
|
|
|
assert reloaded_exif[40963] == 455
|
|
|
|
assert reloaded_exif[11] == "Pillow test"
|
|
|
|
|
|
|
|
with Image.open("Tests/images/no-dpi-in-exif.jpg") as im: # Big endian
|
|
|
|
exif = im.getexif()
|
|
|
|
assert 258 not in exif
|
|
|
|
assert 40962 in exif
|
|
|
|
assert exif[40963] == 200
|
|
|
|
assert exif[305] == "Adobe Photoshop CC 2017 (Macintosh)"
|
|
|
|
|
|
|
|
out = str(tmp_path / "temp.jpg")
|
|
|
|
exif[258] = 8
|
|
|
|
del exif[34665]
|
|
|
|
exif[40963] = 455
|
|
|
|
exif[305] = "Pillow test"
|
|
|
|
im.save(out, exif=exif)
|
|
|
|
with Image.open(out) as reloaded:
|
|
|
|
reloaded_exif = reloaded.getexif()
|
|
|
|
assert reloaded_exif[258] == 8
|
|
|
|
assert 34665 not in reloaded_exif
|
|
|
|
assert reloaded_exif[40963] == 455
|
|
|
|
assert reloaded_exif[305] == "Pillow test"
|
|
|
|
|
|
|
|
@skip_unless_feature("webp")
|
|
|
|
@skip_unless_feature("webp_anim")
|
|
|
|
def test_exif_webp(self, tmp_path):
|
|
|
|
with Image.open("Tests/images/hopper.webp") as im:
|
|
|
|
exif = im.getexif()
|
|
|
|
assert exif == {}
|
|
|
|
|
|
|
|
out = str(tmp_path / "temp.webp")
|
|
|
|
exif[258] = 8
|
|
|
|
exif[40963] = 455
|
|
|
|
exif[305] = "Pillow test"
|
|
|
|
|
|
|
|
def check_exif():
|
|
|
|
with Image.open(out) as reloaded:
|
|
|
|
reloaded_exif = reloaded.getexif()
|
|
|
|
assert reloaded_exif[258] == 8
|
|
|
|
assert reloaded_exif[40963] == 455
|
|
|
|
assert reloaded_exif[305] == "Pillow test"
|
|
|
|
|
|
|
|
im.save(out, exif=exif)
|
|
|
|
check_exif()
|
|
|
|
im.save(out, exif=exif, save_all=True)
|
|
|
|
check_exif()
|
|
|
|
|
|
|
|
def test_exif_png(self, tmp_path):
|
|
|
|
with Image.open("Tests/images/exif.png") as im:
|
|
|
|
exif = im.getexif()
|
|
|
|
assert exif == {274: 1}
|
|
|
|
|
|
|
|
out = str(tmp_path / "temp.png")
|
|
|
|
exif[258] = 8
|
|
|
|
del exif[274]
|
|
|
|
exif[40963] = 455
|
|
|
|
exif[305] = "Pillow test"
|
|
|
|
im.save(out, exif=exif)
|
|
|
|
|
|
|
|
with Image.open(out) as reloaded:
|
|
|
|
reloaded_exif = reloaded.getexif()
|
|
|
|
assert reloaded_exif == {258: 8, 40963: 455, 305: "Pillow test"}
|
|
|
|
|
|
|
|
def test_exif_interop(self):
|
|
|
|
with Image.open("Tests/images/flower.jpg") as im:
|
|
|
|
exif = im.getexif()
|
|
|
|
assert exif.get_ifd(0xA005) == {
|
|
|
|
1: "R98",
|
|
|
|
2: b"0100",
|
|
|
|
4097: 2272,
|
|
|
|
4098: 1704,
|
|
|
|
}
|
|
|
|
|
2020-03-31 09:25:26 +03:00
|
|
|
@pytest.mark.parametrize(
|
2020-08-31 00:37:17 +03:00
|
|
|
"test_module",
|
|
|
|
[PIL, Image],
|
2020-03-31 09:25:26 +03:00
|
|
|
)
|
|
|
|
def test_pillow_version(self, test_module):
|
2020-03-30 13:53:02 +03:00
|
|
|
with pytest.warns(DeprecationWarning):
|
2020-03-31 09:25:26 +03:00
|
|
|
assert test_module.PILLOW_VERSION == PIL.__version__
|
2020-03-31 09:41:47 +03:00
|
|
|
|
|
|
|
with pytest.warns(DeprecationWarning):
|
2020-03-31 09:25:26 +03:00
|
|
|
str(test_module.PILLOW_VERSION)
|
2020-03-30 13:53:02 +03:00
|
|
|
|
|
|
|
with pytest.warns(DeprecationWarning):
|
2020-03-31 09:25:26 +03:00
|
|
|
assert int(test_module.PILLOW_VERSION[0]) >= 7
|
2020-03-30 13:53:02 +03:00
|
|
|
|
2020-03-31 09:41:47 +03:00
|
|
|
with pytest.warns(DeprecationWarning):
|
2020-03-31 09:25:26 +03:00
|
|
|
assert test_module.PILLOW_VERSION < "9.9.0"
|
2020-03-31 09:41:47 +03:00
|
|
|
|
|
|
|
with pytest.warns(DeprecationWarning):
|
2020-03-31 09:25:26 +03:00
|
|
|
assert test_module.PILLOW_VERSION <= "9.9.0"
|
2020-03-31 09:41:47 +03:00
|
|
|
|
|
|
|
with pytest.warns(DeprecationWarning):
|
2020-03-31 09:25:26 +03:00
|
|
|
assert test_module.PILLOW_VERSION != "7.0.0"
|
2020-03-31 09:41:47 +03:00
|
|
|
|
|
|
|
with pytest.warns(DeprecationWarning):
|
2020-03-31 09:25:26 +03:00
|
|
|
assert test_module.PILLOW_VERSION >= "7.0.0"
|
2020-03-31 09:41:47 +03:00
|
|
|
|
|
|
|
with pytest.warns(DeprecationWarning):
|
2020-03-31 09:25:26 +03:00
|
|
|
assert test_module.PILLOW_VERSION > "7.0.0"
|
2020-03-31 09:41:47 +03:00
|
|
|
|
2019-09-30 11:45:43 +03:00
|
|
|
def test_overrun(self):
|
2020-08-31 00:37:17 +03:00
|
|
|
"""For overrun completeness, test as:
|
2020-04-01 10:52:21 +03:00
|
|
|
valgrind pytest -qq Tests/test_image.py::TestImage::test_overrun | grep decode.c
|
2020-03-09 23:21:40 +03:00
|
|
|
"""
|
2019-12-21 10:38:22 +03:00
|
|
|
for file in [
|
|
|
|
"fli_overrun.bin",
|
|
|
|
"sgi_overrun.bin",
|
2020-01-01 06:16:45 +03:00
|
|
|
"sgi_overrun_expandrow.bin",
|
|
|
|
"sgi_overrun_expandrow2.bin",
|
2019-12-21 10:38:22 +03:00
|
|
|
"pcx_overrun.bin",
|
|
|
|
"pcx_overrun2.bin",
|
2020-03-09 23:21:40 +03:00
|
|
|
"01r_00.pcx",
|
2019-12-21 10:38:22 +03:00
|
|
|
]:
|
Improve handling of file resources
Follow Python's file object semantics. User code is responsible for
closing resources (usually through a context manager) in a deterministic
way.
To achieve this, remove __del__ functions. These functions used to
closed open file handlers in an attempt to silence Python
ResourceWarnings. However, using __del__ has the following drawbacks:
- __del__ isn't called until the object's reference count reaches 0.
Therefore, resource handlers remain open or in use longer than
necessary.
- The __del__ method isn't guaranteed to execute on system exit. See the
Python documentation:
https://docs.python.org/3/reference/datamodel.html#object.__del__
> It is not guaranteed that __del__() methods are called for objects
> that still exist when the interpreter exits.
- Exceptions that occur inside __del__ are ignored instead of raised.
This has the potential of hiding bugs. This is also in the Python
documentation:
> Warning: Due to the precarious circumstances under which __del__()
> methods are invoked, exceptions that occur during their execution
> are ignored, and a warning is printed to sys.stderr instead.
Instead, always close resource handlers when they are no longer in use.
This will close the file handler at a specified point in the user's code
and not wait until the interpreter chooses to. It is always guaranteed
to run. And, if an exception occurs while closing the file handler, the
bug will not be ignored.
Now, when code receives a ResourceWarning, it will highlight an area
that is mishandling resources. It should not simply be silenced, but
fixed by closing resources with a context manager.
All warnings that were emitted during tests have been cleaned up. To
enable warnings, I passed the `-Wa` CLI option to Python. This exposed
some mishandling of resources in ImageFile.__init__() and
SpiderImagePlugin.loadImageSeries(), they too were fixed.
2019-05-25 19:30:58 +03:00
|
|
|
with Image.open(os.path.join("Tests/images", file)) as im:
|
|
|
|
try:
|
|
|
|
im.load()
|
2020-02-23 00:03:01 +03:00
|
|
|
assert False
|
2019-11-01 14:22:56 +03:00
|
|
|
except OSError as e:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert str(e) == "buffer overrun when reading image file"
|
2019-09-30 11:45:43 +03:00
|
|
|
|
2020-01-02 07:23:36 +03:00
|
|
|
with Image.open("Tests/images/fli_overrun2.bin") as im:
|
|
|
|
try:
|
|
|
|
im.seek(1)
|
2020-02-23 00:03:01 +03:00
|
|
|
assert False
|
2020-01-02 07:23:36 +03:00
|
|
|
except OSError as e:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert str(e) == "buffer overrun when reading image file"
|
2020-01-02 07:23:36 +03:00
|
|
|
|
2020-05-25 19:51:30 +03:00
|
|
|
def test_show_deprecation(self, monkeypatch):
|
|
|
|
monkeypatch.setattr(Image, "_show", lambda *args, **kwargs: None)
|
|
|
|
|
|
|
|
im = Image.new("RGB", (50, 50), "white")
|
|
|
|
|
|
|
|
with pytest.warns(None) as raised:
|
|
|
|
im.show()
|
|
|
|
assert not raised
|
|
|
|
|
|
|
|
with pytest.warns(DeprecationWarning):
|
|
|
|
im.show(command="mock")
|
|
|
|
|
2017-08-31 16:18:59 +03:00
|
|
|
|
2019-09-30 17:56:31 +03:00
|
|
|
class MockEncoder:
|
2017-04-20 14:14:23 +03:00
|
|
|
pass
|
|
|
|
|
2017-03-11 20:03:09 +03:00
|
|
|
|
|
|
|
def mock_encode(*args):
|
|
|
|
encoder = MockEncoder()
|
|
|
|
encoder.args = args
|
|
|
|
return encoder
|
|
|
|
|
2017-04-20 14:14:23 +03:00
|
|
|
|
2020-02-23 00:03:01 +03:00
|
|
|
class TestRegistry:
|
2017-03-11 20:03:09 +03:00
|
|
|
def test_encode_registry(self):
|
|
|
|
|
2019-06-13 18:54:24 +03:00
|
|
|
Image.register_encoder("MOCK", mock_encode)
|
2020-02-22 16:06:21 +03:00
|
|
|
assert "MOCK" in Image.ENCODERS
|
2017-03-11 20:03:09 +03:00
|
|
|
|
2019-06-13 18:54:24 +03:00
|
|
|
enc = Image._getencoder("RGB", "MOCK", ("args",), extra=("extra",))
|
2017-03-11 20:03:09 +03:00
|
|
|
|
2020-02-22 16:06:21 +03:00
|
|
|
assert isinstance(enc, MockEncoder)
|
|
|
|
assert enc.args == ("RGB", "args", "extra")
|
2017-03-11 20:03:09 +03:00
|
|
|
|
|
|
|
def test_encode_registry_fail(self):
|
2020-04-07 09:58:21 +03:00
|
|
|
with pytest.raises(OSError):
|
2020-02-22 16:06:21 +03:00
|
|
|
Image._getencoder("RGB", "DoesNotExist", ("args",), extra=("extra",))
|