Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e783caa

Browse files
committed
Merge pull request matplotlib#4197 from mdboom/faster-paths
PRF : Generate path strings in C++ for PDF and PS
2 parents 32a41bc + d04bc6b commit e783caa

File tree

6 files changed

+229
-112
lines changed

6 files changed

+229
-112
lines changed

lib/matplotlib/backends/backend_pdf.py

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
from matplotlib.backends.backend_mixed import MixedModeRenderer
3838
from matplotlib.cbook import Bunch, is_string_like, \
3939
get_realpath_and_stat, is_writable_file_like, maxdict
40-
from matplotlib.mlab import quad2cubic
4140
from matplotlib.figure import Figure
4241
from matplotlib.font_manager import findfont, is_opentype_cff_font
4342
from matplotlib.afm import AFM
@@ -48,6 +47,7 @@
4847
from matplotlib.mathtext import MathTextParser
4948
from matplotlib.transforms import Affine2D, BboxBase
5049
from matplotlib.path import Path
50+
from matplotlib import _path
5151
from matplotlib import ttconv
5252

5353
# Overview
@@ -287,6 +287,17 @@ def __repr__(self):
287287
def pdfRepr(self):
288288
return self.op
289289

290+
291+
class Verbatim(object):
292+
"""Store verbatim PDF command content for later inclusion in the
293+
stream."""
294+
def __init__(self, x):
295+
self._x = x
296+
297+
def pdfRepr(self):
298+
return self._x
299+
300+
290301
# PDF operators (not an exhaustive list)
291302
_pdfops = dict(
292303
close_fill_stroke=b'b', fill_stroke=b'B', fill=b'f', closepath=b'h',
@@ -1388,32 +1399,11 @@ def writePathCollectionTemplates(self):
13881399

13891400
@staticmethod
13901401
def pathOperations(path, transform, clip=None, simplify=None, sketch=None):
1391-
cmds = []
1392-
last_points = None
1393-
for points, code in path.iter_segments(transform, clip=clip,
1394-
simplify=simplify,
1395-
sketch=sketch):
1396-
if code == Path.MOVETO:
1397-
# This is allowed anywhere in the path
1398-
cmds.extend(points)
1399-
cmds.append(Op.moveto)
1400-
elif code == Path.CLOSEPOLY:
1401-
cmds.append(Op.closepath)
1402-
elif last_points is None:
1403-
# The other operations require a previous point
1404-
raise ValueError('Path lacks initial MOVETO')
1405-
elif code == Path.LINETO:
1406-
cmds.extend(points)
1407-
cmds.append(Op.lineto)
1408-
elif code == Path.CURVE3:
1409-
points = quad2cubic(*(list(last_points[-2:]) + list(points)))
1410-
cmds.extend(points[2:])
1411-
cmds.append(Op.curveto)
1412-
elif code == Path.CURVE4:
1413-
cmds.extend(points)
1414-
cmds.append(Op.curveto)
1415-
last_points = points
1416-
return cmds
1402+
return [Verbatim(_path.convert_to_string(
1403+
path, transform, clip, simplify, sketch,
1404+
6,
1405+
[Op.moveto.op, Op.lineto.op, b'', Op.curveto.op, Op.closepath.op],
1406+
True))]
14171407

14181408
def writePath(self, path, transform, clip=False, sketch=None):
14191409
if clip:

lib/matplotlib/backends/backend_ps.py

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ def _fn_name(): return sys._getframe(1).f_code.co_name
2626

2727
from matplotlib.cbook import is_string_like, get_realpath_and_stat, \
2828
is_writable_file_like, maxdict, file_requires_unicode
29-
from matplotlib.mlab import quad2cubic
3029
from matplotlib.figure import Figure
3130

3231
from matplotlib.font_manager import findfont, is_opentype_cff_font
@@ -36,6 +35,7 @@ def _fn_name(): return sys._getframe(1).f_code.co_name
3635
from matplotlib._mathtext_data import uni2type1
3736
from matplotlib.text import Text
3837
from matplotlib.path import Path
38+
from matplotlib import _path
3939
from matplotlib.transforms import Affine2D
4040

4141
from matplotlib.backends.backend_mixed import MixedModeRenderer
@@ -538,27 +538,9 @@ def _convert_path(self, path, transform, clip=False, simplify=None):
538538
self.height * 72.0)
539539
else:
540540
clip = None
541-
for points, code in path.iter_segments(transform, clip=clip,
542-
simplify=simplify):
543-
if code == Path.MOVETO:
544-
ps.append("%g %g m" % tuple(points))
545-
elif code == Path.CLOSEPOLY:
546-
ps.append("cl")
547-
elif last_points is None:
548-
# The other operations require a previous point
549-
raise ValueError('Path lacks initial MOVETO')
550-
elif code == Path.LINETO:
551-
ps.append("%g %g l" % tuple(points))
552-
elif code == Path.CURVE3:
553-
points = quad2cubic(*(list(last_points[-2:]) + list(points)))
554-
ps.append("%g %g %g %g %g %g c" %
555-
tuple(points[2:]))
556-
elif code == Path.CURVE4:
557-
ps.append("%g %g %g %g %g %g c" % tuple(points))
558-
last_points = points
559-
560-
ps = "\n".join(ps)
561-
return ps
541+
return _path.convert_to_string(
542+
path, transform, clip, simplify, None,
543+
6, [b'm', b'l', b'', b'c', b'cl'], True).decode('ascii')
562544

