Skip to content

Commit e981e1e

Browse files
efiringmdboom
authored andcommitted
Merge pull request #6178 from mdboom/macagg
Use Agg for rendering in the Mac OSX backend
1 parent 9655f45 commit e981e1e

File tree

5 files changed

+273
-3671
lines changed

5 files changed

+273
-3671
lines changed

lib/matplotlib/animation.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -895,7 +895,8 @@ def _blit_clear(self, artists, bg_cache):
895895
# cache and restore.
896896
axes = set(a.axes for a in artists)
897897
for a in axes:
898-
a.figure.canvas.restore_region(bg_cache[a])
898+
if a in bg_cache:
899+
a.figure.canvas.restore_region(bg_cache[a])
899900

900901
def _setup_blit(self):
901902
# Setting up the blit requires: a cache of the background for the

lib/matplotlib/backends/backend_agg.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ def print_raw(self, filename_or_obj, *args, **kwargs):
509509
finally:
510510
if close:
511511
filename_or_obj.close()
512-
renderer.dpi = original_dpi
512+
renderer.dpi = original_dpi
513513
print_rgba = print_raw
514514

515515
def print_png(self, filename_or_obj, *args, **kwargs):
@@ -528,16 +528,18 @@ def print_png(self, filename_or_obj, *args, **kwargs):
528528
finally:
529529
if close:
530530
filename_or_obj.close()
531-
renderer.dpi = original_dpi
531+
renderer.dpi = original_dpi
532532

533533
def print_to_buffer(self):
534534
FigureCanvasAgg.draw(self)
535535
renderer = self.get_renderer()
536536
original_dpi = renderer.dpi
537537
renderer.dpi = self.figure.dpi
538-
result = (renderer._renderer.buffer_rgba(),
539-
(int(renderer.width), int(renderer.height)))
540-
renderer.dpi = original_dpi
538+
try:
539+
result = (renderer._renderer.buffer_rgba(),
540+
(int(renderer.width), int(renderer.height)))
541+
finally:
542+
renderer.dpi = original_dpi
541543
return result
542544

543545
if _has_pil:

lib/matplotlib/backends/backend_macosx.py

+52-249
Original file line numberDiff line numberDiff line change
@@ -4,231 +4,29 @@
44
from matplotlib.externals import six
55

66
import os
7-
import numpy
87

98
from matplotlib._pylab_helpers import Gcf
10-
from matplotlib.backend_bases import RendererBase, GraphicsContextBase,\
11-
FigureManagerBase, FigureCanvasBase, NavigationToolbar2, TimerBase
9+
from matplotlib.backend_bases import FigureManagerBase, FigureCanvasBase, \
10+
NavigationToolbar2, TimerBase
1211
from matplotlib.backend_bases import ShowBase
1312

14-
from matplotlib.cbook import maxdict
1513
from matplotlib.figure import Figure
16-
from matplotlib.path import Path
17-
from matplotlib.mathtext import MathTextParser
18-
from matplotlib.colors import colorConverter
1914
from matplotlib import rcParams
2015

2116
from matplotlib.widgets import SubplotTool
2217

2318
import matplotlib
2419
from matplotlib.backends import _macosx
2520

21+
from .backend_agg import RendererAgg, FigureCanvasAgg
22+
2623

2724
class Show(ShowBase):
2825
def mainloop(self):
2926
_macosx.show()
3027
show = Show()
3128

3229

