Skip to content

cairo: remove the append_path() fast path #13042

New issue

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

Merged
merged 1 commit into from
Dec 29, 2018
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 15 additions & 86 deletions lib/matplotlib/backends/backend_cairo.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,93 +71,22 @@ 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, remove_nans=True, 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 = np.asarray(ctx.get_current_point())
a = points[:2]
b = points[-2:]
ctx.curve_to(*(cur / 3 + a * 2 / 3), *(a * 2 / 3 + b / 3), *b)
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, remove_nans=True, clip=clip)
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 cairo.__name__ == "cairocffi"
else _append_paths_slow)


def _append_path(ctx, path, transform, clip=None):
return _append_paths(ctx, [path], [transform], clip)
for points, code in path.iter_segments(
transform, remove_nans=True, 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 = np.asarray(ctx.get_current_point())
a = points[:2]
b = points[-2:]
ctx.curve_to(*(cur / 3 + a * 2 / 3), *(a * 2 / 3 + b / 3), *b)
elif code == Path.CURVE4:
ctx.curve_to(*points)


class RendererCairo(RendererBase):
Expand Down