mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-02-04 21:50:54 +03:00
Merge branch 'master' into apng
This commit is contained in:
commit
9f61be4c72
15
CHANGES.rst
15
CHANGES.rst
|
@ -5,6 +5,21 @@ Changelog (Pillow)
|
||||||
7.1.0 (unreleased)
|
7.1.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Add JPEG comment to info dictionary #4455
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fix size calculation of Image.thumbnail() #4404
|
||||||
|
[orlnub123]
|
||||||
|
|
||||||
|
- Fixed stroke on FreeType < 2.9 #4401
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- If present, only use alpha channel for bounding box #4454
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Warn if an unknown feature is passed to features.check() #4438
|
||||||
|
[jdufresne]
|
||||||
|
|
||||||
- Fix Name field length when saving IM images #4424
|
- Fix Name field length when saving IM images #4424
|
||||||
[hugovk, radarhere]
|
[hugovk, radarhere]
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ Helper functions.
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
@ -192,21 +191,9 @@ class PillowTestCase(unittest.TestCase):
|
||||||
self.addCleanup(self.delete_tempfile, path)
|
self.addCleanup(self.delete_tempfile, path)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
def open_withImagemagick(self, f):
|
|
||||||
if not imagemagick_available():
|
|
||||||
raise OSError()
|
|
||||||
|
|
||||||
outfile = self.tempfile("temp.png")
|
@pytest.mark.skipif(sys.platform.startswith("win32"), reason="Requires Unix or macOS")
|
||||||
rc = subprocess.call(
|
class PillowLeakTestCase:
|
||||||
[IMCONVERT, f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
|
|
||||||
)
|
|
||||||
if rc:
|
|
||||||
raise OSError
|
|
||||||
return Image.open(outfile)
|
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(sys.platform.startswith("win32"), "requires Unix or macOS")
|
|
||||||
class PillowLeakTestCase(PillowTestCase):
|
|
||||||
# requires unix/macOS
|
# requires unix/macOS
|
||||||
iterations = 100 # count
|
iterations = 100 # count
|
||||||
mem_limit = 512 # k
|
mem_limit = 512 # k
|
||||||
|
|
BIN
Tests/images/imagedraw_stroke_descender.png
Normal file
BIN
Tests/images/imagedraw_stroke_descender.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
|
@ -3,109 +3,108 @@ import os
|
||||||
import pytest
|
import pytest
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import PillowTestCase, assert_image_similar
|
from .helper import assert_image_similar
|
||||||
|
|
||||||
base = os.path.join("Tests", "images", "bmp")
|
base = os.path.join("Tests", "images", "bmp")
|
||||||
|
|
||||||
|
|
||||||
class TestBmpReference(PillowTestCase):
|
def get_files(d, ext=".bmp"):
|
||||||
def get_files(self, d, ext=".bmp"):
|
return [
|
||||||
return [
|
os.path.join(base, d, f) for f in os.listdir(os.path.join(base, d)) if ext in f
|
||||||
os.path.join(base, d, f)
|
]
|
||||||
for f in os.listdir(os.path.join(base, d))
|
|
||||||
if ext in f
|
|
||||||
]
|
|
||||||
|
|
||||||
def test_bad(self):
|
|
||||||
""" These shouldn't crash/dos, but they shouldn't return anything
|
|
||||||
either """
|
|
||||||
for f in self.get_files("b"):
|
|
||||||
|
|
||||||
def open(f):
|
def test_bad():
|
||||||
try:
|
""" These shouldn't crash/dos, but they shouldn't return anything
|
||||||
with Image.open(f) as im:
|
either """
|
||||||
im.load()
|
for f in get_files("b"):
|
||||||
except Exception: # as msg:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Assert that there is no unclosed file warning
|
def open(f):
|
||||||
pytest.warns(None, open, f)
|
|
||||||
|
|
||||||
def test_questionable(self):
|
|
||||||
""" These shouldn't crash/dos, but it's not well defined that these
|
|
||||||
are in spec """
|
|
||||||
supported = [
|
|
||||||
"pal8os2v2.bmp",
|
|
||||||
"rgb24prof.bmp",
|
|
||||||
"pal1p1.bmp",
|
|
||||||
"pal8offs.bmp",
|
|
||||||
"rgb24lprof.bmp",
|
|
||||||
"rgb32fakealpha.bmp",
|
|
||||||
"rgb24largepal.bmp",
|
|
||||||
"pal8os2sp.bmp",
|
|
||||||
"rgb32bf-xbgr.bmp",
|
|
||||||
]
|
|
||||||
for f in self.get_files("q"):
|
|
||||||
try:
|
try:
|
||||||
with Image.open(f) as im:
|
with Image.open(f) as im:
|
||||||
im.load()
|
im.load()
|
||||||
if os.path.basename(f) not in supported:
|
|
||||||
print("Please add %s to the partially supported bmp specs." % f)
|
|
||||||
except Exception: # as msg:
|
except Exception: # as msg:
|
||||||
if os.path.basename(f) in supported:
|
pass
|
||||||
raise
|
|
||||||
|
|
||||||
def test_good(self):
|
# Assert that there is no unclosed file warning
|
||||||
""" These should all work. There's a set of target files in the
|
pytest.warns(None, open, f)
|
||||||
html directory that we can compare against. """
|
|
||||||
|
|
||||||
# Target files, if they're not just replacing the extension
|
|
||||||
file_map = {
|
|
||||||
"pal1wb.bmp": "pal1.png",
|
|
||||||
"pal4rle.bmp": "pal4.png",
|
|
||||||
"pal8-0.bmp": "pal8.png",
|
|
||||||
"pal8rle.bmp": "pal8.png",
|
|
||||||
"pal8topdown.bmp": "pal8.png",
|
|
||||||
"pal8nonsquare.bmp": "pal8nonsquare-v.png",
|
|
||||||
"pal8os2.bmp": "pal8.png",
|
|
||||||
"pal8os2sp.bmp": "pal8.png",
|
|
||||||
"pal8os2v2.bmp": "pal8.png",
|
|
||||||
"pal8os2v2-16.bmp": "pal8.png",
|
|
||||||
"pal8v4.bmp": "pal8.png",
|
|
||||||
"pal8v5.bmp": "pal8.png",
|
|
||||||
"rgb16-565pal.bmp": "rgb16-565.png",
|
|
||||||
"rgb24pal.bmp": "rgb24.png",
|
|
||||||
"rgb32.bmp": "rgb24.png",
|
|
||||||
"rgb32bf.bmp": "rgb24.png",
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_compare(f):
|
def test_questionable():
|
||||||
name = os.path.split(f)[1]
|
""" These shouldn't crash/dos, but it's not well defined that these
|
||||||
if name in file_map:
|
are in spec """
|
||||||
return os.path.join(base, "html", file_map[name])
|
supported = [
|
||||||
name = os.path.splitext(name)[0]
|
"pal8os2v2.bmp",
|
||||||
return os.path.join(base, "html", "%s.png" % name)
|
"rgb24prof.bmp",
|
||||||
|
"pal1p1.bmp",
|
||||||
|
"pal8offs.bmp",
|
||||||
|
"rgb24lprof.bmp",
|
||||||
|
"rgb32fakealpha.bmp",
|
||||||
|
"rgb24largepal.bmp",
|
||||||
|
"pal8os2sp.bmp",
|
||||||
|
"rgb32bf-xbgr.bmp",
|
||||||
|
]
|
||||||
|
for f in get_files("q"):
|
||||||
|
try:
|
||||||
|
with Image.open(f) as im:
|
||||||
|
im.load()
|
||||||
|
if os.path.basename(f) not in supported:
|
||||||
|
print("Please add %s to the partially supported bmp specs." % f)
|
||||||
|
except Exception: # as msg:
|
||||||
|
if os.path.basename(f) in supported:
|
||||||
|
raise
|
||||||
|
|
||||||
for f in self.get_files("g"):
|
|
||||||
try:
|
|
||||||
with Image.open(f) as im:
|
|
||||||
im.load()
|
|
||||||
with Image.open(get_compare(f)) as compare:
|
|
||||||
compare.load()
|
|
||||||
if im.mode == "P":
|
|
||||||
# assert image similar doesn't really work
|
|
||||||
# with paletized image, since the palette might
|
|
||||||
# be differently ordered for an equivalent image.
|
|
||||||
im = im.convert("RGBA")
|
|
||||||
compare = im.convert("RGBA")
|
|
||||||
assert_image_similar(im, compare, 5)
|
|
||||||
|
|
||||||
except Exception as msg:
|
def test_good():
|
||||||
# there are three here that are unsupported:
|
""" These should all work. There's a set of target files in the
|
||||||
unsupported = (
|
html directory that we can compare against. """
|
||||||
os.path.join(base, "g", "rgb32bf.bmp"),
|
|
||||||
os.path.join(base, "g", "pal8rle.bmp"),
|
# Target files, if they're not just replacing the extension
|
||||||
os.path.join(base, "g", "pal4rle.bmp"),
|
file_map = {
|
||||||
)
|
"pal1wb.bmp": "pal1.png",
|
||||||
if f not in unsupported:
|
"pal4rle.bmp": "pal4.png",
|
||||||
self.fail("Unsupported Image {}: {}".format(f, msg))
|
"pal8-0.bmp": "pal8.png",
|
||||||
|
"pal8rle.bmp": "pal8.png",
|
||||||
|
"pal8topdown.bmp": "pal8.png",
|
||||||
|
"pal8nonsquare.bmp": "pal8nonsquare-v.png",
|
||||||
|
"pal8os2.bmp": "pal8.png",
|
||||||
|
"pal8os2sp.bmp": "pal8.png",
|
||||||
|
"pal8os2v2.bmp": "pal8.png",
|
||||||
|
"pal8os2v2-16.bmp": "pal8.png",
|
||||||
|
"pal8v4.bmp": "pal8.png",
|
||||||
|
"pal8v5.bmp": "pal8.png",
|
||||||
|
"rgb16-565pal.bmp": "rgb16-565.png",
|
||||||
|
"rgb24pal.bmp": "rgb24.png",
|
||||||
|
"rgb32.bmp": "rgb24.png",
|
||||||
|
"rgb32bf.bmp": "rgb24.png",
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_compare(f):
|
||||||
|
name = os.path.split(f)[1]
|
||||||
|
if name in file_map:
|
||||||
|
return os.path.join(base, "html", file_map[name])
|
||||||
|
name = os.path.splitext(name)[0]
|
||||||
|
return os.path.join(base, "html", "%s.png" % name)
|
||||||
|
|
||||||
|
for f in get_files("g"):
|
||||||
|
try:
|
||||||
|
with Image.open(f) as im:
|
||||||
|
im.load()
|
||||||
|
with Image.open(get_compare(f)) as compare:
|
||||||
|
compare.load()
|
||||||
|
if im.mode == "P":
|
||||||
|
# assert image similar doesn't really work
|
||||||
|
# with paletized image, since the palette might
|
||||||
|
# be differently ordered for an equivalent image.
|
||||||
|
im = im.convert("RGBA")
|
||||||
|
compare = im.convert("RGBA")
|
||||||
|
assert_image_similar(im, compare, 5)
|
||||||
|
|
||||||
|
except Exception as msg:
|
||||||
|
# there are three here that are unsupported:
|
||||||
|
unsupported = (
|
||||||
|
os.path.join(base, "g", "rgb32bf.bmp"),
|
||||||
|
os.path.join(base, "g", "pal8rle.bmp"),
|
||||||
|
os.path.join(base, "g", "pal4rle.bmp"),
|
||||||
|
)
|
||||||
|
assert f in unsupported, "Unsupported Image {}: {}".format(f, msg)
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import PillowTestCase, is_pypy
|
from .helper import is_pypy
|
||||||
|
|
||||||
|
|
||||||
def test_get_stats():
|
def test_get_stats():
|
||||||
|
@ -32,8 +31,8 @@ def test_reset_stats():
|
||||||
assert stats["blocks_cached"] == 0
|
assert stats["blocks_cached"] == 0
|
||||||
|
|
||||||
|
|
||||||
class TestCoreMemory(PillowTestCase):
|
class TestCoreMemory:
|
||||||
def tearDown(self):
|
def teardown_method(self):
|
||||||
# Restore default values
|
# Restore default values
|
||||||
Image.core.set_alignment(1)
|
Image.core.set_alignment(1)
|
||||||
Image.core.set_block_size(1024 * 1024)
|
Image.core.set_block_size(1024 * 1024)
|
||||||
|
@ -114,7 +113,7 @@ class TestCoreMemory(PillowTestCase):
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Image.core.set_blocks_max(2 ** 29)
|
Image.core.set_blocks_max(2 ** 29)
|
||||||
|
|
||||||
@unittest.skipIf(is_pypy(), "images are not collected")
|
@pytest.mark.skipif(is_pypy(), reason="Images not collected")
|
||||||
def test_set_blocks_max_stats(self):
|
def test_set_blocks_max_stats(self):
|
||||||
Image.core.reset_stats()
|
Image.core.reset_stats()
|
||||||
Image.core.set_blocks_max(128)
|
Image.core.set_blocks_max(128)
|
||||||
|
@ -129,7 +128,7 @@ class TestCoreMemory(PillowTestCase):
|
||||||
assert stats["freed_blocks"] == 0
|
assert stats["freed_blocks"] == 0
|
||||||
assert stats["blocks_cached"] == 64
|
assert stats["blocks_cached"] == 64
|
||||||
|
|
||||||
@unittest.skipIf(is_pypy(), "images are not collected")
|
@pytest.mark.skipif(is_pypy(), reason="Images not collected")
|
||||||
def test_clear_cache_stats(self):
|
def test_clear_cache_stats(self):
|
||||||
Image.core.reset_stats()
|
Image.core.reset_stats()
|
||||||
Image.core.clear_cache()
|
Image.core.clear_cache()
|
||||||
|
@ -163,8 +162,8 @@ class TestCoreMemory(PillowTestCase):
|
||||||
assert stats["freed_blocks"] >= 16
|
assert stats["freed_blocks"] >= 16
|
||||||
|
|
||||||
|
|
||||||
class TestEnvVars(PillowTestCase):
|
class TestEnvVars:
|
||||||
def tearDown(self):
|
def teardown_method(self):
|
||||||
# Restore default values
|
# Restore default values
|
||||||
Image.core.set_alignment(1)
|
Image.core.set_alignment(1)
|
||||||
Image.core.set_block_size(1024 * 1024)
|
Image.core.set_block_size(1024 * 1024)
|
||||||
|
|
|
@ -44,6 +44,13 @@ def test_check_modules():
|
||||||
assert features.check_codec(feature) in [True, False]
|
assert features.check_codec(feature) in [True, False]
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_warns_on_nonexistent():
|
||||||
|
with pytest.warns(UserWarning) as cm:
|
||||||
|
has_feature = features.check("typo")
|
||||||
|
assert has_feature is False
|
||||||
|
assert str(cm[-1].message) == "Unknown feature 'typo'."
|
||||||
|
|
||||||
|
|
||||||
def test_supported_modules():
|
def test_supported_modules():
|
||||||
assert isinstance(features.get_supported_modules(), list)
|
assert isinstance(features.get_supported_modules(), list)
|
||||||
assert isinstance(features.get_supported_codecs(), list)
|
assert isinstance(features.get_supported_codecs(), list)
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import PillowTestCase, assert_image_equal
|
from .helper import assert_image_equal
|
||||||
|
|
||||||
|
|
||||||
class TestFileBlp(PillowTestCase):
|
def test_load_blp2_raw():
|
||||||
def test_load_blp2_raw(self):
|
with Image.open("Tests/images/blp/blp2_raw.blp") as im:
|
||||||
with Image.open("Tests/images/blp/blp2_raw.blp") as im:
|
with Image.open("Tests/images/blp/blp2_raw.png") as target:
|
||||||
with Image.open("Tests/images/blp/blp2_raw.png") as target:
|
assert_image_equal(im, target)
|
||||||
assert_image_equal(im, target)
|
|
||||||
|
|
||||||
def test_load_blp2_dxt1(self):
|
|
||||||
with Image.open("Tests/images/blp/blp2_dxt1.blp") as im:
|
|
||||||
with Image.open("Tests/images/blp/blp2_dxt1.png") as target:
|
|
||||||
assert_image_equal(im, target)
|
|
||||||
|
|
||||||
def test_load_blp2_dxt1a(self):
|
def test_load_blp2_dxt1():
|
||||||
with Image.open("Tests/images/blp/blp2_dxt1a.blp") as im:
|
with Image.open("Tests/images/blp/blp2_dxt1.blp") as im:
|
||||||
with Image.open("Tests/images/blp/blp2_dxt1a.png") as target:
|
with Image.open("Tests/images/blp/blp2_dxt1.png") as target:
|
||||||
assert_image_equal(im, target)
|
assert_image_equal(im, target)
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_blp2_dxt1a():
|
||||||
|
with Image.open("Tests/images/blp/blp2_dxt1a.blp") as im:
|
||||||
|
with Image.open("Tests/images/blp/blp2_dxt1a.png") as target:
|
||||||
|
assert_image_equal(im, target)
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import PillowTestCase, assert_image_equal, assert_image_similar
|
from .helper import assert_image_equal, assert_image_similar
|
||||||
|
|
||||||
|
|
||||||
class TestFileFtex(PillowTestCase):
|
def test_load_raw():
|
||||||
def test_load_raw(self):
|
with Image.open("Tests/images/ftex_uncompressed.ftu") as im:
|
||||||
with Image.open("Tests/images/ftex_uncompressed.ftu") as im:
|
with Image.open("Tests/images/ftex_uncompressed.png") as target:
|
||||||
with Image.open("Tests/images/ftex_uncompressed.png") as target:
|
assert_image_equal(im, target)
|
||||||
assert_image_equal(im, target)
|
|
||||||
|
|
||||||
def test_load_dxt1(self):
|
|
||||||
with Image.open("Tests/images/ftex_dxt1.ftc") as im:
|
def test_load_dxt1():
|
||||||
with Image.open("Tests/images/ftex_dxt1.png") as target:
|
with Image.open("Tests/images/ftex_dxt1.ftc") as im:
|
||||||
assert_image_similar(im, target.convert("RGBA"), 15)
|
with Image.open("Tests/images/ftex_dxt1.png") as target:
|
||||||
|
assert_image_similar(im, target.convert("RGBA"), 15)
|
||||||
|
|
|
@ -6,7 +6,6 @@ import pytest
|
||||||
from PIL import ExifTags, Image, ImageFile, JpegImagePlugin
|
from PIL import ExifTags, Image, ImageFile, JpegImagePlugin
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
PillowTestCase,
|
|
||||||
assert_image,
|
assert_image,
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
assert_image_similar,
|
assert_image_similar,
|
||||||
|
@ -15,14 +14,13 @@ from .helper import (
|
||||||
hopper,
|
hopper,
|
||||||
is_win32,
|
is_win32,
|
||||||
skip_unless_feature,
|
skip_unless_feature,
|
||||||
unittest,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
TEST_FILE = "Tests/images/hopper.jpg"
|
TEST_FILE = "Tests/images/hopper.jpg"
|
||||||
|
|
||||||
|
|
||||||
@skip_unless_feature("jpg")
|
@skip_unless_feature("jpg")
|
||||||
class TestFileJpeg(PillowTestCase):
|
class TestFileJpeg:
|
||||||
def roundtrip(self, im, **options):
|
def roundtrip(self, im, **options):
|
||||||
out = BytesIO()
|
out = BytesIO()
|
||||||
im.save(out, "JPEG", **options)
|
im.save(out, "JPEG", **options)
|
||||||
|
@ -62,6 +60,8 @@ class TestFileJpeg(PillowTestCase):
|
||||||
)
|
)
|
||||||
assert len(im.applist) == 2
|
assert len(im.applist) == 2
|
||||||
|
|
||||||
|
assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0\x00"
|
||||||
|
|
||||||
def test_cmyk(self):
|
def test_cmyk(self):
|
||||||
# Test CMYK handling. Thanks to Tim and Charlie for test data,
|
# Test CMYK handling. Thanks to Tim and Charlie for test data,
|
||||||
# Michael for getting me to look one more time.
|
# Michael for getting me to look one more time.
|
||||||
|
@ -101,13 +101,13 @@ class TestFileJpeg(PillowTestCase):
|
||||||
assert test(100, 200) == (100, 200)
|
assert test(100, 200) == (100, 200)
|
||||||
assert test(0) is None # square pixels
|
assert test(0) is None # square pixels
|
||||||
|
|
||||||
def test_icc(self):
|
def test_icc(self, tmp_path):
|
||||||
# Test ICC support
|
# Test ICC support
|
||||||
with Image.open("Tests/images/rgb.jpg") as im1:
|
with Image.open("Tests/images/rgb.jpg") as im1:
|
||||||
icc_profile = im1.info["icc_profile"]
|
icc_profile = im1.info["icc_profile"]
|
||||||
assert len(icc_profile) == 3144
|
assert len(icc_profile) == 3144
|
||||||
# Roundtrip via physical file.
|
# Roundtrip via physical file.
|
||||||
f = self.tempfile("temp.jpg")
|
f = str(tmp_path / "temp.jpg")
|
||||||
im1.save(f, icc_profile=icc_profile)
|
im1.save(f, icc_profile=icc_profile)
|
||||||
with Image.open(f) as im2:
|
with Image.open(f) as im2:
|
||||||
assert im2.info.get("icc_profile") == icc_profile
|
assert im2.info.get("icc_profile") == icc_profile
|
||||||
|
@ -140,12 +140,12 @@ class TestFileJpeg(PillowTestCase):
|
||||||
test(ImageFile.MAXBLOCK + 1) # full buffer block plus one byte
|
test(ImageFile.MAXBLOCK + 1) # full buffer block plus one byte
|
||||||
test(ImageFile.MAXBLOCK * 4 + 3) # large block
|
test(ImageFile.MAXBLOCK * 4 + 3) # large block
|
||||||
|
|
||||||
def test_large_icc_meta(self):
|
def test_large_icc_meta(self, tmp_path):
|
||||||
# https://github.com/python-pillow/Pillow/issues/148
|
# https://github.com/python-pillow/Pillow/issues/148
|
||||||
# Sometimes the meta data on the icc_profile block is bigger than
|
# Sometimes the meta data on the icc_profile block is bigger than
|
||||||
# Image.MAXBLOCK or the image size.
|
# Image.MAXBLOCK or the image size.
|
||||||
with Image.open("Tests/images/icc_profile_big.jpg") as im:
|
with Image.open("Tests/images/icc_profile_big.jpg") as im:
|
||||||
f = self.tempfile("temp.jpg")
|
f = str(tmp_path / "temp.jpg")
|
||||||
icc_profile = im.info["icc_profile"]
|
icc_profile = im.info["icc_profile"]
|
||||||
# Should not raise IOError for image with icc larger than image size.
|
# Should not raise IOError for image with icc larger than image size.
|
||||||
im.save(
|
im.save(
|
||||||
|
@ -166,9 +166,9 @@ class TestFileJpeg(PillowTestCase):
|
||||||
assert im1.bytes >= im2.bytes
|
assert im1.bytes >= im2.bytes
|
||||||
assert im1.bytes >= im3.bytes
|
assert im1.bytes >= im3.bytes
|
||||||
|
|
||||||
def test_optimize_large_buffer(self):
|
def test_optimize_large_buffer(self, tmp_path):
|
||||||
# https://github.com/python-pillow/Pillow/issues/148
|
# https://github.com/python-pillow/Pillow/issues/148
|
||||||
f = self.tempfile("temp.jpg")
|
f = str(tmp_path / "temp.jpg")
|
||||||
# this requires ~ 1.5x Image.MAXBLOCK
|
# this requires ~ 1.5x Image.MAXBLOCK
|
||||||
im = Image.new("RGB", (4096, 4096), 0xFF3333)
|
im = Image.new("RGB", (4096, 4096), 0xFF3333)
|
||||||
im.save(f, format="JPEG", optimize=True)
|
im.save(f, format="JPEG", optimize=True)
|
||||||
|
@ -184,14 +184,14 @@ class TestFileJpeg(PillowTestCase):
|
||||||
assert_image_equal(im1, im3)
|
assert_image_equal(im1, im3)
|
||||||
assert im1.bytes >= im3.bytes
|
assert im1.bytes >= im3.bytes
|
||||||
|
|
||||||
def test_progressive_large_buffer(self):
|
def test_progressive_large_buffer(self, tmp_path):
|
||||||
f = self.tempfile("temp.jpg")
|
f = str(tmp_path / "temp.jpg")
|
||||||
# this requires ~ 1.5x Image.MAXBLOCK
|
# this requires ~ 1.5x Image.MAXBLOCK
|
||||||
im = Image.new("RGB", (4096, 4096), 0xFF3333)
|
im = Image.new("RGB", (4096, 4096), 0xFF3333)
|
||||||
im.save(f, format="JPEG", progressive=True)
|
im.save(f, format="JPEG", progressive=True)
|
||||||
|
|
||||||
def test_progressive_large_buffer_highest_quality(self):
|
def test_progressive_large_buffer_highest_quality(self, tmp_path):
|
||||||
f = self.tempfile("temp.jpg")
|
f = str(tmp_path / "temp.jpg")
|
||||||
im = self.gen_random_image((255, 255))
|
im = self.gen_random_image((255, 255))
|
||||||
# this requires more bytes than pixels in the image
|
# this requires more bytes than pixels in the image
|
||||||
im.save(f, format="JPEG", progressive=True, quality=100)
|
im.save(f, format="JPEG", progressive=True, quality=100)
|
||||||
|
@ -202,9 +202,9 @@ class TestFileJpeg(PillowTestCase):
|
||||||
im = self.gen_random_image((256, 256), "CMYK")
|
im = self.gen_random_image((256, 256), "CMYK")
|
||||||
im.save(f, format="JPEG", progressive=True, quality=94)
|
im.save(f, format="JPEG", progressive=True, quality=94)
|
||||||
|
|
||||||
def test_large_exif(self):
|
def test_large_exif(self, tmp_path):
|
||||||
# https://github.com/python-pillow/Pillow/issues/148
|
# https://github.com/python-pillow/Pillow/issues/148
|
||||||
f = self.tempfile("temp.jpg")
|
f = str(tmp_path / "temp.jpg")
|
||||||
im = hopper()
|
im = hopper()
|
||||||
im.save(f, "JPEG", quality=90, exif=b"1" * 65532)
|
im.save(f, "JPEG", quality=90, exif=b"1" * 65532)
|
||||||
|
|
||||||
|
@ -301,7 +301,7 @@ class TestFileJpeg(PillowTestCase):
|
||||||
|
|
||||||
im3 = self.roundtrip(hopper(), quality=0)
|
im3 = self.roundtrip(hopper(), quality=0)
|
||||||
assert_image(im1, im3.mode, im3.size)
|
assert_image(im1, im3.mode, im3.size)
|
||||||
self.assertGreater(im2.bytes, im3.bytes)
|
assert im2.bytes > im3.bytes
|
||||||
|
|
||||||
def test_smooth(self):
|
def test_smooth(self):
|
||||||
im1 = self.roundtrip(hopper())
|
im1 = self.roundtrip(hopper())
|
||||||
|
@ -346,18 +346,18 @@ class TestFileJpeg(PillowTestCase):
|
||||||
with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
|
with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
|
||||||
assert im._getmp() is None
|
assert im._getmp() is None
|
||||||
|
|
||||||
def test_quality_keep(self):
|
def test_quality_keep(self, tmp_path):
|
||||||
# RGB
|
# RGB
|
||||||
with Image.open("Tests/images/hopper.jpg") as im:
|
with Image.open("Tests/images/hopper.jpg") as im:
|
||||||
f = self.tempfile("temp.jpg")
|
f = str(tmp_path / "temp.jpg")
|
||||||
im.save(f, quality="keep")
|
im.save(f, quality="keep")
|
||||||
# Grayscale
|
# Grayscale
|
||||||
with Image.open("Tests/images/hopper_gray.jpg") as im:
|
with Image.open("Tests/images/hopper_gray.jpg") as im:
|
||||||
f = self.tempfile("temp.jpg")
|
f = str(tmp_path / "temp.jpg")
|
||||||
im.save(f, quality="keep")
|
im.save(f, quality="keep")
|
||||||
# CMYK
|
# CMYK
|
||||||
with Image.open("Tests/images/pil_sample_cmyk.jpg") as im:
|
with Image.open("Tests/images/pil_sample_cmyk.jpg") as im:
|
||||||
f = self.tempfile("temp.jpg")
|
f = str(tmp_path / "temp.jpg")
|
||||||
im.save(f, quality="keep")
|
im.save(f, quality="keep")
|
||||||
|
|
||||||
def test_junk_jpeg_header(self):
|
def test_junk_jpeg_header(self):
|
||||||
|
@ -389,16 +389,16 @@ class TestFileJpeg(PillowTestCase):
|
||||||
with pytest.raises(IOError):
|
with pytest.raises(IOError):
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
def _n_qtables_helper(self, n, test_file):
|
def test_qtables(self, tmp_path):
|
||||||
with Image.open(test_file) as im:
|
def _n_qtables_helper(n, test_file):
|
||||||
f = self.tempfile("temp.jpg")
|
with Image.open(test_file) as im:
|
||||||
im.save(f, qtables=[[n] * 64] * n)
|
f = str(tmp_path / "temp.jpg")
|
||||||
with Image.open(f) as im:
|
im.save(f, qtables=[[n] * 64] * n)
|
||||||
assert len(im.quantization) == n
|
with Image.open(f) as im:
|
||||||
reloaded = self.roundtrip(im, qtables="keep")
|
assert len(im.quantization) == n
|
||||||
assert im.quantization == reloaded.quantization
|
reloaded = self.roundtrip(im, qtables="keep")
|
||||||
|
assert im.quantization == reloaded.quantization
|
||||||
|
|
||||||
def test_qtables(self):
|
|
||||||
with Image.open("Tests/images/hopper.jpg") as im:
|
with Image.open("Tests/images/hopper.jpg") as im:
|
||||||
qtables = im.quantization
|
qtables = im.quantization
|
||||||
reloaded = self.roundtrip(im, qtables=qtables, subsampling=0)
|
reloaded = self.roundtrip(im, qtables=qtables, subsampling=0)
|
||||||
|
@ -470,14 +470,14 @@ class TestFileJpeg(PillowTestCase):
|
||||||
30,
|
30,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._n_qtables_helper(1, "Tests/images/hopper_gray.jpg")
|
_n_qtables_helper(1, "Tests/images/hopper_gray.jpg")
|
||||||
self._n_qtables_helper(1, "Tests/images/pil_sample_rgb.jpg")
|
_n_qtables_helper(1, "Tests/images/pil_sample_rgb.jpg")
|
||||||
self._n_qtables_helper(2, "Tests/images/pil_sample_rgb.jpg")
|
_n_qtables_helper(2, "Tests/images/pil_sample_rgb.jpg")
|
||||||
self._n_qtables_helper(3, "Tests/images/pil_sample_rgb.jpg")
|
_n_qtables_helper(3, "Tests/images/pil_sample_rgb.jpg")
|
||||||
self._n_qtables_helper(1, "Tests/images/pil_sample_cmyk.jpg")
|
_n_qtables_helper(1, "Tests/images/pil_sample_cmyk.jpg")
|
||||||
self._n_qtables_helper(2, "Tests/images/pil_sample_cmyk.jpg")
|
_n_qtables_helper(2, "Tests/images/pil_sample_cmyk.jpg")
|
||||||
self._n_qtables_helper(3, "Tests/images/pil_sample_cmyk.jpg")
|
_n_qtables_helper(3, "Tests/images/pil_sample_cmyk.jpg")
|
||||||
self._n_qtables_helper(4, "Tests/images/pil_sample_cmyk.jpg")
|
_n_qtables_helper(4, "Tests/images/pil_sample_cmyk.jpg")
|
||||||
|
|
||||||
# not a sequence
|
# not a sequence
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
|
@ -496,16 +496,16 @@ class TestFileJpeg(PillowTestCase):
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
self.roundtrip(im, qtables=[[1, 2, 3, 4]])
|
self.roundtrip(im, qtables=[[1, 2, 3, 4]])
|
||||||
|
|
||||||
@unittest.skipUnless(djpeg_available(), "djpeg not available")
|
@pytest.mark.skipif(not djpeg_available(), reason="djpeg not available")
|
||||||
def test_load_djpeg(self):
|
def test_load_djpeg(self):
|
||||||
with Image.open(TEST_FILE) as img:
|
with Image.open(TEST_FILE) as img:
|
||||||
img.load_djpeg()
|
img.load_djpeg()
|
||||||
assert_image_similar(img, Image.open(TEST_FILE), 0)
|
assert_image_similar(img, Image.open(TEST_FILE), 0)
|
||||||
|
|
||||||
@unittest.skipUnless(cjpeg_available(), "cjpeg not available")
|
@pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available")
|
||||||
def test_save_cjpeg(self):
|
def test_save_cjpeg(self, tmp_path):
|
||||||
with Image.open(TEST_FILE) as img:
|
with Image.open(TEST_FILE) as img:
|
||||||
tempfile = self.tempfile("temp.jpg")
|
tempfile = str(tmp_path / "temp.jpg")
|
||||||
JpegImagePlugin._save_cjpeg(img, 0, tempfile)
|
JpegImagePlugin._save_cjpeg(img, 0, tempfile)
|
||||||
# Default save quality is 75%, so a tiny bit of difference is alright
|
# Default save quality is 75%, so a tiny bit of difference is alright
|
||||||
assert_image_similar(img, Image.open(tempfile), 17)
|
assert_image_similar(img, Image.open(tempfile), 17)
|
||||||
|
@ -518,9 +518,9 @@ class TestFileJpeg(PillowTestCase):
|
||||||
assert tag_ids["RelatedImageWidth"] == 0x1001
|
assert tag_ids["RelatedImageWidth"] == 0x1001
|
||||||
assert tag_ids["RelatedImageLength"] == 0x1002
|
assert tag_ids["RelatedImageLength"] == 0x1002
|
||||||
|
|
||||||
def test_MAXBLOCK_scaling(self):
|
def test_MAXBLOCK_scaling(self, tmp_path):
|
||||||
im = self.gen_random_image((512, 512))
|
im = self.gen_random_image((512, 512))
|
||||||
f = self.tempfile("temp.jpeg")
|
f = str(tmp_path / "temp.jpeg")
|
||||||
im.save(f, quality=100, optimize=True)
|
im.save(f, quality=100, optimize=True)
|
||||||
|
|
||||||
with Image.open(f) as reloaded:
|
with Image.open(f) as reloaded:
|
||||||
|
@ -555,9 +555,9 @@ class TestFileJpeg(PillowTestCase):
|
||||||
with pytest.raises(IOError):
|
with pytest.raises(IOError):
|
||||||
img.save(out, "JPEG")
|
img.save(out, "JPEG")
|
||||||
|
|
||||||
def test_save_tiff_with_dpi(self):
|
def test_save_tiff_with_dpi(self, tmp_path):
|
||||||
# Arrange
|
# Arrange
|
||||||
outfile = self.tempfile("temp.tif")
|
outfile = str(tmp_path / "temp.tif")
|
||||||
with Image.open("Tests/images/hopper.tif") as im:
|
with Image.open("Tests/images/hopper.tif") as im:
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
|
@ -577,8 +577,8 @@ class TestFileJpeg(PillowTestCase):
|
||||||
with Image.open("Tests/images/iptc_roundDown.jpg") as im:
|
with Image.open("Tests/images/iptc_roundDown.jpg") as im:
|
||||||
assert im.info["dpi"] == (2, 2)
|
assert im.info["dpi"] == (2, 2)
|
||||||
|
|
||||||
def test_save_dpi_rounding(self):
|
def test_save_dpi_rounding(self, tmp_path):
|
||||||
outfile = self.tempfile("temp.jpg")
|
outfile = str(tmp_path / "temp.jpg")
|
||||||
with Image.open("Tests/images/hopper.jpg") as im:
|
with Image.open("Tests/images/hopper.jpg") as im:
|
||||||
im.save(outfile, dpi=(72.2, 72.2))
|
im.save(outfile, dpi=(72.2, 72.2))
|
||||||
|
|
||||||
|
@ -690,11 +690,11 @@ class TestFileJpeg(PillowTestCase):
|
||||||
assert [65504, 24] == apps_13_lengths
|
assert [65504, 24] == apps_13_lengths
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(is_win32(), "Windows only")
|
@pytest.mark.skipif(not is_win32(), reason="Windows only")
|
||||||
@skip_unless_feature("jpg")
|
@skip_unless_feature("jpg")
|
||||||
class TestFileCloseW32(PillowTestCase):
|
class TestFileCloseW32:
|
||||||
def test_fd_leak(self):
|
def test_fd_leak(self, tmp_path):
|
||||||
tmpfile = self.tempfile("temp.jpg")
|
tmpfile = str(tmp_path / "temp.jpg")
|
||||||
|
|
||||||
with Image.open("Tests/images/hopper.jpg") as im:
|
with Image.open("Tests/images/hopper.jpg") as im:
|
||||||
im.save(tmpfile)
|
im.save(tmpfile)
|
||||||
|
|
|
@ -5,7 +5,6 @@ import pytest
|
||||||
from PIL import Image, ImageFile, Jpeg2KImagePlugin
|
from PIL import Image, ImageFile, Jpeg2KImagePlugin
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
PillowTestCase,
|
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
assert_image_similar,
|
assert_image_similar,
|
||||||
is_big_endian,
|
is_big_endian,
|
||||||
|
@ -13,6 +12,8 @@ from .helper import (
|
||||||
skip_unless_feature,
|
skip_unless_feature,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pytestmark = skip_unless_feature("jpg_2000")
|
||||||
|
|
||||||
test_card = Image.open("Tests/images/test-card.png")
|
test_card = Image.open("Tests/images/test-card.png")
|
||||||
test_card.load()
|
test_card.load()
|
||||||
|
|
||||||
|
@ -21,190 +22,207 @@ test_card.load()
|
||||||
# 'Not enough memory to handle tile data'
|
# 'Not enough memory to handle tile data'
|
||||||
|
|
||||||
|
|
||||||
@skip_unless_feature("jpg_2000")
|
def roundtrip(im, **options):
|
||||||
class TestFileJpeg2k(PillowTestCase):
|
out = BytesIO()
|
||||||
def roundtrip(self, im, **options):
|
im.save(out, "JPEG2000", **options)
|
||||||
out = BytesIO()
|
test_bytes = out.tell()
|
||||||
im.save(out, "JPEG2000", **options)
|
out.seek(0)
|
||||||
test_bytes = out.tell()
|
im = Image.open(out)
|
||||||
out.seek(0)
|
im.bytes = test_bytes # for testing only
|
||||||
im = Image.open(out)
|
im.load()
|
||||||
im.bytes = test_bytes # for testing only
|
return im
|
||||||
|
|
||||||
|
|
||||||
|
def test_sanity():
|
||||||
|
# Internal version number
|
||||||
|
assert re.search(r"\d+\.\d+\.\d+$", Image.core.jp2klib_version)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/test-card-lossless.jp2") as im:
|
||||||
|
px = im.load()
|
||||||
|
assert px[0, 0] == (0, 0, 0)
|
||||||
|
assert im.mode == "RGB"
|
||||||
|
assert im.size == (640, 480)
|
||||||
|
assert im.format == "JPEG2000"
|
||||||
|
assert im.get_format_mimetype() == "image/jp2"
|
||||||
|
|
||||||
|
|
||||||
|
def test_jpf():
|
||||||
|
with Image.open("Tests/images/balloon.jpf") as im:
|
||||||
|
assert im.format == "JPEG2000"
|
||||||
|
assert im.get_format_mimetype() == "image/jpx"
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_file():
|
||||||
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
|
||||||
|
with pytest.raises(SyntaxError):
|
||||||
|
Jpeg2KImagePlugin.Jpeg2KImageFile(invalid_file)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bytesio():
|
||||||
|
with open("Tests/images/test-card-lossless.jp2", "rb") as f:
|
||||||
|
data = BytesIO(f.read())
|
||||||
|
with Image.open(data) as im:
|
||||||
im.load()
|
im.load()
|
||||||
return im
|
|
||||||
|
|
||||||
def test_sanity(self):
|
|
||||||
# Internal version number
|
|
||||||
assert re.search(r"\d+\.\d+\.\d+$", Image.core.jp2klib_version)
|
|
||||||
|
|
||||||
with Image.open("Tests/images/test-card-lossless.jp2") as im:
|
|
||||||
px = im.load()
|
|
||||||
assert px[0, 0] == (0, 0, 0)
|
|
||||||
assert im.mode == "RGB"
|
|
||||||
assert im.size == (640, 480)
|
|
||||||
assert im.format == "JPEG2000"
|
|
||||||
assert im.get_format_mimetype() == "image/jp2"
|
|
||||||
|
|
||||||
def test_jpf(self):
|
|
||||||
with Image.open("Tests/images/balloon.jpf") as im:
|
|
||||||
assert im.format == "JPEG2000"
|
|
||||||
assert im.get_format_mimetype() == "image/jpx"
|
|
||||||
|
|
||||||
def test_invalid_file(self):
|
|
||||||
invalid_file = "Tests/images/flower.jpg"
|
|
||||||
|
|
||||||
with pytest.raises(SyntaxError):
|
|
||||||
Jpeg2KImagePlugin.Jpeg2KImageFile(invalid_file)
|
|
||||||
|
|
||||||
def test_bytesio(self):
|
|
||||||
with open("Tests/images/test-card-lossless.jp2", "rb") as f:
|
|
||||||
data = BytesIO(f.read())
|
|
||||||
with Image.open(data) as im:
|
|
||||||
im.load()
|
|
||||||
assert_image_similar(im, test_card, 1.0e-3)
|
|
||||||
|
|
||||||
# These two test pre-written JPEG 2000 files that were not written with
|
|
||||||
# PIL (they were made using Adobe Photoshop)
|
|
||||||
|
|
||||||
def test_lossless(self):
|
|
||||||
with Image.open("Tests/images/test-card-lossless.jp2") as im:
|
|
||||||
im.load()
|
|
||||||
outfile = self.tempfile("temp_test-card.png")
|
|
||||||
im.save(outfile)
|
|
||||||
assert_image_similar(im, test_card, 1.0e-3)
|
assert_image_similar(im, test_card, 1.0e-3)
|
||||||
|
|
||||||
def test_lossy_tiled(self):
|
|
||||||
with Image.open("Tests/images/test-card-lossy-tiled.jp2") as im:
|
|
||||||
im.load()
|
|
||||||
assert_image_similar(im, test_card, 2.0)
|
|
||||||
|
|
||||||
def test_lossless_rt(self):
|
# These two test pre-written JPEG 2000 files that were not written with
|
||||||
im = self.roundtrip(test_card)
|
# PIL (they were made using Adobe Photoshop)
|
||||||
assert_image_equal(im, test_card)
|
|
||||||
|
|
||||||
def test_lossy_rt(self):
|
|
||||||
im = self.roundtrip(test_card, quality_layers=[20])
|
def test_lossless(tmp_path):
|
||||||
|
with Image.open("Tests/images/test-card-lossless.jp2") as im:
|
||||||
|
im.load()
|
||||||
|
outfile = str(tmp_path / "temp_test-card.png")
|
||||||
|
im.save(outfile)
|
||||||
|
assert_image_similar(im, test_card, 1.0e-3)
|
||||||
|
|
||||||
|
|
||||||
|
def test_lossy_tiled():
|
||||||
|
with Image.open("Tests/images/test-card-lossy-tiled.jp2") as im:
|
||||||
|
im.load()
|
||||||
assert_image_similar(im, test_card, 2.0)
|
assert_image_similar(im, test_card, 2.0)
|
||||||
|
|
||||||
def test_tiled_rt(self):
|
|
||||||
im = self.roundtrip(test_card, tile_size=(128, 128))
|
|
||||||
assert_image_equal(im, test_card)
|
|
||||||
|
|
||||||
def test_tiled_offset_rt(self):
|
def test_lossless_rt():
|
||||||
im = self.roundtrip(
|
im = roundtrip(test_card)
|
||||||
test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(32, 32)
|
assert_image_equal(im, test_card)
|
||||||
)
|
|
||||||
assert_image_equal(im, test_card)
|
|
||||||
|
|
||||||
def test_tiled_offset_too_small(self):
|
|
||||||
|
def test_lossy_rt():
|
||||||
|
im = roundtrip(test_card, quality_layers=[20])
|
||||||
|
assert_image_similar(im, test_card, 2.0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_tiled_rt():
|
||||||
|
im = roundtrip(test_card, tile_size=(128, 128))
|
||||||
|
assert_image_equal(im, test_card)
|
||||||
|
|
||||||
|
|
||||||
|
def test_tiled_offset_rt():
|
||||||
|
im = roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(32, 32))
|
||||||
|
assert_image_equal(im, test_card)
|
||||||
|
|
||||||
|
|
||||||
|
def test_tiled_offset_too_small():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(128, 32))
|
||||||
|
|
||||||
|
|
||||||
|
def test_irreversible_rt():
|
||||||
|
im = roundtrip(test_card, irreversible=True, quality_layers=[20])
|
||||||
|
assert_image_similar(im, test_card, 2.0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_prog_qual_rt():
|
||||||
|
im = roundtrip(test_card, quality_layers=[60, 40, 20], progression="LRCP")
|
||||||
|
assert_image_similar(im, test_card, 2.0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_prog_res_rt():
|
||||||
|
im = roundtrip(test_card, num_resolutions=8, progression="RLCP")
|
||||||
|
assert_image_equal(im, test_card)
|
||||||
|
|
||||||
|
|
||||||
|
def test_reduce():
|
||||||
|
with Image.open("Tests/images/test-card-lossless.jp2") as im:
|
||||||
|
im.reduce = 2
|
||||||
|
im.load()
|
||||||
|
assert im.size == (160, 120)
|
||||||
|
|
||||||
|
|
||||||
|
def test_layers_type(tmp_path):
|
||||||
|
outfile = str(tmp_path / "temp_layers.jp2")
|
||||||
|
for quality_layers in [[100, 50, 10], (100, 50, 10), None]:
|
||||||
|
test_card.save(outfile, quality_layers=quality_layers)
|
||||||
|
|
||||||
|
for quality_layers in ["quality_layers", ("100", "50", "10")]:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
self.roundtrip(
|
|
||||||
test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(128, 32)
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_irreversible_rt(self):
|
|
||||||
im = self.roundtrip(test_card, irreversible=True, quality_layers=[20])
|
|
||||||
assert_image_similar(im, test_card, 2.0)
|
|
||||||
|
|
||||||
def test_prog_qual_rt(self):
|
|
||||||
im = self.roundtrip(test_card, quality_layers=[60, 40, 20], progression="LRCP")
|
|
||||||
assert_image_similar(im, test_card, 2.0)
|
|
||||||
|
|
||||||
def test_prog_res_rt(self):
|
|
||||||
im = self.roundtrip(test_card, num_resolutions=8, progression="RLCP")
|
|
||||||
assert_image_equal(im, test_card)
|
|
||||||
|
|
||||||
def test_reduce(self):
|
|
||||||
with Image.open("Tests/images/test-card-lossless.jp2") as im:
|
|
||||||
im.reduce = 2
|
|
||||||
im.load()
|
|
||||||
assert im.size == (160, 120)
|
|
||||||
|
|
||||||
def test_layers_type(self):
|
|
||||||
outfile = self.tempfile("temp_layers.jp2")
|
|
||||||
for quality_layers in [[100, 50, 10], (100, 50, 10), None]:
|
|
||||||
test_card.save(outfile, quality_layers=quality_layers)
|
test_card.save(outfile, quality_layers=quality_layers)
|
||||||
|
|
||||||
for quality_layers in ["quality_layers", ("100", "50", "10")]:
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
test_card.save(outfile, quality_layers=quality_layers)
|
|
||||||
|
|
||||||
def test_layers(self):
|
def test_layers():
|
||||||
out = BytesIO()
|
out = BytesIO()
|
||||||
test_card.save(
|
test_card.save(out, "JPEG2000", quality_layers=[100, 50, 10], progression="LRCP")
|
||||||
out, "JPEG2000", quality_layers=[100, 50, 10], progression="LRCP"
|
out.seek(0)
|
||||||
)
|
|
||||||
out.seek(0)
|
|
||||||
|
|
||||||
with Image.open(out) as im:
|
with Image.open(out) as im:
|
||||||
im.layers = 1
|
im.layers = 1
|
||||||
im.load()
|
im.load()
|
||||||
assert_image_similar(im, test_card, 13)
|
assert_image_similar(im, test_card, 13)
|
||||||
|
|
||||||
out.seek(0)
|
out.seek(0)
|
||||||
with Image.open(out) as im:
|
with Image.open(out) as im:
|
||||||
im.layers = 3
|
im.layers = 3
|
||||||
im.load()
|
im.load()
|
||||||
assert_image_similar(im, test_card, 0.4)
|
assert_image_similar(im, test_card, 0.4)
|
||||||
|
|
||||||
def test_rgba(self):
|
|
||||||
# Arrange
|
|
||||||
with Image.open("Tests/images/rgb_trns_ycbc.j2k") as j2k:
|
|
||||||
with Image.open("Tests/images/rgb_trns_ycbc.jp2") as jp2:
|
|
||||||
|
|
||||||
# Act
|
def test_rgba():
|
||||||
j2k.load()
|
# Arrange
|
||||||
jp2.load()
|
with Image.open("Tests/images/rgb_trns_ycbc.j2k") as j2k:
|
||||||
|
with Image.open("Tests/images/rgb_trns_ycbc.jp2") as jp2:
|
||||||
|
|
||||||
# Assert
|
# Act
|
||||||
assert j2k.mode == "RGBA"
|
|
||||||
assert jp2.mode == "RGBA"
|
|
||||||
|
|
||||||
def test_16bit_monochrome_has_correct_mode(self):
|
|
||||||
with Image.open("Tests/images/16bit.cropped.j2k") as j2k:
|
|
||||||
j2k.load()
|
j2k.load()
|
||||||
assert j2k.mode == "I;16"
|
|
||||||
|
|
||||||
with Image.open("Tests/images/16bit.cropped.jp2") as jp2:
|
|
||||||
jp2.load()
|
jp2.load()
|
||||||
assert jp2.mode == "I;16"
|
|
||||||
|
|
||||||
@pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian")
|
# Assert
|
||||||
def test_16bit_monochrome_jp2_like_tiff(self):
|
assert j2k.mode == "RGBA"
|
||||||
with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit:
|
assert jp2.mode == "RGBA"
|
||||||
with Image.open("Tests/images/16bit.cropped.jp2") as jp2:
|
|
||||||
assert_image_similar(jp2, tiff_16bit, 1e-3)
|
|
||||||
|
|
||||||
@pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian")
|
|
||||||
def test_16bit_monochrome_j2k_like_tiff(self):
|
|
||||||
with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit:
|
|
||||||
with Image.open("Tests/images/16bit.cropped.j2k") as j2k:
|
|
||||||
assert_image_similar(j2k, tiff_16bit, 1e-3)
|
|
||||||
|
|
||||||
def test_16bit_j2k_roundtrips(self):
|
def test_16bit_monochrome_has_correct_mode():
|
||||||
with Image.open("Tests/images/16bit.cropped.j2k") as j2k:
|
with Image.open("Tests/images/16bit.cropped.j2k") as j2k:
|
||||||
im = self.roundtrip(j2k)
|
j2k.load()
|
||||||
assert_image_equal(im, j2k)
|
assert j2k.mode == "I;16"
|
||||||
|
|
||||||
def test_16bit_jp2_roundtrips(self):
|
with Image.open("Tests/images/16bit.cropped.jp2") as jp2:
|
||||||
|
jp2.load()
|
||||||
|
assert jp2.mode == "I;16"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian")
|
||||||
|
def test_16bit_monochrome_jp2_like_tiff():
|
||||||
|
with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit:
|
||||||
with Image.open("Tests/images/16bit.cropped.jp2") as jp2:
|
with Image.open("Tests/images/16bit.cropped.jp2") as jp2:
|
||||||
im = self.roundtrip(jp2)
|
assert_image_similar(jp2, tiff_16bit, 1e-3)
|
||||||
assert_image_equal(im, jp2)
|
|
||||||
|
|
||||||
def test_unbound_local(self):
|
|
||||||
# prepatch, a malformed jp2 file could cause an UnboundLocalError
|
|
||||||
# exception.
|
|
||||||
with pytest.raises(IOError):
|
|
||||||
Image.open("Tests/images/unbound_variable.jp2")
|
|
||||||
|
|
||||||
def test_parser_feed(self):
|
@pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian")
|
||||||
# Arrange
|
def test_16bit_monochrome_j2k_like_tiff():
|
||||||
with open("Tests/images/test-card-lossless.jp2", "rb") as f:
|
with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit:
|
||||||
data = f.read()
|
with Image.open("Tests/images/16bit.cropped.j2k") as j2k:
|
||||||
|
assert_image_similar(j2k, tiff_16bit, 1e-3)
|
||||||
|
|
||||||
# Act
|
|
||||||
p = ImageFile.Parser()
|
|
||||||
p.feed(data)
|
|
||||||
|
|
||||||
# Assert
|
def test_16bit_j2k_roundtrips():
|
||||||
assert p.image.size == (640, 480)
|
with Image.open("Tests/images/16bit.cropped.j2k") as j2k:
|
||||||
|
im = roundtrip(j2k)
|
||||||
|
assert_image_equal(im, j2k)
|
||||||
|
|
||||||
|
|
||||||
|
def test_16bit_jp2_roundtrips():
|
||||||
|
with Image.open("Tests/images/16bit.cropped.jp2") as jp2:
|
||||||
|
im = roundtrip(jp2)
|
||||||
|
assert_image_equal(im, jp2)
|
||||||
|
|
||||||
|
|
||||||
|
def test_unbound_local():
|
||||||
|
# prepatch, a malformed jp2 file could cause an UnboundLocalError exception.
|
||||||
|
with pytest.raises(IOError):
|
||||||
|
Image.open("Tests/images/unbound_variable.jp2")
|
||||||
|
|
||||||
|
|
||||||
|
def test_parser_feed():
|
||||||
|
# Arrange
|
||||||
|
with open("Tests/images/test-card-lossless.jp2", "rb") as f:
|
||||||
|
data = f.read()
|
||||||
|
|
||||||
|
# Act
|
||||||
|
p = ImageFile.Parser()
|
||||||
|
p.feed(data)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert p.image.size == (640, 480)
|
||||||
|
|
|
@ -10,7 +10,6 @@ import pytest
|
||||||
from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags
|
from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
PillowTestCase,
|
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
assert_image_equal_tofile,
|
assert_image_equal_tofile,
|
||||||
assert_image_similar,
|
assert_image_similar,
|
||||||
|
@ -23,8 +22,8 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@skip_unless_feature("libtiff")
|
@skip_unless_feature("libtiff")
|
||||||
class LibTiffTestCase(PillowTestCase):
|
class LibTiffTestCase:
|
||||||
def _assert_noerr(self, im):
|
def _assert_noerr(self, tmp_path, im):
|
||||||
"""Helper tests that assert basic sanity about the g4 tiff reading"""
|
"""Helper tests that assert basic sanity about the g4 tiff reading"""
|
||||||
# 1 bit
|
# 1 bit
|
||||||
assert im.mode == "1"
|
assert im.mode == "1"
|
||||||
|
@ -40,7 +39,7 @@ class LibTiffTestCase(PillowTestCase):
|
||||||
print(dir(im))
|
print(dir(im))
|
||||||
|
|
||||||
# can we write it back out, in a different form.
|
# can we write it back out, in a different form.
|
||||||
out = self.tempfile("temp.png")
|
out = str(tmp_path / "temp.png")
|
||||||
im.save(out)
|
im.save(out)
|
||||||
|
|
||||||
out_bytes = io.BytesIO()
|
out_bytes = io.BytesIO()
|
||||||
|
@ -48,29 +47,29 @@ class LibTiffTestCase(PillowTestCase):
|
||||||
|
|
||||||
|
|
||||||
class TestFileLibTiff(LibTiffTestCase):
|
class TestFileLibTiff(LibTiffTestCase):
|
||||||
def test_g4_tiff(self):
|
def test_g4_tiff(self, tmp_path):
|
||||||
"""Test the ordinary file path load path"""
|
"""Test the ordinary file path load path"""
|
||||||
|
|
||||||
test_file = "Tests/images/hopper_g4_500.tif"
|
test_file = "Tests/images/hopper_g4_500.tif"
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
assert im.size == (500, 500)
|
assert im.size == (500, 500)
|
||||||
self._assert_noerr(im)
|
self._assert_noerr(tmp_path, im)
|
||||||
|
|
||||||
def test_g4_large(self):
|
def test_g4_large(self, tmp_path):
|
||||||
test_file = "Tests/images/pport_g4.tif"
|
test_file = "Tests/images/pport_g4.tif"
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
self._assert_noerr(im)
|
self._assert_noerr(tmp_path, im)
|
||||||
|
|
||||||
def test_g4_tiff_file(self):
|
def test_g4_tiff_file(self, tmp_path):
|
||||||
"""Testing the string load path"""
|
"""Testing the string load path"""
|
||||||
|
|
||||||
test_file = "Tests/images/hopper_g4_500.tif"
|
test_file = "Tests/images/hopper_g4_500.tif"
|
||||||
with open(test_file, "rb") as f:
|
with open(test_file, "rb") as f:
|
||||||
with Image.open(f) as im:
|
with Image.open(f) as im:
|
||||||
assert im.size == (500, 500)
|
assert im.size == (500, 500)
|
||||||
self._assert_noerr(im)
|
self._assert_noerr(tmp_path, im)
|
||||||
|
|
||||||
def test_g4_tiff_bytesio(self):
|
def test_g4_tiff_bytesio(self, tmp_path):
|
||||||
"""Testing the stringio loading code path"""
|
"""Testing the stringio loading code path"""
|
||||||
test_file = "Tests/images/hopper_g4_500.tif"
|
test_file = "Tests/images/hopper_g4_500.tif"
|
||||||
s = io.BytesIO()
|
s = io.BytesIO()
|
||||||
|
@ -79,9 +78,9 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
s.seek(0)
|
s.seek(0)
|
||||||
with Image.open(s) as im:
|
with Image.open(s) as im:
|
||||||
assert im.size == (500, 500)
|
assert im.size == (500, 500)
|
||||||
self._assert_noerr(im)
|
self._assert_noerr(tmp_path, im)
|
||||||
|
|
||||||
def test_g4_non_disk_file_object(self):
|
def test_g4_non_disk_file_object(self, tmp_path):
|
||||||
"""Testing loading from non-disk non-BytesIO file object"""
|
"""Testing loading from non-disk non-BytesIO file object"""
|
||||||
test_file = "Tests/images/hopper_g4_500.tif"
|
test_file = "Tests/images/hopper_g4_500.tif"
|
||||||
s = io.BytesIO()
|
s = io.BytesIO()
|
||||||
|
@ -91,7 +90,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
r = io.BufferedReader(s)
|
r = io.BufferedReader(s)
|
||||||
with Image.open(r) as im:
|
with Image.open(r) as im:
|
||||||
assert im.size == (500, 500)
|
assert im.size == (500, 500)
|
||||||
self._assert_noerr(im)
|
self._assert_noerr(tmp_path, im)
|
||||||
|
|
||||||
def test_g4_eq_png(self):
|
def test_g4_eq_png(self):
|
||||||
""" Checking that we're actually getting the data that we expect"""
|
""" Checking that we're actually getting the data that we expect"""
|
||||||
|
@ -106,18 +105,18 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
with Image.open("Tests/images/g4-fillorder-test.tif") as g4:
|
with Image.open("Tests/images/g4-fillorder-test.tif") as g4:
|
||||||
assert_image_equal(g4, png)
|
assert_image_equal(g4, png)
|
||||||
|
|
||||||
def test_g4_write(self):
|
def test_g4_write(self, tmp_path):
|
||||||
"""Checking to see that the saved image is the same as what we wrote"""
|
"""Checking to see that the saved image is the same as what we wrote"""
|
||||||
test_file = "Tests/images/hopper_g4_500.tif"
|
test_file = "Tests/images/hopper_g4_500.tif"
|
||||||
with Image.open(test_file) as orig:
|
with Image.open(test_file) as orig:
|
||||||
out = self.tempfile("temp.tif")
|
out = str(tmp_path / "temp.tif")
|
||||||
rot = orig.transpose(Image.ROTATE_90)
|
rot = orig.transpose(Image.ROTATE_90)
|
||||||
assert rot.size == (500, 500)
|
assert rot.size == (500, 500)
|
||||||
rot.save(out)
|
rot.save(out)
|
||||||
|
|
||||||
with Image.open(out) as reread:
|
with Image.open(out) as reread:
|
||||||
assert reread.size == (500, 500)
|
assert reread.size == (500, 500)
|
||||||
self._assert_noerr(reread)
|
self._assert_noerr(tmp_path, reread)
|
||||||
assert_image_equal(reread, rot)
|
assert_image_equal(reread, rot)
|
||||||
assert reread.info["compression"] == "group4"
|
assert reread.info["compression"] == "group4"
|
||||||
|
|
||||||
|
@ -135,10 +134,10 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
|
|
||||||
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
|
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
|
||||||
|
|
||||||
def test_write_metadata(self):
|
def test_write_metadata(self, tmp_path):
|
||||||
""" Test metadata writing through libtiff """
|
""" Test metadata writing through libtiff """
|
||||||
for legacy_api in [False, True]:
|
for legacy_api in [False, True]:
|
||||||
f = self.tempfile("temp.tiff")
|
f = str(tmp_path / "temp.tiff")
|
||||||
with Image.open("Tests/images/hopper_g4.tif") as img:
|
with Image.open("Tests/images/hopper_g4.tif") as img:
|
||||||
img.save(f, tiffinfo=img.tag)
|
img.save(f, tiffinfo=img.tag)
|
||||||
|
|
||||||
|
@ -183,7 +182,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
for field in requested_fields:
|
for field in requested_fields:
|
||||||
assert field in reloaded, "%s not in metadata" % field
|
assert field in reloaded, "%s not in metadata" % field
|
||||||
|
|
||||||
def test_additional_metadata(self):
|
def test_additional_metadata(self, tmp_path):
|
||||||
# these should not crash. Seriously dummy data, most of it doesn't make
|
# these should not crash. Seriously dummy data, most of it doesn't make
|
||||||
# any sense, so we're running up against limits where we're asking
|
# any sense, so we're running up against limits where we're asking
|
||||||
# libtiff to do stupid things.
|
# libtiff to do stupid things.
|
||||||
|
@ -232,14 +231,14 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
# Extra samples really doesn't make sense in this application.
|
# Extra samples really doesn't make sense in this application.
|
||||||
del new_ifd[338]
|
del new_ifd[338]
|
||||||
|
|
||||||
out = self.tempfile("temp.tif")
|
out = str(tmp_path / "temp.tif")
|
||||||
TiffImagePlugin.WRITE_LIBTIFF = True
|
TiffImagePlugin.WRITE_LIBTIFF = True
|
||||||
|
|
||||||
im.save(out, tiffinfo=new_ifd)
|
im.save(out, tiffinfo=new_ifd)
|
||||||
|
|
||||||
TiffImagePlugin.WRITE_LIBTIFF = False
|
TiffImagePlugin.WRITE_LIBTIFF = False
|
||||||
|
|
||||||
def test_custom_metadata(self):
|
def test_custom_metadata(self, tmp_path):
|
||||||
tc = namedtuple("test_case", "value,type,supported_by_default")
|
tc = namedtuple("test_case", "value,type,supported_by_default")
|
||||||
custom = {
|
custom = {
|
||||||
37000 + k: v
|
37000 + k: v
|
||||||
|
@ -284,7 +283,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
def check_tags(tiffinfo):
|
def check_tags(tiffinfo):
|
||||||
im = hopper()
|
im = hopper()
|
||||||
|
|
||||||
out = self.tempfile("temp.tif")
|
out = str(tmp_path / "temp.tif")
|
||||||
im.save(out, tiffinfo=tiffinfo)
|
im.save(out, tiffinfo=tiffinfo)
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
with Image.open(out) as reloaded:
|
||||||
|
@ -323,26 +322,26 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
)
|
)
|
||||||
TiffImagePlugin.WRITE_LIBTIFF = False
|
TiffImagePlugin.WRITE_LIBTIFF = False
|
||||||
|
|
||||||
def test_int_dpi(self):
|
def test_int_dpi(self, tmp_path):
|
||||||
# issue #1765
|
# issue #1765
|
||||||
im = hopper("RGB")
|
im = hopper("RGB")
|
||||||
out = self.tempfile("temp.tif")
|
out = str(tmp_path / "temp.tif")
|
||||||
TiffImagePlugin.WRITE_LIBTIFF = True
|
TiffImagePlugin.WRITE_LIBTIFF = True
|
||||||
im.save(out, dpi=(72, 72))
|
im.save(out, dpi=(72, 72))
|
||||||
TiffImagePlugin.WRITE_LIBTIFF = False
|
TiffImagePlugin.WRITE_LIBTIFF = False
|
||||||
with Image.open(out) as reloaded:
|
with Image.open(out) as reloaded:
|
||||||
assert reloaded.info["dpi"] == (72.0, 72.0)
|
assert reloaded.info["dpi"] == (72.0, 72.0)
|
||||||
|
|
||||||
def test_g3_compression(self):
|
def test_g3_compression(self, tmp_path):
|
||||||
with Image.open("Tests/images/hopper_g4_500.tif") as i:
|
with Image.open("Tests/images/hopper_g4_500.tif") as i:
|
||||||
out = self.tempfile("temp.tif")
|
out = str(tmp_path / "temp.tif")
|
||||||
i.save(out, compression="group3")
|
i.save(out, compression="group3")
|
||||||
|
|
||||||
with Image.open(out) as reread:
|
with Image.open(out) as reread:
|
||||||
assert reread.info["compression"] == "group3"
|
assert reread.info["compression"] == "group3"
|
||||||
assert_image_equal(reread, i)
|
assert_image_equal(reread, i)
|
||||||
|
|
||||||
def test_little_endian(self):
|
def test_little_endian(self, tmp_path):
|
||||||
with Image.open("Tests/images/16bit.deflate.tif") as im:
|
with Image.open("Tests/images/16bit.deflate.tif") as im:
|
||||||
assert im.getpixel((0, 0)) == 480
|
assert im.getpixel((0, 0)) == 480
|
||||||
assert im.mode == "I;16"
|
assert im.mode == "I;16"
|
||||||
|
@ -352,7 +351,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
assert b[0] == ord(b"\xe0")
|
assert b[0] == ord(b"\xe0")
|
||||||
assert b[1] == ord(b"\x01")
|
assert b[1] == ord(b"\x01")
|
||||||
|
|
||||||
out = self.tempfile("temp.tif")
|
out = str(tmp_path / "temp.tif")
|
||||||
# out = "temp.le.tif"
|
# out = "temp.le.tif"
|
||||||
im.save(out)
|
im.save(out)
|
||||||
with Image.open(out) as reread:
|
with Image.open(out) as reread:
|
||||||
|
@ -361,7 +360,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
# UNDONE - libtiff defaults to writing in native endian, so
|
# UNDONE - libtiff defaults to writing in native endian, so
|
||||||
# on big endian, we'll get back mode = 'I;16B' here.
|
# on big endian, we'll get back mode = 'I;16B' here.
|
||||||
|
|
||||||
def test_big_endian(self):
|
def test_big_endian(self, tmp_path):
|
||||||
with Image.open("Tests/images/16bit.MM.deflate.tif") as im:
|
with Image.open("Tests/images/16bit.MM.deflate.tif") as im:
|
||||||
assert im.getpixel((0, 0)) == 480
|
assert im.getpixel((0, 0)) == 480
|
||||||
assert im.mode == "I;16B"
|
assert im.mode == "I;16B"
|
||||||
|
@ -372,17 +371,17 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
assert b[0] == ord(b"\x01")
|
assert b[0] == ord(b"\x01")
|
||||||
assert b[1] == ord(b"\xe0")
|
assert b[1] == ord(b"\xe0")
|
||||||
|
|
||||||
out = self.tempfile("temp.tif")
|
out = str(tmp_path / "temp.tif")
|
||||||
im.save(out)
|
im.save(out)
|
||||||
with Image.open(out) as reread:
|
with Image.open(out) as reread:
|
||||||
assert reread.info["compression"] == im.info["compression"]
|
assert reread.info["compression"] == im.info["compression"]
|
||||||
assert reread.getpixel((0, 0)) == 480
|
assert reread.getpixel((0, 0)) == 480
|
||||||
|
|
||||||
def test_g4_string_info(self):
|
def test_g4_string_info(self, tmp_path):
|
||||||
"""Tests String data in info directory"""
|
"""Tests String data in info directory"""
|
||||||
test_file = "Tests/images/hopper_g4_500.tif"
|
test_file = "Tests/images/hopper_g4_500.tif"
|
||||||
with Image.open(test_file) as orig:
|
with Image.open(test_file) as orig:
|
||||||
out = self.tempfile("temp.tif")
|
out = str(tmp_path / "temp.tif")
|
||||||
|
|
||||||
orig.tag[269] = "temp.tif"
|
orig.tag[269] = "temp.tif"
|
||||||
orig.save(out)
|
orig.save(out)
|
||||||
|
@ -406,10 +405,10 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
|
|
||||||
assert_image_equal_tofile(im, "Tests/images/12in16bit.tif")
|
assert_image_equal_tofile(im, "Tests/images/12in16bit.tif")
|
||||||
|
|
||||||
def test_blur(self):
|
def test_blur(self, tmp_path):
|
||||||
# test case from irc, how to do blur on b/w image
|
# test case from irc, how to do blur on b/w image
|
||||||
# and save to compressed tif.
|
# and save to compressed tif.
|
||||||
out = self.tempfile("temp.tif")
|
out = str(tmp_path / "temp.tif")
|
||||||
with Image.open("Tests/images/pport_g4.tif") as im:
|
with Image.open("Tests/images/pport_g4.tif") as im:
|
||||||
im = im.convert("L")
|
im = im.convert("L")
|
||||||
|
|
||||||
|
@ -421,11 +420,11 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
|
|
||||||
assert_image_equal(im, im2)
|
assert_image_equal(im, im2)
|
||||||
|
|
||||||
def test_compressions(self):
|
def test_compressions(self, tmp_path):
|
||||||
# Test various tiff compressions and assert similar image content but reduced
|
# Test various tiff compressions and assert similar image content but reduced
|
||||||
# file sizes.
|
# file sizes.
|
||||||
im = hopper("RGB")
|
im = hopper("RGB")
|
||||||
out = self.tempfile("temp.tif")
|
out = str(tmp_path / "temp.tif")
|
||||||
im.save(out)
|
im.save(out)
|
||||||
size_raw = os.path.getsize(out)
|
size_raw = os.path.getsize(out)
|
||||||
|
|
||||||
|
@ -449,9 +448,9 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
assert size_compressed > size_jpeg
|
assert size_compressed > size_jpeg
|
||||||
assert size_jpeg > size_jpeg_30
|
assert size_jpeg > size_jpeg_30
|
||||||
|
|
||||||
def test_quality(self):
|
def test_quality(self, tmp_path):
|
||||||
im = hopper("RGB")
|
im = hopper("RGB")
|
||||||
out = self.tempfile("temp.tif")
|
out = str(tmp_path / "temp.tif")
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
im.save(out, compression="tiff_lzw", quality=50)
|
im.save(out, compression="tiff_lzw", quality=50)
|
||||||
|
@ -464,21 +463,21 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
im.save(out, compression="jpeg", quality=0)
|
im.save(out, compression="jpeg", quality=0)
|
||||||
im.save(out, compression="jpeg", quality=100)
|
im.save(out, compression="jpeg", quality=100)
|
||||||
|
|
||||||
def test_cmyk_save(self):
|
def test_cmyk_save(self, tmp_path):
|
||||||
im = hopper("CMYK")
|
im = hopper("CMYK")
|
||||||
out = self.tempfile("temp.tif")
|
out = str(tmp_path / "temp.tif")
|
||||||
|
|
||||||
im.save(out, compression="tiff_adobe_deflate")
|
im.save(out, compression="tiff_adobe_deflate")
|
||||||
with Image.open(out) as im2:
|
with Image.open(out) as im2:
|
||||||
assert_image_equal(im, im2)
|
assert_image_equal(im, im2)
|
||||||
|
|
||||||
def xtest_bw_compression_w_rgb(self):
|
def xtest_bw_compression_w_rgb(self, tmp_path):
|
||||||
""" This test passes, but when running all tests causes a failure due
|
""" This test passes, but when running all tests causes a failure due
|
||||||
to output on stderr from the error thrown by libtiff. We need to
|
to output on stderr from the error thrown by libtiff. We need to
|
||||||
capture that but not now"""
|
capture that but not now"""
|
||||||
|
|
||||||
im = hopper("RGB")
|
im = hopper("RGB")
|
||||||
out = self.tempfile("temp.tif")
|
out = str(tmp_path / "temp.tif")
|
||||||
|
|
||||||
with pytest.raises(IOError):
|
with pytest.raises(IOError):
|
||||||
im.save(out, compression="tiff_ccitt")
|
im.save(out, compression="tiff_ccitt")
|
||||||
|
@ -619,22 +618,22 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
TiffImagePlugin.WRITE_LIBTIFF = False
|
TiffImagePlugin.WRITE_LIBTIFF = False
|
||||||
TiffImagePlugin.READ_LIBTIFF = False
|
TiffImagePlugin.READ_LIBTIFF = False
|
||||||
|
|
||||||
def test_crashing_metadata(self):
|
def test_crashing_metadata(self, tmp_path):
|
||||||
# issue 1597
|
# issue 1597
|
||||||
with Image.open("Tests/images/rdf.tif") as im:
|
with Image.open("Tests/images/rdf.tif") as im:
|
||||||
out = self.tempfile("temp.tif")
|
out = str(tmp_path / "temp.tif")
|
||||||
|
|
||||||
TiffImagePlugin.WRITE_LIBTIFF = True
|
TiffImagePlugin.WRITE_LIBTIFF = True
|
||||||
# this shouldn't crash
|
# this shouldn't crash
|
||||||
im.save(out, format="TIFF")
|
im.save(out, format="TIFF")
|
||||||
TiffImagePlugin.WRITE_LIBTIFF = False
|
TiffImagePlugin.WRITE_LIBTIFF = False
|
||||||
|
|
||||||
def test_page_number_x_0(self):
|
def test_page_number_x_0(self, tmp_path):
|
||||||
# Issue 973
|
# Issue 973
|
||||||
# Test TIFF with tag 297 (Page Number) having value of 0 0.
|
# Test TIFF with tag 297 (Page Number) having value of 0 0.
|
||||||
# The first number is the current page number.
|
# The first number is the current page number.
|
||||||
# The second is the total number of pages, zero means not available.
|
# The second is the total number of pages, zero means not available.
|
||||||
outfile = self.tempfile("temp.tif")
|
outfile = str(tmp_path / "temp.tif")
|
||||||
# Created by printing a page in Chrome to PDF, then:
|
# Created by printing a page in Chrome to PDF, then:
|
||||||
# /usr/bin/gs -q -sDEVICE=tiffg3 -sOutputFile=total-pages-zero.tif
|
# /usr/bin/gs -q -sDEVICE=tiffg3 -sOutputFile=total-pages-zero.tif
|
||||||
# -dNOPAUSE /tmp/test.pdf -c quit
|
# -dNOPAUSE /tmp/test.pdf -c quit
|
||||||
|
@ -643,10 +642,10 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
# Should not divide by zero
|
# Should not divide by zero
|
||||||
im.save(outfile)
|
im.save(outfile)
|
||||||
|
|
||||||
def test_fd_duplication(self):
|
def test_fd_duplication(self, tmp_path):
|
||||||
# https://github.com/python-pillow/Pillow/issues/1651
|
# https://github.com/python-pillow/Pillow/issues/1651
|
||||||
|
|
||||||
tmpfile = self.tempfile("temp.tif")
|
tmpfile = str(tmp_path / "temp.tif")
|
||||||
with open(tmpfile, "wb") as f:
|
with open(tmpfile, "wb") as f:
|
||||||
with open("Tests/images/g4-multi.tiff", "rb") as src:
|
with open("Tests/images/g4-multi.tiff", "rb") as src:
|
||||||
f.write(src.read())
|
f.write(src.read())
|
||||||
|
@ -685,9 +684,9 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
assert im.size == (10, 10)
|
assert im.size == (10, 10)
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
def test_save_tiff_with_jpegtables(self):
|
def test_save_tiff_with_jpegtables(self, tmp_path):
|
||||||
# Arrange
|
# Arrange
|
||||||
outfile = self.tempfile("temp.tif")
|
outfile = str(tmp_path / "temp.tif")
|
||||||
|
|
||||||
# Created with ImageMagick: convert hopper.jpg hopper_jpg.tif
|
# Created with ImageMagick: convert hopper.jpg hopper_jpg.tif
|
||||||
# Contains JPEGTables (347) tag
|
# Contains JPEGTables (347) tag
|
||||||
|
|
|
@ -15,16 +15,16 @@ class TestFileLibTiffSmall(LibTiffTestCase):
|
||||||
file just before reading in libtiff. These tests remain
|
file just before reading in libtiff. These tests remain
|
||||||
to ensure that it stays fixed. """
|
to ensure that it stays fixed. """
|
||||||
|
|
||||||
def test_g4_hopper_file(self):
|
def test_g4_hopper_file(self, tmp_path):
|
||||||
"""Testing the open file load path"""
|
"""Testing the open file load path"""
|
||||||
|
|
||||||
test_file = "Tests/images/hopper_g4.tif"
|
test_file = "Tests/images/hopper_g4.tif"
|
||||||
with open(test_file, "rb") as f:
|
with open(test_file, "rb") as f:
|
||||||
with Image.open(f) as im:
|
with Image.open(f) as im:
|
||||||
assert im.size == (128, 128)
|
assert im.size == (128, 128)
|
||||||
self._assert_noerr(im)
|
self._assert_noerr(tmp_path, im)
|
||||||
|
|
||||||
def test_g4_hopper_bytesio(self):
|
def test_g4_hopper_bytesio(self, tmp_path):
|
||||||
"""Testing the bytesio loading code path"""
|
"""Testing the bytesio loading code path"""
|
||||||
test_file = "Tests/images/hopper_g4.tif"
|
test_file = "Tests/images/hopper_g4.tif"
|
||||||
s = BytesIO()
|
s = BytesIO()
|
||||||
|
@ -33,12 +33,12 @@ class TestFileLibTiffSmall(LibTiffTestCase):
|
||||||
s.seek(0)
|
s.seek(0)
|
||||||
with Image.open(s) as im:
|
with Image.open(s) as im:
|
||||||
assert im.size == (128, 128)
|
assert im.size == (128, 128)
|
||||||
self._assert_noerr(im)
|
self._assert_noerr(tmp_path, im)
|
||||||
|
|
||||||
def test_g4_hopper(self):
|
def test_g4_hopper(self, tmp_path):
|
||||||
"""The 128x128 lena image failed for some reason."""
|
"""The 128x128 lena image failed for some reason."""
|
||||||
|
|
||||||
test_file = "Tests/images/hopper_g4.tif"
|
test_file = "Tests/images/hopper_g4.tif"
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
assert im.size == (128, 128)
|
assert im.size == (128, 128)
|
||||||
self._assert_noerr(im)
|
self._assert_noerr(tmp_path, im)
|
||||||
|
|
|
@ -1,71 +1,90 @@
|
||||||
import os.path
|
import os.path
|
||||||
|
import subprocess
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
PillowTestCase,
|
IMCONVERT,
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
hopper,
|
hopper,
|
||||||
imagemagick_available,
|
imagemagick_available,
|
||||||
skip_known_bad_test,
|
skip_known_bad_test,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_roundtrip = imagemagick_available()
|
||||||
|
|
||||||
class TestFilePalm(PillowTestCase):
|
|
||||||
_roundtrip = imagemagick_available()
|
|
||||||
|
|
||||||
def helper_save_as_palm(self, mode):
|
def helper_save_as_palm(tmp_path, mode):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
outfile = self.tempfile("temp_" + mode + ".palm")
|
outfile = str(tmp_path / ("temp_" + mode + ".palm"))
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
im.save(outfile)
|
im.save(outfile)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert os.path.isfile(outfile)
|
assert os.path.isfile(outfile)
|
||||||
assert os.path.getsize(outfile) > 0
|
assert os.path.getsize(outfile) > 0
|
||||||
|
|
||||||
def roundtrip(self, mode):
|
|
||||||
if not self._roundtrip:
|
|
||||||
return
|
|
||||||
|
|
||||||
im = hopper(mode)
|
def open_with_imagemagick(tmp_path, f):
|
||||||
outfile = self.tempfile("temp.palm")
|
if not imagemagick_available():
|
||||||
|
raise OSError()
|
||||||
|
|
||||||
im.save(outfile)
|
outfile = str(tmp_path / "temp.png")
|
||||||
converted = self.open_withImagemagick(outfile)
|
rc = subprocess.call(
|
||||||
assert_image_equal(converted, im)
|
[IMCONVERT, f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
|
||||||
|
)
|
||||||
|
if rc:
|
||||||
|
raise OSError
|
||||||
|
return Image.open(outfile)
|
||||||
|
|
||||||
def test_monochrome(self):
|
|
||||||
# Arrange
|
|
||||||
mode = "1"
|
|
||||||
|
|
||||||
# Act / Assert
|
def roundtrip(tmp_path, mode):
|
||||||
self.helper_save_as_palm(mode)
|
if not _roundtrip:
|
||||||
self.roundtrip(mode)
|
return
|
||||||
|
|
||||||
def test_p_mode(self):
|
im = hopper(mode)
|
||||||
# Arrange
|
outfile = str(tmp_path / "temp.palm")
|
||||||
mode = "P"
|
|
||||||
|
|
||||||
# Act / Assert
|
im.save(outfile)
|
||||||
self.helper_save_as_palm(mode)
|
converted = open_with_imagemagick(tmp_path, outfile)
|
||||||
skip_known_bad_test("Palm P image is wrong")
|
assert_image_equal(converted, im)
|
||||||
self.roundtrip(mode)
|
|
||||||
|
|
||||||
def test_l_ioerror(self):
|
|
||||||
# Arrange
|
|
||||||
mode = "L"
|
|
||||||
|
|
||||||
# Act / Assert
|
def test_monochrome(tmp_path):
|
||||||
with pytest.raises(IOError):
|
# Arrange
|
||||||
self.helper_save_as_palm(mode)
|
mode = "1"
|
||||||
|
|
||||||
def test_rgb_ioerror(self):
|
# Act / Assert
|
||||||
# Arrange
|
helper_save_as_palm(tmp_path, mode)
|
||||||
mode = "RGB"
|
roundtrip(tmp_path, mode)
|
||||||
|
|
||||||
# Act / Assert
|
|
||||||
with pytest.raises(IOError):
|
def test_p_mode(tmp_path):
|
||||||
self.helper_save_as_palm(mode)
|
# Arrange
|
||||||
|
mode = "P"
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
helper_save_as_palm(tmp_path, mode)
|
||||||
|
skip_known_bad_test("Palm P image is wrong")
|
||||||
|
roundtrip(tmp_path, mode)
|
||||||
|
|
||||||
|
|
||||||
|
def test_l_ioerror(tmp_path):
|
||||||
|
# Arrange
|
||||||
|
mode = "L"
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
with pytest.raises(IOError):
|
||||||
|
helper_save_as_palm(tmp_path, mode)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rgb_ioerror(tmp_path):
|
||||||
|
# Arrange
|
||||||
|
mode = "RGB"
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
with pytest.raises(IOError):
|
||||||
|
helper_save_as_palm(tmp_path, mode)
|
||||||
|
|
|
@ -1,18 +1,15 @@
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import PillowTestCase
|
|
||||||
|
|
||||||
|
def test_load_raw():
|
||||||
|
with Image.open("Tests/images/hopper.pcd") as im:
|
||||||
|
im.load() # should not segfault.
|
||||||
|
|
||||||
class TestFilePcd(PillowTestCase):
|
# Note that this image was created with a resized hopper
|
||||||
def test_load_raw(self):
|
# image, which was then converted to pcd with imagemagick
|
||||||
with Image.open("Tests/images/hopper.pcd") as im:
|
# and the colors are wonky in Pillow. It's unclear if this
|
||||||
im.load() # should not segfault.
|
# is a pillow or a convert issue, as other images not generated
|
||||||
|
# from convert look find on pillow and not imagemagick.
|
||||||
|
|
||||||
# Note that this image was created with a resized hopper
|
# target = hopper().resize((768,512))
|
||||||
# image, which was then converted to pcd with imagemagick
|
# assert_image_similar(im, target, 10)
|
||||||
# and the colors are wonky in Pillow. It's unclear if this
|
|
||||||
# is a pillow or a convert issue, as other images not generated
|
|
||||||
# from convert look find on pillow and not imagemagick.
|
|
||||||
|
|
||||||
# target = hopper().resize((768,512))
|
|
||||||
# assert_image_similar(im, target, 10)
|
|
||||||
|
|
|
@ -1,130 +1,142 @@
|
||||||
import pytest
|
import pytest
|
||||||
from PIL import Image, ImageFile, PcxImagePlugin
|
from PIL import Image, ImageFile, PcxImagePlugin
|
||||||
|
|
||||||
from .helper import PillowTestCase, assert_image_equal, hopper
|
from .helper import assert_image_equal, hopper
|
||||||
|
|
||||||
|
|
||||||
class TestFilePcx(PillowTestCase):
|
def _roundtrip(tmp_path, im):
|
||||||
def _roundtrip(self, im):
|
f = str(tmp_path / "temp.pcx")
|
||||||
f = self.tempfile("temp.pcx")
|
im.save(f)
|
||||||
|
with Image.open(f) as im2:
|
||||||
|
assert im2.mode == im.mode
|
||||||
|
assert im2.size == im.size
|
||||||
|
assert im2.format == "PCX"
|
||||||
|
assert im2.get_format_mimetype() == "image/x-pcx"
|
||||||
|
assert_image_equal(im2, im)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sanity(tmp_path):
|
||||||
|
for mode in ("1", "L", "P", "RGB"):
|
||||||
|
_roundtrip(tmp_path, hopper(mode))
|
||||||
|
|
||||||
|
# Test an unsupported mode
|
||||||
|
f = str(tmp_path / "temp.pcx")
|
||||||
|
im = hopper("RGBA")
|
||||||
|
with pytest.raises(ValueError):
|
||||||
im.save(f)
|
im.save(f)
|
||||||
with Image.open(f) as im2:
|
|
||||||
assert im2.mode == im.mode
|
|
||||||
assert im2.size == im.size
|
|
||||||
assert im2.format == "PCX"
|
|
||||||
assert im2.get_format_mimetype() == "image/x-pcx"
|
|
||||||
assert_image_equal(im2, im)
|
|
||||||
|
|
||||||
def test_sanity(self):
|
|
||||||
for mode in ("1", "L", "P", "RGB"):
|
|
||||||
self._roundtrip(hopper(mode))
|
|
||||||
|
|
||||||
# Test an unsupported mode
|
def test_invalid_file():
|
||||||
f = self.tempfile("temp.pcx")
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
im = hopper("RGBA")
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
im.save(f)
|
|
||||||
|
|
||||||
def test_invalid_file(self):
|
with pytest.raises(SyntaxError):
|
||||||
invalid_file = "Tests/images/flower.jpg"
|
PcxImagePlugin.PcxImageFile(invalid_file)
|
||||||
|
|
||||||
with pytest.raises(SyntaxError):
|
|
||||||
PcxImagePlugin.PcxImageFile(invalid_file)
|
|
||||||
|
|
||||||
def test_odd(self):
|
def test_odd(tmp_path):
|
||||||
# see issue #523, odd sized images should have a stride that's even.
|
# See issue #523, odd sized images should have a stride that's even.
|
||||||
# not that imagemagick or gimp write pcx that way.
|
# Not that ImageMagick or GIMP write PCX that way.
|
||||||
# we were not handling properly.
|
# We were not handling properly.
|
||||||
for mode in ("1", "L", "P", "RGB"):
|
for mode in ("1", "L", "P", "RGB"):
|
||||||
# larger, odd sized images are better here to ensure that
|
# larger, odd sized images are better here to ensure that
|
||||||
# we handle interrupted scan lines properly.
|
# we handle interrupted scan lines properly.
|
||||||
self._roundtrip(hopper(mode).resize((511, 511)))
|
_roundtrip(tmp_path, hopper(mode).resize((511, 511)))
|
||||||
|
|
||||||
def test_pil184(self):
|
|
||||||
# Check reading of files where xmin/xmax is not zero.
|
|
||||||
|
|
||||||
test_file = "Tests/images/pil184.pcx"
|
def test_pil184():
|
||||||
with Image.open(test_file) as im:
|
# Check reading of files where xmin/xmax is not zero.
|
||||||
assert im.size == (447, 144)
|
|
||||||
assert im.tile[0][1] == (0, 0, 447, 144)
|
|
||||||
|
|
||||||
# Make sure all pixels are either 0 or 255.
|
test_file = "Tests/images/pil184.pcx"
|
||||||
assert im.histogram()[0] + im.histogram()[255] == 447 * 144
|
with Image.open(test_file) as im:
|
||||||
|
assert im.size == (447, 144)
|
||||||
|
assert im.tile[0][1] == (0, 0, 447, 144)
|
||||||
|
|
||||||
def test_1px_width(self):
|
# Make sure all pixels are either 0 or 255.
|
||||||
im = Image.new("L", (1, 256))
|
assert im.histogram()[0] + im.histogram()[255] == 447 * 144
|
||||||
px = im.load()
|
|
||||||
for y in range(256):
|
|
||||||
px[0, y] = y
|
|
||||||
self._roundtrip(im)
|
|
||||||
|
|
||||||
def test_large_count(self):
|
|
||||||
im = Image.new("L", (256, 1))
|
def test_1px_width(tmp_path):
|
||||||
px = im.load()
|
im = Image.new("L", (1, 256))
|
||||||
|
px = im.load()
|
||||||
|
for y in range(256):
|
||||||
|
px[0, y] = y
|
||||||
|
_roundtrip(tmp_path, im)
|
||||||
|
|
||||||
|
|
||||||
|
def test_large_count(tmp_path):
|
||||||
|
im = Image.new("L", (256, 1))
|
||||||
|
px = im.load()
|
||||||
|
for x in range(256):
|
||||||
|
px[x, 0] = x // 67 * 67
|
||||||
|
_roundtrip(tmp_path, im)
|
||||||
|
|
||||||
|
|
||||||
|
def _test_buffer_overflow(tmp_path, im, size=1024):
|
||||||
|
_last = ImageFile.MAXBLOCK
|
||||||
|
ImageFile.MAXBLOCK = size
|
||||||
|
try:
|
||||||
|
_roundtrip(tmp_path, im)
|
||||||
|
finally:
|
||||||
|
ImageFile.MAXBLOCK = _last
|
||||||
|
|
||||||
|
|
||||||
|
def test_break_in_count_overflow(tmp_path):
|
||||||
|
im = Image.new("L", (256, 5))
|
||||||
|
px = im.load()
|
||||||
|
for y in range(4):
|
||||||
for x in range(256):
|
for x in range(256):
|
||||||
px[x, 0] = x // 67 * 67
|
px[x, y] = x % 128
|
||||||
self._roundtrip(im)
|
_test_buffer_overflow(tmp_path, im)
|
||||||
|
|
||||||
def _test_buffer_overflow(self, im, size=1024):
|
|
||||||
_last = ImageFile.MAXBLOCK
|
|
||||||
ImageFile.MAXBLOCK = size
|
|
||||||
try:
|
|
||||||
self._roundtrip(im)
|
|
||||||
finally:
|
|
||||||
ImageFile.MAXBLOCK = _last
|
|
||||||
|
|
||||||
def test_break_in_count_overflow(self):
|
def test_break_one_in_loop(tmp_path):
|
||||||
im = Image.new("L", (256, 5))
|
im = Image.new("L", (256, 5))
|
||||||
px = im.load()
|
px = im.load()
|
||||||
for y in range(4):
|
for y in range(5):
|
||||||
for x in range(256):
|
for x in range(256):
|
||||||
px[x, y] = x % 128
|
px[x, y] = x % 128
|
||||||
self._test_buffer_overflow(im)
|
_test_buffer_overflow(tmp_path, im)
|
||||||
|
|
||||||
def test_break_one_in_loop(self):
|
|
||||||
im = Image.new("L", (256, 5))
|
|
||||||
px = im.load()
|
|
||||||
for y in range(5):
|
|
||||||
for x in range(256):
|
|
||||||
px[x, y] = x % 128
|
|
||||||
self._test_buffer_overflow(im)
|
|
||||||
|
|
||||||
def test_break_many_in_loop(self):
|
def test_break_many_in_loop(tmp_path):
|
||||||
im = Image.new("L", (256, 5))
|
im = Image.new("L", (256, 5))
|
||||||
px = im.load()
|
px = im.load()
|
||||||
for y in range(4):
|
for y in range(4):
|
||||||
for x in range(256):
|
for x in range(256):
|
||||||
px[x, y] = x % 128
|
px[x, y] = x % 128
|
||||||
for x in range(8):
|
for x in range(8):
|
||||||
px[x, 4] = 16
|
px[x, 4] = 16
|
||||||
self._test_buffer_overflow(im)
|
_test_buffer_overflow(tmp_path, im)
|
||||||
|
|
||||||
def test_break_one_at_end(self):
|
|
||||||
im = Image.new("L", (256, 5))
|
|
||||||
px = im.load()
|
|
||||||
for y in range(5):
|
|
||||||
for x in range(256):
|
|
||||||
px[x, y] = x % 128
|
|
||||||
px[0, 3] = 128 + 64
|
|
||||||
self._test_buffer_overflow(im)
|
|
||||||
|
|
||||||
def test_break_many_at_end(self):
|
def test_break_one_at_end(tmp_path):
|
||||||
im = Image.new("L", (256, 5))
|
im = Image.new("L", (256, 5))
|
||||||
px = im.load()
|
px = im.load()
|
||||||
for y in range(5):
|
for y in range(5):
|
||||||
for x in range(256):
|
for x in range(256):
|
||||||
px[x, y] = x % 128
|
px[x, y] = x % 128
|
||||||
for x in range(4):
|
px[0, 3] = 128 + 64
|
||||||
px[x * 2, 3] = 128 + 64
|
_test_buffer_overflow(tmp_path, im)
|
||||||
px[x + 256 - 4, 3] = 0
|
|
||||||
self._test_buffer_overflow(im)
|
|
||||||
|
|
||||||
def test_break_padding(self):
|
|
||||||
im = Image.new("L", (257, 5))
|
def test_break_many_at_end(tmp_path):
|
||||||
px = im.load()
|
im = Image.new("L", (256, 5))
|
||||||
for y in range(5):
|
px = im.load()
|
||||||
for x in range(257):
|
for y in range(5):
|
||||||
px[x, y] = x % 128
|
for x in range(256):
|
||||||
for x in range(5):
|
px[x, y] = x % 128
|
||||||
px[x, 3] = 0
|
for x in range(4):
|
||||||
self._test_buffer_overflow(im)
|
px[x * 2, 3] = 128 + 64
|
||||||
|
px[x + 256 - 4, 3] = 0
|
||||||
|
_test_buffer_overflow(tmp_path, im)
|
||||||
|
|
||||||
|
|
||||||
|
def test_break_padding(tmp_path):
|
||||||
|
im = Image.new("L", (257, 5))
|
||||||
|
px = im.load()
|
||||||
|
for y in range(5):
|
||||||
|
for x in range(257):
|
||||||
|
px[x, y] = x % 128
|
||||||
|
for x in range(5):
|
||||||
|
px[x, 3] = 0
|
||||||
|
_test_buffer_overflow(tmp_path, im)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import re
|
import re
|
||||||
import unittest
|
|
||||||
import zlib
|
import zlib
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
@ -8,7 +7,6 @@ from PIL import Image, ImageFile, PngImagePlugin
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
PillowLeakTestCase,
|
PillowLeakTestCase,
|
||||||
PillowTestCase,
|
|
||||||
assert_image,
|
assert_image,
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
hopper,
|
hopper,
|
||||||
|
@ -55,7 +53,7 @@ def roundtrip(im, **options):
|
||||||
|
|
||||||
|
|
||||||
@skip_unless_feature("zlib")
|
@skip_unless_feature("zlib")
|
||||||
class TestFilePng(PillowTestCase):
|
class TestFilePng:
|
||||||
def get_chunks(self, filename):
|
def get_chunks(self, filename):
|
||||||
chunks = []
|
chunks = []
|
||||||
with open(filename, "rb") as fp:
|
with open(filename, "rb") as fp:
|
||||||
|
@ -72,12 +70,12 @@ class TestFilePng(PillowTestCase):
|
||||||
return chunks
|
return chunks
|
||||||
|
|
||||||
@pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian")
|
@pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian")
|
||||||
def test_sanity(self):
|
def test_sanity(self, tmp_path):
|
||||||
|
|
||||||
# internal version number
|
# internal version number
|
||||||
assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", Image.core.zlib_version)
|
assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", Image.core.zlib_version)
|
||||||
|
|
||||||
test_file = self.tempfile("temp.png")
|
test_file = str(tmp_path / "temp.png")
|
||||||
|
|
||||||
hopper("RGB").save(test_file)
|
hopper("RGB").save(test_file)
|
||||||
|
|
||||||
|
@ -232,14 +230,14 @@ class TestFilePng(PillowTestCase):
|
||||||
# image has 876 transparent pixels
|
# image has 876 transparent pixels
|
||||||
assert im.getchannel("A").getcolors()[0][0] == 876
|
assert im.getchannel("A").getcolors()[0][0] == 876
|
||||||
|
|
||||||
def test_save_p_transparent_palette(self):
|
def test_save_p_transparent_palette(self, tmp_path):
|
||||||
in_file = "Tests/images/pil123p.png"
|
in_file = "Tests/images/pil123p.png"
|
||||||
with Image.open(in_file) as im:
|
with Image.open(in_file) as im:
|
||||||
# 'transparency' contains a byte string with the opacity for
|
# 'transparency' contains a byte string with the opacity for
|
||||||
# each palette entry
|
# each palette entry
|
||||||
assert len(im.info["transparency"]) == 256
|
assert len(im.info["transparency"]) == 256
|
||||||
|
|
||||||
test_file = self.tempfile("temp.png")
|
test_file = str(tmp_path / "temp.png")
|
||||||
im.save(test_file)
|
im.save(test_file)
|
||||||
|
|
||||||
# check if saved image contains same transparency
|
# check if saved image contains same transparency
|
||||||
|
@ -253,14 +251,14 @@ class TestFilePng(PillowTestCase):
|
||||||
# image has 124 unique alpha values
|
# image has 124 unique alpha values
|
||||||
assert len(im.getchannel("A").getcolors()) == 124
|
assert len(im.getchannel("A").getcolors()) == 124
|
||||||
|
|
||||||
def test_save_p_single_transparency(self):
|
def test_save_p_single_transparency(self, tmp_path):
|
||||||
in_file = "Tests/images/p_trns_single.png"
|
in_file = "Tests/images/p_trns_single.png"
|
||||||
with Image.open(in_file) as im:
|
with Image.open(in_file) as im:
|
||||||
# pixel value 164 is full transparent
|
# pixel value 164 is full transparent
|
||||||
assert im.info["transparency"] == 164
|
assert im.info["transparency"] == 164
|
||||||
assert im.getpixel((31, 31)) == 164
|
assert im.getpixel((31, 31)) == 164
|
||||||
|
|
||||||
test_file = self.tempfile("temp.png")
|
test_file = str(tmp_path / "temp.png")
|
||||||
im.save(test_file)
|
im.save(test_file)
|
||||||
|
|
||||||
# check if saved image contains same transparency
|
# check if saved image contains same transparency
|
||||||
|
@ -276,14 +274,14 @@ class TestFilePng(PillowTestCase):
|
||||||
# image has 876 transparent pixels
|
# image has 876 transparent pixels
|
||||||
assert im.getchannel("A").getcolors()[0][0] == 876
|
assert im.getchannel("A").getcolors()[0][0] == 876
|
||||||
|
|
||||||
def test_save_p_transparent_black(self):
|
def test_save_p_transparent_black(self, tmp_path):
|
||||||
# check if solid black image with full transparency
|
# check if solid black image with full transparency
|
||||||
# is supported (check for #1838)
|
# is supported (check for #1838)
|
||||||
im = Image.new("RGBA", (10, 10), (0, 0, 0, 0))
|
im = Image.new("RGBA", (10, 10), (0, 0, 0, 0))
|
||||||
assert im.getcolors() == [(100, (0, 0, 0, 0))]
|
assert im.getcolors() == [(100, (0, 0, 0, 0))]
|
||||||
|
|
||||||
im = im.convert("P")
|
im = im.convert("P")
|
||||||
test_file = self.tempfile("temp.png")
|
test_file = str(tmp_path / "temp.png")
|
||||||
im.save(test_file)
|
im.save(test_file)
|
||||||
|
|
||||||
# check if saved image contains same transparency
|
# check if saved image contains same transparency
|
||||||
|
@ -294,7 +292,7 @@ class TestFilePng(PillowTestCase):
|
||||||
assert_image(im, "RGBA", (10, 10))
|
assert_image(im, "RGBA", (10, 10))
|
||||||
assert im.getcolors() == [(100, (0, 0, 0, 0))]
|
assert im.getcolors() == [(100, (0, 0, 0, 0))]
|
||||||
|
|
||||||
def test_save_greyscale_transparency(self):
|
def test_save_greyscale_transparency(self, tmp_path):
|
||||||
for mode, num_transparent in {"1": 1994, "L": 559, "I": 559}.items():
|
for mode, num_transparent in {"1": 1994, "L": 559, "I": 559}.items():
|
||||||
in_file = "Tests/images/" + mode.lower() + "_trns.png"
|
in_file = "Tests/images/" + mode.lower() + "_trns.png"
|
||||||
with Image.open(in_file) as im:
|
with Image.open(in_file) as im:
|
||||||
|
@ -304,7 +302,7 @@ class TestFilePng(PillowTestCase):
|
||||||
im_rgba = im.convert("RGBA")
|
im_rgba = im.convert("RGBA")
|
||||||
assert im_rgba.getchannel("A").getcolors()[0][0] == num_transparent
|
assert im_rgba.getchannel("A").getcolors()[0][0] == num_transparent
|
||||||
|
|
||||||
test_file = self.tempfile("temp.png")
|
test_file = str(tmp_path / "temp.png")
|
||||||
im.save(test_file)
|
im.save(test_file)
|
||||||
|
|
||||||
with Image.open(test_file) as test_im:
|
with Image.open(test_file) as test_im:
|
||||||
|
@ -315,10 +313,10 @@ class TestFilePng(PillowTestCase):
|
||||||
test_im_rgba = test_im.convert("RGBA")
|
test_im_rgba = test_im.convert("RGBA")
|
||||||
assert test_im_rgba.getchannel("A").getcolors()[0][0] == num_transparent
|
assert test_im_rgba.getchannel("A").getcolors()[0][0] == num_transparent
|
||||||
|
|
||||||
def test_save_rgb_single_transparency(self):
|
def test_save_rgb_single_transparency(self, tmp_path):
|
||||||
in_file = "Tests/images/caption_6_33_22.png"
|
in_file = "Tests/images/caption_6_33_22.png"
|
||||||
with Image.open(in_file) as im:
|
with Image.open(in_file) as im:
|
||||||
test_file = self.tempfile("temp.png")
|
test_file = str(tmp_path / "temp.png")
|
||||||
im.save(test_file)
|
im.save(test_file)
|
||||||
|
|
||||||
def test_load_verify(self):
|
def test_load_verify(self):
|
||||||
|
@ -483,12 +481,12 @@ class TestFilePng(PillowTestCase):
|
||||||
im = roundtrip(im, transparency=(0, 1, 2))
|
im = roundtrip(im, transparency=(0, 1, 2))
|
||||||
assert im.info["transparency"] == (0, 1, 2)
|
assert im.info["transparency"] == (0, 1, 2)
|
||||||
|
|
||||||
def test_trns_p(self):
|
def test_trns_p(self, tmp_path):
|
||||||
# Check writing a transparency of 0, issue #528
|
# Check writing a transparency of 0, issue #528
|
||||||
im = hopper("P")
|
im = hopper("P")
|
||||||
im.info["transparency"] = 0
|
im.info["transparency"] = 0
|
||||||
|
|
||||||
f = self.tempfile("temp.png")
|
f = str(tmp_path / "temp.png")
|
||||||
im.save(f)
|
im.save(f)
|
||||||
|
|
||||||
with Image.open(f) as im2:
|
with Image.open(f) as im2:
|
||||||
|
@ -539,9 +537,9 @@ class TestFilePng(PillowTestCase):
|
||||||
assert repr_png.format == "PNG"
|
assert repr_png.format == "PNG"
|
||||||
assert_image_equal(im, repr_png)
|
assert_image_equal(im, repr_png)
|
||||||
|
|
||||||
def test_chunk_order(self):
|
def test_chunk_order(self, tmp_path):
|
||||||
with Image.open("Tests/images/icc_profile.png") as im:
|
with Image.open("Tests/images/icc_profile.png") as im:
|
||||||
test_file = self.tempfile("temp.png")
|
test_file = str(tmp_path / "temp.png")
|
||||||
im.convert("P").save(test_file, dpi=(100, 100))
|
im.convert("P").save(test_file, dpi=(100, 100))
|
||||||
|
|
||||||
chunks = self.get_chunks(test_file)
|
chunks = self.get_chunks(test_file)
|
||||||
|
@ -598,34 +596,34 @@ class TestFilePng(PillowTestCase):
|
||||||
exif = im._getexif()
|
exif = im._getexif()
|
||||||
assert exif[274] == 1
|
assert exif[274] == 1
|
||||||
|
|
||||||
def test_exif_save(self):
|
def test_exif_save(self, tmp_path):
|
||||||
with Image.open("Tests/images/exif.png") as im:
|
with Image.open("Tests/images/exif.png") as im:
|
||||||
test_file = self.tempfile("temp.png")
|
test_file = str(tmp_path / "temp.png")
|
||||||
im.save(test_file)
|
im.save(test_file)
|
||||||
|
|
||||||
with Image.open(test_file) as reloaded:
|
with Image.open(test_file) as reloaded:
|
||||||
exif = reloaded._getexif()
|
exif = reloaded._getexif()
|
||||||
assert exif[274] == 1
|
assert exif[274] == 1
|
||||||
|
|
||||||
def test_exif_from_jpg(self):
|
def test_exif_from_jpg(self, tmp_path):
|
||||||
with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
|
with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
|
||||||
test_file = self.tempfile("temp.png")
|
test_file = str(tmp_path / "temp.png")
|
||||||
im.save(test_file)
|
im.save(test_file)
|
||||||
|
|
||||||
with Image.open(test_file) as reloaded:
|
with Image.open(test_file) as reloaded:
|
||||||
exif = reloaded._getexif()
|
exif = reloaded._getexif()
|
||||||
assert exif[305] == "Adobe Photoshop CS Macintosh"
|
assert exif[305] == "Adobe Photoshop CS Macintosh"
|
||||||
|
|
||||||
def test_exif_argument(self):
|
def test_exif_argument(self, tmp_path):
|
||||||
with Image.open(TEST_PNG_FILE) as im:
|
with Image.open(TEST_PNG_FILE) as im:
|
||||||
test_file = self.tempfile("temp.png")
|
test_file = str(tmp_path / "temp.png")
|
||||||
im.save(test_file, exif=b"exifstring")
|
im.save(test_file, exif=b"exifstring")
|
||||||
|
|
||||||
with Image.open(test_file) as reloaded:
|
with Image.open(test_file) as reloaded:
|
||||||
assert reloaded.info["exif"] == b"Exif\x00\x00exifstring"
|
assert reloaded.info["exif"] == b"Exif\x00\x00exifstring"
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(is_win32(), "requires Unix or macOS")
|
@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS")
|
||||||
@skip_unless_feature("zlib")
|
@skip_unless_feature("zlib")
|
||||||
class TestTruncatedPngPLeaks(PillowLeakTestCase):
|
class TestTruncatedPngPLeaks(PillowLeakTestCase):
|
||||||
mem_limit = 2 * 1024 # max increase in K
|
mem_limit = 2 * 1024 # max increase in K
|
||||||
|
|
|
@ -1,77 +1,82 @@
|
||||||
import pytest
|
import pytest
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import PillowTestCase, assert_image_equal, assert_image_similar, hopper
|
from .helper import assert_image_equal, assert_image_similar, hopper
|
||||||
|
|
||||||
# sample ppm stream
|
# sample ppm stream
|
||||||
test_file = "Tests/images/hopper.ppm"
|
TEST_FILE = "Tests/images/hopper.ppm"
|
||||||
|
|
||||||
|
|
||||||
class TestFilePpm(PillowTestCase):
|
def test_sanity():
|
||||||
def test_sanity(self):
|
with Image.open(TEST_FILE) as im:
|
||||||
with Image.open(test_file) as im:
|
im.load()
|
||||||
im.load()
|
assert im.mode == "RGB"
|
||||||
assert im.mode == "RGB"
|
assert im.size == (128, 128)
|
||||||
assert im.size == (128, 128)
|
assert im.format, "PPM"
|
||||||
assert im.format, "PPM"
|
assert im.get_format_mimetype() == "image/x-portable-pixmap"
|
||||||
assert im.get_format_mimetype() == "image/x-portable-pixmap"
|
|
||||||
|
|
||||||
def test_16bit_pgm(self):
|
|
||||||
with Image.open("Tests/images/16_bit_binary.pgm") as im:
|
|
||||||
im.load()
|
|
||||||
assert im.mode == "I"
|
|
||||||
assert im.size == (20, 100)
|
|
||||||
assert im.get_format_mimetype() == "image/x-portable-graymap"
|
|
||||||
|
|
||||||
with Image.open("Tests/images/16_bit_binary_pgm.png") as tgt:
|
def test_16bit_pgm():
|
||||||
assert_image_equal(im, tgt)
|
with Image.open("Tests/images/16_bit_binary.pgm") as im:
|
||||||
|
im.load()
|
||||||
|
assert im.mode == "I"
|
||||||
|
assert im.size == (20, 100)
|
||||||
|
assert im.get_format_mimetype() == "image/x-portable-graymap"
|
||||||
|
|
||||||
def test_16bit_pgm_write(self):
|
with Image.open("Tests/images/16_bit_binary_pgm.png") as tgt:
|
||||||
with Image.open("Tests/images/16_bit_binary.pgm") as im:
|
assert_image_equal(im, tgt)
|
||||||
im.load()
|
|
||||||
|
|
||||||
f = self.tempfile("temp.pgm")
|
|
||||||
im.save(f, "PPM")
|
|
||||||
|
|
||||||
with Image.open(f) as reloaded:
|
def test_16bit_pgm_write(tmp_path):
|
||||||
assert_image_equal(im, reloaded)
|
with Image.open("Tests/images/16_bit_binary.pgm") as im:
|
||||||
|
im.load()
|
||||||
|
|
||||||
def test_pnm(self):
|
f = str(tmp_path / "temp.pgm")
|
||||||
with Image.open("Tests/images/hopper.pnm") as im:
|
im.save(f, "PPM")
|
||||||
assert_image_similar(im, hopper(), 0.0001)
|
|
||||||
|
|
||||||
f = self.tempfile("temp.pnm")
|
with Image.open(f) as reloaded:
|
||||||
im.save(f)
|
assert_image_equal(im, reloaded)
|
||||||
|
|
||||||
with Image.open(f) as reloaded:
|
|
||||||
assert_image_equal(im, reloaded)
|
|
||||||
|
|
||||||
def test_truncated_file(self):
|
def test_pnm(tmp_path):
|
||||||
path = self.tempfile("temp.pgm")
|
with Image.open("Tests/images/hopper.pnm") as im:
|
||||||
with open(path, "w") as f:
|
assert_image_similar(im, hopper(), 0.0001)
|
||||||
f.write("P6")
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
f = str(tmp_path / "temp.pnm")
|
||||||
Image.open(path)
|
im.save(f)
|
||||||
|
|
||||||
def test_neg_ppm(self):
|
with Image.open(f) as reloaded:
|
||||||
# Storage.c accepted negative values for xsize, ysize. the
|
assert_image_equal(im, reloaded)
|
||||||
# internal open_ppm function didn't check for sanity but it
|
|
||||||
# has been removed. The default opener doesn't accept negative
|
|
||||||
# sizes.
|
|
||||||
|
|
||||||
with pytest.raises(IOError):
|
|
||||||
Image.open("Tests/images/negative_size.ppm")
|
|
||||||
|
|
||||||
def test_mimetypes(self):
|
def test_truncated_file(tmp_path):
|
||||||
path = self.tempfile("temp.pgm")
|
path = str(tmp_path / "temp.pgm")
|
||||||
|
with open(path, "w") as f:
|
||||||
|
f.write("P6")
|
||||||
|
|
||||||
with open(path, "w") as f:
|
with pytest.raises(ValueError):
|
||||||
f.write("P4\n128 128\n255")
|
Image.open(path)
|
||||||
with Image.open(path) as im:
|
|
||||||
assert im.get_format_mimetype() == "image/x-portable-bitmap"
|
|
||||||
|
|
||||||
with open(path, "w") as f:
|
|
||||||
f.write("PyCMYK\n128 128\n255")
|
def test_neg_ppm():
|
||||||
with Image.open(path) as im:
|
# Storage.c accepted negative values for xsize, ysize. the
|
||||||
assert im.get_format_mimetype() == "image/x-portable-anymap"
|
# internal open_ppm function didn't check for sanity but it
|
||||||
|
# has been removed. The default opener doesn't accept negative
|
||||||
|
# sizes.
|
||||||
|
|
||||||
|
with pytest.raises(IOError):
|
||||||
|
Image.open("Tests/images/negative_size.ppm")
|
||||||
|
|
||||||
|
|
||||||
|
def test_mimetypes(tmp_path):
|
||||||
|
path = str(tmp_path / "temp.pgm")
|
||||||
|
|
||||||
|
with open(path, "w") as f:
|
||||||
|
f.write("P4\n128 128\n255")
|
||||||
|
with Image.open(path) as im:
|
||||||
|
assert im.get_format_mimetype() == "image/x-portable-bitmap"
|
||||||
|
|
||||||
|
with open(path, "w") as f:
|
||||||
|
f.write("PyCMYK\n128 128\n255")
|
||||||
|
with Image.open(path) as im:
|
||||||
|
assert im.get_format_mimetype() == "image/x-portable-anymap"
|
||||||
|
|
|
@ -1,92 +1,100 @@
|
||||||
import pytest
|
import pytest
|
||||||
from PIL import Image, SgiImagePlugin
|
from PIL import Image, SgiImagePlugin
|
||||||
|
|
||||||
from .helper import PillowTestCase, assert_image_equal, assert_image_similar, hopper
|
from .helper import assert_image_equal, assert_image_similar, hopper
|
||||||
|
|
||||||
|
|
||||||
class TestFileSgi(PillowTestCase):
|
def test_rgb():
|
||||||
def test_rgb(self):
|
# Created with ImageMagick then renamed:
|
||||||
# Created with ImageMagick then renamed:
|
# convert hopper.ppm -compress None sgi:hopper.rgb
|
||||||
# convert hopper.ppm -compress None sgi:hopper.rgb
|
test_file = "Tests/images/hopper.rgb"
|
||||||
test_file = "Tests/images/hopper.rgb"
|
|
||||||
|
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
assert_image_equal(im, hopper())
|
assert_image_equal(im, hopper())
|
||||||
assert im.get_format_mimetype() == "image/rgb"
|
assert im.get_format_mimetype() == "image/rgb"
|
||||||
|
|
||||||
def test_rgb16(self):
|
|
||||||
test_file = "Tests/images/hopper16.rgb"
|
|
||||||
|
|
||||||
with Image.open(test_file) as im:
|
def test_rgb16():
|
||||||
assert_image_equal(im, hopper())
|
test_file = "Tests/images/hopper16.rgb"
|
||||||
|
|
||||||
def test_l(self):
|
with Image.open(test_file) as im:
|
||||||
# Created with ImageMagick
|
assert_image_equal(im, hopper())
|
||||||
# convert hopper.ppm -monochrome -compress None sgi:hopper.bw
|
|
||||||
test_file = "Tests/images/hopper.bw"
|
|
||||||
|
|
||||||
with Image.open(test_file) as im:
|
|
||||||
assert_image_similar(im, hopper("L"), 2)
|
|
||||||
assert im.get_format_mimetype() == "image/sgi"
|
|
||||||
|
|
||||||
def test_rgba(self):
|
def test_l():
|
||||||
# Created with ImageMagick:
|
# Created with ImageMagick
|
||||||
# convert transparent.png -compress None transparent.sgi
|
# convert hopper.ppm -monochrome -compress None sgi:hopper.bw
|
||||||
test_file = "Tests/images/transparent.sgi"
|
test_file = "Tests/images/hopper.bw"
|
||||||
|
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
with Image.open("Tests/images/transparent.png") as target:
|
assert_image_similar(im, hopper("L"), 2)
|
||||||
assert_image_equal(im, target)
|
assert im.get_format_mimetype() == "image/sgi"
|
||||||
assert im.get_format_mimetype() == "image/sgi"
|
|
||||||
|
|
||||||
def test_rle(self):
|
|
||||||
# Created with ImageMagick:
|
|
||||||
# convert hopper.ppm hopper.sgi
|
|
||||||
test_file = "Tests/images/hopper.sgi"
|
|
||||||
|
|
||||||
with Image.open(test_file) as im:
|
def test_rgba():
|
||||||
with Image.open("Tests/images/hopper.rgb") as target:
|
# Created with ImageMagick:
|
||||||
assert_image_equal(im, target)
|
# convert transparent.png -compress None transparent.sgi
|
||||||
|
test_file = "Tests/images/transparent.sgi"
|
||||||
|
|
||||||
def test_rle16(self):
|
with Image.open(test_file) as im:
|
||||||
test_file = "Tests/images/tv16.sgi"
|
with Image.open("Tests/images/transparent.png") as target:
|
||||||
|
assert_image_equal(im, target)
|
||||||
|
assert im.get_format_mimetype() == "image/sgi"
|
||||||
|
|
||||||
with Image.open(test_file) as im:
|
|
||||||
with Image.open("Tests/images/tv.rgb") as target:
|
|
||||||
assert_image_equal(im, target)
|
|
||||||
|
|
||||||
def test_invalid_file(self):
|
def test_rle():
|
||||||
invalid_file = "Tests/images/flower.jpg"
|
# Created with ImageMagick:
|
||||||
|
# convert hopper.ppm hopper.sgi
|
||||||
|
test_file = "Tests/images/hopper.sgi"
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with Image.open(test_file) as im:
|
||||||
SgiImagePlugin.SgiImageFile(invalid_file)
|
with Image.open("Tests/images/hopper.rgb") as target:
|
||||||
|
assert_image_equal(im, target)
|
||||||
|
|
||||||
def test_write(self):
|
|
||||||
def roundtrip(img):
|
|
||||||
out = self.tempfile("temp.sgi")
|
|
||||||
img.save(out, format="sgi")
|
|
||||||
with Image.open(out) as reloaded:
|
|
||||||
assert_image_equal(img, reloaded)
|
|
||||||
|
|
||||||
for mode in ("L", "RGB", "RGBA"):
|
def test_rle16():
|
||||||
roundtrip(hopper(mode))
|
test_file = "Tests/images/tv16.sgi"
|
||||||
|
|
||||||
# Test 1 dimension for an L mode image
|
with Image.open(test_file) as im:
|
||||||
roundtrip(Image.new("L", (10, 1)))
|
with Image.open("Tests/images/tv.rgb") as target:
|
||||||
|
assert_image_equal(im, target)
|
||||||
|
|
||||||
def test_write16(self):
|
|
||||||
test_file = "Tests/images/hopper16.rgb"
|
|
||||||
|
|
||||||
with Image.open(test_file) as im:
|
def test_invalid_file():
|
||||||
out = self.tempfile("temp.sgi")
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
im.save(out, format="sgi", bpc=2)
|
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
with pytest.raises(ValueError):
|
||||||
assert_image_equal(im, reloaded)
|
SgiImagePlugin.SgiImageFile(invalid_file)
|
||||||
|
|
||||||
def test_unsupported_mode(self):
|
|
||||||
im = hopper("LA")
|
|
||||||
out = self.tempfile("temp.sgi")
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
def test_write(tmp_path):
|
||||||
im.save(out, format="sgi")
|
def roundtrip(img):
|
||||||
|
out = str(tmp_path / "temp.sgi")
|
||||||
|
img.save(out, format="sgi")
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert_image_equal(img, reloaded)
|
||||||
|
|
||||||
|
for mode in ("L", "RGB", "RGBA"):
|
||||||
|
roundtrip(hopper(mode))
|
||||||
|
|
||||||
|
# Test 1 dimension for an L mode image
|
||||||
|
roundtrip(Image.new("L", (10, 1)))
|
||||||
|
|
||||||
|
|
||||||
|
def test_write16(tmp_path):
|
||||||
|
test_file = "Tests/images/hopper16.rgb"
|
||||||
|
|
||||||
|
with Image.open(test_file) as im:
|
||||||
|
out = str(tmp_path / "temp.sgi")
|
||||||
|
im.save(out, format="sgi", bpc=2)
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert_image_equal(im, reloaded)
|
||||||
|
|
||||||
|
|
||||||
|
def test_unsupported_mode(tmp_path):
|
||||||
|
im = hopper("LA")
|
||||||
|
out = str(tmp_path / "temp.sgi")
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
im.save(out, format="sgi")
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import unittest
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -8,7 +7,6 @@ from PIL import Image, TiffImagePlugin
|
||||||
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
|
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
PillowTestCase,
|
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
assert_image_equal_tofile,
|
assert_image_equal_tofile,
|
||||||
assert_image_similar,
|
assert_image_similar,
|
||||||
|
@ -21,10 +19,10 @@ from .helper import (
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TestFileTiff(PillowTestCase):
|
class TestFileTiff:
|
||||||
def test_sanity(self):
|
def test_sanity(self, tmp_path):
|
||||||
|
|
||||||
filename = self.tempfile("temp.tif")
|
filename = str(tmp_path / "temp.tif")
|
||||||
|
|
||||||
hopper("RGB").save(filename)
|
hopper("RGB").save(filename)
|
||||||
|
|
||||||
|
@ -54,7 +52,7 @@ class TestFileTiff(PillowTestCase):
|
||||||
with Image.open(filename):
|
with Image.open(filename):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@unittest.skipIf(is_pypy(), "Requires CPython")
|
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
|
||||||
def test_unclosed_file(self):
|
def test_unclosed_file(self):
|
||||||
def open():
|
def open():
|
||||||
im = Image.open("Tests/images/multipage.tiff")
|
im = Image.open("Tests/images/multipage.tiff")
|
||||||
|
@ -155,8 +153,8 @@ class TestFileTiff(PillowTestCase):
|
||||||
assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit
|
assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit
|
||||||
assert im.info["dpi"] == (dpi[1], dpi[1])
|
assert im.info["dpi"] == (dpi[1], dpi[1])
|
||||||
|
|
||||||
def test_save_dpi_rounding(self):
|
def test_save_dpi_rounding(self, tmp_path):
|
||||||
outfile = self.tempfile("temp.tif")
|
outfile = str(tmp_path / "temp.tif")
|
||||||
with Image.open("Tests/images/hopper.tif") as im:
|
with Image.open("Tests/images/hopper.tif") as im:
|
||||||
for dpi in (72.2, 72.8):
|
for dpi in (72.2, 72.8):
|
||||||
im.save(outfile, dpi=(dpi, dpi))
|
im.save(outfile, dpi=(dpi, dpi))
|
||||||
|
@ -190,14 +188,14 @@ class TestFileTiff(PillowTestCase):
|
||||||
# Should not raise struct.error.
|
# Should not raise struct.error.
|
||||||
pytest.warns(UserWarning, i._getexif)
|
pytest.warns(UserWarning, i._getexif)
|
||||||
|
|
||||||
def test_save_rgba(self):
|
def test_save_rgba(self, tmp_path):
|
||||||
im = hopper("RGBA")
|
im = hopper("RGBA")
|
||||||
outfile = self.tempfile("temp.tif")
|
outfile = str(tmp_path / "temp.tif")
|
||||||
im.save(outfile)
|
im.save(outfile)
|
||||||
|
|
||||||
def test_save_unsupported_mode(self):
|
def test_save_unsupported_mode(self, tmp_path):
|
||||||
im = hopper("HSV")
|
im = hopper("HSV")
|
||||||
outfile = self.tempfile("temp.tif")
|
outfile = str(tmp_path / "temp.tif")
|
||||||
with pytest.raises(IOError):
|
with pytest.raises(IOError):
|
||||||
im.save(outfile)
|
im.save(outfile)
|
||||||
|
|
||||||
|
@ -459,9 +457,9 @@ class TestFileTiff(PillowTestCase):
|
||||||
assert im2.mode == "L"
|
assert im2.mode == "L"
|
||||||
assert_image_equal(im, im2)
|
assert_image_equal(im, im2)
|
||||||
|
|
||||||
def test_with_underscores(self):
|
def test_with_underscores(self, tmp_path):
|
||||||
kwargs = {"resolution_unit": "inch", "x_resolution": 72, "y_resolution": 36}
|
kwargs = {"resolution_unit": "inch", "x_resolution": 72, "y_resolution": 36}
|
||||||
filename = self.tempfile("temp.tif")
|
filename = str(tmp_path / "temp.tif")
|
||||||
hopper("RGB").save(filename, **kwargs)
|
hopper("RGB").save(filename, **kwargs)
|
||||||
with Image.open(filename) as im:
|
with Image.open(filename) as im:
|
||||||
|
|
||||||
|
@ -473,14 +471,14 @@ class TestFileTiff(PillowTestCase):
|
||||||
assert im.tag_v2[X_RESOLUTION] == 72
|
assert im.tag_v2[X_RESOLUTION] == 72
|
||||||
assert im.tag_v2[Y_RESOLUTION] == 36
|
assert im.tag_v2[Y_RESOLUTION] == 36
|
||||||
|
|
||||||
def test_roundtrip_tiff_uint16(self):
|
def test_roundtrip_tiff_uint16(self, tmp_path):
|
||||||
# Test an image of all '0' values
|
# Test an image of all '0' values
|
||||||
pixel_value = 0x1234
|
pixel_value = 0x1234
|
||||||
infile = "Tests/images/uint16_1_4660.tif"
|
infile = "Tests/images/uint16_1_4660.tif"
|
||||||
with Image.open(infile) as im:
|
with Image.open(infile) as im:
|
||||||
assert im.getpixel((0, 0)) == pixel_value
|
assert im.getpixel((0, 0)) == pixel_value
|
||||||
|
|
||||||
tmpfile = self.tempfile("temp.tif")
|
tmpfile = str(tmp_path / "temp.tif")
|
||||||
im.save(tmpfile)
|
im.save(tmpfile)
|
||||||
|
|
||||||
with Image.open(tmpfile) as reloaded:
|
with Image.open(tmpfile) as reloaded:
|
||||||
|
@ -512,9 +510,9 @@ class TestFileTiff(PillowTestCase):
|
||||||
with Image.open(infile) as im:
|
with Image.open(infile) as im:
|
||||||
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
|
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
|
||||||
|
|
||||||
def test_palette(self):
|
def test_palette(self, tmp_path):
|
||||||
for mode in ["P", "PA"]:
|
def roundtrip(mode):
|
||||||
outfile = self.tempfile("temp.tif")
|
outfile = str(tmp_path / "temp.tif")
|
||||||
|
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
im.save(outfile)
|
im.save(outfile)
|
||||||
|
@ -522,6 +520,9 @@ class TestFileTiff(PillowTestCase):
|
||||||
with Image.open(outfile) as reloaded:
|
with Image.open(outfile) as reloaded:
|
||||||
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
|
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
|
||||||
|
|
||||||
|
for mode in ["P", "PA"]:
|
||||||
|
roundtrip(mode)
|
||||||
|
|
||||||
def test_tiff_save_all(self):
|
def test_tiff_save_all(self):
|
||||||
mp = BytesIO()
|
mp = BytesIO()
|
||||||
with Image.open("Tests/images/multipage.tiff") as im:
|
with Image.open("Tests/images/multipage.tiff") as im:
|
||||||
|
@ -552,7 +553,7 @@ class TestFileTiff(PillowTestCase):
|
||||||
with Image.open(mp) as reread:
|
with Image.open(mp) as reread:
|
||||||
assert reread.n_frames == 3
|
assert reread.n_frames == 3
|
||||||
|
|
||||||
def test_saving_icc_profile(self):
|
def test_saving_icc_profile(self, tmp_path):
|
||||||
# Tests saving TIFF with icc_profile set.
|
# Tests saving TIFF with icc_profile set.
|
||||||
# At the time of writing this will only work for non-compressed tiffs
|
# At the time of writing this will only work for non-compressed tiffs
|
||||||
# as libtiff does not support embedded ICC profiles,
|
# as libtiff does not support embedded ICC profiles,
|
||||||
|
@ -561,14 +562,14 @@ class TestFileTiff(PillowTestCase):
|
||||||
im.info["icc_profile"] = "Dummy value"
|
im.info["icc_profile"] = "Dummy value"
|
||||||
|
|
||||||
# Try save-load round trip to make sure both handle icc_profile.
|
# Try save-load round trip to make sure both handle icc_profile.
|
||||||
tmpfile = self.tempfile("temp.tif")
|
tmpfile = str(tmp_path / "temp.tif")
|
||||||
im.save(tmpfile, "TIFF", compression="raw")
|
im.save(tmpfile, "TIFF", compression="raw")
|
||||||
with Image.open(tmpfile) as reloaded:
|
with Image.open(tmpfile) as reloaded:
|
||||||
assert b"Dummy value" == reloaded.info["icc_profile"]
|
assert b"Dummy value" == reloaded.info["icc_profile"]
|
||||||
|
|
||||||
def test_close_on_load_exclusive(self):
|
def test_close_on_load_exclusive(self, tmp_path):
|
||||||
# similar to test_fd_leak, but runs on unixlike os
|
# similar to test_fd_leak, but runs on unixlike os
|
||||||
tmpfile = self.tempfile("temp.tif")
|
tmpfile = str(tmp_path / "temp.tif")
|
||||||
|
|
||||||
with Image.open("Tests/images/uint16_1_4660.tif") as im:
|
with Image.open("Tests/images/uint16_1_4660.tif") as im:
|
||||||
im.save(tmpfile)
|
im.save(tmpfile)
|
||||||
|
@ -579,8 +580,8 @@ class TestFileTiff(PillowTestCase):
|
||||||
im.load()
|
im.load()
|
||||||
assert fp.closed
|
assert fp.closed
|
||||||
|
|
||||||
def test_close_on_load_nonexclusive(self):
|
def test_close_on_load_nonexclusive(self, tmp_path):
|
||||||
tmpfile = self.tempfile("temp.tif")
|
tmpfile = str(tmp_path / "temp.tif")
|
||||||
|
|
||||||
with Image.open("Tests/images/uint16_1_4660.tif") as im:
|
with Image.open("Tests/images/uint16_1_4660.tif") as im:
|
||||||
im.save(tmpfile)
|
im.save(tmpfile)
|
||||||
|
@ -601,10 +602,10 @@ class TestFileTiff(PillowTestCase):
|
||||||
Image.open("Tests/images/string_dimension.tiff")
|
Image.open("Tests/images/string_dimension.tiff")
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(is_win32(), "Windows only")
|
@pytest.mark.skipif(not is_win32(), reason="Windows only")
|
||||||
class TestFileTiffW32(PillowTestCase):
|
class TestFileTiffW32:
|
||||||
def test_fd_leak(self):
|
def test_fd_leak(self, tmp_path):
|
||||||
tmpfile = self.tempfile("temp.tif")
|
tmpfile = str(tmp_path / "temp.tif")
|
||||||
|
|
||||||
# this is an mmaped file.
|
# this is an mmaped file.
|
||||||
with Image.open("Tests/images/uint16_1_4660.tif") as im:
|
with Image.open("Tests/images/uint16_1_4660.tif") as im:
|
||||||
|
|
|
@ -5,321 +5,335 @@ import pytest
|
||||||
from PIL import Image, TiffImagePlugin, TiffTags
|
from PIL import Image, TiffImagePlugin, TiffTags
|
||||||
from PIL.TiffImagePlugin import IFDRational
|
from PIL.TiffImagePlugin import IFDRational
|
||||||
|
|
||||||
from .helper import PillowTestCase, assert_deep_equal, hopper
|
from .helper import assert_deep_equal, hopper
|
||||||
|
|
||||||
tag_ids = {info.name: info.value for info in TiffTags.TAGS_V2.values()}
|
TAG_IDS = {info.name: info.value for info in TiffTags.TAGS_V2.values()}
|
||||||
|
|
||||||
|
|
||||||
class TestFileTiffMetadata(PillowTestCase):
|
def test_rt_metadata(tmp_path):
|
||||||
def test_rt_metadata(self):
|
""" Test writing arbitrary metadata into the tiff image directory
|
||||||
""" Test writing arbitrary metadata into the tiff image directory
|
Use case is ImageJ private tags, one numeric, one arbitrary
|
||||||
Use case is ImageJ private tags, one numeric, one arbitrary
|
data. https://github.com/python-pillow/Pillow/issues/291
|
||||||
data. https://github.com/python-pillow/Pillow/issues/291
|
"""
|
||||||
"""
|
|
||||||
|
img = hopper()
|
||||||
img = hopper()
|
|
||||||
|
# Behaviour change: re #1416
|
||||||
# Behaviour change: re #1416
|
# Pre ifd rewrite, ImageJMetaData was being written as a string(2),
|
||||||
# Pre ifd rewrite, ImageJMetaData was being written as a string(2),
|
# Post ifd rewrite, it's defined as arbitrary bytes(7). It should
|
||||||
# Post ifd rewrite, it's defined as arbitrary bytes(7). It should
|
# roundtrip with the actual bytes, rather than stripped text
|
||||||
# roundtrip with the actual bytes, rather than stripped text
|
# of the premerge tests.
|
||||||
# of the premerge tests.
|
#
|
||||||
#
|
# For text items, we still have to decode('ascii','replace') because
|
||||||
# For text items, we still have to decode('ascii','replace') because
|
# the tiff file format can't take 8 bit bytes in that field.
|
||||||
# the tiff file format can't take 8 bit bytes in that field.
|
|
||||||
|
basetextdata = "This is some arbitrary metadata for a text field"
|
||||||
basetextdata = "This is some arbitrary metadata for a text field"
|
bindata = basetextdata.encode("ascii") + b" \xff"
|
||||||
bindata = basetextdata.encode("ascii") + b" \xff"
|
textdata = basetextdata + " " + chr(255)
|
||||||
textdata = basetextdata + " " + chr(255)
|
reloaded_textdata = basetextdata + " ?"
|
||||||
reloaded_textdata = basetextdata + " ?"
|
floatdata = 12.345
|
||||||
floatdata = 12.345
|
doubledata = 67.89
|
||||||
doubledata = 67.89
|
info = TiffImagePlugin.ImageFileDirectory()
|
||||||
info = TiffImagePlugin.ImageFileDirectory()
|
|
||||||
|
ImageJMetaData = TAG_IDS["ImageJMetaData"]
|
||||||
ImageJMetaData = tag_ids["ImageJMetaData"]
|
ImageJMetaDataByteCounts = TAG_IDS["ImageJMetaDataByteCounts"]
|
||||||
ImageJMetaDataByteCounts = tag_ids["ImageJMetaDataByteCounts"]
|
ImageDescription = TAG_IDS["ImageDescription"]
|
||||||
ImageDescription = tag_ids["ImageDescription"]
|
|
||||||
|
info[ImageJMetaDataByteCounts] = len(bindata)
|
||||||
info[ImageJMetaDataByteCounts] = len(bindata)
|
info[ImageJMetaData] = bindata
|
||||||
info[ImageJMetaData] = bindata
|
info[TAG_IDS["RollAngle"]] = floatdata
|
||||||
info[tag_ids["RollAngle"]] = floatdata
|
info.tagtype[TAG_IDS["RollAngle"]] = 11
|
||||||
info.tagtype[tag_ids["RollAngle"]] = 11
|
info[TAG_IDS["YawAngle"]] = doubledata
|
||||||
info[tag_ids["YawAngle"]] = doubledata
|
info.tagtype[TAG_IDS["YawAngle"]] = 12
|
||||||
info.tagtype[tag_ids["YawAngle"]] = 12
|
|
||||||
|
info[ImageDescription] = textdata
|
||||||
info[ImageDescription] = textdata
|
|
||||||
|
f = str(tmp_path / "temp.tif")
|
||||||
f = self.tempfile("temp.tif")
|
|
||||||
|
img.save(f, tiffinfo=info)
|
||||||
img.save(f, tiffinfo=info)
|
|
||||||
|
with Image.open(f) as loaded:
|
||||||
with Image.open(f) as loaded:
|
|
||||||
|
assert loaded.tag[ImageJMetaDataByteCounts] == (len(bindata),)
|
||||||
assert loaded.tag[ImageJMetaDataByteCounts] == (len(bindata),)
|
assert loaded.tag_v2[ImageJMetaDataByteCounts] == (len(bindata),)
|
||||||
assert loaded.tag_v2[ImageJMetaDataByteCounts] == (len(bindata),)
|
|
||||||
|
assert loaded.tag[ImageJMetaData] == bindata
|
||||||
assert loaded.tag[ImageJMetaData] == bindata
|
assert loaded.tag_v2[ImageJMetaData] == bindata
|
||||||
assert loaded.tag_v2[ImageJMetaData] == bindata
|
|
||||||
|
assert loaded.tag[ImageDescription] == (reloaded_textdata,)
|
||||||
assert loaded.tag[ImageDescription] == (reloaded_textdata,)
|
assert loaded.tag_v2[ImageDescription] == reloaded_textdata
|
||||||
assert loaded.tag_v2[ImageDescription] == reloaded_textdata
|
|
||||||
|
loaded_float = loaded.tag[TAG_IDS["RollAngle"]][0]
|
||||||
loaded_float = loaded.tag[tag_ids["RollAngle"]][0]
|
assert round(abs(loaded_float - floatdata), 5) == 0
|
||||||
assert round(abs(loaded_float - floatdata), 5) == 0
|
loaded_double = loaded.tag[TAG_IDS["YawAngle"]][0]
|
||||||
loaded_double = loaded.tag[tag_ids["YawAngle"]][0]
|
assert round(abs(loaded_double - doubledata), 7) == 0
|
||||||
assert round(abs(loaded_double - doubledata), 7) == 0
|
|
||||||
|
# check with 2 element ImageJMetaDataByteCounts, issue #2006
|
||||||
# check with 2 element ImageJMetaDataByteCounts, issue #2006
|
|
||||||
|
info[ImageJMetaDataByteCounts] = (8, len(bindata) - 8)
|
||||||
info[ImageJMetaDataByteCounts] = (8, len(bindata) - 8)
|
img.save(f, tiffinfo=info)
|
||||||
img.save(f, tiffinfo=info)
|
with Image.open(f) as loaded:
|
||||||
with Image.open(f) as loaded:
|
|
||||||
|
assert loaded.tag[ImageJMetaDataByteCounts] == (8, len(bindata) - 8)
|
||||||
assert loaded.tag[ImageJMetaDataByteCounts] == (8, len(bindata) - 8)
|
assert loaded.tag_v2[ImageJMetaDataByteCounts] == (8, len(bindata) - 8)
|
||||||
assert loaded.tag_v2[ImageJMetaDataByteCounts] == (8, len(bindata) - 8)
|
|
||||||
|
|
||||||
def test_read_metadata(self):
|
def test_read_metadata():
|
||||||
with Image.open("Tests/images/hopper_g4.tif") as img:
|
with Image.open("Tests/images/hopper_g4.tif") as img:
|
||||||
|
|
||||||
assert {
|
assert {
|
||||||
"YResolution": IFDRational(4294967295, 113653537),
|
"YResolution": IFDRational(4294967295, 113653537),
|
||||||
"PlanarConfiguration": 1,
|
"PlanarConfiguration": 1,
|
||||||
"BitsPerSample": (1,),
|
"BitsPerSample": (1,),
|
||||||
"ImageLength": 128,
|
"ImageLength": 128,
|
||||||
"Compression": 4,
|
"Compression": 4,
|
||||||
"FillOrder": 1,
|
"FillOrder": 1,
|
||||||
"RowsPerStrip": 128,
|
"RowsPerStrip": 128,
|
||||||
"ResolutionUnit": 3,
|
"ResolutionUnit": 3,
|
||||||
"PhotometricInterpretation": 0,
|
"PhotometricInterpretation": 0,
|
||||||
"PageNumber": (0, 1),
|
"PageNumber": (0, 1),
|
||||||
"XResolution": IFDRational(4294967295, 113653537),
|
"XResolution": IFDRational(4294967295, 113653537),
|
||||||
"ImageWidth": 128,
|
"ImageWidth": 128,
|
||||||
"Orientation": 1,
|
"Orientation": 1,
|
||||||
"StripByteCounts": (1968,),
|
"StripByteCounts": (1968,),
|
||||||
"SamplesPerPixel": 1,
|
"SamplesPerPixel": 1,
|
||||||
"StripOffsets": (8,),
|
"StripOffsets": (8,),
|
||||||
} == img.tag_v2.named()
|
} == img.tag_v2.named()
|
||||||
|
|
||||||
assert {
|
assert {
|
||||||
"YResolution": ((4294967295, 113653537),),
|
"YResolution": ((4294967295, 113653537),),
|
||||||
"PlanarConfiguration": (1,),
|
"PlanarConfiguration": (1,),
|
||||||
"BitsPerSample": (1,),
|
"BitsPerSample": (1,),
|
||||||
"ImageLength": (128,),
|
"ImageLength": (128,),
|
||||||
"Compression": (4,),
|
"Compression": (4,),
|
||||||
"FillOrder": (1,),
|
"FillOrder": (1,),
|
||||||
"RowsPerStrip": (128,),
|
"RowsPerStrip": (128,),
|
||||||
"ResolutionUnit": (3,),
|
"ResolutionUnit": (3,),
|
||||||
"PhotometricInterpretation": (0,),
|
"PhotometricInterpretation": (0,),
|
||||||
"PageNumber": (0, 1),
|
"PageNumber": (0, 1),
|
||||||
"XResolution": ((4294967295, 113653537),),
|
"XResolution": ((4294967295, 113653537),),
|
||||||
"ImageWidth": (128,),
|
"ImageWidth": (128,),
|
||||||
"Orientation": (1,),
|
"Orientation": (1,),
|
||||||
"StripByteCounts": (1968,),
|
"StripByteCounts": (1968,),
|
||||||
"SamplesPerPixel": (1,),
|
"SamplesPerPixel": (1,),
|
||||||
"StripOffsets": (8,),
|
"StripOffsets": (8,),
|
||||||
} == img.tag.named()
|
} == img.tag.named()
|
||||||
|
|
||||||
def test_write_metadata(self):
|
|
||||||
""" Test metadata writing through the python code """
|
def test_write_metadata(tmp_path):
|
||||||
with Image.open("Tests/images/hopper.tif") as img:
|
""" Test metadata writing through the python code """
|
||||||
f = self.tempfile("temp.tiff")
|
with Image.open("Tests/images/hopper.tif") as img:
|
||||||
img.save(f, tiffinfo=img.tag)
|
f = str(tmp_path / "temp.tiff")
|
||||||
|
img.save(f, tiffinfo=img.tag)
|
||||||
original = img.tag_v2.named()
|
|
||||||
|
original = img.tag_v2.named()
|
||||||
with Image.open(f) as loaded:
|
|
||||||
reloaded = loaded.tag_v2.named()
|
with Image.open(f) as loaded:
|
||||||
|
reloaded = loaded.tag_v2.named()
|
||||||
ignored = ["StripByteCounts", "RowsPerStrip", "PageNumber", "StripOffsets"]
|
|
||||||
|
ignored = ["StripByteCounts", "RowsPerStrip", "PageNumber", "StripOffsets"]
|
||||||
for tag, value in reloaded.items():
|
|
||||||
if tag in ignored:
|
|
||||||
continue
|
|
||||||
if isinstance(original[tag], tuple) and isinstance(
|
|
||||||
original[tag][0], IFDRational
|
|
||||||
):
|
|
||||||
# Need to compare element by element in the tuple,
|
|
||||||
# not comparing tuples of object references
|
|
||||||
assert_deep_equal(
|
|
||||||
original[tag],
|
|
||||||
value,
|
|
||||||
"{} didn't roundtrip, {}, {}".format(tag, original[tag], value),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
assert original[tag] == value, "{} didn't roundtrip, {}, {}".format(
|
|
||||||
tag, original[tag], value
|
|
||||||
)
|
|
||||||
|
|
||||||
for tag, value in original.items():
|
|
||||||
if tag not in ignored:
|
|
||||||
assert value == reloaded[tag], "%s didn't roundtrip" % tag
|
|
||||||
|
|
||||||
def test_no_duplicate_50741_tag(self):
|
|
||||||
assert tag_ids["MakerNoteSafety"] == 50741
|
|
||||||
assert tag_ids["BestQualityScale"] == 50780
|
|
||||||
|
|
||||||
def test_empty_metadata(self):
|
|
||||||
f = io.BytesIO(b"II*\x00\x08\x00\x00\x00")
|
|
||||||
head = f.read(8)
|
|
||||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
|
||||||
# Should not raise struct.error.
|
|
||||||
pytest.warns(UserWarning, info.load, f)
|
|
||||||
|
|
||||||
def test_iccprofile(self):
|
|
||||||
# https://github.com/python-pillow/Pillow/issues/1462
|
|
||||||
out = self.tempfile("temp.tiff")
|
|
||||||
with Image.open("Tests/images/hopper.iccprofile.tif") as im:
|
|
||||||
im.save(out)
|
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
|
||||||
assert not isinstance(im.info["icc_profile"], tuple)
|
|
||||||
assert im.info["icc_profile"] == reloaded.info["icc_profile"]
|
|
||||||
|
|
||||||
def test_iccprofile_binary(self):
|
|
||||||
# https://github.com/python-pillow/Pillow/issues/1526
|
|
||||||
# We should be able to load this,
|
|
||||||
# but probably won't be able to save it.
|
|
||||||
|
|
||||||
with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im:
|
|
||||||
assert im.tag_v2.tagtype[34675] == 1
|
|
||||||
assert im.info["icc_profile"]
|
|
||||||
|
|
||||||
def test_iccprofile_save_png(self):
|
|
||||||
with Image.open("Tests/images/hopper.iccprofile.tif") as im:
|
|
||||||
outfile = self.tempfile("temp.png")
|
|
||||||
im.save(outfile)
|
|
||||||
|
|
||||||
def test_iccprofile_binary_save_png(self):
|
|
||||||
with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im:
|
|
||||||
outfile = self.tempfile("temp.png")
|
|
||||||
im.save(outfile)
|
|
||||||
|
|
||||||
def test_exif_div_zero(self):
|
|
||||||
im = hopper()
|
|
||||||
info = TiffImagePlugin.ImageFileDirectory_v2()
|
|
||||||
info[41988] = TiffImagePlugin.IFDRational(0, 0)
|
|
||||||
|
|
||||||
out = self.tempfile("temp.tiff")
|
|
||||||
im.save(out, tiffinfo=info, compression="raw")
|
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
|
||||||
assert 0 == reloaded.tag_v2[41988].numerator
|
|
||||||
assert 0 == reloaded.tag_v2[41988].denominator
|
|
||||||
|
|
||||||
def test_ifd_unsigned_rational(self):
|
|
||||||
im = hopper()
|
|
||||||
info = TiffImagePlugin.ImageFileDirectory_v2()
|
|
||||||
|
|
||||||
max_long = 2 ** 32 - 1
|
|
||||||
|
|
||||||
# 4 bytes unsigned long
|
|
||||||
numerator = max_long
|
|
||||||
|
|
||||||
info[41493] = TiffImagePlugin.IFDRational(numerator, 1)
|
|
||||||
|
|
||||||
out = self.tempfile("temp.tiff")
|
|
||||||
im.save(out, tiffinfo=info, compression="raw")
|
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
|
||||||
assert max_long == reloaded.tag_v2[41493].numerator
|
|
||||||
assert 1 == reloaded.tag_v2[41493].denominator
|
|
||||||
|
|
||||||
# out of bounds of 4 byte unsigned long
|
|
||||||
numerator = max_long + 1
|
|
||||||
|
|
||||||
info[41493] = TiffImagePlugin.IFDRational(numerator, 1)
|
|
||||||
|
|
||||||
out = self.tempfile("temp.tiff")
|
|
||||||
im.save(out, tiffinfo=info, compression="raw")
|
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
|
||||||
assert max_long == reloaded.tag_v2[41493].numerator
|
|
||||||
assert 1 == reloaded.tag_v2[41493].denominator
|
|
||||||
|
|
||||||
def test_ifd_signed_rational(self):
|
|
||||||
im = hopper()
|
|
||||||
info = TiffImagePlugin.ImageFileDirectory_v2()
|
|
||||||
|
|
||||||
# pair of 4 byte signed longs
|
|
||||||
numerator = 2 ** 31 - 1
|
|
||||||
denominator = -(2 ** 31)
|
|
||||||
|
|
||||||
info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
|
|
||||||
|
|
||||||
out = self.tempfile("temp.tiff")
|
for tag, value in reloaded.items():
|
||||||
im.save(out, tiffinfo=info, compression="raw")
|
if tag in ignored:
|
||||||
|
continue
|
||||||
|
if isinstance(original[tag], tuple) and isinstance(
|
||||||
|
original[tag][0], IFDRational
|
||||||
|
):
|
||||||
|
# Need to compare element by element in the tuple,
|
||||||
|
# not comparing tuples of object references
|
||||||
|
assert_deep_equal(
|
||||||
|
original[tag],
|
||||||
|
value,
|
||||||
|
"{} didn't roundtrip, {}, {}".format(tag, original[tag], value),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
assert original[tag] == value, "{} didn't roundtrip, {}, {}".format(
|
||||||
|
tag, original[tag], value
|
||||||
|
)
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
for tag, value in original.items():
|
||||||
assert numerator == reloaded.tag_v2[37380].numerator
|
if tag not in ignored:
|
||||||
assert denominator == reloaded.tag_v2[37380].denominator
|
assert value == reloaded[tag], "%s didn't roundtrip" % tag
|
||||||
|
|
||||||
numerator = -(2 ** 31)
|
|
||||||
denominator = 2 ** 31 - 1
|
|
||||||
|
|
||||||
info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
|
def test_no_duplicate_50741_tag():
|
||||||
|
assert TAG_IDS["MakerNoteSafety"] == 50741
|
||||||
|
assert TAG_IDS["BestQualityScale"] == 50780
|
||||||
|
|
||||||
out = self.tempfile("temp.tiff")
|
|
||||||
im.save(out, tiffinfo=info, compression="raw")
|
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
def test_empty_metadata():
|
||||||
assert numerator == reloaded.tag_v2[37380].numerator
|
f = io.BytesIO(b"II*\x00\x08\x00\x00\x00")
|
||||||
assert denominator == reloaded.tag_v2[37380].denominator
|
head = f.read(8)
|
||||||
|
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||||
|
# Should not raise struct.error.
|
||||||
|
pytest.warns(UserWarning, info.load, f)
|
||||||
|
|
||||||
# out of bounds of 4 byte signed long
|
|
||||||
numerator = -(2 ** 31) - 1
|
|
||||||
denominator = 1
|
|
||||||
|
|
||||||
info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
|
def test_iccprofile(tmp_path):
|
||||||
|
# https://github.com/python-pillow/Pillow/issues/1462
|
||||||
|
out = str(tmp_path / "temp.tiff")
|
||||||
|
with Image.open("Tests/images/hopper.iccprofile.tif") as im:
|
||||||
|
im.save(out)
|
||||||
|
|
||||||
out = self.tempfile("temp.tiff")
|
with Image.open(out) as reloaded:
|
||||||
im.save(out, tiffinfo=info, compression="raw")
|
assert not isinstance(im.info["icc_profile"], tuple)
|
||||||
|
assert im.info["icc_profile"] == reloaded.info["icc_profile"]
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
|
||||||
assert 2 ** 31 - 1 == reloaded.tag_v2[37380].numerator
|
|
||||||
assert -1 == reloaded.tag_v2[37380].denominator
|
|
||||||
|
|
||||||
def test_ifd_signed_long(self):
|
def test_iccprofile_binary():
|
||||||
im = hopper()
|
# https://github.com/python-pillow/Pillow/issues/1526
|
||||||
info = TiffImagePlugin.ImageFileDirectory_v2()
|
# We should be able to load this,
|
||||||
|
# but probably won't be able to save it.
|
||||||
|
|
||||||
info[37000] = -60000
|
with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im:
|
||||||
|
assert im.tag_v2.tagtype[34675] == 1
|
||||||
|
assert im.info["icc_profile"]
|
||||||
|
|
||||||
out = self.tempfile("temp.tiff")
|
|
||||||
im.save(out, tiffinfo=info, compression="raw")
|
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
def test_iccprofile_save_png(tmp_path):
|
||||||
assert reloaded.tag_v2[37000] == -60000
|
with Image.open("Tests/images/hopper.iccprofile.tif") as im:
|
||||||
|
outfile = str(tmp_path / "temp.png")
|
||||||
|
im.save(outfile)
|
||||||
|
|
||||||
def test_empty_values(self):
|
|
||||||
data = io.BytesIO(
|
|
||||||
b"II*\x00\x08\x00\x00\x00\x03\x00\x1a\x01\x05\x00\x00\x00\x00\x00"
|
|
||||||
b"\x00\x00\x00\x00\x1b\x01\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
||||||
b"\x98\x82\x02\x00\x07\x00\x00\x002\x00\x00\x00\x00\x00\x00\x00a "
|
|
||||||
b"text\x00\x00"
|
|
||||||
)
|
|
||||||
head = data.read(8)
|
|
||||||
info = TiffImagePlugin.ImageFileDirectory_v2(head)
|
|
||||||
info.load(data)
|
|
||||||
# Should not raise ValueError.
|
|
||||||
info = dict(info)
|
|
||||||
assert 33432 in info
|
|
||||||
|
|
||||||
def test_PhotoshopInfo(self):
|
def test_iccprofile_binary_save_png(tmp_path):
|
||||||
with Image.open("Tests/images/issue_2278.tif") as im:
|
with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im:
|
||||||
assert len(im.tag_v2[34377]) == 1
|
outfile = str(tmp_path / "temp.png")
|
||||||
assert isinstance(im.tag_v2[34377][0], bytes)
|
im.save(outfile)
|
||||||
out = self.tempfile("temp.tiff")
|
|
||||||
im.save(out)
|
|
||||||
with Image.open(out) as reloaded:
|
|
||||||
assert len(reloaded.tag_v2[34377]) == 1
|
|
||||||
assert isinstance(reloaded.tag_v2[34377][0], bytes)
|
|
||||||
|
|
||||||
def test_too_many_entries(self):
|
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
|
||||||
|
|
||||||
# 277: ("SamplesPerPixel", SHORT, 1),
|
def test_exif_div_zero(tmp_path):
|
||||||
ifd._tagdata[277] = struct.pack("hh", 4, 4)
|
im = hopper()
|
||||||
ifd.tagtype[277] = TiffTags.SHORT
|
info = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
|
info[41988] = TiffImagePlugin.IFDRational(0, 0)
|
||||||
|
|
||||||
# Should not raise ValueError.
|
out = str(tmp_path / "temp.tiff")
|
||||||
pytest.warns(UserWarning, lambda: ifd[277])
|
im.save(out, tiffinfo=info, compression="raw")
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert 0 == reloaded.tag_v2[41988].numerator
|
||||||
|
assert 0 == reloaded.tag_v2[41988].denominator
|
||||||
|
|
||||||
|
|
||||||
|
def test_ifd_unsigned_rational(tmp_path):
|
||||||
|
im = hopper()
|
||||||
|
info = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
|
|
||||||
|
max_long = 2 ** 32 - 1
|
||||||
|
|
||||||
|
# 4 bytes unsigned long
|
||||||
|
numerator = max_long
|
||||||
|
|
||||||
|
info[41493] = TiffImagePlugin.IFDRational(numerator, 1)
|
||||||
|
|
||||||
|
out = str(tmp_path / "temp.tiff")
|
||||||
|
im.save(out, tiffinfo=info, compression="raw")
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert max_long == reloaded.tag_v2[41493].numerator
|
||||||
|
assert 1 == reloaded.tag_v2[41493].denominator
|
||||||
|
|
||||||
|
# out of bounds of 4 byte unsigned long
|
||||||
|
numerator = max_long + 1
|
||||||
|
|
||||||
|
info[41493] = TiffImagePlugin.IFDRational(numerator, 1)
|
||||||
|
|
||||||
|
out = str(tmp_path / "temp.tiff")
|
||||||
|
im.save(out, tiffinfo=info, compression="raw")
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert max_long == reloaded.tag_v2[41493].numerator
|
||||||
|
assert 1 == reloaded.tag_v2[41493].denominator
|
||||||
|
|
||||||
|
|
||||||
|
def test_ifd_signed_rational(tmp_path):
|
||||||
|
im = hopper()
|
||||||
|
info = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
|
|
||||||
|
# pair of 4 byte signed longs
|
||||||
|
numerator = 2 ** 31 - 1
|
||||||
|
denominator = -(2 ** 31)
|
||||||
|
|
||||||
|
info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
|
||||||
|
|
||||||
|
out = str(tmp_path / "temp.tiff")
|
||||||
|
im.save(out, tiffinfo=info, compression="raw")
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert numerator == reloaded.tag_v2[37380].numerator
|
||||||
|
assert denominator == reloaded.tag_v2[37380].denominator
|
||||||
|
|
||||||
|
numerator = -(2 ** 31)
|
||||||
|
denominator = 2 ** 31 - 1
|
||||||
|
|
||||||
|
info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
|
||||||
|
|
||||||
|
out = str(tmp_path / "temp.tiff")
|
||||||
|
im.save(out, tiffinfo=info, compression="raw")
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert numerator == reloaded.tag_v2[37380].numerator
|
||||||
|
assert denominator == reloaded.tag_v2[37380].denominator
|
||||||
|
|
||||||
|
# out of bounds of 4 byte signed long
|
||||||
|
numerator = -(2 ** 31) - 1
|
||||||
|
denominator = 1
|
||||||
|
|
||||||
|
info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
|
||||||
|
|
||||||
|
out = str(tmp_path / "temp.tiff")
|
||||||
|
im.save(out, tiffinfo=info, compression="raw")
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert 2 ** 31 - 1 == reloaded.tag_v2[37380].numerator
|
||||||
|
assert -1 == reloaded.tag_v2[37380].denominator
|
||||||
|
|
||||||
|
|
||||||
|
def test_ifd_signed_long(tmp_path):
|
||||||
|
im = hopper()
|
||||||
|
info = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
|
|
||||||
|
info[37000] = -60000
|
||||||
|
|
||||||
|
out = str(tmp_path / "temp.tiff")
|
||||||
|
im.save(out, tiffinfo=info, compression="raw")
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert reloaded.tag_v2[37000] == -60000
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_values():
|
||||||
|
data = io.BytesIO(
|
||||||
|
b"II*\x00\x08\x00\x00\x00\x03\x00\x1a\x01\x05\x00\x00\x00\x00\x00"
|
||||||
|
b"\x00\x00\x00\x00\x1b\x01\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||||
|
b"\x98\x82\x02\x00\x07\x00\x00\x002\x00\x00\x00\x00\x00\x00\x00a "
|
||||||
|
b"text\x00\x00"
|
||||||
|
)
|
||||||
|
head = data.read(8)
|
||||||
|
info = TiffImagePlugin.ImageFileDirectory_v2(head)
|
||||||
|
info.load(data)
|
||||||
|
# Should not raise ValueError.
|
||||||
|
info = dict(info)
|
||||||
|
assert 33432 in info
|
||||||
|
|
||||||
|
|
||||||
|
def test_PhotoshopInfo(tmp_path):
|
||||||
|
with Image.open("Tests/images/issue_2278.tif") as im:
|
||||||
|
assert len(im.tag_v2[34377]) == 1
|
||||||
|
assert isinstance(im.tag_v2[34377][0], bytes)
|
||||||
|
out = str(tmp_path / "temp.tiff")
|
||||||
|
im.save(out)
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert len(reloaded.tag_v2[34377]) == 1
|
||||||
|
assert isinstance(reloaded.tag_v2[34377][0], bytes)
|
||||||
|
|
||||||
|
|
||||||
|
def test_too_many_entries():
|
||||||
|
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
|
|
||||||
|
# 277: ("SamplesPerPixel", SHORT, 1),
|
||||||
|
ifd._tagdata[277] = struct.pack("hh", 4, 4)
|
||||||
|
ifd.tagtype[277] = TiffTags.SHORT
|
||||||
|
|
||||||
|
# Should not raise ValueError.
|
||||||
|
pytest.warns(UserWarning, lambda: ifd[277])
|
||||||
|
|
|
@ -3,142 +3,134 @@ import itertools
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import PillowTestCase, assert_image_similar, hopper
|
from .helper import assert_image_similar, hopper
|
||||||
|
|
||||||
|
|
||||||
class TestFormatHSV(PillowTestCase):
|
def int_to_float(i):
|
||||||
def int_to_float(self, i):
|
return i / 255
|
||||||
return i / 255
|
|
||||||
|
|
||||||
def str_to_float(self, i):
|
|
||||||
return ord(i) / 255
|
|
||||||
|
|
||||||
def tuple_to_ints(self, tp):
|
def str_to_float(i):
|
||||||
x, y, z = tp
|
return ord(i) / 255
|
||||||
return int(x * 255.0), int(y * 255.0), int(z * 255.0)
|
|
||||||
|
|
||||||
def test_sanity(self):
|
|
||||||
Image.new("HSV", (100, 100))
|
|
||||||
|
|
||||||
def wedge(self):
|
def tuple_to_ints(tp):
|
||||||
w = Image._wedge()
|
x, y, z = tp
|
||||||
w90 = w.rotate(90)
|
return int(x * 255.0), int(y * 255.0), int(z * 255.0)
|
||||||
|
|
||||||
(px, h) = w.size
|
|
||||||
|
|
||||||
r = Image.new("L", (px * 3, h))
|
def test_sanity():
|
||||||
g = r.copy()
|
Image.new("HSV", (100, 100))
|
||||||
b = r.copy()
|
|
||||||
|
|
||||||
r.paste(w, (0, 0))
|
|
||||||
r.paste(w90, (px, 0))
|
|
||||||
|
|
||||||
g.paste(w90, (0, 0))
|
def wedge():
|
||||||
g.paste(w, (2 * px, 0))
|
w = Image._wedge()
|
||||||
|
w90 = w.rotate(90)
|
||||||
|
|
||||||
b.paste(w, (px, 0))
|
(px, h) = w.size
|
||||||
b.paste(w90, (2 * px, 0))
|
|
||||||
|
|
||||||
img = Image.merge("RGB", (r, g, b))
|
r = Image.new("L", (px * 3, h))
|
||||||
|
g = r.copy()
|
||||||
|
b = r.copy()
|
||||||
|
|
||||||
return img
|
r.paste(w, (0, 0))
|
||||||
|
r.paste(w90, (px, 0))
|
||||||
|
|
||||||
def to_xxx_colorsys(self, im, func, mode):
|
g.paste(w90, (0, 0))
|
||||||
# convert the hard way using the library colorsys routines.
|
g.paste(w, (2 * px, 0))
|
||||||
|
|
||||||
(r, g, b) = im.split()
|
b.paste(w, (px, 0))
|
||||||
|
b.paste(w90, (2 * px, 0))
|
||||||
|
|
||||||
conv_func = self.int_to_float
|
img = Image.merge("RGB", (r, g, b))
|
||||||
|
|
||||||
converted = [
|
return img
|
||||||
self.tuple_to_ints(func(conv_func(_r), conv_func(_g), conv_func(_b)))
|
|
||||||
for (_r, _g, _b) in itertools.zip_longest(
|
|
||||||
r.tobytes(), g.tobytes(), b.tobytes()
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
new_bytes = b"".join(
|
|
||||||
bytes(chr(h) + chr(s) + chr(v), "latin-1") for (h, s, v) in converted
|
|
||||||
)
|
|
||||||
|
|
||||||
hsv = Image.frombytes(mode, r.size, new_bytes)
|
def to_xxx_colorsys(im, func, mode):
|
||||||
|
# convert the hard way using the library colorsys routines.
|
||||||
|
|
||||||
return hsv
|
(r, g, b) = im.split()
|
||||||
|
|
||||||
def to_hsv_colorsys(self, im):
|
conv_func = int_to_float
|
||||||
return self.to_xxx_colorsys(im, colorsys.rgb_to_hsv, "HSV")
|
|
||||||
|
|
||||||
def to_rgb_colorsys(self, im):
|
converted = [
|
||||||
return self.to_xxx_colorsys(im, colorsys.hsv_to_rgb, "RGB")
|
tuple_to_ints(func(conv_func(_r), conv_func(_g), conv_func(_b)))
|
||||||
|
for (_r, _g, _b) in itertools.zip_longest(r.tobytes(), g.tobytes(), b.tobytes())
|
||||||
|
]
|
||||||
|
|
||||||
def test_wedge(self):
|
new_bytes = b"".join(
|
||||||
src = self.wedge().resize((3 * 32, 32), Image.BILINEAR)
|
bytes(chr(h) + chr(s) + chr(v), "latin-1") for (h, s, v) in converted
|
||||||
im = src.convert("HSV")
|
)
|
||||||
comparable = self.to_hsv_colorsys(src)
|
|
||||||
|
|
||||||
assert_image_similar(
|
hsv = Image.frombytes(mode, r.size, new_bytes)
|
||||||
im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong"
|
|
||||||
)
|
|
||||||
assert_image_similar(
|
|
||||||
im.getchannel(1),
|
|
||||||
comparable.getchannel(1),
|
|
||||||
1,
|
|
||||||
"Saturation conversion is wrong",
|
|
||||||
)
|
|
||||||
assert_image_similar(
|
|
||||||
im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong"
|
|
||||||
)
|
|
||||||
|
|
||||||
comparable = src
|
return hsv
|
||||||
im = im.convert("RGB")
|
|
||||||
|
|
||||||
assert_image_similar(
|
|
||||||
im.getchannel(0), comparable.getchannel(0), 3, "R conversion is wrong"
|
|
||||||
)
|
|
||||||
assert_image_similar(
|
|
||||||
im.getchannel(1), comparable.getchannel(1), 3, "G conversion is wrong"
|
|
||||||
)
|
|
||||||
assert_image_similar(
|
|
||||||
im.getchannel(2), comparable.getchannel(2), 3, "B conversion is wrong"
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_convert(self):
|
def to_hsv_colorsys(im):
|
||||||
im = hopper("RGB").convert("HSV")
|
return to_xxx_colorsys(im, colorsys.rgb_to_hsv, "HSV")
|
||||||
comparable = self.to_hsv_colorsys(hopper("RGB"))
|
|
||||||
|
|
||||||
assert_image_similar(
|
|
||||||
im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong"
|
|
||||||
)
|
|
||||||
assert_image_similar(
|
|
||||||
im.getchannel(1),
|
|
||||||
comparable.getchannel(1),
|
|
||||||
1,
|
|
||||||
"Saturation conversion is wrong",
|
|
||||||
)
|
|
||||||
assert_image_similar(
|
|
||||||
im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong"
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_hsv_to_rgb(self):
|
def to_rgb_colorsys(im):
|
||||||
comparable = self.to_hsv_colorsys(hopper("RGB"))
|
return to_xxx_colorsys(im, colorsys.hsv_to_rgb, "RGB")
|
||||||
converted = comparable.convert("RGB")
|
|
||||||
comparable = self.to_rgb_colorsys(comparable)
|
|
||||||
|
|
||||||
assert_image_similar(
|
|
||||||
converted.getchannel(0),
|
def test_wedge():
|
||||||
comparable.getchannel(0),
|
src = wedge().resize((3 * 32, 32), Image.BILINEAR)
|
||||||
3,
|
im = src.convert("HSV")
|
||||||
"R conversion is wrong",
|
comparable = to_hsv_colorsys(src)
|
||||||
)
|
|
||||||
assert_image_similar(
|
assert_image_similar(
|
||||||
converted.getchannel(1),
|
im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong"
|
||||||
comparable.getchannel(1),
|
)
|
||||||
3,
|
assert_image_similar(
|
||||||
"G conversion is wrong",
|
im.getchannel(1), comparable.getchannel(1), 1, "Saturation conversion is wrong",
|
||||||
)
|
)
|
||||||
assert_image_similar(
|
assert_image_similar(
|
||||||
converted.getchannel(2),
|
im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong"
|
||||||
comparable.getchannel(2),
|
)
|
||||||
3,
|
|
||||||
"B conversion is wrong",
|
comparable = src
|
||||||
)
|
im = im.convert("RGB")
|
||||||
|
|
||||||
|
assert_image_similar(
|
||||||
|
im.getchannel(0), comparable.getchannel(0), 3, "R conversion is wrong"
|
||||||
|
)
|
||||||
|
assert_image_similar(
|
||||||
|
im.getchannel(1), comparable.getchannel(1), 3, "G conversion is wrong"
|
||||||
|
)
|
||||||
|
assert_image_similar(
|
||||||
|
im.getchannel(2), comparable.getchannel(2), 3, "B conversion is wrong"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_convert():
|
||||||
|
im = hopper("RGB").convert("HSV")
|
||||||
|
comparable = to_hsv_colorsys(hopper("RGB"))
|
||||||
|
|
||||||
|
assert_image_similar(
|
||||||
|
im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong"
|
||||||
|
)
|
||||||
|
assert_image_similar(
|
||||||
|
im.getchannel(1), comparable.getchannel(1), 1, "Saturation conversion is wrong",
|
||||||
|
)
|
||||||
|
assert_image_similar(
|
||||||
|
im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_hsv_to_rgb():
|
||||||
|
comparable = to_hsv_colorsys(hopper("RGB"))
|
||||||
|
converted = comparable.convert("RGB")
|
||||||
|
comparable = to_rgb_colorsys(comparable)
|
||||||
|
|
||||||
|
assert_image_similar(
|
||||||
|
converted.getchannel(0), comparable.getchannel(0), 3, "R conversion is wrong",
|
||||||
|
)
|
||||||
|
assert_image_similar(
|
||||||
|
converted.getchannel(1), comparable.getchannel(1), 3, "G conversion is wrong",
|
||||||
|
)
|
||||||
|
assert_image_similar(
|
||||||
|
converted.getchannel(2), comparable.getchannel(2), 3, "B conversion is wrong",
|
||||||
|
)
|
||||||
|
|
|
@ -2,13 +2,12 @@ import ctypes
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
|
||||||
from distutils import ccompiler, sysconfig
|
from distutils import ccompiler, sysconfig
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import PillowTestCase, assert_image_equal, hopper, is_win32, on_ci
|
from .helper import assert_image_equal, hopper, is_win32, on_ci
|
||||||
|
|
||||||
# CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2
|
# CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2
|
||||||
# https://github.com/eliben/pycparser/pull/198#issuecomment-317001670
|
# https://github.com/eliben/pycparser/pull/198#issuecomment-317001670
|
||||||
|
@ -22,17 +21,17 @@ else:
|
||||||
cffi = None
|
cffi = None
|
||||||
|
|
||||||
|
|
||||||
class AccessTest(PillowTestCase):
|
class AccessTest:
|
||||||
# initial value
|
# initial value
|
||||||
_init_cffi_access = Image.USE_CFFI_ACCESS
|
_init_cffi_access = Image.USE_CFFI_ACCESS
|
||||||
_need_cffi_access = False
|
_need_cffi_access = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setup_class(cls):
|
||||||
Image.USE_CFFI_ACCESS = cls._need_cffi_access
|
Image.USE_CFFI_ACCESS = cls._need_cffi_access
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(cls):
|
def teardown_class(cls):
|
||||||
Image.USE_CFFI_ACCESS = cls._init_cffi_access
|
Image.USE_CFFI_ACCESS = cls._init_cffi_access
|
||||||
|
|
||||||
|
|
||||||
|
@ -200,17 +199,17 @@ class TestImageGetPixel(AccessTest):
|
||||||
assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0)
|
assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(cffi is None, "No cffi")
|
@pytest.mark.skipif(cffi is None, reason="No CFFI")
|
||||||
class TestCffiPutPixel(TestImagePutPixel):
|
class TestCffiPutPixel(TestImagePutPixel):
|
||||||
_need_cffi_access = True
|
_need_cffi_access = True
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(cffi is None, "No cffi")
|
@pytest.mark.skipif(cffi is None, reason="No CFFI")
|
||||||
class TestCffiGetPixel(TestImageGetPixel):
|
class TestCffiGetPixel(TestImageGetPixel):
|
||||||
_need_cffi_access = True
|
_need_cffi_access = True
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(cffi is None, "No cffi")
|
@pytest.mark.skipif(cffi is None, reason="No CFFI")
|
||||||
class TestCffi(AccessTest):
|
class TestCffi(AccessTest):
|
||||||
_need_cffi_access = True
|
_need_cffi_access = True
|
||||||
|
|
||||||
|
@ -326,10 +325,11 @@ class TestCffi(AccessTest):
|
||||||
assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0)
|
assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
class TestEmbeddable(unittest.TestCase):
|
class TestEmbeddable:
|
||||||
@unittest.skipIf(
|
@pytest.mark.skipif(
|
||||||
not is_win32() or on_ci(),
|
not is_win32() or on_ci(),
|
||||||
"Failing on AppVeyor / GitHub Actions when run from subprocess, not from shell",
|
reason="Failing on AppVeyor / GitHub Actions when run from subprocess, "
|
||||||
|
"not from shell",
|
||||||
)
|
)
|
||||||
def test_embeddable(self):
|
def test_embeddable(self):
|
||||||
with open("embed_pil.c", "w") as fh:
|
with open("embed_pil.c", "w") as fh:
|
||||||
|
|
|
@ -1,250 +1,261 @@
|
||||||
import pytest
|
import pytest
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import (
|
from .helper import assert_image, assert_image_equal, assert_image_similar, hopper
|
||||||
PillowTestCase,
|
|
||||||
assert_image,
|
|
||||||
assert_image_equal,
|
|
||||||
assert_image_similar,
|
|
||||||
hopper,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestImageConvert(PillowTestCase):
|
def test_sanity():
|
||||||
def test_sanity(self):
|
def convert(im, mode):
|
||||||
def convert(im, mode):
|
out = im.convert(mode)
|
||||||
out = im.convert(mode)
|
assert out.mode == mode
|
||||||
assert out.mode == mode
|
assert out.size == im.size
|
||||||
assert out.size == im.size
|
|
||||||
|
|
||||||
modes = (
|
modes = (
|
||||||
"1",
|
"1",
|
||||||
"L",
|
"L",
|
||||||
"LA",
|
"LA",
|
||||||
"P",
|
"P",
|
||||||
"PA",
|
"PA",
|
||||||
"I",
|
"I",
|
||||||
"F",
|
"F",
|
||||||
"RGB",
|
"RGB",
|
||||||
"RGBA",
|
"RGBA",
|
||||||
"RGBX",
|
"RGBX",
|
||||||
"CMYK",
|
"CMYK",
|
||||||
"YCbCr",
|
"YCbCr",
|
||||||
"HSV",
|
"HSV",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for mode in modes:
|
||||||
|
im = hopper(mode)
|
||||||
for mode in modes:
|
for mode in modes:
|
||||||
im = hopper(mode)
|
convert(im, mode)
|
||||||
for mode in modes:
|
|
||||||
convert(im, mode)
|
|
||||||
|
|
||||||
# Check 0
|
# Check 0
|
||||||
im = Image.new(mode, (0, 0))
|
im = Image.new(mode, (0, 0))
|
||||||
for mode in modes:
|
for mode in modes:
|
||||||
convert(im, mode)
|
convert(im, mode)
|
||||||
|
|
||||||
def test_default(self):
|
|
||||||
|
|
||||||
im = hopper("P")
|
def test_default():
|
||||||
assert_image(im, "P", im.size)
|
|
||||||
im = im.convert()
|
|
||||||
assert_image(im, "RGB", im.size)
|
|
||||||
im = im.convert()
|
|
||||||
assert_image(im, "RGB", im.size)
|
|
||||||
|
|
||||||
# ref https://github.com/python-pillow/Pillow/issues/274
|
im = hopper("P")
|
||||||
|
assert_image(im, "P", im.size)
|
||||||
|
im = im.convert()
|
||||||
|
assert_image(im, "RGB", im.size)
|
||||||
|
im = im.convert()
|
||||||
|
assert_image(im, "RGB", im.size)
|
||||||
|
|
||||||
def _test_float_conversion(self, im):
|
|
||||||
orig = im.getpixel((5, 5))
|
|
||||||
converted = im.convert("F").getpixel((5, 5))
|
|
||||||
assert orig == converted
|
|
||||||
|
|
||||||
def test_8bit(self):
|
# ref https://github.com/python-pillow/Pillow/issues/274
|
||||||
with Image.open("Tests/images/hopper.jpg") as im:
|
|
||||||
self._test_float_conversion(im.convert("L"))
|
|
||||||
|
|
||||||
def test_16bit(self):
|
|
||||||
with Image.open("Tests/images/16bit.cropped.tif") as im:
|
|
||||||
self._test_float_conversion(im)
|
|
||||||
|
|
||||||
def test_16bit_workaround(self):
|
def _test_float_conversion(im):
|
||||||
with Image.open("Tests/images/16bit.cropped.tif") as im:
|
orig = im.getpixel((5, 5))
|
||||||
self._test_float_conversion(im.convert("I"))
|
converted = im.convert("F").getpixel((5, 5))
|
||||||
|
assert orig == converted
|
||||||
|
|
||||||
def test_rgba_p(self):
|
|
||||||
im = hopper("RGBA")
|
|
||||||
im.putalpha(hopper("L"))
|
|
||||||
|
|
||||||
converted = im.convert("P")
|
def test_8bit():
|
||||||
comparable = converted.convert("RGBA")
|
with Image.open("Tests/images/hopper.jpg") as im:
|
||||||
|
_test_float_conversion(im.convert("L"))
|
||||||
|
|
||||||
assert_image_similar(im, comparable, 20)
|
|
||||||
|
|
||||||
def test_trns_p(self):
|
def test_16bit():
|
||||||
im = hopper("P")
|
with Image.open("Tests/images/16bit.cropped.tif") as im:
|
||||||
im.info["transparency"] = 0
|
_test_float_conversion(im)
|
||||||
|
|
||||||
f = self.tempfile("temp.png")
|
|
||||||
|
|
||||||
im_l = im.convert("L")
|
def test_16bit_workaround():
|
||||||
assert im_l.info["transparency"] == 0 # undone
|
with Image.open("Tests/images/16bit.cropped.tif") as im:
|
||||||
im_l.save(f)
|
_test_float_conversion(im.convert("I"))
|
||||||
|
|
||||||
im_rgb = im.convert("RGB")
|
|
||||||
assert im_rgb.info["transparency"] == (0, 0, 0) # undone
|
|
||||||
im_rgb.save(f)
|
|
||||||
|
|
||||||
# ref https://github.com/python-pillow/Pillow/issues/664
|
def test_rgba_p():
|
||||||
|
im = hopper("RGBA")
|
||||||
|
im.putalpha(hopper("L"))
|
||||||
|
|
||||||
def test_trns_p_rgba(self):
|
converted = im.convert("P")
|
||||||
# Arrange
|
comparable = converted.convert("RGBA")
|
||||||
im = hopper("P")
|
|
||||||
im.info["transparency"] = 128
|
|
||||||
|
|
||||||
# Act
|
assert_image_similar(im, comparable, 20)
|
||||||
im_rgba = im.convert("RGBA")
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
assert "transparency" not in im_rgba.info
|
|
||||||
# https://github.com/python-pillow/Pillow/issues/2702
|
|
||||||
assert im_rgba.palette is None
|
|
||||||
|
|
||||||
def test_trns_l(self):
|
def test_trns_p(tmp_path):
|
||||||
im = hopper("L")
|
im = hopper("P")
|
||||||
im.info["transparency"] = 128
|
im.info["transparency"] = 0
|
||||||
|
|
||||||
f = self.tempfile("temp.png")
|
f = str(tmp_path / "temp.png")
|
||||||
|
|
||||||
im_rgb = im.convert("RGB")
|
im_l = im.convert("L")
|
||||||
assert im_rgb.info["transparency"] == (128, 128, 128) # undone
|
assert im_l.info["transparency"] == 0 # undone
|
||||||
im_rgb.save(f)
|
im_l.save(f)
|
||||||
|
|
||||||
|
im_rgb = im.convert("RGB")
|
||||||
|
assert im_rgb.info["transparency"] == (0, 0, 0) # undone
|
||||||
|
im_rgb.save(f)
|
||||||
|
|
||||||
|
|
||||||
|
# ref https://github.com/python-pillow/Pillow/issues/664
|
||||||
|
|
||||||
|
|
||||||
|
def test_trns_p_rgba():
|
||||||
|
# Arrange
|
||||||
|
im = hopper("P")
|
||||||
|
im.info["transparency"] = 128
|
||||||
|
|
||||||
|
# Act
|
||||||
|
im_rgba = im.convert("RGBA")
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert "transparency" not in im_rgba.info
|
||||||
|
# https://github.com/python-pillow/Pillow/issues/2702
|
||||||
|
assert im_rgba.palette is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_trns_l(tmp_path):
|
||||||
|
im = hopper("L")
|
||||||
|
im.info["transparency"] = 128
|
||||||
|
|
||||||
|
f = str(tmp_path / "temp.png")
|
||||||
|
|
||||||
|
im_rgb = im.convert("RGB")
|
||||||
|
assert im_rgb.info["transparency"] == (128, 128, 128) # undone
|
||||||
|
im_rgb.save(f)
|
||||||
|
|
||||||
|
im_p = im.convert("P")
|
||||||
|
assert "transparency" in im_p.info
|
||||||
|
im_p.save(f)
|
||||||
|
|
||||||
|
im_p = pytest.warns(UserWarning, im.convert, "P", palette=Image.ADAPTIVE)
|
||||||
|
assert "transparency" not in im_p.info
|
||||||
|
im_p.save(f)
|
||||||
|
|
||||||
|
|
||||||
|
def test_trns_RGB(tmp_path):
|
||||||
|
im = hopper("RGB")
|
||||||
|
im.info["transparency"] = im.getpixel((0, 0))
|
||||||
|
|
||||||
|
f = str(tmp_path / "temp.png")
|
||||||
|
|
||||||
|
im_l = im.convert("L")
|
||||||
|
assert im_l.info["transparency"] == im_l.getpixel((0, 0)) # undone
|
||||||
|
im_l.save(f)
|
||||||
|
|
||||||
|
im_p = im.convert("P")
|
||||||
|
assert "transparency" in im_p.info
|
||||||
|
im_p.save(f)
|
||||||
|
|
||||||
|
im_rgba = im.convert("RGBA")
|
||||||
|
assert "transparency" not in im_rgba.info
|
||||||
|
im_rgba.save(f)
|
||||||
|
|
||||||
|
im_p = pytest.warns(UserWarning, im.convert, "P", palette=Image.ADAPTIVE)
|
||||||
|
assert "transparency" not in im_p.info
|
||||||
|
im_p.save(f)
|
||||||
|
|
||||||
|
|
||||||
|
def test_gif_with_rgba_palette_to_p():
|
||||||
|
# See https://github.com/python-pillow/Pillow/issues/2433
|
||||||
|
with Image.open("Tests/images/hopper.gif") as im:
|
||||||
|
im.info["transparency"] = 255
|
||||||
|
im.load()
|
||||||
|
assert im.palette.mode == "RGBA"
|
||||||
im_p = im.convert("P")
|
im_p = im.convert("P")
|
||||||
assert "transparency" in im_p.info
|
|
||||||
im_p.save(f)
|
|
||||||
|
|
||||||
im_p = pytest.warns(UserWarning, im.convert, "P", palette=Image.ADAPTIVE)
|
# Should not raise ValueError: unrecognized raw mode
|
||||||
assert "transparency" not in im_p.info
|
im_p.load()
|
||||||
im_p.save(f)
|
|
||||||
|
|
||||||
def test_trns_RGB(self):
|
|
||||||
|
def test_p_la():
|
||||||
|
im = hopper("RGBA")
|
||||||
|
alpha = hopper("L")
|
||||||
|
im.putalpha(alpha)
|
||||||
|
|
||||||
|
comparable = im.convert("P").convert("LA").getchannel("A")
|
||||||
|
|
||||||
|
assert_image_similar(alpha, comparable, 5)
|
||||||
|
|
||||||
|
|
||||||
|
def test_matrix_illegal_conversion():
|
||||||
|
# Arrange
|
||||||
|
im = hopper("CMYK")
|
||||||
|
# fmt: off
|
||||||
|
matrix = (
|
||||||
|
0.412453, 0.357580, 0.180423, 0,
|
||||||
|
0.212671, 0.715160, 0.072169, 0,
|
||||||
|
0.019334, 0.119193, 0.950227, 0)
|
||||||
|
# fmt: on
|
||||||
|
assert im.mode != "RGB"
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
im.convert(mode="CMYK", matrix=matrix)
|
||||||
|
|
||||||
|
|
||||||
|
def test_matrix_wrong_mode():
|
||||||
|
# Arrange
|
||||||
|
im = hopper("L")
|
||||||
|
# fmt: off
|
||||||
|
matrix = (
|
||||||
|
0.412453, 0.357580, 0.180423, 0,
|
||||||
|
0.212671, 0.715160, 0.072169, 0,
|
||||||
|
0.019334, 0.119193, 0.950227, 0)
|
||||||
|
# fmt: on
|
||||||
|
assert im.mode == "L"
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
im.convert(mode="L", matrix=matrix)
|
||||||
|
|
||||||
|
|
||||||
|
def test_matrix_xyz():
|
||||||
|
def matrix_convert(mode):
|
||||||
|
# Arrange
|
||||||
im = hopper("RGB")
|
im = hopper("RGB")
|
||||||
im.info["transparency"] = im.getpixel((0, 0))
|
im.info["transparency"] = (255, 0, 0)
|
||||||
|
|
||||||
f = self.tempfile("temp.png")
|
|
||||||
|
|
||||||
im_l = im.convert("L")
|
|
||||||
assert im_l.info["transparency"] == im_l.getpixel((0, 0)) # undone
|
|
||||||
im_l.save(f)
|
|
||||||
|
|
||||||
im_p = im.convert("P")
|
|
||||||
assert "transparency" in im_p.info
|
|
||||||
im_p.save(f)
|
|
||||||
|
|
||||||
im_rgba = im.convert("RGBA")
|
|
||||||
assert "transparency" not in im_rgba.info
|
|
||||||
im_rgba.save(f)
|
|
||||||
|
|
||||||
im_p = pytest.warns(UserWarning, im.convert, "P", palette=Image.ADAPTIVE)
|
|
||||||
assert "transparency" not in im_p.info
|
|
||||||
im_p.save(f)
|
|
||||||
|
|
||||||
def test_gif_with_rgba_palette_to_p(self):
|
|
||||||
# See https://github.com/python-pillow/Pillow/issues/2433
|
|
||||||
with Image.open("Tests/images/hopper.gif") as im:
|
|
||||||
im.info["transparency"] = 255
|
|
||||||
im.load()
|
|
||||||
assert im.palette.mode == "RGBA"
|
|
||||||
im_p = im.convert("P")
|
|
||||||
|
|
||||||
# Should not raise ValueError: unrecognized raw mode
|
|
||||||
im_p.load()
|
|
||||||
|
|
||||||
def test_p_la(self):
|
|
||||||
im = hopper("RGBA")
|
|
||||||
alpha = hopper("L")
|
|
||||||
im.putalpha(alpha)
|
|
||||||
|
|
||||||
comparable = im.convert("P").convert("LA").getchannel("A")
|
|
||||||
|
|
||||||
assert_image_similar(alpha, comparable, 5)
|
|
||||||
|
|
||||||
def test_matrix_illegal_conversion(self):
|
|
||||||
# Arrange
|
|
||||||
im = hopper("CMYK")
|
|
||||||
# fmt: off
|
# fmt: off
|
||||||
matrix = (
|
matrix = (
|
||||||
0.412453, 0.357580, 0.180423, 0,
|
0.412453, 0.357580, 0.180423, 0,
|
||||||
0.212671, 0.715160, 0.072169, 0,
|
0.212671, 0.715160, 0.072169, 0,
|
||||||
0.019334, 0.119193, 0.950227, 0)
|
0.019334, 0.119193, 0.950227, 0)
|
||||||
# fmt: on
|
# fmt: on
|
||||||
assert im.mode != "RGB"
|
|
||||||
|
|
||||||
# Act / Assert
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
im.convert(mode="CMYK", matrix=matrix)
|
|
||||||
|
|
||||||
def test_matrix_wrong_mode(self):
|
|
||||||
# Arrange
|
|
||||||
im = hopper("L")
|
|
||||||
# fmt: off
|
|
||||||
matrix = (
|
|
||||||
0.412453, 0.357580, 0.180423, 0,
|
|
||||||
0.212671, 0.715160, 0.072169, 0,
|
|
||||||
0.019334, 0.119193, 0.950227, 0)
|
|
||||||
# fmt: on
|
|
||||||
assert im.mode == "L"
|
|
||||||
|
|
||||||
# Act / Assert
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
im.convert(mode="L", matrix=matrix)
|
|
||||||
|
|
||||||
def test_matrix_xyz(self):
|
|
||||||
def matrix_convert(mode):
|
|
||||||
# Arrange
|
|
||||||
im = hopper("RGB")
|
|
||||||
im.info["transparency"] = (255, 0, 0)
|
|
||||||
# fmt: off
|
|
||||||
matrix = (
|
|
||||||
0.412453, 0.357580, 0.180423, 0,
|
|
||||||
0.212671, 0.715160, 0.072169, 0,
|
|
||||||
0.019334, 0.119193, 0.950227, 0)
|
|
||||||
# fmt: on
|
|
||||||
assert im.mode == "RGB"
|
|
||||||
|
|
||||||
# Act
|
|
||||||
# Convert an RGB image to the CIE XYZ colour space
|
|
||||||
converted_im = im.convert(mode=mode, matrix=matrix)
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
assert converted_im.mode == mode
|
|
||||||
assert converted_im.size == im.size
|
|
||||||
with Image.open("Tests/images/hopper-XYZ.png") as target:
|
|
||||||
if converted_im.mode == "RGB":
|
|
||||||
assert_image_similar(converted_im, target, 3)
|
|
||||||
assert converted_im.info["transparency"] == (105, 54, 4)
|
|
||||||
else:
|
|
||||||
assert_image_similar(converted_im, target.getchannel(0), 1)
|
|
||||||
assert converted_im.info["transparency"] == 105
|
|
||||||
|
|
||||||
matrix_convert("RGB")
|
|
||||||
matrix_convert("L")
|
|
||||||
|
|
||||||
def test_matrix_identity(self):
|
|
||||||
# Arrange
|
|
||||||
im = hopper("RGB")
|
|
||||||
# fmt: off
|
|
||||||
identity_matrix = (
|
|
||||||
1, 0, 0, 0,
|
|
||||||
0, 1, 0, 0,
|
|
||||||
0, 0, 1, 0)
|
|
||||||
# fmt: on
|
|
||||||
assert im.mode == "RGB"
|
assert im.mode == "RGB"
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
# Convert with an identity matrix
|
# Convert an RGB image to the CIE XYZ colour space
|
||||||
converted_im = im.convert(mode="RGB", matrix=identity_matrix)
|
converted_im = im.convert(mode=mode, matrix=matrix)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
# No change
|
assert converted_im.mode == mode
|
||||||
assert_image_equal(converted_im, im)
|
assert converted_im.size == im.size
|
||||||
|
with Image.open("Tests/images/hopper-XYZ.png") as target:
|
||||||
|
if converted_im.mode == "RGB":
|
||||||
|
assert_image_similar(converted_im, target, 3)
|
||||||
|
assert converted_im.info["transparency"] == (105, 54, 4)
|
||||||
|
else:
|
||||||
|
assert_image_similar(converted_im, target.getchannel(0), 1)
|
||||||
|
assert converted_im.info["transparency"] == 105
|
||||||
|
|
||||||
|
matrix_convert("RGB")
|
||||||
|
matrix_convert("L")
|
||||||
|
|
||||||
|
|
||||||
|
def test_matrix_identity():
|
||||||
|
# Arrange
|
||||||
|
im = hopper("RGB")
|
||||||
|
# fmt: off
|
||||||
|
identity_matrix = (
|
||||||
|
1, 0, 0, 0,
|
||||||
|
0, 1, 0, 0,
|
||||||
|
0, 0, 1, 0)
|
||||||
|
# fmt: on
|
||||||
|
assert im.mode == "RGB"
|
||||||
|
|
||||||
|
# Act
|
||||||
|
# Convert with an identity matrix
|
||||||
|
converted_im = im.convert(mode="RGB", matrix=identity_matrix)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
# No change
|
||||||
|
assert_image_equal(converted_im, im)
|
||||||
|
|
|
@ -10,29 +10,32 @@ def test_sanity():
|
||||||
|
|
||||||
|
|
||||||
def test_bbox():
|
def test_bbox():
|
||||||
|
def check(im, fill_color):
|
||||||
|
assert im.getbbox() is None
|
||||||
|
|
||||||
|
im.paste(fill_color, (10, 25, 90, 75))
|
||||||
|
assert im.getbbox() == (10, 25, 90, 75)
|
||||||
|
|
||||||
|
im.paste(fill_color, (25, 10, 75, 90))
|
||||||
|
assert im.getbbox() == (10, 10, 90, 90)
|
||||||
|
|
||||||
|
im.paste(fill_color, (-10, -10, 110, 110))
|
||||||
|
assert im.getbbox() == (0, 0, 100, 100)
|
||||||
|
|
||||||
# 8-bit mode
|
# 8-bit mode
|
||||||
im = Image.new("L", (100, 100), 0)
|
im = Image.new("L", (100, 100), 0)
|
||||||
assert im.getbbox() is None
|
check(im, 255)
|
||||||
|
|
||||||
im.paste(255, (10, 25, 90, 75))
|
|
||||||
assert im.getbbox() == (10, 25, 90, 75)
|
|
||||||
|
|
||||||
im.paste(255, (25, 10, 75, 90))
|
|
||||||
assert im.getbbox() == (10, 10, 90, 90)
|
|
||||||
|
|
||||||
im.paste(255, (-10, -10, 110, 110))
|
|
||||||
assert im.getbbox() == (0, 0, 100, 100)
|
|
||||||
|
|
||||||
# 32-bit mode
|
# 32-bit mode
|
||||||
im = Image.new("RGB", (100, 100), 0)
|
im = Image.new("RGB", (100, 100), 0)
|
||||||
assert im.getbbox() is None
|
check(im, 255)
|
||||||
|
|
||||||
im.paste(255, (10, 25, 90, 75))
|
for mode in ("RGBA", "RGBa"):
|
||||||
assert im.getbbox() == (10, 25, 90, 75)
|
for color in ((0, 0, 0, 0), (127, 127, 127, 0), (255, 255, 255, 0)):
|
||||||
|
im = Image.new(mode, (100, 100), color)
|
||||||
|
check(im, (255, 255, 255, 255))
|
||||||
|
|
||||||
im.paste(255, (25, 10, 75, 90))
|
for mode in ("La", "LA", "PA"):
|
||||||
assert im.getbbox() == (10, 10, 90, 90)
|
for color in ((0, 0), (127, 0), (255, 0)):
|
||||||
|
im = Image.new(mode, (100, 100), color)
|
||||||
im.paste(255, (-10, -10, 110, 110))
|
check(im, (255, 255))
|
||||||
assert im.getbbox() == (0, 0, 100, 100)
|
|
||||||
|
|
|
@ -1,62 +1,63 @@
|
||||||
from PIL import Image, features
|
from PIL import Image, features
|
||||||
|
|
||||||
from .helper import PillowTestCase, assert_image_equal, hopper
|
from .helper import assert_image_equal, hopper
|
||||||
|
|
||||||
|
|
||||||
class TestImageSplit(PillowTestCase):
|
def test_split():
|
||||||
def test_split(self):
|
def split(mode):
|
||||||
def split(mode):
|
layers = hopper(mode).split()
|
||||||
layers = hopper(mode).split()
|
return [(i.mode, i.size[0], i.size[1]) for i in layers]
|
||||||
return [(i.mode, i.size[0], i.size[1]) for i in layers]
|
|
||||||
|
|
||||||
assert split("1") == [("1", 128, 128)]
|
assert split("1") == [("1", 128, 128)]
|
||||||
assert split("L") == [("L", 128, 128)]
|
assert split("L") == [("L", 128, 128)]
|
||||||
assert split("I") == [("I", 128, 128)]
|
assert split("I") == [("I", 128, 128)]
|
||||||
assert split("F") == [("F", 128, 128)]
|
assert split("F") == [("F", 128, 128)]
|
||||||
assert split("P") == [("P", 128, 128)]
|
assert split("P") == [("P", 128, 128)]
|
||||||
assert split("RGB") == [("L", 128, 128), ("L", 128, 128), ("L", 128, 128)]
|
assert split("RGB") == [("L", 128, 128), ("L", 128, 128), ("L", 128, 128)]
|
||||||
assert split("RGBA") == [
|
assert split("RGBA") == [
|
||||||
("L", 128, 128),
|
("L", 128, 128),
|
||||||
("L", 128, 128),
|
("L", 128, 128),
|
||||||
("L", 128, 128),
|
("L", 128, 128),
|
||||||
("L", 128, 128),
|
("L", 128, 128),
|
||||||
]
|
]
|
||||||
assert split("CMYK") == [
|
assert split("CMYK") == [
|
||||||
("L", 128, 128),
|
("L", 128, 128),
|
||||||
("L", 128, 128),
|
("L", 128, 128),
|
||||||
("L", 128, 128),
|
("L", 128, 128),
|
||||||
("L", 128, 128),
|
("L", 128, 128),
|
||||||
]
|
]
|
||||||
assert split("YCbCr") == [("L", 128, 128), ("L", 128, 128), ("L", 128, 128)]
|
assert split("YCbCr") == [("L", 128, 128), ("L", 128, 128), ("L", 128, 128)]
|
||||||
|
|
||||||
def test_split_merge(self):
|
|
||||||
def split_merge(mode):
|
|
||||||
return Image.merge(mode, hopper(mode).split())
|
|
||||||
|
|
||||||
assert_image_equal(hopper("1"), split_merge("1"))
|
def test_split_merge():
|
||||||
assert_image_equal(hopper("L"), split_merge("L"))
|
def split_merge(mode):
|
||||||
assert_image_equal(hopper("I"), split_merge("I"))
|
return Image.merge(mode, hopper(mode).split())
|
||||||
assert_image_equal(hopper("F"), split_merge("F"))
|
|
||||||
assert_image_equal(hopper("P"), split_merge("P"))
|
|
||||||
assert_image_equal(hopper("RGB"), split_merge("RGB"))
|
|
||||||
assert_image_equal(hopper("RGBA"), split_merge("RGBA"))
|
|
||||||
assert_image_equal(hopper("CMYK"), split_merge("CMYK"))
|
|
||||||
assert_image_equal(hopper("YCbCr"), split_merge("YCbCr"))
|
|
||||||
|
|
||||||
def test_split_open(self):
|
assert_image_equal(hopper("1"), split_merge("1"))
|
||||||
if features.check("zlib"):
|
assert_image_equal(hopper("L"), split_merge("L"))
|
||||||
test_file = self.tempfile("temp.png")
|
assert_image_equal(hopper("I"), split_merge("I"))
|
||||||
else:
|
assert_image_equal(hopper("F"), split_merge("F"))
|
||||||
test_file = self.tempfile("temp.pcx")
|
assert_image_equal(hopper("P"), split_merge("P"))
|
||||||
|
assert_image_equal(hopper("RGB"), split_merge("RGB"))
|
||||||
|
assert_image_equal(hopper("RGBA"), split_merge("RGBA"))
|
||||||
|
assert_image_equal(hopper("CMYK"), split_merge("CMYK"))
|
||||||
|
assert_image_equal(hopper("YCbCr"), split_merge("YCbCr"))
|
||||||
|
|
||||||
def split_open(mode):
|
|
||||||
hopper(mode).save(test_file)
|
|
||||||
with Image.open(test_file) as im:
|
|
||||||
return len(im.split())
|
|
||||||
|
|
||||||
assert split_open("1") == 1
|
def test_split_open(tmp_path):
|
||||||
assert split_open("L") == 1
|
if features.check("zlib"):
|
||||||
assert split_open("P") == 1
|
test_file = str(tmp_path / "temp.png")
|
||||||
assert split_open("RGB") == 3
|
else:
|
||||||
if features.check("zlib"):
|
test_file = str(tmp_path / "temp.pcx")
|
||||||
assert split_open("RGBA") == 4
|
|
||||||
|
def split_open(mode):
|
||||||
|
hopper(mode).save(test_file)
|
||||||
|
with Image.open(test_file) as im:
|
||||||
|
return len(im.split())
|
||||||
|
|
||||||
|
assert split_open("1") == 1
|
||||||
|
assert split_open("L") == 1
|
||||||
|
assert split_open("P") == 1
|
||||||
|
assert split_open("RGB") == 3
|
||||||
|
if features.check("zlib"):
|
||||||
|
assert split_open("RGBA") == 4
|
||||||
|
|
|
@ -38,9 +38,9 @@ def test_aspect():
|
||||||
im.thumbnail((100, 50))
|
im.thumbnail((100, 50))
|
||||||
assert im.size == (100, 50)
|
assert im.size == (100, 50)
|
||||||
|
|
||||||
im = Image.new("L", (128, 128))
|
im = Image.new("L", (64, 64))
|
||||||
im.thumbnail((100, 100))
|
im.thumbnail((100, 100))
|
||||||
assert im.size == (100, 100)
|
assert im.size == (64, 64)
|
||||||
|
|
||||||
im = Image.new("L", (256, 162)) # ratio is 1.5802469136
|
im = Image.new("L", (256, 162)) # ratio is 1.5802469136
|
||||||
im.thumbnail((33, 33))
|
im.thumbnail((33, 33))
|
||||||
|
@ -50,11 +50,23 @@ def test_aspect():
|
||||||
im.thumbnail((33, 33))
|
im.thumbnail((33, 33))
|
||||||
assert im.size == (21, 33) # ratio is 0.6363636364
|
assert im.size == (21, 33) # ratio is 0.6363636364
|
||||||
|
|
||||||
|
im = Image.new("L", (145, 100)) # ratio is 1.45
|
||||||
|
im.thumbnail((50, 50))
|
||||||
|
assert im.size == (50, 34) # ratio is 1.47058823529
|
||||||
|
|
||||||
|
im = Image.new("L", (100, 145)) # ratio is 0.689655172414
|
||||||
|
im.thumbnail((50, 50))
|
||||||
|
assert im.size == (34, 50) # ratio is 0.68
|
||||||
|
|
||||||
|
im = Image.new("L", (100, 30)) # ratio is 3.333333333333
|
||||||
|
im.thumbnail((75, 75))
|
||||||
|
assert im.size == (75, 23) # ratio is 3.260869565217
|
||||||
|
|
||||||
|
|
||||||
def test_float():
|
def test_float():
|
||||||
im = Image.new("L", (128, 128))
|
im = Image.new("L", (128, 128))
|
||||||
im.thumbnail((99.9, 99.9))
|
im.thumbnail((99.9, 99.9))
|
||||||
assert im.size == (100, 100)
|
assert im.size == (99, 99)
|
||||||
|
|
||||||
|
|
||||||
def test_no_resize():
|
def test_no_resize():
|
||||||
|
|
|
@ -944,6 +944,22 @@ def test_stroke():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@skip_unless_feature("freetype2")
|
||||||
|
def test_stroke_descender():
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (120, 130))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 120)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.text((10, 0), "y", "#f00", font, stroke_width=2, stroke_fill="#0f0")
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert_image_similar(
|
||||||
|
im, Image.open("Tests/images/imagedraw_stroke_descender.png"), 6.76
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@skip_unless_feature("freetype2")
|
@skip_unless_feature("freetype2")
|
||||||
def test_stroke_multiline():
|
def test_stroke_multiline():
|
||||||
# Arrange
|
# Arrange
|
||||||
|
|
|
@ -1,140 +1,149 @@
|
||||||
import pytest
|
import pytest
|
||||||
from PIL import Image, ImagePalette
|
from PIL import Image, ImagePalette
|
||||||
|
|
||||||
from .helper import PillowTestCase, assert_image_equal
|
from .helper import assert_image_equal
|
||||||
|
|
||||||
|
|
||||||
class TestImagePalette(PillowTestCase):
|
def test_sanity():
|
||||||
def test_sanity(self):
|
|
||||||
|
|
||||||
ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
|
ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
ImagePalette.ImagePalette("RGB", list(range(256)) * 2)
|
ImagePalette.ImagePalette("RGB", list(range(256)) * 2)
|
||||||
|
|
||||||
def test_getcolor(self):
|
|
||||||
|
|
||||||
palette = ImagePalette.ImagePalette()
|
def test_getcolor():
|
||||||
|
|
||||||
test_map = {}
|
palette = ImagePalette.ImagePalette()
|
||||||
for i in range(256):
|
|
||||||
test_map[palette.getcolor((i, i, i))] = i
|
|
||||||
|
|
||||||
assert len(test_map) == 256
|
test_map = {}
|
||||||
with pytest.raises(ValueError):
|
for i in range(256):
|
||||||
palette.getcolor((1, 2, 3))
|
test_map[palette.getcolor((i, i, i))] = i
|
||||||
|
|
||||||
# Test unknown color specifier
|
assert len(test_map) == 256
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
palette.getcolor("unknown")
|
palette.getcolor((1, 2, 3))
|
||||||
|
|
||||||
def test_file(self):
|
# Test unknown color specifier
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
palette.getcolor("unknown")
|
||||||
|
|
||||||
palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
|
|
||||||
|
|
||||||
f = self.tempfile("temp.lut")
|
def test_file(tmp_path):
|
||||||
|
|
||||||
|
palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
|
||||||
|
|
||||||
|
f = str(tmp_path / "temp.lut")
|
||||||
|
|
||||||
|
palette.save(f)
|
||||||
|
|
||||||
|
p = ImagePalette.load(f)
|
||||||
|
|
||||||
|
# load returns raw palette information
|
||||||
|
assert len(p[0]) == 768
|
||||||
|
assert p[1] == "RGB"
|
||||||
|
|
||||||
|
p = ImagePalette.raw(p[1], p[0])
|
||||||
|
assert isinstance(p, ImagePalette.ImagePalette)
|
||||||
|
assert p.palette == palette.tobytes()
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_linear_lut():
|
||||||
|
# Arrange
|
||||||
|
black = 0
|
||||||
|
white = 255
|
||||||
|
|
||||||
|
# Act
|
||||||
|
lut = ImagePalette.make_linear_lut(black, white)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert isinstance(lut, list)
|
||||||
|
assert len(lut) == 256
|
||||||
|
# Check values
|
||||||
|
for i in range(0, len(lut)):
|
||||||
|
assert lut[i] == i
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_linear_lut_not_yet_implemented():
|
||||||
|
# Update after FIXME
|
||||||
|
# Arrange
|
||||||
|
black = 1
|
||||||
|
white = 255
|
||||||
|
|
||||||
|
# Act
|
||||||
|
with pytest.raises(NotImplementedError):
|
||||||
|
ImagePalette.make_linear_lut(black, white)
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_gamma_lut():
|
||||||
|
# Arrange
|
||||||
|
exp = 5
|
||||||
|
|
||||||
|
# Act
|
||||||
|
lut = ImagePalette.make_gamma_lut(exp)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert isinstance(lut, list)
|
||||||
|
assert len(lut) == 256
|
||||||
|
# Check a few values
|
||||||
|
assert lut[0] == 0
|
||||||
|
assert lut[63] == 0
|
||||||
|
assert lut[127] == 8
|
||||||
|
assert lut[191] == 60
|
||||||
|
assert lut[255] == 255
|
||||||
|
|
||||||
|
|
||||||
|
def test_rawmode_valueerrors(tmp_path):
|
||||||
|
# Arrange
|
||||||
|
palette = ImagePalette.raw("RGB", list(range(256)) * 3)
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
palette.tobytes()
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
palette.getcolor((1, 2, 3))
|
||||||
|
f = str(tmp_path / "temp.lut")
|
||||||
|
with pytest.raises(ValueError):
|
||||||
palette.save(f)
|
palette.save(f)
|
||||||
|
|
||||||
p = ImagePalette.load(f)
|
|
||||||
|
|
||||||
# load returns raw palette information
|
def test_getdata():
|
||||||
assert len(p[0]) == 768
|
# Arrange
|
||||||
assert p[1] == "RGB"
|
data_in = list(range(256)) * 3
|
||||||
|
palette = ImagePalette.ImagePalette("RGB", data_in)
|
||||||
|
|
||||||
p = ImagePalette.raw(p[1], p[0])
|
# Act
|
||||||
assert isinstance(p, ImagePalette.ImagePalette)
|
mode, data_out = palette.getdata()
|
||||||
assert p.palette == palette.tobytes()
|
|
||||||
|
|
||||||
def test_make_linear_lut(self):
|
# Assert
|
||||||
# Arrange
|
assert mode == "RGB;L"
|
||||||
black = 0
|
|
||||||
white = 255
|
|
||||||
|
|
||||||
# Act
|
|
||||||
lut = ImagePalette.make_linear_lut(black, white)
|
|
||||||
|
|
||||||
# Assert
|
def test_rawmode_getdata():
|
||||||
assert isinstance(lut, list)
|
# Arrange
|
||||||
assert len(lut) == 256
|
data_in = list(range(256)) * 3
|
||||||
# Check values
|
palette = ImagePalette.raw("RGB", data_in)
|
||||||
for i in range(0, len(lut)):
|
|
||||||
assert lut[i] == i
|
|
||||||
|
|
||||||
def test_make_linear_lut_not_yet_implemented(self):
|
# Act
|
||||||
# Update after FIXME
|
rawmode, data_out = palette.getdata()
|
||||||
# Arrange
|
|
||||||
black = 1
|
|
||||||
white = 255
|
|
||||||
|
|
||||||
# Act
|
# Assert
|
||||||
with pytest.raises(NotImplementedError):
|
assert rawmode == "RGB"
|
||||||
ImagePalette.make_linear_lut(black, white)
|
assert data_in == data_out
|
||||||
|
|
||||||
def test_make_gamma_lut(self):
|
|
||||||
# Arrange
|
|
||||||
exp = 5
|
|
||||||
|
|
||||||
# Act
|
def test_2bit_palette(tmp_path):
|
||||||
lut = ImagePalette.make_gamma_lut(exp)
|
# issue #2258, 2 bit palettes are corrupted.
|
||||||
|
outfile = str(tmp_path / "temp.png")
|
||||||
|
|
||||||
# Assert
|
rgb = b"\x00" * 2 + b"\x01" * 2 + b"\x02" * 2
|
||||||
assert isinstance(lut, list)
|
img = Image.frombytes("P", (6, 1), rgb)
|
||||||
assert len(lut) == 256
|
img.putpalette(b"\xFF\x00\x00\x00\xFF\x00\x00\x00\xFF") # RGB
|
||||||
# Check a few values
|
img.save(outfile, format="PNG")
|
||||||
assert lut[0] == 0
|
|
||||||
assert lut[63] == 0
|
|
||||||
assert lut[127] == 8
|
|
||||||
assert lut[191] == 60
|
|
||||||
assert lut[255] == 255
|
|
||||||
|
|
||||||
def test_rawmode_valueerrors(self):
|
with Image.open(outfile) as reloaded:
|
||||||
# Arrange
|
assert_image_equal(img, reloaded)
|
||||||
palette = ImagePalette.raw("RGB", list(range(256)) * 3)
|
|
||||||
|
|
||||||
# Act / Assert
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
palette.tobytes()
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
palette.getcolor((1, 2, 3))
|
|
||||||
f = self.tempfile("temp.lut")
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
palette.save(f)
|
|
||||||
|
|
||||||
def test_getdata(self):
|
def test_invalid_palette():
|
||||||
# Arrange
|
with pytest.raises(IOError):
|
||||||
data_in = list(range(256)) * 3
|
ImagePalette.load("Tests/images/hopper.jpg")
|
||||||
palette = ImagePalette.ImagePalette("RGB", data_in)
|
|
||||||
|
|
||||||
# Act
|
|
||||||
mode, data_out = palette.getdata()
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
assert mode == "RGB;L"
|
|
||||||
|
|
||||||
def test_rawmode_getdata(self):
|
|
||||||
# Arrange
|
|
||||||
data_in = list(range(256)) * 3
|
|
||||||
palette = ImagePalette.raw("RGB", data_in)
|
|
||||||
|
|
||||||
# Act
|
|
||||||
rawmode, data_out = palette.getdata()
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
assert rawmode == "RGB"
|
|
||||||
assert data_in == data_out
|
|
||||||
|
|
||||||
def test_2bit_palette(self):
|
|
||||||
# issue #2258, 2 bit palettes are corrupted.
|
|
||||||
outfile = self.tempfile("temp.png")
|
|
||||||
|
|
||||||
rgb = b"\x00" * 2 + b"\x01" * 2 + b"\x02" * 2
|
|
||||||
img = Image.frombytes("P", (6, 1), rgb)
|
|
||||||
img.putpalette(b"\xFF\x00\x00\x00\xFF\x00\x00\x00\xFF") # RGB
|
|
||||||
img.save(outfile, format="PNG")
|
|
||||||
|
|
||||||
with Image.open(outfile) as reloaded:
|
|
||||||
assert_image_equal(img, reloaded)
|
|
||||||
|
|
||||||
def test_invalid_palette(self):
|
|
||||||
with pytest.raises(IOError):
|
|
||||||
ImagePalette.load("Tests/images/hopper.jpg")
|
|
||||||
|
|
|
@ -1,98 +1,105 @@
|
||||||
import pytest
|
import pytest
|
||||||
from PIL import Image, ImageSequence, TiffImagePlugin
|
from PIL import Image, ImageSequence, TiffImagePlugin
|
||||||
|
|
||||||
from .helper import PillowTestCase, assert_image_equal, hopper, skip_unless_feature
|
from .helper import assert_image_equal, hopper, skip_unless_feature
|
||||||
|
|
||||||
|
|
||||||
class TestImageSequence(PillowTestCase):
|
def test_sanity(tmp_path):
|
||||||
def test_sanity(self):
|
|
||||||
|
|
||||||
test_file = self.tempfile("temp.im")
|
test_file = str(tmp_path / "temp.im")
|
||||||
|
|
||||||
im = hopper("RGB")
|
im = hopper("RGB")
|
||||||
im.save(test_file)
|
im.save(test_file)
|
||||||
|
|
||||||
seq = ImageSequence.Iterator(im)
|
seq = ImageSequence.Iterator(im)
|
||||||
|
|
||||||
index = 0
|
index = 0
|
||||||
for frame in seq:
|
for frame in seq:
|
||||||
assert_image_equal(im, frame)
|
assert_image_equal(im, frame)
|
||||||
assert im.tell() == index
|
assert im.tell() == index
|
||||||
index += 1
|
index += 1
|
||||||
|
|
||||||
assert index == 1
|
assert index == 1
|
||||||
|
|
||||||
with pytest.raises(AttributeError):
|
with pytest.raises(AttributeError):
|
||||||
ImageSequence.Iterator(0)
|
ImageSequence.Iterator(0)
|
||||||
|
|
||||||
def test_iterator(self):
|
|
||||||
with Image.open("Tests/images/multipage.tiff") as im:
|
|
||||||
i = ImageSequence.Iterator(im)
|
|
||||||
for index in range(0, im.n_frames):
|
|
||||||
assert i[index] == next(i)
|
|
||||||
with pytest.raises(IndexError):
|
|
||||||
i[index + 1]
|
|
||||||
with pytest.raises(StopIteration):
|
|
||||||
next(i)
|
|
||||||
|
|
||||||
def test_iterator_min_frame(self):
|
def test_iterator():
|
||||||
with Image.open("Tests/images/hopper.psd") as im:
|
with Image.open("Tests/images/multipage.tiff") as im:
|
||||||
i = ImageSequence.Iterator(im)
|
i = ImageSequence.Iterator(im)
|
||||||
for index in range(1, im.n_frames):
|
for index in range(0, im.n_frames):
|
||||||
assert i[index] == next(i)
|
assert i[index] == next(i)
|
||||||
|
with pytest.raises(IndexError):
|
||||||
|
i[index + 1]
|
||||||
|
with pytest.raises(StopIteration):
|
||||||
|
next(i)
|
||||||
|
|
||||||
def _test_multipage_tiff(self):
|
|
||||||
with Image.open("Tests/images/multipage.tiff") as im:
|
|
||||||
for index, frame in enumerate(ImageSequence.Iterator(im)):
|
|
||||||
frame.load()
|
|
||||||
assert index == im.tell()
|
|
||||||
frame.convert("RGB")
|
|
||||||
|
|
||||||
def test_tiff(self):
|
def test_iterator_min_frame():
|
||||||
self._test_multipage_tiff()
|
with Image.open("Tests/images/hopper.psd") as im:
|
||||||
|
i = ImageSequence.Iterator(im)
|
||||||
|
for index in range(1, im.n_frames):
|
||||||
|
assert i[index] == next(i)
|
||||||
|
|
||||||
@skip_unless_feature("libtiff")
|
|
||||||
def test_libtiff(self):
|
|
||||||
TiffImagePlugin.READ_LIBTIFF = True
|
|
||||||
self._test_multipage_tiff()
|
|
||||||
TiffImagePlugin.READ_LIBTIFF = False
|
|
||||||
|
|
||||||
def test_consecutive(self):
|
def _test_multipage_tiff():
|
||||||
with Image.open("Tests/images/multipage.tiff") as im:
|
with Image.open("Tests/images/multipage.tiff") as im:
|
||||||
firstFrame = None
|
for index, frame in enumerate(ImageSequence.Iterator(im)):
|
||||||
for frame in ImageSequence.Iterator(im):
|
frame.load()
|
||||||
if firstFrame is None:
|
assert index == im.tell()
|
||||||
firstFrame = frame.copy()
|
frame.convert("RGB")
|
||||||
for frame in ImageSequence.Iterator(im):
|
|
||||||
assert_image_equal(frame, firstFrame)
|
|
||||||
break
|
|
||||||
|
|
||||||
def test_palette_mmap(self):
|
|
||||||
# Using mmap in ImageFile can require to reload the palette.
|
|
||||||
with Image.open("Tests/images/multipage-mmap.tiff") as im:
|
|
||||||
color1 = im.getpalette()[0:3]
|
|
||||||
im.seek(0)
|
|
||||||
color2 = im.getpalette()[0:3]
|
|
||||||
assert color1 == color2
|
|
||||||
|
|
||||||
def test_all_frames(self):
|
def test_tiff():
|
||||||
# Test a single image
|
_test_multipage_tiff()
|
||||||
with Image.open("Tests/images/iss634.gif") as im:
|
|
||||||
ims = ImageSequence.all_frames(im)
|
|
||||||
|
|
||||||
assert len(ims) == 42
|
|
||||||
for i, im_frame in enumerate(ims):
|
|
||||||
assert im_frame is not im
|
|
||||||
|
|
||||||
im.seek(i)
|
@skip_unless_feature("libtiff")
|
||||||
assert_image_equal(im, im_frame)
|
def test_libtiff():
|
||||||
|
TiffImagePlugin.READ_LIBTIFF = True
|
||||||
|
_test_multipage_tiff()
|
||||||
|
TiffImagePlugin.READ_LIBTIFF = False
|
||||||
|
|
||||||
# Test a series of images
|
|
||||||
ims = ImageSequence.all_frames([im, hopper(), im])
|
|
||||||
assert len(ims) == 85
|
|
||||||
|
|
||||||
# Test an operation
|
def test_consecutive():
|
||||||
ims = ImageSequence.all_frames(im, lambda im_frame: im_frame.rotate(90))
|
with Image.open("Tests/images/multipage.tiff") as im:
|
||||||
for i, im_frame in enumerate(ims):
|
firstFrame = None
|
||||||
im.seek(i)
|
for frame in ImageSequence.Iterator(im):
|
||||||
assert_image_equal(im.rotate(90), im_frame)
|
if firstFrame is None:
|
||||||
|
firstFrame = frame.copy()
|
||||||
|
for frame in ImageSequence.Iterator(im):
|
||||||
|
assert_image_equal(frame, firstFrame)
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def test_palette_mmap():
|
||||||
|
# Using mmap in ImageFile can require to reload the palette.
|
||||||
|
with Image.open("Tests/images/multipage-mmap.tiff") as im:
|
||||||
|
color1 = im.getpalette()[0:3]
|
||||||
|
im.seek(0)
|
||||||
|
color2 = im.getpalette()[0:3]
|
||||||
|
assert color1 == color2
|
||||||
|
|
||||||
|
|
||||||
|
def test_all_frames():
|
||||||
|
# Test a single image
|
||||||
|
with Image.open("Tests/images/iss634.gif") as im:
|
||||||
|
ims = ImageSequence.all_frames(im)
|
||||||
|
|
||||||
|
assert len(ims) == 42
|
||||||
|
for i, im_frame in enumerate(ims):
|
||||||
|
assert im_frame is not im
|
||||||
|
|
||||||
|
im.seek(i)
|
||||||
|
assert_image_equal(im, im_frame)
|
||||||
|
|
||||||
|
# Test a series of images
|
||||||
|
ims = ImageSequence.all_frames([im, hopper(), im])
|
||||||
|
assert len(ims) == 85
|
||||||
|
|
||||||
|
# Test an operation
|
||||||
|
ims = ImageSequence.all_frames(im, lambda im_frame: im_frame.rotate(90))
|
||||||
|
for i, im_frame in enumerate(ims):
|
||||||
|
im.seek(i)
|
||||||
|
assert_image_equal(im.rotate(90), im_frame)
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import unittest
|
import pytest
|
||||||
|
|
||||||
from PIL import ImageWin
|
from PIL import ImageWin
|
||||||
|
|
||||||
from .helper import PillowTestCase, hopper, is_win32
|
from .helper import hopper, is_win32
|
||||||
|
|
||||||
|
|
||||||
class TestImageWin(PillowTestCase):
|
class TestImageWin:
|
||||||
def test_sanity(self):
|
def test_sanity(self):
|
||||||
dir(ImageWin)
|
dir(ImageWin)
|
||||||
|
|
||||||
|
@ -32,8 +31,8 @@ class TestImageWin(PillowTestCase):
|
||||||
assert wnd2 == 50
|
assert wnd2 == 50
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(is_win32(), "Windows only")
|
@pytest.mark.skipif(not is_win32(), reason="Windows only")
|
||||||
class TestImageWinDib(PillowTestCase):
|
class TestImageWinDib:
|
||||||
def test_dib_image(self):
|
def test_dib_image(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = hopper()
|
im = hopper()
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
import pytest
|
||||||
from PIL import GifImagePlugin, Image, JpegImagePlugin
|
from PIL import GifImagePlugin, Image, JpegImagePlugin
|
||||||
|
|
||||||
from .helper import (
|
from .helper import cjpeg_available, djpeg_available, is_win32, netpbm_available
|
||||||
PillowTestCase,
|
|
||||||
cjpeg_available,
|
|
||||||
djpeg_available,
|
|
||||||
is_win32,
|
|
||||||
netpbm_available,
|
|
||||||
unittest,
|
|
||||||
)
|
|
||||||
|
|
||||||
TEST_JPG = "Tests/images/hopper.jpg"
|
TEST_JPG = "Tests/images/hopper.jpg"
|
||||||
TEST_GIF = "Tests/images/hopper.gif"
|
TEST_GIF = "Tests/images/hopper.gif"
|
||||||
|
@ -17,38 +11,38 @@ TEST_GIF = "Tests/images/hopper.gif"
|
||||||
test_filenames = ("temp_';", 'temp_";', "temp_'\"|", "temp_'\"||", "temp_'\"&&")
|
test_filenames = ("temp_';", 'temp_";', "temp_'\"|", "temp_'\"||", "temp_'\"&&")
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(is_win32(), "requires Unix or macOS")
|
@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS")
|
||||||
class TestShellInjection(PillowTestCase):
|
class TestShellInjection:
|
||||||
def assert_save_filename_check(self, src_img, save_func):
|
def assert_save_filename_check(self, tmp_path, src_img, save_func):
|
||||||
for filename in test_filenames:
|
for filename in test_filenames:
|
||||||
dest_file = self.tempfile(filename)
|
dest_file = str(tmp_path / filename)
|
||||||
save_func(src_img, 0, dest_file)
|
save_func(src_img, 0, dest_file)
|
||||||
# If file can't be opened, shell injection probably occurred
|
# If file can't be opened, shell injection probably occurred
|
||||||
with Image.open(dest_file) as im:
|
with Image.open(dest_file) as im:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
@unittest.skipUnless(djpeg_available(), "djpeg not available")
|
@pytest.mark.skipif(not djpeg_available(), reason="djpeg not available")
|
||||||
def test_load_djpeg_filename(self):
|
def test_load_djpeg_filename(self, tmp_path):
|
||||||
for filename in test_filenames:
|
for filename in test_filenames:
|
||||||
src_file = self.tempfile(filename)
|
src_file = str(tmp_path / filename)
|
||||||
shutil.copy(TEST_JPG, src_file)
|
shutil.copy(TEST_JPG, src_file)
|
||||||
|
|
||||||
with Image.open(src_file) as im:
|
with Image.open(src_file) as im:
|
||||||
im.load_djpeg()
|
im.load_djpeg()
|
||||||
|
|
||||||
@unittest.skipUnless(cjpeg_available(), "cjpeg not available")
|
@pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available")
|
||||||
def test_save_cjpeg_filename(self):
|
def test_save_cjpeg_filename(self, tmp_path):
|
||||||
with Image.open(TEST_JPG) as im:
|
with Image.open(TEST_JPG) as im:
|
||||||
self.assert_save_filename_check(im, JpegImagePlugin._save_cjpeg)
|
self.assert_save_filename_check(tmp_path, im, JpegImagePlugin._save_cjpeg)
|
||||||
|
|
||||||
@unittest.skipUnless(netpbm_available(), "netpbm not available")
|
@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available")
|
||||||
def test_save_netpbm_filename_bmp_mode(self):
|
def test_save_netpbm_filename_bmp_mode(self, tmp_path):
|
||||||
with Image.open(TEST_GIF) as im:
|
with Image.open(TEST_GIF) as im:
|
||||||
im = im.convert("RGB")
|
im = im.convert("RGB")
|
||||||
self.assert_save_filename_check(im, GifImagePlugin._save_netpbm)
|
self.assert_save_filename_check(tmp_path, im, GifImagePlugin._save_netpbm)
|
||||||
|
|
||||||
@unittest.skipUnless(netpbm_available(), "netpbm not available")
|
@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available")
|
||||||
def test_save_netpbm_filename_l_mode(self):
|
def test_save_netpbm_filename_l_mode(self, tmp_path):
|
||||||
with Image.open(TEST_GIF) as im:
|
with Image.open(TEST_GIF) as im:
|
||||||
im = im.convert("L")
|
im = im.convert("L")
|
||||||
self.assert_save_filename_check(im, GifImagePlugin._save_netpbm)
|
self.assert_save_filename_check(tmp_path, im, GifImagePlugin._save_netpbm)
|
||||||
|
|
|
@ -3,56 +3,58 @@ from fractions import Fraction
|
||||||
from PIL import Image, TiffImagePlugin, features
|
from PIL import Image, TiffImagePlugin, features
|
||||||
from PIL.TiffImagePlugin import IFDRational
|
from PIL.TiffImagePlugin import IFDRational
|
||||||
|
|
||||||
from .helper import PillowTestCase, hopper
|
from .helper import hopper
|
||||||
|
|
||||||
|
|
||||||
class Test_IFDRational(PillowTestCase):
|
def _test_equal(num, denom, target):
|
||||||
def _test_equal(self, num, denom, target):
|
|
||||||
|
|
||||||
t = IFDRational(num, denom)
|
t = IFDRational(num, denom)
|
||||||
|
|
||||||
assert target == t
|
assert target == t
|
||||||
assert t == target
|
assert t == target
|
||||||
|
|
||||||
def test_sanity(self):
|
|
||||||
|
|
||||||
self._test_equal(1, 1, 1)
|
def test_sanity():
|
||||||
self._test_equal(1, 1, Fraction(1, 1))
|
|
||||||
|
|
||||||
self._test_equal(2, 2, 1)
|
_test_equal(1, 1, 1)
|
||||||
self._test_equal(1.0, 1, Fraction(1, 1))
|
_test_equal(1, 1, Fraction(1, 1))
|
||||||
|
|
||||||
self._test_equal(Fraction(1, 1), 1, Fraction(1, 1))
|
_test_equal(2, 2, 1)
|
||||||
self._test_equal(IFDRational(1, 1), 1, 1)
|
_test_equal(1.0, 1, Fraction(1, 1))
|
||||||
|
|
||||||
self._test_equal(1, 2, Fraction(1, 2))
|
_test_equal(Fraction(1, 1), 1, Fraction(1, 1))
|
||||||
self._test_equal(1, 2, IFDRational(1, 2))
|
_test_equal(IFDRational(1, 1), 1, 1)
|
||||||
|
|
||||||
def test_nonetype(self):
|
_test_equal(1, 2, Fraction(1, 2))
|
||||||
# Fails if the _delegate function doesn't return a valid function
|
_test_equal(1, 2, IFDRational(1, 2))
|
||||||
|
|
||||||
xres = IFDRational(72)
|
|
||||||
yres = IFDRational(72)
|
|
||||||
assert xres._val is not None
|
|
||||||
assert xres.numerator is not None
|
|
||||||
assert xres.denominator is not None
|
|
||||||
assert yres._val is not None
|
|
||||||
|
|
||||||
assert xres and 1
|
def test_nonetype():
|
||||||
assert xres and yres
|
# Fails if the _delegate function doesn't return a valid function
|
||||||
|
|
||||||
def test_ifd_rational_save(self):
|
xres = IFDRational(72)
|
||||||
methods = (True, False)
|
yres = IFDRational(72)
|
||||||
if not features.check("libtiff"):
|
assert xres._val is not None
|
||||||
methods = (False,)
|
assert xres.numerator is not None
|
||||||
|
assert xres.denominator is not None
|
||||||
|
assert yres._val is not None
|
||||||
|
|
||||||
for libtiff in methods:
|
assert xres and 1
|
||||||
TiffImagePlugin.WRITE_LIBTIFF = libtiff
|
assert xres and yres
|
||||||
|
|
||||||
im = hopper()
|
|
||||||
out = self.tempfile("temp.tiff")
|
|
||||||
res = IFDRational(301, 1)
|
|
||||||
im.save(out, dpi=(res, res), compression="raw")
|
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
def test_ifd_rational_save(tmp_path):
|
||||||
assert float(IFDRational(301, 1)) == float(reloaded.tag_v2[282])
|
methods = (True, False)
|
||||||
|
if not features.check("libtiff"):
|
||||||
|
methods = (False,)
|
||||||
|
|
||||||
|
for libtiff in methods:
|
||||||
|
TiffImagePlugin.WRITE_LIBTIFF = libtiff
|
||||||
|
|
||||||
|
im = hopper()
|
||||||
|
out = str(tmp_path / "temp.tiff")
|
||||||
|
res = IFDRational(301, 1)
|
||||||
|
im.save(out, dpi=(res, res), compression="raw")
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert float(IFDRational(301, 1)) == float(reloaded.tag_v2[282])
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
from .helper import PillowTestCase, assert_image_equal, assert_image_similar, hopper
|
from .helper import assert_image_equal, assert_image_similar, hopper
|
||||||
|
|
||||||
|
|
||||||
class TestUploader(PillowTestCase):
|
def check_upload_equal():
|
||||||
def check_upload_equal(self):
|
result = hopper("P").convert("RGB")
|
||||||
result = hopper("P").convert("RGB")
|
target = hopper("RGB")
|
||||||
target = hopper("RGB")
|
assert_image_equal(result, target)
|
||||||
assert_image_equal(result, target)
|
|
||||||
|
|
||||||
def check_upload_similar(self):
|
|
||||||
result = hopper("P").convert("RGB")
|
def check_upload_similar():
|
||||||
target = hopper("RGB")
|
result = hopper("P").convert("RGB")
|
||||||
assert_image_similar(result, target, 0)
|
target = hopper("RGB")
|
||||||
|
assert_image_similar(result, target, 0)
|
||||||
|
|
|
@ -298,6 +298,11 @@ The :py:meth:`~PIL.Image.Image.open` method may set the following
|
||||||
**exif**
|
**exif**
|
||||||
Raw EXIF data from the image.
|
Raw EXIF data from the image.
|
||||||
|
|
||||||
|
**comment**
|
||||||
|
A comment about the image.
|
||||||
|
|
||||||
|
.. versionadded:: 7.1.0
|
||||||
|
|
||||||
|
|
||||||
The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,8 @@ Convert files to JPEG
|
||||||
outfile = f + ".jpg"
|
outfile = f + ".jpg"
|
||||||
if infile != outfile:
|
if infile != outfile:
|
||||||
try:
|
try:
|
||||||
Image.open(infile).save(outfile)
|
with Image.open(infile) as im:
|
||||||
|
im.save(outfile)
|
||||||
except IOError:
|
except IOError:
|
||||||
print("cannot convert", infile)
|
print("cannot convert", infile)
|
||||||
|
|
||||||
|
|
|
@ -47,8 +47,8 @@ Basic Installation
|
||||||
|
|
||||||
Install Pillow with :command:`pip`::
|
Install Pillow with :command:`pip`::
|
||||||
|
|
||||||
python -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
python -m pip install --upgrade Pillow
|
python3 -m pip install --upgrade Pillow
|
||||||
|
|
||||||
|
|
||||||
Windows Installation
|
Windows Installation
|
||||||
|
@ -59,8 +59,8 @@ supported Pythons in both 32 and 64-bit versions in wheel, egg, and
|
||||||
executable installers. These binaries have all of the optional
|
executable installers. These binaries have all of the optional
|
||||||
libraries included except for raqm and libimagequant::
|
libraries included except for raqm and libimagequant::
|
||||||
|
|
||||||
python -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
python -m pip install --upgrade Pillow
|
python3 -m pip install --upgrade Pillow
|
||||||
|
|
||||||
|
|
||||||
macOS Installation
|
macOS Installation
|
||||||
|
@ -71,8 +71,8 @@ versions in the wheel format. These include support for all optional
|
||||||
libraries except libimagequant. Raqm support requires libraqm,
|
libraries except libimagequant. Raqm support requires libraqm,
|
||||||
fribidi, and harfbuzz to be installed separately::
|
fribidi, and harfbuzz to be installed separately::
|
||||||
|
|
||||||
python -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
python -m pip install --upgrade Pillow
|
python3 -m pip install --upgrade Pillow
|
||||||
|
|
||||||
Linux Installation
|
Linux Installation
|
||||||
^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
@ -82,8 +82,8 @@ versions in the manylinux wheel format. These include support for all
|
||||||
optional libraries except libimagequant. Raqm support requires
|
optional libraries except libimagequant. Raqm support requires
|
||||||
libraqm, fribidi, and harfbuzz to be installed separately::
|
libraqm, fribidi, and harfbuzz to be installed separately::
|
||||||
|
|
||||||
python -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
python -m pip install --upgrade Pillow
|
python3 -m pip install --upgrade Pillow
|
||||||
|
|
||||||
Most major Linux distributions, including Fedora, Debian/Ubuntu and
|
Most major Linux distributions, including Fedora, Debian/Ubuntu and
|
||||||
ArchLinux also include Pillow in packages that previously contained
|
ArchLinux also include Pillow in packages that previously contained
|
||||||
|
@ -195,8 +195,8 @@ Many of Pillow's features require external libraries:
|
||||||
|
|
||||||
Once you have installed the prerequisites, run::
|
Once you have installed the prerequisites, run::
|
||||||
|
|
||||||
python -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
python -m pip install --upgrade Pillow
|
python3 -m pip install --upgrade Pillow
|
||||||
|
|
||||||
If the prerequisites are installed in the standard library locations
|
If the prerequisites are installed in the standard library locations
|
||||||
for your machine (e.g. :file:`/usr` or :file:`/usr/local`), no
|
for your machine (e.g. :file:`/usr` or :file:`/usr/local`), no
|
||||||
|
@ -206,7 +206,7 @@ those locations by editing :file:`setup.py` or
|
||||||
:file:`setup.cfg`, or by adding environment variables on the command
|
:file:`setup.cfg`, or by adding environment variables on the command
|
||||||
line::
|
line::
|
||||||
|
|
||||||
CFLAGS="-I/usr/pkg/include" python -m pip install --upgrade Pillow
|
CFLAGS="-I/usr/pkg/include" python3 -m pip install --upgrade Pillow
|
||||||
|
|
||||||
If Pillow has been previously built without the required
|
If Pillow has been previously built without the required
|
||||||
prerequisites, it may be necessary to manually clear the pip cache or
|
prerequisites, it may be necessary to manually clear the pip cache or
|
||||||
|
@ -254,7 +254,7 @@ Sample usage::
|
||||||
|
|
||||||
or using pip::
|
or using pip::
|
||||||
|
|
||||||
python -m pip install --upgrade Pillow --global-option="build_ext" --global-option="--enable-[feature]"
|
python3 -m pip install --upgrade Pillow --global-option="build_ext" --global-option="--enable-[feature]"
|
||||||
|
|
||||||
|
|
||||||
Building on macOS
|
Building on macOS
|
||||||
|
@ -280,8 +280,8 @@ Then see ``depends/install_raqm_cmake.sh`` to install libraqm.
|
||||||
|
|
||||||
Now install Pillow with::
|
Now install Pillow with::
|
||||||
|
|
||||||
python -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
python -m pip install --upgrade Pillow
|
python3 -m pip install --upgrade Pillow
|
||||||
|
|
||||||
or from within the uncompressed source directory::
|
or from within the uncompressed source directory::
|
||||||
|
|
||||||
|
|
|
@ -20,14 +20,14 @@ Example: Draw a gray cross over an image
|
||||||
|
|
||||||
from PIL import Image, ImageDraw
|
from PIL import Image, ImageDraw
|
||||||
|
|
||||||
im = Image.open("hopper.jpg")
|
with Image.open("hopper.jpg") as im:
|
||||||
|
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
draw.line((0, 0) + im.size, fill=128)
|
draw.line((0, 0) + im.size, fill=128)
|
||||||
draw.line((0, im.size[1], im.size[0], 0), fill=128)
|
draw.line((0, im.size[1], im.size[0], 0), fill=128)
|
||||||
|
|
||||||
# write to stdout
|
# write to stdout
|
||||||
im.save(sys.stdout, "PNG")
|
im.save(sys.stdout, "PNG")
|
||||||
|
|
||||||
|
|
||||||
Concepts
|
Concepts
|
||||||
|
|
|
@ -14,12 +14,11 @@ Extracting frames from an animation
|
||||||
|
|
||||||
from PIL import Image, ImageSequence
|
from PIL import Image, ImageSequence
|
||||||
|
|
||||||
im = Image.open("animation.fli")
|
with Image.open("animation.fli") as im:
|
||||||
|
index = 1
|
||||||
index = 1
|
for frame in ImageSequence.Iterator(im):
|
||||||
for frame in ImageSequence.Iterator(im):
|
frame.save("frame%d.png" % index)
|
||||||
frame.save("frame%d.png" % index)
|
index += 1
|
||||||
index += 1
|
|
||||||
|
|
||||||
The :py:class:`~PIL.ImageSequence.Iterator` class
|
The :py:class:`~PIL.ImageSequence.Iterator` class
|
||||||
-------------------------------------------------
|
-------------------------------------------------
|
||||||
|
|
|
@ -17,8 +17,8 @@ changes it.
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
im = Image.open('hopper.jpg')
|
with Image.open('hopper.jpg') as im:
|
||||||
px = im.load()
|
px = im.load()
|
||||||
print (px[4,4])
|
print (px[4,4])
|
||||||
px[4,4] = (0,0,0)
|
px[4,4] = (0,0,0)
|
||||||
print (px[4,4])
|
print (px[4,4])
|
||||||
|
|
|
@ -18,8 +18,8 @@ The following script loads an image, accesses one pixel from it, then changes it
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
im = Image.open('hopper.jpg')
|
with Image.open('hopper.jpg') as im:
|
||||||
px = im.load()
|
px = im.load()
|
||||||
print (px[4,4])
|
print (px[4,4])
|
||||||
px[4,4] = (0,0,0)
|
px[4,4] = (0,0,0)
|
||||||
print (px[4,4])
|
print (px[4,4])
|
||||||
|
|
|
@ -18,6 +18,27 @@ been resolved.
|
||||||
im = Image.open("hopper.jpg")
|
im = Image.open("hopper.jpg")
|
||||||
im.save("out.jpg", quality=0)
|
im.save("out.jpg", quality=0)
|
||||||
|
|
||||||
|
API Additions
|
||||||
|
=============
|
||||||
|
|
||||||
|
Reading JPEG comments
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
When opening a JPEG image, the comment may now be read into
|
||||||
|
:py:attr:`~PIL.Image.Image.info`.
|
||||||
|
|
||||||
|
Other Changes
|
||||||
|
=============
|
||||||
|
|
||||||
|
If present, only use alpha channel for bounding box
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
When the :py:meth:`~PIL.Image.Image.getbbox` method calculates the bounding
|
||||||
|
box, for an RGB image it trims black pixels. Similarly, for an RGBA image it
|
||||||
|
would trim black transparent pixels. This is now changed so that if an image
|
||||||
|
has an alpha channel (RGBA, RGBa, PA, LA, La), any transparent pixels are
|
||||||
|
trimmed.
|
||||||
|
|
||||||
Improved APNG support
|
Improved APNG support
|
||||||
^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
@ -25,4 +46,4 @@ Added support for reading and writing Animated Portable Network Graphics (APNG)
|
||||||
The PNG plugin now supports using the :py:meth:`~PIL.Image.Image.seek` method and the
|
The PNG plugin now supports using the :py:meth:`~PIL.Image.Image.seek` method and the
|
||||||
:py:class:`~PIL.ImageSequence.Iterator` class to read APNG frame sequences.
|
:py:class:`~PIL.ImageSequence.Iterator` class to read APNG frame sequences.
|
||||||
The PNG plugin also now supports using the ``append_images`` argument to write APNG frame
|
The PNG plugin also now supports using the ``append_images`` argument to write APNG frame
|
||||||
sequences. See :ref:`apng-sequences` for further details.
|
sequences. See :ref:`apng-sequences` for further details.
|
|
@ -141,8 +141,8 @@ def Ghostscript(tile, size, fp, scale=1):
|
||||||
startupinfo = subprocess.STARTUPINFO()
|
startupinfo = subprocess.STARTUPINFO()
|
||||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||||
subprocess.check_call(command, startupinfo=startupinfo)
|
subprocess.check_call(command, startupinfo=startupinfo)
|
||||||
im = Image.open(outfile)
|
out_im = Image.open(outfile)
|
||||||
im.load()
|
out_im.load()
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
os.unlink(outfile)
|
os.unlink(outfile)
|
||||||
|
@ -151,7 +151,9 @@ def Ghostscript(tile, size, fp, scale=1):
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return im.im.copy()
|
im = out_im.im.copy()
|
||||||
|
out_im.close()
|
||||||
|
return im
|
||||||
|
|
||||||
|
|
||||||
class PSFile:
|
class PSFile:
|
||||||
|
|
|
@ -370,7 +370,7 @@ if __name__ == "__main__":
|
||||||
for size in imf.info["sizes"]:
|
for size in imf.info["sizes"]:
|
||||||
imf.size = size
|
imf.size = size
|
||||||
imf.save("out-%s-%s-%s.png" % size)
|
imf.save("out-%s-%s-%s.png" % size)
|
||||||
im = Image.open(sys.argv[1])
|
with Image.open(sys.argv[1]) as im:
|
||||||
im.save("out.png")
|
im.save("out.png")
|
||||||
if sys.platform == "windows":
|
if sys.platform == "windows":
|
||||||
os.startfile("out.png")
|
os.startfile("out.png")
|
||||||
|
|
|
@ -2236,20 +2236,22 @@ class Image:
|
||||||
:returns: None
|
:returns: None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# preserve aspect ratio
|
x, y = map(math.floor, size)
|
||||||
x, y = self.size
|
if x >= self.width and y >= self.height:
|
||||||
if x > size[0]:
|
|
||||||
y = max(round(y * size[0] / x), 1)
|
|
||||||
x = round(size[0])
|
|
||||||
if y > size[1]:
|
|
||||||
x = max(round(x * size[1] / y), 1)
|
|
||||||
y = round(size[1])
|
|
||||||
size = x, y
|
|
||||||
box = None
|
|
||||||
|
|
||||||
if size == self.size:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def round_aspect(number, key):
|
||||||
|
return max(min(math.floor(number), math.ceil(number), key=key), 1)
|
||||||
|
|
||||||
|
# preserve aspect ratio
|
||||||
|
aspect = self.width / self.height
|
||||||
|
if x / y >= aspect:
|
||||||
|
x = round_aspect(y * aspect, key=lambda n: abs(aspect - n / y))
|
||||||
|
else:
|
||||||
|
y = round_aspect(x / aspect, key=lambda n: abs(aspect - x / n))
|
||||||
|
size = (x, y)
|
||||||
|
|
||||||
|
box = None
|
||||||
if reducing_gap is not None:
|
if reducing_gap is not None:
|
||||||
res = self.draft(None, (size[0] * reducing_gap, size[1] * reducing_gap))
|
res = self.draft(None, (size[0] * reducing_gap, size[1] * reducing_gap))
|
||||||
if res is not None:
|
if res is not None:
|
||||||
|
|
|
@ -71,7 +71,10 @@ class ImageFont:
|
||||||
def _load_pilfont(self, filename):
|
def _load_pilfont(self, filename):
|
||||||
|
|
||||||
with open(filename, "rb") as fp:
|
with open(filename, "rb") as fp:
|
||||||
|
image = None
|
||||||
for ext in (".png", ".gif", ".pbm"):
|
for ext in (".png", ".gif", ".pbm"):
|
||||||
|
if image:
|
||||||
|
image.close()
|
||||||
try:
|
try:
|
||||||
fullname = os.path.splitext(filename)[0] + ext
|
fullname = os.path.splitext(filename)[0] + ext
|
||||||
image = Image.open(fullname)
|
image = Image.open(fullname)
|
||||||
|
@ -81,11 +84,14 @@ class ImageFont:
|
||||||
if image and image.mode in ("1", "L"):
|
if image and image.mode in ("1", "L"):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
if image:
|
||||||
|
image.close()
|
||||||
raise OSError("cannot find glyph data file")
|
raise OSError("cannot find glyph data file")
|
||||||
|
|
||||||
self.file = fullname
|
self.file = fullname
|
||||||
|
|
||||||
return self._load_pilfont_data(fp, image)
|
self._load_pilfont_data(fp, image)
|
||||||
|
image.close()
|
||||||
|
|
||||||
def _load_pilfont_data(self, file, image):
|
def _load_pilfont_data(self, file, image):
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,9 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False):
|
||||||
im.load()
|
im.load()
|
||||||
os.unlink(filepath)
|
os.unlink(filepath)
|
||||||
if bbox:
|
if bbox:
|
||||||
im = im.crop(bbox)
|
im_cropped = im.crop(bbox)
|
||||||
|
im.close()
|
||||||
|
return im_cropped
|
||||||
else:
|
else:
|
||||||
offset, size, data = Image.core.grabscreen(include_layered_windows, all_screens)
|
offset, size, data = Image.core.grabscreen(include_layered_windows, all_screens)
|
||||||
im = Image.frombytes(
|
im = Image.frombytes(
|
||||||
|
|
|
@ -203,4 +203,5 @@ if __name__ == "__main__":
|
||||||
print("Syntax: python ImageShow.py imagefile [title]")
|
print("Syntax: python ImageShow.py imagefile [title]")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
print(show(Image.open(sys.argv[1]), *sys.argv[2:]))
|
with Image.open(sys.argv[1]) as im:
|
||||||
|
print(show(im, *sys.argv[2:]))
|
||||||
|
|
|
@ -158,9 +158,9 @@ class IptcImageFile(ImageFile.ImageFile):
|
||||||
o.close()
|
o.close()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_im = Image.open(outfile)
|
with Image.open(outfile) as _im:
|
||||||
_im.load()
|
_im.load()
|
||||||
self.im = _im.im
|
self.im = _im.im
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
os.unlink(outfile)
|
os.unlink(outfile)
|
||||||
|
|
|
@ -176,6 +176,7 @@ def COM(self, marker):
|
||||||
n = i16(self.fp.read(2)) - 2
|
n = i16(self.fp.read(2)) - 2
|
||||||
s = ImageFile._safe_read(self.fp, n)
|
s = ImageFile._safe_read(self.fp, n)
|
||||||
|
|
||||||
|
self.info["comment"] = s
|
||||||
self.app["COM"] = s # compatibility
|
self.app["COM"] = s # compatibility
|
||||||
self.applist.append(("COM", s))
|
self.applist.append(("COM", s))
|
||||||
|
|
||||||
|
@ -448,9 +449,9 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
raise ValueError("Invalid Filename")
|
raise ValueError("Invalid Filename")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_im = Image.open(path)
|
with Image.open(path) as _im:
|
||||||
_im.load()
|
_im.load()
|
||||||
self.im = _im.im
|
self.im = _im.im
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
os.unlink(path)
|
os.unlink(path)
|
||||||
|
|
|
@ -304,21 +304,21 @@ if __name__ == "__main__":
|
||||||
print("input image must be in Spider format")
|
print("input image must be in Spider format")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
im = Image.open(filename)
|
with Image.open(filename) as im:
|
||||||
print("image: " + str(im))
|
print("image: " + str(im))
|
||||||
print("format: " + str(im.format))
|
print("format: " + str(im.format))
|
||||||
print("size: " + str(im.size))
|
print("size: " + str(im.size))
|
||||||
print("mode: " + str(im.mode))
|
print("mode: " + str(im.mode))
|
||||||
print("max, min: ", end=" ")
|
print("max, min: ", end=" ")
|
||||||
print(im.getextrema())
|
print(im.getextrema())
|
||||||
|
|
||||||
if len(sys.argv) > 2:
|
if len(sys.argv) > 2:
|
||||||
outfile = sys.argv[2]
|
outfile = sys.argv[2]
|
||||||
|
|
||||||
# perform some image operation
|
# perform some image operation
|
||||||
im = im.transpose(Image.FLIP_LEFT_RIGHT)
|
im = im.transpose(Image.FLIP_LEFT_RIGHT)
|
||||||
print(
|
print(
|
||||||
"saving a flipped version of %s as %s "
|
"saving a flipped version of %s as %s "
|
||||||
% (os.path.basename(filename), outfile)
|
% (os.path.basename(filename), outfile)
|
||||||
)
|
)
|
||||||
im.save(outfile, SpiderImageFile.format)
|
im.save(outfile, SpiderImageFile.format)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import collections
|
import collections
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
import PIL
|
import PIL
|
||||||
|
|
||||||
|
@ -76,14 +77,14 @@ def get_supported_features():
|
||||||
|
|
||||||
|
|
||||||
def check(feature):
|
def check(feature):
|
||||||
return (
|
if feature in modules:
|
||||||
feature in modules
|
return check_module(feature)
|
||||||
and check_module(feature)
|
if feature in codecs:
|
||||||
or feature in codecs
|
return check_codec(feature)
|
||||||
and check_codec(feature)
|
if feature in features:
|
||||||
or feature in features
|
return check_feature(feature)
|
||||||
and check_feature(feature)
|
warnings.warn("Unknown feature '%s'." % feature, stacklevel=2)
|
||||||
)
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_supported():
|
def get_supported():
|
||||||
|
|
|
@ -782,9 +782,6 @@ font_render(FontObject* self, PyObject* args)
|
||||||
im = (Imaging) id;
|
im = (Imaging) id;
|
||||||
/* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960 */
|
/* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960 */
|
||||||
load_flags = FT_LOAD_NO_BITMAP;
|
load_flags = FT_LOAD_NO_BITMAP;
|
||||||
if (stroker == NULL) {
|
|
||||||
load_flags |= FT_LOAD_RENDER;
|
|
||||||
}
|
|
||||||
if (mask) {
|
if (mask) {
|
||||||
load_flags |= FT_LOAD_TARGET_MONO;
|
load_flags |= FT_LOAD_TARGET_MONO;
|
||||||
}
|
}
|
||||||
|
@ -792,7 +789,7 @@ font_render(FontObject* self, PyObject* args)
|
||||||
ascender = 0;
|
ascender = 0;
|
||||||
for (i = 0; i < count; i++) {
|
for (i = 0; i < count; i++) {
|
||||||
index = glyph_info[i].index;
|
index = glyph_info[i].index;
|
||||||
error = FT_Load_Glyph(self->face, index, load_flags);
|
error = FT_Load_Glyph(self->face, index, load_flags | FT_LOAD_RENDER);
|
||||||
if (error) {
|
if (error) {
|
||||||
return geterror(error);
|
return geterror(error);
|
||||||
}
|
}
|
||||||
|
@ -806,6 +803,10 @@ font_render(FontObject* self, PyObject* args)
|
||||||
ascender = temp;
|
ascender = temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (stroker == NULL) {
|
||||||
|
load_flags |= FT_LOAD_RENDER;
|
||||||
|
}
|
||||||
|
|
||||||
x = y = 0;
|
x = y = 0;
|
||||||
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
||||||
for (i = 0; i < count; i++) {
|
for (i = 0; i < count; i++) {
|
||||||
|
@ -908,7 +909,7 @@ font_render(FontObject* self, PyObject* args)
|
||||||
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) ||\
|
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) ||\
|
||||||
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1)
|
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1)
|
||||||
static PyObject*
|
static PyObject*
|
||||||
font_getvarnames(FontObject* self, PyObject* args)
|
font_getvarnames(FontObject* self)
|
||||||
{
|
{
|
||||||
int error;
|
int error;
|
||||||
FT_UInt i, j, num_namedstyles, name_count;
|
FT_UInt i, j, num_namedstyles, name_count;
|
||||||
|
@ -947,7 +948,7 @@ font_render(FontObject* self, PyObject* args)
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
font_getvaraxes(FontObject* self, PyObject* args)
|
font_getvaraxes(FontObject* self)
|
||||||
{
|
{
|
||||||
int error;
|
int error;
|
||||||
FT_UInt i, j, num_axis, name_count;
|
FT_UInt i, j, num_axis, name_count;
|
||||||
|
@ -1077,8 +1078,8 @@ static PyMethodDef font_methods[] = {
|
||||||
#if FREETYPE_MAJOR > 2 ||\
|
#if FREETYPE_MAJOR > 2 ||\
|
||||||
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) ||\
|
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) ||\
|
||||||
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1)
|
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1)
|
||||||
{"getvarnames", (PyCFunction) font_getvarnames, METH_VARARGS },
|
{"getvarnames", (PyCFunction) font_getvarnames, METH_NOARGS },
|
||||||
{"getvaraxes", (PyCFunction) font_getvaraxes, METH_VARARGS },
|
{"getvaraxes", (PyCFunction) font_getvaraxes, METH_NOARGS },
|
||||||
{"setvarname", (PyCFunction) font_setvarname, METH_VARARGS},
|
{"setvarname", (PyCFunction) font_setvarname, METH_VARARGS},
|
||||||
{"setvaraxes", (PyCFunction) font_setvaraxes, METH_VARARGS},
|
{"setvaraxes", (PyCFunction) font_setvaraxes, METH_VARARGS},
|
||||||
#endif
|
#endif
|
||||||
|
|
27
src/_webp.c
27
src/_webp.c
|
@ -369,7 +369,7 @@ PyObject* _anim_decoder_dealloc(PyObject* self)
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* _anim_decoder_get_info(PyObject* self, PyObject* args)
|
PyObject* _anim_decoder_get_info(PyObject* self)
|
||||||
{
|
{
|
||||||
WebPAnimDecoderObject* decp = (WebPAnimDecoderObject *)self;
|
WebPAnimDecoderObject* decp = (WebPAnimDecoderObject *)self;
|
||||||
WebPAnimInfo* info = &(decp->info);
|
WebPAnimInfo* info = &(decp->info);
|
||||||
|
@ -406,7 +406,7 @@ PyObject* _anim_decoder_get_chunk(PyObject* self, PyObject* args)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* _anim_decoder_get_next(PyObject* self, PyObject* args)
|
PyObject* _anim_decoder_get_next(PyObject* self)
|
||||||
{
|
{
|
||||||
uint8_t* buf;
|
uint8_t* buf;
|
||||||
int timestamp;
|
int timestamp;
|
||||||
|
@ -428,13 +428,7 @@ PyObject* _anim_decoder_get_next(PyObject* self, PyObject* args)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* _anim_decoder_has_more_frames(PyObject* self, PyObject* args)
|
PyObject* _anim_decoder_reset(PyObject* self)
|
||||||
{
|
|
||||||
WebPAnimDecoderObject* decp = (WebPAnimDecoderObject*)self;
|
|
||||||
return Py_BuildValue("i", WebPAnimDecoderHasMoreFrames(decp->dec));
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject* _anim_decoder_reset(PyObject* self, PyObject* args)
|
|
||||||
{
|
{
|
||||||
WebPAnimDecoderObject* decp = (WebPAnimDecoderObject *)self;
|
WebPAnimDecoderObject* decp = (WebPAnimDecoderObject *)self;
|
||||||
WebPAnimDecoderReset(decp->dec);
|
WebPAnimDecoderReset(decp->dec);
|
||||||
|
@ -489,11 +483,10 @@ static PyTypeObject WebPAnimEncoder_Type = {
|
||||||
|
|
||||||
// WebPAnimDecoder methods
|
// WebPAnimDecoder methods
|
||||||
static struct PyMethodDef _anim_decoder_methods[] = {
|
static struct PyMethodDef _anim_decoder_methods[] = {
|
||||||
{"get_info", (PyCFunction)_anim_decoder_get_info, METH_VARARGS, "get_info"},
|
{"get_info", (PyCFunction)_anim_decoder_get_info, METH_NOARGS, "get_info"},
|
||||||
{"get_chunk", (PyCFunction)_anim_decoder_get_chunk, METH_VARARGS, "get_chunk"},
|
{"get_chunk", (PyCFunction)_anim_decoder_get_chunk, METH_VARARGS, "get_chunk"},
|
||||||
{"get_next", (PyCFunction)_anim_decoder_get_next, METH_VARARGS, "get_next"},
|
{"get_next", (PyCFunction)_anim_decoder_get_next, METH_NOARGS, "get_next"},
|
||||||
{"has_more_frames", (PyCFunction)_anim_decoder_has_more_frames, METH_VARARGS, "has_more_frames"},
|
{"reset", (PyCFunction)_anim_decoder_reset, METH_NOARGS, "reset"},
|
||||||
{"reset", (PyCFunction)_anim_decoder_reset, METH_VARARGS, "reset"},
|
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -775,7 +768,7 @@ end:
|
||||||
|
|
||||||
// Return the decoder's version number, packed in hexadecimal using 8bits for
|
// Return the decoder's version number, packed in hexadecimal using 8bits for
|
||||||
// each of major/minor/revision. E.g: v2.5.7 is 0x020507.
|
// each of major/minor/revision. E.g: v2.5.7 is 0x020507.
|
||||||
PyObject* WebPDecoderVersion_wrapper(PyObject* self, PyObject* args){
|
PyObject* WebPDecoderVersion_wrapper() {
|
||||||
return Py_BuildValue("i", WebPGetDecoderVersion());
|
return Py_BuildValue("i", WebPGetDecoderVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -787,7 +780,7 @@ int WebPDecoderBuggyAlpha(void) {
|
||||||
return WebPGetDecoderVersion()==0x0103;
|
return WebPGetDecoderVersion()==0x0103;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* WebPDecoderBuggyAlpha_wrapper(PyObject* self, PyObject* args){
|
PyObject* WebPDecoderBuggyAlpha_wrapper() {
|
||||||
return Py_BuildValue("i", WebPDecoderBuggyAlpha());
|
return Py_BuildValue("i", WebPDecoderBuggyAlpha());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -803,8 +796,8 @@ static PyMethodDef webpMethods[] =
|
||||||
#endif
|
#endif
|
||||||
{"WebPEncode", WebPEncode_wrapper, METH_VARARGS, "WebPEncode"},
|
{"WebPEncode", WebPEncode_wrapper, METH_VARARGS, "WebPEncode"},
|
||||||
{"WebPDecode", WebPDecode_wrapper, METH_VARARGS, "WebPDecode"},
|
{"WebPDecode", WebPDecode_wrapper, METH_VARARGS, "WebPDecode"},
|
||||||
{"WebPDecoderVersion", WebPDecoderVersion_wrapper, METH_VARARGS, "WebPVersion"},
|
{"WebPDecoderVersion", WebPDecoderVersion_wrapper, METH_NOARGS, "WebPVersion"},
|
||||||
{"WebPDecoderBuggyAlpha", WebPDecoderBuggyAlpha_wrapper, METH_VARARGS, "WebPDecoderBuggyAlpha"},
|
{"WebPDecoderBuggyAlpha", WebPDecoderBuggyAlpha_wrapper, METH_NOARGS, "WebPDecoderBuggyAlpha"},
|
||||||
{NULL, NULL}
|
{NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -55,8 +55,19 @@ ImagingGetBBox(Imaging im, int bbox[4])
|
||||||
GETBBOX(image8, 0xff);
|
GETBBOX(image8, 0xff);
|
||||||
} else {
|
} else {
|
||||||
INT32 mask = 0xffffffff;
|
INT32 mask = 0xffffffff;
|
||||||
if (im->bands == 3)
|
if (im->bands == 3) {
|
||||||
((UINT8*) &mask)[3] = 0;
|
((UINT8*) &mask)[3] = 0;
|
||||||
|
} else if (strcmp(im->mode, "RGBa") == 0 ||
|
||||||
|
strcmp(im->mode, "RGBA") == 0 ||
|
||||||
|
strcmp(im->mode, "La") == 0 ||
|
||||||
|
strcmp(im->mode, "LA") == 0 ||
|
||||||
|
strcmp(im->mode, "PA") == 0) {
|
||||||
|
#ifdef WORDS_BIGENDIAN
|
||||||
|
mask = 0x000000ff;
|
||||||
|
#else
|
||||||
|
mask = 0xff000000;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
GETBBOX(image32, mask);
|
GETBBOX(image32, mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user