Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions lib/matplotlib/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,8 +461,6 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
if not (A.ndim == 2 or A.ndim == 3 and A.shape[-1] in (3, 4)):
raise ValueError(f"Invalid shape {A.shape} for image data")

float_rgba_in = A.ndim == 3 and A.shape[-1] == 4 and A.dtype.kind == 'f'

# if antialiased, this needs to change as window sizes
# change:
interpolation_stage = self._interpolation_stage
Expand Down Expand Up @@ -566,13 +564,22 @@ def get_scaled_dtype(A):
# Resample in premultiplied alpha space. (TODO: Consider
# implementing premultiplied-space resampling in
# span_image_resample_rgba_affine::generate?)
if float_rgba_in and np.ndim(alpha) == 0 and np.any(A[..., 3] < 1):
# Do not modify original RGBA input
A = A.copy()
A[..., :3] *= A[..., 3:]
# Multiplying the whole array and then restoring the alpha channel
# is faster than an in-place multiply of the strided A[..., :3]
# view. If alpha is uniformly 1, premultiplication can be skipped.
alpha_in = A[..., 3]
if (alpha_in != 1).any():
A = A * alpha_in[..., None]
A[..., 3] = alpha_in
res = _resample(self, A, out_shape, t)
np.divide(res[..., :3], res[..., 3:], out=res[..., :3],
where=res[..., 3:] != 0)
# Demultiply. Zeroes in the divisor are replaced by ones,
# which leaves the corresponding (premultiplied) RGB values
# untouched. Dividing the whole contiguous array is several
# times faster than np.divide(..., where=...) into the strided
# res[..., :3] view.
alpha_out = res[..., 3].copy()
res /= np.where(alpha_out != 0, alpha_out, 1)[..., None]
res[..., 3] = alpha_out
if post_apply_alpha:
res[..., 3] *= alpha

Expand Down
20 changes: 20 additions & 0 deletions lib/matplotlib/tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -1860,6 +1860,26 @@ def test_resample_dtypes(dtype, ndim):
axes_image.make_image(None)[0]


@pytest.mark.parametrize('alpha', [0, 0.25, 0.5, 0.75, 1])
def test_rgba_stage_demultiplies_resampled_alpha(alpha):
# Issue 32240: resampling in the 'rgba' stage happens in premultiplied alpha
# space, so the RGB channels are demultiplied afterwards. The original
# colors must come back independently of the alpha channel, and a zero
# resampled alpha must not be divided by.
rgb = [0.2, 0.4, 0.6]
data = np.empty((4, 4, 4))
data[..., :3] = rgb
data[..., 3] = alpha
fig, ax = plt.subplots()
axes_image = ax.imshow(data, interpolation='nearest', interpolation_stage='rgba')
image = axes_image.make_image(None)[0]
# A fully transparent image premultiplies to zero and is never divided back.
expected_rgb = np.multiply(rgb, 255) if alpha else [0, 0, 0]
# The rendered image is a single flat colour, so compare against it directly.
assert_array_equal(np.unique(image.reshape(-1, 4), axis=0),
[[*expected_rgb, int(alpha * 255)]])


@pytest.mark.parametrize('intp_stage', ('data', 'rgba'))
@check_figures_equal(extensions=['png', 'pdf', 'svg'])
def test_interpolation_stage_rgba_respects_alpha_param(fig_test, fig_ref, intp_stage):
Expand Down
Loading