1
from __future__ import annotations
4
from pathlib import Path
8
from PIL import EpsImagePlugin, Image, UnidentifiedImageError, features
12
assert_image_similar_tofile,
15
mark_if_feature_version,
19
HAS_GHOSTSCRIPT = EpsImagePlugin.has_ghostscript()
21
# Our two EPS test files (they are identical except for their bounding boxes)
22
FILE1 = "Tests/images/zero_bb.eps"
23
FILE2 = "Tests/images/non_zero_bb.eps"
25
# Due to palletization, we'll need to convert these to RGB after load
26
FILE1_COMPARE = "Tests/images/zero_bb.png"
27
FILE1_COMPARE_SCALE2 = "Tests/images/zero_bb_scale2.png"
29
FILE2_COMPARE = "Tests/images/non_zero_bb.png"
30
FILE2_COMPARE_SCALE2 = "Tests/images/non_zero_bb_scale2.png"
32
# EPS test files with binary preview
33
FILE3 = "Tests/images/binary_preview_map.eps"
35
# Three unsigned 32bit little-endian values:
36
# 0xC6D3D0C5 magic number
37
# byte position of start of postscript section (12)
38
# byte length of postscript section (0)
39
# this byte length isn't valid, but we don't read it
40
simple_binary_header = b"\xc5\xd0\xd3\xc6\x0c\x00\x00\x00\x00\x00\x00\x00"
42
# taken from page 8 of the specification
43
# https://web.archive.org/web/20220120164601/https://www.adobe.com/content/dam/acom/en/devnet/actionscript/articles/5002.EPSF_Spec.pdf
45
b"%!PS-Adobe-3.0 EPSF-3.0",
46
b"%%BoundingBox: 5 5 105 105",
49
b"0 90 rlineto 90 0 rlineto 0 -90 rlineto closepath",
52
simple_eps_file_with_comments = (
55
b"%%Comment1: Some Value",
56
b"%%SecondComment: Another Value",
60
simple_eps_file_without_version = simple_eps_file[1:]
61
simple_eps_file_without_boundingbox = simple_eps_file[:1] + simple_eps_file[2:]
62
simple_eps_file_with_invalid_boundingbox = (
63
simple_eps_file[:1] + (b"%%BoundingBox: a b c d",) + simple_eps_file[2:]
65
simple_eps_file_with_invalid_boundingbox_valid_imagedata = (
66
simple_eps_file_with_invalid_boundingbox + (b"%ImageData: 100 100 8 3",)
68
simple_eps_file_with_long_ascii_comment = (
69
simple_eps_file[:2] + (b"%%Comment: " + b"X" * 300,) + simple_eps_file[2:]
71
simple_eps_file_with_long_binary_data = (
74
b"%%BeginBinary: 300",
82
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
83
@pytest.mark.parametrize("filename, size", ((FILE1, (460, 352)), (FILE2, (360, 252))))
84
@pytest.mark.parametrize("scale", (1, 2))
85
def test_sanity(filename: str, size: tuple[int, int], scale: int) -> None:
86
expected_size = tuple(s * scale for s in size)
87
with Image.open(filename) as image:
88
image.load(scale=scale)
89
assert image.mode == "RGB"
90
assert image.size == expected_size
91
assert image.format == "EPS"
94
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
95
def test_load() -> None:
96
with Image.open(FILE1) as im:
97
assert im.load()[0, 0] == (255, 255, 255)
99
# Test again now that it has already been loaded once
100
assert im.load()[0, 0] == (255, 255, 255)
103
def test_binary() -> None:
105
assert EpsImagePlugin.gs_binary is not None
107
assert EpsImagePlugin.gs_binary is False
110
assert EpsImagePlugin.gs_windows_binary is None
111
elif not HAS_GHOSTSCRIPT:
112
assert EpsImagePlugin.gs_windows_binary is False
114
assert EpsImagePlugin.gs_windows_binary is not None
117
def test_invalid_file() -> None:
118
invalid_file = "Tests/images/flower.jpg"
119
with pytest.raises(SyntaxError):
120
EpsImagePlugin.EpsImageFile(invalid_file)
123
def test_binary_header_only() -> None:
124
data = io.BytesIO(simple_binary_header)
125
with pytest.raises(SyntaxError, match='EPS header missing "%!PS-Adobe" comment'):
126
EpsImagePlugin.EpsImageFile(data)
129
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
130
def test_missing_version_comment(prefix: bytes) -> None:
131
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_without_version))
132
with pytest.raises(SyntaxError):
133
EpsImagePlugin.EpsImageFile(data)
136
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
137
def test_missing_boundingbox_comment(prefix: bytes) -> None:
138
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_without_boundingbox))
139
with pytest.raises(SyntaxError, match='EPS header missing "%%BoundingBox" comment'):
140
EpsImagePlugin.EpsImageFile(data)
143
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
144
def test_invalid_boundingbox_comment(prefix: bytes) -> None:
145
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_invalid_boundingbox))
146
with pytest.raises(OSError, match="cannot determine EPS bounding box"):
147
EpsImagePlugin.EpsImageFile(data)
150
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
151
def test_invalid_boundingbox_comment_valid_imagedata_comment(prefix: bytes) -> None:
153
prefix + b"\n".join(simple_eps_file_with_invalid_boundingbox_valid_imagedata)
155
with Image.open(data) as img:
156
assert img.mode == "RGB"
157
assert img.size == (100, 100)
158
assert img.format == "EPS"
161
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
162
def test_ascii_comment_too_long(prefix: bytes) -> None:
163
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_long_ascii_comment))
164
with pytest.raises(SyntaxError, match="not an EPS file"):
165
EpsImagePlugin.EpsImageFile(data)
168
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
169
def test_long_binary_data(prefix: bytes) -> None:
170
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_long_binary_data))
171
EpsImagePlugin.EpsImageFile(data)
174
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
175
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
176
def test_load_long_binary_data(prefix: bytes) -> None:
177
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_long_binary_data))
178
with Image.open(data) as img:
180
assert img.mode == "RGB"
181
assert img.size == (100, 100)
182
assert img.format == "EPS"
185
@mark_if_feature_version(
186
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
188
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
189
def test_cmyk() -> None:
190
with Image.open("Tests/images/pil_sample_cmyk.eps") as cmyk_image:
191
assert cmyk_image.mode == "CMYK"
192
assert cmyk_image.size == (100, 100)
193
assert cmyk_image.format == "EPS"
196
assert cmyk_image.mode == "RGB"
198
if features.check("jpg"):
199
assert_image_similar_tofile(
200
cmyk_image, "Tests/images/pil_sample_rgb.jpg", 10
204
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
205
def test_showpage() -> None:
206
# See https://github.com/python-pillow/Pillow/issues/2615
207
with Image.open("Tests/images/reqd_showpage.eps") as plot_image:
208
with Image.open("Tests/images/reqd_showpage.png") as target:
209
# should not crash/hang
211
# fonts could be slightly different
212
assert_image_similar(plot_image, target, 6)
215
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
216
def test_transparency() -> None:
217
with Image.open("Tests/images/reqd_showpage.eps") as plot_image:
218
plot_image.load(transparency=True)
219
assert plot_image.mode == "RGBA"
221
with Image.open("Tests/images/reqd_showpage_transparency.png") as target:
222
# fonts could be slightly different
223
assert_image_similar(plot_image, target, 6)
226
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
227
def test_file_object(tmp_path: Path) -> None:
229
with Image.open(FILE1) as image1:
230
with open(str(tmp_path / "temp.eps"), "wb") as fh:
231
image1.save(fh, "EPS")
234
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
235
def test_bytesio_object() -> None:
236
with open(FILE1, "rb") as f:
237
img_bytes = io.BytesIO(f.read())
239
with Image.open(img_bytes) as img:
242
with Image.open(FILE1_COMPARE) as image1_scale1_compare:
243
image1_scale1_compare = image1_scale1_compare.convert("RGB")
244
image1_scale1_compare.load()
245
assert_image_similar(img, image1_scale1_compare, 5)
248
def test_1_mode() -> None:
249
with Image.open("Tests/images/1.eps") as im:
250
assert im.mode == "1"
253
def test_image_mode_not_supported(tmp_path: Path) -> None:
255
tmpfile = str(tmp_path / "temp.eps")
256
with pytest.raises(ValueError):
260
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
261
@skip_unless_feature("zlib")
262
def test_render_scale1() -> None:
263
# We need png support for these render test
266
with Image.open(FILE1) as image1_scale1:
268
with Image.open(FILE1_COMPARE) as image1_scale1_compare:
269
image1_scale1_compare = image1_scale1_compare.convert("RGB")
270
image1_scale1_compare.load()
271
assert_image_similar(image1_scale1, image1_scale1_compare, 5)
273
# Non-zero bounding box
274
with Image.open(FILE2) as image2_scale1:
276
with Image.open(FILE2_COMPARE) as image2_scale1_compare:
277
image2_scale1_compare = image2_scale1_compare.convert("RGB")
278
image2_scale1_compare.load()
279
assert_image_similar(image2_scale1, image2_scale1_compare, 10)
282
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
283
@skip_unless_feature("zlib")
284
def test_render_scale2() -> None:
285
# We need png support for these render test
288
with Image.open(FILE1) as image1_scale2:
289
image1_scale2.load(scale=2)
290
with Image.open(FILE1_COMPARE_SCALE2) as image1_scale2_compare:
291
image1_scale2_compare = image1_scale2_compare.convert("RGB")
292
image1_scale2_compare.load()
293
assert_image_similar(image1_scale2, image1_scale2_compare, 5)
295
# Non-zero bounding box
296
with Image.open(FILE2) as image2_scale2:
297
image2_scale2.load(scale=2)
298
with Image.open(FILE2_COMPARE_SCALE2) as image2_scale2_compare:
299
image2_scale2_compare = image2_scale2_compare.convert("RGB")
300
image2_scale2_compare.load()
301
assert_image_similar(image2_scale2, image2_scale2_compare, 10)
304
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
305
@pytest.mark.parametrize("filename", (FILE1, FILE2, "Tests/images/illu10_preview.eps"))
306
def test_resize(filename: str) -> None:
307
with Image.open(filename) as im:
308
new_size = (100, 100)
309
im = im.resize(new_size)
310
assert im.size == new_size
313
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
314
@pytest.mark.parametrize("filename", (FILE1, FILE2))
315
def test_thumbnail(filename: str) -> None:
317
with Image.open(filename) as im:
318
new_size = (100, 100)
319
im.thumbnail(new_size)
320
assert max(im.size) == max(new_size)
323
def test_read_binary_preview() -> None:
325
# open image with binary preview
326
with Image.open(FILE3):
330
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
331
@pytest.mark.parametrize(
333
(b"\r\n", b"\n", b"\n\r", b"\r"),
335
def test_readline(prefix: bytes, line_ending: bytes) -> None:
336
simple_file = prefix + line_ending.join(simple_eps_file_with_comments)
337
data = io.BytesIO(simple_file)
338
test_file = EpsImagePlugin.EpsImageFile(data)
339
assert test_file.info["Comment1"] == "Some Value"
340
assert test_file.info["SecondComment"] == "Another Value"
341
assert test_file.size == (100, 100)
344
@pytest.mark.parametrize(
347
"Tests/images/illu10_no_preview.eps",
348
"Tests/images/illu10_preview.eps",
349
"Tests/images/illuCS6_no_preview.eps",
350
"Tests/images/illuCS6_preview.eps",
353
def test_open_eps(filename: str) -> None:
354
# https://github.com/python-pillow/Pillow/issues/1104
355
with Image.open(filename) as img:
356
assert img.mode == "RGB"
359
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
360
def test_emptyline() -> None:
361
# Test file includes an empty line in the header data
362
emptyline_file = "Tests/images/zero_bb_emptyline.eps"
364
with Image.open(emptyline_file) as image:
366
assert image.mode == "RGB"
367
assert image.size == (460, 352)
368
assert image.format == "EPS"
371
@pytest.mark.timeout(timeout=5)
372
@pytest.mark.parametrize(
374
["Tests/images/timeout-d675703545fee17acab56e5fec644c19979175de.eps"],
376
def test_timeout(test_file: str) -> None:
377
with open(test_file, "rb") as f:
378
with pytest.raises(UnidentifiedImageError):
383
def test_bounding_box_in_trailer() -> None:
384
# Check bounding boxes are parsed in the same way
385
# when specified in the header and the trailer
387
Image.open("Tests/images/zero_bb_trailer.eps") as trailer_image,
388
Image.open(FILE1) as header_image,
390
assert trailer_image.size == header_image.size
393
def test_eof_before_bounding_box() -> None:
394
with pytest.raises(OSError):
395
with Image.open("Tests/images/zero_bb_eof_before_boundingbox.eps"):
399
def test_invalid_data_after_eof() -> None:
400
with open("Tests/images/illuCS6_preview.eps", "rb") as f:
401
img_bytes = io.BytesIO(f.read() + b"\r\n%" + (b" " * 255))
403
with Image.open(img_bytes) as img:
404
assert img.mode == "RGB"