Merge pull request #4482 from radarhere/pytest

Removed use of PillowTestCase in various tests
This commit is contained in:
Hugo van Kemenade 2020-03-22 23:40:14 +02:00 committed by GitHub
commit 291f1eb1e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1246 additions and 1192 deletions

View File

@ -7,267 +7,280 @@ import time
import pytest import pytest
from PIL import Image, PdfParser from PIL import Image, PdfParser
from .helper import PillowTestCase, hopper from .helper import hopper
class TestFilePdf(PillowTestCase): def helper_save_as_pdf(tmp_path, mode, **kwargs):
def helper_save_as_pdf(self, mode, **kwargs): # Arrange
# Arrange im = hopper(mode)
im = hopper(mode) outfile = str(tmp_path / ("temp_" + mode + ".pdf"))
outfile = self.tempfile("temp_" + mode + ".pdf")
# Act # Act
im.save(outfile, **kwargs) im.save(outfile, **kwargs)
# Assert # Assert
assert os.path.isfile(outfile) assert os.path.isfile(outfile)
assert os.path.getsize(outfile) > 0 assert os.path.getsize(outfile) > 0
with PdfParser.PdfParser(outfile) as pdf: with PdfParser.PdfParser(outfile) as pdf:
if kwargs.get("append_images", False) or kwargs.get("append", False): if kwargs.get("append_images", False) or kwargs.get("append", False):
assert len(pdf.pages) > 1 assert len(pdf.pages) > 1
else: else:
assert len(pdf.pages) > 0 assert len(pdf.pages) > 0
with open(outfile, "rb") as fp: with open(outfile, "rb") as fp:
contents = fp.read() contents = fp.read()
size = tuple( size = tuple(
int(d) int(d) for d in contents.split(b"/MediaBox [ 0 0 ")[1].split(b"]")[0].split()
for d in contents.split(b"/MediaBox [ 0 0 ")[1].split(b"]")[0].split() )
) assert im.size == size
assert im.size == size
return outfile return outfile
def test_monochrome(self):
# Arrange
mode = "1"
# Act / Assert def test_monochrome(tmp_path):
self.helper_save_as_pdf(mode) # Arrange
mode = "1"
def test_greyscale(self): # Act / Assert
# Arrange helper_save_as_pdf(tmp_path, mode)
mode = "L"
# Act / Assert
self.helper_save_as_pdf(mode)
def test_rgb(self): def test_greyscale(tmp_path):
# Arrange # Arrange
mode = "RGB" mode = "L"
# Act / Assert # Act / Assert
self.helper_save_as_pdf(mode) helper_save_as_pdf(tmp_path, mode)
def test_p_mode(self):
# Arrange
mode = "P"
# Act / Assert def test_rgb(tmp_path):
self.helper_save_as_pdf(mode) # Arrange
mode = "RGB"
def test_cmyk_mode(self): # Act / Assert
# Arrange helper_save_as_pdf(tmp_path, mode)
mode = "CMYK"
# Act / Assert
self.helper_save_as_pdf(mode)
def test_unsupported_mode(self): def test_p_mode(tmp_path):
im = hopper("LA") # Arrange
outfile = self.tempfile("temp_LA.pdf") mode = "P"
with pytest.raises(ValueError): # Act / Assert
im.save(outfile) helper_save_as_pdf(tmp_path, mode)
def test_save_all(self):
# Single frame image
self.helper_save_as_pdf("RGB", save_all=True)
# Multiframe image def test_cmyk_mode(tmp_path):
with Image.open("Tests/images/dispose_bgnd.gif") as im: # Arrange
mode = "CMYK"
outfile = self.tempfile("temp.pdf") # Act / Assert
im.save(outfile, save_all=True) helper_save_as_pdf(tmp_path, mode)
assert os.path.isfile(outfile)
assert os.path.getsize(outfile) > 0
# Append images def test_unsupported_mode(tmp_path):
ims = [hopper()] im = hopper("LA")
im.copy().save(outfile, save_all=True, append_images=ims) outfile = str(tmp_path / "temp_LA.pdf")
assert os.path.isfile(outfile) with pytest.raises(ValueError):
assert os.path.getsize(outfile) > 0 im.save(outfile)
# Test appending using a generator
def imGenerator(ims):
yield from ims
im.save(outfile, save_all=True, append_images=imGenerator(ims)) def test_save_all(tmp_path):
# Single frame image
helper_save_as_pdf(tmp_path, "RGB", save_all=True)
# Multiframe image
with Image.open("Tests/images/dispose_bgnd.gif") as im:
outfile = str(tmp_path / "temp.pdf")
im.save(outfile, save_all=True)
assert os.path.isfile(outfile) assert os.path.isfile(outfile)
assert os.path.getsize(outfile) > 0 assert os.path.getsize(outfile) > 0
# Append JPEG images # Append images
with Image.open("Tests/images/flower.jpg") as jpeg: ims = [hopper()]
jpeg.save(outfile, save_all=True, append_images=[jpeg.copy()]) im.copy().save(outfile, save_all=True, append_images=ims)
assert os.path.isfile(outfile) assert os.path.isfile(outfile)
assert os.path.getsize(outfile) > 0 assert os.path.getsize(outfile) > 0
def test_multiframe_normal_save(self): # Test appending using a generator
# Test saving a multiframe image without save_all def imGenerator(ims):
with Image.open("Tests/images/dispose_bgnd.gif") as im: yield from ims
outfile = self.tempfile("temp.pdf") im.save(outfile, save_all=True, append_images=imGenerator(ims))
im.save(outfile)
assert os.path.isfile(outfile) assert os.path.isfile(outfile)
assert os.path.getsize(outfile) > 0 assert os.path.getsize(outfile) > 0
def test_pdf_open(self): # Append JPEG images
# fail on a buffer full of null bytes with Image.open("Tests/images/flower.jpg") as jpeg:
with pytest.raises(PdfParser.PdfFormatError): jpeg.save(outfile, save_all=True, append_images=[jpeg.copy()])
PdfParser.PdfParser(buf=bytearray(65536))
# make an empty PDF object assert os.path.isfile(outfile)
with PdfParser.PdfParser() as empty_pdf: assert os.path.getsize(outfile) > 0
assert len(empty_pdf.pages) == 0
assert len(empty_pdf.info) == 0
assert not empty_pdf.should_close_buf
assert not empty_pdf.should_close_file
# make a PDF file
pdf_filename = self.helper_save_as_pdf("RGB")
# open the PDF file def test_multiframe_normal_save(tmp_path):
with PdfParser.PdfParser(filename=pdf_filename) as hopper_pdf: # Test saving a multiframe image without save_all
with Image.open("Tests/images/dispose_bgnd.gif") as im:
outfile = str(tmp_path / "temp.pdf")
im.save(outfile)
assert os.path.isfile(outfile)
assert os.path.getsize(outfile) > 0
def test_pdf_open(tmp_path):
# fail on a buffer full of null bytes
with pytest.raises(PdfParser.PdfFormatError):
PdfParser.PdfParser(buf=bytearray(65536))
# make an empty PDF object
with PdfParser.PdfParser() as empty_pdf:
assert len(empty_pdf.pages) == 0
assert len(empty_pdf.info) == 0
assert not empty_pdf.should_close_buf
assert not empty_pdf.should_close_file
# make a PDF file
pdf_filename = helper_save_as_pdf(tmp_path, "RGB")
# open the PDF file
with PdfParser.PdfParser(filename=pdf_filename) as hopper_pdf:
assert len(hopper_pdf.pages) == 1
assert hopper_pdf.should_close_buf
assert hopper_pdf.should_close_file
# read a PDF file from a buffer with a non-zero offset
with open(pdf_filename, "rb") as f:
content = b"xyzzy" + f.read()
with PdfParser.PdfParser(buf=content, start_offset=5) as hopper_pdf:
assert len(hopper_pdf.pages) == 1
assert not hopper_pdf.should_close_buf
assert not hopper_pdf.should_close_file
# read a PDF file from an already open file
with open(pdf_filename, "rb") as f:
with PdfParser.PdfParser(f=f) as hopper_pdf:
assert len(hopper_pdf.pages) == 1 assert len(hopper_pdf.pages) == 1
assert hopper_pdf.should_close_buf assert hopper_pdf.should_close_buf
assert hopper_pdf.should_close_file
# read a PDF file from a buffer with a non-zero offset
with open(pdf_filename, "rb") as f:
content = b"xyzzy" + f.read()
with PdfParser.PdfParser(buf=content, start_offset=5) as hopper_pdf:
assert len(hopper_pdf.pages) == 1
assert not hopper_pdf.should_close_buf
assert not hopper_pdf.should_close_file assert not hopper_pdf.should_close_file
# read a PDF file from an already open file
with open(pdf_filename, "rb") as f:
with PdfParser.PdfParser(f=f) as hopper_pdf:
assert len(hopper_pdf.pages) == 1
assert hopper_pdf.should_close_buf
assert not hopper_pdf.should_close_file
def test_pdf_append_fails_on_nonexistent_file(self): def test_pdf_append_fails_on_nonexistent_file():
im = hopper("RGB") im = hopper("RGB")
with tempfile.TemporaryDirectory() as temp_dir: with tempfile.TemporaryDirectory() as temp_dir:
with pytest.raises(IOError): with pytest.raises(IOError):
im.save(os.path.join(temp_dir, "nonexistent.pdf"), append=True) im.save(os.path.join(temp_dir, "nonexistent.pdf"), append=True)
def check_pdf_pages_consistency(self, pdf):
pages_info = pdf.read_indirect(pdf.pages_ref)
assert b"Parent" not in pages_info
assert b"Kids" in pages_info
kids_not_used = pages_info[b"Kids"]
for page_ref in pdf.pages:
while True:
if page_ref in kids_not_used:
kids_not_used.remove(page_ref)
page_info = pdf.read_indirect(page_ref)
assert b"Parent" in page_info
page_ref = page_info[b"Parent"]
if page_ref == pdf.pages_ref:
break
assert pdf.pages_ref == page_info[b"Parent"]
assert kids_not_used == []
def test_pdf_append(self): def check_pdf_pages_consistency(pdf):
# make a PDF file pages_info = pdf.read_indirect(pdf.pages_ref)
pdf_filename = self.helper_save_as_pdf("RGB", producer="PdfParser") assert b"Parent" not in pages_info
assert b"Kids" in pages_info
kids_not_used = pages_info[b"Kids"]
for page_ref in pdf.pages:
while True:
if page_ref in kids_not_used:
kids_not_used.remove(page_ref)
page_info = pdf.read_indirect(page_ref)
assert b"Parent" in page_info
page_ref = page_info[b"Parent"]
if page_ref == pdf.pages_ref:
break
assert pdf.pages_ref == page_info[b"Parent"]
assert kids_not_used == []
# open it, check pages and info
with PdfParser.PdfParser(pdf_filename, mode="r+b") as pdf:
assert len(pdf.pages) == 1
assert len(pdf.info) == 4
assert pdf.info.Title == os.path.splitext(os.path.basename(pdf_filename))[0]
assert pdf.info.Producer == "PdfParser"
assert b"CreationDate" in pdf.info
assert b"ModDate" in pdf.info
self.check_pdf_pages_consistency(pdf)
# append some info def test_pdf_append(tmp_path):
pdf.info.Title = "abc" # make a PDF file
pdf.info.Author = "def" pdf_filename = helper_save_as_pdf(tmp_path, "RGB", producer="PdfParser")
pdf.info.Subject = "ghi\uABCD"
pdf.info.Keywords = "qw)e\\r(ty"
pdf.info.Creator = "hopper()"
pdf.start_writing()
pdf.write_xref_and_trailer()
# open it again, check pages and info again # open it, check pages and info
with PdfParser.PdfParser(pdf_filename) as pdf: with PdfParser.PdfParser(pdf_filename, mode="r+b") as pdf:
assert len(pdf.pages) == 1 assert len(pdf.pages) == 1
assert len(pdf.info) == 8 assert len(pdf.info) == 4
assert pdf.info.Title == "abc" assert pdf.info.Title == os.path.splitext(os.path.basename(pdf_filename))[0]
assert b"CreationDate" in pdf.info assert pdf.info.Producer == "PdfParser"
assert b"ModDate" in pdf.info assert b"CreationDate" in pdf.info
self.check_pdf_pages_consistency(pdf) assert b"ModDate" in pdf.info
check_pdf_pages_consistency(pdf)
# append two images # append some info
mode_CMYK = hopper("CMYK") pdf.info.Title = "abc"
mode_P = hopper("P") pdf.info.Author = "def"
mode_CMYK.save(pdf_filename, append=True, save_all=True, append_images=[mode_P]) pdf.info.Subject = "ghi\uABCD"
pdf.info.Keywords = "qw)e\\r(ty"
pdf.info.Creator = "hopper()"
pdf.start_writing()
pdf.write_xref_and_trailer()
# open the PDF again, check pages and info again # open it again, check pages and info again
with PdfParser.PdfParser(pdf_filename) as pdf: with PdfParser.PdfParser(pdf_filename) as pdf:
assert len(pdf.pages) == 3 assert len(pdf.pages) == 1
assert len(pdf.info) == 8 assert len(pdf.info) == 8
assert PdfParser.decode_text(pdf.info[b"Title"]) == "abc" assert pdf.info.Title == "abc"
assert pdf.info.Title == "abc" assert b"CreationDate" in pdf.info
assert pdf.info.Producer == "PdfParser" assert b"ModDate" in pdf.info
assert pdf.info.Keywords == "qw)e\\r(ty" check_pdf_pages_consistency(pdf)
assert pdf.info.Subject == "ghi\uABCD"
assert b"CreationDate" in pdf.info
assert b"ModDate" in pdf.info
self.check_pdf_pages_consistency(pdf)
def test_pdf_info(self): # append two images
# make a PDF file mode_CMYK = hopper("CMYK")
pdf_filename = self.helper_save_as_pdf( mode_P = hopper("P")
"RGB", mode_CMYK.save(pdf_filename, append=True, save_all=True, append_images=[mode_P])
title="title",
author="author",
subject="subject",
keywords="keywords",
creator="creator",
producer="producer",
creationDate=time.strptime("2000", "%Y"),
modDate=time.strptime("2001", "%Y"),
)
# open it, check pages and info # open the PDF again, check pages and info again
with PdfParser.PdfParser(pdf_filename) as pdf: with PdfParser.PdfParser(pdf_filename) as pdf:
assert len(pdf.info) == 8 assert len(pdf.pages) == 3
assert pdf.info.Title == "title" assert len(pdf.info) == 8
assert pdf.info.Author == "author" assert PdfParser.decode_text(pdf.info[b"Title"]) == "abc"
assert pdf.info.Subject == "subject" assert pdf.info.Title == "abc"
assert pdf.info.Keywords == "keywords" assert pdf.info.Producer == "PdfParser"
assert pdf.info.Creator == "creator" assert pdf.info.Keywords == "qw)e\\r(ty"
assert pdf.info.Producer == "producer" assert pdf.info.Subject == "ghi\uABCD"
assert pdf.info.CreationDate == time.strptime("2000", "%Y") assert b"CreationDate" in pdf.info
assert pdf.info.ModDate == time.strptime("2001", "%Y") assert b"ModDate" in pdf.info
self.check_pdf_pages_consistency(pdf) check_pdf_pages_consistency(pdf)
def test_pdf_append_to_bytesio(self):
im = hopper("RGB") def test_pdf_info(tmp_path):
f = io.BytesIO() # make a PDF file
im.save(f, format="PDF") pdf_filename = helper_save_as_pdf(
initial_size = len(f.getvalue()) tmp_path,
assert initial_size > 0 "RGB",
im = hopper("P") title="title",
f = io.BytesIO(f.getvalue()) author="author",
im.save(f, format="PDF", append=True) subject="subject",
assert len(f.getvalue()) > initial_size keywords="keywords",
creator="creator",
producer="producer",
creationDate=time.strptime("2000", "%Y"),
modDate=time.strptime("2001", "%Y"),
)
# open it, check pages and info
with PdfParser.PdfParser(pdf_filename) as pdf:
assert len(pdf.info) == 8
assert pdf.info.Title == "title"
assert pdf.info.Author == "author"
assert pdf.info.Subject == "subject"
assert pdf.info.Keywords == "keywords"
assert pdf.info.Creator == "creator"
assert pdf.info.Producer == "producer"
assert pdf.info.CreationDate == time.strptime("2000", "%Y")
assert pdf.info.ModDate == time.strptime("2001", "%Y")
check_pdf_pages_consistency(pdf)
def test_pdf_append_to_bytesio():
im = hopper("RGB")
f = io.BytesIO()
im.save(f, format="PDF")
initial_size = len(f.getvalue())
assert initial_size > 0
im = hopper("P")
f = io.BytesIO(f.getvalue())
im.save(f, format="PDF", append=True)
assert len(f.getvalue()) > initial_size

