Revision: 4875
http://matplotlib.svn.sourceforge.net/matplotlib/?rev=4875&view=rev
Author: mdboom
Date: 2008-01-18 06:44:10 -0800 (Fri, 18 Jan 2008)
Log Message:
-----------
Add line simplification, to cut down on the number of line segments
that need to be stroked. Affects *Agg and Gdk backends.
Modified Paths:
--------------
trunk/matplotlib/lib/matplotlib/backends/backend_gdk.py
trunk/matplotlib/lib/matplotlib/path.py
trunk/matplotlib/src/_backend_agg.cpp
trunk/matplotlib/src/_path.cpp
trunk/matplotlib/src/agg_py_path_iterator.h
Modified: trunk/matplotlib/lib/matplotlib/backends/backend_gdk.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/backends/backend_gdk.py 2008-01-17
04:13:27 UTC (rev 4874)
+++ trunk/matplotlib/lib/matplotlib/backends/backend_gdk.py 2008-01-18
14:44:10 UTC (rev 4875)
@@ -84,7 +84,7 @@
def draw_path(self, gc, path, transform, rgbFace=None):
transform = transform + Affine2D(). \
scale(1.0, -1.0).translate(0, self.height)
- polygons = path.to_polygons(transform)
+ polygons = path.to_polygons(transform, self.width, self.height)
for polygon in polygons:
# draw_polygon won't take an arbitrary sequence -- it must be a
list
# of tuples
Modified: trunk/matplotlib/lib/matplotlib/path.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/path.py 2008-01-17 04:13:27 UTC (rev
4874)
+++ trunk/matplotlib/lib/matplotlib/path.py 2008-01-18 14:44:10 UTC (rev
4875)
@@ -283,7 +283,7 @@
new_codes = None
return Path(vertices, new_codes)
- def to_polygons(self, transform=None):
+ def to_polygons(self, transform=None, width=0, height=0):
"""
Convert this path to a list of polygons. Each polygon is an
Nx2 array of vertices. In other words, each polygon has no
@@ -292,13 +292,13 @@
if transform is not None:
transform = transform.frozen()
# Deal with the common and simple case
- if self.codes is None:
+ if self.codes is None and len(self.vertices) < 100:
if len(self.vertices):
return [transform.transform(self.vertices)]
return []
# Deal with the case where there are curves and/or multiple
# subpaths (using extension code)
- return convert_path_to_polygons(self, transform)
+ return convert_path_to_polygons(self, transform, width, height)
_unit_rectangle = None
[EMAIL PROTECTED]
Modified: trunk/matplotlib/src/_backend_agg.cpp
===================================================================
--- trunk/matplotlib/src/_backend_agg.cpp 2008-01-17 04:13:27 UTC (rev
4874)
+++ trunk/matplotlib/src/_backend_agg.cpp 2008-01-18 14:44:10 UTC (rev
4875)
@@ -20,6 +20,7 @@
#include "agg_span_image_filter_gray.h"
#include "agg_span_image_filter_rgba.h"
#include "agg_span_interpolator_linear.h"
+#include "agg_conv_shorten_path.h"
#include "util/agg_color_conv_rgb8.h"
#include "ft2font.h"
@@ -84,31 +85,6 @@
return Py::String(PyString_FromStringAndSize((const char*)data,
height*stride), true);
}
-template<class VertexSource> class conv_quantize
-{
-public:
- conv_quantize(VertexSource& source, bool quantize) :
- m_source(&source), m_quantize(quantize) {}
-
- void rewind(unsigned path_id) {
- m_source->rewind(path_id);
- }
-
- unsigned vertex(double* x, double* y) {
- unsigned cmd = m_source->vertex(x, y);
- if (m_quantize && agg::is_vertex(cmd)) {
- *x = round(*x) + 0.5;
- *y = round(*y) + 0.5;
- }
- return cmd;
- }
-
-private:
- VertexSource* m_source;
- bool m_quantize;
-};
-
-
GCAgg::GCAgg(const Py::Object &gc, double dpi) :
dpi(dpi), isaa(true), linewidth(1.0), alpha(1.0),
dashOffset(0.0)
@@ -358,8 +334,8 @@
template<class Path>
bool should_snap(Path& path, const agg::trans_affine& trans) {
- // If this contains only straight horizontal or vertical lines, quantize to
nearest
- // pixels
+ // If this contains only straight horizontal or vertical lines, it should be
+ // quantized to the nearest pixels
double x0, y0, x1, y1;
unsigned code;
@@ -392,6 +368,11 @@
return true;
}
+template<class Path>
+bool should_simplify(Path& path) {
+ return !path.has_curves() && path.total_vertices() > 5;
+}
+
Py::Object
RendererAgg::copy_from_bbox(const Py::Tuple& args) {
//copy region in bbox to buffer and return swig/agg buffer object
@@ -479,7 +460,7 @@
Py::Object
RendererAgg::draw_markers(const Py::Tuple& args) {
typedef agg::conv_transform<PathIterator> transformed_path_t;
- typedef conv_quantize<transformed_path_t> quantize_t;
+ typedef SimplifyPath<transformed_path_t> simplify_t;
typedef agg::conv_curve<transformed_path_t> curve_t;
typedef agg::conv_stroke<curve_t> stroke_t;
typedef agg::pixfmt_amask_adaptor<pixfmt, alpha_mask_type> pixfmt_amask_type;
@@ -510,8 +491,8 @@
bool snap = should_snap(path, trans);
transformed_path_t path_transformed(path, trans);
GCAgg gc = GCAgg(gc_obj, dpi);
- quantize_t path_quantized(path_transformed, snap);
- path_quantized.rewind(0);
+ simplify_t path_simplified(path_transformed, snap, false, width, height);
+ path_simplified.rewind(0);
facepair_t face = _get_rgba_face(face_obj, gc.alpha);
@@ -564,7 +545,7 @@
agg::serialized_scanlines_adaptor_aa8::embedded_scanline sl;
if (has_clippath) {
- while (path_quantized.vertex(&x, &y) != agg::path_cmd_stop) {
+ while (path_simplified.vertex(&x, &y) != agg::path_cmd_stop) {
pixfmt_amask_type pfa(*pixFmt, *alphaMask);
amask_ren_type r(pfa);
amask_aa_renderer_type ren(r);
@@ -579,7 +560,7 @@
agg::render_scanlines(sa, sl, ren);
}
} else {
- while (path_quantized.vertex(&x, &y) != agg::path_cmd_stop) {
+ while (path_simplified.vertex(&x, &y) != agg::path_cmd_stop) {
if (face.first) {
rendererAA->color(face.second);
sa.init(fillCache, fillSize, x, y);
@@ -881,8 +862,8 @@
Py::Object
RendererAgg::draw_path(const Py::Tuple& args) {
typedef agg::conv_transform<PathIterator> transformed_path_t;
- typedef conv_quantize<transformed_path_t> quantize_t;
- typedef agg::conv_curve<quantize_t> curve_t;
+ typedef SimplifyPath<transformed_path_t> simplify_t;
+ typedef agg::conv_curve<simplify_t> curve_t;
_VERBOSE("RendererAgg::draw_path");
args.verify_length(3, 4);
@@ -906,9 +887,11 @@
trans *= agg::trans_affine_scaling(1.0, -1.0);
trans *= agg::trans_affine_translation(0.0, (double)height);
bool snap = should_snap(path, trans);
+ bool simplify = should_simplify(path);
+
transformed_path_t tpath(path, trans);
- quantize_t quantized(tpath, snap);
- curve_t curve(quantized);
+ simplify_t simplified(tpath, snap, simplify, width, height);
+ curve_t curve(simplified);
if (snap)
gc.isaa = false;
@@ -934,8 +917,8 @@
const Py::SeqBase<Py::Object>& linestyles_obj,
const Py::SeqBase<Py::Int>& antialiaseds) {
typedef agg::conv_transform<typename PathGenerator::path_iterator>
transformed_path_t;
- typedef conv_quantize<transformed_path_t> quantize_t;
- typedef agg::conv_curve<quantize_t>
quantized_curve_t;
+ typedef SimplifyPath<transformed_path_t> simplify_t;
+ typedef agg::conv_curve<simplify_t>
simplified_curve_t;
typedef agg::conv_curve<transformed_path_t> curve_t;
GCAgg gc(dpi);
@@ -1068,12 +1051,12 @@
gc.isaa = bool(Py::Int(antialiaseds[i % Naa]));
transformed_path_t tpath(path, trans);
- quantize_t quantized(tpath, snap);
+ simplify_t simplified(tpath, snap, false, width, height);
if (has_curves) {
- quantized_curve_t curve(quantized);
+ simplified_curve_t curve(simplified);
_draw_path(curve, has_clippath, face, gc);
} else {
- _draw_path(quantized, has_clippath, face, gc);
+ _draw_path(simplified, has_clippath, face, gc);
}
} else {
gc.isaa = bool(Py::Int(antialiaseds[i % Naa]));
Modified: trunk/matplotlib/src/_path.cpp
===================================================================
--- trunk/matplotlib/src/_path.cpp 2008-01-17 04:13:27 UTC (rev 4874)
+++ trunk/matplotlib/src/_path.cpp 2008-01-18 14:44:10 UTC (rev 4875)
@@ -1102,17 +1102,23 @@
Py::Object _path_module::convert_path_to_polygons(const Py::Tuple& args)
{
typedef agg::conv_transform<PathIterator> transformed_path_t;
- typedef agg::conv_curve<transformed_path_t> curve_t;
+ typedef SimplifyPath<transformed_path_t> simplify_t;
+ typedef agg::conv_curve<simplify_t> curve_t;
typedef std::vector<double> vertices_t;
- args.verify_length(2);
+ args.verify_length(4);
PathIterator path(args[0]);
agg::trans_affine trans = py_to_agg_transformation_matrix(args[1], false);
+ double width = Py::Float(args[2]);
+ double height = Py::Float(args[3]);
+ bool simplify = !path.has_curves();
+
transformed_path_t tpath(path, trans);
- curve_t curve(tpath);
+ simplify_t simplified(tpath, false, simplify, width, height);
+ curve_t curve(simplified);
Py::List polygons;
vertices_t polygon;
Modified: trunk/matplotlib/src/agg_py_path_iterator.h
===================================================================
--- trunk/matplotlib/src/agg_py_path_iterator.h 2008-01-17 04:13:27 UTC (rev
4874)
+++ trunk/matplotlib/src/agg_py_path_iterator.h 2008-01-18 14:44:10 UTC (rev
4875)
@@ -6,6 +6,7 @@
#include "numpy/arrayobject.h"
#include "agg_path_storage.h"
#include "MPL_isnan.h"
+#include <deque>
class PathIterator
{
@@ -46,11 +47,16 @@
static const unsigned code_map[];
private:
- inline unsigned vertex(unsigned idx, double* x, double* y)
+ inline void vertex(const unsigned idx, double* x, double* y)
{
char* pair = (char*)PyArray_GETPTR2(m_vertices, idx, 0);
*x = *(double*)pair;
*y = *(double*)(pair + PyArray_STRIDE(m_vertices, 1));
+ }
+
+ inline unsigned vertex_with_code(const unsigned idx, double* x, double* y)
+ {
+ vertex(idx, x, y);
if (m_codes)
{
return code_map[(int)*(char *)PyArray_GETPTR1(m_codes, idx)];
@@ -65,11 +71,12 @@
inline unsigned vertex(double* x, double* y)
{
if (m_iterator >= m_total_vertices) return agg::path_cmd_stop;
- unsigned code = vertex(m_iterator++, x, y);
+ unsigned code = vertex_with_code(m_iterator++, x, y);
while ((MPL_isnan64(*x) || MPL_isnan64(*y)) &&
- m_iterator < m_total_vertices) {
- vertex(m_iterator++, x, y);
- code = agg::path_cmd_move_to;
+ m_iterator < m_total_vertices)
+ {
+ vertex(m_iterator++, x, y);
+ code = agg::path_cmd_move_to;
}
return code;
}
@@ -100,4 +107,368 @@
agg::path_cmd_end_poly | agg::path_flags_close
};
+#define DEBUG_SIMPLIFY 0
+
+template<class VertexSource>
+class SimplifyPath
+{
+public:
+ SimplifyPath(VertexSource& source, bool quantize, bool simplify,
+ double width = 0.0, double height = 0.0) :
+ m_source(&source), m_quantize(quantize), m_simplify(simplify),
+ m_width(width), m_height(height),
+ m_moveto(true), m_lastx(0.0), m_lasty(0.0), m_clipped(false),
+ m_do_clipping(width > 0.0 && height > 0.0),
+ m_origdx(0.0), m_origdy(0.0),
+ m_origdNorm2(0.0), m_dnorm2Max(0.0), m_dnorm2Min(0.0),
+ m_haveMin(false), m_lastMax(false), m_maxX(0.0), m_maxY(0.0),
+ m_minX(0.0), m_minY(0.0), m_lastWrittenX(0.0), m_lastWrittenY(0.0),
+ m_done(false)
+#if DEBUG_SIMPLIFY
+ , m_pushed(0), m_skipped(0)
+#endif
+ {
+ // empty
+ }
+
+#if DEBUG_SIMPLIFY
+ ~SimplifyPath()
+ {
+ printf("%d %d\n", m_pushed, m_skipped);
+ }
+#endif
+
+ void rewind(unsigned path_id)
+ {
+ m_source->rewind(path_id);
+ }
+
+ unsigned vertex(double* x, double* y)
+ {
+ unsigned cmd;
+
+ // The simplification algorithm doesn't support curves or compound
paths
+ // so we just don't do it at all in that case...
+ if (!m_simplify)
+ {
+ cmd = m_source->vertex(x, y);
+ if (m_quantize && agg::is_vertex(cmd))
+ {
+ *x = round(*x) + 0.5;
+ *y = round(*y) + 0.5;
+ }
+ return cmd;
+ }
+
+ //idea: we can skip drawing many lines: lines < 1 pixel in length,
lines
+ //outside of the drawing area, and we can combine sequential parallel
lines
+ //into a single line instead of redrawing lines over the same points.
+ //The loop below works a bit like a state machine, where what it does
depends
+ //on what it did in the last looping. To test whether sequential lines
+ //are close to parallel, I calculate the distance moved perpendicular
to the
+ //last line. Once it gets too big, the lines cannot be combined.
+
+ // This code was originally written by someone else (John Hunter?) and
I
+ // have modified to work in-place -- meaning not creating an entirely
+ // new path list each time. In order to do that without too much
+ // additional code complexity, it keeps a small queue around so that
+ // multiple points can be emitted in a single call, and those points
+ // will be popped from the queue in subsequent calls. The following
+ // block will empty the queue before proceeding to the main loop below.
+ if (m_queue.size())
+ {
+ const item& front = m_queue.front();
+ unsigned cmd = front.cmd;
+ *x = front.x;
+ *y = front.y;
+ m_queue.pop_front();
+ return cmd;
+ }
+
+ // If the queue is now empty, and the path was fully consumed
+ // in the last call to the main loop, return agg::path_cmd_stop to
+ // signal that there are no more points to emit.
+ if (m_done)
+ return agg::path_cmd_stop;
+
+ // The main simplification loop. The point is consume only as many
+ // points as necessary until some have been added to the outbound
+ // queue, not to run through the entire path in one go. This
+ // eliminates the need to allocate and fill an entire additional path
+ // array on each draw.
+ while ((cmd = m_source->vertex(x, y)) != agg::path_cmd_stop)
+ {
+ // Do any quantization if requested
+ if (m_quantize && agg::is_vertex(cmd))
+ {
+ *x = round(*x) + 0.5;
+ *y = round(*y) + 0.5;
+ }
+
+ //if we are starting a new path segment, move to the first point
+ // + init
+ if (m_moveto)
+ {
+ m_queue.push_back(item(agg::path_cmd_move_to, *x, *y));
+ m_lastx = *x;
+ m_lasty = *y;
+ m_moveto = false;
+ m_origdNorm2 = 0.0;
+#if DEBUG_SIMPLIFY
+ m_pushed++;
+#endif
+ break;
+ }
+
+ // Don't render line segments less than one pixel long
+ if (fabs(*x - m_lastx) < 1.0 && fabs(*y - m_lasty) < 1.0)
+ {
+#if DEBUG_SIMPLIFY
+ m_skipped++;
+#endif
+ continue;
+ }
+
+ //skip any lines that are outside the drawing area. Note: More
lines
+ //could be clipped, but a more involved calculation would be needed
+ if (m_do_clipping &&
+ ((*x < -1 && m_lastx < -1) ||
+ (*x > m_width + 1 && m_lastx > m_width + 1) ||
+ (*y < -1 && m_lasty < -1) ||
+ (*y > m_height + 1 && m_lasty > m_height + 1)))
+ {
+ m_lastx = *x;
+ m_lasty = *y;
+ m_clipped = true;
+ continue;
+ }
+
+ // if we have no orig vector, set it to this vector and
+ // continue.
+ // this orig vector is the reference vector we will build
+ // up the line to
+
+ if (m_origdNorm2 == 0)
+ {
+ if (m_clipped)
+ {
+ m_queue.push_back(item(agg::path_cmd_move_to, m_lastx,
m_lasty));
+ m_clipped = false;
+ }
+
+ m_origdx = *x - m_lastx;
+ m_origdy = *y - m_lasty;
+ m_origdNorm2 = m_origdx*m_origdx + m_origdy+m_origdy;
+
+ //set all the variables to reflect this new orig vecor
+ m_dnorm2Max = m_origdNorm2;
+ m_dnorm2Min = 0;
+ m_haveMin = false;
+ m_lastMax = true;
+
+ m_maxX = *x;
+ m_maxY = *y;
+ m_minX = m_lastx;
+ m_minY = m_lasty;
+
+ m_lastWrittenX = m_lastx;
+ m_lastWrittenY = m_lasty;
+
+ // set the last point seen
+ m_lastx = *x;
+ m_lasty = *y;
+ continue;
+ }
+
+ //if got to here, then we have an orig vector and we just got
+ //a vector in the sequence.
+
+ //check that the perpendicular distance we have moved from the
+ //last written point compared to the line we are building is not
too
+ //much. If o is the orig vector (we are building on), and v is the
+ //vector from the last written point to the current point, then the
+ //perpendicular vector is p = v - (o.v)o, and we normalize o (by
+ //dividing the second term by o.o).
+
+ // get the v vector
+ double totdx = *x - m_lastWrittenX;
+ double totdy = *y - m_lastWrittenY;
+ double totdot = m_origdx*totdx + m_origdy*totdy;
+
+ // get the para vector ( = (o.v)o/(o.o))
+ double paradx = totdot*m_origdx/m_origdNorm2;
+ double parady = totdot*m_origdy/m_origdNorm2;
+ double paradNorm2 = paradx*paradx + parady*parady;
+
+ // get the perp vector ( = v - para)
+ double perpdx = totdx - paradx;
+ double perpdy = totdy - parady;
+ double perpdNorm2 = perpdx*perpdx + perpdy*perpdy;
+
+ //if the perp vector is less than some number of (squared)
+ //pixels in size, then merge the current vector
+ if (perpdNorm2 < 0.25)
+ {
+ //check if the current vector is parallel or
+ //anti-parallel to the orig vector. If it is parallel, test
+ //if it is the longest of the vectors we are merging in that
+ //direction. If anti-p, test if it is the longest in the
+ //opposite direction (the min of our final line)
+
+ m_lastMax = false;
+ if (totdot >= 0)
+ {
+ if (paradNorm2 > m_dnorm2Max)
+ {
+ m_lastMax = true;
+ m_dnorm2Max = paradNorm2;
+ m_maxX = m_lastWrittenX + paradx;
+ m_maxY = m_lastWrittenY + parady;
+ }
+ }
+ else
+ {
+ m_haveMin = true;
+ if (paradNorm2 > m_dnorm2Min)
+ {
+ m_dnorm2Min = paradNorm2;
+ m_minX = m_lastWrittenX + paradx;
+ m_minY = m_lastWrittenY + parady;
+ }
+ }
+
+ m_lastx = *x;
+ m_lasty = *y;
+ continue;
+ }
+
+ //if we get here, then this vector was not similar enough to the
+ //line we are building, so we need to draw that line and start the
+ //next one.
+
+ //if the line needs to extend in the opposite direction from the
+ //direction we are drawing in, move back to we start drawing from
+ //back there.
+ if (m_haveMin)
+ m_queue.push_back(item(agg::path_cmd_line_to, m_minX, m_minY));
+ m_queue.push_back(item(agg::path_cmd_line_to, m_maxX, m_maxY));
+
+ //if we clipped some segments between this line and the next line
+ //we are starting, we also need to move to the last point.
+ if (m_clipped)
+ {
+ m_queue.push_back(item(agg::path_cmd_move_to, m_lastx,
m_lasty));
+ }
+ else if (!m_lastMax)
+ {
+ //if the last line was not the longest line, then move back to
+ //the end point of the last line in the sequence. Only do this
+ //if not clipped, since in that case lastx,lasty is not part of
+ //the line just drawn.
+
+ //Would be move_to if not for the artifacts
+ m_queue.push_back(item(agg::path_cmd_line_to, m_lastx,
m_lasty));
+ }
+
+ //now reset all the variables to get ready for the next line
+
+ m_origdx = *x - m_lastx;
+ m_origdy = *y - m_lasty;
+ m_origdNorm2 = m_origdx*m_origdx + m_origdy*m_origdy;
+
+ m_dnorm2Max = m_origdNorm2;
+ m_dnorm2Min = 0;
+ m_haveMin = false;
+ m_lastMax = true;
+ m_maxX = *x;
+ m_maxY = *y;
+ m_minX = m_lastx;
+ m_minY = m_lasty;
+
+ m_lastWrittenX = m_lastx;
+ m_lastWrittenY = m_lasty;
+
+ m_clipped = false;
+
+ m_lastx = *x;
+ m_lasty = *y;
+#if DEBUG_SIMPLIFY
+ m_pushed++;
+#endif
+ break;
+ }
+
+ // Fill the queue with the remaining vertices if we've finished the
+ // path in the above loop. Mark the path as done, so we don't call
+ // m_source->vertex again and segfault.
+ if (cmd == agg::path_cmd_stop)
+ {
+ if (m_origdNorm2 != 0)
+ {
+ if (m_haveMin)
+ m_queue.push_back(item(agg::path_cmd_line_to, m_minX,
m_minY));
+ m_queue.push_back(item(agg::path_cmd_line_to, m_maxX, m_maxY));
+ }
+ m_done = true;
+ }
+
+ // Return the first item in the queue, if any, otherwise
+ // indicate that we're done.
+ if (m_queue.size())
+ {
+ const item& front = m_queue.front();
+ unsigned cmd = front.cmd;
+ *x = front.x;
+ *y = front.y;
+ m_queue.pop_front();
+ return cmd;
+ }
+ else
+ {
+ return agg::path_cmd_stop;
+ }
+ }
+
+private:
+ VertexSource* m_source;
+ bool m_quantize;
+ bool m_simplify;
+ double m_width, m_height;
+
+ struct item
+ {
+ item(unsigned cmd_, const double& x_, double& y_) :
+ cmd(cmd_), x(x_), y(y_) {}
+ unsigned cmd;
+ double x;
+ double y;
+ };
+ typedef std::deque<item> ItemQueue;
+ ItemQueue m_queue;
+ bool m_moveto;
+ double m_lastx, m_lasty;
+ bool m_clipped;
+ bool m_do_clipping;
+
+ double m_origdx;
+ double m_origdy;
+ double m_origdNorm2;
+ double m_dnorm2Max;
+ double m_dnorm2Min;
+ bool m_haveMin;
+ bool m_lastMax;
+ double m_maxX;
+ double m_maxY;
+ double m_minX;
+ double m_minY;
+ double m_lastWrittenX;
+ double m_lastWrittenY;
+ bool m_done;
+
+#if DEBUG_SIMPLIFY
+ unsigned m_pushed;
+ unsigned m_skipped;
+#endif
+};
+
#endif // __AGG_PY_PATH_ITERATOR_H__
This was sent by the SourceForge.net collaborative development platform, the
world's largest Open Source development site.
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Matplotlib-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins