Skip to content

Commit

Permalink
Merge pull request #6879 from Yay295/eps_plugin_perf
Browse files Browse the repository at this point in the history
  • Loading branch information
hugovk committed Apr 1, 2023
2 parents 3cfdef3 + 61d0c8f commit 48b0be2
Show file tree
Hide file tree
Showing 6 changed files with 262 additions and 100 deletions.
2 changes: 1 addition & 1 deletion Tests/test_deprecate.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_version(version, expected):


def test_unknown_version():
expected = r"Unknown removal version, update PIL\._deprecate\?"
expected = r"Unknown removal version: 12345. Update PIL\._deprecate\?"
with pytest.raises(ValueError, match=expected):
_deprecate.deprecate("Old thing", 12345, "new thing")

Expand Down
178 changes: 145 additions & 33 deletions Tests/test_file_eps.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,34 +28,65 @@
# EPS test files with binary preview
FILE3 = "Tests/images/binary_preview_map.eps"

# Three unsigned 32bit little-endian values:
# 0xC6D3D0C5 magic number
# byte position of start of postscript section (12)
# byte length of postscript section (0)
# this byte length isn't valid, but we don't read it
simple_binary_header = b"\xc5\xd0\xd3\xc6\x0c\x00\x00\x00\x00\x00\x00\x00"

# taken from page 8 of the specification
# https://web.archive.org/web/20220120164601/https://www.adobe.com/content/dam/acom/en/devnet/actionscript/articles/5002.EPSF_Spec.pdf
simple_eps_file = (
b"%!PS-Adobe-3.0 EPSF-3.0",
b"%%BoundingBox: 5 5 105 105",
b"10 setlinewidth",
b"10 10 moveto",
b"0 90 rlineto 90 0 rlineto 0 -90 rlineto closepath",
b"stroke",
)
simple_eps_file_with_comments = (
simple_eps_file[:1]
+ (
b"%%Comment1: Some Value",
b"%%SecondComment: Another Value",
)
+ simple_eps_file[1:]
)
simple_eps_file_without_version = simple_eps_file[1:]
simple_eps_file_without_boundingbox = simple_eps_file[:1] + simple_eps_file[2:]
simple_eps_file_with_invalid_boundingbox = (
simple_eps_file[:1] + (b"%%BoundingBox: a b c d",) + simple_eps_file[2:]
)
simple_eps_file_with_invalid_boundingbox_valid_imagedata = (
simple_eps_file_with_invalid_boundingbox + (b"%ImageData: 100 100 8 3",)
)
simple_eps_file_with_long_ascii_comment = (
simple_eps_file[:2] + (b"%%Comment: " + b"X" * 300,) + simple_eps_file[2:]
)
simple_eps_file_with_long_binary_data = (
simple_eps_file[:2]
+ (
b"%%BeginBinary: 300",
b"\0" * 300,
b"%%EndBinary",
)
+ simple_eps_file[2:]
)

@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
def test_sanity():
# Regular scale
with Image.open(FILE1) as image1:
image1.load()
assert image1.mode == "RGB"
assert image1.size == (460, 352)
assert image1.format == "EPS"

with Image.open(FILE2) as image2:
image2.load()
assert image2.mode == "RGB"
assert image2.size == (360, 252)
assert image2.format == "EPS"

# Double scale
with Image.open(FILE1) as image1_scale2:
image1_scale2.load(scale=2)
assert image1_scale2.mode == "RGB"
assert image1_scale2.size == (920, 704)
assert image1_scale2.format == "EPS"

with Image.open(FILE2) as image2_scale2:
image2_scale2.load(scale=2)
assert image2_scale2.mode == "RGB"
assert image2_scale2.size == (720, 504)
assert image2_scale2.format == "EPS"
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
@pytest.mark.parametrize(
("filename", "size"), ((FILE1, (460, 352)), (FILE2, (360, 252)))
)
@pytest.mark.parametrize("scale", (1, 2))
def test_sanity(filename, size, scale):
expected_size = tuple(s * scale for s in size)
with Image.open(filename) as image:
image.load(scale=scale)
assert image.mode == "RGB"
assert image.size == expected_size
assert image.format == "EPS"


@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
Expand All @@ -69,11 +100,72 @@ def test_load():

def test_invalid_file():
invalid_file = "Tests/images/flower.jpg"

with pytest.raises(SyntaxError):
EpsImagePlugin.EpsImageFile(invalid_file)


def test_binary_header_only():
data = io.BytesIO(simple_binary_header)
with pytest.raises(SyntaxError, match='EPS header missing "%!PS-Adobe" comment'):
EpsImagePlugin.EpsImageFile(data)


@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
def test_missing_version_comment(prefix):
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_without_version))
with pytest.raises(SyntaxError):
EpsImagePlugin.EpsImageFile(data)


@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
def test_missing_boundingbox_comment(prefix):
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_without_boundingbox))
with pytest.raises(SyntaxError, match='EPS header missing "%%BoundingBox" comment'):
EpsImagePlugin.EpsImageFile(data)


