KJB
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Public Types | Public Member Functions | Static Public Member Functions | Protected Member Functions | List of all members
kjb::qd::PixPath Class Reference

Representation of a sequence of pixel coordinates. More...

#include <pixpath.h>

Inheritance diagram for kjb::qd::PixPath:
kjb::qd::PixPath_expander kjb::qd::PixPathAc kjb::qd::PolyPath

Public Types

typedef const PixPointconst_reference
 Reference to an element – required to use std::back_inserter(). More...
 
typedef PixPoint value_type
 Element type – required to use std::back_inserter() in c++11. More...
 
typedef VecPP::const_iterator const_iterator
 constant iterator through PixPath points More...
 
typedef
VecPP::const_reverse_iterator 
const_reverse_iterator
 constant reverse iterator through PixPath points More...
 

Public Member Functions

virtual ~PixPath ()
 obligatory dtor More...
 
virtual PixPathassign (unsigned index, const PixPoint &newp)
 Change a member of the path to a new PixPoint. More...
 
const PixPointoperator[] (unsigned index) const
 Vector-like access to points (returning an rvalue). More...
 
virtual void push_back (const PixPoint &pp)
 Vector-like appending a new PixPoint on the end of the object. More...
 
virtual void pop_back ()
 
size_t size () const
 Number of points in the path (not the same as its length). More...
 
virtual void swap (PixPath &vpp)
 Swap the contents of two PixPaths. More...
 
virtual void clear ()
 Discard all points. More...
 
const_iterator begin () const
 Return an iterator pointing to the first point in the path. More...
 
const_iterator end () const
 Rtn iterator pointing to just beyond the last point in the path. More...
 
const_reverse_iterator rbegin () const
 return a reverse-moving iterator to the last point in path More...
 
const_reverse_iterator rend () const
 return one-before-first iterator, in symmetry with end(). More...
 
virtual PixPathappend (const_iterator begin, const_iterator end)
 Append a range from a given other path to the end of this path. More...
 
virtual PixPathappend (const PixPath &suffix)
 Append the given path to the tail of this path; overwrites. More...
 
virtual int append_no_overlap (const PixPath &suffix)
 Like append(), with extra avoid-the-duplicates fuss; overwrites. More...
 
PixPath subrange (unsigned, unsigned) const
 Returns a subrange of this PixPath object. More...
 
PixPath prefix (unsigned postlast) const
 Variation on subrange: return a prefix of this path (by value) More...
 
PixPath suffix (unsigned first) const
 Variation on subrange: return a suffix of this path (by value) More...
 
bool self_intersect (PixPoint *where=00) const
 Reveal whether the path has any self-intersections. More...
 
bool connected8 (bool emit_verbose_debug_output=false) const
 Is the path all connected, using 8-connectivity? More...
 
PixPoint front () const
 Return (by value) the first point in the path. More...
 
PixPoint back () const
 Return (by value) the last point in the path. More...
 
virtual int arclength (std::vector< float > *) const
 Return a vector of arclengths along the polygonal path. More...
 
virtual float arclength () const
 compute and return polygonal path length of this path More...
 
std::string str (const std::string &separator="\n") const
 Return the path as an ASCII string. More...
 
int save (const std::string &filename) const
 Saves the path to an ASCII file, returns NO_ERROR or ERROR. More...
 
bool operator== (const PixPath &) const
 Compare PixPaths; true iff the sequence of points is identical. More...
 
bool operator!= (const PixPath &other) const
 Compare PixPaths; true iff the sequence of points differs. More...
 
PixPath operator+ (const PixPath &) const
 Add, element-by-element, two PixPaths of the same length. More...
 
virtual PixPoint boundbox_min_min () const
 Return the corner point of a bounding box, with min x and y. More...
 
virtual PixPoint boundbox_max_max () const
 Similar to boundbox_min_min() but returns maximum x, y coords. More...
 
bool boundbox_within_boundbox (const PixPath &outer) const
 Test whether the axis-aligned bounding box of this path lies within the axis-aligned bounding box of the given path. More...
 
bool so_boundbox_holds_within (const PixPoint &q) const
 sort of like boundbox_within_boundbox but works on a single point More...
 
unsigned hits (const PixPoint &query_point) const
 Return number of times (maybe 0) the path hits a query point. More...
 
unsigned hits (const PixPath &query_path) const
 return number of common points between query_path and this path More...
 
PixPoint nearest (const PixPoint &, const_iterator *q=00) const
 Return the PixPath member closest to a given query point. More...
 
PixPoint nearest (const kjb::Vector2 &, const_iterator *q=00) const
 Return the PixPath member closest to the given real xy coordinates. More...
 
PixPathow_reverse ()
 Reverse the order of the points in the path (overwriting!) More...
 
