2016-12-02 15:40:32 +03:00
|
|
|
from contextlib import contextmanager
|
|
|
|
|
2017-04-20 14:14:23 +03:00
|
|
|
from PIL import Image, ImageDraw
|
2016-02-04 15:17:00 +03:00
|
|
|
|
2019-07-06 23:40:53 +03:00
|
|
|
from .helper import PillowTestCase, hopper, unittest
|
|
|
|
|
2016-02-05 01:57:13 +03:00
|
|
|
|
2016-05-21 23:58:54 +03:00
|
|
|
class TestImagingResampleVulnerability(PillowTestCase):
|
2016-02-05 01:57:13 +03:00
|
|
|
# see https://github.com/python-pillow/Pillow/issues/1710
|
2016-02-04 15:17:00 +03:00
|
|
|
def test_overflow(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
im = hopper("L")
|
2016-02-04 15:17:00 +03:00
|
|
|
xsize = 0x100000008 // 4
|
2016-02-05 01:57:13 +03:00
|
|
|
ysize = 1000 # unimportant
|
2016-11-06 19:58:45 +03:00
|
|
|
with self.assertRaises(MemoryError):
|
2016-02-04 17:57:57 +03:00
|
|
|
# any resampling filter will do here
|
2016-06-16 21:57:02 +03:00
|
|
|
im.im.resize((xsize, ysize), Image.BILINEAR)
|
2016-02-04 15:17:00 +03:00
|
|
|
|
2016-02-04 17:57:57 +03:00
|
|
|
def test_invalid_size(self):
|
|
|
|
im = hopper()
|
|
|
|
|
2017-06-03 07:05:24 +03:00
|
|
|
# Should not crash
|
2016-02-05 01:57:13 +03:00
|
|
|
im.resize((100, 100))
|
|
|
|
|
2016-11-06 19:58:45 +03:00
|
|
|
with self.assertRaises(ValueError):
|
2016-02-05 01:57:13 +03:00
|
|
|
im.resize((-100, 100))
|
2016-02-04 17:57:57 +03:00
|
|
|
|
2016-11-06 19:58:45 +03:00
|
|
|
with self.assertRaises(ValueError):
|
2016-02-05 01:57:13 +03:00
|
|
|
im.resize((100, -100))
|
2016-02-04 15:17:00 +03:00
|
|
|
|
2016-11-24 03:30:36 +03:00
|
|
|
def test_modify_after_resizing(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
im = hopper("RGB")
|
2016-11-24 03:30:36 +03:00
|
|
|
# get copy with same size
|
|
|
|
copy = im.resize(im.size)
|
|
|
|
# some in-place operation
|
2019-06-13 18:54:24 +03:00
|
|
|
copy.paste("black", (0, 0, im.width // 2, im.height // 2))
|
2016-11-24 03:30:36 +03:00
|
|
|
# image should be different
|
|
|
|
self.assertNotEqual(im.tobytes(), copy.tobytes())
|
|
|
|
|
2016-05-21 23:58:54 +03:00
|
|
|
|
|
|
|
class TestImagingCoreResampleAccuracy(PillowTestCase):
|
2016-05-27 07:09:49 +03:00
|
|
|
def make_case(self, mode, size, color):
|
2016-05-21 23:58:54 +03:00
|
|
|
"""Makes a sample image with two dark and two bright squares.
|
|
|
|
For example:
|
|
|
|
e0 e0 1f 1f
|
|
|
|
e0 e0 1f 1f
|
|
|
|
1f 1f e0 e0
|
|
|
|
1f 1f e0 e0
|
|
|
|
"""
|
2019-06-13 18:54:24 +03:00
|
|
|
case = Image.new("L", size, 255 - color)
|
2016-05-27 10:27:10 +03:00
|
|
|
rectangle = ImageDraw.Draw(case).rectangle
|
|
|
|
rectangle((0, 0, size[0] // 2 - 1, size[1] // 2 - 1), color)
|
|
|
|
rectangle((size[0] // 2, size[1] // 2, size[0], size[1]), color)
|
2016-05-21 23:58:54 +03:00
|
|
|
|
2016-09-03 05:17:22 +03:00
|
|
|
return Image.merge(mode, [case] * len(mode))
|
2016-05-21 23:58:54 +03:00
|
|
|
|
|
|
|
def make_sample(self, data, size):
|
|
|
|
"""Restores a sample image from given data string which contains
|
|
|
|
hex-encoded pixels from the top left fourth of a sample.
|
|
|
|
"""
|
2019-06-13 18:54:24 +03:00
|
|
|
data = data.replace(" ", "")
|
|
|
|
sample = Image.new("L", size)
|
2016-05-21 23:58:54 +03:00
|
|
|
s_px = sample.load()
|
|
|
|
w, h = size[0] // 2, size[1] // 2
|
|
|
|
for y in range(h):
|
|
|
|
for x in range(w):
|
2019-06-13 18:54:24 +03:00
|
|
|
val = int(data[(y * w + x) * 2 : (y * w + x + 1) * 2], 16)
|
2016-05-21 23:58:54 +03:00
|
|
|
s_px[x, y] = val
|
|
|
|
s_px[size[0] - x - 1, size[1] - y - 1] = val
|
|
|
|
s_px[x, size[1] - y - 1] = 255 - val
|
|
|
|
s_px[size[0] - x - 1, y] = 255 - val
|
|
|
|
return sample
|
|
|
|
|
|
|
|
def check_case(self, case, sample):
|
2016-05-27 07:09:49 +03:00
|
|
|
s_px = sample.load()
|
|
|
|
c_px = case.load()
|
|
|
|
for y in range(case.size[1]):
|
|
|
|
for x in range(case.size[0]):
|
|
|
|
if c_px[x, y] != s_px[x, y]:
|
2019-06-13 18:54:24 +03:00
|
|
|
message = "\nHave: \n{}\n\nExpected: \n{}".format(
|
|
|
|
self.serialize_image(case), self.serialize_image(sample)
|
2016-05-27 07:09:49 +03:00
|
|
|
)
|
|
|
|
self.assertEqual(s_px[x, y], c_px[x, y], message)
|
2016-05-21 23:58:54 +03:00
|
|
|
|
|
|
|
def serialize_image(self, image):
|
|
|
|
s_px = image.load()
|
2019-06-13 18:54:24 +03:00
|
|
|
return "\n".join(
|
|
|
|
" ".join("{:02x}".format(s_px[x, y]) for x in range(image.size[0]))
|
2016-05-21 23:58:54 +03:00
|
|
|
for y in range(image.size[1])
|
|
|
|
)
|
|
|
|
|
2016-06-16 21:57:02 +03:00
|
|
|
def test_reduce_box(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
for mode in ["RGBX", "RGB", "La", "L"]:
|
|
|
|
case = self.make_case(mode, (8, 8), 0xE1)
|
2016-06-16 21:57:02 +03:00
|
|
|
case = case.resize((4, 4), Image.BOX)
|
2019-06-13 18:54:24 +03:00
|
|
|
# fmt: off
|
|
|
|
data = ("e1 e1"
|
|
|
|
"e1 e1")
|
|
|
|
# fmt: on
|
2016-06-16 21:57:02 +03:00
|
|
|
for channel in case.split():
|
|
|
|
self.check_case(channel, self.make_sample(data, (4, 4)))
|
|
|
|
|
2016-05-21 23:58:54 +03:00
|
|
|
def test_reduce_bilinear(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
for mode in ["RGBX", "RGB", "La", "L"]:
|
|
|
|
case = self.make_case(mode, (8, 8), 0xE1)
|
2016-05-27 07:09:49 +03:00
|
|
|
case = case.resize((4, 4), Image.BILINEAR)
|
2019-06-13 18:54:24 +03:00
|
|
|
# fmt: off
|
|
|
|
data = ("e1 c9"
|
|
|
|
"c9 b7")
|
|
|
|
# fmt: on
|
2016-05-27 07:09:49 +03:00
|
|
|
for channel in case.split():
|
|
|
|
self.check_case(channel, self.make_sample(data, (4, 4)))
|
2016-05-21 23:58:54 +03:00
|
|
|
|
2016-06-16 21:57:02 +03:00
|
|
|
def test_reduce_hamming(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
for mode in ["RGBX", "RGB", "La", "L"]:
|
|
|
|
case = self.make_case(mode, (8, 8), 0xE1)
|
2016-06-16 21:57:02 +03:00
|
|
|
case = case.resize((4, 4), Image.HAMMING)
|
2019-06-13 18:54:24 +03:00
|
|
|
# fmt: off
|
|
|
|
data = ("e1 da"
|
|
|
|
"da d3")
|
|
|
|
# fmt: on
|
2016-06-16 21:57:02 +03:00
|
|
|
for channel in case.split():
|
|
|
|
self.check_case(channel, self.make_sample(data, (4, 4)))
|
|
|
|
|
2016-05-21 23:58:54 +03:00
|
|
|
def test_reduce_bicubic(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
for mode in ["RGBX", "RGB", "La", "L"]:
|
|
|
|
case = self.make_case(mode, (12, 12), 0xE1)
|
2016-05-27 07:09:49 +03:00
|
|
|
case = case.resize((6, 6), Image.BICUBIC)
|
2019-06-13 18:54:24 +03:00
|
|
|
# fmt: off
|
|
|
|
data = ("e1 e3 d4"
|
|
|
|
"e3 e5 d6"
|
|
|
|
"d4 d6 c9")
|
|
|
|
# fmt: on
|
2016-05-27 07:09:49 +03:00
|
|
|
for channel in case.split():
|
|
|
|
self.check_case(channel, self.make_sample(data, (6, 6)))
|
2016-05-21 23:58:54 +03:00
|
|
|
|
|
|
|
def test_reduce_lanczos(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
for mode in ["RGBX", "RGB", "La", "L"]:
|
|
|
|
case = self.make_case(mode, (16, 16), 0xE1)
|
2016-05-27 07:09:49 +03:00
|
|
|
case = case.resize((8, 8), Image.LANCZOS)
|
2019-06-13 18:54:24 +03:00
|
|
|
# fmt: off
|
|
|
|
data = ("e1 e0 e4 d7"
|
|
|
|
"e0 df e3 d6"
|
|
|
|
"e4 e3 e7 da"
|
|
|
|
"d7 d6 d9 ce")
|
|
|
|
# fmt: on
|
2016-05-27 07:09:49 +03:00
|
|
|
for channel in case.split():
|
|
|
|
self.check_case(channel, self.make_sample(data, (8, 8)))
|
2016-05-21 23:58:54 +03:00
|
|
|
|
2016-06-16 21:57:02 +03:00
|
|
|
def test_enlarge_box(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
for mode in ["RGBX", "RGB", "La", "L"]:
|
|
|
|
case = self.make_case(mode, (2, 2), 0xE1)
|
2016-06-16 21:57:02 +03:00
|
|
|
case = case.resize((4, 4), Image.BOX)
|
2019-06-13 18:54:24 +03:00
|
|
|
# fmt: off
|
|
|
|
data = ("e1 e1"
|
|
|
|
"e1 e1")
|
|
|
|
# fmt: on
|
2016-06-16 21:57:02 +03:00
|
|
|
for channel in case.split():
|
|
|
|
self.check_case(channel, self.make_sample(data, (4, 4)))
|
|
|
|
|
2016-05-21 23:58:54 +03:00
|
|
|
def test_enlarge_bilinear(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
for mode in ["RGBX", "RGB", "La", "L"]:
|
|
|
|
case = self.make_case(mode, (2, 2), 0xE1)
|
2016-05-27 07:09:49 +03:00
|
|
|
case = case.resize((4, 4), Image.BILINEAR)
|
2019-06-13 18:54:24 +03:00
|
|
|
# fmt: off
|
|
|
|
data = ("e1 b0"
|
|
|
|
"b0 98")
|
|
|
|
# fmt: on
|
2016-05-27 07:09:49 +03:00
|
|
|
for channel in case.split():
|
|
|
|
self.check_case(channel, self.make_sample(data, (4, 4)))
|
2016-05-21 23:58:54 +03:00
|
|
|
|
2016-06-16 21:57:02 +03:00
|
|
|
def test_enlarge_hamming(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
for mode in ["RGBX", "RGB", "La", "L"]:
|
|
|
|
case = self.make_case(mode, (2, 2), 0xE1)
|
2017-05-29 14:28:55 +03:00
|
|
|
case = case.resize((4, 4), Image.HAMMING)
|
2019-06-13 18:54:24 +03:00
|
|
|
# fmt: off
|
|
|
|
data = ("e1 d2"
|
|
|
|
"d2 c5")
|
|
|
|
# fmt: on
|
2016-06-16 21:57:02 +03:00
|
|
|
for channel in case.split():
|
2017-05-29 14:28:55 +03:00
|
|
|
self.check_case(channel, self.make_sample(data, (4, 4)))
|
2016-06-16 21:57:02 +03:00
|
|
|
|
2016-05-21 23:58:54 +03:00
|
|
|
def test_enlarge_bicubic(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
for mode in ["RGBX", "RGB", "La", "L"]:
|
|
|
|
case = self.make_case(mode, (4, 4), 0xE1)
|
2016-05-27 07:09:49 +03:00
|
|
|
case = case.resize((8, 8), Image.BICUBIC)
|
2019-06-13 18:54:24 +03:00
|
|
|
# fmt: off
|
|
|
|
data = ("e1 e5 ee b9"
|
|
|
|
"e5 e9 f3 bc"
|
|
|
|
"ee f3 fd c1"
|
|
|
|
"b9 bc c1 a2")
|
|
|
|
# fmt: on
|
2016-05-27 07:09:49 +03:00
|
|
|
for channel in case.split():
|
|
|
|
self.check_case(channel, self.make_sample(data, (8, 8)))
|
2016-05-21 23:58:54 +03:00
|
|
|
|
|
|
|
def test_enlarge_lanczos(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
for mode in ["RGBX", "RGB", "La", "L"]:
|
|
|
|
case = self.make_case(mode, (6, 6), 0xE1)
|
2016-05-27 07:09:49 +03:00
|
|
|
case = case.resize((12, 12), Image.LANCZOS)
|
2019-06-13 18:54:24 +03:00
|
|
|
data = (
|
|
|
|
"e1 e0 db ed f5 b8"
|
|
|
|
"e0 df da ec f3 b7"
|
|
|
|
"db db d6 e7 ee b5"
|
|
|
|
"ed ec e6 fb ff bf"
|
|
|
|
"f5 f4 ee ff ff c4"
|
|
|
|
"b8 b7 b4 bf c4 a0"
|
|
|
|
)
|
2016-05-27 07:09:49 +03:00
|
|
|
for channel in case.split():
|
|
|
|
self.check_case(channel, self.make_sample(data, (12, 12)))
|
2016-05-21 23:58:54 +03:00
|
|
|
|
|
|
|
|
2016-05-03 14:22:51 +03:00
|
|
|
class CoreResampleConsistencyTest(PillowTestCase):
|
2016-05-05 11:53:43 +03:00
|
|
|
def make_case(self, mode, fill):
|
|
|
|
im = Image.new(mode, (512, 9), fill)
|
2018-10-02 11:55:28 +03:00
|
|
|
return im.resize((9, 512), Image.LANCZOS), im.load()[0, 0]
|
2016-05-03 14:22:51 +03:00
|
|
|
|
2016-05-09 14:37:50 +03:00
|
|
|
def run_case(self, case):
|
|
|
|
channel, color = case
|
|
|
|
px = channel.load()
|
|
|
|
for x in range(channel.size[0]):
|
|
|
|
for y in range(channel.size[1]):
|
|
|
|
if px[x, y] != color:
|
2019-06-13 18:54:24 +03:00
|
|
|
message = "{} != {} for pixel {}".format(px[x, y], color, (x, y))
|
2016-05-09 14:37:50 +03:00
|
|
|
self.assertEqual(px[x, y], color, message)
|
2016-05-03 14:22:51 +03:00
|
|
|
|
2016-05-05 11:53:43 +03:00
|
|
|
def test_8u(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
im, color = self.make_case("RGB", (0, 64, 255))
|
2016-05-09 14:37:50 +03:00
|
|
|
r, g, b = im.split()
|
|
|
|
self.run_case((r, color[0]))
|
|
|
|
self.run_case((g, color[1]))
|
|
|
|
self.run_case((b, color[2]))
|
2019-06-13 18:54:24 +03:00
|
|
|
self.run_case(self.make_case("L", 12))
|
2016-05-05 11:53:43 +03:00
|
|
|
|
|
|
|
def test_32i(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
self.run_case(self.make_case("I", 12))
|
|
|
|
self.run_case(self.make_case("I", 0x7FFFFFFF))
|
|
|
|
self.run_case(self.make_case("I", -12))
|
|
|
|
self.run_case(self.make_case("I", -1 << 31))
|
2016-05-05 11:53:43 +03:00
|
|
|
|
|
|
|
def test_32f(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
self.run_case(self.make_case("F", 1))
|
|
|
|
self.run_case(self.make_case("F", 3.40282306074e38))
|
|
|
|
self.run_case(self.make_case("F", 1.175494e-38))
|
|
|
|
self.run_case(self.make_case("F", 1.192093e-07))
|
2016-05-05 11:53:43 +03:00
|
|
|
|
2016-05-03 14:22:51 +03:00
|
|
|
|
2016-05-09 22:13:44 +03:00
|
|
|
class CoreResampleAlphaCorrectTest(PillowTestCase):
|
2016-05-10 17:26:39 +03:00
|
|
|
def make_levels_case(self, mode):
|
|
|
|
i = Image.new(mode, (256, 16))
|
2016-05-09 22:13:44 +03:00
|
|
|
px = i.load()
|
|
|
|
for y in range(i.size[1]):
|
|
|
|
for x in range(i.size[0]):
|
2016-05-10 17:26:39 +03:00
|
|
|
pix = [x] * len(mode)
|
|
|
|
pix[-1] = 255 - y * 16
|
|
|
|
px[x, y] = tuple(pix)
|
|
|
|
return i
|
2016-05-09 22:13:44 +03:00
|
|
|
|
2016-05-10 17:26:39 +03:00
|
|
|
def run_levels_case(self, i):
|
2016-05-09 22:13:44 +03:00
|
|
|
px = i.load()
|
|
|
|
for y in range(i.size[1]):
|
2016-11-07 15:33:46 +03:00
|
|
|
used_colors = {px[x, y][0] for x in range(i.size[0])}
|
2019-06-13 18:54:24 +03:00
|
|
|
self.assertEqual(
|
|
|
|
256,
|
|
|
|
len(used_colors),
|
|
|
|
"All colors should present in resized image. "
|
|
|
|
"Only {} on {} line.".format(len(used_colors), y),
|
|
|
|
)
|
2016-05-09 22:13:44 +03:00
|
|
|
|
2016-05-10 17:26:39 +03:00
|
|
|
@unittest.skip("current implementation isn't precise enough")
|
|
|
|
def test_levels_rgba(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
case = self.make_levels_case("RGBA")
|
2016-06-16 21:57:02 +03:00
|
|
|
self.run_levels_case(case.resize((512, 32), Image.BOX))
|
2016-05-10 17:26:39 +03:00
|
|
|
self.run_levels_case(case.resize((512, 32), Image.BILINEAR))
|
2016-06-16 21:57:02 +03:00
|
|
|
self.run_levels_case(case.resize((512, 32), Image.HAMMING))
|
2016-05-10 17:26:39 +03:00
|
|
|
self.run_levels_case(case.resize((512, 32), Image.BICUBIC))
|
|
|
|
self.run_levels_case(case.resize((512, 32), Image.LANCZOS))
|
|
|
|
|
2016-05-10 21:46:40 +03:00
|
|
|
@unittest.skip("current implementation isn't precise enough")
|
2016-05-10 17:26:39 +03:00
|
|
|
def test_levels_la(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
case = self.make_levels_case("LA")
|
2016-06-16 21:57:02 +03:00
|
|
|
self.run_levels_case(case.resize((512, 32), Image.BOX))
|
2016-05-10 17:26:39 +03:00
|
|
|
self.run_levels_case(case.resize((512, 32), Image.BILINEAR))
|
2016-06-16 21:57:02 +03:00
|
|
|
self.run_levels_case(case.resize((512, 32), Image.HAMMING))
|
2016-05-10 17:26:39 +03:00
|
|
|
self.run_levels_case(case.resize((512, 32), Image.BICUBIC))
|
|
|
|
self.run_levels_case(case.resize((512, 32), Image.LANCZOS))
|
|
|
|
|
2016-11-24 03:08:57 +03:00
|
|
|
def make_dirty_case(self, mode, clean_pixel, dirty_pixel):
|
2016-05-10 17:26:39 +03:00
|
|
|
i = Image.new(mode, (64, 64), dirty_pixel)
|
|
|
|
px = i.load()
|
|
|
|
xdiv4 = i.size[0] // 4
|
|
|
|
ydiv4 = i.size[1] // 4
|
|
|
|
for y in range(ydiv4 * 2):
|
|
|
|
for x in range(xdiv4 * 2):
|
|
|
|
px[x + xdiv4, y + ydiv4] = clean_pixel
|
|
|
|
return i
|
|
|
|
|
2016-11-24 03:08:57 +03:00
|
|
|
def run_dirty_case(self, i, clean_pixel):
|
2016-05-09 23:06:34 +03:00
|
|
|
px = i.load()
|
2016-05-10 17:26:39 +03:00
|
|
|
for y in range(i.size[1]):
|
|
|
|
for x in range(i.size[0]):
|
|
|
|
if px[x, y][-1] != 0 and px[x, y][:-1] != clean_pixel:
|
2019-06-13 18:54:24 +03:00
|
|
|
message = "pixel at ({}, {}) is differ:\n{}\n{}".format(
|
|
|
|
x, y, px[x, y], clean_pixel
|
|
|
|
)
|
2016-05-10 17:26:39 +03:00
|
|
|
self.assertEqual(px[x, y][:3], clean_pixel, message)
|
|
|
|
|
|
|
|
def test_dirty_pixels_rgba(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
case = self.make_dirty_case("RGBA", (255, 255, 0, 128), (0, 0, 255, 0))
|
2016-11-24 03:08:57 +03:00
|
|
|
self.run_dirty_case(case.resize((20, 20), Image.BOX), (255, 255, 0))
|
2019-06-13 18:54:24 +03:00
|
|
|
self.run_dirty_case(case.resize((20, 20), Image.BILINEAR), (255, 255, 0))
|
|
|
|
self.run_dirty_case(case.resize((20, 20), Image.HAMMING), (255, 255, 0))
|
|
|
|
self.run_dirty_case(case.resize((20, 20), Image.BICUBIC), (255, 255, 0))
|
|
|
|
self.run_dirty_case(case.resize((20, 20), Image.LANCZOS), (255, 255, 0))
|
2016-05-10 17:26:39 +03:00
|
|
|
|
|
|
|
def test_dirty_pixels_la(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
case = self.make_dirty_case("LA", (255, 128), (0, 0))
|
2016-11-24 03:08:57 +03:00
|
|
|
self.run_dirty_case(case.resize((20, 20), Image.BOX), (255,))
|
|
|
|
self.run_dirty_case(case.resize((20, 20), Image.BILINEAR), (255,))
|
|
|
|
self.run_dirty_case(case.resize((20, 20), Image.HAMMING), (255,))
|
|
|
|
self.run_dirty_case(case.resize((20, 20), Image.BICUBIC), (255,))
|
|
|
|
self.run_dirty_case(case.resize((20, 20), Image.LANCZOS), (255,))
|
2016-05-09 23:06:34 +03:00
|
|
|
|
2016-05-09 22:13:44 +03:00
|
|
|
|
2016-06-15 00:55:57 +03:00
|
|
|
class CoreResamplePassesTest(PillowTestCase):
|
2016-12-02 15:40:32 +03:00
|
|
|
@contextmanager
|
|
|
|
def count(self, diff):
|
2019-06-13 18:54:24 +03:00
|
|
|
count = Image.core.get_stats()["new_count"]
|
2016-12-02 15:40:32 +03:00
|
|
|
yield
|
2019-06-13 18:54:24 +03:00
|
|
|
self.assertEqual(Image.core.get_stats()["new_count"] - count, diff)
|
2016-12-02 15:40:32 +03:00
|
|
|
|
2016-06-15 00:55:57 +03:00
|
|
|
def test_horizontal(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
im = hopper("L")
|
2016-12-02 15:40:32 +03:00
|
|
|
with self.count(1):
|
|
|
|
im.resize((im.size[0] - 10, im.size[1]), Image.BILINEAR)
|
2016-06-15 00:55:57 +03:00
|
|
|
|
|
|
|
def test_vertical(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
im = hopper("L")
|
2016-12-02 15:40:32 +03:00
|
|
|
with self.count(1):
|
|
|
|
im.resize((im.size[0], im.size[1] - 10), Image.BILINEAR)
|
2016-06-15 00:55:57 +03:00
|
|
|
|
|
|
|
def test_both(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
im = hopper("L")
|
2016-12-02 15:40:32 +03:00
|
|
|
with self.count(2):
|
|
|
|
im.resize((im.size[0] - 10, im.size[1] - 10), Image.BILINEAR)
|
2016-06-15 00:55:57 +03:00
|
|
|
|
2016-12-02 15:40:32 +03:00
|
|
|
def test_box_horizontal(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
im = hopper("L")
|
2016-12-02 15:40:32 +03:00
|
|
|
box = (20, 0, im.size[0] - 20, im.size[1])
|
|
|
|
with self.count(1):
|
|
|
|
# the same size, but different box
|
|
|
|
with_box = im.resize(im.size, Image.BILINEAR, box)
|
2016-12-02 15:42:33 +03:00
|
|
|
with self.count(2):
|
|
|
|
cropped = im.crop(box).resize(im.size, Image.BILINEAR)
|
2016-12-02 15:40:32 +03:00
|
|
|
self.assert_image_similar(with_box, cropped, 0.1)
|
|
|
|
|
|
|
|
def test_box_vertical(self):
|
2019-06-13 18:54:24 +03:00
|
|
|
im = hopper("L")
|
2016-12-02 15:40:32 +03:00
|
|
|
box = (0, 20, im.size[0], im.size[1] - 20)
|
|
|
|
with self.count(1):
|
|
|
|
# the same size, but different box
|
|
|
|
with_box = im.resize(im.size, Image.BILINEAR, box)
|
2016-12-02 15:42:33 +03:00
|
|
|
with self.count(2):
|
|
|
|
cropped = im.crop(box).resize(im.size, Image.BILINEAR)
|
2016-12-02 15:40:32 +03:00
|
|
|
self.assert_image_similar(with_box, cropped, 0.1)
|
2016-06-15 00:55:57 +03:00
|
|
|
|
2017-08-31 22:28:43 +03:00
|
|
|
|
2016-07-03 02:09:39 +03:00
|
|
|
class CoreResampleCoefficientsTest(PillowTestCase):
|
|
|
|
def test_reduce(self):
|
|
|
|
test_color = 254
|
|
|
|
|
|
|
|
for size in range(400000, 400010, 2):
|
2019-06-13 18:54:24 +03:00
|
|
|
i = Image.new("L", (size, 1), 0)
|
2016-07-03 02:09:39 +03:00
|
|
|
draw = ImageDraw.Draw(i)
|
|
|
|
draw.rectangle((0, 0, i.size[0] // 2 - 1, 0), test_color)
|
|
|
|
|
|
|
|
px = i.resize((5, i.size[1]), Image.BICUBIC).load()
|
|
|
|
if px[2, 0] != test_color // 2:
|
|
|
|
self.assertEqual(test_color // 2, px[2, 0])
|
|
|
|
|
2016-10-17 11:24:40 +03:00
|
|
|
def test_nonzero_coefficients(self):
|
|
|
|
# regression test for the wrong coefficients calculation
|
|
|
|
# due to bug https://github.com/python-pillow/Pillow/issues/2161
|
2019-06-13 18:54:24 +03:00
|
|
|
im = Image.new("RGBA", (1280, 1280), (0x20, 0x40, 0x60, 0xFF))
|
2016-10-17 11:24:40 +03:00
|
|
|
histogram = im.resize((256, 256), Image.BICUBIC).histogram()
|
|
|
|
|
2018-06-24 15:32:25 +03:00
|
|
|
# first channel
|
|
|
|
self.assertEqual(histogram[0x100 * 0 + 0x20], 0x10000)
|
|
|
|
# second channel
|
|
|
|
self.assertEqual(histogram[0x100 * 1 + 0x40], 0x10000)
|
|
|
|
# third channel
|
|
|
|
self.assertEqual(histogram[0x100 * 2 + 0x60], 0x10000)
|
|
|
|
# fourth channel
|
2019-06-13 18:54:24 +03:00
|
|
|
self.assertEqual(histogram[0x100 * 3 + 0xFF], 0x10000)
|
2016-10-17 11:24:40 +03:00
|
|
|
|
2016-07-03 02:09:39 +03:00
|
|
|
|
2016-12-02 02:59:40 +03:00
|
|
|
class CoreResampleBoxTest(PillowTestCase):
|
2016-11-24 04:40:54 +03:00
|
|
|
def test_wrong_arguments(self):
|
|
|
|
im = hopper()
|
2019-06-13 18:54:24 +03:00
|
|
|
for resample in (
|
|
|
|
Image.NEAREST,
|
|
|
|
Image.BOX,
|
|
|
|
Image.BILINEAR,
|
|
|
|
Image.HAMMING,
|
|
|
|
Image.BICUBIC,
|
|
|
|
Image.LANCZOS,
|
|
|
|
):
|
2016-11-24 04:40:54 +03:00
|
|
|
im.resize((32, 32), resample, (0, 0, im.width, im.height))
|
|
|
|
im.resize((32, 32), resample, (20, 20, im.width, im.height))
|
2017-08-11 19:36:46 +03:00
|
|
|
im.resize((32, 32), resample, (20, 20, 20, 100))
|
|
|
|
im.resize((32, 32), resample, (20, 20, 100, 20))
|
2016-11-24 04:40:54 +03:00
|
|
|
|
2019-06-13 18:54:24 +03:00
|
|
|
with self.assertRaisesRegex(TypeError, "must be sequence of length 4"):
|
2016-11-24 04:40:54 +03:00
|
|
|
im.resize((32, 32), resample, (im.width, im.height))
|
|
|
|
|
2018-04-10 13:40:44 +03:00
|
|
|
with self.assertRaisesRegex(ValueError, "can't be negative"):
|
2016-11-24 04:40:54 +03:00
|
|
|
im.resize((32, 32), resample, (-20, 20, 100, 100))
|
2018-04-10 13:40:44 +03:00
|
|
|
with self.assertRaisesRegex(ValueError, "can't be negative"):
|
2016-11-24 04:40:54 +03:00
|
|
|
im.resize((32, 32), resample, (20, -20, 100, 100))
|
|
|
|
|
2018-04-10 13:40:44 +03:00
|
|
|
with self.assertRaisesRegex(ValueError, "can't be empty"):
|
2017-08-11 19:36:46 +03:00
|
|
|
im.resize((32, 32), resample, (20.1, 20, 20, 100))
|
2018-04-10 13:40:44 +03:00
|
|
|
with self.assertRaisesRegex(ValueError, "can't be empty"):
|
2017-08-11 19:36:46 +03:00
|
|
|
im.resize((32, 32), resample, (20, 20.1, 100, 20))
|
2018-04-10 13:40:44 +03:00
|
|
|
with self.assertRaisesRegex(ValueError, "can't be empty"):
|
2017-08-11 19:36:46 +03:00
|
|
|
im.resize((32, 32), resample, (20.1, 20.1, 20, 20))
|
2016-11-24 04:40:54 +03:00
|
|
|
|
2018-04-10 13:40:44 +03:00
|
|
|
with self.assertRaisesRegex(ValueError, "can't exceed"):
|
2016-11-24 04:40:54 +03:00
|
|
|
im.resize((32, 32), resample, (0, 0, im.width + 1, im.height))
|
2018-04-10 13:40:44 +03:00
|
|
|
with self.assertRaisesRegex(ValueError, "can't exceed"):
|
2016-11-24 04:40:54 +03:00
|
|
|
im.resize((32, 32), resample, (0, 0, im.width, im.height + 1))
|
|
|
|
|
2016-12-02 02:59:40 +03:00
|
|
|
def resize_tiled(self, im, dst_size, xtiles, ytiles):
|
|
|
|
def split_range(size, tiles):
|
|
|
|
scale = size / tiles
|
|
|
|
for i in range(tiles):
|
|
|
|
yield (int(round(scale * i)), int(round(scale * (i + 1))))
|
2016-11-24 05:12:41 +03:00
|
|
|
|
2016-12-02 02:59:40 +03:00
|
|
|
tiled = Image.new(im.mode, dst_size)
|
|
|
|
scale = (im.size[0] / tiled.size[0], im.size[1] / tiled.size[1])
|
2016-11-24 05:12:41 +03:00
|
|
|
|
2016-12-02 02:59:40 +03:00
|
|
|
for y0, y1 in split_range(dst_size[1], ytiles):
|
|
|
|
for x0, x1 in split_range(dst_size[0], xtiles):
|
2019-06-13 18:54:24 +03:00
|
|
|
box = (x0 * scale[0], y0 * scale[1], x1 * scale[0], y1 * scale[1])
|
2016-12-02 02:59:40 +03:00
|
|
|
tile = im.resize((x1 - x0, y1 - y0), Image.BICUBIC, box)
|
|
|
|
tiled.paste(tile, (x0, y0))
|
|
|
|
return tiled
|
2016-11-24 06:11:36 +03:00
|
|
|
|
2016-12-02 02:59:40 +03:00
|
|
|
def test_tiles(self):
|
|
|
|
im = Image.open("Tests/images/flower.jpg")
|
2018-01-17 14:01:37 +03:00
|
|
|
self.assertEqual(im.size, (480, 360))
|
2016-12-02 02:59:40 +03:00
|
|
|
dst_size = (251, 188)
|
|
|
|
reference = im.resize(dst_size, Image.BICUBIC)
|
2016-11-26 01:38:55 +03:00
|
|
|
|
2016-12-02 02:59:40 +03:00
|
|
|
for tiles in [(1, 1), (3, 3), (9, 7), (100, 100)]:
|
|
|
|
tiled = self.resize_tiled(im, dst_size, *tiles)
|
|
|
|
self.assert_image_similar(reference, tiled, 0.01)
|
2016-11-26 01:38:55 +03:00
|
|
|
|
2016-12-02 02:59:40 +03:00
|
|
|
def test_subsample(self):
|
|
|
|
# This test shows advantages of the subpixel resizing
|
|
|
|
# after supersampling (e.g. during JPEG decoding).
|
|
|
|
im = Image.open("Tests/images/flower.jpg")
|
2018-01-17 14:01:37 +03:00
|
|
|
self.assertEqual(im.size, (480, 360))
|
2016-12-02 02:59:40 +03:00
|
|
|
dst_size = (48, 36)
|
|
|
|
# Reference is cropped image resized to destination
|
|
|
|
reference = im.crop((0, 0, 473, 353)).resize(dst_size, Image.BICUBIC)
|
|
|
|
# Image.BOX emulates supersampling (480 / 8 = 60, 360 / 8 = 45)
|
|
|
|
supersampled = im.resize((60, 45), Image.BOX)
|
|
|
|
|
2019-06-13 18:54:24 +03:00
|
|
|
with_box = supersampled.resize(dst_size, Image.BICUBIC, (0, 0, 59.125, 44.125))
|
2016-12-02 02:59:40 +03:00
|
|
|
without_box = supersampled.resize(dst_size, Image.BICUBIC)
|
|
|
|
|
|
|
|
# error with box should be much smaller than without
|
|
|
|
self.assert_image_similar(reference, with_box, 6)
|
2018-10-02 10:57:07 +03:00
|
|
|
with self.assertRaisesRegex(AssertionError, r"difference 29\."):
|
2016-12-02 02:59:40 +03:00
|
|
|
self.assert_image_similar(reference, without_box, 5)
|
2016-11-26 01:38:55 +03:00
|
|
|
|
2016-12-02 16:33:48 +03:00
|
|
|
def test_formats(self):
|
|
|
|
for resample in [Image.NEAREST, Image.BILINEAR]:
|
2019-06-13 18:54:24 +03:00
|
|
|
for mode in ["RGB", "L", "RGBA", "LA", "I", ""]:
|
2016-12-02 16:33:48 +03:00
|
|
|
im = hopper(mode)
|
|
|
|
box = (20, 20, im.size[0] - 20, im.size[1] - 20)
|
|
|
|
with_box = im.resize((32, 32), resample, box)
|
|
|
|
cropped = im.crop(box).resize((32, 32), resample)
|
|
|
|
self.assert_image_similar(cropped, with_box, 0.4)
|
|
|
|
|
2017-08-31 22:28:43 +03:00
|
|
|
def test_passthrough(self):
|
2017-09-01 08:42:57 +03:00
|
|
|
# When no resize is required
|
2017-08-31 22:28:43 +03:00
|
|
|
im = hopper()
|
|
|
|
|
|
|
|
for size, box in [
|
|
|
|
((40, 50), (0, 0, 40, 50)),
|
|
|
|
((40, 50), (0, 10, 40, 60)),
|
|
|
|
((40, 50), (10, 0, 50, 50)),
|
|
|
|
((40, 50), (10, 20, 50, 70)),
|
|
|
|
]:
|
|
|
|
try:
|
|
|
|
res = im.resize(size, Image.LANCZOS, box)
|
|
|
|
self.assertEqual(res.size, size)
|
|
|
|
self.assert_image_equal(res, im.crop(box))
|
|
|
|
except AssertionError:
|
2019-06-13 18:54:24 +03:00
|
|
|
print(">>>", size, box)
|
2017-08-31 22:28:43 +03:00
|
|
|
raise
|
|
|
|
|
|
|
|
def test_no_passthrough(self):
|
2017-09-01 08:42:57 +03:00
|
|
|
# When resize is required
|
2017-08-31 22:28:43 +03:00
|
|
|
im = hopper()
|
|
|
|
|
|
|
|
for size, box in [
|
|
|
|
((40, 50), (0.4, 0.4, 40.4, 50.4)),
|
|
|
|
((40, 50), (0.4, 10.4, 40.4, 60.4)),
|
|
|
|
((40, 50), (10.4, 0.4, 50.4, 50.4)),
|
|
|
|
((40, 50), (10.4, 20.4, 50.4, 70.4)),
|
|
|
|
]:
|
|
|
|
try:
|
|
|
|
res = im.resize(size, Image.LANCZOS, box)
|
|
|
|
self.assertEqual(res.size, size)
|
2018-10-02 10:57:07 +03:00
|
|
|
with self.assertRaisesRegex(AssertionError, r"difference \d"):
|
2017-08-31 22:28:43 +03:00
|
|
|
# check that the difference at least that much
|
|
|
|
self.assert_image_similar(res, im.crop(box), 20)
|
|
|
|
except AssertionError:
|
2019-06-13 18:54:24 +03:00
|
|
|
print(">>>", size, box)
|
2017-08-31 22:28:43 +03:00
|
|
|
raise
|
|
|
|
|
|
|
|
def test_skip_horizontal(self):
|
2017-09-01 08:42:57 +03:00
|
|
|
# Can skip resize for one dimension
|
2017-08-31 22:28:43 +03:00
|
|
|
im = hopper()
|
|
|
|
|
|
|
|
for flt in [Image.NEAREST, Image.BICUBIC]:
|
|
|
|
for size, box in [
|
|
|
|
((40, 50), (0, 0, 40, 90)),
|
|
|
|
((40, 50), (0, 20, 40, 90)),
|
|
|
|
((40, 50), (10, 0, 50, 90)),
|
|
|
|
((40, 50), (10, 20, 50, 90)),
|
|
|
|
]:
|
|
|
|
try:
|
|
|
|
res = im.resize(size, flt, box)
|
|
|
|
self.assertEqual(res.size, size)
|
|
|
|
# Borders should be slightly different
|
2019-06-13 18:54:24 +03:00
|
|
|
self.assert_image_similar(res, im.crop(box).resize(size, flt), 0.4)
|
2017-08-31 22:28:43 +03:00
|
|
|
except AssertionError:
|
2019-06-13 18:54:24 +03:00
|
|
|
print(">>>", size, box, flt)
|
2017-08-31 22:28:43 +03:00
|
|
|
raise
|
|
|
|
|
|
|
|
def test_skip_vertical(self):
|
2017-09-01 08:42:57 +03:00
|
|
|
# Can skip resize for one dimension
|
2017-08-31 22:28:43 +03:00
|
|
|
im = hopper()
|
|
|
|
|
|
|
|
for flt in [Image.NEAREST, Image.BICUBIC]:
|
|
|
|
for size, box in [
|
|
|
|
((40, 50), (0, 0, 90, 50)),
|
|
|
|
((40, 50), (20, 0, 90, 50)),
|
|
|
|
((40, 50), (0, 10, 90, 60)),
|
|
|
|
((40, 50), (20, 10, 90, 60)),
|
|
|
|
]:
|
|
|
|
try:
|
|
|
|
res = im.resize(size, flt, box)
|
|
|
|
self.assertEqual(res.size, size)
|
|
|
|
# Borders should be slightly different
|
2019-06-13 18:54:24 +03:00
|
|
|
self.assert_image_similar(res, im.crop(box).resize(size, flt), 0.4)
|
2017-08-31 22:28:43 +03:00
|
|
|
except AssertionError:
|
2019-06-13 18:54:24 +03:00
|
|
|
print(">>>", size, box, flt)
|
2017-08-31 22:28:43 +03:00
|
|
|
raise
|