@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
def test_invalid_boundingbox_comment(prefix):
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_invalid_boundingbox))
with pytest.raises(OSError, match="cannot determine EPS bounding box"):
EpsImagePlugin.EpsImageFile(data)


@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
def test_invalid_boundingbox_comment_valid_imagedata_comment(prefix):
data = io.BytesIO(
prefix + b"\n".join(simple_eps_file_with_invalid_boundingbox_valid_imagedata)
)
with Image.open(data) as img:
assert img.mode == "RGB"
assert img.size == (100, 100)
assert img.format == "EPS"


@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
def test_ascii_comment_too_long(prefix):
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_long_ascii_comment))
with pytest.raises(SyntaxError, match="not an EPS file"):
EpsImagePlugin.EpsImageFile(data)


@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
def test_long_binary_data(prefix):
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_long_binary_data))
EpsImagePlugin.EpsImageFile(data)


@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
def test_load_long_binary_data(prefix):
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_long_binary_data))
with Image.open(data) as img:
img.load()
assert img.mode == "RGB"
assert img.size == (100, 100)
assert img.format == "EPS"


@mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
)
Expand All @@ -100,7 +192,7 @@ def test_showpage():
with Image.open("Tests/images/reqd_showpage.png") as target:
# should not crash/hang
plot_image.load()
# fonts could be slightly different
# fonts could be slightly different
assert_image_similar(plot_image, target, 6)


Expand All @@ -111,7 +203,7 @@ def test_transparency():
assert plot_image.mode == "RGBA"

with Image.open("Tests/images/reqd_showpage_transparency.png") as target:
# fonts could be slightly different
# fonts could be slightly different
assert_image_similar(plot_image, target, 6)


Expand Down Expand Up @@ -206,7 +298,6 @@ def test_resize(filename):
@pytest.mark.parametrize("filename", (FILE1, FILE2))
def test_thumbnail(filename):
# Issue #619
# Arrange
with Image.open(filename) as im:
new_size = (100, 100)
im.thumbnail(new_size)
Expand All @@ -220,7 +311,7 @@ def test_read_binary_preview():
pass


def test_readline(tmp_path):
def test_readline_psfile(tmp_path):
# check all the freaking line endings possible from the spec
# test_string = u'something\r\nelse\n\rbaz\rbif\n'
line_endings = ["\r\n", "\n", "\n\r", "\r"]
Expand All @@ -237,7 +328,8 @@ def _test_readline(t, ending):

def _test_readline_io_psfile(test_string, ending):
f = io.BytesIO(test_string.encode("latin-1"))
t = EpsImagePlugin.PSFile(f)
with pytest.warns(DeprecationWarning):
t = EpsImagePlugin.PSFile(f)
_test_readline(t, ending)

def _test_readline_file_psfile(test_string, ending):
Expand All @@ -246,7 +338,8 @@ def _test_readline_file_psfile(test_string, ending):
w.write(test_string.encode("latin-1"))

with open(f, "rb") as r:
t = EpsImagePlugin.PSFile(r)
with pytest.warns(DeprecationWarning):
t = EpsImagePlugin.PSFile(r)
_test_readline(t, ending)

for ending in line_endings:
Expand All @@ -255,6 +348,25 @@ def _test_readline_file_psfile(test_string, ending):
_test_readline_file_psfile(s, ending)


def test_psfile_deprecation():
with pytest.warns(DeprecationWarning):
EpsImagePlugin.PSFile(None)


@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
@pytest.mark.parametrize(
"line_ending",
(b"\r\n", b"\n", b"\n\r", b"\r"),
)
def test_readline(prefix, line_ending):
simple_file = prefix + line_ending.join(simple_eps_file_with_comments)
data = io.BytesIO(simple_file)
test_file = EpsImagePlugin.EpsImageFile(data)
assert test_file.info["Comment1"] == "Some Value"
assert test_file.info["SecondComment"] == "Another Value"
assert test_file.size == (100, 100)


@pytest.mark.parametrize(
"filename",
(
Expand Down
10 changes: 10 additions & 0 deletions docs/deprecations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,16 @@ Use instead::
left, top, right, bottom = draw.multiline_textbbox((0, 0), "Hello\nworld")
width, height = right - left, bottom - top

PSFile
~~~~~~

.. deprecated:: 9.5.0

The :py:class:`~PIL.EpsImagePlugin.PSFile` class has been deprecated and will
be removed in Pillow 11 (2024-10-15). This class was only made as a helper to
be used internally, so there is no replacement. If you need this functionality
though, it is a very short class that can easily be recreated in your own code.

Removed features
----------------

Expand Down
9 changes: 6 additions & 3 deletions docs/releasenotes/9.5.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ TODO
Deprecations
============

TODO
^^^^
PSFile
^^^^^^

TODO
The :py:class:`~PIL.EpsImagePlugin.PSFile` class has been deprecated and will
be removed in Pillow 11 (2024-10-15). This class was only made as a helper to
be used internally, so there is no replacement. If you need this functionality
though, it is a very short class that can easily be recreated in your own code.

API Changes
===========
Expand Down

0 comments on commit 48b0be2

Please sign in to comment.