PixPath reverse () const
 return a copy of this list after reversing the front-back order. More...
 
int cubic_fit (std::vector< float > *x, std::vector< float > *y, int ref=0) const
 Fit a cubic polynomial to the given list of x and y points. More...
 
PixPath interpolate (bool forbid_self_intersection=false) const
 This interpolates pixels between unconnected pixels. More...
 
PixPath cull_redundant_points () const
 Remove "unnecessary" points (ones that can be bresenham interpolated) More...
 
PixPath merge_redundant_slopes () const
 return a path with interior points removed but preserve all slopes. More...
 
PixPath expel (const_iterator) const
 Duplicate this path, except that we omit the indicated point. More...
 
PixPath expel (size_t index_of_unwanted_point) const
 Duplicate this path, except we omit the point with given index. More...
 
virtual float hausdorff_distance (const PixPath &that) const
 Compute the Hausdorff metric between two sets of points. More...
 
float closest_pair (const_iterator *pa=0, const_iterator *pb=0) const
 Find the distance between closest pair of points, ignoring adjacency. More...
 
float closest_adjacent_pair (const_iterator *pa=0) const
 Find a pair of adjacent points at least as close as any other. More...
 
bool has_subsequence (const PixPath &subseq) const
 tests whether a given path is a subsequence of this path More...
 
double angle_at (unsigned) const
 Compute included angle at some interior point in a path. More...
 
PixPoint::Integer bracket_cross_at (unsigned index) const
 cross product of line segs (index-1,index+1) x (index,index+1) More...
 
double heading_shift_at (unsigned index) const
 compute shift in "heading" angle at an interior point More...
 
bool intersect_at_with (unsigned preindex1, unsigned preindex2, unsigned *endpoint_intersector_index=00) const
 Test whether two segments of this path intersect. More...
 
bool do_segments_intersect (unsigned *s1=00, unsigned *s2=00, unsigned *count=00) const
 quadratic time test whether any 2 path segments cross each other More...
 
unsigned longest_segment (float *length=00) const
 return index of first point of longest segment More...
 
kjb::Vector2 centroid () const
 return the "mean" of the points (nonempty, none may be unused) More...
 
unsigned member_near_centroid () const
 return index of a member point nearest centroid() (qv) More...
 

Static Public Member Functions

static PixPath reserve (size_t potential_size=0)
 named ctor creates an empty path but reserves some memory for it More...
 
static PixPath load (const std::string &filename)
 named ctor loads path coordinates from an ASCII file More...
 
static PixPath fenceposts (const PixPoint &pt_a, const PixPoint &pt_b, size_t sz)
 named ctor makes a path of evenly spaced points from a to b More...
 
static PixPath parse (const std::string &coords)
 named ctor builds from string-format list of coordinates More...
 

Protected Member Functions

 PixPath ()
 Default ctor starts as empty, and you can push points onto it. More...
 
 PixPath (size_t potential_size)
 ctor receives the size to reserve More...
 
 PixPath (const std::string &)
 ctor to load from an ASCII file More...
 
 PixPath (const PixPoint &, const PixPoint &, size_t)
 ctor makes evenly spaced points between termini More...
 
 PixPath (const std::string &, int)
 ctor that parses a string of characters More...
 

Detailed Description

Representation of a sequence of pixel coordinates.

The following class started out as a simple typedef of a vector of PixPoint objects, but then it got a bit fancier. The essential characteristics are as follows:

Todo:
replace map with a multimap containing the indices along the path that hit each point.

Bounding Boxes

To test whether a path lies entirely within a given axis-aligned bounding box, there are two ways to do it and they both take linear time. Suppose we have a bounding box defined by two PixPoints, min_min and width_height. As the names suggest, min_min.x is the minimum valid X-coordinate, and min_min.y is the minimum valid Y-coordinate. The width of the box is width_height.x pixels, and the height is width_height.y pixels; which means, for example, that any point with X-coordinate of min_min.x + width_height.x is just out of bounds. Suppose the path we want to test is called "path."

Testing using PixPath methods

PixPath lets you test containment of two paths' minimal, axis-aligned bounding boxes:

bb.push_back( min_min );
bb.push_back( min_min + width_height - PixPoint(1,1) );
if ( width_height.is_poz_poz() && path.boundbox_within_boundbox( bb ) )
std::cout << "path stays inside bounding box\n";
else
std::cout << "path leaves bounding box somewhere\n";

We subtract PixPoint(1,1) in the third line because we need the upper right maximum inbounds point, and min_min+width_height is out of bounds. But then we must use method PixPoint::is_poz_poz() in case the width or height is less than 1.

Testing using PixPoint functor

We can use functor PixPoint::Is_inbounds to look for an out-of-bounds point:

