From 468a9770fec749b0fc7dbf51b6a0e9077e64d025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Schl=C3=BCter?= Date: Mon, 18 Jul 2016 17:06:06 +0200 Subject: [PATCH 1/4] PGF backend: skip drawing of empty images --- lib/matplotlib/backends/backend_pgf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index 76d8be186a24..49c6f87a6d83 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -612,6 +612,9 @@ def _pgf_path_draw(self, stroke=True, fill=False): def draw_image(self, gc, x, y, im): # TODO: Almost no documentation for the behavior of this function. # Something missing? + h, w = im.shape[:2] + if w == 0 or h == 0: + return # save the images to png files path = os.path.dirname(self.fh.name) @@ -623,7 +626,6 @@ def draw_image(self, gc, x, y, im): # reference the image in the pgf picture writeln(self.fh, r"\begin{pgfscope}") self._print_pgf_clip(gc) - h, w = im.shape[:2] f = 1. / self.dpi # from display coords to inch writeln(self.fh, r"\pgftext[at=\pgfqpoint{%fin}{%fin},left,bottom]{\pgfimage[interpolate=true,width=%fin,height=%fin]{%s}}" % (x * f, y * f, w * f, h * f, fname_img)) writeln(self.fh, r"\end{pgfscope}") From 4bcb38f358e1c788806130591f6932b38c2d2e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Schl=C3=BCter?= Date: Mon, 18 Jul 2016 17:06:36 +0200 Subject: [PATCH 2/4] PGF backend: support affine image transformations --- lib/matplotlib/backends/backend_pgf.py | 34 +++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index 49c6f87a6d83..5e77786635b9 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -609,9 +609,20 @@ def _pgf_path_draw(self, stroke=True, fill=False): actions.append("fill") writeln(self.fh, r"\pgfusepath{%s}" % ",".join(actions)) - def draw_image(self, gc, x, y, im): - # TODO: Almost no documentation for the behavior of this function. - # Something missing? + def option_scale_image(self): + """ + pgf backend supports affine transform of image. + """ + return True + + def option_image_nocomposite(self): + """ + return whether to generate a composite image from multiple images on + a set of axes + """ + return not rcParams['image.composite_image'] + + def draw_image(self, gc, x, y, im, transform=None): h, w = im.shape[:2] if w == 0 or h == 0: return @@ -627,7 +638,22 @@ def draw_image(self, gc, x, y, im): writeln(self.fh, r"\begin{pgfscope}") self._print_pgf_clip(gc) f = 1. / self.dpi # from display coords to inch - writeln(self.fh, r"\pgftext[at=\pgfqpoint{%fin}{%fin},left,bottom]{\pgfimage[interpolate=true,width=%fin,height=%fin]{%s}}" % (x * f, y * f, w * f, h * f, fname_img)) + if transform is None: + writeln(self.fh, + r"\pgfsys@transformshift{%fin}{%fin}" % (x * f, y * f)) + w, h = w * f, h * f + else: + tr1, tr2, tr3, tr4, tr5, tr6 = transform.frozen().to_values() + writeln(self.fh, + r"\pgfsys@transformcm{%f}{%f}{%f}{%f}{%fin}{%fin}" % + (tr1 * f, tr2 * f, tr3 * f, tr4 * f, + (tr5 + x) * f, (tr6 + y) * f)) + w = h = 1 # scale is already included in the transform + interp = str(transform is None).lower() # interpolation in PDF reader + writeln(self.fh, + r"\pgftext[left,bottom]" + r"{\pgfimage[interpolate=%s,width=%fin,height=%fin]{%s}}" % + (interp, w, h, fname_img)) writeln(self.fh, r"\end{pgfscope}") def draw_tex(self, gc, x, y, s, prop, angle, ismath="TeX!", mtext=None): From d931283c9adf082dc9c6ae74a9e9609f6dd306f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Schl=C3=BCter?= Date: Mon, 18 Jul 2016 19:32:24 +0200 Subject: [PATCH 3/4] Add more transforms to affine image transform demo --- examples/api/demo_affine_image.py | 49 +++++++++++++++++++------------ 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/examples/api/demo_affine_image.py b/examples/api/demo_affine_image.py index 384df3845b05..7f1f91aff940 100644 --- a/examples/api/demo_affine_image.py +++ b/examples/api/demo_affine_image.py @@ -1,11 +1,10 @@ """ -For the backends that supports draw_image with optional affine +For the backends that support draw_image with optional affine transform (e.g., agg, ps backend), the image of the output should -have its boundary matches the red rectangles. +have its boundary match the dashed yellow rectangle. """ import numpy as np -import matplotlib.cm as cm import matplotlib.mlab as mlab import matplotlib.pyplot as plt import matplotlib.transforms as mtransforms @@ -21,25 +20,37 @@ def get_image(): return Z -if 1: +def do_plot(ax, Z, transform): + im = ax.imshow(Z, interpolation='none', + origin='lower', + extent=[-2, 4, -3, 2], clip_on=True) - # image rotation + trans_data = transform + ax.transData + im.set_transform(trans_data) - fig, ax1 = plt.subplots(1, 1) - Z = get_image() - im1 = ax1.imshow(Z, interpolation='none', - origin='lower', - extent=[-2, 4, -3, 2], clip_on=True) + # display intended extent of the image + x1, x2, y1, y2 = im.get_extent() + ax.plot([x1, x2, x2, x1, x1], [y1, y1, y2, y2, y1], "y--", + transform=trans_data) + ax.set_xlim(-5, 5) + ax.set_ylim(-4, 4) - trans_data2 = mtransforms.Affine2D().rotate_deg(30) + ax1.transData - im1.set_transform(trans_data2) - # display intended extent of the image - x1, x2, y1, y2 = im1.get_extent() - x3, y3 = x2, y1 +# prepare image and figure +fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2) +Z = get_image() + +# image rotation +do_plot(ax1, Z, mtransforms.Affine2D().rotate_deg(30)) + +# image skew +do_plot(ax2, Z, mtransforms.Affine2D().skew_deg(30, 15)) + +# scale and reflection +do_plot(ax3, Z, mtransforms.Affine2D().scale(-1, .5)) - ax1.plot([x1, x2, x2, x1, x1], [y1, y1, y2, y2, y1], "--", - transform=trans_data2) +# everything and a translation +do_plot(ax4, Z, mtransforms.Affine2D(). + rotate_deg(30).skew_deg(30, 15).scale(-1, .5).translate(.5, -1)) - ax1.set_xlim(-3, 5) - ax1.set_ylim(-4, 4) +plt.show() From 0eb17a4c4b709172c6488400412cd982f4ae2cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Schl=C3=BCter?= Date: Wed, 20 Jul 2016 15:19:47 +0200 Subject: [PATCH 4/4] RendererBase: improve draw_image() docstring --- lib/matplotlib/backend_bases.py | 34 ++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 2427830a5f76..bc5b62da9302 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -508,30 +508,34 @@ def get_image_magnification(self): """ return 1.0 - def draw_image(self, gc, x, y, im, trans=None): + def draw_image(self, gc, x, y, im, transform=None): """ - Draw the image instance into the current axes; + Draw an RGBA image. *gc* - a GraphicsContext containing clipping information + a :class:`GraphicsContextBase` instance with clipping information. *x* - is the distance in pixels from the left hand side of the canvas. + the distance in physical units (i.e., dots or pixels) from the left + hand side of the canvas. *y* - the distance from the origin. That is, if origin is - upper, y is the distance from top. If origin is lower, y - is the distance from bottom + the distance in physical units (i.e., dots or pixels) from the + bottom side of the canvas. *im* An NxMx4 array of RGBA pixels (of dtype uint8). - *trans* - If the concrete backend is written such that - `option_scale_image` returns `True`, an affine - transformation may also be passed to `draw_image`. The - backend should apply the transformation to the image - before applying the translation of `x` and `y`. + *transform* + If and only if the concrete backend is written such that + :meth:`option_scale_image` returns ``True``, an affine + transformation *may* be passed to :meth:`draw_image`. It takes the + form of a :class:`~matplotlib.transforms.Affine2DBase` instance. + The translation vector of the transformation is given in physical + units (i.e., dots or pixels). Note that the transformation does not + override `x` and `y`, and has to be applied *before* translating + the result by `x` and `y` (this can be accomplished by adding `x` + and `y` to the translation vector defined by `transform`). """ raise NotImplementedError @@ -544,8 +548,8 @@ def option_image_nocomposite(self): def option_scale_image(self): """ - override this method for renderers that support arbitrary - scaling of image (most of the vector backend). + override this method for renderers that support arbitrary affine + transformations in :meth:`draw_image` (most vector backends). """ return False