+// Copyright 2000-2005 the Contributors, as shown in the revision logs.
+// Licensed under the GNU General Public License version 2 ("the License").
+// You may not use this file except in compliance with the License.
+
// FIXME
-// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
package org.ibex.graphics;
import java.util.*;
+import org.ibex.util.*;
/** an abstract path; may contain splines and arcs */
public class Path {
static final byte TYPE_CUBIC = 3;
static final byte TYPE_QUADRADIC = 4;
- public static Path parse(String s) { return Tokenizer.parse(s); }
-
// FIXME: hack
private String toString;
- private Path(String s) { this.toString = s; }
public String toString() { return toString; }
+ public Path(String s) {
+ this.toString = s;
+ Tokenizer t = new Tokenizer(s);
+ char last_command = 'M';
+ boolean first = true;
+ while(t.hasMoreTokens()) {
+ char command = t.parseCommand();
+ if (first && command == 'm') command = 'M';
+ if (first && command != 'M') throw new RuntimeException("the first command of a path must be 'M'");
+ first = false;
+ boolean relative = Character.toLowerCase(command) == command;
+ command = Character.toLowerCase(command);
+ parseSingleCommandAndArguments(t, command, relative);
+ last_command = command;
+ }
+ }
+
+ //#repeat width/height multiply_px/multiply_py horizontalBounds/verticalBounds
+ public long horizontalBounds(Affine a) {
+ // FIXME wrong
+ float min = Float.MAX_VALUE;
+ float max = Float.MIN_VALUE;
+ for(int i=0; i<numvertices; i++) {
+ min = Math.min(min, a.multiply_px(x[i], y[i]));
+ max = Math.max(max, a.multiply_px(x[i], y[i]));
+ }
+ return Encode.twoFloatsToLong(max, min);
+ }
+ //#end
+
+ public long transform(Affine a, boolean forReal) { return transform(a, forReal, true); }
+ public long transform(Affine a, boolean forReal, boolean widthheight) {
+ float minx = Integer.MAX_VALUE; float miny = Integer.MAX_VALUE;
+ float maxx = Integer.MIN_VALUE; float maxy = Integer.MIN_VALUE;
+ for(int i=0; i<numvertices; i++) {
+ if (type[i] == TYPE_ARCTO) { /* FIXME!!! WRONG!!!! */ continue; }
+ float x = a.multiply_px(this.x[i], this.y[i]); if (x>maxx) maxx = x; if (x<minx) minx = x;
+ float y = a.multiply_py(this.x[i], this.y[i]); if (y>maxy) maxy = y; if (y<miny) miny = y;
+ float c1x = a.multiply_px(this.c1x[i], this.c1y[i]); if (c1x>maxx) maxx = c1x; if (c1x<minx) minx = c1x;
+ float c1y = a.multiply_py(this.c1x[i], this.c1y[i]); if (c1y>maxy) maxy = c1y; if (c1y<miny) miny = c1y;
+ float c2x = a.multiply_px(this.c2x[i], this.c2y[i]); if (c2x>maxx) maxx = c2x; if (c2x<minx) minx = c2x;
+ float c2y = a.multiply_py(this.c2x[i], this.c2y[i]); if (c2y>maxy) maxy = c2y; if (c2y<miny) miny = c2y;
+ if (forReal) {
+ this.x[i] = x; this.y[i] = y;
+ this.c1x[i] = c1x; this.c1y[i] = c1y;
+ this.c2x[i] = c2x; this.c2y[i] = c2y;
+ }
+ }
+ if (widthheight) return ((((long)Float.floatToIntBits(maxx - minx)) << 32) | ((long)Float.floatToIntBits(maxy - miny)));
+ else return ((((long)Float.floatToIntBits(minx)) << 32) | ((long)Float.floatToIntBits(miny)));
+ }
+
+ public void alignToOrigin() {
+ float minx = Integer.MAX_VALUE; float miny = Integer.MAX_VALUE;
+ for(int i=0; i<numvertices; i++) { if (x[i] < minx) minx = x[i]; if (y[i] < miny) miny = y[i]; }
+ for(int i=0; i<numvertices; i++) {
+ x[i] -= minx; y[i] -= miny;
+ if (type[i] == TYPE_ARCTO) continue;
+ c1x[i] -= minx; c2x[i] -= minx; c1y[i] -= miny; c2y[i] -= miny;
+ }
+ }
+
+
public static class Tokenizer {
// FIXME: check array bounds exception for improperly terminated string
String s;
throw new RuntimeException("didn't expect character \"" + c + "\" in a numeric constant");
}
}
- if (start == i) throw new RuntimeException("FIXME");
- return Float.parseFloat(s.substring(start, i)) * multiplier;
+ //if (start == i) throw new RuntimeException("FIXME");
+ if (start == i) return (float)0.0;
+ try {
+ return Float.parseFloat(s.substring(start, i)) * multiplier;
+ } catch (NumberFormatException nfe) {
+ Log.warn(Path.class, "offending string was \"" + s.substring(start, i) + "\"");
+ throw nfe;
+ }
}
}
/** Creates a concrete vector path transformed through the given matrix. */
- public Raster realize(Affine a) {
+ public void addTo(Polygon ret, Affine a) {
+ float NUMSTEPS = 5; // FIXME
+ ret.x[0] = a.multiply_px(x[0], y[0]);
+ ret.y[0] = a.multiply_py(x[0], y[0]);
- Raster ret = new Raster();
- int NUMSTEPS = 5; // FIXME
- ret.numvertices = 1;
- ret.x[0] = (int)Math.round(a.multiply_px(x[0], y[0]));
- ret.y[0] = (int)Math.round(a.multiply_py(x[0], y[0]));
-
- for(int i=1; i<numvertices; i++) {
+ for(int i=0; i<numvertices; i++) {
if (type[i] == TYPE_LINETO) {
- float rx = x[i];
- float ry = y[i];
- ret.x[ret.numvertices] = (int)Math.round(a.multiply_px(rx, ry));
- ret.y[ret.numvertices] = (int)Math.round(a.multiply_py(rx, ry));
- ret.edges[ret.numedges++] = ret.numvertices - 1; ret.numvertices++;
+ float rx = x[i+1];
+ float ry = y[i+1];
+ ret.add(a.multiply_px(rx, ry), a.multiply_py(rx, ry));
} else if (type[i] == TYPE_MOVETO) {
- float rx = x[i];
- float ry = y[i];
- ret.x[ret.numvertices] = (int)Math.round(a.multiply_px(rx, ry));
- ret.y[ret.numvertices] = (int)Math.round(a.multiply_py(rx, ry));
- ret.numvertices++;
+ float rx = x[i+1];
+ float ry = y[i+1];
+ ret.newcontour();
+ ret.add(a.multiply_px(rx, ry), a.multiply_py(rx, ry));
} else if (type[i] == TYPE_ARCTO) {
float rx = c1x[i];
ry * (float)Math.sin(theta) * (float)Math.sin(phi) + cx;
float rastery = rx * (float)Math.cos(theta) * (float)Math.sin(phi) +
ry * (float)Math.cos(phi) * (float)Math.sin(theta) + cy;
- ret.x[ret.numvertices] = (int)Math.round(a.multiply_px(rasterx, rastery));
- ret.y[ret.numvertices] = (int)Math.round(a.multiply_py(rasterx, rastery));
- ret.edges[ret.numedges++] = ret.numvertices - 1; ret.numvertices++;
+ ret.add(a.multiply_px(rasterx, rastery), a.multiply_py(rasterx, rastery));
theta += dtheta / NUMSTEPS;
}
float cy = 3 * c1y[i] - 3 * y[i];
float dy = y[i];
- for(float t=0; t<1; t += 1 / (float)NUMSTEPS) {
+ float x0 = a.multiply_px(x[i], y[i]);
+ float y0 = a.multiply_py(x[i], y[i]);
+ float x1 = a.multiply_px(x[i+1], y[i+1]);
+ float y1 = a.multiply_py(x[i+1], y[i+1]);
+ float steps = (float)Math.sqrt( (x1-x0) * (x1-x0) + (y1-y0) * (y1-y0) );
+
+ for(float t=0; t<1; t += 1 / (steps/20)) {
float rx = ax * t * t * t + bx * t * t + cx * t + dx;
float ry = ay * t * t * t + by * t * t + cy * t + dy;
- ret.x[ret.numvertices] = (int)Math.round(a.multiply_px(rx, ry));
- ret.y[ret.numvertices] = (int)Math.round(a.multiply_py(rx, ry));
- ret.edges[ret.numedges++] = ret.numvertices - 1; ret.numvertices++;
+ ret.add(a.multiply_px(rx, ry), a.multiply_py(rx, ry));
}
float cy = 2 * c1y[i] - 2 * y[i];
float dy = y[i];
- for(float t=0; t<1; t += 1 / (float)NUMSTEPS) {
+ float x0 = a.multiply_px(x[i], y[i]);
+ float y0 = a.multiply_py(x[i], y[i]);
+ float x1 = a.multiply_px(x[i+1], y[i+1]);
+ float y1 = a.multiply_py(x[i+1], y[i+1]);
+ float steps = (float)Math.sqrt( (x1-x0) * (x1-x0) + (y1-y0) * (y1-y0) );
+
+ for(float t=0; t<1; t += 1 / (steps/20)) {
float rx = bx * t * t + cx * t + dx;
float ry = by * t * t + cy * t + dy;
- ret.x[ret.numvertices] = (int)Math.round(a.multiply_px(rx, ry));
- ret.y[ret.numvertices] = (int)Math.round(a.multiply_py(rx, ry));
- ret.edges[ret.numedges++] = ret.numvertices - 1; ret.numvertices++;
+ ret.add(a.multiply_px(rx, ry), a.multiply_py(rx, ry));
}
}
-
}
-
- if (ret.numedges > 0) ret.sort(0, ret.numedges - 1, false);
- return ret;
}
protected void parseSingleCommandAndArguments(Tokenizer t, char command, boolean relative) {
- if (numvertices == 0 && command != 'm') throw new RuntimeException("first command MUST be an 'm'");
+ if (numvertices == 0 && command != 'm')
+ throw new RuntimeException("first command MUST be an 'm', not a " + command);
if (numvertices > x.length - 2) {
float[] new_x = new float[x.length * 2]; System.arraycopy(x, 0, new_x, 0, x.length); x = new_x;
float[] new_y = new float[y.length * 2]; System.arraycopy(y, 0, new_y, 0, y.length); y = new_y;
case 'z': {
int where;
type[numvertices-1] = TYPE_LINETO;
- for(where = numvertices - 1; where > 0; where--)
- if (type[where - 1] == TYPE_MOVETO) break;
- x[numvertices] = x[where];
- y[numvertices] = y[where];
+ for(where = numvertices-2; where >= 0 && type[where] != TYPE_MOVETO; where--);
+ x[numvertices] = x[where+1];
+ y[numvertices] = y[where+1];
numvertices++;
closed = true;
+ // FIXME: actually, we should search back to the last 'z' or 'm', not just 'm'
break;
}
if (numvertices > 0) type[numvertices-1] = TYPE_MOVETO;
x[numvertices] = t.parseFloat() + (relative ? x[numvertices - 1] : 0);
y[numvertices] = t.parseFloat() + (relative ? y[numvertices - 1] : 0);
- numvertices++;
+ if (numvertices > 2 && type[numvertices-2] == TYPE_MOVETO) {
+ x[numvertices-1] = x[numvertices];
+ y[numvertices-1] = y[numvertices];
+ } else {
+ numvertices++;
+ }
break;
}
}
-
- // Rasterized Vector Path //////////////////////////////////////////////////////////////////////////////
-
- /** a vector path */
- public static class Raster {
-
- // the vertices of this path
- int[] x = new int[DEFAULT_PATHLEN];
- int[] y = new int[DEFAULT_PATHLEN];
- int numvertices = 0;
-
- /**
- * A list of the vertices on this path which *start* an *edge* (rather than a moveto), sorted by increasing y.
- * example: x[edges[1]],y[edges[1]] - x[edges[i]+1],y[edges[i]+1] is the second-topmost edge
- * note that if x[i],y[i] - x[i+1],y[i+1] is a MOVETO, then no element in edges will be equal to i
- */
- int[] edges = new int[DEFAULT_PATHLEN];
- int numedges = 0;
-
- /** translate a rasterized path */
- public void translate(int dx, int dy) { for(int i=0; i<numvertices; i++) { x[i] += dx; y[i] += dy; } }
-
- /** simple quicksort, from http://sourceforge.net/snippet/detail.php?type=snippet&id=100240 */
- int sort(int left, int right, boolean partition) {
- if (partition) {
- int i, j, middle;
- middle = (left + right) / 2;
- int s = edges[right]; edges[right] = edges[middle]; edges[middle] = s;
- for (i = left - 1, j = right; ; ) {
- while (y[edges[++i]] < y[edges[right]]);
- while (j > left && y[edges[--j]] > y[edges[right]]);
- if (i >= j) break;
- s = edges[i]; edges[i] = edges[j]; edges[j] = s;
- }
- s = edges[right]; edges[right] = edges[i]; edges[i] = s;
- return i;
- } else {
- if (left >= right) return 0;
- int p = sort(left, right, true);
- sort(left, p - 1, false);
- sort(p + 1, right, false);
- return 0;
- }
- }
-
- /** finds the x value at which the line intercepts the line y=_y */
- private int intercept(int i, float _y, boolean includeTop, boolean includeBottom) {
- if (includeTop ? (_y < Math.min(y[i], y[i+1])) : (_y <= Math.min(y[i], y[i+1])))
- return Integer.MIN_VALUE;
- if (includeBottom ? (_y > Math.max(y[i], y[i+1])) : (_y >= Math.max(y[i], y[i+1])))
- return Integer.MIN_VALUE;
- return (int)Math.round((((float)(x[i + 1] - x[i])) /
- ((float)(y[i + 1] - y[i])) ) * ((float)(_y - y[i])) + x[i]);
- }
-
- /** fill the interior of the path */
- public void fill(PixelBuffer buf, Paint paint) {
- if (numedges == 0) return;
- int y0 = y[edges[0]], y1 = y0;
- boolean useEvenOdd = false;
-
- // we iterate over all endpoints in increasing y-coordinate order
- for(int index = 1; index<numedges; index++) {
- int count = 0;
-
- // we now examine the horizontal band between y=y0 and y=y1
- y0 = y1;
- y1 = y[edges[index]];
- if (y0 == y1) continue;
-
- // within this band, we iterate over all edges
- int x0 = Integer.MIN_VALUE;
- int leftSegment = -1;
- while(true) {
- int x1 = Integer.MAX_VALUE;
- int rightSegment = Integer.MAX_VALUE;
- for(int i=0; i<numedges; i++) {
- if (y[edges[i]] == y[edges[i]+1]) continue; // ignore horizontal lines; they are irrelevant.
- // we order the segments by the x-coordinate of their midpoint;
- // since segments cannot intersect, this is a well-ordering
- int i0 = intercept(edges[i], y0, true, false);
- int i1 = intercept(edges[i], y1, false, true);
- if (i0 == Integer.MIN_VALUE || i1 == Integer.MIN_VALUE) continue;
- int midpoint = i0 + i1;
- if (midpoint < x0) continue;
- if (midpoint == x0 && i <= leftSegment) continue;
- if (midpoint > x1) continue;
- if (midpoint == x1 && i >= rightSegment) continue;
- rightSegment = i;
- x1 = midpoint;
- }
- if (leftSegment == rightSegment || rightSegment == Integer.MAX_VALUE) break;
- if (leftSegment != -1)
- if ((useEvenOdd && count % 2 != 0) || (!useEvenOdd && count != 0))
- paint.fillTrapezoid(intercept(edges[leftSegment], y0, true, true),
- intercept(edges[rightSegment], y0, true, true), y0,
- intercept(edges[leftSegment], y1, true, true),
- intercept(edges[rightSegment], y1, true, true), y1,
- buf);
- if (useEvenOdd) count++;
- else count += (y[edges[rightSegment]] < y[edges[rightSegment]+1]) ? -1 : 1;
- leftSegment = rightSegment; x0 = x1;
- }
- }
- }
-
- /** stroke the outline of the path */
- public void stroke(PixelBuffer buf, int width, int color) { stroke(buf, width, color, null, 0, 0); }
- public void stroke(PixelBuffer buf, int width, int color, String dashArray, int dashOffset, float segLength) {
-
- if (dashArray == null) {
- for(int i=0; i<numedges; i++)
- buf.drawLine((int)x[edges[i]],
- (int)y[edges[i]], (int)x[edges[i]+1], (int)y[edges[i]+1], width, color, false);
- return;
- }
-
- float ratio = 1;
- if (segLength > 0) {
- float actualLength = 0;
- for(int i=0; i<numvertices; i++) {
- // skip over MOVETOs -- they do not contribute to path length
- if (x[i] == x[i+1] && y[i] == y[i+1]) continue;
- if (x[i+1] == x[i+2] && y[i+1] == y[i+2]) continue;
- int x1 = x[i];
- int x2 = x[i + 1];
- int y1 = y[i];
- int y2 = y[i + 1];
- actualLength += java.lang.Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
- }
- ratio = actualLength / segLength;
- }
- Tokenizer pt = new Tokenizer(dashArray);
- Vector v = new Vector();
- while (pt.hasMoreTokens()) v.addElement(new Float(pt.parseFloat()));
- float[] dashes = new float[v.size() % 2 == 0 ? v.size() : 2 * v.size()];
- for(int i=0; i<dashes.length; i++) dashes[i] = ((Float)v.elementAt(i % v.size())).floatValue();
- int dashpos = dashOffset;
- boolean on = dashpos % 2 == 0;
- for(int i=0; i<numvertices; i++) {
- // skip over MOVETOs -- they do not contribute to path length
- if (x[i] == x[i+1] && y[i] == y[i+1]) continue;
- if (x[i+1] == x[i+2] && y[i+1] == y[i+2]) continue;
- int x1 = (int)x[i];
- int x2 = (int)x[i + 1];
- int y1 = (int)y[i];
- int y2 = (int)y[i + 1];
- float segmentLength = (float)java.lang.Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
- int _x1 = x1, _y1 = y1;
- float pos = 0;
- do {
- pos = Math.min(segmentLength, pos + dashes[dashpos] * ratio);
- if (pos != segmentLength) dashpos = (dashpos + 1) % dashes.length;
- int _x2 = (int)((x2 * pos + x1 * (segmentLength - pos)) / segmentLength);
- int _y2 = (int)((y2 * pos + y1 * (segmentLength - pos)) / segmentLength);
- if (on) buf.drawLine(_x1, _y1, _x2, _y2, width, color, false);
- on = !on;
- _x1 = _x2; _y1 = _y2;
- } while(pos < segmentLength);
- }
- }
-
- // FEATURE: make this faster and cache it; also deal with negative coordinates
- public int boundingBoxWidth() {
- int ret = 0;
- for(int i=0; i<numvertices; i++) ret = Math.max(ret, x[i]);
- return ret;
- }
-
- // FEATURE: make this faster and cache it; also deal with negative coordinates
- public int boundingBoxHeight() {
- int ret = 0;
- for(int i=0; i<numvertices; i++) ret = Math.max(ret, y[i]);
- return ret;
- }
- }
}