PixPath::const_iterator q = std::find_if( path.begin(), path.end(),
std::not1( PixPoint::Is_inbounds( min_min, width_height ) ) ) );
if ( path.end() == q )
std::cout << "path stays inside bounding box\n";
else
std::cout <<"path leaves bounding box when it hits "<< q->str() <<'\n';

This approach obliges you to include headers <algorithm> and <functional>.

Member Typedef Documentation

typedef VecPP::const_iterator kjb::qd::PixPath::const_iterator

constant iterator through PixPath points

Reference to an element – required to use std::back_inserter().

typedef VecPP::const_reverse_iterator kjb::qd::PixPath::const_reverse_iterator

constant reverse iterator through PixPath points

Element type – required to use std::back_inserter() in c++11.

Constructor & Destructor Documentation

kjb::qd::PixPath::PixPath ( )
inlineprotected

Default ctor starts as empty, and you can push points onto it.

kjb::qd::PixPath::PixPath ( size_t  potential_size)
inlineprotected

ctor receives the size to reserve

kjb::qd::PixPath::PixPath ( const std::string &  filename)
protected

ctor to load from an ASCII file

kjb::qd::PixPath::PixPath ( const PixPoint pa,
const PixPoint pb,
size_t  sz 
)
protected

ctor makes evenly spaced points between termini

kjb::qd::PixPath::PixPath ( const std::string &  coords,
int   
)
protected

ctor that parses a string of characters

virtual kjb::qd::PixPath::~PixPath ( )
inlinevirtual

obligatory dtor

Member Function Documentation

double kjb::qd::PixPath::angle_at ( unsigned  index) const

Compute included angle at some interior point in a path.

Parameters
indexArray index of the interior point where to do the computation.

The angle we are talking about is the included angle between two line segments defined by a central (interior) point, and the central point's prececessor and successor points. You specify the index of the central point: it must not be the front or back point, because those points lack a predecessor and successor point, respectively.

This does not tell you whether the angle is clockwise or counterclockwise. Consider using bracket_cross_at() if you need to know.

Returns
Angle, in radians, in range [0, M_PI]. For a straight piece of trail, the angle will be close to M_PI.
Precondition
index must be an interior point, not equal to zero or size()-1.
The points at index + 1 and index - 1 must be distinct from the point at index.
Exceptions
Degenerate_triangleif the interior point is equal to its predecessor or successor point: the angle is undefined in that case, of course.
std::out_of_rangeif the specified index is not that of an interior point

Implementation

The basic idea is that for vectors $ p, q $ the dot product $ p \cdot q $ satisfies

\[ p \cdot q = \vert p \vert \cdot \vert q \vert \cos \theta \]

where $ \theta $ is the included angle. So all we have to do is compute $ p $ and $ q $ at the given interior point, then use a little algebra.

PixPath & kjb::qd::PixPath::append ( const_iterator  begin,
const_iterator  end 
)
virtual

Append a range from a given other path to the end of this path.

Parameters
beginiterator pointing to first point to append
enditerator pointing one-past-last point to append
Precondition
begin, end must define an empty or increasing sequence in a different PixPath; behavior is undefined otherwise (probably bad).
Warning
This is not a const method; it modifies the path!
Returns
A reference to this path (after the modification of course)

Be aware that if the first point of suffix is the same as the last point in this path, this method does not care about that, and the result will be a self-intersection at that point.

Reimplemented in kjb::qd::PixPathAc.

PixPath & kjb::qd::PixPath::append ( const PixPath suffix)
virtual

Append the given path to the tail of this path; overwrites.

Parameters
suffixa path to become the new suffix of this path.
Returns
A reference to this path (after the modification of course)
Warning
This is not a const method; it modifies the path!

Be aware that if the first point of suffix is the same as the last point in the path, this method does not care about that, and the result will be a self-intersection at that point.

Reimplemented in kjb::qd::PixPathAc.

int kjb::qd::PixPath::append_no_overlap ( const PixPath suffix)
virtual

Like append(), with extra avoid-the-duplicates fuss; overwrites.

Parameters
suffixa path to become the new suffix of this path, except for the first point of suffix
Precondition
First point of suffix must be equal to the last point of this path.
Returns
kjb_c::NO_ERROR, or kjb_c::ERROR if the precondition is not met.
Warning
This is not a const method; it modifies the path!

Use this when you KNOW the first point of suffix is the same as the last point of this path, and you want to drop one of them before appending. We test the preconditon, then skip suffix[0] and append suffix[1] and all the rest of the suffix points; then we return NO_ERROR.

This is pretty useful when trying to "transitively" join a path from a to b, then a path from b to c, and you don't want 'b' in there twice.

Reimplemented in kjb::qd::PixPathAc.

int kjb::qd::PixPath::arclength ( std::vector< float > *  alvec) const
virtual

