Repository: matplotlib/matplotlib

Issue:
[Bug]: interpolation_stage="rgba" does not respect array-alpha
### Bug summary

Passing interpolation_stage="rgba" to imshow() results in array-valued alphas being ignored.

### Code for reproduction

```Python
from pylab import *

im = arange(9).reshape(3, 3)
alpha = linspace(0, 1, 9).reshape(3, 3)

subplot(121).imshow(im, alpha=alpha)  # interpolation_stage="alpha"
subplot(122).imshow(im, alpha=alpha, interpolation_stage="rgba")
show()
```


### Actual outcome

![Figure_1](https://github.com/matplotlib/matplotlib/assets/1322974/39600e92-64b7-48ab-8d49-e72dfdaa2bcb)


### Expected outcome

Same image on left and right (interpolation_stage should not matter at all here.

### Additional information

_No response_

### Operating system

macos

### Matplotlib Version

3.10.0.dev260+g7275ad95ff

### Matplotlib Backend

qtagg

### Python version

3.12

### Jupyter version

no

### Installation

git checkout


Discussion:
Thanks for raising this issue. The problem is with `_ImageBase::_make_image` where [`alpha` is written as a scalar when `interpolation_stage='rgba'`](https://github.com/matplotlib/matplotlib/blob/main/lib/matplotlib/image.py#L556).

I have submitted https://github.com/matplotlib/matplotlib/pull/28437 which will hopefully resolve this issue.



Patch:
diff --git a/doc/api/next_api_changes/behavior/28437-CH.rst b/doc/api/next_api_changes/behavior/28437-CH.rst
new file mode 100644
index 000000000000..6121dfec8163
--- /dev/null
+++ b/doc/api/next_api_changes/behavior/28437-CH.rst
@@ -0,0 +1,8 @@
+*alpha* parameter handling on images
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When passing and array to ``imshow(..., alpha=...)``, the parameter was silently ignored
+if the image data was a RGB or RBGA image or if :rc:`interpolation_state`
+resolved to "rbga".
+
+This is now fixed, and the alpha array overwrites any previous transparency information.
diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py
index f71d49db8ad8..ddb4fd610b12 100644
--- a/lib/matplotlib/image.py
+++ b/lib/matplotlib/image.py
@@ -501,17 +501,27 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
                 if A.ndim == 2:  # interpolation_stage = 'rgba'
                     self.norm.autoscale_None(A)
                     A = self.to_rgba(A)
-                alpha = self._get_scalar_alpha()
-                if A.shape[2] == 3:
-                    # No need to resample alpha or make a full array; NumPy will expand
-                    # this out and cast to uint8 if necessary when it's assigned to the
-                    # alpha channel below.
-                    output_alpha = (255 * alpha) if A.dtype == np.uint8 else alpha
-                else:
-                    output_alpha = _resample(  # resample alpha channel
-                        self, A[..., 3], out_shape, t, alpha=alpha)
-                output = _resample(  # resample rgb channels
-                    self, _rgb_to_rgba(A[..., :3]), out_shape, t, alpha=alpha)
+                alpha = self.get_alpha()
+                if alpha is None:  # alpha parameter not specified
+                    if A.shape[2] == 3:  # image has no alpha channel
+                        output_alpha = 255 if A.dtype == np.uint8 else 1.0
+                    else:
+                        output_alpha = _resample(  # resample alpha channel
+                            self, A[..., 3], out_shape, t)
+                    output = _resample(  # resample rgb channels
+                        self, _rgb_to_rgba(A[..., :3]), out_shape, t)
+                elif np.ndim(alpha) > 0:  # Array alpha
+                    # user-specified array alpha overrides the existing alpha channel
+                    output_alpha = _resample(self, alpha, out_shape, t)
+                    output = _resample(
+                        self, _rgb_to_rgba(A[..., :3]), out_shape, t)
+                else:  # Scalar alpha
+                    if A.shape[2] == 3:  # broadcast scalar alpha
+                        output_alpha = (255 * alpha) if A.dtype == np.uint8 else alpha
+                    else:  # or apply scalar alpha to existing alpha channel
+                        output_alpha = _resample(self, A[..., 3], out_shape, t) * alpha
+                    output = _resample(
+                        self, _rgb_to_rgba(A[..., :3]), out_shape, t)
                 output[..., 3] = output_alpha  # recombine rgb and alpha
 
             # output is now either a 2D array of normed (int or float) data


Test patch:
diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py
index d8ac3786c395..7437651d2b86 100644
--- a/lib/matplotlib/tests/test_image.py
+++ b/lib/matplotlib/tests/test_image.py
@@ -1711,3 +1711,49 @@ def test_resample_dtypes(dtype, ndim):
     axes_image = ax.imshow(data)
     # Before fix the following raises ValueError for some dtypes.
     axes_image.make_image(None)[0]
+
+
+@pytest.mark.parametrize('intp_stage', ('data', 'rgba'))
+@check_figures_equal()
+def test_interpolation_stage_rgba_respects_alpha_param(fig_test, fig_ref, intp_stage):
+    axs_tst = fig_test.subplots(2, 3)
+    axs_ref = fig_ref.subplots(2, 3)
+    ny, nx = 3, 3
+    scalar_alpha = 0.5
+    array_alpha = np.random.rand(ny, nx)
+
+    # When the image does not have an alpha channel, alpha should be specified
+    # by the user or default to 1.0
+    im_rgb = np.random.rand(ny, nx, 3)
+    im_concat_default_a = np.ones((ny, nx, 1))  # alpha defaults to 1.0
+    im_rgba = np.concatenate(  # combine rgb channels with array alpha
+        (im_rgb, array_alpha.reshape((ny, nx, 1))), axis=-1
+    )
+    axs_tst[0][0].imshow(im_rgb)
+    axs_ref[0][0].imshow(np.concatenate((im_rgb, im_concat_default_a), axis=-1))
+    axs_tst[0][1].imshow(im_rgb, interpolation_stage=intp_stage, alpha=scalar_alpha)
+    axs_ref[0][1].imshow(
+        np.concatenate(  # combine rgb channels with broadcasted scalar alpha
+            (im_rgb, scalar_alpha * im_concat_default_a), axis=-1
+        ), interpolation_stage=intp_stage
+    )
+    axs_tst[0][2].imshow(im_rgb, interpolation_stage=intp_stage, alpha=array_alpha)
+    axs_ref[0][2].imshow(im_rgba, interpolation_stage=intp_stage)
+
+    # When the image already has an alpha channel, multiply it by the
+    # scalar alpha param, or replace it by the array alpha param
+    axs_tst[1][0].imshow(im_rgba)
+    axs_ref[1][0].imshow(im_rgb, alpha=array_alpha)
+    axs_tst[1][1].imshow(im_rgba, interpolation_stage=intp_stage, alpha=scalar_alpha)
+    axs_ref[1][1].imshow(
+        np.concatenate(  # combine rgb channels with scaled array alpha
+            (im_rgb, scalar_alpha * array_alpha.reshape((ny, nx, 1))), axis=-1
+        ), interpolation_stage=intp_stage
+    )
+    new_array_alpha = np.random.rand(ny, nx)
+    axs_tst[1][2].imshow(im_rgba, interpolation_stage=intp_stage, alpha=new_array_alpha)
+    axs_ref[1][2].imshow(
+        np.concatenate(  # combine rgb channels with new array alpha
+            (im_rgb, new_array_alpha.reshape((ny, nx, 1))), axis=-1
+        ), interpolation_stage=intp_stage
+    )