mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-27 09:44:31 +03:00
Added a rational class for TiffIFD that allows for 0/0
This commit is contained in:
parent
c33fc39e76
commit
deecbcd3a3
|
@ -47,9 +47,10 @@ from PIL import _binary
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
|
from numbers import Number, Rational
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import itertools
|
import itertools
|
||||||
from numbers import Number
|
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
|
@ -215,8 +216,7 @@ def _accept(prefix):
|
||||||
|
|
||||||
def _limit_rational(val, max_val):
|
def _limit_rational(val, max_val):
|
||||||
inv = abs(val) > 1
|
inv = abs(val) > 1
|
||||||
f = Fraction.from_float(1 / val if inv else val).limit_denominator(max_val)
|
n_d = IFDRational(1 / val if inv else val).limit_rational(max_val)
|
||||||
n_d = (f.numerator, f.denominator)
|
|
||||||
return n_d[::-1] if inv else n_d
|
return n_d[::-1] if inv else n_d
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -225,6 +225,64 @@ def _limit_rational(val, max_val):
|
||||||
_load_dispatch = {}
|
_load_dispatch = {}
|
||||||
_write_dispatch = {}
|
_write_dispatch = {}
|
||||||
|
|
||||||
|
class IFDRational(Fraction):
|
||||||
|
""" Implements a rational class where 0/0 is a legal value to match
|
||||||
|
the in the wild use of exif rationals.
|
||||||
|
|
||||||
|
e.g., DigitalZoomRatio - 0.00/0.00 indicates that no digital zoom was used
|
||||||
|
"""
|
||||||
|
|
||||||
|
""" If the denominator is 0, store this as a float('nan'), otherwise store
|
||||||
|
as a fractions.Fraction(). Delegate as appropriate
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ('numerator', 'denominator', '_val')
|
||||||
|
|
||||||
|
def __init__(self, value, denominator=1):
|
||||||
|
"""
|
||||||
|
:param value: either an integer numerator, a
|
||||||
|
float/rational/other number, or an IFDRational
|
||||||
|
:param denominator: Optional integer denominator
|
||||||
|
"""
|
||||||
|
self.denominator = denominator
|
||||||
|
self.numerator = value
|
||||||
|
|
||||||
|
if type(value) == IFDRational:
|
||||||
|
self.denominator = value.denominator
|
||||||
|
self.numerator = value.numerator
|
||||||
|
self._val = value._val
|
||||||
|
return
|
||||||
|
|
||||||
|
if denominator == 0:
|
||||||
|
self._val = float('nan')
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
if denominator == 1:
|
||||||
|
self._val = Fraction(value)
|
||||||
|
else:
|
||||||
|
self._val = Fraction(value, denominator)
|
||||||
|
except:
|
||||||
|
print(type(value), type(denominator))
|
||||||
|
raise
|
||||||
|
|
||||||
|
def limit_rational(self, max_denominator):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param max_denominator: Integer, the maximum denominator value
|
||||||
|
:returns: Tuple of (numerator, denominator)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.denominator == 0:
|
||||||
|
return (self.numerator, self.denominator)
|
||||||
|
|
||||||
|
f = self._val.limit_denominator(max_denominator)
|
||||||
|
return (f.numerator, f.denominator)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(float(self._val))
|
||||||
|
|
||||||
|
|
||||||
class ImageFileDirectory_v2(collections.MutableMapping):
|
class ImageFileDirectory_v2(collections.MutableMapping):
|
||||||
"""This class represents a TIFF tag directory. To speed things up, we
|
"""This class represents a TIFF tag directory. To speed things up, we
|
||||||
|
@ -477,7 +535,7 @@ class ImageFileDirectory_v2(collections.MutableMapping):
|
||||||
@_register_loader(5, 8)
|
@_register_loader(5, 8)
|
||||||
def load_rational(self, data, legacy_api=True):
|
def load_rational(self, data, legacy_api=True):
|
||||||
vals = self._unpack("{0}L".format(len(data) // 4), data)
|
vals = self._unpack("{0}L".format(len(data) // 4), data)
|
||||||
combine = lambda a, b: (a, b) if legacy_api else a / b
|
combine = lambda a, b: (a, b) if legacy_api else IFDRational(a, b)
|
||||||
return tuple(combine(num, denom)
|
return tuple(combine(num, denom)
|
||||||
for num, denom in zip(vals[::2], vals[1::2]))
|
for num, denom in zip(vals[::2], vals[1::2]))
|
||||||
|
|
||||||
|
@ -497,7 +555,7 @@ class ImageFileDirectory_v2(collections.MutableMapping):
|
||||||
@_register_loader(10, 8)
|
@_register_loader(10, 8)
|
||||||
def load_signed_rational(self, data, legacy_api=True):
|
def load_signed_rational(self, data, legacy_api=True):
|
||||||
vals = self._unpack("{0}l".format(len(data) // 4), data)
|
vals = self._unpack("{0}l".format(len(data) // 4), data)
|
||||||
combine = lambda a, b: (a, b) if legacy_api else a / b
|
combine = lambda a, b: (a, b) if legacy_api else IFDRational(a, b)
|
||||||
return tuple(combine(num, denom)
|
return tuple(combine(num, denom)
|
||||||
for num, denom in zip(vals[::2], vals[1::2]))
|
for num, denom in zip(vals[::2], vals[1::2]))
|
||||||
|
|
||||||
|
@ -1296,6 +1354,8 @@ def _save(im, fp, filename):
|
||||||
if k not in atts and k not in blocklist:
|
if k not in atts and k not in blocklist:
|
||||||
if isinstance(v, unicode if bytes is str else str):
|
if isinstance(v, unicode if bytes is str else str):
|
||||||
atts[k] = v.encode('ascii', 'replace') + b"\0"
|
atts[k] = v.encode('ascii', 'replace') + b"\0"
|
||||||
|
elif isinstance(v, IFDRational):
|
||||||
|
atts[k] = float(v)
|
||||||
else:
|
else:
|
||||||
atts[k] = v
|
atts[k] = v
|
||||||
|
|
||||||
|
|
|
@ -84,9 +84,9 @@ class TestFileTiff(PillowTestCase):
|
||||||
self.assertIsInstance(im.tag[X_RESOLUTION][0], tuple)
|
self.assertIsInstance(im.tag[X_RESOLUTION][0], tuple)
|
||||||
self.assertIsInstance(im.tag[Y_RESOLUTION][0], tuple)
|
self.assertIsInstance(im.tag[Y_RESOLUTION][0], tuple)
|
||||||
|
|
||||||
# v2 api
|
#v2 api
|
||||||
self.assertIsInstance(im.tag_v2[X_RESOLUTION], float)
|
self.assert_(isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational))
|
||||||
self.assertIsInstance(im.tag_v2[Y_RESOLUTION], float)
|
self.assert_(isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational))
|
||||||
|
|
||||||
self.assertEqual(im.info['dpi'], (72., 72.))
|
self.assertEqual(im.info['dpi'], (72., 72.))
|
||||||
|
|
||||||
|
|
|
@ -126,8 +126,10 @@ class TestFileTiffMetadata(PillowTestCase):
|
||||||
|
|
||||||
for tag, value in reloaded.items():
|
for tag, value in reloaded.items():
|
||||||
if tag not in ignored:
|
if tag not in ignored:
|
||||||
self.assertEqual(
|
self.assertEqual(original[tag],
|
||||||
original[tag], value, "%s didn't roundtrip" % tag)
|
value,
|
||||||
|
"%s didn't roundtrip, %s, %s" %
|
||||||
|
(tag, original[tag], value))
|
||||||
|
|
||||||
for tag, value in original.items():
|
for tag, value in original.items():
|
||||||
if tag not in ignored:
|
if tag not in ignored:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user