Return a vector of arclengths along the polygonal path.

Precondition
Input pointer must not equal null.
Returns
kjb_c::NO_ERROR, or ERROR if the input equals null

The pointer input (which must be non-null) points to a vector of output such that the i-th point in the PixPath sequence has an arclength from the first point as the i-th entry of the vector. Arclength is defined as the length of the polygonal path defined by the PixPath points.

Reimplemented in kjb::qd::PixPathAc.

float kjb::qd::PixPath::arclength ( ) const
virtual

compute and return polygonal path length of this path

Returns
the length of the polygonal path defined by the points.
Exceptions
kjb::KJB_errorif the arclength computation fails.

This method takes linear time in the number of points of the path. So if you need to access it a lot, consider using PixPathAc, which memoizes the answer.

Reimplemented in kjb::qd::PixPathAc.

virtual PixPath& kjb::qd::PixPath::assign ( unsigned  index,
const PixPoint newp 
)
inlinevirtual

Change a member of the path to a new PixPoint.

Parameters
indexIndex of point (aka vertex) in this path to be clobbered
newpNew location to store at the given index
Precondition
path must have size() exceeding given param 'index' value.
Returns
reference to this object after the modification

I cannot do this by overloading operator[] because I don't know how to make that approach update the map or the set.

Reimplemented in kjb::qd::PixPathAc.

PixPoint kjb::qd::PixPath::back ( ) const
inline

Return (by value) the last point in the path.

const_iterator kjb::qd::PixPath::begin ( ) const
inline

Return an iterator pointing to the first point in the path.

PixPoint kjb::qd::PixPath::boundbox_max_max ( ) const
virtual

Similar to boundbox_min_min() but returns maximum x, y coords.

PixPoint kjb::qd::PixPath::boundbox_min_min ( ) const
virtual

Return the corner point of a bounding box, with min x and y.

To restate that brief description more coherently, this method computes (in LINEAR TIME!) the smallest axis-aligned bounding box around the path and returns the coordinates of the corner point having the minimum x coordinate and minimum y coordinate.

Returns
PixPoint with x value = min{ x | (x,y) is in this path}, y similar. Note this point is not necessarily in the path.
Exceptions
Too_smallif the path contains no points
Warning
This method takes linear time and does not memoize its results.

The method is virtual because someone someday might want memoization.

bool kjb::qd::PixPath::boundbox_within_boundbox ( const PixPath outer) const
inline

Test whether the axis-aligned bounding box of this path lies within the axis-aligned bounding box of the given path.

Parameters
outerpath to be tested as the candidate container of this path
Returns
true iff this path lies within a.a. bounding-box of 'outer' path

"Within" here is liberally defined to mean in the interior OR on the boundary.

PixPoint::Integer kjb::qd::PixPath::bracket_cross_at ( unsigned  index) const
inline

cross product of line segs (index-1,index+1) x (index,index+1)

Parameters
indexmust be the array index of an interior point

Let point q be the point on this path with the given index. Let points p, r be the predecessor and successor points.

This algorithm is drawn from Cormen, Leiserson, Rivest, Stein chap 33 section 1.

Returns
Zero if points p, q, r are collinear;
Positive if path from p to q to r is counterclockwise;
Negative if path from p to q to r is clockwise.
Abs. value of result is twice the area of the triangle p-q-r.
kjb::Vector2 kjb::qd::PixPath::centroid ( ) const

return the "mean" of the points (nonempty, none may be unused)

virtual void kjb::qd::PixPath::clear ( )
inlinevirtual

Discard all points.

Reimplemented in kjb::qd::PixPathAc.

float kjb::qd::PixPath::closest_adjacent_pair ( const_iterator pa = 0) const

Find a pair of adjacent points at least as close as any other.

Parameters
[out]paOptional output parameter: iterator pointing to one of the pair, such that *pa and *(pa+1) have minimum distance.
Returns
distance between an adjacent pair of points with min. distance
Warning
Keep in mind that the result is unlikely to be unique! Since the coordinates are integers, it is VERY COMMON for multiple pairs of adjacent points to have the same distance; thus it is a bad idea to think of the result as THE closest adjacent pair.
float kjb::qd::PixPath::closest_pair ( const_iterator pa = 0,
const_iterator pb = 0 
) const

Find the distance between closest pair of points, ignoring adjacency.

Returns
distance between closest pair of points in the set of points
Parameters
[out]paIf this and pa are set to a non-null location, then at exit *pa will hold an iterator pointing to one of the pair of closest points.
[out]pbLike pa, but if assigned, *pb will hold an iterator pointing to the other point in the closest pair.
Exceptions
Too_smallif the object does not contain a pair of points (or if all, or all but one, are unused).
See Also
PixPoint::is_unused()
PixPath::closest_adjacent_pair()

