added smoother vectorization to Path.java, did some reorg
authoradam <adam@megacz.com>
Mon, 3 May 2004 00:53:38 +0000 (00:53 +0000)
committeradam <adam@megacz.com>
Mon, 3 May 2004 00:53:38 +0000 (00:53 +0000)
darcs-hash:20040503005338-5007d-170aabbc75ccc7640d0248ebe6465bca7a7e0090.gz

src/org/ibex/graphics/Path.java

index 1e5c79f..4340015 100644 (file)
@@ -5,6 +5,7 @@
 // FIXME
 package org.ibex.graphics;
 import java.util.*;
+import org.ibex.util.*;
 
 /** an abstract path; may contain splines and arcs */
 public class Path {
@@ -39,7 +40,59 @@ 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;
+    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;
+        }
+    }
+
+    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;
+        }
+    }
+
 
     // FIXME: hack
     private String toString;
@@ -105,34 +158,35 @@ public class Path {
                     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) {
+        long start = System.currentTimeMillis(); try {
+        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];
@@ -185,9 +239,7 @@ public class Path {
                         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;
                 }
 
@@ -202,12 +254,16 @@ public class Path {
                 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));
                 }
 
 
@@ -220,24 +276,26 @@ public class Path {
                 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;
+        } finally { Scheduler.rasterizing += System.currentTimeMillis() - start; }
     }
 
     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;
@@ -246,12 +304,12 @@ public class Path {
             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;
             }
 
@@ -259,7 +317,12 @@ public class Path {
                 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;
             }
 
@@ -368,180 +431,4 @@ public class Path {
 
     }
 
-
-    // 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;
-        }
-    }
 }