The GNU Implementation of FlatteningPathIterator

Sascha Brawer, November 2003

This document describes the GNU implementation of the class java.awt.geom.FlatteningPathIterator. It does not describe how a programmer should use this class; please refer to the generated API documentation for this purpose. Instead, it is intended for maintenance programmers who want to understand the implementation, for example because they want to extend the class or fix a bug.

Data Structures

The algorithm uses a stack. Its allocation is delayed to the time when the source path iterator actually returns the first curved segment (either SEG_QUADTO or SEG_CUBICTO). If the input path does not contain any curved segments, the value of the stack variable stays null. In this quite common case, the memory consumption is minimal.

stack
The variable stack is a double array that holds the start, control and end points of individual sub-segments.
recLevel
The variable recLevel holds how many recursive sub-divisions were needed to calculate a segment. The original curve has recursion level 0. For each sub-division, the corresponding recursion level is increased by one.
stackSize
Finally, the variable stackSize indicates how many sub-segments are stored on the stack.

Algorithm

The implementation separately processes each segment that the base iterator returns.

In the case of SEG_CLOSE, SEG_MOVETO and SEG_LINETO segments, the implementation simply hands the segment to the consumer, without actually doing anything.

Any SEG_QUADTO and SEG_CUBICTO segments need to be flattened. Flattening is performed with a fixed-sized stack, holding the coordinates of subdivided segments. When the base iterator returns a SEG_QUADTO and SEG_CUBICTO segments, it is recursively flattened as follows:

  1. Intialization: Allocate memory for the stack (unless a sufficiently large stack has been allocated previously). Push the original quadratic or cubic curve onto the stack. Mark that segment as having a recLevel of zero.
  2. If the stack is empty, flattening the segment is complete, and the next segment is fetched from the base iterator.
  3. If the stack is not empty, pop a curve segment from the stack.

The implementation is slightly complicated by the fact that consumers pull the flattened segments from the FlatteningPathIterator. This means that we actually cannot “hand the curent segment over to the consumer.” But the algorithm is easier to understand if one assumes a push paradigm.

Example

The following example shows how a FlatteningPathIterator processes a SEG_QUADTO segment. It is (arbitrarily) assumed that the recursion limit was set to 2.

ABC DEFGH
stack[0] Sll.x
stack[1] Sll.y
stack[2] Cll.x
stack[3] Cll.y
stack[4] Sl.x Ell.x = Slr.x Slr.x Srl.x
stack[5] Sl.y Ell.x = Slr.y Slr.y Srl.y
stack[6] Cl.x Clr.x Clr.x Crl.x
stack[7] Cl.y Clr.y Clr.y Crl.y
stack[8] S.x El.x = Sr.x Elr.x = Sr.x Elr.x = Sr.x Sr.x Erl.x = Srr.x Srr.x
stack[9] S.y El.y = Sr.y Elr.y = Sr.y Elr.y = Sr.y Sr.y Erl.y = Srr.y Srr.y
stack[10] C.x Cr.x Cr.x Cr.x Cr.x Crr.x Crr.x
stack[11] C.y Cr.y Cr.y Cr.y Cr.y Crr.y Crr.y
stack[12] E.x Er.x Er.x Er.x Er.x Err.x Err.x
stack[13] E.y Er.y Er.y Er.y Er.y Err.y Err.x
stackSize 1 2 3 2 1 2 1 0
recLevel[2] 2
recLevel[1] 1 2 2 2
recLevel[0] 0 1 1 1 1 2 2
  1. The data structures are initialized as follows. Column A shows the state after the initialization step.
  2. The algorithm proceeds by taking the topmost curve segment (SCE) from the stack. Column B shows the state after the first iteration.
  3. Again, the topmost curve segment (SlClEl) is taken from the stack. Column C shows the state after the second iteration.
  4. The topmost curve segment (SllCllEll) is popped from the stack. The new state is shown in column D.
  5. The topmost curve segment (SlrClrElr) is popped from the stack. The new state is shown in column E.
  6. The algorithm proceeds by taking the topmost curve segment (SrCrEr) from the stack. The new state is shown in column F.
  7. The topmost curve segment (SrlCrlErl) is popped from the stack. The new state is shown in column G.
  8. The topmost curve segment (SrrCrrErr) is popped from the stack. The new state is shown in column H.
  9. The stack is now empty. The FlatteningPathIterator will fetch the next segment from the base iterator, and process it.

In order to split the most recently pushed segment, the subdivideQuadratic() method passes stack directly to QuadCurve2D.subdivide(double[],int,double[],int,double[],int). Because the stack grows towards the beginning of the array, no data needs to be copied around: subdivide will directly store the result into the stack, which will have the contents shown to the right.