diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 884868345..399736750 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -1,6 +1,7 @@ from __future__ import annotations import io +import math import struct from pathlib import Path @@ -280,6 +281,33 @@ def test_writing_other_types_to_undefined( assert reloaded.tag_v2[33723] == b"1" +@pytest.mark.parametrize( + "value, expected", + ( + (IFDRational(1, 0), TiffTags.RATIONAL), + (IFDRational(-1, 0), TiffTags.SIGNED_RATIONAL), + ), +) +def test_tagtype_on_zero_denominator( + value: IFDRational, expected: int, tmp_path: Path +) -> None: + info = TiffImagePlugin.ImageFileDirectory_v2() + + info[37380] = value + assert info.tagtype[37380] == expected + + im = hopper() + out = tmp_path / "temp.tiff" + im.save(out, tiffinfo=info) + + with Image.open(out) as reloaded: + assert isinstance(reloaded, TiffImagePlugin.TiffImageFile) + if expected == TiffTags.RATIONAL: + assert reloaded.tag_v2[37380] == math.inf + elif TiffTags.SIGNED_RATIONAL: + assert reloaded.tag_v2[37380] == -math.inf + + def test_undefined_zero(tmp_path: Path) -> None: # Check that the tag has not been changed since this test was created tag = TiffTags.TAGS_V2[45059] diff --git a/Tests/test_tiff_ifdrational.py b/Tests/test_tiff_ifdrational.py index 42d06b896..4fd5b8fbe 100644 --- a/Tests/test_tiff_ifdrational.py +++ b/Tests/test_tiff_ifdrational.py @@ -1,5 +1,6 @@ from __future__ import annotations +import math from fractions import Fraction from pathlib import Path @@ -74,3 +75,20 @@ def test_ifd_rational_save( with Image.open(out) as reloaded: assert isinstance(reloaded, TiffImagePlugin.TiffImageFile) assert float(IFDRational(301, 1)) == float(reloaded.tag_v2[282]) + + +@pytest.mark.parametrize( + "numerator, denominator, expected_result", + [ + (1, 1, 1.0), + (1, 0, math.inf), + (-1, 0, -math.inf), + (0, 0, float("nan")), + ], +) +def test_float_cast(numerator: int, denominator: int, expected_result: float) -> None: + value = float(IFDRational(numerator, denominator)) + if math.isnan(expected_result): + assert value + else: + assert value == expected_result diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 88af9162e..bbc9ffce8 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -372,7 +372,12 @@ class IFDRational(Rational): self._denominator = denominator if denominator == 0: - self._val = float("nan") + if value == 0: + self._val = float("nan") + elif value > 0: + self._val = math.inf + else: + self._val = -math.inf elif denominator == 1: self._val = Fraction(value) elif int(value) == value: @@ -402,6 +407,9 @@ class IFDRational(Rational): f = self._val.limit_denominator(max_denominator) return f.numerator, f.denominator + def __float__(self) -> float: + return float(self._val) + def __repr__(self) -> str: return str(float(self._val))