Showing the changes to java/pkg/sun/java2d/pisces (13 of 18352 files). Show all
Unified · Split
| @@ -0,0 +1,290 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. | |
| 3 | + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| 4 | + * | |
| 5 | + * This code is free software; you can redistribute it and/or modify it | |
| 6 | + * under the terms of the GNU General Public License version 2 only, as | |
| 7 | + * published by the Free Software Foundation. Oracle designates this | |
| 8 | + * particular file as subject to the "Classpath" exception as provided | |
| 9 | + * by Oracle in the LICENSE file that accompanied this code. | |
| 10 | + * | |
| 11 | + * This code is distributed in the hope that it will be useful, but WITHOUT | |
| 12 | + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| 13 | + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| 14 | + * version 2 for more details (a copy is included in the LICENSE file that | |
| 15 | + * accompanied this code). | |
| 16 | + * | |
| 17 | + * You should have received a copy of the GNU General Public License version | |
| 18 | + * 2 along with this work; if not, write to the Free Software Foundation, | |
| 19 | + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 20 | + * | |
| 21 | + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| 22 | + * or visit www.oracle.com if you need additional information or have any | |
| 23 | + * questions. | |
| 24 | + */ | |
| 25 | + | |
| 26 | +package sun.java2d.pisces; | |
| 27 | + | |
| 28 | +import java.util.Iterator; | |
| 29 | + | |
| 30 | +final class Curve { | |
| 31 | + | |
| 32 | + float ax, ay, bx, by, cx, cy, dx, dy; | |
| 33 | + float dax, day, dbx, dby; | |
| 34 | + | |
| 35 | + Curve() { | |
| 36 | + } | |
| 37 | + | |
| 38 | + void set(float[] points, int type) { | |
| 39 | + switch(type) { | |
| 40 | + case 8: | |
| 41 | + set(points[0], points[1], | |
| 42 | + points[2], points[3], | |
| 43 | + points[4], points[5], | |
| 44 | + points[6], points[7]); | |
| 45 | + break; | |
| 46 | + case 6: | |
| 47 | + set(points[0], points[1], | |
| 48 | + points[2], points[3], | |
| 49 | + points[4], points[5]); | |
| 50 | + break; | |
| 51 | + default: | |
| 52 | + throw new InternalError("Curves can only be cubic or quadratic"); | |
| 53 | + } | |
| 54 | + } | |
| 55 | + | |
| 56 | + void set(float x1, float y1, | |
| 57 | + float x2, float y2, | |
| 58 | + float x3, float y3, | |
| 59 | + float x4, float y4) | |
| 60 | + { | |
| 61 | + ax = 3 * (x2 - x3) + x4 - x1; | |
| 62 | + ay = 3 * (y2 - y3) + y4 - y1; | |
| 63 | + bx = 3 * (x1 - 2 * x2 + x3); | |
| 64 | + by = 3 * (y1 - 2 * y2 + y3); | |
| 65 | + cx = 3 * (x2 - x1); | |
| 66 | + cy = 3 * (y2 - y1); | |
| 67 | + dx = x1; | |
| 68 | + dy = y1; | |
| 69 | + dax = 3 * ax; day = 3 * ay; | |
| 70 | + dbx = 2 * bx; dby = 2 * by; | |
| 71 | + } | |
| 72 | + | |
| 73 | + void set(float x1, float y1, | |
| 74 | + float x2, float y2, | |
| 75 | + float x3, float y3) | |
| 76 | + { | |
| 77 | + ax = ay = 0f; | |
| 78 | + | |
| 79 | + bx = x1 - 2 * x2 + x3; | |
| 80 | + by = y1 - 2 * y2 + y3; | |
| 81 | + cx = 2 * (x2 - x1); | |
| 82 | + cy = 2 * (y2 - y1); | |
| 83 | + dx = x1; | |
| 84 | + dy = y1; | |
| 85 | + dax = 0; day = 0; | |
| 86 | + dbx = 2 * bx; dby = 2 * by; | |
| 87 | + } | |
| 88 | + | |
| 89 | + float xat(float t) { | |
| 90 | + return t * (t * (t * ax + bx) + cx) + dx; | |
| 91 | + } | |
| 92 | + float yat(float t) { | |
| 93 | + return t * (t * (t * ay + by) + cy) + dy; | |
| 94 | + } | |
| 95 | + | |
| 96 | + float dxat(float t) { | |
| 97 | + return t * (t * dax + dbx) + cx; | |
| 98 | + } | |
| 99 | + | |
| 100 | + float dyat(float t) { | |
| 101 | + return t * (t * day + dby) + cy; | |
| 102 | + } | |
| 103 | + | |
| 104 | + int dxRoots(float[] roots, int off) { | |
| 105 | + return Helpers.quadraticRoots(dax, dbx, cx, roots, off); | |
| 106 | + } | |
| 107 | + | |
| 108 | + int dyRoots(float[] roots, int off) { | |
| 109 | + return Helpers.quadraticRoots(day, dby, cy, roots, off); | |
| 110 | + } | |
| 111 | + | |
| 112 | + int infPoints(float[] pts, int off) { | |
| 113 | + // inflection point at t if -f'(t)x*f''(t)y + f'(t)y*f''(t)x == 0 | |
| 114 | + // Fortunately, this turns out to be quadratic, so there are at | |
| 115 | + // most 2 inflection points. | |
| 116 | + final float a = dax * dby - dbx * day; | |
| 117 | + final float b = 2 * (cy * dax - day * cx); | |
| 118 | + final float c = cy * dbx - cx * dby; | |
| 119 | + | |
| 120 | + return Helpers.quadraticRoots(a, b, c, pts, off); | |
| 121 | + } | |
| 122 | + | |
| 123 | + // finds points where the first and second derivative are | |
| 124 | + // perpendicular. This happens when g(t) = f'(t)*f''(t) == 0 (where | |
| 125 | + // * is a dot product). Unfortunately, we have to solve a cubic. | |
| 126 | + private int perpendiculardfddf(float[] pts, int off) { | |
| 127 | + assert pts.length >= off + 4; | |
| 128 | + | |
| 129 | + // these are the coefficients of some multiple of g(t) (not g(t), | |
| 130 | + // because the roots of a polynomial are not changed after multiplication | |
| 131 | + // by a constant, and this way we save a few multiplications). | |
| 132 | + final float a = 2*(dax*dax + day*day); | |
| 133 | + final float b = 3*(dax*dbx + day*dby); | |
| 134 | + final float c = 2*(dax*cx + day*cy) + dbx*dbx + dby*dby; | |
| 135 | + final float d = dbx*cx + dby*cy; | |
| 136 | + return Helpers.cubicRootsInAB(a, b, c, d, pts, off, 0f, 1f); | |
| 137 | + } | |
| 138 | + | |
| 139 | + // Tries to find the roots of the function ROC(t)-w in [0, 1). It uses | |
| 140 | + // a variant of the false position algorithm to find the roots. False | |
| 141 | + // position requires that 2 initial values x0,x1 be given, and that the | |
| 142 | + // function must have opposite signs at those values. To find such | |
| 143 | + // values, we need the local extrema of the ROC function, for which we | |
| 144 | + // need the roots of its derivative; however, it's harder to find the | |
| 145 | + // roots of the derivative in this case than it is to find the roots | |
| 146 | + // of the original function. So, we find all points where this curve's | |
| 147 | + // first and second derivative are perpendicular, and we pretend these | |
| 148 | + // are our local extrema. There are at most 3 of these, so we will check | |
| 149 | + // at most 4 sub-intervals of (0,1). ROC has asymptotes at inflection | |
| 150 | + // points, so roc-w can have at least 6 roots. This shouldn't be a | |
| 151 | + // problem for what we're trying to do (draw a nice looking curve). | |
| 152 | + int rootsOfROCMinusW(float[] roots, int off, final float w, final float err) { | |
| 153 | + // no OOB exception, because by now off<=6, and roots.length >= 10 | |
| 154 | + assert off <= 6 && roots.length >= 10; | |
| 155 | + int ret = off; | |
| 156 | + int numPerpdfddf = perpendiculardfddf(roots, off); | |
| 157 | + float t0 = 0, ft0 = ROCsq(t0) - w*w; | |
| 158 | + roots[off + numPerpdfddf] = 1f; // always check interval end points | |
| 159 | + numPerpdfddf++; | |
| 160 | + for (int i = off; i < off + numPerpdfddf; i++) { | |
| 161 | + float t1 = roots[i], ft1 = ROCsq(t1) - w*w; | |
| 162 | + if (ft0 == 0f) { | |
| 163 | + roots[ret++] = t0; | |
| 164 | + } else if (ft1 * ft0 < 0f) { // have opposite signs | |
| 165 | + // (ROC(t)^2 == w^2) == (ROC(t) == w) is true because | |
| 166 | + // ROC(t) >= 0 for all t. | |
| 167 | + roots[ret++] = falsePositionROCsqMinusX(t0, t1, w*w, err); | |
| 168 | + } | |
| 169 | + t0 = t1; | |
| 170 | + ft0 = ft1; | |
| 171 | + } | |
| 172 | + | |
| 173 | + return ret - off; | |
| 174 | + } | |
| 175 | + | |
| 176 | + private static float eliminateInf(float x) { | |
| 177 | + return (x == Float.POSITIVE_INFINITY ? Float.MAX_VALUE : | |
| 178 | + (x == Float.NEGATIVE_INFINITY ? Float.MIN_VALUE : x)); | |
| 179 | + } | |
| 180 | + | |
| 181 | + // A slight modification of the false position algorithm on wikipedia. | |
| 182 | + // This only works for the ROCsq-x functions. It might be nice to have | |
| 183 | + // the function as an argument, but that would be awkward in java6. | |
| 184 | + // TODO: It is something to consider for java8 (or whenever lambda | |
| 185 | + // expressions make it into the language), depending on how closures | |
| 186 | + // and turn out. Same goes for the newton's method | |
| 187 | + // algorithm in Helpers.java | |
| 188 | + private float falsePositionROCsqMinusX(float x0, float x1, | |
| 189 | + final float x, final float err) | |
| 190 | + { | |
| 191 | + final int iterLimit = 100; | |
| 192 | + int side = 0; | |
| 193 | + float t = x1, ft = eliminateInf(ROCsq(t) - x); | |
| 194 | + float s = x0, fs = eliminateInf(ROCsq(s) - x); | |
| 195 | + float r = s, fr; | |
| 196 | + for (int i = 0; i < iterLimit && Math.abs(t - s) > err * Math.abs(t + s); i++) { | |
| 197 | + r = (fs * t - ft * s) / (fs - ft); | |
| 198 | + fr = ROCsq(r) - x; | |
| 199 | + if (sameSign(fr, ft)) { | |
| 200 | + ft = fr; t = r; | |
| 201 | + if (side < 0) { | |
| 202 | + fs /= (1 << (-side)); | |
| 203 | + side--; | |
| 204 | + } else { | |
| 205 | + side = -1; | |
| 206 | + } | |
| 207 | + } else if (fr * fs > 0) { | |
| 208 | + fs = fr; s = r; | |
| 209 | + if (side > 0) { | |
| 210 | + ft /= (1 << side); | |
| 211 | + side++; | |
| 212 | + } else { | |
| 213 | + side = 1; | |
| 214 | + } | |
| 215 | + } else { | |
| 216 | + break; | |
| 217 | + } | |
| 218 | + } | |
| 219 | + return r; | |
| 220 | + } | |
| 221 | + | |
| 222 | + private static boolean sameSign(double x, double y) { | |
| 223 | + // another way is to test if x*y > 0. This is bad for small x, y. | |
| 224 | + return (x < 0 && y < 0) || (x > 0 && y > 0); | |
| 225 | + } | |
| 226 | + | |
| 227 | + // returns the radius of curvature squared at t of this curve | |
| 228 | + // see http://en.wikipedia.org/wiki/Radius_of_curvature_(applications) | |
| 229 | + private float ROCsq(final float t) { | |
| 230 | + // dx=xat(t) and dy=yat(t). These calls have been inlined for efficiency | |
| 231 | + final float dx = t * (t * dax + dbx) + cx; | |
| 232 | + final float dy = t * (t * day + dby) + cy; | |
| 233 | + final float ddx = 2 * dax * t + dbx; | |
| 234 | + final float ddy = 2 * day * t + dby; | |
| 235 | + final float dx2dy2 = dx*dx + dy*dy; | |
| 236 | + final float ddx2ddy2 = ddx*ddx + ddy*ddy; | |
| 237 | + final float ddxdxddydy = ddx*dx + ddy*dy; | |
| 238 | + return dx2dy2*((dx2dy2*dx2dy2) / (dx2dy2 * ddx2ddy2 - ddxdxddydy*ddxdxddydy)); | |
| 239 | + } | |
| 240 | + | |
| 241 | + // curve to be broken should be in pts | |
| 242 | + // this will change the contents of pts but not Ts | |
| 243 | + // TODO: There's no reason for Ts to be an array. All we need is a sequence | |
| 244 | + // of t values at which to subdivide. An array statisfies this condition, | |
| 245 | + // but is unnecessarily restrictive. Ts should be an Iterator<Float> instead. | |
| 246 | + // Doing this will also make dashing easier, since we could easily make | |
| 247 | + // LengthIterator an Iterator<Float> and feed it to this function to simplify | |
| 248 | + // the loop in Dasher.somethingTo. | |
| 249 | + static Iterator<Integer> breakPtsAtTs(final float[] pts, final int type, | |
| 250 | + final float[] Ts, final int numTs) | |
| 251 | + { | |
| 252 | + assert pts.length >= 2*type && numTs <= Ts.length; | |
| 253 | + return new Iterator<Integer>() { | |
| 254 | + // these prevent object creation and destruction during autoboxing. | |
| 255 | + // Because of this, the compiler should be able to completely | |
| 256 | + // eliminate the boxing costs. | |
| 257 | + final Integer i0 = 0; | |
| 258 | + final Integer itype = type; | |
| 259 | + int nextCurveIdx = 0; | |
| 260 | + Integer curCurveOff = i0; | |
| 261 | + float prevT = 0; | |
| 262 | + | |
| 263 | + @Override public boolean hasNext() { | |
| 264 | + return nextCurveIdx < numTs + 1; | |
| 265 | + } | |
| 266 | + | |
| 267 | + @Override public Integer next() { | |
| 268 | + Integer ret; | |
| 269 | + if (nextCurveIdx < numTs) { | |
| 270 | + float curT = Ts[nextCurveIdx]; | |
| 271 | + float splitT = (curT - prevT) / (1 - prevT); | |
| 272 | + Helpers.subdivideAt(splitT, | |
| 273 | + pts, curCurveOff, | |
| 274 | + pts, 0, | |
| 275 | + pts, type, type); | |
| 276 | + prevT = curT; | |
| 277 | + ret = i0; | |
| 278 | + curCurveOff = itype; | |
| 279 | + } else { | |
| 280 | + ret = curCurveOff; | |
| 281 | + } | |
| 282 | + nextCurveIdx++; | |
| 283 | + return ret; | |
| 284 | + } | |
| 285 | + | |
| 286 | + @Override public void remove() {} | |
| 287 | + }; | |
| 288 | + } | |
| 289 | +} | |
| 290 | + | |
| @@ -0,0 +1,575 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. | |
| 3 | + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| 4 | + * | |
| 5 | + * This code is free software; you can redistribute it and/or modify it | |
| 6 | + * under the terms of the GNU General Public License version 2 only, as | |
| 7 | + * published by the Free Software Foundation. Oracle designates this | |
| 8 | + * particular file as subject to the "Classpath" exception as provided | |
| 9 | + * by Oracle in the LICENSE file that accompanied this code. | |
| 10 | + * | |
| 11 | + * This code is distributed in the hope that it will be useful, but WITHOUT | |
| 12 | + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| 13 | + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| 14 | + * version 2 for more details (a copy is included in the LICENSE file that | |
| 15 | + * accompanied this code). | |
| 16 | + * | |
| 17 | + * You should have received a copy of the GNU General Public License version | |
| 18 | + * 2 along with this work; if not, write to the Free Software Foundation, | |
| 19 | + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 20 | + * | |
| 21 | + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| 22 | + * or visit www.oracle.com if you need additional information or have any | |
| 23 | + * questions. | |
| 24 | + */ | |
| 25 | + | |
| 26 | +package sun.java2d.pisces; | |
| 27 | + | |
| 28 | +import sun.awt.geom.PathConsumer2D; | |
| 29 | + | |
| 30 | +/** | |
| 31 | + * The <code>Dasher</code> class takes a series of linear commands | |
| 32 | + * (<code>moveTo</code>, <code>lineTo</code>, <code>close</code> and | |
| 33 | + * <code>end</code>) and breaks them into smaller segments according to a | |
| 34 | + * dash pattern array and a starting dash phase. | |
| 35 | + * | |
| 36 | + * <p> Issues: in J2Se, a zero length dash segment as drawn as a very | |
| 37 | + * short dash, whereas Pisces does not draw anything. The PostScript | |
| 38 | + * semantics are unclear. | |
| 39 | + * | |
| 40 | + */ | |
| 41 | +final class Dasher implements sun.awt.geom.PathConsumer2D { | |
| 42 | + | |
| 43 | + private final PathConsumer2D out; | |
| 44 | + private final float[] dash; | |
| 45 | + private final float startPhase; | |
| 46 | + private final boolean startDashOn; | |
| 47 | + private final int startIdx; | |
| 48 | + | |
| 49 | + private boolean starting; | |
| 50 | + private boolean needsMoveTo; | |
| 51 | + | |
| 52 | + private int idx; | |
| 53 | + private boolean dashOn; | |
| 54 | + private float phase; | |
| 55 | + | |
| 56 | + private float sx, sy; | |
| 57 | + private float x0, y0; | |
| 58 | + | |
| 59 | + // temporary storage for the current curve | |
| 60 | + private float[] curCurvepts; | |
| 61 | + | |
| 62 | + /** | |
| 63 | + * Constructs a <code>Dasher</code>. | |
| 64 | + * | |
| 65 | + * @param out an output <code>PathConsumer2D</code>. | |
| 66 | + * @param dash an array of <code>float</code>s containing the dash pattern | |
| 67 | + * @param phase a <code>float</code> containing the dash phase | |
| 68 | + */ | |
| 69 | + public Dasher(PathConsumer2D out, float[] dash, float phase) { | |
| 70 | + if (phase < 0) { | |
| 71 | + throw new IllegalArgumentException("phase < 0 !"); | |
| 72 | + } | |
| 73 | + | |
| 74 | + this.out = out; | |
| 75 | + | |
| 76 | + // Normalize so 0 <= phase < dash[0] | |
| 77 | + int idx = 0; | |
| 78 | + dashOn = true; | |
| 79 | + float d; | |
| 80 | + while (phase >= (d = dash[idx])) { | |
| 81 | + phase -= d; | |
| 82 | + idx = (idx + 1) % dash.length; | |
| 83 | + dashOn = !dashOn; | |
| 84 | + } | |
| 85 | + | |
| 86 | + this.dash = dash; | |
| 87 | + this.startPhase = this.phase = phase; | |
| 88 | + this.startDashOn = dashOn; | |
| 89 | + this.startIdx = idx; | |
| 90 | + this.starting = true; | |
| 91 | + | |
| 92 | + // we need curCurvepts to be able to contain 2 curves because when | |
| 93 | + // dashing curves, we need to subdivide it | |
| 94 | + curCurvepts = new float[8 * 2]; | |
| 95 | + } | |
| 96 | + | |
| 97 | + public void moveTo(float x0, float y0) { | |
| 98 | + if (firstSegidx > 0) { | |
| 99 | + out.moveTo(sx, sy); | |
| 100 | + emitFirstSegments(); | |
| 101 | + } | |
| 102 | + needsMoveTo = true; | |
| 103 | + this.idx = startIdx; | |
| 104 | + this.dashOn = this.startDashOn; | |
| 105 | + this.phase = this.startPhase; | |
| 106 | + this.sx = this.x0 = x0; | |
| 107 | + this.sy = this.y0 = y0; | |
| 108 | + this.starting = true; | |
| 109 | + } | |
| 110 | + | |
| 111 | + private void emitSeg(float[] buf, int off, int type) { | |
| 112 | + switch (type) { | |
| 113 | + case 8: | |
| 114 | + out.curveTo(buf[off+0], buf[off+1], | |
| 115 | + buf[off+2], buf[off+3], | |
| 116 | + buf[off+4], buf[off+5]); | |
| 117 | + break; | |
| 118 | + case 6: | |
| 119 | + out.quadTo(buf[off+0], buf[off+1], | |
| 120 | + buf[off+2], buf[off+3]); | |
| 121 | + break; | |
| 122 | + case 4: | |
| 123 | + out.lineTo(buf[off], buf[off+1]); | |
| 124 | + } | |
| 125 | + } | |
| 126 | + | |
| 127 | + private void emitFirstSegments() { | |
| 128 | + for (int i = 0; i < firstSegidx; ) { | |
| 129 | + emitSeg(firstSegmentsBuffer, i+1, (int)firstSegmentsBuffer[i]); | |
| 130 | + i += (((int)firstSegmentsBuffer[i]) - 1); | |
| 131 | + } | |
| 132 | + firstSegidx = 0; | |
| 133 | + } | |
| 134 | + | |
| 135 | + // We don't emit the first dash right away. If we did, caps would be | |
| 136 | + // drawn on it, but we need joins to be drawn if there's a closePath() | |
| 137 | + // So, we store the path elements that make up the first dash in the | |
| 138 | + // buffer below. | |
| 139 | + private float[] firstSegmentsBuffer = new float[7]; | |
| 140 | + private int firstSegidx = 0; | |
| 141 | + // precondition: pts must be in relative coordinates (relative to x0,y0) | |
| 142 | + // fullCurve is true iff the curve in pts has not been split. | |
| 143 | + private void goTo(float[] pts, int off, final int type) { | |
| 144 | + float x = pts[off + type - 4]; | |
| 145 | + float y = pts[off + type - 3]; | |
| 146 | + if (dashOn) { | |
| 147 | + if (starting) { | |
| 148 | + firstSegmentsBuffer = Helpers.widenArray(firstSegmentsBuffer, | |
| 149 | + firstSegidx, type - 2); | |
| 150 | + firstSegmentsBuffer[firstSegidx++] = type; | |
| 151 | + System.arraycopy(pts, off, firstSegmentsBuffer, firstSegidx, type - 2); | |
| 152 | + firstSegidx += type - 2; | |
| 153 | + } else { | |
| 154 | + if (needsMoveTo) { | |
| 155 | + out.moveTo(x0, y0); | |
| 156 | + needsMoveTo = false; | |
| 157 | + } | |
| 158 | + emitSeg(pts, off, type); | |
| 159 | + } | |
| 160 | + } else { | |
| 161 | + starting = false; | |
| 162 | + needsMoveTo = true; | |
| 163 | + } | |
| 164 | + this.x0 = x; | |
| 165 | + this.y0 = y; | |
| 166 | + } | |
| 167 | + | |
| 168 | + public void lineTo(float x1, float y1) { | |
| 169 | + float dx = x1 - x0; | |
| 170 | + float dy = y1 - y0; | |
| 171 | + | |
| 172 | + float len = (float) Math.sqrt(dx*dx + dy*dy); | |
| 173 | + | |
| 174 | + if (len == 0) { | |
| 175 | + return; | |
| 176 | + } | |
| 177 | + | |
| 178 | + // The scaling factors needed to get the dx and dy of the | |
| 179 | + // transformed dash segments. | |
| 180 | + float cx = dx / len; | |
| 181 | + float cy = dy / len; | |
| 182 | + | |
| 183 | + while (true) { | |
| 184 | + float leftInThisDashSegment = dash[idx] - phase; | |
| 185 | + if (len <= leftInThisDashSegment) { | |
| 186 | + curCurvepts[0] = x1; | |
| 187 | + curCurvepts[1] = y1; | |
| 188 | + goTo(curCurvepts, 0, 4); | |
| 189 | + // Advance phase within current dash segment | |
| 190 | + phase += len; | |
| 191 | + if (len == leftInThisDashSegment) { | |
| 192 | + phase = 0f; | |
| 193 | + idx = (idx + 1) % dash.length; | |
| 194 | + dashOn = !dashOn; | |
| 195 | + } | |
| 196 | + return; | |
| 197 | + } | |
| 198 | + | |
| 199 | + float dashdx = dash[idx] * cx; | |
| 200 | + float dashdy = dash[idx] * cy; | |
| 201 | + if (phase == 0) { | |
| 202 | + curCurvepts[0] = x0 + dashdx; | |
| 203 | + curCurvepts[1] = y0 + dashdy; | |
| 204 | + } else { | |
| 205 | + float p = leftInThisDashSegment / dash[idx]; | |
| 206 | + curCurvepts[0] = x0 + p * dashdx; | |
| 207 | + curCurvepts[1] = y0 + p * dashdy; | |
| 208 | + } | |
| 209 | + | |
| 210 | + goTo(curCurvepts, 0, 4); | |
| 211 | + | |
| 212 | + len -= leftInThisDashSegment; | |
| 213 | + // Advance to next dash segment | |
| 214 | + idx = (idx + 1) % dash.length; | |
| 215 | + dashOn = !dashOn; | |
| 216 | + phase = 0; | |
| 217 | + } | |
| 218 | + } | |
| 219 | + | |
| 220 | + private LengthIterator li = null; | |
| 221 | + | |
| 222 | + // preconditions: curCurvepts must be an array of length at least 2 * type, | |
| 223 | + // that contains the curve we want to dash in the first type elements | |
| 224 | + private void somethingTo(int type) { | |
| 225 | + if (pointCurve(curCurvepts, type)) { | |
| 226 | + return; | |
| 227 | + } | |
| 228 | + if (li == null) { | |
| 229 | + li = new LengthIterator(4, 0.01f); | |
| 230 | + } | |
| 231 | + li.initializeIterationOnCurve(curCurvepts, type); | |
| 232 | + | |
| 233 | + int curCurveoff = 0; // initially the current curve is at curCurvepts[0...type] | |
| 234 | + float lastSplitT = 0; | |
| 235 | + float t = 0; | |
| 236 | + float leftInThisDashSegment = dash[idx] - phase; | |
| 237 | + while ((t = li.next(leftInThisDashSegment)) < 1) { | |
| 238 | + if (t != 0) { | |
| 239 | + Helpers.subdivideAt((t - lastSplitT) / (1 - lastSplitT), | |
| 240 | + curCurvepts, curCurveoff, | |
| 241 | + curCurvepts, 0, | |
| 242 | + curCurvepts, type, type); | |
| 243 | + lastSplitT = t; | |
| 244 | + goTo(curCurvepts, 2, type); | |
| 245 | + curCurveoff = type; | |
| 246 | + } | |
| 247 | + // Advance to next dash segment | |
| 248 | + idx = (idx + 1) % dash.length; | |
| 249 | + dashOn = !dashOn; | |
| 250 | + phase = 0; | |
| 251 | + leftInThisDashSegment = dash[idx]; | |
| 252 | + } | |
| 253 | + goTo(curCurvepts, curCurveoff+2, type); | |
| 254 | + phase += li.lastSegLen(); | |
| 255 | + if (phase >= dash[idx]) { | |
| 256 | + phase = 0f; | |
| 257 | + idx = (idx + 1) % dash.length; | |
| 258 | + dashOn = !dashOn; | |
| 259 | + } | |
| 260 | + } | |
| 261 | + | |
| 262 | + private static boolean pointCurve(float[] curve, int type) { | |
| 263 | + for (int i = 2; i < type; i++) { | |
| 264 | + if (curve[i] != curve[i-2]) { | |
| 265 | + return false; | |
| 266 | + } | |
| 267 | + } | |
| 268 | + return true; | |
| 269 | + } | |
| 270 | + | |
| 271 | + // Objects of this class are used to iterate through curves. They return | |
| 272 | + // t values where the left side of the curve has a specified length. | |
| 273 | + // It does this by subdividing the input curve until a certain error | |
| 274 | + // condition has been met. A recursive subdivision procedure would | |
| 275 | + // return as many as 1<<limit curves, but this is an iterator and we | |
| 276 | + // don't need all the curves all at once, so what we carry out a | |
| 277 | + // lazy inorder traversal of the recursion tree (meaning we only move | |
| 278 | + // through the tree when we need the next subdivided curve). This saves | |
| 279 | + // us a lot of memory because at any one time we only need to store | |
| 280 | + // limit+1 curves - one for each level of the tree + 1. | |
| 281 | + // NOTE: the way we do things here is not enough to traverse a general | |
| 282 | + // tree; however, the trees we are interested in have the property that | |
| 283 | + // every non leaf node has exactly 2 children | |
| 284 | + private static class LengthIterator { | |
| 285 | + private enum Side {LEFT, RIGHT}; | |
| 286 | + // Holds the curves at various levels of the recursion. The root | |
| 287 | + // (i.e. the original curve) is at recCurveStack[0] (but then it | |
| 288 | + // gets subdivided, the left half is put at 1, so most of the time | |
| 289 | + // only the right half of the original curve is at 0) | |
| 290 | + private float[][] recCurveStack; | |
| 291 | + // sides[i] indicates whether the node at level i+1 in the path from | |
| 292 | + // the root to the current leaf is a left or right child of its parent. | |
| 293 | + private Side[] sides; | |
| 294 | + private int curveType; | |
| 295 | + private final int limit; | |
| 296 | + private final float ERR; | |
| 297 | + private final float minTincrement; | |
| 298 | + // lastT and nextT delimit the current leaf. | |
| 299 | + private float nextT; | |
| 300 | + private float lenAtNextT; | |
| 301 | + private float lastT; | |
| 302 | + private float lenAtLastT; | |
| 303 | + private float lenAtLastSplit; | |
| 304 | + private float lastSegLen; | |
| 305 | + // the current level in the recursion tree. 0 is the root. limit | |
| 306 | + // is the deepest possible leaf. | |
| 307 | + private int recLevel; | |
| 308 | + private boolean done; | |
| 309 | + | |
| 310 | + // the lengths of the lines of the control polygon. Only its first | |
| 311 | + // curveType/2 - 1 elements are valid. This is an optimization. See | |
| 312 | + // next(float) for more detail. | |
| 313 | + private float[] curLeafCtrlPolyLengths = new float[3]; | |
| 314 | + | |
| 315 | + public LengthIterator(int reclimit, float err) { | |
| 316 | + this.limit = reclimit; | |
| 317 | + this.minTincrement = 1f / (1 << limit); | |
| 318 | + this.ERR = err; | |
| 319 | + this.recCurveStack = new float[reclimit+1][8]; | |
| 320 | + this.sides = new Side[reclimit]; | |
| 321 | + // if any methods are called without first initializing this object on | |
| 322 | + // a curve, we want it to fail ASAP. | |
| 323 | + this.nextT = Float.MAX_VALUE; | |
| 324 | + this.lenAtNextT = Float.MAX_VALUE; | |
| 325 | + this.lenAtLastSplit = Float.MIN_VALUE; | |
| 326 | + this.recLevel = Integer.MIN_VALUE; | |
| 327 | + this.lastSegLen = Float.MAX_VALUE; | |
| 328 | + this.done = true; | |
| 329 | + } | |
| 330 | + | |
| 331 | + public void initializeIterationOnCurve(float[] pts, int type) { | |
| 332 | + System.arraycopy(pts, 0, recCurveStack[0], 0, type); | |
| 333 | + this.curveType = type; | |
| 334 | + this.recLevel = 0; | |
| 335 | + this.lastT = 0; | |
| 336 | + this.lenAtLastT = 0; | |
| 337 | + this.nextT = 0; | |
| 338 | + this.lenAtNextT = 0; | |
| 339 | + goLeft(); // initializes nextT and lenAtNextT properly | |
| 340 | + this.lenAtLastSplit = 0; | |
| 341 | + if (recLevel > 0) { | |
| 342 | + this.sides[0] = Side.LEFT; | |
| 343 | + this.done = false; | |
| 344 | + } else { | |
| 345 | + // the root of the tree is a leaf so we're done. | |
| 346 | + this.sides[0] = Side.RIGHT; | |
| 347 | + this.done = true; | |
| 348 | + } | |
| 349 | + this.lastSegLen = 0; | |
| 350 | + } | |
| 351 | + | |
| 352 | + // 0 == false, 1 == true, -1 == invalid cached value. | |
| 353 | + private int cachedHaveLowAcceleration = -1; | |
| 354 | + | |
| 355 | + private boolean haveLowAcceleration(float err) { | |
| 356 | + if (cachedHaveLowAcceleration == -1) { | |
| 357 | + final float len1 = curLeafCtrlPolyLengths[0]; | |
| 358 | + final float len2 = curLeafCtrlPolyLengths[1]; | |
| 359 | + // the test below is equivalent to !within(len1/len2, 1, err). | |
| 360 | + // It is using a multiplication instead of a division, so it | |
| 361 | + // should be a bit faster. | |
| 362 | + if (!Helpers.within(len1, len2, err*len2)) { | |
| 363 | + cachedHaveLowAcceleration = 0; | |
| 364 | + return false; | |
| 365 | + } | |
| 366 | + if (curveType == 8) { | |
| 367 | + final float len3 = curLeafCtrlPolyLengths[2]; | |
| 368 | + // if len1 is close to 2 and 2 is close to 3, that probably | |
| 369 | + // means 1 is close to 3 so the second part of this test might | |
| 370 | + // not be needed, but it doesn't hurt to include it. | |
| 371 | + if (!(Helpers.within(len2, len3, err*len3) && | |
| 372 | + Helpers.within(len1, len3, err*len3))) { | |
| 373 | + cachedHaveLowAcceleration = 0; | |
| 374 | + return false; | |
| 375 | + } | |
| 376 | + } | |
| 377 | + cachedHaveLowAcceleration = 1; | |
| 378 | + return true; | |
| 379 | + } | |
| 380 | + | |
| 381 | + return (cachedHaveLowAcceleration == 1); | |
| 382 | + } | |
| 383 | + | |
| 384 | + // we want to avoid allocations/gc so we keep this array so we | |
| 385 | + // can put roots in it, | |
| 386 | + private float[] nextRoots = new float[4]; | |
| 387 | + | |
| 388 | + // caches the coefficients of the current leaf in its flattened | |
| 389 | + // form (see inside next() for what that means). The cache is | |
| 390 | + // invalid when it's third element is negative, since in any | |
| 391 | + // valid flattened curve, this would be >= 0. | |
| 392 | + private float[] flatLeafCoefCache = new float[] {0, 0, -1, 0}; | |
| 393 | + // returns the t value where the remaining curve should be split in | |
| 394 | + // order for the left subdivided curve to have length len. If len | |
| 395 | + // is >= than the length of the uniterated curve, it returns 1. | |
| 396 | + public float next(final float len) { | |
| 397 | + final float targetLength = lenAtLastSplit + len; | |
| 398 | + while(lenAtNextT < targetLength) { | |
| 399 | + if (done) { | |
| 400 | + lastSegLen = lenAtNextT - lenAtLastSplit; | |
| 401 | + return 1; | |
| 402 | + } | |
| 403 | + goToNextLeaf(); | |
| 404 | + } | |
| 405 | + lenAtLastSplit = targetLength; | |
| 406 | + final float leaflen = lenAtNextT - lenAtLastT; | |
| 407 | + float t = (targetLength - lenAtLastT) / leaflen; | |
| 408 | + | |
| 409 | + // cubicRootsInAB is a fairly expensive call, so we just don't do it | |
| 410 | + // if the acceleration in this section of the curve is small enough. | |
| 411 | + if (!haveLowAcceleration(0.05f)) { | |
| 412 | + // We flatten the current leaf along the x axis, so that we're | |
| 413 | + // left with a, b, c which define a 1D Bezier curve. We then | |
| 414 | + // solve this to get the parameter of the original leaf that | |
| 415 | + // gives us the desired length. | |
| 416 | + | |
| 417 | + if (flatLeafCoefCache[2] < 0) { | |
| 418 | + float x = 0+curLeafCtrlPolyLengths[0], | |
| 419 | + y = x+curLeafCtrlPolyLengths[1]; | |
| 420 | + if (curveType == 8) { | |
| 421 | + float z = y + curLeafCtrlPolyLengths[2]; | |
| 422 | + flatLeafCoefCache[0] = 3*(x - y) + z; | |
| 423 | + flatLeafCoefCache[1] = 3*(y - 2*x); | |
| 424 | + flatLeafCoefCache[2] = 3*x; | |
| 425 | + flatLeafCoefCache[3] = -z; | |
| 426 | + } else if (curveType == 6) { | |
| 427 | + flatLeafCoefCache[0] = 0f; | |
| 428 | + flatLeafCoefCache[1] = y - 2*x; | |
| 429 | + flatLeafCoefCache[2] = 2*x; | |
| 430 | + flatLeafCoefCache[3] = -y; | |
| 431 | + } | |
| 432 | + } | |
| 433 | + float a = flatLeafCoefCache[0]; | |
| 434 | + float b = flatLeafCoefCache[1]; | |
| 435 | + float c = flatLeafCoefCache[2]; | |
| 436 | + float d = t*flatLeafCoefCache[3]; | |
| 437 | + | |
| 438 | + // we use cubicRootsInAB here, because we want only roots in 0, 1, | |
| 439 | + // and our quadratic root finder doesn't filter, so it's just a | |
| 440 | + // matter of convenience. | |
| 441 | + int n = Helpers.cubicRootsInAB(a, b, c, d, nextRoots, 0, 0, 1); | |
| 442 | + if (n == 1 && !Float.isNaN(nextRoots[0])) { | |
| 443 | + t = nextRoots[0]; | |
| 444 | + } | |
| 445 | + } | |
| 446 | + // t is relative to the current leaf, so we must make it a valid parameter | |
| 447 | + // of the original curve. | |
| 448 | + t = t * (nextT - lastT) + lastT; | |
| 449 | + if (t >= 1) { | |
| 450 | + t = 1; | |
| 451 | + done = true; | |
| 452 | + } | |
| 453 | + // even if done = true, if we're here, that means targetLength | |
| 454 | + // is equal to, or very, very close to the total length of the | |
| 455 | + // curve, so lastSegLen won't be too high. In cases where len | |
| 456 | + // overshoots the curve, this method will exit in the while | |
| 457 | + // loop, and lastSegLen will still be set to the right value. | |
| 458 | + lastSegLen = len; | |
| 459 | + return t; | |
| 460 | + } | |
| 461 | + | |
| 462 | + public float lastSegLen() { | |
| 463 | + return lastSegLen; | |
| 464 | + } | |
| 465 | + | |
| 466 | + // go to the next leaf (in an inorder traversal) in the recursion tree | |
| 467 | + // preconditions: must be on a leaf, and that leaf must not be the root. | |
| 468 | + private void goToNextLeaf() { | |
| 469 | + // We must go to the first ancestor node that has an unvisited | |
| 470 | + // right child. | |
| 471 | + recLevel--; | |
| 472 | + while(sides[recLevel] == Side.RIGHT) { | |
| 473 | + if (recLevel == 0) { | |
| 474 | + done = true; | |
| 475 | + return; | |
| 476 | + } | |
| 477 | + recLevel--; | |
| 478 | + } | |
| 479 | + | |
| 480 | + sides[recLevel] = Side.RIGHT; | |
| 481 | + System.arraycopy(recCurveStack[recLevel], 0, recCurveStack[recLevel+1], 0, curveType); | |
| 482 | + recLevel++; | |
| 483 | + goLeft(); | |
| 484 | + } | |
| 485 | + | |
| 486 | + // go to the leftmost node from the current node. Return its length. | |
| 487 | + private void goLeft() { | |
| 488 | + float len = onLeaf(); | |
| 489 | + if (len >= 0) { | |
| 490 | + lastT = nextT; | |
| 491 | + lenAtLastT = lenAtNextT; | |
| 492 | + nextT += (1 << (limit - recLevel)) * minTincrement; | |
| 493 | + lenAtNextT += len; | |
| 494 | + // invalidate caches | |
| 495 | + flatLeafCoefCache[2] = -1; | |
| 496 | + cachedHaveLowAcceleration = -1; | |
| 497 | + } else { | |
| 498 | + Helpers.subdivide(recCurveStack[recLevel], 0, | |
| 499 | + recCurveStack[recLevel+1], 0, | |
| 500 | + recCurveStack[recLevel], 0, curveType); | |
| 501 | + sides[recLevel] = Side.LEFT; | |
| 502 | + recLevel++; | |
| 503 | + goLeft(); | |
| 504 | + } | |
| 505 | + } | |
| 506 | + | |
| 507 | + // this is a bit of a hack. It returns -1 if we're not on a leaf, and | |
| 508 | + // the length of the leaf if we are on a leaf. | |
| 509 | + private float onLeaf() { | |
| 510 | + float[] curve = recCurveStack[recLevel]; | |
| 511 | + float polyLen = 0; | |
| 512 | + | |
| 513 | + float x0 = curve[0], y0 = curve[1]; | |
| 514 | + for (int i = 2; i < curveType; i += 2) { | |
| 515 | + final float x1 = curve[i], y1 = curve[i+1]; | |
| 516 | + final float len = Helpers.linelen(x0, y0, x1, y1); | |
| 517 | + polyLen += len; | |
| 518 | + curLeafCtrlPolyLengths[i/2 - 1] = len; | |
| 519 | + x0 = x1; | |
| 520 | + y0 = y1; | |
| 521 | + } | |
| 522 | + | |
| 523 | + final float lineLen = Helpers.linelen(curve[0], curve[1], curve[curveType-2], curve[curveType-1]); | |
| 524 | + if (polyLen - lineLen < ERR || recLevel == limit) { | |
| 525 | + return (polyLen + lineLen)/2; | |
| 526 | + } | |
| 527 | + return -1; | |
| 528 | + } | |
| 529 | + } | |
| 530 | + | |
| 531 | + @Override | |
| 532 | + public void curveTo(float x1, float y1, | |
| 533 | + float x2, float y2, | |
| 534 | + float x3, float y3) | |
| 535 | + { | |
| 536 | + curCurvepts[0] = x0; curCurvepts[1] = y0; | |
| 537 | + curCurvepts[2] = x1; curCurvepts[3] = y1; | |
| 538 | + curCurvepts[4] = x2; curCurvepts[5] = y2; | |
| 539 | + curCurvepts[6] = x3; curCurvepts[7] = y3; | |
| 540 | + somethingTo(8); | |
| 541 | + } | |
| 542 | + | |
| 543 | + @Override | |
| 544 | + public void quadTo(float x1, float y1, float x2, float y2) { | |
| 545 | + curCurvepts[0] = x0; curCurvepts[1] = y0; | |
| 546 | + curCurvepts[2] = x1; curCurvepts[3] = y1; | |
| 547 | + curCurvepts[4] = x2; curCurvepts[5] = y2; | |
| 548 | + somethingTo(6); | |
| 549 | + } | |
| 550 | + | |
| 551 | + public void closePath() { | |
| 552 | + lineTo(sx, sy); | |
| 553 | + if (firstSegidx > 0) { | |
| 554 | + if (!dashOn || needsMoveTo) { | |
| 555 | + out.moveTo(sx, sy); | |
| 556 | + } | |
| 557 | + emitFirstSegments(); | |
| 558 | + } | |
| 559 | + moveTo(sx, sy); | |
| 560 | + } | |
| 561 | + | |
| 562 | + public void pathDone() { | |
| 563 | + if (firstSegidx > 0) { | |
| 564 | + out.moveTo(sx, sy); | |
| 565 | + emitFirstSegments(); | |
| 566 | + } | |
| 567 | + out.pathDone(); | |
| 568 | + } | |
| 569 | + | |
| 570 | + @Override | |
| 571 | + public long getNativeConsumer() { | |
| 572 | + throw new InternalError("Dasher does not use a native consumer"); | |
| 573 | + } | |
| 574 | +} | |
| 575 | + | |
| @@ -0,0 +1,458 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. | |
| 3 | + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| 4 | + * | |
| 5 | + * This code is free software; you can redistribute it and/or modify it | |
| 6 | + * under the terms of the GNU General Public License version 2 only, as | |
| 7 | + * published by the Free Software Foundation. Oracle designates this | |
| 8 | + * particular file as subject to the "Classpath" exception as provided | |
| 9 | + * by Oracle in the LICENSE file that accompanied this code. | |
| 10 | + * | |
| 11 | + * This code is distributed in the hope that it will be useful, but WITHOUT | |
| 12 | + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| 13 | + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| 14 | + * version 2 for more details (a copy is included in the LICENSE file that | |
| 15 | + * accompanied this code). | |
| 16 | + * | |
| 17 | + * You should have received a copy of the GNU General Public License version | |
| 18 | + * 2 along with this work; if not, write to the Free Software Foundation, | |
| 19 | + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 20 | + * | |
| 21 | + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| 22 | + * or visit www.oracle.com if you need additional information or have any | |
| 23 | + * questions. | |
| 24 | + */ | |
| 25 | + | |
| 26 | +package sun.java2d.pisces; | |
| 27 | + | |
| 28 | +import java.util.Arrays; | |
| 29 | +import static java.lang.Math.PI; | |
| 30 | +import static java.lang.Math.cos; | |
| 31 | +import static java.lang.Math.sqrt; | |
| 32 | +import static java.lang.Math.cbrt; | |
| 33 | +import static java.lang.Math.acos; | |
| 34 | + | |
| 35 | + | |
| 36 | +final class Helpers { | |
| 37 | + private Helpers() { | |
| 38 | + throw new Error("This is a non instantiable class"); | |
| 39 | + } | |
| 40 | + | |
| 41 | + static boolean within(final float x, final float y, final float err) { | |
| 42 | + final float d = y - x; | |
| 43 | + return (d <= err && d >= -err); | |
| 44 | + } | |
| 45 | + | |
| 46 | + static boolean within(final double x, final double y, final double err) { | |
| 47 | + final double d = y - x; | |
| 48 | + return (d <= err && d >= -err); | |
| 49 | + } | |
| 50 | + | |
| 51 | + static int quadraticRoots(final float a, final float b, | |
| 52 | + final float c, float[] zeroes, final int off) | |
| 53 | + { | |
| 54 | + int ret = off; | |
| 55 | + float t; | |
| 56 | + if (a != 0f) { | |
| 57 | + final float dis = b*b - 4*a*c; | |
| 58 | + if (dis > 0) { | |
| 59 | + final float sqrtDis = (float)Math.sqrt(dis); | |
| 60 | + // depending on the sign of b we use a slightly different | |
| 61 | + // algorithm than the traditional one to find one of the roots | |
| 62 | + // so we can avoid adding numbers of different signs (which | |
| 63 | + // might result in loss of precision). | |
| 64 | + if (b >= 0) { | |
| 65 | + zeroes[ret++] = (2 * c) / (-b - sqrtDis); | |
| 66 | + zeroes[ret++] = (-b - sqrtDis) / (2 * a); | |
| 67 | + } else { | |
| 68 | + zeroes[ret++] = (-b + sqrtDis) / (2 * a); | |
| 69 | + zeroes[ret++] = (2 * c) / (-b + sqrtDis); | |
| 70 | + } | |
| 71 | + } else if (dis == 0f) { | |
| 72 | + t = (-b) / (2 * a); | |
| 73 | + zeroes[ret++] = t; | |
| 74 | + } | |
| 75 | + } else { | |
| 76 | + if (b != 0f) { | |
| 77 | + t = (-c) / b; | |
| 78 | + zeroes[ret++] = t; | |
| 79 | + } | |
| 80 | + } | |
| 81 | + return ret - off; | |
| 82 | + } | |
| 83 | + | |
| 84 | + // find the roots of g(t) = d*t^3 + a*t^2 + b*t + c in [A,B) | |
| 85 | + static int cubicRootsInAB(float d, float a, float b, float c, | |
| 86 | + float[] pts, final int off, | |
| 87 | + final float A, final float B) | |
| 88 | + { | |
| 89 | + if (d == 0) { | |
| 90 | + int num = quadraticRoots(a, b, c, pts, off); | |
| 91 | + return filterOutNotInAB(pts, off, num, A, B) - off; | |
| 92 | + } | |
| 93 | + // From Graphics Gems: | |
| 94 | + // http://tog.acm.org/resources/GraphicsGems/gems/Roots3And4.c | |
| 95 | + // (also from awt.geom.CubicCurve2D. But here we don't need as | |
| 96 | + // much accuracy and we don't want to create arrays so we use | |
| 97 | + // our own customized version). | |
| 98 | + | |
| 99 | + /* normal form: x^3 + ax^2 + bx + c = 0 */ | |
| 100 | + a /= d; | |
| 101 | + b /= d; | |
| 102 | + c /= d; | |
| 103 | + | |
| 104 | + // substitute x = y - A/3 to eliminate quadratic term: | |
| 105 | + // x^3 +Px + Q = 0 | |
| 106 | + // | |
| 107 | + // Since we actually need P/3 and Q/2 for all of the | |
| 108 | + // calculations that follow, we will calculate | |
| 109 | + // p = P/3 | |
| 110 | + // q = Q/2 | |
| 111 | + // instead and use those values for simplicity of the code. | |
| 112 | + double sq_A = a * a; | |
| 113 | + double p = 1.0/3 * (-1.0/3 * sq_A + b); | |
| 114 | + double q = 1.0/2 * (2.0/27 * a * sq_A - 1.0/3 * a * b + c); | |
| 115 | + | |
| 116 | + /* use Cardano's formula */ | |
| 117 | + | |
| 118 | + double cb_p = p * p * p; | |
| 119 | + double D = q * q + cb_p; | |
| 120 | + | |
| 121 | + int num; | |
| 122 | + if (D < 0) { | |
| 123 | + // see: http://en.wikipedia.org/wiki/Cubic_function#Trigonometric_.28and_hyperbolic.29_method | |
| 124 | + final double phi = 1.0/3 * acos(-q / sqrt(-cb_p)); | |
| 125 | + final double t = 2 * sqrt(-p); | |
| 126 | + | |
| 127 | + pts[ off+0 ] = (float)( t * cos(phi)); | |
| 128 | + pts[ off+1 ] = (float)(-t * cos(phi + PI / 3)); | |
| 129 | + pts[ off+2 ] = (float)(-t * cos(phi - PI / 3)); | |
| 130 | + num = 3; | |
| 131 | + } else { | |
| 132 | + final double sqrt_D = sqrt(D); | |
| 133 | + final double u = cbrt(sqrt_D - q); | |
| 134 | + final double v = - cbrt(sqrt_D + q); | |
| 135 | + | |
| 136 | + pts[ off ] = (float)(u + v); | |
| 137 | + num = 1; | |
| 138 | + | |
| 139 | + if (within(D, 0, 1e-8)) { | |
| 140 | + pts[off+1] = -(pts[off] / 2); | |
| 141 | + num = 2; | |
| 142 | + } | |
| 143 | + } | |
| 144 | + | |
| 145 | + final float sub = 1.0f/3 * a; | |
| 146 | + | |
| 147 | + for (int i = 0; i < num; ++i) { | |
| 148 | + pts[ off+i ] -= sub; | |
| 149 | + } | |
| 150 | + | |
| 151 | + return filterOutNotInAB(pts, off, num, A, B) - off; | |
| 152 | + } | |
| 153 | + | |
| 154 | + // These use a hardcoded factor of 2 for increasing sizes. Perhaps this | |
| 155 | + // should be provided as an argument. | |
| 156 | + static float[] widenArray(float[] in, final int cursize, final int numToAdd) { | |
| 157 | + if (in.length >= cursize + numToAdd) { | |
| 158 | + return in; | |
| 159 | + } | |
| 160 | + return Arrays.copyOf(in, 2 * (cursize + numToAdd)); | |
| 161 | + } | |
| 162 | + | |
| 163 | + static int[] widenArray(int[] in, final int cursize, final int numToAdd) { | |
| 164 | + if (in.length >= cursize + numToAdd) { | |
| 165 | + return in; | |
| 166 | + } | |
| 167 | + return Arrays.copyOf(in, 2 * (cursize + numToAdd)); | |
| 168 | + } | |
| 169 | + | |
| 170 | + static float evalCubic(final float a, final float b, | |
| 171 | + final float c, final float d, | |
| 172 | + final float t) | |
| 173 | + { | |
| 174 | + return t * (t * (t * a + b) + c) + d; | |
| 175 | + } | |
| 176 | + | |
| 177 | + static float evalQuad(final float a, final float b, | |
| 178 | + final float c, final float t) | |
| 179 | + { | |
| 180 | + return t * (t * a + b) + c; | |
| 181 | + } | |
| 182 | + | |
| 183 | + // returns the index 1 past the last valid element remaining after filtering | |
| 184 | + static int filterOutNotInAB(float[] nums, final int off, final int len, | |
| 185 | + final float a, final float b) | |
| 186 | + { | |
| 187 | + int ret = off; | |
| 188 | + for (int i = off; i < off + len; i++) { | |
| 189 | + if (nums[i] >= a && nums[i] < b) { | |
| 190 | + nums[ret++] = nums[i]; | |
| 191 | + } | |
| 192 | + } | |
| 193 | + return ret; | |
| 194 | + } | |
| 195 | + | |
| 196 | + static float polyLineLength(float[] poly, final int off, final int nCoords) { | |
| 197 | + assert nCoords % 2 == 0 && poly.length >= off + nCoords : ""; | |
| 198 | + float acc = 0; | |
| 199 | + for (int i = off + 2; i < off + nCoords; i += 2) { | |
| 200 | + acc += linelen(poly[i], poly[i+1], poly[i-2], poly[i-1]); | |
| 201 | + } | |
| 202 | + return acc; | |
| 203 | + } | |
| 204 | + | |
| 205 | + static float linelen(float x1, float y1, float x2, float y2) { | |
| 206 | + final float dx = x2 - x1; | |
| 207 | + final float dy = y2 - y1; | |
| 208 | + return (float)Math.sqrt(dx*dx + dy*dy); | |
| 209 | + } | |
| 210 | + | |
| 211 | + static void subdivide(float[] src, int srcoff, float[] left, int leftoff, | |
| 212 | + float[] right, int rightoff, int type) | |
| 213 | + { | |
| 214 | + switch(type) { | |
| 215 | + case 6: | |
| 216 | + Helpers.subdivideQuad(src, srcoff, left, leftoff, right, rightoff); | |
| 217 | + break; | |
| 218 | + case 8: | |
| 219 | + Helpers.subdivideCubic(src, srcoff, left, leftoff, right, rightoff); | |
| 220 | + break; | |
| 221 | + default: | |
| 222 | + throw new InternalError("Unsupported curve type"); | |
| 223 | + } | |
| 224 | + } | |
| 225 | + | |
| 226 | + static void isort(float[] a, int off, int len) { | |
| 227 | + for (int i = off + 1; i < off + len; i++) { | |
| 228 | + float ai = a[i]; | |
| 229 | + int j = i - 1; | |
| 230 | + for (; j >= off && a[j] > ai; j--) { | |
| 231 | + a[j+1] = a[j]; | |
| 232 | + } | |
| 233 | + a[j+1] = ai; | |
| 234 | + } | |
| 235 | + } | |
| 236 | + | |
| 237 | + // Most of these are copied from classes in java.awt.geom because we need | |
| 238 | + // float versions of these functions, and Line2D, CubicCurve2D, | |
| 239 | + // QuadCurve2D don't provide them. | |
| 240 | + /** | |
| 241 | + * Subdivides the cubic curve specified by the coordinates | |
| 242 | + * stored in the <code>src</code> array at indices <code>srcoff</code> | |
| 243 | + * through (<code>srcoff</code> + 7) and stores the | |
| 244 | + * resulting two subdivided curves into the two result arrays at the | |
| 245 | + * corresponding indices. | |
| 246 | + * Either or both of the <code>left</code> and <code>right</code> | |
| 247 | + * arrays may be <code>null</code> or a reference to the same array | |
| 248 | + * as the <code>src</code> array. | |
| 249 | + * Note that the last point in the first subdivided curve is the | |
| 250 | + * same as the first point in the second subdivided curve. Thus, | |
| 251 | + * it is possible to pass the same array for <code>left</code> | |
| 252 | + * and <code>right</code> and to use offsets, such as <code>rightoff</code> | |
| 253 | + * equals (<code>leftoff</code> + 6), in order | |
| 254 | + * to avoid allocating extra storage for this common point. | |
| 255 | + * @param src the array holding the coordinates for the source curve | |
| 256 | + * @param srcoff the offset into the array of the beginning of the | |
| 257 | + * the 6 source coordinates | |
| 258 | + * @param left the array for storing the coordinates for the first | |
| 259 | + * half of the subdivided curve | |
| 260 | + * @param leftoff the offset into the array of the beginning of the | |
| 261 | + * the 6 left coordinates | |
| 262 | + * @param right the array for storing the coordinates for the second | |
| 263 | + * half of the subdivided curve | |
| 264 | + * @param rightoff the offset into the array of the beginning of the | |
| 265 | + * the 6 right coordinates | |
| 266 | + * @since 1.7 | |
| 267 | + */ | |
| 268 | + static void subdivideCubic(float src[], int srcoff, | |
| 269 | + float left[], int leftoff, | |
| 270 | + float right[], int rightoff) | |
| 271 | + { | |
| 272 | + float x1 = src[srcoff + 0]; | |
| 273 | + float y1 = src[srcoff + 1]; | |
| 274 | + float ctrlx1 = src[srcoff + 2]; | |
| 275 | + float ctrly1 = src[srcoff + 3]; | |
| 276 | + float ctrlx2 = src[srcoff + 4]; | |
| 277 | + float ctrly2 = src[srcoff + 5]; | |
| 278 | + float x2 = src[srcoff + 6]; | |
| 279 | + float y2 = src[srcoff + 7]; | |
| 280 | + if (left != null) { | |
| 281 | + left[leftoff + 0] = x1; | |
| 282 | + left[leftoff + 1] = y1; | |
| 283 | + } | |
| 284 | + if (right != null) { | |
| 285 | + right[rightoff + 6] = x2; | |
| 286 | + right[rightoff + 7] = y2; | |
| 287 | + } | |
| 288 | + x1 = (x1 + ctrlx1) / 2.0f; | |
| 289 | + y1 = (y1 + ctrly1) / 2.0f; | |
| 290 | + x2 = (x2 + ctrlx2) / 2.0f; | |
| 291 | + y2 = (y2 + ctrly2) / 2.0f; | |
| 292 | + float centerx = (ctrlx1 + ctrlx2) / 2.0f; | |
| 293 | + float centery = (ctrly1 + ctrly2) / 2.0f; | |
| 294 | + ctrlx1 = (x1 + centerx) / 2.0f; | |
| 295 | + ctrly1 = (y1 + centery) / 2.0f; | |
| 296 | + ctrlx2 = (x2 + centerx) / 2.0f; | |
| 297 | + ctrly2 = (y2 + centery) / 2.0f; | |
| 298 | + centerx = (ctrlx1 + ctrlx2) / 2.0f; | |
| 299 | + centery = (ctrly1 + ctrly2) / 2.0f; | |
| 300 | + if (left != null) { | |
| 301 | + left[leftoff + 2] = x1; | |
| 302 | + left[leftoff + 3] = y1; | |
| 303 | + left[leftoff + 4] = ctrlx1; | |
| 304 | + left[leftoff + 5] = ctrly1; | |
| 305 | + left[leftoff + 6] = centerx; | |
| 306 | + left[leftoff + 7] = centery; | |
| 307 | + } | |
| 308 | + if (right != null) { | |
| 309 | + right[rightoff + 0] = centerx; | |
| 310 | + right[rightoff + 1] = centery; | |
| 311 | + right[rightoff + 2] = ctrlx2; | |
| 312 | + right[rightoff + 3] = ctrly2; | |
| 313 | + right[rightoff + 4] = x2; | |
| 314 | + right[rightoff + 5] = y2; | |
| 315 | + } | |
| 316 | + } | |
| 317 | + | |
| 318 | + | |
| 319 | + static void subdivideCubicAt(float t, float src[], int srcoff, | |
| 320 | + float left[], int leftoff, | |
| 321 | + float right[], int rightoff) | |
| 322 | + { | |
| 323 | + float x1 = src[srcoff + 0]; | |
| 324 | + float y1 = src[srcoff + 1]; | |
| 325 | + float ctrlx1 = src[srcoff + 2]; | |
| 326 | + float ctrly1 = src[srcoff + 3]; | |
| 327 | + float ctrlx2 = src[srcoff + 4]; | |
| 328 | + float ctrly2 = src[srcoff + 5]; | |
| 329 | + float x2 = src[srcoff + 6]; | |
| 330 | + float y2 = src[srcoff + 7]; | |
| 331 | + if (left != null) { | |
| 332 | + left[leftoff + 0] = x1; | |
| 333 | + left[leftoff + 1] = y1; | |
| 334 | + } | |
| 335 | + if (right != null) { | |
| 336 | + right[rightoff + 6] = x2; | |
| 337 | + right[rightoff + 7] = y2; | |
| 338 | + } | |
| 339 | + x1 = x1 + t * (ctrlx1 - x1); | |
| 340 | + y1 = y1 + t * (ctrly1 - y1); | |
| 341 | + x2 = ctrlx2 + t * (x2 - ctrlx2); | |
| 342 | + y2 = ctrly2 + t * (y2 - ctrly2); | |
| 343 | + float centerx = ctrlx1 + t * (ctrlx2 - ctrlx1); | |
| 344 | + float centery = ctrly1 + t * (ctrly2 - ctrly1); | |
| 345 | + ctrlx1 = x1 + t * (centerx - x1); | |
| 346 | + ctrly1 = y1 + t * (centery - y1); | |
| 347 | + ctrlx2 = centerx + t * (x2 - centerx); | |
| 348 | + ctrly2 = centery + t * (y2 - centery); | |
| 349 | + centerx = ctrlx1 + t * (ctrlx2 - ctrlx1); | |
| 350 | + centery = ctrly1 + t * (ctrly2 - ctrly1); | |
| 351 | + if (left != null) { | |
| 352 | + left[leftoff + 2] = x1; | |
| 353 | + left[leftoff + 3] = y1; | |
| 354 | + left[leftoff + 4] = ctrlx1; | |
| 355 | + left[leftoff + 5] = ctrly1; | |
| 356 | + left[leftoff + 6] = centerx; | |
| 357 | + left[leftoff + 7] = centery; | |
| 358 | + } | |
| 359 | + if (right != null) { | |
| 360 | + right[rightoff + 0] = centerx; | |
| 361 | + right[rightoff + 1] = centery; | |
| 362 | + right[rightoff + 2] = ctrlx2; | |
| 363 | + right[rightoff + 3] = ctrly2; | |
| 364 | + right[rightoff + 4] = x2; | |
| 365 | + right[rightoff + 5] = y2; | |
| 366 | + } | |
| 367 | + } | |
| 368 | + | |
| 369 | + static void subdivideQuad(float src[], int srcoff, | |
| 370 | + float left[], int leftoff, | |
| 371 | + float right[], int rightoff) | |
| 372 | + { | |
| 373 | + float x1 = src[srcoff + 0]; | |
| 374 | + float y1 = src[srcoff + 1]; | |
| 375 | + float ctrlx = src[srcoff + 2]; | |
| 376 | + float ctrly = src[srcoff + 3]; | |
| 377 | + float x2 = src[srcoff + 4]; | |
| 378 | + float y2 = src[srcoff + 5]; | |
| 379 | + if (left != null) { | |
| 380 | + left[leftoff + 0] = x1; | |
| 381 | + left[leftoff + 1] = y1; | |
| 382 | + } | |
| 383 | + if (right != null) { | |
| 384 | + right[rightoff + 4] = x2; | |
| 385 | + right[rightoff + 5] = y2; | |
| 386 | + } | |
| 387 | + x1 = (x1 + ctrlx) / 2.0f; | |
| 388 | + y1 = (y1 + ctrly) / 2.0f; | |
| 389 | + x2 = (x2 + ctrlx) / 2.0f; | |
| 390 | + y2 = (y2 + ctrly) / 2.0f; | |
| 391 | + ctrlx = (x1 + x2) / 2.0f; | |
| 392 | + ctrly = (y1 + y2) / 2.0f; | |
| 393 | + if (left != null) { | |
| 394 | + left[leftoff + 2] = x1; | |
| 395 | + left[leftoff + 3] = y1; | |
| 396 | + left[leftoff + 4] = ctrlx; | |
| 397 | + left[leftoff + 5] = ctrly; | |
| 398 | + } | |
| 399 | + if (right != null) { | |
| 400 | + right[rightoff + 0] = ctrlx; | |
| 401 | + right[rightoff + 1] = ctrly; | |
| 402 | + right[rightoff + 2] = x2; | |
| 403 | + right[rightoff + 3] = y2; | |
| 404 | + } | |
| 405 | + } | |
| 406 | + | |
| 407 | + static void subdivideQuadAt(float t, float src[], int srcoff, | |
| 408 | + float left[], int leftoff, | |
| 409 | + float right[], int rightoff) | |
| 410 | + { | |
| 411 | + float x1 = src[srcoff + 0]; | |
| 412 | + float y1 = src[srcoff + 1]; | |
| 413 | + float ctrlx = src[srcoff + 2]; | |
| 414 | + float ctrly = src[srcoff + 3]; | |
| 415 | + float x2 = src[srcoff + 4]; | |
| 416 | + float y2 = src[srcoff + 5]; | |
| 417 | + if (left != null) { | |
| 418 | + left[leftoff + 0] = x1; | |
| 419 | + left[leftoff + 1] = y1; | |
| 420 | + } | |
| 421 | + if (right != null) { | |
| 422 | + right[rightoff + 4] = x2; | |
| 423 | + right[rightoff + 5] = y2; | |
| 424 | + } | |
| 425 | + x1 = x1 + t * (ctrlx - x1); | |
| 426 | + y1 = y1 + t * (ctrly - y1); | |
| 427 | + x2 = ctrlx + t * (x2 - ctrlx); | |
| 428 | + y2 = ctrly + t * (y2 - ctrly); | |
| 429 | + ctrlx = x1 + t * (x2 - x1); | |
| 430 | + ctrly = y1 + t * (y2 - y1); | |
| 431 | + if (left != null) { | |
| 432 | + left[leftoff + 2] = x1; | |
| 433 | + left[leftoff + 3] = y1; | |
| 434 | + left[leftoff + 4] = ctrlx; | |
| 435 | + left[leftoff + 5] = ctrly; | |
| 436 | + } | |
| 437 | + if (right != null) { | |
| 438 | + right[rightoff + 0] = ctrlx; | |
| 439 | + right[rightoff + 1] = ctrly; | |
| 440 | + right[rightoff + 2] = x2; | |
| 441 | + right[rightoff + 3] = y2; | |
| 442 | + } | |
| 443 | + } | |
| 444 | + | |
| 445 | + static void subdivideAt(float t, float src[], int srcoff, | |
| 446 | + float left[], int leftoff, | |
| 447 | + float right[], int rightoff, int size) | |
| 448 | + { | |
| 449 | + switch(size) { | |
| 450 | + case 8: | |
| 451 | + subdivideCubicAt(t, src, srcoff, left, leftoff, right, rightoff); | |
| 452 | + break; | |
| 453 | + case 6: | |
| 454 | + subdivideQuadAt(t, src, srcoff, left, leftoff, right, rightoff); | |
| 455 | + break; | |
| 456 | + } | |
| 457 | + } | |
| 458 | +} | |
| @@ -0,0 +1,6 @@ | ||
| 1 | +<../../../../../../mkconfig | |
| 2 | + | |
| 3 | +DIRS=\ | |
| 4 | + services\ | |
| 5 | + | |
| 6 | +<$ROOT/mkfiles/mksubdirs | |
| @@ -0,0 +1,2 @@ | ||
| 1 | +<../../../../../../../mkconfig | |
| 2 | + | |
| @@ -0,0 +1,2 @@ | ||
| 1 | +# Pisces Rendering Engine module | |
| 2 | +sun.java2d.pisces.PiscesRenderingEngine | |
| @@ -0,0 +1,186 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. | |
| 3 | + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| 4 | + * | |
| 5 | + * This code is free software; you can redistribute it and/or modify it | |
| 6 | + * under the terms of the GNU General Public License version 2 only, as | |
| 7 | + * published by the Free Software Foundation. Oracle designates this | |
| 8 | + * particular file as subject to the "Classpath" exception as provided | |
| 9 | + * by Oracle in the LICENSE file that accompanied this code. | |
| 10 | + * | |
| 11 | + * This code is distributed in the hope that it will be useful, but WITHOUT | |
| 12 | + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| 13 | + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| 14 | + * version 2 for more details (a copy is included in the LICENSE file that | |
| 15 | + * accompanied this code). | |
| 16 | + * | |
| 17 | + * You should have received a copy of the GNU General Public License version | |
| 18 | + * 2 along with this work; if not, write to the Free Software Foundation, | |
| 19 | + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 20 | + * | |
| 21 | + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| 22 | + * or visit www.oracle.com if you need additional information or have any | |
| 23 | + * questions. | |
| 24 | + */ | |
| 25 | + | |
| 26 | +package sun.java2d.pisces; | |
| 27 | + | |
| 28 | +import java.util.Arrays; | |
| 29 | + | |
| 30 | +/** | |
| 31 | + * An object used to cache pre-rendered complex paths. | |
| 32 | + * | |
| 33 | + * @see PiscesRenderer#render | |
| 34 | + */ | |
| 35 | +final class PiscesCache { | |
| 36 | + | |
| 37 | + final int bboxX0, bboxY0, bboxX1, bboxY1; | |
| 38 | + | |
| 39 | + // rowAARLE[i] holds the encoding of the pixel row with y = bboxY0+i. | |
| 40 | + // The format of each of the inner arrays is: rowAARLE[i][0,1] = (x0, n) | |
| 41 | + // where x0 is the first x in row i with nonzero alpha, and n is the | |
| 42 | + // number of RLE entries in this row. rowAARLE[i][j,j+1] for j>1 is | |
| 43 | + // (val,runlen) | |
| 44 | + final int[][] rowAARLE; | |
| 45 | + | |
| 46 | + // RLE encodings are added in increasing y rows and then in increasing | |
| 47 | + // x inside those rows. Therefore, at any one time there is a well | |
| 48 | + // defined position (x,y) where a run length is about to be added (or | |
| 49 | + // the row terminated). x0,y0 is this (x,y)-(bboxX0,bboxY0). They | |
| 50 | + // are used to get indices into the current tile. | |
| 51 | + private int x0 = Integer.MIN_VALUE, y0 = Integer.MIN_VALUE; | |
| 52 | + | |
| 53 | + // touchedTile[i][j] is the sum of all the alphas in the tile with | |
| 54 | + // y=i*TILE_SIZE+bboxY0 and x=j*TILE_SIZE+bboxX0. | |
| 55 | + private final int[][] touchedTile; | |
| 56 | + | |
| 57 | + static final int TILE_SIZE_LG = 5; | |
| 58 | + static final int TILE_SIZE = 1 << TILE_SIZE_LG; // 32 | |
| 59 | + private static final int INIT_ROW_SIZE = 8; // enough for 3 run lengths | |
| 60 | + | |
| 61 | + PiscesCache(int minx, int miny, int maxx, int maxy) { | |
| 62 | + assert maxy >= miny && maxx >= minx; | |
| 63 | + bboxX0 = minx; | |
| 64 | + bboxY0 = miny; | |
| 65 | + bboxX1 = maxx + 1; | |
| 66 | + bboxY1 = maxy + 1; | |
| 67 | + // we could just leave the inner arrays as null and allocate them | |
| 68 | + // lazily (which would be beneficial for shapes with gaps), but we | |
| 69 | + // assume there won't be too many of those so we allocate everything | |
| 70 | + // up front (which is better for other cases) | |
| 71 | + rowAARLE = new int[bboxY1 - bboxY0 + 1][INIT_ROW_SIZE]; | |
| 72 | + x0 = 0; | |
| 73 | + y0 = -1; // -1 makes the first assert in startRow succeed | |
| 74 | + // the ceiling of (maxy - miny + 1) / TILE_SIZE; | |
| 75 | + int nyTiles = (maxy - miny + TILE_SIZE) >> TILE_SIZE_LG; | |
| 76 | + int nxTiles = (maxx - minx + TILE_SIZE) >> TILE_SIZE_LG; | |
| 77 | + | |
| 78 | + touchedTile = new int[nyTiles][nxTiles]; | |
| 79 | + } | |
| 80 | + | |
| 81 | + void addRLERun(int val, int runLen) { | |
| 82 | + if (runLen > 0) { | |
| 83 | + addTupleToRow(y0, val, runLen); | |
| 84 | + if (val != 0) { | |
| 85 | + // the x and y of the current row, minus bboxX0, bboxY0 | |
| 86 | + int tx = x0 >> TILE_SIZE_LG; | |
| 87 | + int ty = y0 >> TILE_SIZE_LG; | |
| 88 | + int tx1 = (x0 + runLen - 1) >> TILE_SIZE_LG; | |
| 89 | + // while we forbid rows from starting before bboxx0, our users | |
| 90 | + // can still store rows that go beyond bboxx1 (although this | |
| 91 | + // shouldn't happen), so it's a good idea to check that i | |
| 92 | + // is not going out of bounds in touchedTile[ty] | |
| 93 | + if (tx1 >= touchedTile[ty].length) { | |
| 94 | + tx1 = touchedTile[ty].length - 1; | |
| 95 | + } | |
| 96 | + if (tx <= tx1) { | |
| 97 | + int nextTileXCoord = (tx + 1) << TILE_SIZE_LG; | |
| 98 | + if (nextTileXCoord > x0+runLen) { | |
| 99 | + touchedTile[ty][tx] += val * runLen; | |
| 100 | + } else { | |
| 101 | + touchedTile[ty][tx] += val * (nextTileXCoord - x0); | |
| 102 | + } | |
| 103 | + tx++; | |
| 104 | + } | |
| 105 | + // don't go all the way to tx1 - we need to handle the last | |
| 106 | + // tile as a special case (just like we did with the first | |
| 107 | + for (; tx < tx1; tx++) { | |
| 108 | +// try { | |
| 109 | + touchedTile[ty][tx] += (val << TILE_SIZE_LG); | |
| 110 | +// } catch (RuntimeException e) { | |
| 111 | +// System.out.println("x0, y0: " + x0 + ", " + y0); | |
| 112 | +// System.out.printf("tx, ty, tx1: %d, %d, %d %n", tx, ty, tx1); | |
| 113 | +// System.out.printf("bboxX/Y0/1: %d, %d, %d, %d %n", | |
| 114 | +// bboxX0, bboxY0, bboxX1, bboxY1); | |
| 115 | +// throw e; | |
| 116 | +// } | |
| 117 | + } | |
| 118 | + // they will be equal unless x0>>TILE_SIZE_LG == tx1 | |
| 119 | + if (tx == tx1) { | |
| 120 | + int lastXCoord = Math.min(x0 + runLen, (tx + 1) << TILE_SIZE_LG); | |
| 121 | + int txXCoord = tx << TILE_SIZE_LG; | |
| 122 | + touchedTile[ty][tx] += val * (lastXCoord - txXCoord); | |
| 123 | + } | |
| 124 | + } | |
| 125 | + x0 += runLen; | |
| 126 | + } | |
| 127 | + } | |
| 128 | + | |
| 129 | + void startRow(int y, int x) { | |
| 130 | + // rows are supposed to be added by increasing y. | |
| 131 | + assert y - bboxY0 > y0; | |
| 132 | + assert y <= bboxY1; // perhaps this should be < instead of <= | |
| 133 | + | |
| 134 | + y0 = y - bboxY0; | |
| 135 | + // this should be a new, uninitialized row. | |
| 136 | + assert rowAARLE[y0][1] == 0; | |
| 137 | + | |
| 138 | + x0 = x - bboxX0; | |
| 139 | + assert x0 >= 0 : "Input must not be to the left of bbox bounds"; | |
| 140 | + | |
| 141 | + // the way addTupleToRow is implemented it would work for this but it's | |
| 142 | + // not a good idea to use it because it is meant for adding | |
| 143 | + // RLE tuples, not the first tuple (which is special). | |
| 144 | + rowAARLE[y0][0] = x; | |
| 145 | + rowAARLE[y0][1] = 2; | |
| 146 | + } | |
| 147 | + | |
| 148 | + int alphaSumInTile(int x, int y) { | |
| 149 | + x -= bboxX0; | |
| 150 | + y -= bboxY0; | |
| 151 | + return touchedTile[y>>TILE_SIZE_LG][x>>TILE_SIZE_LG]; | |
| 152 | + } | |
| 153 | + | |
| 154 | + int minTouched(int rowidx) { | |
| 155 | + return rowAARLE[rowidx][0]; | |
| 156 | + } | |
| 157 | + | |
| 158 | + int rowLength(int rowidx) { | |
| 159 | + return rowAARLE[rowidx][1]; | |
| 160 | + } | |
| 161 | + | |
| 162 | + private void addTupleToRow(int row, int a, int b) { | |
| 163 | + int end = rowAARLE[row][1]; | |
| 164 | + rowAARLE[row] = Helpers.widenArray(rowAARLE[row], end, 2); | |
| 165 | + rowAARLE[row][end++] = a; | |
| 166 | + rowAARLE[row][end++] = b; | |
| 167 | + rowAARLE[row][1] = end; | |
| 168 | + } | |
| 169 | + | |
| 170 | + @Override | |
| 171 | + public String toString() { | |
| 172 | + String ret = "bbox = ["+ | |
| 173 | + bboxX0+", "+bboxY0+" => "+ | |
| 174 | + bboxX1+", "+bboxY1+"]\n"; | |
| 175 | + for (int[] row : rowAARLE) { | |
| 176 | + if (row != null) { | |
| 177 | + ret += ("minTouchedX=" + row[0] + | |
| 178 | + "\tRLE Entries: " + Arrays.toString( | |
| 179 | + Arrays.copyOfRange(row, 2, row[1])) + "\n"); | |
| 180 | + } else { | |
| 181 | + ret += "[]\n"; | |
| 182 | + } | |
| 183 | + } | |
| 184 | + return ret; | |
| 185 | + } | |
| 186 | +} | |
| @@ -0,0 +1,656 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. | |
| 3 | + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| 4 | + * | |
| 5 | + * This code is free software; you can redistribute it and/or modify it | |
| 6 | + * under the terms of the GNU General Public License version 2 only, as | |
| 7 | + * published by the Free Software Foundation. Oracle designates this | |
| 8 | + * particular file as subject to the "Classpath" exception as provided | |
| 9 | + * by Oracle in the LICENSE file that accompanied this code. | |
| 10 | + * | |
| 11 | + * This code is distributed in the hope that it will be useful, but WITHOUT | |
| 12 | + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| 13 | + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| 14 | + * version 2 for more details (a copy is included in the LICENSE file that | |
| 15 | + * accompanied this code). | |
| 16 | + * | |
| 17 | + * You should have received a copy of the GNU General Public License version | |
| 18 | + * 2 along with this work; if not, write to the Free Software Foundation, | |
| 19 | + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 20 | + * | |
| 21 | + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| 22 | + * or visit www.oracle.com if you need additional information or have any | |
| 23 | + * questions. | |
| 24 | + */ | |
| 25 | + | |
| 26 | +package sun.java2d.pisces; | |
| 27 | + | |
| 28 | +import java.awt.Shape; | |
| 29 | +import java.awt.BasicStroke; | |
| 30 | +import java.awt.geom.Path2D; | |
| 31 | +import java.awt.geom.AffineTransform; | |
| 32 | +import java.awt.geom.PathIterator; | |
| 33 | + | |
| 34 | +import sun.awt.geom.PathConsumer2D; | |
| 35 | +import sun.java2d.pipe.Region; | |
| 36 | +import sun.java2d.pipe.RenderingEngine; | |
| 37 | +import sun.java2d.pipe.AATileGenerator; | |
| 38 | + | |
| 39 | +public class PiscesRenderingEngine extends RenderingEngine { | |
| 40 | + private static enum NormMode {OFF, ON_NO_AA, ON_WITH_AA} | |
| 41 | + | |
| 42 | + /** | |
| 43 | + * Create a widened path as specified by the parameters. | |
| 44 | + * <p> | |
| 45 | + * The specified {@code src} {@link Shape} is widened according | |
| 46 | + * to the specified attribute parameters as per the | |
| 47 | + * {@link BasicStroke} specification. | |
| 48 | + * | |
| 49 | + * @param src the source path to be widened | |
| 50 | + * @param width the width of the widened path as per {@code BasicStroke} | |
| 51 | + * @param caps the end cap decorations as per {@code BasicStroke} | |
| 52 | + * @param join the segment join decorations as per {@code BasicStroke} | |
| 53 | + * @param miterlimit the miter limit as per {@code BasicStroke} | |
| 54 | + * @param dashes the dash length array as per {@code BasicStroke} | |
| 55 | + * @param dashphase the initial dash phase as per {@code BasicStroke} | |
| 56 | + * @return the widened path stored in a new {@code Shape} object | |
| 57 | + * @since 1.7 | |
| 58 | + */ | |
| 59 | + public Shape createStrokedShape(Shape src, | |
| 60 | + float width, | |
| 61 | + int caps, | |
| 62 | + int join, | |
| 63 | + float miterlimit, | |
| 64 | + float dashes[], | |
| 65 | + float dashphase) | |
| 66 | + { | |
| 67 | + final Path2D p2d = new Path2D.Float(); | |
| 68 | + | |
| 69 | + strokeTo(src, | |
| 70 | + null, | |
| 71 | + width, | |
| 72 | + NormMode.OFF, | |
| 73 | + caps, | |
| 74 | + join, | |
| 75 | + miterlimit, | |
| 76 | + dashes, | |
| 77 | + dashphase, | |
| 78 | + new PathConsumer2D() { | |
| 79 | + public void moveTo(float x0, float y0) { | |
| 80 | + p2d.moveTo(x0, y0); | |
| 81 | + } | |
| 82 | + public void lineTo(float x1, float y1) { | |
| 83 | + p2d.lineTo(x1, y1); | |
| 84 | + } | |
| 85 | + public void closePath() { | |
| 86 | + p2d.closePath(); | |
| 87 | + } | |
| 88 | + public void pathDone() {} | |
| 89 | + public void curveTo(float x1, float y1, | |
| 90 | + float x2, float y2, | |
| 91 | + float x3, float y3) { | |
| 92 | + p2d.curveTo(x1, y1, x2, y2, x3, y3); | |
| 93 | + } | |
| 94 | + public void quadTo(float x1, float y1, float x2, float y2) { | |
| 95 | + p2d.quadTo(x1, y1, x2, y2); | |
| 96 | + } | |
| 97 | + public long getNativeConsumer() { | |
| 98 | + throw new InternalError("Not using a native peer"); | |
| 99 | + } | |
| 100 | + }); | |
| 101 | + return p2d; | |
| 102 | + } | |
| 103 | + | |
| 104 | + /** | |
| 105 | + * Sends the geometry for a widened path as specified by the parameters | |
| 106 | + * to the specified consumer. | |
| 107 | + * <p> | |
| 108 | + * The specified {@code src} {@link Shape} is widened according | |
| 109 | + * to the parameters specified by the {@link BasicStroke} object. | |
| 110 | + * Adjustments are made to the path as appropriate for the | |
| 111 | + * {@link VALUE_STROKE_NORMALIZE} hint if the {@code normalize} | |
| 112 | + * boolean parameter is true. | |
| 113 | + * Adjustments are made to the path as appropriate for the | |
| 114 | + * {@link VALUE_ANTIALIAS_ON} hint if the {@code antialias} | |
| 115 | + * boolean parameter is true. | |
| 116 | + * <p> | |
| 117 | + * The geometry of the widened path is forwarded to the indicated | |
| 118 | + * {@link PathConsumer2D} object as it is calculated. | |
| 119 | + * | |
| 120 | + * @param src the source path to be widened | |
| 121 | + * @param bs the {@code BasicSroke} object specifying the | |
| 122 | + * decorations to be applied to the widened path | |
| 123 | + * @param normalize indicates whether stroke normalization should | |
| 124 | + * be applied | |
| 125 | + * @param antialias indicates whether or not adjustments appropriate | |
| 126 | + * to antialiased rendering should be applied | |
| 127 | + * @param consumer the {@code PathConsumer2D} instance to forward | |
| 128 | + * the widened geometry to | |
| 129 | + * @since 1.7 | |
| 130 | + */ | |
| 131 | + public void strokeTo(Shape src, | |
| 132 | + AffineTransform at, | |
| 133 | + BasicStroke bs, | |
| 134 | + boolean thin, | |
| 135 | + boolean normalize, | |
| 136 | + boolean antialias, | |
| 137 | + final PathConsumer2D consumer) | |
| 138 | + { | |
| 139 | + NormMode norm = (normalize) ? | |
| 140 | + ((antialias) ? NormMode.ON_WITH_AA : NormMode.ON_NO_AA) | |
| 141 | + : NormMode.OFF; | |
| 142 | + strokeTo(src, at, bs, thin, norm, antialias, consumer); | |
| 143 | + } | |
| 144 | + | |
| 145 | + void strokeTo(Shape src, | |
| 146 | + AffineTransform at, | |
| 147 | + BasicStroke bs, | |
| 148 | + boolean thin, | |
| 149 | + NormMode normalize, | |
| 150 | + boolean antialias, | |
| 151 | + PathConsumer2D pc2d) | |
| 152 | + { | |
| 153 | + float lw; | |
| 154 | + if (thin) { | |
| 155 | + if (antialias) { | |
| 156 | + lw = userSpaceLineWidth(at, 0.5f); | |
| 157 | + } else { | |
| 158 | + lw = userSpaceLineWidth(at, 1.0f); | |
| 159 | + } | |
| 160 | + } else { | |
| 161 | + lw = bs.getLineWidth(); | |
| 162 | + } | |
| 163 | + strokeTo(src, | |
| 164 | + at, | |
| 165 | + lw, | |
| 166 | + normalize, | |
| 167 | + bs.getEndCap(), | |
| 168 | + bs.getLineJoin(), | |
| 169 | + bs.getMiterLimit(), | |
| 170 | + bs.getDashArray(), | |
| 171 | + bs.getDashPhase(), | |
| 172 | + pc2d); | |
| 173 | + } | |
| 174 | + | |
| 175 | + private float userSpaceLineWidth(AffineTransform at, float lw) { | |
| 176 | + | |
| 177 | + double widthScale; | |
| 178 | + | |
| 179 | + if ((at.getType() & (AffineTransform.TYPE_GENERAL_TRANSFORM | | |
| 180 | + AffineTransform.TYPE_GENERAL_SCALE)) != 0) { | |
| 181 | + widthScale = Math.sqrt(at.getDeterminant()); | |
| 182 | + } else { | |
| 183 | + /* First calculate the "maximum scale" of this transform. */ | |
| 184 | + double A = at.getScaleX(); // m00 | |
| 185 | + double C = at.getShearX(); // m01 | |
| 186 | + double B = at.getShearY(); // m10 | |
| 187 | + double D = at.getScaleY(); // m11 | |
| 188 | + | |
| 189 | + /* | |
| 190 | + * Given a 2 x 2 affine matrix [ A B ] such that | |
| 191 | + * [ C D ] | |
| 192 | + * v' = [x' y'] = [Ax + Cy, Bx + Dy], we want to | |
| 193 | + * find the maximum magnitude (norm) of the vector v' | |
| 194 | + * with the constraint (x^2 + y^2 = 1). | |
| 195 | + * The equation to maximize is | |
| 196 | + * |v'| = sqrt((Ax+Cy)^2+(Bx+Dy)^2) | |
| 197 | + * or |v'| = sqrt((AA+BB)x^2 + 2(AC+BD)xy + (CC+DD)y^2). | |
| 198 | + * Since sqrt is monotonic we can maximize |v'|^2 | |
| 199 | + * instead and plug in the substitution y = sqrt(1 - x^2). | |
| 200 | + * Trigonometric equalities can then be used to get | |
| 201 | + * rid of most of the sqrt terms. | |
| 202 | + */ | |
| 203 | + | |
| 204 | + double EA = A*A + B*B; // x^2 coefficient | |
| 205 | + double EB = 2*(A*C + B*D); // xy coefficient | |
| 206 | + double EC = C*C + D*D; // y^2 coefficient | |
| 207 | + | |
| 208 | + /* | |
| 209 | + * There is a lot of calculus omitted here. | |
| 210 | + * | |
| 211 | + * Conceptually, in the interests of understanding the | |
| 212 | + * terms that the calculus produced we can consider | |
| 213 | + * that EA and EC end up providing the lengths along | |
| 214 | + * the major axes and the hypot term ends up being an | |
| 215 | + * adjustment for the additional length along the off-axis | |
| 216 | + * angle of rotated or sheared ellipses as well as an | |
| 217 | + * adjustment for the fact that the equation below | |
| 218 | + * averages the two major axis lengths. (Notice that | |
| 219 | + * the hypot term contains a part which resolves to the | |
| 220 | + * difference of these two axis lengths in the absence | |
| 221 | + * of rotation.) | |
| 222 | + * | |
| 223 | + * In the calculus, the ratio of the EB and (EA-EC) terms | |
| 224 | + * ends up being the tangent of 2*theta where theta is | |
| 225 | + * the angle that the long axis of the ellipse makes | |
| 226 | + * with the horizontal axis. Thus, this equation is | |
| 227 | + * calculating the length of the hypotenuse of a triangle | |
| 228 | + * along that axis. | |
| 229 | + */ | |
| 230 | + | |
| 231 | + double hypot = Math.sqrt(EB*EB + (EA-EC)*(EA-EC)); | |
| 232 | + /* sqrt omitted, compare to squared limits below. */ | |
| 233 | + double widthsquared = ((EA + EC + hypot)/2.0); | |
| 234 | + | |
| 235 | + widthScale = Math.sqrt(widthsquared); | |
| 236 | + } | |
| 237 | + | |
| 238 | + return (float) (lw / widthScale); | |
| 239 | + } | |
| 240 | + | |
| 241 | + void strokeTo(Shape src, | |
| 242 | + AffineTransform at, | |
| 243 | + float width, | |
| 244 | + NormMode normalize, | |
| 245 | + int caps, | |
| 246 | + int join, | |
| 247 | + float miterlimit, | |
| 248 | + float dashes[], | |
| 249 | + float dashphase, | |
| 250 | + PathConsumer2D pc2d) | |
| 251 | + { | |
| 252 | + // We use strokerat and outat so that in Stroker and Dasher we can work only | |
| 253 | + // with the pre-transformation coordinates. This will repeat a lot of | |
| 254 | + // computations done in the path iterator, but the alternative is to | |
| 255 | + // work with transformed paths and compute untransformed coordinates | |
| 256 | + // as needed. This would be faster but I do not think the complexity | |
| 257 | + // of working with both untransformed and transformed coordinates in | |
| 258 | + // the same code is worth it. | |
| 259 | + // However, if a path's width is constant after a transformation, | |
| 260 | + // we can skip all this untransforming. | |
| 261 | + | |
| 262 | + // If normalization is off we save some transformations by not | |
| 263 | + // transforming the input to pisces. Instead, we apply the | |
| 264 | + // transformation after the path processing has been done. | |
| 265 | + // We can't do this if normalization is on, because it isn't a good | |
| 266 | + // idea to normalize before the transformation is applied. | |
| 267 | + AffineTransform strokerat = null; | |
| 268 | + AffineTransform outat = null; | |
| 269 | + | |
| 270 | + PathIterator pi = null; | |
| 271 | + | |
| 272 | + if (at != null && !at.isIdentity()) { | |
| 273 | + final double a = at.getScaleX(); | |
| 274 | + final double b = at.getShearX(); | |
| 275 | + final double c = at.getShearY(); | |
| 276 | + final double d = at.getScaleY(); | |
| 277 | + final double det = a * d - c * b; | |
| 278 | + if (Math.abs(det) <= 2 * Float.MIN_VALUE) { | |
| 279 | + // this rendering engine takes one dimensional curves and turns | |
| 280 | + // them into 2D shapes by giving them width. | |
| 281 | + // However, if everything is to be passed through a singular | |
| 282 | + // transformation, these 2D shapes will be squashed down to 1D | |
| 283 | + // again so, nothing can be drawn. | |
| 284 | + | |
| 285 | + // Every path needs an initial moveTo and a pathDone. If these | |
| 286 | + // are not there this causes a SIGSEGV in libawt.so (at the time | |
| 287 | + // of writing of this comment (September 16, 2010)). Actually, | |
| 288 | + // I am not sure if the moveTo is necessary to avoid the SIGSEGV | |
| 289 | + // but the pathDone is definitely needed. | |
| 290 | + pc2d.moveTo(0, 0); | |
| 291 | + pc2d.pathDone(); | |
| 292 | + return; | |
| 293 | + } | |
| 294 | + | |
| 295 | + // If the transform is a constant multiple of an orthogonal transformation | |
| 296 | + // then every length is just multiplied by a constant, so we just | |
| 297 | + // need to transform input paths to stroker and tell stroker | |
| 298 | + // the scaled width. This condition is satisfied if | |
| 299 | + // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we | |
| 300 | + // leave a bit of room for error. | |
| 301 | + if (nearZero(a*b + c*d, 2) && nearZero(a*a+c*c - (b*b+d*d), 2)) { | |
| 302 | + double scale = Math.sqrt(a*a + c*c); | |
| 303 | + if (dashes != null) { | |
| 304 | + dashes = java.util.Arrays.copyOf(dashes, dashes.length); | |
| 305 | + for (int i = 0; i < dashes.length; i++) { | |
| 306 | + dashes[i] = (float)(scale * dashes[i]); | |
| 307 | + } | |
| 308 | + dashphase = (float)(scale * dashphase); | |
| 309 | + } | |
| 310 | + width = (float)(scale * width); | |
| 311 | + pi = src.getPathIterator(at); | |
| 312 | + if (normalize != NormMode.OFF) { | |
| 313 | + pi = new NormalizingPathIterator(pi, normalize); | |
| 314 | + } | |
| 315 | + // by now strokerat == null && outat == null. Input paths to | |
| 316 | + // stroker (and maybe dasher) will have the full transform at | |
| 317 | + // applied to them and nothing will happen to the output paths. | |
| 318 | + } else { | |
| 319 | + if (normalize != NormMode.OFF) { | |
| 320 | + strokerat = at; | |
| 321 | + pi = src.getPathIterator(at); | |
| 322 | + pi = new NormalizingPathIterator(pi, normalize); | |
| 323 | + // by now strokerat == at && outat == null. Input paths to | |
| 324 | + // stroker (and maybe dasher) will have the full transform at | |
| 325 | + // applied to them, then they will be normalized, and then | |
| 326 | + // the inverse of *only the non translation part of at* will | |
| 327 | + // be applied to the normalized paths. This won't cause problems | |
| 328 | + // in stroker, because, suppose at = T*A, where T is just the | |
| 329 | + // translation part of at, and A is the rest. T*A has already | |
| 330 | + // been applied to Stroker/Dasher's input. Then Ainv will be | |
| 331 | + // applied. Ainv*T*A is not equal to T, but it is a translation, | |
| 332 | + // which means that none of stroker's assumptions about its | |
| 333 | + // input will be violated. After all this, A will be applied | |
| 334 | + // to stroker's output. | |
| 335 | + } else { | |
| 336 | + outat = at; | |
| 337 | + pi = src.getPathIterator(null); | |
| 338 | + // outat == at && strokerat == null. This is because if no | |
| 339 | + // normalization is done, we can just apply all our | |
| 340 | + // transformations to stroker's output. | |
| 341 | + } | |
| 342 | + } | |
| 343 | + } else { | |
| 344 | + // either at is null or it's the identity. In either case | |
| 345 | + // we don't transform the path. | |
| 346 | + pi = src.getPathIterator(null); | |
| 347 | + if (normalize != NormMode.OFF) { | |
| 348 | + pi = new NormalizingPathIterator(pi, normalize); | |
| 349 | + } | |
| 350 | + } | |
| 351 | + | |
| 352 | + // by now, at least one of outat and strokerat will be null. Unless at is not | |
| 353 | + // a constant multiple of an orthogonal transformation, they will both be | |
| 354 | + // null. In other cases, outat == at if normalization is off, and if | |
| 355 | + // normalization is on, strokerat == at. | |
| 356 | + pc2d = TransformingPathConsumer2D.transformConsumer(pc2d, outat); | |
| 357 | + pc2d = TransformingPathConsumer2D.deltaTransformConsumer(pc2d, strokerat); | |
| 358 | + pc2d = new Stroker(pc2d, width, caps, join, miterlimit); | |
| 359 | + if (dashes != null) { | |
| 360 | + pc2d = new Dasher(pc2d, dashes, dashphase); | |
| 361 | + } | |
| 362 | + pc2d = TransformingPathConsumer2D.inverseDeltaTransformConsumer(pc2d, strokerat); | |
| 363 | + pathTo(pi, pc2d); | |
| 364 | + } | |
| 365 | + | |
| 366 | + private static boolean nearZero(double num, int nulps) { | |
| 367 | + return Math.abs(num) < nulps * Math.ulp(num); | |
| 368 | + } | |
| 369 | + | |
| 370 | + private static class NormalizingPathIterator implements PathIterator { | |
| 371 | + | |
| 372 | + private final PathIterator src; | |
| 373 | + | |
| 374 | + // the adjustment applied to the current position. | |
| 375 | + private float curx_adjust, cury_adjust; | |
| 376 | + // the adjustment applied to the last moveTo position. | |
| 377 | + private float movx_adjust, movy_adjust; | |
| 378 | + | |
| 379 | + // constants used in normalization computations | |
| 380 | + private final float lval, rval; | |
| 381 | + | |
| 382 | + NormalizingPathIterator(PathIterator src, NormMode mode) { | |
| 383 | + this.src = src; | |
| 384 | + switch (mode) { | |
| 385 | + case ON_NO_AA: | |
| 386 | + // round to nearest (0.25, 0.25) pixel | |
| 387 | + lval = rval = 0.25f; | |
| 388 | + break; | |
| 389 | + case ON_WITH_AA: | |
| 390 | + // round to nearest pixel center | |
| 391 | + lval = 0f; | |
| 392 | + rval = 0.5f; | |
| 393 | + break; | |
| 394 | + case OFF: | |
| 395 | + throw new InternalError("A NormalizingPathIterator should " + | |
| 396 | + "not be created if no normalization is being done"); | |
| 397 | + default: | |
| 398 | + throw new InternalError("Unrecognized normalization mode"); | |
| 399 | + } | |
| 400 | + } | |
| 401 | + | |
| 402 | + public int currentSegment(float[] coords) { | |
| 403 | + int type = src.currentSegment(coords); | |
| 404 | + | |
| 405 | + int lastCoord; | |
| 406 | + switch(type) { | |
| 407 | + case PathIterator.SEG_CUBICTO: | |
| 408 | + lastCoord = 4; | |
| 409 | + break; | |
| 410 | + case PathIterator.SEG_QUADTO: | |
| 411 | + lastCoord = 2; | |
| 412 | + break; | |
| 413 | + case PathIterator.SEG_LINETO: | |
| 414 | + case PathIterator.SEG_MOVETO: | |
| 415 | + lastCoord = 0; | |
| 416 | + break; | |
| 417 | + case PathIterator.SEG_CLOSE: | |
| 418 | + // we don't want to deal with this case later. We just exit now | |
| 419 | + curx_adjust = movx_adjust; | |
| 420 | + cury_adjust = movy_adjust; | |
| 421 | + return type; | |
| 422 | + default: | |
| 423 | + throw new InternalError("Unrecognized curve type"); | |
| 424 | + } | |
| 425 | + | |
| 426 | + // normalize endpoint | |
| 427 | + float x_adjust = (float)Math.floor(coords[lastCoord] + lval) + | |
| 428 | + rval - coords[lastCoord]; | |
| 429 | + float y_adjust = (float)Math.floor(coords[lastCoord+1] + lval) + | |
| 430 | + rval - coords[lastCoord + 1]; | |
| 431 | + | |
| 432 | + coords[lastCoord ] += x_adjust; | |
| 433 | + coords[lastCoord + 1] += y_adjust; | |
| 434 | + | |
| 435 | + // now that the end points are done, normalize the control points | |
| 436 | + switch(type) { | |
| 437 | + case PathIterator.SEG_CUBICTO: | |
| 438 | + coords[0] += curx_adjust; | |
| 439 | + coords[1] += cury_adjust; | |
| 440 | + coords[2] += x_adjust; | |
| 441 | + coords[3] += y_adjust; | |
| 442 | + break; | |
| 443 | + case PathIterator.SEG_QUADTO: | |
| 444 | + coords[0] += (curx_adjust + x_adjust) / 2; | |
| 445 | + coords[1] += (cury_adjust + y_adjust) / 2; | |
| 446 | + break; | |
| 447 | + case PathIterator.SEG_LINETO: | |
| 448 | + break; | |
| 449 | + case PathIterator.SEG_MOVETO: | |
| 450 | + movx_adjust = x_adjust; | |
| 451 | + movy_adjust = y_adjust; | |
| 452 | + break; | |
| 453 | + case PathIterator.SEG_CLOSE: | |
| 454 | + throw new InternalError("This should be handled earlier."); | |
| 455 | + } | |
| 456 | + curx_adjust = x_adjust; | |
| 457 | + cury_adjust = y_adjust; | |
| 458 | + return type; | |
| 459 | + } | |
| 460 | + | |
| 461 | + public int currentSegment(double[] coords) { | |
| 462 | + float[] tmp = new float[6]; | |
| 463 | + int type = this.currentSegment(tmp); | |
| 464 | + for (int i = 0; i < 6; i++) { | |
| 465 | + coords[i] = (float) tmp[i]; | |
| 466 | + } | |
| 467 | + return type; | |
| 468 | + } | |
| 469 | + | |
| 470 | + public int getWindingRule() { | |
| 471 | + return src.getWindingRule(); | |
| 472 | + } | |
| 473 | + | |
| 474 | + public boolean isDone() { | |
| 475 | + return src.isDone(); | |
| 476 | + } | |
| 477 | + | |
| 478 | + public void next() { | |
| 479 | + src.next(); | |
| 480 | + } | |
| 481 | + } | |
| 482 | + | |
| 483 | + static void pathTo(PathIterator pi, PathConsumer2D pc2d) { | |
| 484 | + RenderingEngine.feedConsumer(pi, pc2d); | |
| 485 | + pc2d.pathDone(); | |
| 486 | + } | |
| 487 | + | |
| 488 | + /** | |
| 489 | + * Construct an antialiased tile generator for the given shape with | |
| 490 | + * the given rendering attributes and store the bounds of the tile | |
| 491 | + * iteration in the bbox parameter. | |
| 492 | + * The {@code at} parameter specifies a transform that should affect | |
| 493 | + * both the shape and the {@code BasicStroke} attributes. | |
| 494 | + * The {@code clip} parameter specifies the current clip in effect | |
| 495 | + * in device coordinates and can be used to prune the data for the | |
| 496 | + * operation, but the renderer is not required to perform any | |
| 497 | + * clipping. | |
| 498 | + * If the {@code BasicStroke} parameter is null then the shape | |
| 499 | + * should be filled as is, otherwise the attributes of the | |
| 500 | + * {@code BasicStroke} should be used to specify a draw operation. | |
| 501 | + * The {@code thin} parameter indicates whether or not the | |
| 502 | + * transformed {@code BasicStroke} represents coordinates smaller | |
| 503 | + * than the minimum resolution of the antialiasing rasterizer as | |
| 504 | + * specified by the {@code getMinimumAAPenWidth()} method. | |
| 505 | + * <p> | |
| 506 | + * Upon returning, this method will fill the {@code bbox} parameter | |
| 507 | + * with 4 values indicating the bounds of the iteration of the | |
| 508 | + * tile generator. | |
| 509 | + * The iteration order of the tiles will be as specified by the | |
| 510 | + * pseudo-code: | |
| 511 | + * <pre> | |
| 512 | + * for (y = bbox[1]; y < bbox[3]; y += tileheight) { | |
| 513 | + * for (x = bbox[0]; x < bbox[2]; x += tilewidth) { | |
| 514 | + * } | |
| 515 | + * } | |
| 516 | + * </pre> | |
| 517 | + * If there is no output to be rendered, this method may return | |
| 518 | + * null. | |
| 519 | + * | |
| 520 | + * @param s the shape to be rendered (fill or draw) | |
| 521 | + * @param at the transform to be applied to the shape and the | |
| 522 | + * stroke attributes | |
| 523 | + * @param clip the current clip in effect in device coordinates | |
| 524 | + * @param bs if non-null, a {@code BasicStroke} whose attributes | |
| 525 | + * should be applied to this operation | |
| 526 | + * @param thin true if the transformed stroke attributes are smaller | |
| 527 | + * than the minimum dropout pen width | |
| 528 | + * @param normalize true if the {@code VALUE_STROKE_NORMALIZE} | |
| 529 | + * {@code RenderingHint} is in effect | |
| 530 | + * @param bbox returns the bounds of the iteration | |
| 531 | + * @return the {@code AATileGenerator} instance to be consulted | |
| 532 | + * for tile coverages, or null if there is no output to render | |
| 533 | + * @since 1.7 | |
| 534 | + */ | |
| 535 | + public AATileGenerator getAATileGenerator(Shape s, | |
| 536 | + AffineTransform at, | |
| 537 | + Region clip, | |
| 538 | + BasicStroke bs, | |
| 539 | + boolean thin, | |
| 540 | + boolean normalize, | |
| 541 | + int bbox[]) | |
| 542 | + { | |
| 543 | + Renderer r; | |
| 544 | + NormMode norm = (normalize) ? NormMode.ON_WITH_AA : NormMode.OFF; | |
| 545 | + if (bs == null) { | |
| 546 | + PathIterator pi; | |
| 547 | + if (normalize) { | |
| 548 | + pi = new NormalizingPathIterator(s.getPathIterator(at), norm); | |
| 549 | + } else { | |
| 550 | + pi = s.getPathIterator(at); | |
| 551 | + } | |
| 552 | + r = new Renderer(3, 3, | |
| 553 | + clip.getLoX(), clip.getLoY(), | |
| 554 | + clip.getWidth(), clip.getHeight(), | |
| 555 | + pi.getWindingRule()); | |
| 556 | + pathTo(pi, r); | |
| 557 | + } else { | |
| 558 | + r = new Renderer(3, 3, | |
| 559 | + clip.getLoX(), clip.getLoY(), | |
| 560 | + clip.getWidth(), clip.getHeight(), | |
| 561 | + PathIterator.WIND_NON_ZERO); | |
| 562 | + strokeTo(s, at, bs, thin, norm, true, r); | |
| 563 | + } | |
| 564 | + r.endRendering(); | |
| 565 | + PiscesTileGenerator ptg = new PiscesTileGenerator(r, r.MAX_AA_ALPHA); | |
| 566 | + ptg.getBbox(bbox); | |
| 567 | + return ptg; | |
| 568 | + } | |
| 569 | + | |
| 570 | + public AATileGenerator getAATileGenerator(double x, double y, | |
| 571 | + double dx1, double dy1, | |
| 572 | + double dx2, double dy2, | |
| 573 | + double lw1, double lw2, | |
| 574 | + Region clip, | |
| 575 | + int bbox[]) | |
| 576 | + { | |
| 577 | + // REMIND: Deal with large coordinates! | |
| 578 | + double ldx1, ldy1, ldx2, ldy2; | |
| 579 | + boolean innerpgram = (lw1 > 0 && lw2 > 0); | |
| 580 | + | |
| 581 | + if (innerpgram) { | |
| 582 | + ldx1 = dx1 * lw1; | |
| 583 | + ldy1 = dy1 * lw1; | |
| 584 | + ldx2 = dx2 * lw2; | |
| 585 | + ldy2 = dy2 * lw2; | |
| 586 | + x -= (ldx1 + ldx2) / 2.0; | |
| 587 | + y -= (ldy1 + ldy2) / 2.0; | |
| 588 | + dx1 += ldx1; | |
| 589 | + dy1 += ldy1; | |
| 590 | + dx2 += ldx2; | |
| 591 | + dy2 += ldy2; | |
| 592 | + if (lw1 > 1 && lw2 > 1) { | |
| 593 | + // Inner parallelogram was entirely consumed by stroke... | |
| 594 | + innerpgram = false; | |
| 595 | + } | |
| 596 | + } else { | |
| 597 | + ldx1 = ldy1 = ldx2 = ldy2 = 0; | |
| 598 | + } | |
| 599 | + | |
| 600 | + Renderer r = new Renderer(3, 3, | |
| 601 | + clip.getLoX(), clip.getLoY(), | |
| 602 | + clip.getWidth(), clip.getHeight(), | |
| 603 | + PathIterator.WIND_EVEN_ODD); | |
| 604 | + | |
| 605 | + r.moveTo((float) x, (float) y); | |
| 606 | + r.lineTo((float) (x+dx1), (float) (y+dy1)); | |
| 607 | + r.lineTo((float) (x+dx1+dx2), (float) (y+dy1+dy2)); | |
| 608 | + r.lineTo((float) (x+dx2), (float) (y+dy2)); | |
| 609 | + r.closePath(); | |
| 610 | + | |
| 611 | + if (innerpgram) { | |
| 612 | + x += ldx1 + ldx2; | |
| 613 | + y += ldy1 + ldy2; | |
| 614 | + dx1 -= 2.0 * ldx1; | |
| 615 | + dy1 -= 2.0 * ldy1; | |
| 616 | + dx2 -= 2.0 * ldx2; | |
| 617 | + dy2 -= 2.0 * ldy2; | |
| 618 | + r.moveTo((float) x, (float) y); | |
| 619 | + r.lineTo((float) (x+dx1), (float) (y+dy1)); | |
| 620 | + r.lineTo((float) (x+dx1+dx2), (float) (y+dy1+dy2)); | |
| 621 | + r.lineTo((float) (x+dx2), (float) (y+dy2)); | |
| 622 | + r.closePath(); | |
| 623 | + } | |
| 624 | + | |
| 625 | + r.pathDone(); | |
| 626 | + | |
| 627 | + r.endRendering(); | |
| 628 | + PiscesTileGenerator ptg = new PiscesTileGenerator(r, r.MAX_AA_ALPHA); | |
| 629 | + ptg.getBbox(bbox); | |
| 630 | + return ptg; | |
| 631 | + } | |
| 632 | + | |
| 633 | + /** | |
| 634 | + * Returns the minimum pen width that the antialiasing rasterizer | |
| 635 | + * can represent without dropouts occuring. | |
| 636 | + * @since 1.7 | |
| 637 | + */ | |
| 638 | + public float getMinimumAAPenSize() { | |
| 639 | + return 0.5f; | |
| 640 | + } | |
| 641 | + | |
| 642 | + static { | |
| 643 | + if (PathIterator.WIND_NON_ZERO != Renderer.WIND_NON_ZERO || | |
| 644 | + PathIterator.WIND_EVEN_ODD != Renderer.WIND_EVEN_ODD || | |
| 645 | + BasicStroke.JOIN_MITER != Stroker.JOIN_MITER || | |
| 646 | + BasicStroke.JOIN_ROUND != Stroker.JOIN_ROUND || | |
| 647 | + BasicStroke.JOIN_BEVEL != Stroker.JOIN_BEVEL || | |
| 648 | + BasicStroke.CAP_BUTT != Stroker.CAP_BUTT || | |
| 649 | + BasicStroke.CAP_ROUND != Stroker.CAP_ROUND || | |
| 650 | + BasicStroke.CAP_SQUARE != Stroker.CAP_SQUARE) | |
| 651 | + { | |
| 652 | + throw new InternalError("mismatched renderer constants"); | |
| 653 | + } | |
| 654 | + } | |
| 655 | +} | |
| 656 | + | |
| @@ -0,0 +1,252 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. | |
| 3 | + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| 4 | + * | |
| 5 | + * This code is free software; you can redistribute it and/or modify it | |
| 6 | + * under the terms of the GNU General Public License version 2 only, as | |
| 7 | + * published by the Free Software Foundation. Oracle designates this | |
| 8 | + * particular file as subject to the "Classpath" exception as provided | |
| 9 | + * by Oracle in the LICENSE file that accompanied this code. | |
| 10 | + * | |
| 11 | + * This code is distributed in the hope that it will be useful, but WITHOUT | |
| 12 | + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| 13 | + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| 14 | + * version 2 for more details (a copy is included in the LICENSE file that | |
| 15 | + * accompanied this code). | |
| 16 | + * | |
| 17 | + * You should have received a copy of the GNU General Public License version | |
| 18 | + * 2 along with this work; if not, write to the Free Software Foundation, | |
| 19 | + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 20 | + * | |
| 21 | + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| 22 | + * or visit www.oracle.com if you need additional information or have any | |
| 23 | + * questions. | |
| 24 | + */ | |
| 25 | + | |
| 26 | +package sun.java2d.pisces; | |
| 27 | + | |
| 28 | +import java.util.Map; | |
| 29 | +import java.util.concurrent.ConcurrentHashMap; | |
| 30 | + | |
| 31 | +import sun.java2d.pipe.AATileGenerator; | |
| 32 | + | |
| 33 | +final class PiscesTileGenerator implements AATileGenerator { | |
| 34 | + public static final int TILE_SIZE = PiscesCache.TILE_SIZE; | |
| 35 | + | |
| 36 | + // perhaps we should be using weak references here, but right now | |
| 37 | + // that's not necessary. The way the renderer is, this map will | |
| 38 | + // never contain more than one element - the one with key 64, since | |
| 39 | + // we only do 8x8 supersampling. | |
| 40 | + private static final Map<Integer, byte[]> alphaMapsCache = new | |
| 41 | + ConcurrentHashMap<Integer, byte[]>(); | |
| 42 | + | |
| 43 | + PiscesCache cache; | |
| 44 | + int x, y; | |
| 45 | + final int maxalpha; | |
| 46 | + private final int maxTileAlphaSum; | |
| 47 | + | |
| 48 | + // The alpha map used by this object (taken out of our map cache) to convert | |
| 49 | + // pixel coverage counts gotten from PiscesCache (which are in the range | |
| 50 | + // [0, maxalpha]) into alpha values, which are in [0,256). | |
| 51 | + byte alphaMap[]; | |
| 52 | + | |
| 53 | + public PiscesTileGenerator(Renderer r, int maxalpha) { | |
| 54 | + this.cache = r.getCache(); | |
| 55 | + this.x = cache.bboxX0; | |
| 56 | + this.y = cache.bboxY0; | |
| 57 | + this.alphaMap = getAlphaMap(maxalpha); | |
| 58 | + this.maxalpha = maxalpha; | |
| 59 | + this.maxTileAlphaSum = TILE_SIZE*TILE_SIZE*maxalpha; | |
| 60 | + } | |
| 61 | + | |
| 62 | + private static byte[] buildAlphaMap(int maxalpha) { | |
| 63 | + byte[] alMap = new byte[maxalpha+1]; | |
| 64 | + int halfmaxalpha = maxalpha>>2; | |
| 65 | + for (int i = 0; i <= maxalpha; i++) { | |
| 66 | + alMap[i] = (byte) ((i * 255 + halfmaxalpha) / maxalpha); | |
| 67 | + } | |
| 68 | + return alMap; | |
| 69 | + } | |
| 70 | + | |
| 71 | + public static byte[] getAlphaMap(int maxalpha) { | |
| 72 | + if (!alphaMapsCache.containsKey(maxalpha)) { | |
| 73 | + alphaMapsCache.put(maxalpha, buildAlphaMap(maxalpha)); | |
| 74 | + } | |
| 75 | + return alphaMapsCache.get(maxalpha); | |
| 76 | + } | |
| 77 | + | |
| 78 | + public void getBbox(int bbox[]) { | |
| 79 | + bbox[0] = cache.bboxX0; | |
| 80 | + bbox[1] = cache.bboxY0; | |
| 81 | + bbox[2] = cache.bboxX1; | |
| 82 | + bbox[3] = cache.bboxY1; | |
| 83 | + //System.out.println("bbox["+bbox[0]+", "+bbox[1]+" => "+bbox[2]+", "+bbox[3]+"]"); | |
| 84 | + } | |
| 85 | + | |
| 86 | + /** | |
| 87 | + * Gets the width of the tiles that the generator batches output into. | |
| 88 | + * @return the width of the standard alpha tile | |
| 89 | + */ | |
| 90 | + public int getTileWidth() { | |
| 91 | + return TILE_SIZE; | |
| 92 | + } | |
| 93 | + | |
| 94 | + /** | |
| 95 | + * Gets the height of the tiles that the generator batches output into. | |
| 96 | + * @return the height of the standard alpha tile | |
| 97 | + */ | |
| 98 | + public int getTileHeight() { | |
| 99 | + return TILE_SIZE; | |
| 100 | + } | |
| 101 | + | |
| 102 | + /** | |
| 103 | + * Gets the typical alpha value that will characterize the current | |
| 104 | + * tile. | |
| 105 | + * The answer may be 0x00 to indicate that the current tile has | |
| 106 | + * no coverage in any of its pixels, or it may be 0xff to indicate | |
| 107 | + * that the current tile is completely covered by the path, or any | |
| 108 | + * other value to indicate non-trivial coverage cases. | |
| 109 | + * @return 0x00 for no coverage, 0xff for total coverage, or any other | |
| 110 | + * value for partial coverage of the tile | |
| 111 | + */ | |
| 112 | + public int getTypicalAlpha() { | |
| 113 | + int al = cache.alphaSumInTile(x, y); | |
| 114 | + // Note: if we have a filled rectangle that doesn't end on a tile | |
| 115 | + // border, we could still return 0xff, even though al!=maxTileAlphaSum | |
| 116 | + // This is because if we return 0xff, our users will fill a rectangle | |
| 117 | + // starting at x,y that has width = Math.min(TILE_SIZE, bboxX1-x), | |
| 118 | + // and height min(TILE_SIZE,bboxY1-y), which is what should happen. | |
| 119 | + // However, to support this, we would have to use 2 Math.min's | |
| 120 | + // and 2 multiplications per tile, instead of just 2 multiplications | |
| 121 | + // to compute maxTileAlphaSum. The savings offered would probably | |
| 122 | + // not be worth it, considering how rare this case is. | |
| 123 | + // Note: I have not tested this, so in the future if it is determined | |
| 124 | + // that it is worth it, it should be implemented. Perhaps this method's | |
| 125 | + // interface should be changed to take arguments the width and height | |
| 126 | + // of the current tile. This would eliminate the 2 Math.min calls that | |
| 127 | + // would be needed here, since our caller needs to compute these 2 | |
| 128 | + // values anyway. | |
| 129 | + return (al == 0x00 ? 0x00 : | |
| 130 | + (al == maxTileAlphaSum ? 0xff : 0x80)); | |
| 131 | + } | |
| 132 | + | |
| 133 | + /** | |
| 134 | + * Skips the current tile and moves on to the next tile. | |
| 135 | + * Either this method, or the getAlpha() method should be called | |
| 136 | + * once per tile, but not both. | |
| 137 | + */ | |
| 138 | + public void nextTile() { | |
| 139 | + if ((x += TILE_SIZE) >= cache.bboxX1) { | |
| 140 | + x = cache.bboxX0; | |
| 141 | + y += TILE_SIZE; | |
| 142 | + } | |
| 143 | + } | |
| 144 | + | |
| 145 | + /** | |
| 146 | + * Gets the alpha coverage values for the current tile. | |
| 147 | + * Either this method, or the nextTile() method should be called | |
| 148 | + * once per tile, but not both. | |
| 149 | + */ | |
| 150 | + public void getAlpha(byte tile[], int offset, int rowstride) { | |
| 151 | + // Decode run-length encoded alpha mask data | |
| 152 | + // The data for row j begins at cache.rowOffsetsRLE[j] | |
| 153 | + // and is encoded as a set of 2-byte pairs (val, runLen) | |
| 154 | + // terminated by a (0, 0) pair. | |
| 155 | + | |
| 156 | + int x0 = this.x; | |
| 157 | + int x1 = x0 + TILE_SIZE; | |
| 158 | + int y0 = this.y; | |
| 159 | + int y1 = y0 + TILE_SIZE; | |
| 160 | + if (x1 > cache.bboxX1) x1 = cache.bboxX1; | |
| 161 | + if (y1 > cache.bboxY1) y1 = cache.bboxY1; | |
| 162 | + y0 -= cache.bboxY0; | |
| 163 | + y1 -= cache.bboxY0; | |
| 164 | + | |
| 165 | + int idx = offset; | |
| 166 | + for (int cy = y0; cy < y1; cy++) { | |
| 167 | + int[] row = cache.rowAARLE[cy]; | |
| 168 | + assert row != null; | |
| 169 | + int cx = cache.minTouched(cy); | |
| 170 | + if (cx > x1) cx = x1; | |
| 171 | + | |
| 172 | + for (int i = x0; i < cx; i++) { | |
| 173 | + tile[idx++] = 0x00; | |
| 174 | + } | |
| 175 | + | |
| 176 | + int pos = 2; | |
| 177 | + while (cx < x1 && pos < row[1]) { | |
| 178 | + byte val; | |
| 179 | + int runLen = 0; | |
| 180 | + assert row[1] > 2; | |
| 181 | + try { | |
| 182 | + val = alphaMap[row[pos]]; | |
| 183 | + runLen = row[pos + 1]; | |
| 184 | + assert runLen > 0; | |
| 185 | + } catch (RuntimeException e0) { | |
| 186 | + System.out.println("maxalpha = "+maxalpha); | |
| 187 | + System.out.println("tile["+x0+", "+y0+ | |
| 188 | + " => "+x1+", "+y1+"]"); | |
| 189 | + System.out.println("cx = "+cx+", cy = "+cy); | |
| 190 | + System.out.println("idx = "+idx+", pos = "+pos); | |
| 191 | + System.out.println("len = "+runLen); | |
| 192 | + System.out.print(cache.toString()); | |
| 193 | + e0.printStackTrace(); | |
| 194 | + throw e0; | |
| 195 | + } | |
| 196 | + | |
| 197 | + int rx0 = cx; | |
| 198 | + cx += runLen; | |
| 199 | + int rx1 = cx; | |
| 200 | + if (rx0 < x0) rx0 = x0; | |
| 201 | + if (rx1 > x1) rx1 = x1; | |
| 202 | + runLen = rx1 - rx0; | |
| 203 | + //System.out.println("M["+runLen+"]"); | |
| 204 | + while (--runLen >= 0) { | |
| 205 | + try { | |
| 206 | + tile[idx++] = val; | |
| 207 | + } catch (RuntimeException e) { | |
| 208 | + System.out.println("maxalpha = "+maxalpha); | |
| 209 | + System.out.println("tile["+x0+", "+y0+ | |
| 210 | + " => "+x1+", "+y1+"]"); | |
| 211 | + System.out.println("cx = "+cx+", cy = "+cy); | |
| 212 | + System.out.println("idx = "+idx+", pos = "+pos); | |
| 213 | + System.out.println("rx0 = "+rx0+", rx1 = "+rx1); | |
| 214 | + System.out.println("len = "+runLen); | |
| 215 | + System.out.print(cache.toString()); | |
| 216 | + e.printStackTrace(); | |
| 217 | + throw e; | |
| 218 | + } | |
| 219 | + } | |
| 220 | + pos += 2; | |
| 221 | + } | |
| 222 | + if (cx < x0) { cx = x0; } | |
| 223 | + while (cx < x1) { | |
| 224 | + tile[idx++] = 0x00; | |
| 225 | + cx++; | |
| 226 | + } | |
| 227 | + /* | |
| 228 | + for (int i = idx - (x1-x0); i < idx; i++) { | |
| 229 | + System.out.print(hex(tile[i], 2)); | |
| 230 | + } | |
| 231 | + System.out.println(); | |
| 232 | + */ | |
| 233 | + idx += (rowstride - (x1-x0)); | |
| 234 | + } | |
| 235 | + nextTile(); | |
| 236 | + } | |
| 237 | + | |
| 238 | + static String hex(int v, int d) { | |
| 239 | + String s = Integer.toHexString(v); | |
| 240 | + while (s.length() < d) { | |
| 241 | + s = "0"+s; | |
| 242 | + } | |
| 243 | + return s.substring(0, d); | |
| 244 | + } | |
| 245 | + | |
| 246 | + /** | |
| 247 | + * Disposes this tile generator. | |
| 248 | + * No further calls will be made on this instance. | |
| 249 | + */ | |
| 250 | + public void dispose() {} | |
| 251 | +} | |
| 252 | + | |
| @@ -0,0 +1,571 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. | |
| 3 | + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| 4 | + * | |
| 5 | + * This code is free software; you can redistribute it and/or modify it | |
| 6 | + * under the terms of the GNU General Public License version 2 only, as | |
| 7 | + * published by the Free Software Foundation. Oracle designates this | |
| 8 | + * particular file as subject to the "Classpath" exception as provided | |
| 9 | + * by Oracle in the LICENSE file that accompanied this code. | |
| 10 | + * | |
| 11 | + * This code is distributed in the hope that it will be useful, but WITHOUT | |
| 12 | + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| 13 | + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| 14 | + * version 2 for more details (a copy is included in the LICENSE file that | |
| 15 | + * accompanied this code). | |
| 16 | + * | |
| 17 | + * You should have received a copy of the GNU General Public License version | |
| 18 | + * 2 along with this work; if not, write to the Free Software Foundation, | |
| 19 | + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 20 | + * | |
| 21 | + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| 22 | + * or visit www.oracle.com if you need additional information or have any | |
| 23 | + * questions. | |
| 24 | + */ | |
| 25 | + | |
| 26 | +package sun.java2d.pisces; | |
| 27 | + | |
| 28 | +import sun.awt.geom.PathConsumer2D; | |
| 29 | + | |
| 30 | +final class Renderer implements PathConsumer2D { | |
| 31 | + | |
| 32 | + private class ScanlineIterator { | |
| 33 | + | |
| 34 | + private int[] crossings; | |
| 35 | + | |
| 36 | + // crossing bounds. The bounds are not necessarily tight (the scan line | |
| 37 | + // at minY, for example, might have no crossings). The x bounds will | |
| 38 | + // be accumulated as crossings are computed. | |
| 39 | + private final int maxY; | |
| 40 | + private int nextY; | |
| 41 | + | |
| 42 | + // indices into the segment pointer lists. They indicate the "active" | |
| 43 | + // sublist in the segment lists (the portion of the list that contains | |
| 44 | + // all the segments that cross the next scan line). | |
| 45 | + private int edgeCount; | |
| 46 | + private int[] edgePtrs; | |
| 47 | + | |
| 48 | + private static final int INIT_CROSSINGS_SIZE = 10; | |
| 49 | + | |
| 50 | + // Preconditions: Only subpixel scanlines in the range | |
| 51 | + // (start <= subpixel_y <= end) will be evaluated. No | |
| 52 | + // edge may have a valid (i.e. inside the supplied clip) | |
| 53 | + // crossing that would be generated outside that range. | |
| 54 | + private ScanlineIterator(int start, int end) { | |
| 55 | + crossings = new int[INIT_CROSSINGS_SIZE]; | |
| 56 | + edgePtrs = new int[INIT_CROSSINGS_SIZE]; | |
| 57 | + | |
| 58 | + nextY = start; | |
| 59 | + maxY = end; | |
| 60 | + edgeCount = 0; | |
| 61 | + } | |
| 62 | + | |
| 63 | + private int next() { | |
| 64 | + int cury = nextY++; | |
| 65 | + int bucket = cury - boundsMinY; | |
| 66 | + int count = this.edgeCount; | |
| 67 | + int ptrs[] = this.edgePtrs; | |
| 68 | + int bucketcount = edgeBucketCounts[bucket]; | |
| 69 | + if ((bucketcount & 0x1) != 0) { | |
| 70 | + int newCount = 0; | |
| 71 | + for (int i = 0; i < count; i++) { | |
| 72 | + int ecur = ptrs[i]; | |
| 73 | + if (edges[ecur+YMAX] > cury) { | |
| 74 | + ptrs[newCount++] = ecur; | |
| 75 | + } | |
| 76 | + } | |
| 77 | + count = newCount; | |
| 78 | + } | |
| 79 | + ptrs = Helpers.widenArray(ptrs, count, bucketcount >> 1); | |
| 80 | + for (int ecur = edgeBuckets[bucket]; ecur != NULL; ecur = (int)edges[ecur+NEXT]) { | |
| 81 | + ptrs[count++] = ecur; | |
| 82 | + // REMIND: Adjust start Y if necessary | |
| 83 | + } | |
| 84 | + this.edgePtrs = ptrs; | |
| 85 | + this.edgeCount = count; | |
| 86 | +// if ((count & 0x1) != 0) { | |
| 87 | +// System.out.println("ODD NUMBER OF EDGES!!!!"); | |
| 88 | +// } | |
| 89 | + int xings[] = this.crossings; | |
| 90 | + if (xings.length < count) { | |
| 91 | + this.crossings = xings = new int[ptrs.length]; | |
| 92 | + } | |
| 93 | + for (int i = 0; i < count; i++) { | |
| 94 | + int ecur = ptrs[i]; | |
| 95 | + float curx = edges[ecur+CURX]; | |
| 96 | + int cross = ((int) curx) << 1; | |
| 97 | + edges[ecur+CURX] = curx + edges[ecur+SLOPE]; | |
| 98 | + if (edges[ecur+OR] > 0) { | |
| 99 | + cross |= 1; | |
| 100 | + } | |
| 101 | + int j = i; | |
| 102 | + while (--j >= 0) { | |
| 103 | + int jcross = xings[j]; | |
| 104 | + if (jcross <= cross) { | |
| 105 | + break; | |
| 106 | + } | |
| 107 | + xings[j+1] = jcross; | |
| 108 | + ptrs[j+1] = ptrs[j]; | |
| 109 | + } | |
| 110 | + xings[j+1] = cross; | |
| 111 | + ptrs[j+1] = ecur; | |
| 112 | + } | |
| 113 | + return count; | |
| 114 | + } | |
| 115 | + | |
| 116 | + private boolean hasNext() { | |
| 117 | + return nextY < maxY; | |
| 118 | + } | |
| 119 | + | |
| 120 | + private int curY() { | |
| 121 | + return nextY - 1; | |
| 122 | + } | |
| 123 | + } | |
| 124 | + | |
| 125 | + | |
| 126 | +////////////////////////////////////////////////////////////////////////////// | |
| 127 | +// EDGE LIST | |
| 128 | +////////////////////////////////////////////////////////////////////////////// | |
| 129 | +// TODO(maybe): very tempting to use fixed point here. A lot of opportunities | |
| 130 | +// for shifts and just removing certain operations altogether. | |
| 131 | + | |
| 132 | + // common to all types of input path segments. | |
| 133 | + private static final int YMAX = 0; | |
| 134 | + private static final int CURX = 1; | |
| 135 | + // NEXT and OR are meant to be indices into "int" fields, but arrays must | |
| 136 | + // be homogenous, so every field is a float. However floats can represent | |
| 137 | + // exactly up to 26 bit ints, so we're ok. | |
| 138 | + private static final int OR = 2; | |
| 139 | + private static final int SLOPE = 3; | |
| 140 | + private static final int NEXT = 4; | |
| 141 | + | |
| 142 | + private float edgeMinY = Float.POSITIVE_INFINITY; | |
| 143 | + private float edgeMaxY = Float.NEGATIVE_INFINITY; | |
| 144 | + private float edgeMinX = Float.POSITIVE_INFINITY; | |
| 145 | + private float edgeMaxX = Float.NEGATIVE_INFINITY; | |
| 146 | + | |
| 147 | + private static final int SIZEOF_EDGE = 5; | |
| 148 | + // don't just set NULL to -1, because we want NULL+NEXT to be negative. | |
| 149 | + private static final int NULL = -SIZEOF_EDGE; | |
| 150 | + private float[] edges = null; | |
| 151 | + private static final int INIT_NUM_EDGES = 8; | |
| 152 | + private int[] edgeBuckets = null; | |
| 153 | + private int[] edgeBucketCounts = null; // 2*newedges + (1 if pruning needed) | |
| 154 | + private int numEdges; | |
| 155 | + | |
| 156 | + private static final float DEC_BND = 20f; | |
| 157 | + private static final float INC_BND = 8f; | |
| 158 | + | |
| 159 | + // each bucket is a linked list. this method adds eptr to the | |
| 160 | + // start of the "bucket"th linked list. | |
| 161 | + private void addEdgeToBucket(final int eptr, final int bucket) { | |
| 162 | + edges[eptr+NEXT] = edgeBuckets[bucket]; | |
| 163 | + edgeBuckets[bucket] = eptr; | |
| 164 | + edgeBucketCounts[bucket] += 2; | |
| 165 | + } | |
| 166 | + | |
| 167 | + // Flattens using adaptive forward differencing. This only carries out | |
| 168 | + // one iteration of the AFD loop. All it does is update AFD variables (i.e. | |
| 169 | + // X0, Y0, D*[X|Y], COUNT; not variables used for computing scanline crossings). | |
| 170 | + private void quadBreakIntoLinesAndAdd(float x0, float y0, | |
| 171 | + final Curve c, | |
| 172 | + final float x2, final float y2) | |
| 173 | + { | |
| 174 | + final float QUAD_DEC_BND = 32; | |
| 175 | + final int countlg = 4; | |
| 176 | + int count = 1 << countlg; | |
| 177 | + int countsq = count * count; | |
| 178 | + float maxDD = Math.max(c.dbx / countsq, c.dby / countsq); | |
| 179 | + while (maxDD > QUAD_DEC_BND) { | |
| 180 | + maxDD /= 4; | |
| 181 | + count <<= 1; | |
| 182 | + } | |
| 183 | + | |
| 184 | + countsq = count * count; | |
| 185 | + final float ddx = c.dbx / countsq; | |
| 186 | + final float ddy = c.dby / countsq; | |
| 187 | + float dx = c.bx / countsq + c.cx / count; | |
| 188 | + float dy = c.by / countsq + c.cy / count; | |
| 189 | + | |
| 190 | + while (count-- > 1) { | |
| 191 | + float x1 = x0 + dx; | |
| 192 | + dx += ddx; | |
| 193 | + float y1 = y0 + dy; | |
| 194 | + dy += ddy; | |
| 195 | + addLine(x0, y0, x1, y1); | |
| 196 | + x0 = x1; | |
| 197 | + y0 = y1; | |
| 198 | + } | |
| 199 | + addLine(x0, y0, x2, y2); | |
| 200 | + } | |
| 201 | + | |
| 202 | + // x0, y0 and x3,y3 are the endpoints of the curve. We could compute these | |
| 203 | + // using c.xat(0),c.yat(0) and c.xat(1),c.yat(1), but this might introduce | |
| 204 | + // numerical errors, and our callers already have the exact values. | |
| 205 | + // Another alternative would be to pass all the control points, and call c.set | |
| 206 | + // here, but then too many numbers are passed around. | |
| 207 | + private void curveBreakIntoLinesAndAdd(float x0, float y0, | |
| 208 | + final Curve c, | |
| 209 | + final float x3, final float y3) | |
| 210 | + { | |
| 211 | + final int countlg = 3; | |
| 212 | + int count = 1 << countlg; | |
| 213 | + | |
| 214 | + // the dx and dy refer to forward differencing variables, not the last | |
| 215 | + // coefficients of the "points" polynomial | |
| 216 | + float dddx, dddy, ddx, ddy, dx, dy; | |
| 217 | + dddx = 2f * c.dax / (1 << (3 * countlg)); | |
| 218 | + dddy = 2f * c.day / (1 << (3 * countlg)); | |
| 219 | + | |
| 220 | + ddx = dddx + c.dbx / (1 << (2 * countlg)); | |
| 221 | + ddy = dddy + c.dby / (1 << (2 * countlg)); | |
| 222 | + dx = c.ax / (1 << (3 * countlg)) + c.bx / (1 << (2 * countlg)) + c.cx / (1 << countlg); | |
| 223 | + dy = c.ay / (1 << (3 * countlg)) + c.by / (1 << (2 * countlg)) + c.cy / (1 << countlg); | |
| 224 | + | |
| 225 | + // we use x0, y0 to walk the line | |
| 226 | + float x1 = x0, y1 = y0; | |
| 227 | + while (count > 0) { | |
| 228 | + while (Math.abs(ddx) > DEC_BND || Math.abs(ddy) > DEC_BND) { | |
| 229 | + dddx /= 8; | |
| 230 | + dddy /= 8; | |
| 231 | + ddx = ddx/4 - dddx; | |
| 232 | + ddy = ddy/4 - dddy; | |
| 233 | + dx = (dx - ddx) / 2; | |
| 234 | + dy = (dy - ddy) / 2; | |
| 235 | + count <<= 1; | |
| 236 | + } | |
| 237 | + // can only do this on even "count" values, because we must divide count by 2 | |
| 238 | + while (count % 2 == 0 && Math.abs(dx) <= INC_BND && Math.abs(dy) <= INC_BND) { | |
| 239 | + dx = 2 * dx + ddx; | |
| 240 | + dy = 2 * dy + ddy; | |
| 241 | + ddx = 4 * (ddx + dddx); | |
| 242 | + ddy = 4 * (ddy + dddy); | |
| 243 | + dddx = 8 * dddx; | |
| 244 | + dddy = 8 * dddy; | |
| 245 | + count >>= 1; | |
| 246 | + } | |
| 247 | + count--; | |
| 248 | + if (count > 0) { | |
| 249 | + x1 += dx; | |
| 250 | + dx += ddx; | |
| 251 | + ddx += dddx; | |
| 252 | + y1 += dy; | |
| 253 | + dy += ddy; | |
| 254 | + ddy += dddy; | |
| 255 | + } else { | |
| 256 | + x1 = x3; | |
| 257 | + y1 = y3; | |
| 258 | + } | |
| 259 | + addLine(x0, y0, x1, y1); | |
| 260 | + x0 = x1; | |
| 261 | + y0 = y1; | |
| 262 | + } | |
| 263 | + } | |
| 264 | + | |
| 265 | + private void addLine(float x1, float y1, float x2, float y2) { | |
| 266 | + float or = 1; // orientation of the line. 1 if y increases, 0 otherwise. | |
| 267 | + if (y2 < y1) { | |
| 268 | + or = y2; // no need to declare a temp variable. We have or. | |
| 269 | + y2 = y1; | |
| 270 | + y1 = or; | |
| 271 | + or = x2; | |
| 272 | + x2 = x1; | |
| 273 | + x1 = or; | |
| 274 | + or = 0; | |
| 275 | + } | |
| 276 | + final int firstCrossing = Math.max((int)Math.ceil(y1), boundsMinY); | |
| 277 | + final int lastCrossing = Math.min((int)Math.ceil(y2), boundsMaxY); | |
| 278 | + if (firstCrossing >= lastCrossing) { | |
| 279 | + return; | |
| 280 | + } | |
| 281 | + if (y1 < edgeMinY) { edgeMinY = y1; } | |
| 282 | + if (y2 > edgeMaxY) { edgeMaxY = y2; } | |
| 283 | + | |
| 284 | + final float slope = (x2 - x1) / (y2 - y1); | |
| 285 | + | |
| 286 | + if (slope > 0) { // <==> x1 < x2 | |
| 287 | + if (x1 < edgeMinX) { edgeMinX = x1; } | |
| 288 | + if (x2 > edgeMaxX) { edgeMaxX = x2; } | |
| 289 | + } else { | |
| 290 | + if (x2 < edgeMinX) { edgeMinX = x2; } | |
| 291 | + if (x1 > edgeMaxX) { edgeMaxX = x1; } | |
| 292 | + } | |
| 293 | + | |
| 294 | + final int ptr = numEdges * SIZEOF_EDGE; | |
| 295 | + edges = Helpers.widenArray(edges, ptr, SIZEOF_EDGE); | |
| 296 | + numEdges++; | |
| 297 | + edges[ptr+OR] = or; | |
| 298 | + edges[ptr+CURX] = x1 + (firstCrossing - y1) * slope; | |
| 299 | + edges[ptr+SLOPE] = slope; | |
| 300 | + edges[ptr+YMAX] = lastCrossing; | |
| 301 | + final int bucketIdx = firstCrossing - boundsMinY; | |
| 302 | + addEdgeToBucket(ptr, bucketIdx); | |
| 303 | + edgeBucketCounts[lastCrossing - boundsMinY] |= 1; | |
| 304 | + } | |
| 305 | + | |
| 306 | +// END EDGE LIST | |
| 307 | +////////////////////////////////////////////////////////////////////////////// | |
| 308 | + | |
| 309 | + | |
| 310 | + public static final int WIND_EVEN_ODD = 0; | |
| 311 | + public static final int WIND_NON_ZERO = 1; | |
| 312 | + | |
| 313 | + // Antialiasing | |
| 314 | + final private int SUBPIXEL_LG_POSITIONS_X; | |
| 315 | + final private int SUBPIXEL_LG_POSITIONS_Y; | |
| 316 | + final private int SUBPIXEL_POSITIONS_X; | |
| 317 | + final private int SUBPIXEL_POSITIONS_Y; | |
| 318 | + final private int SUBPIXEL_MASK_X; | |
| 319 | + final private int SUBPIXEL_MASK_Y; | |
| 320 | + final int MAX_AA_ALPHA; | |
| 321 | + | |
| 322 | + // Cache to store RLE-encoded coverage mask of the current primitive | |
| 323 | + PiscesCache cache; | |
| 324 | + | |
| 325 | + // Bounds of the drawing region, at subpixel precision. | |
| 326 | + private final int boundsMinX, boundsMinY, boundsMaxX, boundsMaxY; | |
| 327 | + | |
| 328 | + // Current winding rule | |
| 329 | + private final int windingRule; | |
| 330 | + | |
| 331 | + // Current drawing position, i.e., final point of last segment | |
| 332 | + private float x0, y0; | |
| 333 | + | |
| 334 | + // Position of most recent 'moveTo' command | |
| 335 | + private float pix_sx0, pix_sy0; | |
| 336 | + | |
| 337 | + public Renderer(int subpixelLgPositionsX, int subpixelLgPositionsY, | |
| 338 | + int pix_boundsX, int pix_boundsY, | |
| 339 | + int pix_boundsWidth, int pix_boundsHeight, | |
| 340 | + int windingRule) | |
| 341 | + { | |
| 342 | + this.SUBPIXEL_LG_POSITIONS_X = subpixelLgPositionsX; | |
| 343 | + this.SUBPIXEL_LG_POSITIONS_Y = subpixelLgPositionsY; | |
| 344 | + this.SUBPIXEL_MASK_X = (1 << (SUBPIXEL_LG_POSITIONS_X)) - 1; | |
| 345 | + this.SUBPIXEL_MASK_Y = (1 << (SUBPIXEL_LG_POSITIONS_Y)) - 1; | |
| 346 | + this.SUBPIXEL_POSITIONS_X = 1 << (SUBPIXEL_LG_POSITIONS_X); | |
| 347 | + this.SUBPIXEL_POSITIONS_Y = 1 << (SUBPIXEL_LG_POSITIONS_Y); | |
| 348 | + this.MAX_AA_ALPHA = (SUBPIXEL_POSITIONS_X * SUBPIXEL_POSITIONS_Y); | |
| 349 | + | |
| 350 | + this.windingRule = windingRule; | |
| 351 | + | |
| 352 | + this.boundsMinX = pix_boundsX * SUBPIXEL_POSITIONS_X; | |
| 353 | + this.boundsMinY = pix_boundsY * SUBPIXEL_POSITIONS_Y; | |
| 354 | + this.boundsMaxX = (pix_boundsX + pix_boundsWidth) * SUBPIXEL_POSITIONS_X; | |
| 355 | + this.boundsMaxY = (pix_boundsY + pix_boundsHeight) * SUBPIXEL_POSITIONS_Y; | |
| 356 | + | |
| 357 | + edges = new float[INIT_NUM_EDGES * SIZEOF_EDGE]; | |
| 358 | + numEdges = 0; | |
| 359 | + edgeBuckets = new int[boundsMaxY - boundsMinY]; | |
| 360 | + java.util.Arrays.fill(edgeBuckets, NULL); | |
| 361 | + edgeBucketCounts = new int[edgeBuckets.length + 1]; | |
| 362 | + } | |
| 363 | + | |
| 364 | + private float tosubpixx(float pix_x) { | |
| 365 | + return pix_x * SUBPIXEL_POSITIONS_X; | |
| 366 | + } | |
| 367 | + private float tosubpixy(float pix_y) { | |
| 368 | + return pix_y * SUBPIXEL_POSITIONS_Y; | |
| 369 | + } | |
| 370 | + | |
| 371 | + public void moveTo(float pix_x0, float pix_y0) { | |
| 372 | + closePath(); | |
| 373 | + this.pix_sx0 = pix_x0; | |
| 374 | + this.pix_sy0 = pix_y0; | |
| 375 | + this.y0 = tosubpixy(pix_y0); | |
| 376 | + this.x0 = tosubpixx(pix_x0); | |
| 377 | + } | |
| 378 | + | |
| 379 | + public void lineTo(float pix_x1, float pix_y1) { | |
| 380 | + float x1 = tosubpixx(pix_x1); | |
| 381 | + float y1 = tosubpixy(pix_y1); | |
| 382 | + addLine(x0, y0, x1, y1); | |
| 383 | + x0 = x1; | |
| 384 | + y0 = y1; | |
| 385 | + } | |
| 386 | + | |
| 387 | + private Curve c = new Curve(); | |
| 388 | + @Override public void curveTo(float x1, float y1, | |
| 389 | + float x2, float y2, | |
| 390 | + float x3, float y3) | |
| 391 | + { | |
| 392 | + final float xe = tosubpixx(x3); | |
| 393 | + final float ye = tosubpixy(y3); | |
| 394 | + c.set(x0, y0, tosubpixx(x1), tosubpixy(y1), tosubpixx(x2), tosubpixy(y2), xe, ye); | |
| 395 | + curveBreakIntoLinesAndAdd(x0, y0, c, xe, ye); | |
| 396 | + x0 = xe; | |
| 397 | + y0 = ye; | |
| 398 | + } | |
| 399 | + | |
| 400 | + @Override public void quadTo(float x1, float y1, float x2, float y2) { | |
| 401 | + final float xe = tosubpixx(x2); | |
| 402 | + final float ye = tosubpixy(y2); | |
| 403 | + c.set(x0, y0, tosubpixx(x1), tosubpixy(y1), xe, ye); | |
| 404 | + quadBreakIntoLinesAndAdd(x0, y0, c, xe, ye); | |
| 405 | + x0 = xe; | |
| 406 | + y0 = ye; | |
| 407 | + } | |
| 408 | + | |
| 409 | + public void closePath() { | |
| 410 | + // lineTo expects its input in pixel coordinates. | |
| 411 | + lineTo(pix_sx0, pix_sy0); | |
| 412 | + } | |
| 413 | + | |
| 414 | + public void pathDone() { | |
| 415 | + closePath(); | |
| 416 | + } | |
| 417 | + | |
| 418 | + | |
| 419 | + @Override | |
| 420 | + public long getNativeConsumer() { | |
| 421 | + throw new InternalError("Renderer does not use a native consumer."); | |
| 422 | + } | |
| 423 | + | |
| 424 | + private void _endRendering(final int pix_bboxx0, final int pix_bboxx1, | |
| 425 | + int ymin, int ymax) | |
| 426 | + { | |
| 427 | + // Mask to determine the relevant bit of the crossing sum | |
| 428 | + // 0x1 if EVEN_ODD, all bits if NON_ZERO | |
| 429 | + int mask = (windingRule == WIND_EVEN_ODD) ? 0x1 : ~0x0; | |
| 430 | + | |
| 431 | + // add 2 to better deal with the last pixel in a pixel row. | |
| 432 | + int width = pix_bboxx1 - pix_bboxx0; | |
| 433 | + int[] alpha = new int[width+2]; | |
| 434 | + | |
| 435 | + int bboxx0 = pix_bboxx0 << SUBPIXEL_LG_POSITIONS_X; | |
| 436 | + int bboxx1 = pix_bboxx1 << SUBPIXEL_LG_POSITIONS_X; | |
| 437 | + | |
| 438 | + // Now we iterate through the scanlines. We must tell emitRow the coord | |
| 439 | + // of the first non-transparent pixel, so we must keep accumulators for | |
| 440 | + // the first and last pixels of the section of the current pixel row | |
| 441 | + // that we will emit. | |
| 442 | + // We also need to accumulate pix_bbox*, but the iterator does it | |
| 443 | + // for us. We will just get the values from it once this loop is done | |
| 444 | + int pix_maxX = Integer.MIN_VALUE; | |
| 445 | + int pix_minX = Integer.MAX_VALUE; | |
| 446 | + | |
| 447 | + int y = boundsMinY; // needs to be declared here so we emit the last row properly. | |
| 448 | + ScanlineIterator it = this.new ScanlineIterator(ymin, ymax); | |
| 449 | + for ( ; it.hasNext(); ) { | |
| 450 | + int numCrossings = it.next(); | |
| 451 | + int[] crossings = it.crossings; | |
| 452 | + y = it.curY(); | |
| 453 | + | |
| 454 | + if (numCrossings > 0) { | |
| 455 | + int lowx = crossings[0] >> 1; | |
| 456 | + int highx = crossings[numCrossings - 1] >> 1; | |
| 457 | + int x0 = Math.max(lowx, bboxx0); | |
| 458 | + int x1 = Math.min(highx, bboxx1); | |
| 459 | + | |
| 460 | + pix_minX = Math.min(pix_minX, x0 >> SUBPIXEL_LG_POSITIONS_X); | |
| 461 | + pix_maxX = Math.max(pix_maxX, x1 >> SUBPIXEL_LG_POSITIONS_X); | |
| 462 | + } | |
| 463 | + | |
| 464 | + int sum = 0; | |
| 465 | + int prev = bboxx0; | |
| 466 | + for (int i = 0; i < numCrossings; i++) { | |
| 467 | + int curxo = crossings[i]; | |
| 468 | + int curx = curxo >> 1; | |
| 469 | + // to turn {0, 1} into {-1, 1}, multiply by 2 and subtract 1. | |
| 470 | + int crorientation = ((curxo & 0x1) << 1) - 1; | |
| 471 | + if ((sum & mask) != 0) { | |
| 472 | + int x0 = Math.max(prev, bboxx0); | |
| 473 | + int x1 = Math.min(curx, bboxx1); | |
| 474 | + if (x0 < x1) { | |
| 475 | + x0 -= bboxx0; // turn x0, x1 from coords to indeces | |
| 476 | + x1 -= bboxx0; // in the alpha array. | |
| 477 | + | |
| 478 | + int pix_x = x0 >> SUBPIXEL_LG_POSITIONS_X; | |
| 479 | + int pix_xmaxm1 = (x1 - 1) >> SUBPIXEL_LG_POSITIONS_X; | |
| 480 | + | |
| 481 | + if (pix_x == pix_xmaxm1) { | |
| 482 | + // Start and end in same pixel | |
| 483 | + alpha[pix_x] += (x1 - x0); | |
| 484 | + alpha[pix_x+1] -= (x1 - x0); | |
| 485 | + } else { | |
| 486 | + int pix_xmax = x1 >> SUBPIXEL_LG_POSITIONS_X; | |
| 487 | + alpha[pix_x] += SUBPIXEL_POSITIONS_X - (x0 & SUBPIXEL_MASK_X); | |
| 488 | + alpha[pix_x+1] += (x0 & SUBPIXEL_MASK_X); | |
| 489 | + alpha[pix_xmax] -= SUBPIXEL_POSITIONS_X - (x1 & SUBPIXEL_MASK_X); | |
| 490 | + alpha[pix_xmax+1] -= (x1 & SUBPIXEL_MASK_X); | |
| 491 | + } | |
| 492 | + } | |
| 493 | + } | |
| 494 | + sum += crorientation; | |
| 495 | + prev = curx; | |
| 496 | + } | |
| 497 | + | |
| 498 | + // even if this last row had no crossings, alpha will be zeroed | |
| 499 | + // from the last emitRow call. But this doesn't matter because | |
| 500 | + // maxX < minX, so no row will be emitted to the cache. | |
| 501 | + if ((y & SUBPIXEL_MASK_Y) == SUBPIXEL_MASK_Y) { | |
| 502 | + emitRow(alpha, y >> SUBPIXEL_LG_POSITIONS_Y, pix_minX, pix_maxX); | |
| 503 | + pix_minX = Integer.MAX_VALUE; | |
| 504 | + pix_maxX = Integer.MIN_VALUE; | |
| 505 | + } | |
| 506 | + } | |
| 507 | + | |
| 508 | + // Emit final row | |
| 509 | + if (pix_maxX >= pix_minX) { | |
| 510 | + emitRow(alpha, y >> SUBPIXEL_LG_POSITIONS_Y, pix_minX, pix_maxX); | |
| 511 | + } | |
| 512 | + } | |
| 513 | + | |
| 514 | + public void endRendering() { | |
| 515 | + int spminX = Math.max((int)Math.ceil(edgeMinX), boundsMinX); | |
| 516 | + int spmaxX = Math.min((int)Math.ceil(edgeMaxX), boundsMaxX); | |
| 517 | + int spminY = Math.max((int)Math.ceil(edgeMinY), boundsMinY); | |
| 518 | + int spmaxY = Math.min((int)Math.ceil(edgeMaxY), boundsMaxY); | |
| 519 | + | |
| 520 | + int pminX = spminX >> SUBPIXEL_LG_POSITIONS_X; | |
| 521 | + int pmaxX = (spmaxX + SUBPIXEL_MASK_X) >> SUBPIXEL_LG_POSITIONS_X; | |
| 522 | + int pminY = spminY >> SUBPIXEL_LG_POSITIONS_Y; | |
| 523 | + int pmaxY = (spmaxY + SUBPIXEL_MASK_Y) >> SUBPIXEL_LG_POSITIONS_Y; | |
| 524 | + | |
| 525 | + if (pminX > pmaxX || pminY > pmaxY) { | |
| 526 | + this.cache = new PiscesCache(boundsMinX >> SUBPIXEL_LG_POSITIONS_X, | |
| 527 | + boundsMinY >> SUBPIXEL_LG_POSITIONS_Y, | |
| 528 | + boundsMaxX >> SUBPIXEL_LG_POSITIONS_X, | |
| 529 | + boundsMaxY >> SUBPIXEL_LG_POSITIONS_Y); | |
| 530 | + return; | |
| 531 | + } | |
| 532 | + | |
| 533 | + this.cache = new PiscesCache(pminX, pminY, pmaxX, pmaxY); | |
| 534 | + _endRendering(pminX, pmaxX, spminY, spmaxY); | |
| 535 | + } | |
| 536 | + | |
| 537 | + public PiscesCache getCache() { | |
| 538 | + if (cache == null) { | |
| 539 | + throw new InternalError("cache not yet initialized"); | |
| 540 | + } | |
| 541 | + return cache; | |
| 542 | + } | |
| 543 | + | |
| 544 | + private void emitRow(int[] alphaRow, int pix_y, int pix_from, int pix_to) { | |
| 545 | + // Copy rowAA data into the cache if one is present | |
| 546 | + if (cache != null) { | |
| 547 | + if (pix_to >= pix_from) { | |
| 548 | + cache.startRow(pix_y, pix_from); | |
| 549 | + | |
| 550 | + // Perform run-length encoding and store results in the cache | |
| 551 | + int from = pix_from - cache.bboxX0; | |
| 552 | + int to = pix_to - cache.bboxX0; | |
| 553 | + | |
| 554 | + int runLen = 1; | |
| 555 | + int startVal = alphaRow[from]; | |
| 556 | + for (int i = from + 1; i <= to; i++) { | |
| 557 | + int nextVal = startVal + alphaRow[i]; | |
| 558 | + if (nextVal == startVal) { | |
| 559 | + runLen++; | |
| 560 | + } else { | |
| 561 | + cache.addRLERun(startVal, runLen); | |
| 562 | + runLen = 1; | |
| 563 | + startVal = nextVal; | |
| 564 | + } | |
| 565 | + } | |
| 566 | + cache.addRLERun(startVal, runLen); | |
| 567 | + } | |
| 568 | + } | |
| 569 | + java.util.Arrays.fill(alphaRow, 0); | |
| 570 | + } | |
| 571 | +} | |
| @@ -0,0 +1,1223 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. | |
| 3 | + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| 4 | + * | |
| 5 | + * This code is free software; you can redistribute it and/or modify it | |
| 6 | + * under the terms of the GNU General Public License version 2 only, as | |
| 7 | + * published by the Free Software Foundation. Oracle designates this | |
| 8 | + * particular file as subject to the "Classpath" exception as provided | |
| 9 | + * by Oracle in the LICENSE file that accompanied this code. | |
| 10 | + * | |
| 11 | + * This code is distributed in the hope that it will be useful, but WITHOUT | |
| 12 | + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| 13 | + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| 14 | + * version 2 for more details (a copy is included in the LICENSE file that | |
| 15 | + * accompanied this code). | |
| 16 | + * | |
| 17 | + * You should have received a copy of the GNU General Public License version | |
| 18 | + * 2 along with this work; if not, write to the Free Software Foundation, | |
| 19 | + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 20 | + * | |
| 21 | + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| 22 | + * or visit www.oracle.com if you need additional information or have any | |
| 23 | + * questions. | |
| 24 | + */ | |
| 25 | + | |
| 26 | +package sun.java2d.pisces; | |
| 27 | + | |
| 28 | +import java.util.Arrays; | |
| 29 | +import java.util.Iterator; | |
| 30 | +import static java.lang.Math.ulp; | |
| 31 | +import static java.lang.Math.sqrt; | |
| 32 | + | |
| 33 | +import sun.awt.geom.PathConsumer2D; | |
| 34 | + | |
| 35 | +// TODO: some of the arithmetic here is too verbose and prone to hard to | |
| 36 | +// debug typos. We should consider making a small Point/Vector class that | |
| 37 | +// has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such | |
| 38 | +final class Stroker implements PathConsumer2D { | |
| 39 | + | |
| 40 | + private static final int MOVE_TO = 0; | |
| 41 | + private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad | |
| 42 | + private static final int CLOSE = 2; | |
| 43 | + | |
| 44 | + /** | |
| 45 | + * Constant value for join style. | |
| 46 | + */ | |
| 47 | + public static final int JOIN_MITER = 0; | |
| 48 | + | |
| 49 | + /** | |
| 50 | + * Constant value for join style. | |
| 51 | + */ | |
| 52 | + public static final int JOIN_ROUND = 1; | |
| 53 | + | |
| 54 | + /** | |
| 55 | + * Constant value for join style. | |
| 56 | + */ | |
| 57 | + public static final int JOIN_BEVEL = 2; | |
| 58 | + | |
| 59 | + /** | |
| 60 | + * Constant value for end cap style. | |
| 61 | + */ | |
| 62 | + public static final int CAP_BUTT = 0; | |
| 63 | + | |
| 64 | + /** | |
| 65 | + * Constant value for end cap style. | |
| 66 | + */ | |
| 67 | + public static final int CAP_ROUND = 1; | |
| 68 | + | |
| 69 | + /** | |
| 70 | + * Constant value for end cap style. | |
| 71 | + */ | |
| 72 | + public static final int CAP_SQUARE = 2; | |
| 73 | + | |
| 74 | + private final PathConsumer2D out; | |
| 75 | + | |
| 76 | + private final int capStyle; | |
| 77 | + private final int joinStyle; | |
| 78 | + | |
| 79 | + private final float lineWidth2; | |
| 80 | + | |
| 81 | + private final float[][] offset = new float[3][2]; | |
| 82 | + private final float[] miter = new float[2]; | |
| 83 | + private final float miterLimitSq; | |
| 84 | + | |
| 85 | + private int prev; | |
| 86 | + | |
| 87 | + // The starting point of the path, and the slope there. | |
| 88 | + private float sx0, sy0, sdx, sdy; | |
| 89 | + // the current point and the slope there. | |
| 90 | + private float cx0, cy0, cdx, cdy; // c stands for current | |
| 91 | + // vectors that when added to (sx0,sy0) and (cx0,cy0) respectively yield the | |
| 92 | + // first and last points on the left parallel path. Since this path is | |
| 93 | + // parallel, it's slope at any point is parallel to the slope of the | |
| 94 | + // original path (thought they may have different directions), so these | |
| 95 | + // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that | |
| 96 | + // would be error prone and hard to read, so we keep these anyway. | |
| 97 | + private float smx, smy, cmx, cmy; | |
| 98 | + | |
| 99 | + private final PolyStack reverse = new PolyStack(); | |
| 100 | + | |
| 101 | + /** | |
| 102 | + * Constructs a <code>Stroker</code>. | |
| 103 | + * | |
| 104 | + * @param pc2d an output <code>PathConsumer2D</code>. | |
| 105 | + * @param lineWidth the desired line width in pixels | |
| 106 | + * @param capStyle the desired end cap style, one of | |
| 107 | + * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or | |
| 108 | + * <code>CAP_SQUARE</code>. | |
| 109 | + * @param joinStyle the desired line join style, one of | |
| 110 | + * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or | |
| 111 | + * <code>JOIN_BEVEL</code>. | |
| 112 | + * @param miterLimit the desired miter limit | |
| 113 | + */ | |
| 114 | + public Stroker(PathConsumer2D pc2d, | |
| 115 | + float lineWidth, | |
| 116 | + int capStyle, | |
| 117 | + int joinStyle, | |
| 118 | + float miterLimit) | |
| 119 | + { | |
| 120 | + this.out = pc2d; | |
| 121 | + | |
| 122 | + this.lineWidth2 = lineWidth / 2; | |
| 123 | + this.capStyle = capStyle; | |
| 124 | + this.joinStyle = joinStyle; | |
| 125 | + | |
| 126 | + float limit = miterLimit * lineWidth2; | |
| 127 | + this.miterLimitSq = limit*limit; | |
| 128 | + | |
| 129 | + this.prev = CLOSE; | |
| 130 | + } | |
| 131 | + | |
| 132 | + private static void computeOffset(final float lx, final float ly, | |
| 133 | + final float w, final float[] m) | |
| 134 | + { | |
| 135 | + final float len = (float) sqrt(lx*lx + ly*ly); | |
| 136 | + if (len == 0) { | |
| 137 | + m[0] = m[1] = 0; | |
| 138 | + } else { | |
| 139 | + m[0] = (ly * w)/len; | |
| 140 | + m[1] = -(lx * w)/len; | |
| 141 | + } | |
| 142 | + } | |
| 143 | + | |
| 144 | + // Returns true if the vectors (dx1, dy1) and (dx2, dy2) are | |
| 145 | + // clockwise (if dx1,dy1 needs to be rotated clockwise to close | |
| 146 | + // the smallest angle between it and dx2,dy2). | |
| 147 | + // This is equivalent to detecting whether a point q is on the right side | |
| 148 | + // of a line passing through points p1, p2 where p2 = p1+(dx1,dy1) and | |
| 149 | + // q = p2+(dx2,dy2), which is the same as saying p1, p2, q are in a | |
| 150 | + // clockwise order. | |
| 151 | + // NOTE: "clockwise" here assumes coordinates with 0,0 at the bottom left. | |
| 152 | + private static boolean isCW(final float dx1, final float dy1, | |
| 153 | + final float dx2, final float dy2) | |
| 154 | + { | |
| 155 | + return dx1 * dy2 <= dy1 * dx2; | |
| 156 | + } | |
| 157 | + | |
| 158 | + // pisces used to use fixed point arithmetic with 16 decimal digits. I | |
| 159 | + // didn't want to change the values of the constant below when I converted | |
| 160 | + // it to floating point, so that's why the divisions by 2^16 are there. | |
| 161 | + private static final float ROUND_JOIN_THRESHOLD = 1000/65536f; | |
| 162 | + | |
| 163 | + private void drawRoundJoin(float x, float y, | |
| 164 | + float omx, float omy, float mx, float my, | |
| 165 | + boolean rev, | |
| 166 | + float threshold) | |
| 167 | + { | |
| 168 | + if ((omx == 0 && omy == 0) || (mx == 0 && my == 0)) { | |
| 169 | + return; | |
| 170 | + } | |
| 171 | + | |
| 172 | + float domx = omx - mx; | |
| 173 | + float domy = omy - my; | |
| 174 | + float len = domx*domx + domy*domy; | |
| 175 | + if (len < threshold) { | |
| 176 | + return; | |
| 177 | + } | |
| 178 | + | |
| 179 | + if (rev) { | |
| 180 | + omx = -omx; | |
| 181 | + omy = -omy; | |
| 182 | + mx = -mx; | |
| 183 | + my = -my; | |
| 184 | + } | |
| 185 | + drawRoundJoin(x, y, omx, omy, mx, my, rev); | |
| 186 | + } | |
| 187 | + | |
| 188 | + private void drawRoundJoin(float cx, float cy, | |
| 189 | + float omx, float omy, | |
| 190 | + float mx, float my, | |
| 191 | + boolean rev) | |
| 192 | + { | |
| 193 | + // The sign of the dot product of mx,my and omx,omy is equal to the | |
| 194 | + // the sign of the cosine of ext | |
| 195 | + // (ext is the angle between omx,omy and mx,my). | |
| 196 | + double cosext = omx * mx + omy * my; | |
| 197 | + // If it is >=0, we know that abs(ext) is <= 90 degrees, so we only | |
| 198 | + // need 1 curve to approximate the circle section that joins omx,omy | |
| 199 | + // and mx,my. | |
| 200 | + final int numCurves = cosext >= 0 ? 1 : 2; | |
| 201 | + | |
| 202 | + switch (numCurves) { | |
| 203 | + case 1: | |
| 204 | + drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev); | |
| 205 | + break; | |
| 206 | + case 2: | |
| 207 | + // we need to split the arc into 2 arcs spanning the same angle. | |
| 208 | + // The point we want will be one of the 2 intersections of the | |
| 209 | + // perpendicular bisector of the chord (omx,omy)->(mx,my) and the | |
| 210 | + // circle. We could find this by scaling the vector | |
| 211 | + // (omx+mx, omy+my)/2 so that it has length=lineWidth2 (and thus lies | |
| 212 | + // on the circle), but that can have numerical problems when the angle | |
| 213 | + // between omx,omy and mx,my is close to 180 degrees. So we compute a | |
| 214 | + // normal of (omx,omy)-(mx,my). This will be the direction of the | |
| 215 | + // perpendicular bisector. To get one of the intersections, we just scale | |
| 216 | + // this vector that its length is lineWidth2 (this works because the | |
| 217 | + // perpendicular bisector goes through the origin). This scaling doesn't | |
| 218 | + // have numerical problems because we know that lineWidth2 divided by | |
| 219 | + // this normal's length is at least 0.5 and at most sqrt(2)/2 (because | |
| 220 | + // we know the angle of the arc is > 90 degrees). | |
| 221 | + float nx = my - omy, ny = omx - mx; | |
| 222 | + float nlen = (float) sqrt(nx*nx + ny*ny); | |
| 223 | + float scale = lineWidth2/nlen; | |
| 224 | + float mmx = nx * scale, mmy = ny * scale; | |
| 225 | + | |
| 226 | + // if (isCW(omx, omy, mx, my) != isCW(mmx, mmy, mx, my)) then we've | |
| 227 | + // computed the wrong intersection so we get the other one. | |
| 228 | + // The test above is equivalent to if (rev). | |
| 229 | + if (rev) { | |
| 230 | + mmx = -mmx; | |
| 231 | + mmy = -mmy; | |
| 232 | + } | |
| 233 | + drawBezApproxForArc(cx, cy, omx, omy, mmx, mmy, rev); | |
| 234 | + drawBezApproxForArc(cx, cy, mmx, mmy, mx, my, rev); | |
| 235 | + break; | |
| 236 | + } | |
| 237 | + } | |
| 238 | + | |
| 239 | + // the input arc defined by omx,omy and mx,my must span <= 90 degrees. | |
| 240 | + private void drawBezApproxForArc(final float cx, final float cy, | |
| 241 | + final float omx, final float omy, | |
| 242 | + final float mx, final float my, | |
| 243 | + boolean rev) | |
| 244 | + { | |
| 245 | + float cosext2 = (omx * mx + omy * my) / (2 * lineWidth2 * lineWidth2); | |
| 246 | + // cv is the length of P1-P0 and P2-P3 divided by the radius of the arc | |
| 247 | + // (so, cv assumes the arc has radius 1). P0, P1, P2, P3 are the points that | |
| 248 | + // define the bezier curve we're computing. | |
| 249 | + // It is computed using the constraints that P1-P0 and P3-P2 are parallel | |
| 250 | + // to the arc tangents at the endpoints, and that |P1-P0|=|P3-P2|. | |
| 251 | + float cv = (float) ((4.0 / 3.0) * sqrt(0.5-cosext2) / | |
| 252 | + (1.0 + sqrt(cosext2+0.5))); | |
| 253 | + // if clockwise, we need to negate cv. | |
| 254 | + if (rev) { // rev is equivalent to isCW(omx, omy, mx, my) | |
| 255 | + cv = -cv; | |
| 256 | + } | |
| 257 | + final float x1 = cx + omx; | |
| 258 | + final float y1 = cy + omy; | |
| 259 | + final float x2 = x1 - cv * omy; | |
| 260 | + final float y2 = y1 + cv * omx; | |
| 261 | + | |
| 262 | + final float x4 = cx + mx; | |
| 263 | + final float y4 = cy + my; | |
| 264 | + final float x3 = x4 + cv * my; | |
| 265 | + final float y3 = y4 - cv * mx; | |
| 266 | + | |
| 267 | + emitCurveTo(x1, y1, x2, y2, x3, y3, x4, y4, rev); | |
| 268 | + } | |
| 269 | + | |
| 270 | + private void drawRoundCap(float cx, float cy, float mx, float my) { | |
| 271 | + final float C = 0.5522847498307933f; | |
| 272 | + // the first and second arguments of the following two calls | |
| 273 | + // are really will be ignored by emitCurveTo (because of the false), | |
| 274 | + // but we put them in anyway, as opposed to just giving it 4 zeroes, | |
| 275 | + // because it's just 4 additions and it's not good to rely on this | |
| 276 | + // sort of assumption (right now it's true, but that may change). | |
| 277 | + emitCurveTo(cx+mx, cy+my, | |
| 278 | + cx+mx-C*my, cy+my+C*mx, | |
| 279 | + cx-my+C*mx, cy+mx+C*my, | |
| 280 | + cx-my, cy+mx, | |
| 281 | + false); | |
| 282 | + emitCurveTo(cx-my, cy+mx, | |
| 283 | + cx-my-C*mx, cy+mx-C*my, | |
| 284 | + cx-mx-C*my, cy-my+C*mx, | |
| 285 | + cx-mx, cy-my, | |
| 286 | + false); | |
| 287 | + } | |
| 288 | + | |
| 289 | + // Put the intersection point of the lines (x0, y0) -> (x1, y1) | |
| 290 | + // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1]. | |
| 291 | + // If the lines are parallel, it will put a non finite number in m. | |
| 292 | + private void computeIntersection(final float x0, final float y0, | |
| 293 | + final float x1, final float y1, | |
| 294 | + final float x0p, final float y0p, | |
| 295 | + final float x1p, final float y1p, | |
| 296 | + final float[] m, int off) | |
| 297 | + { | |
| 298 | + float x10 = x1 - x0; | |
| 299 | + float y10 = y1 - y0; | |
| 300 | + float x10p = x1p - x0p; | |
| 301 | + float y10p = y1p - y0p; | |
| 302 | + | |
| 303 | + float den = x10*y10p - x10p*y10; | |
| 304 | + float t = x10p*(y0-y0p) - y10p*(x0-x0p); | |
| 305 | + t /= den; | |
| 306 | + m[off++] = x0 + t*x10; | |
| 307 | + m[off] = y0 + t*y10; | |
| 308 | + } | |
| 309 | + | |
| 310 | + private void drawMiter(final float pdx, final float pdy, | |
| 311 | + final float x0, final float y0, | |
| 312 | + final float dx, final float dy, | |
| 313 | + float omx, float omy, float mx, float my, | |
| 314 | + boolean rev) | |
| 315 | + { | |
| 316 | + if ((mx == omx && my == omy) || | |
| 317 | + (pdx == 0 && pdy == 0) || | |
| 318 | + (dx == 0 && dy == 0)) | |
| 319 | + { | |
| 320 | + return; | |
| 321 | + } | |
| 322 | + | |
| 323 | + if (rev) { | |
| 324 | + omx = -omx; | |
| 325 | + omy = -omy; | |
| 326 | + mx = -mx; | |
| 327 | + my = -my; | |
| 328 | + } | |
| 329 | + | |
| 330 | + computeIntersection((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy, | |
| 331 | + (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my, | |
| 332 | + miter, 0); | |
| 333 | + | |
| 334 | + float lenSq = (miter[0]-x0)*(miter[0]-x0) + (miter[1]-y0)*(miter[1]-y0); | |
| 335 | + | |
| 336 | + // If the lines are parallel, lenSq will be either NaN or +inf | |
| 337 | + // (actually, I'm not sure if the latter is possible. The important | |
| 338 | + // thing is that -inf is not possible, because lenSq is a square). | |
| 339 | + // For both of those values, the comparison below will fail and | |
| 340 | + // no miter will be drawn, which is correct. | |
| 341 | + if (lenSq < miterLimitSq) { | |
| 342 | + emitLineTo(miter[0], miter[1], rev); | |
| 343 | + } | |
| 344 | + } | |
| 345 | + | |
| 346 | + public void moveTo(float x0, float y0) { | |
| 347 | + if (prev == DRAWING_OP_TO) { | |
| 348 | + finish(); | |
| 349 | + } | |
| 350 | + this.sx0 = this.cx0 = x0; | |
| 351 | + this.sy0 = this.cy0 = y0; | |
| 352 | + this.cdx = this.sdx = 1; | |
| 353 | + this.cdy = this.sdy = 0; | |
| 354 | + this.prev = MOVE_TO; | |
| 355 | + } | |
| 356 | + | |
| 357 | + public void lineTo(float x1, float y1) { | |
| 358 | + float dx = x1 - cx0; | |
| 359 | + float dy = y1 - cy0; | |
| 360 | + if (dx == 0f && dy == 0f) { | |
| 361 | + dx = 1; | |
| 362 | + } | |
| 363 | + computeOffset(dx, dy, lineWidth2, offset[0]); | |
| 364 | + float mx = offset[0][0]; | |
| 365 | + float my = offset[0][1]; | |
| 366 | + | |
| 367 | + drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my); | |
| 368 | + | |
| 369 | + emitLineTo(cx0 + mx, cy0 + my); | |
| 370 | + emitLineTo(x1 + mx, y1 + my); | |
| 371 | + | |
| 372 | + emitLineTo(cx0 - mx, cy0 - my, true); | |
| 373 | + emitLineTo(x1 - mx, y1 - my, true); | |
| 374 | + | |
| 375 | + this.cmx = mx; | |
| 376 | + this.cmy = my; | |
| 377 | + this.cdx = dx; | |
| 378 | + this.cdy = dy; | |
| 379 | + this.cx0 = x1; | |
| 380 | + this.cy0 = y1; | |
| 381 | + this.prev = DRAWING_OP_TO; | |
| 382 | + } | |
| 383 | + | |
| 384 | + public void closePath() { | |
| 385 | + if (prev != DRAWING_OP_TO) { | |
| 386 | + if (prev == CLOSE) { | |
| 387 | + return; | |
| 388 | + } | |
| 389 | + emitMoveTo(cx0, cy0 - lineWidth2); | |
| 390 | + this.cmx = this.smx = 0; | |
| 391 | + this.cmy = this.smy = -lineWidth2; | |
| 392 | + this.cdx = this.sdx = 1; | |
| 393 | + this.cdy = this.sdy = 0; | |
| 394 | + finish(); | |
| 395 | + return; | |
| 396 | + } | |
| 397 | + | |
| 398 | + if (cx0 != sx0 || cy0 != sy0) { | |
| 399 | + lineTo(sx0, sy0); | |
| 400 | + } | |
| 401 | + | |
| 402 | + drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy); | |
| 403 | + | |
| 404 | + emitLineTo(sx0 + smx, sy0 + smy); | |
| 405 | + | |
| 406 | + emitMoveTo(sx0 - smx, sy0 - smy); | |
| 407 | + emitReverse(); | |
| 408 | + | |
| 409 | + this.prev = CLOSE; | |
| 410 | + emitClose(); | |
| 411 | + } | |
| 412 | + | |
| 413 | + private void emitReverse() { | |
| 414 | + while(!reverse.isEmpty()) { | |
| 415 | + reverse.pop(out); | |
| 416 | + } | |
| 417 | + } | |
| 418 | + | |
| 419 | + public void pathDone() { | |
| 420 | + if (prev == DRAWING_OP_TO) { | |
| 421 | + finish(); | |
| 422 | + } | |
| 423 | + | |
| 424 | + out.pathDone(); | |
| 425 | + // this shouldn't matter since this object won't be used | |
| 426 | + // after the call to this method. | |
| 427 | + this.prev = CLOSE; | |
| 428 | + } | |
| 429 | + | |
| 430 | + private void finish() { | |
| 431 | + if (capStyle == CAP_ROUND) { | |
| 432 | + drawRoundCap(cx0, cy0, cmx, cmy); | |
| 433 | + } else if (capStyle == CAP_SQUARE) { | |
| 434 | + emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy); | |
| 435 | + emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy); | |
| 436 | + } | |
| 437 | + | |
| 438 | + emitReverse(); | |
| 439 | + | |
| 440 | + if (capStyle == CAP_ROUND) { | |
| 441 | + drawRoundCap(sx0, sy0, -smx, -smy); | |
| 442 | + } else if (capStyle == CAP_SQUARE) { | |
| 443 | + emitLineTo(sx0 + smy - smx, sy0 - smx - smy); | |
| 444 | + emitLineTo(sx0 + smy + smx, sy0 - smx + smy); | |
| 445 | + } | |
| 446 | + | |
| 447 | + emitClose(); | |
| 448 | + } | |
| 449 | + | |
| 450 | + private void emitMoveTo(final float x0, final float y0) { | |
| 451 | + out.moveTo(x0, y0); | |
| 452 | + } | |
| 453 | + | |
| 454 | + private void emitLineTo(final float x1, final float y1) { | |
| 455 | + out.lineTo(x1, y1); | |
| 456 | + } | |
| 457 | + | |
| 458 | + private void emitLineTo(final float x1, final float y1, | |
| 459 | + final boolean rev) | |
| 460 | + { | |
| 461 | + if (rev) { | |
| 462 | + reverse.pushLine(x1, y1); | |
| 463 | + } else { | |
| 464 | + emitLineTo(x1, y1); | |
| 465 | + } | |
| 466 | + } | |
| 467 | + | |
| 468 | + private void emitQuadTo(final float x0, final float y0, | |
| 469 | + final float x1, final float y1, | |
| 470 | + final float x2, final float y2, final boolean rev) | |
| 471 | + { | |
| 472 | + if (rev) { | |
| 473 | + reverse.pushQuad(x0, y0, x1, y1); | |
| 474 | + } else { | |
| 475 | + out.quadTo(x1, y1, x2, y2); | |
| 476 | + } | |
| 477 | + } | |
| 478 | + | |
| 479 | + private void emitCurveTo(final float x0, final float y0, | |
| 480 | + final float x1, final float y1, | |
| 481 | + final float x2, final float y2, | |
| 482 | + final float x3, final float y3, final boolean rev) | |
| 483 | + { | |
| 484 | + if (rev) { | |
| 485 | + reverse.pushCubic(x0, y0, x1, y1, x2, y2); | |
| 486 | + } else { | |
| 487 | + out.curveTo(x1, y1, x2, y2, x3, y3); | |
| 488 | + } | |
| 489 | + } | |
| 490 | + | |
| 491 | + private void emitClose() { | |
| 492 | + out.closePath(); | |
| 493 | + } | |
| 494 | + | |
| 495 | + private void drawJoin(float pdx, float pdy, | |
| 496 | + float x0, float y0, | |
| 497 | + float dx, float dy, | |
| 498 | + float omx, float omy, | |
| 499 | + float mx, float my) | |
| 500 | + { | |
| 501 | + if (prev != DRAWING_OP_TO) { | |
| 502 | + emitMoveTo(x0 + mx, y0 + my); | |
| 503 | + this.sdx = dx; | |
| 504 | + this.sdy = dy; | |
| 505 | + this.smx = mx; | |
| 506 | + this.smy = my; | |
| 507 | + } else { | |
| 508 | + boolean cw = isCW(pdx, pdy, dx, dy); | |
| 509 | + if (joinStyle == JOIN_MITER) { | |
| 510 | + drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw); | |
| 511 | + } else if (joinStyle == JOIN_ROUND) { | |
| 512 | + drawRoundJoin(x0, y0, | |
| 513 | + omx, omy, | |
| 514 | + mx, my, cw, | |
| 515 | + ROUND_JOIN_THRESHOLD); | |
| 516 | + } | |
| 517 | + emitLineTo(x0, y0, !cw); | |
| 518 | + } | |
| 519 | + prev = DRAWING_OP_TO; | |
| 520 | + } | |
| 521 | + | |
| 522 | + private static boolean within(final float x1, final float y1, | |
| 523 | + final float x2, final float y2, | |
| 524 | + final float ERR) | |
| 525 | + { | |
| 526 | + assert ERR > 0 : ""; | |
| 527 | + // compare taxicab distance. ERR will always be small, so using | |
| 528 | + // true distance won't give much benefit | |
| 529 | + return (Helpers.within(x1, x2, ERR) && // we want to avoid calling Math.abs | |
| 530 | + Helpers.within(y1, y2, ERR)); // this is just as good. | |
| 531 | + } | |
| 532 | + | |
| 533 | + private void getLineOffsets(float x1, float y1, | |
| 534 | + float x2, float y2, | |
| 535 | + float[] left, float[] right) { | |
| 536 | + computeOffset(x2 - x1, y2 - y1, lineWidth2, offset[0]); | |
| 537 | + left[0] = x1 + offset[0][0]; | |
| 538 | + left[1] = y1 + offset[0][1]; | |
| 539 | + left[2] = x2 + offset[0][0]; | |
| 540 | + left[3] = y2 + offset[0][1]; | |
| 541 | + right[0] = x1 - offset[0][0]; | |
| 542 | + right[1] = y1 - offset[0][1]; | |
| 543 | + right[2] = x2 - offset[0][0]; | |
| 544 | + right[3] = y2 - offset[0][1]; | |
| 545 | + } | |
| 546 | + | |
| 547 | + private int computeOffsetCubic(float[] pts, final int off, | |
| 548 | + float[] leftOff, float[] rightOff) | |
| 549 | + { | |
| 550 | + // if p1=p2 or p3=p4 it means that the derivative at the endpoint | |
| 551 | + // vanishes, which creates problems with computeOffset. Usually | |
| 552 | + // this happens when this stroker object is trying to winden | |
| 553 | + // a curve with a cusp. What happens is that curveTo splits | |
| 554 | + // the input curve at the cusp, and passes it to this function. | |
| 555 | + // because of inaccuracies in the splitting, we consider points | |
| 556 | + // equal if they're very close to each other. | |
| 557 | + final float x1 = pts[off + 0], y1 = pts[off + 1]; | |
| 558 | + final float x2 = pts[off + 2], y2 = pts[off + 3]; | |
| 559 | + final float x3 = pts[off + 4], y3 = pts[off + 5]; | |
| 560 | + final float x4 = pts[off + 6], y4 = pts[off + 7]; | |
| 561 | + | |
| 562 | + float dx4 = x4 - x3; | |
| 563 | + float dy4 = y4 - y3; | |
| 564 | + float dx1 = x2 - x1; | |
| 565 | + float dy1 = y2 - y1; | |
| 566 | + | |
| 567 | + // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4, | |
| 568 | + // in which case ignore if p1 == p2 | |
| 569 | + final boolean p1eqp2 = within(x1,y1,x2,y2, 6 * ulp(y2)); | |
| 570 | + final boolean p3eqp4 = within(x3,y3,x4,y4, 6 * ulp(y4)); | |
| 571 | + if (p1eqp2 && p3eqp4) { | |
| 572 | + getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); | |
| 573 | + return 4; | |
| 574 | + } else if (p1eqp2) { | |
| 575 | + dx1 = x3 - x1; | |
| 576 | + dy1 = y3 - y1; | |
| 577 | + } else if (p3eqp4) { | |
| 578 | + dx4 = x4 - x2; | |
| 579 | + dy4 = y4 - y2; | |
| 580 | + } | |
| 581 | + | |
| 582 | + // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line | |
| 583 | + float dotsq = (dx1 * dx4 + dy1 * dy4); | |
| 584 | + dotsq = dotsq * dotsq; | |
| 585 | + float l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4; | |
| 586 | + if (Helpers.within(dotsq, l1sq * l4sq, 4 * ulp(dotsq))) { | |
| 587 | + getLineOffsets(x1, y1, x4, y4, leftOff, rightOff); | |
| 588 | + return 4; | |
| 589 | + } | |
| 590 | + | |
| 591 | +// What we're trying to do in this function is to approximate an ideal | |
| 592 | +// offset curve (call it I) of the input curve B using a bezier curve Bp. | |
| 593 | +// The constraints I use to get the equations are: | |
| 594 | +// | |
| 595 | +// 1. The computed curve Bp should go through I(0) and I(1). These are | |
| 596 | +// x1p, y1p, x4p, y4p, which are p1p and p4p. We still need to find | |
| 597 | +// 4 variables: the x and y components of p2p and p3p (i.e. x2p, y2p, x3p, y3p). | |
| 598 | +// | |
| 599 | +// 2. Bp should have slope equal in absolute value to I at the endpoints. So, | |
| 600 | +// (by the way, the operator || in the comments below means "aligned with". | |
| 601 | +// It is defined on vectors, so when we say I'(0) || Bp'(0) we mean that | |
| 602 | +// vectors I'(0) and Bp'(0) are aligned, which is the same as saying | |
| 603 | +// that the tangent lines of I and Bp at 0 are parallel. Mathematically | |
| 604 | +// this means (I'(t) || Bp'(t)) <==> (I'(t) = c * Bp'(t)) where c is some | |
| 605 | +// nonzero constant.) | |
| 606 | +// I'(0) || Bp'(0) and I'(1) || Bp'(1). Obviously, I'(0) || B'(0) and | |
| 607 | +// I'(1) || B'(1); therefore, Bp'(0) || B'(0) and Bp'(1) || B'(1). | |
| 608 | +// We know that Bp'(0) || (p2p-p1p) and Bp'(1) || (p4p-p3p) and the same | |
| 609 | +// is true for any bezier curve; therefore, we get the equations | |
| 610 | +// (1) p2p = c1 * (p2-p1) + p1p | |
| 611 | +// (2) p3p = c2 * (p4-p3) + p4p | |
| 612 | +// We know p1p, p4p, p2, p1, p3, and p4; therefore, this reduces the number | |
| 613 | +// of unknowns from 4 to 2 (i.e. just c1 and c2). | |
| 614 | +// To eliminate these 2 unknowns we use the following constraint: | |
| 615 | +// | |
| 616 | +// 3. Bp(0.5) == I(0.5). Bp(0.5)=(x,y) and I(0.5)=(xi,yi), and I should note | |
| 617 | +// that I(0.5) is *the only* reason for computing dxm,dym. This gives us | |
| 618 | +// (3) Bp(0.5) = (p1p + 3 * (p2p + p3p) + p4p)/8, which is equivalent to | |
| 619 | +// (4) p2p + p3p = (Bp(0.5)*8 - p1p - p4p) / 3 | |
| 620 | +// We can substitute (1) and (2) from above into (4) and we get: | |
| 621 | +// (5) c1*(p2-p1) + c2*(p4-p3) = (Bp(0.5)*8 - p1p - p4p)/3 - p1p - p4p | |
| 622 | +// which is equivalent to | |
| 623 | +// (6) c1*(p2-p1) + c2*(p4-p3) = (4/3) * (Bp(0.5) * 2 - p1p - p4p) | |
| 624 | +// | |
| 625 | +// The right side of this is a 2D vector, and we know I(0.5), which gives us | |
| 626 | +// Bp(0.5), which gives us the value of the right side. | |
| 627 | +// The left side is just a matrix vector multiplication in disguise. It is | |
| 628 | +// | |
| 629 | +// [x2-x1, x4-x3][c1] | |
| 630 | +// [y2-y1, y4-y3][c2] | |
| 631 | +// which, is equal to | |
| 632 | +// [dx1, dx4][c1] | |
| 633 | +// [dy1, dy4][c2] | |
| 634 | +// At this point we are left with a simple linear system and we solve it by | |
| 635 | +// getting the inverse of the matrix above. Then we use [c1,c2] to compute | |
| 636 | +// p2p and p3p. | |
| 637 | + | |
| 638 | + float x = 0.125f * (x1 + 3 * (x2 + x3) + x4); | |
| 639 | + float y = 0.125f * (y1 + 3 * (y2 + y3) + y4); | |
| 640 | + // (dxm,dym) is some tangent of B at t=0.5. This means it's equal to | |
| 641 | + // c*B'(0.5) for some constant c. | |
| 642 | + float dxm = x3 + x4 - x1 - x2, dym = y3 + y4 - y1 - y2; | |
| 643 | + | |
| 644 | + // this computes the offsets at t=0, 0.5, 1, using the property that | |
| 645 | + // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to | |
| 646 | + // the (dx/dt, dy/dt) vectors at the endpoints. | |
| 647 | + computeOffset(dx1, dy1, lineWidth2, offset[0]); | |
| 648 | + computeOffset(dxm, dym, lineWidth2, offset[1]); | |
| 649 | + computeOffset(dx4, dy4, lineWidth2, offset[2]); | |
| 650 | + float x1p = x1 + offset[0][0]; // start | |
| 651 | + float y1p = y1 + offset[0][1]; // point | |
| 652 | + float xi = x + offset[1][0]; // interpolation | |
| 653 | + float yi = y + offset[1][1]; // point | |
| 654 | + float x4p = x4 + offset[2][0]; // end | |
| 655 | + float y4p = y4 + offset[2][1]; // point | |
| 656 | + | |
| 657 | + float invdet43 = 4f / (3f * (dx1 * dy4 - dy1 * dx4)); | |
| 658 | + | |
| 659 | + float two_pi_m_p1_m_p4x = 2*xi - x1p - x4p; | |
| 660 | + float two_pi_m_p1_m_p4y = 2*yi - y1p - y4p; | |
| 661 | + float c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y); | |
| 662 | + float c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x); | |
| 663 | + | |
| 664 | + float x2p, y2p, x3p, y3p; | |
| 665 | + x2p = x1p + c1*dx1; | |
| 666 | + y2p = y1p + c1*dy1; | |
| 667 | + x3p = x4p + c2*dx4; | |
| 668 | + y3p = y4p + c2*dy4; | |
| 669 | + | |
| 670 | + leftOff[0] = x1p; leftOff[1] = y1p; | |
| 671 | + leftOff[2] = x2p; leftOff[3] = y2p; | |
| 672 | + leftOff[4] = x3p; leftOff[5] = y3p; | |
| 673 | + leftOff[6] = x4p; leftOff[7] = y4p; | |
| 674 | + | |
| 675 | + x1p = x1 - offset[0][0]; y1p = y1 - offset[0][1]; | |
| 676 | + xi = xi - 2 * offset[1][0]; yi = yi - 2 * offset[1][1]; | |
| 677 | + x4p = x4 - offset[2][0]; y4p = y4 - offset[2][1]; | |
| 678 | + | |
| 679 | + two_pi_m_p1_m_p4x = 2*xi - x1p - x4p; | |
| 680 | + two_pi_m_p1_m_p4y = 2*yi - y1p - y4p; | |
| 681 | + c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y); | |
| 682 | + c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x); | |
| 683 | + | |
| 684 | + x2p = x1p + c1*dx1; | |
| 685 | + y2p = y1p + c1*dy1; | |
| 686 | + x3p = x4p + c2*dx4; | |
| 687 | + y3p = y4p + c2*dy4; | |
| 688 | + | |
| 689 | + rightOff[0] = x1p; rightOff[1] = y1p; | |
| 690 | + rightOff[2] = x2p; rightOff[3] = y2p; | |
| 691 | + rightOff[4] = x3p; rightOff[5] = y3p; | |
| 692 | + rightOff[6] = x4p; rightOff[7] = y4p; | |
| 693 | + return 8; | |
| 694 | + } | |
| 695 | + | |
| 696 | + // return the kind of curve in the right and left arrays. | |
| 697 | + private int computeOffsetQuad(float[] pts, final int off, | |
| 698 | + float[] leftOff, float[] rightOff) | |
| 699 | + { | |
| 700 | + final float x1 = pts[off + 0], y1 = pts[off + 1]; | |
| 701 | + final float x2 = pts[off + 2], y2 = pts[off + 3]; | |
| 702 | + final float x3 = pts[off + 4], y3 = pts[off + 5]; | |
| 703 | + | |
| 704 | + final float dx3 = x3 - x2; | |
| 705 | + final float dy3 = y3 - y2; | |
| 706 | + final float dx1 = x2 - x1; | |
| 707 | + final float dy1 = y2 - y1; | |
| 708 | + | |
| 709 | + // this computes the offsets at t = 0, 1 | |
| 710 | + computeOffset(dx1, dy1, lineWidth2, offset[0]); | |
| 711 | + computeOffset(dx3, dy3, lineWidth2, offset[1]); | |
| 712 | + | |
| 713 | + leftOff[0] = x1 + offset[0][0]; leftOff[1] = y1 + offset[0][1]; | |
| 714 | + leftOff[4] = x3 + offset[1][0]; leftOff[5] = y3 + offset[1][1]; | |
| 715 | + rightOff[0] = x1 - offset[0][0]; rightOff[1] = y1 - offset[0][1]; | |
| 716 | + rightOff[4] = x3 - offset[1][0]; rightOff[5] = y3 - offset[1][1]; | |
| 717 | + | |
| 718 | + float x1p = leftOff[0]; // start | |
| 719 | + float y1p = leftOff[1]; // point | |
| 720 | + float x3p = leftOff[4]; // end | |
| 721 | + float y3p = leftOff[5]; // point | |
| 722 | + | |
| 723 | + // Corner cases: | |
| 724 | + // 1. If the two control vectors are parallel, we'll end up with NaN's | |
| 725 | + // in leftOff (and rightOff in the body of the if below), so we'll | |
| 726 | + // do getLineOffsets, which is right. | |
| 727 | + // 2. If the first or second two points are equal, then (dx1,dy1)==(0,0) | |
| 728 | + // or (dx3,dy3)==(0,0), so (x1p, y1p)==(x1p+dx1, y1p+dy1) | |
| 729 | + // or (x3p, y3p)==(x3p-dx3, y3p-dy3), which means that | |
| 730 | + // computeIntersection will put NaN's in leftOff and right off, and | |
| 731 | + // we will do getLineOffsets, which is right. | |
| 732 | + computeIntersection(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff, 2); | |
| 733 | + float cx = leftOff[2]; | |
| 734 | + float cy = leftOff[3]; | |
| 735 | + | |
| 736 | + if (!(isFinite(cx) && isFinite(cy))) { | |
| 737 | + // maybe the right path is not degenerate. | |
| 738 | + x1p = rightOff[0]; | |
| 739 | + y1p = rightOff[1]; | |
| 740 | + x3p = rightOff[4]; | |
| 741 | + y3p = rightOff[5]; | |
| 742 | + computeIntersection(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff, 2); | |
| 743 | + cx = rightOff[2]; | |
| 744 | + cy = rightOff[3]; | |
| 745 | + if (!(isFinite(cx) && isFinite(cy))) { | |
| 746 | + // both are degenerate. This curve is a line. | |
| 747 | + getLineOffsets(x1, y1, x3, y3, leftOff, rightOff); | |
| 748 | + return 4; | |
| 749 | + } | |
| 750 | + // {left,right}Off[0,1,4,5] are already set to the correct values. | |
| 751 | + leftOff[2] = 2*x2 - cx; | |
| 752 | + leftOff[3] = 2*y2 - cy; | |
| 753 | + return 6; | |
| 754 | + } | |
| 755 | + | |
| 756 | + // rightOff[2,3] = (x2,y2) - ((left_x2, left_y2) - (x2, y2)) | |
| 757 | + // == 2*(x2, y2) - (left_x2, left_y2) | |
| 758 | + rightOff[2] = 2*x2 - cx; | |
| 759 | + rightOff[3] = 2*y2 - cy; | |
| 760 | + return 6; | |
| 761 | + } | |
| 762 | + | |
| 763 | + private static boolean isFinite(float x) { | |
| 764 | + return (Float.NEGATIVE_INFINITY < x && x < Float.POSITIVE_INFINITY); | |
| 765 | + } | |
| 766 | + | |
| 767 | + // This is where the curve to be processed is put. We give it | |
| 768 | + // enough room to store 2 curves: one for the current subdivision, the | |
| 769 | + // other for the rest of the curve. | |
| 770 | + private float[] middle = new float[2*8]; | |
| 771 | + private float[] lp = new float[8]; | |
| 772 | + private float[] rp = new float[8]; | |
| 773 | + private static final int MAX_N_CURVES = 11; | |
| 774 | + private float[] subdivTs = new float[MAX_N_CURVES - 1]; | |
| 775 | + | |
| 776 | + // If this class is compiled with ecj, then Hotspot crashes when OSR | |
| 777 | + // compiling this function. See bugs 7004570 and 6675699 | |
| 778 | + // TODO: until those are fixed, we should work around that by | |
| 779 | + // manually inlining this into curveTo and quadTo. | |
| 780 | +/******************************* WORKAROUND ********************************** | |
| 781 | + private void somethingTo(final int type) { | |
| 782 | + // need these so we can update the state at the end of this method | |
| 783 | + final float xf = middle[type-2], yf = middle[type-1]; | |
| 784 | + float dxs = middle[2] - middle[0]; | |
| 785 | + float dys = middle[3] - middle[1]; | |
| 786 | + float dxf = middle[type - 2] - middle[type - 4]; | |
| 787 | + float dyf = middle[type - 1] - middle[type - 3]; | |
| 788 | + switch(type) { | |
| 789 | + case 6: | |
| 790 | + if ((dxs == 0f && dys == 0f) || | |
| 791 | + (dxf == 0f && dyf == 0f)) { | |
| 792 | + dxs = dxf = middle[4] - middle[0]; | |
| 793 | + dys = dyf = middle[5] - middle[1]; | |
| 794 | + } | |
| 795 | + break; | |
| 796 | + case 8: | |
| 797 | + boolean p1eqp2 = (dxs == 0f && dys == 0f); | |
| 798 | + boolean p3eqp4 = (dxf == 0f && dyf == 0f); | |
| 799 | + if (p1eqp2) { | |
| 800 | + dxs = middle[4] - middle[0]; | |
| 801 | + dys = middle[5] - middle[1]; | |
| 802 | + if (dxs == 0f && dys == 0f) { | |
| 803 | + dxs = middle[6] - middle[0]; | |
| 804 | + dys = middle[7] - middle[1]; | |
| 805 | + } | |
| 806 | + } | |
| 807 | + if (p3eqp4) { | |
| 808 | + dxf = middle[6] - middle[2]; | |
| 809 | + dyf = middle[7] - middle[3]; | |
| 810 | + if (dxf == 0f && dyf == 0f) { | |
| 811 | + dxf = middle[6] - middle[0]; | |
| 812 | + dyf = middle[7] - middle[1]; | |
| 813 | + } | |
| 814 | + } | |
| 815 | + } | |
| 816 | + if (dxs == 0f && dys == 0f) { | |
| 817 | + // this happens iff the "curve" is just a point | |
| 818 | + lineTo(middle[0], middle[1]); | |
| 819 | + return; | |
| 820 | + } | |
| 821 | + // if these vectors are too small, normalize them, to avoid future | |
| 822 | + // precision problems. | |
| 823 | + if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) { | |
| 824 | + float len = (float) sqrt(dxs*dxs + dys*dys); | |
| 825 | + dxs /= len; | |
| 826 | + dys /= len; | |
| 827 | + } | |
| 828 | + if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) { | |
| 829 | + float len = (float) sqrt(dxf*dxf + dyf*dyf); | |
| 830 | + dxf /= len; | |
| 831 | + dyf /= len; | |
| 832 | + } | |
| 833 | + | |
| 834 | + computeOffset(dxs, dys, lineWidth2, offset[0]); | |
| 835 | + final float mx = offset[0][0]; | |
| 836 | + final float my = offset[0][1]; | |
| 837 | + drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, mx, my); | |
| 838 | + | |
| 839 | + int nSplits = findSubdivPoints(middle, subdivTs, type, lineWidth2); | |
| 840 | + | |
| 841 | + int kind = 0; | |
| 842 | + Iterator<Integer> it = Curve.breakPtsAtTs(middle, type, subdivTs, nSplits); | |
| 843 | + while(it.hasNext()) { | |
| 844 | + int curCurveOff = it.next(); | |
| 845 | + | |
| 846 | + switch (type) { | |
| 847 | + case 8: | |
| 848 | + kind = computeOffsetCubic(middle, curCurveOff, lp, rp); | |
| 849 | + break; | |
| 850 | + case 6: | |
| 851 | + kind = computeOffsetQuad(middle, curCurveOff, lp, rp); | |
| 852 | + break; | |
| 853 | + } | |
| 854 | + emitLineTo(lp[0], lp[1]); | |
| 855 | + switch(kind) { | |
| 856 | + case 8: | |
| 857 | + emitCurveTo(lp[0], lp[1], lp[2], lp[3], lp[4], lp[5], lp[6], lp[7], false); | |
| 858 | + emitCurveTo(rp[0], rp[1], rp[2], rp[3], rp[4], rp[5], rp[6], rp[7], true); | |
| 859 | + break; | |
| 860 | + case 6: | |
| 861 | + emitQuadTo(lp[0], lp[1], lp[2], lp[3], lp[4], lp[5], false); | |
| 862 | + emitQuadTo(rp[0], rp[1], rp[2], rp[3], rp[4], rp[5], true); | |
| 863 | + break; | |
| 864 | + case 4: | |
| 865 | + emitLineTo(lp[2], lp[3]); | |
| 866 | + emitLineTo(rp[0], rp[1], true); | |
| 867 | + break; | |
| 868 | + } | |
| 869 | + emitLineTo(rp[kind - 2], rp[kind - 1], true); | |
| 870 | + } | |
| 871 | + | |
| 872 | + this.cmx = (lp[kind - 2] - rp[kind - 2]) / 2; | |
| 873 | + this.cmy = (lp[kind - 1] - rp[kind - 1]) / 2; | |
| 874 | + this.cdx = dxf; | |
| 875 | + this.cdy = dyf; | |
| 876 | + this.cx0 = xf; | |
| 877 | + this.cy0 = yf; | |
| 878 | + this.prev = DRAWING_OP_TO; | |
| 879 | + } | |
| 880 | +****************************** END WORKAROUND *******************************/ | |
| 881 | + | |
| 882 | + // finds values of t where the curve in pts should be subdivided in order | |
| 883 | + // to get good offset curves a distance of w away from the middle curve. | |
| 884 | + // Stores the points in ts, and returns how many of them there were. | |
| 885 | + private static Curve c = new Curve(); | |
| 886 | + private static int findSubdivPoints(float[] pts, float[] ts, final int type, final float w) | |
| 887 | + { | |
| 888 | + final float x12 = pts[2] - pts[0]; | |
| 889 | + final float y12 = pts[3] - pts[1]; | |
| 890 | + // if the curve is already parallel to either axis we gain nothing | |
| 891 | + // from rotating it. | |
| 892 | + if (y12 != 0f && x12 != 0f) { | |
| 893 | + // we rotate it so that the first vector in the control polygon is | |
| 894 | + // parallel to the x-axis. This will ensure that rotated quarter | |
| 895 | + // circles won't be subdivided. | |
| 896 | + final float hypot = (float) sqrt(x12 * x12 + y12 * y12); | |
| 897 | + final float cos = x12 / hypot; | |
| 898 | + final float sin = y12 / hypot; | |
| 899 | + final float x1 = cos * pts[0] + sin * pts[1]; | |
| 900 | + final float y1 = cos * pts[1] - sin * pts[0]; | |
| 901 | + final float x2 = cos * pts[2] + sin * pts[3]; | |
| 902 | + final float y2 = cos * pts[3] - sin * pts[2]; | |
| 903 | + final float x3 = cos * pts[4] + sin * pts[5]; | |
| 904 | + final float y3 = cos * pts[5] - sin * pts[4]; | |
| 905 | + switch(type) { | |
| 906 | + case 8: | |
| 907 | + final float x4 = cos * pts[6] + sin * pts[7]; | |
| 908 | + final float y4 = cos * pts[7] - sin * pts[6]; | |
| 909 | + c.set(x1, y1, x2, y2, x3, y3, x4, y4); | |
| 910 | + break; | |
| 911 | + case 6: | |
| 912 | + c.set(x1, y1, x2, y2, x3, y3); | |
| 913 | + break; | |
| 914 | + } | |
| 915 | + } else { | |
| 916 | + c.set(pts, type); | |
| 917 | + } | |
| 918 | + | |
| 919 | + int ret = 0; | |
| 920 | + // we subdivide at values of t such that the remaining rotated | |
| 921 | + // curves are monotonic in x and y. | |
| 922 | + ret += c.dxRoots(ts, ret); | |
| 923 | + ret += c.dyRoots(ts, ret); | |
| 924 | + // subdivide at inflection points. | |
| 925 | + if (type == 8) { | |
| 926 | + // quadratic curves can't have inflection points | |
| 927 | + ret += c.infPoints(ts, ret); | |
| 928 | + } | |
| 929 | + | |
| 930 | + // now we must subdivide at points where one of the offset curves will have | |
| 931 | + // a cusp. This happens at ts where the radius of curvature is equal to w. | |
| 932 | + ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001f); | |
| 933 | + | |
| 934 | + ret = Helpers.filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f); | |
| 935 | + Helpers.isort(ts, 0, ret); | |
| 936 | + return ret; | |
| 937 | + } | |
| 938 | + | |
| 939 | + @Override public void curveTo(float x1, float y1, | |
| 940 | + float x2, float y2, | |
| 941 | + float x3, float y3) | |
| 942 | + { | |
| 943 | + middle[0] = cx0; middle[1] = cy0; | |
| 944 | + middle[2] = x1; middle[3] = y1; | |
| 945 | + middle[4] = x2; middle[5] = y2; | |
| 946 | + middle[6] = x3; middle[7] = y3; | |
| 947 | + | |
| 948 | + // inlined version of somethingTo(8); | |
| 949 | + // See the TODO on somethingTo | |
| 950 | + | |
| 951 | + // need these so we can update the state at the end of this method | |
| 952 | + final float xf = middle[6], yf = middle[7]; | |
| 953 | + float dxs = middle[2] - middle[0]; | |
| 954 | + float dys = middle[3] - middle[1]; | |
| 955 | + float dxf = middle[6] - middle[4]; | |
| 956 | + float dyf = middle[7] - middle[5]; | |
| 957 | + | |
| 958 | + boolean p1eqp2 = (dxs == 0f && dys == 0f); | |
| 959 | + boolean p3eqp4 = (dxf == 0f && dyf == 0f); | |
| 960 | + if (p1eqp2) { | |
| 961 | + dxs = middle[4] - middle[0]; | |
| 962 | + dys = middle[5] - middle[1]; | |
| 963 | + if (dxs == 0f && dys == 0f) { | |
| 964 | + dxs = middle[6] - middle[0]; | |
| 965 | + dys = middle[7] - middle[1]; | |
| 966 | + } | |
| 967 | + } | |
| 968 | + if (p3eqp4) { | |
| 969 | + dxf = middle[6] - middle[2]; | |
| 970 | + dyf = middle[7] - middle[3]; | |
| 971 | + if (dxf == 0f && dyf == 0f) { | |
| 972 | + dxf = middle[6] - middle[0]; | |
| 973 | + dyf = middle[7] - middle[1]; | |
| 974 | + } | |
| 975 | + } | |
| 976 | + if (dxs == 0f && dys == 0f) { | |
| 977 | + // this happens iff the "curve" is just a point | |
| 978 | + lineTo(middle[0], middle[1]); | |
| 979 | + return; | |
| 980 | + } | |
| 981 | + | |
| 982 | + // if these vectors are too small, normalize them, to avoid future | |
| 983 | + // precision problems. | |
| 984 | + if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) { | |
| 985 | + float len = (float) sqrt(dxs*dxs + dys*dys); | |
| 986 | + dxs /= len; | |
| 987 | + dys /= len; | |
| 988 | + } | |
| 989 | + if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) { | |
| 990 | + float len = (float) sqrt(dxf*dxf + dyf*dyf); | |
| 991 | + dxf /= len; | |
| 992 | + dyf /= len; | |
| 993 | + } | |
| 994 | + | |
| 995 | + computeOffset(dxs, dys, lineWidth2, offset[0]); | |
| 996 | + final float mx = offset[0][0]; | |
| 997 | + final float my = offset[0][1]; | |
| 998 | + drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, mx, my); | |
| 999 | + | |
| 1000 | + int nSplits = findSubdivPoints(middle, subdivTs, 8, lineWidth2); | |
| 1001 | + | |
| 1002 | + int kind = 0; | |
| 1003 | + Iterator<Integer> it = Curve.breakPtsAtTs(middle, 8, subdivTs, nSplits); | |
| 1004 | + while(it.hasNext()) { | |
| 1005 | + int curCurveOff = it.next(); | |
| 1006 | + | |
| 1007 | + kind = computeOffsetCubic(middle, curCurveOff, lp, rp); | |
| 1008 | + emitLineTo(lp[0], lp[1]); | |
| 1009 | + switch(kind) { | |
| 1010 | + case 8: | |
| 1011 | + emitCurveTo(lp[0], lp[1], lp[2], lp[3], lp[4], lp[5], lp[6], lp[7], false); | |
| 1012 | + emitCurveTo(rp[0], rp[1], rp[2], rp[3], rp[4], rp[5], rp[6], rp[7], true); | |
| 1013 | + break; | |
| 1014 | + case 4: | |
| 1015 | + emitLineTo(lp[2], lp[3]); | |
| 1016 | + emitLineTo(rp[0], rp[1], true); | |
| 1017 | + break; | |
| 1018 | + } | |
| 1019 | + emitLineTo(rp[kind - 2], rp[kind - 1], true); | |
| 1020 | + } | |
| 1021 | + | |
| 1022 | + this.cmx = (lp[kind - 2] - rp[kind - 2]) / 2; | |
| 1023 | + this.cmy = (lp[kind - 1] - rp[kind - 1]) / 2; | |
| 1024 | + this.cdx = dxf; | |
| 1025 | + this.cdy = dyf; | |
| 1026 | + this.cx0 = xf; | |
| 1027 | + this.cy0 = yf; | |
| 1028 | + this.prev = DRAWING_OP_TO; | |
| 1029 | + } | |
| 1030 | + | |
| 1031 | + @Override public void quadTo(float x1, float y1, float x2, float y2) { | |
| 1032 | + middle[0] = cx0; middle[1] = cy0; | |
| 1033 | + middle[2] = x1; middle[3] = y1; | |
| 1034 | + middle[4] = x2; middle[5] = y2; | |
| 1035 | + | |
| 1036 | + // inlined version of somethingTo(8); | |
| 1037 | + // See the TODO on somethingTo | |
| 1038 | + | |
| 1039 | + // need these so we can update the state at the end of this method | |
| 1040 | + final float xf = middle[4], yf = middle[5]; | |
| 1041 | + float dxs = middle[2] - middle[0]; | |
| 1042 | + float dys = middle[3] - middle[1]; | |
| 1043 | + float dxf = middle[4] - middle[2]; | |
| 1044 | + float dyf = middle[5] - middle[3]; | |
| 1045 | + if ((dxs == 0f && dys == 0f) || (dxf == 0f && dyf == 0f)) { | |
| 1046 | + dxs = dxf = middle[4] - middle[0]; | |
| 1047 | + dys = dyf = middle[5] - middle[1]; | |
| 1048 | + } | |
| 1049 | + if (dxs == 0f && dys == 0f) { | |
| 1050 | + // this happens iff the "curve" is just a point | |
| 1051 | + lineTo(middle[0], middle[1]); | |
| 1052 | + return; | |
| 1053 | + } | |
| 1054 | + // if these vectors are too small, normalize them, to avoid future | |
| 1055 | + // precision problems. | |
| 1056 | + if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) { | |
| 1057 | + float len = (float) sqrt(dxs*dxs + dys*dys); | |
| 1058 | + dxs /= len; | |
| 1059 | + dys /= len; | |
| 1060 | + } | |
| 1061 | + if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) { | |
| 1062 | + float len = (float) sqrt(dxf*dxf + dyf*dyf); | |
| 1063 | + dxf /= len; | |
| 1064 | + dyf /= len; | |
| 1065 | + } | |
| 1066 | + | |
| 1067 | + computeOffset(dxs, dys, lineWidth2, offset[0]); | |
| 1068 | + final float mx = offset[0][0]; | |
| 1069 | + final float my = offset[0][1]; | |
| 1070 | + drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, mx, my); | |
| 1071 | + | |
| 1072 | + int nSplits = findSubdivPoints(middle, subdivTs, 6, lineWidth2); | |
| 1073 | + | |
| 1074 | + int kind = 0; | |
| 1075 | + Iterator<Integer> it = Curve.breakPtsAtTs(middle, 6, subdivTs, nSplits); | |
| 1076 | + while(it.hasNext()) { | |
| 1077 | + int curCurveOff = it.next(); | |
| 1078 | + | |
| 1079 | + kind = computeOffsetQuad(middle, curCurveOff, lp, rp); | |
| 1080 | + emitLineTo(lp[0], lp[1]); | |
| 1081 | + switch(kind) { | |
| 1082 | + case 6: | |
| 1083 | + emitQuadTo(lp[0], lp[1], lp[2], lp[3], lp[4], lp[5], false); | |
| 1084 | + emitQuadTo(rp[0], rp[1], rp[2], rp[3], rp[4], rp[5], true); | |
| 1085 | + break; | |
| 1086 | + case 4: | |
| 1087 | + emitLineTo(lp[2], lp[3]); | |
| 1088 | + emitLineTo(rp[0], rp[1], true); | |
| 1089 | + break; | |
| 1090 | + } | |
| 1091 | + emitLineTo(rp[kind - 2], rp[kind - 1], true); | |
| 1092 | + } | |
| 1093 | + | |
| 1094 | + this.cmx = (lp[kind - 2] - rp[kind - 2]) / 2; | |
| 1095 | + this.cmy = (lp[kind - 1] - rp[kind - 1]) / 2; | |
| 1096 | + this.cdx = dxf; | |
| 1097 | + this.cdy = dyf; | |
| 1098 | + this.cx0 = xf; | |
| 1099 | + this.cy0 = yf; | |
| 1100 | + this.prev = DRAWING_OP_TO; | |
| 1101 | + } | |
| 1102 | + | |
| 1103 | + @Override public long getNativeConsumer() { | |
| 1104 | + throw new InternalError("Stroker doesn't use a native consumer"); | |
| 1105 | + } | |
| 1106 | + | |
| 1107 | + // a stack of polynomial curves where each curve shares endpoints with | |
| 1108 | + // adjacent ones. | |
| 1109 | + private static final class PolyStack { | |
| 1110 | + float[] curves; | |
| 1111 | + int end; | |
| 1112 | + int[] curveTypes; | |
| 1113 | + int numCurves; | |
| 1114 | + | |
| 1115 | + private static final int INIT_SIZE = 50; | |
| 1116 | + | |
| 1117 | + PolyStack() { | |
| 1118 | + curves = new float[8 * INIT_SIZE]; | |
| 1119 | + curveTypes = new int[INIT_SIZE]; | |
| 1120 | + end = 0; | |
| 1121 | + numCurves = 0; | |
| 1122 | + } | |
| 1123 | + | |
| 1124 | + public boolean isEmpty() { | |
| 1125 | + return numCurves == 0; | |
| 1126 | + } | |
| 1127 | + | |
| 1128 | + private void ensureSpace(int n) { | |
| 1129 | + if (end + n >= curves.length) { | |
| 1130 | + int newSize = (end + n) * 2; | |
| 1131 | + curves = Arrays.copyOf(curves, newSize); | |
| 1132 | + } | |
| 1133 | + if (numCurves >= curveTypes.length) { | |
| 1134 | + int newSize = numCurves * 2; | |
| 1135 | + curveTypes = Arrays.copyOf(curveTypes, newSize); | |
| 1136 | + } | |
| 1137 | + } | |
| 1138 | + | |
| 1139 | + public void pushCubic(float x0, float y0, | |
| 1140 | + float x1, float y1, | |
| 1141 | + float x2, float y2) | |
| 1142 | + { | |
| 1143 | + ensureSpace(6); | |
| 1144 | + curveTypes[numCurves++] = 8; | |
| 1145 | + // assert(x0 == lastX && y0 == lastY) | |
| 1146 | + | |
| 1147 | + // we reverse the coordinate order to make popping easier | |
| 1148 | + curves[end++] = x2; curves[end++] = y2; | |
| 1149 | + curves[end++] = x1; curves[end++] = y1; | |
| 1150 | + curves[end++] = x0; curves[end++] = y0; | |
| 1151 | + } | |
| 1152 | + | |
| 1153 | + public void pushQuad(float x0, float y0, | |
| 1154 | + float x1, float y1) | |
| 1155 | + { | |
| 1156 | + ensureSpace(4); | |
| 1157 | + curveTypes[numCurves++] = 6; | |
| 1158 | + // assert(x0 == lastX && y0 == lastY) | |
| 1159 | + curves[end++] = x1; curves[end++] = y1; | |
| 1160 | + curves[end++] = x0; curves[end++] = y0; | |
| 1161 | + } | |
| 1162 | + | |
| 1163 | + public void pushLine(float x, float y) { | |
| 1164 | + ensureSpace(2); | |
| 1165 | + curveTypes[numCurves++] = 4; | |
| 1166 | + // assert(x0 == lastX && y0 == lastY) | |
| 1167 | + curves[end++] = x; curves[end++] = y; | |
| 1168 | + } | |
| 1169 | + | |
| 1170 | + @SuppressWarnings("unused") | |
| 1171 | + public int pop(float[] pts) { | |
| 1172 | + int ret = curveTypes[numCurves - 1]; | |
| 1173 | + numCurves--; | |
| 1174 | + end -= (ret - 2); | |
| 1175 | + System.arraycopy(curves, end, pts, 0, ret - 2); | |
| 1176 | + return ret; | |
| 1177 | + } | |
| 1178 | + | |
| 1179 | + public void pop(PathConsumer2D io) { | |
| 1180 | + numCurves--; | |
| 1181 | + int type = curveTypes[numCurves]; | |
| 1182 | + end -= (type - 2); | |
| 1183 | + switch(type) { | |
| 1184 | + case 8: | |
| 1185 | + io.curveTo(curves[end+0], curves[end+1], | |
| 1186 | + curves[end+2], curves[end+3], | |
| 1187 | + curves[end+4], curves[end+5]); | |
| 1188 | + break; | |
| 1189 | + case 6: | |
| 1190 | + io.quadTo(curves[end+0], curves[end+1], | |
| 1191 | + curves[end+2], curves[end+3]); | |
| 1192 | + break; | |
| 1193 | + case 4: | |
| 1194 | + io.lineTo(curves[end], curves[end+1]); | |
| 1195 | + } | |
| 1196 | + } | |
| 1197 | + | |
| 1198 | + @Override | |
| 1199 | + public String toString() { | |
| 1200 | + String ret = ""; | |
| 1201 | + int nc = numCurves; | |
| 1202 | + int end = this.end; | |
| 1203 | + while (nc > 0) { | |
| 1204 | + nc--; | |
| 1205 | + int type = curveTypes[numCurves]; | |
| 1206 | + end -= (type - 2); | |
| 1207 | + switch(type) { | |
| 1208 | + case 8: | |
| 1209 | + ret += "cubic: "; | |
| 1210 | + break; | |
| 1211 | + case 6: | |
| 1212 | + ret += "quad: "; | |
| 1213 | + break; | |
| 1214 | + case 4: | |
| 1215 | + ret += "line: "; | |
| 1216 | + break; | |
| 1217 | + } | |
| 1218 | + ret += Arrays.toString(Arrays.copyOfRange(curves, end, end+type-2)) + "\n"; | |
| 1219 | + } | |
| 1220 | + return ret; | |
| 1221 | + } | |
| 1222 | + } | |
| 1223 | +} | |
| @@ -0,0 +1,393 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. | |
| 3 | + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| 4 | + * | |
| 5 | + * This code is free software; you can redistribute it and/or modify it | |
| 6 | + * under the terms of the GNU General Public License version 2 only, as | |
| 7 | + * published by the Free Software Foundation. Oracle designates this | |
| 8 | + * particular file as subject to the "Classpath" exception as provided | |
| 9 | + * by Oracle in the LICENSE file that accompanied this code. | |
| 10 | + * | |
| 11 | + * This code is distributed in the hope that it will be useful, but WITHOUT | |
| 12 | + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| 13 | + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| 14 | + * version 2 for more details (a copy is included in the LICENSE file that | |
| 15 | + * accompanied this code). | |
| 16 | + * | |
| 17 | + * You should have received a copy of the GNU General Public License version | |
| 18 | + * 2 along with this work; if not, write to the Free Software Foundation, | |
| 19 | + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 20 | + * | |
| 21 | + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| 22 | + * or visit www.oracle.com if you need additional information or have any | |
| 23 | + * questions. | |
| 24 | + */ | |
| 25 | + | |
| 26 | +package sun.java2d.pisces; | |
| 27 | + | |
| 28 | +import sun.awt.geom.PathConsumer2D; | |
| 29 | +import java.awt.geom.AffineTransform; | |
| 30 | + | |
| 31 | +final class TransformingPathConsumer2D { | |
| 32 | + public static PathConsumer2D | |
| 33 | + transformConsumer(PathConsumer2D out, | |
| 34 | + AffineTransform at) | |
| 35 | + { | |
| 36 | + if (at == null) { | |
| 37 | + return out; | |
| 38 | + } | |
| 39 | + float Mxx = (float) at.getScaleX(); | |
| 40 | + float Mxy = (float) at.getShearX(); | |
| 41 | + float Mxt = (float) at.getTranslateX(); | |
| 42 | + float Myx = (float) at.getShearY(); | |
| 43 | + float Myy = (float) at.getScaleY(); | |
| 44 | + float Myt = (float) at.getTranslateY(); | |
| 45 | + if (Mxy == 0f && Myx == 0f) { | |
| 46 | + if (Mxx == 1f && Myy == 1f) { | |
| 47 | + if (Mxt == 0f && Myt == 0f) { | |
| 48 | + return out; | |
| 49 | + } else { | |
| 50 | + return new TranslateFilter(out, Mxt, Myt); | |
| 51 | + } | |
| 52 | + } else { | |
| 53 | + if (Mxt == 0f && Myt == 0f) { | |
| 54 | + return new DeltaScaleFilter(out, Mxx, Myy); | |
| 55 | + } else { | |
| 56 | + return new ScaleFilter(out, Mxx, Myy, Mxt, Myt); | |
| 57 | + } | |
| 58 | + } | |
| 59 | + } else if (Mxt == 0f && Myt == 0f) { | |
| 60 | + return new DeltaTransformFilter(out, Mxx, Mxy, Myx, Myy); | |
| 61 | + } else { | |
| 62 | + return new TransformFilter(out, Mxx, Mxy, Mxt, Myx, Myy, Myt); | |
| 63 | + } | |
| 64 | + } | |
| 65 | + | |
| 66 | + public static PathConsumer2D | |
| 67 | + deltaTransformConsumer(PathConsumer2D out, | |
| 68 | + AffineTransform at) | |
| 69 | + { | |
| 70 | + if (at == null) { | |
| 71 | + return out; | |
| 72 | + } | |
| 73 | + float Mxx = (float) at.getScaleX(); | |
| 74 | + float Mxy = (float) at.getShearX(); | |
| 75 | + float Myx = (float) at.getShearY(); | |
| 76 | + float Myy = (float) at.getScaleY(); | |
| 77 | + if (Mxy == 0f && Myx == 0f) { | |
| 78 | + if (Mxx == 1f && Myy == 1f) { | |
| 79 | + return out; | |
| 80 | + } else { | |
| 81 | + return new DeltaScaleFilter(out, Mxx, Myy); | |
| 82 | + } | |
| 83 | + } else { | |
| 84 | + return new DeltaTransformFilter(out, Mxx, Mxy, Myx, Myy); | |
| 85 | + } | |
| 86 | + } | |
| 87 | + | |
| 88 | + public static PathConsumer2D | |
| 89 | + inverseDeltaTransformConsumer(PathConsumer2D out, | |
| 90 | + AffineTransform at) | |
| 91 | + { | |
| 92 | + if (at == null) { | |
| 93 | + return out; | |
| 94 | + } | |
| 95 | + float Mxx = (float) at.getScaleX(); | |
| 96 | + float Mxy = (float) at.getShearX(); | |
| 97 | + float Myx = (float) at.getShearY(); | |
| 98 | + float Myy = (float) at.getScaleY(); | |
| 99 | + if (Mxy == 0f && Myx == 0f) { | |
| 100 | + if (Mxx == 1f && Myy == 1f) { | |
| 101 | + return out; | |
| 102 | + } else { | |
| 103 | + return new DeltaScaleFilter(out, 1.0f/Mxx, 1.0f/Myy); | |
| 104 | + } | |
| 105 | + } else { | |
| 106 | + float det = Mxx * Myy - Mxy * Myx; | |
| 107 | + return new DeltaTransformFilter(out, | |
| 108 | + Myy / det, | |
| 109 | + -Mxy / det, | |
| 110 | + -Myx / det, | |
| 111 | + Mxx / det); | |
| 112 | + } | |
| 113 | + } | |
| 114 | + | |
| 115 | + static final class TranslateFilter implements PathConsumer2D { | |
| 116 | + private final PathConsumer2D out; | |
| 117 | + private final float tx; | |
| 118 | + private final float ty; | |
| 119 | + | |
| 120 | + TranslateFilter(PathConsumer2D out, | |
| 121 | + float tx, float ty) | |
| 122 | + { | |
| 123 | + this.out = out; | |
| 124 | + this.tx = tx; | |
| 125 | + this.ty = ty; | |
| 126 | + } | |
| 127 | + | |
| 128 | + public void moveTo(float x0, float y0) { | |
| 129 | + out.moveTo(x0 + tx, y0 + ty); | |
| 130 | + } | |
| 131 | + | |
| 132 | + public void lineTo(float x1, float y1) { | |
| 133 | + out.lineTo(x1 + tx, y1 + ty); | |
| 134 | + } | |
| 135 | + | |
| 136 | + public void quadTo(float x1, float y1, | |
| 137 | + float x2, float y2) | |
| 138 | + { | |
| 139 | + out.quadTo(x1 + tx, y1 + ty, | |
| 140 | + x2 + tx, y2 + ty); | |
| 141 | + } | |
| 142 | + | |
| 143 | + public void curveTo(float x1, float y1, | |
| 144 | + float x2, float y2, | |
| 145 | + float x3, float y3) | |
| 146 | + { | |
| 147 | + out.curveTo(x1 + tx, y1 + ty, | |
| 148 | + x2 + tx, y2 + ty, | |
| 149 | + x3 + tx, y3 + ty); | |
| 150 | + } | |
| 151 | + | |
| 152 | + public void closePath() { | |
| 153 | + out.closePath(); | |
| 154 | + } | |
| 155 | + | |
| 156 | + public void pathDone() { | |
| 157 | + out.pathDone(); | |
| 158 | + } | |
| 159 | + | |
| 160 | + public long getNativeConsumer() { | |
| 161 | + return 0; | |
| 162 | + } | |
| 163 | + } | |
| 164 | + | |
| 165 | + static final class ScaleFilter implements PathConsumer2D { | |
| 166 | + private final PathConsumer2D out; | |
| 167 | + private final float sx; | |
| 168 | + private final float sy; | |
| 169 | + private final float tx; | |
| 170 | + private final float ty; | |
| 171 | + | |
| 172 | + ScaleFilter(PathConsumer2D out, | |
| 173 | + float sx, float sy, float tx, float ty) | |
| 174 | + { | |
| 175 | + this.out = out; | |
| 176 | + this.sx = sx; | |
| 177 | + this.sy = sy; | |
| 178 | + this.tx = tx; | |
| 179 | + this.ty = ty; | |
| 180 | + } | |
| 181 | + | |
| 182 | + public void moveTo(float x0, float y0) { | |
| 183 | + out.moveTo(x0 * sx + tx, y0 * sy + ty); | |
| 184 | + } | |
| 185 | + | |
| 186 | + public void lineTo(float x1, float y1) { | |
| 187 | + out.lineTo(x1 * sx + tx, y1 * sy + ty); | |
| 188 | + } | |
| 189 | + | |
| 190 | + public void quadTo(float x1, float y1, | |
| 191 | + float x2, float y2) | |
| 192 | + { | |
| 193 | + out.quadTo(x1 * sx + tx, y1 * sy + ty, | |
| 194 | + x2 * sx + tx, y2 * sy + ty); | |
| 195 | + } | |
| 196 | + | |
| 197 | + public void curveTo(float x1, float y1, | |
| 198 | + float x2, float y2, | |
| 199 | + float x3, float y3) | |
| 200 | + { | |
| 201 | + out.curveTo(x1 * sx + tx, y1 * sy + ty, | |
| 202 | + x2 * sx + tx, y2 * sy + ty, | |
| 203 | + x3 * sx + tx, y3 * sy + ty); | |
| 204 | + } | |
| 205 | + | |
| 206 | + public void closePath() { | |
| 207 | + out.closePath(); | |
| 208 | + } | |
| 209 | + | |
| 210 | + public void pathDone() { | |
| 211 | + out.pathDone(); | |
| 212 | + } | |
| 213 | + | |
| 214 | + public long getNativeConsumer() { | |
| 215 | + return 0; | |
| 216 | + } | |
| 217 | + } | |
| 218 | + | |
| 219 | + static final class TransformFilter implements PathConsumer2D { | |
| 220 | + private final PathConsumer2D out; | |
| 221 | + private final float Mxx; | |
| 222 | + private final float Mxy; | |
| 223 | + private final float Mxt; | |
| 224 | + private final float Myx; | |
| 225 | + private final float Myy; | |
| 226 | + private final float Myt; | |
| 227 | + | |
| 228 | + TransformFilter(PathConsumer2D out, | |
| 229 | + float Mxx, float Mxy, float Mxt, | |
| 230 | + float Myx, float Myy, float Myt) | |
| 231 | + { | |
| 232 | + this.out = out; | |
| 233 | + this.Mxx = Mxx; | |
| 234 | + this.Mxy = Mxy; | |
| 235 | + this.Mxt = Mxt; | |
| 236 | + this.Myx = Myx; | |
| 237 | + this.Myy = Myy; | |
| 238 | + this.Myt = Myt; | |
| 239 | + } | |
| 240 | + | |
| 241 | + public void moveTo(float x0, float y0) { | |
| 242 | + out.moveTo(x0 * Mxx + y0 * Mxy + Mxt, | |
| 243 | + x0 * Myx + y0 * Myy + Myt); | |
| 244 | + } | |
| 245 | + | |
| 246 | + public void lineTo(float x1, float y1) { | |
| 247 | + out.lineTo(x1 * Mxx + y1 * Mxy + Mxt, | |
| 248 | + x1 * Myx + y1 * Myy + Myt); | |
| 249 | + } | |
| 250 | + | |
| 251 | + public void quadTo(float x1, float y1, | |
| 252 | + float x2, float y2) | |
| 253 | + { | |
| 254 | + out.quadTo(x1 * Mxx + y1 * Mxy + Mxt, | |
| 255 | + x1 * Myx + y1 * Myy + Myt, | |
| 256 | + x2 * Mxx + y2 * Mxy + Mxt, | |
| 257 | + x2 * Myx + y2 * Myy + Myt); | |
| 258 | + } | |
| 259 | + | |
| 260 | + public void curveTo(float x1, float y1, | |
| 261 | + float x2, float y2, | |
| 262 | + float x3, float y3) | |
| 263 | + { | |
| 264 | + out.curveTo(x1 * Mxx + y1 * Mxy + Mxt, | |
| 265 | + x1 * Myx + y1 * Myy + Myt, | |
| 266 | + x2 * Mxx + y2 * Mxy + Mxt, | |
| 267 | + x2 * Myx + y2 * Myy + Myt, | |
| 268 | + x3 * Mxx + y3 * Mxy + Mxt, | |
| 269 | + x3 * Myx + y3 * Myy + Myt); | |
| 270 | + } | |
| 271 | + | |
| 272 | + public void closePath() { | |
| 273 | + out.closePath(); | |
| 274 | + } | |
| 275 | + | |
| 276 | + public void pathDone() { | |
| 277 | + out.pathDone(); | |
| 278 | + } | |
| 279 | + | |
| 280 | + public long getNativeConsumer() { | |
| 281 | + return 0; | |
| 282 | + } | |
| 283 | + } | |
| 284 | + | |
| 285 | + static final class DeltaScaleFilter implements PathConsumer2D { | |
| 286 | + private final float sx, sy; | |
| 287 | + private final PathConsumer2D out; | |
| 288 | + | |
| 289 | + public DeltaScaleFilter(PathConsumer2D out, float Mxx, float Myy) { | |
| 290 | + sx = Mxx; | |
| 291 | + sy = Myy; | |
| 292 | + this.out = out; | |
| 293 | + } | |
| 294 | + | |
| 295 | + public void moveTo(float x0, float y0) { | |
| 296 | + out.moveTo(x0 * sx, y0 * sy); | |
| 297 | + } | |
| 298 | + | |
| 299 | + public void lineTo(float x1, float y1) { | |
| 300 | + out.lineTo(x1 * sx, y1 * sy); | |
| 301 | + } | |
| 302 | + | |
| 303 | + public void quadTo(float x1, float y1, | |
| 304 | + float x2, float y2) | |
| 305 | + { | |
| 306 | + out.quadTo(x1 * sx, y1 * sy, | |
| 307 | + x2 * sx, y2 * sy); | |
| 308 | + } | |
| 309 | + | |
| 310 | + public void curveTo(float x1, float y1, | |
| 311 | + float x2, float y2, | |
| 312 | + float x3, float y3) | |
| 313 | + { | |
| 314 | + out.curveTo(x1 * sx, y1 * sy, | |
| 315 | + x2 * sx, y2 * sy, | |
| 316 | + x3 * sx, y3 * sy); | |
| 317 | + } | |
| 318 | + | |
| 319 | + public void closePath() { | |
| 320 | + out.closePath(); | |
| 321 | + } | |
| 322 | + | |
| 323 | + public void pathDone() { | |
| 324 | + out.pathDone(); | |
| 325 | + } | |
| 326 | + | |
| 327 | + public long getNativeConsumer() { | |
| 328 | + return 0; | |
| 329 | + } | |
| 330 | + } | |
| 331 | + | |
| 332 | + static final class DeltaTransformFilter implements PathConsumer2D { | |
| 333 | + private PathConsumer2D out; | |
| 334 | + private final float Mxx; | |
| 335 | + private final float Mxy; | |
| 336 | + private final float Myx; | |
| 337 | + private final float Myy; | |
| 338 | + | |
| 339 | + DeltaTransformFilter(PathConsumer2D out, | |
| 340 | + float Mxx, float Mxy, | |
| 341 | + float Myx, float Myy) | |
| 342 | + { | |
| 343 | + this.out = out; | |
| 344 | + this.Mxx = Mxx; | |
| 345 | + this.Mxy = Mxy; | |
| 346 | + this.Myx = Myx; | |
| 347 | + this.Myy = Myy; | |
| 348 | + } | |
| 349 | + | |
| 350 | + public void moveTo(float x0, float y0) { | |
| 351 | + out.moveTo(x0 * Mxx + y0 * Mxy, | |
| 352 | + x0 * Myx + y0 * Myy); | |
| 353 | + } | |
| 354 | + | |
| 355 | + public void lineTo(float x1, float y1) { | |
| 356 | + out.lineTo(x1 * Mxx + y1 * Mxy, | |
| 357 | + x1 * Myx + y1 * Myy); | |
| 358 | + } | |
| 359 | + | |
| 360 | + public void quadTo(float x1, float y1, | |
| 361 | + float x2, float y2) | |
| 362 | + { | |
| 363 | + out.quadTo(x1 * Mxx + y1 * Mxy, | |
| 364 | + x1 * Myx + y1 * Myy, | |
| 365 | + x2 * Mxx + y2 * Mxy, | |
| 366 | + x2 * Myx + y2 * Myy); | |
| 367 | + } | |
| 368 | + | |
| 369 | + public void curveTo(float x1, float y1, | |
| 370 | + float x2, float y2, | |
| 371 | + float x3, float y3) | |
| 372 | + { | |
| 373 | + out.curveTo(x1 * Mxx + y1 * Mxy, | |
| 374 | + x1 * Myx + y1 * Myy, | |
| 375 | + x2 * Mxx + y2 * Mxy, | |
| 376 | + x2 * Myx + y2 * Myy, | |
| 377 | + x3 * Mxx + y3 * Mxy, | |
| 378 | + x3 * Myx + y3 * Myy); | |
| 379 | + } | |
| 380 | + | |
| 381 | + public void closePath() { | |
| 382 | + out.closePath(); | |
| 383 | + } | |
| 384 | + | |
| 385 | + public void pathDone() { | |
| 386 | + out.pathDone(); | |
| 387 | + } | |
| 388 | + | |
| 389 | + public long getNativeConsumer() { | |
| 390 | + return 0; | |
| 391 | + } | |
| 392 | + } | |
| 393 | +} | |
| @@ -0,0 +1,20 @@ | ||
| 1 | +<../../../../../mkconfig | |
| 2 | + | |
| 3 | +DIRS=\ | |
| 4 | + META-INF\ | |
| 5 | + | |
| 6 | +TARG=\ | |
| 7 | + Curve.dis\ | |
| 8 | + Dasher.dis\ | |
| 9 | + Helpers.dis\ | |
| 10 | + PiscesCache.dis\ | |
| 11 | + PiscesRenderingEngine.dis\ | |
| 12 | + PiscesTileGenerator.dis\ | |
| 13 | + Renderer.dis\ | |
| 14 | + Stroker.dis\ | |
| 15 | + TransformingPathConsumer2D.dis\ | |
| 16 | + | |
| 17 | +DISBIN=$ROOT/java/dis/java/java/sun/java2d/pisces | |
| 18 | + | |
| 19 | +<$ROOT/mkfiles/mkjava | |
| 20 | +<$ROOT/mkfiles/mksubdirs | |