mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-25 17:36:18 +03:00
989c9b303a
(cherry picked from commit 25ce233edf732edb5660f877365379377a64f136)
897 lines
32 KiB
Python
897 lines
32 KiB
Python
import base64
|
|
import io
|
|
import itertools
|
|
import os
|
|
import re
|
|
from collections import namedtuple
|
|
from ctypes import c_float
|
|
|
|
import pytest
|
|
|
|
from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags, features
|
|
|
|
from .helper import (
|
|
assert_image_equal,
|
|
assert_image_equal_tofile,
|
|
assert_image_similar,
|
|
assert_image_similar_tofile,
|
|
hopper,
|
|
is_big_endian,
|
|
skip_unless_feature,
|
|
)
|
|
|
|
|
|
@skip_unless_feature("libtiff")
|
|
class LibTiffTestCase:
|
|
def _assert_noerr(self, tmp_path, im):
|
|
"""Helper tests that assert basic sanity about the g4 tiff reading"""
|
|
# 1 bit
|
|
assert im.mode == "1"
|
|
|
|
# Does the data actually load
|
|
im.load()
|
|
im.getdata()
|
|
|
|
try:
|
|
assert im._compression == "group4"
|
|
except AttributeError:
|
|
print("No _compression")
|
|
print(dir(im))
|
|
|
|
# can we write it back out, in a different form.
|
|
out = str(tmp_path / "temp.png")
|
|
im.save(out)
|
|
|
|
out_bytes = io.BytesIO()
|
|
im.save(out_bytes, format="tiff", compression="group4")
|
|
|
|
|
|
class TestFileLibTiff(LibTiffTestCase):
|
|
def test_version(self):
|
|
assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("libtiff"))
|
|
|
|
def test_g4_tiff(self, tmp_path):
|
|
"""Test the ordinary file path load path"""
|
|
|
|
test_file = "Tests/images/hopper_g4_500.tif"
|
|
with Image.open(test_file) as im:
|
|
assert im.size == (500, 500)
|
|
self._assert_noerr(tmp_path, im)
|
|
|
|
def test_g4_large(self, tmp_path):
|
|
test_file = "Tests/images/pport_g4.tif"
|
|
with Image.open(test_file) as im:
|
|
self._assert_noerr(tmp_path, im)
|
|
|
|
def test_g4_tiff_file(self, tmp_path):
|
|
"""Testing the string load path"""
|
|
|
|
test_file = "Tests/images/hopper_g4_500.tif"
|
|
with open(test_file, "rb") as f:
|
|
with Image.open(f) as im:
|
|
assert im.size == (500, 500)
|
|
self._assert_noerr(tmp_path, im)
|
|
|
|
def test_g4_tiff_bytesio(self, tmp_path):
|
|
"""Testing the stringio loading code path"""
|
|
test_file = "Tests/images/hopper_g4_500.tif"
|
|
s = io.BytesIO()
|
|
with open(test_file, "rb") as f:
|
|
s.write(f.read())
|
|
s.seek(0)
|
|
with Image.open(s) as im:
|
|
assert im.size == (500, 500)
|
|
self._assert_noerr(tmp_path, im)
|
|
|
|
def test_g4_non_disk_file_object(self, tmp_path):
|
|
"""Testing loading from non-disk non-BytesIO file object"""
|
|
test_file = "Tests/images/hopper_g4_500.tif"
|
|
s = io.BytesIO()
|
|
with open(test_file, "rb") as f:
|
|
s.write(f.read())
|
|
s.seek(0)
|
|
r = io.BufferedReader(s)
|
|
with Image.open(r) as im:
|
|
assert im.size == (500, 500)
|
|
self._assert_noerr(tmp_path, im)
|
|
|
|
def test_g4_eq_png(self):
|
|
""" Checking that we're actually getting the data that we expect"""
|
|
with Image.open("Tests/images/hopper_bw_500.png") as png:
|
|
with Image.open("Tests/images/hopper_g4_500.tif") as g4:
|
|
assert_image_equal(g4, png)
|
|
|
|
# see https://github.com/python-pillow/Pillow/issues/279
|
|
def test_g4_fillorder_eq_png(self):
|
|
""" Checking that we're actually getting the data that we expect"""
|
|
with Image.open("Tests/images/g4-fillorder-test.png") as png:
|
|
with Image.open("Tests/images/g4-fillorder-test.tif") as g4:
|
|
assert_image_equal(g4, png)
|
|
|
|
def test_g4_write(self, tmp_path):
|
|
"""Checking to see that the saved image is the same as what we wrote"""
|
|
test_file = "Tests/images/hopper_g4_500.tif"
|
|
with Image.open(test_file) as orig:
|
|
out = str(tmp_path / "temp.tif")
|
|
rot = orig.transpose(Image.ROTATE_90)
|
|
assert rot.size == (500, 500)
|
|
rot.save(out)
|
|
|
|
with Image.open(out) as reread:
|
|
assert reread.size == (500, 500)
|
|
self._assert_noerr(tmp_path, reread)
|
|
assert_image_equal(reread, rot)
|
|
assert reread.info["compression"] == "group4"
|
|
|
|
assert reread.info["compression"] == orig.info["compression"]
|
|
|
|
assert orig.tobytes() != reread.tobytes()
|
|
|
|
def test_adobe_deflate_tiff(self):
|
|
test_file = "Tests/images/tiff_adobe_deflate.tif"
|
|
with Image.open(test_file) as im:
|
|
assert im.mode == "RGB"
|
|
assert im.size == (278, 374)
|
|
assert im.tile[0][:3] == ("libtiff", (0, 0, 278, 374), 0)
|
|
im.load()
|
|
|
|
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
|
|
|
|
def test_write_metadata(self, tmp_path):
|
|
""" Test metadata writing through libtiff """
|
|
for legacy_api in [False, True]:
|
|
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 (
|
|
c_float(val[0][0] / val[0][1]).value
|
|
== c_float(value[0][0] / value[0][1]).value
|
|
), f"{tag} didn't roundtrip"
|
|
else:
|
|
assert (
|
|
c_float(val).value == c_float(value).value
|
|
), f"{tag} didn't roundtrip"
|
|
else:
|
|
assert val == value, f"{tag} didn't roundtrip"
|
|
|
|
# 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"
|
|
|
|
def test_additional_metadata(self, tmp_path):
|
|
# 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.
|
|
|
|
# Get the list of the ones that we should be able to write
|
|
|
|
core_items = {
|
|
tag: info
|
|
for tag, info in ((s, TiffTags.lookup(s)) for s in TiffTags.LIBTIFF_CORE)
|
|
if info.type is not None
|
|
}
|
|
|
|
# Exclude ones that have special meaning
|
|
# that we're already testing them
|
|
with Image.open("Tests/images/hopper_g4.tif") as im:
|
|
for tag in im.tag_v2:
|
|
try:
|
|
del core_items[tag]
|
|
except KeyError:
|
|
pass
|
|
del core_items[320] # colormap is special, tested below
|
|
|
|
# Type codes:
|
|
# 2: "ascii",
|
|
# 3: "short",
|
|
# 4: "long",
|
|
# 5: "rational",
|
|
# 12: "double",
|
|
# Type: dummy value
|
|
values = {
|
|
2: "test",
|
|
3: 1,
|
|
4: 2 ** 20,
|
|
5: TiffImagePlugin.IFDRational(100, 1),
|
|
12: 1.05,
|
|
}
|
|
|
|
new_ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
|
for tag, info in core_items.items():
|
|
if info.length == 1:
|
|
new_ifd[tag] = values[info.type]
|
|
if info.length == 0:
|
|
new_ifd[tag] = tuple(values[info.type] for _ in range(3))
|
|
else:
|
|
new_ifd[tag] = tuple(values[info.type] for _ in range(info.length))
|
|
|
|
# Extra samples really doesn't make sense in this application.
|
|
del new_ifd[338]
|
|
|
|
out = str(tmp_path / "temp.tif")
|
|
TiffImagePlugin.WRITE_LIBTIFF = True
|
|
|
|
im.save(out, tiffinfo=new_ifd)
|
|
|
|
TiffImagePlugin.WRITE_LIBTIFF = False
|
|
|
|
def test_custom_metadata(self, tmp_path):
|
|
tc = namedtuple("test_case", "value,type,supported_by_default")
|
|
custom = {
|
|
37000 + k: v
|
|
for k, v in enumerate(
|
|
[
|
|
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(
|
|
(-123456789, 9, 34, 234, 219387, -92432323),
|
|
TiffTags.SIGNED_LONG,
|
|
False,
|
|
),
|
|
tc((4.25, 5.25), TiffTags.FLOAT, True),
|
|
tc((4.25, 5.25), TiffTags.DOUBLE, True),
|
|
# array of TIFF_BYTE requires bytes instead of tuple for backwards
|
|
# compatibility
|
|
tc(bytes([4]), TiffTags.BYTE, True),
|
|
tc(bytes((4, 9, 10)), TiffTags.BYTE, True),
|
|
]
|
|
)
|
|
}
|
|
|
|
libtiffs = [False]
|
|
if Image.core.libtiff_support_custom_tags:
|
|
libtiffs.append(True)
|
|
|
|
for libtiff in libtiffs:
|
|
TiffImagePlugin.WRITE_LIBTIFF = libtiff
|
|
|
|
def check_tags(tiffinfo):
|
|
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
|
|
}
|
|
)
|
|
TiffImagePlugin.WRITE_LIBTIFF = False
|
|
|
|
def test_xmlpacket_tag(self, tmp_path):
|
|
TiffImagePlugin.WRITE_LIBTIFF = True
|
|
|
|
out = str(tmp_path / "temp.tif")
|
|
hopper().save(out, tiffinfo={700: b"xmlpacket tag"})
|
|
TiffImagePlugin.WRITE_LIBTIFF = False
|
|
|
|
with Image.open(out) as reloaded:
|
|
if 700 in reloaded.tag_v2:
|
|
assert reloaded.tag_v2[700] == b"xmlpacket tag"
|
|
|
|
def test_int_dpi(self, tmp_path):
|
|
# issue #1765
|
|
im = hopper("RGB")
|
|
out = str(tmp_path / "temp.tif")
|
|
TiffImagePlugin.WRITE_LIBTIFF = True
|
|
im.save(out, dpi=(72, 72))
|
|
TiffImagePlugin.WRITE_LIBTIFF = False
|
|
with Image.open(out) as reloaded:
|
|
assert reloaded.info["dpi"] == (72.0, 72.0)
|
|
|
|
def test_g3_compression(self, tmp_path):
|
|
with Image.open("Tests/images/hopper_g4_500.tif") as i:
|
|
out = str(tmp_path / "temp.tif")
|
|
i.save(out, compression="group3")
|
|
|
|
with Image.open(out) as reread:
|
|
assert reread.info["compression"] == "group3"
|
|
assert_image_equal(reread, i)
|
|
|
|
def test_little_endian(self, tmp_path):
|
|
with Image.open("Tests/images/16bit.deflate.tif") as im:
|
|
assert im.getpixel((0, 0)) == 480
|
|
assert im.mode == "I;16"
|
|
|
|
b = im.tobytes()
|
|
# Bytes are in image native order (little endian)
|
|
assert b[0] == ord(b"\xe0")
|
|
assert b[1] == ord(b"\x01")
|
|
|
|
out = str(tmp_path / "temp.tif")
|
|
# out = "temp.le.tif"
|
|
im.save(out)
|
|
with Image.open(out) as reread:
|
|
assert reread.info["compression"] == im.info["compression"]
|
|
assert reread.getpixel((0, 0)) == 480
|
|
# UNDONE - libtiff defaults to writing in native endian, so
|
|
# on big endian, we'll get back mode = 'I;16B' here.
|
|
|
|
def test_big_endian(self, tmp_path):
|
|
with Image.open("Tests/images/16bit.MM.deflate.tif") as im:
|
|
assert im.getpixel((0, 0)) == 480
|
|
assert im.mode == "I;16B"
|
|
|
|
b = im.tobytes()
|
|
|
|
# Bytes are in image native order (big endian)
|
|
assert b[0] == ord(b"\x01")
|
|
assert b[1] == ord(b"\xe0")
|
|
|
|
out = str(tmp_path / "temp.tif")
|
|
im.save(out)
|
|
with Image.open(out) as reread:
|
|
assert reread.info["compression"] == im.info["compression"]
|
|
assert reread.getpixel((0, 0)) == 480
|
|
|
|
def test_g4_string_info(self, tmp_path):
|
|
"""Tests String data in info directory"""
|
|
test_file = "Tests/images/hopper_g4_500.tif"
|
|
with Image.open(test_file) as orig:
|
|
out = str(tmp_path / "temp.tif")
|
|
|
|
orig.tag[269] = "temp.tif"
|
|
orig.save(out)
|
|
|
|
with Image.open(out) as reread:
|
|
assert "temp.tif" == reread.tag_v2[269]
|
|
assert "temp.tif" == reread.tag[269][0]
|
|
|
|
def test_12bit_rawmode(self):
|
|
"""Are we generating the same interpretation
|
|
of the image as Imagemagick is?"""
|
|
TiffImagePlugin.READ_LIBTIFF = True
|
|
with Image.open("Tests/images/12bit.cropped.tif") as im:
|
|
im.load()
|
|
TiffImagePlugin.READ_LIBTIFF = False
|
|
# 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.
|
|
|
|
assert_image_equal_tofile(im, "Tests/images/12in16bit.tif")
|
|
|
|
def test_blur(self, tmp_path):
|
|
# test case from irc, how to do blur on b/w image
|
|
# and save to compressed tif.
|
|
out = str(tmp_path / "temp.tif")
|
|
with Image.open("Tests/images/pport_g4.tif") as im:
|
|
im = im.convert("L")
|
|
|
|
im = im.filter(ImageFilter.GaussianBlur(4))
|
|
im.save(out, compression="tiff_adobe_deflate")
|
|
|
|
with Image.open(out) as im2:
|
|
im2.load()
|
|
|
|
assert_image_equal(im, im2)
|
|
|
|
def test_compressions(self, tmp_path):
|
|
# Test various tiff compressions and assert similar image content but reduced
|
|
# file sizes.
|
|
im = hopper("RGB")
|
|
out = str(tmp_path / "temp.tif")
|
|
im.save(out)
|
|
size_raw = os.path.getsize(out)
|
|
|
|
for compression in ("packbits", "tiff_lzw"):
|
|
im.save(out, compression=compression)
|
|
size_compressed = os.path.getsize(out)
|
|
with Image.open(out) as im2:
|
|
assert_image_equal(im, im2)
|
|
|
|
im.save(out, compression="jpeg")
|
|
size_jpeg = os.path.getsize(out)
|
|
with Image.open(out) as im2:
|
|
assert_image_similar(im, im2, 30)
|
|
|
|
im.save(out, compression="jpeg", quality=30)
|
|
size_jpeg_30 = os.path.getsize(out)
|
|
with Image.open(out) as im3:
|
|
assert_image_similar(im2, im3, 30)
|
|
|
|
assert size_raw > size_compressed
|
|
assert size_compressed > size_jpeg
|
|
assert size_jpeg > size_jpeg_30
|
|
|
|
def test_tiff_jpeg_compression(self, tmp_path):
|
|
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"
|
|
|
|
def test_quality(self, tmp_path):
|
|
im = hopper("RGB")
|
|
out = str(tmp_path / "temp.tif")
|
|
|
|
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")
|
|
im.save(out, compression="jpeg", quality=0)
|
|
im.save(out, compression="jpeg", quality=100)
|
|
|
|
def test_cmyk_save(self, tmp_path):
|
|
im = hopper("CMYK")
|
|
out = str(tmp_path / "temp.tif")
|
|
|
|
im.save(out, compression="tiff_adobe_deflate")
|
|
with Image.open(out) as im2:
|
|
assert_image_equal(im, im2)
|
|
|
|
def test_palette_save(self, tmp_path):
|
|
im = hopper("P")
|
|
out = str(tmp_path / "temp.tif")
|
|
|
|
TiffImagePlugin.WRITE_LIBTIFF = True
|
|
im.save(out)
|
|
TiffImagePlugin.WRITE_LIBTIFF = False
|
|
|
|
with Image.open(out) as reloaded:
|
|
# colormap/palette tag
|
|
assert len(reloaded.tag_v2[320]) == 768
|
|
|
|
def xtest_bw_compression_w_rgb(self, tmp_path):
|
|
"""This test passes, but when running all tests causes a failure due
|
|
to output on stderr from the error thrown by libtiff. We need to
|
|
capture that but not now"""
|
|
|
|
im = hopper("RGB")
|
|
out = str(tmp_path / "temp.tif")
|
|
|
|
with pytest.raises(OSError):
|
|
im.save(out, compression="tiff_ccitt")
|
|
with pytest.raises(OSError):
|
|
im.save(out, compression="group3")
|
|
with pytest.raises(OSError):
|
|
im.save(out, compression="group4")
|
|
|
|
def test_fp_leak(self):
|
|
im = Image.open("Tests/images/hopper_g4_500.tif")
|
|
fn = im.fp.fileno()
|
|
|
|
os.fstat(fn)
|
|
im.load() # this should close it.
|
|
with pytest.raises(OSError):
|
|
os.fstat(fn)
|
|
im = None # this should force even more closed.
|
|
with pytest.raises(OSError):
|
|
os.fstat(fn)
|
|
with pytest.raises(OSError):
|
|
os.close(fn)
|
|
|
|
def test_multipage(self):
|
|
# issue #862
|
|
TiffImagePlugin.READ_LIBTIFF = True
|
|
with Image.open("Tests/images/multipage.tiff") as im:
|
|
# file is a multipage tiff, 10x10 green, 10x10 red, 20x20 blue
|
|
|
|
im.seek(0)
|
|
assert im.size == (10, 10)
|
|
assert im.convert("RGB").getpixel((0, 0)) == (0, 128, 0)
|
|
assert im.tag.next
|
|
|
|
im.seek(1)
|
|
assert im.size == (10, 10)
|
|
assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0)
|
|
assert im.tag.next
|
|
|
|
im.seek(2)
|
|
assert not im.tag.next
|
|
assert im.size == (20, 20)
|
|
assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 255)
|
|
|
|
TiffImagePlugin.READ_LIBTIFF = False
|
|
|
|
def test_multipage_nframes(self):
|
|
# issue #862
|
|
TiffImagePlugin.READ_LIBTIFF = True
|
|
with Image.open("Tests/images/multipage.tiff") as im:
|
|
frames = im.n_frames
|
|
assert frames == 3
|
|
for _ in range(frames):
|
|
im.seek(0)
|
|
# Should not raise ValueError: I/O operation on closed file
|
|
im.load()
|
|
|
|
TiffImagePlugin.READ_LIBTIFF = False
|
|
|
|
def test__next(self):
|
|
TiffImagePlugin.READ_LIBTIFF = True
|
|
with Image.open("Tests/images/hopper.tif") as im:
|
|
assert not im.tag.next
|
|
im.load()
|
|
assert not im.tag.next
|
|
|
|
def test_4bit(self):
|
|
# Arrange
|
|
test_file = "Tests/images/hopper_gray_4bpp.tif"
|
|
original = hopper("L")
|
|
|
|
# Act
|
|
TiffImagePlugin.READ_LIBTIFF = True
|
|
with Image.open(test_file) as im:
|
|
TiffImagePlugin.READ_LIBTIFF = False
|
|
|
|
# Assert
|
|
assert im.size == (128, 128)
|
|
assert im.mode == "L"
|
|
assert_image_similar(im, original, 7.3)
|
|
|
|
def test_gray_semibyte_per_pixel(self):
|
|
test_files = (
|
|
(
|
|
24.8, # epsilon
|
|
( # group
|
|
"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",
|
|
),
|
|
),
|
|
(
|
|
7.3, # epsilon
|
|
( # group
|
|
"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",
|
|
),
|
|
),
|
|
)
|
|
original = hopper("L")
|
|
for epsilon, group in test_files:
|
|
with Image.open(group[0]) as im:
|
|
assert im.size == (128, 128)
|
|
assert im.mode == "L"
|
|
assert_image_similar(im, original, epsilon)
|
|
for file in group[1:]:
|
|
with Image.open(file) as im2:
|
|
assert im2.size == (128, 128)
|
|
assert im2.mode == "L"
|
|
assert_image_equal(im, im2)
|
|
|
|
def test_save_bytesio(self):
|
|
# PR 1011
|
|
# Test TIFF saving to io.BytesIO() object.
|
|
|
|
TiffImagePlugin.WRITE_LIBTIFF = True
|
|
TiffImagePlugin.READ_LIBTIFF = True
|
|
|
|
# Generate test image
|
|
pilim = hopper()
|
|
|
|
def save_bytesio(compression=None):
|
|
|
|
buffer_io = io.BytesIO()
|
|
pilim.save(buffer_io, format="tiff", compression=compression)
|
|
buffer_io.seek(0)
|
|
|
|
with Image.open(buffer_io) as pilim_load:
|
|
assert_image_similar(pilim, pilim_load, 0)
|
|
|
|
save_bytesio()
|
|
save_bytesio("raw")
|
|
save_bytesio("packbits")
|
|
save_bytesio("tiff_lzw")
|
|
|
|
TiffImagePlugin.WRITE_LIBTIFF = False
|
|
TiffImagePlugin.READ_LIBTIFF = False
|
|
|
|
def test_crashing_metadata(self, tmp_path):
|
|
# issue 1597
|
|
with Image.open("Tests/images/rdf.tif") as im:
|
|
out = str(tmp_path / "temp.tif")
|
|
|
|
TiffImagePlugin.WRITE_LIBTIFF = True
|
|
# this shouldn't crash
|
|
im.save(out, format="TIFF")
|
|
TiffImagePlugin.WRITE_LIBTIFF = False
|
|
|
|
def test_page_number_x_0(self, tmp_path):
|
|
# 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.
|
|
outfile = str(tmp_path / "temp.tif")
|
|
# 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"
|
|
with Image.open(infile) as im:
|
|
# Should not divide by zero
|
|
im.save(outfile)
|
|
|
|
def test_fd_duplication(self, tmp_path):
|
|
# https://github.com/python-pillow/Pillow/issues/1651
|
|
|
|
tmpfile = str(tmp_path / "temp.tif")
|
|
with open(tmpfile, "wb") as f:
|
|
with open("Tests/images/g4-multi.tiff", "rb") as src:
|
|
f.write(src.read())
|
|
|
|
im = Image.open(tmpfile)
|
|
im.n_frames
|
|
im.close()
|
|
# Should not raise PermissionError.
|
|
os.remove(tmpfile)
|
|
|
|
def test_read_icc(self):
|
|
with Image.open("Tests/images/hopper.iccprofile.tif") as img:
|
|
icc = img.info.get("icc_profile")
|
|
assert icc is not None
|
|
TiffImagePlugin.READ_LIBTIFF = True
|
|
with Image.open("Tests/images/hopper.iccprofile.tif") as img:
|
|
icc_libtiff = img.info.get("icc_profile")
|
|
assert icc_libtiff is not None
|
|
TiffImagePlugin.READ_LIBTIFF = False
|
|
assert icc == icc_libtiff
|
|
|
|
def test_write_icc(self, tmp_path):
|
|
def check_write(libtiff):
|
|
TiffImagePlugin.WRITE_LIBTIFF = libtiff
|
|
|
|
with Image.open("Tests/images/hopper.iccprofile.tif") as img:
|
|
icc_profile = img.info["icc_profile"]
|
|
|
|
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"]
|
|
|
|
libtiffs = []
|
|
if Image.core.libtiff_support_custom_tags:
|
|
libtiffs.append(True)
|
|
libtiffs.append(False)
|
|
|
|
for libtiff in libtiffs:
|
|
check_write(libtiff)
|
|
|
|
def test_multipage_compression(self):
|
|
with Image.open("Tests/images/compression.tif") as im:
|
|
|
|
im.seek(0)
|
|
assert im._compression == "tiff_ccitt"
|
|
assert im.size == (10, 10)
|
|
|
|
im.seek(1)
|
|
assert im._compression == "packbits"
|
|
assert im.size == (10, 10)
|
|
im.load()
|
|
|
|
im.seek(0)
|
|
assert im._compression == "tiff_ccitt"
|
|
assert im.size == (10, 10)
|
|
im.load()
|
|
|
|
def test_save_tiff_with_jpegtables(self, tmp_path):
|
|
# Arrange
|
|
outfile = str(tmp_path / "temp.tif")
|
|
|
|
# Created with ImageMagick: convert hopper.jpg hopper_jpg.tif
|
|
# Contains JPEGTables (347) tag
|
|
infile = "Tests/images/hopper_jpg.tif"
|
|
with Image.open(infile) as im:
|
|
# Act / Assert
|
|
# Should not raise UnicodeDecodeError or anything else
|
|
im.save(outfile)
|
|
|
|
def test_16bit_RGB_tiff(self):
|
|
with Image.open("Tests/images/tiff_16bit_RGB.tiff") as im:
|
|
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),
|
|
)
|
|
]
|
|
im.load()
|
|
|
|
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png")
|
|
|
|
def test_16bit_RGBa_tiff(self):
|
|
with Image.open("Tests/images/tiff_16bit_RGBa.tiff") as im:
|
|
assert im.mode == "RGBA"
|
|
assert im.size == (100, 40)
|
|
assert im.tile, [
|
|
("libtiff", (0, 0, 100, 40), 0, ("RGBa;16N", "tiff_lzw", False, 38236))
|
|
]
|
|
im.load()
|
|
|
|
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png")
|
|
|
|
@skip_unless_feature("jpg")
|
|
def test_gimp_tiff(self):
|
|
# Read TIFF JPEG images from GIMP [@PIL168]
|
|
filename = "Tests/images/pil168.tif"
|
|
with Image.open(filename) as im:
|
|
assert im.mode == "RGB"
|
|
assert im.size == (256, 256)
|
|
assert im.tile == [
|
|
("libtiff", (0, 0, 256, 256), 0, ("RGB", "jpeg", False, 5122))
|
|
]
|
|
im.load()
|
|
|
|
assert_image_equal_tofile(im, "Tests/images/pil168.png")
|
|
|
|
def test_sampleformat(self):
|
|
# https://github.com/python-pillow/Pillow/issues/1466
|
|
with Image.open("Tests/images/copyleft.tiff") as im:
|
|
assert im.mode == "RGB"
|
|
|
|
assert_image_equal_tofile(im, "Tests/images/copyleft.png", mode="RGB")
|
|
|
|
def test_lzw(self):
|
|
with Image.open("Tests/images/hopper_lzw.tif") as im:
|
|
assert im.mode == "RGB"
|
|
assert im.size == (128, 128)
|
|
assert im.format == "TIFF"
|
|
im2 = hopper()
|
|
assert_image_similar(im, im2, 5)
|
|
|
|
def test_strip_cmyk_jpeg(self):
|
|
infile = "Tests/images/tiff_strip_cmyk_jpeg.tif"
|
|
with Image.open(infile) as im:
|
|
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
|
|
|
|
def test_strip_cmyk_16l_jpeg(self):
|
|
infile = "Tests/images/tiff_strip_cmyk_16l_jpeg.tif"
|
|
with Image.open(infile) as im:
|
|
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
|
|
|
|
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
|
|
def test_strip_ycbcr_jpeg_2x2_sampling(self):
|
|
infile = "Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif"
|
|
with Image.open(infile) as im:
|
|
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5)
|
|
|
|
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
|
|
def test_strip_ycbcr_jpeg_1x1_sampling(self):
|
|
infile = "Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif"
|
|
with Image.open(infile) as im:
|
|
assert_image_equal_tofile(im, "Tests/images/flower2.jpg")
|
|
|
|
def test_tiled_cmyk_jpeg(self):
|
|
infile = "Tests/images/tiff_tiled_cmyk_jpeg.tif"
|
|
with Image.open(infile) as im:
|
|
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
|
|
|
|
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
|
|
def test_tiled_ycbcr_jpeg_1x1_sampling(self):
|
|
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif"
|
|
with Image.open(infile) as im:
|
|
assert_image_equal_tofile(im, "Tests/images/flower2.jpg")
|
|
|
|
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
|
|
def test_tiled_ycbcr_jpeg_2x2_sampling(self):
|
|
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif"
|
|
with Image.open(infile) as im:
|
|
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5)
|
|
|
|
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
|
|
def test_old_style_jpeg(self):
|
|
infile = "Tests/images/old-style-jpeg-compression.tif"
|
|
with Image.open(infile) as im:
|
|
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
|
|
|
|
def test_no_rows_per_strip(self):
|
|
# This image does not have a RowsPerStrip TIFF tag
|
|
infile = "Tests/images/no_rows_per_strip.tif"
|
|
with Image.open(infile) as im:
|
|
im.load()
|
|
assert im.size == (950, 975)
|
|
|
|
def test_orientation(self):
|
|
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.load()
|
|
|
|
assert_image_similar(base_im, im, 0.7)
|
|
|
|
def test_sampleformat_not_corrupted(self):
|
|
# 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()
|
|
|
|
def test_realloc_overflow(self):
|
|
TiffImagePlugin.READ_LIBTIFF = True
|
|
with Image.open("Tests/images/tiff_overflow_rows_per_strip.tif") as im:
|
|
with pytest.raises(OSError) as e:
|
|
im.load()
|
|
|
|
# Assert that the error code is IMAGING_CODEC_MEMORY
|
|
assert str(e.value) == "-9"
|
|
TiffImagePlugin.READ_LIBTIFF = False
|