View File

@ -5,204 +5,202 @@ from itertools import product
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import PillowTestCase, assert_image_equal, hopper from .helper import assert_image_equal, hopper
_TGA_DIR = os.path.join("Tests", "images", "tga") _TGA_DIR = os.path.join("Tests", "images", "tga")
_TGA_DIR_COMMON = os.path.join(_TGA_DIR, "common") _TGA_DIR_COMMON = os.path.join(_TGA_DIR, "common")
class TestFileTga(PillowTestCase): _MODES = ("L", "LA", "P", "RGB", "RGBA")
_ORIGINS = ("tl", "bl")
_MODES = ("L", "LA", "P", "RGB", "RGBA") _ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1}
_ORIGINS = ("tl", "bl")
_ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1}
def test_sanity(self): def test_sanity(tmp_path):
for mode in self._MODES: for mode in _MODES:
png_paths = glob(
os.path.join(_TGA_DIR_COMMON, "*x*_{}.png".format(mode.lower()))
)
for png_path in png_paths: def roundtrip(original_im):
with Image.open(png_path) as reference_im: out = str(tmp_path / "temp.tga")
assert reference_im.mode == mode
path_no_ext = os.path.splitext(png_path)[0] original_im.save(out, rle=rle)
for origin, rle in product(self._ORIGINS, (True, False)): with Image.open(out) as saved_im:
tga_path = "{}_{}_{}.tga".format( if rle:
path_no_ext, origin, "rle" if rle else "raw" assert (
saved_im.info["compression"] == original_im.info["compression"]
)
assert saved_im.info["orientation"] == original_im.info["orientation"]
if mode == "P":
assert saved_im.getpalette() == original_im.getpalette()
assert_image_equal(saved_im, original_im)
png_paths = glob(
os.path.join(_TGA_DIR_COMMON, "*x*_{}.png".format(mode.lower()))
)
for png_path in png_paths:
with Image.open(png_path) as reference_im:
assert reference_im.mode == mode
path_no_ext = os.path.splitext(png_path)[0]
for origin, rle in product(_ORIGINS, (True, False)):
tga_path = "{}_{}_{}.tga".format(
path_no_ext, origin, "rle" if rle else "raw"
)
with Image.open(tga_path) as original_im:
assert original_im.format == "TGA"
assert original_im.get_format_mimetype() == "image/x-tga"
if rle:
assert original_im.info["compression"] == "tga_rle"
assert (
original_im.info["orientation"]
== _ORIGIN_TO_ORIENTATION[origin]
) )
if mode == "P":
assert original_im.getpalette() == reference_im.getpalette()
with Image.open(tga_path) as original_im: assert_image_equal(original_im, reference_im)
assert original_im.format == "TGA"
assert original_im.get_format_mimetype() == "image/x-tga"
if rle:
assert original_im.info["compression"] == "tga_rle"
assert (
original_im.info["orientation"]
== self._ORIGIN_TO_ORIENTATION[origin]
)
if mode == "P":
assert (
original_im.getpalette()
== reference_im.getpalette()
)
assert_image_equal(original_im, reference_im) roundtrip(original_im)
# Generate a new test name every time so the
# test will not fail with permission error
# on Windows.
out = self.tempfile("temp.tga")
original_im.save(out, rle=rle) def test_id_field():
with Image.open(out) as saved_im: # tga file with id field
if rle: test_file = "Tests/images/tga_id_field.tga"
assert (
saved_im.info["compression"]
== original_im.info["compression"]
)
assert (
saved_im.info["orientation"]
== original_im.info["orientation"]
)
if mode == "P":
assert (
saved_im.getpalette()
== original_im.getpalette()
)
assert_image_equal(saved_im, original_im) # Act
with Image.open(test_file) as im:
def test_id_field(self): # Assert
# tga file with id field assert im.size == (100, 100)
test_file = "Tests/images/tga_id_field.tga"
# Act
with Image.open(test_file) as im:
# Assert def test_id_field_rle():
assert im.size == (100, 100) # tga file with id field
test_file = "Tests/images/rgb32rle.tga"
def test_id_field_rle(self): # Act
# tga file with id field with Image.open(test_file) as im:
test_file = "Tests/images/rgb32rle.tga"
# Act # Assert
with Image.open(test_file) as im: assert im.size == (199, 199)
# Assert
assert im.size == (199, 199)
def test_save(self): def test_save(tmp_path):
test_file = "Tests/images/tga_id_field.tga" test_file = "Tests/images/tga_id_field.tga"
with Image.open(test_file) as im: with Image.open(test_file) as im:
out = self.tempfile("temp.tga") out = str(tmp_path / "temp.tga")
# Save # Save
im.save(out) im.save(out)
with Image.open(out) as test_im:
assert test_im.size == (100, 100)
assert test_im.info["id_section"] == im.info["id_section"]
# RGBA save
im.convert("RGBA").save(out)
with Image.open(out) as test_im: with Image.open(out) as test_im:
assert test_im.size == (100, 100) assert test_im.size == (100, 100)
assert test_im.info["id_section"] == im.info["id_section"]
def test_save_wrong_mode(self):
im = hopper("PA")
out = self.tempfile("temp.tga")
with pytest.raises(OSError):
im.save(out)
def test_save_id_section(self):
test_file = "Tests/images/rgb32rle.tga"
with Image.open(test_file) as im:
out = self.tempfile("temp.tga")
# Check there is no id section
im.save(out)
with Image.open(out) as test_im:
assert "id_section" not in test_im.info
# Save with custom id section
im.save(out, id_section=b"Test content")
with Image.open(out) as test_im:
assert test_im.info["id_section"] == b"Test content"
# Save with custom id section greater than 255 characters
id_section = b"Test content" * 25
pytest.warns(UserWarning, lambda: im.save(out, id_section=id_section))
with Image.open(out) as test_im:
assert test_im.info["id_section"] == id_section[:255]
test_file = "Tests/images/tga_id_field.tga"
with Image.open(test_file) as im:
# Save with no id section
im.save(out, id_section="")
with Image.open(out) as test_im:
assert "id_section" not in test_im.info
def test_save_orientation(self):
test_file = "Tests/images/rgb32rle.tga"
out = self.tempfile("temp.tga")
with Image.open(test_file) as im:
assert im.info["orientation"] == -1
im.save(out, orientation=1)
with Image.open(out) as test_im:
assert test_im.info["orientation"] == 1
def test_save_rle(self):
test_file = "Tests/images/rgb32rle.tga"
with Image.open(test_file) as im:
assert im.info["compression"] == "tga_rle"
out = self.tempfile("temp.tga")
# Save
im.save(out)
with Image.open(out) as test_im:
assert test_im.size == (199, 199)
assert test_im.info["compression"] == "tga_rle"
# Save without compression
im.save(out, compression=None)
with Image.open(out) as test_im:
assert "compression" not in test_im.info
# RGBA save # RGBA save
im.convert("RGBA").save(out) im.convert("RGBA").save(out)
with Image.open(out) as test_im: with Image.open(out) as test_im:
assert test_im.size == (199, 199) assert test_im.size == (100, 100)
test_file = "Tests/images/tga_id_field.tga"
with Image.open(test_file) as im:
assert "compression" not in im.info
# Save with compression def test_save_wrong_mode(tmp_path):
im.save(out, compression="tga_rle") im = hopper("PA")
with Image.open(out) as test_im: out = str(tmp_path / "temp.tga")
assert test_im.info["compression"] == "tga_rle"
def test_save_l_transparency(self): with pytest.raises(OSError):
# There are 559 transparent pixels in la.tga. im.save(out)
num_transparent = 559
in_file = "Tests/images/la.tga"
with Image.open(in_file) as im:
assert im.mode == "LA"
assert im.getchannel("A").getcolors()[0][0] == num_transparent
out = self.tempfile("temp.tga") def test_save_id_section(tmp_path):
im.save(out) test_file = "Tests/images/rgb32rle.tga"
with Image.open(test_file) as im:
out = str(tmp_path / "temp.tga")
with Image.open(out) as test_im: # Check there is no id section
assert test_im.mode == "LA" im.save(out)
assert test_im.getchannel("A").getcolors()[0][0] == num_transparent with Image.open(out) as test_im:
assert "id_section" not in test_im.info
assert_image_equal(im, test_im) # Save with custom id section
im.save(out, id_section=b"Test content")
with Image.open(out) as test_im:
assert test_im.info["id_section"] == b"Test content"
# Save with custom id section greater than 255 characters
id_section = b"Test content" * 25
pytest.warns(UserWarning, lambda: im.save(out, id_section=id_section))
with Image.open(out) as test_im:
assert test_im.info["id_section"] == id_section[:255]
test_file = "Tests/images/tga_id_field.tga"
with Image.open(test_file) as im:
# Save with no id section
im.save(out, id_section="")
with Image.open(out) as test_im:
assert "id_section" not in test_im.info
def test_save_orientation(tmp_path):
test_file = "Tests/images/rgb32rle.tga"
out = str(tmp_path / "temp.tga")
with Image.open(test_file) as im:
assert im.info["orientation"] == -1
im.save(out, orientation=1)
with Image.open(out) as test_im:
assert test_im.info["orientation"] == 1
def test_save_rle(tmp_path):
test_file = "Tests/images/rgb32rle.tga"
with Image.open(test_file) as im:
assert im.info["compression"] == "tga_rle"
out = str(tmp_path / "temp.tga")
# Save
im.save(out)
with Image.open(out) as test_im:
assert test_im.size == (199, 199)
assert test_im.info["compression"] == "tga_rle"
# Save without compression
im.save(out, compression=None)
with Image.open(out) as test_im:
assert "compression" not in test_im.info
# RGBA save
im.convert("RGBA").save(out)
with Image.open(out) as test_im:
assert test_im.size == (199, 199)
test_file = "Tests/images/tga_id_field.tga"
with Image.open(test_file) as im:
assert "compression" not in im.info
# Save with compression
im.save(out, compression="tga_rle")
with Image.open(out) as test_im:
assert test_im.info["compression"] == "tga_rle"
def test_save_l_transparency(tmp_path):
# There are 559 transparent pixels in la.tga.
num_transparent = 559
in_file = "Tests/images/la.tga"
with Image.open(in_file) as im:
assert im.mode == "LA"
assert im.getchannel("A").getcolors()[0][0] == num_transparent
out = str(tmp_path / "temp.tga")
im.save(out)
with Image.open(out) as test_im:
assert test_im.mode == "LA"
assert test_im.getchannel("A").getcolors()[0][0] == num_transparent
assert_image_equal(im, test_im)