563545
def _get_clip_path(self, clippath, clippath_transform):
564546
key = (clippath, id(clippath_transform))

lib/matplotlib/backends/backend_svg.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,9 @@ def _convert_path(self, path, transform=None, clip=None, simplify=None):
545545
clip = (0.0, 0.0, self.width, self.height)
546546
else:
547547
clip = None
548-
return _path.convert_to_svg(path, transform, clip, simplify, 6)
548+
return _path.convert_to_string(
549+
path, transform, clip, simplify, None, 6,
550+
[b'M', b'L', b'Q', b'C', b'z'], False).decode('ascii')
549551

550552
def draw_path(self, gc, path, transform, rgbFace=None):
551553
trans_and_flip = self._make_flip_transform(transform)

lib/matplotlib/mlab.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4007,6 +4007,8 @@ def quad2cubic(q0x, q0y, q1x, q1y, q2x, q2y):
40074007
points of a quadratic curve, and the output is a tuple of *x* and
40084008
*y* coordinates of the four control points of the cubic curve.
40094009
"""
4010+
# TODO: Candidate for deprecation -- no longer used internally
4011+
40104012
# c0x, c0y = q0x, q0y
40114013
c1x, c1y = q0x + 2./3. * (q1x - q0x), q0y + 2./3. * (q1y - q0y)
40124014
c2x, c2y = c1x + 1./3. * (q2x - q0x), c1y + 1./3. * (q2y - q0y)

src/_path.h

Lines changed: 139 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -939,79 +939,166 @@ void cleanup_path(PathIterator &path,
939939
}
940940
}
941941

942+
void quad2cubic(double x0, double y0,
943+
double x1, double y1,
944+
double x2, double y2,
945+
double *outx, double *outy)
946+
{
947+
948+
outx[0] = x0 + 2./3. * (x1 - x0);
949+
outy[0] = y0 + 2./3. * (y1 - y0);
950+
outx[1] = outx[0] + 1./3. * (x2 - x0);
951+
outy[1] = outy[0] + 1./3. * (y2 - y0);
952+
outx[2] = x2;
953+
outy[2] = y2;
954+
}
955+
956+
char *__append_to_string(char *p, char *buffer, size_t buffersize,
957+
const char *content)
958+
{
959+
int buffersize_int = (int)buffersize;
960+
961+
for (const char *i = content; *i; ++i) {
962+
if (p < buffer || p - buffer >= buffersize_int) {
963+
return NULL;
964+
}
965+
966+
*p++ = *i;
967+
}
968+
969+
return p;
970+
}
971+
942972
template <class PathIterator>
943-
void convert_to_svg(PathIterator &path,
944-
agg::trans_affine &trans,
945-
agg::rect_d &clip_rect,
946-
bool simplify,
947-
int precision,
948-
char *buffer,
949-
size_t *buffersize)
973+
int __convert_to_string(PathIterator &path,
974+
int precision,
975+
char **codes,
976+
bool postfix,
977+
char *buffer,
978+
size_t *buffersize)
950979
{
951980
#if PY_VERSION_HEX < 0x02070000
952981
char format[64];
953982
snprintf(format, 64, "%s.%dg", "%", precision);
954983
#endif
955984

956-
typedef agg::conv_transform<py::PathIterator> transformed_path_t;
957-
typedef PathNanRemover<transformed_path_t> nan_removal_t;
958-
typedef PathClipper<nan_removal_t> clipped_t;
959-
typedef PathSimplifier<clipped_t> simplify_t;
960-
961-
bool do_clip = (clip_rect.x1 < clip_rect.x2 && clip_rect.y1 < clip_rect.y2);
962-
963-
transformed_path_t tpath(path, trans);
964-
nan_removal_t nan_removed(tpath, true, path.has_curves());
965-
clipped_t clipped(nan_removed, do_clip, clip_rect);
966-
simplify_t simplified(clipped, simplify, path.simplify_threshold());
967-
968985
char *p = buffer;
986+
double x[3];
987+
double y[3];
988+
double last_x = 0.0;
989+
double last_y = 0.0;
969990

970-
const char codes[] = { 'M', 'L', 'Q', 'C' };
971-
const int waits[] = { 1, 1, 2, 3 };
972-
973-
int wait = 0;
991+
const int sizes[] = { 1, 1, 2, 3 };
992+
int size = 0;
974993
unsigned code;
975-
double x = 0, y = 0;
976-
while ((code = simplified.vertex(&x, &y)) != agg::path_cmd_stop) {
977-
if (wait == 0) {
978-
*p++ = '\n';
979994

980-
if (code == 0x4f) {
981-
*p++ = 'z';
982-
*p++ = '\n';
983-
continue;
995+
while ((code = path.vertex(&x[0], &y[0])) != agg::path_cmd_stop) {
996+
if (code == 0x4f) {
997+
if ((p = __append_to_string(p, buffer, *buffersize, codes[4])) == NULL) return 1;
998+
} else if (code < 5) {
999+
size = sizes[code - 1];
1000+
1001+
for (int i = 1; i < size; ++i) {
1002+
unsigned subcode = path.vertex(&x[i], &y[i]);
1003+
if (subcode != code) {
1004+
return 2;
1005+
}
9841006
}
9851007

986-
*p++ = codes[code - 1];
987-
wait = waits[code - 1];
988-
} else {
989-
*p++ = ' ';
990-
}
1008+
/* For formats that don't support quad curves, convert to
1009+
cubic curves */
1010+
if (code == CURVE3 && codes[code - 1][0] == '\0') {
1011+
quad2cubic(last_x, last_y, x[0], y[0], x[1], y[1], x, y);
1012+
code++;
1013+
size = 3;
1014+
}
9911015

1016+
if (!postfix) {
1017+
if ((p = __append_to_string(p, buffer, *buffersize, codes[code - 1])) == NULL) return 1;
1018+
if ((p = __append_to_string(p, buffer, *buffersize, " ")) == NULL) return 1;
1019+
}
1020+
1021+
for (int i = 0; i < size; ++i) {
9921022
#if PY_VERSION_HEX >= 0x02070000
993-
char *str;
994-
str = PyOS_double_to_string(x, 'g', precision, 0, NULL);
995-
p += snprintf(p, *buffersize - (p - buffer), "%s", str);
996-
PyMem_Free(str);
997-
*p++ = ' ';
998-
str = PyOS_double_to_string(y, 'g', precision, 0, NULL);
999-
p += snprintf(p, *buffersize - (p - buffer), "%s", str);
1000-
PyMem_Free(str);
1023+
char *str;
1024+
str = PyOS_double_to_string(x[i], 'g', precision, 0, NULL);
1025+
if ((p = __append_to_string(p, buffer, *buffersize, str)) == NULL) {
1026+
PyMem_Free(str);
1027+
return 1;
1028+
}
1029+
PyMem_Free(str);
1030+
if ((p = __append_to_string(p, buffer, *buffersize, " ")) == NULL) return 1;
1031+
str = PyOS_double_to_string(y[i], 'g', precision, 0, NULL);
1032+
if ((p = __append_to_string(p, buffer, *buffersize, str)) == NULL) {
1033+
PyMem_Free(str);
1034+
return 1;
1035+
}
1036+
PyMem_Free(str);
1037+
if ((p = __append_to_string(p, buffer, *buffersize, " ")) == NULL) return 1;
10011038
#else
1002-
char str[64];
1003-
PyOS_ascii_formatd(str, 64, format, x);
1004-
p += snprintf(p, *buffersize - (p - buffer), "%s", str);
1005-
*p++ = ' ';
1006-
PyOS_ascii_formatd(str, 64, format, y);
1007-
p += snprintf(p, *buffersize - (p - buffer), "%s", str);
1039+
char str[64];
1040+
PyOS_ascii_formatd(str, 64, format, x[i]);
1041+
if ((p = __append_to_string(p, buffer, *buffersize, str)) == NULL) return 1;
1042+
p = __append_to_string(p, buffer, *buffersize, " ");
1043+
PyOS_ascii_formatd(str, 64, format, y[i]);
1044+
if ((p = __append_to_string(p, buffer, *buffersize, str)) == NULL) return 1;
1045+
if ((p = __append_to_string(p, buffer, *buffersize, " ")) == NULL) return 1;
10081046
#endif
1047+
}
1048+
1049+
if (postfix) {
1050+
if ((p = __append_to_string(p, buffer, *buffersize, codes[code - 1])) == NULL) return 1;
1051+
}
1052+
1053+
last_x = x[size - 1];
1054+
last_y = y[size - 1];
1055+
} else {
1056+
// Unknown code value
1057+
return 2;
1058+
}
10091059

1010-
--wait;
1060+
if ((p = __append_to_string(p, buffer, *buffersize, "\n")) == NULL) return 1;
10111061
}
10121062

1013-
*p = '\0';
10141063
*buffersize = p - buffer;
1064+
1065+
return 0;
1066+
}
1067+
1068+
template <class PathIterator>
1069+
int convert_to_string(PathIterator &path,
1070+
agg::trans_affine &trans,
1071+
agg::rect_d &clip_rect,
1072+
bool simplify,
1073+
SketchParams sketch_params,
1074+
int precision,
1075+
char **codes,
1076+
bool postfix,
1077+
char *buffer,
1078+
size_t *buffersize)
1079+
{
1080+
typedef agg::conv_transform<py::PathIterator> transformed_path_t;
1081+
typedef PathNanRemover<transformed_path_t> nan_removal_t;
1082+
typedef PathClipper<nan_removal_t> clipped_t;
1083+
typedef PathSimplifier<clipped_t> simplify_t;
1084+
typedef agg::conv_curve<simplify_t> curve_t;
1085+
typedef Sketch<curve_t> sketch_t;
1086+
1087+
bool do_clip = (clip_rect.x1 < clip_rect.x2 && clip_rect.y1 < clip_rect.y2);
1088+
1089+
transformed_path_t tpath(path, trans);
1090+
nan_removal_t nan_removed(tpath, true, path.has_curves());
1091+
clipped_t clipped(nan_removed, do_clip, clip_rect);
1092+
simplify_t simplified(clipped, simplify, path.simplify_threshold());
1093+
1094+
if (sketch_params.scale == 0.0) {
1095+
return __convert_to_string(simplified, precision, codes, postfix, buffer, buffersize);
1096+
} else {
1097+
curve_t curve(simplified);
1098+
sketch_t sketch(curve, sketch_params.scale, sketch_params.length, sketch_params.randomness);
1099+
return __convert_to_string(sketch, precision, codes, postfix, buffer, buffersize);
1100+
}
1101+
10151102
}
10161103

10171104
#endif

0 commit comments

Comments
 (0)