33-
class RendererMac(RendererBase):
34-
"""
35-
The renderer handles drawing/rendering operations. Most of the renderer's
36-
methods forward the command to the renderer's graphics context. The
37-
renderer does not wrap a C object and is written in pure Python.
38-
"""
39-
40-
texd = maxdict(50) # a cache of tex image rasters
41-
42-
def __init__(self, dpi, width, height):
43-
RendererBase.__init__(self)
44-
self.dpi = dpi
45-
self.width = width
46-
self.height = height
47-
self.gc = GraphicsContextMac()
48-
self.gc.set_dpi(self.dpi)
49-
self.mathtext_parser = MathTextParser('MacOSX')
50-
51-
def set_width_height (self, width, height):
52-
self.width, self.height = width, height
53-
54-
def draw_path(self, gc, path, transform, rgbFace=None):
55-
if rgbFace is not None:
56-
rgbFace = tuple(rgbFace)
57-
linewidth = gc.get_linewidth()
58-
gc.draw_path(path, transform, linewidth, rgbFace)
59-
60-
def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None):
61-
if rgbFace is not None:
62-
rgbFace = tuple(rgbFace)
63-
linewidth = gc.get_linewidth()
64-
gc.draw_markers(marker_path, marker_trans, path, trans, linewidth, rgbFace)
65-
66-
def draw_path_collection(self, gc, master_transform, paths, all_transforms,
67-
offsets, offsetTrans, facecolors, edgecolors,
68-
linewidths, linestyles, antialiaseds, urls,
69-
offset_position):
70-
if offset_position=='data':
71-
offset_position = True
72-
else:
73-
offset_position = False
74-
path_ids = []
75-
for path, transform in self._iter_collection_raw_paths(
76-
master_transform, paths, all_transforms):
77-
path_ids.append((path, transform))
78-
master_transform = master_transform.get_matrix()
79-
offsetTrans = offsetTrans.get_matrix()
80-
gc.draw_path_collection(master_transform, path_ids, all_transforms,
81-
offsets, offsetTrans, facecolors, edgecolors,
82-
linewidths, linestyles, antialiaseds,
83-
offset_position)
84-
85-
def draw_quad_mesh(self, gc, master_transform, meshWidth, meshHeight,
86-
coordinates, offsets, offsetTrans, facecolors,
87-
antialiased, edgecolors):
88-
gc.draw_quad_mesh(master_transform.get_matrix(),
89-
meshWidth,
90-
meshHeight,
91-
coordinates,
92-
offsets,
93-
offsetTrans.get_matrix(),
94-
facecolors,
95-
antialiased,
96-
edgecolors)
97-
98-
def new_gc(self):
99-
self.gc.save()
100-
self.gc.set_hatch(None)
101-
self.gc._alpha = 1.0
102-
self.gc._forced_alpha = False # if True, _alpha overrides A from RGBA
103-
return self.gc
104-
105-
def draw_gouraud_triangle(self, gc, points, colors, transform):
106-
points = transform.transform(points)
107-
gc.draw_gouraud_triangle(points, colors)
108-
109-
def get_image_magnification(self):
110-
return self.gc.get_image_magnification()
111-
112-
def draw_image(self, gc, x, y, im):
113-
nrows, ncols, data = im.as_rgba_str()
114-
gc.draw_image(x, y, nrows, ncols, data)
115-
116-
def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!', mtext=None):
117-
# todo, handle props, angle, origins
118-
scale = self.gc.get_image_magnification()
119-
size = prop.get_size_in_points()
120-
texmanager = self.get_texmanager()
121-
key = s, size, self.dpi, angle, texmanager.get_font_config()
122-
im = self.texd.get(key) # Not sure what this does; just copied from backend_agg.py
123-
if im is None:
124-
Z = texmanager.get_grey(s, size, self.dpi*scale)
125-
Z = numpy.array(255.0 - Z * 255.0, numpy.uint8)
126-
127-
gc.draw_mathtext(x, y, angle, Z)
128-
129-
def _draw_mathtext(self, gc, x, y, s, prop, angle):
130-
scale = self.gc.get_image_magnification()
131-
ox, oy, width, height, descent, image, used_characters = \
132-
self.mathtext_parser.parse(s, self.dpi*scale, prop)
133-
descent /= scale
134-
xd = descent * numpy.sin(numpy.deg2rad(angle))
135-
yd = descent * numpy.cos(numpy.deg2rad(angle))
136-
x = numpy.round(x + ox + xd)
137-
y = numpy.round(y + oy - yd)
138-
gc.draw_mathtext(x, y, angle, 255 - image.as_array())
139-
140-
def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
141-
if ismath:
142-
self._draw_mathtext(gc, x, y, s, prop, angle)
143-
else:
144-
family = prop.get_family()
145-
weight = prop.get_weight()
146-
# transform weight into string for the native backend
147-
if weight >= 700:
148-
weight = 'bold'
149-
else:
150-
weight = 'normal'
151-
style = prop.get_style()
152-
points = prop.get_size_in_points()
153-
size = self.points_to_pixels(points)
154-
gc.draw_text(x, y, six.text_type(s), family, size, weight, style, angle)
155-
156-
def get_text_width_height_descent(self, s, prop, ismath):
157-
if ismath=='TeX':
158-
# todo: handle props
159-
texmanager = self.get_texmanager()
160-
fontsize = prop.get_size_in_points()
161-
w, h, d = texmanager.get_text_width_height_descent(s, fontsize,
162-
renderer=self)
163-
return w, h, d
164-
if ismath:
165-
ox, oy, width, height, descent, fonts, used_characters = \
166-
self.mathtext_parser.parse(s, self.dpi, prop)
167-
return width, height, descent
168-
family = prop.get_family()
169-
weight = prop.get_weight()
170-
# transform weight into string for the native backend
171-
if weight >= 700:
172-
weight = 'bold'
173-
else:
174-
weight = 'normal'
175-
style = prop.get_style()
176-
points = prop.get_size_in_points()
177-
size = self.points_to_pixels(points)
178-
width, height, descent = self.gc.get_text_width_height_descent(
179-
six.text_type(s), family, size, weight, style)
180-
return width, height, descent
181-
182-
def flipy(self):
183-
return False
184-
185-
def points_to_pixels(self, points):
186-
return points/72.0 * self.dpi
187-
188-
def option_image_nocomposite(self):
189-
return True
190-
191-
192-
class GraphicsContextMac(_macosx.GraphicsContext, GraphicsContextBase):
193-
"""
194-
The GraphicsContext wraps a Quartz graphics context. All methods
195-
are implemented at the C-level in macosx.GraphicsContext. These
196-
methods set drawing properties such as the line style, fill color,
197-
etc. The actual drawing is done by the Renderer, which draws into
198-
the GraphicsContext.
199-
"""
200-
def __init__(self):
201-
GraphicsContextBase.__init__(self)
202-
_macosx.GraphicsContext.__init__(self)
203-
204-
def set_alpha(self, alpha):
205-
GraphicsContextBase.set_alpha(self, alpha)
206-
_alpha = self.get_alpha()
207-
_macosx.GraphicsContext.set_alpha(self, _alpha, self.get_forced_alpha())
208-
rgb = self.get_rgb()
209-
_macosx.GraphicsContext.set_foreground(self, rgb)
210-
211-
def set_foreground(self, fg, isRGBA=False):
212-
GraphicsContextBase.set_foreground(self, fg, isRGBA)
213-
rgb = self.get_rgb()
214-
_macosx.GraphicsContext.set_foreground(self, rgb)
215-
216-
def set_graylevel(self, fg):
217-
GraphicsContextBase.set_graylevel(self, fg)
218-
_macosx.GraphicsContext.set_graylevel(self, fg)
219-
220-
def set_clip_rectangle(self, box):
221-
GraphicsContextBase.set_clip_rectangle(self, box)
222-
if not box: return
223-
_macosx.GraphicsContext.set_clip_rectangle(self, box.bounds)
224-
225-
def set_clip_path(self, path):
226-
GraphicsContextBase.set_clip_path(self, path)
227-
if not path: return
228-
path = path.get_fully_transformed_path()
229-
_macosx.GraphicsContext.set_clip_path(self, path)
230-
231-
23230
########################################################################
23331
#
23432
# The following functions and classes are for pylab and implement
@@ -285,7 +83,7 @@ class TimerMac(_macosx.Timer, TimerBase):
28583
# completely implemented at the C-level (in _macosx.Timer)
28684

