Author: [EMAIL PROTECTED]
Date: Tue Nov 4 16:53:44 2008
New Revision: 405
Added:
piccolo2d.java/trunk/extras/src/main/java/edu/umd/cs/piccolox/util/PSemanticStroke.java
(contents, props changed)
Removed:
piccolo2d.java/trunk/extras/src/test/java/edu/umd/cs/piccolox/util/PFixedWidthStrokeMRO.java
piccolo2d.java/trunk/extras/src/test/java/edu/umd/cs/piccolox/util/PFixedWidthStrokeTest.java
Modified:
piccolo2d.java/trunk/extras/src/main/java/edu/umd/cs/piccolox/util/PFixedWidthStroke.java
Log:
issue#49 issue#48 done for the first. Is it still compatible?
Modified:
piccolo2d.java/trunk/extras/src/main/java/edu/umd/cs/piccolox/util/PFixedWidthStroke.java
==============================================================================
---
piccolo2d.java/trunk/extras/src/main/java/edu/umd/cs/piccolox/util/PFixedWidthStroke.java
(original)
+++
piccolo2d.java/trunk/extras/src/main/java/edu/umd/cs/piccolox/util/PFixedWidthStroke.java
Tue Nov 4 16:53:44 2008
@@ -31,381 +31,120 @@
import java.awt.BasicStroke;
import java.awt.Shape;
import java.awt.Stroke;
-import java.awt.geom.GeneralPath;
-import java.awt.geom.PathIterator;
-import java.awt.geom.Rectangle2D;
+import java.io.ObjectStreamException;
import java.io.Serializable;
-import sun.dc.path.FastPathProducer;
-import sun.dc.path.PathConsumer;
-import sun.dc.path.PathException;
-import sun.dc.pr.PathDasher;
-import sun.dc.pr.PathStroker;
-import sun.dc.pr.Rasterizer;
-
-import edu.umd.cs.piccolo.util.PAffineTransform;
-import edu.umd.cs.piccolo.util.PDebug;
-import edu.umd.cs.piccolo.util.PPaintContext;
-import edu.umd.cs.piccolo.util.PPickPath;
-
/**
- * <b>PFixedWidthStroke</b> is the same as [EMAIL PROTECTED]
java.awt.BasicStroke}
except
- * that PFixedWidthStroke has a fixed width on the screen so that even
when the
- * canvas view is zooming its width stays the same in canvas coordinates.
Note
- * that this stroke draws in the inside of the stroked shape, instead of
the
- * normal draw on center behavior.
- * <P>
+ * <b>PFixedWidthStroke</b> is the same as [EMAIL PROTECTED] BasicStroke}
except that
+ * PFixedWidthStroke has a fixed width on the screen so that even when the
+ * canvas view is zooming its width stays the same in canvas coordinates.
+ * <p>
+ * [EMAIL PROTECTED] #createStrokedShape(Shape)} checks if the scale has
changed
since the
+ * last usage and if that's the case calls [EMAIL PROTECTED]
#newStroke(float)} to
get a
+ * new [EMAIL PROTECTED] Stroke} instance to delegate to.
+ * <p>
+ * <b>CAUTION!</b> this implementation falls short for large scaling
factors -
+ * the effective miterlimit might drop below 1.0 which isn't permitted by
+ * [EMAIL PROTECTED] BasicStroke} and therefore limited to a minimal 1.0 by
this
+ * implementation. A more sophisticated implementation might use the
approach
+ * mentioned at http://code.google.com/p/piccolo2d/issues/detail?id=49
+ * <p>
+ * <b>CAUTION!</b> after extreme scaling this implementation seems to
change to
+ * internal state of the base stroke. Try PathExample with extreme zoom in
and
+ * zoom back to the original scale. The pickable circles disappear.
Strange!
*
* @see edu.umd.cs.piccolo.nodes.PPath
+ * @see BasicStroke
* @version 1.0
* @author Jesse Grosjean
+ * @author Marcus Rohrmoser
*/
-public class PFixedWidthStroke implements Stroke, Serializable {
-
- private static PAffineTransform TEMP_TRANSFORM = new
PAffineTransform();
- private static GeneralPath TEMP_PATH = new
GeneralPath(GeneralPath.WIND_NON_ZERO);
-
- final static int JOIN_MITER = BasicStroke.JOIN_MITER;
- final static int JOIN_ROUND = BasicStroke.JOIN_ROUND;
- final static int JOIN_BEVEL = BasicStroke.JOIN_BEVEL;
- final static int CAP_BUTT = BasicStroke.CAP_BUTT;
- final static int CAP_ROUND = BasicStroke.CAP_ROUND;
- final static int CAP_SQUARE = BasicStroke.CAP_SQUARE;
-
- private float width;
- private int join;
- private int cap;
- private float miterlimit;
- private float dash[];
- private float dash_phase;
-
- private static final int RasterizerCaps[] = { Rasterizer.BUTT,
Rasterizer.ROUND, Rasterizer.SQUARE };
-
- private static final int RasterizerCorners[] = { Rasterizer.MITER,
Rasterizer.ROUND, Rasterizer.BEVEL };
-
- private class FillAdapter implements PathConsumer {
- boolean closed;
- GeneralPath path;
-
- public FillAdapter() {
- path = TEMP_PATH;
- path.reset();
- }
-
- public Shape getShape() {
- return path;
- }
-
- public void beginPath() {
- }
-
- public void beginSubpath(float x0, float y0) {
- if (closed) {
- path.closePath();
- closed = false;
- }
- path.moveTo(x0, y0);
- }
-
- public void appendLine(float x1, float y1) {
- path.lineTo(x1, y1);
- }
-
- public void appendQuadratic(float xm, float ym, float x1, float
y1) {
- path.quadTo(xm, ym, x1, y1);
- }
-
- public void appendCubic(float xm, float ym, float xn, float yn,
float x1, float y1) {
- path.curveTo(xm, ym, xn, yn, x1, y1);
- }
-
- public void closedSubpath() {
- closed = true;
- }
-
- public void endPath() {
- if (closed) {
- path.closePath();
- closed = false;
- }
- }
+public class PFixedWidthStroke extends PSemanticStroke implements
Serializable {
- public void useProxy(FastPathProducer proxy) throws PathException {
- proxy.sendTo(this);
- }
-
- public long getCPathConsumer() {
- return 0;
- }
+ // make them public if required or delete when cleaning up for 2.0
+ private static final int CAP_BUTT = BasicStroke.CAP_BUTT;
+ private static final int CAP_ROUND = BasicStroke.CAP_ROUND;
+ private static final int CAP_SQUARE = BasicStroke.CAP_SQUARE;
+ private static final int JOIN_BEVEL = BasicStroke.JOIN_BEVEL;
+ private static final int JOIN_MITER = BasicStroke.JOIN_MITER;
+ private static final int JOIN_ROUND = BasicStroke.JOIN_ROUND;
+
+ private static final long serialVersionUID = -2503357070350473610L;
+
+ // avoid repeated cloning:
+ private transient final float dash[];
+ // avoid repeated instantiations:
+ private transient final float tmpDash[];
- public void dispose() {
- }
-
- public PathConsumer getConsumer() {
- return null;
- }
+ public PFixedWidthStroke() {
+ this(1.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f,
null, 0.0f);
}
- public PFixedWidthStroke() {
- this(1.0f, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f);
+ /** This should be "public" and the "main" constructor. */
+ private PFixedWidthStroke(final BasicStroke stroke) {
+ super(stroke);
+ dash = stroke.getDashArray();
+ tmpDash = dash == null ? null : new float[dash.length];
}
- public PFixedWidthStroke(float width) {
- this(width, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f);
+ public PFixedWidthStroke(final float width) {
+ this(width, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f,
null, 0.0f);
}
- public PFixedWidthStroke(float width, int cap, int join) {
+ public PFixedWidthStroke(final float width, final int cap, final int
join) {
this(width, cap, join, 10.0f, null, 0.0f);
}
- public PFixedWidthStroke(float width, int cap, int join, float
miterlimit) {
+ public PFixedWidthStroke(final float width, final int cap, final int
join, final float miterlimit) {
this(width, cap, join, miterlimit, null, 0.0f);
}
- public PFixedWidthStroke(float width, int cap, int join, float
miterlimit, float dash[], float dash_phase) {
- if (width < 0.0f) {
- throw new IllegalArgumentException("negative width");
- }
- if (cap != CAP_BUTT && cap != CAP_ROUND && cap != CAP_SQUARE) {
- throw new IllegalArgumentException("illegal end cap value");
- }
- if (join == JOIN_MITER) {
- if (miterlimit < 1.0f) {
- throw new IllegalArgumentException("miter limit < 1");
- }
- }
- else if (join != JOIN_ROUND && join != JOIN_BEVEL) {
- throw new IllegalArgumentException("illegal line join value");
- }
- if (dash != null) {
- if (dash_phase < 0.0f) {
- throw new IllegalArgumentException("negative dash phase");
- }
- boolean allzero = true;
- for (int i = 0; i < dash.length; i++) {
- float d = dash[i];
- if (d > 0.0) {
- allzero = false;
- }
- else if (d < 0.0) {
- throw new IllegalArgumentException("negative dash
length");
- }
- }
-
- if (allzero) {
- throw new IllegalArgumentException("dash lengths all
zero");
- }
- }
- this.width = width;
- this.cap = cap;
- this.join = join;
- this.miterlimit = miterlimit;
- if (dash != null) {
- this.dash = (float[]) dash.clone();
- }
- this.dash_phase = dash_phase;
+ public PFixedWidthStroke(final float width, final int cap, final int
join, final float miterlimit,
+ final float dash[], final float dash_phase) {
+ this(new BasicStroke(width, cap, join, miterlimit, dash,
dash_phase));
}
public Object clone() {
- try {
- return super.clone();
- }
- catch (CloneNotSupportedException e) {
- e.printStackTrace();
- }
- return null;
- }
-
- public Shape createStrokedShape(Shape s) {
- FillAdapter filler = new FillAdapter();
- PathStroker stroker = new PathStroker(filler);
- PathConsumer consumer;
-
- // Fixed Width Additions, always stroke path inside shape.
- // Also keeps dashes fixed when zooming (thanks to Shawn
Castrianni -
- // Dec 2006)
- float fixedScale = 1.0f;
-
- if (PDebug.getProcessingOutput()) {
- if (PPaintContext.CURRENT_PAINT_CONTEXT != null) {
- fixedScale = 1.0f / (float)
PPaintContext.CURRENT_PAINT_CONTEXT.getScale();
- }
- }
- else {
- if (PPickPath.CURRENT_PICK_PATH != null) {
- fixedScale = 1.0f / (float)
PPickPath.CURRENT_PICK_PATH.getScale();
- }
- }
- float fixedWidth = width * fixedScale;
-
- Rectangle2D bounds = s.getBounds2D();
- double scale = 1.0;
-
- if (bounds.getWidth() > bounds.getHeight()) {
- if (bounds.getWidth() != 0) {
- scale = (bounds.getWidth() - fixedWidth) /
bounds.getWidth();
- }
- }
- else {
- if (bounds.getHeight() != 0) {
- scale = (bounds.getHeight() - fixedWidth) /
bounds.getHeight();
- }
- }
-
- TEMP_TRANSFORM.setToIdentity();
- TEMP_TRANSFORM.scaleAboutPoint(scale, bounds.getCenterX(),
bounds.getCenterY());
- stroker.setPenDiameter(fixedWidth);
- PathIterator pi = s.getPathIterator(TEMP_TRANSFORM);
-
- stroker.setPenT4(null);
- stroker.setCaps(RasterizerCaps[cap]);
- stroker.setCorners(RasterizerCorners[join], miterlimit);
- if (dash != null) {
- // Fixed Width Additions
- float fixedDash[] = new float[dash.length];
- for (int i = 0; i < dash.length; i++) {
- fixedDash[i] = dash[i] * fixedScale;
- }
- float fixedDashPhase = dash_phase * fixedScale;
- PathDasher dasher = new PathDasher(stroker);
- dasher.setDash(fixedDash, fixedDashPhase);
- dasher.setDashT4(null);
- consumer = dasher;
- }
- else {
- consumer = stroker;
- }
-
- try {
- consumer.beginPath();
- boolean pathClosed = false;
- float mx = 0.0f;
- float my = 0.0f;
- float point[] = new float[6];
-
- while (!pi.isDone()) {
- int type = pi.currentSegment(point);
- if (pathClosed == true) {
- pathClosed = false;
- if (type != PathIterator.SEG_MOVETO) {
- // Force current point back to last moveto point
- consumer.beginSubpath(mx, my);
- }
- }
- switch (type) {
- case PathIterator.SEG_MOVETO:
- mx = point[0];
- my = point[1];
- consumer.beginSubpath(point[0], point[1]);
- break;
- case PathIterator.SEG_LINETO:
- consumer.appendLine(point[0], point[1]);
- break;
- case PathIterator.SEG_QUADTO:
- // Quadratic curves take two points
- consumer.appendQuadratic(point[0], point[1],
point[2], point[3]);
- break;
- case PathIterator.SEG_CUBICTO:
- // Cubic curves take three points
- consumer.appendCubic(point[0], point[1], point[2],
point[3], point[4], point[5]);
- break;
- case PathIterator.SEG_CLOSE:
- consumer.closedSubpath();
- pathClosed = true;
- break;
- }
- pi.next();
- }
-
- consumer.endPath();
-
- consumer.dispose(); // hack to fix memory leak, shouldn't be
- // neccessary but is.
- }
- catch (PathException e) {
- throw new InternalError("Unable to Stroke shape (" +
e.getMessage() + ")");
- }
-
- return filler.getShape();
+ throw new UnsupportedOperationException("Not implemented.");
}
- public boolean equals(Object obj) {
- if (!(obj instanceof PFixedWidthStroke)) {
- return false;
- }
-
- PFixedWidthStroke bs = (PFixedWidthStroke) obj;
- if (width != bs.width) {
- return false;
- }
-
- if (join != bs.join) {
- return false;
- }
-
- if (cap != bs.cap) {
- return false;
- }
-
- if (miterlimit != bs.miterlimit) {
- return false;
- }
-
- if (dash != null) {
- if (dash_phase != bs.dash_phase) {
- return false;
- }
-
- if (!java.util.Arrays.equals(dash, bs.dash)) {
- return false;
- }
- }
- else if (bs.dash != null) {
- return false;
- }
-
- return true;
+ public float[] getDashArray() {
+ return ((BasicStroke) stroke).getDashArray();
}
- private float[] getDashArray() {
- if (dash == null) {
- return null;
- }
-
- return (float[]) dash.clone();
- }
-
- private float getDashPhase() {
- return dash_phase;
+ public float getDashPhase() {
+ return ((BasicStroke) stroke).getDashPhase();
}
- private int getEndCap() {
- return cap;
+ public int getEndCap() {
+ return ((BasicStroke) stroke).getEndCap();
}
- private int getLineJoin() {
- return join;
+ public int getLineJoin() {
+ return ((BasicStroke) stroke).getLineJoin();
}
- private float getLineWidth() {
- return width;
+ public float getLineWidth() {
+ return ((BasicStroke) stroke).getLineWidth();
}
- private float getMiterLimit() {
- return miterlimit;
+ public float getMiterLimit() {
+ return ((BasicStroke) stroke).getMiterLimit();
}
- public int hashCode() {
- int hash = Float.floatToIntBits(width);
- hash = hash * 31 + join;
- hash = hash * 31 + cap;
- hash = hash * 31 + Float.floatToIntBits(miterlimit);
- if (dash != null) {
- hash = hash * 31 + Float.floatToIntBits(dash_phase);
- for (int i = 0; i < dash.length; i++) {
- hash = hash * 31 + Float.floatToIntBits(dash[i]);
+ protected Stroke newStroke(final float activeScale) {
+ if (tmpDash != null) {
+ for (int i = dash.length - 1; i >= 0; i--) {
+ tmpDash[i] = dash[i] / activeScale;
}
}
- return hash;
+ final float ml = getMiterLimit() / activeScale;
+ return new BasicStroke(getLineWidth() / activeScale, getEndCap(),
getLineJoin(), ml < 1.0f ? 1.0f : ml,
+ tmpDash, getDashPhase() / activeScale);
+ }
+
+ /** Is it really necessary to implement [EMAIL PROTECTED] Serializable}? */
+ protected Object readResolve() throws ObjectStreamException {
+ return new PFixedWidthStroke((BasicStroke) stroke);
}
-}
\ No newline at end of file
+}
Added:
piccolo2d.java/trunk/extras/src/main/java/edu/umd/cs/piccolox/util/PSemanticStroke.java
==============================================================================
--- (empty file)
+++
piccolo2d.java/trunk/extras/src/main/java/edu/umd/cs/piccolox/util/PSemanticStroke.java
Tue Nov 4 16:53:44 2008
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2008, Piccolo2D project, http://piccolo2d.org
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
this list of conditions
+ * and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions
+ * and the following disclaimer in the documentation and/or other
materials provided with the
+ * distribution.
+ *
+ * None of the name of the University of Maryland, the name of the
Piccolo2D project, or the names of its
+ * contributors may be used to endorse or promote products derived from
this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package edu.umd.cs.piccolox.util;
+
+import java.awt.Shape;
+import java.awt.Stroke;
+
+import edu.umd.cs.piccolo.util.PDebug;
+import edu.umd.cs.piccolo.util.PPaintContext;
+import edu.umd.cs.piccolo.util.PPickPath;
+
+/**
+ *
+ * @see edu.umd.cs.piccolo.nodes.PPath
+ * @see Stroke
+ * @version 1.3
+ * @author Marcus Rohrmoser
+ */
+abstract class PSemanticStroke implements Stroke {
+ protected static final double THRESHOLD = 1e-6;
+
+ private transient float recentScale;
+ private transient Stroke recentStroke;
+ protected final Stroke stroke;
+
+ protected PSemanticStroke(final Stroke stroke) {
+ this.stroke = recentStroke = stroke;
+ recentScale = 1.0F;
+ }
+
+ /**
+ * Ask [EMAIL PROTECTED] #getActiveScale()}, call [EMAIL PROTECTED]
#newStroke(float)} if
+ * necessary and delegate to [EMAIL PROTECTED]
Stroke#createStrokedShape(Shape)}
+ */
+ public Shape createStrokedShape(final Shape s) {
+ final float currentScale = getActiveScale();
+ if (Math.abs(currentScale - recentScale) > THRESHOLD) {
+ recentStroke = newStroke(recentScale = currentScale);
+ }
+ return recentStroke.createStrokedShape(s);
+ }
+
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final PSemanticStroke other = (PSemanticStroke) obj;
+ if (stroke == null) {
+ if (other.stroke != null) {
+ return false;
+ }
+ }
+ else if (!stroke.equals(other.stroke)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Detect the current scale. Made protected to enable custom
+ * re-implementations.
+ */
+ protected float getActiveScale() {
+ // FIXME Honestly I don't understand this distinction - shouldn't
it
+ // always be PPaintContext.CURRENT_PAINT_CONTEXT regardless of the
+ // debugging flag?
+ if (PDebug.getProcessingOutput()) {
+ if (PPaintContext.CURRENT_PAINT_CONTEXT != null) {
+ return (float)
PPaintContext.CURRENT_PAINT_CONTEXT.getScale();
+ }
+ }
+ else {
+ if (PPickPath.CURRENT_PICK_PATH != null) {
+ return (float) PPickPath.CURRENT_PICK_PATH.getScale();
+ }
+ }
+ return 1.0f;
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (stroke == null ? 0 : stroke.hashCode());
+ return result;
+ }
+
+ /**
+ * Factory to create a new internal stroke delegate. Made protected to
+ * enable custom re-implementations.
+ */
+ protected abstract Stroke newStroke(final float activeScale);
+
+ public String toString() {
+ return stroke.toString();
+ }
+}
--~--~---------~--~----~------------~-------~--~----~
Piccolo2D Developers Group: http://groups.google.com/group/piccolo2d-dev?hl=en
-~----------~----~----~----~------~----~------~--~---