| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| Expand Up | @@ -3,12 +3,12 @@ | |
| ============================== | ||
| :Author: Steve Chaplin and others | ||
|
|
||
| This backend depends on `cairo <http://cairographics.org>`_, and either on | ||
| cairocffi, or (Python 2 only) on pycairo. | ||
| This backend depends on cairocffi or pycairo. | ||
| """ | ||
|
|
||
| import six | ||
|
|
||
| import copy | ||
| import gzip | ||
| import sys | ||
| import warnings | ||
| Expand All | @@ -35,13 +35,14 @@ | |
| "cairo>=1.4.0 is required".format(cairo.version)) | ||
| backend_version = cairo.version | ||
|
|
||
| from matplotlib import cbook | ||
| from matplotlib.backend_bases import ( | ||
| _Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase, | ||
| RendererBase) | ||
| from matplotlib.font_manager import ttfFontProperty | ||
| from matplotlib.mathtext import MathTextParser | ||
| from matplotlib.path import Path | ||
| from matplotlib.transforms import Affine2D | ||
| from matplotlib.font_manager import ttfFontProperty | ||
|
|
||
|
|
||
| def _premultiplied_argb32_to_unmultiplied_rgba8888(buf): | ||
| Expand Down Expand Up | @@ -79,6 +80,93 @@ def buffer_info(self): | |
| return (self.__data, self.__size) | ||
|
|
||
|
|
||
| # Mapping from Matplotlib Path codes to cairo path codes. | ||
| _MPL_TO_CAIRO_PATH_TYPE = np.zeros(80, dtype=int) # CLOSEPOLY = 79. | ||
| _MPL_TO_CAIRO_PATH_TYPE[Path.MOVETO] = cairo.PATH_MOVE_TO | ||
| _MPL_TO_CAIRO_PATH_TYPE[Path.LINETO] = cairo.PATH_LINE_TO | ||
| _MPL_TO_CAIRO_PATH_TYPE[Path.CURVE4] = cairo.PATH_CURVE_TO | ||
| _MPL_TO_CAIRO_PATH_TYPE[Path.CLOSEPOLY] = cairo.PATH_CLOSE_PATH | ||
| # Sizes in cairo_path_data_t of each cairo path element. | ||
| _CAIRO_PATH_TYPE_SIZES = np.zeros(4, dtype=int) | ||
| _CAIRO_PATH_TYPE_SIZES[cairo.PATH_MOVE_TO] = 2 | ||
| _CAIRO_PATH_TYPE_SIZES[cairo.PATH_LINE_TO] = 2 | ||
| _CAIRO_PATH_TYPE_SIZES[cairo.PATH_CURVE_TO] = 4 | ||
| _CAIRO_PATH_TYPE_SIZES[cairo.PATH_CLOSE_PATH] = 1 | ||
|
|
||
|
|
||
| def _append_paths_slow(ctx, paths, transforms, clip=None): | ||
| for path, transform in zip(paths, transforms): | ||
| for points, code in path.iter_segments(transform, clip=clip): | ||
| if code == Path.MOVETO: | ||
| ctx.move_to(*points) | ||
| elif code == Path.CLOSEPOLY: | ||
| ctx.close_path() | ||
| elif code == Path.LINETO: | ||
| ctx.line_to(*points) | ||
| elif code == Path.CURVE3: | ||
| cur = ctx.get_current_point() | ||
| ctx.curve_to( | ||
| *np.concatenate([cur / 3 + points[:2] * 2 / 3, | ||
| points[:2] * 2 / 3 + points[-2:] / 3])) | ||
| elif code == Path.CURVE4: | ||
| ctx.curve_to(*points) | ||
|
|
||
|
|
||
| def _append_paths_fast(ctx, paths, transforms, clip=None): | ||
| # We directly convert to the internal representation used by cairo, for | ||
| # which ABI compatibility is guaranteed. The layout for each item is | ||
| # --CODE(4)-- -LENGTH(4)- ---------PAD(8)--------- | ||
| # ----------X(8)---------- ----------Y(8)---------- | ||
| # with the size in bytes in parentheses, and (X, Y) repeated as many times | ||
| # as there are points for the current code. | ||
| ffi = cairo.ffi | ||
|
|
||
| # Convert curves to segment, so that 1. we don't have to handle | ||
| # variable-sized CURVE-n codes, and 2. we don't have to implement degree | ||
| # elevation for quadratic Beziers. | ||
| cleaneds = [path.cleaned(transform=transform, clip=clip, curves=False) | ||
| for path, transform in zip(paths, transforms)] | ||
| vertices = np.concatenate([cleaned.vertices for cleaned in cleaneds]) | ||
| codes = np.concatenate([cleaned.codes for cleaned in cleaneds]) | ||
|
|
||
| # Remove unused vertices and convert to cairo codes. Note that unlike | ||
| # cairo_close_path, we do not explicitly insert an extraneous MOVE_TO after | ||
| # CLOSE_PATH, so our resulting buffer may be smaller. | ||
| vertices = vertices[(codes != Path.STOP) & (codes != Path.CLOSEPOLY)] | ||
| codes = codes[codes != Path.STOP] | ||
| codes = _MPL_TO_CAIRO_PATH_TYPE[codes] | ||
|
|
||
| # Where are the headers of each cairo portions? | ||
| cairo_type_sizes = _CAIRO_PATH_TYPE_SIZES[codes] | ||
| cairo_type_positions = np.insert(np.cumsum(cairo_type_sizes), 0, 0) | ||
| cairo_num_data = cairo_type_positions[-1] | ||
| cairo_type_positions = cairo_type_positions[:-1] | ||
|
|
||
| # Fill the buffer. | ||
| buf = np.empty(cairo_num_data * 16, np.uint8) | ||
| as_int = np.frombuffer(buf.data, np.int32) | ||
| as_int[::4][cairo_type_positions] = codes | ||
| as_int[1::4][cairo_type_positions] = cairo_type_sizes | ||
| as_float = np.frombuffer(buf.data, np.float64) | ||
| mask = np.ones_like(as_float, bool) | ||
| mask[::2][cairo_type_positions] = mask[1::2][cairo_type_positions] = False | ||
| as_float[mask] = vertices.ravel() | ||
|
|
||
| # Construct the cairo_path_t, and pass it to the context. | ||
| ptr = ffi.new("cairo_path_t *") | ||
| ptr.status = cairo.STATUS_SUCCESS | ||
| ptr.data = ffi.cast("cairo_path_data_t *", ffi.from_buffer(buf)) | ||
| ptr.num_data = cairo_num_data | ||
| cairo.cairo.cairo_append_path(ctx._pointer, ptr) | ||
|
|
||
|
|
||
| _append_paths = _append_paths_fast if HAS_CAIRO_CFFI else _append_paths_slow | ||
|
|
||
|
|
||
| def _append_path(ctx, path, transform, clip=None): | ||
| return _append_paths(ctx, [path], [transform], clip) | ||
|
|
||
|
|
||
| class RendererCairo(RendererBase): | ||
| fontweights = { | ||
| 100 : cairo.FONT_WEIGHT_NORMAL, | ||
| Expand Down Expand Up | @@ -139,37 +227,20 @@ def _fill_and_stroke(self, ctx, fill_c, alpha, alpha_overrides): | |
| ctx.stroke() | ||
|
|
||
| @staticmethod | ||
| @cbook.deprecated("3.0") | ||
| def convert_path(ctx, path, transform, clip=None): | ||
| for points, code in path.iter_segments(transform, clip=clip): | ||
| if code == Path.MOVETO: | ||
| ctx.move_to(*points) | ||
| elif code == Path.CLOSEPOLY: | ||
| ctx.close_path() | ||
| elif code == Path.LINETO: | ||
| ctx.line_to(*points) | ||
| elif code == Path.CURVE3: | ||
| ctx.curve_to(points[0], points[1], | ||
| points[0], points[1], | ||
| points[2], points[3]) | ||
| elif code == Path.CURVE4: | ||
| ctx.curve_to(*points) | ||
| _append_path(ctx, path, transform, clip) | ||
|
|
||
| def draw_path(self, gc, path, transform, rgbFace=None): | ||
| ctx = gc.ctx | ||
|
|
||
| # We'll clip the path to the actual rendering extents | ||
| # if the path isn't filled. | ||
| if rgbFace is None and gc.get_hatch() is None: | ||
| clip = ctx.clip_extents() | ||
| else: | ||
| clip = None | ||
|
|
||
| # Clip the path to the actual rendering extents if it isn't filled. | ||
| clip = (ctx.clip_extents() | ||
| if rgbFace is None and gc.get_hatch() is None | ||
| else None) | ||
| transform = (transform | ||
| + Affine2D().scale(1.0, -1.0).translate(0, self.height)) | ||
|
|
||
| + Affine2D().scale(1, -1).translate(0, self.height)) | ||
|
Comment thread
Copy link
Copy Markdown
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityCould be += to make one line?
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Qualitytransforms are mutable but don't implement iadd (so += actually doesn't modify the transform in place), but I was too lazy to check and want to make clear that we're indeed not mutating the transform.
Sorry, something went wrong.
All reactions
|
||
| ctx.new_path() | ||
| self.convert_path(ctx, path, transform, clip) | ||
|
|
||
| _append_path(ctx, path, transform, clip) | ||
| self._fill_and_stroke( | ||
| ctx, rgbFace, gc.get_alpha(), gc.get_forced_alpha()) | ||
|
|
||
| Expand All | @@ -179,8 +250,7 @@ def draw_markers(self, gc, marker_path, marker_trans, path, transform, | |
|
|
||
| ctx.new_path() | ||
| # Create the path for the marker; it needs to be flipped here already! | ||
| self.convert_path( | ||
| ctx, marker_path, marker_trans + Affine2D().scale(1.0, -1.0)) | ||
| _append_path(ctx, marker_path, marker_trans + Affine2D().scale(1, -1)) | ||
| marker_path = ctx.copy_path_flat() | ||
|
|
||
| # Figure out whether the path has a fill | ||
| Expand All | @@ -193,7 +263,7 @@ def draw_markers(self, gc, marker_path, marker_trans, path, transform, | |
| filled = True | ||
|
|
||
| transform = (transform | ||
| + Affine2D().scale(1.0, -1.0).translate(0, self.height)) | ||
| + Affine2D().scale(1, -1).translate(0, self.height)) | ||
|
Comment thread
Copy link
Copy Markdown
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityAlso +=?
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Qualityas above
Sorry, something went wrong.
All reactions
|
||
|
|
||
| ctx.new_path() | ||
| for i, (vertices, codes) in enumerate( | ||
| Expand Down Expand Up | @@ -221,6 +291,57 @@ def draw_markers(self, gc, marker_path, marker_trans, path, transform, | |
| self._fill_and_stroke( | ||
| ctx, rgbFace, gc.get_alpha(), gc.get_forced_alpha()) | ||
|
|
||
| def draw_path_collection( | ||
| self, gc, master_transform, paths, all_transforms, offsets, | ||
| offsetTrans, facecolors, edgecolors, linewidths, linestyles, | ||
| antialiaseds, urls, offset_position): | ||
|
|
||
| path_ids = [] | ||
| for path, transform in self._iter_collection_raw_paths( | ||
| master_transform, paths, all_transforms): | ||
| path_ids.append((path, Affine2D(transform))) | ||
|
|
||
| reuse_key = None | ||
| grouped_draw = [] | ||
|
|
||
| def _draw_paths(): | ||
| if not grouped_draw: | ||
| return | ||
| gc_vars, rgb_fc = reuse_key | ||
| gc = copy.copy(gc0) | ||
| # We actually need to call the setters to reset the internal state. | ||
| vars(gc).update(gc_vars) | ||
| for k, v in gc_vars.items(): | ||
| if k == "_linestyle": # Deprecated, no effect. | ||
| continue | ||
| try: | ||
| getattr(gc, "set" + k)(v) | ||
| except (AttributeError, TypeError) as e: | ||
| pass | ||
| gc.ctx.new_path() | ||
| paths, transforms = zip(*grouped_draw) | ||
| grouped_draw.clear() | ||
| _append_paths(gc.ctx, paths, transforms) | ||
| self._fill_and_stroke( | ||
| gc.ctx, rgb_fc, gc.get_alpha(), gc.get_forced_alpha()) | ||
|
|
||
| for xo, yo, path_id, gc0, rgb_fc in self._iter_collection( | ||
| gc, master_transform, all_transforms, path_ids, offsets, | ||
| offsetTrans, facecolors, edgecolors, linewidths, linestyles, | ||
| antialiaseds, urls, offset_position): | ||
| path, transform = path_id | ||
| transform = (Affine2D(transform.get_matrix()) | ||
| .translate(xo, yo - self.height).scale(1, -1)) | ||
| # rgb_fc could be a ndarray, for which equality is elementwise. | ||
| new_key = vars(gc0), tuple(rgb_fc) if rgb_fc is not None else None | ||
| if new_key == reuse_key: | ||
| grouped_draw.append((path, transform)) | ||
| else: | ||
| _draw_paths() | ||
|
Comment thread
Copy link
Copy Markdown
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityDo we need to worry about draw order and such here?
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityThe draw order is maintained, it's just that I batch the draws that use the same gc together.
Sorry, something went wrong.
All reactions
|
||
| grouped_draw.append((path, transform)) | ||
| reuse_key = new_key | ||
| _draw_paths() | ||
|
|
||
| def draw_image(self, gc, x, y, im): | ||
| # bbox - not currently used | ||
| if sys.byteorder == 'little': | ||
| Expand All | @@ -233,12 +354,12 @@ def draw_image(self, gc, x, y, im): | |
| # on ctypes to get a pointer to the numpy array. This works | ||
| # correctly on a numpy array in python3 but not 2.7. We replicate | ||
| # the array.array functionality here to get cross version support. | ||
| imbuffer = ArrayWrapper(im.flatten()) | ||
| imbuffer = ArrayWrapper(im.ravel()) | ||
| else: | ||
| # pycairo uses PyObject_AsWriteBuffer to get a pointer to the | ||
| # py2cairo uses PyObject_AsWriteBuffer to get a pointer to the | ||
| # numpy array; this works correctly on a regular numpy array but | ||
| # not on a py2 memoryview. | ||
| imbuffer = im.flatten() | ||
| # not on a memory view. | ||
| imbuffer = im.ravel() | ||
| surface = cairo.ImageSurface.create_for_data( | ||
| imbuffer, cairo.FORMAT_ARGB32, | ||
| im.shape[1], im.shape[0], im.shape[1]*4) | ||
| Expand All | @@ -247,7 +368,7 @@ def draw_image(self, gc, x, y, im): | |
|
|
||
| ctx.save() | ||
| ctx.set_source_surface(surface, float(x), float(y)) | ||
| if gc.get_alpha() != 1.0: | ||
| if gc.get_alpha() != 1: | ||
| ctx.paint_with_alpha(gc.get_alpha()) | ||
| else: | ||
| ctx.paint() | ||
| Expand Down Expand Up | @@ -299,7 +420,6 @@ def _draw_mathtext(self, gc, x, y, s, prop, angle): | |
| ctx.move_to(ox, oy) | ||
|
|
||
| fontProp = ttfFontProperty(font) | ||
| ctx.save() | ||
| ctx.select_font_face(fontProp.name, | ||
| self.fontangles[fontProp.style], | ||
| self.fontweights[fontProp.weight]) | ||
| Expand All | @@ -309,7 +429,6 @@ def _draw_mathtext(self, gc, x, y, s, prop, angle): | |
| if not six.PY3 and isinstance(s, six.text_type): | ||
| s = s.encode("utf-8") | ||
| ctx.show_text(s) | ||
| ctx.restore() | ||
|
|
||
| for ox, oy, w, h in rects: | ||
| ctx.new_path() | ||
| Expand Down Expand Up | @@ -415,7 +534,7 @@ def set_clip_path(self, path): | |
| ctx.new_path() | ||
| affine = (affine | ||
| + Affine2D().scale(1, -1).translate(0, self.renderer.height)) | ||
| RendererCairo.convert_path(ctx, tpath, affine) | ||
| _append_path(ctx, tpath, affine) | ||
| ctx.clip() | ||
|
|
||
| def set_dashes(self, offset, dashes): | ||
| Expand Down | ||
| Back | FazBrowse Home | New Git URL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityNot sure why this is different than before?
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Qualityprevious version was actually incorrect (see basically https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Degree_elevation)..
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.