/[ascend]/branches/pallav/ascxx/backend_agg.py
ViewVC logotype

Annotation of /branches/pallav/ascxx/backend_agg.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2868 - (hide annotations) (download) (as text)
Mon Mar 23 16:45:33 2015 UTC (7 years, 10 months ago) by pallav
File MIME type: text/x-python
File size: 18778 byte(s)
Added the matplotlib backend files
1 pallav 2868 """
2     An agg http://antigrain.com/ backend
3    
4     Features that are implemented
5    
6     * capstyles and join styles
7     * dashes
8     * linewidth
9     * lines, rectangles, ellipses
10     * clipping to a rectangle
11     * output to RGBA and PNG
12     * alpha blending
13     * DPI scaling properly - everything scales properly (dashes, linewidths, etc)
14     * draw polygon
15     * freetype2 w/ ft2font
16    
17     TODO:
18    
19     * integrate screen dpi w/ ppi and text
20    
21     """
22     from __future__ import absolute_import, division, print_function, unicode_literals
23    
24     import six
25    
26     import threading
27     import numpy as np
28    
29     from matplotlib import verbose, rcParams
30     from matplotlib.backend_bases import RendererBase,\
31     FigureManagerBase, FigureCanvasBase
32     from matplotlib.cbook import is_string_like, maxdict
33     from matplotlib.figure import Figure
34     from matplotlib.font_manager import findfont
35     from matplotlib.ft2font import FT2Font, LOAD_FORCE_AUTOHINT, LOAD_NO_HINTING, \
36     LOAD_DEFAULT, LOAD_NO_AUTOHINT
37     from matplotlib.mathtext import MathTextParser
38     from matplotlib.path import Path
39     from matplotlib.transforms import Bbox, BboxBase
40    
41     from matplotlib.backends._backend_agg import RendererAgg as _RendererAgg
42     from matplotlib import _png
43    
44     backend_version = 'v2.2'
45    
46     def get_hinting_flag():
47     mapping = {
48     True: LOAD_FORCE_AUTOHINT,
49     False: LOAD_NO_HINTING,
50     'either': LOAD_DEFAULT,
51     'native': LOAD_NO_AUTOHINT,
52     'auto': LOAD_FORCE_AUTOHINT,
53     'none': LOAD_NO_HINTING
54     }
55     return mapping[rcParams['text.hinting']]
56    
57    
58     class RendererAgg(RendererBase):
59     """
60     The renderer handles all the drawing primitives using a graphics
61     context instance that controls the colors/styles
62     """
63     debug=1
64    
65     # we want to cache the fonts at the class level so that when
66     # multiple figures are created we can reuse them. This helps with
67     # a bug on windows where the creation of too many figures leads to
68     # too many open file handles. However, storing them at the class
69     # level is not thread safe. The solution here is to let the
70     # FigureCanvas acquire a lock on the fontd at the start of the
71     # draw, and release it when it is done. This allows multiple
72     # renderers to share the cached fonts, but only one figure can
73     # draw at at time and so the font cache is used by only one
74     # renderer at a time
75    
76     lock = threading.RLock()
77     _fontd = maxdict(50)
78     def __init__(self, width, height, dpi):
79     if __debug__: verbose.report('RendererAgg.__init__', 'debug-annoying')
80     RendererBase.__init__(self)
81     self.texd = maxdict(50) # a cache of tex image rasters
82    
83     self.dpi = dpi
84     self.width = width
85     self.height = height
86     if __debug__: verbose.report('RendererAgg.__init__ width=%s, height=%s'%(width, height), 'debug-annoying')
87     self._renderer = _RendererAgg(int(width), int(height), dpi, debug=False)
88     self._filter_renderers = []
89    
90     if __debug__: verbose.report('RendererAgg.__init__ _RendererAgg done',
91     'debug-annoying')
92    
93     self._update_methods()
94     self.mathtext_parser = MathTextParser('Agg')
95    
96     self.bbox = Bbox.from_bounds(0, 0, self.width, self.height)
97     if __debug__: verbose.report('RendererAgg.__init__ done',
98     'debug-annoying')
99    
100     def _get_hinting_flag(self):
101     if rcParams['text.hinting']:
102     return LOAD_FORCE_AUTOHINT
103     else:
104     return LOAD_NO_HINTING
105    
106     # for filtering to work with rasterization, methods needs to be wrapped.
107     # maybe there is better way to do it.
108     def draw_markers(self, *kl, **kw):
109     return self._renderer.draw_markers(*kl, **kw)
110    
111     def draw_path_collection(self, *kl, **kw):
112     return self._renderer.draw_path_collection(*kl, **kw)
113    
114     def _update_methods(self):
115     #self.draw_path = self._renderer.draw_path # see below
116     #self.draw_markers = self._renderer.draw_markers
117     #self.draw_path_collection = self._renderer.draw_path_collection
118     self.draw_quad_mesh = self._renderer.draw_quad_mesh
119     self.draw_gouraud_triangle = self._renderer.draw_gouraud_triangle
120     self.draw_gouraud_triangles = self._renderer.draw_gouraud_triangles
121     self.draw_image = self._renderer.draw_image
122     self.copy_from_bbox = self._renderer.copy_from_bbox
123     self.tostring_rgba_minimized = self._renderer.tostring_rgba_minimized
124    
125     def draw_path(self, gc, path, transform, rgbFace=None):
126     """
127     Draw the path
128     """
129     nmax = rcParams['agg.path.chunksize'] # here at least for testing
130     npts = path.vertices.shape[0]
131     if (nmax > 100 and npts > nmax and path.should_simplify and
132     rgbFace is None and gc.get_hatch() is None):
133     nch = np.ceil(npts/float(nmax))
134     chsize = int(np.ceil(npts/nch))
135     i0 = np.arange(0, npts, chsize)
136     i1 = np.zeros_like(i0)
137     i1[:-1] = i0[1:] - 1
138     i1[-1] = npts
139     for ii0, ii1 in zip(i0, i1):
140     v = path.vertices[ii0:ii1,:]
141     c = path.codes
142     if c is not None:
143     c = c[ii0:ii1]
144     c[0] = Path.MOVETO # move to end of last chunk
145     p = Path(v, c)
146     self._renderer.draw_path(gc, p, transform, rgbFace)
147     else:
148     self._renderer.draw_path(gc, path, transform, rgbFace)
149    
150     def draw_mathtext(self, gc, x, y, s, prop, angle):
151     """
152     Draw the math text using matplotlib.mathtext
153     """
154     if __debug__: verbose.report('RendererAgg.draw_mathtext',
155     'debug-annoying')
156     ox, oy, width, height, descent, font_image, used_characters = \
157     self.mathtext_parser.parse(s, self.dpi, prop)
158    
159     xd = descent * np.sin(np.deg2rad(angle))
160     yd = descent * np.cos(np.deg2rad(angle))
161     x = np.round(x + ox + xd)
162     y = np.round(y - oy + yd)
163     self._renderer.draw_text_image(font_image, x, y + 1, angle, gc)
164    
165     def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
166     """
167     Render the text
168     """
169     if __debug__: verbose.report('RendererAgg.draw_text', 'debug-annoying')
170    
171     if ismath:
172     return self.draw_mathtext(gc, x, y, s, prop, angle)
173    
174     flags = get_hinting_flag()
175     font = self._get_agg_font(prop)
176     if font is None: return None
177     if len(s) == 1 and ord(s) > 127:
178     font.load_char(ord(s), flags=flags)
179     else:
180     # We pass '0' for angle here, since it will be rotated (in raster
181     # space) in the following call to draw_text_image).
182     font.set_text(s, 0, flags=flags)
183     font.draw_glyphs_to_bitmap(antialiased=rcParams['text.antialiased'])
184     d = font.get_descent() / 64.0
185     # The descent needs to be adjusted for the angle
186     xd = -d * np.sin(np.deg2rad(angle))
187     yd = d * np.cos(np.deg2rad(angle))
188    
189     #print x, y, int(x), int(y), s
190     self._renderer.draw_text_image(
191     font.get_image(), np.round(x - xd), np.round(y + yd) + 1, angle, gc)
192    
193     def get_text_width_height_descent(self, s, prop, ismath):
194     """
195     get the width and height in display coords of the string s
196     with FontPropertry prop
197    
198     # passing rgb is a little hack to make cacheing in the
199     # texmanager more efficient. It is not meant to be used
200     # outside the backend
201     """
202     if rcParams['text.usetex']:
203     # todo: handle props
204     size = prop.get_size_in_points()
205     texmanager = self.get_texmanager()
206     fontsize = prop.get_size_in_points()
207     w, h, d = texmanager.get_text_width_height_descent(s, fontsize,
208     renderer=self)
209     return w, h, d
210    
211     if ismath:
212     ox, oy, width, height, descent, fonts, used_characters = \
213     self.mathtext_parser.parse(s, self.dpi, prop)
214     return width, height, descent
215    
216     flags = get_hinting_flag()
217     font = self._get_agg_font(prop)
218     font.set_text(s, 0.0, flags=flags) # the width and height of unrotated string
219     w, h = font.get_width_height()
220     d = font.get_descent()
221     w /= 64.0 # convert from subpixels
222     h /= 64.0
223     d /= 64.0
224     return w, h, d
225    
226    
227     def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!', mtext=None):
228     # todo, handle props, angle, origins
229     size = prop.get_size_in_points()
230    
231     texmanager = self.get_texmanager()
232     key = s, size, self.dpi, angle, texmanager.get_font_config()
233     im = self.texd.get(key)
234     if im is None:
235     Z = texmanager.get_grey(s, size, self.dpi)
236     Z = np.array(Z * 255.0, np.uint8)
237    
238     w, h, d = self.get_text_width_height_descent(s, prop, ismath)
239     xd = d * np.sin(np.deg2rad(angle))
240     yd = d * np.cos(np.deg2rad(angle))
241     x = np.round(x + xd)
242     y = np.round(y + yd)
243    
244     self._renderer.draw_text_image(Z, x, y, angle, gc)
245    
246     def get_canvas_width_height(self):
247     'return the canvas width and height in display coords'
248     return self.width, self.height
249    
250     def _get_agg_font(self, prop):
251     """
252     Get the font for text instance t, cacheing for efficiency
253     """
254     if __debug__: verbose.report('RendererAgg._get_agg_font',
255     'debug-annoying')
256    
257     key = hash(prop)
258     font = RendererAgg._fontd.get(key)
259    
260     if font is None:
261     fname = findfont(prop)
262     font = RendererAgg._fontd.get(fname)
263     if font is None:
264     font = FT2Font(
265     str(fname),
266     hinting_factor=rcParams['text.hinting_factor'])
267     RendererAgg._fontd[fname] = font
268     RendererAgg._fontd[key] = font
269    
270     font.clear()
271     size = prop.get_size_in_points()
272     font.set_size(size, self.dpi)
273    
274     return font
275    
276     def points_to_pixels(self, points):
277     """
278     convert point measures to pixes using dpi and the pixels per
279     inch of the display
280     """
281     if __debug__: verbose.report('RendererAgg.points_to_pixels',
282     'debug-annoying')
283     return points*self.dpi/72.0
284    
285     def tostring_rgb(self):
286     if __debug__: verbose.report('RendererAgg.tostring_rgb',
287     'debug-annoying')
288     return self._renderer.tostring_rgb()
289    
290     def tostring_argb(self):
291     if __debug__: verbose.report('RendererAgg.tostring_argb',
292     'debug-annoying')
293     return self._renderer.tostring_argb()
294    
295     def buffer_rgba(self):
296     if __debug__: verbose.report('RendererAgg.buffer_rgba',
297     'debug-annoying')
298     return self._renderer.buffer_rgba()
299    
300     def clear(self):
301     self._renderer.clear()
302    
303     def option_image_nocomposite(self):
304     # It is generally faster to composite each image directly to
305     # the Figure, and there's no file size benefit to compositing
306     # with the Agg backend
307     return True
308    
309     def option_scale_image(self):
310     """
311     agg backend support arbitrary scaling of image.
312     """
313     return True
314    
315     def restore_region(self, region, bbox=None, xy=None):
316     """
317     Restore the saved region. If bbox (instance of BboxBase, or
318     its extents) is given, only the region specified by the bbox
319     will be restored. *xy* (a tuple of two floasts) optionally
320     specifies the new position (the LLC of the original region,
321     not the LLC of the bbox) where the region will be restored.
322    
323     >>> region = renderer.copy_from_bbox()
324     >>> x1, y1, x2, y2 = region.get_extents()
325     >>> renderer.restore_region(region, bbox=(x1+dx, y1, x2, y2),
326     ... xy=(x1-dx, y1))
327    
328     """
329     if bbox is not None or xy is not None:
330     if bbox is None:
331     x1, y1, x2, y2 = region.get_extents()
332     elif isinstance(bbox, BboxBase):
333     x1, y1, x2, y2 = bbox.extents
334     else:
335     x1, y1, x2, y2 = bbox
336    
337     if xy is None:
338     ox, oy = x1, y1
339     else:
340     ox, oy = xy
341    
342     self._renderer.restore_region2(region, x1, y1, x2, y2, ox, oy)
343    
344     else:
345     self._renderer.restore_region(region)
346    
347     def start_filter(self):
348     """
349     Start filtering. It simply create a new canvas (the old one is saved).
350     """
351     self._filter_renderers.append(self._renderer)
352     self._renderer = _RendererAgg(int(self.width), int(self.height),
353     self.dpi)
354     self._update_methods()
355    
356     def stop_filter(self, post_processing):
357     """
358     Save the plot in the current canvas as a image and apply
359     the *post_processing* function.
360    
361     def post_processing(image, dpi):
362     # ny, nx, depth = image.shape
363     # image (numpy array) has RGBA channels and has a depth of 4.
364     ...
365     # create a new_image (numpy array of 4 channels, size can be
366     # different). The resulting image may have offsets from
367     # lower-left corner of the original image
368     return new_image, offset_x, offset_y
369    
370     The saved renderer is restored and the returned image from
371     post_processing is plotted (using draw_image) on it.
372     """
373    
374     # WARNING.
375     # For agg_filter to work, the rendere's method need
376     # to overridden in the class. See draw_markers, and draw_path_collections
377    
378     from matplotlib._image import fromarray
379    
380     width, height = int(self.width), int(self.height)
381    
382     buffer, bounds = self._renderer.tostring_rgba_minimized()
383    
384     l, b, w, h = bounds
385    
386    
387     self._renderer = self._filter_renderers.pop()
388     self._update_methods()
389    
390     if w > 0 and h > 0:
391     img = np.fromstring(buffer, np.uint8)
392     img, ox, oy = post_processing(img.reshape((h, w, 4)) / 255.,
393     self.dpi)
394     image = fromarray(img, 1)
395     image.flipud_out()
396    
397     gc = self.new_gc()
398     self._renderer.draw_image(gc,
399     l+ox, height - b - h +oy,
400     image)
401    
402    
403     def new_figure_manager(num, *args, **kwargs):
404     """
405     Create a new figure manager instance
406     """
407     if __debug__: verbose.report('backend_agg.new_figure_manager',
408     'debug-annoying')
409    
410    
411     FigureClass = kwargs.pop('FigureClass', Figure)
412     thisFig = FigureClass(*args, **kwargs)
413     return new_figure_manager_given_figure(num, thisFig)
414    
415    
416     def new_figure_manager_given_figure(num, figure):
417     """
418     Create a new figure manager instance for the given figure.
419     """
420     canvas = FigureCanvasAgg(figure)
421     manager = FigureManagerBase(canvas, num)
422     return manager
423    
424    
425     class FigureCanvasAgg(FigureCanvasBase):
426     """
427     The canvas the figure renders into. Calls the draw and print fig
428     methods, creates the renderers, etc...
429    
430     Public attribute
431    
432     figure - A Figure instance
433     """
434    
435     def copy_from_bbox(self, bbox):
436     renderer = self.get_renderer()
437     return renderer.copy_from_bbox(bbox)
438    
439     def restore_region(self, region, bbox=None, xy=None):
440     renderer = self.get_renderer()
441     return renderer.restore_region(region, bbox, xy)
442    
443     def draw(self):
444     """
445     Draw the figure using the renderer
446     """
447     if __debug__: verbose.report('FigureCanvasAgg.draw', 'debug-annoying')
448    
449     self.renderer = self.get_renderer(cleared=True)
450     # acquire a lock on the shared font cache
451     RendererAgg.lock.acquire()
452    
453     try:
454     self.figure.draw(self.renderer)
455     finally:
456     RendererAgg.lock.release()
457    
458    
459    
460     def get_renderer(self, cleared=False):
461     l, b, w, h = self.figure.bbox.bounds
462     key = w, h, self.figure.dpi
463     try: self._lastKey, self.renderer
464     except AttributeError: need_new_renderer = True
465     else: need_new_renderer = (self._lastKey != key)
466    
467     if need_new_renderer:
468     self.renderer = RendererAgg(w, h, self.figure.dpi)
469     self._lastKey = key
470     elif cleared:
471     self.renderer.clear()
472     return self.renderer
473    
474     def tostring_rgb(self):
475     if __debug__: verbose.report('FigureCanvasAgg.tostring_rgb',
476     'debug-annoying')
477     return self.renderer.tostring_rgb()
478    
479     def tostring_argb(self):
480     if __debug__: verbose.report('FigureCanvasAgg.tostring_argb',
481     'debug-annoying')
482     return self.renderer.tostring_argb()
483    
484     def buffer_rgba(self):
485     if __debug__: verbose.report('FigureCanvasAgg.buffer_rgba',
486     'debug-annoying')
487     return self.renderer.buffer_rgba()
488    
489     def print_raw(self, filename_or_obj, *args, **kwargs):
490     FigureCanvasAgg.draw(self)
491     renderer = self.get_renderer()
492     original_dpi = renderer.dpi
493     renderer.dpi = self.figure.dpi
494     if is_string_like(filename_or_obj):
495     filename_or_obj = open(filename_or_obj, 'wb')
496     close = True
497     else:
498     close = False
499     try:
500     renderer._renderer.write_rgba(filename_or_obj)
501     finally:
502     if close:
503     filename_or_obj.close()
504     renderer.dpi = original_dpi
505     print_rgba = print_raw
506    
507     def print_png(self, filename_or_obj, *args, **kwargs):
508     FigureCanvasAgg.draw(self)
509     renderer = self.get_renderer()
510     original_dpi = renderer.dpi
511     renderer.dpi = self.figure.dpi
512     if is_string_like(filename_or_obj):
513     filename_or_obj = open(filename_or_obj, 'wb')
514     close = True
515     else:
516     close = False
517     try:
518     _png.write_png(renderer._renderer.buffer_rgba(),
519     renderer.width, renderer.height,
520     filename_or_obj, self.figure.dpi)
521     finally:
522     if close:
523     filename_or_obj.close()
524     renderer.dpi = original_dpi
525    
526     def print_to_buffer(self):
527     FigureCanvasAgg.draw(self)
528     renderer = self.get_renderer()
529     original_dpi = renderer.dpi
530     renderer.dpi = self.figure.dpi
531     result = (renderer._renderer.buffer_rgba(),
532     (int(renderer.width), int(renderer.height)))
533     renderer.dpi = original_dpi
534     return result

john.pye@anu.edu.au
ViewVC Help
Powered by ViewVC 1.1.22