Fix #5670. No double endpoints in Path.to_polygon #5672

Merged
merged 3 commits into from Dec 15, 2015
@@ -10,6 +10,7 @@
from nose.tools import assert_raises, assert_equal
from matplotlib.testing.decorators import image_comparison
import matplotlib.pyplot as plt
+from matplotlib import transforms
def test_readonly_path():
@@ -113,6 +114,41 @@ def test_marker_paths_pdf():
plt.ylim(-1, 7)
+def test_path_no_doubled_point_in_to_polygon():
+ hand = np.array(
+ [[1.64516129, 1.16145833],
+ [1.64516129, 1.59375],
+ [1.35080645, 1.921875],
+ [1.375, 2.18229167],
+ [1.68548387, 1.9375],
+ [1.60887097, 2.55208333],
+ [1.68548387, 2.69791667],
+ [1.76209677, 2.56770833],
+ [1.83064516, 1.97395833],
+ [1.89516129, 2.75],
+ [1.9516129, 2.84895833],
+ [2.01209677, 2.76041667],
+ [1.99193548, 1.99479167],
+ [2.11290323, 2.63020833],
+ [2.2016129, 2.734375],
+ [2.25403226, 2.60416667],
+ [2.14919355, 1.953125],
+ [2.30645161, 2.36979167],
+ [2.39112903, 2.36979167],
+ [2.41532258, 2.1875],
+ [2.1733871, 1.703125],
+ [2.07782258, 1.16666667]])
+
+ (r0, c0, r1, c1) = (1.0, 1.5, 2.1, 2.5)
+
+ poly = Path(np.vstack((hand[:, 1], hand[:, 0])).T, closed=True)
+ clip_rect = transforms.Bbox([[r0, c0], [r1, c1]])
+ poly_clipped = poly.clip_to_bbox(clip_rect).to_polygons()[0]
+
+ assert np.all(poly_clipped[-2] != poly_clipped[-1])
+ assert np.all(poly_clipped[-1] == poly_clipped[0])
+
+
if __name__ == '__main__':
import nose
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)
@@ -162,7 +162,7 @@ def onselect(epress, erelease):
extents = [int(e) for e in tool.extents]
assert extents == [70, 129, 70, 130], extents
- assert tool.geometry.shape == (2, 74)
+ assert tool.geometry.shape == (2, 73)
assert_allclose(tool.geometry[:, 0], [70., 100])
@@ -245,7 +245,7 @@ def on_clicked(self, func):
"""
When the button is clicked, call this *func* with event.
- A connection id is returned. It can be used to disconnect
+ A connection id is returned. It can be used to disconnect
the button from its callback.
"""
cid = self.cnt
@@ -265,7 +265,7 @@ class Slider(AxesWidget):
"""
A slider representing a floating point range.
- For the slider to remain responsive you must maintain a
+ For the slider to remain responsive you must maintain a
reference to it.
The following attributes are defined
@@ -2036,7 +2036,7 @@ def geometry(self):
if hasattr(self.to_draw, 'get_verts'):
xfm = self.ax.transData.inverted()
y, x = xfm.transform(self.to_draw.get_verts()).T
- return np.array([x[:-1], y[:-1]])
+ return np.array([x, y])
else:
return np.array(self.to_draw.get_data())
View
@@ -27,6 +27,16 @@ struct XY
XY(double x_, double y_) : x(x_), y(y_)
{
}
+
+ bool operator==(const XY& o)
+ {
+ return (x == o.x && y == o.y);
+ }
+
+ bool operator!=(const XY& o)
+ {
+ return (x != o.x || y != o.y);
+ }
};
//
@@ -838,6 +848,25 @@ bool path_intersects_path(PathIterator1 &p1, PathIterator2 &p2)
return false;
}
+void _finalize_polygon(std::vector<Polygon> &result)
+{
+ Polygon &polygon = result.back();
+
+ if (result.size() == 0) {
+ return;
+ }
+
+ /* Clean up the last polygon in the result. If less than a
+ triangle, remove it. */
+ if (polygon.size() < 3) {
+ result.pop_back();
+ } else {
+ if (polygon.front() != polygon.back()) {
+ polygon.push_back(polygon.front());
+ }
+ }
+}
+
template <class PathIterator>
void convert_path_to_polygons(PathIterator &path,
agg::trans_affine &trans,
@@ -867,24 +896,20 @@ void convert_path_to_polygons(PathIterator &path,
while ((code = curve.vertex(&x, &y)) != agg::path_cmd_stop) {
if ((code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) {
- if (polygon->size() >= 1) {
- polygon->push_back((*polygon)[0]);
- result.push_back(Polygon());
- polygon = &result.back();
- }
+ _finalize_polygon(result);
+ result.push_back(Polygon());
+ polygon = &result.back();
} else {
- if (code == agg::path_cmd_move_to && polygon->size() >= 1) {
- polygon->push_back((*polygon)[0]);
+ if (code == agg::path_cmd_move_to) {
+ _finalize_polygon(result);
result.push_back(Polygon());
polygon = &result.back();
}
polygon->push_back(XY(x, y));
}
}
- if (polygon->size() == 0) {
- result.pop_back();
- }
+ _finalize_polygon(result);
}
template <class VertexSource>
View
@@ -8,16 +8,29 @@
PyObject *convert_polygon_vector(std::vector<Polygon> &polygons)
{
PyObject *pyresult = PyList_New(polygons.size());
+ bool fix_endpoints;
for (size_t i = 0; i < polygons.size(); ++i) {
Polygon poly = polygons[i];
- npy_intp dims[] = {(npy_intp)poly.size() + 1, 2 };
- numpy::array_view<double, 2> subresult(dims);
+ npy_intp dims[2];
+ dims[1] = 2;
+
+ if (poly.front() != poly.back()) {
+ /* Make last point same as first, if not already */
+ dims[0] = (npy_intp)poly.size() + 1;
+ fix_endpoints = true;
+ } else {
+ dims[0] = (npy_intp)poly.size();
+ fix_endpoints = false;
+ }
- /* Make last point same as first. */
+ numpy::array_view<double, 2> subresult(dims);
memcpy(subresult.data(), &poly[0], sizeof(double) * poly.size() * 2);
- subresult(poly.size(), 0) = poly[0].x;
- subresult(poly.size(), 1) = poly[0].y;
+
+ if (fix_endpoints) {
+ subresult(poly.size(), 0) = poly.front().x;
+ subresult(poly.size(), 1) = poly.front().y;
+ }
if (PyList_SetItem(pyresult, i, subresult.pyobj())) {
Py_DECREF(pyresult);