2023-12-21 14:13:31 +03:00
|
|
|
from __future__ import annotations
|
2024-01-20 14:23:03 +03:00
|
|
|
|
2019-10-07 16:28:36 +03:00
|
|
|
import base64
|
2014-11-16 07:38:52 +03:00
|
|
|
import io
|
2015-03-01 06:44:38 +03:00
|
|
|
import itertools
|
2015-02-06 21:58:07 +03:00
|
|
|
import os
|
2020-06-15 16:32:30 +03:00
|
|
|
import re
|
2022-09-05 14:44:12 +03:00
|
|
|
import sys
|
2024-01-31 12:12:58 +03:00
|
|
|
from pathlib import Path
|
2024-03-23 21:48:46 +03:00
|
|
|
from typing import Any, NamedTuple
|
2013-04-22 00:51:16 +04:00
|
|
|
|
2020-02-22 16:06:21 +03:00
|
|
|
import pytest
|
2020-09-01 20:16:46 +03:00
|
|
|
|
2023-09-08 01:23:35 +03:00
|
|
|
from PIL import Image, ImageFilter, ImageOps, TiffImagePlugin, TiffTags, features
|
2024-03-22 15:43:55 +03:00
|
|
|
from PIL.TiffImagePlugin import OSUBFILETYPE, SAMPLEFORMAT, STRIPOFFSETS, SUBIFD
|
2019-07-06 23:40:53 +03:00
|
|
|
|
2020-01-30 17:56:07 +03:00
|
|
|
from .helper import (
|
|
|
|
assert_image_equal,
|
|
|
|
assert_image_equal_tofile,
|
|
|
|
assert_image_similar,
|
|
|
|
assert_image_similar_tofile,
|
|
|
|
hopper,
|
2021-04-10 00:33:21 +03:00
|
|
|
mark_if_feature_version,
|
2021-04-10 17:58:01 +03:00
|
|
|
skip_unless_feature,
|
2020-01-30 17:56:07 +03:00
|
|
|
)
|
2013-04-22 00:51:16 +04:00
|
|
|
|
2014-11-17 11:33:31 +03:00
|
|
|
|
2020-02-18 01:03:32 +03:00
|
|
|
@skip_unless_feature("libtiff")
|
2020-03-02 17:02:19 +03:00
|
|
|
class LibTiffTestCase:
|
2024-03-02 05:12:17 +03:00
|
|
|
def _assert_noerr(self, tmp_path: Path, im: TiffImagePlugin.TiffImageFile) -> None:
|
2014-06-10 13:10:47 +04:00
|
|
|
"""Helper tests that assert basic sanity about the g4 tiff reading"""
|
|
|
|
# 1 bit
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.mode == "1"
|
2013-04-22 00:51:16 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
# Does the data actually load
|
|
|
|
im.load()
|
|
|
|
im.getdata()
|
2013-04-22 00:51:16 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
try:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im._compression == "group4"
|
2018-06-11 09:00:57 +03:00
|
|
|
except AttributeError:
|
2014-06-10 13:10:47 +04:00
|
|
|
print("No _compression")
|
2015-04-24 02:26:52 +03:00
|
|
|
print(dir(im))
|
2013-04-22 00:51:16 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
# can we write it back out, in a different form.
|
2020-03-02 17:02:19 +03:00
|
|
|
out = str(tmp_path / "temp.png")
|
2014-06-10 13:10:47 +04:00
|
|
|
im.save(out)
|
2013-04-22 00:51:16 +04:00
|
|
|
|
2016-11-23 17:24:40 +03:00
|
|
|
out_bytes = io.BytesIO()
|
2019-06-13 18:54:11 +03:00
|
|
|
im.save(out_bytes, format="tiff", compression="group4")
|
2014-11-17 11:33:31 +03:00
|
|
|
|
2017-04-20 14:14:23 +03:00
|
|
|
|
2014-08-26 20:57:15 +04:00
|
|
|
class TestFileLibTiff(LibTiffTestCase):
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_version(self) -> None:
|
2024-05-30 05:00:50 +03:00
|
|
|
version = features.version_codec("libtiff")
|
|
|
|
assert version is not None
|
2024-06-11 00:15:47 +03:00
|
|
|
assert re.search(r"\d+\.\d+\.\d+t?$", version)
|
2020-06-15 16:32:30 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_g4_tiff(self, tmp_path: Path) -> None:
|
2014-06-10 13:10:47 +04:00
|
|
|
"""Test the ordinary file path load path"""
|
2013-04-22 00:51:16 +04:00
|
|
|
|
2015-04-02 11:45:24 +03:00
|
|
|
test_file = "Tests/images/hopper_g4_500.tif"
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(test_file) as im:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.size == (500, 500)
|
2020-03-02 17:02:19 +03:00
|
|
|
self._assert_noerr(tmp_path, im)
|
2013-04-22 00:51:16 +04:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_g4_large(self, tmp_path: Path) -> None:
|
2015-04-24 11:24:52 +03:00
|
|
|
test_file = "Tests/images/pport_g4.tif"
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(test_file) as im:
|
2020-03-02 17:02:19 +03:00
|
|
|
self._assert_noerr(tmp_path, im)
|
2013-04-22 00:51:16 +04:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_g4_tiff_file(self, tmp_path: Path) -> None:
|
2014-06-10 13:10:47 +04:00
|
|
|
"""Testing the string load path"""
|
|
|
|
|
2015-04-02 11:45:24 +03:00
|
|
|
test_file = "Tests/images/hopper_g4_500.tif"
|
2019-06-13 18:54:11 +03:00
|
|
|
with open(test_file, "rb") as f:
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(f) as im:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.size == (500, 500)
|
2020-03-02 17:02:19 +03:00
|
|
|
self._assert_noerr(tmp_path, im)
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_g4_tiff_bytesio(self, tmp_path: Path) -> None:
|
2014-06-10 13:10:47 +04:00
|
|
|
"""Testing the stringio loading code path"""
|
2015-04-02 11:45:24 +03:00
|
|
|
test_file = "Tests/images/hopper_g4_500.tif"
|
2014-11-16 07:38:52 +03:00
|
|
|
s = io.BytesIO()
|
2019-06-13 18:54:11 +03:00
|
|
|
with open(test_file, "rb") as f:
|
2014-06-10 13:10:47 +04:00
|
|
|
s.write(f.read())
|
|
|
|
s.seek(0)
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(s) as im:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.size == (500, 500)
|
2020-03-02 17:02:19 +03:00
|
|
|
self._assert_noerr(tmp_path, im)
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_g4_non_disk_file_object(self, tmp_path: Path) -> None:
|
2019-08-19 19:46:07 +03:00
|
|
|
"""Testing loading from non-disk non-BytesIO file object"""
|
2019-08-03 03:26:10 +03:00
|
|
|
test_file = "Tests/images/hopper_g4_500.tif"
|
|
|
|
with open(test_file, "rb") as f:
|
2024-07-06 12:17:23 +03:00
|
|
|
data = f.read()
|
|
|
|
|
|
|
|
class NonBytesIO(io.RawIOBase):
|
|
|
|
def read(self, size: int = -1) -> bytes:
|
|
|
|
nonlocal data
|
|
|
|
if size == -1:
|
|
|
|
size = len(data)
|
|
|
|
result = data[:size]
|
|
|
|
data = data[size:]
|
|
|
|
return result
|
|
|
|
|
|
|
|
def readable(self) -> bool:
|
|
|
|
return True
|
|
|
|
|
|
|
|
r = io.BufferedReader(NonBytesIO())
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(r) as im:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.size == (500, 500)
|
2020-03-02 17:02:19 +03:00
|
|
|
self._assert_noerr(tmp_path, im)
|
2019-08-03 03:26:10 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_g4_eq_png(self) -> None:
|
2021-08-12 14:50:09 +03:00
|
|
|
"""Checking that we're actually getting the data that we expect"""
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open("Tests/images/hopper_bw_500.png") as png:
|
2021-02-21 14:15:56 +03:00
|
|
|
assert_image_equal_tofile(png, "Tests/images/hopper_g4_500.tif")
|
2014-06-10 13:10:47 +04:00
|
|
|
|
|
|
|
# see https://github.com/python-pillow/Pillow/issues/279
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_g4_fillorder_eq_png(self) -> None:
|
2021-08-12 14:50:09 +03:00
|
|
|
"""Checking that we're actually getting the data that we expect"""
|
2021-02-21 14:15:56 +03:00
|
|
|
with Image.open("Tests/images/g4-fillorder-test.tif") as g4:
|
|
|
|
assert_image_equal_tofile(g4, "Tests/images/g4-fillorder-test.png")
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_g4_write(self, tmp_path: Path) -> None:
|
2014-06-10 13:10:47 +04:00
|
|
|
"""Checking to see that the saved image is the same as what we wrote"""
|
2015-04-02 11:45:24 +03:00
|
|
|
test_file = "Tests/images/hopper_g4_500.tif"
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(test_file) as orig:
|
2020-03-02 17:02:19 +03:00
|
|
|
out = str(tmp_path / "temp.tif")
|
2022-01-15 01:02:31 +03:00
|
|
|
rot = orig.transpose(Image.Transpose.ROTATE_90)
|
2020-02-22 16:06:21 +03:00
|
|
|
assert rot.size == (500, 500)
|
2019-11-25 23:03:23 +03:00
|
|
|
rot.save(out)
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(out) as reread:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert reread.size == (500, 500)
|
2020-03-02 17:02:19 +03:00
|
|
|
self._assert_noerr(tmp_path, reread)
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_equal(reread, rot)
|
2020-02-22 16:06:21 +03:00
|
|
|
assert reread.info["compression"] == "group4"
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2020-02-22 16:06:21 +03:00
|
|
|
assert reread.info["compression"] == orig.info["compression"]
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2020-02-22 16:06:21 +03:00
|
|
|
assert orig.tobytes() != reread.tobytes()
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_adobe_deflate_tiff(self) -> None:
|
2015-04-02 11:45:24 +03:00
|
|
|
test_file = "Tests/images/tiff_adobe_deflate.tif"
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(test_file) as im:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.mode == "RGB"
|
|
|
|
assert im.size == (278, 374)
|
|
|
|
assert im.tile[0][:3] == ("libtiff", (0, 0, 278, 374), 0)
|
2019-11-25 23:03:23 +03:00
|
|
|
im.load()
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
|
2017-12-20 15:09:26 +03:00
|
|
|
|
2022-08-23 14:41:32 +03:00
|
|
|
@pytest.mark.parametrize("legacy_api", (False, True))
|
2024-02-07 11:16:28 +03:00
|
|
|
def test_write_metadata(self, legacy_api: bool, tmp_path: Path) -> None:
|
2021-08-12 14:50:09 +03:00
|
|
|
"""Test metadata writing through libtiff"""
|
2022-08-23 14:41:32 +03:00
|
|
|
f = str(tmp_path / "temp.tiff")
|
|
|
|
with Image.open("Tests/images/hopper_g4.tif") as img:
|
|
|
|
img.save(f, tiffinfo=img.tag)
|
|
|
|
|
|
|
|
if legacy_api:
|
|
|
|
original = img.tag.named()
|
|
|
|
else:
|
|
|
|
original = img.tag_v2.named()
|
|
|
|
|
|
|
|
# PhotometricInterpretation is set from SAVE_INFO,
|
|
|
|
# not the original image.
|
|
|
|
ignored = [
|
|
|
|
"StripByteCounts",
|
|
|
|
"RowsPerStrip",
|
|
|
|
"PageNumber",
|
|
|
|
"PhotometricInterpretation",
|
|
|
|
]
|
|
|
|
|
|
|
|
with Image.open(f) as loaded:
|
|
|
|
if legacy_api:
|
|
|
|
reloaded = loaded.tag.named()
|
|
|
|
else:
|
|
|
|
reloaded = loaded.tag_v2.named()
|
|
|
|
|
|
|
|
for tag, value in itertools.chain(reloaded.items(), original.items()):
|
|
|
|
if tag not in ignored:
|
|
|
|
val = original[tag]
|
|
|
|
if tag.endswith("Resolution"):
|
|
|
|
if legacy_api:
|
|
|
|
assert val[0][0] / val[0][1] == (
|
|
|
|
4294967295 / 113653537
|
|
|
|
), f"{tag} didn't roundtrip"
|
2015-07-01 04:29:29 +03:00
|
|
|
else:
|
2022-08-23 14:41:32 +03:00
|
|
|
assert val == 37.79000115940079, f"{tag} didn't roundtrip"
|
|
|
|
else:
|
|
|
|
assert val == value, f"{tag} didn't roundtrip"
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2022-08-23 14:41:32 +03:00
|
|
|
# https://github.com/python-pillow/Pillow/issues/1561
|
|
|
|
requested_fields = ["StripByteCounts", "RowsPerStrip", "StripOffsets"]
|
|
|
|
for field in requested_fields:
|
|
|
|
assert field in reloaded, f"{field} not in metadata"
|
2015-12-14 23:24:01 +03:00
|
|
|
|
2020-12-31 01:38:40 +03:00
|
|
|
@pytest.mark.valgrind_known_error(reason="Known invalid metadata")
|
2024-04-18 10:57:40 +03:00
|
|
|
def test_additional_metadata(
|
|
|
|
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
|
|
) -> None:
|
2016-01-01 16:30:40 +03:00
|
|
|
# these should not crash. Seriously dummy data, most of it doesn't make
|
|
|
|
# any sense, so we're running up against limits where we're asking
|
|
|
|
# libtiff to do stupid things.
|
2016-02-05 01:57:13 +03:00
|
|
|
|
2016-01-01 16:30:40 +03:00
|
|
|
# Get the list of the ones that we should be able to write
|
|
|
|
|
2019-06-13 18:54:11 +03:00
|
|
|
core_items = {
|
|
|
|
tag: info
|
|
|
|
for tag, info in ((s, TiffTags.lookup(s)) for s in TiffTags.LIBTIFF_CORE)
|
|
|
|
if info.type is not None
|
|
|
|
}
|
2016-02-05 01:57:13 +03:00
|
|
|
|
2016-08-04 09:40:12 +03:00
|
|
|
# Exclude ones that have special meaning
|
|
|
|
# that we're already testing them
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open("Tests/images/hopper_g4.tif") as im:
|
|
|
|
for tag in im.tag_v2:
|
|
|
|
try:
|
|
|
|
del core_items[tag]
|
|
|
|
except KeyError:
|
|
|
|
pass
|
2020-05-26 09:38:38 +03:00
|
|
|
del core_items[320] # colormap is special, tested below
|
2019-11-25 23:03:23 +03:00
|
|
|
|
|
|
|
# Type codes:
|
|
|
|
# 2: "ascii",
|
|
|
|
# 3: "short",
|
|
|
|
# 4: "long",
|
|
|
|
# 5: "rational",
|
|
|
|
# 12: "double",
|
|
|
|
# Type: dummy value
|
|
|
|
values = {
|
|
|
|
2: "test",
|
|
|
|
3: 1,
|
2022-03-04 08:42:24 +03:00
|
|
|
4: 2**20,
|
2019-11-25 23:03:23 +03:00
|
|
|
5: TiffImagePlugin.IFDRational(100, 1),
|
|
|
|
12: 1.05,
|
|
|
|
}
|
|
|
|
|
|
|
|
new_ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
|
|
|
for tag, info in core_items.items():
|
2024-07-20 06:14:18 +03:00
|
|
|
assert info.type is not None
|
2024-07-20 22:22:13 +03:00
|
|
|
if info.length == 1:
|
2024-07-20 06:14:18 +03:00
|
|
|
new_ifd[tag] = values[info.type]
|
2024-07-20 22:22:13 +03:00
|
|
|
elif not info.length:
|
|
|
|
new_ifd[tag] = tuple(values[info.type] for _ in range(3))
|
2019-11-25 23:03:23 +03:00
|
|
|
else:
|
|
|
|
new_ifd[tag] = tuple(values[info.type] for _ in range(info.length))
|
2016-01-01 16:30:40 +03:00
|
|
|
|
2019-11-25 23:03:23 +03:00
|
|
|
# Extra samples really doesn't make sense in this application.
|
|
|
|
del new_ifd[338]
|
2016-01-01 16:30:40 +03:00
|
|
|
|
2020-03-02 17:02:19 +03:00
|
|
|
out = str(tmp_path / "temp.tif")
|
2024-04-18 10:57:40 +03:00
|
|
|
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
|
2016-01-01 16:30:40 +03:00
|
|
|
|
2019-11-25 23:03:23 +03:00
|
|
|
im.save(out, tiffinfo=new_ifd)
|
2016-01-01 16:30:40 +03:00
|
|
|
|
2024-04-22 11:45:41 +03:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"libtiff",
|
|
|
|
(
|
|
|
|
pytest.param(
|
|
|
|
True,
|
|
|
|
marks=pytest.mark.skipif(
|
|
|
|
not getattr(Image.core, "libtiff_support_custom_tags", False),
|
|
|
|
reason="Custom tags not supported by older libtiff",
|
|
|
|
),
|
|
|
|
),
|
|
|
|
False,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
def test_custom_metadata(
|
|
|
|
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path, libtiff: bool
|
|
|
|
) -> None:
|
|
|
|
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", libtiff)
|
|
|
|
|
2024-03-23 21:48:46 +03:00
|
|
|
class Tc(NamedTuple):
|
|
|
|
value: Any
|
|
|
|
type: int
|
|
|
|
supported_by_default: bool
|
|
|
|
|
2018-10-25 11:45:13 +03:00
|
|
|
custom = {
|
2019-05-21 14:18:09 +03:00
|
|
|
37000 + k: v
|
|
|
|
for k, v in enumerate(
|
|
|
|
[
|
2024-03-23 21:48:46 +03:00
|
|
|
Tc(4, TiffTags.SHORT, True),
|
|
|
|
Tc(123456789, TiffTags.LONG, True),
|
|
|
|
Tc(-4, TiffTags.SIGNED_BYTE, False),
|
|
|
|
Tc(-4, TiffTags.SIGNED_SHORT, False),
|
|
|
|
Tc(-123456789, TiffTags.SIGNED_LONG, False),
|
|
|
|
Tc(TiffImagePlugin.IFDRational(4, 7), TiffTags.RATIONAL, True),
|
|
|
|
Tc(4.25, TiffTags.FLOAT, True),
|
|
|
|
Tc(4.25, TiffTags.DOUBLE, True),
|
|
|
|
Tc("custom tag value", TiffTags.ASCII, True),
|
|
|
|
Tc(b"custom tag value", TiffTags.BYTE, True),
|
|
|
|
Tc((4, 5, 6), TiffTags.SHORT, True),
|
|
|
|
Tc((123456789, 9, 34, 234, 219387, 92432323), TiffTags.LONG, True),
|
|
|
|
Tc((-4, 9, 10), TiffTags.SIGNED_BYTE, False),
|
|
|
|
Tc((-4, 5, 6), TiffTags.SIGNED_SHORT, False),
|
|
|
|
Tc(
|
2019-05-21 14:18:09 +03:00
|
|
|
(-123456789, 9, 34, 234, 219387, -92432323),
|
|
|
|
TiffTags.SIGNED_LONG,
|
|
|
|
False,
|
|
|
|
),
|
2024-03-23 21:48:46 +03:00
|
|
|
Tc((4.25, 5.25), TiffTags.FLOAT, True),
|
|
|
|
Tc((4.25, 5.25), TiffTags.DOUBLE, True),
|
2019-05-21 14:18:09 +03:00
|
|
|
# array of TIFF_BYTE requires bytes instead of tuple for backwards
|
|
|
|
# compatibility
|
2024-03-23 21:48:46 +03:00
|
|
|
Tc(bytes([4]), TiffTags.BYTE, True),
|
|
|
|
Tc(bytes((4, 9, 10)), TiffTags.BYTE, True),
|
2019-05-21 14:18:09 +03:00
|
|
|
]
|
|
|
|
)
|
2018-10-25 11:45:13 +03:00
|
|
|
}
|
2018-10-25 11:36:49 +03:00
|
|
|
|
2024-04-22 11:45:41 +03:00
|
|
|
def check_tags(
|
|
|
|
tiffinfo: TiffImagePlugin.ImageFileDirectory_v2 | dict[int, str]
|
|
|
|
) -> None:
|
|
|
|
im = hopper()
|
|
|
|
|
|
|
|
out = str(tmp_path / "temp.tif")
|
|
|
|
im.save(out, tiffinfo=tiffinfo)
|
|
|
|
|
|
|
|
with Image.open(out) as reloaded:
|
|
|
|
for tag, value in tiffinfo.items():
|
|
|
|
reloaded_value = reloaded.tag_v2[tag]
|
|
|
|
if (
|
|
|
|
isinstance(reloaded_value, TiffImagePlugin.IFDRational)
|
|
|
|
and libtiff
|
|
|
|
):
|
|
|
|
# libtiff does not support real RATIONALS
|
|
|
|
assert round(abs(float(reloaded_value) - float(value)), 7) == 0
|
|
|
|
continue
|
|
|
|
|
|
|
|
assert reloaded_value == value
|
|
|
|
|
|
|
|
# Test with types
|
|
|
|
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
|
|
|
for tag, tagdata in custom.items():
|
|
|
|
ifd[tag] = tagdata.value
|
|
|
|
ifd.tagtype[tag] = tagdata.type
|
|
|
|
check_tags(ifd)
|
|
|
|
|
|
|
|
# Test without types. This only works for some types, int for example are
|
|
|
|
# always encoded as LONG and not SIGNED_LONG.
|
|
|
|
check_tags(
|
|
|
|
{
|
|
|
|
tag: tagdata.value
|
|
|
|
for tag, tagdata in custom.items()
|
|
|
|
if tagdata.supported_by_default
|
|
|
|
}
|
|
|
|
)
|
2018-10-25 11:45:13 +03:00
|
|
|
|
2024-03-22 15:43:55 +03:00
|
|
|
def test_osubfiletype(self, tmp_path: Path) -> None:
|
|
|
|
outfile = str(tmp_path / "temp.tif")
|
|
|
|
with Image.open("Tests/images/g4_orientation_6.tif") as im:
|
|
|
|
im.tag_v2[OSUBFILETYPE] = 1
|
|
|
|
im.save(outfile)
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_subifd(self, tmp_path: Path) -> None:
|
2021-01-28 12:57:24 +03:00
|
|
|
outfile = str(tmp_path / "temp.tif")
|
|
|
|
with Image.open("Tests/images/g4_orientation_6.tif") as im:
|
|
|
|
im.tag_v2[SUBIFD] = 10000
|
|
|
|
|
|
|
|
# Should not segfault
|
|
|
|
im.save(outfile)
|
|
|
|
|
2024-04-18 10:57:40 +03:00
|
|
|
def test_xmlpacket_tag(
|
|
|
|
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
|
|
) -> None:
|
|
|
|
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
|
2020-05-03 12:41:38 +03:00
|
|
|
|
|
|
|
out = str(tmp_path / "temp.tif")
|
|
|
|
hopper().save(out, tiffinfo={700: b"xmlpacket tag"})
|
|
|
|
|
|
|
|
with Image.open(out) as reloaded:
|
|
|
|
if 700 in reloaded.tag_v2:
|
|
|
|
assert reloaded.tag_v2[700] == b"xmlpacket tag"
|
|
|
|
|
2024-04-18 10:57:40 +03:00
|
|
|
def test_int_dpi(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
2016-12-13 23:49:47 +03:00
|
|
|
# issue #1765
|
2019-06-13 18:54:11 +03:00
|
|
|
im = hopper("RGB")
|
2020-03-02 17:02:19 +03:00
|
|
|
out = str(tmp_path / "temp.tif")
|
2024-04-18 10:57:40 +03:00
|
|
|
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
|
2016-12-13 23:49:47 +03:00
|
|
|
im.save(out, dpi=(72, 72))
|
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(out) as reloaded:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert reloaded.info["dpi"] == (72.0, 72.0)
|
2016-12-13 23:49:47 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_g3_compression(self, tmp_path: Path) -> None:
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open("Tests/images/hopper_g4_500.tif") as i:
|
2020-03-02 17:02:19 +03:00
|
|
|
out = str(tmp_path / "temp.tif")
|
2019-11-25 23:03:23 +03:00
|
|
|
i.save(out, compression="group3")
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(out) as reread:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert reread.info["compression"] == "group3"
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_equal(reread, i)
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_little_endian(self, tmp_path: Path) -> None:
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open("Tests/images/16bit.deflate.tif") as im:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.getpixel((0, 0)) == 480
|
|
|
|
assert im.mode == "I;16"
|
2019-11-25 23:03:23 +03:00
|
|
|
|
|
|
|
b = im.tobytes()
|
|
|
|
# Bytes are in image native order (little endian)
|
2020-02-22 16:06:21 +03:00
|
|
|
assert b[0] == ord(b"\xe0")
|
|
|
|
assert b[1] == ord(b"\x01")
|
2019-11-25 23:03:23 +03:00
|
|
|
|
2020-03-02 17:02:19 +03:00
|
|
|
out = str(tmp_path / "temp.tif")
|
2019-11-25 23:03:23 +03:00
|
|
|
# out = "temp.le.tif"
|
|
|
|
im.save(out)
|
|
|
|
with Image.open(out) as reread:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert reread.info["compression"] == im.info["compression"]
|
|
|
|
assert reread.getpixel((0, 0)) == 480
|
2014-06-10 13:10:47 +04:00
|
|
|
# UNDONE - libtiff defaults to writing in native endian, so
|
|
|
|
# on big endian, we'll get back mode = 'I;16B' here.
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_big_endian(self, tmp_path: Path) -> None:
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open("Tests/images/16bit.MM.deflate.tif") as im:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.getpixel((0, 0)) == 480
|
|
|
|
assert im.mode == "I;16B"
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2019-11-25 23:03:23 +03:00
|
|
|
b = im.tobytes()
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2019-11-25 23:03:23 +03:00
|
|
|
# Bytes are in image native order (big endian)
|
2020-02-22 16:06:21 +03:00
|
|
|
assert b[0] == ord(b"\x01")
|
|
|
|
assert b[1] == ord(b"\xe0")
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2020-03-02 17:02:19 +03:00
|
|
|
out = str(tmp_path / "temp.tif")
|
2019-11-25 23:03:23 +03:00
|
|
|
im.save(out)
|
|
|
|
with Image.open(out) as reread:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert reread.info["compression"] == im.info["compression"]
|
|
|
|
assert reread.getpixel((0, 0)) == 480
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_g4_string_info(self, tmp_path: Path) -> None:
|
2014-06-10 13:10:47 +04:00
|
|
|
"""Tests String data in info directory"""
|
2015-04-02 11:45:24 +03:00
|
|
|
test_file = "Tests/images/hopper_g4_500.tif"
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(test_file) as orig:
|
2020-03-02 17:02:19 +03:00
|
|
|
out = str(tmp_path / "temp.tif")
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2019-11-25 23:03:23 +03:00
|
|
|
orig.tag[269] = "temp.tif"
|
|
|
|
orig.save(out)
|
2014-06-10 13:10:47 +04: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(out) as reread:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert "temp.tif" == reread.tag_v2[269]
|
|
|
|
assert "temp.tif" == reread.tag[269][0]
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2024-04-18 10:57:40 +03:00
|
|
|
def test_12bit_rawmode(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
2020-09-01 20:16:46 +03:00
|
|
|
"""Are we generating the same interpretation
|
|
|
|
of the image as Imagemagick is?"""
|
2024-04-18 10:57:40 +03:00
|
|
|
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open("Tests/images/12bit.cropped.tif") as im:
|
|
|
|
im.load()
|
2024-04-18 10:57:40 +03:00
|
|
|
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", False)
|
2019-11-25 23:03:23 +03:00
|
|
|
# to make the target --
|
|
|
|
# convert 12bit.cropped.tif -depth 16 tmp.tif
|
|
|
|
# convert tmp.tif -evaluate RightShift 4 12in16bit2.tif
|
|
|
|
# imagemagick will auto scale so that a 12bit FFF is 16bit FFF0,
|
|
|
|
# so we need to unshift so that the integer values are the same.
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_equal_tofile(im, "Tests/images/12in16bit.tif")
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_blur(self, tmp_path: Path) -> None:
|
2014-06-10 13:10:47 +04:00
|
|
|
# test case from irc, how to do blur on b/w image
|
|
|
|
# and save to compressed tif.
|
2020-03-02 17:02:19 +03:00
|
|
|
out = str(tmp_path / "temp.tif")
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open("Tests/images/pport_g4.tif") as im:
|
|
|
|
im = im.convert("L")
|
2014-06-10 13:10:47 +04:00
|
|
|
|
|
|
|
im = im.filter(ImageFilter.GaussianBlur(4))
|
2019-06-13 18:54:11 +03:00
|
|
|
im.save(out, compression="tiff_adobe_deflate")
|
2013-04-22 00:51:16 +04:00
|
|
|
|
2021-02-21 14:15:56 +03:00
|
|
|
assert_image_equal_tofile(im, out)
|
2013-11-06 08:49:09 +04:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_compressions(self, tmp_path: Path) -> None:
|
2019-06-04 14:30:13 +03:00
|
|
|
# Test various tiff compressions and assert similar image content but reduced
|
|
|
|
# file sizes.
|
2019-06-13 18:54:11 +03:00
|
|
|
im = hopper("RGB")
|
2020-03-02 17:02:19 +03:00
|
|
|
out = str(tmp_path / "temp.tif")
|
2019-06-04 14:30:13 +03:00
|
|
|
im.save(out)
|
|
|
|
size_raw = os.path.getsize(out)
|
2013-11-06 08:49:09 +04:00
|
|
|
|
2019-06-13 18:54:11 +03:00
|
|
|
for compression in ("packbits", "tiff_lzw"):
|
2014-06-10 13:10:47 +04:00
|
|
|
im.save(out, compression=compression)
|
2019-06-04 14:30:13 +03:00
|
|
|
size_compressed = os.path.getsize(out)
|
2021-02-21 14:15:56 +03:00
|
|
|
assert_image_equal_tofile(im, out)
|
2013-11-06 08:53:18 +04:00
|
|
|
|
2019-06-13 18:54:11 +03:00
|
|
|
im.save(out, compression="jpeg")
|
2019-06-04 14:30:13 +03:00
|
|
|
size_jpeg = os.path.getsize(out)
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(out) as im2:
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_similar(im, im2, 30)
|
2013-11-22 10:13:57 +04:00
|
|
|
|
2019-06-04 14:30:13 +03:00
|
|
|
im.save(out, compression="jpeg", quality=30)
|
|
|
|
size_jpeg_30 = os.path.getsize(out)
|
2021-02-21 14:22:29 +03:00
|
|
|
assert_image_similar_tofile(im2, out, 30)
|
2019-06-04 14:30:13 +03:00
|
|
|
|
2020-02-22 16:06:21 +03:00
|
|
|
assert size_raw > size_compressed
|
|
|
|
assert size_compressed > size_jpeg
|
|
|
|
assert size_jpeg > size_jpeg_30
|
2019-06-04 14:30:13 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_tiff_jpeg_compression(self, tmp_path: Path) -> None:
|
2020-05-15 15:37:13 +03:00
|
|
|
im = hopper("RGB")
|
|
|
|
out = str(tmp_path / "temp.tif")
|
|
|
|
im.save(out, compression="tiff_jpeg")
|
|
|
|
|
|
|
|
with Image.open(out) as reloaded:
|
|
|
|
assert reloaded.info["compression"] == "jpeg"
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_tiff_deflate_compression(self, tmp_path: Path) -> None:
|
2021-03-19 04:00:29 +03:00
|
|
|
im = hopper("RGB")
|
|
|
|
out = str(tmp_path / "temp.tif")
|
|
|
|
im.save(out, compression="tiff_deflate")
|
|
|
|
|
|
|
|
with Image.open(out) as reloaded:
|
|
|
|
assert reloaded.info["compression"] == "tiff_adobe_deflate"
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_quality(self, tmp_path: Path) -> None:
|
2019-06-04 14:30:13 +03:00
|
|
|
im = hopper("RGB")
|
2020-03-02 17:02:19 +03:00
|
|
|
out = str(tmp_path / "temp.tif")
|
2019-06-04 14:30:13 +03:00
|
|
|
|
2020-02-22 16:06:21 +03:00
|
|
|
with pytest.raises(ValueError):
|
|
|
|
im.save(out, compression="tiff_lzw", quality=50)
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
im.save(out, compression="jpeg", quality=-1)
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
im.save(out, compression="jpeg", quality=101)
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
im.save(out, compression="jpeg", quality="good")
|
2019-06-04 14:30:13 +03:00
|
|
|
im.save(out, compression="jpeg", quality=0)
|
|
|
|
im.save(out, compression="jpeg", quality=100)
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_cmyk_save(self, tmp_path: Path) -> None:
|
2019-06-13 18:54:11 +03:00
|
|
|
im = hopper("CMYK")
|
2020-03-02 17:02:19 +03:00
|
|
|
out = str(tmp_path / "temp.tif")
|
2014-06-10 13:10:47 +04:00
|
|
|
|
2019-06-13 18:54:11 +03:00
|
|
|
im.save(out, compression="tiff_adobe_deflate")
|
2021-02-21 14:15:56 +03:00
|
|
|
assert_image_equal_tofile(im, out)
|
2014-06-03 14:02:44 +04:00
|
|
|
|
2022-04-21 04:26:34 +03:00
|
|
|
@pytest.mark.parametrize("im", (hopper("P"), Image.new("P", (1, 1), "#000")))
|
2024-04-18 10:57:40 +03:00
|
|
|
def test_palette_save(
|
|
|
|
self, im: Image.Image, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
|
|
) -> None:
|
2020-05-26 09:38:38 +03:00
|
|
|
out = str(tmp_path / "temp.tif")
|
|
|
|
|
2024-04-18 10:57:40 +03:00
|
|
|
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
|
2020-05-26 09:38:38 +03:00
|
|
|
im.save(out)
|
|
|
|
|
|
|
|
with Image.open(out) as reloaded:
|
|
|
|
# colormap/palette tag
|
|
|
|
assert len(reloaded.tag_v2[320]) == 768
|
|
|
|
|
2022-10-03 08:57:42 +03:00
|
|
|
@pytest.mark.parametrize("compression", ("tiff_ccitt", "group3", "group4"))
|
2024-02-07 11:16:28 +03:00
|
|
|
def test_bw_compression_w_rgb(self, compression: str, tmp_path: Path) -> None:
|
2019-06-13 18:54:11 +03:00
|
|
|
im = hopper("RGB")
|
2020-03-02 17:02:19 +03:00
|
|
|
out = str(tmp_path / "temp.tif")
|
2013-11-06 08:49:09 +04:00
|
|
|
|
2020-04-07 09:58:21 +03:00
|
|
|
with pytest.raises(OSError):
|
2022-10-03 08:57:42 +03:00
|
|
|
im.save(out, compression=compression)
|
2013-11-06 08:59:57 +04:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_fp_leak(self) -> None:
|
2024-03-02 05:12:17 +03:00
|
|
|
im: Image.Image | None = Image.open("Tests/images/hopper_g4_500.tif")
|
|
|
|
assert im is not None
|
2014-06-10 13:10:47 +04:00
|
|
|
fn = im.fp.fileno()
|
2014-06-03 14:02:44 +04:00
|
|
|
|
2014-06-10 13:10:47 +04:00
|
|
|
os.fstat(fn)
|
|
|
|
im.load() # this should close it.
|
2020-02-22 16:06:21 +03:00
|
|
|
with pytest.raises(OSError):
|
|
|
|
os.fstat(fn)
|
2014-06-10 13:10:47 +04:00
|
|
|
im = None # this should force even more closed.
|
2020-02-22 16:06:21 +03:00
|
|
|
with pytest.raises(OSError):
|
|
|
|
os.fstat(fn)
|
|
|
|
with pytest.raises(OSError):
|
|
|
|
os.close(fn)
|
2013-11-06 08:59:57 +04:00
|
|
|
|
2024-04-18 10:57:40 +03:00
|
|
|
def test_multipage(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
2014-08-21 08:43:46 +04:00
|
|
|
# issue #862
|
2024-04-18 10:57:40 +03:00
|
|
|
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
|
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/multipage.tiff") as im:
|
|
|
|
# file is a multipage tiff, 10x10 green, 10x10 red, 20x20 blue
|
2014-08-21 08:43:46 +04: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
|
|
|
im.seek(0)
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.size == (10, 10)
|
|
|
|
assert im.convert("RGB").getpixel((0, 0)) == (0, 128, 0)
|
|
|
|
assert im.tag.next
|
2014-08-21 08:43:46 +04: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
|
|
|
im.seek(1)
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.size == (10, 10)
|
|
|
|
assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0)
|
|
|
|
assert im.tag.next
|
2014-08-21 08:43:46 +04: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
|
|
|
im.seek(2)
|
2020-02-22 16:06:21 +03:00
|
|
|
assert not im.tag.next
|
|
|
|
assert im.size == (20, 20)
|
|
|
|
assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 255)
|
2014-09-05 13:14:45 +04:00
|
|
|
|
2024-04-18 10:57:40 +03:00
|
|
|
def test_multipage_nframes(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
2016-11-17 15:43:11 +03:00
|
|
|
# issue #862
|
2024-04-18 10:57:40 +03:00
|
|
|
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
|
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/multipage.tiff") as im:
|
|
|
|
frames = im.n_frames
|
2020-02-22 16:06:21 +03:00
|
|
|
assert frames == 3
|
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
|
|
|
for _ in range(frames):
|
|
|
|
im.seek(0)
|
|
|
|
# Should not raise ValueError: I/O operation on closed file
|
|
|
|
im.load()
|
2016-11-17 15:43:11 +03:00
|
|
|
|
2024-04-18 10:57:40 +03:00
|
|
|
def test_multipage_seek_backwards(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
|
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
|
2021-04-26 13:27:34 +03:00
|
|
|
with Image.open("Tests/images/multipage.tiff") as im:
|
|
|
|
im.seek(1)
|
|
|
|
im.load()
|
|
|
|
|
|
|
|
im.seek(0)
|
|
|
|
assert im.convert("RGB").getpixel((0, 0)) == (0, 128, 0)
|
|
|
|
|
2024-04-18 10:57:40 +03:00
|
|
|
def test__next(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
|
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open("Tests/images/hopper.tif") as im:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert not im.tag.next
|
2019-11-25 23:03:23 +03:00
|
|
|
im.load()
|
2020-02-22 16:06:21 +03:00
|
|
|
assert not im.tag.next
|
2013-11-22 10:14:29 +04:00
|
|
|
|
2024-04-18 10:57:40 +03:00
|
|
|
def test_4bit(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
2014-10-29 21:07:20 +03:00
|
|
|
# Arrange
|
|
|
|
test_file = "Tests/images/hopper_gray_4bpp.tif"
|
|
|
|
original = hopper("L")
|
|
|
|
|
|
|
|
# Act
|
2024-04-18 10:57:40 +03:00
|
|
|
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(test_file) as im:
|
2014-10-29 21:07:20 +03:00
|
|
|
|
2019-11-25 23:03:23 +03:00
|
|
|
# Assert
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.size == (128, 128)
|
|
|
|
assert im.mode == "L"
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_similar(im, original, 7.3)
|
2014-10-29 21:07:20 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_gray_semibyte_per_pixel(self) -> None:
|
2016-03-29 13:56:37 +03:00
|
|
|
test_files = (
|
|
|
|
(
|
2016-08-04 09:40:12 +03:00
|
|
|
24.8, # epsilon
|
|
|
|
( # group
|
2016-03-29 13:56:37 +03:00
|
|
|
"Tests/images/tiff_gray_2_4_bpp/hopper2.tif",
|
|
|
|
"Tests/images/tiff_gray_2_4_bpp/hopper2I.tif",
|
|
|
|
"Tests/images/tiff_gray_2_4_bpp/hopper2R.tif",
|
|
|
|
"Tests/images/tiff_gray_2_4_bpp/hopper2IR.tif",
|
2019-06-13 18:54:11 +03:00
|
|
|
),
|
2016-03-29 13:56:37 +03:00
|
|
|
),
|
|
|
|
(
|
2016-08-04 09:40:12 +03:00
|
|
|
7.3, # epsilon
|
|
|
|
( # group
|
2016-03-29 13:56:37 +03:00
|
|
|
"Tests/images/tiff_gray_2_4_bpp/hopper4.tif",
|
|
|
|
"Tests/images/tiff_gray_2_4_bpp/hopper4I.tif",
|
|
|
|
"Tests/images/tiff_gray_2_4_bpp/hopper4R.tif",
|
|
|
|
"Tests/images/tiff_gray_2_4_bpp/hopper4IR.tif",
|
2019-06-13 18:54:11 +03:00
|
|
|
),
|
2016-03-29 13:56:37 +03:00
|
|
|
),
|
|
|
|
)
|
|
|
|
original = hopper("L")
|
|
|
|
for epsilon, group in test_files:
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(group[0]) as im:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.size == (128, 128)
|
|
|
|
assert im.mode == "L"
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_similar(im, original, epsilon)
|
2016-03-29 13:56:37 +03:00
|
|
|
for file in group[1:]:
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(file) as im2:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im2.size == (128, 128)
|
|
|
|
assert im2.mode == "L"
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_equal(im, im2)
|
2016-03-29 13:56:37 +03:00
|
|
|
|
2024-04-18 10:57:40 +03:00
|
|
|
def test_save_bytesio(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
2014-11-16 07:38:52 +03:00
|
|
|
# PR 1011
|
|
|
|
# Test TIFF saving to io.BytesIO() object.
|
2014-11-17 11:33:31 +03:00
|
|
|
|
2024-04-18 10:57:40 +03:00
|
|
|
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
|
|
|
|
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
|
2014-11-16 07:38:52 +03:00
|
|
|
|
|
|
|
# Generate test image
|
|
|
|
pilim = hopper()
|
|
|
|
|
2024-02-07 11:16:28 +03:00
|
|
|
def save_bytesio(compression: str | None = None) -> None:
|
2014-11-16 07:38:52 +03:00
|
|
|
buffer_io = io.BytesIO()
|
|
|
|
pilim.save(buffer_io, format="tiff", compression=compression)
|
|
|
|
buffer_io.seek(0)
|
|
|
|
|
2024-06-01 14:31:53 +03:00
|
|
|
with Image.open(buffer_io) as saved_im:
|
|
|
|
assert_image_similar(pilim, saved_im, 0)
|
2014-11-17 11:33:31 +03:00
|
|
|
|
2018-08-25 01:21:43 +03:00
|
|
|
save_bytesio()
|
2019-06-13 18:54:11 +03:00
|
|
|
save_bytesio("raw")
|
2014-11-16 07:38:52 +03:00
|
|
|
save_bytesio("packbits")
|
|
|
|
save_bytesio("tiff_lzw")
|
2014-11-17 11:33:31 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_save_ycbcr(self, tmp_path: Path) -> None:
|
2021-07-09 18:20:36 +03:00
|
|
|
im = hopper("YCbCr")
|
|
|
|
outfile = str(tmp_path / "temp.tif")
|
|
|
|
im.save(outfile, compression="jpeg")
|
|
|
|
|
|
|
|
with Image.open(outfile) as reloaded:
|
|
|
|
assert reloaded.tag_v2[530] == (1, 1)
|
|
|
|
assert reloaded.tag_v2[532] == (0, 255, 128, 255, 128, 255)
|
|
|
|
|
2024-06-07 06:25:56 +03:00
|
|
|
def test_exif_ifd(self) -> None:
|
|
|
|
out = io.BytesIO()
|
2023-04-01 02:27:39 +03:00
|
|
|
with Image.open("Tests/images/tiff_adobe_deflate.tif") as im:
|
2023-04-01 04:21:16 +03:00
|
|
|
assert im.tag_v2[34665] == 125456
|
2024-06-07 06:25:56 +03:00
|
|
|
im.save(out, "TIFF")
|
2023-04-01 02:27:39 +03:00
|
|
|
|
2024-06-07 06:25:56 +03:00
|
|
|
with Image.open(out) as reloaded:
|
|
|
|
assert 34665 not in reloaded.tag_v2
|
|
|
|
|
|
|
|
im.save(out, "TIFF", tiffinfo={34665: 125456})
|
|
|
|
|
|
|
|
with Image.open(out) as reloaded:
|
2023-04-01 04:21:16 +03:00
|
|
|
if Image.core.libtiff_support_custom_tags:
|
|
|
|
assert reloaded.tag_v2[34665] == 125456
|
|
|
|
|
2024-04-18 10:57:40 +03:00
|
|
|
def test_crashing_metadata(
|
|
|
|
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
|
|
) -> None:
|
2015-12-30 18:54:14 +03:00
|
|
|
# issue 1597
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open("Tests/images/rdf.tif") as im:
|
2020-03-02 17:02:19 +03:00
|
|
|
out = str(tmp_path / "temp.tif")
|
2015-12-30 18:54:14 +03:00
|
|
|
|
2024-04-18 10:57:40 +03:00
|
|
|
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
|
2019-11-25 23:03:23 +03:00
|
|
|
# this shouldn't crash
|
|
|
|
im.save(out, format="TIFF")
|
2015-12-30 18:54:14 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_page_number_x_0(self, tmp_path: Path) -> None:
|
2016-05-06 19:07:00 +03:00
|
|
|
# Issue 973
|
|
|
|
# Test TIFF with tag 297 (Page Number) having value of 0 0.
|
|
|
|
# The first number is the current page number.
|
|
|
|
# The second is the total number of pages, zero means not available.
|
2020-03-02 17:02:19 +03:00
|
|
|
outfile = str(tmp_path / "temp.tif")
|
2016-05-06 19:07:00 +03:00
|
|
|
# Created by printing a page in Chrome to PDF, then:
|
|
|
|
# /usr/bin/gs -q -sDEVICE=tiffg3 -sOutputFile=total-pages-zero.tif
|
|
|
|
# -dNOPAUSE /tmp/test.pdf -c quit
|
|
|
|
infile = "Tests/images/total-pages-zero.tif"
|
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(infile) as im:
|
|
|
|
# Should not divide by zero
|
|
|
|
im.save(outfile)
|
2016-05-06 19:07:00 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_fd_duplication(self, tmp_path: Path) -> None:
|
2016-09-30 00:29:19 +03:00
|
|
|
# https://github.com/python-pillow/Pillow/issues/1651
|
|
|
|
|
2020-03-02 17:02:19 +03:00
|
|
|
tmpfile = str(tmp_path / "temp.tif")
|
2019-06-13 18:54:11 +03:00
|
|
|
with open(tmpfile, "wb") as f:
|
|
|
|
with open("Tests/images/g4-multi.tiff", "rb") as src:
|
2016-09-30 00:29:19 +03:00
|
|
|
f.write(src.read())
|
|
|
|
|
|
|
|
im = Image.open(tmpfile)
|
2024-03-02 05:12:17 +03:00
|
|
|
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
2018-06-15 12:55:48 +03:00
|
|
|
im.n_frames
|
2016-09-30 00:29:19 +03:00
|
|
|
im.close()
|
2016-11-06 19:58:45 +03:00
|
|
|
# Should not raise PermissionError.
|
|
|
|
os.remove(tmpfile)
|
2016-09-30 00:29:19 +03:00
|
|
|
|
2024-04-18 10:57:40 +03:00
|
|
|
def test_read_icc(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
2016-11-04 18:37:49 +03:00
|
|
|
with Image.open("Tests/images/hopper.iccprofile.tif") as img:
|
2019-06-13 18:54:11 +03:00
|
|
|
icc = img.info.get("icc_profile")
|
2020-02-22 16:06:21 +03:00
|
|
|
assert icc is not None
|
2024-04-18 10:57:40 +03:00
|
|
|
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
|
2016-11-04 18:37:49 +03:00
|
|
|
with Image.open("Tests/images/hopper.iccprofile.tif") as img:
|
2019-06-13 18:54:11 +03:00
|
|
|
icc_libtiff = img.info.get("icc_profile")
|
2020-02-22 16:06:21 +03:00
|
|
|
assert icc_libtiff is not None
|
|
|
|
assert icc == icc_libtiff
|
2016-05-06 19:07:00 +03:00
|
|
|
|
2024-04-19 00:47:14 +03:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"libtiff",
|
|
|
|
(
|
|
|
|
pytest.param(
|
|
|
|
True,
|
|
|
|
marks=pytest.mark.skipif(
|
2024-04-22 11:30:00 +03:00
|
|
|
not getattr(Image.core, "libtiff_support_custom_tags", False),
|
2024-04-19 00:47:14 +03:00
|
|
|
reason="Custom tags not supported by older libtiff",
|
|
|
|
),
|
|
|
|
),
|
|
|
|
False,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
def test_write_icc(
|
|
|
|
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path, libtiff: bool
|
|
|
|
) -> None:
|
|
|
|
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", libtiff)
|
2020-05-06 13:12:59 +03:00
|
|
|
|
2024-04-19 00:47:14 +03:00
|
|
|
with Image.open("Tests/images/hopper.iccprofile.tif") as img:
|
|
|
|
icc_profile = img.info["icc_profile"]
|
2020-05-06 13:12:59 +03:00
|
|
|
|
2024-04-19 00:47:14 +03:00
|
|
|
out = str(tmp_path / "temp.tif")
|
|
|
|
img.save(out, icc_profile=icc_profile)
|
|
|
|
with Image.open(out) as reloaded:
|
|
|
|
assert icc_profile == reloaded.info["icc_profile"]
|
2020-05-06 13:12:59 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_multipage_compression(self) -> None:
|
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/compression.tif") as im:
|
|
|
|
im.seek(0)
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im._compression == "tiff_ccitt"
|
|
|
|
assert im.size == (10, 10)
|
2016-12-03 16:37:31 +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
|
|
|
im.seek(1)
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im._compression == "packbits"
|
|
|
|
assert im.size == (10, 10)
|
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
|
|
|
im.load()
|
2016-12-03 16:37:31 +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
|
|
|
im.seek(0)
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im._compression == "tiff_ccitt"
|
|
|
|
assert im.size == (10, 10)
|
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
|
|
|
im.load()
|
2016-12-03 16:37:31 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_save_tiff_with_jpegtables(self, tmp_path: Path) -> None:
|
2016-12-03 16:37:31 +03:00
|
|
|
# Arrange
|
2020-03-02 17:02:19 +03:00
|
|
|
outfile = str(tmp_path / "temp.tif")
|
2016-12-03 16:37:31 +03:00
|
|
|
|
|
|
|
# Created with ImageMagick: convert hopper.jpg hopper_jpg.tif
|
|
|
|
# Contains JPEGTables (347) tag
|
|
|
|
infile = "Tests/images/hopper_jpg.tif"
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(infile) as im:
|
|
|
|
# Act / Assert
|
|
|
|
# Should not raise UnicodeDecodeError or anything else
|
|
|
|
im.save(outfile)
|
2016-12-03 16:37:31 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_16bit_RGB_tiff(self) -> None:
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open("Tests/images/tiff_16bit_RGB.tiff") as im:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.mode == "RGB"
|
|
|
|
assert im.size == (100, 40)
|
|
|
|
assert im.tile, [
|
|
|
|
(
|
|
|
|
"libtiff",
|
|
|
|
(0, 0, 100, 40),
|
|
|
|
0,
|
|
|
|
("RGB;16N", "tiff_adobe_deflate", False, 8),
|
|
|
|
)
|
|
|
|
]
|
2019-11-25 23:03:23 +03:00
|
|
|
im.load()
|
2019-05-08 22:08:17 +03:00
|
|
|
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png")
|
2019-05-08 22:08:17 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_16bit_RGBa_tiff(self) -> None:
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open("Tests/images/tiff_16bit_RGBa.tiff") as im:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.mode == "RGBA"
|
|
|
|
assert im.size == (100, 40)
|
|
|
|
assert im.tile, [
|
2020-09-01 20:16:46 +03:00
|
|
|
("libtiff", (0, 0, 100, 40), 0, ("RGBa;16N", "tiff_lzw", False, 38236))
|
2020-02-22 16:06:21 +03:00
|
|
|
]
|
2019-11-25 23:03:23 +03:00
|
|
|
im.load()
|
2017-12-20 15:22:28 +03:00
|
|
|
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png")
|
2017-12-20 15:22:28 +03:00
|
|
|
|
2020-02-18 01:03:32 +03:00
|
|
|
@skip_unless_feature("jpg")
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_gimp_tiff(self) -> None:
|
2017-12-20 15:22:28 +03:00
|
|
|
# Read TIFF JPEG images from GIMP [@PIL168]
|
|
|
|
filename = "Tests/images/pil168.tif"
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(filename) as im:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.mode == "RGB"
|
|
|
|
assert im.size == (256, 256)
|
|
|
|
assert im.tile == [
|
|
|
|
("libtiff", (0, 0, 256, 256), 0, ("RGB", "jpeg", False, 5122))
|
|
|
|
]
|
2019-11-25 23:03:23 +03:00
|
|
|
im.load()
|
2017-12-20 15:22:28 +03:00
|
|
|
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_equal_tofile(im, "Tests/images/pil168.png")
|
2017-12-20 15:22:28 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_sampleformat(self) -> None:
|
2017-12-20 15:22:28 +03:00
|
|
|
# https://github.com/python-pillow/Pillow/issues/1466
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open("Tests/images/copyleft.tiff") as im:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.mode == "RGB"
|
2017-12-20 15:22:28 +03:00
|
|
|
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_equal_tofile(im, "Tests/images/copyleft.png", mode="RGB")
|
2017-12-20 15:22:28 +03:00
|
|
|
|
2024-04-18 10:57:40 +03:00
|
|
|
def test_sampleformat_write(
|
|
|
|
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
|
|
) -> None:
|
2021-11-23 00:10:18 +03:00
|
|
|
im = Image.new("F", (1, 1))
|
|
|
|
out = str(tmp_path / "temp.tif")
|
2024-04-18 10:57:40 +03:00
|
|
|
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
|
2021-11-23 00:10:18 +03:00
|
|
|
im.save(out)
|
|
|
|
|
|
|
|
with Image.open(out) as reloaded:
|
|
|
|
assert reloaded.mode == "F"
|
|
|
|
assert reloaded.getexif()[SAMPLEFORMAT] == 3
|
|
|
|
|
2024-02-07 11:16:28 +03:00
|
|
|
def test_lzma(self, capfd: pytest.CaptureFixture[str]) -> None:
|
2022-09-05 11:13:52 +03:00
|
|
|
try:
|
|
|
|
with Image.open("Tests/images/hopper_lzma.tif") as im:
|
|
|
|
assert im.mode == "RGB"
|
|
|
|
assert im.size == (128, 128)
|
|
|
|
assert im.format == "TIFF"
|
|
|
|
im2 = hopper()
|
|
|
|
assert_image_similar(im, im2, 5)
|
|
|
|
except OSError:
|
|
|
|
captured = capfd.readouterr()
|
|
|
|
if "LZMA compression support is not configured" in captured.err:
|
|
|
|
pytest.skip("LZMA compression support is not configured")
|
2022-09-05 14:44:12 +03:00
|
|
|
sys.stdout.write(captured.out)
|
|
|
|
sys.stderr.write(captured.err)
|
2022-09-05 11:13:52 +03:00
|
|
|
raise
|
|
|
|
|
2024-02-07 11:16:28 +03:00
|
|
|
def test_webp(self, capfd: pytest.CaptureFixture[str]) -> None:
|
2022-09-05 12:58:42 +03:00
|
|
|
try:
|
|
|
|
with Image.open("Tests/images/hopper_webp.tif") as im:
|
|
|
|
assert im.mode == "RGB"
|
|
|
|
assert im.size == (128, 128)
|
|
|
|
assert im.format == "TIFF"
|
|
|
|
assert_image_similar_tofile(im, "Tests/images/hopper_webp.png", 1)
|
|
|
|
except OSError:
|
|
|
|
captured = capfd.readouterr()
|
|
|
|
if "WEBP compression support is not configured" in captured.err:
|
|
|
|
pytest.skip("WEBP compression support is not configured")
|
2022-09-05 14:44:12 +03:00
|
|
|
if (
|
|
|
|
"Compression scheme 50001 strip decoding is not implemented"
|
|
|
|
in captured.err
|
|
|
|
):
|
|
|
|
pytest.skip(
|
|
|
|
"Compression scheme 50001 strip decoding is not implemented"
|
|
|
|
)
|
|
|
|
sys.stdout.write(captured.out)
|
|
|
|
sys.stderr.write(captured.err)
|
2022-09-05 12:58:42 +03:00
|
|
|
raise
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_lzw(self) -> None:
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open("Tests/images/hopper_lzw.tif") as im:
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.mode == "RGB"
|
|
|
|
assert im.size == (128, 128)
|
|
|
|
assert im.format == "TIFF"
|
2019-11-25 23:03:23 +03:00
|
|
|
im2 = hopper()
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_similar(im, im2, 5)
|
2017-12-20 15:22:28 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_strip_cmyk_jpeg(self) -> None:
|
2018-07-17 07:41:32 +03:00
|
|
|
infile = "Tests/images/tiff_strip_cmyk_jpeg.tif"
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(infile) as im:
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
|
2018-07-17 07:41:32 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_strip_cmyk_16l_jpeg(self) -> None:
|
2019-04-30 17:42:30 +03:00
|
|
|
infile = "Tests/images/tiff_strip_cmyk_16l_jpeg.tif"
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(infile) as im:
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
|
2019-04-30 17:42:30 +03:00
|
|
|
|
2021-04-10 17:58:01 +03:00
|
|
|
@mark_if_feature_version(
|
|
|
|
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
|
|
|
)
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_strip_ycbcr_jpeg_2x2_sampling(self) -> None:
|
2018-07-17 07:41:32 +03:00
|
|
|
infile = "Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif"
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(infile) as im:
|
2022-08-04 13:03:24 +03:00
|
|
|
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 1.2)
|
2018-07-17 07:41:32 +03:00
|
|
|
|
2021-04-10 17:58:01 +03:00
|
|
|
@mark_if_feature_version(
|
|
|
|
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
|
|
|
)
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_strip_ycbcr_jpeg_1x1_sampling(self) -> None:
|
2018-07-17 07:41:32 +03:00
|
|
|
infile = "Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif"
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(infile) as im:
|
2022-08-04 13:03:24 +03:00
|
|
|
assert_image_similar_tofile(im, "Tests/images/flower2.jpg", 0.01)
|
2018-07-17 07:41:32 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_tiled_cmyk_jpeg(self) -> None:
|
2018-07-17 07:41:32 +03:00
|
|
|
infile = "Tests/images/tiff_tiled_cmyk_jpeg.tif"
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(infile) as im:
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
|
2018-07-17 07:41:32 +03:00
|
|
|
|
2021-04-10 17:58:01 +03:00
|
|
|
@mark_if_feature_version(
|
|
|
|
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
|
|
|
)
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_tiled_ycbcr_jpeg_1x1_sampling(self) -> None:
|
2018-07-17 07:41:32 +03:00
|
|
|
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif"
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(infile) as im:
|
2022-08-04 13:03:24 +03:00
|
|
|
assert_image_similar_tofile(im, "Tests/images/flower2.jpg", 0.01)
|
2018-07-17 07:41:32 +03:00
|
|
|
|
2021-04-10 17:58:01 +03:00
|
|
|
@mark_if_feature_version(
|
|
|
|
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
|
|
|
)
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_tiled_ycbcr_jpeg_2x2_sampling(self) -> None:
|
2018-07-17 07:41:32 +03:00
|
|
|
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif"
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open(infile) as im:
|
2022-08-04 13:03:24 +03:00
|
|
|
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 1.5)
|
2018-07-17 07:41:32 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_strip_planar_rgb(self) -> None:
|
2020-05-23 19:24:41 +03:00
|
|
|
# gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \
|
|
|
|
# tiff_strip_raw.tif tiff_strip_planar_lzw.tiff
|
|
|
|
infile = "Tests/images/tiff_strip_planar_lzw.tiff"
|
|
|
|
with Image.open(infile) as im:
|
|
|
|
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_tiled_planar_rgb(self) -> None:
|
2020-05-23 19:24:41 +03:00
|
|
|
# gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \
|
|
|
|
# tiff_tiled_raw.tif tiff_tiled_planar_lzw.tiff
|
|
|
|
infile = "Tests/images/tiff_tiled_planar_lzw.tiff"
|
|
|
|
with Image.open(infile) as im:
|
|
|
|
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_tiled_planar_16bit_RGB(self) -> None:
|
2020-05-23 19:24:41 +03:00
|
|
|
# gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \
|
|
|
|
# tiff_16bit_RGB.tiff tiff_tiled_planar_16bit_RGB.tiff
|
|
|
|
with Image.open("Tests/images/tiff_tiled_planar_16bit_RGB.tiff") as im:
|
|
|
|
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png")
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_strip_planar_16bit_RGB(self) -> None:
|
2020-05-23 19:24:41 +03:00
|
|
|
# gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \
|
|
|
|
# tiff_16bit_RGB.tiff tiff_strip_planar_16bit_RGB.tiff
|
|
|
|
with Image.open("Tests/images/tiff_strip_planar_16bit_RGB.tiff") as im:
|
|
|
|
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png")
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_tiled_planar_16bit_RGBa(self) -> None:
|
2020-05-23 19:24:41 +03:00
|
|
|
# gdal_translate -co TILED=yes \
|
|
|
|
# -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \
|
|
|
|
# tiff_16bit_RGBa.tiff tiff_tiled_planar_16bit_RGBa.tiff
|
|
|
|
with Image.open("Tests/images/tiff_tiled_planar_16bit_RGBa.tiff") as im:
|
|
|
|
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png")
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_strip_planar_16bit_RGBa(self) -> None:
|
2020-05-23 19:24:41 +03:00
|
|
|
# gdal_translate -co TILED=no \
|
|
|
|
# -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \
|
|
|
|
# tiff_16bit_RGBa.tiff tiff_strip_planar_16bit_RGBa.tiff
|
|
|
|
with Image.open("Tests/images/tiff_strip_planar_16bit_RGBa.tiff") as im:
|
|
|
|
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png")
|
|
|
|
|
2021-11-18 14:01:53 +03:00
|
|
|
@pytest.mark.parametrize("compression", (None, "jpeg"))
|
2024-02-07 11:16:28 +03:00
|
|
|
def test_block_tile_tags(self, compression: str | None, tmp_path: Path) -> None:
|
2021-11-18 14:01:53 +03:00
|
|
|
im = hopper()
|
|
|
|
out = str(tmp_path / "temp.tif")
|
|
|
|
|
|
|
|
tags = {
|
|
|
|
TiffImagePlugin.TILEWIDTH: 256,
|
|
|
|
TiffImagePlugin.TILELENGTH: 256,
|
|
|
|
TiffImagePlugin.TILEOFFSETS: 256,
|
|
|
|
TiffImagePlugin.TILEBYTECOUNTS: 256,
|
|
|
|
}
|
|
|
|
im.save(out, exif=tags, compression=compression)
|
|
|
|
|
|
|
|
with Image.open(out) as reloaded:
|
2022-10-13 05:20:11 +03:00
|
|
|
for tag in tags:
|
2021-11-18 14:01:53 +03:00
|
|
|
assert tag not in reloaded.getexif()
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_old_style_jpeg(self) -> None:
|
2021-05-04 09:50:12 +03:00
|
|
|
with Image.open("Tests/images/old-style-jpeg-compression.tif") as im:
|
|
|
|
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_open_missing_samplesperpixel(self) -> None:
|
2021-05-04 09:50:12 +03:00
|
|
|
with Image.open(
|
|
|
|
"Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif"
|
|
|
|
) as im:
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
|
2019-09-13 15:36:26 +03:00
|
|
|
|
2023-01-29 09:29:36 +03:00
|
|
|
@pytest.mark.parametrize(
|
2023-03-12 14:32:38 +03:00
|
|
|
"file_name, mode, size, tile",
|
2023-01-29 09:29:36 +03:00
|
|
|
[
|
|
|
|
(
|
|
|
|
"tiff_wrong_bits_per_sample.tiff",
|
|
|
|
"RGBA",
|
|
|
|
(52, 53),
|
|
|
|
[("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))],
|
|
|
|
),
|
|
|
|
(
|
|
|
|
"tiff_wrong_bits_per_sample_2.tiff",
|
|
|
|
"RGB",
|
|
|
|
(16, 16),
|
|
|
|
[("raw", (0, 0, 16, 16), 8, ("RGB", 0, 1))],
|
|
|
|
),
|
|
|
|
(
|
|
|
|
"tiff_wrong_bits_per_sample_3.tiff",
|
|
|
|
"RGBA",
|
|
|
|
(512, 256),
|
|
|
|
[("libtiff", (0, 0, 512, 256), 0, ("RGBA", "tiff_lzw", False, 48782))],
|
|
|
|
),
|
|
|
|
],
|
|
|
|
)
|
2024-02-07 11:16:28 +03:00
|
|
|
def test_wrong_bits_per_sample(
|
2024-07-05 20:56:24 +03:00
|
|
|
self,
|
|
|
|
file_name: str,
|
|
|
|
mode: str,
|
|
|
|
size: tuple[int, int],
|
|
|
|
tile: list[tuple[str, tuple[int, int, int, int], int, tuple[Any, ...]]],
|
2024-02-07 11:16:28 +03:00
|
|
|
) -> None:
|
2023-01-29 09:29:36 +03:00
|
|
|
with Image.open("Tests/images/" + file_name) as im:
|
|
|
|
assert im.mode == mode
|
|
|
|
assert im.size == size
|
|
|
|
assert im.tile == tile
|
|
|
|
im.load()
|
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_no_rows_per_strip(self) -> None:
|
2019-09-18 15:07:17 +03:00
|
|
|
# This image does not have a RowsPerStrip TIFF tag
|
|
|
|
infile = "Tests/images/no_rows_per_strip.tif"
|
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(infile) as im:
|
|
|
|
im.load()
|
2020-02-22 16:06:21 +03:00
|
|
|
assert im.size == (950, 975)
|
2019-09-20 22:59:29 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_orientation(self) -> None:
|
2019-11-25 23:03:23 +03:00
|
|
|
with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
|
|
|
|
for i in range(2, 9):
|
|
|
|
with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
|
2023-09-08 07:09:47 +03:00
|
|
|
assert 274 in im.tag_v2
|
|
|
|
|
2019-11-25 23:03:23 +03:00
|
|
|
im.load()
|
2023-09-08 07:09:47 +03:00
|
|
|
assert 274 not in im.tag_v2
|
2019-09-13 15:36:26 +03:00
|
|
|
|
2020-01-30 17:56:07 +03:00
|
|
|
assert_image_similar(base_im, im, 0.7)
|
2019-09-29 14:06:11 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_exif_transpose(self) -> None:
|
2023-09-08 01:23:35 +03:00
|
|
|
with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
|
|
|
|
for i in range(2, 9):
|
|
|
|
with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
|
|
|
|
im = ImageOps.exif_transpose(im)
|
|
|
|
|
|
|
|
assert_image_similar(base_im, im, 0.7)
|
|
|
|
|
2024-11-20 14:40:29 +03:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"test_file",
|
|
|
|
[
|
|
|
|
"Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif",
|
|
|
|
"Tests/images/old-style-jpeg-compression.tif",
|
|
|
|
],
|
|
|
|
)
|
|
|
|
def test_buffering(self, test_file: str) -> None:
|
|
|
|
# load exif first
|
|
|
|
with Image.open(open(test_file, "rb", buffering=1048576)) as im:
|
|
|
|
exif = dict(im.getexif())
|
|
|
|
|
|
|
|
# load image before exif
|
|
|
|
with Image.open(open(test_file, "rb", buffering=1048576)) as im2:
|
|
|
|
im2.load()
|
|
|
|
exif_after_load = dict(im2.getexif())
|
|
|
|
|
|
|
|
assert exif == exif_after_load
|
|
|
|
|
2021-01-07 16:50:25 +03:00
|
|
|
@pytest.mark.valgrind_known_error(reason="Backtrace in Python Core")
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_sampleformat_not_corrupted(self) -> None:
|
2019-09-29 14:06:11 +03:00
|
|
|
# Assert that a TIFF image with SampleFormat=UINT tag is not corrupted
|
|
|
|
# when saving to a new file.
|
|
|
|
# Pillow 6.0 fails with "OSError: cannot identify image file".
|
|
|
|
tiff = io.BytesIO(
|
|
|
|
base64.b64decode(
|
|
|
|
b"SUkqAAgAAAAPAP4ABAABAAAAAAAAAAABBAABAAAAAQAAAAEBBAABAAAAAQAA"
|
|
|
|
b"AAIBAwADAAAAwgAAAAMBAwABAAAACAAAAAYBAwABAAAAAgAAABEBBAABAAAA"
|
|
|
|
b"4AAAABUBAwABAAAAAwAAABYBBAABAAAAAQAAABcBBAABAAAACwAAABoBBQAB"
|
|
|
|
b"AAAAyAAAABsBBQABAAAA0AAAABwBAwABAAAAAQAAACgBAwABAAAAAQAAAFMB"
|
|
|
|
b"AwADAAAA2AAAAAAAAAAIAAgACAABAAAAAQAAAAEAAAABAAAAAQABAAEAAAB4"
|
|
|
|
b"nGNgYAAAAAMAAQ=="
|
|
|
|
)
|
|
|
|
)
|
|
|
|
out = io.BytesIO()
|
|
|
|
with Image.open(tiff) as im:
|
|
|
|
im.save(out, format="tiff")
|
|
|
|
out.seek(0)
|
|
|
|
with Image.open(out) as im:
|
|
|
|
im.load()
|
2020-01-01 08:38:37 +03:00
|
|
|
|
2024-04-18 10:57:40 +03:00
|
|
|
def test_realloc_overflow(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
|
monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True)
|
2020-01-01 08:38:37 +03:00
|
|
|
with Image.open("Tests/images/tiff_overflow_rows_per_strip.tif") as im:
|
2020-04-07 09:58:21 +03:00
|
|
|
with pytest.raises(OSError) as e:
|
2020-01-01 08:38:37 +03:00
|
|
|
im.load()
|
|
|
|
|
|
|
|
# Assert that the error code is IMAGING_CODEC_MEMORY
|
2020-02-22 16:06:21 +03:00
|
|
|
assert str(e.value) == "-9"
|
2021-06-03 15:53:41 +03:00
|
|
|
|
2021-07-07 12:16:44 +03:00
|
|
|
@pytest.mark.parametrize("compression", ("tiff_adobe_deflate", "jpeg"))
|
2024-02-07 11:16:28 +03:00
|
|
|
def test_save_multistrip(self, compression: str, tmp_path: Path) -> None:
|
2021-06-03 15:53:41 +03:00
|
|
|
im = hopper("RGB").resize((256, 256))
|
|
|
|
out = str(tmp_path / "temp.tif")
|
2021-07-07 12:16:44 +03:00
|
|
|
im.save(out, compression=compression)
|
2021-06-03 15:53:41 +03:00
|
|
|
|
|
|
|
with Image.open(out) as im:
|
|
|
|
# Assert that there are multiple strips
|
2024-03-02 05:12:17 +03:00
|
|
|
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
2021-06-03 15:53:41 +03:00
|
|
|
assert len(im.tag_v2[STRIPOFFSETS]) > 1
|
2021-10-08 04:53:53 +03:00
|
|
|
|
2022-08-01 14:41:17 +03:00
|
|
|
@pytest.mark.parametrize("argument", (True, False))
|
2024-02-07 11:16:28 +03:00
|
|
|
def test_save_single_strip(self, argument: bool, tmp_path: Path) -> None:
|
2021-10-08 04:53:53 +03:00
|
|
|
im = hopper("RGB").resize((256, 256))
|
|
|
|
out = str(tmp_path / "temp.tif")
|
|
|
|
|
2022-08-01 14:41:17 +03:00
|
|
|
if not argument:
|
|
|
|
TiffImagePlugin.STRIP_SIZE = 2**18
|
2021-10-08 04:53:53 +03:00
|
|
|
try:
|
2024-02-07 11:16:28 +03:00
|
|
|
arguments: dict[str, str | int] = {"compression": "tiff_adobe_deflate"}
|
2022-08-01 14:41:17 +03:00
|
|
|
if argument:
|
|
|
|
arguments["strip_size"] = 2**18
|
2024-07-06 12:17:23 +03:00
|
|
|
im.save(out, "TIFF", **arguments)
|
2021-10-14 00:09:36 +03:00
|
|
|
|
2021-10-08 04:53:53 +03:00
|
|
|
with Image.open(out) as im:
|
2024-03-02 05:12:17 +03:00
|
|
|
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
2021-10-08 04:53:53 +03:00
|
|
|
assert len(im.tag_v2[STRIPOFFSETS]) == 1
|
|
|
|
finally:
|
|
|
|
TiffImagePlugin.STRIP_SIZE = 65536
|
2021-10-13 15:50:23 +03:00
|
|
|
|
2021-10-08 05:12:21 +03:00
|
|
|
@pytest.mark.parametrize("compression", ("tiff_adobe_deflate", None))
|
2024-02-07 11:16:28 +03:00
|
|
|
def test_save_zero(self, compression: str | None, tmp_path: Path) -> None:
|
2021-10-08 05:12:21 +03:00
|
|
|
im = Image.new("RGB", (0, 0))
|
|
|
|
out = str(tmp_path / "temp.tif")
|
|
|
|
with pytest.raises(SystemError):
|
|
|
|
im.save(out, compression=compression)
|
2023-01-29 09:18:17 +03:00
|
|
|
|
2024-01-31 12:12:58 +03:00
|
|
|
def test_save_many_compressed(self, tmp_path: Path) -> None:
|
2023-03-04 03:44:45 +03:00
|
|
|
im = hopper()
|
|
|
|
out = str(tmp_path / "temp.tif")
|
|
|
|
for _ in range(10000):
|
|
|
|
im.save(out, compression="jpeg")
|
2023-03-12 14:30:15 +03:00
|
|
|
|
2023-01-29 09:18:17 +03:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"path, sizes",
|
|
|
|
(
|
|
|
|
("Tests/images/hopper.tif", ()),
|
|
|
|
("Tests/images/child_ifd.tiff", (16, 8)),
|
|
|
|
("Tests/images/child_ifd_jpeg.tiff", (20,)),
|
|
|
|
),
|
|
|
|
)
|
2024-02-07 11:16:28 +03:00
|
|
|
def test_get_child_images(self, path: str, sizes: tuple[int, ...]) -> None:
|
2023-01-29 09:18:17 +03:00
|
|
|
with Image.open(path) as im:
|
|
|
|
ims = im.get_child_images()
|
|
|
|
|
|
|
|
assert len(ims) == len(sizes)
|
|
|
|
for i, im in enumerate(ims):
|
|
|
|
w = sizes[i]
|
|
|
|
expected = Image.new("RGB", (w, w), "#f00")
|
|
|
|
assert_image_similar(im, expected, 1)
|