Merge branch 'master' into apng

This commit is contained in:
Andrew Murray 2020-03-09 23:29:40 +11:00 committed by GitHub
commit 9f61be4c72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 1981 additions and 1810 deletions

View File

@ -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]

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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"

View File

@ -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")

View File

@ -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:

View File

@ -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])

View File

@ -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",
)

View File

@ -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:

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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():

View File

@ -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

View File

@ -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")

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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])

View File

@ -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)

View File

@ -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:

View File

@ -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)

View File

@ -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::

View File

@ -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

View File

@ -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
------------------------------------------------- -------------------------------------------------

View File

@ -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])

View File

@ -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])

View File

@ -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.

View File

@ -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:

View File

@ -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")

View File

@ -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:

View File

@ -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):

View File

@ -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(

View File

@ -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:]))

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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():

View File

@ -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

View File

@ -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}
}; };

View File

@ -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);
} }