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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2868 - (show annotations) (download) (as text)
Mon Mar 23 16:45:33 2015 UTC (8 years ago) by pallav
File MIME type: text/x-python
File size: 18778 byte(s)
Added the matplotlib backend files
1 """
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