1
from __future__ import annotations
6
from pathlib import Path
15
UnidentifiedImageError,
23
assert_image_similar_tofile,
25
skip_unless_feature_version,
28
EXTRA_DIR = "Tests/images/jpeg2000"
30
pytestmark = skip_unless_feature("jpg_2000")
32
test_card = Image.open("Tests/images/test-card.png")
40
def roundtrip(im: Image.Image, **options: Any) -> Image.Image:
42
im.save(out, "JPEG2000", **options)
44
with Image.open(out) as im:
49
def test_sanity() -> None:
51
version = features.version_codec("jpg_2000")
52
assert version is not None
53
assert re.search(r"\d+\.\d+\.\d+$", version)
55
with Image.open("Tests/images/test-card-lossless.jp2") as im:
57
assert px[0, 0] == (0, 0, 0)
58
assert im.mode == "RGB"
59
assert im.size == (640, 480)
60
assert im.format == "JPEG2000"
61
assert im.get_format_mimetype() == "image/jp2"
64
def test_jpf() -> None:
65
with Image.open("Tests/images/balloon.jpf") as im:
66
assert im.format == "JPEG2000"
67
assert im.get_format_mimetype() == "image/jpx"
70
def test_invalid_file() -> None:
71
invalid_file = "Tests/images/flower.jpg"
73
with pytest.raises(SyntaxError):
74
Jpeg2KImagePlugin.Jpeg2KImageFile(invalid_file)
77
def test_bytesio() -> None:
78
with open("Tests/images/test-card-lossless.jp2", "rb") as f:
79
data = BytesIO(f.read())
80
with Image.open(data) as im:
82
assert_image_similar(im, test_card, 1.0e-3)
89
def test_lossless(tmp_path: Path) -> None:
90
with Image.open("Tests/images/test-card-lossless.jp2") as im:
92
outfile = str(tmp_path / "temp_test-card.png")
94
assert_image_similar(im, test_card, 1.0e-3)
97
def test_lossy_tiled() -> None:
98
assert_image_similar_tofile(
99
test_card, "Tests/images/test-card-lossy-tiled.jp2", 2.0
103
def test_lossless_rt() -> None:
104
im = roundtrip(test_card)
105
assert_image_equal(im, test_card)
108
def test_lossy_rt() -> None:
109
im = roundtrip(test_card, quality_layers=[20])
110
assert_image_similar(im, test_card, 2.0)
113
def test_tiled_rt() -> None:
114
im = roundtrip(test_card, tile_size=(128, 128))
115
assert_image_equal(im, test_card)
118
def test_tiled_offset_rt() -> None:
119
im = roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(32, 32))
120
assert_image_equal(im, test_card)
123
def test_tiled_offset_too_small() -> None:
124
with pytest.raises(ValueError):
125
roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(128, 32))
128
def test_irreversible_rt() -> None:
129
im = roundtrip(test_card, irreversible=True, quality_layers=[20])
130
assert_image_similar(im, test_card, 2.0)
133
def test_prog_qual_rt() -> None:
134
im = roundtrip(test_card, quality_layers=[60, 40, 20], progression="LRCP")
135
assert_image_similar(im, test_card, 2.0)
138
def test_prog_res_rt() -> None:
139
im = roundtrip(test_card, num_resolutions=8, progression="RLCP")
140
assert_image_equal(im, test_card)
143
@pytest.mark.parametrize("num_resolutions", range(2, 6))
144
def test_default_num_resolutions(num_resolutions: int) -> None:
145
d = 1 << (num_resolutions - 1)
146
im = test_card.resize((d - 1, d - 1))
147
with pytest.raises(OSError):
148
roundtrip(im, num_resolutions=num_resolutions)
149
reloaded = roundtrip(im)
150
assert_image_equal(im, reloaded)
153
def test_reduce() -> None:
154
with Image.open("Tests/images/test-card-lossless.jp2") as im:
155
assert callable(im.reduce)
158
assert im.reduce == 2
161
assert im.size == (160, 120)
163
im.thumbnail((40, 40))
164
assert im.size == (40, 30)
167
def test_load_dpi() -> None:
168
with Image.open("Tests/images/test-card-lossless.jp2") as im:
169
assert im.info["dpi"] == (71.9836, 71.9836)
171
with Image.open("Tests/images/zero_dpi.jp2") as im:
172
assert "dpi" not in im.info
175
def test_restricted_icc_profile() -> None:
176
ImageFile.LOAD_TRUNCATED_IMAGES = True
179
with Image.open("Tests/images/balloon_eciRGBv2_aware.jp2") as im:
180
assert im.mode == "RGB"
182
ImageFile.LOAD_TRUNCATED_IMAGES = False
185
def test_header_errors() -> None:
187
"Tests/images/invalid_header_length.jp2",
188
"Tests/images/not_enough_data.jp2",
190
with pytest.raises(UnidentifiedImageError):
191
with Image.open(path):
194
with pytest.raises(OSError):
195
with Image.open("Tests/images/expected_to_read.jp2"):
199
def test_layers_type(tmp_path: Path) -> None:
200
outfile = str(tmp_path / "temp_layers.jp2")
201
for quality_layers in [[100, 50, 10], (100, 50, 10), None]:
202
test_card.save(outfile, quality_layers=quality_layers)
204
for quality_layers_str in ["quality_layers", ("100", "50", "10")]:
205
with pytest.raises(ValueError):
206
test_card.save(outfile, quality_layers=quality_layers_str)
209
def test_layers() -> None:
211
test_card.save(out, "JPEG2000", quality_layers=[100, 50, 10], progression="LRCP")
214
with Image.open(out) as im:
217
assert_image_similar(im, test_card, 13)
220
with Image.open(out) as im:
223
assert_image_similar(im, test_card, 0.4)
226
@pytest.mark.parametrize(
227
"name, args, offset, data",
229
("foo.j2k", {}, 0, b"\xff\x4f"),
230
("foo.jp2", {}, 4, b"jP"),
231
(None, {"no_jp2": True}, 0, b"\xff\x4f"),
232
("foo.j2k", {"no_jp2": True}, 0, b"\xff\x4f"),
233
("foo.jp2", {"no_jp2": True}, 0, b"\xff\x4f"),
234
("foo.j2k", {"no_jp2": False}, 0, b"\xff\x4f"),
235
("foo.jp2", {"no_jp2": False}, 4, b"jP"),
236
(None, {"no_jp2": False}, 4, b"jP"),
239
def test_no_jp2(name: str, args: dict[str, bool], offset: int, data: bytes) -> None:
243
test_card.save(out, "JPEG2000", **args)
245
assert out.read(2) == data
248
def test_mct() -> None:
252
test_card.save(out, "JPEG2000", mct=val, no_jp2=True)
254
assert out.getvalue()[59] == val
255
with Image.open(out) as im:
256
assert_image_similar(im, test_card, 1.0e-3)
261
with Image.open("Tests/images/16bit.cropped.jp2") as jp2:
262
jp2.save(out, "JPEG2000", mct=val, no_jp2=True)
264
assert out.getvalue()[53] == 0
265
with Image.open(out) as im:
266
assert_image_similar(im, jp2, 1.0e-3)
269
def test_sgnd(tmp_path: Path) -> None:
270
outfile = str(tmp_path / "temp.jp2")
272
im = Image.new("L", (1, 1))
274
with Image.open(outfile) as reloaded:
275
assert reloaded.getpixel((0, 0)) == 0
277
im = Image.new("L", (1, 1))
278
im.save(outfile, signed=True)
279
with Image.open(outfile) as reloaded_signed:
280
assert reloaded_signed.getpixel((0, 0)) == 128
283
@pytest.mark.parametrize("ext", (".j2k", ".jp2"))
284
def test_rgba(ext: str) -> None:
286
with Image.open("Tests/images/rgb_trns_ycbc" + ext) as im:
291
assert im.mode == "RGBA"
295
not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
297
@skip_unless_feature_version("jpg_2000", "2.5.1")
298
def test_cmyk() -> None:
299
with Image.open(f"{EXTRA_DIR}/issue205.jp2") as im:
300
assert im.mode == "CMYK"
301
assert im.getpixel((0, 0)) == (185, 134, 0, 0)
304
@pytest.mark.parametrize("ext", (".j2k", ".jp2"))
305
def test_16bit_monochrome_has_correct_mode(ext: str) -> None:
306
with Image.open("Tests/images/16bit.cropped" + ext) as im:
308
assert im.mode == "I;16"
311
def test_16bit_monochrome_jp2_like_tiff() -> None:
312
with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit:
313
assert_image_similar_tofile(tiff_16bit, "Tests/images/16bit.cropped.jp2", 1e-3)
316
def test_16bit_monochrome_j2k_like_tiff() -> None:
317
with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit:
318
assert_image_similar_tofile(tiff_16bit, "Tests/images/16bit.cropped.j2k", 1e-3)
321
def test_16bit_j2k_roundtrips() -> None:
322
with Image.open("Tests/images/16bit.cropped.j2k") as j2k:
324
assert_image_equal(im, j2k)
327
def test_16bit_jp2_roundtrips() -> None:
328
with Image.open("Tests/images/16bit.cropped.jp2") as jp2:
330
assert_image_equal(im, jp2)
333
def test_issue_6194() -> None:
334
with Image.open("Tests/images/issue_6194.j2k") as im:
335
assert im.getpixel((5, 5)) == 31
338
def test_unknown_j2k_mode() -> None:
339
with pytest.raises(UnidentifiedImageError):
340
with Image.open("Tests/images/unknown_mode.j2k"):
344
def test_unbound_local() -> None:
346
with pytest.raises(UnidentifiedImageError):
347
with Image.open("Tests/images/unbound_variable.jp2"):
351
def test_parser_feed() -> None:
353
with open("Tests/images/test-card-lossless.jp2", "rb") as f:
357
p = ImageFile.Parser()
361
assert p.image is not None
362
assert p.image.size == (640, 480)
366
not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
368
@pytest.mark.parametrize("name", ("subsampling_1", "subsampling_2", "zoo1", "zoo2"))
369
def test_subsampling_decode(name: str) -> None:
370
test = f"{EXTRA_DIR}/{name}.jp2"
371
reference = f"{EXTRA_DIR}/{name}.ppm"
373
with Image.open(test) as im:
375
with Image.open(reference) as im2:
376
width, height = im2.size
380
width, height = width * 2, height * 2
381
expected = im2.resize((width, height), Image.Resampling.NEAREST)
382
assert_image_similar(im, expected, epsilon)
386
not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
388
def test_pclr() -> None:
389
with Image.open(f"{EXTRA_DIR}/issue104_jpxstream.jp2") as im:
390
assert im.mode == "P"
391
assert len(im.palette.colors) == 256
392
assert im.palette.colors[(255, 255, 255)] == 0
395
def test_comment() -> None:
396
with Image.open("Tests/images/comment.jp2") as im:
397
assert im.info["comment"] == b"Created by OpenJPEG version 2.5.0"
400
with open("Tests/images/comment.jp2", "rb") as fp:
401
b = BytesIO(fp.read(130))
402
with Image.open(b) as im:
406
def test_save_comment() -> None:
407
for comment in ("Created by Pillow", b"Created by Pillow"):
409
test_card.save(out, "JPEG2000", comment=comment)
411
with Image.open(out) as im:
412
assert im.info["comment"] == b"Created by Pillow"
415
long_comment = b" " * 65531
416
test_card.save(out, "JPEG2000", comment=long_comment)
417
with Image.open(out) as im:
418
assert im.info["comment"] == long_comment
420
with pytest.raises(ValueError):
421
test_card.save(out, "JPEG2000", comment=long_comment + b" ")
424
@pytest.mark.parametrize(
427
"Tests/images/crash-4fb027452e6988530aa5dabee76eecacb3b79f8a.j2k",
428
"Tests/images/crash-7d4c83eb92150fb8f1653a697703ae06ae7c4998.j2k",
429
"Tests/images/crash-ccca68ff40171fdae983d924e127a721cab2bd50.j2k",
430
"Tests/images/crash-d2c93af851d3ab9a19e34503626368b2ecde9c03.j2k",
433
def test_crashes(test_file: str) -> None:
434
with open(test_file, "rb") as f:
435
with Image.open(f) as im:
443
@skip_unless_feature_version("jpg_2000", "2.4.0")
444
def test_plt_marker() -> None:
447
test_card.save(out, "JPEG2000", no_jp2=True, plt=True)
452
pytest.fail("End of stream without PLT")
454
jp2_boxid = _binary.i16be(marker)
455
if jp2_boxid == 0xFF4F:
458
elif jp2_boxid == 0xFF58:
461
elif jp2_boxid == 0xFF93:
462
pytest.fail("SOD without finding PLT first")
465
length = _binary.i16be(hdr)
466
out.seek(length - 2, os.SEEK_CUR)
469
def test_9bit() -> None:
470
with Image.open("Tests/images/9bit.j2k") as im:
471
assert im.mode == "I;16"
472
assert im.size == (128, 128)