diff --git a/lib/matplotlib/cm.py b/lib/matplotlib/cm.py index f3a80f470d7..dcc0a1db6bd 100644 --- a/lib/matplotlib/cm.py +++ b/lib/matplotlib/cm.py @@ -269,10 +269,6 @@ def to_rgba(self, x, alpha=None, bytes=False, norm=True): if norm: x = self.norm(x) rgba = self.cmap(x, alpha=alpha, bytes=bytes) - # For floating-point greyscale images, we treat negative as - # transparent so we copy that over to the alpha channel - if x.ndim == 2 and x.dtype.kind == 'f': - rgba[:, :, 3][x < 0.0] = 0 return rgba def set_array(self, A): diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index ade8bd8a28f..a29e62cc78a 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -354,20 +354,22 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, out_height = int(out_height_base) if not unsampled: + created_rgba_mask = False + if A.ndim == 2: A = self.norm(A) + # If the image is greyscale, convert to RGBA with the + # correct alpha channel for resizing + rgba = np.empty((A.shape[0], A.shape[1], 4), dtype=A.dtype) + rgba[..., 0:3] = np.expand_dims(A, 2) if A.dtype.kind == 'f': - # For floating-point greyscale images, we treat negative - # numbers as transparent. - - # TODO: Use np.full when we support Numpy 1.9 as a - # minimum - output = np.empty((out_height, out_width), dtype=A.dtype) - output[...] = -100.0 + rgba[..., 3] = ~A.mask else: - output = np.zeros((out_height, out_width), dtype=A.dtype) - + rgba[..., 3] = np.where(A.mask, 0, np.iinfo(A.dtype).max) + A = rgba + output = np.zeros((out_height, out_width, 4), dtype=A.dtype) alpha = 1.0 + created_rgba_mask = True elif A.ndim == 3: # Always convert to RGBA, even if only RGB input if A.shape[2] == 3: @@ -388,10 +390,16 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, self.get_resample(), alpha, self.get_filternorm() or 0.0, self.get_filterrad() or 0.0) + if created_rgba_mask: + # Convert back to a masked greyscale array so + # colormapping works correctly + output = np.ma.masked_array( + output[..., 0], output[..., 3] < 0.5) + output = self.to_rgba(output, bytes=True, norm=False) - # Apply alpha *after* if the input was greyscale - if A.ndim == 2: + # Apply alpha *after* if the input was greyscale without a mask + if A.ndim == 2 or created_rgba_mask: alpha = self.get_alpha() if alpha is not None and alpha != 1.0: alpha_channel = output[:, :, 3] diff --git a/lib/matplotlib/tests/baseline_images/test_image/mask_image.pdf b/lib/matplotlib/tests/baseline_images/test_image/mask_image.pdf new file mode 100644 index 00000000000..695b149f03d Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_image/mask_image.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/mask_image.png b/lib/matplotlib/tests/baseline_images/test_image/mask_image.png new file mode 100644 index 00000000000..1e4f2873e39 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_image/mask_image.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/mask_image.svg b/lib/matplotlib/tests/baseline_images/test_image/mask_image.svg new file mode 100644 index 00000000000..174c5942c2c --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_image/mask_image.svg @@ -0,0 +1,365 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_image/rotate_image.png b/lib/matplotlib/tests/baseline_images/test_image/rotate_image.png index fd72f2a9bff..cb1853ed179 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/rotate_image.png and b/lib/matplotlib/tests/baseline_images/test_image/rotate_image.png differ diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index 252c5248a68..6af9a5ef5f1 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -610,6 +610,25 @@ def test_image_preserve_size2(): np.identity(n, bool)[::-1]) +@image_comparison(baseline_images=['mask_image'], + remove_text=True) +def test_mask_image(): + # Test mask image two ways: Using nans and using a masked array. + + fig, (ax1, ax2) = plt.subplots(1, 2) + + A = np.ones((5, 5)) + A[1:2, 1:2] = np.nan + + ax1.imshow(A, interpolation='nearest') + + A = np.zeros((5, 5), dtype=np.bool) + A[1:2, 1:2] = True + A = np.ma.masked_array(np.ones((5, 5), dtype=np.uint16), A) + + ax2.imshow(A, interpolation='nearest') + + if __name__=='__main__': import nose nose.runmodule(argv=['-s','--with-doctest'], exit=False) diff --git a/src/_image_resample.h b/src/_image_resample.h index 3bf7aa182fb..cee102244f2 100644 --- a/src/_image_resample.h +++ b/src/_image_resample.h @@ -533,6 +533,35 @@ template <> class type_mapping }; +template <> class type_mapping +{ + public: + typedef agg::rgba16 color_type; + typedef fixed_blender_rgba_plain blender_type; + typedef fixed_blender_rgba_pre pre_blender_type; + typedef agg::pixfmt_alpha_blend_rgba pixfmt_type; + typedef agg::pixfmt_alpha_blend_rgba pixfmt_pre_type; + + template + struct span_gen_affine_type + { + typedef agg::span_image_resample_rgba_affine type; + }; + + template + struct span_gen_filter_type + { + typedef agg::span_image_filter_rgba type; + }; + + template + struct span_gen_nn_type + { + typedef agg::span_image_filter_rgba_nn type; + }; +}; + + template <> class type_mapping { public: diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index dac7c44e2ad..aac86bf9367 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -226,6 +226,19 @@ image_resample(PyObject *self, PyObject* args, PyObject *kwargs) params); Py_END_ALLOW_THREADS break; + case NPY_UINT16: + case NPY_INT16: + Py_BEGIN_ALLOW_THREADS + resample( + (agg::rgba16 *)PyArray_DATA(input_array), + PyArray_DIM(input_array, 1), + PyArray_DIM(input_array, 0), + (agg::rgba16 *)PyArray_DATA(output_array), + PyArray_DIM(output_array, 1), + PyArray_DIM(output_array, 0), + params); + Py_END_ALLOW_THREADS + break; case NPY_FLOAT32: Py_BEGIN_ALLOW_THREADS resample( @@ -254,13 +267,13 @@ image_resample(PyObject *self, PyObject* args, PyObject *kwargs) PyErr_SetString( PyExc_ValueError, "3-dimensional arrays must be of dtype unsigned byte, " - "float32 or float64"); + "unsigned short, float32 or float64"); goto error; } } else { PyErr_Format( PyExc_ValueError, - "If 3-dimensional, array must be RGBA. Got %d.", + "If 3-dimensional, array must be RGBA. Got %d planes.", PyArray_DIM(input_array, 2)); goto error; }