test_agg.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. import io
  2. import numpy as np
  3. from numpy.testing import assert_array_almost_equal
  4. import pytest
  5. from matplotlib import (
  6. collections, path, pyplot as plt, transforms as mtransforms, rcParams)
  7. from matplotlib.image import imread
  8. from matplotlib.figure import Figure
  9. from matplotlib.testing.decorators import image_comparison
  10. def test_repeated_save_with_alpha():
  11. # We want an image which has a background color of bluish green, with an
  12. # alpha of 0.25.
  13. fig = Figure([1, 0.4])
  14. fig.set_facecolor((0, 1, 0.4))
  15. fig.patch.set_alpha(0.25)
  16. # The target color is fig.patch.get_facecolor()
  17. buf = io.BytesIO()
  18. fig.savefig(buf,
  19. facecolor=fig.get_facecolor(),
  20. edgecolor='none')
  21. # Save the figure again to check that the
  22. # colors don't bleed from the previous renderer.
  23. buf.seek(0)
  24. fig.savefig(buf,
  25. facecolor=fig.get_facecolor(),
  26. edgecolor='none')
  27. # Check the first pixel has the desired color & alpha
  28. # (approx: 0, 1.0, 0.4, 0.25)
  29. buf.seek(0)
  30. assert_array_almost_equal(tuple(imread(buf)[0, 0]),
  31. (0.0, 1.0, 0.4, 0.250),
  32. decimal=3)
  33. def test_large_single_path_collection():
  34. buff = io.BytesIO()
  35. # Generates a too-large single path in a path collection that
  36. # would cause a segfault if the draw_markers optimization is
  37. # applied.
  38. f, ax = plt.subplots()
  39. collection = collections.PathCollection(
  40. [path.Path([[-10, 5], [10, 5], [10, -5], [-10, -5], [-10, 5]])])
  41. ax.add_artist(collection)
  42. ax.set_xlim(10**-3, 1)
  43. plt.savefig(buff)
  44. def test_marker_with_nan():
  45. # This creates a marker with nans in it, which was segfaulting the
  46. # Agg backend (see #3722)
  47. fig, ax = plt.subplots(1)
  48. steps = 1000
  49. data = np.arange(steps)
  50. ax.semilogx(data)
  51. ax.fill_between(data, data*0.8, data*1.2)
  52. buf = io.BytesIO()
  53. fig.savefig(buf, format='png')
  54. def test_long_path():
  55. buff = io.BytesIO()
  56. fig, ax = plt.subplots()
  57. np.random.seed(0)
  58. points = np.random.rand(70000)
  59. ax.plot(points)
  60. fig.savefig(buff, format='png')
  61. @image_comparison(['agg_filter.png'], remove_text=True)
  62. def test_agg_filter():
  63. def smooth1d(x, window_len):
  64. # copied from http://www.scipy.org/Cookbook/SignalSmooth
  65. s = np.r_[
  66. 2*x[0] - x[window_len:1:-1], x, 2*x[-1] - x[-1:-window_len:-1]]
  67. w = np.hanning(window_len)
  68. y = np.convolve(w/w.sum(), s, mode='same')
  69. return y[window_len-1:-window_len+1]
  70. def smooth2d(A, sigma=3):
  71. window_len = max(int(sigma), 3) * 2 + 1
  72. A = np.apply_along_axis(smooth1d, 0, A, window_len)
  73. A = np.apply_along_axis(smooth1d, 1, A, window_len)
  74. return A
  75. class BaseFilter:
  76. def get_pad(self, dpi):
  77. return 0
  78. def process_image(padded_src, dpi):
  79. raise NotImplementedError("Should be overridden by subclasses")
  80. def __call__(self, im, dpi):
  81. pad = self.get_pad(dpi)
  82. padded_src = np.pad(im, [(pad, pad), (pad, pad), (0, 0)],
  83. "constant")
  84. tgt_image = self.process_image(padded_src, dpi)
  85. return tgt_image, -pad, -pad
  86. class OffsetFilter(BaseFilter):
  87. def __init__(self, offsets=(0, 0)):
  88. self.offsets = offsets
  89. def get_pad(self, dpi):
  90. return int(max(self.offsets) / 72 * dpi)
  91. def process_image(self, padded_src, dpi):
  92. ox, oy = self.offsets
  93. a1 = np.roll(padded_src, int(ox / 72 * dpi), axis=1)
  94. a2 = np.roll(a1, -int(oy / 72 * dpi), axis=0)
  95. return a2
  96. class GaussianFilter(BaseFilter):
  97. """Simple Gaussian filter."""
  98. def __init__(self, sigma, alpha=0.5, color=(0, 0, 0)):
  99. self.sigma = sigma
  100. self.alpha = alpha
  101. self.color = color
  102. def get_pad(self, dpi):
  103. return int(self.sigma*3 / 72 * dpi)
  104. def process_image(self, padded_src, dpi):
  105. tgt_image = np.empty_like(padded_src)
  106. tgt_image[:, :, :3] = self.color
  107. tgt_image[:, :, 3] = smooth2d(padded_src[:, :, 3] * self.alpha,
  108. self.sigma / 72 * dpi)
  109. return tgt_image
  110. class DropShadowFilter(BaseFilter):
  111. def __init__(self, sigma, alpha=0.3, color=(0, 0, 0), offsets=(0, 0)):
  112. self.gauss_filter = GaussianFilter(sigma, alpha, color)
  113. self.offset_filter = OffsetFilter(offsets)
  114. def get_pad(self, dpi):
  115. return max(self.gauss_filter.get_pad(dpi),
  116. self.offset_filter.get_pad(dpi))
  117. def process_image(self, padded_src, dpi):
  118. t1 = self.gauss_filter.process_image(padded_src, dpi)
  119. t2 = self.offset_filter.process_image(t1, dpi)
  120. return t2
  121. fig, ax = plt.subplots()
  122. # draw lines
  123. l1, = ax.plot([0.1, 0.5, 0.9], [0.1, 0.9, 0.5], "bo-",
  124. mec="b", mfc="w", lw=5, mew=3, ms=10, label="Line 1")
  125. l2, = ax.plot([0.1, 0.5, 0.9], [0.5, 0.2, 0.7], "ro-",
  126. mec="r", mfc="w", lw=5, mew=3, ms=10, label="Line 1")
  127. gauss = DropShadowFilter(4)
  128. for l in [l1, l2]:
  129. # draw shadows with same lines with slight offset.
  130. xx = l.get_xdata()
  131. yy = l.get_ydata()
  132. shadow, = ax.plot(xx, yy)
  133. shadow.update_from(l)
  134. # offset transform
  135. ot = mtransforms.offset_copy(l.get_transform(), ax.figure,
  136. x=4.0, y=-6.0, units='points')
  137. shadow.set_transform(ot)
  138. # adjust zorder of the shadow lines so that it is drawn below the
  139. # original lines
  140. shadow.set_zorder(l.get_zorder() - 0.5)
  141. shadow.set_agg_filter(gauss)
  142. shadow.set_rasterized(True) # to support mixed-mode renderers
  143. ax.set_xlim(0., 1.)
  144. ax.set_ylim(0., 1.)
  145. ax.xaxis.set_visible(False)
  146. ax.yaxis.set_visible(False)
  147. def test_too_large_image():
  148. fig = plt.figure(figsize=(300, 1000))
  149. buff = io.BytesIO()
  150. with pytest.raises(ValueError):
  151. fig.savefig(buff)
  152. def test_chunksize():
  153. x = range(200)
  154. # Test without chunksize
  155. fig, ax = plt.subplots()
  156. ax.plot(x, np.sin(x))
  157. fig.canvas.draw()
  158. # Test with chunksize
  159. fig, ax = plt.subplots()
  160. rcParams['agg.path.chunksize'] = 105
  161. ax.plot(x, np.sin(x))
  162. fig.canvas.draw()
  163. @pytest.mark.backend('Agg')
  164. def test_jpeg_dpi():
  165. Image = pytest.importorskip("PIL.Image")
  166. # Check that dpi is set correctly in jpg files.
  167. plt.plot([0, 1, 2], [0, 1, 0])
  168. buf = io.BytesIO()
  169. plt.savefig(buf, format="jpg", dpi=200)
  170. im = Image.open(buf)
  171. assert im.info['dpi'] == (200, 200)
  172. def test_pil_kwargs_png():
  173. Image = pytest.importorskip("PIL.Image")
  174. from PIL.PngImagePlugin import PngInfo
  175. buf = io.BytesIO()
  176. pnginfo = PngInfo()
  177. pnginfo.add_text("Software", "test")
  178. plt.figure().savefig(buf, format="png", pil_kwargs={"pnginfo": pnginfo})
  179. im = Image.open(buf)
  180. assert im.info["Software"] == "test"
  181. def test_pil_kwargs_tiff():
  182. Image = pytest.importorskip("PIL.Image")
  183. from PIL.TiffTags import TAGS_V2 as TAGS
  184. buf = io.BytesIO()
  185. pil_kwargs = {"description": "test image"}
  186. plt.figure().savefig(buf, format="tiff", pil_kwargs=pil_kwargs)
  187. im = Image.open(buf)
  188. tags = {TAGS[k].name: v for k, v in im.tag_v2.items()}
  189. assert tags["ImageDescription"] == "test image"