Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/java2d/marlin/Stroker.java
38918 views
/*1* Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.java2d.marlin;2627import java.util.Arrays;28import static java.lang.Math.ulp;29import static java.lang.Math.sqrt;3031import sun.awt.geom.PathConsumer2D;32import sun.java2d.marlin.Curve.BreakPtrIterator;333435// TODO: some of the arithmetic here is too verbose and prone to hard to36// debug typos. We should consider making a small Point/Vector class that37// has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such38final class Stroker implements PathConsumer2D, MarlinConst {3940private static final int MOVE_TO = 0;41private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad42private static final int CLOSE = 2;4344/**45* Constant value for join style.46*/47public static final int JOIN_MITER = 0;4849/**50* Constant value for join style.51*/52public static final int JOIN_ROUND = 1;5354/**55* Constant value for join style.56*/57public static final int JOIN_BEVEL = 2;5859/**60* Constant value for end cap style.61*/62public static final int CAP_BUTT = 0;6364/**65* Constant value for end cap style.66*/67public static final int CAP_ROUND = 1;6869/**70* Constant value for end cap style.71*/72public static final int CAP_SQUARE = 2;7374// pisces used to use fixed point arithmetic with 16 decimal digits. I75// didn't want to change the values of the constant below when I converted76// it to floating point, so that's why the divisions by 2^16 are there.77private static final float ROUND_JOIN_THRESHOLD = 1000/65536f;7879private static final float C = 0.5522847498307933f;8081private static final int MAX_N_CURVES = 11;8283private PathConsumer2D out;8485private int capStyle;86private int joinStyle;8788private float lineWidth2;89private float invHalfLineWidth2Sq;9091private final float[] offset0 = new float[2];92private final float[] offset1 = new float[2];93private final float[] offset2 = new float[2];94private final float[] miter = new float[2];95private float miterLimitSq;9697private int prev;9899// The starting point of the path, and the slope there.100private float sx0, sy0, sdx, sdy;101// the current point and the slope there.102private float cx0, cy0, cdx, cdy; // c stands for current103// vectors that when added to (sx0,sy0) and (cx0,cy0) respectively yield the104// first and last points on the left parallel path. Since this path is105// parallel, it's slope at any point is parallel to the slope of the106// original path (thought they may have different directions), so these107// could be computed from sdx,sdy and cdx,cdy (and vice versa), but that108// would be error prone and hard to read, so we keep these anyway.109private float smx, smy, cmx, cmy;110111private final PolyStack reverse;112113// This is where the curve to be processed is put. We give it114// enough room to store 2 curves: one for the current subdivision, the115// other for the rest of the curve.116private final float[] middle = new float[2 * 8];117private final float[] lp = new float[8];118private final float[] rp = new float[8];119private final float[] subdivTs = new float[MAX_N_CURVES - 1];120121// per-thread renderer context122final RendererContext rdrCtx;123124// dirty curve125final Curve curve;126127/**128* Constructs a <code>Stroker</code>.129* @param rdrCtx per-thread renderer context130*/131Stroker(final RendererContext rdrCtx) {132this.rdrCtx = rdrCtx;133134this.reverse = new PolyStack(rdrCtx);135this.curve = rdrCtx.curve;136}137138/**139* Inits the <code>Stroker</code>.140*141* @param pc2d an output <code>PathConsumer2D</code>.142* @param lineWidth the desired line width in pixels143* @param capStyle the desired end cap style, one of144* <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or145* <code>CAP_SQUARE</code>.146* @param joinStyle the desired line join style, one of147* <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or148* <code>JOIN_BEVEL</code>.149* @param miterLimit the desired miter limit150* @return this instance151*/152Stroker init(PathConsumer2D pc2d,153float lineWidth,154int capStyle,155int joinStyle,156float miterLimit)157{158this.out = pc2d;159160this.lineWidth2 = lineWidth / 2f;161this.invHalfLineWidth2Sq = 1f / (2f * lineWidth2 * lineWidth2);162this.capStyle = capStyle;163this.joinStyle = joinStyle;164165float limit = miterLimit * lineWidth2;166this.miterLimitSq = limit * limit;167168this.prev = CLOSE;169170rdrCtx.stroking = 1;171172return this; // fluent API173}174175/**176* Disposes this stroker:177* clean up before reusing this instance178*/179void dispose() {180reverse.dispose();181182if (doCleanDirty) {183// Force zero-fill dirty arrays:184Arrays.fill(offset0, 0f);185Arrays.fill(offset1, 0f);186Arrays.fill(offset2, 0f);187Arrays.fill(miter, 0f);188Arrays.fill(middle, 0f);189Arrays.fill(lp, 0f);190Arrays.fill(rp, 0f);191Arrays.fill(subdivTs, 0f);192}193}194195private static void computeOffset(final float lx, final float ly,196final float w, final float[] m)197{198float len = lx*lx + ly*ly;199if (len == 0f) {200m[0] = 0f;201m[1] = 0f;202} else {203len = (float) sqrt(len);204m[0] = (ly * w) / len;205m[1] = -(lx * w) / len;206}207}208209// Returns true if the vectors (dx1, dy1) and (dx2, dy2) are210// clockwise (if dx1,dy1 needs to be rotated clockwise to close211// the smallest angle between it and dx2,dy2).212// This is equivalent to detecting whether a point q is on the right side213// of a line passing through points p1, p2 where p2 = p1+(dx1,dy1) and214// q = p2+(dx2,dy2), which is the same as saying p1, p2, q are in a215// clockwise order.216// NOTE: "clockwise" here assumes coordinates with 0,0 at the bottom left.217private static boolean isCW(final float dx1, final float dy1,218final float dx2, final float dy2)219{220return dx1 * dy2 <= dy1 * dx2;221}222223private void drawRoundJoin(float x, float y,224float omx, float omy, float mx, float my,225boolean rev,226float threshold)227{228if ((omx == 0 && omy == 0) || (mx == 0 && my == 0)) {229return;230}231232float domx = omx - mx;233float domy = omy - my;234float len = domx*domx + domy*domy;235if (len < threshold) {236return;237}238239if (rev) {240omx = -omx;241omy = -omy;242mx = -mx;243my = -my;244}245drawRoundJoin(x, y, omx, omy, mx, my, rev);246}247248private void drawRoundJoin(float cx, float cy,249float omx, float omy,250float mx, float my,251boolean rev)252{253// The sign of the dot product of mx,my and omx,omy is equal to the254// the sign of the cosine of ext255// (ext is the angle between omx,omy and mx,my).256final float cosext = omx * mx + omy * my;257// If it is >=0, we know that abs(ext) is <= 90 degrees, so we only258// need 1 curve to approximate the circle section that joins omx,omy259// and mx,my.260final int numCurves = (cosext >= 0f) ? 1 : 2;261262switch (numCurves) {263case 1:264drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev);265break;266case 2:267// we need to split the arc into 2 arcs spanning the same angle.268// The point we want will be one of the 2 intersections of the269// perpendicular bisector of the chord (omx,omy)->(mx,my) and the270// circle. We could find this by scaling the vector271// (omx+mx, omy+my)/2 so that it has length=lineWidth2 (and thus lies272// on the circle), but that can have numerical problems when the angle273// between omx,omy and mx,my is close to 180 degrees. So we compute a274// normal of (omx,omy)-(mx,my). This will be the direction of the275// perpendicular bisector. To get one of the intersections, we just scale276// this vector that its length is lineWidth2 (this works because the277// perpendicular bisector goes through the origin). This scaling doesn't278// have numerical problems because we know that lineWidth2 divided by279// this normal's length is at least 0.5 and at most sqrt(2)/2 (because280// we know the angle of the arc is > 90 degrees).281float nx = my - omy, ny = omx - mx;282float nlen = (float) sqrt(nx*nx + ny*ny);283float scale = lineWidth2/nlen;284float mmx = nx * scale, mmy = ny * scale;285286// if (isCW(omx, omy, mx, my) != isCW(mmx, mmy, mx, my)) then we've287// computed the wrong intersection so we get the other one.288// The test above is equivalent to if (rev).289if (rev) {290mmx = -mmx;291mmy = -mmy;292}293drawBezApproxForArc(cx, cy, omx, omy, mmx, mmy, rev);294drawBezApproxForArc(cx, cy, mmx, mmy, mx, my, rev);295break;296default:297}298}299300// the input arc defined by omx,omy and mx,my must span <= 90 degrees.301private void drawBezApproxForArc(final float cx, final float cy,302final float omx, final float omy,303final float mx, final float my,304boolean rev)305{306final float cosext2 = (omx * mx + omy * my) * invHalfLineWidth2Sq;307308// check round off errors producing cos(ext) > 1 and a NaN below309// cos(ext) == 1 implies colinear segments and an empty join anyway310if (cosext2 >= 0.5f) {311// just return to avoid generating a flat curve:312return;313}314315// cv is the length of P1-P0 and P2-P3 divided by the radius of the arc316// (so, cv assumes the arc has radius 1). P0, P1, P2, P3 are the points that317// define the bezier curve we're computing.318// It is computed using the constraints that P1-P0 and P3-P2 are parallel319// to the arc tangents at the endpoints, and that |P1-P0|=|P3-P2|.320float cv = (float) ((4.0 / 3.0) * sqrt(0.5 - cosext2) /321(1.0 + sqrt(cosext2 + 0.5)));322// if clockwise, we need to negate cv.323if (rev) { // rev is equivalent to isCW(omx, omy, mx, my)324cv = -cv;325}326final float x1 = cx + omx;327final float y1 = cy + omy;328final float x2 = x1 - cv * omy;329final float y2 = y1 + cv * omx;330331final float x4 = cx + mx;332final float y4 = cy + my;333final float x3 = x4 + cv * my;334final float y3 = y4 - cv * mx;335336emitCurveTo(x1, y1, x2, y2, x3, y3, x4, y4, rev);337}338339private void drawRoundCap(float cx, float cy, float mx, float my) {340// the first and second arguments of the following two calls341// are really will be ignored by emitCurveTo (because of the false),342// but we put them in anyway, as opposed to just giving it 4 zeroes,343// because it's just 4 additions and it's not good to rely on this344// sort of assumption (right now it's true, but that may change).345emitCurveTo(cx+mx-C*my, cy+my+C*mx,346cx-my+C*mx, cy+mx+C*my,347cx-my, cy+mx);348emitCurveTo(cx-my-C*mx, cy+mx-C*my,349cx-mx-C*my, cy-my+C*mx,350cx-mx, cy-my);351}352353// Put the intersection point of the lines (x0, y0) -> (x1, y1)354// and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1].355// If the lines are parallel, it will put a non finite number in m.356private static void computeIntersection(final float x0, final float y0,357final float x1, final float y1,358final float x0p, final float y0p,359final float x1p, final float y1p,360final float[] m, int off)361{362float x10 = x1 - x0;363float y10 = y1 - y0;364float x10p = x1p - x0p;365float y10p = y1p - y0p;366367float den = x10*y10p - x10p*y10;368float t = x10p*(y0-y0p) - y10p*(x0-x0p);369t /= den;370m[off++] = x0 + t*x10;371m[off] = y0 + t*y10;372}373374private void drawMiter(final float pdx, final float pdy,375final float x0, final float y0,376final float dx, final float dy,377float omx, float omy, float mx, float my,378boolean rev)379{380if ((mx == omx && my == omy) ||381(pdx == 0f && pdy == 0f) ||382(dx == 0f && dy == 0f))383{384return;385}386387if (rev) {388omx = -omx;389omy = -omy;390mx = -mx;391my = -my;392}393394computeIntersection((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy,395(dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my,396miter, 0);397398final float miterX = miter[0];399final float miterY = miter[1];400float lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0);401402// If the lines are parallel, lenSq will be either NaN or +inf403// (actually, I'm not sure if the latter is possible. The important404// thing is that -inf is not possible, because lenSq is a square).405// For both of those values, the comparison below will fail and406// no miter will be drawn, which is correct.407if (lenSq < miterLimitSq) {408emitLineTo(miterX, miterY, rev);409}410}411412@Override413public void moveTo(float x0, float y0) {414if (prev == DRAWING_OP_TO) {415finish();416}417this.sx0 = this.cx0 = x0;418this.sy0 = this.cy0 = y0;419this.cdx = this.sdx = 1;420this.cdy = this.sdy = 0;421this.prev = MOVE_TO;422}423424@Override425public void lineTo(float x1, float y1) {426float dx = x1 - cx0;427float dy = y1 - cy0;428if (dx == 0f && dy == 0f) {429dx = 1f;430}431computeOffset(dx, dy, lineWidth2, offset0);432final float mx = offset0[0];433final float my = offset0[1];434435drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my);436437emitLineTo(cx0 + mx, cy0 + my);438emitLineTo( x1 + mx, y1 + my);439440emitLineToRev(cx0 - mx, cy0 - my);441emitLineToRev( x1 - mx, y1 - my);442443this.cmx = mx;444this.cmy = my;445this.cdx = dx;446this.cdy = dy;447this.cx0 = x1;448this.cy0 = y1;449this.prev = DRAWING_OP_TO;450}451452@Override453public void closePath() {454if (prev != DRAWING_OP_TO) {455if (prev == CLOSE) {456return;457}458emitMoveTo(cx0, cy0 - lineWidth2);459this.cmx = this.smx = 0;460this.cmy = this.smy = -lineWidth2;461this.cdx = this.sdx = 1;462this.cdy = this.sdy = 0;463finish();464return;465}466467if (cx0 != sx0 || cy0 != sy0) {468lineTo(sx0, sy0);469}470471drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy);472473emitLineTo(sx0 + smx, sy0 + smy);474475emitMoveTo(sx0 - smx, sy0 - smy);476emitReverse();477478this.prev = CLOSE;479emitClose();480}481482private void emitReverse() {483reverse.popAll(out);484}485486@Override487public void pathDone() {488if (prev == DRAWING_OP_TO) {489finish();490}491492out.pathDone();493494// this shouldn't matter since this object won't be used495// after the call to this method.496this.prev = CLOSE;497498// Dispose this instance:499dispose();500}501502private void finish() {503if (capStyle == CAP_ROUND) {504drawRoundCap(cx0, cy0, cmx, cmy);505} else if (capStyle == CAP_SQUARE) {506emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);507emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);508}509510emitReverse();511512if (capStyle == CAP_ROUND) {513drawRoundCap(sx0, sy0, -smx, -smy);514} else if (capStyle == CAP_SQUARE) {515emitLineTo(sx0 + smy - smx, sy0 - smx - smy);516emitLineTo(sx0 + smy + smx, sy0 - smx + smy);517}518519emitClose();520}521522private void emitMoveTo(final float x0, final float y0) {523out.moveTo(x0, y0);524}525526private void emitLineTo(final float x1, final float y1) {527out.lineTo(x1, y1);528}529530private void emitLineToRev(final float x1, final float y1) {531reverse.pushLine(x1, y1);532}533534private void emitLineTo(final float x1, final float y1,535final boolean rev)536{537if (rev) {538emitLineToRev(x1, y1);539} else {540emitLineTo(x1, y1);541}542}543544private void emitQuadTo(final float x1, final float y1,545final float x2, final float y2)546{547out.quadTo(x1, y1, x2, y2);548}549550private void emitQuadToRev(final float x0, final float y0,551final float x1, final float y1)552{553reverse.pushQuad(x0, y0, x1, y1);554}555556private void emitCurveTo(final float x1, final float y1,557final float x2, final float y2,558final float x3, final float y3)559{560out.curveTo(x1, y1, x2, y2, x3, y3);561}562563private void emitCurveToRev(final float x0, final float y0,564final float x1, final float y1,565final float x2, final float y2)566{567reverse.pushCubic(x0, y0, x1, y1, x2, y2);568}569570private void emitCurveTo(final float x0, final float y0,571final float x1, final float y1,572final float x2, final float y2,573final float x3, final float y3, final boolean rev)574{575if (rev) {576reverse.pushCubic(x0, y0, x1, y1, x2, y2);577} else {578out.curveTo(x1, y1, x2, y2, x3, y3);579}580}581582private void emitClose() {583out.closePath();584}585586private void drawJoin(float pdx, float pdy,587float x0, float y0,588float dx, float dy,589float omx, float omy,590float mx, float my)591{592if (prev != DRAWING_OP_TO) {593emitMoveTo(x0 + mx, y0 + my);594this.sdx = dx;595this.sdy = dy;596this.smx = mx;597this.smy = my;598} else {599boolean cw = isCW(pdx, pdy, dx, dy);600if (joinStyle == JOIN_MITER) {601drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);602} else if (joinStyle == JOIN_ROUND) {603drawRoundJoin(x0, y0,604omx, omy,605mx, my, cw,606ROUND_JOIN_THRESHOLD);607}608emitLineTo(x0, y0, !cw);609}610prev = DRAWING_OP_TO;611}612613private static boolean within(final float x1, final float y1,614final float x2, final float y2,615final float ERR)616{617assert ERR > 0 : "";618// compare taxicab distance. ERR will always be small, so using619// true distance won't give much benefit620return (Helpers.within(x1, x2, ERR) && // we want to avoid calling Math.abs621Helpers.within(y1, y2, ERR)); // this is just as good.622}623624private void getLineOffsets(float x1, float y1,625float x2, float y2,626float[] left, float[] right) {627computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0);628final float mx = offset0[0];629final float my = offset0[1];630left[0] = x1 + mx;631left[1] = y1 + my;632left[2] = x2 + mx;633left[3] = y2 + my;634right[0] = x1 - mx;635right[1] = y1 - my;636right[2] = x2 - mx;637right[3] = y2 - my;638}639640private int computeOffsetCubic(float[] pts, final int off,641float[] leftOff, float[] rightOff)642{643// if p1=p2 or p3=p4 it means that the derivative at the endpoint644// vanishes, which creates problems with computeOffset. Usually645// this happens when this stroker object is trying to winden646// a curve with a cusp. What happens is that curveTo splits647// the input curve at the cusp, and passes it to this function.648// because of inaccuracies in the splitting, we consider points649// equal if they're very close to each other.650final float x1 = pts[off + 0], y1 = pts[off + 1];651final float x2 = pts[off + 2], y2 = pts[off + 3];652final float x3 = pts[off + 4], y3 = pts[off + 5];653final float x4 = pts[off + 6], y4 = pts[off + 7];654655float dx4 = x4 - x3;656float dy4 = y4 - y3;657float dx1 = x2 - x1;658float dy1 = y2 - y1;659660// if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4,661// in which case ignore if p1 == p2662final boolean p1eqp2 = within(x1,y1,x2,y2, 6f * ulp(y2));663final boolean p3eqp4 = within(x3,y3,x4,y4, 6f * ulp(y4));664if (p1eqp2 && p3eqp4) {665getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);666return 4;667} else if (p1eqp2) {668dx1 = x3 - x1;669dy1 = y3 - y1;670} else if (p3eqp4) {671dx4 = x4 - x2;672dy4 = y4 - y2;673}674675// if p2-p1 and p4-p3 are parallel, that must mean this curve is a line676float dotsq = (dx1 * dx4 + dy1 * dy4);677dotsq *= dotsq;678float l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4;679if (Helpers.within(dotsq, l1sq * l4sq, 4f * ulp(dotsq))) {680getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);681return 4;682}683684// What we're trying to do in this function is to approximate an ideal685// offset curve (call it I) of the input curve B using a bezier curve Bp.686// The constraints I use to get the equations are:687//688// 1. The computed curve Bp should go through I(0) and I(1). These are689// x1p, y1p, x4p, y4p, which are p1p and p4p. We still need to find690// 4 variables: the x and y components of p2p and p3p (i.e. x2p, y2p, x3p, y3p).691//692// 2. Bp should have slope equal in absolute value to I at the endpoints. So,693// (by the way, the operator || in the comments below means "aligned with".694// It is defined on vectors, so when we say I'(0) || Bp'(0) we mean that695// vectors I'(0) and Bp'(0) are aligned, which is the same as saying696// that the tangent lines of I and Bp at 0 are parallel. Mathematically697// this means (I'(t) || Bp'(t)) <==> (I'(t) = c * Bp'(t)) where c is some698// nonzero constant.)699// I'(0) || Bp'(0) and I'(1) || Bp'(1). Obviously, I'(0) || B'(0) and700// I'(1) || B'(1); therefore, Bp'(0) || B'(0) and Bp'(1) || B'(1).701// We know that Bp'(0) || (p2p-p1p) and Bp'(1) || (p4p-p3p) and the same702// is true for any bezier curve; therefore, we get the equations703// (1) p2p = c1 * (p2-p1) + p1p704// (2) p3p = c2 * (p4-p3) + p4p705// We know p1p, p4p, p2, p1, p3, and p4; therefore, this reduces the number706// of unknowns from 4 to 2 (i.e. just c1 and c2).707// To eliminate these 2 unknowns we use the following constraint:708//709// 3. Bp(0.5) == I(0.5). Bp(0.5)=(x,y) and I(0.5)=(xi,yi), and I should note710// that I(0.5) is *the only* reason for computing dxm,dym. This gives us711// (3) Bp(0.5) = (p1p + 3 * (p2p + p3p) + p4p)/8, which is equivalent to712// (4) p2p + p3p = (Bp(0.5)*8 - p1p - p4p) / 3713// We can substitute (1) and (2) from above into (4) and we get:714// (5) c1*(p2-p1) + c2*(p4-p3) = (Bp(0.5)*8 - p1p - p4p)/3 - p1p - p4p715// which is equivalent to716// (6) c1*(p2-p1) + c2*(p4-p3) = (4/3) * (Bp(0.5) * 2 - p1p - p4p)717//718// The right side of this is a 2D vector, and we know I(0.5), which gives us719// Bp(0.5), which gives us the value of the right side.720// The left side is just a matrix vector multiplication in disguise. It is721//722// [x2-x1, x4-x3][c1]723// [y2-y1, y4-y3][c2]724// which, is equal to725// [dx1, dx4][c1]726// [dy1, dy4][c2]727// At this point we are left with a simple linear system and we solve it by728// getting the inverse of the matrix above. Then we use [c1,c2] to compute729// p2p and p3p.730731float x = (x1 + 3f * (x2 + x3) + x4) / 8f;732float y = (y1 + 3f * (y2 + y3) + y4) / 8f;733// (dxm,dym) is some tangent of B at t=0.5. This means it's equal to734// c*B'(0.5) for some constant c.735float dxm = x3 + x4 - x1 - x2, dym = y3 + y4 - y1 - y2;736737// this computes the offsets at t=0, 0.5, 1, using the property that738// for any bezier curve the vectors p2-p1 and p4-p3 are parallel to739// the (dx/dt, dy/dt) vectors at the endpoints.740computeOffset(dx1, dy1, lineWidth2, offset0);741computeOffset(dxm, dym, lineWidth2, offset1);742computeOffset(dx4, dy4, lineWidth2, offset2);743float x1p = x1 + offset0[0]; // start744float y1p = y1 + offset0[1]; // point745float xi = x + offset1[0]; // interpolation746float yi = y + offset1[1]; // point747float x4p = x4 + offset2[0]; // end748float y4p = y4 + offset2[1]; // point749750float invdet43 = 4f / (3f * (dx1 * dy4 - dy1 * dx4));751752float two_pi_m_p1_m_p4x = 2f * xi - x1p - x4p;753float two_pi_m_p1_m_p4y = 2f * yi - y1p - y4p;754float c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y);755float c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x);756757float x2p, y2p, x3p, y3p;758x2p = x1p + c1*dx1;759y2p = y1p + c1*dy1;760x3p = x4p + c2*dx4;761y3p = y4p + c2*dy4;762763leftOff[0] = x1p; leftOff[1] = y1p;764leftOff[2] = x2p; leftOff[3] = y2p;765leftOff[4] = x3p; leftOff[5] = y3p;766leftOff[6] = x4p; leftOff[7] = y4p;767768x1p = x1 - offset0[0]; y1p = y1 - offset0[1];769xi = xi - 2f * offset1[0]; yi = yi - 2f * offset1[1];770x4p = x4 - offset2[0]; y4p = y4 - offset2[1];771772two_pi_m_p1_m_p4x = 2f * xi - x1p - x4p;773two_pi_m_p1_m_p4y = 2f * yi - y1p - y4p;774c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y);775c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x);776777x2p = x1p + c1*dx1;778y2p = y1p + c1*dy1;779x3p = x4p + c2*dx4;780y3p = y4p + c2*dy4;781782rightOff[0] = x1p; rightOff[1] = y1p;783rightOff[2] = x2p; rightOff[3] = y2p;784rightOff[4] = x3p; rightOff[5] = y3p;785rightOff[6] = x4p; rightOff[7] = y4p;786return 8;787}788789// return the kind of curve in the right and left arrays.790private int computeOffsetQuad(float[] pts, final int off,791float[] leftOff, float[] rightOff)792{793final float x1 = pts[off + 0], y1 = pts[off + 1];794final float x2 = pts[off + 2], y2 = pts[off + 3];795final float x3 = pts[off + 4], y3 = pts[off + 5];796797final float dx3 = x3 - x2;798final float dy3 = y3 - y2;799final float dx1 = x2 - x1;800final float dy1 = y2 - y1;801802// this computes the offsets at t = 0, 1803computeOffset(dx1, dy1, lineWidth2, offset0);804computeOffset(dx3, dy3, lineWidth2, offset1);805806leftOff[0] = x1 + offset0[0]; leftOff[1] = y1 + offset0[1];807leftOff[4] = x3 + offset1[0]; leftOff[5] = y3 + offset1[1];808rightOff[0] = x1 - offset0[0]; rightOff[1] = y1 - offset0[1];809rightOff[4] = x3 - offset1[0]; rightOff[5] = y3 - offset1[1];810811float x1p = leftOff[0]; // start812float y1p = leftOff[1]; // point813float x3p = leftOff[4]; // end814float y3p = leftOff[5]; // point815816// Corner cases:817// 1. If the two control vectors are parallel, we'll end up with NaN's818// in leftOff (and rightOff in the body of the if below), so we'll819// do getLineOffsets, which is right.820// 2. If the first or second two points are equal, then (dx1,dy1)==(0,0)821// or (dx3,dy3)==(0,0), so (x1p, y1p)==(x1p+dx1, y1p+dy1)822// or (x3p, y3p)==(x3p-dx3, y3p-dy3), which means that823// computeIntersection will put NaN's in leftOff and right off, and824// we will do getLineOffsets, which is right.825computeIntersection(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff, 2);826float cx = leftOff[2];827float cy = leftOff[3];828829if (!(isFinite(cx) && isFinite(cy))) {830// maybe the right path is not degenerate.831x1p = rightOff[0];832y1p = rightOff[1];833x3p = rightOff[4];834y3p = rightOff[5];835computeIntersection(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff, 2);836cx = rightOff[2];837cy = rightOff[3];838if (!(isFinite(cx) && isFinite(cy))) {839// both are degenerate. This curve is a line.840getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);841return 4;842}843// {left,right}Off[0,1,4,5] are already set to the correct values.844leftOff[2] = 2f * x2 - cx;845leftOff[3] = 2f * y2 - cy;846return 6;847}848849// rightOff[2,3] = (x2,y2) - ((left_x2, left_y2) - (x2, y2))850// == 2*(x2, y2) - (left_x2, left_y2)851rightOff[2] = 2f * x2 - cx;852rightOff[3] = 2f * y2 - cy;853return 6;854}855856private static boolean isFinite(float x) {857return (Float.NEGATIVE_INFINITY < x && x < Float.POSITIVE_INFINITY);858}859860// If this class is compiled with ecj, then Hotspot crashes when OSR861// compiling this function. See bugs 7004570 and 6675699862// TODO: until those are fixed, we should work around that by863// manually inlining this into curveTo and quadTo.864/******************************* WORKAROUND **********************************865private void somethingTo(final int type) {866// need these so we can update the state at the end of this method867final float xf = middle[type-2], yf = middle[type-1];868float dxs = middle[2] - middle[0];869float dys = middle[3] - middle[1];870float dxf = middle[type - 2] - middle[type - 4];871float dyf = middle[type - 1] - middle[type - 3];872switch(type) {873case 6:874if ((dxs == 0f && dys == 0f) ||875(dxf == 0f && dyf == 0f)) {876dxs = dxf = middle[4] - middle[0];877dys = dyf = middle[5] - middle[1];878}879break;880case 8:881boolean p1eqp2 = (dxs == 0f && dys == 0f);882boolean p3eqp4 = (dxf == 0f && dyf == 0f);883if (p1eqp2) {884dxs = middle[4] - middle[0];885dys = middle[5] - middle[1];886if (dxs == 0f && dys == 0f) {887dxs = middle[6] - middle[0];888dys = middle[7] - middle[1];889}890}891if (p3eqp4) {892dxf = middle[6] - middle[2];893dyf = middle[7] - middle[3];894if (dxf == 0f && dyf == 0f) {895dxf = middle[6] - middle[0];896dyf = middle[7] - middle[1];897}898}899}900if (dxs == 0f && dys == 0f) {901// this happens iff the "curve" is just a point902lineTo(middle[0], middle[1]);903return;904}905// if these vectors are too small, normalize them, to avoid future906// precision problems.907if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {908float len = (float) sqrt(dxs*dxs + dys*dys);909dxs /= len;910dys /= len;911}912if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {913float len = (float) sqrt(dxf*dxf + dyf*dyf);914dxf /= len;915dyf /= len;916}917918computeOffset(dxs, dys, lineWidth2, offset0);919final float mx = offset0[0];920final float my = offset0[1];921drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, mx, my);922923int nSplits = findSubdivPoints(curve, middle, subdivTs, type, lineWidth2);924925int kind = 0;926BreakPtrIterator it = curve.breakPtsAtTs(middle, type, subdivTs, nSplits);927while(it.hasNext()) {928int curCurveOff = it.next();929930switch (type) {931case 8:932kind = computeOffsetCubic(middle, curCurveOff, lp, rp);933break;934case 6:935kind = computeOffsetQuad(middle, curCurveOff, lp, rp);936break;937}938emitLineTo(lp[0], lp[1]);939switch(kind) {940case 8:941emitCurveTo(lp[2], lp[3], lp[4], lp[5], lp[6], lp[7]);942emitCurveToRev(rp[0], rp[1], rp[2], rp[3], rp[4], rp[5]);943break;944case 6:945emitQuadTo(lp[2], lp[3], lp[4], lp[5]);946emitQuadToRev(rp[0], rp[1], rp[2], rp[3]);947break;948case 4:949emitLineTo(lp[2], lp[3]);950emitLineTo(rp[0], rp[1], true);951break;952}953emitLineTo(rp[kind - 2], rp[kind - 1], true);954}955956this.cmx = (lp[kind - 2] - rp[kind - 2]) / 2;957this.cmy = (lp[kind - 1] - rp[kind - 1]) / 2;958this.cdx = dxf;959this.cdy = dyf;960this.cx0 = xf;961this.cy0 = yf;962this.prev = DRAWING_OP_TO;963}964****************************** END WORKAROUND *******************************/965966// finds values of t where the curve in pts should be subdivided in order967// to get good offset curves a distance of w away from the middle curve.968// Stores the points in ts, and returns how many of them there were.969private static int findSubdivPoints(final Curve c, float[] pts, float[] ts,970final int type, final float w)971{972final float x12 = pts[2] - pts[0];973final float y12 = pts[3] - pts[1];974// if the curve is already parallel to either axis we gain nothing975// from rotating it.976if (y12 != 0f && x12 != 0f) {977// we rotate it so that the first vector in the control polygon is978// parallel to the x-axis. This will ensure that rotated quarter979// circles won't be subdivided.980final float hypot = (float) sqrt(x12 * x12 + y12 * y12);981final float cos = x12 / hypot;982final float sin = y12 / hypot;983final float x1 = cos * pts[0] + sin * pts[1];984final float y1 = cos * pts[1] - sin * pts[0];985final float x2 = cos * pts[2] + sin * pts[3];986final float y2 = cos * pts[3] - sin * pts[2];987final float x3 = cos * pts[4] + sin * pts[5];988final float y3 = cos * pts[5] - sin * pts[4];989990switch(type) {991case 8:992final float x4 = cos * pts[6] + sin * pts[7];993final float y4 = cos * pts[7] - sin * pts[6];994c.set(x1, y1, x2, y2, x3, y3, x4, y4);995break;996case 6:997c.set(x1, y1, x2, y2, x3, y3);998break;999default:1000}1001} else {1002c.set(pts, type);1003}10041005int ret = 0;1006// we subdivide at values of t such that the remaining rotated1007// curves are monotonic in x and y.1008ret += c.dxRoots(ts, ret);1009ret += c.dyRoots(ts, ret);1010// subdivide at inflection points.1011if (type == 8) {1012// quadratic curves can't have inflection points1013ret += c.infPoints(ts, ret);1014}10151016// now we must subdivide at points where one of the offset curves will have1017// a cusp. This happens at ts where the radius of curvature is equal to w.1018ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001f);10191020ret = Helpers.filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f);1021Helpers.isort(ts, 0, ret);1022return ret;1023}10241025@Override public void curveTo(float x1, float y1,1026float x2, float y2,1027float x3, float y3)1028{1029final float[] mid = middle;10301031mid[0] = cx0; mid[1] = cy0;1032mid[2] = x1; mid[3] = y1;1033mid[4] = x2; mid[5] = y2;1034mid[6] = x3; mid[7] = y3;10351036// inlined version of somethingTo(8);1037// See the TODO on somethingTo10381039// need these so we can update the state at the end of this method1040final float xf = mid[6], yf = mid[7];1041float dxs = mid[2] - mid[0];1042float dys = mid[3] - mid[1];1043float dxf = mid[6] - mid[4];1044float dyf = mid[7] - mid[5];10451046boolean p1eqp2 = (dxs == 0f && dys == 0f);1047boolean p3eqp4 = (dxf == 0f && dyf == 0f);1048if (p1eqp2) {1049dxs = mid[4] - mid[0];1050dys = mid[5] - mid[1];1051if (dxs == 0f && dys == 0f) {1052dxs = mid[6] - mid[0];1053dys = mid[7] - mid[1];1054}1055}1056if (p3eqp4) {1057dxf = mid[6] - mid[2];1058dyf = mid[7] - mid[3];1059if (dxf == 0f && dyf == 0f) {1060dxf = mid[6] - mid[0];1061dyf = mid[7] - mid[1];1062}1063}1064if (dxs == 0f && dys == 0f) {1065// this happens if the "curve" is just a point1066lineTo(mid[0], mid[1]);1067return;1068}10691070// if these vectors are too small, normalize them, to avoid future1071// precision problems.1072if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {1073float len = (float) sqrt(dxs*dxs + dys*dys);1074dxs /= len;1075dys /= len;1076}1077if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {1078float len = (float) sqrt(dxf*dxf + dyf*dyf);1079dxf /= len;1080dyf /= len;1081}10821083computeOffset(dxs, dys, lineWidth2, offset0);1084drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]);10851086int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);10871088final float[] l = lp;1089final float[] r = rp;10901091int kind = 0;1092BreakPtrIterator it = curve.breakPtsAtTs(mid, 8, subdivTs, nSplits);1093while(it.hasNext()) {1094int curCurveOff = it.next();10951096kind = computeOffsetCubic(mid, curCurveOff, l, r);1097emitLineTo(l[0], l[1]);10981099switch(kind) {1100case 8:1101emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]);1102emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]);1103break;1104case 4:1105emitLineTo(l[2], l[3]);1106emitLineToRev(r[0], r[1]);1107break;1108default:1109}1110emitLineToRev(r[kind - 2], r[kind - 1]);1111}11121113this.cmx = (l[kind - 2] - r[kind - 2]) / 2f;1114this.cmy = (l[kind - 1] - r[kind - 1]) / 2f;1115this.cdx = dxf;1116this.cdy = dyf;1117this.cx0 = xf;1118this.cy0 = yf;1119this.prev = DRAWING_OP_TO;1120}11211122@Override public void quadTo(float x1, float y1, float x2, float y2) {1123final float[] mid = middle;11241125mid[0] = cx0; mid[1] = cy0;1126mid[2] = x1; mid[3] = y1;1127mid[4] = x2; mid[5] = y2;11281129// inlined version of somethingTo(8);1130// See the TODO on somethingTo11311132// need these so we can update the state at the end of this method1133final float xf = mid[4], yf = mid[5];1134float dxs = mid[2] - mid[0];1135float dys = mid[3] - mid[1];1136float dxf = mid[4] - mid[2];1137float dyf = mid[5] - mid[3];1138if ((dxs == 0f && dys == 0f) || (dxf == 0f && dyf == 0f)) {1139dxs = dxf = mid[4] - mid[0];1140dys = dyf = mid[5] - mid[1];1141}1142if (dxs == 0f && dys == 0f) {1143// this happens if the "curve" is just a point1144lineTo(mid[0], mid[1]);1145return;1146}1147// if these vectors are too small, normalize them, to avoid future1148// precision problems.1149if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {1150float len = (float) sqrt(dxs*dxs + dys*dys);1151dxs /= len;1152dys /= len;1153}1154if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {1155float len = (float) sqrt(dxf*dxf + dyf*dyf);1156dxf /= len;1157dyf /= len;1158}11591160computeOffset(dxs, dys, lineWidth2, offset0);1161drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]);11621163int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);11641165final float[] l = lp;1166final float[] r = rp;11671168int kind = 0;1169BreakPtrIterator it = curve.breakPtsAtTs(mid, 6, subdivTs, nSplits);1170while(it.hasNext()) {1171int curCurveOff = it.next();11721173kind = computeOffsetQuad(mid, curCurveOff, l, r);1174emitLineTo(l[0], l[1]);11751176switch(kind) {1177case 6:1178emitQuadTo(l[2], l[3], l[4], l[5]);1179emitQuadToRev(r[0], r[1], r[2], r[3]);1180break;1181case 4:1182emitLineTo(l[2], l[3]);1183emitLineToRev(r[0], r[1]);1184break;1185default:1186}1187emitLineToRev(r[kind - 2], r[kind - 1]);1188}11891190this.cmx = (l[kind - 2] - r[kind - 2]) / 2f;1191this.cmy = (l[kind - 1] - r[kind - 1]) / 2f;1192this.cdx = dxf;1193this.cdy = dyf;1194this.cx0 = xf;1195this.cy0 = yf;1196this.prev = DRAWING_OP_TO;1197}11981199@Override public long getNativeConsumer() {1200throw new InternalError("Stroker doesn't use a native consumer");1201}12021203// a stack of polynomial curves where each curve shares endpoints with1204// adjacent ones.1205static final class PolyStack {1206private static final byte TYPE_LINETO = (byte) 0;1207private static final byte TYPE_QUADTO = (byte) 1;1208private static final byte TYPE_CUBICTO = (byte) 2;12091210float[] curves;1211int end;1212byte[] curveTypes;1213int numCurves;12141215// per-thread renderer context1216final RendererContext rdrCtx;12171218// per-thread initial arrays (large enough to satisfy most usages: 8192)1219// +1 to avoid recycling in Helpers.widenArray()1220private final float[] curves_initial = new float[INITIAL_LARGE_ARRAY + 1]; // 32K1221private final byte[] curveTypes_initial = new byte[INITIAL_LARGE_ARRAY + 1]; // 8K12221223// used marks (stats only)1224int curveTypesUseMark;1225int curvesUseMark;12261227/**1228* Constructor1229* @param rdrCtx per-thread renderer context1230*/1231PolyStack(final RendererContext rdrCtx) {1232this.rdrCtx = rdrCtx;12331234curves = curves_initial;1235curveTypes = curveTypes_initial;1236end = 0;1237numCurves = 0;12381239if (doStats) {1240curveTypesUseMark = 0;1241curvesUseMark = 0;1242}1243}12441245/**1246* Disposes this PolyStack:1247* clean up before reusing this instance1248*/1249void dispose() {1250end = 0;1251numCurves = 0;12521253if (doStats) {1254RendererContext.stats.stat_rdr_poly_stack_types1255.add(curveTypesUseMark);1256RendererContext.stats.stat_rdr_poly_stack_curves1257.add(curvesUseMark);1258// reset marks1259curveTypesUseMark = 0;1260curvesUseMark = 0;1261}12621263// Return arrays:1264// curves and curveTypes are kept dirty1265if (curves != curves_initial) {1266rdrCtx.putDirtyFloatArray(curves);1267curves = curves_initial;1268}12691270if (curveTypes != curveTypes_initial) {1271rdrCtx.putDirtyByteArray(curveTypes);1272curveTypes = curveTypes_initial;1273}1274}12751276private void ensureSpace(final int n) {1277// use substraction to avoid integer overflow:1278if (curves.length - end < n) {1279if (doStats) {1280RendererContext.stats.stat_array_stroker_polystack_curves1281.add(end + n);1282}1283curves = rdrCtx.widenDirtyFloatArray(curves, end, end + n);1284}1285if (curveTypes.length <= numCurves) {1286if (doStats) {1287RendererContext.stats.stat_array_stroker_polystack_curveTypes1288.add(numCurves + 1);1289}1290curveTypes = rdrCtx.widenDirtyByteArray(curveTypes,1291numCurves,1292numCurves + 1);1293}1294}12951296void pushCubic(float x0, float y0,1297float x1, float y1,1298float x2, float y2)1299{1300ensureSpace(6);1301curveTypes[numCurves++] = TYPE_CUBICTO;1302// we reverse the coordinate order to make popping easier1303final float[] _curves = curves;1304int e = end;1305_curves[e++] = x2; _curves[e++] = y2;1306_curves[e++] = x1; _curves[e++] = y1;1307_curves[e++] = x0; _curves[e++] = y0;1308end = e;1309}13101311void pushQuad(float x0, float y0,1312float x1, float y1)1313{1314ensureSpace(4);1315curveTypes[numCurves++] = TYPE_QUADTO;1316final float[] _curves = curves;1317int e = end;1318_curves[e++] = x1; _curves[e++] = y1;1319_curves[e++] = x0; _curves[e++] = y0;1320end = e;1321}13221323void pushLine(float x, float y) {1324ensureSpace(2);1325curveTypes[numCurves++] = TYPE_LINETO;1326curves[end++] = x; curves[end++] = y;1327}13281329void popAll(PathConsumer2D io) {1330if (doStats) {1331// update used marks:1332if (numCurves > curveTypesUseMark) {1333curveTypesUseMark = numCurves;1334}1335if (end > curvesUseMark) {1336curvesUseMark = end;1337}1338}1339final byte[] _curveTypes = curveTypes;1340final float[] _curves = curves;1341int nc = numCurves;1342int e = end;13431344while (nc != 0) {1345switch(_curveTypes[--nc]) {1346case TYPE_LINETO:1347e -= 2;1348io.lineTo(_curves[e], _curves[e+1]);1349continue;1350case TYPE_QUADTO:1351e -= 4;1352io.quadTo(_curves[e+0], _curves[e+1],1353_curves[e+2], _curves[e+3]);1354continue;1355case TYPE_CUBICTO:1356e -= 6;1357io.curveTo(_curves[e+0], _curves[e+1],1358_curves[e+2], _curves[e+3],1359_curves[e+4], _curves[e+5]);1360continue;1361default:1362}1363}1364numCurves = 0;1365end = 0;1366}13671368@Override1369public String toString() {1370String ret = "";1371int nc = numCurves;1372int e = end;1373int len;1374while (nc != 0) {1375switch(curveTypes[--nc]) {1376case TYPE_LINETO:1377len = 2;1378ret += "line: ";1379break;1380case TYPE_QUADTO:1381len = 4;1382ret += "quad: ";1383break;1384case TYPE_CUBICTO:1385len = 6;1386ret += "cubic: ";1387break;1388default:1389len = 0;1390}1391e -= len;1392ret += Arrays.toString(Arrays.copyOfRange(curves, e, e+len))1393+ "\n";1394}1395return ret;1396}1397}1398}139914001401