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,23 +3,21 @@ 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) os.path.join(base, d, f) for f in os.listdir(os.path.join(base, d)) if ext in f
for f in os.listdir(os.path.join(base, d))
if ext in f
] ]
def test_bad(self):
def test_bad():
""" These shouldn't crash/dos, but they shouldn't return anything """ These shouldn't crash/dos, but they shouldn't return anything
either """ either """
for f in self.get_files("b"): for f in get_files("b"):
def open(f): def open(f):
try: try:
@ -31,7 +29,8 @@ class TestBmpReference(PillowTestCase):
# Assert that there is no unclosed file warning # Assert that there is no unclosed file warning
pytest.warns(None, open, f) pytest.warns(None, open, f)
def test_questionable(self):
def test_questionable():
""" These shouldn't crash/dos, but it's not well defined that these """ These shouldn't crash/dos, but it's not well defined that these
are in spec """ are in spec """
supported = [ supported = [
@ -45,7 +44,7 @@ class TestBmpReference(PillowTestCase):
"pal8os2sp.bmp", "pal8os2sp.bmp",
"rgb32bf-xbgr.bmp", "rgb32bf-xbgr.bmp",
] ]
for f in self.get_files("q"): for f in get_files("q"):
try: try:
with Image.open(f) as im: with Image.open(f) as im:
im.load() im.load()
@ -55,7 +54,8 @@ class TestBmpReference(PillowTestCase):
if os.path.basename(f) in supported: if os.path.basename(f) in supported:
raise raise
def test_good(self):
def test_good():
""" These should all work. There's a set of target files in the """ These should all work. There's a set of target files in the
html directory that we can compare against. """ html directory that we can compare against. """
@ -86,7 +86,7 @@ class TestBmpReference(PillowTestCase):
name = os.path.splitext(name)[0] name = os.path.splitext(name)[0]
return os.path.join(base, "html", "%s.png" % name) return os.path.join(base, "html", "%s.png" % name)
for f in self.get_files("g"): for f in get_files("g"):
try: try:
with Image.open(f) as im: with Image.open(f) as im:
im.load() im.load()
@ -107,5 +107,4 @@ class TestBmpReference(PillowTestCase):
os.path.join(base, "g", "pal8rle.bmp"), os.path.join(base, "g", "pal8rle.bmp"),
os.path.join(base, "g", "pal4rle.bmp"), os.path.join(base, "g", "pal4rle.bmp"),
) )
if f not in unsupported: assert f in unsupported, "Unsupported Image {}: {}".format(f, msg)
self.fail("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):
def test_load_blp2_dxt1():
with Image.open("Tests/images/blp/blp2_dxt1.blp") as im: with Image.open("Tests/images/blp/blp2_dxt1.blp") as im:
with Image.open("Tests/images/blp/blp2_dxt1.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(self):
def test_load_blp2_dxt1a():
with Image.open("Tests/images/blp/blp2_dxt1a.blp") as im: with Image.open("Tests/images/blp/blp2_dxt1a.blp") as im:
with Image.open("Tests/images/blp/blp2_dxt1a.png") as target: with Image.open("Tests/images/blp/blp2_dxt1a.png") as target:
assert_image_equal(im, 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):
def test_load_dxt1():
with Image.open("Tests/images/ftex_dxt1.ftc") as im: with Image.open("Tests/images/ftex_dxt1.ftc") as im:
with Image.open("Tests/images/ftex_dxt1.png") as target: with Image.open("Tests/images/ftex_dxt1.png") as target:
assert_image_similar(im, target.convert("RGBA"), 15) 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):
def _n_qtables_helper(n, test_file):
with Image.open(test_file) as im: with Image.open(test_file) as im:
f = self.tempfile("temp.jpg") f = str(tmp_path / "temp.jpg")
im.save(f, qtables=[[n] * 64] * n) im.save(f, qtables=[[n] * 64] * n)
with Image.open(f) as im: with Image.open(f) as im:
assert len(im.quantization) == n assert len(im.quantization) == n
reloaded = self.roundtrip(im, qtables="keep") reloaded = self.roundtrip(im, qtables="keep")
assert im.quantization == reloaded.quantization 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,9 +22,7 @@ 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):
def roundtrip(self, im, **options):
out = BytesIO() out = BytesIO()
im.save(out, "JPEG2000", **options) im.save(out, "JPEG2000", **options)
test_bytes = out.tell() test_bytes = out.tell()
@ -33,7 +32,8 @@ class TestFileJpeg2k(PillowTestCase):
im.load() im.load()
return im return im
def test_sanity(self):
def test_sanity():
# Internal version number # Internal version number
assert re.search(r"\d+\.\d+\.\d+$", Image.core.jp2klib_version) assert re.search(r"\d+\.\d+\.\d+$", Image.core.jp2klib_version)
@ -45,83 +45,95 @@ class TestFileJpeg2k(PillowTestCase):
assert im.format == "JPEG2000" assert im.format == "JPEG2000"
assert im.get_format_mimetype() == "image/jp2" assert im.get_format_mimetype() == "image/jp2"
def test_jpf(self):
def test_jpf():
with Image.open("Tests/images/balloon.jpf") as im: with Image.open("Tests/images/balloon.jpf") as im:
assert im.format == "JPEG2000" assert im.format == "JPEG2000"
assert im.get_format_mimetype() == "image/jpx" assert im.get_format_mimetype() == "image/jpx"
def test_invalid_file(self):
def test_invalid_file():
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
Jpeg2KImagePlugin.Jpeg2KImageFile(invalid_file) Jpeg2KImagePlugin.Jpeg2KImageFile(invalid_file)
def test_bytesio(self):
def test_bytesio():
with open("Tests/images/test-card-lossless.jp2", "rb") as f: with open("Tests/images/test-card-lossless.jp2", "rb") as f:
data = BytesIO(f.read()) data = BytesIO(f.read())
with Image.open(data) as im: with Image.open(data) as im:
im.load() im.load()
assert_image_similar(im, test_card, 1.0e-3) assert_image_similar(im, test_card, 1.0e-3)
# These two test pre-written JPEG 2000 files that were not written with # These two test pre-written JPEG 2000 files that were not written with
# PIL (they were made using Adobe Photoshop) # PIL (they were made using Adobe Photoshop)
def test_lossless(self):
def test_lossless(tmp_path):
with Image.open("Tests/images/test-card-lossless.jp2") as im: with Image.open("Tests/images/test-card-lossless.jp2") as im:
im.load() im.load()
outfile = self.tempfile("temp_test-card.png") outfile = str(tmp_path / "temp_test-card.png")
im.save(outfile) 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):
def test_lossy_tiled():
with Image.open("Tests/images/test-card-lossy-tiled.jp2") as im: with Image.open("Tests/images/test-card-lossy-tiled.jp2") as im:
im.load() im.load()
assert_image_similar(im, test_card, 2.0) assert_image_similar(im, test_card, 2.0)
def test_lossless_rt(self):
im = self.roundtrip(test_card) def test_lossless_rt():
im = roundtrip(test_card)
assert_image_equal(im, test_card) assert_image_equal(im, test_card)
def test_lossy_rt(self):
im = self.roundtrip(test_card, quality_layers=[20]) def test_lossy_rt():
im = roundtrip(test_card, quality_layers=[20])
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)) def test_tiled_rt():
im = roundtrip(test_card, tile_size=(128, 128))
assert_image_equal(im, test_card) assert_image_equal(im, test_card)
def test_tiled_offset_rt(self):
im = self.roundtrip( def test_tiled_offset_rt():
test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(32, 32) im = roundtrip(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_tiled_offset_too_small():
with pytest.raises(ValueError): with pytest.raises(ValueError):
self.roundtrip( roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(128, 32))
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]) def test_irreversible_rt():
im = roundtrip(test_card, irreversible=True, quality_layers=[20])
assert_image_similar(im, test_card, 2.0) 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") def test_prog_qual_rt():
im = roundtrip(test_card, quality_layers=[60, 40, 20], progression="LRCP")
assert_image_similar(im, test_card, 2.0) assert_image_similar(im, test_card, 2.0)
def test_prog_res_rt(self):
im = self.roundtrip(test_card, num_resolutions=8, progression="RLCP") def test_prog_res_rt():
im = roundtrip(test_card, num_resolutions=8, progression="RLCP")
assert_image_equal(im, test_card) assert_image_equal(im, test_card)
def test_reduce(self):
def test_reduce():
with Image.open("Tests/images/test-card-lossless.jp2") as im: with Image.open("Tests/images/test-card-lossless.jp2") as im:
im.reduce = 2 im.reduce = 2
im.load() im.load()
assert im.size == (160, 120) assert im.size == (160, 120)
def test_layers_type(self):
outfile = self.tempfile("temp_layers.jp2") def test_layers_type(tmp_path):
outfile = str(tmp_path / "temp_layers.jp2")
for quality_layers in [[100, 50, 10], (100, 50, 10), None]: 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)
@ -129,11 +141,10 @@ class TestFileJpeg2k(PillowTestCase):
with pytest.raises(ValueError): with pytest.raises(ValueError):
test_card.save(outfile, quality_layers=quality_layers) 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:
@ -147,7 +158,8 @@ class TestFileJpeg2k(PillowTestCase):
im.load() im.load()
assert_image_similar(im, test_card, 0.4) assert_image_similar(im, test_card, 0.4)
def test_rgba(self):
def test_rgba():
# Arrange # Arrange
with Image.open("Tests/images/rgb_trns_ycbc.j2k") as j2k: with Image.open("Tests/images/rgb_trns_ycbc.j2k") as j2k:
with Image.open("Tests/images/rgb_trns_ycbc.jp2") as jp2: with Image.open("Tests/images/rgb_trns_ycbc.jp2") as jp2:
@ -160,7 +172,8 @@ class TestFileJpeg2k(PillowTestCase):
assert j2k.mode == "RGBA" assert j2k.mode == "RGBA"
assert jp2.mode == "RGBA" assert jp2.mode == "RGBA"
def test_16bit_monochrome_has_correct_mode(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:
j2k.load() j2k.load()
assert j2k.mode == "I;16" assert j2k.mode == "I;16"
@ -169,35 +182,40 @@ class TestFileJpeg2k(PillowTestCase):
jp2.load() jp2.load()
assert jp2.mode == "I;16" assert jp2.mode == "I;16"
@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_16bit_monochrome_jp2_like_tiff(self): 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.tif") as tiff_16bit:
with Image.open("Tests/images/16bit.cropped.jp2") as jp2: with Image.open("Tests/images/16bit.cropped.jp2") as jp2:
assert_image_similar(jp2, tiff_16bit, 1e-3) assert_image_similar(jp2, tiff_16bit, 1e-3)
@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_16bit_monochrome_j2k_like_tiff(self): def test_16bit_monochrome_j2k_like_tiff():
with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit: with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit:
with Image.open("Tests/images/16bit.cropped.j2k") as j2k: with Image.open("Tests/images/16bit.cropped.j2k") as j2k:
assert_image_similar(j2k, tiff_16bit, 1e-3) assert_image_similar(j2k, tiff_16bit, 1e-3)
def test_16bit_j2k_roundtrips(self):
def test_16bit_j2k_roundtrips():
with Image.open("Tests/images/16bit.cropped.j2k") as j2k: with Image.open("Tests/images/16bit.cropped.j2k") as j2k:
im = self.roundtrip(j2k) im = roundtrip(j2k)
assert_image_equal(im, j2k) assert_image_equal(im, j2k)
def test_16bit_jp2_roundtrips(self):
def test_16bit_jp2_roundtrips():
with Image.open("Tests/images/16bit.cropped.jp2") as jp2: with Image.open("Tests/images/16bit.cropped.jp2") as jp2:
im = self.roundtrip(jp2) im = roundtrip(jp2)
assert_image_equal(im, jp2) assert_image_equal(im, jp2)
def test_unbound_local(self):
# prepatch, a malformed jp2 file could cause an UnboundLocalError def test_unbound_local():
# exception. # prepatch, a malformed jp2 file could cause an UnboundLocalError exception.
with pytest.raises(IOError): with pytest.raises(IOError):
Image.open("Tests/images/unbound_variable.jp2") Image.open("Tests/images/unbound_variable.jp2")
def test_parser_feed(self):
def test_parser_feed():
# Arrange # Arrange
with open("Tests/images/test-card-lossless.jp2", "rb") as f: with open("Tests/images/test-card-lossless.jp2", "rb") as f:
data = f.read() data = f.read()

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,23 +1,24 @@
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,
) )
class TestFilePalm(PillowTestCase):
_roundtrip = imagemagick_available() _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)
@ -26,46 +27,64 @@ class TestFilePalm(PillowTestCase):
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: def open_with_imagemagick(tmp_path, f):
if not imagemagick_available():
raise OSError()
outfile = str(tmp_path / "temp.png")
rc = subprocess.call(
[IMCONVERT, f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
)
if rc:
raise OSError
return Image.open(outfile)
def roundtrip(tmp_path, mode):
if not _roundtrip:
return return
im = hopper(mode) im = hopper(mode)
outfile = self.tempfile("temp.palm") outfile = str(tmp_path / "temp.palm")
im.save(outfile) im.save(outfile)
converted = self.open_withImagemagick(outfile) converted = open_with_imagemagick(tmp_path, outfile)
assert_image_equal(converted, im) assert_image_equal(converted, im)
def test_monochrome(self):
def test_monochrome(tmp_path):
# Arrange # Arrange
mode = "1" mode = "1"
# Act / Assert # Act / Assert
self.helper_save_as_palm(mode) helper_save_as_palm(tmp_path, mode)
self.roundtrip(mode) roundtrip(tmp_path, mode)
def test_p_mode(self):
def test_p_mode(tmp_path):
# Arrange # Arrange
mode = "P" mode = "P"
# Act / Assert # Act / Assert
self.helper_save_as_palm(mode) helper_save_as_palm(tmp_path, mode)
skip_known_bad_test("Palm P image is wrong") skip_known_bad_test("Palm P image is wrong")
self.roundtrip(mode) roundtrip(tmp_path, mode)
def test_l_ioerror(self):
def test_l_ioerror(tmp_path):
# Arrange # Arrange
mode = "L" mode = "L"
# Act / Assert # Act / Assert
with pytest.raises(IOError): with pytest.raises(IOError):
self.helper_save_as_palm(mode) helper_save_as_palm(tmp_path, mode)
def test_rgb_ioerror(self):
def test_rgb_ioerror(tmp_path):
# Arrange # Arrange
mode = "RGB" mode = "RGB"
# Act / Assert # Act / Assert
with pytest.raises(IOError): with pytest.raises(IOError):
self.helper_save_as_palm(mode) helper_save_as_palm(tmp_path, mode)

View File

@ -1,10 +1,7 @@
from PIL import Image from PIL import Image
from .helper import PillowTestCase
def test_load_raw():
class TestFilePcd(PillowTestCase):
def test_load_raw(self):
with Image.open("Tests/images/hopper.pcd") as im: with Image.open("Tests/images/hopper.pcd") as im:
im.load() # should not segfault. im.load() # should not segfault.

View File

@ -1,12 +1,11 @@
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) im.save(f)
with Image.open(f) as im2: with Image.open(f) as im2:
assert im2.mode == im.mode assert im2.mode == im.mode
@ -15,32 +14,36 @@ class TestFilePcx(PillowTestCase):
assert im2.get_format_mimetype() == "image/x-pcx" assert im2.get_format_mimetype() == "image/x-pcx"
assert_image_equal(im2, im) assert_image_equal(im2, im)
def test_sanity(self):
def test_sanity(tmp_path):
for mode in ("1", "L", "P", "RGB"): for mode in ("1", "L", "P", "RGB"):
self._roundtrip(hopper(mode)) _roundtrip(tmp_path, hopper(mode))
# Test an unsupported mode # Test an unsupported mode
f = self.tempfile("temp.pcx") f = str(tmp_path / "temp.pcx")
im = hopper("RGBA") im = hopper("RGBA")
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.save(f) im.save(f)
def test_invalid_file(self):
def test_invalid_file():
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
PcxImagePlugin.PcxImageFile(invalid_file) PcxImagePlugin.PcxImageFile(invalid_file)
def test_odd(self):
# see issue #523, odd sized images should have a stride that's even. def test_odd(tmp_path):
# not that imagemagick or gimp write pcx that way. # See issue #523, odd sized images should have a stride that's even.
# we were not handling properly. # Not that ImageMagick or GIMP write PCX that way.
# 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):
def test_pil184():
# Check reading of files where xmin/xmax is not zero. # Check reading of files where xmin/xmax is not zero.
test_file = "Tests/images/pil184.pcx" test_file = "Tests/images/pil184.pcx"
@ -51,45 +54,51 @@ class TestFilePcx(PillowTestCase):
# Make sure all pixels are either 0 or 255. # Make sure all pixels are either 0 or 255.
assert im.histogram()[0] + im.histogram()[255] == 447 * 144 assert im.histogram()[0] + im.histogram()[255] == 447 * 144
def test_1px_width(self):
def test_1px_width(tmp_path):
im = Image.new("L", (1, 256)) im = Image.new("L", (1, 256))
px = im.load() px = im.load()
for y in range(256): for y in range(256):
px[0, y] = y px[0, y] = y
self._roundtrip(im) _roundtrip(tmp_path, im)
def test_large_count(self):
def test_large_count(tmp_path):
im = Image.new("L", (256, 1)) im = Image.new("L", (256, 1))
px = im.load() px = im.load()
for x in range(256): for x in range(256):
px[x, 0] = x // 67 * 67 px[x, 0] = x // 67 * 67
self._roundtrip(im) _roundtrip(tmp_path, im)
def _test_buffer_overflow(self, im, size=1024):
def _test_buffer_overflow(tmp_path, im, size=1024):
_last = ImageFile.MAXBLOCK _last = ImageFile.MAXBLOCK
ImageFile.MAXBLOCK = size ImageFile.MAXBLOCK = size
try: try:
self._roundtrip(im) _roundtrip(tmp_path, im)
finally: finally:
ImageFile.MAXBLOCK = _last ImageFile.MAXBLOCK = _last
def test_break_in_count_overflow(self):
def test_break_in_count_overflow(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
self._test_buffer_overflow(im) _test_buffer_overflow(tmp_path, im)
def test_break_one_in_loop(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(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
self._test_buffer_overflow(im) _test_buffer_overflow(tmp_path, 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):
@ -97,18 +106,20 @@ class TestFilePcx(PillowTestCase):
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):
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
px[0, 3] = 128 + 64 px[0, 3] = 128 + 64
self._test_buffer_overflow(im) _test_buffer_overflow(tmp_path, im)
def test_break_many_at_end(self):
def test_break_many_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):
@ -117,9 +128,10 @@ class TestFilePcx(PillowTestCase):
for x in range(4): for x in range(4):
px[x * 2, 3] = 128 + 64 px[x * 2, 3] = 128 + 64
px[x + 256 - 4, 3] = 0 px[x + 256 - 4, 3] = 0
self._test_buffer_overflow(im) _test_buffer_overflow(tmp_path, im)
def test_break_padding(self):
def test_break_padding(tmp_path):
im = Image.new("L", (257, 5)) im = Image.new("L", (257, 5))
px = im.load() px = im.load()
for y in range(5): for y in range(5):
@ -127,4 +139,4 @@ class TestFilePcx(PillowTestCase):
px[x, y] = x % 128 px[x, y] = x % 128
for x in range(5): for x in range(5):
px[x, 3] = 0 px[x, 3] = 0
self._test_buffer_overflow(im) _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,22 +1,22 @@
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):
def test_16bit_pgm():
with Image.open("Tests/images/16_bit_binary.pgm") as im: with Image.open("Tests/images/16_bit_binary.pgm") as im:
im.load() im.load()
assert im.mode == "I" assert im.mode == "I"
@ -26,35 +26,39 @@ class TestFilePpm(PillowTestCase):
with Image.open("Tests/images/16_bit_binary_pgm.png") as tgt: with Image.open("Tests/images/16_bit_binary_pgm.png") as tgt:
assert_image_equal(im, tgt) assert_image_equal(im, tgt)
def test_16bit_pgm_write(self):
def test_16bit_pgm_write(tmp_path):
with Image.open("Tests/images/16_bit_binary.pgm") as im: with Image.open("Tests/images/16_bit_binary.pgm") as im:
im.load() im.load()
f = self.tempfile("temp.pgm") f = str(tmp_path / "temp.pgm")
im.save(f, "PPM") im.save(f, "PPM")
with Image.open(f) as reloaded: with Image.open(f) as reloaded:
assert_image_equal(im, reloaded) assert_image_equal(im, reloaded)
def test_pnm(self):
def test_pnm(tmp_path):
with Image.open("Tests/images/hopper.pnm") as im: with Image.open("Tests/images/hopper.pnm") as im:
assert_image_similar(im, hopper(), 0.0001) assert_image_similar(im, hopper(), 0.0001)
f = self.tempfile("temp.pnm") f = str(tmp_path / "temp.pnm")
im.save(f) im.save(f)
with Image.open(f) as reloaded: with Image.open(f) as reloaded:
assert_image_equal(im, reloaded) assert_image_equal(im, reloaded)
def test_truncated_file(self):
path = self.tempfile("temp.pgm") def test_truncated_file(tmp_path):
path = str(tmp_path / "temp.pgm")
with open(path, "w") as f: with open(path, "w") as f:
f.write("P6") f.write("P6")
with pytest.raises(ValueError): with pytest.raises(ValueError):
Image.open(path) Image.open(path)
def test_neg_ppm(self):
def test_neg_ppm():
# Storage.c accepted negative values for xsize, ysize. the # Storage.c accepted negative values for xsize, ysize. the
# internal open_ppm function didn't check for sanity but it # internal open_ppm function didn't check for sanity but it
# has been removed. The default opener doesn't accept negative # has been removed. The default opener doesn't accept negative
@ -63,8 +67,9 @@ class TestFilePpm(PillowTestCase):
with pytest.raises(IOError): with pytest.raises(IOError):
Image.open("Tests/images/negative_size.ppm") Image.open("Tests/images/negative_size.ppm")
def test_mimetypes(self):
path = self.tempfile("temp.pgm") def test_mimetypes(tmp_path):
path = str(tmp_path / "temp.pgm")
with open(path, "w") as f: with open(path, "w") as f:
f.write("P4\n128 128\n255") f.write("P4\n128 128\n255")

