Skip to content

Commit

Permalink
Merge pull request #6792 from f0k/pgf-transform
Browse files Browse the repository at this point in the history
ENH: PGF Backend: Support interpolation='none'
  • Loading branch information
tacaswell committed Aug 27, 2016
1 parent ac01d85 commit e7d7a49
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 39 deletions.
49 changes: 30 additions & 19 deletions 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
Expand All @@ -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()
34 changes: 19 additions & 15 deletions lib/matplotlib/backend_bases.py
Expand Up @@ -515,30 +515,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

Expand All @@ -551,8 +555,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

Expand Down
38 changes: 33 additions & 5 deletions lib/matplotlib/backends/backend_pgf.py
Expand Up @@ -609,9 +609,23 @@ 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

# save the images to png files
path = os.path.dirname(self.fh.name)
Expand All @@ -623,9 +637,23 @@ 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))
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):
Expand Down

0 comments on commit e7d7a49

Please sign in to comment.