diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 6ca472518b65..390f5e95b4ac 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -423,7 +423,7 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, # So that the image is aligned with the edge of the Axes, we want to # round up the output width to the next integer. This also means # scaling the transform slightly to account for the extra subpixel. - if ((not unsampled) and t.is_affine and round_to_pixel_border and + if ((not unsampled) and round_to_pixel_border and (out_width_base % 1.0 != 0.0 or out_height_base % 1.0 != 0.0)): out_width = math.ceil(out_width_base) out_height = math.ceil(out_height_base) diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index 1c89bc5e7912..aa8f6ed835dd 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -1641,6 +1641,17 @@ def test__resample_valid_output(): resample(np.zeros((9, 9)), out) +@pytest.fixture +def nonaffine_identity(): + class NonAffineIdentityTransform(Transform): + input_dims = 2 + output_dims = 2 + + def inverted(self): + return self + return NonAffineIdentityTransform() + + @pytest.mark.parametrize("data, interpolation, expected", [(np.array([[0.1, 0.3, 0.2]]), mimage.NEAREST, np.array([[0.1, 0.1, 0.1, 0.3, 0.3, 0.3, 0.3, 0.2, 0.2, 0.2]])), @@ -1649,7 +1660,7 @@ def test__resample_valid_output(): 0.28476562, 0.2546875, 0.22460938, 0.20002441, 0.20002441]])), ] ) -def test_resample_nonaffine(data, interpolation, expected): +def test_resample_nonaffine(data, interpolation, expected, nonaffine_identity): # Test that equivalent affine and nonaffine transforms resample the same # Create a simple affine transform for scaling the input array @@ -1661,13 +1672,7 @@ def test_resample_nonaffine(data, interpolation, expected): # Create a nonaffine version of the same transform # by compositing with a nonaffine identity transform - class NonAffineIdentityTransform(Transform): - input_dims = 2 - output_dims = 2 - - def inverted(self): - return self - nonaffine_transform = NonAffineIdentityTransform() + affine_transform + nonaffine_transform = nonaffine_identity + affine_transform nonaffine_result = np.empty_like(expected) mimage.resample(data, nonaffine_result, nonaffine_transform, @@ -1675,6 +1680,28 @@ def inverted(self): assert_allclose(nonaffine_result, expected, atol=5e-3) +@check_figures_equal() +def test_nonaffine_scaling_to_axes_edges(fig_test, fig_ref, nonaffine_identity): + # Test that plotting an image with equivalent affine and nonaffine + # transforms is scaled the same to the axes edges + data = np.arange(16).reshape((4, 4)) % 3 + + # Specifically choose an axes bbox that has a fractional pixel + + fig_test.set_size_inches(5, 5) + fig_test.set_dpi(100) + ax = fig_test.subplots() + ax.set_position([0.2, 0.2, 300.5 / 500, 300.5 / 500]) + ax.imshow(data, interpolation='nearest', + transform=nonaffine_identity + ax.transData) + + fig_ref.set_size_inches(5, 5) + fig_ref.set_dpi(100) + ax = fig_ref.subplots() + ax.set_position([0.2, 0.2, 300.5 / 500, 300.5 / 500]) + ax.imshow(data, interpolation='nearest') + + def test_axesimage_get_shape(): # generate dummy image to test get_shape method ax = plt.gca()