mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-11-10 19:56:47 +03:00
Merge pull request #4482 from radarhere/pytest
Removed use of PillowTestCase in various tests
This commit is contained in:
commit
291f1eb1e2
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)))
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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"))
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user