This returns the distance between the closest pair of distinct entries in the object, provided that the object contains at least two entries that are not unused. "Distinct entries" means that the entries must be stored in distinct indices of this object (indices differ by at least one); it does not mean that the values differ. Thus, zero is a valid return value, and it occurs when the object contains two copies of the same point.

If pa is omitted or pb is omitted, or either equals null, then neither will be assigned.

This algorithm takes time O(N log N) where N is the object size. It is described in Chapter 15 of Goodrich & Tamassia's book, Data Structures and Algorithms in Java, John Wiley, 1998.

Note again this method ignores adjacency (i.e., where in the list the two points are contained). If you want to constrain the answer so that the two points are successive entries in the list, use PixPath::closest_adjacent_pair().

bool kjb::qd::PixPath::connected8 ( bool  emit_verbose_debug_output = false) const

Is the path all connected, using 8-connectivity?

int kjb::qd::PixPath::cubic_fit ( std::vector< float > *  x,
std::vector< float > *  y,
int  ref = 0 
) const

Fit a cubic polynomial to the given list of x and y points.

Parameters
[out]xWhere to put the coefficients for the x coordinate fit
[out]yWhere to put the coefficients for the y coordinate fit
[in]refIndex value of the point in the path that you want to be the "reference" point corresponding to t=0.

This fits a cubic polynomial to the vector of points. The coeffients are returned in x and y (which must both be non-nil). The polynomial fit for the x points is thus cubic_x(t) = (*x)[0] + t (*x)[1] + t^2 (*x)[2] + t*3 (*x)[3]. Similarly for y. The values of t are the Euclidean distances along the polygonal path, i.e., the shortest possible distances between points. Note that the interpolation will not in general fall exactly on these points The point corresponding to t = 0 is by default the first point, but it may be shifted to any of the points in the PixPath by providing the index in parameter ref (which must be valid or ERROR is returned). The fit is effected using the Moore-Penrose pseudoinverse, which assumes Gaussian errors. This can spell trouble if your errors are not Gaussian.

Returns
kjb_c::ERROR or NO_ERROR indicating outcome of computation.
Exceptions
Too_smallif this path has fewer than 4 points (which means the system is underdetermined).
PixPath kjb::qd::PixPath::cull_redundant_points ( ) const

Remove "unnecessary" points (ones that can be bresenham interpolated)

Returns
path possibly reduced from this one
See Also
merge_redundant_slopes.
reduce_pixels_pv

This function sort of does the opposite of interpolate. It returns a path p such that interpolate() and p.interpolate() should return equivalent paths. Note that the input is not necessarily 8-connected. This method does not always reduce the number of points to the minimum. Sometimes the function reduce_pixels_pv() works better, other times worse.

The criterion for "redundant" is limited to PixPoint resolution; if you render (or do whatever) with resolution finer than a PixPoint, the output will not be equivalent to the input. For example, PixPoint(0,0) PixPoint(1,1) PixPoint(2,1) PixPoint(3,2) would under certain circumstances be culled to its endpoints, but if your use exploits the fact of the slope of +1 between the first and last pair, the results of this function will be unsatisfactory (having culled too much). For a more conservative culling algorithm, try merge_redundant_slopes().

Bresenham result keeps pixels 0, 3 (discards 1, 2).
If all you can show are blocky pixels, then it is fine to drop 1 and 2.
.........###....
.........#3#....
.........###....
...######.......
...#1##2#.......
...######.......
###.............
#0#.............
###.............
If you have sub-PixPoint resolution you will not get an equivalent path.
If you drop pixels 1, 2 then connecting 0 to 3 won't look the same.
................
..........3.....
........./......
......../.......
....1--2........
.../............
../.............
.0..............
................
bool kjb::qd::PixPath::do_segments_intersect ( unsigned *  s1 = 00,
unsigned *  s2 = 00,
unsigned *  count = 00 
) const

quadratic time test whether any 2 path segments cross each other

Parameters
[out]s1Optional; if it and s2 are not equal to null, at exit it points to a location containing the index of the first point of the first segment in the intersection. If omitted or left equal to null, it and s2 are ignored.
[out]s2Optional pointer to index of second segment in intersection. Can be omitted or set equal to null.
[out]countOptional pointer to number of intersections. This can affect running time: if omitted or set equal to null, this method returns on the first intersection it finds. Otherwise, the method counts all intersections.
Returns
true iff two polygonal-path segments intersect.

If you omit or set to null either of s1 or s2, then both are regarded as omitted.

Segments are defined between each pair of adjacent points. Of course in every polygonal path of 3 or more vertices, adjacent segments share a common "hinge" endpoint – but those commonalities do not count. Yet if there is a zero-length segment in the path, then this returns true.