28785

288-
class FigureCanvasMac(_macosx.FigureCanvas, FigureCanvasBase):
86+
class FigureCanvasMac(_macosx.FigureCanvas, FigureCanvasAgg):
28987
"""
29088
The canvas the figure renders into. Calls the draw and print fig
29189
methods, creates the renderers, etc...
@@ -300,61 +98,66 @@ class FigureCanvasMac(_macosx.FigureCanvas, FigureCanvasBase):
30098
key_press_event, and key_release_event are called from there.
30199
"""
302100

303-
filetypes = FigureCanvasBase.filetypes.copy()
304-
filetypes['bmp'] = 'Windows bitmap'
305-
filetypes['jpeg'] = 'JPEG'
306-
filetypes['jpg'] = 'JPEG'
307-
filetypes['gif'] = 'Graphics Interchange Format'
308-
filetypes['tif'] = 'Tagged Image Format File'
309-
filetypes['tiff'] = 'Tagged Image Format File'
310-
311101
def __init__(self, figure):
312102
FigureCanvasBase.__init__(self, figure)
313103
width, height = self.get_width_height()
314-
self.renderer = RendererMac(figure.dpi, width, height)
315104
_macosx.FigureCanvas.__init__(self, width, height)
105+
self._needs_draw = True
106+
self._device_scale = 1.0
107+
108+
def _set_device_scale(self, value):
109+
if self._device_scale != value:
110+
self.figure.dpi = self.figure.dpi / self._device_scale * value
111+
self._device_scale = value
112+
113+
def get_renderer(self, cleared=False):
114+
l, b, w, h = self.figure.bbox.bounds
115+
key = w, h, self.figure.dpi
116+
try:
117+
self._lastKey, self._renderer
118+
except AttributeError:
119+
need_new_renderer = True
120+
else:
121+
need_new_renderer = (self._lastKey != key)
316122

