mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-30 19:24:34 +03:00
Restore legacy TIFF API.
To have the old API that always returns tuples, and fractions as pairs, set the `legacy_api` attribute of the IFD to True. This should alleviate concerns about backwards compatibility.
This commit is contained in:
parent
38f7e23495
commit
93abbd0caa
|
@ -247,10 +247,8 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
* self._tagdata = {} Key: numerical tiff tag number
|
* self._tagdata = {} Key: numerical tiff tag number
|
||||||
Value: undecoded byte string from file
|
Value: undecoded byte string from file
|
||||||
|
|
||||||
Tags will be found in either self._tags or self._tagdata, but not
|
Tags will be found in the private attributes self._tagdata, and in
|
||||||
both. The union of the two should contain all the tags from the Tiff
|
self._tags once decoded.
|
||||||
image file. External classes shouldn't reference these unless they're
|
|
||||||
really sure what they're doing.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None):
|
def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None):
|
||||||
|
@ -275,9 +273,17 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
raise SyntaxError("not a TIFF IFD")
|
raise SyntaxError("not a TIFF IFD")
|
||||||
self.reset()
|
self.reset()
|
||||||
self.next, = self._unpack("L", ifh[4:])
|
self.next, = self._unpack("L", ifh[4:])
|
||||||
|
self._legacy_api = False
|
||||||
|
|
||||||
prefix = property(lambda self: self._prefix)
|
prefix = property(lambda self: self._prefix)
|
||||||
offset = property(lambda self: self._offset)
|
offset = property(lambda self: self._offset)
|
||||||
|
legacy_api = property(lambda self: self._legacy_api)
|
||||||
|
|
||||||
|
@legacy_api.setter
|
||||||
|
def legacy_api(self, value):
|
||||||
|
if value != self._legacy_api:
|
||||||
|
self._tags.clear()
|
||||||
|
self._legacy_api = value
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self._tags = {}
|
self._tags = {}
|
||||||
|
@ -302,18 +308,18 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
for code, value in self.items())
|
for code, value in self.items())
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(self._tagdata) + len(self._tags)
|
return len(set(self._tagdata) | set(self._tags))
|
||||||
|
|
||||||
def __getitem__(self, tag):
|
def __getitem__(self, tag):
|
||||||
try:
|
if tag not in self._tags: # unpack on the fly
|
||||||
return self._tags[tag]
|
|
||||||
except KeyError: # unpack on the fly
|
|
||||||
data = self._tagdata[tag]
|
data = self._tagdata[tag]
|
||||||
typ = self.tagtype[tag]
|
typ = self.tagtype[tag]
|
||||||
size, handler = self._load_dispatch[typ]
|
size, handler = self._load_dispatch[typ]
|
||||||
self[tag] = handler(self, data) # check type
|
self[tag] = handler(self, data) # check type
|
||||||
del self._tagdata[tag]
|
val = self._tags[tag]
|
||||||
return self[tag]
|
if self.legacy_api and not isinstance(val, (tuple, bytes)):
|
||||||
|
val = val,
|
||||||
|
return val
|
||||||
|
|
||||||
def __contains__(self, tag):
|
def __contains__(self, tag):
|
||||||
return tag in self._tags or tag in self._tagdata
|
return tag in self._tags or tag in self._tagdata
|
||||||
|
@ -355,6 +361,8 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
for value in values]
|
for value in values]
|
||||||
values = tuple(info.cvt_enum(value) for value in values)
|
values = tuple(info.cvt_enum(value) for value in values)
|
||||||
if info.length == 1:
|
if info.length == 1:
|
||||||
|
if self.legacy_api and self.tagtype[tag] in [5, 10]:
|
||||||
|
values = values,
|
||||||
self._tags[tag], = values
|
self._tags[tag], = values
|
||||||
else:
|
else:
|
||||||
self._tags[tag] = values
|
self._tags[tag] = values
|
||||||
|
@ -364,7 +372,7 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
self._tagdata.pop(tag, None)
|
self._tagdata.pop(tag, None)
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return itertools.chain(list(self._tags), list(self._tagdata))
|
return iter(set(self._tagdata) | set(self._tags))
|
||||||
|
|
||||||
def _unpack(self, fmt, data):
|
def _unpack(self, fmt, data):
|
||||||
return struct.unpack(self._endian + fmt, data)
|
return struct.unpack(self._endian + fmt, data)
|
||||||
|
@ -398,10 +406,19 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
b"".join(self._pack(fmt, value) for value in values))
|
b"".join(self._pack(fmt, value) for value in values))
|
||||||
|
|
||||||
list(map(_register_basic,
|
list(map(_register_basic,
|
||||||
[(1, "B", "byte"), (3, "H", "short"), (4, "L", "long"),
|
[(3, "H", "short"), (4, "L", "long"),
|
||||||
(6, "b", "signed byte"), (8, "h", "signed short"),
|
(6, "b", "signed byte"), (8, "h", "signed short"),
|
||||||
(9, "l", "signed long"), (11, "f", "float"), (12, "d", "double")]))
|
(9, "l", "signed long"), (11, "f", "float"), (12, "d", "double")]))
|
||||||
|
|
||||||
|
@_register_loader(1, 1) # Basic type, except for the legacy API.
|
||||||
|
def load_byte(self, data):
|
||||||
|
return (data if self.legacy_api else
|
||||||
|
tuple(map(ord, data) if bytes is str else data))
|
||||||
|
|
||||||
|
@_register_writer(1) # Basic type, except for the legacy API.
|
||||||
|
def write_byte(self, data):
|
||||||
|
return data
|
||||||
|
|
||||||
@_register_loader(2, 1)
|
@_register_loader(2, 1)
|
||||||
def load_string(self, data):
|
def load_string(self, data):
|
||||||
if data.endswith(b"\0"):
|
if data.endswith(b"\0"):
|
||||||
|
@ -418,7 +435,9 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
@_register_loader(5, 8)
|
@_register_loader(5, 8)
|
||||||
def load_rational(self, data):
|
def load_rational(self, data):
|
||||||
vals = self._unpack("{0}L".format(len(data) // 4), data)
|
vals = self._unpack("{0}L".format(len(data) // 4), data)
|
||||||
return tuple(num / denom for num, denom in zip(vals[::2], vals[1::2]))
|
combine = lambda a, b: (a, b) if self.legacy_api else a / b
|
||||||
|
return tuple(combine(num, denom)
|
||||||
|
for num, denom in zip(vals[::2], vals[1::2]))
|
||||||
|
|
||||||
@_register_writer(5)
|
@_register_writer(5)
|
||||||
def write_rational(self, *values):
|
def write_rational(self, *values):
|
||||||
|
@ -436,7 +455,9 @@ class ImageFileDirectory(collections.MutableMapping):
|
||||||
@_register_loader(10, 8)
|
@_register_loader(10, 8)
|
||||||
def load_signed_rational(self, data):
|
def load_signed_rational(self, data):
|
||||||
vals = self._unpack("{0}l".format(len(data) // 4), data)
|
vals = self._unpack("{0}l".format(len(data) // 4), data)
|
||||||
return tuple(num / denom for num, denom in zip(vals[::2], vals[1::2]))
|
combine = lambda a, b: (a, b) if self.legacy_api else a / b
|
||||||
|
return tuple(combine(num, denom)
|
||||||
|
for num, denom in zip(vals[::2], vals[1::2]))
|
||||||
|
|
||||||
@_register_writer(10)
|
@_register_writer(10)
|
||||||
def write_signed_rational(self, *values):
|
def write_signed_rational(self, *values):
|
||||||
|
@ -1026,11 +1047,14 @@ def _save(im, fp, filename):
|
||||||
# inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com
|
# inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com
|
||||||
if hasattr(im, 'tag'):
|
if hasattr(im, 'tag'):
|
||||||
# preserve tags from original TIFF image file
|
# preserve tags from original TIFF image file
|
||||||
|
orig_api = im.tag.legacy_api
|
||||||
|
im.tag.legacy_api = False
|
||||||
for key in (RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION,
|
for key in (RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION,
|
||||||
IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, XMP):
|
IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, XMP):
|
||||||
if key in im.tag:
|
if key in im.tag:
|
||||||
ifd[key] = im.tag[key]
|
ifd[key] = im.tag[key]
|
||||||
ifd.tagtype[key] = im.tag.tagtype.get(key, None)
|
ifd.tagtype[key] = im.tag.tagtype.get(key, None)
|
||||||
|
im.tag.legacy_api = orig_api
|
||||||
|
|
||||||
# preserve ICC profile (should also work when saving other formats
|
# preserve ICC profile (should also work when saving other formats
|
||||||
# which support profiles as TIFF) -- 2008-06-06 Florian Hoech
|
# which support profiles as TIFF) -- 2008-06-06 Florian Hoech
|
||||||
|
|
|
@ -43,11 +43,6 @@ class PillowTestCase(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
print("=== orphaned temp file: %s" % path)
|
print("=== orphaned temp file: %s" % path)
|
||||||
|
|
||||||
def assert_almost_equal(self, a, b, msg=None, eps=1e-6):
|
|
||||||
self.assertLess(
|
|
||||||
abs(a-b), eps,
|
|
||||||
msg or "got %r, expected %r" % (a, b))
|
|
||||||
|
|
||||||
def assert_deep_equal(self, a, b, msg=None):
|
def assert_deep_equal(self, a, b, msg=None):
|
||||||
try:
|
try:
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|
|
@ -80,7 +80,7 @@ class TestImage(PillowTestCase):
|
||||||
ret = GimpGradientFile.sphere_increasing(middle, pos)
|
ret = GimpGradientFile.sphere_increasing(middle, pos)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
self.assert_almost_equal(ret, 0.9682458365518543)
|
self.assertAlmostEqual(ret, 0.9682458365518543)
|
||||||
|
|
||||||
def test_sphere_decreasing(self):
|
def test_sphere_decreasing(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
|
|
|
@ -2,7 +2,11 @@ from __future__ import print_function
|
||||||
from helper import unittest, PillowTestCase, hopper, py3
|
from helper import unittest, PillowTestCase, hopper, py3
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
<<<<<<< HEAD
|
||||||
import logging
|
import logging
|
||||||
|
=======
|
||||||
|
import itertools
|
||||||
|
>>>>>>> Restore legacy TIFF API.
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from PIL import Image, TiffImagePlugin
|
from PIL import Image, TiffImagePlugin
|
||||||
|
@ -123,41 +127,27 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
|
|
||||||
def test_write_metadata(self):
|
def test_write_metadata(self):
|
||||||
""" Test metadata writing through libtiff """
|
""" Test metadata writing through libtiff """
|
||||||
img = Image.open('Tests/images/hopper_g4.tif')
|
for legacy_api in [False, True]:
|
||||||
f = self.tempfile('temp.tiff')
|
img = Image.open('Tests/images/hopper_g4.tif')
|
||||||
|
img.tag.legacy_api = legacy_api
|
||||||
|
f = self.tempfile('temp.tiff')
|
||||||
|
|
||||||
img.save(f, tiffinfo=img.tag)
|
img.save(f, tiffinfo=img.tag)
|
||||||
|
original = img.tag.named()
|
||||||
|
|
||||||
loaded = Image.open(f)
|
# PhotometricInterpretation is set from SAVE_INFO,
|
||||||
|
# not the original image.
|
||||||
|
ignored = ['StripByteCounts', 'RowsPerStrip', 'PageNumber',
|
||||||
|
'PhotometricInterpretation']
|
||||||
|
|
||||||
original = img.tag.named()
|
loaded = Image.open(f)
|
||||||
reloaded = loaded.tag.named()
|
loaded.tag.legacy_api = legacy_api
|
||||||
|
reloaded = loaded.tag.named()
|
||||||
|
|
||||||
# PhotometricInterpretation is set from SAVE_INFO,
|
for tag, value in itertools.chain(reloaded.items(), original.items()):
|
||||||
# not the original image.
|
if tag not in ignored:
|
||||||
ignored = [
|
val = original[tag]
|
||||||
'StripByteCounts', 'RowsPerStrip',
|
self.assertEqual(val, value, msg="%s didn't roundtrip" % tag)
|
||||||
'PageNumber', 'PhotometricInterpretation']
|
|
||||||
|
|
||||||
for tag, value in reloaded.items():
|
|
||||||
if tag not in ignored:
|
|
||||||
if tag.endswith('Resolution'):
|
|
||||||
self.assert_almost_equal(
|
|
||||||
original[tag], value,
|
|
||||||
msg="%s didn't roundtrip" % tag)
|
|
||||||
else:
|
|
||||||
self.assertEqual(
|
|
||||||
original[tag], value, "%s didn't roundtrip" % tag)
|
|
||||||
|
|
||||||
for tag, value in original.items():
|
|
||||||
if tag not in ignored:
|
|
||||||
if tag.endswith('Resolution'):
|
|
||||||
self.assert_almost_equal(
|
|
||||||
original[tag], value,
|
|
||||||
msg="%s didn't roundtrip" % tag)
|
|
||||||
else:
|
|
||||||
self.assertEqual(
|
|
||||||
value, reloaded[tag], "%s didn't roundtrip" % tag)
|
|
||||||
|
|
||||||
def test_g3_compression(self):
|
def test_g3_compression(self):
|
||||||
i = Image.open('Tests/images/hopper_g4_500.tif')
|
i = Image.open('Tests/images/hopper_g4_500.tif')
|
||||||
|
|
|
@ -73,12 +73,18 @@ class TestFileTiff(PillowTestCase):
|
||||||
def test_xyres_tiff(self):
|
def test_xyres_tiff(self):
|
||||||
from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION
|
from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION
|
||||||
filename = "Tests/images/pil168.tif"
|
filename = "Tests/images/pil168.tif"
|
||||||
im = Image.open(filename)
|
for legacy_api in [False, True]:
|
||||||
# Try to read a file where X,Y_RESOLUTION are ints
|
im = Image.open(filename)
|
||||||
im.tag[X_RESOLUTION] = (72,)
|
im.tag.legacy_api = legacy_api
|
||||||
im.tag[Y_RESOLUTION] = (72,)
|
if legacy_api:
|
||||||
im._setup()
|
assert isinstance(im.tag[X_RESOLUTION][0], tuple)
|
||||||
self.assertEqual(im.info['dpi'], (72., 72.))
|
assert isinstance(im.tag[Y_RESOLUTION][0], tuple)
|
||||||
|
# Try to read a file where X,Y_RESOLUTION are ints
|
||||||
|
im.tag[X_RESOLUTION] = (72,)
|
||||||
|
im.tag[Y_RESOLUTION] = (72,)
|
||||||
|
im.tag.legacy_api = False # _setup assumes the new API.
|
||||||
|
im._setup()
|
||||||
|
self.assertEqual(im.info['dpi'], (72., 72.))
|
||||||
|
|
||||||
def test_invalid_file(self):
|
def test_invalid_file(self):
|
||||||
invalid_file = "Tests/images/flower.jpg"
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
@ -204,7 +210,6 @@ class TestFileTiff(PillowTestCase):
|
||||||
self.assertEqual(im.convert('RGB').getpixel((0, 0)), (0, 0, 255))
|
self.assertEqual(im.convert('RGB').getpixel((0, 0)), (0, 0, 255))
|
||||||
|
|
||||||
def test___str__(self):
|
def test___str__(self):
|
||||||
# Arrange
|
|
||||||
filename = "Tests/images/pil136.tiff"
|
filename = "Tests/images/pil136.tiff"
|
||||||
im = Image.open(filename)
|
im = Image.open(filename)
|
||||||
|
|
||||||
|
@ -217,127 +222,81 @@ class TestFileTiff(PillowTestCase):
|
||||||
def test_as_dict(self):
|
def test_as_dict(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
filename = "Tests/images/pil136.tiff"
|
filename = "Tests/images/pil136.tiff"
|
||||||
im = Image.open(filename)
|
for legacy_api in [False, True]:
|
||||||
|
im = Image.open(filename)
|
||||||
# Act
|
im.tag.legacy_api = legacy_api
|
||||||
ret = im.ifd.as_dict()
|
self.assertEqual(
|
||||||
|
im.tag.as_dict(),
|
||||||
# Assert
|
{256: (55,), 257: (43,), 258: (8, 8, 8, 8), 259: (1,),
|
||||||
self.assertIsInstance(ret, dict)
|
262: (2,), 296: (2,), 273: (8,), 338: (1,), 277: (4,),
|
||||||
|
279: (9460,), 282: ((720000, 10000),),
|
||||||
self.assertEqual(
|
283: ((720000, 10000),), 284: (1,)} if legacy_api else
|
||||||
ret, {256: 55, 257: 43, 258: (8, 8, 8, 8), 259: 1, 262: 2, 296: 2,
|
{256: 55, 257: 43, 258: (8, 8, 8, 8), 259: 1,
|
||||||
273: (8,), 338: (1,), 277: 4, 279: (9460,),
|
262: 2, 296: 2, 273: (8,), 338: (1,), 277: 4,
|
||||||
282: 72.0, 283: 72.0, 284: 1})
|
279: (9460,), 282: 72.0, 283: 72.0, 284: 1})
|
||||||
|
|
||||||
def test__delitem__(self):
|
def test__delitem__(self):
|
||||||
# Arrange
|
|
||||||
filename = "Tests/images/pil136.tiff"
|
filename = "Tests/images/pil136.tiff"
|
||||||
im = Image.open(filename)
|
im = Image.open(filename)
|
||||||
len_before = len(im.ifd.as_dict())
|
len_before = len(im.ifd.as_dict())
|
||||||
|
|
||||||
# Act
|
|
||||||
del im.ifd[256]
|
del im.ifd[256]
|
||||||
|
|
||||||
# Assert
|
|
||||||
len_after = len(im.ifd.as_dict())
|
len_after = len(im.ifd.as_dict())
|
||||||
self.assertEqual(len_before, len_after + 1)
|
self.assertEqual(len_before, len_after + 1)
|
||||||
|
|
||||||
def test_load_byte(self):
|
def test_load_byte(self):
|
||||||
# Arrange
|
for legacy_api in [False, True]:
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory()
|
ifd = TiffImagePlugin.ImageFileDirectory()
|
||||||
data = b"abc"
|
ifd.legacy_api = legacy_api
|
||||||
|
data = b"abc"
|
||||||
# Act
|
ret = ifd.load_byte(data)
|
||||||
ret = ifd.load_byte(data)
|
self.assertEqual(ret, b"abc" if legacy_api else (97, 98, 99))
|
||||||
|
|
||||||
# Assert
|
|
||||||
self.assertEqual(ret, (97, 98, 99))
|
|
||||||
|
|
||||||
def test_load_string(self):
|
def test_load_string(self):
|
||||||
# Arrange
|
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory()
|
ifd = TiffImagePlugin.ImageFileDirectory()
|
||||||
data = b"abc\0"
|
data = b"abc\0"
|
||||||
|
|
||||||
# Act
|
|
||||||
ret = ifd.load_string(data)
|
ret = ifd.load_string(data)
|
||||||
|
|
||||||
# Assert
|
|
||||||
self.assertEqual(ret, "abc")
|
self.assertEqual(ret, "abc")
|
||||||
|
|
||||||
def test_load_float(self):
|
def test_load_float(self):
|
||||||
# Arrange
|
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory()
|
ifd = TiffImagePlugin.ImageFileDirectory()
|
||||||
data = b"abcdabcd"
|
data = b"abcdabcd"
|
||||||
|
|
||||||
# Act
|
|
||||||
ret = ifd.load_float(data)
|
ret = ifd.load_float(data)
|
||||||
|
|
||||||
# Assert
|
|
||||||
self.assertEqual(ret, (1.6777999408082104e+22, 1.6777999408082104e+22))
|
self.assertEqual(ret, (1.6777999408082104e+22, 1.6777999408082104e+22))
|
||||||
|
|
||||||
def test_load_double(self):
|
def test_load_double(self):
|
||||||
# Arrange
|
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory()
|
ifd = TiffImagePlugin.ImageFileDirectory()
|
||||||
data = b"abcdefghabcdefgh"
|
data = b"abcdefghabcdefgh"
|
||||||
|
|
||||||
# Act
|
|
||||||
ret = ifd.load_double(data)
|
ret = ifd.load_double(data)
|
||||||
|
|
||||||
# Assert
|
|
||||||
self.assertEqual(ret, (8.540883223036124e+194, 8.540883223036124e+194))
|
self.assertEqual(ret, (8.540883223036124e+194, 8.540883223036124e+194))
|
||||||
|
|
||||||
def test_seek(self):
|
def test_seek(self):
|
||||||
# Arrange
|
|
||||||
filename = "Tests/images/pil136.tiff"
|
filename = "Tests/images/pil136.tiff"
|
||||||
im = Image.open(filename)
|
im = Image.open(filename)
|
||||||
|
|
||||||
# Act
|
|
||||||
im.seek(-1)
|
im.seek(-1)
|
||||||
|
|
||||||
# Assert
|
|
||||||
self.assertEqual(im.tell(), 0)
|
self.assertEqual(im.tell(), 0)
|
||||||
|
|
||||||
def test_seek_eof(self):
|
def test_seek_eof(self):
|
||||||
# Arrange
|
|
||||||
filename = "Tests/images/pil136.tiff"
|
filename = "Tests/images/pil136.tiff"
|
||||||
im = Image.open(filename)
|
im = Image.open(filename)
|
||||||
self.assertEqual(im.tell(), 0)
|
self.assertEqual(im.tell(), 0)
|
||||||
|
|
||||||
# Act / Assert
|
|
||||||
self.assertRaises(EOFError, lambda: im.seek(1))
|
self.assertRaises(EOFError, lambda: im.seek(1))
|
||||||
|
|
||||||
def test__limit_rational_int(self):
|
def test__limit_rational_int(self):
|
||||||
# Arrange
|
|
||||||
from PIL.TiffImagePlugin import _limit_rational
|
from PIL.TiffImagePlugin import _limit_rational
|
||||||
value = 34
|
value = 34
|
||||||
|
|
||||||
# Act
|
|
||||||
ret = _limit_rational(value, 65536)
|
ret = _limit_rational(value, 65536)
|
||||||
|
|
||||||
# Assert
|
|
||||||
self.assertEqual(ret, (34, 1))
|
self.assertEqual(ret, (34, 1))
|
||||||
|
|
||||||
def test__limit_rational_float(self):
|
def test__limit_rational_float(self):
|
||||||
# Arrange
|
|
||||||
from PIL.TiffImagePlugin import _limit_rational
|
from PIL.TiffImagePlugin import _limit_rational
|
||||||
value = 22.3
|
value = 22.3
|
||||||
|
|
||||||
# Act
|
|
||||||
ret = _limit_rational(value, 65536)
|
ret = _limit_rational(value, 65536)
|
||||||
|
|
||||||
# Assert
|
|
||||||
self.assertEqual(ret, (223, 10))
|
self.assertEqual(ret, (223, 10))
|
||||||
|
|
||||||
def test_4bit(self):
|
def test_4bit(self):
|
||||||
# Arrange
|
|
||||||
test_file = "Tests/images/hopper_gray_4bpp.tif"
|
test_file = "Tests/images/hopper_gray_4bpp.tif"
|
||||||
original = hopper("L")
|
original = hopper("L")
|
||||||
|
|
||||||
# Act
|
|
||||||
im = Image.open(test_file)
|
im = Image.open(test_file)
|
||||||
|
|
||||||
# Assert
|
|
||||||
self.assertEqual(im.size, (128, 128))
|
self.assertEqual(im.size, (128, 128))
|
||||||
self.assertEqual(im.mode, "L")
|
self.assertEqual(im.mode, "L")
|
||||||
self.assert_image_similar(im, original, 7.3)
|
self.assert_image_similar(im, original, 7.3)
|
||||||
|
@ -347,52 +306,45 @@ class TestFileTiff(PillowTestCase):
|
||||||
# Test TIFF with tag 297 (Page Number) having value of 0 0.
|
# Test TIFF with tag 297 (Page Number) having value of 0 0.
|
||||||
# The first number is the current page number.
|
# The first number is the current page number.
|
||||||
# The second is the total number of pages, zero means not available.
|
# The second is the total number of pages, zero means not available.
|
||||||
|
|
||||||
# Arrange
|
|
||||||
outfile = self.tempfile("temp.tif")
|
outfile = self.tempfile("temp.tif")
|
||||||
|
|
||||||
# Created by printing a page in Chrome to PDF, then:
|
# Created by printing a page in Chrome to PDF, then:
|
||||||
# /usr/bin/gs -q -sDEVICE=tiffg3 -sOutputFile=total-pages-zero.tif
|
# /usr/bin/gs -q -sDEVICE=tiffg3 -sOutputFile=total-pages-zero.tif
|
||||||
# -dNOPAUSE /tmp/test.pdf -c quit
|
# -dNOPAUSE /tmp/test.pdf -c quit
|
||||||
infile = "Tests/images/total-pages-zero.tif"
|
infile = "Tests/images/total-pages-zero.tif"
|
||||||
im = Image.open(infile)
|
im = Image.open(infile)
|
||||||
|
|
||||||
# Act / Assert
|
|
||||||
# Should not divide by zero
|
# Should not divide by zero
|
||||||
im.save(outfile)
|
im.save(outfile)
|
||||||
|
|
||||||
def test_with_underscores(self):
|
def test_with_underscores(self):
|
||||||
# Arrange: use underscores
|
|
||||||
kwargs = {'resolution_unit': 'inch',
|
kwargs = {'resolution_unit': 'inch',
|
||||||
'x_resolution': 72,
|
'x_resolution': 72,
|
||||||
'y_resolution': 36}
|
'y_resolution': 36}
|
||||||
filename = self.tempfile("temp.tif")
|
filename = self.tempfile("temp.tif")
|
||||||
|
|
||||||
# Act
|
|
||||||
hopper("RGB").save(filename, **kwargs)
|
hopper("RGB").save(filename, **kwargs)
|
||||||
|
|
||||||
# Assert
|
|
||||||
from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION
|
from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION
|
||||||
im = Image.open(filename)
|
for legacy_api in [False, True]:
|
||||||
self.assertEqual(im.tag[X_RESOLUTION], 72)
|
im = Image.open(filename)
|
||||||
self.assertEqual(im.tag[Y_RESOLUTION], 36)
|
im.tag.legacy_api = legacy_api
|
||||||
|
self.assertEqual(im.tag[X_RESOLUTION][0][0] if legacy_api
|
||||||
|
else im.tag[X_RESOLUTION], 72)
|
||||||
|
self.assertEqual(im.tag[Y_RESOLUTION][0][0] if legacy_api
|
||||||
|
else im.tag[Y_RESOLUTION], 36)
|
||||||
|
|
||||||
def test_deprecation_warning_with_spaces(self):
|
def test_deprecation_warning_with_spaces(self):
|
||||||
# Arrange: use spaces
|
|
||||||
kwargs = {'resolution unit': 'inch',
|
kwargs = {'resolution unit': 'inch',
|
||||||
'x resolution': 36,
|
'x resolution': 36,
|
||||||
'y resolution': 72}
|
'y resolution': 72}
|
||||||
filename = self.tempfile("temp.tif")
|
filename = self.tempfile("temp.tif")
|
||||||
|
|
||||||
# Act
|
|
||||||
self.assert_warning(DeprecationWarning,
|
self.assert_warning(DeprecationWarning,
|
||||||
lambda: hopper("RGB").save(filename, **kwargs))
|
lambda: hopper("RGB").save(filename, **kwargs))
|
||||||
|
|
||||||
# Assert
|
|
||||||
from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION
|
from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION
|
||||||
im = Image.open(filename)
|
for legacy_api in [False, True]:
|
||||||
self.assertEqual(im.tag[X_RESOLUTION], 36)
|
im = Image.open(filename)
|
||||||
self.assertEqual(im.tag[Y_RESOLUTION], 72)
|
im.tag.legacy_api = legacy_api
|
||||||
|
self.assertEqual(im.tag[X_RESOLUTION][0][0] if legacy_api
|
||||||
|
else im.tag[X_RESOLUTION], 36)
|
||||||
|
self.assertEqual(im.tag[Y_RESOLUTION][0][0] if legacy_api
|
||||||
|
else im.tag[Y_RESOLUTION], 72)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -34,36 +34,64 @@ class TestFileTiffMetadata(PillowTestCase):
|
||||||
|
|
||||||
img.save(f, tiffinfo=info)
|
img.save(f, tiffinfo=info)
|
||||||
|
|
||||||
loaded = Image.open(f)
|
for legacy_api in [False, True]:
|
||||||
|
loaded = Image.open(f)
|
||||||
|
loaded.tag.legacy_api = legacy_api
|
||||||
|
|
||||||
|
self.assertEqual(loaded.tag[50838],
|
||||||
|
(len(basetextdata + " ?"),) if legacy_api else len(basetextdata + " ?"))
|
||||||
|
self.assertEqual(loaded.tag[50839], basetextdata + " ?")
|
||||||
|
loaded_float = loaded.tag[tag_ids['RollAngle']]
|
||||||
|
if legacy_api:
|
||||||
|
loaded_float = loaded_float[0]
|
||||||
|
self.assertAlmostEqual(loaded_float, floatdata, places=5)
|
||||||
|
loaded_double = loaded.tag[tag_ids['YawAngle']]
|
||||||
|
if legacy_api:
|
||||||
|
loaded_double = loaded_double[0]
|
||||||
|
self.assertAlmostEqual(loaded_double, doubledata)
|
||||||
|
|
||||||
self.assertEqual(loaded.tag[50838], (len(basetextdata + " ?"),))
|
|
||||||
self.assertEqual(loaded.tag[50839], basetextdata + " ?")
|
|
||||||
self.assertAlmostEqual(loaded.tag[tag_ids['RollAngle']][0], floatdata,
|
|
||||||
places=5)
|
|
||||||
self.assertAlmostEqual(loaded.tag[tag_ids['YawAngle']], doubledata)
|
|
||||||
|
|
||||||
def test_read_metadata(self):
|
def test_read_metadata(self):
|
||||||
img = Image.open('Tests/images/hopper_g4.tif')
|
for legacy_api in [False, True]:
|
||||||
|
img = Image.open('Tests/images/hopper_g4.tif')
|
||||||
|
img.tag.legacy_api = legacy_api
|
||||||
|
|
||||||
known = {'YResolution': 4294967295 / 113653537,
|
known = {'YResolution': ((4294967295, 113653537),),
|
||||||
'PlanarConfiguration': 1,
|
'PlanarConfiguration': (1,),
|
||||||
'BitsPerSample': (1,),
|
'BitsPerSample': (1,),
|
||||||
'ImageLength': 128,
|
'ImageLength': (128,),
|
||||||
'Compression': 4,
|
'Compression': (4,),
|
||||||
'FillOrder': 1,
|
'FillOrder': (1,),
|
||||||
'RowsPerStrip': 128,
|
'RowsPerStrip': (128,),
|
||||||
'ResolutionUnit': 3,
|
'ResolutionUnit': (3,),
|
||||||
'PhotometricInterpretation': 0,
|
'PhotometricInterpretation': (0,),
|
||||||
'PageNumber': (0, 1),
|
'PageNumber': (0, 1),
|
||||||
'XResolution': 4294967295 / 113653537,
|
'XResolution': ((4294967295, 113653537),),
|
||||||
'ImageWidth': 128,
|
'ImageWidth': (128,),
|
||||||
'Orientation': 1,
|
'Orientation': (1,),
|
||||||
'StripByteCounts': (1968,),
|
'StripByteCounts': (1968,),
|
||||||
'SamplesPerPixel': 1,
|
'SamplesPerPixel': (1,),
|
||||||
'StripOffsets': (8,)
|
'StripOffsets': (8,)
|
||||||
}
|
} if legacy_api else {
|
||||||
|
'YResolution': 4294967295 / 113653537,
|
||||||
|
'PlanarConfiguration': 1,
|
||||||
|
'BitsPerSample': (1,),
|
||||||
|
'ImageLength': 128,
|
||||||
|
'Compression': 4,
|
||||||
|
'FillOrder': 1,
|
||||||
|
'RowsPerStrip': 128,
|
||||||
|
'ResolutionUnit': 3,
|
||||||
|
'PhotometricInterpretation': 0,
|
||||||
|
'PageNumber': (0, 1),
|
||||||
|
'XResolution': 4294967295 / 113653537,
|
||||||
|
'ImageWidth': 128,
|
||||||
|
'Orientation': 1,
|
||||||
|
'StripByteCounts': (1968,),
|
||||||
|
'SamplesPerPixel': 1,
|
||||||
|
'StripOffsets': (8,)
|
||||||
|
}
|
||||||
|
|
||||||
self.assertEqual(known, img.tag.named())
|
self.assertEqual(known, img.tag.named())
|
||||||
|
|
||||||
def test_write_metadata(self):
|
def test_write_metadata(self):
|
||||||
""" Test metadata writing through the python code """
|
""" Test metadata writing through the python code """
|
||||||
|
|
Loading…
Reference in New Issue
Block a user