True examples:

  • (0,0) (2,2) (1,3) (1,0)
    • output *s1=0, *s2=2
  • (0,0) (2,2) (1,3) (0,0)
    • output *s1=0, *s2=2
  • (0,0) (2,2) (1,3) (1,1)
    • output *s1=0, *s2=2
  • (0,0) (2,2) (0,0)
    • output *s1=0, *s2=1
  • (0,0) (2,2) (2,2)
    • output *s1=0, *s2=1

False examples:

  • (0,0) (1,1) (2,2)
  • (0,0) (2,2) (1,3)
  • (0,0) (2,2)

If there are multiple intersections and s1 and s2 do not equal null, we do not define which intersection s1, s2 will indicate. The current implementation might still behave like so: if count equals null, s1 and s2 might indicate intersecting segments closest to begin(), and if count does not equal null, s1 and s2 might indicate intersecting segments closest to end(). This description could be stale, however.

const_iterator kjb::qd::PixPath::end ( ) const
inline

Rtn iterator pointing to just beyond the last point in the path.

PixPath kjb::qd::PixPath::expel ( const_iterator  bad_point) const

Duplicate this path, except that we omit the indicated point.

PixPath kjb::qd::PixPath::expel ( size_t  index_of_unwanted_point) const
inline

Duplicate this path, except we omit the point with given index.

static PixPath kjb::qd::PixPath::fenceposts ( const PixPoint pt_a,
const PixPoint pt_b,
size_t  sz 
)
inlinestatic

named ctor makes a path of evenly spaced points from a to b

Parameters
pt_afirst point of path
pt_blast point of path provided sz is at least 2
sznumber of points in output
Returns
path from a to b

If sz >= 2 then the output will have its first point at 'a' and its last point at 'b', and its size() property will equal sz. The other points will lie more-or-less on the line segment ab, between them, and increasing in distance from point 'a'.

Due to rounding, the points might not lie exactly on this line segment. And if sz is large enough, there can be duplication of points. If sz < 2 I am not going to bother to define the results.

PixPoint kjb::qd::PixPath::front ( ) const
inline

Return (by value) the first point in the path.

bool kjb::qd::PixPath::has_subsequence ( const PixPath subseq) const

tests whether a given path is a subsequence of this path

virtual float kjb::qd::PixPath::hausdorff_distance ( const PixPath that) const
inlinevirtual

Compute the Hausdorff metric between two sets of points.

Parameters
thatother points to compute this one's Hausdorff distance from.
Returns
Hausdorff distance, in units of linear pixel length

This method does not interpolate for you! If you want to compare the gap between polygonal paths, it's up to you to interpolate the input.

double kjb::qd::PixPath::heading_shift_at ( unsigned  index) const
inline

compute shift in "heading" angle at an interior point

This calls method angle_at(), which can throw!

Parameters
indexIndex of INTERIOR point: must be positive and less than (size()-1).
Returns
angle in radians of "shift" away from going in a straight line.

Example: path (0,0) (1,1) (2,2) (2,3) (3,4) has the following shifts:

  • Undefined for index 0 and index 4 (throws std::out_of_range)
  • Index 1: heading shift 0
  • Index 2: heading shift M_PI_4
  • Index 3: heading shift -M_PI_4
unsigned kjb::qd::PixPath::hits ( const PixPoint query_point) const
inline

Return number of times (maybe 0) the path hits a query point.

Parameters
query_pointpoint possibly in list of points (aka path vertices)
Returns
true iff query_point is a point in the list (aka a path vertex)

This is just a lookup of your query point in the map: it does NOT do any sort of interpolation between points. For example, if the path is (0,0) (2,0) then hits(PixPoint(1,0)) returns zero!

This takes O( log N ) time for an N point path. Not O(N). It would be even faster if we used spatial hashing, but we don't; With Boost's help, we could.

unsigned kjb::qd::PixPath::hits ( const PixPath query_path) const

return number of common points between query_path and this path

PixPath kjb::qd::PixPath::interpolate ( bool  forbid_self_intersection = false) const

This interpolates pixels between unconnected pixels.

This function uses the Bresenham line drawing algorithm to "draw" intermediate pixels between non-adjacent pixels on the path. The result is a PixPath that is potentially much longer. Points are only added, not removed, not even duplicates.

Parameters
forbid_self_intersectionOptional. If omitted or false, the output will be fully 8-connected. If true, the output path will not contain self-intersections. The output will contain all the points of this path, in the same sequence, and as many interpolated pixels as possible to make it 8-connected, except when that would cause self-intersection. I think it best not to document the sequential order of omitted would-be interpolated pixels – it might change. This method will run a wee bit slower if the parameter is true.
Exceptions
Self_intersectioniff parameter forbid_self_intersection is true and this path already has a self-intersection.
Returns
a new path hitting points "between" original points.
Postcondition
This path will always be a subsequence of the output.
bool kjb::qd::PixPath::intersect_at_with ( unsigned  preindex1,
unsigned  preindex2,
unsigned *  endpoint_intersector_index = 00 
) const

