From: Sascha Brawer Date: Wed, 19 Nov 2003 12:02:11 +0000 (+0100) Subject: FlatteningPathIterator.java: Entirely re-written. X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=b6b8f690470abd887a1f4de734548edc510f9290;p=gcc.git FlatteningPathIterator.java: Entirely re-written. 2003-11-19 Sascha Brawer * java/awt/geom/FlatteningPathIterator.java: Entirely re-written. * java/awt/geom/doc-files/FlatteningPathIterator-1.html: Describe how the implementation works. From-SVN: r73734 --- diff --git a/libjava/ChangeLog b/libjava/ChangeLog index d58d761ff8f..ef79f94e5b6 100644 --- a/libjava/ChangeLog +++ b/libjava/ChangeLog @@ -1,3 +1,9 @@ +2003-11-19 Sascha Brawer + + * java/awt/geom/FlatteningPathIterator.java: Entirely re-written. + * java/awt/geom/doc-files/FlatteningPathIterator-1.html: + Describe how the implementation works. + 2003-11-19 Michael Koch * java/net/Socket.java diff --git a/libjava/java/awt/geom/FlatteningPathIterator.java b/libjava/java/awt/geom/FlatteningPathIterator.java index a7a57ef6fed..94ff145621b 100644 --- a/libjava/java/awt/geom/FlatteningPathIterator.java +++ b/libjava/java/awt/geom/FlatteningPathIterator.java @@ -1,5 +1,5 @@ -/* FlatteningPathIterator.java -- performs interpolation of curved paths - Copyright (C) 2002 Free Software Foundation +/* FlatteningPathIterator.java -- Approximates curves by straight lines + Copyright (C) 2003 Free Software Foundation This file is part of GNU Classpath. @@ -38,68 +38,542 @@ exception statement from your version. */ package java.awt.geom; +import java.util.NoSuchElementException; + + /** - * This class can be used to perform the flattening required by the Shape - * interface. It interpolates a curved path segment into a sequence of flat - * ones within a certain flatness, up to a recursion limit. + * A PathIterator for approximating curved path segments by sequences + * of straight lines. Instances of this class will only return + * segments of type {@link PathIterator#SEG_MOVETO}, {@link + * PathIterator#SEG_LINETO}, and {@link PathIterator#SEG_CLOSE}. + * + *

The accuracy of the approximation is determined by two + * parameters: + * + *

  • The flatness is a threshold value for deciding when + * a curved segment is consided flat enough for being approximated by + * a single straight line. Flatness is defined as the maximal distance + * of a curve control point to the straight line that connects the + * curve start and end. A lower flatness threshold means a closer + * approximation. See {@link QuadCurve2D#getFlatness()} and {@link + * CubicCurve2D#getFlatness()} for drawings which illustrate the + * meaning of flatness.
  • + * + *
  • The recursion limit imposes an upper bound for how often + * a curved segment gets subdivided. A limit of n means that + * for each individual quadratic and cubic Bézier spline + * segment, at most 2n {@link + * PathIterator#SEG_LINETO} segments will be created.
+ * + *

Memory Efficiency: The memory consumption grows linearly + * with the recursion limit. Neither the flatness parameter nor + * the number of segments in the flattened path will affect the memory + * consumption. + * + *

Thread Safety: Multiple threads can safely work on + * separate instances of this class. However, multiple threads should + * not concurrently access the same instance, as no synchronization is + * performed. + * + * @see Implementation Note + * + * @author Sascha Brawer (brawer@dandelis.ch) * - * @author Eric Blake - * @see Shape - * @see RectangularShape#getPathIterator(AffineTransform, double) * @since 1.2 - * @status STUBS ONLY */ -public class FlatteningPathIterator implements PathIterator +public class FlatteningPathIterator + implements PathIterator { - // The iterator we are applied to. - private PathIterator subIterator; - private double flatness; - private int limit; + /** + * The PathIterator whose curved segments are being approximated. + */ + private final PathIterator srcIter; + + + /** + * The square of the flatness threshold value, which determines when + * a curve segment is considered flat enough that no further + * subdivision is needed. + * + *

Calculating flatness actually produces the squared flatness + * value. To avoid the relatively expensive calculation of a square + * root for each curve segment, we perform all flatness comparisons + * on squared values. + * + * @see QuadCurve2D#getFlatnessSq() + * @see CubicCurve2D#getFlatnessSq() + */ + private final double flatnessSq; + + + /** + * The maximal number of subdivions that are performed to + * approximate a quadratic or cubic curve segment. + */ + private final int recursionLimit; + + + /** + * A stack for holding the coordinates of subdivided segments. + * + * @see Implementation Note + */ + private double[] stack; + + + /** + * The current stack size. + * + * @see Implementation Note + */ + private int stackSize; + + + /** + * The number of recursions that were performed to arrive at + * a segment on the stack. + * + * @see Implementation Note + */ + private int[] recLevel; + + + + private final double[] scratch = new double[6]; + + + /** + * The segment type of the last segment that was returned by + * the source iterator. + */ + private int srcSegType; + + /** + * The current x position of the source iterator. + */ + private double srcPosX; + + + /** + * The current y position of the source iterator. + */ + private double srcPosY; + + + /** + * A flag that indicates when this path iterator has finished its + * iteration over path segments. + */ + private boolean done; + + + /** + * Constructs a new PathIterator for approximating an input + * PathIterator with straight lines. The approximation works by + * recursive subdivisons, until the specified flatness threshold is + * not exceeded. + * + *

There will not be more than 10 nested recursion steps, which + * means that a single SEG_QUADTO or + * SEG_CUBICTO segment is approximated by at most + * 210 = 1024 straight lines. + */ public FlatteningPathIterator(PathIterator src, double flatness) { this(src, flatness, 10); } - public FlatteningPathIterator(PathIterator src, double flatness, int limit) + + + /** + * Constructs a new PathIterator for approximating an input + * PathIterator with straight lines. The approximation works by + * recursive subdivisons, until the specified flatness threshold is + * not exceeded. Additionally, the number of recursions is also + * bound by the specified recursion limit. + */ + public FlatteningPathIterator(PathIterator src, double flatness, + int limit) { - subIterator = src; - this.flatness = flatness; - this.limit = limit; if (flatness < 0 || limit < 0) throw new IllegalArgumentException(); + + srcIter = src; + flatnessSq = flatness * flatness; + recursionLimit = limit; + fetchSegment(); } + + /** + * Returns the maximally acceptable flatness. + * + * @see QuadCurve2D#getFlatness() + * @see CubicCurve2D#getFlatness() + */ public double getFlatness() { - return flatness; + return Math.sqrt(flatnessSq); } + + /** + * Returns the maximum number of recursive curve subdivisions. + */ public int getRecursionLimit() { - return limit; + return recursionLimit; } + + // Documentation will be copied from PathIterator. public int getWindingRule() { - return subIterator.getWindingRule(); + return srcIter.getWindingRule(); } + + // Documentation will be copied from PathIterator. public boolean isDone() { - return subIterator.isDone(); + return done; } + + // Documentation will be copied from PathIterator. public void next() { - throw new Error("not implemented"); + if (stackSize > 0) + { + --stackSize; + if (stackSize > 0) + { + switch (srcSegType) + { + case PathIterator.SEG_QUADTO: + subdivideQuadratic(); + return; + + case PathIterator.SEG_CUBICTO: + subdivideCubic(); + return; + + default: + throw new IllegalStateException(); + } + } + } + + srcIter.next(); + fetchSegment(); } + + // Documentation will be copied from PathIterator. public int currentSegment(double[] coords) { - throw new Error("not implemented"); + if (done) + throw new NoSuchElementException(); + + switch (srcSegType) + { + case PathIterator.SEG_CLOSE: + return srcSegType; + + case PathIterator.SEG_MOVETO: + case PathIterator.SEG_LINETO: + coords[0] = srcPosX; + coords[1] = srcPosY; + return srcSegType; + + case PathIterator.SEG_QUADTO: + if (stackSize == 0) + { + coords[0] = srcPosX; + coords[1] = srcPosY; + } + else + { + int sp = stack.length - 4 * stackSize; + coords[0] = stack[sp + 2]; + coords[1] = stack[sp + 3]; + } + return PathIterator.SEG_LINETO; + + case PathIterator.SEG_CUBICTO: + if (stackSize == 0) + { + coords[0] = srcPosX; + coords[1] = srcPosY; + } + else + { + int sp = stack.length - 6 * stackSize; + coords[0] = stack[sp + 4]; + coords[1] = stack[sp + 5]; + } + return PathIterator.SEG_LINETO; + } + + throw new IllegalStateException(); } + + + // Documentation will be copied from PathIterator. public int currentSegment(float[] coords) { - throw new Error("not implemented"); + if (done) + throw new NoSuchElementException(); + + switch (srcSegType) + { + case PathIterator.SEG_CLOSE: + return srcSegType; + + case PathIterator.SEG_MOVETO: + case PathIterator.SEG_LINETO: + coords[0] = (float) srcPosX; + coords[1] = (float) srcPosY; + return srcSegType; + + case PathIterator.SEG_QUADTO: + if (stackSize == 0) + { + coords[0] = (float) srcPosX; + coords[1] = (float) srcPosY; + } + else + { + int sp = stack.length - 4 * stackSize; + coords[0] = (float) stack[sp + 2]; + coords[1] = (float) stack[sp + 3]; + } + return PathIterator.SEG_LINETO; + + case PathIterator.SEG_CUBICTO: + if (stackSize == 0) + { + coords[0] = (float) srcPosX; + coords[1] = (float) srcPosY; + } + else + { + int sp = stack.length - 6 * stackSize; + coords[0] = (float) stack[sp + 4]; + coords[1] = (float) stack[sp + 5]; + } + return PathIterator.SEG_LINETO; + } + + throw new IllegalStateException(); + } + + + /** + * Fetches the next segment from the source iterator. + */ + private void fetchSegment() + { + int sp; + + if (srcIter.isDone()) + { + done = true; + return; + } + + srcSegType = srcIter.currentSegment(scratch); + + switch (srcSegType) + { + case PathIterator.SEG_CLOSE: + return; + + case PathIterator.SEG_MOVETO: + case PathIterator.SEG_LINETO: + srcPosX = scratch[0]; + srcPosY = scratch[1]; + return; + + case PathIterator.SEG_QUADTO: + if (recursionLimit == 0) + { + srcPosX = scratch[2]; + srcPosY = scratch[3]; + stackSize = 0; + return; + } + sp = 4 * recursionLimit; + stackSize = 1; + if (stack == null) + { + stack = new double[sp + /* 4 + 2 */ 6]; + recLevel = new int[recursionLimit + 1]; + } + recLevel[0] = 0; + stack[sp] = srcPosX; // P1.x + stack[sp + 1] = srcPosY; // P1.y + stack[sp + 2] = scratch[0]; // C.x + stack[sp + 3] = scratch[1]; // C.y + srcPosX = stack[sp + 4] = scratch[2]; // P2.x + srcPosY = stack[sp + 5] = scratch[3]; // P2.y + subdivideQuadratic(); + break; + + case PathIterator.SEG_CUBICTO: + if (recursionLimit == 0) + { + srcPosX = scratch[4]; + srcPosY = scratch[5]; + stackSize = 0; + return; + } + sp = 6 * recursionLimit; + stackSize = 1; + if ((stack == null) || (stack.length < sp + 8)) + { + stack = new double[sp + /* 6 + 2 */ 8]; + recLevel = new int[recursionLimit + 1]; + } + recLevel[0] = 0; + stack[sp] = srcPosX; // P1.x + stack[sp + 1] = srcPosY; // P1.y + stack[sp + 2] = scratch[0]; // C1.x + stack[sp + 3] = scratch[1]; // C1.y + stack[sp + 4] = scratch[2]; // C2.x + stack[sp + 5] = scratch[3]; // C2.y + srcPosX = stack[sp + 6] = scratch[4]; // P2.x + srcPosY = stack[sp + 7] = scratch[5]; // P2.y + subdivideCubic(); + return; + } + } + + + /** + * Repeatedly subdivides the quadratic curve segment that is on top + * of the stack. The iteration terminates when the recursion limit + * has been reached, or when the resulting segment is flat enough. + */ + private void subdivideQuadratic() + { + int sp; + int level; + + sp = stack.length - 4 * stackSize - 2; + level = recLevel[stackSize - 1]; + while ((level < recursionLimit) + && (QuadCurve2D.getFlatnessSq(stack, sp) >= flatnessSq)) + { + recLevel[stackSize] = recLevel[stackSize - 1] = ++level; + QuadCurve2D.subdivide(stack, sp, stack, sp - 4, stack, sp); + ++stackSize; + sp -= 4; + } + } + + + /** + * Repeatedly subdivides the cubic curve segment that is on top + * of the stack. The iteration terminates when the recursion limit + * has been reached, or when the resulting segment is flat enough. + */ + private void subdivideCubic() + { + int sp; + int level; + + sp = stack.length - 6 * stackSize - 2; + level = recLevel[stackSize - 1]; + while ((level < recursionLimit) + && (CubicCurve2D.getFlatnessSq(stack, sp) >= flatnessSq)) + { + recLevel[stackSize] = recLevel[stackSize - 1] = ++level; + + CubicCurve2D.subdivide(stack, sp, stack, sp - 6, stack, sp); + ++stackSize; + sp -= 6; + } } -} // class FlatteningPathIterator + + + /* These routines were useful for debugging. Since they would + * just bloat the implementation, they are commented out. + * + * + + private static String segToString(int segType, double[] d, int offset) + { + String s; + + switch (segType) + { + case PathIterator.SEG_CLOSE: + return "SEG_CLOSE"; + + case PathIterator.SEG_MOVETO: + return "SEG_MOVETO (" + d[offset] + ", " + d[offset + 1] + ")"; + + case PathIterator.SEG_LINETO: + return "SEG_LINETO (" + d[offset] + ", " + d[offset + 1] + ")"; + + case PathIterator.SEG_QUADTO: + return "SEG_QUADTO (" + d[offset] + ", " + d[offset + 1] + + ") (" + d[offset + 2] + ", " + d[offset + 3] + ")"; + + case PathIterator.SEG_CUBICTO: + return "SEG_CUBICTO (" + d[offset] + ", " + d[offset + 1] + + ") (" + d[offset + 2] + ", " + d[offset + 3] + + ") (" + d[offset + 4] + ", " + d[offset + 5] + ")"; + } + + throw new IllegalStateException(); + } + + + private void dumpQuadraticStack(String msg) + { + int sp = stack.length - 4 * stackSize - 2; + int i = 0; + System.err.print(" " + msg + ":"); + while (sp < stack.length) + { + System.err.print(" (" + stack[sp] + ", " + stack[sp+1] + ")"); + if (i < recLevel.length) + System.out.print("/" + recLevel[i++]); + if (sp + 3 < stack.length) + System.err.print(" [" + stack[sp+2] + ", " + stack[sp+3] + "]"); + sp += 4; + } + System.err.println(); + } + + + private void dumpCubicStack(String msg) + { + int sp = stack.length - 6 * stackSize - 2; + int i = 0; + System.err.print(" " + msg + ":"); + while (sp < stack.length) + { + System.err.print(" (" + stack[sp] + ", " + stack[sp+1] + ")"); + if (i < recLevel.length) + System.out.print("/" + recLevel[i++]); + if (sp + 3 < stack.length) + { + System.err.print(" [" + stack[sp+2] + ", " + stack[sp+3] + "]"); + System.err.print(" [" + stack[sp+4] + ", " + stack[sp+5] + "]"); + } + sp += 6; + } + System.err.println(); + } + + * + * + */ +} diff --git a/libjava/java/awt/geom/doc-files/FlatteningPathIterator-1.html b/libjava/java/awt/geom/doc-files/FlatteningPathIterator-1.html new file mode 100644 index 00000000000..5a52d693edd --- /dev/null +++ b/libjava/java/awt/geom/doc-files/FlatteningPathIterator-1.html @@ -0,0 +1,481 @@ + + + + + The GNU Implementation of java.awt.geom.FlatteningPathIterator + + + + + +

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. + +
  3. If the stack is empty, flattening the segment is complete, +and the next segment is fetched from the base iterator.
  4. + +
  5. If the stack is not empty, pop a curve segment from the +stack. + +
    • If its recLevel exceeds the recursion limit, + hand the current segment to the consumer.
    • + +
    • Calculate the squared flatness of the segment. If it smaller + than flatnessSq, hand the current segment to the + consumer.
    • + +
    • Otherwise, split the segment in two halves. Push the right + half onto the stack. Then, push the left half onto the stack. + Continue with step two.
  6. +
+ +

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.

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ABCDEFGH
stack[0]Sll.x
stack[1]Sll.y
stack[2]Cll.x
stack[3]Cll.y
stack[4]Sl.xEll.x + = Slr.xSlr.xSrl.x
stack[5]Sl.yEll.x + = Slr.ySlr.ySrl.y
stack[6]Cl.xClr.xClr.xCrl.x
stack[7]Cl.yClr.yClr.yCrl.y
stack[8]S.xEl.x + = Sr.xElr.x + = Sr.xElr.x + = Sr.xSr.xErl.x + = Srr.xSrr.x
stack[9]S.yEl.y + = Sr.yElr.y + = Sr.yElr.y + = Sr.ySr.yErl.y + = Srr.ySrr.y
stack[10]C.xCr.xCr.xCr.xCr.xCrr.xCrr.x
stack[11]C.yCr.yCr.yCr.yCr.yCrr.yCrr.y
stack[12]E.xEr.xEr.xEr.xEr.xErr.xErr.x
stack[13]E.yEr.yEr.yEr.yEr.yErr.yErr.x
stackSize12321210
recLevel[2]2
recLevel[1]1222
recLevel[0]0111122
+
+ +
    + +
  1. The data structures are initialized as follows. + +
    • The segment’s end point E, control point +C, and start point S are pushed onto the stack.
    • + +
    • Currently, the curve in the stack would be approximated by one + single straight line segment (SE). + Therefore, stackSize is set to 1.
    • + +
    • This single straight line segment is approximating the original + curve, which can be seen as the result of zero recursive + splits. Therefore, recLevel[0] is set to + zero.
    + +Column A shows the state after the initialization step.
  2. + +
  3. The algorithm proceeds by taking the topmost curve segment +(SCE) from the stack. + +
    • The recursion level of this segment (stored in + recLevel[0]) is zero, which is smaller than + the limit 2.
    • + +
    • The method java.awt.geom.QuadCurve2D.getFlatnessSq + is called to calculate the squared flatness.
    • + +
    • For the sake of argument, we assume that the squared flatness is + exceeding the threshold stored in flatnessSq. Thus, the + curve segment SCE gets + subdivided into a left and a right half, namely + SlCl – + El and Sr – + CrEr. Both halves are + pushed onto the stack, so the left half is now on top. + +
       
      The left half starts at the same point + as the original curve, so Sl has the same + coordinates as S. Similarly, the end point of the right + half and of the original curve are identical + (Er = E). More interestingly, the left + half ends where the right half starts. Because + El = Sr, their coordinates need + to be stored only once, which amounts to saving 16 bytes (two + double values) for each iteration.
    + +Column B shows the state after the first iteration.
  4. + +
  5. Again, the topmost curve segment (Sl +– ClEl) is +taken from the stack. + +
    • The recursion level of this segment (stored in + recLevel[1]) is 1, which is smaller than + the limit 2.
    • + +
    • The method java.awt.geom.QuadCurve2D.getFlatnessSq + is called to calculate the squared flatness.
    • + +
    • Assuming that the segment is still not considered + flat enough, it gets subdivided into a left + (SllCll – + Ell) and a right (Slr + – ClrElr) + half.
    + +Column C shows the state after the second iteration.
  6. + +
  7. The topmost curve segment (Sll – +CllEll) is popped from +the stack. + +
    • The recursion level of this segment (stored in + recLevel[2]) is 2, which is not smaller than + the limit 2. Therefore, a SEG_LINETO (from + Sll to Ell) is passed to the + consumer.
    + + The new state is shown in column D.
  8. + + +
  9. The topmost curve segment (Slr – +ClrElr) is popped from +the stack. + +
    • The recursion level of this segment (stored in + recLevel[1]) is 2, which is not smaller than + the limit 2. Therefore, a SEG_LINETO (from + Slr to Elr) is passed to the + consumer.
    + + The new state is shown in column E.
  10. + +
  11. The algorithm proceeds by taking the topmost curve segment +(SrCr – +Er) from the stack. + +
    • The recursion level of this segment (stored in + recLevel[0]) is 1, which is smaller than + the limit 2.
    • + +
    • The method java.awt.geom.QuadCurve2D.getFlatnessSq + is called to calculate the squared flatness.
    • + +
    • For the sake of argument, we again assume that the squared + flatness is exceeding the threshold stored in + flatnessSq. Thus, the curve segment + (SrCr – + Er) is subdivided into a left and a right half, + namely + SrlCrl – + Erl and Srr – + CrrErr. Both halves + are pushed onto the stack.
    + + The new state is shown in column F.
  12. + +
  13. The topmost curve segment (Srl – +CrlErl) is popped from +the stack. + +
    • The recursion level of this segment (stored in + recLevel[2]) is 2, which is not smaller than + the limit 2. Therefore, a SEG_LINETO (from + Srl to Erl) is passed to the + consumer.
    + + The new state is shown in column G.
  14. + +
  15. The topmost curve segment (Srr – +CrrErr) is popped from +the stack. + +
    • The recursion level of this segment (stored in + recLevel[2]) is 2, which is not smaller than + the limit 2. Therefore, a SEG_LINETO (from + Srr to Err) is passed to the + consumer.
    + + The new state is shown in column H.
  16. + +
  17. The stack is now empty. The FlatteningPathIterator will fetch the +next segment from the base iterator, and process it.
  18. + +
+ +

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.

+ + +