View File

@ -2,7 +2,6 @@ import pytest
from PIL import Image, WebPImagePlugin from PIL import Image, WebPImagePlugin
from .helper import ( from .helper import (
PillowTestCase,
assert_image_similar, assert_image_similar,
assert_image_similar_tofile, assert_image_similar_tofile,
hopper, hopper,
@ -17,23 +16,21 @@ except ImportError:
HAVE_WEBP = False HAVE_WEBP = False
class TestUnsupportedWebp(PillowTestCase): class TestUnsupportedWebp:
def test_unsupported(self): def test_unsupported(self):
if HAVE_WEBP: if HAVE_WEBP:
WebPImagePlugin.SUPPORTED = False WebPImagePlugin.SUPPORTED = False
file_path = "Tests/images/hopper.webp" file_path = "Tests/images/hopper.webp"
pytest.warns( pytest.warns(UserWarning, lambda: pytest.raises(IOError, Image.open, file_path))
UserWarning, lambda: self.assertRaises(IOError, Image.open, file_path)
)
if HAVE_WEBP: if HAVE_WEBP:
WebPImagePlugin.SUPPORTED = True WebPImagePlugin.SUPPORTED = True
@skip_unless_feature("webp") @skip_unless_feature("webp")
class TestFileWebp(PillowTestCase): class TestFileWebp:
def setUp(self): def setup_method(self):
self.rgb_mode = "RGB" self.rgb_mode = "RGB"
def test_version(self): def test_version(self):
@ -57,13 +54,13 @@ class TestFileWebp(PillowTestCase):
# dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm # dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm
assert_image_similar_tofile(image, "Tests/images/hopper_webp_bits.ppm", 1.0) assert_image_similar_tofile(image, "Tests/images/hopper_webp_bits.ppm", 1.0)
def test_write_rgb(self): def test_write_rgb(self, tmp_path):
""" """
Can we write a RGB mode file to webp without error. Can we write a RGB mode file to webp without error.
Does it have the bits we expect? Does it have the bits we expect?
""" """
temp_file = self.tempfile("temp.webp") temp_file = str(tmp_path / "temp.webp")
hopper(self.rgb_mode).save(temp_file) hopper(self.rgb_mode).save(temp_file)
with Image.open(temp_file) as image: with Image.open(temp_file) as image:
@ -86,13 +83,13 @@ class TestFileWebp(PillowTestCase):
target = hopper(self.rgb_mode) target = hopper(self.rgb_mode)
assert_image_similar(image, target, 12.0) assert_image_similar(image, target, 12.0)
def test_write_unsupported_mode_L(self): def test_write_unsupported_mode_L(self, tmp_path):
""" """
Saving a black-and-white file to WebP format should work, and be Saving a black-and-white file to WebP format should work, and be
similar to the original file. similar to the original file.
""" """
temp_file = self.tempfile("temp.webp") temp_file = str(tmp_path / "temp.webp")
hopper("L").save(temp_file) hopper("L").save(temp_file)
with Image.open(temp_file) as image: with Image.open(temp_file) as image:
assert image.mode == self.rgb_mode assert image.mode == self.rgb_mode
@ -105,13 +102,13 @@ class TestFileWebp(PillowTestCase):
assert_image_similar(image, target, 10.0) assert_image_similar(image, target, 10.0)
def test_write_unsupported_mode_P(self): def test_write_unsupported_mode_P(self, tmp_path):
""" """
Saving a palette-based file to WebP format should work, and be Saving a palette-based file to WebP format should work, and be
similar to the original file. similar to the original file.
""" """
temp_file = self.tempfile("temp.webp") temp_file = str(tmp_path / "temp.webp")
hopper("P").save(temp_file) hopper("P").save(temp_file)
with Image.open(temp_file) as image: with Image.open(temp_file) as image:
assert image.mode == self.rgb_mode assert image.mode == self.rgb_mode
@ -146,10 +143,10 @@ class TestFileWebp(PillowTestCase):
with pytest.raises(TypeError): with pytest.raises(TypeError):
_webp.WebPDecode() _webp.WebPDecode()
def test_no_resource_warning(self): def test_no_resource_warning(self, tmp_path):
file_path = "Tests/images/hopper.webp" file_path = "Tests/images/hopper.webp"
with Image.open(file_path) as image: with Image.open(file_path) as image:
temp_file = self.tempfile("temp.webp") temp_file = str(tmp_path / "temp.webp")
pytest.warns(None, image.save, temp_file) pytest.warns(None, image.save, temp_file)
def test_file_pointer_could_be_reused(self): def test_file_pointer_could_be_reused(self):
@ -160,16 +157,16 @@ class TestFileWebp(PillowTestCase):
@skip_unless_feature("webp") @skip_unless_feature("webp")
@skip_unless_feature("webp_anim") @skip_unless_feature("webp_anim")
def test_background_from_gif(self): def test_background_from_gif(self, tmp_path):
with Image.open("Tests/images/chi.gif") as im: with Image.open("Tests/images/chi.gif") as im:
original_value = im.convert("RGB").getpixel((1, 1)) original_value = im.convert("RGB").getpixel((1, 1))
# Save as WEBP # Save as WEBP
out_webp = self.tempfile("temp.webp") out_webp = str(tmp_path / "temp.webp")
im.save(out_webp, save_all=True) im.save(out_webp, save_all=True)
# Save as GIF # Save as GIF
out_gif = self.tempfile("temp.gif") out_gif = str(tmp_path / "temp.gif")
Image.open(out_webp).save(out_gif) Image.open(out_webp).save(out_gif)
with Image.open(out_gif) as reread: with Image.open(out_gif) as reread:

View File

@ -1,238 +1,249 @@
import pytest import pytest
from PIL import Image, ImageMath, ImageMode from PIL import Image, ImageMath, ImageMode
from .helper import PillowTestCase, convert_to_comparable from .helper import convert_to_comparable
# There are several internal implementations
remarkable_factors = [
# special implementations
1,
2,
3,
4,
5,
6,
# 1xN implementation
(1, 2),
(1, 3),
(1, 4),
(1, 7),
# Nx1 implementation
(2, 1),
(3, 1),
(4, 1),
(7, 1),
# general implementation with different paths
(4, 6),
(5, 6),
(4, 7),
(5, 7),
(19, 17),
]
gradients_image = Image.open("Tests/images/radial_gradients.png")
gradients_image.load()
class TestImageReduce(PillowTestCase): def test_args_factor():
# There are several internal implementations im = Image.new("L", (10, 10))
remarkable_factors = [
# special implementations
1,
2,
3,
4,
5,
6,
# 1xN implementation
(1, 2),
(1, 3),
(1, 4),
(1, 7),
# Nx1 implementation
(2, 1),
(3, 1),
(4, 1),
(7, 1),
# general implementation with different paths
(4, 6),
(5, 6),
(4, 7),
(5, 7),
(19, 17),
]
@classmethod assert (4, 4) == im.reduce(3).size
def setUpClass(cls): assert (4, 10) == im.reduce((3, 1)).size
cls.gradients_image = Image.open("Tests/images/radial_gradients.png") assert (10, 4) == im.reduce((1, 3)).size
cls.gradients_image.load()
def test_args_factor(self): with pytest.raises(ValueError):
im = Image.new("L", (10, 10)) im.reduce(0)
with pytest.raises(TypeError):
im.reduce(2.0)
with pytest.raises(ValueError):
im.reduce((0, 10))
assert (4, 4) == im.reduce(3).size
assert (4, 10) == im.reduce((3, 1)).size
assert (10, 4) == im.reduce((1, 3)).size
with pytest.raises(ValueError): def test_args_box():
im.reduce(0) im = Image.new("L", (10, 10))
with pytest.raises(TypeError):
im.reduce(2.0)
with pytest.raises(ValueError):
im.reduce((0, 10))
def test_args_box(self): assert (5, 5) == im.reduce(2, (0, 0, 10, 10)).size
im = Image.new("L", (10, 10)) assert (1, 1) == im.reduce(2, (5, 5, 6, 6)).size
assert (5, 5) == im.reduce(2, (0, 0, 10, 10)).size with pytest.raises(TypeError):
assert (1, 1) == im.reduce(2, (5, 5, 6, 6)).size im.reduce(2, "stri")
with pytest.raises(TypeError):
im.reduce(2, 2)
with pytest.raises(ValueError):
im.reduce(2, (0, 0, 11, 10))
with pytest.raises(ValueError):
im.reduce(2, (0, 0, 10, 11))
with pytest.raises(ValueError):
im.reduce(2, (-1, 0, 10, 10))
with pytest.raises(ValueError):
im.reduce(2, (0, -1, 10, 10))
with pytest.raises(ValueError):
im.reduce(2, (0, 5, 10, 5))
with pytest.raises(ValueError):
im.reduce(2, (5, 0, 5, 10))
with pytest.raises(TypeError):
im.reduce(2, "stri")
with pytest.raises(TypeError):
im.reduce(2, 2)
with pytest.raises(ValueError):
im.reduce(2, (0, 0, 11, 10))
with pytest.raises(ValueError):
im.reduce(2, (0, 0, 10, 11))
with pytest.raises(ValueError):
im.reduce(2, (-1, 0, 10, 10))
with pytest.raises(ValueError):
im.reduce(2, (0, -1, 10, 10))
with pytest.raises(ValueError):
im.reduce(2, (0, 5, 10, 5))
with pytest.raises(ValueError):
im.reduce(2, (5, 0, 5, 10))
def test_unsupported_modes(self): def test_unsupported_modes():
im = Image.new("P", (10, 10)) im = Image.new("P", (10, 10))
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.reduce(3) im.reduce(3)
im = Image.new("1", (10, 10)) im = Image.new("1", (10, 10))
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.reduce(3) im.reduce(3)
im = Image.new("I;16", (10, 10)) im = Image.new("I;16", (10, 10))
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.reduce(3) im.reduce(3)
def get_image(self, mode):
mode_info = ImageMode.getmode(mode)
if mode_info.basetype == "L":
bands = [self.gradients_image]
for _ in mode_info.bands[1:]:
# rotate previous image
band = bands[-1].transpose(Image.ROTATE_90)
bands.append(band)
# Correct alpha channel by transforming completely transparent pixels.
# Low alpha values also emphasize error after alpha multiplication.
if mode.endswith("A"):
bands[-1] = bands[-1].point(lambda x: int(85 + x / 1.5))
im = Image.merge(mode, bands)
else:
assert len(mode_info.bands) == 1
im = self.gradients_image.convert(mode)
# change the height to make a not-square image
return im.crop((0, 0, im.width, im.height - 5))
def compare_reduce_with_box(self, im, factor): def get_image(mode):
box = (11, 13, 146, 164) mode_info = ImageMode.getmode(mode)
reduced = im.reduce(factor, box=box) if mode_info.basetype == "L":
reference = im.crop(box).reduce(factor) bands = [gradients_image]
assert reduced == reference for _ in mode_info.bands[1:]:
# rotate previous image
band = bands[-1].transpose(Image.ROTATE_90)
bands.append(band)
# Correct alpha channel by transforming completely transparent pixels.
# Low alpha values also emphasize error after alpha multiplication.
if mode.endswith("A"):
bands[-1] = bands[-1].point(lambda x: int(85 + x / 1.5))
im = Image.merge(mode, bands)
else:
assert len(mode_info.bands) == 1
im = gradients_image.convert(mode)
# change the height to make a not-square image
return im.crop((0, 0, im.width, im.height - 5))
def compare_reduce_with_reference(self, im, factor, average_diff=0.4, max_diff=1):
"""Image.reduce() should look very similar to Image.resize(BOX).
A reference image is compiled from a large source area def compare_reduce_with_box(im, factor):
and possible last column and last row. box = (11, 13, 146, 164)
+-----------+ reduced = im.reduce(factor, box=box)
|..........c| reference = im.crop(box).reduce(factor)
|..........c| assert reduced == reference
|..........c|
|rrrrrrrrrrp|
+-----------+
"""
reduced = im.reduce(factor)
if not isinstance(factor, (list, tuple)):
factor = (factor, factor)
reference = Image.new(im.mode, reduced.size) def compare_reduce_with_reference(im, factor, average_diff=0.4, max_diff=1):
area_size = (im.size[0] // factor[0], im.size[1] // factor[1]) """Image.reduce() should look very similar to Image.resize(BOX).
area_box = (0, 0, area_size[0] * factor[0], area_size[1] * factor[1])
area = im.resize(area_size, Image.BOX, area_box)
reference.paste(area, (0, 0))
if area_size[0] < reduced.size[0]: A reference image is compiled from a large source area
assert reduced.size[0] - area_size[0] == 1 and possible last column and last row.
last_column_box = (area_box[2], 0, im.size[0], area_box[3]) +-----------+
last_column = im.resize((1, area_size[1]), Image.BOX, last_column_box) |..........c|
reference.paste(last_column, (area_size[0], 0)) |..........c|
|..........c|
|rrrrrrrrrrp|
+-----------+
"""
reduced = im.reduce(factor)
if area_size[1] < reduced.size[1]: if not isinstance(factor, (list, tuple)):
assert reduced.size[1] - area_size[1] == 1 factor = (factor, factor)
last_row_box = (0, area_box[3], area_box[2], im.size[1])
last_row = im.resize((area_size[0], 1), Image.BOX, last_row_box)
reference.paste(last_row, (0, area_size[1]))
if area_size[0] < reduced.size[0] and area_size[1] < reduced.size[1]: reference = Image.new(im.mode, reduced.size)
last_pixel_box = (area_box[2], area_box[3], im.size[0], im.size[1]) area_size = (im.size[0] // factor[0], im.size[1] // factor[1])
last_pixel = im.resize((1, 1), Image.BOX, last_pixel_box) area_box = (0, 0, area_size[0] * factor[0], area_size[1] * factor[1])
reference.paste(last_pixel, area_size) area = im.resize(area_size, Image.BOX, area_box)
reference.paste(area, (0, 0))
self.assert_compare_images(reduced, reference, average_diff, max_diff) if area_size[0] < reduced.size[0]:
assert reduced.size[0] - area_size[0] == 1
last_column_box = (area_box[2], 0, im.size[0], area_box[3])
last_column = im.resize((1, area_size[1]), Image.BOX, last_column_box)
reference.paste(last_column, (area_size[0], 0))
def assert_compare_images(self, a, b, max_average_diff, max_diff=255): if area_size[1] < reduced.size[1]:
assert a.mode == b.mode, "got mode %r, expected %r" % (a.mode, b.mode) assert reduced.size[1] - area_size[1] == 1
assert a.size == b.size, "got size %r, expected %r" % (a.size, b.size) last_row_box = (0, area_box[3], area_box[2], im.size[1])
last_row = im.resize((area_size[0], 1), Image.BOX, last_row_box)
reference.paste(last_row, (0, area_size[1]))
a, b = convert_to_comparable(a, b) if area_size[0] < reduced.size[0] and area_size[1] < reduced.size[1]:
last_pixel_box = (area_box[2], area_box[3], im.size[0], im.size[1])
last_pixel = im.resize((1, 1), Image.BOX, last_pixel_box)
reference.paste(last_pixel, area_size)
bands = ImageMode.getmode(a.mode).bands assert_compare_images(reduced, reference, average_diff, max_diff)
for band, ach, bch in zip(bands, a.split(), b.split()):
ch_diff = ImageMath.eval("convert(abs(a - b), 'L')", a=ach, b=bch)
ch_hist = ch_diff.histogram()
average_diff = sum(i * num for i, num in enumerate(ch_hist)) / (
a.size[0] * a.size[1]
)
msg = "average pixel value difference {:.4f} > expected {:.4f} "
"for '{}' band".format(average_diff, max_average_diff, band)
assert max_average_diff >= average_diff, msg
last_diff = [i for i, num in enumerate(ch_hist) if num > 0][-1] def assert_compare_images(a, b, max_average_diff, max_diff=255):
assert ( assert a.mode == b.mode, "got mode %r, expected %r" % (a.mode, b.mode)
max_diff >= last_diff assert a.size == b.size, "got size %r, expected %r" % (a.size, b.size)
), "max pixel value difference {} > expected {} for '{}' band".format(
last_diff, max_diff, band
)
def test_mode_L(self): a, b = convert_to_comparable(a, b)
im = self.get_image("L")
for factor in self.remarkable_factors:
self.compare_reduce_with_reference(im, factor)
self.compare_reduce_with_box(im, factor)
def test_mode_LA(self): bands = ImageMode.getmode(a.mode).bands
im = self.get_image("LA") for band, ach, bch in zip(bands, a.split(), b.split()):
for factor in self.remarkable_factors: ch_diff = ImageMath.eval("convert(abs(a - b), 'L')", a=ach, b=bch)
self.compare_reduce_with_reference(im, factor, 0.8, 5) ch_hist = ch_diff.histogram()
# With opaque alpha, an error should be way smaller. average_diff = sum(i * num for i, num in enumerate(ch_hist)) / (
im.putalpha(Image.new("L", im.size, 255)) a.size[0] * a.size[1]
for factor in self.remarkable_factors: )
self.compare_reduce_with_reference(im, factor) msg = "average pixel value difference {:.4f} > expected {:.4f} "
self.compare_reduce_with_box(im, factor) "for '{}' band".format(average_diff, max_average_diff, band)
assert max_average_diff >= average_diff, msg
def test_mode_La(self): last_diff = [i for i, num in enumerate(ch_hist) if num > 0][-1]
im = self.get_image("La") assert (
for factor in self.remarkable_factors: max_diff >= last_diff
self.compare_reduce_with_reference(im, factor) ), "max pixel value difference {} > expected {} for '{}' band".format(
self.compare_reduce_with_box(im, factor) last_diff, max_diff, band
)
def test_mode_RGB(self):
im = self.get_image("RGB")
for factor in self.remarkable_factors:
self.compare_reduce_with_reference(im, factor)
self.compare_reduce_with_box(im, factor)
def test_mode_RGBA(self): def test_mode_L():
im = self.get_image("RGBA") im = get_image("L")
for factor in self.remarkable_factors: for factor in remarkable_factors:
self.compare_reduce_with_reference(im, factor, 0.8, 5) compare_reduce_with_reference(im, factor)
compare_reduce_with_box(im, factor)
# With opaque alpha, an error should be way smaller.
im.putalpha(Image.new("L", im.size, 255))
for factor in self.remarkable_factors:
self.compare_reduce_with_reference(im, factor)
self.compare_reduce_with_box(im, factor)
def test_mode_RGBa(self): def test_mode_LA():
im = self.get_image("RGBa") im = get_image("LA")
for factor in self.remarkable_factors: for factor in remarkable_factors:
self.compare_reduce_with_reference(im, factor) compare_reduce_with_reference(im, factor, 0.8, 5)
self.compare_reduce_with_box(im, factor)
def test_mode_I(self): # With opaque alpha, an error should be way smaller.
im = self.get_image("I") im.putalpha(Image.new("L", im.size, 255))
for factor in self.remarkable_factors: for factor in remarkable_factors:
self.compare_reduce_with_reference(im, factor) compare_reduce_with_reference(im, factor)
self.compare_reduce_with_box(im, factor) compare_reduce_with_box(im, factor)
def test_mode_F(self):
im = self.get_image("F") def test_mode_La():
for factor in self.remarkable_factors: im = get_image("La")
self.compare_reduce_with_reference(im, factor, 0, 0) for factor in remarkable_factors:
self.compare_reduce_with_box(im, factor) compare_reduce_with_reference(im, factor)
compare_reduce_with_box(im, factor)
def test_mode_RGB():
im = get_image("RGB")
for factor in remarkable_factors:
compare_reduce_with_reference(im, factor)
compare_reduce_with_box(im, factor)
def test_mode_RGBA():
im = get_image("RGBA")
for factor in remarkable_factors:
compare_reduce_with_reference(im, factor, 0.8, 5)
# With opaque alpha, an error should be way smaller.
im.putalpha(Image.new("L", im.size, 255))
for factor in remarkable_factors:
compare_reduce_with_reference(im, factor)
compare_reduce_with_box(im, factor)
def test_mode_RGBa():
im = get_image("RGBa")
for factor in remarkable_factors:
compare_reduce_with_reference(im, factor)
compare_reduce_with_box(im, factor)
def test_mode_I():
im = get_image("I")
for factor in remarkable_factors:
compare_reduce_with_reference(im, factor)
compare_reduce_with_box(im, factor)
def test_mode_F():
im = get_image("F")
for factor in remarkable_factors:
compare_reduce_with_reference(im, factor, 0, 0)
compare_reduce_with_box(im, factor)

View File

@ -3,10 +3,10 @@ import math
import pytest import pytest
from PIL import Image, ImageTransform from PIL import Image, ImageTransform
from .helper import PillowTestCase, assert_image_equal, assert_image_similar, hopper from .helper import assert_image_equal, assert_image_similar, hopper
class TestImageTransform(PillowTestCase): class TestImageTransform:
def test_sanity(self): def test_sanity(self):
im = Image.new("L", (100, 100)) im = Image.new("L", (100, 100))
@ -177,7 +177,7 @@ class TestImageTransform(PillowTestCase):
im.transform((100, 100), Image.EXTENT, (0, 0, w, h), resample) im.transform((100, 100), Image.EXTENT, (0, 0, w, h), resample)
class TestImageTransformAffine(PillowTestCase): class TestImageTransformAffine:
transform = Image.AFFINE transform = Image.AFFINE
def _test_image(self): def _test_image(self):

View File

@ -4,7 +4,6 @@ import pytest
from PIL import EpsImagePlugin, Image, ImageFile, features from PIL import EpsImagePlugin, Image, ImageFile, features
from .helper import ( from .helper import (
PillowTestCase,
assert_image, assert_image,
assert_image_equal, assert_image_equal,
assert_image_similar, assert_image_similar,
@ -19,7 +18,7 @@ MAXBLOCK = ImageFile.MAXBLOCK
SAFEBLOCK = ImageFile.SAFEBLOCK SAFEBLOCK = ImageFile.SAFEBLOCK
class TestImageFile(PillowTestCase): class TestImageFile:
def test_parser(self): def test_parser(self):
def roundtrip(format): def roundtrip(format):
@ -163,7 +162,7 @@ class MockImageFile(ImageFile.ImageFile):
self.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)] self.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)]
class TestPyDecoder(PillowTestCase): class TestPyDecoder:
def get_decoder(self): def get_decoder(self):
decoder = MockPyDecoder(None) decoder = MockPyDecoder(None)
@ -239,7 +238,7 @@ class TestPyDecoder(PillowTestCase):
assert im.format is None assert im.format is None
assert im.get_format_mimetype() is None assert im.get_format_mimetype() is None
def test_exif_jpeg(self): def test_exif_jpeg(self, tmp_path):
with Image.open("Tests/images/exif-72dpi-int.jpg") as im: # Little endian with Image.open("Tests/images/exif-72dpi-int.jpg") as im: # Little endian
exif = im.getexif() exif = im.getexif()
assert 258 not in exif assert 258 not in exif
@ -247,7 +246,7 @@ class TestPyDecoder(PillowTestCase):
assert exif[40963] == 450 assert exif[40963] == 450
assert exif[11] == "gThumb 3.0.1" assert exif[11] == "gThumb 3.0.1"
out = self.tempfile("temp.jpg") out = str(tmp_path / "temp.jpg")
exif[258] = 8 exif[258] = 8
del exif[40960] del exif[40960]
exif[40963] = 455 exif[40963] = 455
@ -267,7 +266,7 @@ class TestPyDecoder(PillowTestCase):
assert exif[40963] == 200 assert exif[40963] == 200
assert exif[305] == "Adobe Photoshop CC 2017 (Macintosh)" assert exif[305] == "Adobe Photoshop CC 2017 (Macintosh)"
out = self.tempfile("temp.jpg") out = str(tmp_path / "temp.jpg")
exif[258] = 8 exif[258] = 8
del exif[34665] del exif[34665]
exif[40963] = 455 exif[40963] = 455
@ -282,12 +281,12 @@ class TestPyDecoder(PillowTestCase):
@skip_unless_feature("webp") @skip_unless_feature("webp")
@skip_unless_feature("webp_anim") @skip_unless_feature("webp_anim")
def test_exif_webp(self): def test_exif_webp(self, tmp_path):
with Image.open("Tests/images/hopper.webp") as im: with Image.open("Tests/images/hopper.webp") as im:
exif = im.getexif() exif = im.getexif()
assert exif == {} assert exif == {}
out = self.tempfile("temp.webp") out = str(tmp_path / "temp.webp")
exif[258] = 8 exif[258] = 8
exif[40963] = 455 exif[40963] = 455
exif[305] = "Pillow test" exif[305] = "Pillow test"
@ -304,12 +303,12 @@ class TestPyDecoder(PillowTestCase):
im.save(out, exif=exif, save_all=True) im.save(out, exif=exif, save_all=True)
check_exif() check_exif()
def test_exif_png(self): def test_exif_png(self, tmp_path):
with Image.open("Tests/images/exif.png") as im: with Image.open("Tests/images/exif.png") as im:
exif = im.getexif() exif = im.getexif()
assert exif == {274: 1} assert exif == {274: 1}
out = self.tempfile("temp.png") out = str(tmp_path / "temp.png")
exif[258] = 8 exif[258] = 8
del exif[274] del exif[274]
exif[40963] = 455 exif[40963] = 455

View File

@ -3,7 +3,6 @@ import distutils.version
import os import os
import re import re
import shutil import shutil
import unittest
from io import BytesIO from io import BytesIO
from unittest import mock from unittest import mock
@ -441,7 +440,7 @@ class TestImageFont(PillowTestCase):
with pytest.raises(UnicodeEncodeError): with pytest.raises(UnicodeEncodeError):
font.getsize("") font.getsize("")
@unittest.skipIf(is_pypy(), "failing on PyPy") @pytest.mark.skipif(is_pypy(), reason="failing on PyPy")
def test_unicode_extended(self): def test_unicode_extended(self):
# issue #3777 # issue #3777
text = "A\u278A\U0001F12B" text = "A\u278A\U0001F12B"
@ -478,7 +477,7 @@ class TestImageFont(PillowTestCase):
name = font.getname() name = font.getname()
assert ("FreeMono", "Regular") == name assert ("FreeMono", "Regular") == name
@unittest.skipIf(is_win32(), "requires Unix or macOS") @pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
def test_find_linux_font(self): def test_find_linux_font(self):
# A lot of mocking here - this is more for hitting code and # A lot of mocking here - this is more for hitting code and
# catching syntax like errors # catching syntax like errors
@ -523,7 +522,7 @@ class TestImageFont(PillowTestCase):
font_directory + "/Duplicate.ttf", "Duplicate" font_directory + "/Duplicate.ttf", "Duplicate"
) )
@unittest.skipIf(is_win32(), "requires Unix or macOS") @pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
def test_find_macos_font(self): def test_find_macos_font(self):
# Like the linux test, more cover hitting code rather than testing # Like the linux test, more cover hitting code rather than testing
# correctness. # correctness.

View File

@ -1,193 +1,207 @@
import pytest
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
from .helper import PillowTestCase, assert_image_similar, skip_unless_feature from .helper import assert_image_similar, skip_unless_feature
FONT_SIZE = 20 FONT_SIZE = 20
FONT_PATH = "Tests/fonts/DejaVuSans.ttf" FONT_PATH = "Tests/fonts/DejaVuSans.ttf"
pytestmark = skip_unless_feature("raqm")
@skip_unless_feature("raqm")
class TestImagecomplextext(PillowTestCase):
def test_english(self):
# smoke test, this should not fail
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im)
draw.text((0, 0), "TEST", font=ttf, fill=500, direction="ltr")
def test_complex_text(self): def test_english():
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) # smoke test, this should not fail
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im)
draw.text((0, 0), "TEST", font=ttf, fill=500, direction="ltr")
im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im)
draw.text((0, 0), "اهلا عمان", font=ttf, fill=500)
target = "Tests/images/test_text.png" def test_complex_text():
with Image.open(target) as target_img: ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
assert_image_similar(im, target_img, 0.5)
def test_y_offset(self): im = Image.new(mode="RGB", size=(300, 100))
ttf = ImageFont.truetype("Tests/fonts/NotoNastaliqUrdu-Regular.ttf", FONT_SIZE) draw = ImageDraw.Draw(im)
draw.text((0, 0), "اهلا عمان", font=ttf, fill=500)
im = Image.new(mode="RGB", size=(300, 100)) target = "Tests/images/test_text.png"
draw = ImageDraw.Draw(im) with Image.open(target) as target_img:
draw.text((0, 0), "العالم العربي", font=ttf, fill=500) assert_image_similar(im, target_img, 0.5)
target = "Tests/images/test_y_offset.png"
with Image.open(target) as target_img:
assert_image_similar(im, target_img, 1.7)
def test_complex_unicode_text(self): def test_y_offset():
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) ttf = ImageFont.truetype("Tests/fonts/NotoNastaliqUrdu-Regular.ttf", FONT_SIZE)
im = Image.new(mode="RGB", size=(300, 100)) im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
draw.text((0, 0), "السلام عليكم", font=ttf, fill=500) draw.text((0, 0), "العالم العربي", font=ttf, fill=500)
target = "Tests/images/test_complex_unicode_text.png" target = "Tests/images/test_y_offset.png"
with Image.open(target) as target_img: with Image.open(target) as target_img:
assert_image_similar(im, target_img, 0.5) assert_image_similar(im, target_img, 1.7)
ttf = ImageFont.truetype("Tests/fonts/KhmerOSBattambang-Regular.ttf", FONT_SIZE)
im = Image.new(mode="RGB", size=(300, 100)) def test_complex_unicode_text():
draw = ImageDraw.Draw(im) ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
draw.text((0, 0), "លោកុប្បត្តិ", font=ttf, fill=500)
target = "Tests/images/test_complex_unicode_text2.png" im = Image.new(mode="RGB", size=(300, 100))
with Image.open(target) as target_img: draw = ImageDraw.Draw(im)
assert_image_similar(im, target_img, 2.3) draw.text((0, 0), "السلام عليكم", font=ttf, fill=500)
def test_text_direction_rtl(self): target = "Tests/images/test_complex_unicode_text.png"
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) with Image.open(target) as target_img:
assert_image_similar(im, target_img, 0.5)
im = Image.new(mode="RGB", size=(300, 100)) ttf = ImageFont.truetype("Tests/fonts/KhmerOSBattambang-Regular.ttf", FONT_SIZE)
draw = ImageDraw.Draw(im)
draw.text((0, 0), "English عربي", font=ttf, fill=500, direction="rtl")
target = "Tests/images/test_direction_rtl.png" im = Image.new(mode="RGB", size=(300, 100))
with Image.open(target) as target_img: draw = ImageDraw.Draw(im)
assert_image_similar(im, target_img, 0.5) draw.text((0, 0), "លោកុប្បត្តិ", font=ttf, fill=500)
def test_text_direction_ltr(self): target = "Tests/images/test_complex_unicode_text2.png"
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) with Image.open(target) as target_img:
assert_image_similar(im, target_img, 2.3)
im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im)
draw.text((0, 0), "سلطنة عمان Oman", font=ttf, fill=500, direction="ltr")
target = "Tests/images/test_direction_ltr.png" def test_text_direction_rtl():
with Image.open(target) as target_img: ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
assert_image_similar(im, target_img, 0.5)
def test_text_direction_rtl2(self): im = Image.new(mode="RGB", size=(300, 100))
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) draw = ImageDraw.Draw(im)
draw.text((0, 0), "English عربي", font=ttf, fill=500, direction="rtl")
im = Image.new(mode="RGB", size=(300, 100)) target = "Tests/images/test_direction_rtl.png"
draw = ImageDraw.Draw(im) with Image.open(target) as target_img:
draw.text((0, 0), "Oman سلطنة عمان", font=ttf, fill=500, direction="rtl") assert_image_similar(im, target_img, 0.5)
target = "Tests/images/test_direction_ltr.png"
with Image.open(target) as target_img:
assert_image_similar(im, target_img, 0.5)
def test_text_direction_ttb(self): def test_text_direction_ltr():
ttf = ImageFont.truetype("Tests/fonts/NotoSansJP-Regular.otf", FONT_SIZE) ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
im = Image.new(mode="RGB", size=(100, 300)) im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
try: draw.text((0, 0), "سلطنة عمان Oman", font=ttf, fill=500, direction="ltr")
draw.text((0, 0), "English あい", font=ttf, fill=500, direction="ttb")
except ValueError as ex:
if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction":
self.skipTest("libraqm 0.7 or greater not available")
target = "Tests/images/test_direction_ttb.png" target = "Tests/images/test_direction_ltr.png"
with Image.open(target) as target_img: with Image.open(target) as target_img:
assert_image_similar(im, target_img, 1.15) assert_image_similar(im, target_img, 0.5)
def test_text_direction_ttb_stroke(self):
ttf = ImageFont.truetype("Tests/fonts/NotoSansJP-Regular.otf", 50)
im = Image.new(mode="RGB", size=(100, 300)) def test_text_direction_rtl2():
draw = ImageDraw.Draw(im) ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
try:
draw.text(
(25, 25),
"あい",
font=ttf,
fill=500,
direction="ttb",
stroke_width=2,
stroke_fill="#0f0",
)
except ValueError as ex:
if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction":
self.skipTest("libraqm 0.7 or greater not available")
target = "Tests/images/test_direction_ttb_stroke.png" im = Image.new(mode="RGB", size=(300, 100))
with Image.open(target) as target_img: draw = ImageDraw.Draw(im)
assert_image_similar(im, target_img, 12.4) draw.text((0, 0), "Oman سلطنة عمان", font=ttf, fill=500, direction="rtl")
def test_ligature_features(self): target = "Tests/images/test_direction_ltr.png"
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) with Image.open(target) as target_img:
assert_image_similar(im, target_img, 0.5)
im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im)
draw.text((0, 0), "filling", font=ttf, fill=500, features=["-liga"])
target = "Tests/images/test_ligature_features.png"
with Image.open(target) as target_img:
assert_image_similar(im, target_img, 0.5)
liga_size = ttf.getsize("fi", features=["-liga"]) def test_text_direction_ttb():
assert liga_size == (13, 19) ttf = ImageFont.truetype("Tests/fonts/NotoSansJP-Regular.otf", FONT_SIZE)
def test_kerning_features(self): im = Image.new(mode="RGB", size=(100, 300))
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) draw = ImageDraw.Draw(im)
try:
draw.text((0, 0), "English あい", font=ttf, fill=500, direction="ttb")
except ValueError as ex:
if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction":
pytest.skip("libraqm 0.7 or greater not available")
im = Image.new(mode="RGB", size=(300, 100)) target = "Tests/images/test_direction_ttb.png"
draw = ImageDraw.Draw(im) with Image.open(target) as target_img:
draw.text((0, 0), "TeToAV", font=ttf, fill=500, features=["-kern"]) assert_image_similar(im, target_img, 1.15)
target = "Tests/images/test_kerning_features.png"
with Image.open(target) as target_img:
assert_image_similar(im, target_img, 0.5)
def test_arabictext_features(self): def test_text_direction_ttb_stroke():
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) ttf = ImageFont.truetype("Tests/fonts/NotoSansJP-Regular.otf", 50)
im = Image.new(mode="RGB", size=(300, 100)) im = Image.new(mode="RGB", size=(100, 300))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
try:
draw.text( draw.text(
(0, 0), (25, 25),
"اللغة العربية", "あい",
font=ttf, font=ttf,
fill=500, fill=500,
features=["-fina", "-init", "-medi"], direction="ttb",
stroke_width=2,
stroke_fill="#0f0",
) )
except ValueError as ex:
if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction":
pytest.skip("libraqm 0.7 or greater not available")
target = "Tests/images/test_arabictext_features.png" target = "Tests/images/test_direction_ttb_stroke.png"
with Image.open(target) as target_img: with Image.open(target) as target_img:
assert_image_similar(im, target_img, 0.5) assert_image_similar(im, target_img, 12.4)
def test_x_max_and_y_offset(self):
ttf = ImageFont.truetype("Tests/fonts/ArefRuqaa-Regular.ttf", 40)
im = Image.new(mode="RGB", size=(50, 100)) def test_ligature_features():
draw = ImageDraw.Draw(im) ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
draw.text((0, 0), "لح", font=ttf, fill=500)
target = "Tests/images/test_x_max_and_y_offset.png" im = Image.new(mode="RGB", size=(300, 100))
with Image.open(target) as target_img: draw = ImageDraw.Draw(im)
assert_image_similar(im, target_img, 0.5) draw.text((0, 0), "filling", font=ttf, fill=500, features=["-liga"])
target = "Tests/images/test_ligature_features.png"
with Image.open(target) as target_img:
assert_image_similar(im, target_img, 0.5)
def test_language(self): liga_size = ttf.getsize("fi", features=["-liga"])
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) assert liga_size == (13, 19)
im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im)
draw.text((0, 0), "абвг", font=ttf, fill=500, language="sr")
target = "Tests/images/test_language.png" def test_kerning_features():
with Image.open(target) as target_img: ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
assert_image_similar(im, target_img, 0.5)
im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im)
draw.text((0, 0), "TeToAV", font=ttf, fill=500, features=["-kern"])
target = "Tests/images/test_kerning_features.png"
with Image.open(target) as target_img:
assert_image_similar(im, target_img, 0.5)
def test_arabictext_features():
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im)
draw.text(
(0, 0),
"اللغة العربية",
font=ttf,
fill=500,
features=["-fina", "-init", "-medi"],
)
target = "Tests/images/test_arabictext_features.png"
with Image.open(target) as target_img:
assert_image_similar(im, target_img, 0.5)
def test_x_max_and_y_offset():
ttf = ImageFont.truetype("Tests/fonts/ArefRuqaa-Regular.ttf", 40)
im = Image.new(mode="RGB", size=(50, 100))
draw = ImageDraw.Draw(im)
draw.text((0, 0), "لح", font=ttf, fill=500)
target = "Tests/images/test_x_max_and_y_offset.png"
with Image.open(target) as target_img:
assert_image_similar(im, target_img, 0.5)
def test_language():
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im)
draw.text((0, 0), "абвг", font=ttf, fill=500, language="sr")
target = "Tests/images/test_language.png"
with Image.open(target) as target_img:
assert_image_similar(im, target_img, 0.5)

View File

@ -1,12 +1,14 @@
import subprocess import subprocess
import sys import sys
from .helper import PillowTestCase, assert_image import pytest
from .helper import assert_image
try: try:
from PIL import ImageGrab from PIL import ImageGrab
class TestImageGrab(PillowTestCase): class TestImageGrab:
def test_grab(self): def test_grab(self):
for im in [ for im in [
ImageGrab.grab(), ImageGrab.grab(),
@ -39,12 +41,13 @@ $bmp = New-Object Drawing.Bitmap 200, 200
except ImportError: except ImportError:
class TestImageGrab(PillowTestCase): class TestImageGrab:
@pytest.mark.skip(reason="ImageGrab ImportError")
def test_skip(self): def test_skip(self):
self.skipTest("ImportError") pass
class TestImageGrabImport(PillowTestCase): class TestImageGrabImport:
def test_import(self): def test_import(self):
# Arrange # Arrange
exception = None exception = None

View File

@ -2,318 +2,340 @@
import pytest import pytest
from PIL import Image, ImageMorph, _imagingmorph from PIL import Image, ImageMorph, _imagingmorph
from .helper import PillowTestCase, assert_image_equal, hopper from .helper import assert_image_equal, hopper
class MorphTests(PillowTestCase): def string_to_img(image_string):
def setUp(self): """Turn a string image representation into a binary image"""
self.A = self.string_to_img( rows = [s for s in image_string.replace(" ", "").split("\n") if len(s)]
""" height = len(rows)
....... width = len(rows[0])
....... im = Image.new("L", (width, height))
..111.. for i in range(width):
..111.. for j in range(height):
..111.. c = rows[j][i]
....... v = c in "X1"
....... im.putpixel((i, j), v)
"""
)
def img_to_string(self, im): return im
"""Turn a (small) binary image into a string representation"""
chars = ".1"
width, height = im.size
return "\n".join(
"".join(chars[im.getpixel((c, r)) > 0] for c in range(width))
for r in range(height)
)
def string_to_img(self, image_string):
"""Turn a string image representation into a binary image"""
rows = [s for s in image_string.replace(" ", "").split("\n") if len(s)]
height = len(rows)
width = len(rows[0])
im = Image.new("L", (width, height))
for i in range(width):
for j in range(height):
c = rows[j][i]
v = c in "X1"
im.putpixel((i, j), v)
return im A = string_to_img(
"""
.......
.......
..111..
..111..
..111..
.......
.......
"""
)
def img_string_normalize(self, im):
return self.img_to_string(self.string_to_img(im))
def assert_img_equal(self, A, B): def img_to_string(im):
assert self.img_to_string(A) == self.img_to_string(B) """Turn a (small) binary image into a string representation"""
chars = ".1"
width, height = im.size
return "\n".join(
"".join(chars[im.getpixel((c, r)) > 0] for c in range(width))
for r in range(height)
)
def assert_img_equal_img_string(self, A, Bstring):
assert self.img_to_string(A) == self.img_string_normalize(Bstring)
def test_str_to_img(self): def img_string_normalize(im):
with Image.open("Tests/images/morph_a.png") as im: return img_to_string(string_to_img(im))
assert_image_equal(self.A, im)
def create_lut(self):
for op in ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge"):
lb = ImageMorph.LutBuilder(op_name=op)
lut = lb.build_lut()
with open("Tests/images/%s.lut" % op, "wb") as f:
f.write(lut)
# create_lut() def assert_img_equal(A, B):
def test_lut(self): assert img_to_string(A) == img_to_string(B)
for op in ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge"):
lb = ImageMorph.LutBuilder(op_name=op)
assert lb.get_lut() is None
lut = lb.build_lut()
with open("Tests/images/%s.lut" % op, "rb") as f:
assert lut == bytearray(f.read())
def test_no_operator_loaded(self): def assert_img_equal_img_string(A, Bstring):
mop = ImageMorph.MorphOp() assert img_to_string(A) == img_string_normalize(Bstring)
with pytest.raises(Exception) as e:
mop.apply(None)
assert str(e.value) == "No operator loaded"
with pytest.raises(Exception) as e:
mop.match(None)
assert str(e.value) == "No operator loaded"
with pytest.raises(Exception) as e:
mop.save_lut(None)
assert str(e.value) == "No operator loaded"
# Test the named patterns
def test_erosion8(self):
# erosion8
mop = ImageMorph.MorphOp(op_name="erosion8")
count, Aout = mop.apply(self.A)
assert count == 8
self.assert_img_equal_img_string(
Aout,
"""
.......
.......
.......
...1...
.......
.......
.......
""",
)
def test_dialation8(self): def test_str_to_img():
# dialation8 with Image.open("Tests/images/morph_a.png") as im:
mop = ImageMorph.MorphOp(op_name="dilation8") assert_image_equal(A, im)
count, Aout = mop.apply(self.A)
assert count == 16
self.assert_img_equal_img_string(
Aout,
"""
.......
.11111.
.11111.
.11111.
.11111.
.11111.
.......
""",
)
def test_erosion4(self):
# erosion4
mop = ImageMorph.MorphOp(op_name="dilation4")
count, Aout = mop.apply(self.A)
assert count == 12
self.assert_img_equal_img_string(
Aout,
"""
.......
..111..
.11111.
.11111.
.11111.
..111..
.......
""",
)
def test_edge(self): def create_lut():
# edge for op in ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge"):
mop = ImageMorph.MorphOp(op_name="edge") lb = ImageMorph.LutBuilder(op_name=op)
count, Aout = mop.apply(self.A)
assert count == 1
self.assert_img_equal_img_string(
Aout,
"""
.......
.......
..111..
..1.1..
..111..
.......
.......
""",
)
def test_corner(self):
# Create a corner detector pattern
mop = ImageMorph.MorphOp(patterns=["1:(... ... ...)->0", "4:(00. 01. ...)->1"])
count, Aout = mop.apply(self.A)
assert count == 5
self.assert_img_equal_img_string(
Aout,
"""
.......
.......
..1.1..
.......
..1.1..
.......
.......
""",
)
# Test the coordinate counting with the same operator
coords = mop.match(self.A)
assert len(coords) == 4
assert tuple(coords) == ((2, 2), (4, 2), (2, 4), (4, 4))
coords = mop.get_on_pixels(Aout)
assert len(coords) == 4
assert tuple(coords) == ((2, 2), (4, 2), (2, 4), (4, 4))
def test_mirroring(self):
# Test 'M' for mirroring
mop = ImageMorph.MorphOp(patterns=["1:(... ... ...)->0", "M:(00. 01. ...)->1"])
count, Aout = mop.apply(self.A)
assert count == 7
self.assert_img_equal_img_string(
Aout,
"""
.......
.......
..1.1..
.......
.......
.......
.......
""",
)
def test_negate(self):
# Test 'N' for negate
mop = ImageMorph.MorphOp(patterns=["1:(... ... ...)->0", "N:(00. 01. ...)->1"])
count, Aout = mop.apply(self.A)
assert count == 8
self.assert_img_equal_img_string(
Aout,
"""
.......
.......
..1....
.......
.......
.......
.......
""",
)
def test_non_binary_images(self):
im = hopper("RGB")
mop = ImageMorph.MorphOp(op_name="erosion8")
with pytest.raises(Exception) as e:
mop.apply(im)
assert str(e.value) == "Image must be binary, meaning it must use mode L"
with pytest.raises(Exception) as e:
mop.match(im)
assert str(e.value) == "Image must be binary, meaning it must use mode L"
with pytest.raises(Exception) as e:
mop.get_on_pixels(im)
assert str(e.value) == "Image must be binary, meaning it must use mode L"
def test_add_patterns(self):
# Arrange
lb = ImageMorph.LutBuilder(op_name="corner")
assert lb.patterns == ["1:(... ... ...)->0", "4:(00. 01. ...)->1"]
new_patterns = ["M:(00. 01. ...)->1", "N:(00. 01. ...)->1"]
# Act
lb.add_patterns(new_patterns)
# Assert
assert lb.patterns == [
"1:(... ... ...)->0",
"4:(00. 01. ...)->1",
"M:(00. 01. ...)->1",
"N:(00. 01. ...)->1",
]
def test_unknown_pattern(self):
with pytest.raises(Exception):
ImageMorph.LutBuilder(op_name="unknown")
def test_pattern_syntax_error(self):
# Arrange
lb = ImageMorph.LutBuilder(op_name="corner")
new_patterns = ["a pattern with a syntax error"]
lb.add_patterns(new_patterns)
# Act / Assert
with pytest.raises(Exception) as e:
lb.build_lut()
assert str(e.value) == 'Syntax error in pattern "a pattern with a syntax error"'
def test_load_invalid_mrl(self):
# Arrange
invalid_mrl = "Tests/images/hopper.png"
mop = ImageMorph.MorphOp()
# Act / Assert
with pytest.raises(Exception) as e:
mop.load_lut(invalid_mrl)
assert str(e.value) == "Wrong size operator file!"
def test_roundtrip_mrl(self):
# Arrange
tempfile = self.tempfile("temp.mrl")
mop = ImageMorph.MorphOp(op_name="corner")
initial_lut = mop.lut
# Act
mop.save_lut(tempfile)
mop.load_lut(tempfile)
# Act / Assert
assert mop.lut == initial_lut
def test_set_lut(self):
# Arrange
lb = ImageMorph.LutBuilder(op_name="corner")
lut = lb.build_lut() lut = lb.build_lut()
mop = ImageMorph.MorphOp() with open("Tests/images/%s.lut" % op, "wb") as f:
f.write(lut)
# Act
mop.set_lut(lut)
# Assert # create_lut()
assert mop.lut == lut def test_lut():
for op in ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge"):
lb = ImageMorph.LutBuilder(op_name=op)
assert lb.get_lut() is None
def test_wrong_mode(self): lut = lb.build_lut()
lut = ImageMorph.LutBuilder(op_name="corner").build_lut() with open("Tests/images/%s.lut" % op, "rb") as f:
imrgb = Image.new("RGB", (10, 10)) assert lut == bytearray(f.read())
iml = Image.new("L", (10, 10))
with pytest.raises(RuntimeError):
_imagingmorph.apply(bytes(lut), imrgb.im.id, iml.im.id)
with pytest.raises(RuntimeError): def test_no_operator_loaded():
_imagingmorph.apply(bytes(lut), iml.im.id, imrgb.im.id) mop = ImageMorph.MorphOp()
with pytest.raises(Exception) as e:
mop.apply(None)
assert str(e.value) == "No operator loaded"
with pytest.raises(Exception) as e:
mop.match(None)
assert str(e.value) == "No operator loaded"
with pytest.raises(Exception) as e:
mop.save_lut(None)
assert str(e.value) == "No operator loaded"
with pytest.raises(RuntimeError):
_imagingmorph.match(bytes(lut), imrgb.im.id)
# Should not raise # Test the named patterns
_imagingmorph.match(bytes(lut), iml.im.id) def test_erosion8():
# erosion8
mop = ImageMorph.MorphOp(op_name="erosion8")
count, Aout = mop.apply(A)
assert count == 8
assert_img_equal_img_string(
Aout,
"""
.......
.......
.......
...1...
.......
.......
.......
""",
)
def test_dialation8():
# dialation8
mop = ImageMorph.MorphOp(op_name="dilation8")
count, Aout = mop.apply(A)
assert count == 16
assert_img_equal_img_string(
Aout,
"""
.......
.11111.
.11111.
.11111.
.11111.
.11111.
.......
""",
)
def test_erosion4():
# erosion4
mop = ImageMorph.MorphOp(op_name="dilation4")
count, Aout = mop.apply(A)
assert count == 12
assert_img_equal_img_string(
Aout,
"""
.......
..111..
.11111.
.11111.
.11111.
..111..
.......
""",
)
def test_edge():
# edge
mop = ImageMorph.MorphOp(op_name="edge")
count, Aout = mop.apply(A)
assert count == 1
assert_img_equal_img_string(
Aout,
"""
.......
.......
..111..
..1.1..
..111..
.......
.......
""",
)
def test_corner():
# Create a corner detector pattern
mop = ImageMorph.MorphOp(patterns=["1:(... ... ...)->0", "4:(00. 01. ...)->1"])
count, Aout = mop.apply(A)
assert count == 5
assert_img_equal_img_string(
Aout,
"""
.......
.......
..1.1..
.......
..1.1..
.......
.......
""",
)
# Test the coordinate counting with the same operator
coords = mop.match(A)
assert len(coords) == 4
assert tuple(coords) == ((2, 2), (4, 2), (2, 4), (4, 4))
coords = mop.get_on_pixels(Aout)
assert len(coords) == 4
assert tuple(coords) == ((2, 2), (4, 2), (2, 4), (4, 4))
def test_mirroring():
# Test 'M' for mirroring
mop = ImageMorph.MorphOp(patterns=["1:(... ... ...)->0", "M:(00. 01. ...)->1"])
count, Aout = mop.apply(A)
assert count == 7
assert_img_equal_img_string(
Aout,
"""
.......
.......
..1.1..
.......
.......
.......
.......
""",
)
def test_negate():
# Test 'N' for negate
mop = ImageMorph.MorphOp(patterns=["1:(... ... ...)->0", "N:(00. 01. ...)->1"])
count, Aout = mop.apply(A)
assert count == 8
assert_img_equal_img_string(
Aout,
"""
.......
.......
..1....
.......
.......
.......
.......
""",
)
def test_non_binary_images():
im = hopper("RGB")
mop = ImageMorph.MorphOp(op_name="erosion8")
with pytest.raises(Exception) as e:
mop.apply(im)
assert str(e.value) == "Image must be binary, meaning it must use mode L"
with pytest.raises(Exception) as e:
mop.match(im)
assert str(e.value) == "Image must be binary, meaning it must use mode L"
with pytest.raises(Exception) as e:
mop.get_on_pixels(im)
assert str(e.value) == "Image must be binary, meaning it must use mode L"
def test_add_patterns():
# Arrange
lb = ImageMorph.LutBuilder(op_name="corner")
assert lb.patterns == ["1:(... ... ...)->0", "4:(00. 01. ...)->1"]
new_patterns = ["M:(00. 01. ...)->1", "N:(00. 01. ...)->1"]
# Act
lb.add_patterns(new_patterns)
# Assert
assert lb.patterns == [
"1:(... ... ...)->0",
"4:(00. 01. ...)->1",
"M:(00. 01. ...)->1",
"N:(00. 01. ...)->1",
]
def test_unknown_pattern():
with pytest.raises(Exception):
ImageMorph.LutBuilder(op_name="unknown")
def test_pattern_syntax_error():
# Arrange
lb = ImageMorph.LutBuilder(op_name="corner")
new_patterns = ["a pattern with a syntax error"]
lb.add_patterns(new_patterns)
# Act / Assert
with pytest.raises(Exception) as e:
lb.build_lut()
assert str(e.value) == 'Syntax error in pattern "a pattern with a syntax error"'
def test_load_invalid_mrl():
# Arrange
invalid_mrl = "Tests/images/hopper.png"
mop = ImageMorph.MorphOp()
# Act / Assert
with pytest.raises(Exception) as e:
mop.load_lut(invalid_mrl)
assert str(e.value) == "Wrong size operator file!"
def test_roundtrip_mrl(tmp_path):
# Arrange
tempfile = str(tmp_path / "temp.mrl")
mop = ImageMorph.MorphOp(op_name="corner")
initial_lut = mop.lut
# Act
mop.save_lut(tempfile)
mop.load_lut(tempfile)
# Act / Assert
assert mop.lut == initial_lut
def test_set_lut():
# Arrange
lb = ImageMorph.LutBuilder(op_name="corner")
lut = lb.build_lut()
mop = ImageMorph.MorphOp()
# Act
mop.set_lut(lut)
# Assert
assert mop.lut == lut
def test_wrong_mode():
lut = ImageMorph.LutBuilder(op_name="corner").build_lut()
imrgb = Image.new("RGB", (10, 10))
iml = Image.new("L", (10, 10))
with pytest.raises(RuntimeError):
_imagingmorph.apply(bytes(lut), imrgb.im.id, iml.im.id)
with pytest.raises(RuntimeError):
_imagingmorph.apply(bytes(lut), iml.im.id, imrgb.im.id)
with pytest.raises(RuntimeError):
_imagingmorph.match(bytes(lut), imrgb.im.id)
# Should not raise
_imagingmorph.match(bytes(lut), iml.im.id)

View File

@ -4,10 +4,8 @@ import struct
import pytest import pytest
from PIL import Image, ImagePath from PIL import Image, ImagePath
from .helper import PillowTestCase
class TestImagePath:
class TestImagePath(PillowTestCase):
def test_path(self): def test_path(self):
p = ImagePath.Path(list(range(10))) p = ImagePath.Path(list(range(10)))

View File

@ -3,12 +3,10 @@ import sys
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import PillowTestCase
X = 255 X = 255
class TestLibPack(PillowTestCase): class TestLibPack:
def assert_pack(self, mode, rawmode, data, *pixels): def assert_pack(self, mode, rawmode, data, *pixels):
""" """
data - either raw bytes with data or just number of bytes in rawmode. data - either raw bytes with data or just number of bytes in rawmode.
@ -223,7 +221,7 @@ class TestLibPack(PillowTestCase):
) )
class TestLibUnpack(PillowTestCase): class TestLibUnpack:
def assert_unpack(self, mode, rawmode, data, *pixels): def assert_unpack(self, mode, rawmode, data, *pixels):
""" """
data - either raw bytes with data or just number of bytes in rawmode. data - either raw bytes with data or just number of bytes in rawmode.

View File

@ -1,105 +1,106 @@
from PIL import Image from PIL import Image
from .helper import PillowTestCase, hopper from .helper import hopper
original = hopper().resize((32, 32)).convert("I")
class TestModeI16(PillowTestCase): def verify(im1):
im2 = original.copy()
assert im1.size == im2.size
pix1 = im1.load()
pix2 = im2.load()
for y in range(im1.size[1]):
for x in range(im1.size[0]):
xy = x, y
p1 = pix1[xy]
p2 = pix2[xy]
assert p1 == p2, "got {!r} from mode {} at {}, expected {!r}".format(
p1, im1.mode, xy, p2
)
original = hopper().resize((32, 32)).convert("I")
def verify(self, im1): def test_basic(tmp_path):
im2 = self.original.copy() # PIL 1.1 has limited support for 16-bit image data. Check that
assert im1.size == im2.size # create/copy/transform and save works as expected.
pix1 = im1.load()
pix2 = im2.load()
for y in range(im1.size[1]):
for x in range(im1.size[0]):
xy = x, y
p1 = pix1[xy]
p2 = pix2[xy]
assert p1 == p2, "got {!r} from mode {} at {}, expected {!r}".format(
p1, im1.mode, xy, p2
)
def test_basic(self): def basic(mode):
# PIL 1.1 has limited support for 16-bit image data. Check that
# create/copy/transform and save works as expected.
def basic(mode): imIn = original.convert(mode)
verify(imIn)
imIn = self.original.convert(mode) w, h = imIn.size
self.verify(imIn)
w, h = imIn.size imOut = imIn.copy()
verify(imOut) # copy
imOut = imIn.copy() imOut = imIn.transform((w, h), Image.EXTENT, (0, 0, w, h))
self.verify(imOut) # copy verify(imOut) # transform
imOut = imIn.transform((w, h), Image.EXTENT, (0, 0, w, h)) filename = str(tmp_path / "temp.im")
self.verify(imOut) # transform imIn.save(filename)
filename = self.tempfile("temp.im") with Image.open(filename) as imOut:
imIn.save(filename)
with Image.open(filename) as imOut: verify(imIn)
verify(imOut)
self.verify(imIn) imOut = imIn.crop((0, 0, w, h))
self.verify(imOut) verify(imOut)
imOut = imIn.crop((0, 0, w, h)) imOut = Image.new(mode, (w, h), None)
self.verify(imOut) imOut.paste(imIn.crop((0, 0, w // 2, h)), (0, 0))
imOut.paste(imIn.crop((w // 2, 0, w, h)), (w // 2, 0))
imOut = Image.new(mode, (w, h), None) verify(imIn)
imOut.paste(imIn.crop((0, 0, w // 2, h)), (0, 0)) verify(imOut)
imOut.paste(imIn.crop((w // 2, 0, w, h)), (w // 2, 0))
self.verify(imIn) imIn = Image.new(mode, (1, 1), 1)
self.verify(imOut) assert imIn.getpixel((0, 0)) == 1
imIn = Image.new(mode, (1, 1), 1) imIn.putpixel((0, 0), 2)
assert imIn.getpixel((0, 0)) == 1 assert imIn.getpixel((0, 0)) == 2
imIn.putpixel((0, 0), 2) if mode == "L":
assert imIn.getpixel((0, 0)) == 2 maximum = 255
else:
maximum = 32767
if mode == "L": imIn = Image.new(mode, (1, 1), 256)
maximum = 255 assert imIn.getpixel((0, 0)) == min(256, maximum)
else:
maximum = 32767
imIn = Image.new(mode, (1, 1), 256) imIn.putpixel((0, 0), 512)
assert imIn.getpixel((0, 0)) == min(256, maximum) assert imIn.getpixel((0, 0)) == min(512, maximum)
imIn.putpixel((0, 0), 512) basic("L")
assert imIn.getpixel((0, 0)) == min(512, maximum)
basic("L") basic("I;16")
basic("I;16B")
basic("I;16L")
basic("I;16") basic("I")
basic("I;16B")
basic("I;16L")
basic("I")
def test_tobytes(self): def test_tobytes():
def tobytes(mode): def tobytes(mode):
return Image.new(mode, (1, 1), 1).tobytes() return Image.new(mode, (1, 1), 1).tobytes()
order = 1 if Image._ENDIAN == "<" else -1 order = 1 if Image._ENDIAN == "<" else -1
assert tobytes("L") == b"\x01" assert tobytes("L") == b"\x01"
assert tobytes("I;16") == b"\x01\x00" assert tobytes("I;16") == b"\x01\x00"
assert tobytes("I;16B") == b"\x00\x01" assert tobytes("I;16B") == b"\x00\x01"
assert tobytes("I") == b"\x01\x00\x00\x00"[::order] assert tobytes("I") == b"\x01\x00\x00\x00"[::order]
def test_convert(self):
im = self.original.copy() def test_convert():
self.verify(im.convert("I;16")) im = original.copy()
self.verify(im.convert("I;16").convert("L"))
self.verify(im.convert("I;16").convert("I"))
self.verify(im.convert("I;16B")) verify(im.convert("I;16"))
self.verify(im.convert("I;16B").convert("L")) verify(im.convert("I;16").convert("L"))
self.verify(im.convert("I;16B").convert("I")) verify(im.convert("I;16").convert("I"))
verify(im.convert("I;16B"))
verify(im.convert("I;16B").convert("L"))
verify(im.convert("I;16B").convert("I"))

View File

@ -2,88 +2,89 @@ import pickle
from PIL import Image from PIL import Image
from .helper import PillowTestCase
def helper_pickle_file(tmp_path, pickle, protocol=0, mode=None):
# Arrange
with Image.open("Tests/images/hopper.jpg") as im:
filename = str(tmp_path / "temp.pkl")
if mode:
im = im.convert(mode)
# Act
with open(filename, "wb") as f:
pickle.dump(im, f, protocol)
with open(filename, "rb") as f:
loaded_im = pickle.load(f)
# Assert
assert im == loaded_im
class TestPickle(PillowTestCase): def helper_pickle_string(
def helper_pickle_file(self, pickle, protocol=0, mode=None): pickle, protocol=0, test_file="Tests/images/hopper.jpg", mode=None
# Arrange ):
with Image.open("Tests/images/hopper.jpg") as im: with Image.open(test_file) as im:
filename = self.tempfile("temp.pkl") if mode:
if mode: im = im.convert(mode)
im = im.convert(mode)
# Act # Act
with open(filename, "wb") as f: dumped_string = pickle.dumps(im, protocol)
pickle.dump(im, f, protocol) loaded_im = pickle.loads(dumped_string)
with open(filename, "rb") as f:
loaded_im = pickle.load(f)
# Assert # Assert
assert im == loaded_im assert im == loaded_im
def helper_pickle_string(
self, pickle, protocol=0, test_file="Tests/images/hopper.jpg", mode=None
):
with Image.open(test_file) as im:
if mode:
im = im.convert(mode)
# Act def test_pickle_image(tmp_path):
dumped_string = pickle.dumps(im, protocol) # Act / Assert
loaded_im = pickle.loads(dumped_string) for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
helper_pickle_string(pickle, protocol)
helper_pickle_file(tmp_path, pickle, protocol)
# Assert
assert im == loaded_im
def test_pickle_image(self): def test_pickle_p_mode():
# Act / Assert # Act / Assert
for test_file in [
"Tests/images/test-card.png",
"Tests/images/zero_bb.png",
"Tests/images/zero_bb_scale2.png",
"Tests/images/non_zero_bb.png",
"Tests/images/non_zero_bb_scale2.png",
"Tests/images/p_trns_single.png",
"Tests/images/pil123p.png",
"Tests/images/itxt_chunks.png",
]:
for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1): for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
self.helper_pickle_string(pickle, protocol) helper_pickle_string(pickle, protocol=protocol, test_file=test_file)
self.helper_pickle_file(pickle, protocol)
def test_pickle_p_mode(self):
# Act / Assert
for test_file in [
"Tests/images/test-card.png",
"Tests/images/zero_bb.png",
"Tests/images/zero_bb_scale2.png",
"Tests/images/non_zero_bb.png",
"Tests/images/non_zero_bb_scale2.png",
"Tests/images/p_trns_single.png",
"Tests/images/pil123p.png",
"Tests/images/itxt_chunks.png",
]:
for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
self.helper_pickle_string(
pickle, protocol=protocol, test_file=test_file
)
def test_pickle_pa_mode(self): def test_pickle_pa_mode(tmp_path):
# Act / Assert # Act / Assert
for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1): for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
self.helper_pickle_string(pickle, protocol, mode="PA") helper_pickle_string(pickle, protocol, mode="PA")
self.helper_pickle_file(pickle, protocol, mode="PA") helper_pickle_file(tmp_path, pickle, protocol, mode="PA")
def test_pickle_l_mode(self):
# Act / Assert
for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
self.helper_pickle_string(pickle, protocol, mode="L")
self.helper_pickle_file(pickle, protocol, mode="L")
def test_pickle_la_mode_with_palette(self): def test_pickle_l_mode(tmp_path):
# Arrange # Act / Assert
filename = self.tempfile("temp.pkl") for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
with Image.open("Tests/images/hopper.jpg") as im: helper_pickle_string(pickle, protocol, mode="L")
im = im.convert("PA") helper_pickle_file(tmp_path, pickle, protocol, mode="L")
# Act / Assert
for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
im.mode = "LA"
with open(filename, "wb") as f:
pickle.dump(im, f, protocol)
with open(filename, "rb") as f:
loaded_im = pickle.load(f)
im.mode = "PA" def test_pickle_la_mode_with_palette(tmp_path):
assert im == loaded_im # Arrange
filename = str(tmp_path / "temp.pkl")
with Image.open("Tests/images/hopper.jpg") as im:
im = im.convert("PA")
# Act / Assert
for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
im.mode = "LA"
with open(filename, "wb") as f:
pickle.dump(im, f, protocol)
with open(filename, "rb") as f:
loaded_im = pickle.load(f)
im.mode = "PA"
assert im == loaded_im