diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index 14a8da9f102..cfe46b65898 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -5,90 +5,109 @@ from .helper import assert_image_equal, hopper -def test_sanity(): - def apply_filter(filter_to_apply): - for mode in ["L", "RGB", "CMYK"]: - im = hopper(mode) - out = im.filter(filter_to_apply) - assert out.mode == im.mode - assert out.size == im.size - - apply_filter(ImageFilter.BLUR) - apply_filter(ImageFilter.CONTOUR) - apply_filter(ImageFilter.DETAIL) - apply_filter(ImageFilter.EDGE_ENHANCE) - apply_filter(ImageFilter.EDGE_ENHANCE_MORE) - apply_filter(ImageFilter.EMBOSS) - apply_filter(ImageFilter.FIND_EDGES) - apply_filter(ImageFilter.SMOOTH) - apply_filter(ImageFilter.SMOOTH_MORE) - apply_filter(ImageFilter.SHARPEN) - apply_filter(ImageFilter.MaxFilter) - apply_filter(ImageFilter.MedianFilter) - apply_filter(ImageFilter.MinFilter) - apply_filter(ImageFilter.ModeFilter) - apply_filter(ImageFilter.GaussianBlur) - apply_filter(ImageFilter.GaussianBlur(5)) - apply_filter(ImageFilter.BoxBlur(5)) - apply_filter(ImageFilter.UnsharpMask) - apply_filter(ImageFilter.UnsharpMask(10)) - +@pytest.mark.parametrize( + "filter_to_apply", + ( + ImageFilter.BLUR, + ImageFilter.CONTOUR, + ImageFilter.DETAIL, + ImageFilter.EDGE_ENHANCE, + ImageFilter.EDGE_ENHANCE_MORE, + ImageFilter.EMBOSS, + ImageFilter.FIND_EDGES, + ImageFilter.SMOOTH, + ImageFilter.SMOOTH_MORE, + ImageFilter.SHARPEN, + ImageFilter.MaxFilter, + ImageFilter.MedianFilter, + ImageFilter.MinFilter, + ImageFilter.ModeFilter, + ImageFilter.GaussianBlur, + ImageFilter.GaussianBlur(5), + ImageFilter.BoxBlur(5), + ImageFilter.UnsharpMask, + ImageFilter.UnsharpMask(10), + ), +) +@pytest.mark.parametrize("mode", ("L", "RGB", "CMYK")) +def test_sanity(filter_to_apply, mode): + im = hopper(mode) + out = im.filter(filter_to_apply) + assert out.mode == im.mode + assert out.size == im.size + + +@pytest.mark.parametrize("mode", ("L", "RGB", "CMYK")) +def test_sanity_error(mode): with pytest.raises(TypeError): - apply_filter("hello") - - -def test_crash(): - - # crashes on small images - im = Image.new("RGB", (1, 1)) - im.filter(ImageFilter.SMOOTH) + im = hopper(mode) + im.filter("hello") - im = Image.new("RGB", (2, 2)) - im.filter(ImageFilter.SMOOTH) - im = Image.new("RGB", (3, 3)) +# crashes on small images +@pytest.mark.parametrize("size", ((1, 1), (2, 2), (3, 3))) +def test_crash(size): + im = Image.new("RGB", size) im.filter(ImageFilter.SMOOTH) -def test_modefilter(): - def modefilter(mode): - im = Image.new(mode, (3, 3), None) - im.putdata(list(range(9))) - # image is: - # 0 1 2 - # 3 4 5 - # 6 7 8 - mod = im.filter(ImageFilter.ModeFilter).getpixel((1, 1)) - im.putdata([0, 0, 1, 2, 5, 1, 5, 2, 0]) # mode=0 - mod2 = im.filter(ImageFilter.ModeFilter).getpixel((1, 1)) - return mod, mod2 - - assert modefilter("1") == (4, 0) - assert modefilter("L") == (4, 0) - assert modefilter("P") == (4, 0) - assert modefilter("RGB") == ((4, 0, 0), (0, 0, 0)) - - -def test_rankfilter(): - def rankfilter(mode): - im = Image.new(mode, (3, 3), None) +@pytest.mark.parametrize( + "mode, expected", + ( + ("1", (4, 0)), + ("L", (4, 0)), + ("P", (4, 0)), + ("RGB", ((4, 0, 0), (0, 0, 0))), + ), +) +def test_modefilter(mode, expected): + im = Image.new(mode, (3, 3), None) + im.putdata(list(range(9))) + # image is: + # 0 1 2 + # 3 4 5 + # 6 7 8 + mod = im.filter(ImageFilter.ModeFilter).getpixel((1, 1)) + im.putdata([0, 0, 1, 2, 5, 1, 5, 2, 0]) # mode=0 + mod2 = im.filter(ImageFilter.ModeFilter).getpixel((1, 1)) + assert (mod, mod2) == expected + + +@pytest.mark.parametrize( + "mode, expected", + ( + ("1", (0, 4, 8)), + ("L", (0, 4, 8)), + ("RGB", ((0, 0, 0), (4, 0, 0), (8, 0, 0))), + ("I", (0, 4, 8)), + ("F", (0.0, 4.0, 8.0)), + ), +) +def test_rankfilter(mode, expected): + im = Image.new(mode, (3, 3), None) + im.putdata(list(range(9))) + # image is: + # 0 1 2 + # 3 4 5 + # 6 7 8 + minimum = im.filter(ImageFilter.MinFilter).getpixel((1, 1)) + med = im.filter(ImageFilter.MedianFilter).getpixel((1, 1)) + maximum = im.filter(ImageFilter.MaxFilter).getpixel((1, 1)) + assert (minimum, med, maximum) == expected + + +@pytest.mark.parametrize( + "filter", (ImageFilter.MinFilter, ImageFilter.MedianFilter, ImageFilter.MaxFilter) +) +def test_rankfilter_error(filter): + with pytest.raises(ValueError): + im = Image.new("P", (3, 3), None) im.putdata(list(range(9))) # image is: # 0 1 2 # 3 4 5 # 6 7 8 - minimum = im.filter(ImageFilter.MinFilter).getpixel((1, 1)) - med = im.filter(ImageFilter.MedianFilter).getpixel((1, 1)) - maximum = im.filter(ImageFilter.MaxFilter).getpixel((1, 1)) - return minimum, med, maximum - - assert rankfilter("1") == (0, 4, 8) - assert rankfilter("L") == (0, 4, 8) - with pytest.raises(ValueError): - rankfilter("P") - assert rankfilter("RGB") == ((0, 0, 0), (4, 0, 0), (8, 0, 0)) - assert rankfilter("I") == (0, 4, 8) - assert rankfilter("F") == (0.0, 4.0, 8.0) + im.filter(filter).getpixel((1, 1)) def test_rankfilter_properties(): @@ -110,7 +129,8 @@ def test_kernel_not_enough_coefficients(): ImageFilter.Kernel((3, 3), (0, 0)) -def test_consistency_3x3(): +@pytest.mark.parametrize("mode", ("L", "LA", "RGB", "CMYK")) +def test_consistency_3x3(mode): with Image.open("Tests/images/hopper.bmp") as source: with Image.open("Tests/images/hopper_emboss.bmp") as reference: kernel = ImageFilter.Kernel( @@ -125,14 +145,14 @@ def test_consistency_3x3(): source = source.split() * 2 reference = reference.split() * 2 - for mode in ["L", "LA", "RGB", "CMYK"]: - assert_image_equal( - Image.merge(mode, source[: len(mode)]).filter(kernel), - Image.merge(mode, reference[: len(mode)]), - ) + assert_image_equal( + Image.merge(mode, source[: len(mode)]).filter(kernel), + Image.merge(mode, reference[: len(mode)]), + ) -def test_consistency_5x5(): +@pytest.mark.parametrize("mode", ("L", "LA", "RGB", "CMYK")) +def test_consistency_5x5(mode): with Image.open("Tests/images/hopper.bmp") as source: with Image.open("Tests/images/hopper_emboss_more.bmp") as reference: kernel = ImageFilter.Kernel( @@ -149,8 +169,7 @@ def test_consistency_5x5(): source = source.split() * 2 reference = reference.split() * 2 - for mode in ["L", "LA", "RGB", "CMYK"]: - assert_image_equal( - Image.merge(mode, source[: len(mode)]).filter(kernel), - Image.merge(mode, reference[: len(mode)]), - ) + assert_image_equal( + Image.merge(mode, source[: len(mode)]).filter(kernel), + Image.merge(mode, reference[: len(mode)]), + ) diff --git a/Tests/test_image_reduce.py b/Tests/test_image_reduce.py index 70dc87f0a86..801161511b9 100644 --- a/Tests/test_image_reduce.py +++ b/Tests/test_image_reduce.py @@ -38,58 +38,64 @@ gradients_image.load() -def test_args_factor(): +@pytest.mark.parametrize( + "size, expected", + ( + (3, (4, 4)), + ((3, 1), (4, 10)), + ((1, 3), (10, 4)), + ), +) +def test_args_factor(size, expected): im = Image.new("L", (10, 10)) + assert expected == im.reduce(size).size - 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): - im.reduce(0) - with pytest.raises(TypeError): - im.reduce(2.0) - with pytest.raises(ValueError): - im.reduce((0, 10)) - - -def test_args_box(): +@pytest.mark.parametrize( + "size, expected_error", ((0, ValueError), (2.0, TypeError), ((0, 10), ValueError)) +) +def test_args_factor_error(size, expected_error): im = Image.new("L", (10, 10)) - - assert (5, 5) == im.reduce(2, (0, 0, 10, 10)).size - assert (1, 1) == im.reduce(2, (5, 5, 6, 6)).size - - 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)) + with pytest.raises(expected_error): + im.reduce(size) + + +@pytest.mark.parametrize( + "size, expected", + ( + ((0, 0, 10, 10), (5, 5)), + ((5, 5, 6, 6), (1, 1)), + ), +) +def test_args_box(size, expected): + im = Image.new("L", (10, 10)) + assert expected == im.reduce(2, size).size + + +@pytest.mark.parametrize( + "size, expected_error", + ( + ("stri", TypeError), + ((0, 0, 11, 10), ValueError), + ((0, 0, 10, 11), ValueError), + ((-1, 0, 10, 10), ValueError), + ((0, -1, 10, 10), ValueError), + ((0, 5, 10, 5), ValueError), + ((5, 0, 5, 10), ValueError), + ), +) +def test_args_box_error(size, expected_error): + im = Image.new("L", (10, 10)) + with pytest.raises(expected_error): + im.reduce(2, size).size -def test_unsupported_modes(): +@pytest.mark.parametrize("mode", ("P", "1", "I;16")) +def test_unsupported_modes(mode): im = Image.new("P", (10, 10)) with pytest.raises(ValueError): im.reduce(3) - im = Image.new("1", (10, 10)) - with pytest.raises(ValueError): - im.reduce(3) - - im = Image.new("I;16", (10, 10)) - with pytest.raises(ValueError): - im.reduce(3) - def get_image(mode): mode_info = ImageMode.getmode(mode) @@ -197,63 +203,69 @@ def test_mode_L(): compare_reduce_with_box(im, factor) -def test_mode_LA(): +@pytest.mark.parametrize("factor", remarkable_factors) +def test_mode_LA(factor): im = get_image("LA") - for factor in remarkable_factors: - compare_reduce_with_reference(im, factor, 0.8, 5) + compare_reduce_with_reference(im, factor, 0.8, 5) + +@pytest.mark.parametrize("factor", remarkable_factors) +def test_mode_LA_opaque(factor): + im = get_image("LA") # 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) + compare_reduce_with_reference(im, factor) + compare_reduce_with_box(im, factor) -def test_mode_La(): +@pytest.mark.parametrize("factor", remarkable_factors) +def test_mode_La(factor): im = get_image("La") - for factor in remarkable_factors: - compare_reduce_with_reference(im, factor) - compare_reduce_with_box(im, factor) + compare_reduce_with_reference(im, factor) + compare_reduce_with_box(im, factor) -def test_mode_RGB(): +@pytest.mark.parametrize("factor", remarkable_factors) +def test_mode_RGB(factor): im = get_image("RGB") - for factor in remarkable_factors: - compare_reduce_with_reference(im, factor) - compare_reduce_with_box(im, factor) + compare_reduce_with_reference(im, factor) + compare_reduce_with_box(im, factor) -def test_mode_RGBA(): +@pytest.mark.parametrize("factor", remarkable_factors) +def test_mode_RGBA(factor): im = get_image("RGBA") - for factor in remarkable_factors: - compare_reduce_with_reference(im, factor, 0.8, 5) + compare_reduce_with_reference(im, factor, 0.8, 5) + +@pytest.mark.parametrize("factor", remarkable_factors) +def test_mode_RGBA_opaque(factor): + im = get_image("RGBA") # 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) + compare_reduce_with_reference(im, factor) + compare_reduce_with_box(im, factor) -def test_mode_RGBa(): +@pytest.mark.parametrize("factor", remarkable_factors) +def test_mode_RGBa(factor): im = get_image("RGBa") - for factor in remarkable_factors: - compare_reduce_with_reference(im, factor) - compare_reduce_with_box(im, factor) + compare_reduce_with_reference(im, factor) + compare_reduce_with_box(im, factor) -def test_mode_I(): +@pytest.mark.parametrize("factor", remarkable_factors) +def test_mode_I(factor): im = get_image("I") - for factor in remarkable_factors: - compare_reduce_with_reference(im, factor) - compare_reduce_with_box(im, factor) + compare_reduce_with_reference(im, factor) + compare_reduce_with_box(im, factor) -def test_mode_F(): +@pytest.mark.parametrize("factor", remarkable_factors) +def test_mode_F(factor): im = get_image("F") - for factor in remarkable_factors: - compare_reduce_with_reference(im, factor, 0, 0) - compare_reduce_with_box(im, factor) + compare_reduce_with_reference(im, factor, 0, 0) + compare_reduce_with_box(im, factor) @skip_unless_feature("jpg_2000") diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index ac0e74969b0..a78349801fc 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -75,23 +75,25 @@ def test_quad(self): assert_image_equal(transformed, scaled) - def test_fill(self): - for mode, pixel in [ - ["RGB", (255, 0, 0)], - ["RGBA", (255, 0, 0, 255)], - ["LA", (76, 0)], - ]: - im = hopper(mode) - (w, h) = im.size - transformed = im.transform( - im.size, - Image.Transform.EXTENT, - (0, 0, w * 2, h * 2), - Image.Resampling.BILINEAR, - fillcolor="red", - ) - - assert transformed.getpixel((w - 1, h - 1)) == pixel + @pytest.mark.parametrize( + "mode, expected_pixel", + ( + ("RGB", (255, 0, 0)), + ("RGBA", (255, 0, 0, 255)), + ("LA", (76, 0)), + ), + ) + def test_fill(self, mode, expected_pixel): + im = hopper(mode) + (w, h) = im.size + transformed = im.transform( + im.size, + Image.Transform.EXTENT, + (0, 0, w * 2, h * 2), + Image.Resampling.BILINEAR, + fillcolor="red", + ) + assert transformed.getpixel((w - 1, h - 1)) == expected_pixel def test_mesh(self): # this should be a checkerboard of halfsized hoppers in ul, lr @@ -222,14 +224,12 @@ def test_missing_method_data(self): with pytest.raises(ValueError): im.transform((100, 100), None) - def test_unknown_resampling_filter(self): + @pytest.mark.parametrize("resample", (Image.Resampling.BOX, "unknown")) + def test_unknown_resampling_filter(self, resample): with hopper() as im: (w, h) = im.size - for resample in (Image.Resampling.BOX, "unknown"): - with pytest.raises(ValueError): - im.transform( - (100, 100), Image.Transform.EXTENT, (0, 0, w, h), resample - ) + with pytest.raises(ValueError): + im.transform((100, 100), Image.Transform.EXTENT, (0, 0, w, h), resample) class TestImageTransformAffine: @@ -239,7 +239,16 @@ def _test_image(self): im = hopper("RGB") return im.crop((10, 20, im.width - 10, im.height - 20)) - def _test_rotate(self, deg, transpose): + @pytest.mark.parametrize( + "deg, transpose", + ( + (0, None), + (90, Image.Transpose.ROTATE_90), + (180, Image.Transpose.ROTATE_180), + (270, Image.Transpose.ROTATE_270), + ), + ) + def test_rotate(self, deg, transpose): im = self._test_image() angle = -math.radians(deg) @@ -271,77 +280,65 @@ def _test_rotate(self, deg, transpose): ) assert_image_equal(transposed, transformed) - def test_rotate_0_deg(self): - self._test_rotate(0, None) - - def test_rotate_90_deg(self): - self._test_rotate(90, Image.Transpose.ROTATE_90) - - def test_rotate_180_deg(self): - self._test_rotate(180, Image.Transpose.ROTATE_180) - - def test_rotate_270_deg(self): - self._test_rotate(270, Image.Transpose.ROTATE_270) - - def _test_resize(self, scale, epsilonscale): + @pytest.mark.parametrize( + "scale, epsilon_scale", + ( + (1.1, 6.9), + (1.5, 5.5), + (2.0, 5.5), + (2.3, 3.7), + (2.5, 3.7), + ), + ) + @pytest.mark.parametrize( + "resample,epsilon", + ( + (Image.Resampling.NEAREST, 0), + (Image.Resampling.BILINEAR, 2), + (Image.Resampling.BICUBIC, 1), + ), + ) + def test_resize(self, scale, epsilon_scale, resample, epsilon): im = self._test_image() size_up = int(round(im.width * scale)), int(round(im.height * scale)) matrix_up = [1 / scale, 0, 0, 0, 1 / scale, 0, 0, 0] matrix_down = [scale, 0, 0, 0, scale, 0, 0, 0] - for resample, epsilon in [ + transformed = im.transform(size_up, self.transform, matrix_up, resample) + transformed = transformed.transform( + im.size, self.transform, matrix_down, resample + ) + assert_image_similar(transformed, im, epsilon * epsilon_scale) + + @pytest.mark.parametrize( + "x, y, epsilon_scale", + ( + (0.1, 0, 3.7), + (0.6, 0, 9.1), + (50, 50, 0), + ), + ) + @pytest.mark.parametrize( + "resample, epsilon", + ( (Image.Resampling.NEAREST, 0), - (Image.Resampling.BILINEAR, 2), + (Image.Resampling.BILINEAR, 1.5), (Image.Resampling.BICUBIC, 1), - ]: - transformed = im.transform(size_up, self.transform, matrix_up, resample) - transformed = transformed.transform( - im.size, self.transform, matrix_down, resample - ) - assert_image_similar(transformed, im, epsilon * epsilonscale) - - def test_resize_1_1x(self): - self._test_resize(1.1, 6.9) - - def test_resize_1_5x(self): - self._test_resize(1.5, 5.5) - - def test_resize_2_0x(self): - self._test_resize(2.0, 5.5) - - def test_resize_2_3x(self): - self._test_resize(2.3, 3.7) - - def test_resize_2_5x(self): - self._test_resize(2.5, 3.7) - - def _test_translate(self, x, y, epsilonscale): + ), + ) + def test_translate(self, x, y, epsilon_scale, resample, epsilon): im = self._test_image() size_up = int(round(im.width + x)), int(round(im.height + y)) matrix_up = [1, 0, -x, 0, 1, -y, 0, 0] matrix_down = [1, 0, x, 0, 1, y, 0, 0] - for resample, epsilon in [ - (Image.Resampling.NEAREST, 0), - (Image.Resampling.BILINEAR, 1.5), - (Image.Resampling.BICUBIC, 1), - ]: - transformed = im.transform(size_up, self.transform, matrix_up, resample) - transformed = transformed.transform( - im.size, self.transform, matrix_down, resample - ) - assert_image_similar(transformed, im, epsilon * epsilonscale) - - def test_translate_0_1(self): - self._test_translate(0.1, 0, 3.7) - - def test_translate_0_6(self): - self._test_translate(0.6, 0, 9.1) - - def test_translate_50(self): - self._test_translate(50, 50, 0) + transformed = im.transform(size_up, self.transform, matrix_up, resample) + transformed = transformed.transform( + im.size, self.transform, matrix_down, resample + ) + assert_image_similar(transformed, im, epsilon * epsilon_scale) class TestImageTransformPerspective(TestImageTransformAffine):