1"""
2===============
3Demo Agg Filter
4===============
5
6"""
7import matplotlib.pyplot as plt
8
9import numpy as np
10import matplotlib.cm as cm
11import matplotlib.transforms as mtransforms
12from matplotlib.colors import LightSource
13from matplotlib.artist import Artist
14
15
16def smooth1d(x, window_len):
17    # copied from http://www.scipy.org/Cookbook/SignalSmooth
18
19    s = np.r_[2*x[0] - x[window_len:1:-1], x, 2*x[-1] - x[-1:-window_len:-1]]
20    w = np.hanning(window_len)
21    y = np.convolve(w/w.sum(), s, mode='same')
22    return y[window_len-1:-window_len+1]
23
24
25def smooth2d(A, sigma=3):
26
27    window_len = max(int(sigma), 3)*2 + 1
28    A1 = np.array([smooth1d(x, window_len) for x in np.asarray(A)])
29    A2 = np.transpose(A1)
30    A3 = np.array([smooth1d(x, window_len) for x in A2])
31    A4 = np.transpose(A3)
32
33    return A4
34
35
36class BaseFilter(object):
37    def prepare_image(self, src_image, dpi, pad):
38        ny, nx, depth = src_image.shape
39        # tgt_image = np.zeros([pad*2+ny, pad*2+nx, depth], dtype="d")
40        padded_src = np.zeros([pad*2 + ny, pad*2 + nx, depth], dtype="d")
41        padded_src[pad:-pad, pad:-pad, :] = src_image[:, :, :]
42
43        return padded_src  # , tgt_image
44
45    def get_pad(self, dpi):
46        return 0
47
48    def __call__(self, im, dpi):
49        pad = self.get_pad(dpi)
50        padded_src = self.prepare_image(im, dpi, pad)
51        tgt_image = self.process_image(padded_src, dpi)
52        return tgt_image, -pad, -pad
53
54
55class OffsetFilter(BaseFilter):
56    def __init__(self, offsets=None):
57        if offsets is None:
58            self.offsets = (0, 0)
59        else:
60            self.offsets = offsets
61
62    def get_pad(self, dpi):
63        return int(max(*self.offsets)/72.*dpi)
64
65    def process_image(self, padded_src, dpi):
66        ox, oy = self.offsets
67        a1 = np.roll(padded_src, int(ox/72.*dpi), axis=1)
68        a2 = np.roll(a1, -int(oy/72.*dpi), axis=0)
69        return a2
70
71
72class GaussianFilter(BaseFilter):
73    "simple gauss filter"
74
75    def __init__(self, sigma, alpha=0.5, color=None):
76        self.sigma = sigma
77        self.alpha = alpha
78        if color is None:
79            self.color = (0, 0, 0)
80        else:
81            self.color = color
82
83    def get_pad(self, dpi):
84        return int(self.sigma*3/72.*dpi)
85
86    def process_image(self, padded_src, dpi):
87        # offsetx, offsety = int(self.offsets[0]), int(self.offsets[1])
88        tgt_image = np.zeros_like(padded_src)
89        aa = smooth2d(padded_src[:, :, -1]*self.alpha,
90                      self.sigma/72.*dpi)
91        tgt_image[:, :, -1] = aa
92        tgt_image[:, :, :-1] = self.color
93        return tgt_image
94
95
96class DropShadowFilter(BaseFilter):
97    def __init__(self, sigma, alpha=0.3, color=None, offsets=None):
98        self.gauss_filter = GaussianFilter(sigma, alpha, color)
99        self.offset_filter = OffsetFilter(offsets)
100
101    def get_pad(self, dpi):
102        return max(self.gauss_filter.get_pad(dpi),
103                   self.offset_filter.get_pad(dpi))
104
105    def process_image(self, padded_src, dpi):
106        t1 = self.gauss_filter.process_image(padded_src, dpi)
107        t2 = self.offset_filter.process_image(t1, dpi)
108        return t2
109
110
111class LightFilter(BaseFilter):
112    "simple gauss filter"
113
114    def __init__(self, sigma, fraction=0.5):
115        self.gauss_filter = GaussianFilter(sigma, alpha=1)
116        self.light_source = LightSource()
117        self.fraction = fraction
118
119    def get_pad(self, dpi):
120        return self.gauss_filter.get_pad(dpi)
121
122    def process_image(self, padded_src, dpi):
123        t1 = self.gauss_filter.process_image(padded_src, dpi)
124        elevation = t1[:, :, 3]
125        rgb = padded_src[:, :, :3]
126
127        rgb2 = self.light_source.shade_rgb(rgb, elevation,
128                                           fraction=self.fraction)
129
130        tgt = np.empty_like(padded_src)
131        tgt[:, :, :3] = rgb2
132        tgt[:, :, 3] = padded_src[:, :, 3]
133
134        return tgt
135
136
137class GrowFilter(BaseFilter):
138    "enlarge the area"
139
140    def __init__(self, pixels, color=None):
141        self.pixels = pixels
142        if color is None:
143            self.color = (1, 1, 1)
144        else:
145            self.color = color
146
147    def __call__(self, im, dpi):
148        pad = self.pixels
149        ny, nx, depth = im.shape
150        new_im = np.empty([pad*2 + ny, pad*2 + nx, depth], dtype="d")
151        alpha = new_im[:, :, 3]
152        alpha.fill(0)
153        alpha[pad:-pad, pad:-pad] = im[:, :, -1]
154        alpha2 = np.clip(smooth2d(alpha, self.pixels/72.*dpi) * 5, 0, 1)
155        new_im[:, :, -1] = alpha2
156        new_im[:, :, :-1] = self.color
157        offsetx, offsety = -pad, -pad
158
159        return new_im, offsetx, offsety
160
161
162class FilteredArtistList(Artist):
163    """
164    A simple container to draw filtered artist.
165    """
166
167    def __init__(self, artist_list, filter):
168        self._artist_list = artist_list
169        self._filter = filter
170        Artist.__init__(self)
171
172    def draw(self, renderer):
173        renderer.start_rasterizing()
174        renderer.start_filter()
175        for a in self._artist_list:
176            a.draw(renderer)
177        renderer.stop_filter(self._filter)
178        renderer.stop_rasterizing()
179
180
181def filtered_text(ax):
182    # mostly copied from contour_demo.py
183
184    # prepare image
185    delta = 0.025
186    x = np.arange(-3.0, 3.0, delta)
187    y = np.arange(-2.0, 2.0, delta)
188    X, Y = np.meshgrid(x, y)
189    Z1 = np.exp(-X**2 - Y**2)
190    Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
191    Z = (Z1 - Z2) * 2
192
193    # draw
194    im = ax.imshow(Z, interpolation='bilinear', origin='lower',
195                   cmap=cm.gray, extent=(-3, 3, -2, 2))
196    levels = np.arange(-1.2, 1.6, 0.2)
197    CS = ax.contour(Z, levels,
198                    origin='lower',
199                    linewidths=2,
200                    extent=(-3, 3, -2, 2))
201
202    ax.set_aspect("auto")
203
204    # contour label
205    cl = ax.clabel(CS, levels[1::2],  # label every second level
206                   inline=1,
207                   fmt='%1.1f',
208                   fontsize=11)
209
210    # change clable color to black
211    from matplotlib.patheffects import Normal
212    for t in cl:
213        t.set_color("k")
214        # to force TextPath (i.e., same font in all backends)
215        t.set_path_effects([Normal()])
216
217    # Add white glows to improve visibility of labels.
218    white_glows = FilteredArtistList(cl, GrowFilter(3))
219    ax.add_artist(white_glows)
220    white_glows.set_zorder(cl[0].get_zorder() - 0.1)
221
222    ax.xaxis.set_visible(False)
223    ax.yaxis.set_visible(False)
224
225
226def drop_shadow_line(ax):
227    # copied from examples/misc/svg_filter_line.py
228
229    # draw lines
230    l1, = ax.plot([0.1, 0.5, 0.9], [0.1, 0.9, 0.5], "bo-",
231                  mec="b", mfc="w", lw=5, mew=3, ms=10, label="Line 1")
232    l2, = ax.plot([0.1, 0.5, 0.9], [0.5, 0.2, 0.7], "ro-",
233                  mec="r", mfc="w", lw=5, mew=3, ms=10, label="Line 1")
234
235    gauss = DropShadowFilter(4)
236
237    for l in [l1, l2]:
238
239        # draw shadows with same lines with slight offset.
240
241        xx = l.get_xdata()
242        yy = l.get_ydata()
243        shadow, = ax.plot(xx, yy)
244        shadow.update_from(l)
245
246        # offset transform
247        ot = mtransforms.offset_copy(l.get_transform(), ax.figure,
248                                     x=4.0, y=-6.0, units='points')
249
250        shadow.set_transform(ot)
251
252        # adjust zorder of the shadow lines so that it is drawn below the
253        # original lines
254        shadow.set_zorder(l.get_zorder() - 0.5)
255        shadow.set_agg_filter(gauss)
256        shadow.set_rasterized(True)  # to support mixed-mode renderers
257
258    ax.set_xlim(0., 1.)
259    ax.set_ylim(0., 1.)
260
261    ax.xaxis.set_visible(False)
262    ax.yaxis.set_visible(False)
263
264
265def drop_shadow_patches(ax):
266    # Copied from barchart_demo.py
267    N = 5
268    menMeans = (20, 35, 30, 35, 27)
269
270    ind = np.arange(N)  # the x locations for the groups
271    width = 0.35       # the width of the bars
272
273    rects1 = ax.bar(ind, menMeans, width, color='r', ec="w", lw=2)
274
275    womenMeans = (25, 32, 34, 20, 25)
276    rects2 = ax.bar(ind + width + 0.1, womenMeans, width,
277                    color='y', ec="w", lw=2)
278
279    # gauss = GaussianFilter(1.5, offsets=(1,1), )
280    gauss = DropShadowFilter(5, offsets=(1, 1), )
281    shadow = FilteredArtistList(rects1 + rects2, gauss)
282    ax.add_artist(shadow)
283    shadow.set_zorder(rects1[0].get_zorder() - 0.1)
284
285    ax.set_ylim(0, 40)
286
287    ax.xaxis.set_visible(False)
288    ax.yaxis.set_visible(False)
289
290
291def light_filter_pie(ax):
292    fracs = [15, 30, 45, 10]
293    explode = (0, 0.05, 0, 0)
294    pies = ax.pie(fracs, explode=explode)
295    ax.patch.set_visible(True)
296
297    light_filter = LightFilter(9)
298    for p in pies[0]:
299        p.set_agg_filter(light_filter)
300        p.set_rasterized(True)  # to support mixed-mode renderers
301        p.set(ec="none",
302              lw=2)
303
304    gauss = DropShadowFilter(9, offsets=(3, 4), alpha=0.7)
305    shadow = FilteredArtistList(pies[0], gauss)
306    ax.add_artist(shadow)
307    shadow.set_zorder(pies[0][0].get_zorder() - 0.1)
308
309
310if 1:
311
312    plt.figure(1, figsize=(6, 6))
313    plt.subplots_adjust(left=0.05, right=0.95)
314
315    ax = plt.subplot(221)
316    filtered_text(ax)
317
318    ax = plt.subplot(222)
319    drop_shadow_line(ax)
320
321    ax = plt.subplot(223)
322    drop_shadow_patches(ax)
323
324    ax = plt.subplot(224)
325    ax.set_aspect(1)
326    light_filter_pie(ax)
327    ax.set_frame_on(True)
328
329    plt.show()
330