{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n# AGG filter\n\nMost pixel-based backends in Matplotlib use `Anti-Grain Geometry (AGG)`_ for\nrendering. You can modify the rendering of Artists by applying a filter via\n`.Artist.set_agg_filter`.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import matplotlib.pyplot as plt\nimport numpy as np\n\nfrom matplotlib.artist import Artist\nfrom matplotlib.colors import LightSource\nimport matplotlib.transforms as mtransforms\n\n\ndef smooth1d(x, window_len):\n    # copied from https://scipy-cookbook.readthedocs.io/items/SignalSmooth.html\n    s = np.r_[2*x[0] - x[window_len:1:-1], x, 2*x[-1] - x[-1:-window_len:-1]]\n    w = np.hanning(window_len)\n    y = np.convolve(w/w.sum(), s, mode='same')\n    return y[window_len-1:-window_len+1]\n\n\ndef smooth2d(A, sigma=3):\n    window_len = max(int(sigma), 3) * 2 + 1\n    A = np.apply_along_axis(smooth1d, 0, A, window_len)\n    A = np.apply_along_axis(smooth1d, 1, A, window_len)\n    return A\n\n\nclass BaseFilter:\n\n    def get_pad(self, dpi):\n        return 0\n\n    def process_image(self, padded_src, dpi):\n        raise NotImplementedError(\"Should be overridden by subclasses\")\n\n    def __call__(self, im, dpi):\n        pad = self.get_pad(dpi)\n        padded_src = np.pad(im, [(pad, pad), (pad, pad), (0, 0)], \"constant\")\n        tgt_image = self.process_image(padded_src, dpi)\n        return tgt_image, -pad, -pad\n\n\nclass OffsetFilter(BaseFilter):\n\n    def __init__(self, offsets=(0, 0)):\n        self.offsets = offsets\n\n    def get_pad(self, dpi):\n        return int(max(self.offsets) / 72 * dpi)\n\n    def process_image(self, padded_src, dpi):\n        ox, oy = self.offsets\n        a1 = np.roll(padded_src, int(ox / 72 * dpi), axis=1)\n        a2 = np.roll(a1, -int(oy / 72 * dpi), axis=0)\n        return a2\n\n\nclass GaussianFilter(BaseFilter):\n    \"\"\"Simple Gaussian filter.\"\"\"\n\n    def __init__(self, sigma, alpha=0.5, color=(0, 0, 0)):\n        self.sigma = sigma\n        self.alpha = alpha\n        self.color = color\n\n    def get_pad(self, dpi):\n        return int(self.sigma*3 / 72 * dpi)\n\n    def process_image(self, padded_src, dpi):\n        tgt_image = np.empty_like(padded_src)\n        tgt_image[:, :, :3] = self.color\n        tgt_image[:, :, 3] = smooth2d(padded_src[:, :, 3] * self.alpha,\n                                      self.sigma / 72 * dpi)\n        return tgt_image\n\n\nclass DropShadowFilter(BaseFilter):\n\n    def __init__(self, sigma, alpha=0.3, color=(0, 0, 0), offsets=(0, 0)):\n        self.gauss_filter = GaussianFilter(sigma, alpha, color)\n        self.offset_filter = OffsetFilter(offsets)\n\n    def get_pad(self, dpi):\n        return max(self.gauss_filter.get_pad(dpi),\n                   self.offset_filter.get_pad(dpi))\n\n    def process_image(self, padded_src, dpi):\n        t1 = self.gauss_filter.process_image(padded_src, dpi)\n        t2 = self.offset_filter.process_image(t1, dpi)\n        return t2\n\n\nclass LightFilter(BaseFilter):\n    \"\"\"Apply LightSource filter\"\"\"\n\n    def __init__(self, sigma, fraction=1):\n        \"\"\"\n        Parameters\n        ----------\n        sigma : float\n            sigma for gaussian filter\n        fraction: number, default: 1\n            Increases or decreases the contrast of the hillshade.\n            See `matplotlib.colors.LightSource`\n\n        \"\"\"\n        self.gauss_filter = GaussianFilter(sigma, alpha=1)\n        self.light_source = LightSource()\n        self.fraction = fraction\n\n    def get_pad(self, dpi):\n        return self.gauss_filter.get_pad(dpi)\n\n    def process_image(self, padded_src, dpi):\n        t1 = self.gauss_filter.process_image(padded_src, dpi)\n        elevation = t1[:, :, 3]\n        rgb = padded_src[:, :, :3]\n        alpha = padded_src[:, :, 3:]\n        rgb2 = self.light_source.shade_rgb(rgb, elevation,\n                                           fraction=self.fraction,\n                                           blend_mode=\"overlay\")\n        return np.concatenate([rgb2, alpha], -1)\n\n\nclass GrowFilter(BaseFilter):\n    \"\"\"Enlarge the area.\"\"\"\n\n    def __init__(self, pixels, color=(1, 1, 1)):\n        self.pixels = pixels\n        self.color = color\n\n    def __call__(self, im, dpi):\n        alpha = np.pad(im[..., 3], self.pixels, \"constant\")\n        alpha2 = np.clip(smooth2d(alpha, self.pixels / 72 * dpi) * 5, 0, 1)\n        new_im = np.empty((*alpha2.shape, 4))\n        new_im[:, :, :3] = self.color\n        new_im[:, :, 3] = alpha2\n        offsetx, offsety = -self.pixels, -self.pixels\n        return new_im, offsetx, offsety\n\n\nclass FilteredArtistList(Artist):\n    \"\"\"A simple container to filter multiple artists at once.\"\"\"\n\n    def __init__(self, artist_list, filter):\n        super().__init__()\n        self._artist_list = artist_list\n        self._filter = filter\n\n    def draw(self, renderer):\n        renderer.start_rasterizing()\n        renderer.start_filter()\n        for a in self._artist_list:\n            a.draw(renderer)\n        renderer.stop_filter(self._filter)\n        renderer.stop_rasterizing()\n\n\ndef filtered_text(ax):\n    # mostly copied from contour_demo.py\n\n    # prepare image\n    delta = 0.025\n    x = np.arange(-3.0, 3.0, delta)\n    y = np.arange(-2.0, 2.0, delta)\n    X, Y = np.meshgrid(x, y)\n    Z1 = np.exp(-X**2 - Y**2)\n    Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)\n    Z = (Z1 - Z2) * 2\n\n    # draw\n    ax.imshow(Z, interpolation='bilinear', origin='lower',\n              cmap=\"gray\", extent=(-3, 3, -2, 2), aspect='auto')\n    levels = np.arange(-1.2, 1.6, 0.2)\n    CS = ax.contour(Z, levels,\n                    origin='lower',\n                    linewidths=2,\n                    extent=(-3, 3, -2, 2))\n\n    # contour label\n    cl = ax.clabel(CS, levels[1::2],  # label every second level\n                   fmt='%1.1f',\n                   fontsize=11)\n\n    # change clabel color to black\n    from matplotlib.patheffects import Normal\n    for t in cl:\n        t.set_color(\"k\")\n        # to force TextPath (i.e., same font in all backends)\n        t.set_path_effects([Normal()])\n\n    # Add white glows to improve visibility of labels.\n    white_glows = FilteredArtistList(cl, GrowFilter(3))\n    ax.add_artist(white_glows)\n    white_glows.set_zorder(cl[0].get_zorder() - 0.1)\n\n    ax.xaxis.set_visible(False)\n    ax.yaxis.set_visible(False)\n\n\ndef drop_shadow_line(ax):\n    # copied from examples/misc/svg_filter_line.py\n\n    # draw lines\n    l1, = ax.plot([0.1, 0.5, 0.9], [0.1, 0.9, 0.5], \"bo-\")\n    l2, = ax.plot([0.1, 0.5, 0.9], [0.5, 0.2, 0.7], \"ro-\")\n\n    gauss = DropShadowFilter(4)\n\n    for l in [l1, l2]:\n\n        # draw shadows with same lines with slight offset.\n        xx = l.get_xdata()\n        yy = l.get_ydata()\n        shadow, = ax.plot(xx, yy)\n        shadow.update_from(l)\n\n        # offset transform\n        transform = mtransforms.offset_copy(l.get_transform(), ax.figure,\n                                            x=4.0, y=-6.0, units='points')\n        shadow.set_transform(transform)\n\n        # adjust zorder of the shadow lines so that it is drawn below the\n        # original lines\n        shadow.set_zorder(l.get_zorder() - 0.5)\n        shadow.set_agg_filter(gauss)\n        shadow.set_rasterized(True)  # to support mixed-mode renderers\n\n    ax.set_xlim(0., 1.)\n    ax.set_ylim(0., 1.)\n\n    ax.xaxis.set_visible(False)\n    ax.yaxis.set_visible(False)\n\n\ndef drop_shadow_patches(ax):\n    # Copied from barchart_demo.py\n    N = 5\n    group1_means = [20, 35, 30, 35, 27]\n\n    ind = np.arange(N)  # the x locations for the groups\n    width = 0.35  # the width of the bars\n\n    rects1 = ax.bar(ind, group1_means, width, color='r', ec=\"w\", lw=2)\n\n    group2_means = [25, 32, 34, 20, 25]\n    rects2 = ax.bar(ind + width + 0.1, group2_means, width,\n                    color='y', ec=\"w\", lw=2)\n\n    drop = DropShadowFilter(5, offsets=(1, 1))\n    shadow = FilteredArtistList(rects1 + rects2, drop)\n    ax.add_artist(shadow)\n    shadow.set_zorder(rects1[0].get_zorder() - 0.1)\n\n    ax.set_ylim(0, 40)\n\n    ax.xaxis.set_visible(False)\n    ax.yaxis.set_visible(False)\n\n\ndef light_filter_pie(ax):\n    fracs = [15, 30, 45, 10]\n    explode = (0.1, 0.2, 0.1, 0.1)\n    pie = ax.pie(fracs, explode=explode)\n\n    light_filter = LightFilter(9)\n    for p in pie.wedges:\n        p.set_agg_filter(light_filter)\n        p.set_rasterized(True)  # to support mixed-mode renderers\n        p.set(ec=\"none\",\n              lw=2)\n\n    gauss = DropShadowFilter(9, offsets=(3, -4), alpha=0.7)\n    shadow = FilteredArtistList(pie.wedges, gauss)\n    ax.add_artist(shadow)\n    shadow.set_zorder(pie.wedges[0].get_zorder() - 0.1)\n\n\nif __name__ == \"__main__\":\n\n    fix, axs = plt.subplots(2, 2)\n\n    filtered_text(axs[0, 0])\n    drop_shadow_line(axs[0, 1])\n    drop_shadow_patches(axs[1, 0])\n    light_filter_pie(axs[1, 1])\n    axs[1, 1].set_frame_on(True)\n\n    plt.show()"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.14.6"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}