Test whether two segments of this path intersect.

Parameters
preindex1Defines first segment, from preindex1 to 1+preindex1.
preindex2Defines second segment, from preindex2 to 1+preindex2.
[out]endpoint_intersector_indexOptional output parameter revealing a vertex (if any) that coincides . If omitted or null, or if the return value is false, this parameter is ignored. If no path vertex coincides with an interior point of a segment, this parameter is also ignored. Otherwise, on exit this points a location holding the value of the index of the vertex that lands on the other segment.
Precondition
preindex1 and preindex2 must be smaller than size()-1.
Returns
true if the "closed" segments have a nonempty intersection

Segments are "closed" – they include their terminal vertices as well as their interior points.

In order to use the output parameter effectively, it must be set to a value of size() or greater. Then a change in this value can be detected.

There is no obligation to make preindex1 and preindex2 distinct. If they differ by zero or one then this routine is sure to return true.

Algorithm is from Cormen, Leiserson, Rivest, Stein sec. 33.1.

static PixPath kjb::qd::PixPath::load ( const std::string &  filename)
inlinestatic

named ctor loads path coordinates from an ASCII file

unsigned kjb::qd::PixPath::longest_segment ( float *  length = 00) const

return index of first point of longest segment

This performs a linear-time search through the pixpath for a longest segment. Not necessarily unique.

Parameters
[out]lengthoptional output parameter if you would like to know the length of the longest segments.
Returns
index value j where path[j] and path[1+j] define a segment at least as long as any other segment.
Exceptions
Too_smallif the path has fewer than 2 points.
unsigned kjb::qd::PixPath::member_near_centroid ( ) const

return index of a member point nearest centroid() (qv)

PixPath kjb::qd::PixPath::merge_redundant_slopes ( ) const

return a path with interior points removed but preserve all slopes.

See Also
cull_redundant_points
Returns
a path no larger and with unchanged line segment slopes.

We view the path as a polygonal path. If interior point ppp has predecessor nnn and successor qqq. If ppp lies exactly on the closed line segment (nnn,qqq) then ppp is regarded as redundant and can be removed. This method returns a path will all such entries removed. It is suitable for subpixel rendering schemes.

In many circumstances, this method is too conservative, and the potential user really wants to cull more aggressively such as provided by cull_redundant_points(). If your rendering of PixPoints only has pixel resolution, you probably want that method.

Implementation notes:

Throw out *q_present if vecs dpresent, dfuture have same direction. "Same direction" means collinear and arrows pointing the same way. "Collinear" means zero cross product of dpresent, dfuture. "Arrows pointing same way" means dpresent, dfuture have non-negative dot product (i.e., positive or zero). Cross and dot products are zero if *q_present equals *q_past or *q_present. Thus we KEEP *q_present if the cross product is nonzero or if dot product is negative. This is confusing, isn't it? Let's summarize.

Case 1 Case 2 Case 3
Cross-product of dpresent, dfuture nonzerozerozero
Dot-product of dpresent, dfuture (any)negativezero or positive
Action on *q_present keepkeepdrop
Conclusion about *q_present It is the elbow of an ordinary bend. Dot product truly could be pos, neg, or zero. It is not between *q_past, *q_future, despite being collinear. In fact *q_future must lie on the line segment *q_past, *q_present, although worrying about that fact is someone else's job, not this method's. *q_present is redundant.
PixPoint kjb::qd::PixPath::nearest ( const PixPoint pp,
const_iterator qq = 00 
) const

Return the PixPath member closest to a given query point.

Parameters
[in]ppQuery point
[out]qq(Optional) pointer to const_iterator that, if non-nil on input, is set to indicate the nearest member of PixPath to pp. If more than one member hits pp, this returns the iterator pointing to the member nearest the front of the list.
Returns
a PixPoint member of the PixPath such that no other member of the PixPath is closer to the query point pp.
Precondition
The PixPath must contain at least one point

If the PixPath is empty, the results are formally undefined, but informally consist of an error message and an unused PixPoint. This simple implementation performs a linear scan, which means it is slow. A fancy implementation could maybe be much faster.

Todo:
(a repeat of an earlier point) replace map with multimap so we could find indices of hitpoints in log time.
PixPoint kjb::qd::PixPath::nearest ( const kjb::Vector2 xy,
const_iterator q = 00 
) const

Return the PixPath member closest to the given real xy coordinates.

bool kjb::qd::PixPath::operator!= ( const PixPath other) const
inline

Compare PixPaths; true iff the sequence of points differs.