View File

@ -1,11 +1,10 @@
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"
@ -14,13 +13,15 @@ class TestFileSgi(PillowTestCase):
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):
def test_rgb16():
test_file = "Tests/images/hopper16.rgb" test_file = "Tests/images/hopper16.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())
def test_l(self):
def test_l():
# Created with ImageMagick # Created with ImageMagick
# convert hopper.ppm -monochrome -compress None sgi:hopper.bw # convert hopper.ppm -monochrome -compress None sgi:hopper.bw
test_file = "Tests/images/hopper.bw" test_file = "Tests/images/hopper.bw"
@ -29,7 +30,8 @@ class TestFileSgi(PillowTestCase):
assert_image_similar(im, hopper("L"), 2) assert_image_similar(im, hopper("L"), 2)
assert im.get_format_mimetype() == "image/sgi" assert im.get_format_mimetype() == "image/sgi"
def test_rgba(self):
def test_rgba():
# Created with ImageMagick: # Created with ImageMagick:
# convert transparent.png -compress None transparent.sgi # convert transparent.png -compress None transparent.sgi
test_file = "Tests/images/transparent.sgi" test_file = "Tests/images/transparent.sgi"
@ -39,7 +41,8 @@ class TestFileSgi(PillowTestCase):
assert_image_equal(im, target) assert_image_equal(im, target)
assert im.get_format_mimetype() == "image/sgi" assert im.get_format_mimetype() == "image/sgi"
def test_rle(self):
def test_rle():
# Created with ImageMagick: # Created with ImageMagick:
# convert hopper.ppm hopper.sgi # convert hopper.ppm hopper.sgi
test_file = "Tests/images/hopper.sgi" test_file = "Tests/images/hopper.sgi"
@ -48,22 +51,25 @@ class TestFileSgi(PillowTestCase):
with Image.open("Tests/images/hopper.rgb") as target: with Image.open("Tests/images/hopper.rgb") as target:
assert_image_equal(im, target) assert_image_equal(im, target)
def test_rle16(self):
def test_rle16():
test_file = "Tests/images/tv16.sgi" test_file = "Tests/images/tv16.sgi"
with Image.open(test_file) as im: with Image.open(test_file) as im:
with Image.open("Tests/images/tv.rgb") as target: with Image.open("Tests/images/tv.rgb") as target:
assert_image_equal(im, target) assert_image_equal(im, target)
def test_invalid_file(self):
def test_invalid_file():
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"
with pytest.raises(ValueError): with pytest.raises(ValueError):
SgiImagePlugin.SgiImageFile(invalid_file) SgiImagePlugin.SgiImageFile(invalid_file)
def test_write(self):
def test_write(tmp_path):
def roundtrip(img): def roundtrip(img):
out = self.tempfile("temp.sgi") out = str(tmp_path / "temp.sgi")
img.save(out, format="sgi") img.save(out, format="sgi")
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert_image_equal(img, reloaded) assert_image_equal(img, reloaded)
@ -74,19 +80,21 @@ class TestFileSgi(PillowTestCase):
# Test 1 dimension for an L mode image # Test 1 dimension for an L mode image
roundtrip(Image.new("L", (10, 1))) roundtrip(Image.new("L", (10, 1)))
def test_write16(self):
def test_write16(tmp_path):
test_file = "Tests/images/hopper16.rgb" test_file = "Tests/images/hopper16.rgb"
with Image.open(test_file) as im: with Image.open(test_file) as im:
out = self.tempfile("temp.sgi") out = str(tmp_path / "temp.sgi")
im.save(out, format="sgi", bpc=2) im.save(out, format="sgi", bpc=2)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert_image_equal(im, reloaded) assert_image_equal(im, reloaded)
def test_unsupported_mode(self):
def test_unsupported_mode(tmp_path):
im = hopper("LA") im = hopper("LA")
out = self.tempfile("temp.sgi") out = str(tmp_path / "temp.sgi")
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.save(out, format="sgi") 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,13 +5,12 @@ 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
@ -36,20 +35,20 @@ class TestFileTiffMetadata(PillowTestCase):
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 = self.tempfile("temp.tif") f = str(tmp_path / "temp.tif")
img.save(f, tiffinfo=info) img.save(f, tiffinfo=info)
@ -64,9 +63,9 @@ class TestFileTiffMetadata(PillowTestCase):
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
@ -78,7 +77,8 @@ class TestFileTiffMetadata(PillowTestCase):
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 {
@ -119,10 +119,11 @@ class TestFileTiffMetadata(PillowTestCase):
"StripOffsets": (8,), "StripOffsets": (8,),
} == img.tag.named() } == img.tag.named()
def test_write_metadata(self):
def test_write_metadata(tmp_path):
""" Test metadata writing through the python code """ """ Test metadata writing through the python code """
with Image.open("Tests/images/hopper.tif") as img: with Image.open("Tests/images/hopper.tif") as img:
f = self.tempfile("temp.tiff") f = str(tmp_path / "temp.tiff")
img.save(f, tiffinfo=img.tag) img.save(f, tiffinfo=img.tag)
original = img.tag_v2.named() original = img.tag_v2.named()
@ -154,20 +155,23 @@ class TestFileTiffMetadata(PillowTestCase):
if tag not in ignored: if tag not in ignored:
assert value == reloaded[tag], "%s didn't roundtrip" % tag 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): def test_no_duplicate_50741_tag():
assert TAG_IDS["MakerNoteSafety"] == 50741
assert TAG_IDS["BestQualityScale"] == 50780
def test_empty_metadata():
f = io.BytesIO(b"II*\x00\x08\x00\x00\x00") f = io.BytesIO(b"II*\x00\x08\x00\x00\x00")
head = f.read(8) head = f.read(8)
info = TiffImagePlugin.ImageFileDirectory(head) info = TiffImagePlugin.ImageFileDirectory(head)
# Should not raise struct.error. # Should not raise struct.error.
pytest.warns(UserWarning, info.load, f) pytest.warns(UserWarning, info.load, f)
def test_iccprofile(self):
def test_iccprofile(tmp_path):
# https://github.com/python-pillow/Pillow/issues/1462 # https://github.com/python-pillow/Pillow/issues/1462
out = self.tempfile("temp.tiff") out = str(tmp_path / "temp.tiff")
with Image.open("Tests/images/hopper.iccprofile.tif") as im: with Image.open("Tests/images/hopper.iccprofile.tif") as im:
im.save(out) im.save(out)
@ -175,7 +179,8 @@ class TestFileTiffMetadata(PillowTestCase):
assert not isinstance(im.info["icc_profile"], tuple) assert not isinstance(im.info["icc_profile"], tuple)
assert im.info["icc_profile"] == reloaded.info["icc_profile"] assert im.info["icc_profile"] == reloaded.info["icc_profile"]
def test_iccprofile_binary(self):
def test_iccprofile_binary():
# https://github.com/python-pillow/Pillow/issues/1526 # https://github.com/python-pillow/Pillow/issues/1526
# We should be able to load this, # We should be able to load this,
# but probably won't be able to save it. # but probably won't be able to save it.
@ -184,29 +189,33 @@ class TestFileTiffMetadata(PillowTestCase):
assert im.tag_v2.tagtype[34675] == 1 assert im.tag_v2.tagtype[34675] == 1
assert im.info["icc_profile"] assert im.info["icc_profile"]
def test_iccprofile_save_png(self):
def test_iccprofile_save_png(tmp_path):
with Image.open("Tests/images/hopper.iccprofile.tif") as im: with Image.open("Tests/images/hopper.iccprofile.tif") as im:
outfile = self.tempfile("temp.png") outfile = str(tmp_path / "temp.png")
im.save(outfile) im.save(outfile)
def test_iccprofile_binary_save_png(self):
def test_iccprofile_binary_save_png(tmp_path):
with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im: with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im:
outfile = self.tempfile("temp.png") outfile = str(tmp_path / "temp.png")
im.save(outfile) im.save(outfile)
def test_exif_div_zero(self):
def test_exif_div_zero(tmp_path):
im = hopper() im = hopper()
info = TiffImagePlugin.ImageFileDirectory_v2() info = TiffImagePlugin.ImageFileDirectory_v2()
info[41988] = TiffImagePlugin.IFDRational(0, 0) info[41988] = TiffImagePlugin.IFDRational(0, 0)
out = self.tempfile("temp.tiff") out = str(tmp_path / "temp.tiff")
im.save(out, tiffinfo=info, compression="raw") im.save(out, tiffinfo=info, compression="raw")
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert 0 == reloaded.tag_v2[41988].numerator assert 0 == reloaded.tag_v2[41988].numerator
assert 0 == reloaded.tag_v2[41988].denominator assert 0 == reloaded.tag_v2[41988].denominator
def test_ifd_unsigned_rational(self):
def test_ifd_unsigned_rational(tmp_path):
im = hopper() im = hopper()
info = TiffImagePlugin.ImageFileDirectory_v2() info = TiffImagePlugin.ImageFileDirectory_v2()
@ -217,7 +226,7 @@ class TestFileTiffMetadata(PillowTestCase):
info[41493] = TiffImagePlugin.IFDRational(numerator, 1) info[41493] = TiffImagePlugin.IFDRational(numerator, 1)
out = self.tempfile("temp.tiff") out = str(tmp_path / "temp.tiff")
im.save(out, tiffinfo=info, compression="raw") im.save(out, tiffinfo=info, compression="raw")
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
@ -229,14 +238,15 @@ class TestFileTiffMetadata(PillowTestCase):
info[41493] = TiffImagePlugin.IFDRational(numerator, 1) info[41493] = TiffImagePlugin.IFDRational(numerator, 1)
out = self.tempfile("temp.tiff") out = str(tmp_path / "temp.tiff")
im.save(out, tiffinfo=info, compression="raw") im.save(out, tiffinfo=info, compression="raw")
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert max_long == reloaded.tag_v2[41493].numerator assert max_long == reloaded.tag_v2[41493].numerator
assert 1 == reloaded.tag_v2[41493].denominator assert 1 == reloaded.tag_v2[41493].denominator
def test_ifd_signed_rational(self):
def test_ifd_signed_rational(tmp_path):
im = hopper() im = hopper()
info = TiffImagePlugin.ImageFileDirectory_v2() info = TiffImagePlugin.ImageFileDirectory_v2()
@ -246,7 +256,7 @@ class TestFileTiffMetadata(PillowTestCase):
info[37380] = TiffImagePlugin.IFDRational(numerator, denominator) info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
out = self.tempfile("temp.tiff") out = str(tmp_path / "temp.tiff")
im.save(out, tiffinfo=info, compression="raw") im.save(out, tiffinfo=info, compression="raw")
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
@ -258,7 +268,7 @@ class TestFileTiffMetadata(PillowTestCase):
info[37380] = TiffImagePlugin.IFDRational(numerator, denominator) info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
out = self.tempfile("temp.tiff") out = str(tmp_path / "temp.tiff")
im.save(out, tiffinfo=info, compression="raw") im.save(out, tiffinfo=info, compression="raw")
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
@ -271,26 +281,28 @@ class TestFileTiffMetadata(PillowTestCase):
info[37380] = TiffImagePlugin.IFDRational(numerator, denominator) info[37380] = TiffImagePlugin.IFDRational(numerator, denominator)
out = self.tempfile("temp.tiff") out = str(tmp_path / "temp.tiff")
im.save(out, tiffinfo=info, compression="raw") im.save(out, tiffinfo=info, compression="raw")
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert 2 ** 31 - 1 == reloaded.tag_v2[37380].numerator assert 2 ** 31 - 1 == reloaded.tag_v2[37380].numerator
assert -1 == reloaded.tag_v2[37380].denominator assert -1 == reloaded.tag_v2[37380].denominator
def test_ifd_signed_long(self):
def test_ifd_signed_long(tmp_path):
im = hopper() im = hopper()
info = TiffImagePlugin.ImageFileDirectory_v2() info = TiffImagePlugin.ImageFileDirectory_v2()
info[37000] = -60000 info[37000] = -60000
out = self.tempfile("temp.tiff") out = str(tmp_path / "temp.tiff")
im.save(out, tiffinfo=info, compression="raw") im.save(out, tiffinfo=info, compression="raw")
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert reloaded.tag_v2[37000] == -60000 assert reloaded.tag_v2[37000] == -60000
def test_empty_values(self):
def test_empty_values():
data = io.BytesIO( data = io.BytesIO(
b"II*\x00\x08\x00\x00\x00\x03\x00\x1a\x01\x05\x00\x00\x00\x00\x00" 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"\x00\x00\x00\x00\x1b\x01\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00"
@ -304,17 +316,19 @@ class TestFileTiffMetadata(PillowTestCase):
info = dict(info) info = dict(info)
assert 33432 in info assert 33432 in info
def test_PhotoshopInfo(self):
def test_PhotoshopInfo(tmp_path):
with Image.open("Tests/images/issue_2278.tif") as im: with Image.open("Tests/images/issue_2278.tif") as im:
assert len(im.tag_v2[34377]) == 1 assert len(im.tag_v2[34377]) == 1
assert isinstance(im.tag_v2[34377][0], bytes) assert isinstance(im.tag_v2[34377][0], bytes)
out = self.tempfile("temp.tiff") out = str(tmp_path / "temp.tiff")
im.save(out) im.save(out)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert len(reloaded.tag_v2[34377]) == 1 assert len(reloaded.tag_v2[34377]) == 1
assert isinstance(reloaded.tag_v2[34377][0], bytes) assert isinstance(reloaded.tag_v2[34377][0], bytes)
def test_too_many_entries(self):
def test_too_many_entries():
ifd = TiffImagePlugin.ImageFileDirectory_v2() ifd = TiffImagePlugin.ImageFileDirectory_v2()
# 277: ("SamplesPerPixel", SHORT, 1), # 277: ("SamplesPerPixel", SHORT, 1),

View File

@ -3,24 +3,27 @@ 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):
def str_to_float(i):
return ord(i) / 255 return ord(i) / 255
def tuple_to_ints(self, tp):
def tuple_to_ints(tp):
x, y, z = tp x, y, z = tp
return int(x * 255.0), int(y * 255.0), int(z * 255.0) return int(x * 255.0), int(y * 255.0), int(z * 255.0)
def test_sanity(self):
def test_sanity():
Image.new("HSV", (100, 100)) Image.new("HSV", (100, 100))
def wedge(self):
def wedge():
w = Image._wedge() w = Image._wedge()
w90 = w.rotate(90) w90 = w.rotate(90)
@ -43,18 +46,17 @@ class TestFormatHSV(PillowTestCase):
return img return img
def to_xxx_colorsys(self, im, func, mode):
def to_xxx_colorsys(im, func, mode):
# convert the hard way using the library colorsys routines. # convert the hard way using the library colorsys routines.
(r, g, b) = im.split() (r, g, b) = im.split()
conv_func = self.int_to_float conv_func = int_to_float
converted = [ converted = [
self.tuple_to_ints(func(conv_func(_r), conv_func(_g), conv_func(_b))) tuple_to_ints(func(conv_func(_r), conv_func(_g), conv_func(_b)))
for (_r, _g, _b) in itertools.zip_longest( for (_r, _g, _b) in itertools.zip_longest(r.tobytes(), g.tobytes(), b.tobytes())
r.tobytes(), g.tobytes(), b.tobytes()
)
] ]
new_bytes = b"".join( new_bytes = b"".join(
@ -65,25 +67,25 @@ class TestFormatHSV(PillowTestCase):
return hsv return hsv
def to_hsv_colorsys(self, im):
return self.to_xxx_colorsys(im, colorsys.rgb_to_hsv, "HSV")
def to_rgb_colorsys(self, im): def to_hsv_colorsys(im):
return self.to_xxx_colorsys(im, colorsys.hsv_to_rgb, "RGB") return to_xxx_colorsys(im, colorsys.rgb_to_hsv, "HSV")
def test_wedge(self):
src = self.wedge().resize((3 * 32, 32), Image.BILINEAR) def to_rgb_colorsys(im):
return to_xxx_colorsys(im, colorsys.hsv_to_rgb, "RGB")
def test_wedge():
src = wedge().resize((3 * 32, 32), Image.BILINEAR)
im = src.convert("HSV") im = src.convert("HSV")
comparable = self.to_hsv_colorsys(src) comparable = to_hsv_colorsys(src)
assert_image_similar( assert_image_similar(
im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong" im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong"
) )
assert_image_similar( assert_image_similar(
im.getchannel(1), im.getchannel(1), comparable.getchannel(1), 1, "Saturation conversion is wrong",
comparable.getchannel(1),
1,
"Saturation conversion is wrong",
) )
assert_image_similar( assert_image_similar(
im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong" im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong"
@ -102,43 +104,33 @@ class TestFormatHSV(PillowTestCase):
im.getchannel(2), comparable.getchannel(2), 3, "B conversion is wrong" im.getchannel(2), comparable.getchannel(2), 3, "B conversion is wrong"
) )
def test_convert(self):
def test_convert():
im = hopper("RGB").convert("HSV") im = hopper("RGB").convert("HSV")
comparable = self.to_hsv_colorsys(hopper("RGB")) comparable = to_hsv_colorsys(hopper("RGB"))
assert_image_similar( assert_image_similar(
im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong" im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong"
) )
assert_image_similar( assert_image_similar(
im.getchannel(1), im.getchannel(1), comparable.getchannel(1), 1, "Saturation conversion is wrong",
comparable.getchannel(1),
1,
"Saturation conversion is wrong",
) )
assert_image_similar( assert_image_similar(
im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong" im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong"
) )
def test_hsv_to_rgb(self):
comparable = self.to_hsv_colorsys(hopper("RGB")) def test_hsv_to_rgb():
comparable = to_hsv_colorsys(hopper("RGB"))
converted = comparable.convert("RGB") converted = comparable.convert("RGB")
comparable = self.to_rgb_colorsys(comparable) comparable = to_rgb_colorsys(comparable)
assert_image_similar( assert_image_similar(
converted.getchannel(0), converted.getchannel(0), comparable.getchannel(0), 3, "R conversion is wrong",
comparable.getchannel(0),
3,
"R conversion is wrong",
) )
assert_image_similar( assert_image_similar(
converted.getchannel(1), converted.getchannel(1), comparable.getchannel(1), 3, "G conversion is wrong",
comparable.getchannel(1),
3,
"G conversion is wrong",
) )
assert_image_similar( assert_image_similar(
converted.getchannel(2), converted.getchannel(2), comparable.getchannel(2), 3, "B conversion is wrong",
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,17 +1,10 @@
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
@ -43,7 +36,8 @@ class TestImageConvert(PillowTestCase):
for mode in modes: for mode in modes:
convert(im, mode) convert(im, mode)
def test_default(self):
def test_default():
im = hopper("P") im = hopper("P")
assert_image(im, "P", im.size) assert_image(im, "P", im.size)
@ -52,26 +46,32 @@ class TestImageConvert(PillowTestCase):
im = im.convert() im = im.convert()
assert_image(im, "RGB", im.size) assert_image(im, "RGB", im.size)
# ref https://github.com/python-pillow/Pillow/issues/274 # ref https://github.com/python-pillow/Pillow/issues/274
def _test_float_conversion(self, im):
def _test_float_conversion(im):
orig = im.getpixel((5, 5)) orig = im.getpixel((5, 5))
converted = im.convert("F").getpixel((5, 5)) converted = im.convert("F").getpixel((5, 5))
assert orig == converted assert orig == converted
def test_8bit(self):
def test_8bit():
with Image.open("Tests/images/hopper.jpg") as im: with Image.open("Tests/images/hopper.jpg") as im:
self._test_float_conversion(im.convert("L")) _test_float_conversion(im.convert("L"))
def test_16bit(self):
def test_16bit():
with Image.open("Tests/images/16bit.cropped.tif") as im: with Image.open("Tests/images/16bit.cropped.tif") as im:
self._test_float_conversion(im) _test_float_conversion(im)
def test_16bit_workaround(self):
def test_16bit_workaround():
with Image.open("Tests/images/16bit.cropped.tif") as im: with Image.open("Tests/images/16bit.cropped.tif") as im:
self._test_float_conversion(im.convert("I")) _test_float_conversion(im.convert("I"))
def test_rgba_p(self):
def test_rgba_p():
im = hopper("RGBA") im = hopper("RGBA")
im.putalpha(hopper("L")) im.putalpha(hopper("L"))
@ -80,11 +80,12 @@ class TestImageConvert(PillowTestCase):
assert_image_similar(im, comparable, 20) assert_image_similar(im, comparable, 20)
def test_trns_p(self):
def test_trns_p(tmp_path):
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_l = im.convert("L") im_l = im.convert("L")
assert im_l.info["transparency"] == 0 # undone assert im_l.info["transparency"] == 0 # undone
@ -94,9 +95,11 @@ class TestImageConvert(PillowTestCase):
assert im_rgb.info["transparency"] == (0, 0, 0) # undone assert im_rgb.info["transparency"] == (0, 0, 0) # undone
im_rgb.save(f) im_rgb.save(f)
# ref https://github.com/python-pillow/Pillow/issues/664 # ref https://github.com/python-pillow/Pillow/issues/664
def test_trns_p_rgba(self):
def test_trns_p_rgba():
# Arrange # Arrange
im = hopper("P") im = hopper("P")
im.info["transparency"] = 128 im.info["transparency"] = 128
@ -109,11 +112,12 @@ class TestImageConvert(PillowTestCase):
# https://github.com/python-pillow/Pillow/issues/2702 # https://github.com/python-pillow/Pillow/issues/2702
assert im_rgba.palette is None assert im_rgba.palette is None
def test_trns_l(self):
def test_trns_l(tmp_path):
im = hopper("L") im = hopper("L")
im.info["transparency"] = 128 im.info["transparency"] = 128
f = self.tempfile("temp.png") f = str(tmp_path / "temp.png")
im_rgb = im.convert("RGB") im_rgb = im.convert("RGB")
assert im_rgb.info["transparency"] == (128, 128, 128) # undone assert im_rgb.info["transparency"] == (128, 128, 128) # undone
@ -127,11 +131,12 @@ class TestImageConvert(PillowTestCase):
assert "transparency" not in im_p.info assert "transparency" not in im_p.info
im_p.save(f) im_p.save(f)
def test_trns_RGB(self):
def test_trns_RGB(tmp_path):
im = hopper("RGB") im = hopper("RGB")
im.info["transparency"] = im.getpixel((0, 0)) im.info["transparency"] = im.getpixel((0, 0))
f = self.tempfile("temp.png") f = str(tmp_path / "temp.png")
im_l = im.convert("L") im_l = im.convert("L")
assert im_l.info["transparency"] == im_l.getpixel((0, 0)) # undone assert im_l.info["transparency"] == im_l.getpixel((0, 0)) # undone
@ -149,7 +154,8 @@ class TestImageConvert(PillowTestCase):
assert "transparency" not in im_p.info assert "transparency" not in im_p.info
im_p.save(f) im_p.save(f)
def test_gif_with_rgba_palette_to_p(self):
def test_gif_with_rgba_palette_to_p():
# See https://github.com/python-pillow/Pillow/issues/2433 # See https://github.com/python-pillow/Pillow/issues/2433
with Image.open("Tests/images/hopper.gif") as im: with Image.open("Tests/images/hopper.gif") as im:
im.info["transparency"] = 255 im.info["transparency"] = 255
@ -160,7 +166,8 @@ class TestImageConvert(PillowTestCase):
# Should not raise ValueError: unrecognized raw mode # Should not raise ValueError: unrecognized raw mode
im_p.load() im_p.load()
def test_p_la(self):
def test_p_la():
im = hopper("RGBA") im = hopper("RGBA")
alpha = hopper("L") alpha = hopper("L")
im.putalpha(alpha) im.putalpha(alpha)
@ -169,7 +176,8 @@ class TestImageConvert(PillowTestCase):
assert_image_similar(alpha, comparable, 5) assert_image_similar(alpha, comparable, 5)
def test_matrix_illegal_conversion(self):
def test_matrix_illegal_conversion():
# Arrange # Arrange
im = hopper("CMYK") im = hopper("CMYK")
# fmt: off # fmt: off
@ -184,7 +192,8 @@ class TestImageConvert(PillowTestCase):
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.convert(mode="CMYK", matrix=matrix) im.convert(mode="CMYK", matrix=matrix)
def test_matrix_wrong_mode(self):
def test_matrix_wrong_mode():
# Arrange # Arrange
im = hopper("L") im = hopper("L")
# fmt: off # fmt: off
@ -199,7 +208,8 @@ class TestImageConvert(PillowTestCase):
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.convert(mode="L", matrix=matrix) im.convert(mode="L", matrix=matrix)
def test_matrix_xyz(self):
def test_matrix_xyz():
def matrix_convert(mode): def matrix_convert(mode):
# Arrange # Arrange
im = hopper("RGB") im = hopper("RGB")
@ -230,7 +240,8 @@ class TestImageConvert(PillowTestCase):
matrix_convert("RGB") matrix_convert("RGB")
matrix_convert("L") matrix_convert("L")
def test_matrix_identity(self):
def test_matrix_identity():
# Arrange # Arrange
im = hopper("RGB") im = hopper("RGB")
# fmt: off # fmt: off

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,10 +1,9 @@
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]
@ -29,7 +28,8 @@ class TestImageSplit(PillowTestCase):
] ]
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 test_split_merge():
def split_merge(mode): def split_merge(mode):
return Image.merge(mode, hopper(mode).split()) return Image.merge(mode, hopper(mode).split())
@ -43,11 +43,12 @@ class TestImageSplit(PillowTestCase):
assert_image_equal(hopper("CMYK"), split_merge("CMYK")) assert_image_equal(hopper("CMYK"), split_merge("CMYK"))
assert_image_equal(hopper("YCbCr"), split_merge("YCbCr")) assert_image_equal(hopper("YCbCr"), split_merge("YCbCr"))
def test_split_open(self):
def test_split_open(tmp_path):
if features.check("zlib"): if features.check("zlib"):
test_file = self.tempfile("temp.png") test_file = str(tmp_path / "temp.png")
else: else:
test_file = self.tempfile("temp.pcx") test_file = str(tmp_path / "temp.pcx")
def split_open(mode): def split_open(mode):
hopper(mode).save(test_file) hopper(mode).save(test_file)

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,17 +1,17 @@
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):
def test_getcolor():
palette = ImagePalette.ImagePalette() palette = ImagePalette.ImagePalette()
@ -27,11 +27,12 @@ class TestImagePalette(PillowTestCase):
with pytest.raises(ValueError): with pytest.raises(ValueError):
palette.getcolor("unknown") palette.getcolor("unknown")
def test_file(self):
def test_file(tmp_path):
palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
f = self.tempfile("temp.lut") f = str(tmp_path / "temp.lut")
palette.save(f) palette.save(f)
@ -45,7 +46,8 @@ class TestImagePalette(PillowTestCase):
assert isinstance(p, ImagePalette.ImagePalette) assert isinstance(p, ImagePalette.ImagePalette)
assert p.palette == palette.tobytes() assert p.palette == palette.tobytes()
def test_make_linear_lut(self):
def test_make_linear_lut():
# Arrange # Arrange
black = 0 black = 0
white = 255 white = 255
@ -60,7 +62,8 @@ class TestImagePalette(PillowTestCase):
for i in range(0, len(lut)): for i in range(0, len(lut)):
assert lut[i] == i assert lut[i] == i
def test_make_linear_lut_not_yet_implemented(self):
def test_make_linear_lut_not_yet_implemented():
# Update after FIXME # Update after FIXME
# Arrange # Arrange
black = 1 black = 1
@ -70,7 +73,8 @@ class TestImagePalette(PillowTestCase):
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
ImagePalette.make_linear_lut(black, white) ImagePalette.make_linear_lut(black, white)
def test_make_gamma_lut(self):
def test_make_gamma_lut():
# Arrange # Arrange
exp = 5 exp = 5
@ -87,7 +91,8 @@ class TestImagePalette(PillowTestCase):
assert lut[191] == 60 assert lut[191] == 60
assert lut[255] == 255 assert lut[255] == 255
def test_rawmode_valueerrors(self):
def test_rawmode_valueerrors(tmp_path):
# Arrange # Arrange
palette = ImagePalette.raw("RGB", list(range(256)) * 3) palette = ImagePalette.raw("RGB", list(range(256)) * 3)
@ -96,11 +101,12 @@ class TestImagePalette(PillowTestCase):
palette.tobytes() palette.tobytes()
with pytest.raises(ValueError): with pytest.raises(ValueError):
palette.getcolor((1, 2, 3)) palette.getcolor((1, 2, 3))
f = self.tempfile("temp.lut") f = str(tmp_path / "temp.lut")
with pytest.raises(ValueError): with pytest.raises(ValueError):
palette.save(f) palette.save(f)
def test_getdata(self):
def test_getdata():
# Arrange # Arrange
data_in = list(range(256)) * 3 data_in = list(range(256)) * 3
palette = ImagePalette.ImagePalette("RGB", data_in) palette = ImagePalette.ImagePalette("RGB", data_in)
@ -111,7 +117,8 @@ class TestImagePalette(PillowTestCase):
# Assert # Assert
assert mode == "RGB;L" assert mode == "RGB;L"
def test_rawmode_getdata(self):
def test_rawmode_getdata():
# Arrange # Arrange
data_in = list(range(256)) * 3 data_in = list(range(256)) * 3
palette = ImagePalette.raw("RGB", data_in) palette = ImagePalette.raw("RGB", data_in)
@ -123,9 +130,10 @@ class TestImagePalette(PillowTestCase):
assert rawmode == "RGB" assert rawmode == "RGB"
assert data_in == data_out assert data_in == data_out
def test_2bit_palette(self):
def test_2bit_palette(tmp_path):
# issue #2258, 2 bit palettes are corrupted. # issue #2258, 2 bit palettes are corrupted.
outfile = self.tempfile("temp.png") outfile = str(tmp_path / "temp.png")
rgb = b"\x00" * 2 + b"\x01" * 2 + b"\x02" * 2 rgb = b"\x00" * 2 + b"\x01" * 2 + b"\x02" * 2
img = Image.frombytes("P", (6, 1), rgb) img = Image.frombytes("P", (6, 1), rgb)
@ -135,6 +143,7 @@ class TestImagePalette(PillowTestCase):
with Image.open(outfile) as reloaded: with Image.open(outfile) as reloaded:
assert_image_equal(img, reloaded) assert_image_equal(img, reloaded)
def test_invalid_palette(self):
def test_invalid_palette():
with pytest.raises(IOError): with pytest.raises(IOError):
ImagePalette.load("Tests/images/hopper.jpg") ImagePalette.load("Tests/images/hopper.jpg")

View File

@ -1,13 +1,12 @@
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)
@ -25,7 +24,8 @@ class TestImageSequence(PillowTestCase):
with pytest.raises(AttributeError): with pytest.raises(AttributeError):
ImageSequence.Iterator(0) ImageSequence.Iterator(0)
def test_iterator(self):
def test_iterator():
with Image.open("Tests/images/multipage.tiff") as im: with Image.open("Tests/images/multipage.tiff") as im:
i = ImageSequence.Iterator(im) i = ImageSequence.Iterator(im)
for index in range(0, im.n_frames): for index in range(0, im.n_frames):
@ -35,29 +35,34 @@ class TestImageSequence(PillowTestCase):
with pytest.raises(StopIteration): with pytest.raises(StopIteration):
next(i) next(i)
def test_iterator_min_frame(self):
def test_iterator_min_frame():
with Image.open("Tests/images/hopper.psd") as im: with Image.open("Tests/images/hopper.psd") as im:
i = ImageSequence.Iterator(im) i = ImageSequence.Iterator(im)
for index in range(1, im.n_frames): for index in range(1, im.n_frames):
assert i[index] == next(i) assert i[index] == next(i)
def _test_multipage_tiff(self):
def _test_multipage_tiff():
with Image.open("Tests/images/multipage.tiff") as im: with Image.open("Tests/images/multipage.tiff") as im:
for index, frame in enumerate(ImageSequence.Iterator(im)): for index, frame in enumerate(ImageSequence.Iterator(im)):
frame.load() frame.load()
assert index == im.tell() assert index == im.tell()
frame.convert("RGB") frame.convert("RGB")
def test_tiff(self):
self._test_multipage_tiff() def test_tiff():
_test_multipage_tiff()
@skip_unless_feature("libtiff") @skip_unless_feature("libtiff")
def test_libtiff(self): def test_libtiff():
TiffImagePlugin.READ_LIBTIFF = True TiffImagePlugin.READ_LIBTIFF = True
self._test_multipage_tiff() _test_multipage_tiff()
TiffImagePlugin.READ_LIBTIFF = False TiffImagePlugin.READ_LIBTIFF = False
def test_consecutive(self):
def test_consecutive():
with Image.open("Tests/images/multipage.tiff") as im: with Image.open("Tests/images/multipage.tiff") as im:
firstFrame = None firstFrame = None
for frame in ImageSequence.Iterator(im): for frame in ImageSequence.Iterator(im):
@ -67,7 +72,8 @@ class TestImageSequence(PillowTestCase):
assert_image_equal(frame, firstFrame) assert_image_equal(frame, firstFrame)
break break
def test_palette_mmap(self):
def test_palette_mmap():
# Using mmap in ImageFile can require to reload the palette. # Using mmap in ImageFile can require to reload the palette.
with Image.open("Tests/images/multipage-mmap.tiff") as im: with Image.open("Tests/images/multipage-mmap.tiff") as im:
color1 = im.getpalette()[0:3] color1 = im.getpalette()[0:3]
@ -75,7 +81,8 @@ class TestImageSequence(PillowTestCase):
color2 = im.getpalette()[0:3] color2 = im.getpalette()[0:3]
assert color1 == color2 assert color1 == color2
def test_all_frames(self):
def test_all_frames():
# Test a single image # Test a single image
with Image.open("Tests/images/iss634.gif") as im: with Image.open("Tests/images/iss634.gif") as im:
ims = ImageSequence.all_frames(im) ims = ImageSequence.all_frames(im)

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,32 +3,33 @@ 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))
_test_equal(1, 2, IFDRational(1, 2))
def test_nonetype():
# Fails if the _delegate function doesn't return a valid function # Fails if the _delegate function doesn't return a valid function
xres = IFDRational(72) xres = IFDRational(72)
@ -41,7 +42,8 @@ class Test_IFDRational(PillowTestCase):
assert xres and 1 assert xres and 1
assert xres and yres assert xres and yres
def test_ifd_rational_save(self):
def test_ifd_rational_save(tmp_path):
methods = (True, False) methods = (True, False)
if not features.check("libtiff"): if not features.check("libtiff"):
methods = (False,) methods = (False,)
@ -50,7 +52,7 @@ class Test_IFDRational(PillowTestCase):
TiffImagePlugin.WRITE_LIBTIFF = libtiff TiffImagePlugin.WRITE_LIBTIFF = libtiff
im = hopper() im = hopper()
out = self.tempfile("temp.tiff") out = str(tmp_path / "temp.tiff")
res = IFDRational(301, 1) res = IFDRational(301, 1)
im.save(out, dpi=(res, res), compression="raw") im.save(out, dpi=(res, res), compression="raw")

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):
def check_upload_similar():
result = hopper("P").convert("RGB") result = hopper("P").convert("RGB")
target = hopper("RGB") target = hopper("RGB")
assert_image_similar(result, target, 0) 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,7 +20,7 @@ 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)

View File

@ -14,8 +14,7 @@ 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)

View File

@ -17,7 +17,7 @@ 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)

View File

@ -18,7 +18,7 @@ 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)

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

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,7 +158,7 @@ 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:

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,7 +449,7 @@ 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:

View File

@ -304,7 +304,7 @@ 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))

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