317-
def draw_idle(self, *args, **kwargs):
318-
self.invalidate()
123+
if need_new_renderer:
124+
self._renderer = RendererAgg(w, h, self.figure.dpi)
125+
self._lastKey = key
126+
elif cleared:
127+
self._renderer.clear()
319128

320-
def resize(self, width, height):
321-
self.renderer.set_width_height(width, height)
322-
dpi = self.figure.dpi
323-
width /= dpi
324-
height /= dpi
325-
self.figure.set_size_inches(width, height, forward=False)
326-
FigureCanvasBase.resize_event(self)
129+
return self._renderer
327130

328-
def _print_bitmap(self, filename, *args, **kwargs):
329-
# In backend_bases.py, print_figure changes the dpi of the figure.
330-
# But since we are essentially redrawing the picture, we need the
331-
# original dpi. Pick it up from the renderer.
332-
dpi = kwargs['dpi']
333-
old_dpi = self.figure.dpi
334-
self.figure.dpi = self.renderer.dpi
335-
width, height = self.figure.get_size_inches()
336-
width, height = width*dpi, height*dpi
337-
filename = six.text_type(filename)
338-
self.write_bitmap(filename, width, height, dpi)
339-
self.figure.dpi = old_dpi
131+
def _draw(self):
132+
renderer = self.get_renderer()
340133

341-
def print_bmp(self, filename, *args, **kwargs):
342-
self._print_bitmap(filename, *args, **kwargs)
134+
if not self._needs_draw:
135+
return renderer
343136

344-
def print_jpg(self, filename, *args, **kwargs):
345-
self._print_bitmap(filename, *args, **kwargs)
137+
self.figure.draw(renderer)
138+
self._needs_draw = False
139+
return renderer
346140

347-
def print_jpeg(self, filename, *args, **kwargs):
348-
self._print_bitmap(filename, *args, **kwargs)
141+
def draw(self):
142+
self._draw()
143+
self.invalidate()
349144

350-
def print_tif(self, filename, *args, **kwargs):
351-
self._print_bitmap(filename, *args, **kwargs)
145+
def draw_idle(self, *args, **kwargs):
146+
self._needs_draw = True
147+
self.invalidate()
352148

353-
def print_tiff(self, filename, *args, **kwargs):
354-
self._print_bitmap(filename, *args, **kwargs)
149+
def blit(self, bbox):
150+
self.invalidate()
355151

356-
def print_gif(self, filename, *args, **kwargs):
357-
self._print_bitmap(filename, *args, **kwargs)
152+
def resize(self, width, height):
153+
dpi = self.figure.dpi
154+
width /= dpi
155+
height /= dpi
156+
self.figure.set_size_inches(width * self._device_scale,
157+
height * self._device_scale,
158+
forward=False)
159+
FigureCanvasBase.resize_event(self)
160+
self.draw_idle()
358161

359162
def new_timer(self, *args, **kwargs):
360163
"""

0 commit comments

Comments
 (0)