PixPath kjb::qd::PixPath::operator+ ( const PixPath other) const

Add, element-by-element, two PixPaths of the same length.

Returns
new PixPath defined by elementwise sum of this and other path.

In other words, it's something like this:

for i = 0 to N-1
output[i] = this[i] + that[i]
endfor
Exceptions
Size_mismatchif the paths are not of the same length
bool kjb::qd::PixPath::operator== ( const PixPath other) const

Compare PixPaths; true iff the sequence of points is identical.

const PixPoint& kjb::qd::PixPath::operator[] ( unsigned  index) const
inline

Vector-like access to points (returning an rvalue).

PixPath & kjb::qd::PixPath::ow_reverse ( )

Reverse the order of the points in the path (overwriting!)

Returns
reference to this path after the operation.

The implementation relies on the fact that ppmap contents remain the same whether the points are in forward or reverse order. If we ever do a mulimap, quadtree, or kdtree implementation, this will have to get more complicated.

static PixPath kjb::qd::PixPath::parse ( const std::string &  coords)
inlinestatic

named ctor builds from string-format list of coordinates

void kjb::qd::PixPath::pop_back ( )
virtual
PixPath kjb::qd::PixPath::prefix ( unsigned  postlast) const
inline

Variation on subrange: return a prefix of this path (by value)

virtual void kjb::qd::PixPath::push_back ( const PixPoint pp)
inlinevirtual

Vector-like appending a new PixPoint on the end of the object.

Reimplemented in kjb::qd::PixPathAc.

const_reverse_iterator kjb::qd::PixPath::rbegin ( ) const
inline

return a reverse-moving iterator to the last point in path

const_reverse_iterator kjb::qd::PixPath::rend ( ) const
inline

return one-before-first iterator, in symmetry with end().

static PixPath kjb::qd::PixPath::reserve ( size_t  potential_size = 0)
inlinestatic

named ctor creates an empty path but reserves some memory for it

PixPath kjb::qd::PixPath::reverse ( ) const
inline

return a copy of this list after reversing the front-back order.

int kjb::qd::PixPath::save ( const std::string &  filename) const

Saves the path to an ASCII file, returns NO_ERROR or ERROR.

bool kjb::qd::PixPath::self_intersect ( PixPoint where = 00) const

Reveal whether the path has any self-intersections.

Parameters
[out]whereOptional parameter indicating coordinates of hit
Returns
true iff list of points contains a duplicate list entry.

Unfortunately our data structures do not currently tell us quickly what the indices of the colliding points are.

This tests whether the path has duplicate points, not whether segments between the points intersect. For the latter, try do_segments_intersect().

size_t kjb::qd::PixPath::size ( ) const
inline

Number of points in the path (not the same as its length).

bool kjb::qd::PixPath::so_boundbox_holds_within ( const PixPoint q) const
inline

sort of like boundbox_within_boundbox but works on a single point

Parameters
qquery point to test for membership in semi-open rectang. region
Returns
true if query point q is a member of semi-open region

This was hard to name. The final choice is meant to be scanned to mean a path p has some semi-open axis-aligned minimal bounding box, and that box holds q within itself (or else the return value is false).

The bounding box is semi-open in that points on the min-x and min-y boundaries are (potentially) "in" but points on the max-x and max-y boundaries are definitely not.

std::string kjb::qd::PixPath::str ( const std::string &  separator = "\n") const

Return the path as an ASCII string.

PixPath kjb::qd::PixPath::subrange ( unsigned  index_first,
unsigned  index_postlast 
) const

Returns a subrange of this PixPath object.

Parameters
index_firstWe omit all points with indices less than this value.
index_postlastWe omit all points with indices greater than or equal to this value.
Returns
return path equivalent to a subrange of this path.

The indices are indicated from the first index (zero-based of course) to one-past-the-last desired index.

The above description is phrased to imply that this routine does not ever fail: it is equivalent if index_postlast equals size() or size() + 1000. If index_first is huge, or if index_postlast <= index_first, the result is simply an empty PixPath.

Warning
Notice that the second argument is NOT the length! If you want a subrange of path P starting at index i and of length L, you can compute it like so:
P.subrange( i, i + L );
PixPath kjb::qd::PixPath::suffix ( unsigned  first) const
inline

Variation on subrange: return a suffix of this path (by value)

Parameters
firstIndex of the element of the path that you want to be the first element of the suffix.
Returns
path equivalent to the suffix of this path
Warning
The parameter is NOT the length of the suffix. If you want a suffix of path P with length L, then you should invoke like so:
P.suffix( P.size() - L )
virtual void kjb::qd::PixPath::swap ( PixPath vpp)
inlinevirtual

Swap the contents of two PixPaths.


The documentation for this class was generated from the following files: